Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support dscp classes in socket iptos #2594

Merged
merged 9 commits into from Feb 10, 2022
10 changes: 8 additions & 2 deletions cli/cli.cc
Expand Up @@ -76,7 +76,13 @@ static auto constexpr Options = std::array<tr_option, 19>{
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, nullptr },
{ 'M', "no-portmap", "Disable portmapping", "M", false, nullptr },
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
{ 't', "tos", "Peer socket TOS (0 to 255, default=" TR_DEFAULT_PEER_SOCKET_TOS_STR ")", "t", true, "<tos>" },
{ 't',
"tos",
"Peer socket DSCP / ToS setting (number, or a DSCP string, e.g. 'af11' or 'cs0', default=" TR_DEFAULT_PEER_SOCKET_TOS_STR
")",
"t",
true,
"<dscp-or-tos>" },
{ 'u', "uplimit", "Set max upload speed in " SPEED_K_STR, "u", true, "<speed>" },
{ 'U', "no-uplimit", "Don't limit the upload speed", "U", false, nullptr },
{ 'v', "verify", "Verify the specified torrent", "v", false, nullptr },
Expand Down Expand Up @@ -434,7 +440,7 @@ static int parseCommandLine(tr_variant* d, int argc, char const** argv)
break;

case 't':
tr_variantDictAddInt(d, TR_KEY_peer_socket_tos, atoi(my_optarg));
tr_variantDictAddStr(d, TR_KEY_peer_socket_tos, my_optarg);
break;

case 'u':
Expand Down
40 changes: 33 additions & 7 deletions cli/transmission-cli.1
Expand Up @@ -32,7 +32,7 @@
.Op Fl h
.Op Fl m | M
.Op Fl p Ar port
.Op Fl t Ar tos
.Op Fl -tos Ar tos
.Op Fl u Ar number | Fl U
.Op Fl v
.Op Fl w Ar directory
Expand Down Expand Up @@ -74,17 +74,43 @@ Enable portmapping via NAT-PMP or UPnP
Disable portmapping
.It Fl p -port Ar port
Set the port to listen for incoming peers. (Default: 51413)
.It Fl t -tos Ar tos
Set the peer socket TOS for local router-based traffic shaping.
Valid values are
.It Fl -tos Ar tos
.nf
Use a ToS or DSCP name to set the peer socket service type for
local router-based traffic shaping. Valid values are a decimal
value 0-255, or any of these DSCP strings:
.fi
.Pp
.Dq af11 ,
.Dq af12 ,
.Dq af13 ,
.Dq af21 ,
.Dq af22 ,
.Dq af23 ,
.Dq af31 ,
.Dq af32 ,
.Dq af33 ,
.Dq af41 ,
.Dq af42 ,
.Dq af43 ,
.Dq cs0 ,
.Dq cs1 ,
.Dq cs2 ,
.Dq cs3 ,
.Dq cs4 ,
.Dq cs5 ,
.Dq cs6 ,
.Dq cs7 ,
.Dq ef ,
.Dq le

These ToS keys are deprecated and will be removed in the future:
.Dq default ,
ckerr marked this conversation as resolved.
Show resolved Hide resolved
.Dq lowcost ,
.Dq throughput ,
.Dq lowdelay,
.Dq reliability ,
.Dq lowdelay ,
.Dq throughput
.Pp
a decimal value 0-255 or a hexidecimal value 0x00-0xff)
.It Fl u -uplimit Ar number
Set the maximum upload speed in KB/s
.It Fl U -no-uplimit
Expand Down
65 changes: 64 additions & 1 deletion libtransmission/net.cc
@@ -1,4 +1,4 @@
// This file Copyright © 2010 Transmission authors and contributors.
// This file Copyright © 2010-2022 Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.

Expand All @@ -9,6 +9,7 @@
#include <cstring>
#include <ctime>
#include <string_view>
#include <utility> // std::pair

#include <sys/types.h>

Expand Down Expand Up @@ -149,8 +150,70 @@ int tr_address_compare(tr_address const* a, tr_address const* b)
* TCP sockets
**********************************************************************/

