From ecaa3068d528a6f75a3692be4945e3d1732dacbd Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Thu, 15 Mar 2007 22:03:56 +0000 Subject: [PATCH] added natpmp support. bumped version number to 0.13. added piece priority support to the piece picker. optmized and simplified the piece picker in the process. --- ChangeLog | 4 + Jamfile | 1 + docs/features.rst | 1 + docs/manual.html | 6 +- docs/manual.rst | 6 +- examples/client_test.cpp | 10 +- include/Makefile.am | 1 + include/libtorrent/aux_/session_impl.hpp | 28 +- include/libtorrent/natpmp.hpp | 116 +++++ include/libtorrent/piece_picker.hpp | 113 +++-- include/libtorrent/session_settings.hpp | 3 +- include/libtorrent/version.hpp | 4 +- src/Makefile.am | 5 +- src/bt_peer_connection.cpp | 8 +- src/natpmp.cpp | 324 +++++++++++++ src/peer_connection.cpp | 30 +- src/piece_picker.cpp | 582 ++++++++++------------- src/session_impl.cpp | 71 ++- src/torrent.cpp | 39 +- 19 files changed, 912 insertions(+), 440 deletions(-) create mode 100644 include/libtorrent/natpmp.hpp create mode 100644 src/natpmp.cpp diff --git a/ChangeLog b/ChangeLog index adc67c55f..ae4ae85cc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ + * added support for NAT-PMP + * added support for piece priorities. Piece filtering is now set as + a priority + release 0.12 * fixes to make the DHT more compatible diff --git a/Jamfile b/Jamfile index 6fc8be072..ed30d2a3a 100755 --- a/Jamfile +++ b/Jamfile @@ -41,6 +41,7 @@ SOURCES = peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp + natpmp.cpp piece_picker.cpp policy.cpp session.cpp diff --git a/docs/features.rst b/docs/features.rst index 9d58bb9c3..04a21d6a9 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -32,6 +32,7 @@ following features: * trackerless torrents (using the Mainline kademlia DHT protocol) with some `DHT extensions`_. * support for IPv6 +* NAT-PMP support (automatic port mapping on routers that supports it) * piece-wise, unordered, incremental file allocation * uses separate threads for checking files and for main downloader, with a fool-proof thread-safe library interface. (i.e. There's no way for the diff --git a/docs/manual.html b/docs/manual.html index f3c611476..e69434a2d 100755 --- a/docs/manual.html +++ b/docs/manual.html @@ -657,7 +657,11 @@ response to a get_peerssearch_branching is the number of concurrent search request the node will send when announcing and refreshing the routing table. This parameter is called alpha in the kademlia paper.

-

service_port is the udp port the node will listen to.

+

service_port is the udp port the node will listen to. This will default +to 0, which means the udp listen port will be the same as the tcp listen +port. This is in general a good idea, since some NAT implementations +reserves the udp port for any mapped tcp port, and vice versa. NAT-PMP +guarantees this for example.

max_fail_count is the maximum number of failed tries to contact a node before it is removed from the routing table. If there are known working nodes that are ready to replace a failing node, it will be replaced immediately, diff --git a/docs/manual.rst b/docs/manual.rst index bfde0aa66..01633fca5 100755 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -542,7 +542,11 @@ response to a ``get_peers`` message from another node. send when announcing and refreshing the routing table. This parameter is called alpha in the kademlia paper. -``service_port`` is the udp port the node will listen to. +``service_port`` is the udp port the node will listen to. This will default +to 0, which means the udp listen port will be the same as the tcp listen +port. This is in general a good idea, since some NAT implementations +reserves the udp port for any mapped tcp port, and vice versa. NAT-PMP +guarantees this for example. ``max_fail_count`` is the maximum number of failed tries to contact a node before it is removed from the routing table. If there are known working nodes diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 7070ec513..bae590478 100755 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -252,6 +252,9 @@ void print_peer_info(std::ostream& out, std::vector const for (std::vector::const_iterator i = peers.begin(); i != peers.end(); ++i) { + if (i->flags & (peer_info::handshake | peer_info::connecting | peer_info::queued)) + continue; + out.fill(' '); out.width(2); out << esc("32") << (i->down_speed > 0 ? add_suffix(i->down_speed) + "/s " : " ") @@ -590,9 +593,6 @@ int main(int ac, char* av[]) #ifndef TORRENT_DISABLE_DHT settings.use_dht_as_fallback = false; - dht_settings s; - s.service_port = listen_port; - ses.set_dht_settings(s); boost::filesystem::ifstream dht_state_file(".dht_state" , std::ios_base::binary); dht_state_file.unsetf(std::ios_base::skipws); @@ -968,10 +968,10 @@ int main(int ac, char* av[]) for (int i = 0; i < info.num_files(); ++i) { if (file_progress[i] == 1.f) - out << progress_bar(file_progress[i], 20, "32") << " " + out << progress_bar(file_progress[i], 40, "32") << " " << info.file_at(i).path.leaf() << "\n"; else - out << progress_bar(file_progress[i], 20, "33") << " " + out << progress_bar(file_progress[i], 40, "33") << " " << info.file_at(i).path.leaf() << "\n"; } diff --git a/include/Makefile.am b/include/Makefile.am index fdbdbf683..7b1f4d66a 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -22,6 +22,7 @@ libtorrent/peer.hpp \ libtorrent/peer_connection.hpp \ libtorrent/bt_peer_connection.hpp \ libtorrent/web_peer_connection.hpp \ +libtorrent/natpmp.hpp \ libtorrent/peer_id.hpp \ libtorrent/peer_info.hpp \ libtorrent/peer_request.hpp \ diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 04d71885c..eff7e8a98 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -77,6 +77,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/stat.hpp" #include "libtorrent/file_pool.hpp" #include "libtorrent/bandwidth_manager.hpp" +#include "libtorrent/natpmp.hpp" namespace libtorrent { @@ -213,11 +214,16 @@ namespace libtorrent void add_dht_node(udp::endpoint n); void add_dht_router(std::pair const& node); void set_dht_settings(dht_settings const& s); - dht_settings const& kad_settings() const { return m_dht_settings; } + dht_settings const& get_dht_settings() const { return m_dht_settings; } void start_dht(entry const& startup_state); void stop_dht(); entry dht_state() const; #endif + + // called when a port mapping is successful, or a router returns + // a failure to map a port + void on_port_mapping(int tcp_port, int udp_port, std::string const& errmsg); + bool is_aborted() const { return m_abort; } void set_ip_filter(ip_filter const& f); @@ -328,6 +334,15 @@ 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; @@ -365,7 +380,18 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT boost::intrusive_ptr m_dht; dht_settings m_dht_settings; + // if this is set to true, the dht listen port + // will be set to the same as the tcp listen port + // and will be synchronlized with it as it changes + // it defaults to true + bool m_dht_same_port; + + // see m_external_listen_port. This is the same + // but for the udp port used by the DHT. + int m_external_udp_port; #endif + natpmp m_natpmp; + // the timer used to fire the second_tick deadline_timer m_timer; #ifndef NDEBUG diff --git a/include/libtorrent/natpmp.hpp b/include/libtorrent/natpmp.hpp new file mode 100644 index 000000000..05ac1920e --- /dev/null +++ b/include/libtorrent/natpmp.hpp @@ -0,0 +1,116 @@ +#ifndef TORRENT_NATPMP_HPP +#define TORRENT_NATPMP_HPP + +#include +#include +#include + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) +#include +#endif + +namespace libtorrent +{ + +// int: external tcp port +// int: external udp port +// std::string: error message +typedef boost::function portmap_callback_t; + +class natpmp +{ +public: + natpmp(io_service& ios, portmap_callback_t const& cb); + + // maps the ports, if a port is set to 0 + // it will not be mapped + void set_mappings(int tcp, int udp); + + void close(); + +private: + + void update_mapping(int i, int port); + void send_map_request(int i); + void resend_request(int i, asio::error_code const& e); + void on_reply(asio::error_code const& e + , std::size_t bytes_transferred); + void try_next_mapping(int i); + void update_expiration_timer(); + void refresh_mapping(int i); + void mapping_expired(asio::error_code const& e, int i); + + struct mapping + { + mapping() + : need_update(false) + , local_port(0) + , external_port(0) + , protocol(1) + {} + + // indicates that the mapping has changed + // and needs an update + bool need_update; + + // the time the port mapping will expire + boost::posix_time::ptime expires; + + // the local port for this mapping. If this is set + // to 0, the mapping is not in use + int local_port; + + // the external (on the NAT router) port + // for the mapping. This is the port we + // should announce to others + int external_port; + + // 1 = udp, 2 = tcp + int protocol; + }; + + portmap_callback_t m_callback; + + // 0 is tcp and 1 is udp + mapping m_mappings[2]; + + // the endpoint to the nat router + udp::endpoint m_nat_endpoint; + + // this is the mapping that is currently + // being updated. It is -1 in case no + // mapping is being updated at the moment + int m_currently_mapping; + + // current retry count + int m_retry_count; + + // used to receive responses in + char m_response_buffer[16]; + + // the endpoint we received the message from + udp::endpoint m_remote; + + // the udp socket used to communicate + // with the NAT router + datagram_socket m_socket; + + // used to resend udp packets in case + // they time out + deadline_timer m_send_timer; + + // timer used to refresh mappings + deadline_timer m_refresh_timer; + + bool m_disabled; + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + std::ofstream m_log; +#endif +}; + +} + + +#endif + diff --git a/include/libtorrent/piece_picker.hpp b/include/libtorrent/piece_picker.hpp index 429ec34f6..ceca530d6 100755 --- a/include/libtorrent/piece_picker.hpp +++ b/include/libtorrent/piece_picker.hpp @@ -36,12 +36,14 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef _MSC_VER #pragma warning(push, 1) #endif #include +#include #ifdef _MSC_VER #pragma warning(pop) @@ -137,19 +139,15 @@ namespace libtorrent // (i.e. we don't have to maintain a refcount) void we_have(int index); - // This will mark a piece as unfiltered, and if it was - // previously marked as filtered, it will be considered - // interesting again and be placed in the piece list available - // for downloading. - void mark_as_unfiltered(int index); + // sets the priority of a piece. + // 0 is filtered, i.e. do not download + // 1 is normal priority + // 2 is high priority + // 3 is maximum priority (availability is ignored) + void set_piece_priority(int index, int prio); - // This will mark a piece as filtered. The piece will be - // removed from the list of pieces avalable for downloading - // and hence, will not be downloaded. - void mark_as_filtered(int index); - - // returns true if the pieces at 'index' is marked as filtered - bool is_filtered(int index) const; + // returns the priority for the piece at 'index' + int piece_priority(int index) const; // fills the bitmask with 1's for pieces that are filtered void filtered_pieces(std::vector& mask) const; @@ -236,7 +234,7 @@ namespace libtorrent piece_pos(int peer_count_, int index_) : peer_count(peer_count_) , downloading(0) - , filtered(0) + , piece_priority(1) , index(index_) { assert(peer_count_ >= 0); @@ -244,26 +242,52 @@ namespace libtorrent } // selects which vector to look in - unsigned peer_count : 11; + unsigned peer_count : 10; // is 1 if the piece is marked as being downloaded unsigned downloading : 1; - // is 1 if the piece is filtered (not to be downloaded) - unsigned filtered : 1; + // is 0 if the piece is filtered (not to be downloaded) + // 1 is normal priority (default) + // 2 is high priority + // 3 is maximum priority (ignores availability) + unsigned piece_priority : 2; // index in to the piece_info vector unsigned index : 19; - enum { we_have_index = 0x3ffff }; + enum + { + // index is set to this to indicate that we have the + // piece. There is no entry for the piece in the + // buckets if this is the case. + we_have_index = 0x7ffff, + // the priority value that means the piece is filtered + filter_priority = 0, + // the max number the peer count can hold + max_peer_count = 0x3ff + }; + + bool have() const { return index == we_have_index; } + void set_have() { index = we_have_index; assert(have()); } + + bool filtered() const { return piece_priority == filter_priority; } + void filtered(bool f) { piece_priority = f ? filter_priority : 0; } int priority(int limit) const { - return peer_count >= (unsigned)limit ? limit : peer_count; + if (filtered() || have()) return 0; + // pieces we are currently downloading are prioritized + int prio = downloading ? peer_count : peer_count * 2; + // if the peer_count is 0 or 1, the priority cannot be higher + if (prio <= 1) return prio; + if (prio >= limit * 2) prio = limit * 2; + // the different priority levels + switch (piece_priority) + { + case 2: return prio - 1; + case 3: return 1; + } + return prio; } - - bool ordered(int limit) const - { - return peer_count >= (unsigned)limit; - } - + bool operator!=(piece_pos p) const { return index != p.index || peer_count != p.peer_count; } @@ -272,27 +296,23 @@ namespace libtorrent }; + BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 4); + + bool is_ordered(int priority) const + { + return priority >= m_sequenced_download_threshold * 2; + } void add(int index); - void move(bool downloading, bool filtered, int vec_index, int elem_index); - void remove(bool downloading, bool filtered, int vec_index, int elem_index); - std::vector >& pick_piece_info_vector(bool downloading - , bool filtered); + void move(int vec_index, int elem_index); +// void remove(int vec_index, int elem_index); - std::vector > const& pick_piece_info_vector( - bool downloading, bool filtered) const; - - int add_interesting_blocks_free(const std::vector& piece_list - , const std::vector& pieces - , std::vector& interesting_blocks - , int num_blocks, bool prefer_whole_pieces) const; - - int add_interesting_blocks_partial(const std::vector& piece_list - , const std::vector& pieces - , std::vector& interesting_blocks - , std::vector& backup_blocks - , int num_blocks, bool prefer_whole_pieces - , tcp::endpoint peer) const; + int add_interesting_blocks(const std::vector& piece_list + , const std::vector& pieces + , std::vector& interesting_blocks + , std::vector& backup_blocks + , int num_blocks, bool prefer_whole_pieces + , tcp::endpoint peer) const; // this vector contains all pieces we don't have. @@ -300,14 +320,11 @@ namespace libtorrent // that no peer have, the vector at index 1 contains // all pieces that exactly one peer have, index 2 contains // all pieces exactly two peers have and so on. + // this is not entirely true. The availibility of a piece + // is adjusted depending on its priority. But the principle + // is that the higher index, the lower priority a piece has. std::vector > m_piece_info; - // this vector has the same structure as m_piece_info - // but only contains pieces we are currently downloading - // they have higher priority than pieces we aren't downloading - // during piece picking - std::vector > m_downloading_piece_info; - // this maps indices to number of peers that has this piece and // index into the m_piece_info vectors. // piece_pos::we_have_index means that we have the piece, so it diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index 7f6418d74..646586e26 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -167,7 +167,7 @@ namespace libtorrent dht_settings() : max_peers_reply(50) , search_branching(5) - , service_port(6881) + , service_port(0) , max_fail_count(20) {} @@ -180,6 +180,7 @@ namespace libtorrent int search_branching; // the listen port for the dht. This is a UDP port. + // zero means use the same as the tcp interface int service_port; // the maximum number of times a node can fail diff --git a/include/libtorrent/version.hpp b/include/libtorrent/version.hpp index a96a4d7a4..de1b8bcc8 100755 --- a/include/libtorrent/version.hpp +++ b/include/libtorrent/version.hpp @@ -34,8 +34,8 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_VERSION_HPP_INCLUDED #define LIBTORRENT_VERSION_MAJOR 0 -#define LIBTORRENT_VERSION_MINOR 12 +#define LIBTORRENT_VERSION_MINOR 13 -#define LIBTORRENT_VERSION "0.12.0.0" +#define LIBTORRENT_VERSION "0.13.0.0" #endif diff --git a/src/Makefile.am b/src/Makefile.am index d4c4974a0..71bf4b150 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,8 +3,8 @@ lib_LTLIBRARIES = libtorrent.la libtorrent_la_SOURCES = allocate_resources.cpp \ bandwidth_manager.cpp entry.cpp escape_string.cpp \ peer_connection.cpp bt_peer_connection.cpp web_peer_connection.cpp \ -piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp stat.cpp \ -storage.cpp torrent.cpp torrent_handle.cpp \ +natpmp.cpp piece_picker.cpp policy.cpp session.cpp session_impl.cpp sha1.cpp \ +stat.cpp storage.cpp torrent.cpp torrent_handle.cpp \ torrent_info.cpp tracker_manager.cpp \ http_tracker_connection.cpp udp_tracker_connection.cpp \ alert.cpp identify_client.cpp ip_filter.cpp file.cpp metadata_transfer.cpp \ @@ -50,6 +50,7 @@ $(top_srcdir)/include/libtorrent/peer.hpp \ $(top_srcdir)/include/libtorrent/peer_connection.hpp \ $(top_srcdir)/include/libtorrent/bt_peer_connection.hpp \ $(top_srcdir)/include/libtorrent/web_peer_connection.hpp \ +$(top_srcdir)/include/libtorrent/natpmp.hpp \ $(top_srcdir)/include/libtorrent/peer_id.hpp \ $(top_srcdir)/include/libtorrent/peer_info.hpp \ $(top_srcdir)/include/libtorrent/peer_request.hpp \ diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index d96eea92a..ed6e93a07 100755 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -163,7 +163,11 @@ namespace libtorrent void bt_peer_connection::write_dht_port(int listen_port) { INVARIANT_CHECK; - +#ifdef TORRENT_VERBOSE_LOGGING + using namespace boost::posix_time; + (*m_logger) << to_simple_string(second_clock::universal_time()) + << " ==> DHT_PORT [ " << listen_port << " ]\n"; +#endif buffer::interval packet = allocate_send_buffer(7); detail::write_uint32(3, packet.begin); detail::write_uint8(msg_dht_port, packet.begin); @@ -1287,7 +1291,7 @@ namespace libtorrent #ifndef TORRENT_DISABLE_DHT if (m_supports_dht_port && m_ses.m_dht) - write_dht_port(m_ses.kad_settings().service_port); + write_dht_port(m_ses.get_dht_settings().service_port); #endif m_client_version = identify_client(pid); diff --git a/src/natpmp.cpp b/src/natpmp.cpp new file mode 100644 index 000000000..0fb1975fe --- /dev/null +++ b/src/natpmp.cpp @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include + +using boost::bind; +using namespace libtorrent; +using boost::posix_time::microsec_clock; + +natpmp::natpmp(io_service& ios, portmap_callback_t const& cb) + : m_callback(cb) + , m_currently_mapping(-1) + , m_retry_count(0) + , m_socket(ios) + , m_send_timer(ios) + , m_refresh_timer(ios) + , m_disabled(false) +{ + m_mappings[0].protocol = 2; // tcp + m_mappings[1].protocol = 1; // udp + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log.open("natpmp.log", std::ios::in | std::ios::out | std::ios::trunc); +#endif + + udp::resolver r(ios); + udp::resolver::iterator i = r.resolve(udp::resolver::query(asio::ip::host_name(), "0")); + for (;i != udp::resolver_iterator(); ++i) + { + if (i->endpoint().address().is_v4()) break; + } + + if (i == udp::resolver_iterator()) return; + address_v4 local = i->endpoint().address().to_v4(); +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << to_simple_string(microsec_clock::universal_time()) + << " local ip: " << local.to_string() << std::endl; +#endif + + if ((local.to_ulong() & 0xff000000) != 0x0a000000 + && (local.to_ulong() & 0xfff00000) != 0xac100000 + && (local.to_ulong() & 0xffff0000) != 0xaca80000) + { + // the local address seems to be an external + // internet address. Assume it is not behind a NAT +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "not on a NAT. disable NAT-PMP" << std::endl; +#endif + m_disabled = true; + return; + } + + // assume the router is located on the local + // network as x.x.x.1 + // TODO: find a better way to figure out the router IP + m_nat_endpoint = udp::endpoint( + address_v4((local.to_ulong() & 0xffffff00) | 1), 5351); + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "assuming router is at: " << m_nat_endpoint.address().to_string() << std::endl; +#endif + + m_socket.open(udp::v4()); + m_socket.bind(udp::endpoint()); +} + +void natpmp::set_mappings(int tcp, int udp) +{ + if (m_disabled) return; + update_mapping(0, tcp); + update_mapping(1, udp); +} + +void natpmp::update_mapping(int i, int port) +{ + natpmp::mapping& m = m_mappings[i]; + if (port <= 0) return; + if (m.local_port != port) + m.need_update = true; + + m.local_port = port; + // prefer the same external port as the local port + if (m.external_port == 0) m.external_port = port; + + if (m_currently_mapping == -1) + { + // the socket is not currently in use + // send out a mapping request + m_retry_count = 0; + send_map_request(i); + m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) + , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + } +} + +void natpmp::send_map_request(int i) try +{ + using namespace libtorrent::detail; + using boost::posix_time::milliseconds; + + assert(m_currently_mapping == -1 + || m_currently_mapping == i); + m_currently_mapping = i; + mapping& m = m_mappings[i]; + char buf[12]; + char* out = buf; + write_uint8(0, out); // NAT-PMP version + write_uint8(m.protocol, out); // map "protocol" + write_uint16(0, out); // reserved + write_uint16(m.local_port, out); // private port + write_uint16(m.external_port, out); // requested public port + int ttl = m.external_port == 0 ? 0 : 3600; + write_uint32(ttl, out); // port mapping lifetime + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << to_simple_string(microsec_clock::universal_time()) + << " ==> port map request: " << (m.protocol == 1 ? "udp" : "tcp") + << " local: " << m.local_port << " external: " << m.external_port + << " ttl: " << ttl << std::endl; +#endif + + m_socket.send_to(asio::buffer(buf, 12), m_nat_endpoint); + // linear back-off instead of exponential + ++m_retry_count; + m_send_timer.expires_from_now(milliseconds(250 * m_retry_count)); + m_send_timer.async_wait(bind(&natpmp::resend_request, this, i, _1)); +} +catch (std::exception& e) +{ + std::string err = e.what(); +} + +void natpmp::resend_request(int i, asio::error_code const& e) +{ + using boost::posix_time::hours; + if (e) return; + if (m_retry_count >= 9) + { + m_mappings[i].need_update = false; + // try again in two hours + m_mappings[i].expires + = boost::posix_time::second_clock::universal_time() + hours(2); + return; + } + send_map_request(i); +} + +void natpmp::on_reply(asio::error_code const& e + , std::size_t bytes_transferred) +{ + using namespace libtorrent::detail; + using boost::posix_time::seconds; + if (e) return; + + try + { + + if (m_remote != m_nat_endpoint) + { + m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) + , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + return; + } + + m_send_timer.cancel(); + + assert(m_currently_mapping >= 0); + int i = m_currently_mapping; + mapping& m = m_mappings[i]; + + char* in = m_response_buffer; + int version = read_uint8(in); + int cmd = read_uint8(in); + int result = read_uint16(in); + int time = read_uint32(in); + int private_port = read_uint16(in); + int public_port = read_uint16(in); + int lifetime = read_uint32(in); + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << to_simple_string(microsec_clock::universal_time()) + << " <== port map response: " << (cmd - 128 == 1 ? "udp" : "tcp") + << " local: " << private_port << " external: " << public_port + << " ttl: " << lifetime << std::endl; +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (version != 0) + { + m_log << "*** unexpected version: " << version << std::endl; + } +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (private_port != m.local_port) + { + m_log << "*** unexpected local port: " << private_port << std::endl; + } +#endif + +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + if (cmd != 128 + m.protocol) + { + m_log << "*** unexpected protocol: " << (cmd - 128) << std::endl; + } +#endif + + if (public_port == 0 || lifetime == 0) + { + // this means the mapping was + // successfully closed + m.local_port = 0; + } + else + { + m.expires = boost::posix_time::second_clock::universal_time() + + seconds(int(lifetime * 0.7f)); + m.external_port = public_port; + } + + if (result != 0) + { +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "*** ERROR: " << result << std::endl; +#endif + std::stringstream errmsg; + errmsg << "NAT router reports error (" << result << ") "; + switch (result) + { + case 1: errmsg << "Unsupported protocol version"; break; + case 2: errmsg << "Not authorized to create port map (enable NAT-PMP on your router)"; break; + case 3: errmsg << "Network failure"; break; + case 4: errmsg << "Out of resources"; break; + case 5: errmsg << "Unsupported opcpde"; break; + } + throw std::runtime_error(errmsg.str()); + } + + int tcp_port = 0; + int udp_port = 0; + if (m.protocol == 1) udp_port = m.external_port; + else tcp_port = public_port; + m_callback(tcp_port, udp_port, ""); + } + catch (std::exception& e) + { + using boost::posix_time::hours; + // try again in two hours + m_mappings[m_currently_mapping].expires + = boost::posix_time::second_clock::universal_time() + hours(2); + m_callback(0, 0, e.what()); + } + int i = m_currently_mapping; + m_currently_mapping = -1; + m_mappings[i].need_update = false; + update_expiration_timer(); + try_next_mapping(i); +} + +void natpmp::update_expiration_timer() +{ + using boost::posix_time::seconds; + boost::posix_time::ptime now = boost::posix_time::second_clock::universal_time(); + boost::posix_time::ptime min_expire = now + seconds(3600); + int min_index = -1; + for (int i = 0; i < 2; ++i) + if (m_mappings[i].expires < min_expire + && m_mappings[i].local_port != 0) + { + min_expire = m_mappings[i].expires; + min_index = i; + } + + if (min_index >= 0) + { + m_refresh_timer.expires_from_now(min_expire - now); + m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, this, _1, min_index)); + } +} + +void natpmp::mapping_expired(asio::error_code const& e, int i) +{ + if (e) return; +#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING) + m_log << "*** mapping " << i << " expired, updating" << std::endl; +#endif + refresh_mapping(i); +} + +void natpmp::refresh_mapping(int i) +{ + m_mappings[i].need_update = true; + if (m_currently_mapping == -1) + { + // the socket is not currently in use + // send out a mapping request + m_retry_count = 0; + send_map_request(i); + m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16) + , m_remote, bind(&natpmp::on_reply, this, _1, _2)); + } +} + +void natpmp::try_next_mapping(int i) +{ + ++i; + if (i >= 2) i = 0; + if (m_mappings[i].need_update) + refresh_mapping(i); +} + +void natpmp::close() +{ + if (m_disabled) return; + for (int i = 0; i < 2; ++i) + { + if (m_mappings[i].local_port == 0) + continue; + m_mappings[i].external_port = 0; + refresh_mapping(i); + } +} + diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index efa13d841..331d9b8d6 100755 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -226,30 +226,21 @@ namespace libtorrent // build a vector of all pieces m_num_pieces = 0; - std::vector piece_list; - for (int i = 0; i < (int)m_have_piece.size(); ++i) + bool interesting = false; + for (int i = 0; i < int(m_have_piece.size()); ++i) { if (m_have_piece[i]) { ++m_num_pieces; - piece_list.push_back(i); + t->peer_has(i); + // if the peer has a piece and we don't, the peer is interesting + if (!t->have_piece(i) + && !t->picker().piece_priority(i) == 0) + interesting = true; } } - // let the torrent know which pieces the - // peer has, in a shuffled order - bool interesting = false; - for (std::vector::reverse_iterator i = piece_list.rbegin(); - i != piece_list.rend(); ++i) - { - int index = *i; - t->peer_has(index); - if (!t->have_piece(index) - && !t->picker().is_filtered(index)) - interesting = true; - } - - if (piece_list.size() == m_have_piece.size()) + if (m_num_pieces == int(m_have_piece.size())) { #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " *** THIS IS A SEED ***\n"; @@ -654,7 +645,7 @@ namespace libtorrent if (!t->have_piece(index) && !t->is_seed() && !is_interesting() - && !t->picker().is_filtered(index)) + && t->picker().piece_priority(index) != 0) t->get_policy().peer_is_interesting(*this); } @@ -714,8 +705,7 @@ namespace libtorrent m_have_piece[i] = true; ++m_num_pieces; t->peer_has(i); - if (!t->have_piece(i) - && !t->picker().is_filtered(i)) + if (!t->have_piece(i) && t->picker().piece_priority(i) != 0) interesting = true; } else if (!have && m_have_piece[i]) diff --git a/src/piece_picker.cpp b/src/piece_picker.cpp index a018e42ef..24f5fdecb 100755 --- a/src/piece_picker.cpp +++ b/src/piece_picker.cpp @@ -54,7 +54,6 @@ namespace libtorrent piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks) : m_piece_info(2) - , m_downloading_piece_info(2) , m_piece_map((total_num_blocks + blocks_per_piece-1) / blocks_per_piece) , m_num_filtered(0) , m_num_have_filtered(0) @@ -93,40 +92,17 @@ namespace libtorrent #ifndef NDEBUG m_files_checked_called = true; #endif - // build a vector of all the pieces we don't have - std::vector piece_list; - piece_list.reserve(std::count(pieces.begin(), pieces.end(), false)); - for (std::vector::const_iterator i = pieces.begin(); i != pieces.end(); ++i) { if (*i) continue; int index = static_cast(i - pieces.begin()); - if (m_piece_map[index].filtered) + m_piece_map[index].index = 0; + if (m_piece_map[index].filtered()) { ++m_num_filtered; --m_num_have_filtered; - m_piece_map[index].index = 0; } - else - { - piece_list.push_back(index); - } - } - - // add the pieces to the piece_picker - for (std::vector::reverse_iterator i = piece_list.rbegin(); - i != piece_list.rend(); ++i) - { - int index = *i; - assert(index >= 0); - assert(index < (int)m_piece_map.size()); - assert(m_piece_map[index].index == piece_pos::we_have_index); - assert(m_piece_map[index].peer_count == 0); - assert(m_piece_info.size() == 2); - - add(index); - assert(m_piece_map[index].index != piece_pos::we_have_index); } // if we have fast resume info @@ -160,6 +136,8 @@ namespace libtorrent if (sequenced_download_threshold == m_sequenced_download_threshold) return; + assert(sequenced_download_threshold > 0); + int old_limit = m_sequenced_download_threshold; m_sequenced_download_threshold = sequenced_download_threshold; @@ -169,9 +147,9 @@ namespace libtorrent if (i->priority(old_limit) != i->priority(m_sequenced_download_threshold)) { piece_pos& p = *i; - if (p.index == piece_pos::we_have_index) continue; int prev_priority = p.priority(old_limit); - move(p.downloading, p.filtered, prev_priority, p.index); + if (prev_priority == 0) continue; + move(prev_priority, p.index); } } @@ -217,6 +195,8 @@ namespace libtorrent { assert(sizeof(piece_pos) == 4); + assert(m_piece_info.empty() || m_piece_info[0].empty()); + if (t != 0) assert((int)m_piece_map.size() == t->torrent_file().num_pieces()); @@ -226,7 +206,7 @@ namespace libtorrent i != m_piece_map.end(); ++i) { int index = static_cast(i - m_piece_map.begin()); - if (i->filtered) + if (i->filtered()) { if (i->index != piece_pos::we_have_index) ++num_filtered; @@ -274,40 +254,27 @@ namespace libtorrent // make sure there's no entry // with this index. (there shouldn't // be since the piece_map is piece_pos::we_have_index) - for (std::vector >::const_iterator i = m_piece_info.begin(); - i != m_piece_info.end(); ++i) + for (int i = 0; i < int(m_piece_info.size()); ++i) { - for (std::vector::const_iterator j= i->begin(); - j != i->end(); ++j) + for (int j = 0; j < int(m_piece_info[i].size()); ++j) { - assert(*j != index); + assert(m_piece_info[i][j] != index); } } - - for (std::vector >::const_iterator i = m_downloading_piece_info.begin(); - i != m_downloading_piece_info.end(); ++i) - { - for (std::vector::const_iterator j = i->begin(); - j != i->end(); ++j) - { - assert(*j != index); - } - } - } - else if (!i->filtered) + else if (!i->filtered()) { if (t != 0) assert(!t->have_piece(index)); - const std::vector >& c_vec = pick_piece_info_vector(i->downloading, i->filtered); - assert(i->priority(m_sequenced_download_threshold) < (int)c_vec.size()); - const std::vector& vec = c_vec[i->priority(m_sequenced_download_threshold)]; - if (i->index >= vec.size()) + assert(i->priority(m_sequenced_download_threshold) < int(m_piece_info.size())); + int prio = i->priority(m_sequenced_download_threshold); + if (prio > 0) { - assert(false); + const std::vector& vec = m_piece_info[prio]; + assert (i->index < vec.size()); + assert(vec[i->index] == index); } - assert(vec[i->index] == index); } std::vector::const_iterator down @@ -330,6 +297,7 @@ namespace libtorrent float piece_picker::distributed_copies() const { + // TODO: this is completely broken now const float num_pieces = static_cast(m_piece_map.size()); for (int i = 0; i < (int)m_piece_info.size(); ++i) @@ -346,40 +314,25 @@ namespace libtorrent return 1.f; } - std::vector >& piece_picker::pick_piece_info_vector( - bool downloading, bool filtered) - { - assert(!filtered); - return downloading?m_downloading_piece_info:m_piece_info; - } - - std::vector > const& piece_picker::pick_piece_info_vector( - bool downloading, bool filtered) const - { - assert(!filtered); - return downloading?m_downloading_piece_info:m_piece_info; - } - void piece_picker::add(int index) { assert(index >= 0); - assert(index < (int)m_piece_map.size()); + assert(index < int(m_piece_map.size())); piece_pos& p = m_piece_map[index]; - assert(!p.filtered); - - std::vector >& dst_vec = pick_piece_info_vector( - p.downloading, p.filtered); + assert(!p.filtered()); + assert(!p.have()); int priority = p.priority(m_sequenced_download_threshold); - if ((int)dst_vec.size() <= priority) - dst_vec.resize(priority + 1); + assert(priority > 0); + if (int(m_piece_info.size()) <= priority) + m_piece_info.resize(priority + 1); - assert((int)dst_vec.size() > priority); + assert(int(m_piece_info.size()) > priority); - if (p.ordered(m_sequenced_download_threshold)) + if (is_ordered(priority)) { // the piece should be inserted ordered, not randomly - std::vector& v = dst_vec[priority]; + std::vector& v = m_piece_info[priority]; // assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); std::vector::iterator i = std::lower_bound(v.begin(), v.end() , index/*, std::greater()*/); @@ -393,75 +346,63 @@ namespace libtorrent } // assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); } - else if (dst_vec[priority].size() < 2) + else if (m_piece_info[priority].size() < 2) { - p.index = dst_vec[priority].size(); - dst_vec[priority].push_back(index); + p.index = m_piece_info[priority].size(); + m_piece_info[priority].push_back(index); } else { // find a random position in the destination vector where we will place // this entry. - int dst_index = rand() % dst_vec[priority].size(); + int dst_index = rand() % m_piece_info[priority].size(); // copy the entry at that position to the back - m_piece_map[dst_vec[priority][dst_index]].index - = dst_vec[priority].size(); - dst_vec[priority].push_back(dst_vec[priority][dst_index]); + m_piece_map[m_piece_info[priority][dst_index]].index + = m_piece_info[priority].size(); + m_piece_info[priority].push_back(m_piece_info[priority][dst_index]); // and then replace the one at dst_index with the one we're moving. // this procedure is to make sure there's no ordering when pieces // are moved in sequenced order. p.index = dst_index; - dst_vec[priority][p.index] = index; + m_piece_info[priority][p.index] = index; } } - // will update the piece with the given properties (downloading, filtered, - // priority, elem_index) to place it at the correct position in the - // vectors. - void piece_picker::move(bool downloading, bool filtered, int priority - , int elem_index) + // will update the piece with the given properties (priority, elem_index) + // to place it at the correct position in the vectors. + void piece_picker::move(int priority, int elem_index) { - assert(!filtered); - assert(priority >= 0); + assert(priority > 0); assert(elem_index >= 0); - assert(elem_index != piece_pos::we_have_index); - std::vector >& src_vec(pick_piece_info_vector( - downloading, filtered)); assert(m_files_checked_called); - assert((int)src_vec.size() > priority); - assert((int)src_vec[priority].size() > elem_index); + assert(int(m_piece_info.size()) > priority); + assert(int(m_piece_info[priority].size()) > elem_index); - int index = src_vec[priority][elem_index]; + int index = m_piece_info[priority][elem_index]; // update the piece_map piece_pos& p = m_piece_map[index]; int new_priority = p.priority(m_sequenced_download_threshold); - if (p.downloading == downloading - && p.filtered == filtered - && new_priority == priority) + if (new_priority == priority) return; + + if (int(m_piece_info.size()) <= new_priority + && new_priority > 0) { - assert(p.ordered(m_sequenced_download_threshold)); - return; + m_piece_info.resize(new_priority + 1); + assert(int(m_piece_info.size()) > new_priority); } - std::vector >& dst_vec(pick_piece_info_vector( - p.downloading, p.filtered)); - - assert(&dst_vec != &src_vec || new_priority != priority); - - if ((int)dst_vec.size() <= new_priority) + if (new_priority == 0) { - dst_vec.resize(new_priority + 1); - assert((int)dst_vec.size() > new_priority); + // this means the piece should not have an entry } - - if (p.ordered(m_sequenced_download_threshold)) + else if (is_ordered(new_priority)) { // the piece should be inserted ordered, not randomly - std::vector& v = dst_vec[new_priority]; + std::vector& v = m_piece_info[new_priority]; // assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); std::vector::iterator i = std::lower_bound(v.begin(), v.end() , index/*, std::greater()*/); @@ -475,35 +416,35 @@ namespace libtorrent } // assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); } - else if (dst_vec[new_priority].size() < 2) + else if (m_piece_info[new_priority].size() < 2) { - p.index = dst_vec[new_priority].size(); - dst_vec[new_priority].push_back(index); + p.index = m_piece_info[new_priority].size(); + m_piece_info[new_priority].push_back(index); } else { // find a random position in the destination vector where we will place // this entry. - int dst_index = rand() % dst_vec[new_priority].size(); + int dst_index = rand() % m_piece_info[new_priority].size(); // copy the entry at that position to the back - m_piece_map[dst_vec[new_priority][dst_index]].index - = dst_vec[new_priority].size(); - dst_vec[new_priority].push_back(dst_vec[new_priority][dst_index]); + m_piece_map[m_piece_info[new_priority][dst_index]].index + = m_piece_info[new_priority].size(); + m_piece_info[new_priority].push_back(m_piece_info[new_priority][dst_index]); // and then replace the one at dst_index with the one we're moving. // this procedure is to make sure there's no ordering when pieces // are moved in sequenced order. p.index = dst_index; - dst_vec[new_priority][p.index] = index; + m_piece_info[new_priority][p.index] = index; } - assert(p.index < dst_vec[p.priority(m_sequenced_download_threshold)].size()); - assert(dst_vec[p.priority(m_sequenced_download_threshold)][p.index] == index); + assert(new_priority == 0 || p.index < m_piece_info[p.priority(m_sequenced_download_threshold)].size()); + assert(new_priority == 0 || m_piece_info[p.priority(m_sequenced_download_threshold)][p.index] == index); - if (priority >= m_sequenced_download_threshold) + if (is_ordered(priority)) { // remove the element from the source vector and preserve the order - std::vector& v = src_vec[priority]; + std::vector& v = m_piece_info[priority]; v.erase(v.begin() + elem_index); for (std::vector::iterator i = v.begin() + elem_index; i != v.end(); ++i) @@ -516,44 +457,42 @@ namespace libtorrent { // this will remove elem from the source vector without // preserving order, but the order is random anyway - int replace_index = src_vec[priority][elem_index] = src_vec[priority].back(); + int replace_index = m_piece_info[priority][elem_index] = m_piece_info[priority].back(); if (index != replace_index) { // update the entry we moved from the back m_piece_map[replace_index].index = elem_index; - assert((int)src_vec[priority].size() > elem_index); + assert(int(m_piece_info[priority].size()) > elem_index); // this may not necessarily be the case. If we've just updated the threshold and are updating // the piece map // assert((int)m_piece_map[replace_index].priority(m_sequenced_download_threshold) == priority); - assert((int)m_piece_map[replace_index].index == elem_index); - assert(src_vec[priority][elem_index] == replace_index); + assert(int(m_piece_map[replace_index].index) == elem_index); + assert(m_piece_info[priority][elem_index] == replace_index); } else { - assert((int)src_vec[priority].size() == elem_index+1); + assert(int(m_piece_info[priority].size()) == elem_index+1); } - src_vec[priority].pop_back(); + m_piece_info[priority].pop_back(); } } - - void piece_picker::remove(bool downloading, bool filtered, int priority - , int elem_index) +/* + void piece_picker::remove(int priority, int elem_index) { - assert(!filtered); - assert(priority >= 0); + assert(priority > 0); assert(elem_index >= 0); assert(m_files_checked_called); - std::vector >& src_vec(pick_piece_info_vector(downloading, filtered)); + assert(int(m_piece_info.size()) > priority); + assert(int(m_piece_info[priority].size()) > elem_index); - assert((int)src_vec.size() > priority); - assert((int)src_vec[priority].size() > elem_index); + int index = m_piece_info[priority][elem_index]; - int index = src_vec[priority][elem_index]; + piece_pos& p = m_piece_map[index]; - if (downloading) + if (p.downloading) { std::vector::iterator i = std::find_if(m_downloads.begin(), @@ -562,12 +501,11 @@ namespace libtorrent assert(i != m_downloads.end()); m_downloads.erase(i); } - piece_pos& p = m_piece_map[index]; + p.downloading = 0; - if (p.ordered(m_sequenced_download_threshold)) + if (is_ordered(priority)) { - std::vector& v = src_vec[priority]; -// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); + std::vector& v = m_piece_info[priority]; std::vector::iterator i = v.begin() + elem_index; v.erase(i); i = v.begin() + elem_index; @@ -576,20 +514,18 @@ namespace libtorrent --m_piece_map[*i].index; assert(v[m_piece_map[*i].index] == *i); } -// assert(is_sorted(v.begin(), v.end()/*, std::greater()*/)); } else { // this will remove elem from the vector without // preserving order - index = src_vec[priority][elem_index] = src_vec[priority].back(); + index = m_piece_info[priority][elem_index] = m_piece_info[priority].back(); // update the entry we moved from the back - if ((int)src_vec[priority].size() > elem_index+1) - m_piece_map[index].index = elem_index; - src_vec[priority].pop_back(); + m_piece_map[index].index = elem_index; + m_piece_info[priority].pop_back(); } } - +*/ void piece_picker::restore_piece(int index) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -607,34 +543,39 @@ namespace libtorrent assert(i != m_downloads.end()); m_downloads.erase(i); - m_piece_map[index].downloading = 0; piece_pos& p = m_piece_map[index]; - if (p.filtered) return; - move(true, p.filtered, p.priority(m_sequenced_download_threshold), p.index); + int priority = p.priority(m_sequenced_download_threshold); + p.downloading = 0; + move(priority, p.index); } void piece_picker::inc_refcount(int i) { - TORRENT_PIECE_PICKER_INVARIANT_CHECK; +// TORRENT_PIECE_PICKER_INVARIANT_CHECK; assert(i >= 0); assert(i < (int)m_piece_map.size()); assert(m_files_checked_called); - int index = m_piece_map[i].index; - int prev_priority = m_piece_map[i].priority(m_sequenced_download_threshold); - - assert(m_piece_map[i].peer_count < 2048); - m_piece_map[i].peer_count++; - assert(m_piece_map[i].peer_count != 0); - piece_pos& p = m_piece_map[i]; + int index = p.index; + int prev_priority = p.priority(m_sequenced_download_threshold); + + assert(p.peer_count < piece_pos::max_peer_count); + p.peer_count++; + assert(p.peer_count != 0); // if we have the piece or if it's filtered // we don't have to move any entries in the piece_info vector - if (index == piece_pos::we_have_index || p.filtered - || p.priority(m_sequenced_download_threshold) == prev_priority) return; + if (p.priority(m_sequenced_download_threshold) == prev_priority) return; - move(p.downloading, p.filtered, prev_priority, index); + if (prev_priority == 0) + { + add(i); + } + else + { + move(prev_priority, index); + } #ifndef NDEBUG // integrity_check(); @@ -644,25 +585,23 @@ namespace libtorrent void piece_picker::dec_refcount(int i) { - TORRENT_PIECE_PICKER_INVARIANT_CHECK; +// TORRENT_PIECE_PICKER_INVARIANT_CHECK; assert(m_files_checked_called); assert(i >= 0); assert(i < (int)m_piece_map.size()); - int prev_priority = m_piece_map[i].priority(m_sequenced_download_threshold); - int index = m_piece_map[i].index; - assert(m_piece_map[i].peer_count > 0); - - if (m_piece_map[i].peer_count > 0) - m_piece_map[i].peer_count--; - piece_pos& p = m_piece_map[i]; + int prev_priority = p.priority(m_sequenced_download_threshold); + int index = p.index; + assert(p.peer_count > 0); - if (index == piece_pos::we_have_index || p.filtered - || p.priority(m_sequenced_download_threshold) == prev_priority) return; + if (p.peer_count > 0) + p.peer_count--; - move(p.downloading, p.filtered, prev_priority, index); + if (p.priority(m_sequenced_download_threshold) == prev_priority) return; + + move(prev_priority, index); } // this is used to indicate that we succesfully have @@ -675,46 +614,92 @@ namespace libtorrent assert(index >= 0); assert(index < (int)m_piece_map.size()); - int info_index = m_piece_map[index].index; - int priority = m_piece_map[index].priority(m_sequenced_download_threshold); - - assert(m_piece_map[index].downloading == 1); - - assert(info_index != piece_pos::we_have_index); piece_pos& p = m_piece_map[index]; - if (p.filtered) + int info_index = p.index; + int priority = p.priority(m_sequenced_download_threshold); + + assert(p.downloading == 1); + assert(!p.have()); + if (p.filtered()) { --m_num_filtered; ++m_num_have_filtered; return; } - if (info_index == piece_pos::we_have_index) return; - remove(p.downloading, p.filtered, priority, info_index); - p.index = piece_pos::we_have_index; + if (p.have()) return; + p.set_have(); + if (p.downloading) + { + std::vector::iterator i + = std::find_if(m_downloads.begin(), + m_downloads.end(), + has_index(index)); + assert(i != m_downloads.end()); + m_downloads.erase(i); + p.downloading = 0; + } + assert(p.priority(m_sequenced_download_threshold) == 0); + move(priority, info_index); } - void piece_picker::mark_as_filtered(int index) + void piece_picker::set_piece_priority(int index, int new_piece_priority) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; + assert(new_piece_priority >= 0); + assert(new_piece_priority <= 3); assert(index >= 0); assert(index < (int)m_piece_map.size()); piece_pos& p = m_piece_map[index]; - if (p.filtered == 1) return; - p.filtered = 1; - if (p.index != piece_pos::we_have_index) + + // if the priority isn't changed, don't do anything + if (new_piece_priority == int(p.piece_priority)) return; + + if (new_piece_priority == piece_pos::filter_priority + && p.piece_priority != piece_pos::filter_priority) { - ++m_num_filtered; - remove(p.downloading, false, p.priority(m_sequenced_download_threshold), p.index); - assert(p.filtered == 1); + // the piece just got filtered + if (p.have()) ++m_num_have_filtered; + else ++m_num_filtered; + + if (p.downloading) + { + std::vector::iterator i + = std::find_if(m_downloads.begin(), + m_downloads.end(), + has_index(index)); + assert(i != m_downloads.end()); + m_downloads.erase(i); + } + p.downloading = 0; + } + else if (new_piece_priority != piece_pos::filter_priority + && p.piece_priority == piece_pos::filter_priority) + { + // the piece just got unfiltered + if (p.have()) --m_num_have_filtered; + else --m_num_filtered; + } + assert(m_num_filtered >= 0); + assert(m_num_have_filtered >= 0); + + int prev_priority = p.priority(m_sequenced_download_threshold); + p.piece_priority = new_piece_priority; + int new_priority = p.priority(m_sequenced_download_threshold); + + if (new_priority == prev_priority) return; + + if (prev_priority == 0) + { + add(index); } else { - ++m_num_have_filtered; + move(prev_priority, p.index); } } - +/* // this function can be used for pieces that we don't // have, but have marked as filtered (so we didn't // want to download them) but later want to enable for @@ -727,8 +712,8 @@ namespace libtorrent assert(index < (int)m_piece_map.size()); piece_pos& p = m_piece_map[index]; - if (p.filtered == 0) return; - p.filtered = 0; + if (!p.filtered()) return; + p.filtered(false); if (p.index != piece_pos::we_have_index) { --m_num_filtered; @@ -741,13 +726,13 @@ namespace libtorrent assert(m_num_have_filtered >= 0); } } - - bool piece_picker::is_filtered(int index) const +*/ + int piece_picker::piece_priority(int index) const { assert(index >= 0); assert(index < (int)m_piece_map.size()); - return m_piece_map[index].filtered == 1; + return m_piece_map[index].piece_priority; } void piece_picker::filtered_pieces(std::vector& mask) const @@ -757,7 +742,7 @@ namespace libtorrent for (std::vector::const_iterator i = m_piece_map.begin(), end(m_piece_map.end()); i != end; ++i, ++j) { - *j = i->filtered == 1; + *j = i->filtered(); } } @@ -781,59 +766,29 @@ namespace libtorrent // pieces that 0 other peers has. std::vector >::const_iterator free = m_piece_info.begin() + 1; - assert(m_downloading_piece_info.begin() - != m_downloading_piece_info.end()); - - std::vector >::const_iterator partial - = m_downloading_piece_info.begin() + 1; std::vector backup_blocks; - // this loop will loop from pieces with 1 peer and up + // this loop will loop from pieces with priority 1 and up // until we either reach the end of the piece list or // has filled the interesting_blocks with num_blocks // blocks. - - // it iterates over two ranges simultaneously. The pieces that are - // partially downloaded or partially requested, and the pieces that - // hasn't been requested at all. The default is to prioritize pieces - // that are partially requested/downloaded, so the loop will first - // look for blocks among those pieces. And it will also take two steps - // in that range when iterating. This has the effect that partial pieces - // doesn't have to be as rare as non-requested pieces in order to be - // prefered. - + // When prefer_whole_pieces is set (usually set when downloading from // fast peers) the partial pieces will not be prioritized, but actually // ignored as long as possible. - while((free != m_piece_info.end()) - || (partial != m_downloading_piece_info.end())) + while (free != m_piece_info.end()) { - if (partial != m_downloading_piece_info.end()) - { - for (int i = 0; i < 2; ++i) - { - num_blocks = add_interesting_blocks_partial(*partial, pieces - , interesting_blocks, backup_blocks, num_blocks - , prefer_whole_pieces, peer); - assert(num_blocks >= 0); - if (num_blocks == 0) return; - ++partial; - if (partial == m_downloading_piece_info.end()) break; - } - } - - if (free != m_piece_info.end()) - { - num_blocks = add_interesting_blocks_free(*free, pieces - , interesting_blocks, num_blocks, prefer_whole_pieces); - assert(num_blocks >= 0); - if (num_blocks == 0) return; - ++free; - } + num_blocks = add_interesting_blocks(*free, pieces + , interesting_blocks, backup_blocks, num_blocks + , prefer_whole_pieces, peer); + assert(num_blocks >= 0); + if (num_blocks == 0) return; + ++free; } +// TODO: what's up with this? if (!prefer_whole_pieces) return; assert(num_blocks > 0); @@ -867,112 +822,90 @@ namespace libtorrent } } - int piece_picker::add_interesting_blocks_free(std::vector const& piece_list + int piece_picker::add_interesting_blocks(std::vector const& piece_list , std::vector const& pieces , std::vector& interesting_blocks - , int num_blocks, bool prefer_whole_pieces) const - { - for (std::vector::const_iterator i = piece_list.begin(); - i != piece_list.end(); ++i) - { - assert(*i >= 0); - assert(*i < (int)m_piece_map.size()); - assert(m_piece_map[*i].downloading == 0); - - // if the peer doesn't have the piece - // skip it - if (!pieces[*i]) continue; - - int piece_blocks = blocks_in_piece(*i); - if (!prefer_whole_pieces && piece_blocks > num_blocks) - piece_blocks = num_blocks; - for (int j = 0; j < piece_blocks; ++j) - { - interesting_blocks.push_back(piece_block(*i, j)); - } - num_blocks -= (std::min)(piece_blocks, num_blocks); - assert(num_blocks >= 0); - if (num_blocks == 0) return num_blocks; - } - return num_blocks; - } - - int piece_picker::add_interesting_blocks_partial(std::vector const& piece_list - , const std::vector& pieces - , std::vector& interesting_blocks , std::vector& backup_blocks , int num_blocks, bool prefer_whole_pieces , tcp::endpoint peer) const { - assert(num_blocks > 0); - for (std::vector::const_iterator i = piece_list.begin(); i != piece_list.end(); ++i) { assert(*i >= 0); assert(*i < (int)m_piece_map.size()); + // if the peer doesn't have the piece // skip it if (!pieces[*i]) continue; - assert(m_piece_map[*i].downloading == 1); - - // calculate the number of blocks in this - // piece. It's always m_blocks_per_piece, except - // in the last piece. int num_blocks_in_piece = blocks_in_piece(*i); - std::vector::const_iterator p - = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(*i)); - assert(p != m_downloads.end()); - - // this means that this partial piece has - // been downloaded/requested partially from - // another peer that isn't us. And since - // we prefer whole pieces, add this piece's - // blocks to the backup list. If the prioritized - // blocks aren't enough, blocks from this list - // will be picked. - if (prefer_whole_pieces - && !exclusively_requested_from(*p, num_blocks_in_piece, peer)) + if (m_piece_map[*i].downloading == 1) { - if ((int)backup_blocks.size() >= num_blocks) continue; + std::vector::const_iterator p + = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(*i)); + assert(p != m_downloads.end()); + + // this means that this partial piece has + // been downloaded/requested partially from + // another peer that isn't us. And since + // we prefer whole pieces, add this piece's + // blocks to the backup list. If the prioritized + // blocks aren't enough, blocks from this list + // will be picked. + if (prefer_whole_pieces + && !exclusively_requested_from(*p, num_blocks_in_piece, peer)) + { + if ((int)backup_blocks.size() >= num_blocks) continue; + for (int j = 0; j < num_blocks_in_piece; ++j) + { + if (p->finished_blocks[j] == 1) continue; + if (p->requested_blocks[j] == 1 + && p->info[j].peer == peer) continue; + backup_blocks.push_back(piece_block(*i, j)); + } + continue; + } + for (int j = 0; j < num_blocks_in_piece; ++j) { if (p->finished_blocks[j] == 1) continue; if (p->requested_blocks[j] == 1 && p->info[j].peer == peer) continue; - backup_blocks.push_back(piece_block(*i, j)); + // this block is interesting (we don't have it + // yet). But it may already have been requested + // from another peer. We have to add it anyway + // to allow the requester to determine if the + // block should be requested from more than one + // peer. If it is being downloaded, we continue + // to look for blocks until we have num_blocks + // blocks that have not been requested from any + // other peer. + interesting_blocks.push_back(piece_block(*i, j)); + if (p->requested_blocks[j] == 0) + { + // we have found a block that's free to download + num_blocks--; + if (prefer_whole_pieces) continue; + assert(num_blocks >= 0); + if (num_blocks == 0) return num_blocks; + } } - continue; + assert(num_blocks >= 0 || prefer_whole_pieces); + if (num_blocks < 0) num_blocks = 0; } - - for (int j = 0; j < num_blocks_in_piece; ++j) + else { - if (p->finished_blocks[j] == 1) continue; - if (p->requested_blocks[j] == 1 - && p->info[j].peer == peer) continue; - // this block is interesting (we don't have it - // yet). But it may already have been requested - // from another peer. We have to add it anyway - // to allow the requester to determine if the - // block should be requested from more than one - // peer. If it is being downloaded, we continue - // to look for blocks until we have num_blocks - // blocks that have not been requested from any - // other peer. - interesting_blocks.push_back(piece_block(*i, j)); - if (p->requested_blocks[j] == 0) + if (!prefer_whole_pieces && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) { - // we have found a block that's free to download - num_blocks--; - if (prefer_whole_pieces) continue; - assert(num_blocks >= 0); - if (num_blocks == 0) return num_blocks; + interesting_blocks.push_back(piece_block(*i, j)); } + num_blocks -= (std::min)(num_blocks_in_piece, num_blocks); } - assert(num_blocks >= 0 || prefer_whole_pieces); - if (num_blocks < 0) num_blocks = 0; + assert(num_blocks >= 0); if (num_blocks == 0) return num_blocks; } return num_blocks; @@ -1046,8 +979,9 @@ namespace libtorrent piece_pos& p = m_piece_map[block.piece_index]; if (p.downloading == 0) { + int prio = p.priority(m_sequenced_download_threshold); p.downloading = 1; - move(false, p.filtered, p.priority(m_sequenced_download_threshold), p.index); + move(prio, p.index); downloading_piece dp; dp.index = block.piece_index; @@ -1076,12 +1010,13 @@ namespace libtorrent assert(block.block_index < blocks_in_piece(block.piece_index)); piece_pos& p = m_piece_map[block.piece_index]; - if (p.index == piece_pos::we_have_index || p.filtered) return; + int prio = p.priority(m_sequenced_download_threshold); + if (prio == 0) return; if (p.downloading == 0) { p.downloading = 1; - move(false, p.filtered, p.priority(m_sequenced_download_threshold), p.index); + move(prio, p.index); downloading_piece dp; dp.index = block.piece_index; @@ -1203,9 +1138,10 @@ namespace libtorrent if (i->requested_blocks.count() == 0) { m_downloads.erase(i); - m_piece_map[block.piece_index].downloading = 0; piece_pos& p = m_piece_map[block.piece_index]; - move(true, p.filtered, p.priority(m_sequenced_download_threshold), p.index); + int prio = p.priority(m_sequenced_download_threshold); + p.downloading = 0; + move(prio, p.index); } } diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 9a1ba1b7f..cd906f8af 100755 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -480,6 +480,7 @@ namespace libtorrent { namespace detail , m_tracker_manager(m_settings) , m_listen_port_range(listen_port_range) , m_listen_interface(address::from_string(listen_interface), listen_port_range.first) + , m_external_listen_port(0) , m_abort(false) , m_max_uploads(-1) , m_max_connections(-1) @@ -487,6 +488,11 @@ namespace libtorrent { namespace detail , m_incoming_connection(false) , m_files(40) , m_last_tick(microsec_clock::universal_time()) +#ifndef TORRENT_DISABLE_DHT + , m_dht_same_port(true) + , m_external_udp_port(0) +#endif + , m_natpmp(m_io_service, bind(&session_impl::on_port_mapping, this, _1, _2, _3)) , m_timer(m_io_service) , m_checker_impl(*this) { @@ -619,6 +625,7 @@ namespace libtorrent { namespace detail m_listen_socket->open(m_listen_interface.protocol()); m_listen_socket->bind(m_listen_interface); m_listen_socket->listen(); + m_external_listen_port = m_listen_interface.port(); break; } catch (asio::system_error& e) @@ -672,9 +679,11 @@ namespace libtorrent { namespace detail #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) if (m_listen_socket) { - (*m_logger) << "listening on port: " << m_listen_interface.port() << "\n"; + (*m_logger) << "listening on port: " << m_listen_interface.port() + << " external port: " << m_external_listen_port << "\n"; } #endif + m_natpmp.set_mappings(m_listen_interface.port(), 0); if (m_listen_socket) async_accept(); } @@ -956,7 +965,8 @@ namespace libtorrent { namespace detail if (t.should_request()) { tracker_request req = t.generate_tracker_request(); - req.listen_port = m_listen_interface.port(); + assert(m_external_listen_port > 0); + req.listen_port = m_external_listen_port; req.key = m_key; m_tracker_manager.queue_request(m_strand, req, t.tracker_login() , m_listen_interface.address(), i->second); @@ -1064,6 +1074,8 @@ namespace libtorrent { namespace detail while (!m_abort); deadline_timer tracker_timer(m_io_service); + // this will remove the port mappings + m_natpmp.close(); session_impl::mutex_t::scoped_lock l(m_mutex); @@ -1080,7 +1092,8 @@ namespace libtorrent { namespace detail && !i->second->trackers().empty()) { tracker_request req = i->second->generate_tracker_request(); - req.listen_port = m_listen_interface.port(); + assert(m_external_listen_port > 0); + req.listen_port = m_external_listen_port; req.key = m_key; std::string login = i->second->tracker_login(); #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1368,7 +1381,8 @@ namespace libtorrent { namespace detail { tracker_request req = t.generate_tracker_request(); assert(req.event == tracker_request::stopped); - req.listen_port = m_listen_interface.port(); + assert(m_external_listen_port > 0); + req.listen_port = m_external_listen_port; req.key = m_key; #if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING) @@ -1434,12 +1448,16 @@ namespace libtorrent { namespace detail m_listen_socket.reset(); #ifndef TORRENT_DISABLE_DHT - if (m_listen_interface.address() != new_interface.address() + if ((m_listen_interface.address() != new_interface.address() + || m_dht_same_port) && m_dht) { + if (m_dht_same_port) + m_dht_settings.service_port = new_interface.port(); // the listen interface changed, rebind the dht listen socket as well m_dht->rebind(new_interface.address() , m_dht_settings.service_port); + m_natpmp.set_mappings(0, m_dht_settings.service_port); } #endif @@ -1461,7 +1479,31 @@ namespace libtorrent { namespace detail unsigned short session_impl::listen_port() const { mutex_t::scoped_lock l(m_mutex); - return m_listen_interface.port(); + return m_external_listen_port; + } + + void session_impl::on_port_mapping(int tcp_port, int udp_port + , std::string const& errmsg) + { +#ifndef TORRENT_DISABLE_DHT + if (udp_port != 0) + { + m_external_udp_port = udp_port; + m_dht_settings.service_port = udp_port; + // TODO: generate successful port map alert + } +#endif + + if (tcp_port != 0) + { + m_external_listen_port = tcp_port; + // TODO: generate successful port map alert + } + + if (!errmsg.empty()) + { + // TODO: generate port map failure alert + } } session_status session_impl::status() const @@ -1512,6 +1554,11 @@ namespace libtorrent { namespace detail m_dht->stop(); m_dht = 0; } + if (m_dht_settings.service_port == 0) + m_dht_same_port = true; + m_dht_settings.service_port = m_listen_interface.port(); + m_external_udp_port = m_dht_settings.service_port; + m_natpmp.set_mappings(0, m_dht_settings.service_port); m_dht = new dht::dht_tracker(m_io_service , m_dht_settings, m_listen_interface.address() , startup_state); @@ -1528,13 +1575,23 @@ namespace libtorrent { namespace detail void session_impl::set_dht_settings(dht_settings const& settings) { mutex_t::scoped_lock l(m_mutex); - if (settings.service_port != m_dht_settings.service_port + // only change the dht listen port in case the settings + // contains a vaiid port, and if it is different from + // the current setting + if (settings.service_port != 0) + m_dht_same_port = false; + if (!m_dht_same_port + && settings.service_port != m_dht_settings.service_port && m_dht) { m_dht->rebind(m_listen_interface.address() , settings.service_port); + m_natpmp.set_mappings(0, m_dht_settings.service_port); + m_external_udp_port = settings.service_port; } m_dht_settings = settings; + if (m_dht_same_port) + m_dht_settings.service_port = m_listen_interface.port(); } entry session_impl::dht_state() const diff --git a/src/torrent.cpp b/src/torrent.cpp index f389cc518..04b1b859f 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -493,8 +493,9 @@ namespace libtorrent // TODO: There should be a way to abort an announce operation on the dht. // when the torrent is destructed boost::weak_ptr self(shared_from_this()); + assert(m_ses.m_external_listen_port > 0); m_ses.m_dht->announce(m_torrent_file.info_hash() - , m_ses.m_listen_interface.port() + , m_ses.m_external_listen_port , m_ses.m_strand.wrap(bind(&torrent::on_dht_announce_response_disp, self, _1))); } @@ -720,7 +721,7 @@ namespace libtorrent int corr = m_torrent_file.piece_size(last_piece) - m_torrent_file.piece_length(); total_done += corr; - if (!m_picker->is_filtered(last_piece)) + if (m_picker->piece_priority(last_piece) != 0) wanted_done += corr; } @@ -768,7 +769,7 @@ namespace libtorrent corr += m_torrent_file.piece_size(last_piece) % m_block_size; } total_done += corr; - if (!m_picker->is_filtered(index)) + if (m_picker->piece_priority(index) != 0) wanted_done += corr; } @@ -816,7 +817,7 @@ namespace libtorrent i != downloading_piece.end(); ++i) { total_done += i->second; - if (!m_picker->is_filtered(i->first.piece_index)) + if (m_picker->piece_priority(i->first.piece_index) != 0) wanted_done += i->second; } @@ -1026,8 +1027,7 @@ namespace libtorrent // TODO: update peer's interesting-bit - if (filter) m_picker->mark_as_filtered(index); - else m_picker->mark_as_unfiltered(index); + m_picker->set_piece_priority(index, filter ? 1 : 0); } void torrent::filter_pieces(std::vector const& bitmask) @@ -1042,23 +1042,15 @@ namespace libtorrent // TODO: update peer's interesting-bit - std::vector state; - state.reserve(100); int index = 0; for (std::vector::const_iterator i = bitmask.begin() , end(bitmask.end()); i != end; ++i, ++index) { - if (m_picker->is_filtered(index) == *i) continue; + if ((m_picker->piece_priority(index) == 0) == *i) continue; if (*i) - m_picker->mark_as_filtered(index); + m_picker->set_piece_priority(index, 0); else - state.push_back(index); - } - - for (std::vector::reverse_iterator i = state.rbegin(); - i != state.rend(); ++i) - { - m_picker->mark_as_unfiltered(*i); + m_picker->set_piece_priority(index, 1); } } @@ -1072,7 +1064,7 @@ namespace libtorrent assert(index >= 0); assert(index < m_torrent_file.num_pieces()); - return m_picker->is_filtered(index); + return m_picker->piece_priority(index) == 0; } void torrent::filtered_pieces(std::vector& bitmask) const @@ -1189,19 +1181,12 @@ namespace libtorrent { assert(p->associated_torrent().lock().get() == this); - std::vector piece_list; const std::vector& pieces = p->get_bitfield(); for (std::vector::const_iterator i = pieces.begin(); i != pieces.end(); ++i) { - if (*i) piece_list.push_back(static_cast(i - pieces.begin())); - } - - for (std::vector::reverse_iterator i = piece_list.rbegin(); - i != piece_list.rend(); ++i) - { - peer_lost(*i); + if (*i) peer_lost(static_cast(i - pieces.begin())); } } @@ -2623,7 +2608,7 @@ namespace libtorrent int filtered_pieces = m_picker->num_filtered() + m_picker->num_have_filtered(); int last_piece_index = m_torrent_file.num_pieces() - 1; - if (m_picker->is_filtered(last_piece_index)) + if (m_picker->piece_priority(last_piece_index) == 0) { st.total_wanted -= m_torrent_file.piece_size(last_piece_index); --filtered_pieces;