diff --git a/CMakeLists.txt b/CMakeLists.txt index 5580eea99..944602bae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -740,7 +740,11 @@ if(developer-options) ENABLED TORRENT_PROFILE_CALLS=1) endif() -generate_and_install_pkg_config_file(torrent-rasterbar libtorrent-rasterbar) +# There is little to none support for using pkg-config with MSVC and most users won't bother with it. +# However, msys is a linux-like platform on Windows that do support/prefer using pkg-config. +if (NOT MSVC) + generate_and_install_pkg_config_file(torrent-rasterbar libtorrent-rasterbar) +endif() include(CheckCXXCompilerFlag) diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 14d841551..53d76d545 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -47,6 +47,10 @@ POSSIBILITY OF SUCH DAMAGE. #include #endif +#ifdef TORRENT_UTP_LOG_ENABLE +#include "libtorrent/utp_stream.hpp" +#endif + #include "libtorrent/torrent_info.hpp" #include "libtorrent/announce_entry.hpp" #include "libtorrent/entry.hpp" @@ -1022,8 +1026,13 @@ CLIENT OPTIONS previous command line options, so be sure to specify this first -G Add torrents in seed-mode (i.e. assume all pieces are present and check hashes on-demand) - -O print session stats counters to the log - + -O print session stats counters to the log)" +#ifdef TORRENT_UTP_LOG_ENABLE +R"( + -q Enable uTP transport-level verbose logging +)" +#endif +R"( LIBTORRENT SETTINGS --= set the libtorrent setting to @@ -1190,6 +1199,11 @@ example alert_masks: case 'G': seed_mode = true; --i; break; case 's': save_path = make_absolute_path(arg); break; case 'O': stats_enabled = true; --i; break; +#ifdef TORRENT_UTP_LOG_ENABLE + case 'q': + libtorrent::set_utp_stream_logging(true); + break; +#endif case 'U': torrent_upload_limit = atoi(arg) * 1000; break; case 'D': torrent_download_limit = atoi(arg) * 1000; break; case 'm': monitor_dir = make_absolute_path(arg); break; diff --git a/include/libtorrent/packet_buffer.hpp b/include/libtorrent/packet_buffer.hpp index f00583e4f..8549f0c9a 100644 --- a/include/libtorrent/packet_buffer.hpp +++ b/include/libtorrent/packet_buffer.hpp @@ -78,8 +78,9 @@ namespace libtorrent { packet_ptr insert(index_type idx, packet_ptr value); - int size() const - { return m_size; } + int size() const { return m_size; } + + bool empty() const { return m_size == 0; } std::uint32_t capacity() const { return m_capacity; } diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp index 9eb509db2..28f90f15b 100644 --- a/include/libtorrent/settings_pack.hpp +++ b/include/libtorrent/settings_pack.hpp @@ -1659,6 +1659,11 @@ namespace libtorrent { // systems. close_file_interval, + // When uTP experiences packet loss, it will reduce the congestion + // window, and not reduce it again for this many milliseconds, even if + // experiencing another lost packet. + utp_cwnd_reduce_timer, + // the max number of web seeds to have connected per torrent at any // given time. max_web_seed_connections, diff --git a/include/libtorrent/utp_socket_manager.hpp b/include/libtorrent/utp_socket_manager.hpp index 849598ac4..09883448e 100644 --- a/include/libtorrent/utp_socket_manager.hpp +++ b/include/libtorrent/utp_socket_manager.hpp @@ -107,6 +107,7 @@ namespace libtorrent { int connect_timeout() const { return m_sett.get_int(settings_pack::utp_connect_timeout); } int min_timeout() const { return m_sett.get_int(settings_pack::utp_min_timeout); } int loss_multiplier() const { return m_sett.get_int(settings_pack::utp_loss_multiplier); } + int cwnd_reduce_timer() const { return m_sett.get_int(settings_pack::utp_cwnd_reduce_timer); } std::pair mtu_for_dest(address const& addr); int num_sockets() const { return int(m_utp_sockets.size()); } diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp index 9907422d5..450654e49 100644 --- a/simulation/test_tracker.cpp +++ b/simulation/test_tracker.cpp @@ -285,7 +285,7 @@ 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_v4::from_string("123.0.0.2")); if (ipv6) result.push_back(address_v6::from_string("ff::dead:beef")); return duration_cast(chrono::milliseconds(100)); @@ -323,7 +323,7 @@ void test_ipv6_support(char const* listen_interfaces sim_config network_cfg; sim::simulation sim{network_cfg}; - sim::asio::io_service web_server_v4(sim, address_v4::from_string("10.0.0.2")); + sim::asio::io_service web_server_v4(sim, address_v4::from_string("123.0.0.2")); sim::asio::io_service web_server_v6(sim, address_v6::from_string("ff::dead:beef")); // listen on port 8080 @@ -370,7 +370,7 @@ void test_ipv6_support(char const* listen_interfaces for (int i = 0; i < num_interfaces; i++) { char ep[30]; - std::snprintf(ep, sizeof(ep), "10.0.0.%d", i + 1); + std::snprintf(ep, sizeof(ep), "123.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)); @@ -428,7 +428,7 @@ void test_udpv6_support(char const* listen_interfaces sim_config network_cfg; sim::simulation sim{network_cfg}; - sim::asio::io_service web_server_v4(sim, address_v4::from_string("10.0.0.2")); + sim::asio::io_service web_server_v4(sim, address_v4::from_string("123.0.0.2")); sim::asio::io_service web_server_v6(sim, address_v6::from_string("ff::dead:beef")); int v4_announces = 0; @@ -442,7 +442,7 @@ void test_udpv6_support(char const* listen_interfaces for (int i = 0; i < num_interfaces; i++) { char ep[30]; - std::snprintf(ep, sizeof(ep), "10.0.0.%d", i + 1); + std::snprintf(ep, sizeof(ep), "123.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)); @@ -555,7 +555,7 @@ TORRENT_TEST(ipv6_support_bind_v6_any) TORRENT_TEST(ipv6_support_bind_v4) { - test_ipv6_support("10.0.0.3:6881", 2, 0); + test_ipv6_support("123.0.0.3:6881", 2, 0); } TORRENT_TEST(ipv6_support_bind_v6) @@ -570,12 +570,12 @@ TORRENT_TEST(ipv6_support_bind_v6_3interfaces) TORRENT_TEST(ipv6_support_bind_v4_v6) { - test_ipv6_support("10.0.0.3:6881,[ffff::1337:1]:6881", 2, 2); + test_ipv6_support("123.0.0.3:6881,[ffff::1337:1]:6881", 2, 2); } TORRENT_TEST(ipv6_support_bind_v6_v4) { - test_ipv6_support("[ffff::1337:1]:6881,10.0.0.3:6881", 2, 2); + test_ipv6_support("[ffff::1337:1]:6881,123.0.0.3:6881", 2, 2); } // this runs a simulation of a torrent with tracker(s), making sure the request @@ -584,7 +584,7 @@ TORRENT_TEST(ipv6_support_bind_v6_v4) // trackers to the torrent. It's expected to return the number of seconds to // wait until test2 is called. // The Announce function is called on http requests. Test1 is run on the session -// 5 seconds after startup. The tracker is running at 10.0.0.2 (or tracker.com) +// 5 seconds after startup. The tracker is running at 123.0.0.2 (or tracker.com) // port 8080. template void tracker_test(Setup setup, Announce a, Test1 test1, Test2 test2 @@ -594,7 +594,7 @@ void tracker_test(Setup setup, Announce a, Test1 test1, Test2 test2 sim_config network_cfg; 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_ios(sim, address_v4::from_string("123.0.0.2")); sim::asio::io_service tracker_ios6(sim, address_v6::from_string("ff::dead:beef")); // listen on port 8080 @@ -606,7 +606,7 @@ void tracker_test(Setup setup, Announce a, Test1 test1, Test2 test2 lt::session_proxy zombie; - asio::io_service ios(sim, { address_v4::from_string("10.0.0.3") + asio::io_service ios(sim, { address_v4::from_string("123.0.0.3") , address_v6::from_string("ffff::1337") }); lt::settings_pack sett = settings(); std::unique_ptr ses(new lt::session(sett, ios)); @@ -976,7 +976,7 @@ TORRENT_TEST(tracker_ipv6_argument) { settings_pack pack; pack.set_bool(settings_pack::anonymous_mode, false); - pack.set_str(settings_pack::listen_interfaces, "10.0.0.3:0,[ffff::1337]:0"); + pack.set_str(settings_pack::listen_interfaces, "123.0.0.3:0,[ffff::1337]:0"); ses.apply_settings(pack); p.ti = make_torrent(true); return 60; @@ -1000,7 +1000,7 @@ TORRENT_TEST(tracker_ipv6_argument) std::string::size_type const pos = req.find("&ipv4="); TEST_CHECK(pos != std::string::npos || stop_event); got_ipv4 |= pos != std::string::npos; - TEST_EQUAL(req.substr(pos + 6, req.substr(pos + 6).find_first_of('&')), "10.0.0.3"); + TEST_EQUAL(req.substr(pos + 6, req.substr(pos + 6).find_first_of('&')), "123.0.0.3"); } return sim::send_response(200, "OK", 11) + "d5:peers0:e"; } diff --git a/src/settings_pack.cpp b/src/settings_pack.cpp index 41cbc1622..57da4d509 100644 --- a/src/settings_pack.cpp +++ b/src/settings_pack.cpp @@ -341,6 +341,7 @@ constexpr int CLOSE_FILE_INTERVAL = 0; SET(urlseed_max_request_bytes, 16 * 1024 * 1024, nullptr), SET(web_seed_name_lookup_retry, 1800, nullptr), SET(close_file_interval, CLOSE_FILE_INTERVAL, nullptr), + SET(utp_cwnd_reduce_timer, 100, nullptr), SET(max_web_seed_connections, 3, nullptr), SET(resolver_cache_timeout, 1200, &session_impl::update_resolver_cache_timeout), }}); diff --git a/src/torrent.cpp b/src/torrent.cpp index 20140bdd5..066c0cf13 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -2832,9 +2832,15 @@ bool is_downloading_state(int const st) tcp::endpoint const ep = s.get_local_endpoint(); if (is_any(ep.address())) return; if (is_v6(ep)) - req.ipv6.push_back(ep.address().to_v6()); + { + if (!is_local(ep.address()) && !is_loopback(ep.address())) + req.ipv6.push_back(ep.address().to_v6()); + } else - req.ipv4.push_back(ep.address().to_v4()); + { + if (!is_local(ep.address()) && !is_loopback(ep.address())) + req.ipv4.push_back(ep.address().to_v4()); + } }); } diff --git a/src/utp_stream.cpp b/src/utp_stream.cpp index 746b4c7a8..d2b6f0041 100644 --- a/src/utp_stream.cpp +++ b/src/utp_stream.cpp @@ -289,7 +289,7 @@ struct utp_socket_impl bool consume_incoming_data( utp_header const* ph, std::uint8_t const* ptr, int payload_size, time_point now); void update_mtu_limits(); - void experienced_loss(std::uint32_t seq_nr); + void experienced_loss(std::uint32_t seq_nr, time_point now); void set_state(int s); @@ -394,6 +394,10 @@ struct utp_socket_impl // the last time we stepped the timestamp history time_point m_last_history_step = clock_type::now(); + // the next time we allow a lost packet to halve cwnd. We only do this once every + // 100 ms + time_point m_next_loss; + // the max number of bytes in-flight. This is a fixed point // value, to get the true number of bytes, shift right 16 bits // the value is always >= 0, but the calculations performed on @@ -1533,6 +1537,8 @@ std::pair utp_socket_impl::parse_sack(std::uint16_t const pa if (ack_nr == m_seq_nr) break; } + if (m_outbuf.empty()) m_duplicate_acks = 0; + // now, scan the bits in reverse, and count the number of ACKed packets. Only // lost packets followed by 'dup_ack_limit' packets may be resent // start with the sequence number represented by the last bit in the SACK @@ -1587,19 +1593,20 @@ std::pair utp_socket_impl::parse_sack(std::uint16_t const pa packet* p = m_outbuf.at(pkt_seq); UTP_LOGV("%8p: Packet %d lost. (fast_resend_seq_nr:%d trigger fast-resend)\n" , static_cast(this), pkt_seq, m_fast_resend_seq_nr); - if (p) + if (!p) continue; + + // don't cut cwnd if the packet we lost was the MTU probe + // the logic to handle a lost MTU probe is in resend_packet() + if (cut_cwnd && (pkt_seq != m_mtu_seq || m_mtu_seq == 0)) { - if (resend_packet(p, true)) - { - m_duplicate_acks = 0; - m_fast_resend_seq_nr = (pkt_seq + 1) & ACK_MASK; - } + experienced_loss(pkt_seq, now); + cut_cwnd = false; } - if (cut_cwnd) + if (resend_packet(p, true)) { - experienced_loss(pkt_seq); - cut_cwnd = false; + m_duplicate_acks = 0; + m_fast_resend_seq_nr = (pkt_seq + 1) & ACK_MASK; } } @@ -2251,7 +2258,7 @@ bool utp_socket_impl::resend_packet(packet* p, bool fast_resend) return !m_stalled; } -void utp_socket_impl::experienced_loss(std::uint32_t const seq_nr) +void utp_socket_impl::experienced_loss(std::uint32_t const seq_nr, time_point const now) { INVARIANT_CHECK; @@ -2271,11 +2278,17 @@ void utp_socket_impl::experienced_loss(std::uint32_t const seq_nr) // same packet again, ignore it. if (compare_less_wrap(seq_nr, m_loss_seq_nr + 1, ACK_MASK)) return; + // don't reduce cwnd more than once every 100ms + if (m_next_loss >= now) return; + + m_next_loss = now + milliseconds(m_sm.cwnd_reduce_timer()); + // cut window size in 2 m_cwnd = std::max(m_cwnd * m_sm.loss_multiplier() / 100 , std::int64_t(m_mtu) * (1 << 16)); m_loss_seq_nr = m_seq_nr; - UTP_LOGV("%8p: Lost packet %d caused cwnd cut\n", static_cast(this), seq_nr); + UTP_LOGV("%8p: Lost packet %d caused cwnd cut. m_loss_seq_nr:%d\n" + , static_cast(this), seq_nr, m_seq_nr); // if we happen to be in slow-start mode, we need to leave it // note that we set ssthres to the window size _after_ reducing it. Next slow @@ -2875,6 +2888,8 @@ bool utp_socket_impl::incoming_packet(span buf ++m_duplicate_acks; } + TORRENT_ASSERT_VAL(m_outbuf.size() > 0 || m_duplicate_acks == 0, m_duplicate_acks); + std::uint32_t min_rtt = std::numeric_limits::max(); TORRENT_ASSERT(m_outbuf.at((m_acked_seq_nr + 1) & ACK_MASK) || ((m_seq_nr - m_acked_seq_nr) & ACK_MASK) <= 1); @@ -2904,6 +2919,7 @@ bool utp_socket_impl::incoming_packet(span buf } maybe_inc_acked_seq_nr(); + if (m_outbuf.empty()) m_duplicate_acks = 0; } // look for extended headers @@ -2969,7 +2985,9 @@ bool utp_socket_impl::incoming_packet(span buf if (p) { - experienced_loss(m_fast_resend_seq_nr); + // don't consider a lost probe as proper loss, it doesn't necessarily + // signal congestion + if (!p->mtu_probe) experienced_loss(m_fast_resend_seq_nr, receive_time); resend_packet(p, true); if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; } @@ -3178,6 +3196,8 @@ bool utp_socket_impl::incoming_packet(span buf if (m_state == UTP_STATE_ERROR_WAIT || m_state == UTP_STATE_DELETE) return true; } + TORRENT_ASSERT(!compare_less_wrap(m_seq_nr, m_acked_seq_nr, ACK_MASK)); + #if TORRENT_UTP_LOG if (sample && acked_bytes && prev_bytes_in_flight) { @@ -3246,7 +3266,7 @@ bool utp_socket_impl::incoming_packet(span buf , packet_timeout() , int(total_milliseconds(m_timeout - receive_time)) , int(total_microseconds(receive_time.time_since_epoch())) - , (m_seq_nr - m_acked_seq_nr) & ACK_MASK + , m_outbuf.size() , m_mtu , their_delay_base , std::uint32_t(m_reply_micro) @@ -3532,7 +3552,20 @@ void utp_socket_impl::tick(time_point const now) if (now > m_timeout) { // TIMEOUT! - // set cwnd to 1 MSS + + bool ignore_loss = false; + + if (((m_acked_seq_nr + 1) & ACK_MASK) == m_mtu_seq + && ((m_seq_nr - 1) & ACK_MASK) == m_mtu_seq + && m_mtu_seq != 0) + { + // we timed out, and the only outstanding packet + // we had was the probe. Assume it was dropped + // because it was too big + m_mtu_ceiling = m_mtu - 1; + update_mtu_limits(); + ignore_loss = true; + } // the close_reason here is a bit of a hack. When it's set, it indicates // that the upper layer intends to close the socket. However, it has been @@ -3541,7 +3574,9 @@ void utp_socket_impl::tick(time_point const now) // other end. This catches that case and let the socket time out. if (m_outbuf.size() || m_close_reason != close_reason_t::none) { - ++m_num_timeouts; + // m_num_timeouts is used to update the connection timeout, and if we + // lose this packet because it's an MTU-probe, don't change the timeout + if (!ignore_loss) ++m_num_timeouts; m_sm.inc_stats_counter(counters::utp_timeout); } @@ -3567,52 +3602,45 @@ void utp_socket_impl::tick(time_point const now) return; } - if (((m_acked_seq_nr + 1) & ACK_MASK) == m_mtu_seq - && ((m_seq_nr - 1) & ACK_MASK) == m_mtu_seq - && m_mtu_seq != 0) + if (!ignore_loss) { - // we timed out, and the only outstanding packet - // we had was the probe. Assume it was dropped - // because it was too big - m_mtu_ceiling = m_mtu - 1; - update_mtu_limits(); + // set cwnd to 1 MSS + if (m_bytes_in_flight == 0 && (m_cwnd >> 16) >= m_mtu) + { + // this is just a timeout because this direction of + // the stream is idle. Don't reset the cwnd, just decay it + m_cwnd = std::max(m_cwnd * 2 / 3, std::int64_t(m_mtu) * (1 << 16)); + } + else + { + // we timed out because a packet was not ACKed or because + // the cwnd was made smaller than one packet + m_cwnd = std::int64_t(m_mtu) * (1 << 16); + } + + TORRENT_ASSERT(m_cwnd >= 0); + + m_timeout = now + milliseconds(packet_timeout()); + + UTP_LOGV("%8p: resetting cwnd:%d\n" + , static_cast(this), int(m_cwnd >> 16)); + + // since we've already timed out now, don't count + // loss that we might detect for packets that just + // timed out + m_loss_seq_nr = m_seq_nr; + + // when we time out, the cwnd is reset to 1 MSS, which means we + // need to ramp it up quickly again. enter slow start mode. This time + // we're very likely to have an ssthres set, which will make us leave + // slow start before inducing more delay or loss. + m_slow_start = true; + UTP_LOGV("%8p: slow_start -> 1\n", static_cast(this)); } - if (m_bytes_in_flight == 0 && (m_cwnd >> 16) >= m_mtu) - { - // this is just a timeout because this direction of - // the stream is idle. Don't reset the cwnd, just decay it - m_cwnd = std::max(m_cwnd * 2 / 3, std::int64_t(m_mtu) * (1 << 16)); - } - else - { - // we timed out because a packet was not ACKed or because - // the cwnd was made smaller than one packet - m_cwnd = std::int64_t(m_mtu) * (1 << 16); - } - - TORRENT_ASSERT(m_cwnd >= 0); - - m_timeout = now + milliseconds(packet_timeout()); - - UTP_LOGV("%8p: resetting cwnd:%d\n" - , static_cast(this), int(m_cwnd >> 16)); - // we dropped all packets, that includes the mtu probe m_mtu_seq = 0; - // since we've already timed out now, don't count - // loss that we might detect for packets that just - // timed out - m_loss_seq_nr = m_seq_nr; - - // when we time out, the cwnd is reset to 1 MSS, which means we - // need to ramp it up quickly again. enter slow start mode. This time - // we're very likely to have an ssthres set, which will make us leave - // slow start before inducing more delay or loss. - m_slow_start = true; - UTP_LOGV("%8p: slow_start -> 1\n", static_cast(this)); - // we need to go one past m_seq_nr to cover the case // where we just sent a SYN packet and then adjusted for // the uTorrent sequence number reuse diff --git a/test/test_settings_pack.cpp b/test/test_settings_pack.cpp index 9a6728c07..278a9c83b 100644 --- a/test/test_settings_pack.cpp +++ b/test/test_settings_pack.cpp @@ -257,6 +257,6 @@ TORRENT_TEST(settings_pack_abi) TEST_EQUAL(settings_pack::max_http_recv_buffer_size, settings_pack::int_type_base + 115); TEST_EQUAL(settings_pack::web_seed_name_lookup_retry, settings_pack::int_type_base + 128); TEST_EQUAL(settings_pack::close_file_interval, settings_pack::int_type_base + 129); - TEST_EQUAL(settings_pack::max_web_seed_connections, settings_pack::int_type_base + 130); - TEST_EQUAL(settings_pack::resolver_cache_timeout, settings_pack::int_type_base + 131); + TEST_EQUAL(settings_pack::max_web_seed_connections, settings_pack::int_type_base + 131); + TEST_EQUAL(settings_pack::resolver_cache_timeout, settings_pack::int_type_base + 132); }