diff --git a/ChangeLog b/ChangeLog index 6ef9f5f6b..0a6ace613 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ + * added support for running separate DHT nodes on each network interface + * added support for establishing UTP connections on any network interface + * added support for sending tracker announces on every network interface * introduce "lt" namespace alias * need_save_resume_data() will no longer return true every 15 minutes * make the file_status interface explicitly public types diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp index e73b323c9..277387fef 100644 --- a/bindings/python/src/torrent_handle.cpp +++ b/bindings/python/src/torrent_handle.cpp @@ -261,34 +261,92 @@ list trackers(torrent_handle& h) dict d; d["url"] = i->url; d["trackerid"] = i->trackerid; - d["message"] = i->message; - dict last_error; - last_error["value"] = i->last_error.value(); - last_error["category"] = i->last_error.category().name(); - d["last_error"] = last_error; - if (i->next_announce > min_time()) { - d["next_announce"] = to_ptime(i->next_announce); - } - else { - d["next_announce"] = object(); - } - if (i->min_announce > min_time()) { - d["min_announce"] = to_ptime(i->min_announce); - } - else { - d["min_announce"] = object(); - } - d["scrape_incomplete"] = i->scrape_incomplete; - d["scrape_complete"] = i->scrape_complete; - d["scrape_downloaded"] = i->scrape_downloaded; d["tier"] = i->tier; d["fail_limit"] = i->fail_limit; - d["fails"] = i->fails; d["source"] = i->source; d["verified"] = i->verified; - d["updating"] = i->updating; - d["start_sent"] = i->start_sent; - d["complete_sent"] = i->complete_sent; + +#ifndef TORRENT_NO_DEPRECATE + if (!i->endpoints.empty()) + { + announce_endpoint const& aep = i->endpoints.front(); + d["message"] = aep.message; + dict last_error; + last_error["value"] = aep.last_error.value(); + last_error["category"] = aep.last_error.category().name(); + d["last_error"] = last_error; + if (aep.next_announce > min_time()) { + d["next_announce"] = to_ptime(aep.next_announce); + } + else { + d["next_announce"] = object(); + } + if (aep.min_announce > min_time()) { + d["min_announce"] = to_ptime(aep.min_announce); + } + else { + d["min_announce"] = object(); + } + d["scrape_incomplete"] = aep.scrape_incomplete; + d["scrape_complete"] = aep.scrape_complete; + d["scrape_downloaded"] = aep.scrape_downloaded; + d["fails"] = aep.fails; + d["updating"] = aep.updating; + d["start_sent"] = aep.start_sent; + d["complete_sent"] = aep.complete_sent; + } + else + { + d["message"] = std::string(); + dict last_error; + last_error["value"] = 0; + last_error["category"] = ""; + d["last_error"] = last_error; + d["next_announce"] = object(); + d["min_announce"] = object(); + d["scrape_incomplete"] = 0; + d["scrape_complete"] = 0; + d["scrape_downloaded"] = 0; + d["fails"] = 0; + d["updating"] = false; + d["start_sent"] = false; + d["complete_sent"] = false; + } +#endif + + list aeps; + for (auto const& aep : i->endpoints) + { + dict e; + e["message"] = aep.message; + e["local_address"] = boost::python::make_tuple(aep.local_endpoint.address().to_string(), aep.local_endpoint.port()); + dict last_error; + last_error["value"] = aep.last_error.value(); + last_error["category"] = aep.last_error.category().name(); + e["last_error"] = last_error; + if (aep.next_announce > min_time()) { + e["next_announce"] = to_ptime(aep.next_announce); + } + else { + e["next_announce"] = object(); + } + if (aep.min_announce > min_time()) { + e["min_announce"] = to_ptime(aep.min_announce); + } + else { + e["min_announce"] = object(); + } + e["scrape_incomplete"] = aep.scrape_incomplete; + e["scrape_complete"] = aep.scrape_complete; + e["scrape_downloaded"] = aep.scrape_downloaded; + e["fails"] = aep.fails; + e["updating"] = aep.updating; + e["start_sent"] = aep.start_sent; + e["complete_sent"] = aep.complete_sent; + aeps.append(e); + } + d["endpoints"] = aeps; + #ifndef TORRENT_NO_DEPRECATE d["send_stats"] = i->send_stats; #endif @@ -345,19 +403,19 @@ void set_metadata(torrent_handle& handle, std::string const& buf) std::shared_ptr get_torrent_info(torrent_handle const& h) { - allow_threading_guard guard; - return h.torrent_file(); + allow_threading_guard guard; + return h.torrent_file(); } #else std::shared_ptr get_torrent_info(torrent_handle const& h) { - // I can't figure out how to expose shared_ptr - // as well as supporting mutable instances. So, this hack is better - // than compilation errors. It seems to work on newer versions of boost though - allow_threading_guard guard; - return std::const_pointer_cast(h.torrent_file()); + // I can't figure out how to expose shared_ptr + // as well as supporting mutable instances. So, this hack is better + // than compilation errors. It seems to work on newer versions of boost though + allow_threading_guard guard; + return std::const_pointer_cast(h.torrent_file()); } #endif @@ -391,7 +449,7 @@ void bind_torrent_handle() void (torrent_handle::*rename_file1)(file_index_t, std::wstring const&) const = &torrent_handle::rename_file; #endif - std::vector (torrent_handle::*file_status0)() const = &torrent_handle::file_status; + std::vector (torrent_handle::*file_status0)() const = &torrent_handle::file_status; #define _ allow_threads diff --git a/bindings/python/src/torrent_info.cpp b/bindings/python/src/torrent_info.cpp index c53623088..89dfef174 100644 --- a/bindings/python/src/torrent_info.cpp +++ b/bindings/python/src/torrent_info.cpp @@ -124,23 +124,47 @@ namespace return result; } +#ifndef TORRENT_NO_DEPRECATE // Create getters for announce_entry data members with non-trivial types which need converting. - lt::time_point get_next_announce(announce_entry const& ae) { return ae.next_announce; } - lt::time_point get_min_announce(announce_entry const& ae) { return ae.min_announce; } + lt::time_point get_next_announce(announce_entry const& ae) + { return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().next_announce); } + lt::time_point get_min_announce(announce_entry const& ae) + { return ae.endpoints.empty() ? lt::time_point() : lt::time_point(ae.endpoints.front().min_announce); } // announce_entry data member bit-fields. - int get_fails(announce_entry const& ae) { return ae.fails; } - int get_source(announce_entry const& ae) { return ae.source; } - bool get_verified(announce_entry const& ae) { return ae.verified; } - bool get_updating(announce_entry const& ae) { return ae.updating; } - bool get_start_sent(announce_entry const& ae) { return ae.start_sent; } - bool get_complete_sent(announce_entry const& ae) { return ae.complete_sent; } + int get_fails(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().fails; } + bool get_updating(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().updating; } + bool get_start_sent(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().start_sent; } + bool get_complete_sent(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().complete_sent; } // announce_entry method requires lt::time_point. bool can_announce(announce_entry const& ae, bool is_seed) { + // an entry without endpoints implies it has never been announced so it can be now + if (ae.endpoints.empty()) return true; lt::time_point now = lt::clock_type::now(); - return ae.can_announce(now, is_seed); + return ae.endpoints.front().can_announce(now, is_seed, ae.fail_limit); } + bool is_working(announce_entry const& ae) + { return ae.endpoints.empty() ? false : ae.endpoints.front().is_working(); } +#endif + int get_source(announce_entry const& ae) { return ae.source; } + bool get_verified(announce_entry const& ae) { return ae.verified; } #ifndef TORRENT_NO_DEPRECATE + std::string get_message(announce_entry const& ae) + { return ae.endpoints.empty() ? "" : ae.endpoints.front().message; } + error_code get_last_error(announce_entry const& ae) + { return ae.endpoints.empty() ? error_code() : ae.endpoints.front().last_error; } + int get_scrape_incomplete(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_incomplete; } + int get_scrape_complete(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_complete; } + int get_scrape_downloaded(announce_entry const& ae) + { return ae.endpoints.empty() ? 0 : ae.endpoints.front().scrape_downloaded; } + int next_announce_in(announce_entry const& ae) { return 0; } + int min_announce_in(announce_entry const& ae) { return 0; } bool get_send_stats(announce_entry const& ae) { return ae.send_stats; } std::int64_t get_size(file_entry const& fe) { return fe.size; } std::int64_t get_offset(file_entry const& fe) { return fe.offset; } @@ -158,7 +182,7 @@ std::shared_ptr buffer_constructor0(char const* buf, int len, int { error_code ec; std::shared_ptr ret = std::make_shared(buf - , len, ec, flags); + , len, ec, flags); #ifndef BOOST_NO_EXCEPTIONS if (ec) throw system_error(ec); #endif @@ -167,14 +191,14 @@ std::shared_ptr buffer_constructor0(char const* buf, int len, int std::shared_ptr buffer_constructor1(char const* buf, int len) { - return buffer_constructor0(buf, len, 0); + return buffer_constructor0(buf, len, 0); } std::shared_ptr file_constructor0(std::string const& filename, int flags) { error_code ec; std::shared_ptr ret = std::make_shared(filename - , ec, flags); + , ec, flags); #ifndef BOOST_NO_EXCEPTIONS if (ec) throw system_error(ec); #endif @@ -183,34 +207,34 @@ std::shared_ptr file_constructor0(std::string const& filename, int std::shared_ptr file_constructor1(std::string const& filename) { - return file_constructor0(filename, 0); + return file_constructor0(filename, 0); } std::shared_ptr bencoded_constructor0(entry const& ent, int flags) { - std::vector buf; - bencode(std::back_inserter(buf), ent); + std::vector buf; + bencode(std::back_inserter(buf), ent); - bdecode_node e; - error_code ec; - if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) - { + bdecode_node e; + error_code ec; + if (buf.size() == 0 || bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0) + { #ifndef BOOST_NO_EXCEPTIONS - throw system_error(ec); + throw system_error(ec); #endif - } + } - std::shared_ptr ret = std::make_shared(e - , ec, flags); + std::shared_ptr ret = std::make_shared(e + , ec, flags); #ifndef BOOST_NO_EXCEPTIONS - if (ec) throw system_error(ec); + if (ec) throw system_error(ec); #endif - return ret; + return ret; } std::shared_ptr bencoded_constructor1(entry const& ent) { - return bencoded_constructor0(ent, 0); + return bencoded_constructor0(ent, 0); } using by_value = return_value_policy; @@ -312,29 +336,31 @@ void bind_torrent_info() class_("announce_entry", init()) .def_readwrite("url", &announce_entry::url) .def_readonly("trackerid", &announce_entry::trackerid) - .def_readonly("message", &announce_entry::message) - .def_readonly("last_error", &announce_entry::last_error) +#if !defined TORRENT_NO_DEPRECATE + .add_property("message", &get_message) + .add_property("last_error", &get_last_error) .add_property("next_announce", &get_next_announce) .add_property("min_announce", &get_min_announce) - .def_readonly("scrape_incomplete", &announce_entry::scrape_incomplete) - .def_readonly("scrape_complete", &announce_entry::scrape_complete) - .def_readonly("scrape_downloaded", &announce_entry::scrape_downloaded) + .add_property("scrape_incomplete", &get_scrape_incomplete) + .add_property("scrape_complete", &get_scrape_complete) + .add_property("scrape_downloaded", &get_scrape_downloaded) +#endif .def_readwrite("tier", &announce_entry::tier) .def_readwrite("fail_limit", &announce_entry::fail_limit) - .add_property("fails", &get_fails) .add_property("source", &get_source) .add_property("verified", &get_verified) +#if !defined TORRENT_NO_DEPRECATE + .add_property("fails", &get_fails) .add_property("updating", &get_updating) .add_property("start_sent", &get_start_sent) .add_property("complete_sent", &get_complete_sent) -#if !defined TORRENT_NO_DEPRECATE .add_property("send_stats", &get_send_stats) - .def("next_announce_in", &announce_entry::next_announce_in) - .def("min_announce_in", &announce_entry::min_announce_in) + .def("next_announce_in", &next_announce_in) + .def("min_announce_in", &min_announce_in) + .def("can_announce", &can_announce) + .def("is_working", &is_working) #endif .def("reset", &announce_entry::reset) - .def("can_announce", can_announce) - .def("is_working", &announce_entry::is_working) .def("trim", &announce_entry::trim) ; diff --git a/bindings/python/test.py b/bindings/python/test.py index b1205e98b..4c44a6057 100644 --- a/bindings/python/test.py +++ b/bindings/python/test.py @@ -98,10 +98,14 @@ class test_torrent_handle(unittest.TestCase): trackers = [tracker] self.h.replace_trackers(trackers) tracker_list = [tracker for tracker in self.h.trackers()] + # wait a bit until the endpoints list gets populated + while len(tracker_list[0]['endpoints']) == 0: + time.sleep(0.1) + tracker_list = [tracker for tracker in self.h.trackers()] pickled_trackers = pickle.dumps(tracker_list) unpickled_trackers = pickle.loads(pickled_trackers) self.assertEqual(unpickled_trackers[0]['url'], 'udp://tracker1.com') - self.assertEqual(unpickled_trackers[0]['last_error']['value'], 0) + self.assertEqual(unpickled_trackers[0]['endpoints'][0]['last_error']['value'], 0) def test_file_status(self): self.setup() @@ -129,8 +133,8 @@ class test_torrent_handle(unittest.TestCase): self.setup() self.h.add_tracker({'url':'udp://tracker1.com'}) tr = self.h.trackers()[0] - # wait a bit until a valid timestamp appears - while tr['next_announce'] == None: + # wait a bit until the endpoints list gets populated + while len(tr['endpoints']) == 0: time.sleep(0.1) tr = self.h.trackers()[0] import json @@ -281,10 +285,10 @@ class test_torrent_info(unittest.TestCase): def test_announce_entry(self): ae = lt.announce_entry('test') - self.assertEquals(ae.can_announce(False), True) - self.assertEquals(ae.scrape_incomplete, -1) - self.assertEquals(ae.next_announce, None) - self.assertEquals(ae.last_error.value(), 0) + self.assertEquals(ae.url, 'test') + self.assertEquals(ae.tier, 0) + self.assertEquals(ae.verified, False) + self.assertEquals(ae.source, 0) class test_alerts(unittest.TestCase): diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 4323faa4a..0f7a6861d 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -1719,14 +1719,17 @@ MAGNETURL is a magnet link { for (announce_entry const& ae : h.trackers()) { + auto best_ae = std::min_element(ae.endpoints.begin(), ae.endpoints.end() + , [](announce_endpoint const& l, announce_endpoint const& r) { return l.fails < r.fails; } ); + if (pos + 1 >= terminal_height) break; std::snprintf(str, sizeof(str), "%2d %-55s fails: %-3d (%-3d) %s %s %5d \"%s\" %s\x1b[K\n" - , ae.tier, ae.url.c_str(), ae.fails, ae.fail_limit, ae.verified?"OK ":"- " - , ae.updating?"updating" - :to_string(int(total_seconds(ae.next_announce - now)), 8).c_str() - , int(ae.min_announce > now ? total_seconds(ae.min_announce - now) : 0) - , ae.last_error ? ae.last_error.message().c_str() : "" - , ae.message.c_str()); + , ae.tier, ae.url.c_str() + , best_ae != ae.endpoints.end() ? best_ae->fails : 0, ae.fail_limit, ae.verified?"OK ":"- " + , to_string(int(total_seconds(best_ae->next_announce - now)), 8).c_str() + , int(best_ae->min_announce > now ? total_seconds(best_ae->min_announce - now) : 0) + , best_ae != ae.endpoints.end() && best_ae->last_error ? best_ae->last_error.message().c_str() : "" + , best_ae != ae.endpoints.end() ? best_ae->message.c_str() : ""); out += str; pos += 1; } diff --git a/include/libtorrent/announce_entry.hpp b/include/libtorrent/announce_entry.hpp index ed8210f1f..7ba4ab362 100644 --- a/include/libtorrent/announce_entry.hpp +++ b/include/libtorrent/announce_entry.hpp @@ -37,12 +37,105 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/time.hpp" #include "libtorrent/error_code.hpp" #include "libtorrent/string_view.hpp" +#include "libtorrent/socket.hpp" #include #include +#include namespace libtorrent { + namespace aux { struct session_listen_socket; } + + // announces are sent to each tracker using every listen socket + // this class holds information about one listen socket for one tracker + struct TORRENT_EXPORT announce_endpoint + { + friend class torrent; + friend struct announce_entry; + + // internal + explicit announce_endpoint(aux::session_listen_socket* s); + + // if this tracker has returned an error or warning message + // that message is stored here + std::string message; + + // if this tracker failed the last time it was contacted + // this error code specifies what error occurred + error_code last_error; + + // the local endpoint of the listen interface associated with this endpoint + tcp::endpoint local_endpoint; + + // the time of next tracker announce + time_point32 next_announce = time_point32::min(); + + // no announces before this time + time_point32 min_announce = time_point32::min(); + + private: + // internal + aux::session_listen_socket* socket; + + public: + // TODO: include the number of peers received from this tracker, at last + // announce + + // these are either -1 or the scrape information this tracker last + // responded with. *incomplete* is the current number of downloaders in + // the swarm, *complete* is the current number of seeds in the swarm and + // *downloaded* is the cumulative number of completed downloads of this + // torrent, since the beginning of time (from this tracker's point of + // view). + + // if this tracker has returned scrape data, these fields are filled in + // with valid numbers. Otherwise they are set to -1. the number of + // current downloaders + int scrape_incomplete = -1; + int scrape_complete = -1; + + int scrape_downloaded = -1; + + // the number of times in a row we have failed to announce to this + // tracker. + std::uint8_t fails : 7; + + // true while we're waiting for a response from the tracker. + bool updating : 1; + + // set to true when we get a valid response from an announce + // with event=started. If it is set, we won't send start in the subsequent + // announces. + bool start_sent : 1; + + // set to true when we send a event=completed. + bool complete_sent : 1; + + // internal + bool triggered_manually : 1; + + // reset announce counters and clears the started sent flag. + // The announce_endpoint will look like we've never talked to + // the tracker. + void reset(); + + // updates the failure counter and time-outs for re-trying. + // This is called when the tracker announce fails. + void failed(int backoff_ratio, seconds32 retry_interval = seconds32(0)); + + // returns true if we can announce to this tracker now. + // The current time is passed in as ``now``. The ``is_seed`` + // argument is necessary because once we become a seed, we + // need to announce right away, even if the re-announce timer + // hasn't expired yet. + bool can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const; + + // returns true if the last time we tried to announce to this + // tracker succeeded, or if we haven't tried yet. + bool is_working() const { return fails == 0; } + }; + // this class holds information about one bittorrent tracker, as it // relates to a specific torrent. struct TORRENT_EXPORT announce_entry @@ -62,50 +155,7 @@ namespace libtorrent { // trackerid is sent). std::string trackerid; - // if this tracker has returned an error or warning message - // that message is stored here - std::string message; - - // if this tracker failed the last time it was contacted - // this error code specifies what error occurred - error_code last_error; - -#ifndef TORRENT_NO_DEPRECATE - // returns the number of seconds to the next announce on this tracker. - // ``min_announce_in()`` returns the number of seconds until we are - // allowed to force another tracker update with this tracker. - // - // If the last time this tracker was contacted failed, ``last_error`` is - // the error code describing what error occurred. - TORRENT_DEPRECATED - int next_announce_in() const; - TORRENT_DEPRECATED - int min_announce_in() const; -#endif - - // the time of next tracker announce - time_point32 next_announce = time_point32::min(); - - // no announces before this time - time_point32 min_announce = time_point32::min(); - - // TODO: include the number of peers received from this tracker, at last - // announce - - // these are either -1 or the scrape information this tracker last - // responded with. *incomplete* is the current number of downloaders in - // the swarm, *complete* is the current number of seeds in the swarm and - // *downloaded* is the cumulative number of completed downloads of this - // torrent, since the beginning of time (from this tracker's point of - // view). - - // if this tracker has returned scrape data, these fields are filled in - // with valid numbers. Otherwise they are set to -1. the number of - // current downloaders - int scrape_incomplete = -1; - int scrape_complete = -1; - - int scrape_downloaded = -1; + std::vector endpoints; // the tier this tracker belongs to std::uint8_t tier = 0; @@ -114,13 +164,6 @@ namespace libtorrent { // a row, before this tracker is not used anymore. 0 means unlimited std::uint8_t fail_limit = 0; - // the number of times in a row we have failed to announce to this - // tracker. - std::uint8_t fails:7; - - // true while we're waiting for a response from the tracker. - bool updating:1; - // flags for the source bitmask, each indicating where // we heard about this tracker enum tracker_source @@ -142,57 +185,49 @@ namespace libtorrent { // from this tracker. bool verified:1; - // set to true when we get a valid response from an announce - // with event=started. If it is set, we won't send start in the subsequent - // announces. - bool start_sent:1; - - // set to true when we send a event=completed. - bool complete_sent:1; - #ifndef TORRENT_NO_DEPRECATE // deprecated in 1.2 - // this is false the stats sent to this tracker will be 0 - bool send_stats:1; + // all of these will be set to false or 0 + // use the corresponding members in announce_endpoint + std::uint8_t TORRENT_DEPRECATED_MEMBER fails:7; + bool TORRENT_DEPRECATED_MEMBER send_stats:1; + bool TORRENT_DEPRECATED_MEMBER start_sent:1; + bool TORRENT_DEPRECATED_MEMBER complete_sent:1; + // internal + bool TORRENT_DEPRECATED_MEMBER triggered_manually:1; + bool TORRENT_DEPRECATED_MEMBER updating:1; #else // hidden + std::uint8_t deprecated_fails:7; bool deprecated_send_stats:1; + bool deprecated_start_sent:1; + bool deprecated_complete_sent:1; + bool deprecated_triggered_manually:1; + bool deprecated_updating:1; #endif - // internal - bool triggered_manually:1; - // reset announce counters and clears the started sent flag. // The announce_entry will look like we've never talked to // the tracker. void reset(); - // updates the failure counter and time-outs for re-trying. - // This is called when the tracker announce fails. - void failed(int backoff_ratio, seconds32 retry_interval = seconds32(0)); - #ifndef TORRENT_NO_DEPRECATE - // deprecated in 1.0 - TORRENT_DEPRECATED - bool will_announce(time_point now) const - { - return now <= next_announce - && (fails < fail_limit || fail_limit == 0) - && !updating; - } -#endif - + // deprecated in 1.2, use announce_endpoint::can_announce // returns true if we can announce to this tracker now. // The current time is passed in as ``now``. The ``is_seed`` // argument is necessary because once we become a seed, we // need to announce right away, even if the re-announce timer // hasn't expired yet. - bool can_announce(time_point now, bool is_seed) const; + TORRENT_DEPRECATED bool can_announce(time_point now, bool is_seed) const; + // deprecated in 1.2, use announce_endpoint::is_working // returns true if the last time we tried to announce to this // tracker succeeded, or if we haven't tried yet. - bool is_working() const - { return fails == 0; } + TORRENT_DEPRECATED bool is_working() const; +#endif + + // internal + announce_endpoint* find_endpoint(aux::session_listen_socket* s); // trims whitespace characters from the beginning of the URL. void trim(); diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index df7d09fbb..241994e7c 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -195,6 +195,11 @@ namespace aux { listen_endpoint_t(address adr, int p, std::string dev, bool s) : addr(adr), port(p), device(dev), ssl(s) {} + bool operator==(listen_endpoint_t const& o) const + { + return addr == o.addr && port == o.port && device == o.device && ssl == o.ssl; + } + address addr; int port; std::string device; @@ -210,6 +215,11 @@ namespace aux { std::vector& eps , std::list& sockets); + // expand [::] to all IPv6 interfaces for BEP 45 compliance + TORRENT_EXTRA_EXPORT void expand_unspecified_address( + std::vector const& ifs + , std::vector& eps); + // this is the link between the main thread and the // thread started to run the main downloader loop struct TORRENT_EXTRA_EXPORT session_impl final @@ -563,6 +573,14 @@ namespace aux { std::uint16_t ssl_listen_port() const override; std::uint16_t ssl_listen_port(listen_socket_t* sock) const; + void for_each_listen_socket(std::function f) override + { + for (auto& s : m_listen_sockets) + { + f(&s); + } + } + alert_manager& alerts() override { return m_alerts; } disk_interface& disk_thread() override { return m_disk_thread; } diff --git a/include/libtorrent/aux_/session_interface.hpp b/include/libtorrent/aux_/session_interface.hpp index f2189ee40..8542cc730 100644 --- a/include/libtorrent/aux_/session_interface.hpp +++ b/include/libtorrent/aux_/session_interface.hpp @@ -198,6 +198,8 @@ namespace libtorrent { namespace aux { virtual std::uint16_t listen_port() const = 0; virtual std::uint16_t ssl_listen_port() const = 0; + virtual void for_each_listen_socket(std::function f) = 0; + // ask for which interface and port to bind outgoing peer connections on virtual tcp::endpoint bind_outgoing_socket(socket_type& s, address const& remote_address, error_code& ec) const = 0; diff --git a/include/libtorrent/http_connection.hpp b/include/libtorrent/http_connection.hpp index 911d18bc2..b27c88880 100644 --- a/include/libtorrent/http_connection.hpp +++ b/include/libtorrent/http_connection.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/aux_/disable_warnings_push.hpp" #include +#include #ifdef TORRENT_USE_OPENSSL #include @@ -101,7 +102,7 @@ struct TORRENT_EXTRA_EXPORT http_connection void get(std::string const& url, time_duration timeout = seconds(30) , int prio = 0, aux::proxy_settings const* ps = 0, int handle_redirects = 5 , std::string const& user_agent = std::string() - , address const& bind_addr = address_v4::any() + , boost::optional
const& bind_addr = boost::optional
() , int resolve_flags = 0, std::string const& auth_ = std::string() #if TORRENT_USE_I2P , i2p_connection* i2p_conn = 0 @@ -111,7 +112,7 @@ struct TORRENT_EXTRA_EXPORT http_connection void start(std::string const& hostname, int port , time_duration timeout, int prio = 0, aux::proxy_settings const* ps = 0 , bool ssl = false, int handle_redirect = 5 - , address const& bind_addr = address_v4::any() + , boost::optional
const& bind_addr = boost::optional
() , int resolve_flags = 0 #if TORRENT_USE_I2P , i2p_connection* i2p_conn = 0 @@ -187,9 +188,8 @@ private: // configured to use a proxy aux::proxy_settings m_proxy; - // the address to bind to. address_v4::any() - // means do not bind - address m_bind_addr; + // the address to bind to + boost::optional
m_bind_addr; // if username password was passed in, remember it in case we need to // re-issue the request for a redirect diff --git a/include/libtorrent/tracker_manager.hpp b/include/libtorrent/tracker_manager.hpp index a733175a2..eacb9d97f 100644 --- a/include/libtorrent/tracker_manager.hpp +++ b/include/libtorrent/tracker_manager.hpp @@ -76,7 +76,7 @@ namespace libtorrent { #if TORRENT_USE_I2P class i2p_connection; #endif - namespace aux { struct session_logger; struct session_settings; } + namespace aux { struct session_listen_socket; struct session_logger; struct session_settings; } // returns -1 if gzip header is invalid or the header size in bytes TORRENT_EXTRA_EXPORT int gzip_header(const char* buf, int size); @@ -94,6 +94,7 @@ namespace libtorrent { , kind(announce_request) , key(0) , num_want(0) + , outgoing_socket(nullptr) , private_torrent(false) , triggered_manually(false) #ifdef TORRENT_USE_OPENSSL @@ -151,7 +152,7 @@ namespace libtorrent { #endif sha1_hash info_hash; peer_id pid; - address bind_ip; + aux::session_listen_socket* outgoing_socket; // set to true if the .torrent file this tracker announce is for is marked // as private (i.e. has the "priv": 1 key) @@ -306,7 +307,8 @@ namespace libtorrent { , seconds32 interval = seconds32(0), seconds32 min_interval = seconds32(0)); virtual void start() = 0; virtual void close() = 0; - address const& bind_interface() const { return m_req.bind_ip; } + address bind_interface() const; + aux::session_listen_socket* bind_socket() const { return m_req.outgoing_socket; } void sent_bytes(int bytes); void received_bytes(int bytes); @@ -336,10 +338,12 @@ namespace libtorrent { { public: - typedef std::function , error_code&, int)> send_fun_t; - typedef std::function , error_code&, int)> send_fun_hostname_t; @@ -385,10 +389,12 @@ namespace libtorrent { aux::session_settings const& settings() const { return m_settings; } resolver_interface& host_resolver() { return m_host_resolver; } - void send_hostname(char const* hostname, int port, span p + void send_hostname(aux::session_listen_socket* sock + , char const* hostname, int port, span p , error_code& ec, int flags = 0); - void send(udp::endpoint const& ep, span p + void send(aux::session_listen_socket* sock + , udp::endpoint const& ep, span p , error_code& ec, int flags = 0); private: diff --git a/simulation/test_http_connection.cpp b/simulation/test_http_connection.cpp index 364486a2a..ae7eb8ea8 100644 --- a/simulation/test_http_connection.cpp +++ b/simulation/test_http_connection.cpp @@ -171,7 +171,7 @@ std::shared_ptr test_request(io_service& ios std::printf("CONNECTED: %s\n", url.c_str()); }); - h->get(url, seconds(1), 0, &ps, 5, "test/user-agent", address_v4::any() + h->get(url, seconds(1), 0, &ps, 5, "test/user-agent", address(address_v4::any()) , 0, auth); return h; } diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp index 47120d2f2..2f9a99311 100644 --- a/simulation/test_tracker.cpp +++ b/simulation/test_tracker.cpp @@ -91,6 +91,10 @@ void test_interval(int interval) std::vector announce_alerts; lt::settings_pack default_settings = settings(); + // since the test tracker is only listening on IPv4 we need to configure the + // client to do the same so that the number of tracker_announce_alerts matches + // the number of announces seen by the tracker + default_settings.set_str(settings_pack::listen_interfaces, "0.0.0.0:6881"); lt::add_torrent_params default_add_torrent; setup_swarm(1, swarm_test::upload, sim, default_settings, default_add_torrent @@ -261,6 +265,8 @@ TORRENT_TEST(announce_interval_1200) struct sim_config : sim::default_config { + explicit sim_config(bool ipv6 = true) : ipv6(ipv6) {} + chrono::high_resolution_clock::duration hostname_lookup( asio::ip::address const& requestor , std::string hostname @@ -270,12 +276,15 @@ struct sim_config : sim::default_config if (hostname == "tracker.com") { result.push_back(address_v4::from_string("10.0.0.2")); - result.push_back(address_v6::from_string("ff::dead:beef")); + if (ipv6) + result.push_back(address_v6::from_string("ff::dead:beef")); return duration_cast(chrono::milliseconds(100)); } return default_config::hostname_lookup(requestor, hostname, result, ec); } + + bool ipv6; }; void on_alert_notify(lt::session* ses) @@ -337,11 +346,23 @@ TORRENT_TEST(ipv6_support) return sim::send_response(200, "OK", size) + response; }); + static const int num_interfaces = 3; + { lt::session_proxy zombie; - asio::io_service ios(sim, { address_v4::from_string("10.0.0.3") - , address_v6::from_string("ffff::1337") }); + std::vector ips; + + for (int i = 0; i < num_interfaces; i++) + { + char ep[30]; + std::snprintf(ep, sizeof(ep), "10.0.0.%d", i + 1); + ips.push_back(address::from_string(ep)); + std::snprintf(ep, sizeof(ep), "ffff::1337:%d", i + 1); + ips.push_back(address::from_string(ep)); + } + + asio::io_service ios(sim, ips); lt::settings_pack sett = settings(); std::unique_ptr ses(new lt::session(sett, ios)); @@ -380,7 +401,8 @@ TORRENT_TEST(ipv6_support) // 2 because there's one announce on startup and one when shutting down TEST_EQUAL(v4_announces, 2); - TEST_EQUAL(v6_announces, 2); + // IPv6 will send announces for each interface + TEST_EQUAL(v6_announces, num_interfaces * 2); } // this runs a simulation of a torrent with tracker(s), making sure the request @@ -400,11 +422,14 @@ void tracker_test(Setup setup, Announce a, Test1 test1, Test2 test2 sim::simulation sim{network_cfg}; sim::asio::io_service tracker_ios(sim, address_v4::from_string("10.0.0.2")); + sim::asio::io_service tracker_ios6(sim, address_v6::from_string("ff::dead:beef")); // listen on port 8080 sim::http_server http(tracker_ios, 8080); + sim::http_server http6(tracker_ios6, 8080); http.register_handler(url_path, a); + http6.register_handler(url_path, a); lt::session_proxy zombie; @@ -491,11 +516,15 @@ TORRENT_TEST(test_error) } , [](announce_entry const& ae) { - TEST_EQUAL(ae.is_working(), false); - TEST_EQUAL(ae.message, "test"); TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); - TEST_EQUAL(ae.last_error, error_code(errors::tracker_failure)); - TEST_EQUAL(ae.fails, 1); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.is_working(), false); + TEST_EQUAL(aep.message, "test"); + TEST_EQUAL(aep.last_error, error_code(errors::tracker_failure)); + TEST_EQUAL(aep.fails, 1); + } }); } @@ -513,11 +542,15 @@ TORRENT_TEST(test_warning) } , [](announce_entry const& ae) { - TEST_EQUAL(ae.is_working(), true); - TEST_EQUAL(ae.message, "test2"); TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); - TEST_EQUAL(ae.last_error, error_code()); - TEST_EQUAL(ae.fails, 0); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.is_working(), true); + TEST_EQUAL(aep.message, "test2"); + TEST_EQUAL(aep.last_error, error_code()); + TEST_EQUAL(aep.fails, 0); + } }); } @@ -536,14 +569,18 @@ TORRENT_TEST(test_scrape_data_in_announce) } , [](announce_entry const& ae) { - TEST_EQUAL(ae.is_working(), true); - TEST_EQUAL(ae.message, ""); TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); - TEST_EQUAL(ae.last_error, error_code()); - TEST_EQUAL(ae.fails, 0); - TEST_EQUAL(ae.scrape_complete, 1); - TEST_EQUAL(ae.scrape_incomplete, 2); - TEST_EQUAL(ae.scrape_downloaded, 3); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.is_working(), true); + TEST_EQUAL(aep.message, ""); + TEST_EQUAL(aep.last_error, error_code()); + TEST_EQUAL(aep.fails, 0); + TEST_EQUAL(aep.scrape_complete, 1); + TEST_EQUAL(aep.scrape_incomplete, 2); + TEST_EQUAL(aep.scrape_downloaded, 3); + } }); } @@ -570,9 +607,13 @@ TORRENT_TEST(test_scrape) TEST_EQUAL(tr.size(), 1); announce_entry const& ae = tr[0]; - TEST_EQUAL(ae.scrape_incomplete, 2); - TEST_EQUAL(ae.scrape_complete, 1); - TEST_EQUAL(ae.scrape_downloaded, 3); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.scrape_incomplete, 2); + TEST_EQUAL(aep.scrape_complete, 1); + TEST_EQUAL(aep.scrape_downloaded, 3); + } } , "/scrape"); } @@ -588,11 +629,15 @@ TORRENT_TEST(test_http_status) } , [](announce_entry const& ae) { - TEST_EQUAL(ae.is_working(), false); - TEST_EQUAL(ae.message, "Not A Tracker"); TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); - TEST_EQUAL(ae.last_error, error_code(410, http_category())); - TEST_EQUAL(ae.fails, 1); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.is_working(), false); + TEST_EQUAL(aep.message, "Not A Tracker"); + TEST_EQUAL(aep.last_error, error_code(410, http_category())); + TEST_EQUAL(aep.fails, 1); + } }); } @@ -610,11 +655,15 @@ TORRENT_TEST(test_interval) } , [](announce_entry const& ae) { - TEST_EQUAL(ae.is_working(), true); - TEST_EQUAL(ae.message, ""); TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); - TEST_EQUAL(ae.last_error, error_code()); - TEST_EQUAL(ae.fails, 0); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.is_working(), true); + TEST_EQUAL(aep.message, ""); + TEST_EQUAL(aep.last_error, error_code()); + TEST_EQUAL(aep.fails, 0); + } TEST_EQUAL(ae.trackerid, "testtest"); }); @@ -634,12 +683,16 @@ TORRENT_TEST(test_invalid_bencoding) } , [](announce_entry const& ae) { - TEST_EQUAL(ae.is_working(), false); - TEST_EQUAL(ae.message, ""); TEST_EQUAL(ae.url, "http://tracker.com:8080/announce"); - TEST_EQUAL(ae.last_error, error_code(bdecode_errors::expected_value - , bdecode_category())); - TEST_EQUAL(ae.fails, 1); + TEST_EQUAL(ae.endpoints.size(), 2); + for (auto const& aep : ae.endpoints) + { + TEST_EQUAL(aep.is_working(), false); + TEST_EQUAL(aep.message, ""); + TEST_EQUAL(aep.last_error, error_code(bdecode_errors::expected_value + , bdecode_category())); + TEST_EQUAL(aep.fails, 1); + } }); } @@ -685,22 +738,31 @@ TORRENT_TEST(try_next) std::printf("tracker \"%s\"\n", tr[i].url.c_str()); if (tr[i].url == "http://tracker.com:8080/announce") { - TEST_EQUAL(tr[i].fails, 0); + for (auto const& aep : tr[i].endpoints) + { + TEST_EQUAL(aep.fails, 0); + } TEST_EQUAL(tr[i].verified, true); } else if (tr[i].url == "http://failing-tracker.com/announce") { - TEST_CHECK(tr[i].fails >= 1); + for (auto const& aep : tr[i].endpoints) + { + TEST_CHECK(aep.fails >= 1); + TEST_EQUAL(aep.last_error + , error_code(boost::asio::error::host_not_found)); + } TEST_EQUAL(tr[i].verified, false); - TEST_EQUAL(tr[i].last_error - , error_code(boost::asio::error::host_not_found)); } else if (tr[i].url == "udp://failing-tracker.com/announce") { - TEST_CHECK(tr[i].fails >= 1); TEST_EQUAL(tr[i].verified, false); - TEST_EQUAL(tr[i].last_error - , error_code(boost::asio::error::host_not_found)); + for (auto const& aep : tr[i].endpoints) + { + TEST_CHECK(aep.fails >= 1); + TEST_EQUAL(aep.last_error + , error_code(boost::asio::error::host_not_found)); + } } else { diff --git a/src/announce_entry.cpp b/src/announce_entry.cpp index 2227401d9..90c7f3bf7 100644 --- a/src/announce_entry.cpp +++ b/src/announce_entry.cpp @@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/string_util.hpp" // for is_space #include "libtorrent/aux_/time.hpp" #include "libtorrent/aux_/session_settings.hpp" +#include "libtorrent/aux_/session_listen_socket.hpp" namespace libtorrent { @@ -46,47 +47,55 @@ namespace libtorrent { minutes32 constexpr tracker_retry_delay_max{60}; } - announce_entry::announce_entry(string_view u) - : url(u.to_string()) + announce_endpoint::announce_endpoint(aux::session_listen_socket* s) + : local_endpoint(s ? s->get_local_endpoint() : tcp::endpoint()) + , socket(s) , fails(0) , updating(false) - , source(0) - , verified(false) , start_sent(false) , complete_sent(false) , triggered_manually(false) {} - announce_entry::announce_entry() - : fails(0) - , updating(false) + announce_entry::announce_entry(string_view u) + : url(u.to_string()) , source(0) , verified(false) +#ifndef TORRENT_NO_DEPRECATE + , fails(0) + , send_stats(false) , start_sent(false) , complete_sent(false) , triggered_manually(false) + , updating(false) +#endif + {} + + announce_entry::announce_entry() + : source(0) + , verified(false) +#ifndef TORRENT_NO_DEPRECATE + , fails(0) + , send_stats(false) + , start_sent(false) + , complete_sent(false) + , triggered_manually(false) + , updating(false) +#endif {} announce_entry::~announce_entry() = default; announce_entry::announce_entry(announce_entry const&) = default; announce_entry& announce_entry::operator=(announce_entry const&) = default; -#ifndef TORRENT_NO_DEPRECATE - int announce_entry::next_announce_in() const - { return int(total_seconds(next_announce - aux::time_now())); } - - int announce_entry::min_announce_in() const - { return int(total_seconds(min_announce - aux::time_now())); } -#endif - - void announce_entry::reset() + void announce_endpoint::reset() { start_sent = false; next_announce = time_point32::min(); min_announce = time_point32::min(); } - void announce_entry::failed(int const backoff_ratio, seconds32 const retry_interval) + void announce_endpoint::failed(int const backoff_ratio, seconds32 const retry_interval) { ++fails; // the exponential back-off ends up being: @@ -98,11 +107,11 @@ namespace libtorrent { , tracker_retry_delay_min + fail_square * tracker_retry_delay_min * backoff_ratio / 100 )); - next_announce = aux::time_now32() + delay; + if (!is_working()) next_announce = aux::time_now32() + delay; updating = false; } - bool announce_entry::can_announce(time_point now, bool is_seed) const + bool announce_endpoint::can_announce(time_point now, bool is_seed, std::uint8_t fail_limit) const { // if we're a seed and we haven't sent a completed // event, we need to let this announce through @@ -114,6 +123,34 @@ namespace libtorrent { && !updating; } + void announce_entry::reset() + { + for (auto& aep : endpoints) + aep.reset(); + } + +#ifndef TORRENT_NO_DEPRECATE + bool announce_entry::can_announce(time_point now, bool is_seed) const + { + return std::any_of(endpoints.begin(), endpoints.end() + , [&](announce_endpoint const& aep) { return aep.can_announce(now, is_seed, fail_limit); }); + } + + bool announce_entry::is_working() const + { + return std::any_of(endpoints.begin(), endpoints.end() + , [](announce_endpoint const& aep) { return aep.is_working(); }); + } +#endif + + announce_endpoint* announce_entry::find_endpoint(aux::session_listen_socket* s) + { + auto aep = std::find_if(endpoints.begin(), endpoints.end() + , [&](announce_endpoint const& a) { return a.socket == s; }); + if (aep != endpoints.end()) return &*aep; + else return nullptr; + } + void announce_entry::trim() { while (!url.empty() && is_space(url[0])) diff --git a/src/http_connection.cpp b/src/http_connection.cpp index 25f2e61dd..2fa889f27 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -109,7 +109,7 @@ http_connection::~http_connection() void http_connection::get(std::string const& url, time_duration timeout, int prio , aux::proxy_settings const* ps, int handle_redirects, std::string const& user_agent - , address const& bind_addr, int resolve_flags, std::string const& auth_ + , boost::optional
const& bind_addr, int resolve_flags, std::string const& auth_ #if TORRENT_USE_I2P , i2p_connection* i2p_conn #endif @@ -225,7 +225,7 @@ void http_connection::get(std::string const& url, time_duration timeout, int pri void http_connection::start(std::string const& hostname, int port , time_duration timeout, int prio, aux::proxy_settings const* ps, bool ssl , int handle_redirects - , address const& bind_addr + , boost::optional
const& bind_addr , int resolve_flags #if TORRENT_USE_I2P , i2p_connection* i2p_conn @@ -353,10 +353,10 @@ void http_connection::start(std::string const& hostname, int port instantiate_connection(m_timer.get_io_service() , proxy ? *proxy : null_proxy, m_sock, userdata, nullptr, false, false); - if (m_bind_addr != address_v4::any()) + if (m_bind_addr) { - m_sock.open(m_bind_addr.is_v4() ? tcp::v4() : tcp::v6(), ec); - m_sock.bind(tcp::endpoint(m_bind_addr, 0), ec); + m_sock.open(m_bind_addr->is_v4() ? tcp::v4() : tcp::v6(), ec); + m_sock.bind(tcp::endpoint(*m_bind_addr, 0), ec); if (ec) { m_timer.get_io_service().post(std::bind(&http_connection::callback @@ -530,13 +530,28 @@ void http_connection::on_resolve(error_code const& e aux::random_shuffle(m_endpoints.begin(), m_endpoints.end()); - // sort the endpoints so that the ones with the same IP version as our - // bound listen socket are first. So that when contacting a tracker, - // we'll talk to it from the same IP that we're listening on - if (m_bind_addr != address_v4::any()) - std::partition(m_endpoints.begin(), m_endpoints.end() + // if we have been told to bind to a particular address + // only connect to addresses of the same family + if (m_bind_addr) + { + auto new_end = std::partition(m_endpoints.begin(), m_endpoints.end() , [this] (tcp::endpoint const& ep) - { return ep.address().is_v4() == m_bind_addr.is_v4(); }); + { + if (ep.address().is_v4() != m_bind_addr->is_v4()) + return false; + if (ep.address().is_v4() && m_bind_addr->is_v4()) + return true; + TORRENT_ASSERT(ep.address().is_v6() && m_bind_addr->is_v6()); + return ep.address().to_v6().scope_id() == m_bind_addr->to_v6().scope_id(); + }); + m_endpoints.erase(new_end, m_endpoints.end()); + if (m_endpoints.empty()) + { + callback(error_code(boost::system::errc::address_family_not_supported, generic_category())); + close(); + return; + } + } connect(); } diff --git a/src/session_impl.cpp b/src/session_impl.cpp index bd2d32c0c..1b99d9fb1 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -291,6 +291,49 @@ namespace aux { }); } + // To comply with BEP 45 multi homed clients must run separate DHT nodes + // on each interface they use to talk to the DHT. For IPv6 this is enforced + // by prohibiting creating a listen socket on [::]. Instead the list of + // interfaces is enumerated and sockets are created for each of them. + // This is not enforced for 0.0.0.0 because multi homed IPv4 configurations + // are much less common and the presence of NAT means that we cannot + // automatically determine which interfaces should have DHT nodes started on + // them. + void expand_unspecified_address(std::vector const& ifs + , std::vector& eps) + { + auto unspeficied_begin = std::partition(eps.begin(), eps.end() + , [](listen_endpoint_t const& ep) { return !(ep.addr.is_v6() && ep.addr.is_unspecified()); }); + std::vector unspecified_eps(unspeficied_begin, eps.end()); + eps.erase(unspeficied_begin, eps.end()); + for (auto const& uep : unspecified_eps) + { + for (auto const& ipface : ifs) + { + if (ipface.interface_address.is_v4()) + continue; + if (ipface.interface_address.is_loopback()) + continue; + if (!uep.device.empty() && uep.device != ipface.name) + continue; + if (std::any_of(eps.begin(), eps.end(), [&](listen_endpoint_t const& e) + { + // ignore device name because we don't want to create + // duplicates if the user explicitly configured an address + // without a device name + return e.addr == ipface.interface_address + && e.port == uep.port + && e.ssl == uep.ssl; + })) + { + continue; + } + + eps.emplace_back(ipface.interface_address, uep.port, uep.device, uep.ssl); + } + } + } + void session_impl::init_peer_class_filter(bool unlimited_local) { // set the default peer_class_filter to use the local peer class @@ -423,8 +466,8 @@ namespace aux { , m_upload_rate(peer_connection::upload_channel) , m_host_resolver(m_io_service) , m_tracker_manager( - std::bind(&session_impl::send_udp_packet_deprecated, this, false, _1, _2, _3, _4) - , std::bind(&session_impl::send_udp_packet_hostname_deprecated, this, _1, _2, _3, _4, _5) + std::bind(&session_impl::send_udp_packet_listen, this, _1, _2, _3, _4, _5) + , std::bind(&session_impl::send_udp_packet_hostname_listen, this, _1, _2, _3, _4, _5, _6) , m_stats_counters , m_host_resolver , m_settings @@ -1151,18 +1194,6 @@ namespace { void session_impl::queue_tracker_request(tracker_request& req , std::weak_ptr c) { - req.listen_port = listen_port(); - if (m_key) req.key = m_key; - -#ifdef TORRENT_USE_OPENSSL - // SSL torrents use the SSL listen port - // TODO: 2 this need to be more thought through. There isn't necessarily - // just _one_ SSL listen port, which one we use depends on which interface - // we announce from. - if (req.ssl_ctx) req.listen_port = ssl_listen_port(); - req.ssl_ctx = &m_ssl_ctx; -#endif - #if TORRENT_USE_I2P if (!m_settings.get_str(settings_pack::i2p_hostname).empty()) { @@ -1170,8 +1201,36 @@ namespace { } #endif -//TODO: should there be an option to announce once per listen interface? - m_tracker_manager.queue_request(get_io_service(), req, c); + if (m_key) req.key = m_key; + +#ifdef TORRENT_USE_OPENSSL + bool use_ssl = req.ssl_ctx != nullptr; + req.ssl_ctx = &m_ssl_ctx; +#endif + + if (req.outgoing_socket) + { + listen_socket_t* ls = static_cast(req.outgoing_socket); + req.listen_port = listen_port(ls); +#ifdef TORRENT_USE_OPENSSL + // SSL torrents use the SSL listen port + if (use_ssl) req.listen_port = ssl_listen_port(ls); +#endif + m_tracker_manager.queue_request(get_io_service(), req, c); + } + else + { + for (auto& ls : m_listen_sockets) + { + req.listen_port = listen_port(&ls); +#ifdef TORRENT_USE_OPENSSL + // SSL torrents use the SSL listen port + if (use_ssl) req.listen_port = ssl_listen_port(&ls); +#endif + req.outgoing_socket = &ls; + m_tracker_manager.queue_request(get_io_service(), req, c); + } + } } void session_impl::set_peer_class(peer_class_t cid, peer_class_info const& pci) @@ -1876,6 +1935,14 @@ namespace { interface_to_endpoints(device, port, ssl, eps); } +#if TORRENT_USE_IPV6 + std::vector const ifs = enum_net_interfaces(m_io_service, ec); + if (!ec) + { + expand_unspecified_address(ifs, eps); + } +#endif + auto remove_iter = partition_listen_sockets(eps, m_listen_sockets); while (remove_iter != m_listen_sockets.end()) diff --git a/src/torrent.cpp b/src/torrent.cpp index 72bb04b4d..b11c98ab4 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -1473,8 +1473,6 @@ namespace libtorrent { // this is needed for openssl < 1.0 to decrypt keys created by openssl 1.0+ OpenSSL_add_all_algorithms(); - TORRENT_ASSERT(RAND_status() == 1); - // create the SSL context for this torrent. We need to // inject the root certificate, and no other, to // verify other peers against @@ -2569,6 +2567,28 @@ namespace libtorrent { #endif + namespace + { + struct announce_state + { + explicit announce_state(aux::session_listen_socket* s) + : socket(s) {} + + aux::session_listen_socket* socket; + + // the tier is kept as INT_MAX until we find the first + // tracker that works, then it's set to that tracker's + // tier. + int tier = INT_MAX; + + // have we sent an announce in this tier yet? + bool sent_announce = false; + + // have we finished sending announces on this listen socket? + bool done = false; + }; + } + void torrent::announce_with_tracker(std::uint8_t e) { TORRENT_ASSERT(is_single_thread()); @@ -2666,60 +2686,90 @@ namespace libtorrent { time_point32 const now = aux::time_now32(); - // the tier is kept as INT_MAX until we find the first - // tracker that works, then it's set to that tracker's - // tier. - int tier = INT_MAX; - - // have we sent an announce in this tier yet? - bool sent_announce = false; + // each listen socket gets it's own announce state + // so that each one should get at least one announce + std::vector listen_socket_states; for (auto& ae : m_trackers) { + // update the endpoint list by adding entries for new listen sockets + // and removing entries for non-existent ones + std::vector::size_type valid_endpoints = 0; + m_ses.for_each_listen_socket([&](aux::session_listen_socket* s) { + for (auto& aep : ae.endpoints) + { + if (aep.socket != s) continue; + std::swap(ae.endpoints[valid_endpoints], aep); + valid_endpoints++; + return; + } + + ae.endpoints.emplace_back(s); + std::swap(ae.endpoints[valid_endpoints], ae.endpoints.back()); + valid_endpoints++; + }); + + TORRENT_ASSERT(valid_endpoints <= ae.endpoints.size()); + ae.endpoints.erase(ae.endpoints.begin() + int(valid_endpoints), ae.endpoints.end()); + + // if trackerid is not specified for tracker use default one, probably set explicitly + req.trackerid = ae.trackerid.empty() ? m_trackerid : ae.trackerid; + req.url = ae.url; + + for (auto& aep : ae.endpoints) + { + auto aep_state_iter = std::find_if(listen_socket_states.begin(), listen_socket_states.end() + , [&](announce_state const& s) { return s.socket == aep.socket; }); + if (aep_state_iter == listen_socket_states.end()) + { + listen_socket_states.emplace_back(aep.socket); + aep_state_iter = listen_socket_states.end() - 1; + } + announce_state& state = *aep_state_iter; + + if (state.done) continue; + #ifndef TORRENT_DISABLE_LOGGING if (should_log()) { debug_log("*** tracker: \"%s\" " "[ tiers: %d trackers: %d" " i->tier: %d tier: %d" - " working: %d fails: %d limit: %d upd: %d" + " working: %d limit: %d" " can: %d sent: %d ]" , ae.url.c_str(), settings().get_bool(settings_pack::announce_to_all_tiers) , settings().get_bool(settings_pack::announce_to_all_trackers) - , ae.tier, tier, ae.is_working(), ae.fails, ae.fail_limit - , ae.updating, ae.can_announce(now, is_seed()), sent_announce); + , ae.tier, state.tier, aep.is_working(), ae.fail_limit + , aep.can_announce(now, is_seed(), ae.fail_limit), state.sent_announce); } #endif + if (settings().get_bool(settings_pack::announce_to_all_tiers) && !settings().get_bool(settings_pack::announce_to_all_trackers) - && sent_announce - && ae.tier <= tier - && tier != INT_MAX) + && state.sent_announce + && ae.tier <= state.tier + && state.tier != INT_MAX) continue; - // if trackerid is not specified for tracker use default one, probably set explicitly - req.trackerid = ae.trackerid.empty() ? m_trackerid : ae.trackerid; - - if (ae.tier > tier && sent_announce + if (ae.tier > state.tier && state.sent_announce && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; - if (ae.is_working()) { tier = ae.tier; sent_announce = false; } - if (!ae.can_announce(now, is_seed())) + if (aep.is_working()) { state.tier = ae.tier; state.sent_announce = false; } + if (!aep.can_announce(now, is_seed(), ae.fail_limit)) { // this counts - if (ae.is_working()) sent_announce = true; + if (aep.is_working()) state.sent_announce = true; continue; } - req.url = ae.url; req.event = e; if (req.event == tracker_request::none) { - if (!ae.start_sent) req.event = tracker_request::started; - else if (!ae.complete_sent && is_seed()) req.event = tracker_request::completed; + if (!aep.start_sent) req.event = tracker_request::started; + else if (!aep.complete_sent && is_seed()) req.event = tracker_request::completed; } - req.triggered_manually = ae.triggered_manually; - ae.triggered_manually = false; + req.triggered_manually = aep.triggered_manually; + aep.triggered_manually = false; if (settings().get_bool(settings_pack::force_proxy)) { @@ -2734,7 +2784,7 @@ namespace libtorrent { if ((protocol == "http" || protocol == "https") && proxy_type == settings_pack::none) { - ae.next_announce = now + minutes32(10); + aep.next_announce = now + minutes32(10); if (m_ses.alerts().should_post() || req.triggered_manually) { @@ -2752,7 +2802,7 @@ namespace libtorrent { && proxy_type != settings_pack::socks5_pw && proxy_type != settings_pack::i2p_proxy) { - ae.next_announce = now + minutes32(10); + aep.next_announce = now + minutes32(10); if (m_ses.alerts().should_post() || req.triggered_manually) { @@ -2775,40 +2825,51 @@ namespace libtorrent { } #endif + req.outgoing_socket = aep.socket; + #ifndef TORRENT_DISABLE_LOGGING - debug_log("==> TRACKER REQUEST \"%s\" event: %s abort: %d" - , req.url.c_str() - , (req.event == tracker_request::stopped ? "stopped" - : req.event == tracker_request::started ? "started" : "") - , m_abort); + debug_log("==> TRACKER REQUEST \"%s\" event: %s abort: %d fails: %d upd: %d" + , req.url.c_str() + , (req.event == tracker_request::stopped ? "stopped" + : req.event == tracker_request::started ? "started" : "") + , m_abort + , aep.fails + , aep.updating); - // if we're not logging session logs, don't bother creating an - // observer object just for logging - if (m_abort && m_ses.should_log()) - { - auto tl = std::make_shared(m_ses); - m_ses.queue_tracker_request(req, tl); - } - else + // if we're not logging session logs, don't bother creating an + // observer object just for logging + if (m_abort && m_ses.should_log()) + { + auto tl = std::make_shared(m_ses); + m_ses.queue_tracker_request(req, tl); + } + else #endif - { - m_ses.queue_tracker_request(req, shared_from_this()); + { + m_ses.queue_tracker_request(req, shared_from_this()); + } + + aep.updating = true; + aep.next_announce = now + seconds32(20); + aep.min_announce = now + seconds32(10); + + if (m_ses.alerts().should_post()) + { + m_ses.alerts().emplace_alert( + get_handle(), req.url, req.event); + } + + state.sent_announce = true; + if (aep.is_working() + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) + { + state.done = true; + } } - ae.updating = true; - ae.next_announce = now + seconds32(20); - ae.min_announce = now + seconds32(10); - - if (m_ses.alerts().should_post()) - { - m_ses.alerts().emplace_alert( - get_handle(), req.url, req.event); - } - - sent_announce = true; - if (ae.is_working() - && !settings().get_bool(settings_pack::announce_to_all_trackers) - && !settings().get_bool(settings_pack::announce_to_all_tiers)) + if (std::all_of(listen_socket_states.begin(), listen_socket_states.end() + , [](announce_state const& s) { return s.done; })) break; } update_tracker_timer(now); @@ -2852,7 +2913,12 @@ namespace libtorrent { announce_entry* ae = find_tracker(req.url); if (ae) { - ae->message = msg; + for (auto& aep : ae->endpoints) + { + if (aep.socket != req.outgoing_socket) continue; + aep.message = msg; + break; + } } if (m_ses.alerts().should_post()) @@ -2870,11 +2936,15 @@ namespace libtorrent { announce_entry* ae = find_tracker(req.url); if (ae) { - if (incomplete >= 0) ae->scrape_incomplete = incomplete; - if (complete >= 0) ae->scrape_complete = complete; - if (downloaded >= 0) ae->scrape_downloaded = downloaded; + announce_endpoint* aep = ae->find_endpoint(req.outgoing_socket); + if (aep) + { + if (incomplete >= 0) aep->scrape_incomplete = incomplete; + if (complete >= 0) aep->scrape_complete = complete; + if (downloaded >= 0) aep->scrape_downloaded = downloaded; - update_scrape_state(); + update_scrape_state(); + } } // if this was triggered manually we need to post this unconditionally, @@ -2897,9 +2967,12 @@ namespace libtorrent { int downloaded = -1; for (auto const& t : m_trackers) { - complete = (std::max)(t.scrape_complete, complete); - incomplete = (std::max)(t.scrape_incomplete, incomplete); - downloaded = (std::max)(t.scrape_downloaded, downloaded); + for (auto const& aep : t.endpoints) + { + complete = (std::max)(aep.scrape_complete, complete); + incomplete = (std::max)(aep.scrape_incomplete, incomplete); + downloaded = (std::max)(aep.scrape_downloaded, downloaded); + } } if ((complete >= 0 && int(m_complete) != complete) @@ -2937,7 +3010,8 @@ namespace libtorrent { // out external IP counter (and pass along the IP of the tracker to know // who to attribute this vote to) if (resp.external_ip != address() && !is_any(tracker_ip)) - m_ses.set_external_address(resp.external_ip + m_ses.set_external_address(r.outgoing_socket->get_local_endpoint() + , resp.external_ip , aux::session_interface::source_tracker, tracker_ip); time_point32 now = aux::time_now32(); @@ -2948,30 +3022,34 @@ namespace libtorrent { announce_entry* ae = find_tracker(r.url); if (ae) { - if (resp.incomplete >= 0) ae->scrape_incomplete = resp.incomplete; - if (resp.complete >= 0) ae->scrape_complete = resp.complete; - if (resp.downloaded >= 0) ae->scrape_downloaded = resp.downloaded; - if (!ae->start_sent && r.event == tracker_request::started) - ae->start_sent = true; - if (!ae->complete_sent && r.event == tracker_request::completed) - ae->complete_sent = true; - ae->verified = true; - ae->updating = false; - ae->fails = 0; - ae->next_announce = now + interval; - ae->min_announce = now + resp.min_interval; - int tracker_index = int(ae - &m_trackers[0]); - m_last_working_tracker = std::int8_t(prioritize_tracker(tracker_index)); - - if ((!resp.trackerid.empty()) && (ae->trackerid != resp.trackerid)) + announce_endpoint* aep = ae->find_endpoint(r.outgoing_socket); + if (aep) { - ae->trackerid = resp.trackerid; - if (m_ses.alerts().should_post()) - m_ses.alerts().emplace_alert(get_handle() - , r.url, resp.trackerid); - } + if (resp.incomplete >= 0) aep->scrape_incomplete = resp.incomplete; + if (resp.complete >= 0) aep->scrape_complete = resp.complete; + if (resp.downloaded >= 0) aep->scrape_downloaded = resp.downloaded; + if (!aep->start_sent && r.event == tracker_request::started) + aep->start_sent = true; + if (!aep->complete_sent && r.event == tracker_request::completed) + aep->complete_sent = true; + ae->verified = true; + aep->next_announce = now + interval; + aep->min_announce = now + resp.min_interval; + aep->updating = false; + aep->fails = 0; + int tracker_index = int(ae - &m_trackers[0]); + m_last_working_tracker = std::int8_t(prioritize_tracker(tracker_index)); - update_scrape_state(); + if ((!resp.trackerid.empty()) && (ae->trackerid != resp.trackerid)) + { + ae->trackerid = resp.trackerid; + if (m_ses.alerts().should_post()) + m_ses.alerts().emplace_alert(get_handle() + , r.url, resp.trackerid); + } + + update_scrape_state(); + } } update_tracker_timer(now); @@ -3017,6 +3095,8 @@ namespace libtorrent { } #endif } +#else + TORRENT_UNUSED(tracker_ips); #endif // for each of the peers we got from the tracker @@ -3095,54 +3175,6 @@ namespace libtorrent { , r.url); } - // we're listening on an interface type that was not used - // when talking to the tracker. If there is a matching interface - // type in the tracker IP list, make another tracker request - // using that interface - // in order to avoid triggering this case over and over, don't - // do it if the bind IP for the tracker request that just completed - // matches one of the listen interfaces, since that means this - // announce was the second one - - // TODO: 3 instead of announcing once per IP version, announce once per - // listen interface (i.e. m_listen_sockets) - if (((!is_any(m_ses.get_ipv6_interface().address()) && tracker_ip.is_v4()) - || (!is_any(m_ses.get_ipv4_interface().address()) && tracker_ip.is_v6())) - && r.bind_ip != m_ses.get_ipv4_interface().address() - && r.bind_ip != m_ses.get_ipv6_interface().address()) - { - auto i = std::find_if(tracker_ips.begin(), tracker_ips.end() - , [&] (address const& a) { return a.is_v4() != tracker_ip.is_v4(); }); - if (i != tracker_ips.end()) - { - // the tracker did resolve to a different type of address, so announce - // to that as well - - // TODO 3: there's a bug when removing a torrent or shutting down the session, - // where the second announce is skipped (in this case, the one to the IPv6 - // name). This should be fixed by generalizing the tracker list structure to - // separate the IPv6 and IPv4 addresses as conceptually separate trackers, - // and they should be announced to in parallel - - tracker_request req = r; - - req.private_torrent = m_torrent_file->priv(); - - // tell the tracker to bind to the opposite protocol type - req.bind_ip = tracker_ip.is_v4() - ? m_ses.get_ipv6_interface().address() - : m_ses.get_ipv4_interface().address(); -#ifndef TORRENT_DISABLE_LOGGING - if (should_log()) - { - debug_log("announce again using %s as the bind interface" - , print_address(req.bind_ip).c_str()); - } -#endif - m_ses.queue_tracker_request(req, shared_from_this()); - } - } - do_connect_boost(); state_updated(); @@ -3252,9 +3284,12 @@ namespace libtorrent { { for (auto& e : m_trackers) { - e.next_announce = std::max(time_point_cast(t) - , e.min_announce) + seconds(1); - e.triggered_manually = true; + for (auto& aep : e.endpoints) + { + aep.next_announce = std::max(time_point_cast(t) + , aep.min_announce) + seconds(1); + aep.triggered_manually = true; + } } } else @@ -3262,9 +3297,12 @@ namespace libtorrent { if (tracker_idx < 0 || tracker_idx >= int(m_trackers.size())) return; announce_entry& e = m_trackers[tracker_idx]; - e.next_announce = std::max(time_point_cast(t) - , e.min_announce) + seconds32(1); - e.triggered_manually = true; + for (auto& aep : e.endpoints) + { + aep.next_announce = std::max(time_point_cast(t) + , aep.min_announce) + seconds32(1); + aep.triggered_manually = true; + } } update_tracker_timer(aux::time_now32()); } @@ -5061,8 +5099,10 @@ namespace libtorrent { m_last_working_tracker = -1; for (auto& t : m_trackers) { + t.endpoints.clear(); if (t.source == 0) t.source = announce_entry::source_client; - t.complete_sent = is_seed(); + for (auto& aep : t.endpoints) + aep.complete_sent = is_seed(); } if (settings().get_bool(settings_pack::prefer_udp_trackers)) @@ -5115,6 +5155,7 @@ namespace libtorrent { { return lhs.tier < rhs.tier; }); if (k - m_trackers.begin() < m_last_working_tracker) ++m_last_working_tracker; k = m_trackers.insert(k, url); + k->endpoints.clear(); if (k->source == 0) k->source = announce_entry::source_client; if (!m_paused && !m_trackers.empty()) announce_with_tracker(); return true; @@ -7220,9 +7261,12 @@ namespace libtorrent { time_point32 const now = aux::time_now32(); for (auto& t : m_trackers) { - if (t.complete_sent) continue; - t.next_announce = now; - t.min_announce = now; + for (auto& aep : t.endpoints) + { + if (aep.complete_sent) continue; + aep.next_announce = now; + aep.min_announce = now; + } } announce_with_tracker(); } @@ -7327,7 +7371,8 @@ namespace libtorrent { else { for (auto& t : m_trackers) - t.complete_sent = true; + for (auto& aep : t.endpoints) + aep.complete_sent = true; if (m_state != torrent_status::finished && m_state != torrent_status::seeding) @@ -8555,6 +8600,21 @@ namespace libtorrent { do_connect_boost(); } + namespace + { + struct timer_state + { + explicit timer_state(aux::session_listen_socket* s) + : socket(s) {} + + aux::session_listen_socket* socket; + + int tier = INT_MAX; + bool found_working = false; + bool done = false; + }; + } + void torrent::update_tracker_timer(time_point32 const now) { TORRENT_ASSERT(is_single_thread()); @@ -8567,12 +8627,24 @@ namespace libtorrent { } time_point32 next_announce = time_point32::max(); - int tier = INT_MAX; - bool found_working = false; + std::vector listen_socket_states; for (auto const& t : m_trackers) { + for (auto const& aep : t.endpoints) + { + auto aep_state_iter = std::find_if(listen_socket_states.begin(), listen_socket_states.end() + , [&](timer_state const& s) { return s.socket == aep.socket; }); + if (aep_state_iter == listen_socket_states.end()) + { + listen_socket_states.emplace_back(aep.socket); + aep_state_iter = listen_socket_states.end() - 1; + } + timer_state& state = *aep_state_iter; + + if (state.done) continue; + #ifndef TORRENT_DISABLE_LOGGING if (should_log()) { @@ -8581,35 +8653,42 @@ namespace libtorrent { " found: %d i->tier: %d tier: %d" " working: %d fails: %d limit: %d upd: %d ]" , t.url.c_str(), settings().get_bool(settings_pack::announce_to_all_tiers) - , settings().get_bool(settings_pack::announce_to_all_trackers), found_working - , t.tier, tier, t.is_working(), t.fails, t.fail_limit - , t.updating); + , settings().get_bool(settings_pack::announce_to_all_trackers), state.found_working + , t.tier, state.tier, aep.is_working(), aep.fails, t.fail_limit + , aep.updating); } #endif + if (settings().get_bool(settings_pack::announce_to_all_tiers) - && found_working - && t.tier <= tier - && tier != INT_MAX) + && state.found_working + && t.tier <= state.tier + && state.tier != INT_MAX) continue; - if (t.tier > tier && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; - if (t.is_working()) { tier = t.tier; found_working = false; } - if (t.fails >= t.fail_limit && t.fail_limit != 0) continue; - if (t.updating) + if (t.tier > state.tier && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; + if (aep.is_working()) { state.tier = t.tier; state.found_working = false; } + if (aep.fails >= t.fail_limit && t.fail_limit != 0) continue; + if (aep.updating) { - found_working = true; + state.found_working = true; } else { - time_point32 next_tracker_announce = std::max(t.next_announce, t.min_announce); + time_point32 next_tracker_announce = std::max(aep.next_announce, aep.min_announce); if (next_tracker_announce < next_announce - && (!found_working || t.is_working())) + && (!state.found_working || aep.is_working())) next_announce = next_tracker_announce; } - if (t.is_working()) found_working = true; - if (found_working - && !settings().get_bool(settings_pack::announce_to_all_trackers) - && !settings().get_bool(settings_pack::announce_to_all_tiers)) break; + if (aep.is_working()) state.found_working = true; + if (state.found_working + && !settings().get_bool(settings_pack::announce_to_all_trackers) + && !settings().get_bool(settings_pack::announce_to_all_tiers)) + state.done = true; + } + + if (std::all_of(listen_socket_states.begin(), listen_socket_states.end() + , [](timer_state const& s) { return s.done; })) + break; } if (next_announce <= now) next_announce = now; @@ -8711,8 +8790,11 @@ namespace libtorrent { time_point32 const now = aux::time_now32(); for (auto& t : m_trackers) { - t.next_announce = now; - t.min_announce = now; + for (auto& aep : t.endpoints) + { + aep.next_announce = now; + aep.min_announce = now; + } } announce_with_tracker(tracker_request::stopped); } @@ -10496,7 +10578,8 @@ namespace { { for (auto const& t : m_trackers) { - if (t.updating) continue; + if (std::any_of(t.endpoints.begin(), t.endpoints.end() + , [](announce_endpoint const& aep) { return aep.updating; })) continue; if (!t.verified) continue; st->current_tracker = t.url; break; @@ -10667,16 +10750,24 @@ namespace { { // announce request announce_entry* ae = find_tracker(r.url); + int fails = 0; if (ae) { - ae->failed(settings().get_int(settings_pack::tracker_backoff) - , retry_interval); - ae->last_error = ec; - ae->message = msg; - int const tracker_index = int(ae - m_trackers.data()); + for (auto& aep : ae->endpoints) + { + if (aep.socket != r.outgoing_socket) continue; + aep.failed(settings().get_int(settings_pack::tracker_backoff) + , retry_interval); + aep.last_error = ec; + aep.message = msg; #ifndef TORRENT_DISABLE_LOGGING - debug_log("*** increment tracker fail count [%d]", ae->fails); + debug_log("*** increment tracker fail count [%d]", aep.fails); #endif + break; + } + + int const tracker_index = int(ae - m_trackers.data()); + // never talk to this tracker again if (response_code == 410) ae->fail_limit = 1; @@ -10686,7 +10777,7 @@ namespace { || r.triggered_manually) { m_ses.alerts().emplace_alert(get_handle() - , ae ? ae->fails : 0, response_code, r.url, ec, msg); + , fails, response_code, r.url, ec, msg); } } else diff --git a/src/tracker_manager.cpp b/src/tracker_manager.cpp index cf30e4515..5088f3ffc 100644 --- a/src/tracker_manager.cpp +++ b/src/tracker_manager.cpp @@ -171,6 +171,11 @@ namespace libtorrent { close(); } + address tracker_connection::bind_interface() const + { + return m_req.outgoing_socket->get_local_endpoint().address(); + } + void tracker_connection::sent_bytes(int bytes) { m_man.sent_bytes(bytes); @@ -370,19 +375,21 @@ namespace libtorrent { return p->on_receive_hostname(hostname, buf); } - void tracker_manager::send_hostname(char const* hostname, int const port + void tracker_manager::send_hostname(aux::session_listen_socket* sock + , char const* hostname, int const port , span p, error_code& ec, int const flags) { TORRENT_ASSERT(is_single_thread()); - m_send_fun_hostname(hostname, port, p, ec, flags); + m_send_fun_hostname(sock, hostname, port, p, ec, flags); } - void tracker_manager::send(udp::endpoint const& ep + void tracker_manager::send(aux::session_listen_socket* sock + , udp::endpoint const& ep , span p , error_code& ec, int const flags) { TORRENT_ASSERT(is_single_thread()); - m_send_fun(ep, p, ec, flags); + m_send_fun(sock, ep, p, ec, flags); } void tracker_manager::abort_all_requests(bool all) diff --git a/src/udp_tracker_connection.cpp b/src/udp_tracker_connection.cpp index 0970c4886..ab188ce9e 100644 --- a/src/udp_tracker_connection.cpp +++ b/src/udp_tracker_connection.cpp @@ -198,9 +198,25 @@ namespace libtorrent { // look for an address that has the same kind as the one // we're listening on. To make sure the tracker get our // correct listening address. - + bool is_v4 = bind_interface().is_v4(); +#if TORRENT_USE_IPV6 + auto scope = is_v4 ? 0 : bind_interface().to_v6().scope_id(); +#endif for (auto const& addr : addresses) + { + if (addr.is_v4() != is_v4) continue; +#if TORRENT_USE_IPV6 + if (addr.is_v6() && addr.to_v6().scope_id() != scope) + continue; +#endif m_endpoints.push_back(tcp::endpoint(addr, std::uint16_t(port))); + } + + if (m_endpoints.empty()) + { + fail(error_code(boost::asio::error::address_family_not_supported)); + return; + } if (tracker_req().filter) { @@ -501,13 +517,13 @@ namespace libtorrent { error_code ec; if (!m_hostname.empty()) { - m_man.send_hostname(m_hostname.c_str() + m_man.send_hostname(bind_socket(), m_hostname.c_str() , m_target.port(), buf, ec , udp_socket::tracker_connection); } else { - m_man.send(m_target, buf, ec + m_man.send(bind_socket(), m_target, buf, ec , udp_socket::tracker_connection); } @@ -565,12 +581,12 @@ namespace libtorrent { error_code ec; if (!m_hostname.empty()) { - m_man.send_hostname(m_hostname.c_str(), m_target.port() + m_man.send_hostname(bind_socket(), m_hostname.c_str(), m_target.port() , buf, ec, udp_socket::tracker_connection); } else { - m_man.send(m_target, buf, ec + m_man.send(bind_socket(), m_target, buf, ec , udp_socket::tracker_connection); } m_state = action_t::scrape; @@ -781,13 +797,13 @@ namespace libtorrent { if (!m_hostname.empty()) { - m_man.send_hostname(m_hostname.c_str() + m_man.send_hostname(bind_socket(), m_hostname.c_str() , m_target.port(), {buf, std::size_t(sizeof(buf) - out.size())}, ec , udp_socket::tracker_connection); } else { - m_man.send(m_target, {buf, std::size_t(sizeof(buf) - out.size())}, ec + m_man.send(bind_socket(), m_target, {buf, std::size_t(sizeof(buf) - out.size())}, ec , udp_socket::tracker_connection); } m_state = action_t::announce; diff --git a/test/test_http_connection.cpp b/test/test_http_connection.cpp index 4d89de8c1..559252afc 100644 --- a/test/test_http_connection.cpp +++ b/test/test_http_connection.cpp @@ -120,7 +120,7 @@ void run_test(std::string const& url, int size, int status, int connected std::shared_ptr h = std::make_shared(ios , res, &::http_handler, true, 1024*1024, &::http_connect_handler); - h->get(url, seconds(1), 0, &ps, 5, "test/user-agent", address_v4::any() + h->get(url, seconds(1), 0, &ps, 5, "test/user-agent", address(address_v4::any()) , 0, auth); ios.reset(); error_code e; diff --git a/test/test_listen_socket.cpp b/test/test_listen_socket.cpp index 82ca8680f..344f894f2 100644 --- a/test/test_listen_socket.cpp +++ b/test/test_listen_socket.cpp @@ -203,3 +203,68 @@ TORRENT_TEST(partition_listen_sockets_op_ports) TEST_EQUAL(eps.size(), 2); } +TORRENT_TEST(expand_unspecified) +{ + std::vector ifs; + std::vector eps; + + ip_interface ipi; + ipi.interface_address = address::from_string("127.0.0.1"); + strcpy(ipi.name, "lo"); + ifs.push_back(ipi); + ipi.interface_address = address::from_string("192.168.1.2"); + strcpy(ipi.name, "eth0"); + ifs.push_back(ipi); + ipi.interface_address = address::from_string("24.172.48.90"); + strcpy(ipi.name, "eth1"); + ifs.push_back(ipi); + ipi.interface_address = address::from_string("::1"); + strcpy(ipi.name, "lo"); + ifs.push_back(ipi); + ipi.interface_address = address::from_string("fe80::d250:99ff:fe0c:9b74"); + strcpy(ipi.name, "eth0"); + ifs.push_back(ipi); + ipi.interface_address = address::from_string("2601:646:c600:a3:d250:99ff:fe0c:9b74"); + ifs.push_back(ipi); + + aux::listen_endpoint_t v4_nossl(address::from_string("0.0.0.0"), 6881, std::string(), false); + aux::listen_endpoint_t v4_ssl(address::from_string("0.0.0.0"), 6882, std::string(), true); + aux::listen_endpoint_t v6_unsp_nossl(address::from_string("::"), 6883, std::string(), false); + aux::listen_endpoint_t v6_unsp_ssl(address::from_string("::"), 6884, std::string(), true); + aux::listen_endpoint_t v6_ll_nossl(address::from_string("fe80::d250:99ff:fe0c:9b74"), 6883, std::string(), false); + aux::listen_endpoint_t v6_ll_ssl(address::from_string("fe80::d250:99ff:fe0c:9b74"), 6884, std::string(), true); + aux::listen_endpoint_t v6_g_nossl(address::from_string("2601:646:c600:a3:d250:99ff:fe0c:9b74"), 6883, std::string(), false); + aux::listen_endpoint_t v6_g_ssl(address::from_string("2601:646:c600:a3:d250:99ff:fe0c:9b74"), 6884, std::string(), true); + + eps.push_back(v4_nossl); + eps.push_back(v4_ssl); + eps.push_back(v6_unsp_nossl); + eps.push_back(v6_unsp_ssl); + + aux::expand_unspecified_address(ifs, eps); + + TEST_EQUAL(eps.size(), 6); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v4_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_ssl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_unsp_nossl) == 0); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_unsp_ssl) == 0); + + // test that a user configured endpoint is not duplicated + aux::listen_endpoint_t v6_g_nossl_dev(address::from_string("2601:646:c600:a3:d250:99ff:fe0c:9b74"), 6883, "eth0", false); + + eps.clear(); + eps.push_back(v6_unsp_nossl); + eps.push_back(v6_g_nossl_dev); + + aux::expand_unspecified_address(ifs, eps); + + TEST_EQUAL(eps.size(), 2); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_ll_nossl) == 1); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_nossl) == 0); + TEST_CHECK(std::count(eps.begin(), eps.end(), v6_g_nossl_dev) == 1); +} + diff --git a/test/test_primitives.cpp b/test/test_primitives.cpp index f7296b21f..3cb89f20b 100644 --- a/test/test_primitives.cpp +++ b/test/test_primitives.cpp @@ -50,16 +50,13 @@ TORRENT_TEST(primitives) // make sure the retry interval keeps growing // on failing announces announce_entry ae("dummy"); + ae.endpoints.emplace_back(nullptr); int last = 0; auto const tracker_backoff = 250; for (int i = 0; i < 10; ++i) { - ae.failed(tracker_backoff, seconds32(5)); -#ifndef TORRENT_NO_DEPRECATE - int const delay = ae.next_announce_in(); -#else - int const delay = static_cast(total_seconds(ae.next_announce - clock_type::now())); -#endif + ae.endpoints.front().failed(tracker_backoff, seconds32(5)); + int const delay = static_cast(total_seconds(ae.endpoints.front().next_announce - clock_type::now())); TEST_CHECK(delay > last); last = delay; std::printf("%d, ", delay); diff --git a/test/test_tracker.cpp b/test/test_tracker.cpp index 24591859c..501e2c10e 100644 --- a/test/test_tracker.cpp +++ b/test/test_tracker.cpp @@ -679,8 +679,9 @@ void test_stop_tracker_timeout(bool nostop) std::snprintf(tracker_url, sizeof(tracker_url), "http://127.0.0.1:%d/announce", port); announce_entry ae{tracker_url}; // trick to avoid use of tracker immediately - ae.next_announce = aux::time_now32() + seconds32(1); - ae.min_announce = aux::time_now32() + seconds32(1); + // FIXME + //ae.next_announce = aux::time_now32() + seconds32(1); + //ae.min_announce = aux::time_now32() + seconds32(1); h.add_tracker(ae); s.remove_torrent(h);