OpenVPN client with an authenticated SOCKS5 front door
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>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
#include "harness.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
|
||||
#include "common/logging.h"
|
||||
|
||||
#ifndef OVG_TEST_DATA_DIR
|
||||
#define OVG_TEST_DATA_DIR "tests/data"
|
||||
#endif
|
||||
|
||||
namespace ovgtest {
|
||||
|
||||
std::vector<Case> ®istry() {
|
||||
static std::vector<Case> cases;
|
||||
return cases;
|
||||
}
|
||||
|
||||
Registrar::Registrar(std::string name, std::function<void()> fn) {
|
||||
registry().push_back(Case{std::move(name), std::move(fn)});
|
||||
}
|
||||
|
||||
void fail(const char *file, int line, const std::string &what) {
|
||||
const char *slash = std::strrchr(file, '/');
|
||||
std::string loc = slash ? slash + 1 : file;
|
||||
throw Failure{loc + ":" + std::to_string(line) + ": " + what};
|
||||
}
|
||||
|
||||
void skip(const std::string &reason) { throw Skipped{reason}; }
|
||||
|
||||
std::string data_path(const std::string &leaf) {
|
||||
return std::string(OVG_TEST_DATA_DIR) + "/" + leaf;
|
||||
}
|
||||
|
||||
int run_all(int argc, char **argv) {
|
||||
const char *filter = argc > 1 ? argv[1] : nullptr;
|
||||
|
||||
int passed = 0, failed = 0, filtered = 0, skipped = 0;
|
||||
for (const auto &c : registry()) {
|
||||
if (filter && c.name.find(filter) == std::string::npos) {
|
||||
++filtered;
|
||||
continue;
|
||||
}
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
try {
|
||||
c.fn();
|
||||
const auto ms = std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - t0)
|
||||
.count();
|
||||
std::printf(" \033[32mPASS\033[0m %-44s %6.1fms\n", c.name.c_str(), ms);
|
||||
++passed;
|
||||
} catch (const Skipped &s) {
|
||||
std::printf(" \033[33mSKIP\033[0m %-44s %s\n", c.name.c_str(),
|
||||
s.reason.c_str());
|
||||
++skipped;
|
||||
} catch (const Failure &f) {
|
||||
std::printf(" \033[31mFAIL\033[0m %s\n %s\n", c.name.c_str(),
|
||||
f.message.c_str());
|
||||
++failed;
|
||||
} catch (const std::exception &e) {
|
||||
std::printf(" \033[31mFAIL\033[0m %s\n threw: %s\n",
|
||||
c.name.c_str(), e.what());
|
||||
++failed;
|
||||
} catch (...) {
|
||||
std::printf(" \033[31mFAIL\033[0m %s\n threw unknown exception\n",
|
||||
c.name.c_str());
|
||||
++failed;
|
||||
}
|
||||
}
|
||||
|
||||
std::printf("\n %d passed, %d failed", passed, failed);
|
||||
if (skipped) std::printf(", %d skipped", skipped);
|
||||
if (filtered) std::printf(", %d filtered out", filtered);
|
||||
std::printf("\n\n");
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace ovgtest
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Tests exercise error paths on purpose, so the log would be mostly noise.
|
||||
// OVG_TEST_LOG=debug (or any level name) turns it back on when debugging.
|
||||
const char *lvl = std::getenv("OVG_TEST_LOG");
|
||||
ovg::log::set_level(lvl ? ovg::log::level_from_string(lvl)
|
||||
: ovg::log::Level::Off);
|
||||
return ovgtest::run_all(argc, argv);
|
||||
}
|
||||
Reference in New Issue
Block a user