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>
73 lines
2.0 KiB
C++
73 lines
2.0 KiB
C++
#include "socks5/auth.h"
|
|
|
|
#include "common/logging.h"
|
|
|
|
namespace ovg::socks5 {
|
|
namespace {
|
|
constexpr const char *kMod = "auth";
|
|
}
|
|
|
|
Authenticator::Authenticator(std::vector<Credential> users, bool required)
|
|
: users_(std::move(users)), required_(required) {
|
|
// A fixed decoy, hashed exactly like a real credential, so the miss path
|
|
// costs the same as the hit path. Built once because generating it per
|
|
// attempt would itself be a timing signal.
|
|
decoy_ = make_credential("\x00-nonexistent-\x00", "\x00-nonexistent-\x00");
|
|
if (required_ && users_.empty()) {
|
|
LOG_WARN(kMod, "authentication is required but no credentials are loaded: "
|
|
"every client will be rejected");
|
|
}
|
|
}
|
|
|
|
bool Authenticator::empty() const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
return users_.empty();
|
|
}
|
|
|
|
bool Authenticator::check(const std::string &user,
|
|
const std::string &password) const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
|
|
const Credential *found = nullptr;
|
|
for (const auto &c : users_) {
|
|
// Username comparison is not constant-time and does not need to be: the
|
|
// name is not a secret, and the timing of a string compare over a
|
|
// handful of entries is far below the noise of a network round trip. The
|
|
// hash below is what must not vary.
|
|
if (c.username == user) {
|
|
found = &c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Always exactly one verification, hit or miss.
|
|
const bool ok = verify_credential(found != nullptr ? *found : decoy_, password);
|
|
const bool result = ok && found != nullptr;
|
|
|
|
if (result) {
|
|
++ok_;
|
|
} else {
|
|
++bad_;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t Authenticator::replace(std::vector<Credential> users) {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
users_ = std::move(users);
|
|
LOG_INFO(kMod, "credential set replaced: {} user(s)", users_.size());
|
|
return users_.size();
|
|
}
|
|
|
|
uint64_t Authenticator::successes() const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
return ok_;
|
|
}
|
|
|
|
uint64_t Authenticator::failures() const {
|
|
std::lock_guard<std::mutex> lk(mu_);
|
|
return bad_;
|
|
}
|
|
|
|
} // namespace ovg::socks5
|