From 0d02fe0539f68ce2422bbf74c0eb5f77cfccdca6 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sun, 9 Sep 2007 23:52:34 +0000 Subject: [PATCH] upnp and lsd update. added a broadcast_socket and made the upnp connection use the locally bound ip to specify its address in the soap requests --- Jamfile | 2 + include/libtorrent/broadcast_socket.hpp | 80 +++++++++ include/libtorrent/enum_net.hpp | 44 +++++ include/libtorrent/http_connection.hpp | 11 +- include/libtorrent/lsd.hpp | 18 +- include/libtorrent/upnp.hpp | 28 ++- src/broadcast_socket.cpp | 142 +++++++++++++++ src/enum_net.cpp | 133 ++++++++++++++ src/http_connection.cpp | 1 + src/lsd.cpp | 88 ++------- src/session_impl.cpp | 10 -- src/upnp.cpp | 229 ++++++++---------------- test/test_upnp.cpp | 19 +- 13 files changed, 513 insertions(+), 292 deletions(-) create mode 100644 include/libtorrent/broadcast_socket.hpp create mode 100644 include/libtorrent/enum_net.hpp create mode 100644 src/broadcast_socket.cpp create mode 100644 src/enum_net.cpp diff --git a/Jamfile b/Jamfile index 869c99592..3ebc084c2 100755 --- a/Jamfile +++ b/Jamfile @@ -235,6 +235,8 @@ SOURCES = file_pool lsd disk_io_thread + enum_net + broadcast_socket ; KADEMLIA_SOURCES = diff --git a/include/libtorrent/broadcast_socket.hpp b/include/libtorrent/broadcast_socket.hpp new file mode 100644 index 000000000..bdfe30b6e --- /dev/null +++ b/include/libtorrent/broadcast_socket.hpp @@ -0,0 +1,80 @@ +/* + +Copyright (c) 2007, 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_BROADCAST_SOCKET_HPP_INCLUDED +#define TORRENT_BROADCAST_SOCKET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" +#include +#include +#include + +namespace libtorrent +{ + + bool is_local(address const& a); + address_v4 guess_local_address(asio::io_service&); + + typedef boost::function receive_handler_t; + + class broadcast_socket + { + public: + broadcast_socket(asio::io_service& ios, udp::endpoint const& multicast_endpoint + , receive_handler_t const& handler); + + void send(char const* buffer, int size, asio::error_code& ec); + void close(); + + private: + + struct socket_entry + { + socket_entry(boost::shared_ptr const& s): socket(s) {} + boost::shared_ptr socket; + char buffer[1024]; + udp::endpoint remote; + }; + + void on_receive(socket_entry* s, asio::error_code const& ec + , std::size_t bytes_transferred); + + std::list m_sockets; + udp::endpoint m_multicast_endpoint; + receive_handler_t m_on_receive; + + }; +} + +#endif + diff --git a/include/libtorrent/enum_net.hpp b/include/libtorrent/enum_net.hpp new file mode 100644 index 000000000..0c6063a2b --- /dev/null +++ b/include/libtorrent/enum_net.hpp @@ -0,0 +1,44 @@ +/* + +Copyright (c) 2007, 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_ENUM_NET_HPP_INCLUDED +#define TORRENT_ENUM_NET_HPP_INCLUDED + +#include "libtorrent/socket.hpp" + +namespace libtorrent +{ + std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec); +} + +#endif + diff --git a/include/libtorrent/http_connection.hpp b/include/libtorrent/http_connection.hpp index 409213857..ed639b678 100644 --- a/include/libtorrent/http_connection.hpp +++ b/include/libtorrent/http_connection.hpp @@ -48,9 +48,13 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { +struct http_connection; + typedef boost::function http_handler; +typedef boost::function http_connect_handler; + // TODO: add bind interface // when bottled, the last two arguments to the handler @@ -58,11 +62,13 @@ typedef boost::function, boost::noncopyable { http_connection(asio::io_service& ios, connection_queue& cc - , http_handler handler, bool bottled = true) + , http_handler const& handler, bool bottled = true + , http_connect_handler const& ch = http_connect_handler()) : m_sock(ios) , m_read_pos(0) , m_resolver(ios) , m_handler(handler) + , m_connect_handler(ch) , m_timer(ios) , m_last_receive(time_now()) , m_bottled(bottled) @@ -92,6 +98,8 @@ struct http_connection : boost::enable_shared_from_this, boost: , time_duration timeout, bool handle_redirect = true); void close(); + tcp::socket const& socket() const { return m_sock; } + private: void on_resolve(asio::error_code const& e @@ -112,6 +120,7 @@ private: tcp::resolver m_resolver; http_parser m_parser; http_handler m_handler; + http_connect_handler m_connect_handler; deadline_timer m_timer; time_duration m_timeout; ptime m_last_receive; diff --git a/include/libtorrent/lsd.hpp b/include/libtorrent/lsd.hpp index 9ffbcdfc3..e8eaf0df1 100644 --- a/include/libtorrent/lsd.hpp +++ b/include/libtorrent/lsd.hpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/socket.hpp" #include "libtorrent/peer_id.hpp" +#include "libtorrent/broadcast_socket.hpp" #include #include @@ -58,35 +59,26 @@ public: , peer_callback_t const& cb); ~lsd(); - void rebind(address const& listen_interface); +// void rebind(address const& listen_interface); void announce(sha1_hash const& ih, int listen_port); void close(); private: - static address_v4 lsd_multicast_address; - static udp::endpoint lsd_multicast_endpoint; - void resend_announce(asio::error_code const& e, std::string msg); - void on_announce(asio::error_code const& e + void on_announce(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); - void setup_receive(); +// void setup_receive(); peer_callback_t m_callback; // current retry count int m_retry_count; - // used to receive responses in - char m_receive_buffer[1024]; - - // the endpoint we received the message from - udp::endpoint m_remote; - // the udp socket used to send and receive // multicast messages on - datagram_socket m_socket; + broadcast_socket m_socket; // used to resend udp packets in case // they time out diff --git a/include/libtorrent/upnp.hpp b/include/libtorrent/upnp.hpp index d4b701aad..fc0650631 100644 --- a/include/libtorrent/upnp.hpp +++ b/include/libtorrent/upnp.hpp @@ -34,6 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_UPNP_HPP #include "libtorrent/socket.hpp" +#include "libtorrent/broadcast_socket.hpp" #include "libtorrent/http_connection.hpp" #include "libtorrent/connection_queue.hpp" @@ -56,9 +57,6 @@ POSSIBILITY OF SUCH DAMAGE. namespace libtorrent { -bool is_local(address const& a); -address_v4 guess_local_address(asio::io_service&); - // int: external tcp port // int: external udp port // std::string: error message @@ -72,8 +70,6 @@ public: , portmap_callback_t const& cb); ~upnp(); - void rebind(address const& listen_interface); - // maps the ports, if a port is set to 0 // it will not be mapped void set_mappings(int tcp, int udp); @@ -90,7 +86,7 @@ private: void update_mapping(int i, int port); void resend_request(asio::error_code const& e); - void on_reply(asio::error_code const& e + void on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred); void discover_device(); @@ -106,12 +102,15 @@ private: , int mapping); void on_expire(asio::error_code const& e); - void post(rootdevice& d, std::stringstream const& s - , std::string const& soap_action); void map_port(rootdevice& d, int i); void unmap_port(rootdevice& d, int i); void disable(); + void delete_port_mapping(rootdevice& d, int i); + void create_port_mapping(http_connection& c, rootdevice& d, int i); + void post(upnp::rootdevice const& d, std::string const& soap + , std::string const& soap_action); + struct mapping_t { mapping_t() @@ -198,18 +197,13 @@ private: // current retry count int m_retry_count; - // used to receive responses in - char m_receive_buffer[1024]; + asio::io_service& m_io_service; - // the endpoint we received the message from - udp::endpoint m_remote; + asio::strand m_strand; - // the local address we're listening on - address_v4 m_local_ip; - // the udp socket used to send and receive // multicast messages on the network - datagram_socket m_socket; + broadcast_socket m_socket; // used to resend udp packets in case // they time out @@ -217,8 +211,6 @@ private: // timer used to refresh mappings deadline_timer m_refresh_timer; - - asio::strand m_strand; bool m_disabled; bool m_closing; diff --git a/src/broadcast_socket.cpp b/src/broadcast_socket.cpp new file mode 100644 index 000000000..b7e491283 --- /dev/null +++ b/src/broadcast_socket.cpp @@ -0,0 +1,142 @@ +/* + +Copyright (c) 2007, 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/socket.hpp" +#include "libtorrent/enum_net.hpp" +#include "libtorrent/broadcast_socket.hpp" +#include +#include +#include + +namespace libtorrent +{ + bool is_local(address const& a) + { + if (a.is_v6()) return false; + address_v4 a4 = a.to_v4(); + unsigned long ip = a4.to_ulong(); + return ((ip & 0xff000000) == 0x0a000000 + || (ip & 0xfff00000) == 0xac100000 + || (ip & 0xffff0000) == 0xc0a80000); + } + + address_v4 guess_local_address(asio::io_service& ios) + { + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + // ignore the loopback + if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; + // ignore addresses that are not on a local network + if (!is_local(i->endpoint().address())) continue; + // ignore non-IPv4 addresses + if (i->endpoint().address().is_v4()) break; + } + if (i == udp::resolver_iterator()) return address_v4::any(); + return i->endpoint().address().to_v4(); + } + + broadcast_socket::broadcast_socket(asio::io_service& ios + , udp::endpoint const& multicast_endpoint + , receive_handler_t const& handler) + : m_multicast_endpoint(multicast_endpoint) + , m_on_receive(handler) + { + assert(m_multicast_endpoint.address().is_v4()); + assert(m_multicast_endpoint.address().to_v4().is_multicast()); + + using namespace asio::ip::multicast; + + asio::error_code ec; + std::vector
interfaces = enum_net_interfaces(ios, ec); + + for (std::vector
::const_iterator i = interfaces.begin() + , end(interfaces.end()); i != end; ++i) + { + // only broadcast to IPv4 addresses that are not local + if (!i->is_v4() || !is_local(*i)) continue; + // ignore the loopback interface + if (i->to_v4() == address_v4((127 << 24) + 1)) continue; + + boost::shared_ptr s(new datagram_socket(ios)); + s->open(udp::v4(), ec); + if (ec) continue; + s->set_option(datagram_socket::reuse_address(true), ec); + if (ec) continue; + s->bind(udp::endpoint(*i, 0), ec); + if (ec) continue; + s->set_option(join_group(multicast_endpoint.address()), ec); + if (ec) continue; + s->set_option(outbound_interface(i->to_v4()), ec); + if (ec) continue; + s->set_option(hops(255)); + m_sockets.push_back(socket_entry(s)); + socket_entry& se = m_sockets.back(); + s->async_receive_from(asio::buffer(se.buffer, sizeof(se.buffer)) + , se.remote, bind(&broadcast_socket::on_receive, this, &se, _1, _2)); + } + } + + void broadcast_socket::send(char const* buffer, int size, asio::error_code& ec) + { + for (std::list::iterator i = m_sockets.begin() + , end(m_sockets.end()); i != end; ++i) + { + asio::error_code e; + i->socket->send_to(asio::buffer(buffer, size), m_multicast_endpoint, 0, e); +// std::cerr << " sending on " << i->socket->local_endpoint().address().to_string() << std::endl; + if (e) ec = e; + } + } + + void broadcast_socket::on_receive(socket_entry* s, asio::error_code const& ec + , std::size_t bytes_transferred) + { + if (ec || bytes_transferred == 0) return; + m_on_receive(s->remote, s->buffer, bytes_transferred); + s->socket->async_receive_from(asio::buffer(s->buffer, sizeof(s->buffer)) + , s->remote, bind(&broadcast_socket::on_receive, this, s, _1, _2)); + } + + void broadcast_socket::close() + { + for (std::list::iterator i = m_sockets.begin() + , end(m_sockets.end()); i != end; ++i) + { + i->socket->close(); + } + } +} + + diff --git a/src/enum_net.cpp b/src/enum_net.cpp new file mode 100644 index 000000000..54af814d6 --- /dev/null +++ b/src/enum_net.cpp @@ -0,0 +1,133 @@ +/* + +Copyright (c) 2007, 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. + +*/ + +#if defined __linux__ || defined __MACH__ +#include +#include +#include +#endif + +#include "libtorrent/enum_net.hpp" + +namespace libtorrent +{ + std::vector
const& enum_net_interfaces(asio::io_service& ios, asio::error_code& ec) + { + static std::vector
ret; + if (!ret.empty()) return ret; + +#if defined __linux__ || defined __MACH__ || defined(__FreeBSD__) + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + ec = asio::error::fault; + return ret; + } + ifconf ifc; + char buf[1024]; + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) + { + close(s); + ec = asio::error::fault; + return ret; + } + close(s); + + char *ifr = (char*)ifc.ifc_req; + int remaining = ifc.ifc_len; + + while (remaining) + { + ifreq const& item = *reinterpret_cast(ifr); + if (item.ifr_addr.sa_family == AF_INET) + { + ret.push_back(address::from_string( + inet_ntoa(((sockaddr_in const*)&item.ifr_addr)->sin_addr))); + } + +#if defined __MACH__ || defined(__FreeBSD__) + int current_size = item.ifr_addr.sa_len + IFNAMSIZ; +#elif defined __linux__ + int current_size = sizeof(ifreq); +#endif + ifr += current_size; + remaining -= current_size; + } + +#elif defined WIN32 + + SOCKET s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == SOCKET_ERROR) + { + ec = asio::error::fault; + return ret; + } + + INTERFACE_INFO buffer[30]; + DWORD size; + + if (WSAIoctl(s, SIO_GET_INTERFACE_LIST, 0, 0, buffer, + sizeof(buffer), &size, 0, 0) != 0) + { + closesocket(s); + ec = asio::error::fault; + return ret; + } + closesocket(s); + + int n = size / sizeof(INTERFACE_INFO); + + for (int i = 0; i < n; ++i) + { + sockaddr_in *sockaddr = (sockaddr_in*)&buffer[i].iiAddress; + address a(address::from_string(inet_ntoa(sockaddr->sin_addr))); + if (a == address_v4::any()) continue; + ret.push_back(a); + } + +#else + // make a best guess of the interface we're using and its IP + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + ret.push_back(i->endpoint().address()); + } +#endif + return ret; + } + +} + + diff --git a/src/http_connection.cpp b/src/http_connection.cpp index 53798cae1..788fc3aff 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -165,6 +165,7 @@ void http_connection::on_connect(asio::error_code const& e if (!e) { m_last_receive = time_now(); + if (m_connect_handler) m_connect_handler(*this); asio::async_write(m_sock, asio::buffer(sendbuffer) , bind(&http_connection::on_write, shared_from_this(), _1)); } diff --git a/src/lsd.cpp b/src/lsd.cpp index 76f25548d..d7590ec47 100644 --- a/src/lsd.cpp +++ b/src/lsd.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/lsd.hpp" #include "libtorrent/io.hpp" #include "libtorrent/http_tracker_connection.hpp" + #include #include #include @@ -52,76 +53,22 @@ namespace libtorrent address_v4 guess_local_address(asio::io_service&); } -address_v4 lsd::lsd_multicast_address; -udp::endpoint lsd::lsd_multicast_endpoint; - lsd::lsd(io_service& ios, address const& listen_interface , peer_callback_t const& cb) : m_callback(cb) , m_retry_count(0) - , m_socket(ios) + , m_socket(ios, udp::endpoint(address_v4::from_string("239.192.152.143"), 6771) + , bind(&lsd::on_announce, this, _1, _2, _3)) , m_broadcast_timer(ios) , m_disabled(false) { - // Bittorrent Local discovery multicast address and port - lsd_multicast_address = address_v4::from_string("239.192.152.143"); - lsd_multicast_endpoint = udp::endpoint(lsd_multicast_address, 6771); - #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log.open("lsd.log", std::ios::in | std::ios::out | std::ios::trunc); #endif - assert(lsd_multicast_address.is_multicast()); - rebind(listen_interface); } lsd::~lsd() {} -void lsd::rebind(address const& listen_interface) -{ - address_v4 local_ip = address_v4::any(); - if (listen_interface.is_v4() && listen_interface != address_v4::any()) - { - local_ip = listen_interface.to_v4(); - } - - try - { - // the local interface hasn't changed - if (m_socket.is_open() - && m_socket.local_endpoint().address() == local_ip) - return; - - m_socket.close(); - - using namespace asio::ip::multicast; - - m_socket.open(udp::v4()); - m_socket.set_option(datagram_socket::reuse_address(true)); - m_socket.bind(udp::endpoint(local_ip, 6771)); - -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "local ip: " << local_ip << std::endl; -#endif - - m_socket.set_option(join_group(lsd_multicast_address)); - m_socket.set_option(outbound_interface(local_ip)); - m_socket.set_option(enable_loopback(true)); - m_socket.set_option(hops(255)); - } - catch (std::exception& e) - { -#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) - m_log << "socket multicast error " << e.what() - << ". disabling local service discovery" << std::endl; -#endif - m_disabled = true; - return; - } - m_disabled = false; - - setup_receive(); -} - void lsd::announce(sha1_hash const& ih, int listen_port) { if (m_disabled) return; @@ -136,8 +83,7 @@ void lsd::announce(sha1_hash const& ih, int listen_port) m_retry_count = 0; asio::error_code ec; - m_socket.send_to(asio::buffer(msg.c_str(), msg.size() - 1) - , lsd_multicast_endpoint, 0, ec); + m_socket.send(msg.c_str(), int(msg.size()), ec); if (ec) { m_disabled = true; @@ -157,8 +103,8 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try { if (e) return; - m_socket.send_to(asio::buffer(msg, msg.size() - 1) - , lsd_multicast_endpoint); + asio::error_code ec; + m_socket.send(msg.c_str(), int(msg.size()), ec); ++m_retry_count; if (m_retry_count >= 5) @@ -170,14 +116,13 @@ void lsd::resend_announce(asio::error_code const& e, std::string msg) try catch (std::exception&) {} -void lsd::on_announce(asio::error_code const& e +void lsd::on_announce(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) { using namespace libtorrent::detail; - if (e) return; - char* p = m_receive_buffer; - char* end = m_receive_buffer + bytes_transferred; + char* p = buffer; + char* end = buffer + bytes_transferred; char* line = std::find(p, end, '\n'); for (char* i = p; i < line; ++i) *i = std::tolower(*i); #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) @@ -190,7 +135,6 @@ void lsd::on_announce(asio::error_code const& e m_log << time_now_string() << " *** assumed 'bt-search', ignoring" << std::endl; #endif - setup_receive(); return; } p = line + 1; @@ -223,25 +167,15 @@ void lsd::on_announce(asio::error_code const& e { #if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) m_log << time_now_string() - << " *** incoming local announce " << m_remote.address() + << " *** incoming local announce " << from.address() << ":" << port << " ih: " << ih << std::endl; #endif // we got an announce, pass it on through the callback - try { m_callback(tcp::endpoint(m_remote.address(), port), ih); } + try { m_callback(tcp::endpoint(from.address(), port), ih); } catch (std::exception&) {} } - setup_receive(); } -void lsd::setup_receive() try -{ - assert(m_socket.is_open()); - m_socket.async_receive_from(asio::buffer(m_receive_buffer - , sizeof(m_receive_buffer)), m_remote, bind(&lsd::on_announce, this, _1, _2)); -} -catch (std::exception&) -{} - void lsd::close() { m_socket.close(); diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 3d687eaf8..f973690b3 100755 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -1709,16 +1709,6 @@ namespace detail bool new_listen_address = m_listen_interface.address() != new_interface.address(); - if (new_listen_address) - { - if (m_natpmp.get()) - m_natpmp->rebind(new_interface.address()); - if (m_upnp.get()) - m_upnp->rebind(new_interface.address()); - if (m_lsd.get()) - m_lsd->rebind(new_interface.address()); - } - if (m_natpmp.get()) m_natpmp->set_mappings(m_listen_interface.port(), 0); if (m_upnp.get()) diff --git a/src/upnp.cpp b/src/upnp.cpp index e16c136bb..995897ad5 100644 --- a/src/upnp.cpp +++ b/src/upnp.cpp @@ -53,38 +53,10 @@ POSSIBILITY OF SUCH DAMAGE. using boost::bind; using namespace libtorrent; -address_v4 upnp::upnp_multicast_address; -udp::endpoint upnp::upnp_multicast_endpoint; - namespace libtorrent { - bool is_local(address const& a) - { - if (a.is_v6()) return false; - address_v4 a4 = a.to_v4(); - unsigned long ip = a4.to_ulong(); - return ((ip & 0xff000000) == 0x0a000000 - || (ip & 0xfff00000) == 0xac100000 - || (ip & 0xffff0000) == 0xc0a80000); - } - - address_v4 guess_local_address(asio::io_service& ios) - { - // make a best guess of the interface we're using and its IP - udp::resolver r(ios); - udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); - for (;i != udp::resolver_iterator(); ++i) - { - // ignore the loopback - if (i->endpoint().address() == address_v4((127 << 24) + 1)) continue; - // ignore addresses that are not on a local network - if (!is_local(i->endpoint().address())) continue; - // ignore non-IPv4 addresses - if (i->endpoint().address().is_v4()) break; - } - if (i == udp::resolver_iterator()) return address_v4::any(); - return i->endpoint().address().to_v4(); - } + bool is_local(address const& a); + address_v4 guess_local_address(asio::io_service&); } upnp::upnp(io_service& ios, connection_queue& cc @@ -95,89 +67,27 @@ upnp::upnp(io_service& ios, connection_queue& cc , m_user_agent(user_agent) , m_callback(cb) , m_retry_count(0) - , m_socket(ios) + , m_io_service(ios) + , m_strand(ios) + , m_socket(ios, udp::endpoint(address_v4::from_string("239.255.255.250"), 1900) + , m_strand.wrap(bind(&upnp::on_reply, this, _1, _2, _3))) , m_broadcast_timer(ios) , m_refresh_timer(ios) - , m_strand(ios) , m_disabled(false) , m_closing(false) , m_cc(cc) { - // UPnP multicast address and port - upnp_multicast_address = address_v4::from_string("239.255.255.250"); - upnp_multicast_endpoint = udp::endpoint(upnp_multicast_address, 1900); - #ifdef TORRENT_UPNP_LOGGING m_log.open("upnp.log", std::ios::in | std::ios::out | std::ios::trunc); #endif - rebind(listen_interface); + m_retry_count = 0; + discover_device(); } upnp::~upnp() { } -void upnp::rebind(address const& listen_interface) try -{ - address_v4 bind_to = address_v4::any(); - if (listen_interface.is_v4() && listen_interface != address_v4::any()) - { - m_local_ip = listen_interface.to_v4(); - bind_to = listen_interface.to_v4(); - if (!is_local(m_local_ip)) - { - // the local address seems to be an external - // internet address. Assume it is not behind a NAT - throw std::runtime_error("local IP is not on a local network"); - } - } - else - { - m_local_ip = guess_local_address(m_socket.io_service()); - bind_to = address_v4::any(); - } - - if (!is_local(m_local_ip)) - { - throw std::runtime_error("local host is probably not on a NATed " - "network. disabling UPnP"); - } - -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " local ip: " << m_local_ip.to_string() - << " bind to: " << bind_to.to_string() << std::endl; -#endif - - // the local interface hasn't changed - if (m_socket.is_open() - && m_socket.local_endpoint().address() == m_local_ip) - return; - - m_socket.close(); - - using namespace asio::ip::multicast; - - m_socket.open(udp::v4()); - m_socket.set_option(datagram_socket::reuse_address(true)); - m_socket.bind(udp::endpoint(bind_to, 0)); - - m_socket.set_option(join_group(upnp_multicast_address)); - m_socket.set_option(outbound_interface(bind_to)); - m_socket.set_option(hops(255)); - m_disabled = false; - - m_retry_count = 0; - discover_device(); -} -catch (std::exception& e) -{ - disable(); - std::stringstream msg; - msg << "UPnP portmapping disabled: " << e.what(); - m_callback(0, 0, msg.str()); -}; - void upnp::discover_device() try { const char msearch[] = @@ -188,20 +98,20 @@ void upnp::discover_device() try "MX:3\r\n" "\r\n\r\n"; - m_socket.async_receive_from(asio::buffer(m_receive_buffer - , sizeof(m_receive_buffer)), m_remote, m_strand.wrap(bind( - &upnp::on_reply, this, _1, _2))); - asio::error_code ec; #ifdef TORRENT_DEBUG_UPNP // simulate packet loss if (m_retry_count & 1) #endif - m_socket.send_to(asio::buffer(msearch, sizeof(msearch) - 1) - , upnp_multicast_endpoint, 0, ec); + m_socket.send(msearch, sizeof(msearch) - 1, ec); if (ec) { +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> Broadcast FAILED: " << ec.message() << std::endl + << "aborting" << std::endl; +#endif disable(); return; } @@ -292,7 +202,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -316,20 +226,13 @@ catch (std::exception&) } #endif -void upnp::on_reply(asio::error_code const& e +void upnp::on_reply(udp::endpoint const& from, char* buffer , std::size_t bytes_transferred) #ifndef NDEBUG try #endif { using namespace libtorrent::detail; - if (e) - { -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() << " *** on_reply aborted: " << e.message() << std::endl; -#endif - return; - } // parse out the url for the device @@ -348,15 +251,14 @@ try http_parser p; try { - p.incoming(buffer::const_interval(m_receive_buffer - , m_receive_buffer + bytes_transferred)); + p.incoming(buffer::const_interval(buffer + , buffer + bytes_transferred)); } catch (std::exception& e) { #ifdef TORRENT_UPNP_LOGGING m_log << time_now_string() - << " <== Rootdevice responded with incorrect HTTP packet: " - << e.what() << ". Ignoring device" << std::endl; + << " <== Rootdevice responded with incorrect HTTP packet. Ignoring device" << std::endl; #endif return; } @@ -469,7 +371,7 @@ try rootdevice& d = const_cast(*i); try { - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_xml, this, _1, _2 , boost::ref(d))))); d.upnp_connection->get(d.url); @@ -494,7 +396,7 @@ catch (std::exception&) }; #endif -void upnp::post(rootdevice& d, std::stringstream const& soap +void upnp::post(upnp::rootdevice const& d, std::string const& soap , std::string const& soap_action) { std::stringstream header; @@ -502,12 +404,40 @@ void upnp::post(rootdevice& d, std::stringstream const& soap header << "POST " << d.control_url << " HTTP/1.1\r\n" "Host: " << d.hostname << ":" << d.port << "\r\n" "Content-Type: text/xml; charset=\"utf-8\"\r\n" - "Content-Length: " << soap.str().size() << "\r\n" - "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap.str(); + "Content-Length: " << soap.size() << "\r\n" + "Soapaction: \"" << d.service_namespace << "#" << soap_action << "\"\r\n\r\n" << soap; d.upnp_connection->sendbuffer = header.str(); - d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) - , seconds(10)); + +#ifdef TORRENT_UPNP_LOGGING + m_log << time_now_string() + << " ==> sending: " << soap << std::endl; +#endif + +} + +void upnp::create_port_mapping(http_connection& c, rootdevice& d, int i) +{ + std::string soap_action = "AddPortMapping"; + + std::stringstream soap; + + soap << "\n" + "" + ""; + + soap << "" + "" << d.mapping[i].external_port << "" + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" + "" << d.mapping[i].local_port << "" + "" << c.socket().local_endpoint().address().to_string() << "" + "1" + "" << m_user_agent << "" + "" << d.lease_duration << ""; + soap << ""; + + post(d, soap.str(), soap_action); } void upnp::map_port(rootdevice& d, int i) @@ -528,14 +458,21 @@ void upnp::map_port(rootdevice& d, int i) assert(!d.upnp_connection); assert(d.service_namespace); - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_map_response, this, _1, _2 - , boost::ref(d), i)))); + , boost::ref(d), i)), true + , bind(&upnp::create_port_mapping, this, _1, boost::ref(d), i))); - std::string soap_action = "AddPortMapping"; + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); +} +void upnp::delete_port_mapping(rootdevice& d, int i) +{ std::stringstream soap; + std::string soap_action = "DeletePortMapping"; + soap << "\n" "" @@ -543,20 +480,10 @@ void upnp::map_port(rootdevice& d, int i) soap << "" "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << "" - "" << d.mapping[i].local_port << "" - "" << m_local_ip.to_string() << "" - "1" - "" << m_user_agent << "" - "" << d.lease_duration << ""; + "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; soap << ""; - - post(d, soap, soap_action); -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> AddPortMapping: " << soap.str() << std::endl; -#endif + post(d, soap.str(), soap_action); } // requires the mutex to be locked @@ -574,29 +501,13 @@ void upnp::unmap_port(rootdevice& d, int i) } return; } - d.upnp_connection.reset(new http_connection(m_socket.io_service() + d.upnp_connection.reset(new http_connection(m_io_service , m_cc, m_strand.wrap(bind(&upnp::on_upnp_unmap_response, this, _1, _2 - , boost::ref(d), i)))); + , boost::ref(d), i)), true + , bind(&upnp::delete_port_mapping, this, boost::ref(d), i))); - std::string soap_action = "DeletePortMapping"; - - std::stringstream soap; - - soap << "\n" - "" - ""; - - soap << "" - "" << d.mapping[i].external_port << "" - "" << (d.mapping[i].protocol ? "UDP" : "TCP") << ""; - soap << ""; - - post(d, soap, soap_action); -#ifdef TORRENT_UPNP_LOGGING - m_log << time_now_string() - << " ==> DeletePortMapping: " << soap.str() << std::endl; -#endif + d.upnp_connection->start(d.hostname, boost::lexical_cast(d.port) + , seconds(10)); } namespace diff --git a/test/test_upnp.cpp b/test/test_upnp.cpp index d9a6f581a..29e586ba5 100644 --- a/test/test_upnp.cpp +++ b/test/test_upnp.cpp @@ -16,9 +16,9 @@ int main(int argc, char* argv[]) io_service ios; std::string user_agent = "test agent"; - if (argc != 4) + if (argc != 3) { - std::cerr << "usage: " << argv[0] << " bind-address tcp-port udp-port" << std::endl; + std::cerr << "usage: " << argv[0] << " tcp-port udp-port" << std::endl; return 1; } @@ -34,20 +34,11 @@ int main(int argc, char* argv[]) ios.reset(); ios.run(); - upnp_handler.rebind(address_v4::from_string(argv[1])); - timer.expires_from_now(seconds(2)); - timer.async_wait(boost::bind(&io_service::stop, boost::ref(ios))); - - std::cerr << "rebinding to IP " << argv[1] - << " broadcasting for UPnP device" << std::endl; - - ios.reset(); - ios.run(); - upnp_handler.set_mappings(atoi(argv[2]), atoi(argv[3])); + upnp_handler.set_mappings(atoi(argv[1]), atoi(argv[2])); timer.expires_from_now(seconds(5)); timer.async_wait(boost::bind(&io_service::stop, boost::ref(ios))); - std::cerr << "mapping ports TCP: " << argv[2] - << " UDP: " << argv[3] << std::endl; + std::cerr << "mapping ports TCP: " << argv[1] + << " UDP: " << argv[2] << std::endl; ios.reset(); ios.run();