Skip to content

Commit a1769de

Browse files
committed
Add SOCKS5 support
1 parent 3e3081d commit a1769de

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

Diff for: src/workerd/server/server.c++

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

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

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

Diff for: src/workerd/server/workerd.capnp

+12
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)