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;