// RFCs 2474, 3246, 4594 & 8622
// Service class names are defined in RFC 4594, RFC 5865, and RFC 8622.
// Not all platforms have these IPTOS_ definitions, so hardcode them here
static auto constexpr IpTosNames = std::array<std::pair<int, std::string_view>, 28>{ {
{ 0x00, "cs0" }, // IPTOS_CLASS_CS0
{ 0x04, "le" },
{ 0x20, "cs1" }, // IPTOS_CLASS_CS1
{ 0x28, "af11" }, // IPTOS_DSCP_AF11
{ 0x30, "af12" }, // IPTOS_DSCP_AF12
{ 0x38, "af13" }, // IPTOS_DSCP_AF13
{ 0x40, "cs2" }, // IPTOS_CLASS_CS2
{ 0x48, "af21" }, // IPTOS_DSCP_AF21
{ 0x50, "af22" }, // IPTOS_DSCP_AF22
{ 0x58, "af23" }, // IPTOS_DSCP_AF23
{ 0x60, "cs3" }, // IPTOS_CLASS_CS3
{ 0x68, "af31" }, // IPTOS_DSCP_AF31
{ 0x70, "af32" }, // IPTOS_DSCP_AF32
{ 0x78, "af33" }, // IPTOS_DSCP_AF33
{ 0x80, "cs4" }, // IPTOS_CLASS_CS4
{ 0x88, "af41" }, // IPTOS_DSCP_AF41
{ 0x90, "af42" }, // IPTOS_DSCP_AF42
{ 0x98, "af43" }, // IPTOS_DSCP_AF43
{ 0xa0, "cs5" }, // IPTOS_CLASS_CS5
{ 0xb8, "ef" }, // IPTOS_DSCP_EF
{ 0xc0, "cs6" }, // IPTOS_CLASS_CS6
{ 0xe0, "cs7" }, // IPTOS_CLASS_CS7

// <netinet/ip.h> lists these TOS names as deprecated,
// but keep them defined here for backward compatibility
{ 0x00, "routine" }, // IPTOS_PREC_ROUTINE
{ 0x02, "lowcost" }, // IPTOS_LOWCOST
{ 0x02, "mincost" }, // IPTOS_MINCOST
{ 0x04, "reliable" }, // IPTOS_RELIABILITY
{ 0x08, "throughput" }, // IPTOS_THROUGHPUT
{ 0x10, "lowdelay" }, // IPTOS_LOWDELAY
} };

std::string tr_netTosToName(int tos)
{
auto const test = [tos](auto const& pair)
{
return pair.first == tos;
};
auto const it = std::find_if(std::begin(IpTosNames), std::end(IpTosNames), test);
return it == std::end(IpTosNames) ? std::to_string(tos) : std::string{ it->second };
}

std::optional<int> tr_netTosFromName(std::string_view name)
{
auto const test = [&name](auto const& pair)
{
return pair.second == name;
};
auto const it = std::find_if(std::begin(IpTosNames), std::end(IpTosNames), test);
return it != std::end(IpTosNames) ? it->first : tr_parseNum<int>(name);
}

