diff --git a/ChangeLog b/ChangeLog index c6710b383..01ede6220 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,8 @@ 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 * 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 7deeb75cd..5d4804235 100644 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -1322,7 +1322,7 @@ namespace libtorrent // was opened for listening. struct TORRENT_EXPORT listen_succeeded_alert 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 @@ -1747,8 +1747,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 final : alert { // internal diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index e7f43502b..9b9005a87 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -256,6 +256,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/bencode.hpp b/include/libtorrent/bencode.hpp index 2d21374aa..f1bd1233f 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/broadcast_socket.hpp b/include/libtorrent/broadcast_socket.hpp index 283b42038..72ca96447 100644 --- a/include/libtorrent/broadcast_socket.hpp +++ b/include/libtorrent/broadcast_socket.hpp @@ -50,11 +50,13 @@ 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_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/entry.hpp b/include/libtorrent/entry.hpp index 2f99859e2..0c9a73b28 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. @@ -160,6 +163,7 @@ namespace libtorrent entry& operator=(string_type const&); entry& operator=(list_type const&); entry& operator=(integer_type const&); + entry& operator=(preformatted_type const&); // The ``integer()``, ``string()``, ``list()`` and ``dict()`` functions // are accessors that return the respective type. If the ``entry`` object @@ -216,6 +220,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); @@ -268,17 +274,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/include/libtorrent/socks5_stream.hpp b/include/libtorrent/socks5_stream.hpp index 55f310855..e4176154f 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" #include "libtorrent/debug.hpp" @@ -96,7 +98,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; } @@ -107,19 +109,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).data()); + 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); @@ -139,14 +165,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; @@ -190,6 +229,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 e222b1a13..9f8f21c20 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 c05646d4d..e5f5aba34 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 5fdced139..e1a6fa70c 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -919,7 +919,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 a62ffdcd3..e9a954195 100644 --- a/src/broadcast_socket.cpp +++ b/src/broadcast_socket.cpp @@ -62,6 +62,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/create_torrent.cpp b/src/create_torrent.cpp index 04f009b9f..446cfbae7 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 1c7c5f475..b7218d91f 100644 --- a/src/entry.cpp +++ b/src/entry.cpp @@ -158,6 +158,7 @@ namespace libtorrent entry& entry::operator=(const entry& e) { + if (&e == this) return *this; destruct(); copy(e); return *this; @@ -261,6 +262,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) { @@ -339,6 +363,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 entry& entry::operator=(bdecode_node const& e) { @@ -417,6 +451,17 @@ namespace libtorrent } #endif + entry& entry::operator=(preformatted_type const& v) + { + destruct(); + new(data) preformatted_type(v); + m_type = preformatted_t; +#ifdef TORRENT_DEBUG + m_type_queried = true; +#endif + return *this; + } + entry& entry::operator=(dictionary_type const& v) { destruct(); @@ -475,6 +520,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; @@ -499,6 +546,9 @@ namespace libtorrent break; case undefined_t: break; + case preformatted_t: + new (data) preformatted_type; + break; } m_type = t; #ifdef TORRENT_DEBUG @@ -524,6 +574,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 @@ -547,6 +601,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; @@ -597,6 +654,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; } @@ -689,6 +750,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/session_impl.cpp b/src/session_impl.cpp index 74f1dc259..bdffcaa2b 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -2038,19 +2038,76 @@ namespace aux { 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); - ADD_OUTSTANDING_ASYNC("session_impl::on_socks_accept"); + ADD_OUTSTANDING_ASYNC("session_impl::on_socks_listen"); socks5_stream& s = *m_socks_listen_socket->get(); - s.set_command(2); // 2 means BIND (as opposed to CONNECT) - 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)); + m_socks_listen_port = listen_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" + , tcp::endpoint(), 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.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) + { + COMPLETE_ASYNC("session_impl::on_socks_accept"); + 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" + , tcp::endpoint(), listen_failed_alert::accept, e + , listen_failed_alert::socks5); + return; + } + open_new_incoming_socks_connection(); + incoming_connection(s); } void session_impl::update_i2p_bridge() @@ -2805,24 +2862,6 @@ namespace aux { set_socket_buffer_size(s, m_settings, ec); } - void session_impl::on_socks_accept(boost::shared_ptr const& s - , error_code const& e) - { - COMPLETE_ASYNC("session_impl::on_socks_accept"); - 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" - , tcp::endpoint(), 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 @@ -5245,7 +5284,7 @@ namespace aux { // 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 477d2d8be..567178929 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) { @@ -322,10 +372,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) { @@ -351,21 +400,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) { - ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + 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); } @@ -379,10 +429,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 @@ -412,13 +464,13 @@ namespace libtorrent { if (m_listen == 0) { - ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + 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); } @@ -452,27 +504,12 @@ namespace libtorrent { if (m_listen == 0) { - ADD_OUTSTANDING_ASYNC("socks5_stream::connect1"); + 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 3af351a7a..f8bbdcb03 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 @@ -2944,7 +2945,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(); @@ -4716,11 +4717,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); @@ -6326,7 +6323,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() @@ -6494,8 +6494,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 @@ -8284,7 +8287,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; diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp index e9a079612..a80bf5e43 100644 --- a/src/utp_stream.cpp +++ b/src/utp_stream.cpp @@ -1749,7 +1749,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; @@ -2028,7 +2028,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; diff --git a/test/Jamfile b/test/Jamfile index 1b5eb783d..7abaa29f2 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -112,6 +112,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 42c1a26c4..51dd6f568 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -27,6 +27,7 @@ test_programs = \ test_torrent \ test_tracker \ test_transfer \ + test_create_torrent \ enum_if \ test_utp \ test_session \ @@ -207,6 +208,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/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; 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); +} +