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