void tr_netSetTOS([[maybe_unused]] tr_socket_t s, [[maybe_unused]] int tos, tr_address_type type)
{
if (s == TR_BAD_SOCKET)
{
return;
}

if (type == TR_AF_INET)
{
#if defined(IP_TOS) && !defined(_WIN32)
Expand Down
24 changes: 13 additions & 11 deletions libtransmission/net.h
Expand Up @@ -9,6 +9,8 @@
#endif

#include <cstddef> // size_t
#include <optional>
#include <string>
#include <string_view>

#ifdef _WIN32
Expand Down Expand Up @@ -104,23 +106,12 @@ constexpr bool tr_address_is_valid(tr_address const* a)
* Sockets
**********************************************************************/

/* https://en.wikipedia.org/wiki/Differentiated_services#Class_Selector */
enum
{
TR_IPTOS_LOWCOST = 0x38, /* AF13: low prio, high drop */
TR_IPTOS_LOWDELAY = 0x70, /* AF32: high prio, mid drop */
TR_IPTOS_THRUPUT = 0x20, /* CS1: low prio, undef drop */
TR_IPTOS_RELIABLE = 0x28 /* AF11: low prio, low drop */
};

struct tr_session;

tr_socket_t tr_netBindTCP(tr_address const* addr, tr_port port, bool suppressMsgs);

tr_socket_t tr_netAccept(tr_session* session, tr_socket_t bound, tr_address* setme_addr, tr_port* setme_port);

void tr_netSetTOS(tr_socket_t s, int tos, tr_address_type type);

void tr_netSetCongestionControl(tr_socket_t s, char const* algorithm);

void tr_netClose(tr_session* session, tr_socket_t s);
Expand All @@ -129,6 +120,17 @@ void tr_netCloseSocket(tr_socket_t fd);

bool tr_net_hasIPv6(tr_port);

/// TOS / DSCP

// get a string of one of <netinet/ip.h>'s IPTOS_ values, e.g. "cs0"
std::string tr_netTosToName(int tos);

// get the number that corresponds to the specified IPTOS_ name, e.g. "cs0" returns 0x00
std::optional<int> tr_netTosFromName(std::string_view name);

// set the IPTOS_ value for the specified socket
void tr_netSetTOS(tr_socket_t sock, int tos, tr_address_type type);

/**
* @brief get a human-representable string representing the network error.
* @param err an errno on Unix/Linux and an WSAError on win32)
Expand Down
4 changes: 2 additions & 2 deletions libtransmission/peer-io.cc
Expand Up @@ -624,7 +624,7 @@ static tr_peerIo* tr_peerIoNew(

if (socket.type == TR_PEER_SOCKET_TYPE_TCP)
{
tr_netSetTOS(socket.handle.tcp, session->peerSocketTos(), addr->type);
session->setSocketTOS(socket.handle.tcp, addr->type);
maybeSetCongestionAlgorithm(socket.handle.tcp, session->peerCongestionAlgorithm());
}

Expand Down Expand Up @@ -979,7 +979,7 @@ int tr_peerIoReconnect(tr_peerIo* io)
io->event_write = event_new(session->event_base, io->socket.handle.tcp, EV_WRITE, event_write_cb, io);

event_enable(io, pendingEvents);
tr_netSetTOS(io->socket.handle.tcp, session->peerSocketTos(), io->addr.type);
io->session->setSocketTOS(io->socket.handle.tcp, io->addr.type);
maybeSetCongestionAlgorithm(io->socket.handle.tcp, session->peerCongestionAlgorithm());

return 0;
Expand Down
69 changes: 10 additions & 59 deletions libtransmission/session.cc
Expand Up @@ -250,62 +250,6 @@ tr_address const* tr_sessionGetPublicAddress(tr_session const* session, int tr_a
****
***/

static int parseTos(std::string_view tos_in)
{
auto tos = tr_strlower(tr_strvStrip(tos_in));

if (tos == ""sv || tos == "default"sv)
{
return 0;
}

if (tos == "lowcost"sv || tos == "mincost"sv)
{
return TR_IPTOS_LOWCOST;
}

if (tos == "throughput"sv)
{
return TR_IPTOS_THRUPUT;
}

if (tos == "reliability"sv)
{
return TR_IPTOS_RELIABLE;
}

if (tos == "lowdelay"sv)
{
return TR_IPTOS_LOWDELAY;
}

return std::stoi(tos);
}

static std::string format_tos(int value)
{
switch (value)
{
case 0:
return "default";

case TR_IPTOS_LOWCOST:
return "lowcost";

case TR_IPTOS_THRUPUT:
return "throughput";

case TR_IPTOS_RELIABLE:
return "reliability";

case TR_IPTOS_LOWDELAY:
return "lowdelay";

default:
return std::to_string(value);
}
}

#ifdef TR_LIGHTWEIGHT
#define TR_DEFAULT_ENCRYPTION TR_CLEAR_PREFERRED
#else
Expand Down Expand Up @@ -416,7 +360,7 @@ void tr_sessionGetSettings(tr_session const* s, tr_variant* d)
tr_variantDictAddBool(d, TR_KEY_peer_port_random_on_start, s->isPortRandom);
tr_variantDictAddInt(d, TR_KEY_peer_port_random_low, s->randomPortLow);
tr_variantDictAddInt(d, TR_KEY_peer_port_random_high, s->randomPortHigh);
tr_variantDictAddStr(d, TR_KEY_peer_socket_tos, format_tos(s->peerSocketTos()));
tr_variantDictAddStr(d, TR_KEY_peer_socket_tos, tr_netTosToName(s->peer_socket_tos_));
tr_variantDictAddStr(d, TR_KEY_peer_congestion_algorithm, s->peerCongestionAlgorithm());
tr_variantDictAddBool(d, TR_KEY_pex_enabled, s->isPexEnabled);
tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, tr_sessionIsPortForwardingEnabled(s));
Expand Down Expand Up @@ -827,9 +771,16 @@ static void sessionSetImpl(void* vdata)
tr_sessionSetEncryption(session, tr_encryption_mode(i));
}

