diff --git a/CMakeLists.txt b/CMakeLists.txt index e3dc6dbac..154355a5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(sources http_connection http_stream http_parser + i2p_stream identify_client ip_filter peer_connection diff --git a/ChangeLog b/ChangeLog index 878595203..a0e582be8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ + * added support for i2p torrents + 0.15 release * reduced the number of floating point operations (to better support diff --git a/Jamfile b/Jamfile index 73922fd33..713b99643 100755 --- a/Jamfile +++ b/Jamfile @@ -358,6 +358,7 @@ SOURCES = bt_peer_connection web_peer_connection http_seed_connection + i2p_stream instantiate_connection natpmp piece_picker diff --git a/docs/manual.rst b/docs/manual.rst index 2cc08920f..e5fb9aea8 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -1042,6 +1042,21 @@ These functions returns references to their respective current settings. The ``dht_proxy`` is not available when DHT is disabled. +set_i2p_proxy() i2p_proxy() +--------------------------- + + :: + + void set_i2p_proxy(proxy_settings const&); + proxy_settings const& i2p_proxy(); + +``set_i2p_proxy`` sets the i2p_ proxy, and tries to open a persistant +connection to it. The only used fields in the proxy settings structs +are ``hostname`` and ``port``. + +``i2p_proxy`` returns the current i2p proxy in use. + + start_dht() stop_dht() set_dht_settings() dht_state() ----------------------------------------------------- diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 7c0ea6968..ed2c2023d 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -747,6 +747,9 @@ int main(int argc, char* argv[]) " -t sets the scan interval of the monitor dir\n" " -x loads an emule IP-filter file\n" " -c sets the max number of connections\n" +#if TORRENT_USE_I2P + " -i the hostname to an I2P SAM bridge to use\n" +#endif " -C sets the max cache size. Specified in 16kB blocks\n" " -F sets the UI refresh rate. This is the number of\n" " seconds between screen refreshes.\n" @@ -759,7 +762,6 @@ int main(int argc, char* argv[]) using namespace libtorrent; session_settings settings; - proxy_settings ps; settings.user_agent = "client_test/" LIBTORRENT_VERSION; settings.auto_upload_slots_rate_based = true; @@ -780,7 +782,8 @@ int main(int argc, char* argv[]) // monitor when they're not in the directory anymore. handles_t handles; session ses(fingerprint("LT", LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) - , session::start_default_features | session::add_default_plugins, alert::all_categories); + , session::add_default_plugins + , alert::all_categories & (~alert::dht_notification)); std::vector in; if (load_file(".ses_state", in) == 0) @@ -950,6 +953,17 @@ int main(int argc, char* argv[]) } break; case 'c': ses.set_max_connections(atoi(arg)); break; +#if TORRENT_USE_I2P + case 'i': + { + proxy_settings ps; + ps.hostname = arg; + ps.port = 7656; // default SAM port + ps.type = proxy_settings::i2p_proxy; + ses.set_i2p_proxy(ps); + break; + } +#endif // TORRENT_USE_I2P case 'C': settings.cache_size = atoi(arg); break; } ++i; // skip the argument diff --git a/include/Makefile.am b/include/Makefile.am index a3dce8c58..67ce6bc94 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -33,6 +33,7 @@ libtorrent/http_connection.hpp \ libtorrent/http_stream.hpp \ libtorrent/http_parser.hpp \ libtorrent/http_tracker_connection.hpp \ +libtorrent/i2p_stream.hpp \ libtorrent/identify_client.hpp \ libtorrent/instantiate_connection.hpp \ libtorrent/intrusive_ptr_base.hpp \ diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 0bfa85ecc..19d484731 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -294,6 +294,20 @@ namespace libtorrent { return m_dht_proxy; } #endif +#if TORRENT_USE_I2P + void set_i2p_proxy(proxy_settings const& s) + { + m_i2p_conn.open(s, boost::bind(&session_impl::on_i2p_open, this, _1)); + open_new_incoming_i2p_connection(); + } + void on_i2p_open(error_code const& ec); + proxy_settings const& i2p_proxy() const + { return m_i2p_conn.proxy(); } + void open_new_incoming_i2p_connection(); + void on_i2p_accept(boost::shared_ptr const& s + , error_code const& e); +#endif + #ifndef TORRENT_DISABLE_GEO_IP std::string as_name_for_ip(address const& a); int as_for_ip(address const& a); @@ -380,15 +394,22 @@ namespace libtorrent }; boost::object_pool< policy::ipv4_peer, logging_allocator> m_ipv4_peer_pool; -# if TORRENT_USE_IPV6 +#if TORRENT_USE_IPV6 boost::object_pool< policy::ipv6_peer, logging_allocator> m_ipv6_peer_pool; -# endif +#endif +#if TORRENT_USE_I2P + boost::object_pool< + policy::i2p_peer, logging_allocator> m_i2p_peer_pool; +#endif #else boost::object_pool m_ipv4_peer_pool; -# if TORRENT_USE_IPV6 +#if TORRENT_USE_IPV6 boost::object_pool m_ipv6_peer_pool; -# endif +#endif +#if TORRENT_USE_I2P + boost::object_pool m_i2p_peer_pool; +#endif #endif // this vector is used to store the block_info @@ -523,6 +544,11 @@ namespace libtorrent void open_new_incoming_socks_connection(); +#if TORRENT_USE_I2P + i2p_connection m_i2p_conn; + boost::shared_ptr m_i2p_listen_socket; +#endif + listen_socket_t setup_listener(tcp::endpoint ep, int retries, bool v6_only = false); // the settings for the client diff --git a/include/libtorrent/config.hpp b/include/libtorrent/config.hpp index 029753191..214394254 100644 --- a/include/libtorrent/config.hpp +++ b/include/libtorrent/config.hpp @@ -115,6 +115,8 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_USE_WRITEV 1 #define TORRENT_USE_IOSTREAM 1 +#define TORRENT_USE_I2P 1 + // should wpath or path be used? #if defined UNICODE && !defined BOOST_FILESYSTEM_NARROW_ONLY \ && BOOST_VERSION >= 103400 && !defined __APPLE__ diff --git a/include/libtorrent/error_code.hpp b/include/libtorrent/error_code.hpp index f2ddacf99..11f9758d8 100644 --- a/include/libtorrent/error_code.hpp +++ b/include/libtorrent/error_code.hpp @@ -189,6 +189,8 @@ namespace libtorrent reserved6, reserved7, reserved8, + + no_i2p_router, }; } diff --git a/include/libtorrent/escape_string.hpp b/include/libtorrent/escape_string.hpp index 4590cfb9a..66475a48b 100644 --- a/include/libtorrent/escape_string.hpp +++ b/include/libtorrent/escape_string.hpp @@ -63,6 +63,7 @@ namespace libtorrent // encodes a string using the base64 scheme TORRENT_EXPORT std::string base64encode(std::string const& s); + TORRENT_EXPORT std::string base64decode(std::string const& s); // encodes a string using the base32 scheme TORRENT_EXPORT std::string base32encode(std::string const& s); TORRENT_EXPORT std::string base32decode(std::string const& s); diff --git a/include/libtorrent/http_connection.hpp b/include/libtorrent/http_connection.hpp index 9bf9727c5..eba930a5f 100644 --- a/include/libtorrent/http_connection.hpp +++ b/include/libtorrent/http_connection.hpp @@ -54,6 +54,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/variant_stream.hpp" #endif +#include "libtorrent/i2p_stream.hpp" + namespace libtorrent { @@ -108,12 +110,20 @@ struct http_connection : boost::enable_shared_from_this, boost: void get(std::string const& url, time_duration timeout = seconds(30) , int prio = 0, proxy_settings const* ps = 0, int handle_redirects = 5 - , std::string const& user_agent = "", address const& bind_addr = address_v4::any()); + , std::string const& user_agent = "", address const& bind_addr = address_v4::any() +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = 0 +#endif + ); void start(std::string const& hostname, std::string const& port , time_duration timeout, int prio = 0, proxy_settings const* ps = 0 , bool ssl = false, int handle_redirect = 5 - , address const& bind_addr = address_v4::any()); + , address const& bind_addr = address_v4::any() +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = 0 +#endif + ); void close(); @@ -127,6 +137,10 @@ struct http_connection : boost::enable_shared_from_this, boost: private: +#if TORRENT_USE_I2P + void on_i2p_resolve(error_code const& e + , char const* destination); +#endif void on_resolve(error_code const& e , tcp::resolver::iterator i); void queue_connect(); @@ -146,6 +160,9 @@ private: variant_stream > m_sock; #else socket_type m_sock; +#endif +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; #endif int m_read_pos; tcp::resolver m_resolver; diff --git a/include/libtorrent/http_tracker_connection.hpp b/include/libtorrent/http_tracker_connection.hpp index 47d7a1ac4..d970b509f 100644 --- a/include/libtorrent/http_tracker_connection.hpp +++ b/include/libtorrent/http_tracker_connection.hpp @@ -48,6 +48,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/peer_id.hpp" #include "libtorrent/tracker_manager.hpp" #include "libtorrent/config.hpp" +#include "libtorrent/i2p_stream.hpp" namespace libtorrent { @@ -73,7 +74,11 @@ namespace libtorrent , boost::weak_ptr c , aux::session_impl const& ses , proxy_settings const& ps - , std::string const& password = ""); + , std::string const& password = "" +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn = 0 +#endif + ); void start(); void close(); @@ -100,6 +105,9 @@ namespace libtorrent proxy_settings const& m_ps; connection_queue& m_cc; io_service& m_ios; +#if TORRENT_USE_I2P + i2p_connection* m_i2p_conn; +#endif }; } diff --git a/include/libtorrent/i2p_stream.hpp b/include/libtorrent/i2p_stream.hpp new file mode 100644 index 000000000..38564655d --- /dev/null +++ b/include/libtorrent/i2p_stream.hpp @@ -0,0 +1,217 @@ +/* + +Copyright (c) 2009, 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 TORRENT_I2P_STREAM_HPP_INCLUDED +#define TORRENT_I2P_STREAM_HPP_INCLUDED + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_I2P + +#include "libtorrent/proxy_base.hpp" +#include "libtorrent/session_settings.hpp" + +namespace libtorrent { + + namespace i2p_error { + + enum i2p_error_code + { + no_error = 0, + parse_failed, + cant_reach_peer, + i2p_error, + invalid_key, + invalid_id, + timeout, + key_not_found, + num_errors + }; + } + +struct TORRENT_EXPORT i2p_error_category : boost::system::error_category +{ + virtual const char* name() const; + virtual std::string message(int ev) const; + virtual boost::system::error_condition default_error_condition(int ev) const + { return boost::system::error_condition(ev, *this); } +}; + +extern i2p_error_category i2p_category; + +class i2p_stream : public proxy_base +{ +public: + + explicit i2p_stream(io_service& io_service) + : proxy_base(io_service) + , m_id(0) + , m_command(cmd_create_session) + , m_state(0) + {} + + enum command_t + { + cmd_none, + cmd_create_session, + cmd_connect, + cmd_accept, + cmd_name_lookup, + cmd_incoming + }; + + void set_command(command_t c) { m_command = c; } + + void set_session_id(char const* id) { m_id = id; } + + void set_destination(std::string const& d) { m_dest = d; } + std::string const& destination() { return m_dest; } + + typedef boost::function handler_type; + + template + void async_connect(endpoint_type const& endpoint, Handler const& handler) + { + // since we don't support regular endpoints, just ignore the one + // provided and use m_dest. + + // the connect is split up in the following steps: + // 1. resolve name of proxy server + // 2. connect to SAM bridge + // 4 send command message (CONNECT/ACCEPT) + + // to avoid unnecessary copying of the handler, + // store it in a shaed_ptr + boost::shared_ptr h(new handler_type(handler)); + + tcp::resolver::query q(m_hostname + , boost::lexical_cast(m_port)); + m_resolver.async_resolve(q, boost::bind( + &i2p_stream::do_connect, this, _1, _2, h)); + } + + std::string name_lookup() const { return m_name_lookup; } + void set_name_lookup(char const* name) { m_name_lookup = name; } + + void send_name_lookup(boost::shared_ptr h); + +private: + + bool handle_error(error_code const& e, boost::shared_ptr const& h); + void do_connect(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h); + void connected(error_code const& e, boost::shared_ptr h); + void start_read_line(error_code const& e, boost::shared_ptr h); + void read_line(error_code const& e, boost::shared_ptr h); + void send_connect(boost::shared_ptr h); + void send_accept(boost::shared_ptr h); + void send_session_create(boost::shared_ptr h); + + // send and receive buffer + std::vector m_buffer; + char const* m_id; + int m_command; // 0 = connect, 1 = accept + std::string m_dest; + std::string m_name_lookup; + + enum state_t + { + read_hello_response, + read_connect_response, + read_accept_response, + read_session_create_response, + read_name_lookup_response, + }; + + int m_state; +}; + +class i2p_connection +{ +public: + i2p_connection(io_service& ios); + ~i2p_connection(); + + proxy_settings const& proxy() const { return m_sam_router; } + + bool is_open() const + { + return m_sam_socket + && m_sam_socket->is_open() + && m_state != sam_connecting; + } + void open(proxy_settings const& s, i2p_stream::handler_type const& h); + void close(); + + char const* session_id() const { return m_session_id.c_str(); } + std::string const& local_endpoint() const { return m_i2p_local_endpoint; } + + typedef boost::function name_lookup_handler; + void async_name_lookup(char const* name, name_lookup_handler handler); + +private: + + void on_sam_connect(error_code const& ec, i2p_stream::handler_type const& h); + void do_name_lookup(std::string const& name + , name_lookup_handler const& h); + void on_name_lookup(error_code const& ec + , name_lookup_handler handler); + + void set_local_endpoint(error_code const& ec, char const* dest); + + // to talk to i2p SAM bridge + boost::shared_ptr m_sam_socket; + proxy_settings m_sam_router; + + // our i2p endpoint key + std::string m_i2p_local_endpoint; + std::string m_session_id; + + std::list > m_name_lookup; + + enum state_t + { + sam_connecting, + sam_name_lookup, + sam_idle + }; + + state_t m_state; + + io_service& m_io_service; +}; + +} +#endif // TORRENT_USE_I2P + +#endif + diff --git a/include/libtorrent/policy.hpp b/include/libtorrent/policy.hpp index 125485f7d..96e3d9ff8 100644 --- a/include/libtorrent/policy.hpp +++ b/include/libtorrent/policy.hpp @@ -73,6 +73,11 @@ namespace libtorrent void pulse(); struct peer; + +#if TORRENT_USE_I2P + policy::peer* add_i2p_peer(char const* destination, int source, char flags); +#endif + // this is called once for every peer we get from // the tracker, pex, lsd or dht. policy::peer* add_peer(const tcp::endpoint& remote, const peer_id& pid @@ -129,6 +134,7 @@ namespace libtorrent size_type total_upload() const; libtorrent::address address() const; + char const* dest() const; tcp::endpoint ip() const { return tcp::endpoint(address(), port); } @@ -172,7 +178,6 @@ namespace libtorrent // in number of seconds since session was created boost::uint16_t last_connected; - // the port this peer is or was connected on boost::uint16_t port; @@ -233,6 +238,10 @@ namespace libtorrent // the one to use, false if it's the v4 one bool is_v6_addr:1; #endif +#if TORRENT_USE_I2P + // set if the i2p_destination is in use in the addr union + bool is_i2p_addr:1; +#endif // if this is true, the peer has previously // participated in a piece that failed the piece @@ -261,6 +270,17 @@ namespace libtorrent address_v4 addr; }; +#if TORRENT_USE_I2P + struct i2p_peer : peer + { + i2p_peer(char const* destination, bool connectable, int src); + i2p_peer(char const* destination); + ~i2p_peer(); + + char* destination; + }; +#endif + #if TORRENT_USE_IPV6 struct ipv6_peer : peer { @@ -287,9 +307,27 @@ namespace libtorrent return lhs < rhs->address(); } +#if TORRENT_USE_I2P + bool operator()( + peer const* lhs, char const* rhs) const + { + return strcmp(lhs->dest(), rhs) < 0; + } + + bool operator()( + char const* lhs, peer const* rhs) const + { + return strcmp(lhs, rhs->dest()) < 0; + } +#endif + bool operator()( peer const* lhs, peer const* rhs) const { +#if TORRENT_USE_I2P + if (rhs->is_i2p_addr == lhs->is_i2p_addr) + return strcmp(lhs->dest(), rhs->dest()) < 0; +#endif return lhs->address() < rhs->address(); } }; @@ -328,6 +366,10 @@ namespace libtorrent private: + void update_peer(policy::peer* p, int src, int flags + , tcp::endpoint const& remote, char const* destination); + bool insert_peer(policy::peer* p, iterator iter, int flags); + bool compare_peer_erase(policy::peer const& lhs, policy::peer const& rhs) const; bool compare_peer(policy::peer const& lhs, policy::peer const& rhs , address const& external_ip) const; @@ -376,15 +418,48 @@ namespace libtorrent : peer(ip.port(), connectable, src) , addr(ip.address().to_v4()) { +#if TORRENT_USE_IPV6 is_v6_addr = false; +#endif +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif } inline policy::ipv4_peer::ipv4_peer(libtorrent::address const& a) : addr(a.to_v4()) { +#if TORRENT_USE_IPV6 is_v6_addr = false; +#endif +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif } +#if TORRENT_USE_I2P + inline policy::i2p_peer::i2p_peer(char const* dest, bool connectable, int src) + : peer(0, connectable, src), destination(strdup(dest)) + { +#if TORRENT_USE_IPV6 + is_v6_addr = false; +#endif + is_i2p_addr = true; + } + inline policy::i2p_peer::i2p_peer(char const* dest) + : destination(strdup(dest)) + { +#if TORRENT_USE_IPV6 + is_v6_addr = false; +#endif + is_i2p_addr = true; + } + + inline policy::i2p_peer::~i2p_peer() + { free(destination); } +#endif // TORRENT_USE_I2P + +#if TORRENT_USE_IPV6 inline policy::ipv6_peer::ipv6_peer( tcp::endpoint const& ip, bool connectable, int src ) @@ -392,20 +467,41 @@ namespace libtorrent , addr(ip.address().to_v6().to_bytes()) { is_v6_addr = true; +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif } inline policy::ipv6_peer::ipv6_peer(libtorrent::address const& a) : addr(a.to_v6().to_bytes()) { is_v6_addr = true; +#if TORRENT_USE_I2P + is_i2p_addr = false; +#endif } +#endif // TORRENT_USE_IPV6 +#if TORRENT_USE_I2P + inline char const* policy::peer::dest() const + { + if (is_i2p_addr) + return static_cast(this)->destination; + return ""; + } +#endif + inline libtorrent::address policy::peer::address() const { #if TORRENT_USE_IPV6 if (is_v6_addr) return libtorrent::address_v6( static_cast(this)->addr); + else +#endif +#if TORRENT_USE_I2P + if (is_i2p_addr) return libtorrent::address(); + else #endif return static_cast(this)->addr; } diff --git a/include/libtorrent/session.hpp b/include/libtorrent/session.hpp index 980d2de59..6afc5ed09 100644 --- a/include/libtorrent/session.hpp +++ b/include/libtorrent/session.hpp @@ -375,6 +375,11 @@ namespace libtorrent proxy_settings const& dht_proxy() const; #endif +#if TORRENT_USE_I2P + void set_i2p_proxy(proxy_settings const& s); + proxy_settings const& i2p_proxy() const; +#endif + int upload_rate_limit() const; int download_rate_limit() const; int local_upload_rate_limit() const; diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index 4d923d263..2723c78a4 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -71,7 +71,9 @@ namespace libtorrent http, // http proxy with basic authentication // uses username and password - http_pw + http_pw, + // route through a i2p SAM proxy + i2p_proxy }; proxy_type type; @@ -176,6 +178,7 @@ namespace libtorrent , write_cache_line_size(32) , optimistic_disk_retry(10 * 60) , disable_hash_checks(false) + , allow_i2p_mixed(false) {} // this is the user agent that will be sent to the tracker @@ -621,6 +624,15 @@ namespace libtorrent // testing purposes (typically combined with // disabled_storage) bool disable_hash_checks; + + // if this is true, i2p torrents are allowed + // to also get peers from other sources than + // the tracker, and connect to regular IPs, + // not providing any anonymization. This may + // be useful if the user is not interested in + // the anonymization of i2p, but still wants to + // be able to connect to i2p peers. + bool allow_i2p_mixed; }; #ifndef TORRENT_DISABLE_DHT diff --git a/include/libtorrent/socket_type.hpp b/include/libtorrent/socket_type.hpp index 01fb3f9be..d4026336a 100644 --- a/include/libtorrent/socket_type.hpp +++ b/include/libtorrent/socket_type.hpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socks5_stream.hpp" #include "libtorrent/http_stream.hpp" +#include "libtorrent/i2p_stream.hpp" #include "libtorrent/variant_stream.hpp" namespace libtorrent @@ -42,7 +43,11 @@ namespace libtorrent typedef variant_stream< stream_socket , socks5_stream - , http_stream> socket_type; + , http_stream +#if TORRENT_USE_I2P + , i2p_stream +#endif + > socket_type; } #endif diff --git a/include/libtorrent/time.hpp b/include/libtorrent/time.hpp index 464dba78f..d0502cb55 100644 --- a/include/libtorrent/time.hpp +++ b/include/libtorrent/time.hpp @@ -105,6 +105,7 @@ namespace libtorrent explicit time_duration(boost::int64_t d) : diff(d) {} time_duration& operator-=(time_duration const& c) { diff -= c.diff; return *this; } time_duration& operator+=(time_duration const& c) { diff += c.diff; return *this; } + time_duration& operator*=(int v) { diff *= v; return *this; } time_duration operator+(time_duration const& c) { return time_duration(diff + c.diff); } time_duration operator-(time_duration const& c) { return time_duration(diff - c.diff); } boost::int64_t diff; diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index ae76b0a05..b730125c4 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -513,6 +513,10 @@ namespace libtorrent // all seeds and let the tracker know we're finished. void completed(); +#if TORRENT_USE_I2P + void on_i2p_resolve(error_code const& ec, char const* dest); +#endif + // this is the asio callback that is called when a name // lookup for a PEER is completed. void on_peer_name_lookup(error_code const& e, tcp::resolver::iterator i diff --git a/include/libtorrent/torrent_info.hpp b/include/libtorrent/torrent_info.hpp index efeb7ee39..9fbfcb790 100644 --- a/include/libtorrent/torrent_info.hpp +++ b/include/libtorrent/torrent_info.hpp @@ -256,6 +256,8 @@ namespace libtorrent bool priv() const { return m_private; } + bool is_i2p() const { return m_i2p; } + int piece_size(int index) const { return m_files.piece_size(index); } sha1_hash hash_for_piece(int index) const @@ -367,6 +369,12 @@ namespace libtorrent // be announced on the dht bool m_private; + // this is true if one of the trackers has an .i2p top + // domain in its hostname. This means the DHT and LSD + // features are disabled for this torrent (unless the + // settings allows mixing i2p peers with regular peers) + bool m_i2p; + // this is a copy of the info section from the torrent. // it use maintained in this flat format in order to // make it available through the metadata extension diff --git a/src/Makefile.am b/src/Makefile.am index 4e20f97c1..205c52677 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,7 +26,7 @@ socks5_stream.cpp http_stream.cpp connection_queue.cpp \ disk_io_thread.cpp ut_metadata.cpp lt_trackers.cpp magnet_uri.cpp udp_socket.cpp smart_ban.cpp \ http_parser.cpp gzip.cpp disk_buffer_holder.cpp create_torrent.cpp GeoIP.c \ parse_url.cpp file_storage.cpp error_code.cpp ConvertUTF.cpp \ -allocator.cpp \ +allocator.cpp i2p_stream.cpp \ $(kademlia_sources) noinst_HEADERS = \ @@ -67,6 +67,7 @@ $(top_srcdir)/include/libtorrent/http_stream.hpp \ $(top_srcdir)/include/libtorrent/http_parser.hpp \ $(top_srcdir)/include/libtorrent/session_settings.hpp \ $(top_srcdir)/include/libtorrent/http_tracker_connection.hpp \ +$(top_srcdir)/include/libtorrent/i2p_stream.hpp \ $(top_srcdir)/include/libtorrent/identify_client.hpp \ $(top_srcdir)/include/libtorrent/instantiate_connection.hpp \ $(top_srcdir)/include/libtorrent/intrusive_ptr_base.hpp \ diff --git a/src/error_code.cpp b/src/error_code.cpp index 4d9c8cdc0..c920f11c0 100644 --- a/src/error_code.cpp +++ b/src/error_code.cpp @@ -184,6 +184,7 @@ namespace libtorrent "", "", "", + "no i2p router is set up", }; if (ev < 0 || ev >= sizeof(msgs)/sizeof(msgs[0])) return "Unknown error"; diff --git a/src/http_connection.cpp b/src/http_connection.cpp index 2f59c943f..905d357d3 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -51,7 +51,11 @@ enum { max_bottled_buffer = 1024 * 1024 }; void http_connection::get(std::string const& url, time_duration timeout, int prio , proxy_settings const* ps, int handle_redirects, std::string const& user_agent - , address const& bind_addr) + , address const& bind_addr +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn +#endif + ) { std::string protocol; std::string auth; @@ -137,12 +141,20 @@ void http_connection::get(std::string const& url, time_duration timeout, int pri sendbuffer.assign(request); m_url = url; start(hostname, to_string(port).elems, timeout, prio - , ps, ssl, handle_redirects, bind_addr); + , ps, ssl, handle_redirects, bind_addr +#if TORRENT_USE_I2P + , i2p_conn +#endif + ); } void http_connection::start(std::string const& hostname, std::string const& port , time_duration timeout, int prio, proxy_settings const* ps, bool ssl, int handle_redirects - , address const& bind_addr) + , address const& bind_addr +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn +#endif + ) { TORRENT_ASSERT(prio >= 0 && prio < 2); @@ -184,20 +196,69 @@ void http_connection::start(std::string const& hostname, std::string const& port error_code ec; m_sock.close(ec); +#if TORRENT_USE_I2P + bool is_i2p = false; + char const* top_domain = strrchr(hostname.c_str(), '.'); + if (top_domain && strcmp(top_domain, ".i2p") == 0 && i2p_conn) + { + // this is an i2p name, we need to use the sam connection + // to do the name lookup + is_i2p = true; + m_i2p_conn = i2p_conn; + // quadruple the timeout for i2p destinations + // because i2p is sloooooow + m_timeout *= 4; + } +#endif + +#if TORRENT_USE_I2P + if (is_i2p && i2p_conn->proxy().type != proxy_settings::i2p_proxy) + { + m_resolver.get_io_service().post(boost::bind(&http_connection::callback + , this, error_code(errors::no_i2p_router, libtorrent_category), (char*)0, 0)); + return; + } +#endif + #ifdef TORRENT_USE_OPENSSL if (m_ssl) { m_sock.instantiate >(m_resolver.get_io_service()); ssl_stream* s = m_sock.get >(); TORRENT_ASSERT(s); - bool ret = instantiate_connection(m_resolver.get_io_service(), m_proxy, s->next_layer()); + bool ret = false; +#if TORRENT_USE_I2P + if (is_i2p) + { + ret = instantiate_connection(m_resolver.get_io_service(), i2p_conn->proxy() + , s->next_layer()); + } + else +#endif + { + ret = instantiate_connection(m_resolver.get_io_service(), m_proxy, s->next_layer()); + } + TORRENT_ASSERT(ret); } else { m_sock.instantiate(m_resolver.get_io_service()); - bool ret = instantiate_connection(m_resolver.get_io_service() - , m_proxy, *m_sock.get()); + bool ret = false; +#if TORRENT_USE_I2P + if (is_i2p) + { + ret = instantiate_connection(m_resolver.get_io_service(), i2p_conn->proxy() + , *m_sock.get()); + TORRENT_ASSERT(m_sock.get()); + TORRENT_ASSERT(m_sock.get()->get()); + } + else +#endif + { + ret = instantiate_connection(m_resolver.get_io_service() + , m_proxy, *m_sock.get()); + } TORRENT_ASSERT(ret); } #else @@ -217,9 +278,19 @@ void http_connection::start(std::string const& hostname, std::string const& port } } - tcp::resolver::query query(hostname, port); - m_resolver.async_resolve(query, bind(&http_connection::on_resolve - , shared_from_this(), _1, _2)); +#if TORRENT_USE_I2P + if (is_i2p) + { + i2p_conn->async_name_lookup(hostname.c_str(), bind(&http_connection::on_i2p_resolve + , shared_from_this(), _1, _2)); + } + else +#endif + { + tcp::resolver::query query(hostname, port); + m_resolver.async_resolve(query, bind(&http_connection::on_resolve + , shared_from_this(), _1, _2)); + } m_hostname = hostname; m_port = port; } @@ -290,6 +361,34 @@ void http_connection::close() m_abort = true; } +#if TORRENT_USE_I2P +void http_connection::on_i2p_resolve(error_code const& e + , char const* destination) +{ + if (e) + { + callback(e); + close(); + return; + } + +#ifdef TORRENT_USE_OPENSSL + TORRENT_ASSERT(m_ssl == false); + TORRENT_ASSERT(m_sock.get()); + TORRENT_ASSERT(m_sock.get()->get()); + m_sock.get()->get()->set_destination(destination); + m_sock.get()->get()->set_command(i2p_stream::cmd_connect); + m_sock.get()->get()->set_session_id(m_i2p_conn->session_id()); +#else + m_sock.get()->set_destination(destination); + m_sock.get()->set_command(i2p_stream::cmd_connect); + m_sock.get()->set_session_id(m_i2p_conn->session_id()); +#endif + m_sock.async_connect(tcp::endpoint(), boost::bind(&http_connection::on_connect + , shared_from_this(), _1)); +} +#endif + void http_connection::on_resolve(error_code const& e , tcp::resolver::iterator i) { diff --git a/src/http_tracker_connection.cpp b/src/http_tracker_connection.cpp index f8fbdf00b..e389f5ee1 100644 --- a/src/http_tracker_connection.cpp +++ b/src/http_tracker_connection.cpp @@ -73,13 +73,20 @@ namespace libtorrent , boost::weak_ptr c , aux::session_impl const& ses , proxy_settings const& ps - , std::string const& auth) + , std::string const& auth +#if TORRENT_USE_I2P + , i2p_connection* i2p_conn +#endif + ) : tracker_connection(man, req, ios, c) , m_man(man) , m_ses(ses) , m_ps(ps) , m_cc(cc) , m_ios(ios) +#if TORRENT_USE_I2P + , m_i2p_conn(i2p_conn) +#endif {} void http_tracker_connection::start() @@ -102,6 +109,14 @@ namespace libtorrent url.replace(pos, 8, "scrape"); } +#if TORRENT_USE_I2P + // defined in torrent_info.cpp + bool is_i2p_url(std::string const& url); + bool i2p = is_i2p_url(url); +#else + static const bool i2p = false; +#endif + session_settings const& settings = m_ses.settings(); // if request-string already contains @@ -125,7 +140,9 @@ namespace libtorrent "%s" #endif , escape_string((const char*)&tracker_req().pid[0], 20).c_str() - , tracker_req().listen_port + // the i2p tracker seems to verify that the port is not 0, + // even though it ignores it otherwise + , i2p ? 1 : tracker_req().listen_port , tracker_req().uploaded , tracker_req().downloaded , tracker_req().left @@ -145,6 +162,16 @@ namespace libtorrent url += event_string[tracker_req().event - 1]; } +#if TORRENT_USE_I2P + if (i2p) + { + url += "&ip="; + url += escape_string(m_i2p_conn->local_endpoint().c_str() + , m_i2p_conn->local_endpoint().size()); + url += ".i2p"; + } + else +#endif if (settings.announce_ip != address()) { error_code ec; @@ -152,13 +179,13 @@ namespace libtorrent if (!ec) url += "&ip=" + ip; } - if (!tracker_req().ipv6.empty()) + if (!tracker_req().ipv6.empty() && !i2p) { url += "&ipv6="; url += tracker_req().ipv6; } - if (!tracker_req().ipv4.empty()) + if (!tracker_req().ipv4.empty() && !i2p) { url += "&ipv4="; url += tracker_req().ipv4; @@ -176,7 +203,11 @@ namespace libtorrent :settings.tracker_completion_timeout; m_tracker_connection->get(url, seconds(timeout) - , 1, &m_ps, 5, settings.user_agent, bind_interface()); + , 1, &m_ps, 5, settings.user_agent, bind_interface() +#if TORRENT_USE_I2P + , m_i2p_conn +#endif + ); // the url + 100 estimated header size sent_bytes(url.size() + 100); diff --git a/src/i2p_stream.cpp b/src/i2p_stream.cpp new file mode 100644 index 000000000..705c9ba1d --- /dev/null +++ b/src/i2p_stream.cpp @@ -0,0 +1,430 @@ +/* + +Copyright (c) 2009, 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/pch.hpp" + +#include "libtorrent/i2p_stream.hpp" +#include "libtorrent/assert.hpp" + +#include + +#if TORRENT_USE_I2P + +namespace libtorrent +{ + + i2p_error_category i2p_category; + + const char* i2p_error_category::name() const + { + return "i2p error"; + } + + std::string i2p_error_category::message(int ev) const + { + static char const* messages[] = + { + "no error", + "parse failed", + "cannot reach peer", + "i2p error", + "invalid key", + "invalid id", + "timeout", + "key not found" + }; + + if (ev < 0 || ev > i2p_error::num_errors) return "unknown error"; + return messages[ev]; + } + + i2p_connection::i2p_connection(io_service& ios) + : m_io_service(ios) + {} + + i2p_connection::~i2p_connection() + {} + + void i2p_connection::close() + { + m_sam_socket->close(); + } + + void i2p_connection::open(proxy_settings const& s, i2p_stream::handler_type const& handler) + { + // we already seem to have a session to this SAM router + if (m_sam_router.hostname == s.hostname + && m_sam_router.port == s.port + && is_open()) return; + + m_sam_router = s; + m_sam_router.type = proxy_settings::i2p_proxy; + + m_state = sam_connecting; + + char tmp[20]; + std::generate(tmp, tmp + sizeof(tmp), &std::rand); + m_session_id.resize(sizeof(tmp)*2); + to_hex(tmp, 20, &m_session_id[0]); + + m_sam_socket.reset(new i2p_stream(m_io_service)); + m_sam_socket->set_proxy(m_sam_router.hostname, m_sam_router.port); + m_sam_socket->set_command(i2p_stream::cmd_create_session); + m_sam_socket->set_session_id(m_session_id.c_str()); + + m_sam_socket->async_connect(tcp::endpoint() + , boost::bind(&i2p_connection::on_sam_connect, this, _1, handler)); + } + + void i2p_connection::on_sam_connect(error_code const& ec, i2p_stream::handler_type const& h) + { + m_state = sam_idle; + + do_name_lookup("ME", boost::bind(&i2p_connection::set_local_endpoint, this, _1, _2)); + h(ec); + } + + void i2p_connection::set_local_endpoint(error_code const& ec, char const* dest) + { + if (ec || dest == 0) + { + m_i2p_local_endpoint.clear(); + return; + } + m_i2p_local_endpoint = dest; + } + + void i2p_connection::async_name_lookup(char const* name + , i2p_connection::name_lookup_handler handler) + { + if (m_state == sam_idle && m_name_lookup.empty()) + do_name_lookup(name, handler); + else + m_name_lookup.push_back(std::make_pair(std::string(name), handler)); + } + + void i2p_connection::do_name_lookup(std::string const& name + , name_lookup_handler const& handler) + { + TORRENT_ASSERT(m_state == sam_idle); + m_state = sam_name_lookup; + m_sam_socket->set_name_lookup(name.c_str()); + boost::shared_ptr h(new i2p_stream::handler_type( + boost::bind(&i2p_connection::on_name_lookup, this, _1, handler))); + m_sam_socket->send_name_lookup(h); + } + + void i2p_connection::on_name_lookup(error_code const& ec + , name_lookup_handler handler) + { + m_state = sam_idle; + + std::string name = m_sam_socket->name_lookup(); + if (!m_name_lookup.empty()) + { + std::pair& nl = m_name_lookup.front(); + do_name_lookup(nl.first, nl.second); + m_name_lookup.pop_front(); + } + + if (ec) + { + handler(ec, 0); + return; + } + + handler(ec, name.c_str()); + } + +// TODO: move this to proxy_base and use it in all proxies + bool i2p_stream::handle_error(error_code const& e, boost::shared_ptr const& h) + { + if (!e) return false; +// fprintf(stderr, "i2p error \"%s\"\n", e.message().c_str()); + (*h)(e); + error_code ec; + close(ec); + return true; + } + + void i2p_stream::do_connect(error_code const& e, tcp::resolver::iterator i + , boost::shared_ptr h) + { + if (e || i == tcp::resolver::iterator()) + { + (*h)(e); + error_code ec; + close(ec); + return; + } + + m_sock.async_connect(i->endpoint(), boost::bind( + &i2p_stream::connected, this, _1, h)); + } + + void i2p_stream::connected(error_code const& e, boost::shared_ptr h) + { + if (handle_error(e, h)) return; + + // send hello command + m_state = read_hello_response; + static const char cmd[] = "HELLO VERSION MIN=3.0 MAX=3.0\n"; + async_write(m_sock, asio::buffer(cmd, sizeof(cmd) - 1) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); +// fputs(cmd, stderr); + } + + void i2p_stream::start_read_line(error_code const& e, boost::shared_ptr h) + { + if (handle_error(e, h)) return; + + m_buffer.resize(1); + async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&i2p_stream::read_line, this, _1, h)); + } + + char* string_tokenize(char* last, char sep, char** next) + { + if (last == 0) return 0; + *next = strchr(last, sep); + if (*next == 0) return last; + **next = 0; + ++(*next); + while (**next == sep && **next) ++(*next); + return last; + } + + void i2p_stream::read_line(error_code const& e, boost::shared_ptr h) + { + if (handle_error(e, h)) return; + + int read_pos = m_buffer.size(); + +// fprintf(stderr, "%c", m_buffer[read_pos - 1]); + // look for \n which means end of the response + if (m_buffer[read_pos - 1] != '\n') + { + // read another byte from the socket + m_buffer.resize(read_pos + 1); + async_read(m_sock, asio::buffer(&m_buffer[read_pos], 1) + , boost::bind(&i2p_stream::read_line, this, _1, h)); + return; + } + m_buffer[read_pos - 1] = 0; + + if (m_command == cmd_incoming) + { + // this is the line containing the destination + // of the incoming connection in an accept call + m_dest = &m_buffer[0]; + (*h)(e); + std::vector().swap(m_buffer); + return; + } + + error_code invalid_response(i2p_error::parse_failed + , i2p_category); + + // null-terminate the string and parse it + m_buffer.push_back(0); + char* ptr = &m_buffer[0]; + char* next = ptr; + + char const* expect1 = 0; + char const* expect2 = 0; + + switch (m_state) + { + case read_hello_response: + expect1 = "HELLO"; + expect2 = "REPLY"; + break; + case read_connect_response: + case read_accept_response: + expect1 = "STREAM"; + expect2 = "STATUS"; + break; + case read_session_create_response: + expect1 = "SESSION"; + expect2 = "STATUS"; + break; + case read_name_lookup_response: + expect1 = "NAMING"; + expect2 = "REPLY"; + break; + } + + ptr = string_tokenize(next, ' ', &next); + if (ptr == 0 || strcmp(expect1, ptr)) { handle_error(invalid_response, h); return; } + ptr = string_tokenize(next, ' ', &next); + if (ptr == 0 || strcmp(expect2, ptr)) { handle_error(invalid_response, h); return; } + + int result = 0; + char const* message = 0; + float version = 3.0f; + + for(;;) + { + char* name = string_tokenize(next, '=', &next); + if (name == 0) break; + char* ptr = string_tokenize(next, ' ', &next); + if (ptr == 0) { handle_error(invalid_response, h); return; } + + if (strcmp("RESULT", name) == 0) + { + if (strcmp("OK", ptr) == 0) + result = i2p_error::no_error; + else if (strcmp("CANT_REACH_PEER", ptr) == 0) + result = i2p_error::cant_reach_peer; + else if (strcmp("I2P_ERROR", ptr) == 0) + result = i2p_error::i2p_error; + else if (strcmp("INVALID_KEY", ptr) == 0) + result = i2p_error::invalid_key; + else if (strcmp("INVALID_ID", ptr) == 0) + result = i2p_error::invalid_id; + else if (strcmp("TIMEOUT", ptr) == 0) + result = i2p_error::timeout; + else if (strcmp("KEY_NOT_FOUND", ptr) == 0) + result = i2p_error::key_not_found; + else + result = i2p_error::num_errors; // unknown error + } + else if (strcmp("MESSAGE", name) == 0) + { + message = ptr; + } + else if (strcmp("VERSION", name) == 0) + { + version = atof(ptr); + } + else if (strcmp("VALUE", name) == 0) + { + m_name_lookup = ptr; + } + else if (strcmp("DESTINATION", name) == 0) + { + m_dest = ptr; + } + } + + if (result != i2p_error::no_error) + { + error_code ec(result, i2p_category); + handle_error(ec, h); + return; + } + + switch (m_state) + { + case read_hello_response: + switch (m_command) + { + case cmd_create_session: + send_session_create(h); + break; + case cmd_accept: + send_accept(h); + break; + case cmd_connect: + send_connect(h); + break; + default: + (*h)(e); + std::vector().swap(m_buffer); + } + break; + case read_connect_response: + case read_session_create_response: + case read_name_lookup_response: + (*h)(e); + std::vector().swap(m_buffer); + break; + case read_accept_response: + // the SAM bridge is waiting for an incoming + // connection. + // wait for one more line containing + // the destination of the remote peer + m_command = cmd_incoming; + m_buffer.resize(1); + async_read(m_sock, asio::buffer(m_buffer) + , boost::bind(&i2p_stream::read_line, this, _1, h)); + break; + } + + return; + } + + void i2p_stream::send_connect(boost::shared_ptr h) + { + m_state = read_connect_response; + char cmd[1024]; + int size = snprintf(cmd, sizeof(cmd), "STREAM CONNECT ID=%s DESTINATION=%s\n" + , m_id, m_dest.c_str()); +// fputs(cmd, stderr); + async_write(m_sock, asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } + + void i2p_stream::send_accept(boost::shared_ptr h) + { + m_state = read_accept_response; + char cmd[400]; + int size = snprintf(cmd, sizeof(cmd), "STREAM ACCEPT ID=%s\n", m_id); +// fputs(cmd, stderr); + async_write(m_sock, asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } + + void i2p_stream::send_session_create(boost::shared_ptr h) + { + m_state = read_session_create_response; + char cmd[400]; + int size = snprintf(cmd, sizeof(cmd), "SESSION CREATE STYLE=STREAM ID=%s DESTINATION=TRANSIENT\n" + , m_id); +// fputs(cmd, stderr); + async_write(m_sock, asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } + + void i2p_stream::send_name_lookup(boost::shared_ptr h) + { + m_state = read_name_lookup_response; + char cmd[1024]; + int size = snprintf(cmd, sizeof(cmd), "NAMING LOOKUP NAME=%s\n", m_name_lookup.c_str()); +// fputs(cmd, stderr); + async_write(m_sock, asio::buffer(cmd, size) + , boost::bind(&i2p_stream::start_read_line, this, _1, h)); + } +} + +#endif + diff --git a/src/instantiate_connection.cpp b/src/instantiate_connection.cpp index d744071fd..a6eab35cf 100644 --- a/src/instantiate_connection.cpp +++ b/src/instantiate_connection.cpp @@ -67,6 +67,13 @@ namespace libtorrent if (ps.type == proxy_settings::socks4) s.get()->set_version(4); } +#if TORRENT_USE_I2P + else if (ps.type == proxy_settings::i2p_proxy) + { + s.instantiate(ios); + s.get()->set_proxy(ps.hostname, ps.port); + } +#endif else { return false; diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 625e84853..8c4b26369 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -150,6 +150,14 @@ namespace libtorrent , m_received_in_piece(0) #endif { +#if TORRENT_USE_I2P + if (peerinfo && peerinfo->is_i2p_addr) + { + // quadruple the timeout for i2p peers + m_timeout *= 4; + } +#endif + m_channel_state[upload_channel] = peer_info::bw_idle; m_channel_state[download_channel] = peer_info::bw_idle; @@ -271,6 +279,14 @@ namespace libtorrent , m_received_in_piece(0) #endif { +#if TORRENT_USE_I2P + if (peerinfo && peerinfo->is_i2p_addr) + { + // quadruple the timeout for i2p peers + m_timeout *= 4; + } +#endif + m_channel_state[upload_channel] = peer_info::bw_idle; m_channel_state[download_channel] = peer_info::bw_idle; @@ -938,6 +954,18 @@ namespace libtorrent return; } + i2p_stream* i2ps = m_socket->get(); + if (!i2ps && t->torrent_file().is_i2p() && !m_ses.m_settings.allow_i2p_mixed) + { + // the torrent is an i2p torrent, the peer is a regular peer + // and we don't allow mixed mode. Disconnect the peer. +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_logger) << " rejected regular connection to i2p torrent\n"; +#endif + disconnect(error_code(errors::peer_banned, libtorrent_category), 2); + return; + } + TORRENT_ASSERT(m_torrent.expired()); // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. diff --git a/src/policy.cpp b/src/policy.cpp index f986be009..dc9d2ffbe 100644 --- a/src/policy.cpp +++ b/src/policy.cpp @@ -355,6 +355,12 @@ namespace libtorrent m_torrent->session().m_ipv6_peer_pool.destroy( static_cast(*i)); else +#endif +#if TORRENT_USE_I2P + if ((*i)->is_i2p_addr) + m_torrent->session().m_i2p_peer_pool.destroy( + static_cast(*i)); + else #endif m_torrent->session().m_ipv4_peer_pool.destroy( static_cast(*i)); @@ -512,7 +518,7 @@ namespace libtorrent for (int iterations = (std::min)(int(m_peers.size()), 300); iterations > 0; --iterations) { - if (m_round_robin == m_peers.size()) m_round_robin = 0; + if (m_round_robin == int(m_peers.size())) m_round_robin = 0; peer& pe = *m_peers[m_round_robin]; int current = m_round_robin; @@ -878,6 +884,156 @@ namespace libtorrent TORRENT_ASSERT(m_num_seeds <= m_peers.size()); } + bool policy::insert_peer(policy::peer* p, iterator iter, int flags) + { + TORRENT_ASSERT(p); + + int max_peerlist_size = m_torrent->is_paused() + ?m_torrent->settings().max_paused_peerlist_size + :m_torrent->settings().max_peerlist_size; + + if (max_peerlist_size + && int(m_peers.size()) >= max_peerlist_size) + { + if (p->source == peer_info::resume_data) return false; + + erase_peers(); + if (int(m_peers.size()) >= max_peerlist_size) + return 0; + + // since some peers were removed, we need to + // update the iterator to make it valid again +#if TORRENT_USE_I2P + if (p->is_i2p_addr) + { + iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , p->dest(), peer_address_compare()); + } + else +#endif + iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , p->address(), peer_address_compare()); + } + + if (m_round_robin > iter - m_peers.begin()) ++m_round_robin; + + iter = m_peers.insert(iter, p); + +#ifndef TORRENT_DISABLE_ENCRYPTION + if (flags & 0x01) p->pe_support = true; +#endif + if (flags & 0x02) + { + p->seed = true; + ++m_num_seeds; + } + +#ifndef TORRENT_DISABLE_GEO_IP + int as = ses.as_for_ip(remote.address()); +#ifdef TORRENT_DEBUG + p->inet_as_num = as; +#endif + p->inet_as = ses.lookup_as(as); +#endif + if (is_connect_candidate(*p, m_finished)) + ++m_num_connect_candidates; + + return true; + } + + void policy::update_peer(policy::peer* p, int src, int flags + , tcp::endpoint const& remote, char const* destination) + { + bool was_conn_cand = is_connect_candidate(*p, m_finished); + + p->connectable = true; + + TORRENT_ASSERT(p->address() == remote.address()); + p->port = remote.port(); + p->source |= src; + + // if this peer has failed before, decrease the + // counter to allow it another try, since somebody + // else is appearantly able to connect to it + // only trust this if it comes from the tracker + if (p->failcount > 0 && src == peer_info::tracker) + --p->failcount; + + // if we're connected to this peer + // we already know if it's a seed or not + // so we don't have to trust this source + if ((flags & 0x02) && !p->connection) + { + if (!p->seed) ++m_num_seeds; + p->seed = true; + } + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING + if (p->connection) + { + // this means we're already connected + // to this peer. don't connect to + // it again. + + error_code ec; + char hex_pid[41]; + to_hex((char*)&p->connection->pid()[0], 20, hex_pid); + char msg[200]; + snprintf(msg, 200, "already connected to peer: %s %s" + , print_endpoint(remote).c_str(), hex_pid); + m_torrent->debug_log(msg); + + TORRENT_ASSERT(p->connection->associated_torrent().lock().get() == m_torrent); + } +#endif + + if (was_conn_cand != is_connect_candidate(*p, m_finished)) + { + m_num_connect_candidates += was_conn_cand ? -1 : 1; + if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; + } + } + + policy::peer* policy::add_i2p_peer(char const* destination, int src, char flags) + { + INVARIANT_CHECK; + aux::session_impl& ses = m_torrent->session(); + + bool found = false; + iterator iter = std::lower_bound( + m_peers.begin(), m_peers.end() + , destination, peer_address_compare() + ); + + if (iter != m_peers.end() && strcmp((*iter)->dest(), destination) == 0) + found = true; + + peer* p = 0; + + if (!found) + { + // we don't have any info about this peer. + // add a new entry + p = (peer*)m_torrent->session().m_i2p_peer_pool.malloc(); + if (p == 0) return 0; + m_torrent->session().m_i2p_peer_pool.set_next_size(500); + new (p) i2p_peer(destination, true, src); + if (!insert_peer(p, iter, flags)) + { + m_torrent->session().m_i2p_peer_pool.free((i2p_peer*)p); + return 0; + } + } + else + { + p = *iter; + update_peer(p, src, flags, tcp::endpoint(), destination); + } + return p; + } + policy::peer* policy::add_peer(tcp::endpoint const& remote, peer_id const& pid , int src, char flags) { @@ -889,6 +1045,11 @@ namespace libtorrent aux::session_impl& ses = m_torrent->session(); + // if this is an i2p torrent, and we don't allow mixed mode + // no regular peers should ever be added! + if (!ses.m_settings.allow_i2p_mixed && m_torrent->torrent_file().is_i2p()) + return 0; + port_filter const& pf = ses.m_port_filter; if (pf.access(remote.port()) & port_filter::blocked) { @@ -901,14 +1062,12 @@ namespace libtorrent if (ses.m_ip_filter.access(remote.address()) & ip_filter::blocked) { if (ses.m_alerts.should_post()) - { ses.m_alerts.post_alert(peer_blocked_alert(remote.address())); - } return 0; } iterator iter; - peer* i = 0; + peer* p = 0; int max_peerlist_size = m_torrent->is_paused() ?m_torrent->settings().max_paused_peerlist_size @@ -933,31 +1092,13 @@ namespace libtorrent if (!found) { - if (max_peerlist_size - && int(m_peers.size()) >= max_peerlist_size) - { - if (src == peer_info::resume_data) return 0; - - erase_peers(); - if (int(m_peers.size()) >= max_peerlist_size) - return 0; - - // since some peers were removed, we need to - // update the iterator to make it valid again - iter = std::lower_bound( - m_peers.begin(), m_peers.end() - , remote.address(), peer_address_compare() - ); - } - - if (m_round_robin > iter - m_peers.begin()) ++m_round_robin; - // we don't have any info about this peer. // add a new entry + #if TORRENT_USE_IPV6 bool is_v6 = remote.address().is_v6(); #endif - peer* p = + p = #if TORRENT_USE_IPV6 is_v6 ? (peer*)m_torrent->session().m_ipv6_peer_pool.malloc() : #endif @@ -977,83 +1118,23 @@ namespace libtorrent #endif new (p) ipv4_peer(remote, true, src); - iter = m_peers.insert(iter, p); - - i = *iter; -#ifndef TORRENT_DISABLE_ENCRYPTION - if (flags & 0x01) i->pe_support = true; -#endif - if (flags & 0x02) + if (!insert_peer(p, iter, flags)) { - i->seed = true; - ++m_num_seeds; +#if TORRENT_USE_IPV6 + if (is_v6) m_torrent->session().m_ipv6_peer_pool.free((ipv6_peer*)p); + else +#endif + m_torrent->session().m_ipv4_peer_pool.free((ipv4_peer*)p); + return 0; } - -#ifndef TORRENT_DISABLE_GEO_IP - int as = ses.as_for_ip(remote.address()); -#ifdef TORRENT_DEBUG - i->inet_as_num = as; -#endif - i->inet_as = ses.lookup_as(as); -#endif - if (is_connect_candidate(*i, m_finished)) - ++m_num_connect_candidates; } else { - i = *iter; - - bool was_conn_cand = is_connect_candidate(*i, m_finished); - - i->connectable = true; - - TORRENT_ASSERT(i->address() == remote.address()); - i->port = remote.port(); - i->source |= src; - - // if this peer has failed before, decrease the - // counter to allow it another try, since somebody - // else is appearantly able to connect to it - // only trust this if it comes from the tracker - if (i->failcount > 0 && src == peer_info::tracker) - --i->failcount; - - // if we're connected to this peer - // we already know if it's a seed or not - // so we don't have to trust this source - if ((flags & 0x02) && !i->connection) - { - if (!i->seed) ++m_num_seeds; - i->seed = true; - } - -#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING - if (i->connection) - { - // this means we're already connected - // to this peer. don't connect to - // it again. - - error_code ec; - char hex_pid[41]; - to_hex((char*)&i->connection->pid()[0], 20, hex_pid); - char msg[200]; - snprintf(msg, 200, "already connected to peer: %s %s" - , print_endpoint(remote).c_str(), hex_pid); - m_torrent->debug_log(msg); - - TORRENT_ASSERT(i->connection->associated_torrent().lock().get() == m_torrent); - } -#endif - - if (was_conn_cand != is_connect_candidate(*i, m_finished)) - { - m_num_connect_candidates += was_conn_cand ? -1 : 1; - if (m_num_connect_candidates < 0) m_num_connect_candidates = 0; - } + p = *iter; + update_peer(p, src, flags, remote, 0); } - return i; + return p; } bool policy::connect_one_peer(int session_time) @@ -1326,6 +1407,9 @@ namespace libtorrent #endif #if TORRENT_USE_IPV6 , is_v6_addr(false) +#endif +#if TORRENT_USE_I2P + , is_i2p_addr(false) #endif , on_parole(false) , banned(false) diff --git a/src/session.cpp b/src/session.cpp index 847e5f6f8..e113e39c8 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -715,6 +715,20 @@ namespace libtorrent } #endif +#if TORRENT_USE_I2P + void session::set_i2p_proxy(proxy_settings const& s) + { + session_impl::mutex_t::scoped_lock l(m_impl->m_mutex); + m_impl->set_i2p_proxy(s); + } + + proxy_settings const& session::i2p_proxy() const + { + session_impl::mutex_t::scoped_lock l(m_impl->m_mutex); + return m_impl->i2p_proxy(); + } +#endif + int session::max_uploads() const { session_impl::mutex_t::scoped_lock l(m_impl->m_mutex); diff --git a/src/session_impl.cpp b/src/session_impl.cpp index d968ade46..ee09ec10b 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -192,6 +192,9 @@ namespace aux { #endif , m_tracker_manager(*this, m_tracker_proxy) , m_listen_port_retries(listen_port_range.second - listen_port_range.first) +#if TORRENT_USE_I2P + , m_i2p_conn(m_io_service) +#endif , m_abort(false) , m_paused(false) , m_max_uploads(8) @@ -524,6 +527,9 @@ namespace aux { #endif // abort the main thread m_abort = true; +#if TORRENT_USE_I2P + m_i2p_conn.close(); +#endif m_queued_for_checking.clear(); if (m_lsd) m_lsd->close(); if (m_upnp) m_upnp->close(); @@ -839,6 +845,9 @@ namespace aux { } open_new_incoming_socks_connection(); +#if TORRENT_USE_I2P + open_new_incoming_i2p_connection(); +#endif if (!m_listen_sockets.empty()) { @@ -886,6 +895,47 @@ namespace aux { , boost::bind(&session_impl::on_socks_accept, this, m_socks_listen_socket, _1)); } +#if TORRENT_USE_I2P + void session_impl::on_i2p_open(error_code const& ec) + { + open_new_incoming_i2p_connection(); + } + + void session_impl::open_new_incoming_i2p_connection() + { + if (!m_i2p_conn.is_open()) return; + + if (m_i2p_listen_socket) return; + + m_i2p_listen_socket = boost::shared_ptr(new socket_type(m_io_service)); + bool ret = instantiate_connection(m_io_service, m_i2p_conn.proxy() + , *m_i2p_listen_socket); + TORRENT_ASSERT(ret); + + i2p_stream& s = *m_i2p_listen_socket->get(); + s.set_command(i2p_stream::cmd_accept); + s.set_session_id(m_i2p_conn.session_id()); + s.async_connect(tcp::endpoint(address_v4::any(), m_listen_interface.port()) + , boost::bind(&session_impl::on_i2p_accept, this, m_i2p_listen_socket, _1)); + } + + void session_impl::on_i2p_accept(boost::shared_ptr const& s + , error_code const& e) + { + m_i2p_listen_socket.reset(); + if (e == asio::error::operation_aborted) return; + if (e) + { + if (m_alerts.should_post()) + m_alerts.post_alert(listen_failed_alert(tcp::endpoint( + address_v4::any(), m_listen_interface.port()), e)); + return; + } + open_new_incoming_i2p_connection(); + incoming_connection(s); + } +#endif + #ifndef TORRENT_DISABLE_DHT void session_impl::on_receive_udp(error_code const& e @@ -2323,7 +2373,8 @@ namespace aux { boost::shared_ptr t = find_torrent(ih).lock(); if (!t) return; // don't add peers from lsd to private torrents - if (t->torrent_file().priv()) return; + if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + && !m_settings.allow_i2p_mixed)) return; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << time_now_string() diff --git a/src/torrent.cpp b/src/torrent.cpp index 09d908727..9c6aeae64 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -1138,9 +1138,13 @@ namespace libtorrent if (m_abort) return; TORRENT_ASSERT(!m_torrent_file->priv()); - if (m_torrent_file->is_valid() && m_torrent_file->priv()) + if (m_torrent_file->is_valid() + && (m_torrent_file->priv() + || (torrent_file().is_i2p() + && !m_settings.allow_i2p_mixed))) return; + if (is_paused()) return; boost::weak_ptr self(shared_from_this()); @@ -1187,6 +1191,10 @@ namespace libtorrent m_ses.m_alerts.post_alert(dht_reply_alert( get_handle(), peers.size())); } + + if (torrent_file().priv() || (torrent_file().is_i2p() + && !m_settings.allow_i2p_mixed)) return; + std::for_each(peers.begin(), peers.end(), bind( &policy::add_peer, boost::ref(m_policy), _1, peer_id(0) , peer_info::dht, 0)); @@ -1428,9 +1436,30 @@ namespace libtorrent // assume this is because we got a hostname instead of // an ip address from the tracker - tcp::resolver::query q(i->ip, to_string(i->port).elems); - m_host_resolver.async_resolve(q, - bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i->pid)); +#if TORRENT_USE_I2P + char const* top_domain = strrchr(i->ip.c_str(), '.'); + if (top_domain && strcmp(top_domain, ".i2p") == 0 && m_ses.m_i2p_conn.is_open()) + { + // this is an i2p name, we need to use the sam connection + // to do the name lookup + /* + m_ses.m_i2p_conn.async_name_lookup(i->ip.c_str() + , bind(&torrent::on_i2p_resolve + , shared_from_this(), _1, _2)); + */ + // it seems like you're not supposed to do a name lookup + // on the peers returned from the tracker, but just strip + // the .i2p and use it as a destination + i->ip.resize(i->ip.size() - 4); + m_policy.add_i2p_peer(i->ip.c_str(), peer_info::tracker, 0); + } + else +#endif + { + tcp::resolver::query q(i->ip, to_string(i->port).elems); + m_host_resolver.async_resolve(q, + bind(&torrent::on_peer_name_lookup, shared_from_this(), _1, _2, i->pid)); + } } else { @@ -1483,6 +1512,19 @@ namespace libtorrent } } +#if TORRENT_USE_I2P + void torrent::on_i2p_resolve(error_code const& ec, char const* dest) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + INVARIANT_CHECK; + + if (ec || m_ses.is_aborted()) return; + + m_policy.add_i2p_peer(dest, peer_info::tracker, 0); + } +#endif + void torrent::on_peer_name_lookup(error_code const& e, tcp::resolver::iterator host , peer_id pid) { @@ -1500,10 +1542,7 @@ namespace libtorrent debug_log("blocked ip from tracker: " + host->endpoint().address().to_string(ec)); #endif if (m_ses.m_alerts.should_post()) - { m_ses.m_alerts.post_alert(peer_blocked_alert(host->endpoint().address())); - } - return; } @@ -3837,7 +3876,11 @@ namespace libtorrent peer_iterator i_ = std::find_if(m_connections.begin(), m_connections.end() , bind(&peer_connection::remote, _1) == peerinfo->ip()); TORRENT_ASSERT(i_ == m_connections.end() - || dynamic_cast(*i_) == 0); + || dynamic_cast(*i_) == 0 +#if TORRENT_USE_I2P + || peerinfo->is_i2p_addr +#endif + ); #endif TORRENT_ASSERT(want_more_peers()); @@ -3848,9 +3891,24 @@ namespace libtorrent boost::shared_ptr s(new socket_type(m_ses.m_io_service)); - bool ret = instantiate_connection(m_ses.m_io_service, m_ses.peer_proxy(), *s); - (void)ret; - TORRENT_ASSERT(ret); +#if TORRENT_USE_I2P + bool i2p = peerinfo->is_i2p_addr; + if (i2p) + { + bool ret = instantiate_connection(m_ses.m_io_service, m_ses.i2p_proxy(), *s); + (void)ret; + TORRENT_ASSERT(ret); + s->get()->set_destination(static_cast(peerinfo)->destination); + s->get()->set_command(i2p_stream::cmd_connect); + s->get()->set_session_id(m_ses.m_i2p_conn.session_id()); + } + else +#endif + { + bool ret = instantiate_connection(m_ses.m_io_service, m_ses.peer_proxy(), *s); + (void)ret; + TORRENT_ASSERT(ret); + } m_ses.setup_socket_buffers(*s); @@ -5068,7 +5126,10 @@ namespace libtorrent // private torrents are never announced on LSD // or on DHT, we don't need this timer. - if (!m_torrent_file->is_valid() || !m_torrent_file->priv()) + if (!m_torrent_file->is_valid() + || (!m_torrent_file->priv() + && (!m_torrent_file->is_i2p() + || m_settings.allow_i2p_mixed))) { error_code ec; boost::weak_ptr self(shared_from_this()); diff --git a/src/torrent_info.cpp b/src/torrent_info.cpp index c2628e2a4..736bb72cc 100644 --- a/src/torrent_info.cpp +++ b/src/torrent_info.cpp @@ -62,6 +62,10 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/file.hpp" #include "libtorrent/utf8.hpp" +#if TORRENT_USE_I2P +#include "libtorrent/parse_url.hpp" +#endif + namespace gr = boost::gregorian; namespace libtorrent @@ -366,6 +370,7 @@ namespace libtorrent : m_creation_date(pt::ptime(pt::not_a_date_time)) , m_multifile(false) , m_private(false) + , m_i2p(false) , m_info_section_size(0) , m_piece_hashes(0) , m_merkle_first_leaf(0) @@ -579,6 +584,7 @@ namespace libtorrent m_created_by.swap(ti.m_created_by); swap(m_multifile, ti.m_multifile); swap(m_private, ti.m_private); + swap(m_i2p, ti.m_i2p); swap(m_info_section, ti.m_info_section); swap(m_info_section_size, ti.m_info_section_size); swap(m_piece_hashes, ti.m_piece_hashes); @@ -813,6 +819,19 @@ namespace libtorrent return ret; } +#if TORRENT_USE_I2P + bool is_i2p_url(std::string const& url) + { + using boost::tuples::ignore; + std::string hostname; + error_code ec; + boost::tie(ignore, ignore, hostname, ignore, ignore) + = parse_url_components(url, ec); + char const* top_domain = strrchr(hostname.c_str(), '.'); + return top_domain && strcmp(top_domain, ".i2p") == 0; + } +#endif + bool torrent_info::parse_torrent_file(lazy_entry const& torrent_file, error_code& ec) { if (torrent_file.type() != lazy_entry::dict_t) @@ -838,6 +857,9 @@ namespace libtorrent e.fail_limit = 0; e.source = announce_entry::source_torrent; e.trim(); +#if TORRENT_USE_I2P + if (is_i2p_url(e.url)) m_i2p = true; +#endif m_urls.push_back(e); } } @@ -868,6 +890,9 @@ namespace libtorrent e.fail_limit = 0; e.source = announce_entry::source_torrent; e.trim(); +#if TORRENT_USE_I2P + if (is_i2p_url(e.url)) m_i2p = true; +#endif if (!e.url.empty()) m_urls.push_back(e); } diff --git a/src/tracker_manager.cpp b/src/tracker_manager.cpp index dd2b7fb41..b850e9ddf 100644 --- a/src/tracker_manager.cpp +++ b/src/tracker_manager.cpp @@ -234,7 +234,11 @@ namespace libtorrent { con = new http_tracker_connection( ios, cc, *this, req, c - , m_ses, m_proxy, auth); + , m_ses, m_proxy, auth +#if TORRENT_USE_I2P + , &m_ses.m_i2p_conn +#endif + ); } else if (protocol == "udp") { diff --git a/src/ut_pex.cpp b/src/ut_pex.cpp index d897f3f5d..04414e8f5 100644 --- a/src/ut_pex.cpp +++ b/src/ut_pex.cpp @@ -436,7 +436,8 @@ namespace libtorrent boost::shared_ptr create_ut_pex_plugin(torrent* t, void*) { - if (t->torrent_file().priv()) + if (t->torrent_file().priv() || (t->torrent_file().is_i2p() + && !t->settings().allow_i2p_mixed)) { return boost::shared_ptr(); }