forked from cloud/ovgate
A userspace VPN gateway: builds an OpenVPN tunnel to a VPNGate node with
the OpenVPN 3 core, terminates it in-process with lwIP, and serves SOCKS5
(RFC 1928/1929, CONNECT and UDP ASSOCIATE) over it. No root, no tun
device, no routing table changes.
Layout follows the module boundaries in docs/ARCHITECTURE.md:
vpngate/ directory fetch + CSV parse (lines run to ~13.5 KB, so the
parser streams rather than splitting on newlines)
selector/ two-phase pick: cheap prior over the whole list, then real
TCP handshake timing of the top K
ovpn/ openvpn3 driven through TunBuilder, packets over a socketpair
netstack/ lwIP: the TCP/IP stack that makes "no root" possible
egress/ the swappable way out, and make-before-break switching
socks5/ the front door
health/ per-window scoring, and the decision to move
app/ wiring, admin HTTP, signals
docs/FEASIBILITY.md is the analysis this was built from, including the
one requirement that is not physically possible -- carrying established
TCP connections across a node switch -- and what is done instead
(zero-progress redial, UDP re-homing, grace-period drain).
Tests: 155 without the tunnel egress, 172 with it. The seam is the egress
factory; selection, scoring, history and probing all run for real.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121 lines
4.6 KiB
C++
121 lines
4.6 KiB
C++
// A ~100-line test harness.
|
|
//
|
|
// Pulling in GTest would mean either a system package we cannot assume or a
|
|
// FetchContent download on every clean build. The tests here need registration,
|
|
// assertions and a non-zero exit code; that is all this provides.
|
|
#pragma once
|
|
|
|
#include <concepts>
|
|
#include <functional>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace ovgtest {
|
|
|
|
struct Case {
|
|
std::string name;
|
|
std::function<void()> fn;
|
|
};
|
|
|
|
std::vector<Case> ®istry();
|
|
|
|
struct Registrar {
|
|
Registrar(std::string name, std::function<void()> fn);
|
|
};
|
|
|
|
// Thrown by the CHECK macros; caught per-case by the runner.
|
|
struct Failure {
|
|
std::string message;
|
|
};
|
|
|
|
// Thrown by SKIP: the environment cannot support the test (e.g. a sandbox that
|
|
// transparently accepts every outbound connection, so nothing can be
|
|
// blackholed). Reported distinctly from a pass so it cannot hide a regression.
|
|
struct Skipped {
|
|
std::string reason;
|
|
};
|
|
|
|
[[noreturn]] void fail(const char *file, int line, const std::string &what);
|
|
[[noreturn]] void skip(const std::string &reason);
|
|
|
|
// Absolute path to tests/data, injected by CMake.
|
|
std::string data_path(const std::string &leaf);
|
|
|
|
int run_all(int argc, char **argv);
|
|
|
|
// Best-effort stringification: streamable types get printed, everything else
|
|
// (IpAddress, enums without an operator<<, ...) degrades to a placeholder
|
|
// rather than failing to compile.
|
|
template <typename T>
|
|
concept Streamable = requires(std::ostream &os, const T &v) { os << v; };
|
|
|
|
template <typename T>
|
|
std::string to_str(const T &v) {
|
|
if constexpr (Streamable<T>) {
|
|
std::ostringstream os;
|
|
os << v;
|
|
return os.str();
|
|
} else {
|
|
return "<value>";
|
|
}
|
|
}
|
|
inline std::string to_str(bool v) { return v ? "true" : "false"; }
|
|
inline std::string to_str(const std::string &v) { return "\"" + v + "\""; }
|
|
|
|
} // namespace ovgtest
|
|
|
|
#define OVG_TEST(name) \
|
|
static void name(); \
|
|
static ::ovgtest::Registrar ovg_reg_##name(#name, name); \
|
|
static void name()
|
|
|
|
#define SKIP(reason) ::ovgtest::skip(reason)
|
|
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) ::ovgtest::fail(__FILE__, __LINE__, "CHECK(" #cond ")"); \
|
|
} while (0)
|
|
|
|
#define CHECK_EQ(a, b) \
|
|
do { \
|
|
const auto &ovg_a = (a); \
|
|
const auto &ovg_b = (b); \
|
|
if (!(ovg_a == ovg_b)) \
|
|
::ovgtest::fail(__FILE__, __LINE__, \
|
|
"CHECK_EQ(" #a ", " #b ")\n left = " + \
|
|
::ovgtest::to_str(ovg_a) + \
|
|
"\n right = " + ::ovgtest::to_str(ovg_b)); \
|
|
} while (0)
|
|
|
|
#define CHECK_NE(a, b) \
|
|
do { \
|
|
if ((a) == (b)) \
|
|
::ovgtest::fail(__FILE__, __LINE__, "CHECK_NE(" #a ", " #b ")"); \
|
|
} while (0)
|
|
|
|
#define CHECK_LT(a, b) \
|
|
do { \
|
|
const auto &ovg_a = (a); \
|
|
const auto &ovg_b = (b); \
|
|
if (!(ovg_a < ovg_b)) \
|
|
::ovgtest::fail(__FILE__, __LINE__, \
|
|
"CHECK_LT(" #a ", " #b ")\n left = " + \
|
|
::ovgtest::to_str(ovg_a) + \
|
|
"\n right = " + ::ovgtest::to_str(ovg_b)); \
|
|
} while (0)
|
|
|
|
#define CHECK_GT(a, b) CHECK_LT(b, a)
|
|
|
|
#define CHECK_NEAR(a, b, tol) \
|
|
do { \
|
|
const double ovg_d = static_cast<double>(a) - static_cast<double>(b); \
|
|
if (ovg_d > (tol) || -ovg_d > (tol)) \
|
|
::ovgtest::fail(__FILE__, __LINE__, \
|
|
"CHECK_NEAR(" #a ", " #b ")\n left = " + \
|
|
::ovgtest::to_str(static_cast<double>(a)) + \
|
|
"\n right = " + \
|
|
::ovgtest::to_str(static_cast<double>(b))); \
|
|
} while (0)
|