diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index 36c13c5ab..bfbfa8ae4 100755 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -328,14 +328,33 @@ namespace libtorrent struct TORRENT_EXPORT listen_failed_alert: alert { listen_failed_alert( - const std::string& msg) + tcp::endpoint const& ep + , std::string const& msg) : alert(alert::fatal, msg) + , endpoint(ep) {} + tcp::endpoint endpoint; + virtual std::auto_ptr clone() const { return std::auto_ptr(new listen_failed_alert(*this)); } }; + struct TORRENT_EXPORT listen_succeeded_alert: alert + { + listen_succeeded_alert( + tcp::endpoint const& ep + , std::string const& msg) + : alert(alert::fatal, msg) + , endpoint(ep) + {} + + tcp::endpoint endpoint; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new listen_succeeded_alert(*this)); } + }; + struct TORRENT_EXPORT portmap_error_alert: alert { portmap_error_alert(const std::string& msg) diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 9300a1ce3..afb358afe 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -189,11 +189,16 @@ namespace libtorrent #endif void operator()(); - void open_listen_port(); + void open_listen_port() throw(); - void async_accept(); + // if we are listening on an IPv6 interface + // this will return one of the IPv6 addresses on this + // machine, otherwise just an empty endpoint + tcp::endpoint get_ipv6_interface() const; + + void async_accept(boost::shared_ptr const& listener); void on_incoming_connection(boost::shared_ptr const& s - , boost::weak_ptr const& as, asio::error_code const& e); + , boost::weak_ptr listener, asio::error_code const& e); // must be locked to access the data // in this struct @@ -393,8 +398,10 @@ namespace libtorrent // at startup int m_key; - // the range of ports we try to listen on - std::pair m_listen_port_range; + // the number of retries we make when binding the + // listen socket. For each retry the port number + // is incremented by one + int m_listen_port_retries; // the ip-address of the interface // we are supposed to listen on. @@ -402,17 +409,32 @@ namespace libtorrent // that we should let the os decide which // interface to listen on tcp::endpoint m_listen_interface; - - // this is typically set to the same as the local - // listen port. In case a NAT port forward was - // successfully opened, this will be set to the - // port that is open on the external (NAT) interface - // on the NAT box itself. This is the port that has - // to be published to peers, since this is the port - // the client is reachable through. - int m_external_listen_port; - boost::shared_ptr m_listen_socket; + // if we're listening on an IPv6 interface + // this is one of the non local IPv6 interfaces + // on this machine + tcp::endpoint m_ipv6_interface; + + struct listen_socket_t + { + listen_socket_t(): external_port(0) {} + // this is typically set to the same as the local + // listen port. In case a NAT port forward was + // successfully opened, this will be set to the + // port that is open on the external (NAT) interface + // on the NAT box itself. This is the port that has + // to be published to peers, since this is the port + // the client is reachable through. + int external_port; + + // the actual socket + boost::shared_ptr sock; + }; + // since we might be listening on multiple interfaces + // we might need more than one listen socket + std::list m_listen_sockets; + + listen_socket_t setup_listener(tcp::endpoint ep, int retries); // the settings for the client session_settings m_settings; diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp index 57f7bd851..b1b4d87ac 100755 --- a/include/libtorrent/tracker_manager.hpp +++ b/include/libtorrent/tracker_manager.hpp @@ -113,6 +113,7 @@ namespace libtorrent std::string url; int key; int num_want; + std::string ipv6; }; struct TORRENT_EXPORT request_callback diff --git a/src/alert.cpp b/src/alert.cpp index b990db7e1..80ae6ead8 100755 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -65,7 +65,7 @@ namespace libtorrent { alert_manager::alert_manager() - : m_severity(alert::none) + : m_severity(alert::fatal) {} alert_manager::~alert_manager() diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index ab61d98f9..e27a7f4c3 100755 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -50,7 +50,6 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/version.hpp" #include "libtorrent/extensions.hpp" #include "libtorrent/aux_/session_impl.hpp" -#include "libtorrent/enum_net.hpp" #ifndef TORRENT_DISABLE_ENCRYPTION #include "libtorrent/pe_crypto.hpp" @@ -1475,18 +1474,14 @@ namespace libtorrent detail::write_address(remote().address(), out); handshake["yourip"] = remote_address; handshake["reqq"] = m_ses.settings().max_allowed_in_request_queue; - asio::error_code ec; - std::vector
const& interfaces = enum_net_interfaces(get_socket()->io_service(), ec); - for (std::vector
::const_iterator i = interfaces.begin() - , end(interfaces.end()); i != end; ++i) + + tcp::endpoint ep = m_ses.get_ipv6_interface(); + if (ep != tcp::endpoint()) { - // TODO: only use global IPv6 addresses - if (!i->is_v6() || i->to_v6().is_link_local()) continue; std::string ipv6_address; std::back_insert_iterator out(ipv6_address); - detail::write_address(*i, out); + detail::write_address(ep.address(), out); handshake["ipv6"] = ipv6_address; - break; } // loop backwards, to make the first extension be the last diff --git a/src/http_tracker_connection.cpp b/src/http_tracker_connection.cpp index 0a0e59c48..321a57df7 100755 --- a/src/http_tracker_connection.cpp +++ b/src/http_tracker_connection.cpp @@ -428,6 +428,12 @@ namespace libtorrent m_send_buffer += "supportcrypto=1&"; #endif + if (!url_has_argument(request, "ipv6") && !req.ipv6.empty()) + { + m_send_buffer += "ipv6="; + m_send_buffer += req.ipv6; + } + // extension that tells the tracker that // we don't need any peer_id's in the response if (!url_has_argument(request, "no_peer_id")) diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 6e1129b99..78fd353e4 100755 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -73,6 +73,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/aux_/session_impl.hpp" #include "libtorrent/kademlia/dht_tracker.hpp" +#include "libtorrent/enum_net.hpp" #ifndef TORRENT_DISABLE_ENCRYPTION @@ -512,9 +513,8 @@ namespace detail , m_download_channel(m_io_service, peer_connection::download_channel) , m_upload_channel(m_io_service, peer_connection::upload_channel) , m_tracker_manager(m_settings, m_tracker_proxy) - , m_listen_port_range(listen_port_range) + , m_listen_port_retries(listen_port_range.second - listen_port_range.first) , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) - , m_external_listen_port(0) , m_abort(false) , m_max_uploads(8) , m_max_connections(200) @@ -661,129 +661,212 @@ namespace detail *i = ' '; } - void session_impl::open_listen_port() + tcp::endpoint session_impl::get_ipv6_interface() const { - try - { - // create listener socket - m_listen_socket.reset(new socket_acceptor(m_io_service)); + return m_ipv6_interface; + } - for(;;) - { - try - { - m_listen_socket->open(m_listen_interface.protocol()); - m_listen_socket->set_option(socket_acceptor::reuse_address(true)); - m_listen_socket->bind(m_listen_interface); - m_listen_socket->listen(); - m_listen_interface = m_listen_socket->local_endpoint(); - m_external_listen_port = m_listen_interface.port(); - break; - } - catch (asio::system_error& e) - { - // TODO: make sure this is correct - if (e.code() == asio::error::host_not_found - || m_listen_interface.port() == 0) - { - if (m_alerts.should_post(alert::fatal)) - { - std::string msg = "cannot listen on the given interface '" - + m_listen_interface.address().to_string() + "'"; - m_alerts.post_alert(listen_failed_alert(msg)); - } -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - std::string msg = "cannot listen on the given interface '" - + m_listen_interface.address().to_string() + "'"; - (*m_logger) << msg << "\n"; -#endif - assert(m_listen_socket.unique()); - m_listen_socket.reset(); - break; - } - m_listen_socket->close(); - m_listen_interface.port(m_listen_interface.port() + 1); - if (m_listen_interface.port() > m_listen_port_range.second) - { - std::stringstream msg; - msg << "none of the ports in the range [" - << m_listen_port_range.first - << ", " << m_listen_port_range.second - << "] could be opened for listening"; - m_alerts.post_alert(listen_failed_alert(msg.str())); -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << msg.str() << "\n"; -#endif - m_listen_socket.reset(); - break; - } - } - } + session_impl::listen_socket_t session_impl::setup_listener(tcp::endpoint ep, int retries) + { + asio::error_code ec; + listen_socket_t s; + s.sock.reset(new socket_acceptor(m_io_service)); + s.sock->open(ep.protocol(), ec); + s.sock->set_option(socket_acceptor::reuse_address(true), ec); + s.sock->bind(ep, ec); + while (ec && retries > 0) + { + ec = asio::error_code(); + assert(!ec); + --retries; + ep.port(ep.port() + 1); + s.sock->bind(ep, ec); } - catch (asio::system_error& e) + if (ec) + { + // instead of giving up, try + // let the OS pick a port + ep.port(0); + ec = asio::error_code(); + s.sock->bind(ep, ec); + } + if (ec) + { + // not even that worked, give up + if (m_alerts.should_post(alert::fatal)) + { + std::string msg = "cannot bind to the given interface '" + + boost::lexical_cast(ep) + "' " + ec.message(); + m_alerts.post_alert(listen_failed_alert(ep, msg)); + } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::string msg = "cannot bind to the given interface '" + + boost::lexical_cast(ep) + "' " + ec.message(); + (*m_logger) << msg << "\n"; +#endif + return listen_socket_t(); + } + s.external_port = s.sock->local_endpoint(ec).port(); + s.sock->listen(0, ec); + if (ec) { if (m_alerts.should_post(alert::fatal)) { - m_alerts.post_alert(listen_failed_alert( - std::string("failed to open listen port: ") + e.what())); + std::string msg = "cannot listen the given interface '" + + boost::lexical_cast(ep) + "' " + ec.message(); + m_alerts.post_alert(listen_failed_alert(ep, msg)); + } +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + std::string msg = "cannot listen the given interface '" + + boost::lexical_cast(ep) + "' " + ec.message(); + (*m_logger) << msg << "\n"; +#endif + return listen_socket_t(); + } + + if (m_alerts.should_post(alert::fatal)) + { + std::string msg = "listening on interface " + + boost::lexical_cast(ep); + m_alerts.post_alert(listen_succeeded_alert(ep, msg)); + } + +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << "listening on: " << ep.address().to_string() << ":" << ep.port() + << " external port: " << s.external_port << "\n"; +#endif + return s; + } + + void session_impl::open_listen_port() throw() + { + // close the open listen sockets + m_listen_sockets.clear(); + m_incoming_connection = false; + + if (is_any(m_listen_interface.address())) + { + // this means we should open two listen sockets + // one for IPv4 and one for IPv6 + + listen_socket_t s = setup_listener( + tcp::endpoint(address_v4::any(), m_listen_interface.port()) + , m_listen_port_retries); + + if (s.sock) + { + m_listen_sockets.push_back(s); + async_accept(s.sock); + } + + s = setup_listener( + tcp::endpoint(address_v6::any(), m_listen_interface.port()) + , m_listen_port_retries); + + if (s.sock) + { + m_listen_sockets.push_back(s); + async_accept(s.sock); + } + } + else + { + // we should only open a single listen socket, that + // binds to the given interface + + listen_socket_t s = setup_listener( + m_listen_interface, m_listen_port_retries); + + if (s.sock) + { + m_listen_sockets.push_back(s); + async_accept(s.sock); } } - if (m_listen_socket) + m_ipv6_interface = tcp::endpoint(); + + for (std::list::const_iterator i = m_listen_sockets.begin() + , end(m_listen_sockets.end()); i != end; ++i) { -#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) - (*m_logger) << "listening on port: " << m_listen_interface.port() - << " external port: " << m_external_listen_port << "\n"; -#endif - async_accept(); - if (m_natpmp.get()) - m_natpmp->set_mappings(m_listen_interface.port(), 0); - if (m_upnp.get()) - m_upnp->set_mappings(m_listen_interface.port(), 0); + asio::error_code ec; + tcp::endpoint ep = i->sock->local_endpoint(ec); + if (ec || ep.address().is_v4()) continue; + + std::vector
const& ifs = enum_net_interfaces(m_io_service, ec); + for (std::vector
::const_iterator i = ifs.begin() + , end(ifs.end()); i != end; ++i) + { + if (i->is_v4() || i->to_v6().is_link_local() || i->to_v6().is_loopback()) continue; + m_ipv6_interface = tcp::endpoint(*i, ep.port()); + break; + } + break; + } + + if (!m_listen_sockets.empty()) + { + asio::error_code ec; + tcp::endpoint local = m_listen_sockets.front().sock->local_endpoint(ec); + if (!ec) + { + if (m_natpmp.get()) m_natpmp->set_mappings(local.port(), 0); + if (m_upnp.get()) m_upnp->set_mappings(local.port(), 0); + } } } - void session_impl::async_accept() + void session_impl::async_accept(boost::shared_ptr const& listener) { shared_ptr c(new socket_type(m_io_service)); c->instantiate(); - m_listen_socket->async_accept(c->get() + listener->async_accept(c->get() , bind(&session_impl::on_incoming_connection, this, c - , weak_ptr(m_listen_socket), _1)); + , boost::weak_ptr(listener), _1)); } void session_impl::on_incoming_connection(shared_ptr const& s - , weak_ptr const& listen_socket, asio::error_code const& e) try + , weak_ptr listen_socket, asio::error_code const& e) try { - if (listen_socket.expired()) - return; + boost::shared_ptr listener = listen_socket.lock(); + if (!listener) return; - if (e == asio::error::operation_aborted) - return; + if (e == asio::error::operation_aborted) return; mutex_t::scoped_lock l(m_mutex); - assert(listen_socket.lock() == m_listen_socket); - if (m_abort) return; + asio::error_code ec; if (e) { + tcp::endpoint ep = listener->local_endpoint(ec); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) std::string msg = "error accepting connection on '" - + m_listen_interface.address().to_string() + "'"; + + boost::lexical_cast(ep) + "' " + e.message(); (*m_logger) << msg << "\n"; #endif - assert(m_listen_socket.unique()); - // try any random port - m_listen_interface.port(0); - open_listen_port(); + if (m_alerts.should_post(alert::fatal)) + { + std::string msg = "error accepting connection on '" + + boost::lexical_cast(ep) + "' " + ec.message(); + m_alerts.post_alert(listen_failed_alert(ep, msg)); + } return; } - async_accept(); + async_accept(listener); // we got a connection request! m_incoming_connection = true; - tcp::endpoint endp = s->remote_endpoint(); + tcp::endpoint endp = s->remote_endpoint(ec); + + if (ec) + { +#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) + (*m_logger) << endp << " <== INCOMING CONNECTION FAILED, could " + "not retrieve remote endpoint " << ec.message() << "\n"; +#endif + return; + } #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) (*m_logger) << endp << " <== INCOMING CONNECTION\n"; @@ -803,28 +886,22 @@ namespace detail // check if we have any active torrents // if we don't reject the connection - if (m_torrents.empty()) + if (m_torrents.empty()) return; + + bool has_active_torrent = false; + for (torrent_map::iterator i = m_torrents.begin() + , end(m_torrents.end()); i != end; ++i) { - return; - } - else - { - bool has_active_torrent = false; - for (torrent_map::iterator i = m_torrents.begin() - , end(m_torrents.end()); i != end; ++i) + if (!i->second->is_paused()) { - if (!i->second->is_paused()) - { - has_active_torrent = true; - break; - } + has_active_torrent = true; + break; } - if (!has_active_torrent) - return; } + if (!has_active_torrent) return; boost::intrusive_ptr c( - new bt_peer_connection(*this, s, 0)); + new bt_peer_connection(*this, s, 0)); #ifndef NDEBUG c->m_in_constructor = false; #endif @@ -1064,7 +1141,9 @@ namespace detail if (t.should_request()) { tracker_request req = t.generate_tracker_request(); - req.listen_port = m_external_listen_port; + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; req.key = m_key; m_tracker_manager.queue_request(m_strand, m_half_open, req , t.tracker_login(), m_listen_interface.address(), i->second); @@ -1274,10 +1353,9 @@ namespace detail { eh_initializer(); - if (m_listen_port_range.first != 0 && m_listen_port_range.second != 0) { session_impl::mutex_t::scoped_lock l(m_mutex); - open_listen_port(); + if (m_listen_interface.port() != 0) open_listen_port(); } ptime timer = time_now(); @@ -1331,8 +1409,10 @@ namespace detail && !i->second->trackers().empty()) { tracker_request req = i->second->generate_tracker_request(); - assert(m_external_listen_port > 0); - req.listen_port = m_external_listen_port; + assert(!m_listen_sockets.empty()); + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; req.key = m_key; std::string login = i->second->tracker_login(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1347,8 +1427,8 @@ namespace detail } } - // close listen socket - m_listen_socket.reset(); + // close the listen sockets + m_listen_sockets.clear(); ptime start(time_now()); l.unlock(); @@ -1477,10 +1557,12 @@ namespace detail , bool paused , void* userdata) { + assert(!save_path.empty()); + // if you get this assert, you haven't managed to // open a listen port. call listen_on() first. - assert(m_external_listen_port > 0); - assert(!save_path.empty()); + if (m_listen_sockets.empty()) + throw std::runtime_error("no listen socket opened"); if (ti->begin_files() == ti->end_files()) throw std::runtime_error("no files in torrent"); @@ -1489,7 +1571,7 @@ namespace detail mutex_t::scoped_lock l(m_mutex); mutex::scoped_lock l2(m_checker_impl.m_mutex); - INVARIANT_CHECK; +// INVARIANT_CHECK; if (is_aborted()) throw std::runtime_error("session is closing"); @@ -1573,7 +1655,7 @@ namespace detail // lock the session session_impl::mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; +// INVARIANT_CHECK; // is the torrent already active? if (!find_torrent(info_hash).expired()) @@ -1628,8 +1710,10 @@ namespace detail { tracker_request req = t.generate_tracker_request(); assert(req.event == tracker_request::stopped); - assert(m_external_listen_port > 0); - req.listen_port = m_external_listen_port; + assert(!m_listen_sockets.empty()); + req.listen_port = 0; + if (!m_listen_sockets.empty()) + req.listen_port = m_listen_sockets.front().external_port; req.key = m_key; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1684,19 +1768,15 @@ namespace detail if (net_interface && std::strlen(net_interface) > 0) new_interface = tcp::endpoint(address::from_string(net_interface), port_range.first); else - new_interface = tcp::endpoint(address(), port_range.first); + new_interface = tcp::endpoint(address_v4::any(), port_range.first); - m_listen_port_range = port_range; + m_listen_port_retries = port_range.second - port_range.first; // if the interface is the same and the socket is open // don't do anything if (new_interface == m_listen_interface - && m_listen_socket) return true; + && !m_listen_sockets.empty()) return true; - if (m_listen_socket) - m_listen_socket.reset(); - - m_incoming_connection = false; m_listen_interface = new_interface; open_listen_port(); @@ -1723,13 +1803,14 @@ namespace detail (*m_logger) << time_now_string() << "\n"; #endif - return m_listen_socket; + return !m_listen_sockets.empty(); } unsigned short session_impl::listen_port() const { mutex_t::scoped_lock l(m_mutex); - return m_external_listen_port; + if (m_listen_sockets.empty()) return 0; + return m_listen_sockets.front().external_port;; } void session_impl::announce_lsd(sha1_hash const& ih) @@ -1777,7 +1858,8 @@ namespace detail if (tcp_port != 0) { - m_external_listen_port = tcp_port; + if (!m_listen_sockets.empty()) + m_listen_sockets.front().external_port = tcp_port; if (m_alerts.should_post(alert::info)) { std::stringstream msg; @@ -1801,7 +1883,7 @@ namespace detail { mutex_t::scoped_lock l(m_mutex); - INVARIANT_CHECK; +// INVARIANT_CHECK; session_status s; s.has_incoming_connections = m_incoming_connection; @@ -1945,7 +2027,7 @@ namespace detail bool session_impl::is_listening() const { mutex_t::scoped_lock l(m_mutex); - return m_listen_socket; + return !m_listen_sockets.empty(); } session_impl::~session_impl() diff --git a/src/torrent.cpp b/src/torrent.cpp index 252461bc6..6e941e9ec 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -412,9 +412,9 @@ namespace libtorrent m_last_dht_announce = now; // TODO: There should be a way to abort an announce operation on the dht. // when the torrent is destructed - assert(m_ses.m_external_listen_port > 0); + if (m_ses.m_listen_sockets.empty()) return; m_ses.m_dht->announce(m_torrent_file->info_hash() - , m_ses.m_external_listen_port + , m_ses.m_listen_sockets.front().external_port , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); } #endif @@ -1367,6 +1367,9 @@ namespace libtorrent req.left = bytes_left(); if (req.left == -1) req.left = 16*1024; req.event = m_event; + tcp::endpoint ep = m_ses.get_ipv6_interface(); + if (ep != tcp::endpoint()) + req.ipv6 = ep.address().to_string(); if (m_event != tracker_request::stopped) m_event = tracker_request::none;