From bc4d53c71f88b236bbc0bd2e75f9822fe1fd5f14 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 20 Jan 2018 15:56:45 +0100 Subject: [PATCH] restore support for incoming connections over SOCKS5 (disabled by default) --- ChangeLog | 1 + include/libtorrent/aux_/session_impl.hpp | 12 ++ include/libtorrent/settings_pack.hpp | 6 + include/libtorrent/socks5_stream.hpp | 60 ++++++++- simulation/fake_peer.hpp | 7 + simulation/test_socks5.cpp | 157 ++++++++++++++++++++++- simulation/test_transfer.cpp | 28 +++- src/session_impl.cpp | 131 +++++++++++++++++++ src/settings_pack.cpp | 1 + src/socks5_stream.cpp | 93 +++++++++++++- 10 files changed, 488 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index b50156139..b5391b61d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,5 @@ + * restore support for incoming connections over SOCKS5 (disabled by default) * use unique peer_ids per connection * fix iOS build on recent SDK * fix tracker connection bind issue for IPv6 trackers diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 3d60ce92f..be8f7079d 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -239,6 +239,10 @@ namespace libtorrent void async_accept(boost::shared_ptr const& listener, bool ssl); void on_accept_connection(boost::shared_ptr const& s , boost::weak_ptr listener, error_code const& e, bool ssl); + void on_socks_listen(boost::shared_ptr const& s + , error_code const& e); + void on_socks_accept(boost::shared_ptr const& s + , error_code const& e); void incoming_connection(boost::shared_ptr const& s); @@ -649,6 +653,7 @@ namespace libtorrent void update_listen_interfaces(); void update_privileged_ports(); void update_auto_sequential(); + void update_incoming_socks5(); void update_max_failcount(); void update_close_file_interval(); @@ -886,9 +891,16 @@ namespace libtorrent void ssl_handshake(error_code const& ec, boost::shared_ptr s); #endif + // when as a socks proxy is used for peers, also + // listen for incoming connections on a socks connection + boost::shared_ptr m_socks_listen_socket; + boost::uint16_t m_socks_listen_port; + // round-robin index into m_net_interfaces mutable boost::uint8_t m_interface_index; + void open_new_incoming_socks_connection(); + enum listen_on_flags_t { open_ssl_socket = 0x10 diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp index 30657d2c9..0a08e3aa5 100644 --- a/include/libtorrent/settings_pack.hpp +++ b/include/libtorrent/settings_pack.hpp @@ -696,6 +696,12 @@ namespace libtorrent // any. proxy_tracker_connections, + // this enables a SOCKS5 "extension", in which libtorrent attempts to + // receive incoming connections over a SOCKS5 proxy. The traditional + // interpretation of the RFC and normal SOCKS5 implementations do not + // support this. + incoming_socks5_connections, + max_bool_setting_internal }; diff --git a/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp index e3979b350..fb84f9603 100644 --- a/include/libtorrent/socks5_stream.hpp +++ b/include/libtorrent/socks5_stream.hpp @@ -90,6 +90,7 @@ public: // commands enum { socks5_connect = 1, + socks5_bind = 2, socks5_udp_associate = 3 }; @@ -97,6 +98,7 @@ public: : proxy_base(io_service) , m_version(5) , m_command(socks5_connect) + , m_listen(0) {} void set_version(int v) { m_version = v; } @@ -114,6 +116,40 @@ public: m_password = password; } + template + void async_accept(Handler const& handler) + { + TORRENT_ASSERT(m_listen == 1); + TORRENT_ASSERT(m_command == socks5_bind); + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + error_code e; +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::connect1"); +#endif + connect1(e, boost::make_shared(handler)); + } + + template + void async_listen(tcp::endpoint const& ep, Handler const& handler) + { + m_command = socks5_bind; + + m_remote_endpoint = ep; + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("socks5_stream::name_lookup"); +#endif + tcp::resolver::query q(m_hostname, to_string(m_port).elems); + m_resolver.async_resolve(q, boost::bind( + &socks5_stream::name_lookup, this, _1, _2, h)); + } + void set_dst_name(std::string const& host) { // if this assert trips, set_dst_name() is called wth an IP address rather @@ -139,12 +175,25 @@ public: } #endif +#ifndef BOOST_NO_EXCEPTIONS + endpoint_type local_endpoint() const + { + return m_local_endpoint; + } +#endif + + endpoint_type local_endpoint(error_code&) const + { + return m_local_endpoint; + } + // TODO: 2 add async_connect() that takes a hostname and port as well template void async_connect(endpoint_type const& endpoint, Handler const& handler) { // make sure we don't try to connect to INADDR_ANY. binding is fine, // and using a hostname is fine on SOCKS version 5. + TORRENT_ASSERT(m_command != socks5_bind); TORRENT_ASSERT(endpoint.address() != address() || (!m_dst_name.empty() && m_version == 5)); @@ -192,10 +241,19 @@ private: std::string m_password; std::string m_dst_name; + // when listening via a socks proxy, this is the IP and port our listen + // socket bound to + endpoint_type m_local_endpoint; + int m_version; - // the socks command to send for this connection (connect or udp associate) + // the socks command to send for this connection (connect, bind, + // udp associate) int m_command; + + // set to one when we're waiting for the + // second message to accept an incoming connection + int m_listen; }; } diff --git a/simulation/fake_peer.hpp b/simulation/fake_peer.hpp index c417b8e61..258862fdd 100644 --- a/simulation/fake_peer.hpp +++ b/simulation/fake_peer.hpp @@ -130,6 +130,13 @@ private: { using namespace std::placeholders; + if (ec) + { + printf("fake_peer::connect: (%d) %s\n" + , ec.value(), ec.message().c_str()); + return; + } + asio::ip::tcp::endpoint const ep = m_out_socket.remote_endpoint(); printf("fake_peer::connect (%s) -> (%d) %s\n" , lt::print_endpoint(ep).c_str(), ec.value() diff --git a/simulation/test_socks5.cpp b/simulation/test_socks5.cpp index 7bbc87585..80d90574e 100644 --- a/simulation/test_socks5.cpp +++ b/simulation/test_socks5.cpp @@ -108,6 +108,155 @@ void run_test(Setup const& setup test(sim, *ses, params.ti); } +TORRENT_TEST(socks5_tcp_accept) +{ + using namespace libtorrent; + bool incoming_connection = false; + run_test( + [](lt::session& ses) + { + settings_pack p; + p.set_bool(settings_pack::incoming_socks5_connections, true); + ses.apply_settings(p); + set_proxy(ses, settings_pack::socks5); + }, + [&](lt::session& ses, lt::alert const* alert) { + if (auto* a = lt::alert_cast(alert)) + { + TEST_EQUAL(a->socket_type, 2); + incoming_connection = true; + } + }, + [](sim::simulation& sim, lt::session& ses + , boost::shared_ptr ti) + { + // test connecting to the client via its socks5 listen port + // TODO: maybe we could use peer_conn here instead + fake_peer peer1(sim, "60.0.0.0"); + fake_peer peer2(sim, "60.0.0.1"); + + sim::timer t1(sim, lt::seconds(2), [&](boost::system::error_code const& ec) + { + peer1.connect_to(tcp::endpoint(addr("50.50.50.50"), 6881), ti->info_hash()); + }); + + sim::timer t2(sim, lt::seconds(3), [&](boost::system::error_code const& ec) + { + peer2.connect_to(tcp::endpoint(addr("50.50.50.50"), 6881), ti->info_hash()); + }); + + sim.run(); + } + ); + + TEST_EQUAL(incoming_connection, true); +} + +TORRENT_TEST(socks4_tcp_accept) +{ + using namespace libtorrent; + bool incoming_connection = false; + run_test( + [](lt::session& ses) + { + settings_pack p; + p.set_bool(settings_pack::incoming_socks5_connections, true); + ses.apply_settings(p); + set_proxy(ses, settings_pack::socks4); + }, + [&](lt::session& ses, lt::alert const* alert) { + if (auto* a = lt::alert_cast(alert)) + { + TEST_EQUAL(a->socket_type, 2); + TEST_EQUAL(a->ip.address(), addr("60.0.0.0")) + incoming_connection = true; + } + }, + [](sim::simulation& sim, lt::session& ses + , boost::shared_ptr ti) + { + fake_peer peer1(sim, "60.0.0.0"); + + sim::timer t1(sim, lt::seconds(2), [&](boost::system::error_code const& ec) + { + peer1.connect_to(tcp::endpoint(addr("50.50.50.50"), 6881), ti->info_hash()); + }); + + sim.run(); + } + ); + + TEST_EQUAL(incoming_connection, true); +} + +// make sure a listen_succeeded_alert is issued when successfully listening on +// incoming connections via a socks5 proxy +TORRENT_TEST(socks4_tcp_listen_alert) +{ + using namespace libtorrent; + bool listen_alert = false; + run_test( + [](lt::session& ses) + { + settings_pack p; + p.set_bool(settings_pack::incoming_socks5_connections, true); + ses.apply_settings(p); + set_proxy(ses, settings_pack::socks4); + }, + [&](lt::session& ses, lt::alert const* alert) { + if (auto* a = lt::alert_cast(alert)) + { + if (a->sock_type == listen_succeeded_alert::socks5) + { + TEST_EQUAL(a->endpoint.address(), addr("50.50.50.50")); + TEST_EQUAL(a->endpoint.port(), 6881); + listen_alert = true; + } + } + }, + [](sim::simulation& sim, lt::session& ses + , boost::shared_ptr ti) + { + sim.run(); + } + ); + + TEST_EQUAL(listen_alert, true); +} + +TORRENT_TEST(socks5_tcp_listen_alert) +{ + using namespace libtorrent; + bool listen_alert = false; + run_test( + [](lt::session& ses) + { + settings_pack p; + p.set_bool(settings_pack::incoming_socks5_connections, true); + ses.apply_settings(p); + set_proxy(ses, settings_pack::socks5); + }, + [&](lt::session& ses, lt::alert const* alert) { + if (auto* a = lt::alert_cast(alert)) + { + if (a->sock_type == listen_succeeded_alert::socks5) + { + TEST_EQUAL(a->endpoint.address(), addr("50.50.50.50")); + TEST_EQUAL(a->endpoint.port(), 6881); + listen_alert = true; + } + } + }, + [](sim::simulation& sim, lt::session& ses + , boost::shared_ptr ti) + { + sim.run(); + } + ); + + TEST_EQUAL(listen_alert, true); +} + TORRENT_TEST(socks5_tcp_announce) { using namespace libtorrent; @@ -116,6 +265,9 @@ TORRENT_TEST(socks5_tcp_announce) run_test( [](lt::session& ses) { + settings_pack p; + p.set_bool(settings_pack::incoming_socks5_connections, true); + ses.apply_settings(p); set_proxy(ses, settings_pack::socks5); lt::add_torrent_params params; @@ -127,7 +279,7 @@ TORRENT_TEST(socks5_tcp_announce) [&alert_port](lt::session& ses, lt::alert const* alert) { if (auto* a = lt::alert_cast(alert)) { - if (a->sock_type == listen_succeeded_alert::udp) + if (a->sock_type == listen_succeeded_alert::socks5) { alert_port = a->endpoint.port(); } @@ -161,8 +313,7 @@ TORRENT_TEST(socks5_tcp_announce) } ); - // since force_proxy is enabled, don't send the port - TEST_EQUAL(tracker_port, 0); + TEST_EQUAL(alert_port, tracker_port); TEST_CHECK(alert_port != -1); TEST_CHECK(tracker_port != -1); } diff --git a/simulation/test_transfer.cpp b/simulation/test_transfer.cpp index 566b777e6..2e4b4473d 100644 --- a/simulation/test_transfer.cpp +++ b/simulation/test_transfer.cpp @@ -51,6 +51,8 @@ using namespace sim; namespace lt = libtorrent; +const int connect_socks = 2; + template void run_test( Setup const& setup @@ -110,7 +112,8 @@ void run_test( print_alerts(*ses[0], [=](lt::session& ses, lt::alert const* a) { if (auto ta = alert_cast(a)) { - ta->handle.connect_peer(lt::tcp::endpoint(peer1, 6881)); + ta->handle.connect_peer(lt::tcp::endpoint( + (flags & connect_socks) ? proxy : peer1, 6881)); } on_alert(ses, a); }); @@ -176,6 +179,29 @@ TORRENT_TEST(socks5_tcp_connect) ); } +TORRENT_TEST(socks5_tcp_accept) +{ + using namespace libtorrent; + run_test( + [](lt::session& ses0, lt::session& ses1) + { + // this time, the session accepting the connection is listening on a + // socks5 BIND session + settings_pack p; + p.set_bool(settings_pack::incoming_socks5_connections, true); + ses1.apply_settings(p); + set_proxy(ses1, settings_pack::socks5); + filter_ips(ses0); + }, + [](lt::session& ses, lt::alert const* alert) {}, + [](std::shared_ptr ses[2]) { + TEST_EQUAL(is_seed(*ses[0]), true); + }, + connect_socks + ); +} + + TORRENT_TEST(encryption_tcp) { using namespace libtorrent; diff --git a/src/session_impl.cpp b/src/session_impl.cpp index d12df4698..cac6a61b4 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -376,6 +376,7 @@ namespace aux { #if TORRENT_USE_I2P , m_i2p_conn(m_io_service) #endif + , m_socks_listen_port(0) , m_interface_index(0) , m_unchoke_time_scaler(0) , m_auto_manage_time_scaler(0) @@ -1096,6 +1097,12 @@ namespace aux { TORRENT_ASSERT(!ec); } m_listen_sockets.clear(); + if (m_socks_listen_socket && m_socks_listen_socket->is_open()) + { + m_socks_listen_socket->close(ec); + TORRENT_ASSERT(!ec); + } + m_socks_listen_socket.reset(); #if TORRENT_USE_I2P if (m_i2p_listen_socket && m_i2p_listen_socket->is_open()) @@ -2232,6 +2239,7 @@ retry: , end(m_listen_sockets.end()); i != end; ++i) async_accept(i->sock, i->ssl); + open_new_incoming_socks_connection(); #if TORRENT_USE_I2P open_new_incoming_i2p_connection(); #endif @@ -2279,6 +2287,98 @@ retry: } } + void session_impl::open_new_incoming_socks_connection() + { + int const proxy_type = m_settings.get_int(settings_pack::proxy_type); + + if (proxy_type != settings_pack::socks5 + && proxy_type != settings_pack::socks5_pw + && proxy_type != settings_pack::socks4) + return; + + if (!m_settings.get_bool(settings_pack::incoming_socks5_connections)) + return; + + if (m_socks_listen_socket) return; + + m_socks_listen_socket = boost::make_shared(boost::ref(m_io_service)); + bool const ret = instantiate_connection(m_io_service, proxy() + , *m_socks_listen_socket, NULL, NULL, false, false); + TORRENT_ASSERT_VAL(ret, ret); + TORRENT_UNUSED(ret); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_socks_listen"); +#endif + socks5_stream& s = *m_socks_listen_socket->get(); + + m_socks_listen_port = m_listen_interface.port(); + if (m_socks_listen_port == 0) m_socks_listen_port = 2000 + random() % 60000; + s.async_listen(tcp::endpoint(address_v4::any(), m_socks_listen_port) + , boost::bind(&session_impl::on_socks_listen, this + , m_socks_listen_socket, _1)); + } + + void session_impl::on_socks_listen(boost::shared_ptr const& sock + , error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_socks_listen"); +#endif + + TORRENT_ASSERT(sock == m_socks_listen_socket || !m_socks_listen_socket); + + if (e) + { + m_socks_listen_socket.reset(); + if (e == boost::asio::error::operation_aborted) return; + if (m_alerts.should_post()) + m_alerts.emplace_alert("socks5" + , -1, listen_failed_alert::accept, e + , listen_failed_alert::socks5); + return; + } + + error_code ec; + tcp::endpoint ep = sock->local_endpoint(ec); + TORRENT_ASSERT(!ec); + TORRENT_UNUSED(ec); + + m_socks_listen_port = ep.port(); + + if (m_alerts.should_post()) + m_alerts.emplace_alert( + ep, listen_succeeded_alert::socks5); + +#if defined TORRENT_ASIO_DEBUGGING + add_outstanding_async("session_impl::on_socks_accept"); +#endif + socks5_stream& s = *m_socks_listen_socket->get(); + s.async_accept(boost::bind(&session_impl::on_socks_accept, this + , m_socks_listen_socket, _1)); + } + + void session_impl::on_socks_accept(boost::shared_ptr const& s + , error_code const& e) + { +#if defined TORRENT_ASIO_DEBUGGING + complete_async("session_impl::on_socks_accept"); +#endif + TORRENT_ASSERT(s == m_socks_listen_socket || !m_socks_listen_socket); + m_socks_listen_socket.reset(); + if (e == boost::asio::error::operation_aborted) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.emplace_alert("socks5" + , -1, listen_failed_alert::accept, e + , listen_failed_alert::socks5); + return; + } + open_new_incoming_socks_connection(); + incoming_connection(s); + } + void session_impl::update_i2p_bridge() { // we need this socket to be open before we @@ -5381,6 +5481,22 @@ retry: i->second->update_auto_sequential(); } + void session_impl::update_incoming_socks5() + { + if (!m_settings.get_bool(settings_pack::incoming_socks5_connections)) + { + if (m_socks_listen_socket) + { + m_socks_listen_socket->close(); + m_socks_listen_socket.reset(); + } + } + else + { + open_new_incoming_socks_connection(); + } + } + void session_impl::update_max_failcount() { for (torrent_map::iterator i = m_torrents.begin() @@ -5405,6 +5521,9 @@ retry: void session_impl::update_proxy() { + // in case we just set a socks proxy, we might have to + // open the socks incoming connection + if (!m_socks_listen_socket) open_new_incoming_socks_connection(); m_udp_socket.set_proxy_settings(proxy()); #ifdef TORRENT_USE_OPENSSL @@ -5489,6 +5608,12 @@ retry: boost::uint16_t session_impl::listen_port() const { + // if peer connections are set up to be received over a socks + // proxy, and it's the same one as we're using for the tracker + // just tell the tracker the socks5 port we're listening on + if (m_socks_listen_socket && m_socks_listen_socket->is_open()) + return m_socks_listen_port; + // if not, don't tell the tracker anything if we're in force_proxy // mode. We don't want to leak our listen port since it can // potentially identify us if it is leaked elsewere @@ -5500,6 +5625,12 @@ retry: boost::uint16_t session_impl::ssl_listen_port() const { #ifdef TORRENT_USE_OPENSSL + // if peer connections are set up to be received over a socks + // proxy, and it's the same one as we're using for the tracker + // just tell the tracker the socks5 port we're listening on + if (m_socks_listen_socket && m_socks_listen_socket->is_open()) + return m_socks_listen_port; + // if not, don't tell the tracker anything if we're in force_proxy // mode. We don't want to leak our listen port since it can // potentially identify us if it is leaked elsewere diff --git a/src/settings_pack.cpp b/src/settings_pack.cpp index e0a95b9e1..f34b17919 100644 --- a/src/settings_pack.cpp +++ b/src/settings_pack.cpp @@ -223,6 +223,7 @@ namespace libtorrent SET_NOPREV(proxy_peer_connections, true, 0), SET_NOPREV(auto_sequential, true, &session_impl::update_auto_sequential), SET_NOPREV(proxy_tracker_connections, true, 0), + SET_NOPREV(incoming_socks5_connections, false, &session_impl::update_incoming_socks5), }; int_setting_entry_t int_settings[settings_pack::num_int_settings] = diff --git a/src/socks5_stream.cpp b/src/socks5_stream.cpp index eaf0a12c7..71980cc97 100644 --- a/src/socks5_stream.cpp +++ b/src/socks5_stream.cpp @@ -84,6 +84,56 @@ namespace libtorrent { return socks_category(); } #endif + namespace + { + // parse out the endpoint from a SOCKS response + tcp::endpoint parse_endpoint(std::vector const& buffer + , int const version) + { + using namespace libtorrent::detail; + char const* p = &buffer[0]; + p += 2; // version & response code + if (version == 5) + { + ++p; // reserved byte + int const atyp = read_uint8(p); + + if (atyp == 1) + { + tcp::endpoint ret; + ret.address(read_v4_address(p)); + ret.port(read_uint16(p)); + return ret; + } + else if (atyp == 3) + { + // we don't support resolving the endpoint address + // if we receive a domain name, just set the remote + // endpoint to INADDR_ANY + return tcp::endpoint(); + } + else if (atyp == 4) + { + tcp::endpoint ret; +#if TORRENT_USE_IPV6 + ret.address(read_v6_address(p)); + ret.port(read_uint16(p)); +#endif + return ret; + } + } + else if (version == 4) + { + tcp::endpoint ret; + ret.port(read_uint16(p)); + ret.address(read_v4_address(p)); + return ret; + } + TORRENT_ASSERT(false); + return tcp::endpoint(); + } + } + void socks5_stream::name_lookup(error_code const& e, tcp::resolver::iterator i , boost::shared_ptr h) { @@ -275,7 +325,7 @@ namespace libtorrent :(m_remote_endpoint.address().is_v4()?4:16))); char* p = &m_buffer[0]; write_uint8(5, p); // SOCKS VERSION 5 - write_uint8(m_command, p); // CONNECT command + write_uint8(m_command, p); // CONNECT/BIND command write_uint8(0, p); // reserved if (!m_dst_name.empty()) { @@ -288,7 +338,8 @@ namespace libtorrent else { // we either need a hostname or a valid endpoint - TORRENT_ASSERT(m_remote_endpoint.address() != address()); + TORRENT_ASSERT(m_command == socks5_bind + || m_remote_endpoint.address() != address()); write_uint8(m_remote_endpoint.address().is_v4()?1:4, p); // address type write_address(m_remote_endpoint.address(), p); @@ -306,7 +357,7 @@ namespace libtorrent m_buffer.resize(m_user.size() + 9); char* p = &m_buffer[0]; write_uint8(4, p); // SOCKS VERSION 4 - write_uint8(m_command, p); // CONNECT command + write_uint8(m_command, p); // CONNECT/BIND command write_uint16(m_remote_endpoint.port(), p); write_uint32(m_remote_endpoint.address().to_v4().to_ulong(), p); std::copy(m_user.begin(), m_user.end(), p); @@ -387,6 +438,18 @@ namespace libtorrent // on address type) if (atyp == 1) { + if (m_command == socks5_bind) + { + if (m_listen == 0) + { + m_local_endpoint = parse_endpoint(m_buffer, m_version); + m_listen = 1; + } + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } + } std::vector().swap(m_buffer); (*h)(e); return; @@ -427,6 +490,18 @@ namespace libtorrent // access granted if (response == 90) { + if (m_command == socks5_bind) + { + if (m_listen == 0) + { + m_local_endpoint = parse_endpoint(m_buffer, m_version); + m_listen = 1; + } + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } + } std::vector().swap(m_buffer); (*h)(e); return; @@ -452,6 +527,18 @@ namespace libtorrent if (handle_error(e, h)) return; + if (m_command == socks5_bind) + { + if (m_listen == 0) + { + m_local_endpoint = parse_endpoint(m_buffer, m_version); + m_listen = 1; + } + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } + } std::vector().swap(m_buffer); (*h)(e); }