From a838cdde0aa83d5717856d5b133105453c4e3d53 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sun, 5 Dec 2021 08:33:56 +0800 Subject: [PATCH 01/13] Add UDP SOCKS5 proxy support for BT By inherienting DHTConnectionImpl that is used by UDP trackers and DHT connections --- src/DHTConnectionSocksProxyImpl.cc | 229 +++++++++++++++++++++++++++++ src/DHTConnectionSocksProxyImpl.h | 76 ++++++++++ src/DHTSetup.cc | 13 +- src/Makefile.am | 1 + 4 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 src/DHTConnectionSocksProxyImpl.cc create mode 100644 src/DHTConnectionSocksProxyImpl.h diff --git a/src/DHTConnectionSocksProxyImpl.cc b/src/DHTConnectionSocksProxyImpl.cc new file mode 100644 index 00000000..1ac0bdd0 --- /dev/null +++ b/src/DHTConnectionSocksProxyImpl.cc @@ -0,0 +1,229 @@ +/* */ +#include "DHTConnectionSocksProxyImpl.h" + +#include +#include + +#include "SocketCore.h" + +namespace aria2 { + +DHTConnectionSocksProxyImpl::DHTConnectionSocksProxyImpl(int family) + : DHTConnectionImpl(family), + family_(family), + proxySocket_(std::make_unique()) +{ +} + +DHTConnectionSocksProxyImpl::~DHTConnectionSocksProxyImpl() = default; + +bool DHTConnectionSocksProxyImpl::startProxy(const std::string& host, + uint16_t port, + const std::string& user, + const std::string& passwd, + const std::string& listenAddr, + uint16_t listenPort) +{ + proxySocket_->establishConnection(host, port); + proxySocket_->setBlockingMode(); + Endpoint proxyEndpoint; + unsigned char resBuf[10]; + + // Authentication negotiation + bool noAuth = user.empty() || passwd.empty(); + if (noAuth) { + std::string req("\x05\x01\x00", 3); + size_t resLen = 2; + proxySocket_->writeData(req); + proxySocket_->readData(resBuf, resLen); + if (resBuf[1] != '\x00') { + proxySocket_->closeConnection(); + return false; + } + } + else { + std::string req("\x05\x02\x00\x02", 4); + size_t resLen = 2; + proxySocket_->writeData(req); + Endpoint proxyEndpoint; + proxySocket_->readData(resBuf, resLen); + if (resBuf[1] != '\x02' && resBuf[1] != '\x00') { + proxySocket_->closeConnection(); + return false; + } + + // Username/password authentication + if (resBuf[1] == '\x02') { + req = std::string("\x01", 1); + req.push_back(static_cast(user.length())); + req += user; + req.push_back(static_cast(passwd.length())); + req += passwd; + size_t resLen = 2; + proxySocket_->writeData(req); + proxySocket_->readData(resBuf, resLen); + if (resBuf[1] != '\x00') { + proxySocket_->closeConnection(); + return false; + } + } + } + + // UDP associate + std::string req("\x05\x03\x00", 3); + if (family_ == AF_INET) { + req.push_back('\x01'); + char addrBuf[10]; + net::getBinAddr(addrBuf, listenAddr); + req.append(addrBuf, 4); + } + else { + req.push_back('\x04'); + char addrBuf[20]; + net::getBinAddr(addrBuf, listenAddr); + req.append(addrBuf, 16); + } + uint16_t listenPortBuf = htons(listenPort); + req.append(reinterpret_cast(&listenPortBuf), 2); + size_t resLen = 5; + proxySocket_->writeData(req); + proxySocket_->readData(resBuf, resLen); + if (resBuf[1] != '\x00') { + proxySocket_->closeConnection(); + return false; + } + if (resBuf[3] == '\x01') { + unsigned char addrBuf[6]; + addrBuf[0] = resBuf[4]; + resLen = 5; + proxySocket_->readData(addrBuf + 1, resLen); + char addrStrBuf[20]; + inetNtop(AF_INET, addrBuf, addrStrBuf, 20); + bndAddr_ = std::string(addrStrBuf); + bndPort_ = ntohs(*reinterpret_cast(addrBuf + 4)); + } + else if (resBuf[3] == '\x04') { + unsigned char addrBuf[18]; + addrBuf[0] = resBuf[4]; + resLen = 17; + proxySocket_->readData(addrBuf + 1, resLen); + char addrStrBuf[50]; + inetNtop(AF_INET6, addrBuf, addrStrBuf, 50); + bndAddr_ = std::string(addrStrBuf); + bndPort_ = ntohs(*reinterpret_cast(addrBuf + 16)); + } + else if (resBuf[3] == '\x03') { + bndAddr_ = std::string(resBuf[4] + 2, '\x00'); + resLen = resBuf[4] + 2; + proxySocket_->readData(&bndAddr_[0], resLen); + bndPort_ = ntohs(*reinterpret_cast(&bndAddr_[0] + resBuf[4])); + bndAddr_.resize(resBuf[4]); + } + else { + proxySocket_->closeConnection(); + return false; + } + return true; +} + +ssize_t DHTConnectionSocksProxyImpl::receiveMessage(unsigned char* data, + size_t len, + std::string& host, + uint16_t& port) +{ + Endpoint remoteEndpoint; + size_t resLen = len + family_ == AF_INET ? 10 : 22; + std::string buf; + buf.resize(resLen); + ssize_t length = getSocket()->readDataFrom(&buf[0], resLen, remoteEndpoint); + if (length == 0) { + return length; + } + + // unencapsulate SOCKS5 UDP header if has + if (length > (family_ == AF_INET ? 10 : 22) && + buf.substr(0, 3) == "\x00\x00\x00" && + buf[3] == (family_ == AF_INET ? '\x01' : '\x04')) { + if (family_ == AF_INET) { + char addrBuf[20]; + inetNtop(AF_INET, &buf[4], addrBuf, 20); + host = std::string(addrBuf); + port = ntohs(*(reinterpret_cast(&buf[8]))); + memcpy(data, buf.c_str(), length - 10); + return length - 10; + } + else { + char addrBuf[50]; + inetNtop(AF_INET6, &buf[4], addrBuf, 50); + host = std::string(addrBuf); + port = ntohs(*(reinterpret_cast(&buf[20]))); + memcpy(data, buf.c_str(), length - 22); + return length - 22; + } + } + else { + host = remoteEndpoint.addr; + port = remoteEndpoint.port; + return length; + } +} + +ssize_t DHTConnectionSocksProxyImpl::sendMessage(const unsigned char* data, + size_t len, + const std::string& host, + uint16_t port) +{ + std::string buf; + if (family_ == AF_INET) { + buf.resize(10); + buf[3] = '\x01'; + // host is got from receiveMessage(). And as socket is binded according to + // family, host should be the same family as family_. Omit family checking. + net::getBinAddr(&buf[4], host); + *(reinterpret_cast(&buf[8])) = htons(port); + buf.append(reinterpret_cast(data), len); + } + else { + buf.resize(22); + buf[3] = '\x04'; + net::getBinAddr(&buf[4], host); + *(reinterpret_cast(&buf[20])) = htons(port); + buf.append(reinterpret_cast(data), len); + } + return getSocket()->writeData(&buf[0], buf.length(), bndAddr_, bndPort_); +} + +} // namespace aria2 diff --git a/src/DHTConnectionSocksProxyImpl.h b/src/DHTConnectionSocksProxyImpl.h new file mode 100644 index 00000000..d6f6d54c --- /dev/null +++ b/src/DHTConnectionSocksProxyImpl.h @@ -0,0 +1,76 @@ +/* */ +#ifndef D_DHT_CONNECTION_SOCKS_PROXY_IMPL_H +#define D_DHT_CONNECTION_SOCKS_PROXY_IMPL_H + +#include "DHTConnectionImpl.h" + +#include + +namespace aria2 { + +class SocketCore; + +class DHTConnectionSocksProxyImpl : public DHTConnectionImpl { +private: + std::unique_ptr proxySocket_; + int family_; + std::string bndAddr_; + uint16_t bndPort_; + +public: + DHTConnectionSocksProxyImpl(int family); + + virtual ~DHTConnectionSocksProxyImpl(); + + // Connect to SOCKS5 server to start UDP proxy. + // Leave user and passwd empty to indicate no authentication. + // Leave listen host and port empty / 0 to indicate no receiving from proxy. + bool startProxy(const std::string& host, uint16_t port, + const std::string& user, const std::string& passwd, + const std::string& listenHost, uint16_t listenPort); + + virtual ssize_t receiveMessage(unsigned char* data, size_t len, + std::string& host, + uint16_t& port) CXX11_OVERRIDE; + + virtual ssize_t sendMessage(const unsigned char* data, size_t len, + const std::string& host, + uint16_t port) CXX11_OVERRIDE; +}; + +} // namespace aria2 + +#endif // D_DHT_CONNECTION_SOCKS_PROXY_IMPL_H diff --git a/src/DHTSetup.cc b/src/DHTSetup.cc index d107a53e..5a773c23 100644 --- a/src/DHTSetup.cc +++ b/src/DHTSetup.cc @@ -41,6 +41,7 @@ #include "util.h" #include "DHTNode.h" #include "DHTConnectionImpl.h" +#include "DHTConnectionSocksProxyImpl.h" #include "DHTRoutingTable.h" #include "DHTMessageFactoryImpl.h" #include "DHTMessageTracker.h" @@ -113,7 +114,7 @@ DHTSetup::setup(DownloadEngine* e, int family) } uint16_t port; - auto connection = make_unique(family); + auto connection = make_unique(family); { port = e->getBtRegistry()->getUdpPort(); const std::string& addr = e->getOption()->get( @@ -139,6 +140,16 @@ DHTSetup::setup(DownloadEngine* e, int family) } A2_LOG_DEBUG(fmt("Initialized local node ID=%s", util::toHex(localNode->getID(), DHT_ID_LENGTH).c_str())); + { + if (!connection->startProxy("127.0.0.1", 8000, "", "", + localNode->getIPAddress(), + localNode->getPort())) { + throw DL_ABORT_EX("Error occurred while connecting to SOCKS5 relay " + "server for UDP proxy for DHT and UDP trackers"); + } + } + A2_LOG_DEBUG(fmt("Connected to SOCKS5 relay server %s:%u for UDP proxy", + "127.0.0.1", 8000)); auto tracker = std::make_shared(); auto routingTable = make_unique(localNode); auto factory = make_unique(family); diff --git a/src/Makefile.am b/src/Makefile.am index cb6e3b7c..c352021a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -503,6 +503,7 @@ SRCS += \ DHTBucketTree.cc DHTBucketTree.h\ DHTConnection.h\ DHTConnectionImpl.cc DHTConnectionImpl.h\ + DHTConnectionSocksProxyImpl.cc DHTConnectionSocksProxyImpl.h\ DHTConstants.h\ DHTEntryPointNameResolveCommand.cc DHTEntryPointNameResolveCommand.h\ DHTFindNodeMessage.cc DHTFindNodeMessage.h\ From 2806da3298e716e12aec751f99b2215cbf5bdd62 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sun, 5 Dec 2021 19:36:10 +0800 Subject: [PATCH 02/13] Add bt-udp-socks-proxy option with its utilities --- src/DHTSetup.cc | 14 ++++++++++--- src/FeatureConfig.cc | 3 +++ src/OptionHandlerFactory.cc | 31 +++++++++++++++++++++++++++ src/OptionHandlerImpl.cc | 42 +++++++++++++++++++++++++++++++++++++ src/OptionHandlerImpl.h | 14 +++++++++++++ src/option_processing.cc | 1 + src/prefs.cc | 3 +++ src/prefs.h | 3 +++ src/usage_text.h | 7 +++++++ 9 files changed, 115 insertions(+), 3 deletions(-) diff --git a/src/DHTSetup.cc b/src/DHTSetup.cc index 5a773c23..d8cfb8d6 100644 --- a/src/DHTSetup.cc +++ b/src/DHTSetup.cc @@ -141,15 +141,23 @@ DHTSetup::setup(DownloadEngine* e, int family) A2_LOG_DEBUG(fmt("Initialized local node ID=%s", util::toHex(localNode->getID(), DHT_ID_LENGTH).c_str())); { - if (!connection->startProxy("127.0.0.1", 8000, "", "", + const std::string& user = + e->getOption()->get(PREF_BT_UDP_SOCKS_PROXY_USER); + const std::string& passwd = + e->getOption()->get(PREF_BT_UDP_SOCKS_PROXY_PASSWD); + uri::UriStruct us; + uri::parse(us, e->getOption()->get(PREF_BT_UDP_SOCKS_PROXY)); + const std::string& host = us.host; + uint16_t port = us.port; + if (!connection->startProxy(host, port, user, passwd, localNode->getIPAddress(), localNode->getPort())) { throw DL_ABORT_EX("Error occurred while connecting to SOCKS5 relay " "server for UDP proxy for DHT and UDP trackers"); } + A2_LOG_DEBUG(fmt("Connected to SOCKS5 relay server %s:%u for UDP proxy", + host, port)); } - A2_LOG_DEBUG(fmt("Connected to SOCKS5 relay server %s:%u for UDP proxy", - "127.0.0.1", 8000)); auto tracker = std::make_shared(); auto routingTable = make_unique(localNode); auto factory = make_unique(family); diff --git a/src/FeatureConfig.cc b/src/FeatureConfig.cc index 2b43c8b1..98e6b115 100644 --- a/src/FeatureConfig.cc +++ b/src/FeatureConfig.cc @@ -88,6 +88,9 @@ uint16_t getDefaultPort(const std::string& protocol) else if (protocol == "sftp") { return 22; } + else if (protocol == "socks") { + return 1080; + } else { return 0; } diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc index 6bff4caa..413f01eb 100644 --- a/src/OptionHandlerFactory.cc +++ b/src/OptionHandlerFactory.cc @@ -1371,6 +1371,37 @@ std::vector OptionHandlerFactory::createOptionHandlers() op->setChangeOptionForReserved(true); handlers.push_back(op); } + { + OptionHandler* op(new SocksProxyOptionHandler( + PREF_BT_UDP_SOCKS_PROXY, TEXT_BT_UDP_SOCKS_PROXY, NO_DEFAULT_VALUE)); + op->addTag(TAG_BITTORRENT); + op->setInitialOption(true); + op->setChangeGlobalOption(true); + op->setChangeOptionForReserved(true); + handlers.push_back(op); + } + { + OptionHandler* op(new DefaultOptionHandler(PREF_BT_UDP_SOCKS_PROXY_PASSWD, + TEXT_BT_UDP_SOCKS_PROXY_PASSWD, + NO_DEFAULT_VALUE)); + op->addTag(TAG_BITTORRENT); + op->setEraseAfterParse(true); + op->setInitialOption(true); + op->setChangeGlobalOption(true); + op->setChangeOptionForReserved(true); + handlers.push_back(op); + } + { + OptionHandler* op(new DefaultOptionHandler(PREF_BT_UDP_SOCKS_PROXY_USER, + TEXT_BT_UDP_SOCKS_PROXY_USER, + NO_DEFAULT_VALUE)); + op->addTag(TAG_BITTORRENT); + op->setEraseAfterParse(true); + op->setInitialOption(true); + op->setChangeGlobalOption(true); + op->setChangeOptionForReserved(true); + handlers.push_back(op); + } { OptionHandler* op(new HttpProxyOptionHandler(PREF_ALL_PROXY, TEXT_ALL_PROXY, NO_DEFAULT_VALUE)); diff --git a/src/OptionHandlerImpl.cc b/src/OptionHandlerImpl.cc index 6214e84b..93c82c7d 100644 --- a/src/OptionHandlerImpl.cc +++ b/src/OptionHandlerImpl.cc @@ -529,6 +529,48 @@ std::string HttpProxyOptionHandler::createPossibleValuesString() const return "[http://][USER:PASSWORD@]HOST[:PORT]"; } +SocksProxyOptionHandler::SocksProxyOptionHandler( + PrefPtr pref, const char* description, const std::string& defaultValue, + char shortName) + : AbstractOptionHandler(pref, description, defaultValue, + OptionHandler::REQ_ARG, shortName), + proxyUserPref_(option::k2p(std::string(pref->k) + "-user")), + proxyPasswdPref_(option::k2p(std::string(pref->k) + "-passwd")) +{ +} + +SocksProxyOptionHandler::~SocksProxyOptionHandler() = default; + +void SocksProxyOptionHandler::parseArg(Option& option, + const std::string& optarg) const +{ + if (optarg.empty()) { + option.put(pref_, optarg); + } + else { + std::string uri; + if (util::startsWith(optarg, "socks://") || + util::startsWith(optarg, "socks5://")) { + uri = optarg; + } + else { + uri = "socks://"; + uri += optarg; + } + uri::UriStruct us; + if (!uri::parse(us, uri)) { + throw DL_ABORT_EX(_("unrecognized proxy format")); + } + us.protocol = "socks"; + option.put(pref_, uri::construct(us)); + } +} + +std::string SocksProxyOptionHandler::createPossibleValuesString() const +{ + return "[socks[5]://][USER:PASSWORD@]HOST[:PORT]"; +} + LocalFilePathOptionHandler::LocalFilePathOptionHandler( PrefPtr pref, const char* description, const std::string& defaultValue, bool acceptStdin, char shortName, bool mustExist, diff --git a/src/OptionHandlerImpl.h b/src/OptionHandlerImpl.h index 7846b226..7404e3b6 100644 --- a/src/OptionHandlerImpl.h +++ b/src/OptionHandlerImpl.h @@ -230,6 +230,20 @@ public: virtual std::string createPossibleValuesString() const CXX11_OVERRIDE; }; +class SocksProxyOptionHandler : public AbstractOptionHandler { +private: + PrefPtr proxyUserPref_; + PrefPtr proxyPasswdPref_; + +public: + SocksProxyOptionHandler(PrefPtr pref, const char* description, + const std::string& defaultValue, char shortName = 0); + virtual ~SocksProxyOptionHandler(); + virtual void parseArg(Option& option, + const std::string& optarg) const CXX11_OVERRIDE; + virtual std::string createPossibleValuesString() const CXX11_OVERRIDE; +}; + class LocalFilePathOptionHandler : public AbstractOptionHandler { private: std::string possibleValuesString_; diff --git a/src/option_processing.cc b/src/option_processing.cc index f9891fed..bf4c0196 100644 --- a/src/option_processing.cc +++ b/src/option_processing.cc @@ -263,6 +263,7 @@ error_code::Value option_processing(Option& op, bool standalone, overrideWithEnv(*confOption, oparser, PREF_HTTP_PROXY, "http_proxy"); overrideWithEnv(*confOption, oparser, PREF_HTTPS_PROXY, "https_proxy"); overrideWithEnv(*confOption, oparser, PREF_FTP_PROXY, "ftp_proxy"); + overrideWithEnv(*confOption, oparser, PREF_BT_UDP_SOCKS_PROXY, "bt_udp_sokcs_proxy"); overrideWithEnv(*confOption, oparser, PREF_ALL_PROXY, "all_proxy"); overrideWithEnv(*confOption, oparser, PREF_NO_PROXY, "no_proxy"); if (!standalone) { diff --git a/src/prefs.cc b/src/prefs.cc index fe982685..0184a163 100644 --- a/src/prefs.cc +++ b/src/prefs.cc @@ -437,6 +437,7 @@ PrefPtr PREF_HTTP_PROXY = makePref("http-proxy"); PrefPtr PREF_HTTPS_PROXY = makePref("https-proxy"); PrefPtr PREF_FTP_PROXY = makePref("ftp-proxy"); PrefPtr PREF_ALL_PROXY = makePref("all-proxy"); +PrefPtr PREF_BT_UDP_SOCKS_PROXY = makePref("bt-udp-socks-proxy"); // values: comma separated hostname or domain PrefPtr PREF_NO_PROXY = makePref("no-proxy"); // values: get | tunnel @@ -447,6 +448,8 @@ PrefPtr PREF_HTTPS_PROXY_USER = makePref("https-proxy-user"); PrefPtr PREF_HTTPS_PROXY_PASSWD = makePref("https-proxy-passwd"); PrefPtr PREF_FTP_PROXY_USER = makePref("ftp-proxy-user"); PrefPtr PREF_FTP_PROXY_PASSWD = makePref("ftp-proxy-passwd"); +PrefPtr PREF_BT_UDP_SOCKS_PROXY_USER = makePref("bt-udp-socks-proxy-user"); +PrefPtr PREF_BT_UDP_SOCKS_PROXY_PASSWD = makePref("bt-udp-socks-proxy-passwd"); PrefPtr PREF_ALL_PROXY_USER = makePref("all-proxy-user"); PrefPtr PREF_ALL_PROXY_PASSWD = makePref("all-proxy-passwd"); diff --git a/src/prefs.h b/src/prefs.h index 9719b3be..35da0406 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -388,6 +388,7 @@ extern PrefPtr PREF_CONTENT_DISPOSITION_DEFAULT_UTF8; extern PrefPtr PREF_HTTP_PROXY; extern PrefPtr PREF_HTTPS_PROXY; extern PrefPtr PREF_FTP_PROXY; +extern PrefPtr PREF_BT_UDP_SOCKS_PROXY; extern PrefPtr PREF_ALL_PROXY; // values: comma separated hostname or domain extern PrefPtr PREF_NO_PROXY; @@ -399,6 +400,8 @@ extern PrefPtr PREF_HTTPS_PROXY_USER; extern PrefPtr PREF_HTTPS_PROXY_PASSWD; extern PrefPtr PREF_FTP_PROXY_USER; extern PrefPtr PREF_FTP_PROXY_PASSWD; +extern PrefPtr PREF_BT_UDP_SOCKS_PROXY_USER; +extern PrefPtr PREF_BT_UDP_SOCKS_PROXY_PASSWD; extern PrefPtr PREF_ALL_PROXY_USER; extern PrefPtr PREF_ALL_PROXY_PASSWD; diff --git a/src/usage_text.h b/src/usage_text.h index ebf67e3d..55f79027 100644 --- a/src/usage_text.h +++ b/src/usage_text.h @@ -83,6 +83,9 @@ " previously defined proxy, use \"\".\n" \ " See also the --all-proxy option.\n" \ " This affects all ftp downloads.") +#define TEXT_BT_UDP_SOCKS_PROXY \ + _(" --bt-udp-socks-proxy=PROXY Use a SOCKS5 proxy server for UDP in BitTorrent.\n"\ + " This affects DHT and connecting UDP trackers in BitTorrent.") #define TEXT_ALL_PROXY \ _(" --all-proxy=PROXY Use a proxy server for all protocols. To override\n" \ " a previously defined proxy, use \"\".\n" \ @@ -690,6 +693,10 @@ _(" --ftp-proxy-user=USER Set user for --ftp-proxy.") #define TEXT_FTP_PROXY_PASSWD \ _(" --ftp-proxy-passwd=PASSWD Set password for --ftp-proxy.") +#define TEXT_BT_UDP_SOCKS_PROXY_USER \ + _(" --bt-udp-socks-proxy-user=USER Set user for --http-proxy.") +#define TEXT_BT_UDP_SOCKS_PROXY_PASSWD \ + _(" --bt-udp-socks-proxy-passwd=PASSWD Set password for --http-proxy.") #define TEXT_REMOVE_CONTROL_FILE \ _(" --remove-control-file[=true|false] Remove control file before download. Using\n" \ " with --allow-overwrite=true, download always\n" \ From f2a66a68b3076a2b575fae85a3b9cec2adf25a22 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sun, 5 Dec 2021 20:57:12 +0800 Subject: [PATCH 03/13] Setup BT UDP SOCKS5 proxy on demand --- src/DHTSetup.cc | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/DHTSetup.cc b/src/DHTSetup.cc index d8cfb8d6..380f7276 100644 --- a/src/DHTSetup.cc +++ b/src/DHTSetup.cc @@ -114,7 +114,10 @@ DHTSetup::setup(DownloadEngine* e, int family) } uint16_t port; - auto connection = make_unique(family); + const std::string& proxyUri = e->getOption()->get(PREF_BT_UDP_SOCKS_PROXY); + auto connection = proxyUri.empty() + ? make_unique(family) + : make_unique(family); { port = e->getBtRegistry()->getUdpPort(); const std::string& addr = e->getOption()->get( @@ -140,18 +143,18 @@ DHTSetup::setup(DownloadEngine* e, int family) } A2_LOG_DEBUG(fmt("Initialized local node ID=%s", util::toHex(localNode->getID(), DHT_ID_LENGTH).c_str())); - { + if (!proxyUri.empty()) { + auto c = static_cast(connection.get()); const std::string& user = e->getOption()->get(PREF_BT_UDP_SOCKS_PROXY_USER); const std::string& passwd = e->getOption()->get(PREF_BT_UDP_SOCKS_PROXY_PASSWD); uri::UriStruct us; - uri::parse(us, e->getOption()->get(PREF_BT_UDP_SOCKS_PROXY)); + uri::parse(us, proxyUri); const std::string& host = us.host; uint16_t port = us.port; - if (!connection->startProxy(host, port, user, passwd, - localNode->getIPAddress(), - localNode->getPort())) { + if (!c->startProxy(host, port, user, passwd, localNode->getIPAddress(), + localNode->getPort())) { throw DL_ABORT_EX("Error occurred while connecting to SOCKS5 relay " "server for UDP proxy for DHT and UDP trackers"); } From be6243f0b7a06125357f7417a029cf4c21fb5953 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sun, 5 Dec 2021 21:42:22 +0800 Subject: [PATCH 04/13] Add test for BT UDP SOCKS5 proxy --- test/DHTConnectionSocksProxyImplTest.cc | 82 +++++++++++++++++++++++++ test/Makefile.am | 1 + 2 files changed, 83 insertions(+) create mode 100644 test/DHTConnectionSocksProxyImplTest.cc diff --git a/test/DHTConnectionSocksProxyImplTest.cc b/test/DHTConnectionSocksProxyImplTest.cc new file mode 100644 index 00000000..3af6b7be --- /dev/null +++ b/test/DHTConnectionSocksProxyImplTest.cc @@ -0,0 +1,82 @@ +#include "DHTConnectionSocksProxyImpl.h" + +#include +#include + +#include "Exception.h" +#include "SocketCore.h" +#include "A2STR.h" + +namespace aria2 { + +class DHTConnectionSocksProxyImplTest : public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(DHTConnectionSocksProxyImplTest); + CPPUNIT_TEST(testWriteAndReadData); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() {} + + void tearDown() {} + + void testWriteAndReadData(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(DHTConnectionSocksProxyImplTest); + +void DHTConnectionSocksProxyImplTest::testWriteAndReadData() +{ + try { + DHTConnectionSocksProxyImpl con1(AF_INET); + uint16_t con1port = 0; + CPPUNIT_ASSERT(con1.bind(con1port, "127.0.0.1")); + + DHTConnectionImpl con2(AF_INET); + uint16_t con2port = 0; + CPPUNIT_ASSERT(con2.bind(con2port, "127.0.0.1")); + + // TODO: Requires a SOCKS5 proxy server for tests + return; + CPPUNIT_ASSERT( + con1.startProxy("localhost", 10801, "", "", "127.0.0.1", con1port)); + + std::string message1 = "hello world."; + con1.sendMessage(reinterpret_cast(message1.c_str()), + message1.size(), "127.0.0.1", con2port); + + unsigned char readbuffer[100]; + std::string remoteHost; + uint16_t remotePort; + { + while (!con2.getSocket()->isReadable(0)) + ; + ssize_t rlength = con2.receiveMessage(readbuffer, sizeof(readbuffer), + remoteHost, remotePort); + CPPUNIT_ASSERT_EQUAL((ssize_t)message1.size(), rlength); + CPPUNIT_ASSERT_EQUAL(message1, + std::string(&readbuffer[0], &readbuffer[rlength])); + } + + std::string message2 = "hello world too."; + con2.sendMessage(reinterpret_cast(message2.c_str()), + message2.size(), remoteHost, remotePort); + + { + std::string remoteHost; + uint16_t remotePort; + while (!con1.getSocket()->isReadable(0)) + ; + ssize_t rlength = con1.receiveMessage(readbuffer, sizeof(readbuffer), + remoteHost, remotePort); + CPPUNIT_ASSERT_EQUAL((ssize_t)message2.size(), rlength); + CPPUNIT_ASSERT_EQUAL(message2, + std::string(&readbuffer[0], &readbuffer[rlength])); + } + } + catch (Exception& e) { + CPPUNIT_FAIL(e.stackTrace()); + } +} + +} // namespace aria2 diff --git a/test/Makefile.am b/test/Makefile.am index c7114449..d1cf5cb9 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -170,6 +170,7 @@ aria2c_SOURCES += BtAllowedFastMessageTest.cc\ DHTMessageTrackerEntryTest.cc\ DHTMessageTrackerTest.cc\ DHTConnectionImplTest.cc\ + DHTConnectionSocksProxyImplTest.cc\ DHTPingMessageTest.cc\ DHTPingReplyMessageTest.cc\ DHTFindNodeMessageTest.cc\ From 71b652f8a156fe9b6d28b2470102378b1d872f31 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sun, 5 Dec 2021 21:42:44 +0800 Subject: [PATCH 05/13] Fix receiving handling in BT UDP SOCKS5 proxy --- src/DHTConnectionSocksProxyImpl.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DHTConnectionSocksProxyImpl.cc b/src/DHTConnectionSocksProxyImpl.cc index 1ac0bdd0..852b094f 100644 --- a/src/DHTConnectionSocksProxyImpl.cc +++ b/src/DHTConnectionSocksProxyImpl.cc @@ -165,7 +165,7 @@ ssize_t DHTConnectionSocksProxyImpl::receiveMessage(unsigned char* data, uint16_t& port) { Endpoint remoteEndpoint; - size_t resLen = len + family_ == AF_INET ? 10 : 22; + size_t resLen = len + (family_ == AF_INET ? 10 : 22); std::string buf; buf.resize(resLen); ssize_t length = getSocket()->readDataFrom(&buf[0], resLen, remoteEndpoint); @@ -175,14 +175,14 @@ ssize_t DHTConnectionSocksProxyImpl::receiveMessage(unsigned char* data, // unencapsulate SOCKS5 UDP header if has if (length > (family_ == AF_INET ? 10 : 22) && - buf.substr(0, 3) == "\x00\x00\x00" && + buf.substr(0, 3) == std::string("\x00\x00\x00", 3) && buf[3] == (family_ == AF_INET ? '\x01' : '\x04')) { if (family_ == AF_INET) { char addrBuf[20]; inetNtop(AF_INET, &buf[4], addrBuf, 20); host = std::string(addrBuf); port = ntohs(*(reinterpret_cast(&buf[8]))); - memcpy(data, buf.c_str(), length - 10); + memcpy(data, &buf[10], length - 10); return length - 10; } else { @@ -190,7 +190,7 @@ ssize_t DHTConnectionSocksProxyImpl::receiveMessage(unsigned char* data, inetNtop(AF_INET6, &buf[4], addrBuf, 50); host = std::string(addrBuf); port = ntohs(*(reinterpret_cast(&buf[20]))); - memcpy(data, buf.c_str(), length - 22); + memcpy(data, &buf[22], length - 22); return length - 22; } } From 12d419d7fce01ed655b3806f50233112c2d20a25 Mon Sep 17 00:00:00 2001 From: myl7 Date: Mon, 6 Dec 2021 03:37:13 +0800 Subject: [PATCH 06/13] Fix fmt param type in logging --- src/DHTSetup.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DHTSetup.cc b/src/DHTSetup.cc index 380f7276..02e20f29 100644 --- a/src/DHTSetup.cc +++ b/src/DHTSetup.cc @@ -159,7 +159,7 @@ DHTSetup::setup(DownloadEngine* e, int family) "server for UDP proxy for DHT and UDP trackers"); } A2_LOG_DEBUG(fmt("Connected to SOCKS5 relay server %s:%u for UDP proxy", - host, port)); + host.c_str(), port)); } auto tracker = std::make_shared(); auto routingTable = make_unique(localNode); From 60d986873f0c29015e49a988f64e6ec4803fdf42 Mon Sep 17 00:00:00 2001 From: myl7 Date: Mon, 6 Dec 2021 03:55:47 +0800 Subject: [PATCH 07/13] Fix make_unique importing --- src/DHTConnectionSocksProxyImpl.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DHTConnectionSocksProxyImpl.cc b/src/DHTConnectionSocksProxyImpl.cc index 852b094f..4684e828 100644 --- a/src/DHTConnectionSocksProxyImpl.cc +++ b/src/DHTConnectionSocksProxyImpl.cc @@ -38,13 +38,14 @@ #include #include "SocketCore.h" +#include "a2functional.h" namespace aria2 { DHTConnectionSocksProxyImpl::DHTConnectionSocksProxyImpl(int family) : DHTConnectionImpl(family), family_(family), - proxySocket_(std::make_unique()) + proxySocket_(make_unique()) { } From de08f510be44605309bf5ec2cfd7c603d07332eb Mon Sep 17 00:00:00 2001 From: myl7 Date: Tue, 7 Dec 2021 22:01:01 +0800 Subject: [PATCH 08/13] Extract SOCKS5 logic into new class SocksProxySockct --- src/DHTConnectionSocksProxyImpl.cc | 100 +++----------- src/DHTConnectionSocksProxyImpl.h | 10 +- src/Makefile.am | 1 + src/SocksProxySocket.cc | 201 +++++++++++++++++++++++++++++ src/SocksProxySocket.h | 106 +++++++++++++++ 5 files changed, 331 insertions(+), 87 deletions(-) create mode 100644 src/SocksProxySocket.cc create mode 100644 src/SocksProxySocket.h diff --git a/src/DHTConnectionSocksProxyImpl.cc b/src/DHTConnectionSocksProxyImpl.cc index 4684e828..e8491e08 100644 --- a/src/DHTConnectionSocksProxyImpl.cc +++ b/src/DHTConnectionSocksProxyImpl.cc @@ -39,13 +39,12 @@ #include "SocketCore.h" #include "a2functional.h" +#include "SocksProxySocket.h" namespace aria2 { DHTConnectionSocksProxyImpl::DHTConnectionSocksProxyImpl(int family) - : DHTConnectionImpl(family), - family_(family), - proxySocket_(make_unique()) + : DHTConnectionImpl(family), family_(family) { } @@ -58,103 +57,38 @@ bool DHTConnectionSocksProxyImpl::startProxy(const std::string& host, const std::string& listenAddr, uint16_t listenPort) { - proxySocket_->establishConnection(host, port); - proxySocket_->setBlockingMode(); - Endpoint proxyEndpoint; - unsigned char resBuf[10]; + socket_ = make_unique(family_); + socket_->establish(host, port); // Authentication negotiation bool noAuth = user.empty() || passwd.empty(); if (noAuth) { - std::string req("\x05\x01\x00", 3); - size_t resLen = 2; - proxySocket_->writeData(req); - proxySocket_->readData(resBuf, resLen); - if (resBuf[1] != '\x00') { - proxySocket_->closeConnection(); + int authMethod = + socket_->negotiateAuth(std::vector{SOCKS_AUTH_NO_AUTH}); + if (authMethod < 0) { return false; } } else { - std::string req("\x05\x02\x00\x02", 4); - size_t resLen = 2; - proxySocket_->writeData(req); - Endpoint proxyEndpoint; - proxySocket_->readData(resBuf, resLen); - if (resBuf[1] != '\x02' && resBuf[1] != '\x00') { - proxySocket_->closeConnection(); + int authMethod = socket_->negotiateAuth( + std::vector{SOCKS_AUTH_NO_AUTH, SOCKS_AUTH_USERPASS}); + if (authMethod < 0) { return false; } - // Username/password authentication - if (resBuf[1] == '\x02') { - req = std::string("\x01", 1); - req.push_back(static_cast(user.length())); - req += user; - req.push_back(static_cast(passwd.length())); - req += passwd; - size_t resLen = 2; - proxySocket_->writeData(req); - proxySocket_->readData(resBuf, resLen); - if (resBuf[1] != '\x00') { - proxySocket_->closeConnection(); + // Username/Password authentication + if (authMethod == SOCKS_AUTH_USERPASS) { + int status = socket_->authByUserpass(user, passwd); + if (status != 0) { return false; } } } // UDP associate - std::string req("\x05\x03\x00", 3); - if (family_ == AF_INET) { - req.push_back('\x01'); - char addrBuf[10]; - net::getBinAddr(addrBuf, listenAddr); - req.append(addrBuf, 4); - } - else { - req.push_back('\x04'); - char addrBuf[20]; - net::getBinAddr(addrBuf, listenAddr); - req.append(addrBuf, 16); - } - uint16_t listenPortBuf = htons(listenPort); - req.append(reinterpret_cast(&listenPortBuf), 2); - size_t resLen = 5; - proxySocket_->writeData(req); - proxySocket_->readData(resBuf, resLen); - if (resBuf[1] != '\x00') { - proxySocket_->closeConnection(); - return false; - } - if (resBuf[3] == '\x01') { - unsigned char addrBuf[6]; - addrBuf[0] = resBuf[4]; - resLen = 5; - proxySocket_->readData(addrBuf + 1, resLen); - char addrStrBuf[20]; - inetNtop(AF_INET, addrBuf, addrStrBuf, 20); - bndAddr_ = std::string(addrStrBuf); - bndPort_ = ntohs(*reinterpret_cast(addrBuf + 4)); - } - else if (resBuf[3] == '\x04') { - unsigned char addrBuf[18]; - addrBuf[0] = resBuf[4]; - resLen = 17; - proxySocket_->readData(addrBuf + 1, resLen); - char addrStrBuf[50]; - inetNtop(AF_INET6, addrBuf, addrStrBuf, 50); - bndAddr_ = std::string(addrStrBuf); - bndPort_ = ntohs(*reinterpret_cast(addrBuf + 16)); - } - else if (resBuf[3] == '\x03') { - bndAddr_ = std::string(resBuf[4] + 2, '\x00'); - resLen = resBuf[4] + 2; - proxySocket_->readData(&bndAddr_[0], resLen); - bndPort_ = ntohs(*reinterpret_cast(&bndAddr_[0] + resBuf[4])); - bndAddr_.resize(resBuf[4]); - } - else { - proxySocket_->closeConnection(); + size_t i = + socket_->startUdpProxy(listenAddr, listenPort, &bndAddr_, &bndPort_); + if (i < 0) { return false; } return true; diff --git a/src/DHTConnectionSocksProxyImpl.h b/src/DHTConnectionSocksProxyImpl.h index d6f6d54c..cbb39dcc 100644 --- a/src/DHTConnectionSocksProxyImpl.h +++ b/src/DHTConnectionSocksProxyImpl.h @@ -35,17 +35,18 @@ #ifndef D_DHT_CONNECTION_SOCKS_PROXY_IMPL_H #define D_DHT_CONNECTION_SOCKS_PROXY_IMPL_H -#include "DHTConnectionImpl.h" - #include +#include "DHTConnectionImpl.h" +#include "SocksProxySocket.h" + namespace aria2 { class SocketCore; class DHTConnectionSocksProxyImpl : public DHTConnectionImpl { private: - std::unique_ptr proxySocket_; + std::unique_ptr socket_; int family_; std::string bndAddr_; uint16_t bndPort_; @@ -58,9 +59,10 @@ public: // Connect to SOCKS5 server to start UDP proxy. // Leave user and passwd empty to indicate no authentication. // Leave listen host and port empty / 0 to indicate no receiving from proxy. + // MUST call the method before any receiveMessage/sendMessage. bool startProxy(const std::string& host, uint16_t port, const std::string& user, const std::string& passwd, - const std::string& listenHost, uint16_t listenPort); + const std::string& listenAddr, uint16_t listenPort); virtual ssize_t receiveMessage(unsigned char* data, size_t len, std::string& host, diff --git a/src/Makefile.am b/src/Makefile.am index c352021a..c8d6f4f0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -231,6 +231,7 @@ SRCS = \ SocketBuffer.cc SocketBuffer.h\ SocketCore.cc SocketCore.h\ SocketRecvBuffer.cc SocketRecvBuffer.h\ + SocksProxySocket.cc SocksProxySocket.h\ SpeedCalc.cc SpeedCalc.h\ StatCalc.h\ StreamCheckIntegrityEntry.cc StreamCheckIntegrityEntry.h\ diff --git a/src/SocksProxySocket.cc b/src/SocksProxySocket.cc new file mode 100644 index 00000000..53e42481 --- /dev/null +++ b/src/SocksProxySocket.cc @@ -0,0 +1,201 @@ +/* */ +#include "SocksProxySocket.h" + +#include +#include + +#include "SocketCore.h" +#include "a2functional.h" + +namespace { +const char C_SOCKS_VER = '\x05'; +const char C_AUTH_NONE = '\xff'; +const char C_AUTH_USERPASS_VER = '\x01'; +const char C_AUTH_USERPASS_OK = '\x00'; +const char C_CMD_UDP_ASSOCIATE = '\x03'; +const char C_ADDR_INET = '\x01'; +const char C_ADDR_DOMAIN = '\x03'; +const char C_ADDR_INET6 = '\x04'; +const char C_OK = '\x00'; +} // namespace + +namespace aria2 { + +SocksProxySocket::SocksProxySocket(int family) : family_(family) {} + +SocksProxySocket::~SocksProxySocket() = default; + +void SocksProxySocket::establish(const std::string& host, uint16_t port) +{ + socket_ = make_unique(); + socket_->establishConnection(host, port); + socket_->setBlockingMode(); +} + +void SocksProxySocket::establish(std::unique_ptr socket) +{ + socket_ = std::move(socket); +} + +int SocksProxySocket::negotiateAuth(std::vector expected) +{ + std::stringstream req; + req << C_SOCKS_VER; + req << static_cast(expected.size()); + for (auto c : expected) { + req << c; + } + socket_->writeData(req.str()); + + char res[2]; + size_t resLen = sizeof(res); + socket_->readData(res, resLen); + int authMethod = res[1]; + if (authMethod == C_AUTH_NONE) { + socket_->closeConnection(); + return -1; + } + return authMethod; +} + +char SocksProxySocket::authByUserpass(const std::string& user, + const std::string& passwd) +{ + std::stringstream req; + req << C_AUTH_USERPASS_VER; + req << static_cast(user.length()) << user; + req << static_cast(passwd.length()) << passwd; + socket_->writeData(req.str()); + + char res[2]; + size_t resLen = sizeof(res); + socket_->readData(res, resLen); + if (res[1] != C_AUTH_USERPASS_OK) { + socket_->closeConnection(); + } + return res[1]; +} + +size_t SocksProxySocket::startUdpProxy(const std::string& listenAddr, + uint16_t listenPort, + std::string* bndAddrPtr, + uint16_t* bndPortPtr) +{ + std::stringstream req; + req << C_SOCKS_VER << C_CMD_UDP_ASSOCIATE << 0; + if (family_ == AF_INET) { + if (!listenAddr.empty()) { + char addrBuf[10]; + net::getBinAddr(addrBuf, listenAddr); + req << C_ADDR_INET << std::string(addrBuf, 4); + } + else { + req << std::string(4, '\x00'); + } + } + else { + if (!listenAddr.empty()) { + char addrBuf[20]; + net::getBinAddr(addrBuf, listenAddr); + req << C_ADDR_INET6 << std::string(addrBuf, 16); + } + else { + req << std::string(16, '\x00'); + } + } + if (listenPort) { + uint16_t listenPortBuf = htons(listenPort); + req << std::string(reinterpret_cast(&listenPortBuf), 2); + } + else { + req << std::string(2, '\x00'); + } + socket_->writeData(req.str()); + + char res[5]; + size_t resLen = sizeof(res); + socket_->readData(res, resLen); + if (res[1] != C_OK) { + socket_->closeConnection(); + return -1; + } + + std::string bndAddr; + uint16_t bndPort; + if (res[3] == C_ADDR_INET) { + char addrBuf[6]; + addrBuf[0] = res[4]; + size_t addrLen = sizeof(addrBuf) - 1; + socket_->readData(addrBuf + 1, addrLen); + char addrStrBuf[20]; + inetNtop(AF_INET, addrBuf, addrStrBuf, 20); + bndAddr = std::string(addrStrBuf); + bndPort = ntohs(*reinterpret_cast(addrBuf + 4)); + } + else if (res[3] == C_ADDR_INET6) { + char addrBuf[18]; + addrBuf[0] = res[4]; + size_t addrLen = sizeof(addrBuf) - 1; + socket_->readData(addrBuf + 1, addrLen); + char addrStrBuf[50]; + inetNtop(AF_INET6, addrBuf, addrStrBuf, 50); + bndAddr = std::string(addrStrBuf); + bndPort = ntohs(*reinterpret_cast(addrBuf + 16)); + } + else if (res[3] == C_ADDR_DOMAIN) { + // 2 more bytes to hold port temporarily. + bndAddr = std::string(res[4] + 2, 0); + resLen = res[4] + 2; + socket_->readData(&bndAddr[0], resLen); + bndPort = ntohs(*reinterpret_cast(&bndAddr[0] + res[4])); + bndAddr.resize(res[4]); + } + else { + socket_->closeConnection(); + return -1; + } + + size_t i = bndAddrs_.size(); + bndAddrs_.push_back(bndAddr); + bndPorts_.push_back(bndPort); + if (bndAddrPtr && bndPortPtr) { + *bndAddrPtr = bndAddr; + *bndPortPtr = bndPort; + } + return i; +} + +} // namespace aria2 diff --git a/src/SocksProxySocket.h b/src/SocksProxySocket.h new file mode 100644 index 00000000..1447d08b --- /dev/null +++ b/src/SocksProxySocket.h @@ -0,0 +1,106 @@ +/* */ +#ifndef D_SOCKS_PROXY_SOCKET_H +#define D_SOCKS_PROXY_SOCKET_H + +#include +#include +#include + +namespace aria2 { + +class SocketCore; + +enum SocksProxyAuthMethod { + // No authentication required + SOCKS_AUTH_NO_AUTH = 0, + // Username/Password authentication + SOCKS_AUTH_USERPASS = 2, +}; + +// Other than transferring proxy traffic, +// the class helps to start proxy connections. +class SocksProxySocket { +private: + int family_; + // The socket is not shared because as it is used as some kind of controller, + // when it is closed, all related proxy connections are closed. + std::unique_ptr socket_; + std::vector bndAddrs_; + std::vector bndPorts_; + +public: + SocksProxySocket(int family); + + virtual ~SocksProxySocket(); + + // Establish connection to SOCKS port. + void establish(const std::string& host, uint16_t port); + + // Customize the TCP socket for SOCKS protocol. + // Socket should be blocking to ensure latter steps finished in order. + void establish(std::unique_ptr socket); + + // Negotiate authentication that returns selected auth method in 0-255. + // When no auth method is selected, return -1. + // Auth methods in expected should be from enum SocksProxyAuthMethod, + // which has its auth handlers. + // Returned auth method SHOULD be in expected but it is not checked. + int negotiateAuth(std::vector expected); + + // Username/Password authentication. + // user and pass should not be empty. + // Returns status replied from proxy server. 0 is OK. + char authByUserpass(const std::string& user, const std::string& passwd); + + // Create an UDP association to start UDP proxy. + // Leave listen host and port empty / 0 to indicate no receiving from proxy. + // Returns -1 when error, otherwise the index to get the bnd addr and port. + // Set bndAddrPtr and bndPortPtr to directly get the result bnd addr and port. + size_t startUdpProxy(const std::string& listenAddr, uint16_t listenPort, + std::string* bndAddrPtr = nullptr, + uint16_t* bndPortPtr = nullptr); + + // Get bnd addr and port via index i. + // i is not checked and should be got from start*Proxy methods. + std::pair getBnd(size_t i) + { + return std::make_pair(bndAddrs_[i], bndPorts_[i]); + } +}; + +} // namespace aria2 + +#endif // D_SOCKS_PROXY_SOCKET_H From 9061677c0ea6af48a40c3fe028cc554341670cff Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 17 Dec 2021 08:06:56 +0800 Subject: [PATCH 09/13] Update start udp associate method Rename func according to RFC. Fix bad type of return. Update arg type to get bnd addr and port. --- src/DHTConnectionSocksProxyImpl.cc | 4 ++-- src/SocksProxySocket.cc | 16 ++++++++-------- src/SocksProxySocket.h | 5 ++--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/DHTConnectionSocksProxyImpl.cc b/src/DHTConnectionSocksProxyImpl.cc index e8491e08..16f69b9c 100644 --- a/src/DHTConnectionSocksProxyImpl.cc +++ b/src/DHTConnectionSocksProxyImpl.cc @@ -86,8 +86,8 @@ bool DHTConnectionSocksProxyImpl::startProxy(const std::string& host, } // UDP associate - size_t i = - socket_->startUdpProxy(listenAddr, listenPort, &bndAddr_, &bndPort_); + ssize_t i = socket_->startUdpAssociate(listenAddr, listenPort, + std::make_pair(&bndAddr_, &bndPort_)); if (i < 0) { return false; } diff --git a/src/SocksProxySocket.cc b/src/SocksProxySocket.cc index 53e42481..848a2757 100644 --- a/src/SocksProxySocket.cc +++ b/src/SocksProxySocket.cc @@ -109,10 +109,10 @@ char SocksProxySocket::authByUserpass(const std::string& user, return res[1]; } -size_t SocksProxySocket::startUdpProxy(const std::string& listenAddr, - uint16_t listenPort, - std::string* bndAddrPtr, - uint16_t* bndPortPtr) +ssize_t +SocksProxySocket::startUdpAssociate(const std::string& listenAddr, + uint16_t listenPort, + std::pair bnd) { std::stringstream req; req << C_SOCKS_VER << C_CMD_UDP_ASSOCIATE << 0; @@ -188,12 +188,12 @@ size_t SocksProxySocket::startUdpProxy(const std::string& listenAddr, return -1; } - size_t i = bndAddrs_.size(); + ssize_t i = static_cast(bndAddrs_.size()); bndAddrs_.push_back(bndAddr); bndPorts_.push_back(bndPort); - if (bndAddrPtr && bndPortPtr) { - *bndAddrPtr = bndAddr; - *bndPortPtr = bndPort; + if (bnd.first && bnd.second) { + *(bnd.first) = bndAddr; + *(bnd.second) = bndPort; } return i; } diff --git a/src/SocksProxySocket.h b/src/SocksProxySocket.h index 1447d08b..93854c9d 100644 --- a/src/SocksProxySocket.h +++ b/src/SocksProxySocket.h @@ -89,9 +89,8 @@ public: // Leave listen host and port empty / 0 to indicate no receiving from proxy. // Returns -1 when error, otherwise the index to get the bnd addr and port. // Set bndAddrPtr and bndPortPtr to directly get the result bnd addr and port. - size_t startUdpProxy(const std::string& listenAddr, uint16_t listenPort, - std::string* bndAddrPtr = nullptr, - uint16_t* bndPortPtr = nullptr); + ssize_t startUdpAssociate(const std::string& listenAddr, uint16_t listenPort, + std::pair bnd = {}); // Get bnd addr and port via index i. // i is not checked and should be got from start*Proxy methods. From ddabffd44351d9217d1d9f3f68128537b09ea8b8 Mon Sep 17 00:00:00 2001 From: myl7 Date: Sun, 26 Dec 2021 20:18:43 +0800 Subject: [PATCH 10/13] Fix char appending to sstream and res len counting --- src/SocksProxySocket.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SocksProxySocket.cc b/src/SocksProxySocket.cc index 848a2757..075d9ff1 100644 --- a/src/SocksProxySocket.cc +++ b/src/SocksProxySocket.cc @@ -115,7 +115,7 @@ SocksProxySocket::startUdpAssociate(const std::string& listenAddr, std::pair bnd) { std::stringstream req; - req << C_SOCKS_VER << C_CMD_UDP_ASSOCIATE << 0; + req << C_SOCKS_VER << C_CMD_UDP_ASSOCIATE << '\x00'; if (family_ == AF_INET) { if (!listenAddr.empty()) { char addrBuf[10]; @@ -177,8 +177,8 @@ SocksProxySocket::startUdpAssociate(const std::string& listenAddr, } else if (res[3] == C_ADDR_DOMAIN) { // 2 more bytes to hold port temporarily. - bndAddr = std::string(res[4] + 2, 0); - resLen = res[4] + 2; + size_t resLen = res[4] + 2; + bndAddr = std::string(resLen, '\x00'); socket_->readData(&bndAddr[0], resLen); bndPort = ntohs(*reinterpret_cast(&bndAddr[0] + res[4])); bndAddr.resize(res[4]); From 6ab8af1ef531601f6072ae03eff755ff5d6a49f4 Mon Sep 17 00:00:00 2001 From: myl7 Date: Tue, 28 Dec 2021 16:33:35 +0800 Subject: [PATCH 11/13] Refactor SOCKS socket code Separate sending cmd and receiving reply out as they are shared in procedures. Change returns of start* method. --- src/DHTConnectionSocksProxyImpl.cc | 14 ++--- src/SocksProxySocket.cc | 83 ++++++++++++++++++------------ src/SocksProxySocket.h | 34 ++++++------ 3 files changed, 75 insertions(+), 56 deletions(-) diff --git a/src/DHTConnectionSocksProxyImpl.cc b/src/DHTConnectionSocksProxyImpl.cc index 16f69b9c..45202c19 100644 --- a/src/DHTConnectionSocksProxyImpl.cc +++ b/src/DHTConnectionSocksProxyImpl.cc @@ -63,15 +63,15 @@ bool DHTConnectionSocksProxyImpl::startProxy(const std::string& host, // Authentication negotiation bool noAuth = user.empty() || passwd.empty(); if (noAuth) { - int authMethod = - socket_->negotiateAuth(std::vector{SOCKS_AUTH_NO_AUTH}); + int authMethod = socket_->negotiateAuth( + std::vector{SOCKS_AUTH_NO_AUTH}); if (authMethod < 0) { return false; } } else { - int authMethod = socket_->negotiateAuth( - std::vector{SOCKS_AUTH_NO_AUTH, SOCKS_AUTH_USERPASS}); + int authMethod = socket_->negotiateAuth(std::vector{ + SOCKS_AUTH_NO_AUTH, SOCKS_AUTH_USERPASS}); if (authMethod < 0) { return false; } @@ -86,9 +86,9 @@ bool DHTConnectionSocksProxyImpl::startProxy(const std::string& host, } // UDP associate - ssize_t i = socket_->startUdpAssociate(listenAddr, listenPort, - std::make_pair(&bndAddr_, &bndPort_)); - if (i < 0) { + int i = + socket_->startUdpAssociate(listenAddr, listenPort, bndAddr_, bndPort_); + if (i != 0) { return false; } return true; diff --git a/src/SocksProxySocket.cc b/src/SocksProxySocket.cc index 075d9ff1..ebb18b7e 100644 --- a/src/SocksProxySocket.cc +++ b/src/SocksProxySocket.cc @@ -45,15 +45,17 @@ const char C_SOCKS_VER = '\x05'; const char C_AUTH_NONE = '\xff'; const char C_AUTH_USERPASS_VER = '\x01'; const char C_AUTH_USERPASS_OK = '\x00'; -const char C_CMD_UDP_ASSOCIATE = '\x03'; -const char C_ADDR_INET = '\x01'; -const char C_ADDR_DOMAIN = '\x03'; -const char C_ADDR_INET6 = '\x04'; const char C_OK = '\x00'; } // namespace namespace aria2 { +enum SocksProxyAddrType { + SOCKS_ADDR_INET = 1, + SOCKS_ADDR_DOMAIN = 3, + SOCKS_ADDR_INET6 = 4, +}; + SocksProxySocket::SocksProxySocket(int family) : family_(family) {} SocksProxySocket::~SocksProxySocket() = default; @@ -70,13 +72,13 @@ void SocksProxySocket::establish(std::unique_ptr socket) socket_ = std::move(socket); } -int SocksProxySocket::negotiateAuth(std::vector expected) +int SocksProxySocket::negotiateAuth(std::vector expected) { std::stringstream req; req << C_SOCKS_VER; req << static_cast(expected.size()); for (auto c : expected) { - req << c; + req << static_cast(c); } socket_->writeData(req.str()); @@ -109,77 +111,81 @@ char SocksProxySocket::authByUserpass(const std::string& user, return res[1]; } -ssize_t -SocksProxySocket::startUdpAssociate(const std::string& listenAddr, - uint16_t listenPort, - std::pair bnd) +void SocksProxySocket::sendCmd(SocksProxyCmd cmd, const std::string& dstAddr, + uint16_t dstPort, bool allowEmpty) { std::stringstream req; - req << C_SOCKS_VER << C_CMD_UDP_ASSOCIATE << '\x00'; + req << C_SOCKS_VER << static_cast(cmd) << '\x00'; if (family_ == AF_INET) { - if (!listenAddr.empty()) { + if (!allowEmpty || !dstAddr.empty()) { char addrBuf[10]; - net::getBinAddr(addrBuf, listenAddr); - req << C_ADDR_INET << std::string(addrBuf, 4); + net::getBinAddr(addrBuf, dstAddr); + req << static_cast(SOCKS_ADDR_INET) << std::string(addrBuf, 4); } else { req << std::string(4, '\x00'); } } else { - if (!listenAddr.empty()) { + if (!allowEmpty || !dstAddr.empty()) { char addrBuf[20]; - net::getBinAddr(addrBuf, listenAddr); - req << C_ADDR_INET6 << std::string(addrBuf, 16); + net::getBinAddr(addrBuf, dstAddr); + req << static_cast(SOCKS_ADDR_INET6) << std::string(addrBuf, 16); } else { req << std::string(16, '\x00'); } } - if (listenPort) { - uint16_t listenPortBuf = htons(listenPort); + if (dstPort) { + uint16_t listenPortBuf = htons(dstPort); req << std::string(reinterpret_cast(&listenPortBuf), 2); } else { req << std::string(2, '\x00'); } socket_->writeData(req.str()); +} +int SocksProxySocket::receiveReply(int& bndFamily, std::string& bndAddr, + uint16_t& bndPort) +{ char res[5]; size_t resLen = sizeof(res); socket_->readData(res, resLen); - if (res[1] != C_OK) { + int rep = res[1]; + if (rep != C_OK) { socket_->closeConnection(); - return -1; + return rep; } - std::string bndAddr; - uint16_t bndPort; - if (res[3] == C_ADDR_INET) { + if (res[3] == SOCKS_ADDR_INET) { char addrBuf[6]; addrBuf[0] = res[4]; size_t addrLen = sizeof(addrBuf) - 1; socket_->readData(addrBuf + 1, addrLen); char addrStrBuf[20]; inetNtop(AF_INET, addrBuf, addrStrBuf, 20); + bndFamily = AF_INET; bndAddr = std::string(addrStrBuf); bndPort = ntohs(*reinterpret_cast(addrBuf + 4)); } - else if (res[3] == C_ADDR_INET6) { + else if (res[3] == SOCKS_ADDR_INET6) { char addrBuf[18]; addrBuf[0] = res[4]; size_t addrLen = sizeof(addrBuf) - 1; socket_->readData(addrBuf + 1, addrLen); char addrStrBuf[50]; inetNtop(AF_INET6, addrBuf, addrStrBuf, 50); + bndFamily = AF_INET6; bndAddr = std::string(addrStrBuf); bndPort = ntohs(*reinterpret_cast(addrBuf + 16)); } - else if (res[3] == C_ADDR_DOMAIN) { + else if (res[3] == SOCKS_ADDR_DOMAIN) { // 2 more bytes to hold port temporarily. size_t resLen = res[4] + 2; bndAddr = std::string(resLen, '\x00'); socket_->readData(&bndAddr[0], resLen); + bndFamily = AF_INET + AF_INET6; bndPort = ntohs(*reinterpret_cast(&bndAddr[0] + res[4])); bndAddr.resize(res[4]); } @@ -187,15 +193,26 @@ SocksProxySocket::startUdpAssociate(const std::string& listenAddr, socket_->closeConnection(); return -1; } + return rep; +} - ssize_t i = static_cast(bndAddrs_.size()); - bndAddrs_.push_back(bndAddr); - bndPorts_.push_back(bndPort); - if (bnd.first && bnd.second) { - *(bnd.first) = bndAddr; - *(bnd.second) = bndPort; +int SocksProxySocket::startUdpAssociate(const std::string& listenAddr, + uint16_t listenPort, + std::string& bndAddr, uint16_t& bndPort) +{ + sendCmd(SOCKS_CMD_UDP_ASSOCIATE, listenAddr, listenPort, true); + + int bFamily; + std::string bAddr; + uint16_t bPort; + int rep = receiveReply(bFamily, bAddr, bPort); + if (rep != C_OK) { + return rep; } - return i; + + bndAddr = bAddr; + bndPort = bPort; + return rep; } } // namespace aria2 diff --git a/src/SocksProxySocket.h b/src/SocksProxySocket.h index 93854c9d..f0979b45 100644 --- a/src/SocksProxySocket.h +++ b/src/SocksProxySocket.h @@ -58,8 +58,20 @@ private: // The socket is not shared because as it is used as some kind of controller, // when it is closed, all related proxy connections are closed. std::unique_ptr socket_; - std::vector bndAddrs_; - std::vector bndPorts_; + + enum SocksProxyCmd { + SOCKS_CMD_CONNECT = 1, + SOCKS_CMD_BIND = 2, + SOCKS_CMD_UDP_ASSOCIATE = 3, + }; + + // When allowEmtpy is true, allow dst* to be empty. + void sendCmd(SocksProxyCmd cmd, const std::string& dstAddr, uint16_t dstPort, + bool allowEmpty); + // Returns status replied from proxy server. 0 is OK. > 0 with error messages. + // < 0 for invalid replies from server. + // bndFamily may be AF_INET + AF_INET6 to indicate domain format. + int receiveReply(int& bndFamily, std::string& bndAddr, uint16_t& bndPort); public: SocksProxySocket(int family); @@ -75,10 +87,8 @@ public: // Negotiate authentication that returns selected auth method in 0-255. // When no auth method is selected, return -1. - // Auth methods in expected should be from enum SocksProxyAuthMethod, - // which has its auth handlers. // Returned auth method SHOULD be in expected but it is not checked. - int negotiateAuth(std::vector expected); + int negotiateAuth(std::vector expected); // Username/Password authentication. // user and pass should not be empty. @@ -87,17 +97,9 @@ public: // Create an UDP association to start UDP proxy. // Leave listen host and port empty / 0 to indicate no receiving from proxy. - // Returns -1 when error, otherwise the index to get the bnd addr and port. - // Set bndAddrPtr and bndPortPtr to directly get the result bnd addr and port. - ssize_t startUdpAssociate(const std::string& listenAddr, uint16_t listenPort, - std::pair bnd = {}); - - // Get bnd addr and port via index i. - // i is not checked and should be got from start*Proxy methods. - std::pair getBnd(size_t i) - { - return std::make_pair(bndAddrs_[i], bndPorts_[i]); - } + // Returns status replied from proxy server. 0 is OK. + int startUdpAssociate(const std::string& listenAddr, uint16_t listenPort, + std::string& bndAddr, uint16_t& bndPort); }; } // namespace aria2 From afb45ee0bc2145d408e30034c728411e5d83cde8 Mon Sep 17 00:00:00 2001 From: myl7 Date: Tue, 28 Dec 2021 17:15:08 +0800 Subject: [PATCH 12/13] Add auth helper for no auth or userpass auth --- src/DHTConnectionSocksProxyImpl.cc | 25 +++--------------------- src/SocksProxySocket.cc | 31 ++++++++++++++++++++++++++++-- src/SocksProxySocket.h | 7 ++++++- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/DHTConnectionSocksProxyImpl.cc b/src/DHTConnectionSocksProxyImpl.cc index 45202c19..f7dd7e7c 100644 --- a/src/DHTConnectionSocksProxyImpl.cc +++ b/src/DHTConnectionSocksProxyImpl.cc @@ -61,28 +61,9 @@ bool DHTConnectionSocksProxyImpl::startProxy(const std::string& host, socket_->establish(host, port); // Authentication negotiation - bool noAuth = user.empty() || passwd.empty(); - if (noAuth) { - int authMethod = socket_->negotiateAuth( - std::vector{SOCKS_AUTH_NO_AUTH}); - if (authMethod < 0) { - return false; - } - } - else { - int authMethod = socket_->negotiateAuth(std::vector{ - SOCKS_AUTH_NO_AUTH, SOCKS_AUTH_USERPASS}); - if (authMethod < 0) { - return false; - } - - // Username/Password authentication - if (authMethod == SOCKS_AUTH_USERPASS) { - int status = socket_->authByUserpass(user, passwd); - if (status != 0) { - return false; - } - } + bool res = socket_->authByUserpassOrNone(user, passwd); + if (!res) { + return false; } // UDP associate diff --git a/src/SocksProxySocket.cc b/src/SocksProxySocket.cc index ebb18b7e..5f4d6270 100644 --- a/src/SocksProxySocket.cc +++ b/src/SocksProxySocket.cc @@ -93,8 +93,8 @@ int SocksProxySocket::negotiateAuth(std::vector expected) return authMethod; } -char SocksProxySocket::authByUserpass(const std::string& user, - const std::string& passwd) +int SocksProxySocket::authByUserpass(const std::string& user, + const std::string& passwd) { std::stringstream req; req << C_AUTH_USERPASS_VER; @@ -111,6 +111,33 @@ char SocksProxySocket::authByUserpass(const std::string& user, return res[1]; } +bool SocksProxySocket::authByUserpassOrNone(const std::string& user, + const std::string& passwd) +{ + bool noAuth = user.empty() || passwd.empty(); + if (noAuth) { + int authMethod = + negotiateAuth(std::vector{SOCKS_AUTH_NO_AUTH}); + if (authMethod < 0) { + return false; + } + } + else { + int authMethod = negotiateAuth(std::vector{ + SOCKS_AUTH_NO_AUTH, SOCKS_AUTH_USERPASS}); + if (authMethod < 0) { + return false; + } + if (authMethod == SOCKS_AUTH_USERPASS) { + int status = authByUserpass(user, passwd); + if (status != 0) { + return false; + } + } + } + return true; +} + void SocksProxySocket::sendCmd(SocksProxyCmd cmd, const std::string& dstAddr, uint16_t dstPort, bool allowEmpty) { diff --git a/src/SocksProxySocket.h b/src/SocksProxySocket.h index f0979b45..0b603950 100644 --- a/src/SocksProxySocket.h +++ b/src/SocksProxySocket.h @@ -93,7 +93,12 @@ public: // Username/Password authentication. // user and pass should not be empty. // Returns status replied from proxy server. 0 is OK. - char authByUserpass(const std::string& user, const std::string& passwd); + int authByUserpass(const std::string& user, const std::string& passwd); + + // Helper to negotiate and auth by username/password authentication, or skip + // it if not required. + // Leave either user or pass empty to force no authentication. + bool authByUserpassOrNone(const std::string& user, const std::string& passwd); // Create an UDP association to start UDP proxy. // Leave listen host and port empty / 0 to indicate no receiving from proxy. From 1a2d31fa37504fb113597f5a2ed51faafd4b4728 Mon Sep 17 00:00:00 2001 From: myl7 Date: Fri, 14 Jun 2024 17:48:45 +0800 Subject: [PATCH 13/13] Fix option description of --bt-udp-socks-proxy-user/passwd --- src/usage_text.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usage_text.h b/src/usage_text.h index 90891cd0..7f213ab3 100644 --- a/src/usage_text.h +++ b/src/usage_text.h @@ -697,9 +697,9 @@ #define TEXT_FTP_PROXY_PASSWD \ _(" --ftp-proxy-passwd=PASSWD Set password for --ftp-proxy.") #define TEXT_BT_UDP_SOCKS_PROXY_USER \ - _(" --bt-udp-socks-proxy-user=USER Set user for --http-proxy.") + _(" --bt-udp-socks-proxy-user=USER Set user for --bt-udp-socks-proxy.") #define TEXT_BT_UDP_SOCKS_PROXY_PASSWD \ - _(" --bt-udp-socks-proxy-passwd=PASSWD Set password for --http-proxy.") + _(" --bt-udp-socks-proxy-passwd=PASSWD Set password for --bt-udp-socks-proxy.") #define TEXT_REMOVE_CONTROL_FILE \ _(" --remove-control-file[=true|false] Remove control file before download. Using\n" \ " with --allow-overwrite=true, download always\n" \