diff --git a/CMakeLists.txt b/CMakeLists.txt index da82db116..db217b639 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ set(sources i2p_stream identify_client ip_filter + ip_notifier ip_voter performance_counters peer_class diff --git a/Jamfile b/Jamfile index c6d2b0053..9cf39c88b 100644 --- a/Jamfile +++ b/Jamfile @@ -599,6 +599,7 @@ SOURCES = http_parser identify_client ip_filter + ip_notifier ip_voter merkle peer_connection diff --git a/include/libtorrent/Makefile.am b/include/libtorrent/Makefile.am index 24e2377c7..8879a9232 100644 --- a/include/libtorrent/Makefile.am +++ b/include/libtorrent/Makefile.am @@ -70,6 +70,7 @@ nobase_include_HEADERS = \ io_service.hpp \ io_service_fwd.hpp \ ip_filter.hpp \ + ip_notifier.hpp \ ip_voter.hpp \ lazy_entry.hpp \ link.hpp \ @@ -77,6 +78,7 @@ nobase_include_HEADERS = \ lsd.hpp \ magnet_uri.hpp \ natpmp.hpp \ + netlink.hpp \ operations.hpp \ packet_buffer.hpp \ parse_url.hpp \ diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index b3a06213c..1fd9beab3 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -54,6 +54,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/debug.hpp" #include "libtorrent/piece_block_progress.hpp" #include "libtorrent/ip_filter.hpp" +#include "libtorrent/ip_notifier.hpp" #include "libtorrent/session_status.hpp" #include "libtorrent/add_torrent_params.hpp" #include "libtorrent/stat.hpp" @@ -251,6 +252,7 @@ namespace libtorrent void on_exception(std::exception const& e) override; void on_error(error_code const& ec) override; + void on_ip_change(error_code const& ec); void reopen_listen_sockets(); torrent_peer_allocator_interface* get_peer_allocator() override @@ -858,6 +860,9 @@ namespace libtorrent // at startup int m_key = 0; + // posts a notification when the set of local IPs changes + ip_change_notifier m_ip_notifier; + // the addresses or device names of the interfaces we are supposed to // listen on. if empty, it means that we should let the os decide // which interface to listen on diff --git a/include/libtorrent/ip_notifier.hpp b/include/libtorrent/ip_notifier.hpp new file mode 100644 index 000000000..a268b26fd --- /dev/null +++ b/include/libtorrent/ip_notifier.hpp @@ -0,0 +1,79 @@ +/* + +Copyright (c) 2016, Steven Siloti +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_IP_NOTIFIER_HPP_INCLUDED +#define TORRENT_IP_NOTIFIER_HPP_INCLUDED + +#include +#include + +#include "libtorrent/error_code.hpp" +#include "libtorrent/address.hpp" +#include "libtorrent/io_service.hpp" + +#if defined TORRENT_BUILD_SIMULATOR +#elif TORRENT_USE_NETLINK +#include "libtorrent/netlink.hpp" +#elif defined TORRENT_WINDOWS +#include +#endif + +namespace libtorrent +{ + struct ip_change_notifier : boost::noncopyable + { + explicit ip_change_notifier(io_service& ios); + ~ip_change_notifier(); + + // cb will be invoked when a change is detected in the + // system's IP addresses + void async_wait(std::function cb); + void cancel(); + + private: + void on_notify(error_code const& error + , std::size_t bytes_transferred + , std::function cb); + +#if defined TORRENT_BUILD_SIMULATOR + // TODO simulator support +#elif TORRENT_USE_NETLINK + netlink::socket m_socket; + std::array m_buf; +#elif defined TORRENT_WINDOWS + OVERLAPPED m_ovl = {}; + boost::asio::windows::object_handle m_hnd; +#endif + }; +} + +#endif diff --git a/include/libtorrent/netlink.hpp b/include/libtorrent/netlink.hpp new file mode 100644 index 000000000..c5896af87 --- /dev/null +++ b/include/libtorrent/netlink.hpp @@ -0,0 +1,203 @@ +/* + +Copyright (c) 2016, Steven Siloti +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_NETLINK_HPP +#define TORRENT_NETLINK_HPP + +#include "libtorrent/config.hpp" + +#if TORRENT_USE_NETLINK + +#include +#include +#include +#include +#include + +namespace libtorrent +{ + template + class basic_nl_endpoint + { + public: + using protocol_type = Protocol; + using data_type = boost::asio::detail::socket_addr_type; + + basic_nl_endpoint() + { + std::memset(&sockaddr, 0, sizeof(sockaddr_nl)); + sockaddr.nl_family = AF_NETLINK; + sockaddr.nl_groups = 0; + sockaddr.nl_pid = 0; + } + + basic_nl_endpoint(protocol_type netlink_family, std::uint32_t group, ::pid_t pid = 0) + : proto(netlink_family) + { + std::memset(&sockaddr, 0, sizeof(sockaddr_nl)); + sockaddr.nl_family = AF_NETLINK; + sockaddr.nl_groups = group; + sockaddr.nl_pid = pid; + } + + basic_nl_endpoint(basic_nl_endpoint const& other) + { + sockaddr = other.sockaddr; + } + + basic_nl_endpoint& operator=(const basic_nl_endpoint& other) + { + sockaddr = other.sockaddr; + return *this; + } + + basic_nl_endpoint& operator=(const basic_nl_endpoint&& other) + { + sockaddr = other.sockaddr; + return *this; + } + + protocol_type protocol() const + { + return proto; + } + + data_type* data() + { + return &sockaddr; + } + + const data_type* data() const + { + return (struct sockaddr*)&sockaddr; + } + + std::size_t size() const + { + return sizeof(sockaddr); + } + + std::size_t capacity() const + { + return sizeof(sockaddr); + } + + friend bool operator==(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.sockaddr == r.sockaddr; + } + + friend bool operator!=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l.sockaddr == r.sockaddr); + } + + friend bool operator<(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return l.sockaddr < r.sockaddr; + } + + friend bool operator>(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return r.sockaddr < l.sockaddr; + } + + friend bool operator<=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(r < l); + } + + friend bool operator>=(const basic_nl_endpoint& l + , const basic_nl_endpoint& r) + { + return !(l < r); + } + + private: + protocol_type proto; + sockaddr_nl sockaddr; + }; + + class netlink + { + public: + using endpoint = basic_nl_endpoint; + using socket = boost::asio::basic_raw_socket; + + netlink() : nl_family(0) + { + } + + explicit netlink(int nl_family) + : nl_family(nl_family) + { + } + + int type() const + { + return SOCK_RAW; + } + + int protocol() const + { + return nl_family; + } + + int family() const + { + return AF_NETLINK; + } + + friend bool operator==(const netlink& l, const netlink& r) + { + return l.nl_family == r.nl_family; + } + + friend bool operator!=(const netlink& l, const netlink& r) + { + return l.nl_family != r.nl_family; + } + + private: + int nl_family; + }; + +} + +#endif + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index ca013ce97..80a3e54b6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -88,6 +88,7 @@ libtorrent_rasterbar_la_SOURCES = \ identify_client.cpp \ instantiate_connection.cpp \ ip_filter.cpp \ + ip_notifier.cpp \ ip_voter.cpp \ lazy_bdecode.cpp \ lsd.cpp \ diff --git a/src/ip_notifier.cpp b/src/ip_notifier.cpp new file mode 100644 index 000000000..ec9c1dd61 --- /dev/null +++ b/src/ip_notifier.cpp @@ -0,0 +1,124 @@ +/* + +Copyright (c) 2016, Steven Siloti +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/ip_notifier.hpp" + +#if defined TORRENT_WINDOWS && !defined TORRENT_BUILD_SIMULATOR +#include +#endif + +using namespace std::placeholders; + +namespace libtorrent +{ + ip_change_notifier::ip_change_notifier(io_service& ios) +#if defined TORRENT_BUILD_SIMULATOR +#elif TORRENT_USE_NETLINK + : m_socket(ios + , netlink::endpoint(netlink(NETLINK_ROUTE), RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR)) +#elif defined TORRENT_WINDOWS + : m_hnd(ios, WSACreateEvent()) +#endif + { +#if defined TORRENT_BUILD_SIMULATOR + TORRENT_UNUSED(ios); +#elif defined TORRENT_WINDOWS + if (!m_hnd.is_open()) + throw system_error(WSAGetLastError(), system_category()); + m_ovl.hEvent = m_hnd.native_handle(); +#elif !TORRENT_USE_NETLINK + TORRENT_UNUSED(ios); +#endif + } + + ip_change_notifier::~ip_change_notifier() + { +#if defined TORRENT_WINDOWS && !defined TORRENT_BUILD_SIMULATOR + cancel(); + m_hnd.close(); +#endif + } + + void ip_change_notifier::async_wait(std::function cb) + { +#if defined TORRENT_BUILD_SIMULATOR + TORRENT_UNUSED(cb); +#elif TORRENT_USE_NETLINK + m_socket.async_receive(boost::asio::buffer(m_buf) + , std::bind(&ip_change_notifier::on_notify, this, _1, _2, cb)); +#elif defined TORRENT_WINDOWS + HANDLE hnd; + DWORD err = NotifyAddrChange(&hnd, &m_ovl); + if (err == ERROR_IO_PENDING) + { + m_hnd.async_wait([this,cb](error_code const& ec) { on_notify(ec, 0, cb); }); + } + else + { + m_hnd.get_io_service().post([this,cb,err]() + { cb(error_code(err, system_category())); }); + } +#else + TORRENT_UNUSED(cb); +#endif + } + + void ip_change_notifier::cancel() + { +#if defined TORRENT_BUILD_SIMULATOR +#elif TORRENT_USE_NETLINK + m_socket.cancel(); +#elif defined TORRENT_WINDOWS + CancelIPChangeNotify(&m_ovl); + m_hnd.cancel(); +#endif + } + + void ip_change_notifier::on_notify(error_code const& ec + , std::size_t bytes_transferred + , std::function cb) + { + TORRENT_UNUSED(bytes_transferred); + + // on linux we could parse the message to get information about the + // change but Windows requires the application to enumerate the + // interfaces after a notification so do that for Linux as well to + // minimize the difference between platforms + + // Linux can generate ENOBUFS if the socket's buffers are full + // don't treat it as an error + if (ec.value() == boost::system::errc::no_buffer_space) + cb(error_code()); + else + cb(ec); + } +} diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 9d43b71fc..88e05eb6d 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -411,6 +411,7 @@ namespace aux { #endif ) , m_work(new io_service::work(m_io_service)) + , m_ip_notifier(m_io_service) #if TORRENT_USE_I2P , m_i2p_conn(m_io_service) #endif @@ -605,6 +606,9 @@ namespace aux { session_log(" done starting session"); #endif + m_ip_notifier.async_wait([this](error_code const& e) + { this->wrap(&session_impl::on_ip_change, e); }); + apply_settings_pack_impl(*pack, true); // call update_* after settings set initialized @@ -859,6 +863,9 @@ namespace aux { // abort the main thread m_abort = true; error_code ec; + + m_ip_notifier.cancel(); + #if TORRENT_USE_I2P m_i2p_conn.close(ec); #endif @@ -1686,6 +1693,14 @@ namespace aux { this->abort(); } + void session_impl::on_ip_change(error_code const& ec) + { + if (ec || m_abort) return; + m_ip_notifier.async_wait([this] (error_code const& e) + { this->wrap(&session_impl::on_ip_change, e); }); + reopen_listen_sockets(); + } + void session_impl::reopen_listen_sockets() { #ifndef TORRENT_DISABLE_LOGGING