@@ -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+
827951kj::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));
0 commit comments