Skip to content

Commit a3b178e

Browse files
committed
Add SOCKS5 support
1 parent c2f970f commit a3b178e

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

src/workerd/server/server.c++

+136-1
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,130 @@ private:
749749
}
750750
};
751751

752+
class Socks5ProxyNetwork final: public kj::Network {
753+
public:
754+
Socks5ProxyNetwork(kj::StringPtr proxyHostname,
755+
kj::Network& inner,
756+
kj::Maybe<kj::TlsContext&> tls = kj::none,
757+
kj::Maybe<kj::Own<kj::NetworkAddress>> resolvedProxyAddr = kj::none)
758+
: inner(inner), proxyHostname(kj::mv(proxyHostname)),
759+
proxyAddr(kj::mv(resolvedProxyAddr)), tls(kj::mv(tls)) {}
760+
761+
kj::Promise<kj::Own<kj::NetworkAddress>> parseAddress(kj::StringPtr addr, uint portHint) override {
762+
co_return kj::heap<Socks5NetworkAddress>(co_await resolveProxyAddr(), addr, portHint, tls);
763+
}
764+
765+
kj::Own<kj::NetworkAddress> getSockaddr(const void* sockaddr, uint len) override {
766+
KJ_UNIMPLEMENTED("Socks5NetworkAddress::getSockaddr() not implemented");
767+
}
768+
769+
kj::Own<Network> restrictPeers(
770+
kj::ArrayPtr<const kj::StringPtr> allow,
771+
kj::ArrayPtr<const kj::StringPtr> deny = nullptr) override {
772+
auto addr = proxyAddr.map([](auto& addr) -> kj::Own<kj::NetworkAddress> { return addr->clone(); });
773+
auto restricted = inner.restrictPeers(allow, deny);
774+
return kj::heap<Socks5ProxyNetwork>(
775+
proxyHostname, *restricted, tls, kj::mv(addr)).attach(kj::mv(restricted));
776+
}
777+
778+
private:
779+
kj::Network& inner;
780+
kj::StringPtr proxyHostname;
781+
kj::Maybe<kj::Own<kj::NetworkAddress>> proxyAddr = kj::none;
782+
kj::Maybe<kj::TlsContext&> tls = kj::none;
783+
784+
kj::Promise<kj::Own<kj::NetworkAddress>> resolveProxyAddr() {
785+
KJ_IF_SOME(p, proxyAddr) {
786+
co_return p->clone();
787+
} else {
788+
kj::Own<kj::NetworkAddress> parsed = co_await inner.parseAddress(proxyHostname);
789+
proxyAddr = parsed->clone();
790+
co_return parsed;
791+
}
792+
}
793+
794+
class Socks5NetworkAddress final: public kj::NetworkAddress {
795+
public:
796+
Socks5NetworkAddress(kj::Own<kj::NetworkAddress> proxy, kj::StringPtr upstream, uint portHint,
797+
kj::Maybe<kj::TlsContext&> tls = kj::none)
798+
: proxy(kj::mv(proxy)), upstream(upstream), portHint(portHint), tls(tls) {}
799+
800+
kj::Promise<kj::Own<kj::AsyncIoStream>> connect() override {
801+
KJ_REQUIRE(upstream.size() < 253, "socks5: proxied host is too long");
802+
const kj::byte RESERVED = 0;
803+
const kj::byte SOCKS5_VER = 5;
804+
const kj::byte AUTH_METHOD_NONE = 0;
805+
const kj::byte CMD_CONNECT = 1;
806+
807+
const kj::byte ADDR_IP4 = 1;
808+
const kj::byte ADDR_FQDN = 3;
809+
const kj::byte ADDR_IP6 = 4;
810+
811+
// 1. Send auth request
812+
auto stream = co_await proxy->connect();
813+
kj::byte buf[7 + 253];
814+
buf[0] = SOCKS5_VER;
815+
buf[1] = 1; // one authentication method
816+
buf[2] = AUTH_METHOD_NONE;
817+
co_await stream->write(buf, 3);
818+
819+
// 2. handle auth response
820+
co_await stream->read(buf, 2);
821+
KJ_REQUIRE(buf[0] == SOCKS5_VER, "socks5: unsupported version");
822+
KJ_REQUIRE(buf[1] == AUTH_METHOD_NONE, "socks5: unsupported auth method");
823+
824+
// 3. send connect request
825+
buf[0] = SOCKS5_VER;
826+
buf[1] = CMD_CONNECT;
827+
buf[2] = RESERVED;
828+
buf[3] = ADDR_FQDN;
829+
buf[4] = upstream.size();
830+
memcpy(buf + 5, upstream.begin(), upstream.size());
831+
uint16_t cmdReqSize = upstream.size() + 5 + 2;
832+
buf[cmdReqSize - 2] = portHint >> 8;
833+
buf[cmdReqSize - 1] = portHint & 0xff;
834+
co_await stream->write(buf, cmdReqSize);
835+
836+
// 4. handle connect respond
837+
co_await stream->read(buf, 5);
838+
KJ_REQUIRE(buf[0] == SOCKS5_VER, "socks5: unsupported version");
839+
KJ_REQUIRE(buf[1] == 0, "socks5: failed to connect to upstream");
840+
KJ_REQUIRE(buf[2] == RESERVED, "socks5: invalid connect response reserved byte");
841+
switch(buf[3]) {
842+
case ADDR_IP4: co_await stream->read(buf, 4 + 2 - 1); break;
843+
case ADDR_IP6: co_await stream->read(buf, 16 + 2 - 1); break;
844+
case ADDR_FQDN: co_await stream->read(buf, buf[4] + 2); break;
845+
default: throw KJ_EXCEPTION(FAILED, "socks5: invalid bound address type");
846+
}
847+
848+
// 5. Return connected stream
849+
KJ_IF_SOME(tlsContext, tls) {
850+
co_return co_await tlsContext.wrapClient(kj::mv(stream), upstream);
851+
} else {
852+
co_return stream;
853+
}
854+
}
855+
856+
kj::Own<kj::NetworkAddress> clone() override {
857+
return kj::heap<Socks5NetworkAddress>(proxy->clone(), upstream, portHint, tls);
858+
}
859+
860+
// We don't use any other methods, and they seem kinda annoying to implement.
861+
kj::Own<kj::ConnectionReceiver> listen() override {
862+
KJ_UNIMPLEMENTED("Socks5NetworkAddress::listen() not implemented");
863+
}
864+
kj::String toString() override {
865+
KJ_UNIMPLEMENTED("Socks5NetworkAddress::toString() not implemented");
866+
}
867+
868+
private:
869+
kj::Own<kj::NetworkAddress> proxy;
870+
kj::StringPtr upstream;
871+
uint portHint;
872+
kj::Maybe<kj::TlsContext&> tls = kj::none;
873+
};
874+
};
875+
752876
kj::Own<Server::Service> Server::makeNetworkService(config::Network::Reader conf) {
753877
TRACE_EVENT("workerd", "Server::makeNetworkService()");
754878
auto restrictedNetwork = network.restrictPeers(
@@ -757,7 +881,18 @@ kj::Own<Server::Service> Server::makeNetworkService(config::Network::Reader conf
757881

758882
kj::Maybe<kj::Own<kj::Network>> tlsNetwork;
759883
kj::Maybe<kj::SecureNetworkWrapper&> tlsContext;
760-
if (conf.hasTlsOptions()) {
884+
885+
886+
if (conf.hasProxy()) {
887+
auto proxyConf = conf.getProxy();
888+
if (conf.hasTlsOptions()) {
889+
auto ownedTlsContext = makeTlsContext(conf.getTlsOptions());
890+
tlsNetwork = kj::heap<Socks5ProxyNetwork>(proxyConf.getAddress(), *restrictedNetwork, *ownedTlsContext)
891+
.attach(kj::mv(ownedTlsContext));
892+
}
893+
restrictedNetwork = kj::heap<Socks5ProxyNetwork>(proxyConf.getAddress(), *restrictedNetwork)
894+
.attach(kj::mv(restrictedNetwork));
895+
} else if (conf.hasTlsOptions()) {
761896
auto ownedTlsContext = makeTlsContext(conf.getTlsOptions());
762897
tlsContext = ownedTlsContext;
763898
tlsNetwork = ownedTlsContext->wrapNetwork(*restrictedNetwork).attach(kj::mv(ownedTlsContext));

src/workerd/server/workerd.capnp

+12
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,18 @@ struct Network {
698698
# (The above is exactly the format supported by kj::Network::restrictPeers().)
699699

700700
tlsOptions @2 :TlsOptions;
701+
702+
proxy @3 :ProxyOptions;
703+
}
704+
705+
struct ProxyOptions {
706+
type @0 :ProxyType = socks5;
707+
708+
enum ProxyType {
709+
socks5 @0;
710+
}
711+
712+
address @1 :Text = "127.0.0.1:1080";
701713
}
702714

703715
struct DiskDirectory {

0 commit comments

Comments
 (0)