if (tr_variantDictFindStrView(settings, TR_KEY_peer_socket_tos, &sv))
if (tr_variantDictFindInt(settings, TR_KEY_peer_socket_tos, &i))
{
session->setPeerSocketTos(parseTos(sv));
session->peer_socket_tos_ = i;
}
else if (tr_variantDictFindStrView(settings, TR_KEY_peer_socket_tos, &sv))
{
if (auto ip_tos = tr_netTosFromName(sv); ip_tos)
{
session->peer_socket_tos_ = *ip_tos;
}
}

sv = ""sv;
Expand Down
16 changes: 7 additions & 9 deletions libtransmission/session.h
Expand Up @@ -231,14 +231,9 @@ struct tr_session
peer_congestion_algorithm_ = algorithm;
}

int peerSocketTos() const
void setSocketTOS(tr_socket_t sock, tr_address_type type)
{
return peer_socket_tos_;
}

void setPeerSocketTos(int tos)
{
peer_socket_tos_ = tos;
tr_netSetTOS(sock, peer_socket_tos_, type);
}

public:
Expand Down Expand Up @@ -358,6 +353,11 @@ struct tr_session

std::unique_ptr<tr_rpc_server> rpc_server_;

// One of <netinet/ip.h>'s IPTOS_ values.
// See tr_netTos*() in libtransmission/net.h for more info
// Only session.cc should use this.
int peer_socket_tos_ = *tr_netTosFromName(TR_DEFAULT_PEER_SOCKET_TOS_STR);

private:
static std::recursive_mutex session_mutex_;

Expand All @@ -367,8 +367,6 @@ struct tr_session
std::string incomplete_dir_;
std::string peer_congestion_algorithm_;

int peer_socket_tos_ = 0;

std::array<bool, TR_SCRIPT_N_TYPES> scripts_enabled_;
bool blocklist_enabled_ = false;
bool incomplete_dir_enabled_ = false;
Expand Down
18 changes: 2 additions & 16 deletions libtransmission/tr-udp.cc
Expand Up @@ -107,22 +107,8 @@ void tr_udpSetSocketBuffers(tr_session* session)

void tr_udpSetSocketTOS(tr_session* session)
{
auto const tos = session->peerSocketTos();

if (tos != 0)
{
return;
}

if (session->udp_socket != TR_BAD_SOCKET)
{
tr_netSetTOS(session->udp_socket, tos, TR_AF_INET);
}

if (session->udp6_socket != TR_BAD_SOCKET)
{
tr_netSetTOS(session->udp6_socket, tos, TR_AF_INET6);
}
session->setSocketTOS(session->udp_socket, TR_AF_INET);
session->setSocketTOS(session->udp6_socket, TR_AF_INET6);
}

/* BEP-32 has a rather nice explanation of why we need to bind to one
Expand Down
2 changes: 1 addition & 1 deletion libtransmission/transmission.h
Expand Up @@ -126,7 +126,7 @@ char const* tr_getDefaultDownloadDir(void);
#define TR_DEFAULT_RPC_PORT 9091
#define TR_DEFAULT_RPC_URL_STR "/transmission/"
#define TR_DEFAULT_PEER_PORT_STR "51413"
#define TR_DEFAULT_PEER_SOCKET_TOS_STR "default"
#define TR_DEFAULT_PEER_SOCKET_TOS_STR "cs1"
#define TR_DEFAULT_PEER_LIMIT_GLOBAL_STR "200"
#define TR_DEFAULT_PEER_LIMIT_TORRENT_STR "50"
#define TR_DEFAULT_PEER_LIMIT_TORRENT 50
Expand Down