PR #25396 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25396
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25396.patch

Affects tcp, udp and libcurl


>From 7173ede0875bf13d8a33403503fe79dfbb5fa94c Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 23 Oct 2026 18:54:06 +0200
Subject: [PATCH 1/4] avformat/network: add ff_socket_set_tos()

Signed-off-by: Niklas Haas <[email protected]>
---
 libavformat/network.c | 9 +++++++++
 libavformat/network.h | 7 +++++++
 2 files changed, 16 insertions(+)

diff --git a/libavformat/network.c b/libavformat/network.c
index 2b4c78cfb12..8fa0a1b84df 100644
--- a/libavformat/network.c
+++ b/libavformat/network.c
@@ -212,6 +212,15 @@ int ff_socket(int af, int type, int proto, void *logctx)
     return fd;
 }
 
+int ff_socket_set_tos(int fd, int family, int tos)
+{
+#if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS)
+    if (family == AF_INET6)
+        return setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) ? 
ff_neterrno() : 0;
+#endif
+    return setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) ? 
ff_neterrno() : 0;
+}
+
 int ff_listen(int fd, const struct sockaddr *addr,
               socklen_t addrlen, void *logctx)
 {
diff --git a/libavformat/network.h b/libavformat/network.h
index 7c72c902aa3..195e198cd14 100644
--- a/libavformat/network.h
+++ b/libavformat/network.h
@@ -308,6 +308,13 @@ int ff_http_match_no_proxy(const char *no_proxy, const 
char *hostname);
 
 int ff_socket(int domain, int type, int protocol, void *logctx);
 
+/**
+ * Set the IPv4 ToS or IPv6 traffic class byte of outgoing packets.
+ *
+ * @return 0 on success, AVERROR on failure.
+ */
+int ff_socket_set_tos(int fd, int family, int tos);
+
 void ff_log_net_error(void *ctx, int level, const char* prefix);
 
 /**
-- 
2.54.0


>From 44a05ae253f4e5e27805487357ec263dee03a50e Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 23 Oct 2026 18:54:20 +0200
Subject: [PATCH 2/4] avformat/tcp: add ip_tos option

Signed-off-by: Niklas Haas <[email protected]>
---
 doc/protocols.texi | 4 ++++
 libavformat/tcp.c  | 6 ++++++
 2 files changed, 10 insertions(+)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index aaee2292e9d..00e77f708cb 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -2279,6 +2279,10 @@ Set TCP_NODELAY to disable Nagle's algorithm. Default 
value is 0.
 
 @item tcp_mss=@var{bytes}
 Set maximum segment size for outgoing TCP packets, expressed in bytes.
+
+@item ip_tos=@var{tos}
+Set the IPv4 type of service or IPv6 traffic class byte for outgoing packets,
+e.g. @code{0x88} for DSCP AF41.
 @end table
 
 The following example shows how to setup a listening TCP connection
diff --git a/libavformat/tcp.c b/libavformat/tcp.c
index 09dc8a96796..1c1d1741063 100644
--- a/libavformat/tcp.c
+++ b/libavformat/tcp.c
@@ -46,6 +46,7 @@ typedef struct TCPContext {
     int send_buffer_size;
     int tcp_nodelay;
     int tcp_keepalive;
+    int ip_tos;
 #if !HAVE_WINSOCK2_H
     int tcp_mss;
 #endif /* !HAVE_WINSOCK2_H */
@@ -64,6 +65,7 @@ static const AVOption options[] = {
     { "recv_buffer_size", "Socket receive buffer size (in bytes)",             
OFFSET(recv_buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, 
.flags = D|E },
     { "tcp_nodelay", "Use TCP_NODELAY to disable nagle's algorithm",           
OFFSET(tcp_nodelay), AV_OPT_TYPE_BOOL, { .i64 = 0 },             0, 1, .flags = 
D|E },
     { "tcp_keepalive", "Use TCP keepalive to detect dead connections and keep 
long-lived connections active.",           OFFSET(tcp_keepalive), 
AV_OPT_TYPE_BOOL, { .i64 = 0 },             0, 1, .flags = D|E },
+    { "ip_tos",           "IP ToS / traffic class for outgoing packets",       
OFFSET(ip_tos),      AV_OPT_TYPE_INT, { .i64 = -1 },         -1, 255,     
.flags = D|E },
 #if !HAVE_WINSOCK2_H
     { "tcp_mss",     "Maximum segment size for outgoing TCP packets",          
OFFSET(tcp_mss),     AV_OPT_TYPE_INT, { .i64 = -1 },         -1, INT_MAX, 
.flags = D|E },
 #endif /* !HAVE_WINSOCK2_H */
@@ -133,6 +135,10 @@ static int customize_fd(void *ctx, int fd, int family)
             ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(SO_KEEPALIVE)");
         }
     }
+    if (s->ip_tos >= 0) {
+        if (ff_socket_set_tos(fd, family, s->ip_tos) < 0)
+            ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(IP_TOS)");
+    }
 
 #if !HAVE_WINSOCK2_H
     if (s->tcp_mss > 0) {
-- 
2.54.0


>From ed605fecdefab6d65d4a7e3a66f67c9dd0b47849 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 23 Oct 2026 18:54:32 +0200
Subject: [PATCH 3/4] avformat/udp: add ip_tos option

Mirroring tcp.c. Supports IPv6 traffic class, unlike dscp.

Signed-off-by: Niklas Haas <[email protected]>
---
 doc/protocols.texi | 4 ++++
 libavformat/udp.c  | 7 +++++++
 2 files changed, 11 insertions(+)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index 00e77f708cb..6bb96ad1cb4 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -2507,6 +2507,10 @@ Set the time to live value (for multicast only).
 @item dscp=@var{dscp}
 Set the 6-bit DSCP field for outgoing packets.
 
+@item ip_tos=@var{tos}
+Set the IPv4 type of service or IPv6 traffic class byte for outgoing packets,
+e.g. @code{0x88} for DSCP AF41. Takes precedence over @option{dscp}.
+
 @item connect=@var{1|0}
 Initialize the UDP socket with @code{connect()}. In this case, the
 destination address can't be changed with ff_udp_set_remote_url later.
diff --git a/libavformat/udp.c b/libavformat/udp.c
index a21b04e89c3..7192aca77ee 100644
--- a/libavformat/udp.c
+++ b/libavformat/udp.c
@@ -115,6 +115,7 @@ typedef struct UDPContext {
     char *localaddr;
     int timeout;
     int dscp;
+    int ip_tos;
     struct sockaddr_storage local_addr_storage;
     char *sources;
     char *block;
@@ -140,6 +141,7 @@ static const AVOption options[] = {
     { "broadcast", "explicitly allow or disallow broadcast destination",   
OFFSET(is_broadcast),   AV_OPT_TYPE_BOOL,   { .i64 = 0  },     0, 1,       E },
     { "ttl",            "Time to live (multicast only)",                   
OFFSET(ttl),            AV_OPT_TYPE_INT,    { .i64 = 16 },     0, 255,     E },
     { "dscp",           "DSCP class for outgoing packets",                 
OFFSET(dscp),           AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, 63,      E },
+    { "ip_tos",         "IP ToS / traffic class for outgoing packets",     
OFFSET(ip_tos),         AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, 255,     E },
     { "connect",        "set if connect() should be called on socket",     
OFFSET(is_connected),   AV_OPT_TYPE_BOOL,   { .i64 =  0 },     0, 1,       
.flags = D|E },
     { "fifo_size",      "set the UDP circular buffer size (in 188-byte 
packets)", OFFSET(circular_buffer_size), AV_OPT_TYPE_INT, {.i64 = HAVE_THREADS 
? 7*4096 : 0}, 0, INT_MAX, D },
     { "overrun_nonfatal", "survive in case of UDP receiving circular buffer 
overrun", OFFSET(overrun_nonfatal), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1,    D },
@@ -809,6 +811,11 @@ static int udp_open(URLContext *h, const char *uri, int 
flags)
         }
     }
 
+    if (s->ip_tos >= 0) {
+        if ((ret = ff_socket_set_tos(udp_fd, my_addr.ss_family, s->ip_tos)) < 
0)
+            goto fail;
+    }
+
     /* If multicast, try binding the multicast address first, to avoid
      * receiving UDP packets from other sources aimed at the same UDP
      * port. This fails on windows. This makes sending to the same address
-- 
2.54.0


>From 070b2ecf81a542e958e96dc6068d3e8ef5e5e4b1 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 23 Oct 2026 18:58:36 +0200
Subject: [PATCH 4/4] avformat/libcurl: add ip_tos option

Mirroring the libavformat native socket option.

The other socket options are not very useful to set, or need to be set
differently. libcurl already enables TCP_NODELAY, and already enables
keepalive when -multiple_requests is set. With HTTP/3, it creates UDP
sockets, so the TCP-specific options also fail.

Signed-off-by: Niklas Haas <[email protected]>
---
 doc/protocols.texi    |  4 ++++
 libavformat/libcurl.c | 19 +++++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index 6bb96ad1cb4..4c3e21861b5 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -1183,6 +1183,10 @@ Client certificate and private key files for mutual TLS.
 @item connect_timeout
 Connection timeout in seconds. @code{0} (the default) uses libcurl's own 
timeout.
 
+@item ip_tos
+Set the IPv4 type of service or IPv6 traffic class byte for outgoing packets,
+e.g. @code{0x88} for DSCP AF41.
+
 @item max_redirects
 Maximum number of redirects to follow. Default is @code{16}.
 
diff --git a/libavformat/libcurl.c b/libavformat/libcurl.c
index fc487602db9..a290c804c98 100644
--- a/libavformat/libcurl.c
+++ b/libavformat/libcurl.c
@@ -131,6 +131,7 @@ struct CurlContext {
     int             tls_verify;
     int             seekable_opt;
     int             connect_timeout;
+    int             ip_tos;
     int             max_redirects;
     int             multiple_requests;
     int             http_version;
@@ -1125,6 +1126,19 @@ void ff_curl_loop_free(struct CurlLoop **loop)
 
 static int libcurl_close(URLContext *h);
 
+static int sockopt_callback(void *userdata, curl_socket_t fd, curlsocktype 
purpose)
+{
+    CurlContext *c = userdata;
+    struct sockaddr_storage addr;
+    socklen_t addr_len = sizeof(addr);
+
+    if (getsockname(fd, (struct sockaddr *)&addr, &addr_len))
+        addr.ss_family = AF_UNSPEC;
+    if (ff_socket_set_tos(fd, addr.ss_family, c->ip_tos) < 0)
+        ff_log_net_error(c->h, AV_LOG_WARNING, "setsockopt(IP_TOS)");
+    return CURL_SOCKOPT_OK;
+}
+
 static int debug_callback(CURL *easy, curl_infotype type, char *data,
                           size_t size, void *userdata)
 {
@@ -1293,6 +1307,10 @@ static int setup_curl(CurlContext *c)
     if (c->connect_timeout > 0)
         CURL_SETOPT(CURLOPT_CONNECTTIMEOUT_MS,
                     (long)c->connect_timeout * 1000);
+    if (c->ip_tos >= 0) {
+        CURL_SETOPT(CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
+        CURL_SETOPT(CURLOPT_SOCKOPTDATA, c);
+    }
 
     if (c->user_agent && c->user_agent[0])
         CURL_SETOPT(CURLOPT_USERAGENT, c->user_agent);
@@ -1733,6 +1751,7 @@ static const AVOption options[] = {
     { "cert_file", "client certificate file", OFFSET(cert_file), 
AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
     { "key_file", "client private key file", OFFSET(key_file), 
AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E },
     { "connect_timeout", "connection timeout in seconds (0 = libcurl 
default)", OFFSET(connect_timeout), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX / 
1000, D | E },
+    { "ip_tos", "IP TOS / traffic class for outgoing packets", OFFSET(ip_tos), 
AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 255, D | E },
     { "max_redirects", "maximum number of redirects to follow", 
OFFSET(max_redirects), AV_OPT_TYPE_INT, { .i64 = 16 }, 0, INT_MAX, D },
     { "multiple_requests", "reuse the connection across requests (HTTP 
keep-alive)", OFFSET(multiple_requests), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, 
D | E },
     { "max_retries", "maximum number of retries after a recoverable error", 
OFFSET(max_retries), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, D },
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to