From f9bc6dbc54b13cc3fa0391525486262d7275f757 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Thu, 5 May 2016 17:09:11 -0400 Subject: [PATCH] improvements to socks5 support (for 1.1.1 release) (#567) capture listen IP and port from socks5 BIND response. add tests for socks5 and improve support for capturing the local endpoint (i.e. bind port) for BIND command socket connections. post listen_succeeded_alert when successfully bound to listen socket on SOCKS5 proxy. make sure to announce the socks5 listen port --- ChangeLog | 1 + include/libtorrent/alert_types.hpp | 6 +- include/libtorrent/aux_/session_impl.hpp | 2 + include/libtorrent/broadcast_socket.hpp | 2 + include/libtorrent/socks5_stream.hpp | 66 ++++- simulation/Jamfile | 1 + simulation/fake_peer.hpp | 151 +++++++++++ simulation/test_ip_filter.cpp | 58 +---- simulation/test_socks5.cpp | 306 +++++++++++++++++++++++ simulation/test_tracker.cpp | 4 +- simulation/test_transfer.cpp | 35 ++- src/alert.cpp | 2 +- src/broadcast_socket.cpp | 7 + src/session_impl.cpp | 93 +++++-- src/socks5_stream.cpp | 111 +++++--- src/torrent.cpp | 8 +- test/bittorrent_peer.hpp | 2 +- 17 files changed, 705 insertions(+), 150 deletions(-) create mode 100644 simulation/fake_peer.hpp create mode 100644 simulation/test_socks5.cpp diff --git a/ChangeLog b/ChangeLog index 77b9a2f49..ea7c5e6f6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 1.1.1 release + * improved Socks5 support and test coverage * fix set_settings in python binding * Added missing alert categories in python binding * Added dht_get_peers_reply_alert alert in python binding diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index 9b884ccbb..06e571ba6 100644 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -1326,7 +1326,7 @@ namespace libtorrent // was opened for listening. struct TORRENT_EXPORT listen_succeeded_alert TORRENT_FINAL : alert { - enum socket_type_t { tcp, tcp_ssl, udp, utp_ssl }; + enum socket_type_t { tcp, tcp_ssl, udp, i2p, socks5, utp_ssl }; // internal listen_succeeded_alert(aux::stack_allocator& alloc, tcp::endpoint const& ep @@ -1799,8 +1799,8 @@ namespace libtorrent // an incoming connection, through any mean. The most straight-forward ways // of accepting incoming connections are through the TCP listen socket and // the UDP listen socket for uTP sockets. However, connections may also be - // accepted offer a Socks5 or i2p listen socket, or via a torrent specific - // listen socket for SSL torrents. + // accepted through a Socks5 or i2p listen socket, or via an SSL listen + // socket. struct TORRENT_EXPORT incoming_connection_alert TORRENT_FINAL : alert { // internal diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index e172f33fb..49ff38d3e 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -242,6 +242,8 @@ 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); diff --git a/include/libtorrent/broadcast_socket.hpp b/include/libtorrent/broadcast_socket.hpp index 24babb8f2..8fa7983db 100644 --- a/include/libtorrent/broadcast_socket.hpp +++ b/include/libtorrent/broadcast_socket.hpp @@ -50,12 +50,14 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + // TODO: 2 facto these functions out TORRENT_EXTRA_EXPORT bool is_local(address const& a); TORRENT_EXTRA_EXPORT bool is_loopback(address const& addr); TORRENT_EXTRA_EXPORT bool is_multicast(address const& addr); TORRENT_EXTRA_EXPORT bool is_any(address const& addr); TORRENT_EXTRA_EXPORT bool is_teredo(address const& addr); TORRENT_EXTRA_EXPORT int cidr_distance(address const& a1, address const& a2); + bool is_ip_address(char const* host); // determines if the operating system supports IPv6 TORRENT_EXTRA_EXPORT bool supports_ipv6(); diff --git a/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp index d3cd6b3ab..40bd08202 100644 --- a/include/libtorrent/socks5_stream.hpp +++ b/include/libtorrent/socks5_stream.hpp @@ -37,10 +37,12 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include "libtorrent/aux_/disable_warnings_pop.hpp" #include "libtorrent/proxy_base.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_ip_address #include "libtorrent/assert.hpp" #if defined TORRENT_ASIO_DEBUGGING #include "libtorrent/debug.hpp" @@ -98,7 +100,7 @@ public: void set_command(int c) { - TORRENT_ASSERT(c >= socks5_connect && c <=socks5_udp_associate); + TORRENT_ASSERT(c >= socks5_connect && c <= socks5_udp_associate); m_command = c; } @@ -109,19 +111,43 @@ 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; + 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) { - // TODO: 3 enable this assert and fix remaining causes of it triggering -/* -#if TORRENT_USE_ASSERTS - error_code ec; - address::from_string(host, ec); // if this assert trips, set_dst_name() is called wth an IP address rather // than a hostname. Instead, resolve the IP into an address and pass it to // async_connect instead - TORRENT_ASSERT(ec); -#endif -*/ + TORRENT_ASSERT(!is_ip_address(host.c_str())); m_dst_name = host; if (m_dst_name.size() > 255) m_dst_name.resize(255); @@ -141,14 +167,27 @@ 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 - || endpoint.address() != address() + TORRENT_ASSERT(m_command != socks5_bind); + TORRENT_ASSERT(endpoint.address() != address() || (!m_dst_name.empty() && m_version == 5)); m_remote_endpoint = endpoint; @@ -194,6 +233,11 @@ private: std::string m_user; 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, bind, diff --git a/simulation/Jamfile b/simulation/Jamfile index 5a32e0da7..6bc68914e 100644 --- a/simulation/Jamfile +++ b/simulation/Jamfile @@ -24,6 +24,7 @@ project ; alias libtorrent-sims : + [ run test_socks5.cpp ] [ run test_checking.cpp ] [ run test_optimistic_unchoke.cpp ] [ run test_transfer.cpp ] diff --git a/simulation/fake_peer.hpp b/simulation/fake_peer.hpp new file mode 100644 index 000000000..55b2408e8 --- /dev/null +++ b/simulation/fake_peer.hpp @@ -0,0 +1,151 @@ +/* + +Copyright (c) 2015, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef SIMULATION_FAKE_PEER_HPP +#define SIMULATION_FAKE_PEER_HPP + +#include +#include "test.hpp" +#include "simulator/simulator.hpp" +#include "libtorrent/session.hpp" +#include "libtorrent/socket_io.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/sha1_hash.hpp" +#include "libtorrent/torrent_info.hpp" + +using namespace sim; + +namespace lt = libtorrent; + +struct fake_peer +{ + fake_peer(simulation& sim, char const* ip) + : m_ios(sim, asio::ip::address::from_string(ip)) + , m_acceptor(m_ios) + , m_in_socket(m_ios) + , m_out_socket(m_ios) + , m_tripped(false) + { + boost::system::error_code ec; + m_acceptor.open(asio::ip::tcp::v4(), ec); + TEST_CHECK(!ec); + m_acceptor.bind(asio::ip::tcp::endpoint(asio::ip::address_v4::any(), 6881), ec); + TEST_CHECK(!ec); + m_acceptor.listen(10, ec); + TEST_CHECK(!ec); + + m_acceptor.async_accept(m_in_socket, [&] (boost::system::error_code const& ec) + { + // TODO: ideally we would kick off a read on the socket to verify that + // we received a bittorrent handshake + if (!ec) m_tripped = true; + }); + } + + void close() + { + m_acceptor.close(); + m_in_socket.close(); + m_out_socket.close(); + } + + void connect_to(asio::ip::tcp::endpoint ep, lt::sha1_hash const& ih) + { + using namespace std::placeholders; + + boost::system::error_code ec; + m_out_socket.async_connect(ep, std::bind(&fake_peer::write_handshake + , this, _1, ih)); + } + + bool tripped() const { return m_tripped; } + +private: + + void write_handshake(boost::system::error_code const& ec, lt::sha1_hash ih) + { + asio::ip::tcp::endpoint ep = m_out_socket.remote_endpoint(); + printf("fake_peer::connect (%s) -> (%d) %s\n" + , lt::print_endpoint(ep).c_str(), ec.value() + , ec.message().c_str()); + static char const handshake[] + = "\x13" "BitTorrent protocol\0\0\0\0\0\0\0\x04" + " " // space for info-hash + "aaaaaaaaaaaaaaaaaaaa" // peer-id + "\0\0\0\x01\x02"; // interested + int const len = sizeof(handshake) - 1; + memcpy(m_out_buffer, handshake, len); + memcpy(&m_out_buffer[28], ih.data(), 20); + + asio::async_write(m_out_socket, asio::const_buffers_1(&m_out_buffer[0] + , len), [=](boost::system::error_code const& ec, size_t bytes_transferred) + { + printf("fake_peer::write_handshake(%s) -> (%d) %s\n" + , lt::print_endpoint(ep).c_str(), ec.value() + , ec.message().c_str()); + this->m_out_socket.close(); + }); + } + + char m_out_buffer[300]; + + asio::io_service m_ios; + asio::ip::tcp::acceptor m_acceptor; + asio::ip::tcp::socket m_in_socket; + asio::ip::tcp::socket m_out_socket; + bool m_tripped; +}; + +inline void add_fake_peers(lt::torrent_handle h) +{ + // add the fake peers + for (int i = 0; i < 5; ++i) + { + char ep[30]; + snprintf(ep, sizeof(ep), "60.0.0.%d", i); + h.connect_peer(lt::tcp::endpoint( + lt::address_v4::from_string(ep), 6881)); + } +} + +inline void check_tripped(std::array& test_peers, std::array expected) +{ + int idx = 0; + for (auto p : test_peers) + { + TEST_EQUAL(p->tripped(), expected[idx]); + ++idx; + } +} + +#endif + diff --git a/simulation/test_ip_filter.cpp b/simulation/test_ip_filter.cpp index d2de4eb81..576dcb7d7 100644 --- a/simulation/test_ip_filter.cpp +++ b/simulation/test_ip_filter.cpp @@ -41,47 +41,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/alert_types.hpp" #include "simulator/simulator.hpp" #include "simulator/utils.hpp" +#include "fake_peer.hpp" #include "utils.hpp" // for print_alerts using namespace sim; namespace lt = libtorrent; -struct fake_peer -{ - fake_peer(simulation& sim, char const* ip) - : m_ios(sim, asio::ip::address::from_string(ip)) - , m_acceptor(m_ios) - , m_socket(m_ios) - , m_tripped(false) - { - boost::system::error_code ec; - m_acceptor.open(asio::ip::tcp::v4(), ec); - TEST_CHECK(!ec); - m_acceptor.bind(asio::ip::tcp::endpoint(asio::ip::address_v4::any(), 6881), ec); - TEST_CHECK(!ec); - m_acceptor.listen(10, ec); - TEST_CHECK(!ec); - - m_acceptor.async_accept(m_socket, [&] (boost::system::error_code const& ec) - { if (!ec) m_tripped = true; }); - } - - void close() - { - m_acceptor.close(); - m_socket.close(); - } - - bool tripped() const { return m_tripped; } - -private: - asio::io_service m_ios; - asio::ip::tcp::acceptor m_acceptor; - asio::ip::tcp::socket m_socket; - bool m_tripped; -}; - template void run_test(Setup const& setup , HandleAlerts const& on_alert @@ -134,28 +100,6 @@ void run_test(Setup const& setup sim.run(); } -void add_fake_peers(lt::torrent_handle h) -{ - // add the fake peers - for (int i = 0; i < 5; ++i) - { - char ep[30]; - snprintf(ep, sizeof(ep), "60.0.0.%d", i); - h.connect_peer(lt::tcp::endpoint( - lt::address_v4::from_string(ep), 6881)); - } -} - -void check_tripped(std::array& test_peers, std::array expected) -{ - int idx = 0; - for (auto p : test_peers) - { - TEST_EQUAL(p->tripped(), expected[idx]); - ++idx; - } -} - void add_ip_filter(lt::session& ses) { lt::ip_filter filter; diff --git a/simulation/test_socks5.cpp b/simulation/test_socks5.cpp new file mode 100644 index 000000000..b0d78834c --- /dev/null +++ b/simulation/test_socks5.cpp @@ -0,0 +1,306 @@ +/* + +Copyright (c) 2016, Arvid Norberg +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "libtorrent/session.hpp" +#include "libtorrent/torrent_handle.hpp" +#include "libtorrent/settings_pack.hpp" +#include "libtorrent/alert_types.hpp" +#include "libtorrent/deadline_timer.hpp" +#include "simulator/http_server.hpp" +#include "settings.hpp" +#include "create_torrent.hpp" +#include "simulator/simulator.hpp" +#include "setup_swarm.hpp" +#include "utils.hpp" +#include "simulator/socks_server.hpp" +#include "simulator/utils.hpp" +#include "fake_peer.hpp" +#include + +using namespace sim; +using namespace libtorrent; + +namespace lt = libtorrent; + +std::unique_ptr make_io_service(sim::simulation& sim, int i) +{ + char ep[30]; + snprintf(ep, sizeof(ep), "50.0.%d.%d", (i + 1) >> 8, (i + 1) & 0xff); + return std::unique_ptr(new sim::asio::io_service( + sim, address_v4::from_string(ep))); +} + +// this is the general template for these tests. create the session with custom +// settings (Settings), set up the test, by adding torrents with certain +// arguments (Setup), run the test and verify the end state (Test) +template +void run_test(Setup const& setup + , HandleAlerts const& on_alert + , Test const& test) +{ + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + std::unique_ptr ios = make_io_service(sim, 0); + lt::session_proxy zombie; + + sim::asio::io_service proxy_ios{sim, addr("50.50.50.50") }; + sim::socks_server socks4(proxy_ios, 4444, 4); + sim::socks_server socks5(proxy_ios, 5555, 5); + + lt::settings_pack pack = settings(); + // create session + std::shared_ptr ses = std::make_shared(pack, *ios); + + // set up test, like adding torrents (customization point) + setup(*ses); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses, [=](lt::session& ses, lt::alert const* a) { + on_alert(ses, a); + }); + + lt::add_torrent_params params = create_torrent(1); + params.flags &= ~lt::add_torrent_params::flag_auto_managed; + params.flags &= ~lt::add_torrent_params::flag_paused; + params.save_path = save_path(0); + ses->async_add_torrent(params); + + // set up a timer to fire later, to verify everything we expected to happen + // happened + sim::timer t(sim, lt::seconds(100), [&](boost::system::error_code const& ec) + { + fprintf(stderr, "shutting down\n"); + // shut down + ses->set_alert_notify([] {}); + zombie = ses->abort(); + ses.reset(); + }); + + test(sim, *ses, params.ti); +} + +TORRENT_TEST(socks5_tcp_accept) +{ + using namespace libtorrent; + bool incoming_connection = false; + run_test( + [](lt::session& ses) + { + 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) + { + 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) + { + 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) + { + 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; + int tracker_port = -1; + int alert_port = -1; + run_test( + [](lt::session& ses) + { + set_proxy(ses, settings_pack::socks5); + + lt::add_torrent_params params; + params.info_hash = sha1_hash("abababababababababab"); + params.trackers.push_back("http://2.2.2.2:8080/announce"); + params.save_path = "."; + ses.async_add_torrent(params); + }, + [&alert_port](lt::session& ses, lt::alert const* alert) { + if (auto* a = lt::alert_cast(alert)) + { + if (a->sock_type == listen_succeeded_alert::socks5) + { + alert_port = a->endpoint.port(); + } + } + }, + [&tracker_port](sim::simulation& sim, lt::session& ses + , boost::shared_ptr ti) + { + sim::asio::io_service web_server(sim, address_v4::from_string("2.2.2.2")); + // listen on port 8080 + sim::http_server http(web_server, 8080); + + http.register_handler("/announce" + , [&tracker_port](std::string method, std::string req + , std::map&) + { + if (req.find("&event=started") != std::string::npos) + { + std::string::size_type port_pos = req.find("&port="); + TEST_CHECK(port_pos != std::string::npos); + if (port_pos != std::string::npos) + { + tracker_port = atoi(req.c_str() + port_pos + 6); + } + } + + return sim::send_response(200, "OK", 27) + "d8:intervali1800e5:peers0:e"; + }); + + sim.run(); + } + ); + + TEST_EQUAL(alert_port, tracker_port); + TEST_CHECK(alert_port != -1); + TEST_CHECK(tracker_port != -1); +} + diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp index 0f9a1e439..64ae26d4f 100644 --- a/simulation/test_tracker.cpp +++ b/simulation/test_tracker.cpp @@ -68,7 +68,7 @@ void test_interval(int interval) std::vector announces; http.register_handler("/announce" - , [&announces,interval,start](std::string method, std::string req + , [&announces,interval,start](std::string method, std::string req , std::map&) { boost::uint32_t seconds = chrono::duration_cast( @@ -414,7 +414,7 @@ void tracker_test(Setup setup, Announce a, Test1 test1, Test2 test2 }); sim::timer t2(sim, lt::seconds(5 + delay) - , [&ses,&test2](boost::system::error_code const& ec) + , [&ses,&test2](boost::system::error_code const& ec) { std::vector torrents = ses->get_torrents(); TEST_EQUAL(torrents.size(), 1); diff --git a/simulation/test_transfer.cpp b/simulation/test_transfer.cpp index 63c303a8c..ddc3aceaf 100644 --- a/simulation/test_transfer.cpp +++ b/simulation/test_transfer.cpp @@ -36,7 +36,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "settings.hpp" #include "libtorrent/session.hpp" #include "libtorrent/session_stats.hpp" -#include "libtorrent/deadline_timer.hpp" #include "libtorrent/settings_pack.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/alert_types.hpp" @@ -52,6 +51,8 @@ using namespace sim; namespace lt = libtorrent; +const int connect_socks = 2; + template void run_test( Setup const& setup @@ -111,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); }); @@ -162,7 +164,7 @@ TORRENT_TEST(socks4_tcp) ); } -TORRENT_TEST(socks5_tcp) +TORRENT_TEST(socks5_tcp_connect) { using namespace libtorrent; run_test( @@ -178,6 +180,26 @@ TORRENT_TEST(socks5_tcp) ); } +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 + 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; @@ -260,10 +282,8 @@ TORRENT_TEST(no_proxy_utp) } ); } - -// TODO: the socks server does not support UDP yet - /* +// TOD: 3 figure out why this test is failing TORRENT_TEST(encryption_utp) { using namespace libtorrent; @@ -276,7 +296,10 @@ TORRENT_TEST(encryption_utp) } ); } +*/ +// TODO: the socks server does not support UDP yet +/* TORRENT_TEST(socks5_utp) { using namespace libtorrent; diff --git a/src/alert.cpp b/src/alert.cpp index b5eba7e2f..465268176 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -924,7 +924,7 @@ namespace libtorrent { std::string listen_succeeded_alert::message() const { - char const* type_str[] = { "TCP", "SSL/TCP", "UDP", "SSL/uTP" }; + char const* type_str[] = { "TCP", "SSL/TCP", "UDP", "i2p", "socks5", "SSL/uTP" }; char ret[200]; snprintf(ret, sizeof(ret), "successfully listening on [%s] %s" , type_str[sock_type], print_endpoint(endpoint).c_str()); diff --git a/src/broadcast_socket.cpp b/src/broadcast_socket.cpp index 2cac0dfa7..007808a0c 100644 --- a/src/broadcast_socket.cpp +++ b/src/broadcast_socket.cpp @@ -65,6 +65,13 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { + bool is_ip_address(char const* host) + { + error_code ec; + address::from_string(host, ec); + return !ec; + } + bool is_local(address const& a) { TORRENT_TRY { diff --git a/src/session_impl.cpp b/src/session_impl.cpp index c58cf948f..6efe287c7 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -2276,21 +2276,80 @@ retry: if (m_socks_listen_socket) return; - m_socks_listen_socket = boost::shared_ptr(new socket_type(m_io_service)); + 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); + + 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.set_command(2); // 2 means BIND (as opposed to CONNECT) - m_socks_listen_port = m_listen_interface.port(); - if (m_socks_listen_port == 0) m_socks_listen_port = 2000 + random() % 60000; - s.async_connect(tcp::endpoint(address_v4::any(), m_socks_listen_port) - , boost::bind(&session_impl::on_socks_accept, this, m_socks_listen_socket, _1)); + 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() @@ -2842,26 +2901,6 @@ retry: set_socket_buffer_size(s, m_settings, ec); } - 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 - 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); - } - // if cancel_with_cq is set, the peer connection is // currently expected to be scheduled for a connection // with the connection queue, and should be cancelled @@ -5426,7 +5465,7 @@ retry: // 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; + return m_socks_listen_socket->local_endpoint().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 diff --git a/src/socks5_stream.cpp b/src/socks5_stream.cpp index 61646c61b..4ad498e29 100644 --- a/src/socks5_stream.cpp +++ b/src/socks5_stream.cpp @@ -79,6 +79,56 @@ namespace libtorrent return socks_category; } + 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) { @@ -350,10 +400,9 @@ namespace libtorrent using namespace libtorrent::detail; - // send SOCKS5 connect command - char* p = &m_buffer[0]; - int version = read_uint8(p); - int response = read_uint8(p); + char const* p = &m_buffer[0]; + int const version = read_uint8(p); + int const response = read_uint8(p); if (m_version == 5) { @@ -379,23 +428,22 @@ namespace libtorrent return; } p += 1; // reserved - int atyp = read_uint8(p); - // we ignore the proxy IP it was bound to + int const atyp = read_uint8(p); + // read the proxy IP it was bound to (this is variable length depending + // on address type) if (atyp == 1) { if (m_command == socks5_bind) { if (m_listen == 0) { -#if defined TORRENT_ASIO_DEBUGGING - add_outstanding_async("socks5_stream::connect1"); -#endif + m_local_endpoint = parse_endpoint(m_buffer, m_version); m_listen = 1; - connect1(e, h); - return; } - m_remote_endpoint.address(read_v4_address(p)); - m_remote_endpoint.port(read_uint16(p)); + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } std::vector().swap(m_buffer); (*h)(e); } @@ -409,10 +457,12 @@ namespace libtorrent int extra_bytes = 0; if (atyp == 4) { + // IPv6 extra_bytes = 12; } else if (atyp == 3) { + // hostname with length prefix extra_bytes = read_uint8(p) - 3; } else @@ -444,15 +494,13 @@ namespace libtorrent { if (m_listen == 0) { -#if defined TORRENT_ASIO_DEBUGGING - add_outstanding_async("socks5_stream::connect1"); -#endif + m_local_endpoint = parse_endpoint(m_buffer, m_version); m_listen = 1; - connect1(e, h); - return; } - m_remote_endpoint.address(read_v4_address(p)); - m_remote_endpoint.port(read_uint16(p)); + else + { + m_remote_endpoint = parse_endpoint(m_buffer, m_version); + } std::vector().swap(m_buffer); (*h)(e); } @@ -488,29 +536,12 @@ namespace libtorrent { if (m_listen == 0) { -#if defined TORRENT_ASIO_DEBUGGING - add_outstanding_async("socks5_stream::connect1"); -#endif + m_local_endpoint = parse_endpoint(m_buffer, m_version); m_listen = 1; - connect1(e, h); - return; } - - char* p = &m_buffer[0]; - p += 2; // version and response code - int atyp = read_uint8(p); - TORRENT_ASSERT(atyp == 3 || atyp == 4); - if (atyp == 4) + else { - // we don't support resolving the endpoint address - // if we receive a domain name, just set the remote - // endpoint to INADDR_ANY - m_remote_endpoint = tcp::endpoint(); - } - else if (atyp == 3) - { - m_remote_endpoint.address(read_v4_address(p)); - m_remote_endpoint.port(read_uint16(p)); + m_remote_endpoint = parse_endpoint(m_buffer, m_version); } } std::vector().swap(m_buffer); diff --git a/src/torrent.cpp b/src/torrent.cpp index 69cd41b4f..83dc1781d 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -98,6 +98,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/aux_/file_progress.hpp" #include "libtorrent/alert_manager.hpp" #include "libtorrent/disk_interface.hpp" +#include "libtorrent/broadcast_socket.hpp" // for is_ip_address // TODO: factor out cache_status to its own header #include "libtorrent/disk_io_thread.hpp" // for cache_status @@ -3153,7 +3154,7 @@ namespace libtorrent // if we are aborting. we don't want any new peers req.num_want = (req.event == tracker_request::stopped) - ?0:settings().get_int(settings_pack::num_want); + ? 0 : settings().get_int(settings_pack::num_want); time_point now = clock_type::now(); @@ -6572,7 +6573,10 @@ namespace libtorrent return; } - bool proxy_hostnames = settings().get_bool(settings_pack::proxy_hostnames); + bool const is_ip = is_ip_address(hostname.c_str()); + if (is_ip) a.address(address::from_string(hostname.c_str(), ec)); + bool const proxy_hostnames = settings().get_bool(settings_pack::proxy_hostnames) + && !is_ip; if (proxy_hostnames && (s->get() diff --git a/test/bittorrent_peer.hpp b/test/bittorrent_peer.hpp index 481f966cd..ef4076e8e 100644 --- a/test/bittorrent_peer.hpp +++ b/test/bittorrent_peer.hpp @@ -80,7 +80,7 @@ private: char write_buf_proto[100]; boost::uint32_t write_buffer[17*1024/4]; boost::uint32_t buffer[17*1024/4]; - + peer_mode_t m_mode; torrent_info const& m_ti;