From 7048eb1f5cd9045a40ff4e4aaaa6c29f425dda1c Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Mon, 2 May 2016 00:17:17 -0400 Subject: [PATCH 1/4] be more conservative in marking packets as mtu-probes (#689) --- src/utp_stream.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp index 2f54e2e31..f85412ef3 100644 --- a/src/utp_stream.cpp +++ b/src/utp_stream.cpp @@ -1754,7 +1754,7 @@ private: // send_pkt() again) // returns true if there is more space for payload in our // congestion window, false if there is no more space. -bool utp_socket_impl::send_pkt(int flags) +bool utp_socket_impl::send_pkt(int const flags) { #ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS INVARIANT_CHECK; @@ -2033,7 +2033,7 @@ bool utp_socket_impl::send_pkt(int flags) // for ST_DATA packets, payload size is 0. Such packets do not have unique // sequence numbers and should never be used as mtu probes - if ((mtu_probe || p->mtu_probe) && payload_size > 0) + if ((mtu_probe || p->mtu_probe) && payload_size > m_mtu_floor) { p->mtu_probe = true; m_mtu_seq = m_seq_nr; From f065cacebf8fa491e9f57b196f59af1fc2c33c45 Mon Sep 17 00:00:00 2001 From: Jonathan McDougall Date: Tue, 3 May 2016 01:34:43 -0400 Subject: [PATCH 2/4] * now calling stop_announcing() unconditionally instead of checking !is_paused(), which was always false when aborting a torrent and could cause infinite blocks while destroying a session (#694) fixed torrent invariant check that wasn't using the m_abort flag and correctly stop trackers when shutting down --- src/torrent.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/torrent.cpp b/src/torrent.cpp index 52d1debdc..69cd41b4f 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -4933,11 +4933,7 @@ namespace libtorrent update_want_tick(); update_want_scrape(); update_gauge(); - - // if the torrent is paused, it doesn't need - // to announce with even=stopped again. - if (!is_paused()) - stop_announcing(); + stop_announcing(); error_code ec; m_inactivity_timer.cancel(ec); @@ -8988,7 +8984,7 @@ namespace libtorrent TORRENT_ASSERT(want_peers_download() == m_links[aux::session_interface::torrent_want_peers_download].in_list()); TORRENT_ASSERT(want_peers_finished() == m_links[aux::session_interface::torrent_want_peers_finished].in_list()); TORRENT_ASSERT(want_tick() == m_links[aux::session_interface::torrent_want_tick].in_list()); - TORRENT_ASSERT((!m_allow_peers && m_auto_managed) == m_links[aux::session_interface::torrent_want_scrape].in_list()); + TORRENT_ASSERT((!m_allow_peers && m_auto_managed && !m_abort) == m_links[aux::session_interface::torrent_want_scrape].in_list()); bool is_checking = false; bool is_downloading = false; From f9bc6dbc54b13cc3fa0391525486262d7275f757 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Thu, 5 May 2016 17:09:11 -0400 Subject: [PATCH 3/4] 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; From 985436636e7407018ff56537858feeb5f306d790 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Thu, 5 May 2016 21:38:57 -0400 Subject: [PATCH 4/4] added new preformatted type to bencode entry (#698) added new preformatted type to bencode entry to support carrying a verbatim copy of an already bencoded subtree. This is to support saving torrents in resume data and create_torrent based on existing torrents, in order to preserve key order --- ChangeLog | 1 + include/libtorrent/bencode.hpp | 4 ++ include/libtorrent/entry.hpp | 18 ++++-- src/create_torrent.cpp | 7 ++- src/entry.cpp | 63 ++++++++++++++++++++ src/torrent.cpp | 7 ++- test/Jamfile | 1 + test/Makefile.am | 2 + test/test_bencoding.cpp | 102 +++++++++++++++++++-------------- test/test_create_torrent.cpp | 67 ++++++++++++++++++++++ 10 files changed, 220 insertions(+), 52 deletions(-) create mode 100644 test/test_create_torrent.cpp diff --git a/ChangeLog b/ChangeLog index ea7c5e6f6..eeaa76991 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 1.1.1 release + * added a new "preformatted" type to bencode entry variant type * improved Socks5 support and test coverage * fix set_settings in python binding * Added missing alert categories in python binding diff --git a/include/libtorrent/bencode.hpp b/include/libtorrent/bencode.hpp index 44a3e48f1..7e3a7848d 100644 --- a/include/libtorrent/bencode.hpp +++ b/include/libtorrent/bencode.hpp @@ -210,6 +210,10 @@ namespace libtorrent write_char(out, 'e'); ret += 2; break; + case entry::preformatted_t: + std::copy(e.preformatted().begin(), e.preformatted().end(), out); + ret += e.preformatted().size(); + break; case entry::undefined_t: // trying to encode a structure with uninitialized values! // TORRENT_ASSERT_VAL(false, e.type()); diff --git a/include/libtorrent/entry.hpp b/include/libtorrent/entry.hpp index effb8f78f..e365fd5e6 100644 --- a/include/libtorrent/entry.hpp +++ b/include/libtorrent/entry.hpp @@ -108,6 +108,7 @@ namespace libtorrent typedef std::string string_type; typedef std::list list_type; typedef boost::int64_t integer_type; + typedef std::vector preformatted_type; // the types an entry can have enum data_type @@ -116,7 +117,8 @@ namespace libtorrent string_t, list_t, dictionary_t, - undefined_t + undefined_t, + preformatted_t }; // returns the concrete type of the entry @@ -129,6 +131,7 @@ namespace libtorrent entry(string_type const&); entry(list_type const&); entry(integer_type const&); + entry(preformatted_type const&); // construct an empty entry of the specified type. // see data_type enum. @@ -158,6 +161,7 @@ namespace libtorrent void operator=(string_type const&); void operator=(list_type const&); void operator=(integer_type const&); + void operator=(preformatted_type const&); // The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions // are accessors that return the respective type. If the ``entry`` object @@ -214,6 +218,8 @@ namespace libtorrent const list_type& list() const; dictionary_type& dict(); const dictionary_type& dict() const; + preformatted_type& preformatted(); + const preformatted_type& preformatted() const; // swaps the content of *this* with ``e``. void swap(entry& e); @@ -266,17 +272,19 @@ namespace libtorrent // assumes sizeof(map) == sizeof(map) // and sizeof(list) == sizeof(list) enum { union_size - = max4) + = max5) , sizeof(std::map) , sizeof(string_type) - , sizeof(integer_type)>::value + , sizeof(integer_type) + , sizeof(preformatted_type)>::value }; #else enum { union_size - = max4::value + , sizeof(integer_type) + , sizeof(preformatted_type)>::value }; #endif integer_type data[(union_size + sizeof(integer_type) - 1) diff --git a/src/create_torrent.cpp b/src/create_torrent.cpp index 091a9bb1a..8343054dd 100644 --- a/src/create_torrent.cpp +++ b/src/create_torrent.cpp @@ -416,7 +416,9 @@ namespace libtorrent m_piece_hash.resize(m_files.num_pieces()); for (int i = 0; i < num_pieces(); ++i) set_hash(i, ti.hash_for_piece(i)); - m_info_dict = bdecode(&ti.metadata()[0], &ti.metadata()[0] + ti.metadata_size()); + boost::shared_array const info = ti.metadata(); + int const size = ti.metadata_size(); + m_info_dict.preformatted().assign(&info[0], &info[0] + size); m_info_hash = ti.info_hash(); } @@ -508,7 +510,8 @@ namespace libtorrent } entry& info = dict["info"]; - if (m_info_dict.type() == entry::dictionary_t) + if (m_info_dict.type() == entry::dictionary_t + || m_info_dict.type() == entry::preformatted_t) { info = m_info_dict; return dict; diff --git a/src/entry.cpp b/src/entry.cpp index 97f08d5a9..d47eeb3af 100644 --- a/src/entry.cpp +++ b/src/entry.cpp @@ -158,6 +158,7 @@ namespace libtorrent void entry::operator=(const entry& e) { + if (&e == this) return; destruct(); copy(e); } @@ -254,6 +255,29 @@ namespace libtorrent return *reinterpret_cast(data); } + entry::preformatted_type& entry::preformatted() + { + if (m_type == undefined_t) construct(preformatted_t); +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != preformatted_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(data); + } + + entry::preformatted_type const& entry::preformatted() const + { +#ifndef BOOST_NO_EXCEPTIONS + if (m_type != preformatted_t) throw_type_error(); +#elif defined TORRENT_DEBUG + TORRENT_ASSERT(m_type_queried); +#endif + TORRENT_ASSERT(m_type == preformatted_t); + return *reinterpret_cast(data); + } + entry::entry() : m_type(undefined_t) { @@ -320,6 +344,16 @@ namespace libtorrent m_type = int_t; } + entry::entry(preformatted_type const& v) + : m_type(undefined_t) + { +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + new(data) preformatted_type(v); + m_type = preformatted_t; + } + // convert a bdecode_node into an old skool entry void entry::operator=(bdecode_node const& e) { @@ -396,6 +430,16 @@ namespace libtorrent } #endif + void entry::operator=(preformatted_type const& v) + { + destruct(); + new(data) preformatted_type(v); + m_type = preformatted_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + } + void entry::operator=(dictionary_type const& v) { destruct(); @@ -450,6 +494,8 @@ namespace libtorrent return list() == e.list(); case dictionary_t: return dict() == e.dict(); + case preformatted_t: + return preformatted() == e.preformatted(); default: TORRENT_ASSERT(m_type == undefined_t); return true; @@ -474,6 +520,9 @@ namespace libtorrent break; case undefined_t: break; + case preformatted_t: + new (data) preformatted_type; + break; } m_type = t; #ifdef TORRENT_DEBUG @@ -499,6 +548,10 @@ namespace libtorrent break; case undefined_t: TORRENT_ASSERT(e.type() == undefined_t); + break; + case preformatted_t: + new (data) preformatted_type(e.preformatted()); + break; } m_type = e.type(); #ifdef TORRENT_DEBUG @@ -522,6 +575,9 @@ namespace libtorrent case dictionary_t: call_destructor(reinterpret_cast(data)); break; + case preformatted_t: + call_destructor(reinterpret_cast(data)); + break; default: TORRENT_ASSERT(m_type == undefined_t); break; @@ -572,6 +628,10 @@ namespace libtorrent std::swap(*reinterpret_cast(data) , *reinterpret_cast(e.data)); break; + case preformatted_t: + std::swap(*reinterpret_cast(data) + , *reinterpret_cast(e.data)); + break; default: break; } @@ -664,6 +724,9 @@ namespace libtorrent i->second.to_string_impl(out, indent+2); } } break; + case preformatted_t: + out += "\n"; + break; case undefined_t: default: out += "\n"; diff --git a/src/torrent.cpp b/src/torrent.cpp index 83dc1781d..359b58368 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -7188,8 +7188,11 @@ namespace libtorrent if (valid_metadata()) { if (m_magnet_link || (m_save_resume_flags & torrent_handle::save_info_dict)) - ret["info"] = bdecode(&torrent_file().metadata()[0] - , &torrent_file().metadata()[0] + torrent_file().metadata_size()); + { + boost::shared_array const info = torrent_file().metadata(); + int const size = torrent_file().metadata_size(); + ret["info"].preformatted().assign(&info[0], &info[0] + size); + } } // blocks per piece diff --git a/test/Jamfile b/test/Jamfile index 4d89d2e09..89bce82e5 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -108,6 +108,7 @@ feature.compose valgrind : "valgrind --tool=memcheck test-suite libtorrent : [ run test_primitives.cpp + test_create_torrent.cpp test_packet_buffer.cpp test_timestamp_history.cpp test_sha1_hash.cpp diff --git a/test/Makefile.am b/test/Makefile.am index 2ad4f05b1..ac8899f29 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -26,6 +26,7 @@ test_programs = \ test_torrent \ test_tracker \ test_transfer \ + test_create_torrent \ enum_if \ test_utp \ test_session \ @@ -206,6 +207,7 @@ test_ssl_SOURCES = test_ssl.cpp test_torrent_SOURCES = test_torrent.cpp test_tracker_SOURCES = test_tracker.cpp test_transfer_SOURCES = test_transfer.cpp +test_create_torrent_SOURCES = test_create_torrent.cpp enum_if_SOURCES = enum_if.cpp test_utp_SOURCES = test_utp.cpp test_session_SOURCES = test_session.cpp diff --git a/test/test_bencoding.cpp b/test/test_bencoding.cpp index 7d1cc2c8f..1502437fb 100644 --- a/test/test_bencoding.cpp +++ b/test/test_bencoding.cpp @@ -58,54 +58,72 @@ entry decode(std::string const& str) return bdecode(str.begin(), str.end()); } -TORRENT_TEST(bencoding) +TORRENT_TEST(strings) { - // ** strings ** - { - entry e("spam"); - TEST_CHECK(encode(e) == "4:spam"); - TEST_CHECK(decode(encode(e)) == e); - } + entry e("spam"); + TEST_CHECK(encode(e) == "4:spam"); + TEST_CHECK(decode(encode(e)) == e); +} - // ** integers ** - { - entry e(3); - TEST_CHECK(encode(e) == "i3e"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(integers) +{ + entry e(3); + TEST_CHECK(encode(e) == "i3e"); + TEST_CHECK(decode(encode(e)) == e); +} - { - entry e(-3); - TEST_CHECK(encode(e) == "i-3e"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(integers2) +{ + entry e(-3); + TEST_CHECK(encode(e) == "i-3e"); + TEST_CHECK(decode(encode(e)) == e); +} - { - entry e(int(0)); - TEST_CHECK(encode(e) == "i0e"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(integers3) +{ + entry e(int(0)); + TEST_CHECK(encode(e) == "i0e"); + TEST_CHECK(decode(encode(e)) == e); +} - // ** lists ** - { - entry::list_type l; - l.push_back(entry("spam")); - l.push_back(entry("eggs")); - entry e(l); - TEST_CHECK(encode(e) == "l4:spam4:eggse"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(lists) +{ + entry::list_type l; + l.push_back(entry("spam")); + l.push_back(entry("eggs")); + entry e(l); + TEST_CHECK(encode(e) == "l4:spam4:eggse"); + TEST_CHECK(decode(encode(e)) == e); +} - // ** dictionaries ** - { - entry e(entry::dictionary_t); - e["spam"] = entry("eggs"); - e["cow"] = entry("moo"); - TEST_CHECK(encode(e) == "d3:cow3:moo4:spam4:eggse"); - TEST_CHECK(decode(encode(e)) == e); - } +TORRENT_TEST(dictionaries) +{ + entry e(entry::dictionary_t); + e["spam"] = entry("eggs"); + e["cow"] = entry("moo"); + TEST_CHECK(encode(e) == "d3:cow3:moo4:spam4:eggse"); + TEST_CHECK(decode(encode(e)) == e); +} + +TORRENT_TEST(preformatted) +{ + entry e(entry::preformatted_t); + char const str[] = "foobar"; + e.preformatted().assign(str, str + sizeof(str)-1); + TEST_EQUAL(encode(e), "foobar"); +} + +TORRENT_TEST(preformatted_node) +{ + entry e(entry::dictionary_t); + char const str[] = "foobar"; + e["info"] = entry::preformatted_type(str, str + sizeof(str)-1); + TEST_EQUAL(encode(e), "d4:infofoobare"); +} #ifndef TORRENT_NO_DEPRECATE +TORRENT_TEST(lazy_entry) +{ { char b[] = "i12453e"; lazy_entry e; @@ -609,8 +627,6 @@ TORRENT_TEST(bencoding) printf("%s\n", print_entry(e).c_str()); } } - - -#endif // TORRENT_NO_DEPRECATE } +#endif // TORRENT_NO_DEPRECATE diff --git a/test/test_create_torrent.cpp b/test/test_create_torrent.cpp new file mode 100644 index 000000000..3fb106279 --- /dev/null +++ b/test/test_create_torrent.cpp @@ -0,0 +1,67 @@ +/* + +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 "test.hpp" + +#include "libtorrent/torrent_info.hpp" +#include "libtorrent/create_torrent.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix +#include +#include + +namespace lt = libtorrent; + +// make sure creating a torrent from an existing handle preserves the +// info-dictionary verbatim, so as to not alter the info-hash +TORRENT_TEST(create_verbatim_torrent) +{ + char const test_torrent[] = "d4:infod4:name6:foobar6:lengthi12345e12:piece lengthi65536e6:pieces20:ababababababababababee"; + + lt::torrent_info info(test_torrent, sizeof(test_torrent) - 1); + + lt::create_torrent t(info); + + std::vector buffer; + lt::bencode(std::back_inserter(buffer), t.generate()); + + // now, make sure the info dictionary was unchanged + buffer.push_back('\0'); + char const* dest_info = std::strstr(&buffer[0], "4:info"); + + TEST_CHECK(dest_info != NULL); + + // +1 and -2 here is to strip the outermost dictionary from the source + // torrent, since create_torrent may have added items next to the info dict + TEST_CHECK(memcmp(dest_info, test_torrent + 1, sizeof(test_torrent)-3) == 0); +} +