Skip to content

Commit 2327804

Browse files
committed
Add SOCKS5 support
1 parent c2b2684 commit 2327804

2 files changed

Lines changed: 148 additions & 1 deletion

File tree

src/workerd/server/server.c++

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,130 @@ private:
824824
}
825825
};
826826

827+
class Socks5ProxyNetwork final: public kj::Network {
828+
public:
829+
Socks5ProxyNetwork(kj::StringPtr proxyHostname,
830+
kj::Network& inner,
831+
kj::Maybe<kj::TlsContext&> tls = kj::none,
832+
kj::Maybe<kj::Own<kj::NetworkAddress>> resolvedProxyAddr = kj::none)
833+
: inner(inner), proxyHostname(kj::mv(proxyHostname)),
834+
proxyAddr(kj::mv(resolvedProxyAddr)), tls(kj::mv(tls)) {}
835+
836+
kj::Promise<kj::Own<kj::NetworkAddress>> parseAddress(kj::StringPtr addr, uint portHint) override {
837+
co_return kj::heap<Socks5NetworkAddress>(co_await resolveProxyAddr(), addr, portHint, tls);
838+
}
839+
840+
kj::Own<kj::NetworkAddress> getSockaddr(const void* sockaddr, uint len) override {
841+
KJ_UNIMPLEMENTED("Socks5NetworkAddress::getSockaddr() not implemented");
842+
}
843+
844+
kj::Own<Network> restrictPeers(
845+
kj::ArrayPtr<const kj::StringPtr> allow,
846+
kj::ArrayPtr<const kj::StringPtr> deny = nullptr) override {
847+
auto addr = proxyAddr.map([](auto& addr) -> kj::Own<kj::NetworkAddress> { return addr->clone(); });
848+
auto restricted = inner.restrictPeers(allow, deny);
849+
return kj::heap<Socks5ProxyNetwork>(
850+
proxyHostname, *restricted, tls, kj::mv(addr)).attach(kj::mv(restricted));
851+
}
852+
853+
private:
854+
kj::Network& inner;
855+
kj::StringPtr proxyHostname;
856+
kj::Maybe<kj::Own<kj::NetworkAddress>> proxyAddr = kj::none;
857+
kj::Maybe<kj::TlsContext&> tls = kj::none;
858+
859+
kj::Promise<kj::Own<kj::NetworkAddress>> resolveProxyAddr() {
860+
KJ_IF_SOME(p, proxyAddr) {
861+
co_return p->clone();
862+
} else {
863+
kj::Own<kj::NetworkAddress> parsed = co_await inner.parseAddress(proxyHostname);
864+
proxyAddr = parsed->clone();
865+
co_return parsed;
866+
}
867+
}
868+
869+
class Socks5NetworkAddress final: public kj::NetworkAddress {
870+
public:
871+
Socks5NetworkAddress(kj::Own<kj::NetworkAddress> proxy, kj::StringPtr upstream, uint portHint,
872+
kj::Maybe<kj::TlsContext&> tls = kj::none)
873+
: proxy(kj::mv(proxy)), upstream(upstream), portHint(portHint), tls(tls) {}
874+
875+
kj::Promise<kj::Own<kj::AsyncIoStream>> connect() override {
876+
KJ_REQUIRE(upstream.size() < 253, "socks5: proxied host is too long");
877+
const kj::byte RESERVED = 0;
878+
const kj::byte SOCKS5_VER = 5;
879+
const kj::byte AUTH_METHOD_NONE = 0;
880+
const kj::byte CMD_CONNECT = 1;
881+
882+
const kj::byte ADDR_IP4 = 1;
883+
const kj::byte ADDR_FQDN = 3;
884+
const kj::byte ADDR_IP6 = 4;
885+
886+
// 1. Send auth request
887+
auto stream = co_await proxy->connect();
888+
kj::byte buf[7 + 253];
889+
buf[0] = SOCKS5_VER;
890+
buf[1] = 1; // one authentication method
891+
buf[2] = AUTH_METHOD_NONE;
892+
co_await stream->write(buf, 3);
893+
894+
// 2. handle auth response
895+
co_await stream->read(buf, 2);
896+
KJ_REQUIRE(buf[0] == SOCKS5_VER, "socks5: unsupported version");
897+
KJ_REQUIRE(buf[1] == AUTH_METHOD_NONE, "socks5: unsupported auth method");
898+
899+
// 3. send connect request
900+
buf[0] = SOCKS5_VER;
901+
buf[1] = CMD_CONNECT;
902+
buf[2] = RESERVED;
903+
buf[3] = ADDR_FQDN;
904+
buf[4] = upstream.size();
905+
memcpy(buf + 5, upstream.begin(), upstream.size());
906+
uint16_t cmdReqSize = upstream.size() + 5 + 2;
907+
buf[cmdReqSize - 2] = portHint >> 8;
908+
buf[cmdReqSize - 1] = portHint & 0xff;
909+
co_await stream->write(buf, cmdReqSize);
910+
911+
// 4. handle connect respond
912+
co_await stream->read(buf, 5);
913+
KJ_REQUIRE(buf[0] == SOCKS5_VER, "socks5: unsupported version");
914+
KJ_REQUIRE(buf[1] == 0, "socks5: failed to connect to upstream");
915+
KJ_REQUIRE(buf[2] == RESERVED, "socks5: invalid connect response reserved byte");
916+
switch(buf[3]) {
917+
case ADDR_IP4: co_await stream->read(buf, 4 + 2 - 1); break;
918+
case ADDR_IP6: co_await stream->read(buf, 16 + 2 - 1); break;
919+
case ADDR_FQDN: co_await stream->read(buf, buf[4] + 2); break;
920+
default: throw KJ_EXCEPTION(FAILED, "socks5: invalid bound address type");
921+
}
922+
923+
// 5. Return connected stream
924+
KJ_IF_SOME(tlsContext, tls) {
925+
co_return co_await tlsContext.wrapClient(kj::mv(stream), upstream);
926+
} else {
927+
co_return stream;
928+
}
929+
}
930+
931+
kj::Own<kj::NetworkAddress> clone() override {
932+
return kj::heap<Socks5NetworkAddress>(proxy->clone(), upstream, portHint, tls);
933+
}
934+
935+
// We don't use any other methods, and they seem kinda annoying to implement.
936+
kj::Own<kj::ConnectionReceiver> listen() override {
937+
KJ_UNIMPLEMENTED("Socks5NetworkAddress::listen() not implemented");
938+
}
939+
kj::String toString() override {
940+
KJ_UNIMPLEMENTED("Socks5NetworkAddress::toString() not implemented");
941+
}
942+
943+
private:
944+
kj::Own<kj::NetworkAddress> proxy;
945+
kj::StringPtr upstream;
946+
uint portHint;
947+
kj::Maybe<kj::TlsContext&> tls = kj::none;
948+
};
949+
};
950+
827951
kj::Own<Server::Service> Server::makeNetworkService(config::Network::Reader conf) {
828952
TRACE_EVENT("workerd", "Server::makeNetworkService()");
829953
auto restrictedNetwork = network.restrictPeers(
@@ -832,7 +956,18 @@ kj::Own<Server::Service> Server::makeNetworkService(config::Network::Reader conf
832956

833957
kj::Maybe<kj::Own<kj::Network>> tlsNetwork;
834958
kj::Maybe<kj::SecureNetworkWrapper&> tlsContext;
835-
if (conf.hasTlsOptions()) {
959+
960+
961+
if (conf.hasProxy()) {
962+
auto proxyConf = conf.getProxy();
963+
if (conf.hasTlsOptions()) {
964+
auto ownedTlsContext = makeTlsContext(conf.getTlsOptions());
965+
tlsNetwork = kj::heap<Socks5ProxyNetwork>(proxyConf.getAddress(), *restrictedNetwork, *ownedTlsContext)
966+
.attach(kj::mv(ownedTlsContext));
967+
}
968+
restrictedNetwork = kj::heap<Socks5ProxyNetwork>(proxyConf.getAddress(), *restrictedNetwork)
969+
.attach(kj::mv(restrictedNetwork));
970+
} else if (conf.hasTlsOptions()) {
836971
auto ownedTlsContext = makeTlsContext(conf.getTlsOptions());
837972
tlsContext = ownedTlsContext;
838973
tlsNetwork = ownedTlsContext->wrapNetwork(*restrictedNetwork).attach(kj::mv(ownedTlsContext));

src/workerd/server/workerd.capnp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,18 @@ struct Network {
707707
# (The above is exactly the format supported by kj::Network::restrictPeers().)
708708

709709
tlsOptions @2 :TlsOptions;
710+
711+
proxy @3 :ProxyOptions;
712+
}
713+
714+
struct ProxyOptions {
715+
type @0 :ProxyType = socks5;
716+
717+
enum ProxyType {
718+
socks5 @0;
719+
}
720+
721+
address @1 :Text = "127.0.0.1:1080";
710722
}
711723

712724
struct DiskDirectory {

0 commit comments

Comments
 (0)