From 7353b75697640cd72c424df9844d4ed7c1985b84 Mon Sep 17 00:00:00 2001 From: arvidn Date: Mon, 27 Jan 2025 01:26:03 +0100 Subject: [PATCH] implement i2p_pex, peer exchange support for i2p torrents --- CMakeLists.txt | 2 + ChangeLog | 2 + Jamfile | 1 + Makefile | 2 + .../libtorrent/aux_/bt_peer_connection.hpp | 6 +- include/libtorrent/aux_/peer.hpp | 1 + include/libtorrent/aux_/session_impl.hpp | 1 + include/libtorrent/aux_/session_interface.hpp | 1 + include/libtorrent/aux_/torrent.hpp | 3 +- include/libtorrent/aux_/torrent_peer.hpp | 2 + include/libtorrent/extensions/i2p_pex.hpp | 37 ++ include/libtorrent/i2p_stream.hpp | 2 +- include/libtorrent/libtorrent.hpp | 1 + include/libtorrent/torrent_info.hpp | 2 + src/i2p_pex.cpp | 436 ++++++++++++++++++ src/session_impl.cpp | 2 +- src/session_params.cpp | 6 +- src/torrent.cpp | 37 +- src/ut_pex.cpp | 15 +- test/session_mock.hpp | 8 + test/test_peer_priority.cpp | 1 + test/test_session_params.cpp | 5 + 22 files changed, 545 insertions(+), 28 deletions(-) create mode 100644 include/libtorrent/extensions/i2p_pex.hpp create mode 100644 src/i2p_pex.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b06d3c4b725..71763feeefc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,7 @@ set(libtorrent_extensions_include_files smart_ban.hpp ut_metadata.hpp ut_pex.hpp + i2p_pex.hpp ) set(libtorrent_aux_include_files @@ -443,6 +444,7 @@ set(sources # -- extensions -- smart_ban.cpp ut_pex.cpp + i2p_pex.cpp ut_metadata.cpp ) diff --git a/ChangeLog b/ChangeLog index 63cc8827b7d..520733b10d7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,7 @@ 2.1.0 not released + * support peer exchange for i2p torrents + * implement i2p_pex, peer exchange support for i2p torrents * requires OpenSSL minimum version 1.1.0 with SNI support * try harder to bind TCP and UDP sockets to the same port * made disk_interface's status_t type a flags type diff --git a/Jamfile b/Jamfile index 98293aa6da6..e40188fec8f 100644 --- a/Jamfile +++ b/Jamfile @@ -920,6 +920,7 @@ SOURCES = # -- extensions -- ut_pex + i2p_pex ut_metadata smart_ban ; diff --git a/Makefile b/Makefile index dfa6e3cede5..2f9b73b25a6 100644 --- a/Makefile +++ b/Makefile @@ -427,6 +427,7 @@ SOURCES = \ upnp.cpp \ ut_metadata.cpp \ ut_pex.cpp \ + i2p_pex.cpp \ utf8.cpp \ utp_socket_manager.cpp \ utp_stream.cpp \ @@ -702,6 +703,7 @@ HEADERS = \ extensions/smart_ban.hpp \ extensions/ut_metadata.hpp \ extensions/ut_pex.hpp \ + extensions/i2p_pex.hpp \ \ kademlia/announce_flags.hpp \ kademlia/dht_observer.hpp \ diff --git a/include/libtorrent/aux_/bt_peer_connection.hpp b/include/libtorrent/aux_/bt_peer_connection.hpp index a03c9d3d04b..e7fc1435e5f 100644 --- a/include/libtorrent/aux_/bt_peer_connection.hpp +++ b/include/libtorrent/aux_/bt_peer_connection.hpp @@ -79,10 +79,12 @@ namespace libtorrent::aux { // metadata_msg = 2, upload_only_msg = 3, holepunch_msg = 4, - // recommend_msg = 5, - // comment_msg = 6, + // i2p_pex_msg = 5, dont_have_msg = 7, share_mode_msg = 8 + + // recommend_msg = x, + // comment_msg = x, }; ~bt_peer_connection() override; diff --git a/include/libtorrent/aux_/peer.hpp b/include/libtorrent/aux_/peer.hpp index 423cad28a2b..bcbab353b58 100644 --- a/include/libtorrent/aux_/peer.hpp +++ b/include/libtorrent/aux_/peer.hpp @@ -44,6 +44,7 @@ namespace libtorrent::aux { }; #if TORRENT_USE_I2P + // TODO: it seems unnecessary to wrap this i2p address in a struct struct i2p_peer_entry { sha256_hash destination; diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 0153184c96f..2a326ae050d 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -708,6 +708,7 @@ namespace aux { #endif #if TORRENT_USE_I2P + i2p_connection& i2p_conn() override { return m_i2p_conn; } char const* i2p_session() const override { return m_i2p_conn.session_id(); } std::string const& local_i2p_endpoint() const override { return m_i2p_conn.local_endpoint(); } diff --git a/include/libtorrent/aux_/session_interface.hpp b/include/libtorrent/aux_/session_interface.hpp index 586d45a4ac7..952753f8be4 100644 --- a/include/libtorrent/aux_/session_interface.hpp +++ b/include/libtorrent/aux_/session_interface.hpp @@ -190,6 +190,7 @@ namespace libtorrent::aux { virtual proxy_settings proxy() const = 0; #if TORRENT_USE_I2P + virtual i2p_connection& i2p_conn() = 0; virtual char const* i2p_session() const = 0; virtual std::string const& local_i2p_endpoint() const = 0; #endif diff --git a/include/libtorrent/aux_/torrent.hpp b/include/libtorrent/aux_/torrent.hpp index 57a2b89f285..677327e62e9 100644 --- a/include/libtorrent/aux_/torrent.hpp +++ b/include/libtorrent/aux_/torrent.hpp @@ -921,7 +921,8 @@ namespace libtorrent::aux { void completed(); #if TORRENT_USE_I2P - void on_i2p_resolve(error_code const& ec, char const* dest); + void on_i2p_resolve(error_code const& ec, char const* dest, peer_source_flags_t const source); + void add_i2p_peer(sha256_hash const& dest, peer_source_flags_t source); bool is_i2p() const { return m_i2p; } #endif diff --git a/include/libtorrent/aux_/torrent_peer.hpp b/include/libtorrent/aux_/torrent_peer.hpp index 7d393ba4ac6..f28ab4da55f 100644 --- a/include/libtorrent/aux_/torrent_peer.hpp +++ b/include/libtorrent/aux_/torrent_peer.hpp @@ -219,6 +219,8 @@ namespace libtorrent::aux { i2p_peer(i2p_peer&&) = default; i2p_peer& operator=(i2p_peer&&) & = default; + // TODO: instead of keeping this as a string, make it a (dense) + // sha256_hash aux::string_ptr destination; }; #endif diff --git a/include/libtorrent/extensions/i2p_pex.hpp b/include/libtorrent/extensions/i2p_pex.hpp new file mode 100644 index 00000000000..071c7ba486b --- /dev/null +++ b/include/libtorrent/extensions/i2p_pex.hpp @@ -0,0 +1,37 @@ +/* + +Copyright (c) 2025, 2019-2021, Arvid Norberg +All rights reserved. + +You may use, distribute and modify this code under the terms of the BSD license, +see LICENSE file. +*/ + +#ifndef TORRENT_I2P_PEX_EXTENSION_HPP_INCLUDED +#define TORRENT_I2P_PEX_EXTENSION_HPP_INCLUDED + +#ifndef TORRENT_DISABLE_EXTENSIONS +#if TORRENT_USE_I2P + +#include "libtorrent/config.hpp" + +#include + +namespace libtorrent { + + struct torrent_plugin; + struct torrent_handle; + struct client_data_t; + + // The i2p_pex extension gossips i2p peer addresses, only on i2p torrents. + // The extension will not activate for non-i2p torrents. + // + // This can either be passed in the add_torrent_params::extensions field, or + // via torrent_handle::add_extension(). + TORRENT_EXPORT std::shared_ptr create_i2p_pex_plugin(torrent_handle const&, client_data_t); +} + +#endif +#endif // TORRENT_DISABLE_EXTENSIONS + +#endif // TORRENT_I2P_PEX_EXTENSION_HPP_INCLUDED diff --git a/include/libtorrent/i2p_stream.hpp b/include/libtorrent/i2p_stream.hpp index 8904c5c1bc3..f9b7607a09e 100644 --- a/include/libtorrent/i2p_stream.hpp +++ b/include/libtorrent/i2p_stream.hpp @@ -454,7 +454,7 @@ struct i2p_stream : aux::proxy_base state_t m_state; }; -class i2p_connection +class TORRENT_EXTRA_EXPORT i2p_connection { public: explicit i2p_connection(io_context& ios); diff --git a/include/libtorrent/libtorrent.hpp b/include/libtorrent/libtorrent.hpp index 1d6a027b67a..58322b091bd 100644 --- a/include/libtorrent/libtorrent.hpp +++ b/include/libtorrent/libtorrent.hpp @@ -23,6 +23,7 @@ #include "libtorrent/error.hpp" #include "libtorrent/error_code.hpp" #include "libtorrent/extensions.hpp" +#include "libtorrent/extensions/i2p_pex.hpp" #include "libtorrent/extensions/smart_ban.hpp" #include "libtorrent/extensions/ut_metadata.hpp" #include "libtorrent/extensions/ut_pex.hpp" diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp index fed1864a185..397a82e415c 100644 --- a/include/libtorrent/torrent_info.hpp +++ b/include/libtorrent/torrent_info.hpp @@ -456,6 +456,8 @@ TORRENT_VERSION_NAMESPACE_3 // or not it has a tracker whose URL domain name ends with ".i2p". i2p // torrents disable the DHT and local peer discovery as well as talking // to peers over anything other than the i2p network. + // This is not reliably set for torrents created via resume data or + // magnet links. Prever using torrent::is_i2p() instead. bool is_i2p() const { return bool(m_flags & i2p); } // internal diff --git a/src/i2p_pex.cpp b/src/i2p_pex.cpp new file mode 100644 index 00000000000..b1ed2607753 --- /dev/null +++ b/src/i2p_pex.cpp @@ -0,0 +1,436 @@ +/* + +Copyright (c) 2025, Arvid Norberg +All rights reserved. + +You may use, distribute and modify this code under the terms of the BSD license, +see LICENSE file. +*/ + +#include "libtorrent/config.hpp" + +#ifndef TORRENT_DISABLE_EXTENSIONS +#if TORRENT_USE_I2P + +#include "libtorrent/aux_/peer_connection.hpp" +#include "libtorrent/aux_/bt_peer_connection.hpp" +#include "libtorrent/aux_/array.hpp" +#include "libtorrent/peer_connection_handle.hpp" +#include "libtorrent/bencode.hpp" +#include "libtorrent/hasher.hpp" +#include "libtorrent/aux_/torrent.hpp" +#include "libtorrent/extensions.hpp" +#include "libtorrent/aux_/socket_io.hpp" +#include "libtorrent/peer_info.hpp" +#include "libtorrent/aux_/socket_type.hpp" // for is_utp +#include "libtorrent/performance_counters.hpp" // for counters +#include "libtorrent/extensions/i2p_pex.hpp" +#include "libtorrent/aux_/time.hpp" + +namespace libtorrent { namespace { + + const char extension_name[] = "i2p_pex"; + + enum + { + extension_index = 9, + max_peer_entries = 50 + }; + + bool include_peer(aux::peer_connection const& p) + { + // don't send out those peers that we haven't connected to + // (that have connected to us) and that aren't sharing their + // listening port + if (!p.is_outgoing() && !p.received_listen_port()) return false; + // don't send out peers that we haven't successfully connected to + if (p.is_connecting()) return false; + if (p.in_handshake()) return false; + // filter non-i2p peers. We may have them if we allow mixed-mode + if (!is_i2p(p.get_socket())) return false; + return true; + } + + struct i2p_pex_plugin final + : torrent_plugin + { + // randomize when we rebuild the pex message + // to evenly spread it out across all torrents + // the more torrents we have, the longer we can + // delay the rebuilding + explicit i2p_pex_plugin(aux::torrent& t) + : m_torrent(t) + , m_last_msg(min_time()) {} + + // explicitly disallow assignment, to silence msvc warning + i2p_pex_plugin& operator=(i2p_pex_plugin const&) = delete; + + std::shared_ptr new_connection( + peer_connection_handle const& pc) override; + + std::vector& get_i2p_pex_msg() + { + return m_i2p_pex_msg; + } + + int peers_in_msg() const + { + return m_peers_in_message; + } + + // the second tick of the torrent + // each minute the new lists of "added" + "added.f" and "dropped" + // are calculated here and the pex message is created + // each peer connection will use this message + // max_peer_entries limits the packet size + void tick() override + { + if (m_torrent.flags() & torrent_flags::disable_pex) return; + + time_point const now = aux::time_now(); + if (now - seconds(60) < m_last_msg) return; + m_last_msg = now; + + if (m_torrent.num_peers() == 0) return; + + entry pex; + std::string& pla = pex["added"].string(); + std::string& pld = pex["dropped"].string(); + std::string& plf = pex["added.f"].string(); + std::back_insert_iterator pla_out(pla); + std::back_insert_iterator pld_out(pld); + std::back_insert_iterator plf_out(plf); + + std::set dropped; + m_old_peers.swap(dropped); + + m_peers_in_message = 0; + int num_added = 0; + for (auto const* peer : m_torrent) + { + if (!include_peer(*peer)) continue; + + auto const* pi = peer->peer_info_struct(); + sha256_hash const remote = hasher256(base64decode_i2p(pi->dest())).final(); + m_old_peers.insert(remote); + + auto const di = dropped.find(remote); + if (di == dropped.end()) + { + // don't write too big of a package + if (num_added >= max_peer_entries) break; + + // i->first was added since the last time + std::copy(remote.begin(), remote.end(), pla_out); + // none of the normal ut_pex flags apply to i2p peers, so we + // just send 0 + aux::write_uint8(0, plf_out); + ++num_added; + ++m_peers_in_message; + } + else + { + // this was in the previous message + // so, it wasn't dropped + dropped.erase(di); + } + } + + for (auto const& i : dropped) + { + std::copy(i.begin(), i.end(), pld_out); + ++m_peers_in_message; + } + + m_i2p_pex_msg.clear(); + bencode(std::back_inserter(m_i2p_pex_msg), pex); + } + + private: + aux::torrent& m_torrent; + + std::set m_old_peers; + time_point m_last_msg; + std::vector m_i2p_pex_msg; + int m_peers_in_message = 0; + }; + + struct i2p_pex_peer_plugin final + : peer_plugin + { + i2p_pex_peer_plugin(aux::torrent& t, aux::peer_connection& pc, i2p_pex_plugin& tp) + : m_torrent(t) + , m_pc(pc) + , m_tp(tp) + , m_last_msg(min_time()) + { + for (auto& e : m_last_pex) { + e = min_time(); + } + } + + // explicitly disallow assignment, to silence msvc warning + i2p_pex_peer_plugin& operator=(i2p_pex_peer_plugin const&) = delete; + + void add_handshake(entry& h) override + { + entry& messages = h["m"]; + messages[extension_name] = extension_index; + } + + bool on_extension_handshake(bdecode_node const& h) override + { + m_message_index = 0; + if (h.type() != bdecode_node::dict_t) return false; + bdecode_node const messages = h.dict_find_dict("m"); + if (!messages) return false; + + int const index = int(messages.dict_find_int_value(extension_name, -1)); + if (index == -1) return false; + m_message_index = index; + return true; + } + + bool on_extended(int const length, int const msg, span body) override + { + if (msg != extension_index) return false; + if (m_message_index == 0) return false; + + if (m_torrent.flags() & torrent_flags::disable_pex) return true; + + if (length > 500 * 1024) + { + m_pc.disconnect(errors::pex_message_too_large, operation_t::bittorrent, peer_connection_interface::peer_error); + return true; + } + + if (int(body.size()) < length) return true; + + time_point const now = aux::time_now(); + if (now - seconds(60) < m_last_pex[0]) + { + // this client appears to be trying to flood us + // with pex messages. Don't allow that. + m_pc.disconnect(errors::too_frequent_pex, operation_t::bittorrent); + return true; + } + + std::copy(m_last_pex.begin()+1, m_last_pex.end(), m_last_pex.begin()); + m_last_pex.back() = now; + + bdecode_node pex_msg; + error_code ec; + int const ret = bdecode(body.begin(), body.end(), pex_msg, ec); + if (ret != 0 || pex_msg.type() != bdecode_node::dict_t) + { + m_pc.disconnect(errors::invalid_pex_message, operation_t::bittorrent, peer_connection_interface::peer_error); + return true; + } + + // we ignore the "dropped" field, because we don't need the + // ut_pex_peer_store and was_introduced_by() for i2p + // we also ignore the "added.f" (flags) field, since we don't have + // any flags that apply to i2p peers (yet). + + bdecode_node p = pex_msg.dict_find_string("added"); + + bool peers_added = false; +#ifndef TORRENT_DISABLE_LOGGING + int num_added = 0; +#endif + if (p) + { + int const num_peers = p.string_length() / 32; + char const* in = p.string_ptr(); + +#ifndef TORRENT_DISABLE_LOGGING + num_added = num_peers; +#endif + + for (int i = 0; i < num_peers; ++i) + { + sha256_hash remote; + std::copy(in, in + 32, remote.begin()); + in += 32; + m_torrent.add_i2p_peer(remote, peer_info::pex); + peers_added = true; + } + } + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::incoming_message, "I2P_PEX", "added: %d" + , num_added); +#endif + + m_pc.stats_counters().inc_stats_counter(counters::num_incoming_pex); + + if (peers_added) m_torrent.do_connect_boost(); + return true; + } + + // the peers second tick + // every minute we send a pex message + void tick() override + { + // no handshake yet + if (!m_message_index) return; + + time_point const now = aux::time_now(); + if (now - seconds(60) < m_last_msg) return; + int const num_peers = m_torrent.num_peers(); + if (num_peers <= 1) return; + + m_last_msg = now; + + if (m_first_time) + { + send_i2p_peer_list(); + m_first_time = false; + } + else + { + send_i2p_peer_diff(); + } + } + + void send_i2p_peer_diff() + { + if (m_torrent.flags() & torrent_flags::disable_pex) return; + + // if there's no change in our peer set, don't send anything + if (m_tp.peers_in_msg() == 0) return; + + std::vector const& pex_msg = m_tp.get_i2p_pex_msg(); + + char msg[6]; + char* ptr = msg; + + aux::write_uint32(1 + 1 + int(pex_msg.size()), ptr); + aux::write_uint8(aux::bt_peer_connection::msg_extended, ptr); + aux::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg); + m_pc.send_buffer(pex_msg); + + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended); + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_pex); + +#ifndef TORRENT_DISABLE_LOGGING + if (m_pc.should_log(peer_log_alert::outgoing_message)) + { + bdecode_node m; + error_code ec; + int const ret = bdecode(&pex_msg[0], &pex_msg[0] + pex_msg.size(), m, ec); + TORRENT_ASSERT(ret == 0); + TORRENT_ASSERT(!ec); + TORRENT_UNUSED(ret); + int num_dropped = 0; + int num_added = 0; + bdecode_node e = m.dict_find_string("added"); + if (e) num_added += e.string_length() / 6; + e = m.dict_find_string("dropped"); + if (e) num_dropped += e.string_length() / 6; + m_pc.peer_log(peer_log_alert::outgoing_message, "I2P_PEX_DIFF", "dropped: %d added: %d msg_size: %d" + , num_dropped, num_added, int(pex_msg.size())); + } +#endif + } + + void send_i2p_peer_list() + { + if (m_torrent.flags() & torrent_flags::disable_pex) return; + + entry pex; + // leave the dropped string empty + pex["dropped"].string(); + std::string& pla = pex["added"].string(); + std::string& plf = pex["added.f"].string(); + std::back_insert_iterator pla_out(pla); + std::back_insert_iterator plf_out(plf); + + int num_added = 0; + for (auto const* peer : m_torrent) + { + if (!include_peer(*peer)) continue; + TORRENT_ASSERT(peer->type() == connection_type::bittorrent); + + // don't write too big of a package + if (num_added >= max_peer_entries) break; + + auto const* pi = peer->peer_info_struct(); + sha256_hash const remote = hasher256(base64decode_i2p(pi->dest())).final(); + + std::copy(remote.begin(), remote.end(), pla_out); + aux::write_uint8(0, plf_out); + ++num_added; + } + std::vector pex_msg; + bencode(std::back_inserter(pex_msg), pex); + + char msg[6]; + char* ptr = msg; + + aux::write_uint32(1 + 1 + int(pex_msg.size()), ptr); + aux::write_uint8(aux::bt_peer_connection::msg_extended, ptr); + aux::write_uint8(m_message_index, ptr); + m_pc.send_buffer(msg); + m_pc.send_buffer(pex_msg); + + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended); + m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_pex); + +#ifndef TORRENT_DISABLE_LOGGING + m_pc.peer_log(peer_log_alert::outgoing_message, "I2P_PEX_FULL" + , "added: %d msg_size: %d", num_added, int(pex_msg.size())); +#endif + } + + aux::torrent& m_torrent; + aux::peer_connection& m_pc; + i2p_pex_plugin& m_tp; + + // the last pex messages we received + // [0] is the oldest one. There is a problem with + // rate limited connections, because we may sit + // for a long time, accumulating pex messages, and + // then once we read from the socket it will look like + // we received them all back to back. That's why + // we look at 6 pex messages back. + // TODO: factor this out into "sliding window"? It's shared with ut_pex + aux::array m_last_pex; + + time_point m_last_msg; + int m_message_index = 0; + + // this is initialized to true, and set to + // false after the first pex message has been sent. + // it is used to know if a diff message or a) ful + // message should be sent. + bool m_first_time = true; + }; + + std::shared_ptr i2p_pex_plugin::new_connection(peer_connection_handle const& pc) + { + if (pc.type() != connection_type::bittorrent) return {}; + aux::bt_peer_connection* c = static_cast(pc.native_handle().get()); + + // this extension is only for i2p peer connections + if (!is_i2p(c->get_socket())) return {}; + + return std::make_shared(m_torrent, *c, *this); + } +} } + +namespace libtorrent { + + std::shared_ptr create_i2p_pex_plugin(torrent_handle const& th, client_data_t) + { + aux::torrent* t = th.native_handle().get(); + // only add extension to i2p torrents + if (t->torrent_file().priv() || !t->is_i2p()) + return {}; + return std::make_shared(*t); + } +} + +#endif +#endif diff --git a/src/session_impl.cpp b/src/session_impl.cpp index d95e9e069c0..a92f5ba7195 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -5620,7 +5620,7 @@ namespace { std::shared_ptr t = find_torrent(info_hash_t(ih)).lock(); if (!t) return; // don't add peers from lsd to private torrents - if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + if (t->torrent_file().priv() || (t->is_i2p() && !m_settings.get_bool(settings_pack::allow_i2p_mixed))) return; protocol_version const v = ih == t->torrent_file().info_hashes().v1 diff --git a/src/session_params.cpp b/src/session_params.cpp index 66b13933053..ff3b506cde7 100644 --- a/src/session_params.cpp +++ b/src/session_params.cpp @@ -12,6 +12,7 @@ see LICENSE file. #include "libtorrent/session_params.hpp" #include "libtorrent/aux_/session_impl.hpp" +#include "libtorrent/extensions/i2p_pex.hpp" #include "libtorrent/extensions/ut_pex.hpp" #include "libtorrent/extensions/ut_metadata.hpp" #include "libtorrent/extensions/smart_ban.hpp" @@ -29,7 +30,10 @@ std::vector> default_plugins( return { std::make_shared(create_ut_pex_plugin), std::make_shared(create_ut_metadata_plugin), - std::make_shared(create_smart_ban_plugin) + std::make_shared(create_smart_ban_plugin), +#if TORRENT_USE_I2P + std::make_shared(create_i2p_pex_plugin) +#endif }; #else TORRENT_UNUSED(empty); diff --git a/src/torrent.cpp b/src/torrent.cpp index 74e68430b09..9cc8559aaff 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -3627,10 +3627,13 @@ namespace { ADD_OUTSTANDING_ASYNC("torrent::on_i2p_resolve"); r.i2pconn->async_name_lookup(i.hostname.c_str() , [self = shared_from_this()] (error_code const& ec, char const* dest) - { self->torrent::on_i2p_resolve(ec, dest); }); + { self->torrent::on_i2p_resolve(ec, dest, peer_info::tracker); }); } else { + // TODO: the destination string should be base64 decoded and + // sha256 hashed to form the destination address and store + // that in the i2p_peer object directly torrent_state st = get_peer_list_state(); need_peer_list(); if (m_peer_list->add_i2p_peer(i.hostname, peer_info::tracker, {}, &st)) @@ -3652,15 +3655,7 @@ namespace { { for (auto const& i : resp.i2p_peers) { - torrent_state st = get_peer_list_state(); - peer_entry p; - std::string destination = base32encode_i2p(i.destination); - destination += ".b32.i2p"; - - ADD_OUTSTANDING_ASYNC("torrent::on_i2p_resolve"); - r.i2pconn->async_name_lookup(destination.c_str() - , [self = shared_from_this()] (error_code const& ec, char const* dest) - { self->torrent::on_i2p_resolve(ec, dest); }); + add_i2p_peer(i.destination, peer_info::tracker); } } #endif @@ -3923,7 +3918,25 @@ namespace { #endif #if TORRENT_USE_I2P - void torrent::on_i2p_resolve(error_code const& ec, char const* dest) try + void torrent::add_i2p_peer(sha256_hash const& dest, peer_source_flags_t const source) + { + // TODO: store i2p addresses as a 32 byte sha256 hash in the + // i2p_peer object, rather than resolving it into the full vase64 + // destination. We should keep the full destination as a cache in the + // i2p_peer object still. + i2p_connection& i2pconn = session().i2p_conn(); + if (!i2pconn.is_open()) return; + + std::string destination = base32encode_i2p(dest); + destination += ".b32.i2p"; + + ADD_OUTSTANDING_ASYNC("torrent::on_i2p_resolve"); + i2pconn.async_name_lookup(destination.c_str() + , [self = shared_from_this(), source] (error_code const& ec, char const* d) + { self->torrent::on_i2p_resolve(ec, d, source); }); + } + + void torrent::on_i2p_resolve(error_code const& ec, char const* dest, peer_source_flags_t const source) try { TORRENT_ASSERT(is_single_thread()); @@ -3938,7 +3951,7 @@ namespace { need_peer_list(); torrent_state st = get_peer_list_state(); - if (m_peer_list->add_i2p_peer(dest, peer_info::tracker, {}, &st)) + if (m_peer_list->add_i2p_peer(dest, source, {}, &st)) state_updated(); peers_erased(st.erased); diff --git a/src/ut_pex.cpp b/src/ut_pex.cpp index e5e857da95f..802ac581d5d 100644 --- a/src/ut_pex.cpp +++ b/src/ut_pex.cpp @@ -48,6 +48,7 @@ namespace libtorrent { namespace { // don't send out peers that we haven't successfully connected to if (p.is_connecting()) return false; if (p.in_handshake()) return false; + if (p.type() != connection_type::bittorrent) return false; return true; } @@ -125,10 +126,7 @@ namespace libtorrent { namespace { // don't write too big of a package if (num_added >= max_peer_entries) break; - // only send proper bittorrent peers - if (peer->type() != connection_type::bittorrent) - continue; - + TORRENT_ASSERT(peer->type() == connection_type::bittorrent); auto const* const p = static_cast(peer); // if the peer has told us which port its listening on, @@ -434,7 +432,7 @@ namespace libtorrent { namespace { { if (m_torrent.flags() & torrent_flags::disable_pex) return; - // if there's no change in out peer set, don't send anything + // if there's no change in our peer set, don't send anything if (m_tp.peers_in_msg() == 0) return; std::vector const& pex_msg = m_tp.get_ut_pex_msg(); @@ -502,10 +500,7 @@ namespace libtorrent { namespace { // don't write too big of a package if (num_added >= max_peer_entries) break; - // only send proper bittorrent peers - if (peer->type() != connection_type::bittorrent) - continue; - + TORRENT_ASSERT(peer->type() == connection_type::bittorrent); auto const* const p = static_cast(peer); // no supported flags to set yet @@ -607,7 +602,7 @@ namespace libtorrent { std::shared_ptr create_ut_pex_plugin(torrent_handle const& th, client_data_t) { aux::torrent* t = th.native_handle().get(); - if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + if (t->torrent_file().priv() || (t->is_i2p() && !t->settings().get_bool(settings_pack::allow_i2p_mixed))) { return {}; diff --git a/test/session_mock.hpp b/test/session_mock.hpp index ef98d4ee2c7..9fc3ae48bc7 100644 --- a/test/session_mock.hpp +++ b/test/session_mock.hpp @@ -20,6 +20,9 @@ see LICENSE file. #include "libtorrent/aux_/torrent_peer_allocator.hpp" #include "libtorrent/ip_filter.hpp" #include "libtorrent/peer_class.hpp" +#if TORRENT_USE_I2P +#include "libtorrent/i2p_stream.hpp" +#endif #if TORRENT_USE_SSL #include "libtorrent/aux_/ssl.hpp" @@ -157,6 +160,11 @@ struct session_mock : aux::session_interface return empty; } char const* i2p_session() const override { return nullptr; } + i2p_connection& i2p_conn() override + { + static i2p_connection dummy(_io_context); + return dummy; + } #endif #ifndef TORRENT_DISABLE_DHT diff --git a/test/test_peer_priority.cpp b/test/test_peer_priority.cpp index 7c7abf1ac95..c0dea6e0f8d 100644 --- a/test/test_peer_priority.cpp +++ b/test/test_peer_priority.cpp @@ -10,6 +10,7 @@ see LICENSE file. #include "libtorrent/aux_/peer_list.hpp" #include "setup_transfer.hpp" // for supports_ipv6() +#include "libtorrent/hex.hpp" #include "libtorrent/aux_/disable_warnings_push.hpp" #include #include "libtorrent/aux_/disable_warnings_pop.hpp" diff --git a/test/test_session_params.cpp b/test/test_session_params.cpp index 5b5c2631dfc..b164ea82711 100644 --- a/test/test_session_params.cpp +++ b/test/test_session_params.cpp @@ -60,7 +60,12 @@ TORRENT_TEST(default_plugins) { session_params p1; #ifndef TORRENT_DISABLE_EXTENSIONS +#if TORRENT_USE_I2P + // this also has i2p_pex + TEST_EQUAL(int(p1.extensions.size()), 4); +#else TEST_EQUAL(int(p1.extensions.size()), 3); +#endif #else TEST_EQUAL(int(p1.extensions.size()), 0); #endif