diff --git a/docs/Editing-Configuration-Files.md b/docs/Editing-Configuration-Files.md index c56609fdd..3c484334f 100644 --- a/docs/Editing-Configuration-Files.md +++ b/docs/Editing-Configuration-Files.md @@ -78,7 +78,7 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri * **encryption:** Number (0 = Prefer unencrypted connections, 1 = Prefer encrypted connections, 2 = Require encrypted connections; default = 1) [Encryption](https://wiki.vuze.com/w/Message_Stream_Encryption) preference. Encryption may help get around some ISP filtering, but at the cost of slightly higher CPU use. * **lazy-bitfield-enabled:** Boolean (default = true) May help get around some ISP filtering. [Vuze specification](https://wiki.vuze.com/w/Commandline_options#Network_Options). * **lpd-enabled:** Boolean (default = false) Enable [Local Peer Discovery (LPD)](https://en.wikipedia.org/wiki/Local_Peer_Discovery). - * **message-level:** Number (0 = None, 1 = Critical, 2 = Error, 3 = Warn, 4 = Info, 5 = Debug, 6 = Trace, default = 2) Set verbosity of Transmission's log messages. + * **message-level:** Number (0 = None, 1 = Critical, 2 = Error, 3 = Warn, 4 = Info, 5 = Debug, 6 = Trace; default = 2) Set verbosity of Transmission's log messages. * **pex-enabled:** Boolean (default = true) Enable [Peer Exchange (PEX)](https://en.wikipedia.org/wiki/Peer_exchange). * **pidfile:** String Path to file in which daemon PID will be stored (transmission-daemon only) * **prefetch-enabled:** Boolean (default = true). When enabled, Transmission will hint to the OS which piece data it's about to read from disk in order to satisfy requests from peers. On Linux, this is done by passing `POSIX_FADV_WILLNEED` to [posix_fadvise()](https://www.kernel.org/doc/man-pages/online/pages/man2/posix_fadvise.2.html). On macOS, this is done by passing `F_RDADVISE` to [fcntl()](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html). @@ -89,9 +89,10 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri * **script-torrent-done-filename:** String (default = "") Path to script. * **script-torrent-done-seeding-enabled:** Boolean (default = false) Run a script when a torrent is done seeding. Environmental variables are passed in as detailed on the [Scripts](./Scripts.md) page * **script-torrent-done-seeding-filename:** String (default = "") Path to script. - * **tcp-enabled:** Boolean (default = true) Optionally disable TCP connection to other peers. Never disable TCP when you also disable UTP, because then your client would not be able to communicate. Disabling TCP might also break webseeds. Unless you have a good reason, you should not set this to false. + * **tcp-enabled:** Boolean (default = true) Optionally disable TCP connection to other peers. Never disable TCP when you also disable µTP, because then your client would not be able to communicate. Disabling TCP might also break webseeds. Unless you have a good reason, you should not set this to false. * **torrent-added-verify-mode:** String ("fast", "full", default: "fast") Whether newly-added torrents' local data should be fully verified when added, or wait and verify them on-demand later. See [#2626](https://github.com/transmission/transmission/pull/2626) for more discussion. * **utp-enabled:** Boolean (default = true) Enable [Micro Transport Protocol (µTP)](https://en.wikipedia.org/wiki/Micro_Transport_Protocol) + * **preferred-transport:** String ("utp" = Prefer µTP, "tcp" = Prefer TCP; default = "utp") Choose your preferred transport protocol (has no effect if one of them is disabled). #### Peers * **bind-address-ipv4:** String (default = "0.0.0.0") Where to listen for peer connections. When no valid IPv4 address is provided, Transmission will bind to "0.0.0.0". diff --git a/libtransmission/peer-io.cc b/libtransmission/peer-io.cc index e12cf64f4..01aadbf70 100644 --- a/libtransmission/peer-io.cc +++ b/libtransmission/peer-io.cc @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -19,6 +20,8 @@ #include +#include + #include "libtransmission/transmission.h" #include "libtransmission/bandwidth.h" @@ -124,11 +127,12 @@ std::shared_ptr tr_peerIo::new_outgoing( bool is_seed, bool utp) { - auto const& [addr, port] = socket_address; + using preferred_key_t = std::underlying_type_t; + auto const preferred = session->preferred_transport(); TR_ASSERT(!tr_peer_socket::limit_reached(session)); TR_ASSERT(session != nullptr); - TR_ASSERT(addr.is_valid()); + TR_ASSERT(socket_address.is_valid()); TR_ASSERT(utp || session->allowsTCP()); if (!socket_address.is_valid_for_peers()) @@ -137,27 +141,49 @@ std::shared_ptr tr_peerIo::new_outgoing( } auto peer_io = tr_peerIo::create(session, parent, &info_hash, false, is_seed); - + auto const func = small::max_size_map, TR_NUM_PREFERRED_TRANSPORT>{ + { TR_PREFER_UTP, + [&]() + { #ifdef WITH_UTP - if (utp) - { - auto* const sock = utp_create_socket(session->utp_context); - utp_set_userdata(sock, peer_io.get()); - peer_io->set_socket(tr_peer_socket{ socket_address, sock }); + if (utp) + { + auto* const sock = utp_create_socket(session->utp_context); + utp_set_userdata(sock, peer_io.get()); + peer_io->set_socket(tr_peer_socket{ socket_address, sock }); - auto const [ss, sslen] = socket_address.to_sockaddr(); - if (utp_connect(sock, reinterpret_cast(&ss), sslen) == 0) - { - return peer_io; - } - } + auto const [ss, sslen] = socket_address.to_sockaddr(); + if (utp_connect(sock, reinterpret_cast(&ss), sslen) == 0) + { + return true; + } + } #endif + return false; + } }, + { TR_PREFER_TCP, + [&]() + { + if (!peer_io->socket_.is_valid()) + { + if (auto sock = tr_netOpenPeerSocket(session, socket_address, is_seed); sock.is_valid()) + { + peer_io->set_socket(std::move(sock)); + return true; + } + } + return false; + } } + }; - if (!peer_io->socket_.is_valid()) + if (func.at(preferred)()) { - if (auto sock = tr_netOpenPeerSocket(session, socket_address, is_seed); sock.is_valid()) + return peer_io; + } + for (preferred_key_t i = 0U; i < TR_NUM_PREFERRED_TRANSPORT; ++i) + { + if (i != preferred && func.at(i)()) { - peer_io->set_socket(std::move(sock)); return peer_io; } } diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index d65f7234b..624bc9654 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -45,6 +45,14 @@ enum ReadState READ_ERR }; +enum tr_preferred_transport : uint8_t +{ + // More preferred transports goes on top + TR_PREFER_UTP, + TR_PREFER_TCP, + TR_NUM_PREFERRED_TRANSPORT +}; + class tr_peerIo final : public std::enable_shared_from_this { using DH = tr_message_stream_encryption::DH; diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 1a6a10f41..29d016354 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -1349,7 +1349,7 @@ std::vector tr_peerMgrGetPeers(tr_torrent const* tor, uint8_t address_ty for (auto const* const info : infos) { auto const& socket_address = info->listen_socket_address(); - auto const& addr = socket_address.address(); + [[maybe_unused]] auto const& addr = socket_address.address(); TR_ASSERT(addr.is_valid()); TR_ASSERT(addr.type == address_type); diff --git a/libtransmission/quark.cc b/libtransmission/quark.cc index 41bf3f2a7..0629ccc0f 100644 --- a/libtransmission/quark.cc +++ b/libtransmission/quark.cc @@ -16,7 +16,7 @@ using namespace std::literals; namespace { -auto constexpr MyStatic = std::array{ ""sv, +auto constexpr MyStatic = std::array{ ""sv, "activeTorrentCount"sv, "activity-date"sv, "activityDate"sv, @@ -247,6 +247,7 @@ auto constexpr MyStatic = std::array{ ""sv, "port-forwarding-enabled"sv, "port-is-open"sv, "preallocation"sv, + "preferred-transport"sv, "prefetch-enabled"sv, "primary-mime-type"sv, "priorities"sv, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index 1e3b317d9..2fe78c71d 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -252,6 +252,7 @@ enum TR_KEY_port_forwarding_enabled, TR_KEY_port_is_open, TR_KEY_preallocation, + TR_KEY_preferred_transport, TR_KEY_prefetch_enabled, TR_KEY_primary_mime_type, TR_KEY_priorities, diff --git a/libtransmission/session-settings.h b/libtransmission/session-settings.h index 83d0a734b..7a4a68f10 100644 --- a/libtransmission/session-settings.h +++ b/libtransmission/session-settings.h @@ -12,6 +12,7 @@ #include "libtransmission/log.h" // for tr_log_level #include "libtransmission/net.h" // for tr_port, tr_tos_t +#include "libtransmission/peer-io.h" // tr_preferred_transport #include "libtransmission/quark.h" struct tr_variant; @@ -76,6 +77,7 @@ struct tr_variant; V(TR_KEY_umask, umask, tr_mode_t, 022, "") \ V(TR_KEY_upload_slots_per_torrent, upload_slots_per_torrent, size_t, 8U, "") \ V(TR_KEY_utp_enabled, utp_enabled, bool, true, "") \ + V(TR_KEY_preferred_transport, preferred_transport, tr_preferred_transport, TR_PREFER_UTP, "") \ V(TR_KEY_torrent_added_verify_mode, torrent_added_verify_mode, tr_verify_added_mode, TR_VERIFY_ADDED_FAST, "") struct tr_session_settings diff --git a/libtransmission/session.h b/libtransmission/session.h index 648c9f8dd..9f3e59cac 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -783,6 +783,11 @@ public: [[nodiscard]] bool allowsUTP() const noexcept; + [[nodiscard]] constexpr auto preferred_transport() const noexcept + { + return settings_.preferred_transport; + } + [[nodiscard]] constexpr auto allowsPrefetch() const noexcept { return settings_.is_prefetch_enabled; diff --git a/libtransmission/variant-converters.cc b/libtransmission/variant-converters.cc index d2f23b40a..c4ad95a93 100644 --- a/libtransmission/variant-converters.cc +++ b/libtransmission/variant-converters.cc @@ -17,6 +17,7 @@ #include "libtransmission/log.h" // for tr_log_level #include "libtransmission/net.h" // for tr_port +#include "libtransmission/peer-io.h" // tr_preferred_transport #include "libtransmission/utils.h" // for tr_strv_strip(), tr_strlower() #include "libtransmission/variant.h" @@ -52,6 +53,12 @@ auto constexpr VerifyModeKeys = std::array, TR_NUM_PREFERRED_TRANSPORT>{ { + { "utp", TR_PREFER_UTP }, + { "tcp", TR_PREFER_TCP }, + } }; } // namespace namespace libtransmission @@ -260,6 +267,54 @@ tr_variant VariantConverter::save(tr_preallocation_mode c // --- +template<> +std::optional VariantConverter::load(tr_variant const& src) +{ + static constexpr auto Keys = PreferredTransportKeys; + + if (auto const* val = src.get_if(); val != nullptr) + { + auto const needle = tr_strlower(tr_strv_strip(*val)); + + for (auto const& [name, value] : Keys) + { + if (name == needle) + { + return value; + } + } + } + + if (auto const* val = src.get_if(); val != nullptr) + { + for (auto const& [name, value] : Keys) + { + if (value == *val) + { + return value; + } + } + } + + return {}; +} + +template<> +tr_variant VariantConverter::save(tr_preferred_transport const& val) +{ + for (auto const& [key, value] : PreferredTransportKeys) + { + if (value == val) + { + return key; + } + } + + return static_cast(val); +} + +// --- + template<> std::optional VariantConverter::load(tr_variant const& src) { diff --git a/tests/libtransmission/settings-test.cc b/tests/libtransmission/settings-test.cc index b1b902076..a61d530d3 100644 --- a/tests/libtransmission/settings-test.cc +++ b/tests/libtransmission/settings-test.cc @@ -411,3 +411,44 @@ TEST_F(SettingsTest, canSaveVerify) EXPECT_TRUE(tr_variantDictFindStrView(&var, Key, &val)); EXPECT_EQ("full", val); } + +TEST_F(SettingsTest, canLoadPreferredTransport) +{ + static auto constexpr Key = TR_KEY_preferred_transport; + auto constexpr ExpectedValue = TR_PREFER_TCP; + + auto settings = std::make_unique(); + auto const default_value = settings->preferred_transport; + ASSERT_NE(ExpectedValue, default_value); + + auto var = tr_variant{}; + tr_variantInitDict(&var, 1); + tr_variantDictAddInt(&var, Key, ExpectedValue); + settings->load(var); + EXPECT_EQ(ExpectedValue, settings->preferred_transport); + var.clear(); + + settings = std::make_unique(); + tr_variantInitDict(&var, 1); + tr_variantDictAddStrView(&var, Key, "tcp"); + settings->load(var); + EXPECT_EQ(ExpectedValue, settings->preferred_transport); +} + +TEST_F(SettingsTest, canSavePreferredTransport) +{ + static auto constexpr Key = TR_KEY_preferred_transport; + static auto constexpr ExpectedValue = TR_PREFER_TCP; + + auto settings = tr_session_settings{}; + auto const default_value = settings.preferred_transport; + ASSERT_NE(ExpectedValue, default_value); + + auto var = tr_variant{}; + tr_variantInitDict(&var, 100); + settings.preferred_transport = ExpectedValue; + var = settings.settings(); + auto val = std::string_view{}; + EXPECT_TRUE(tr_variantDictFindStrView(&var, Key, &val)); + EXPECT_EQ("tcp", val); +}