Addition of tcp_keepalive, tcp_keepidle, tcp_keepintvl, and tcp_keepcnt
support to the TCP protocol. Exposeing these options to the HTTP protocol
so they can be used for HTTP(S) connections. Updated documentation.
Tested with: ./configure && make && make fate

Fixes ticket #11671.

Signed-off-by: Practice2001 <[email protected]>
---
 doc/protocols.texi | 26 ++++++++++++++-
 libavformat/http.c | 36 +++++++++++++++++++++
 libavformat/tcp.c  | 80 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 141 insertions(+), 1 deletion(-)

diff --git a/doc/protocols.texi b/doc/protocols.texi
index b74383122a..848f4820e2 100644
--- a/doc/protocols.texi
+++ b/doc/protocols.texi
@@ -989,7 +989,31 @@ Set TCP_NODELAY to disable Nagle's algorithm. Default 
value is 0.
 @item tcp_keepalive=@var{1|0}
 Enable the TCP keepalive mechanism to detect dead peers and help maintain 
long-lived idle connections. Default value is 0.
 
-Only the basic keepalive option (SO_KEEPALIVE) can be enabled or disabled. 
Platform-specific tuning parameters such as TCP_KEEPIDLE, TCP_KEEPINTVL, or 
TCP_KEEPCNT are not configurable and will use the operating system's default 
values.
+The basic keepalive option (SO_KEEPALIVE) can be enabled or disabled. 
Platform-specific tuning parameters such as TCP_KEEPIDLE, TCP_KEEPINTVL, or 
TCP_KEEPCNT are configurable.
+
+@item tcp_keepidle=@var{seconds}
+Set the TCP keepalive idle time (in seconds).
+
+This controls how long the connection must remain idle before the first
+keepalive probe is sent.
+
+Default is 0 (uses system default).
+
+@item tcp_keepintvl=@var{seconds}
+Set the interval (in seconds) between individual TCP keepalive probes.
+
+Default is 0 (uses system default).
+
+@item tcp_keepcnt=@var{count}
+Set the number of unacknowledged keepalive probes that must occur before
+the connection is considered dead.
+
+Default is 0 (uses system default).
+
+@emph{Note:}
+These platform-specific parameters are available on Linux and BSD-derived
+systems. On Windows, only basic keepalive configuration is supported and
+the underlying system will use its own values for probe timing and count.
 
 @end table
 
diff --git a/libavformat/http.c b/libavformat/http.c
index c4e6292a95..1c264d845c 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -146,6 +146,11 @@ typedef struct HTTPContext {
     unsigned int retry_after;
     int reconnect_max_retries;
     int reconnect_delay_total_max;
+    /* TCP keepalive forwarding */
+    int tcp_keepalive;    /* -1 = unset, 0 = off, 1 = on */
+    int tcp_keepidle;     /* seconds, 0 = unset */
+    int tcp_keepintvl;    /* seconds, 0 = unset */
+    int tcp_keepcnt;      /* probe count, 0 = unset */
 } HTTPContext;
 
 #define OFFSET(x) offsetof(HTTPContext, x)
@@ -191,6 +196,10 @@ static const AVOption options[] = {
     { "resource", "The resource requested by a client", OFFSET(resource), 
AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, E },
     { "reply_code", "The http status code to return to a client", 
OFFSET(reply_code), AV_OPT_TYPE_INT, { .i64 = 200}, INT_MIN, 599, E},
     { "short_seek_size", "Threshold to favor readahead over seek.", 
OFFSET(short_seek_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D },
+    { "tcp_keepalive", "Enable SO_KEEPALIVE on underlying TCP socket", 
OFFSET(tcp_keepalive), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, D | E },
+    { "tcp_keepidle", "TCP keepalive idle time (seconds) for underlying TCP 
socket", OFFSET(tcp_keepidle), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D | E 
},
+    { "tcp_keepintvl", "TCP keepalive interval (seconds) for underlying TCP 
socket", OFFSET(tcp_keepintvl), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D | 
E },
+    { "tcp_keepcnt", "TCP keepalive probe count for underlying TCP socket", 
OFFSET(tcp_keepcnt), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, D | E },
     { NULL }
 };
 
@@ -282,6 +291,33 @@ static int http_open_cnx_internal(URLContext *h, 
AVDictionary **options)
 
     ff_url_join(buf, sizeof(buf), lower_proto, NULL, hostname, port, NULL);
 
+
+    /* Forward TCP keepalive options to underlying protocol via options dict.
+    * Prefer using AVDictionary forwarding to modifying the URL (safer). */
+    if (options) {
+        int err = 0;
+        if (s->tcp_keepalive != -1) {
+            err = av_dict_set_int(options, "tcp_keepalive", s->tcp_keepalive ? 
1 : 0, 0);
+            if (err < 0)
+                goto end; /* existing cleanup label in this function */
+        }
+        if (s->tcp_keepidle > 0) {
+            err = av_dict_set_int(options, "tcp_keepidle", s->tcp_keepidle, 0);
+            if (err < 0)
+                goto end;
+        }
+        if (s->tcp_keepintvl > 0) {
+            err = av_dict_set_int(options, "tcp_keepintvl", s->tcp_keepintvl, 
0);
+            if (err < 0)
+                goto end;
+        }
+        if (s->tcp_keepcnt > 0) {
+            err = av_dict_set_int(options, "tcp_keepcnt", s->tcp_keepcnt, 0);
+            if (err < 0)
+                goto end;
+        }
+    }
+
     if (!s->hd) {
         err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE,
                                    &h->interrupt_callback, options,
diff --git a/libavformat/tcp.c b/libavformat/tcp.c
index ce9f69a50b..eb3dd0cc5b 100644
--- a/libavformat/tcp.c
+++ b/libavformat/tcp.c
@@ -46,6 +46,9 @@ typedef struct TCPContext {
     int send_buffer_size;
     int tcp_nodelay;
     int tcp_keepalive;
+    int tcp_keepidle;
+    int tcp_keepintvl;
+    int tcp_keepcnt;
 #if !HAVE_WINSOCK2_H
     int tcp_mss;
 #endif /* !HAVE_WINSOCK2_H */
@@ -54,6 +57,28 @@ typedef struct TCPContext {
 #define OFFSET(x) offsetof(TCPContext, x)
 #define D AV_OPT_FLAG_DECODING_PARAM
 #define E AV_OPT_FLAG_ENCODING_PARAM
+
+/**
+ * @name TCP keepalive tuning options
+ *
+ * These AVOptions extend TCP keepalive controls beyond the basic SO_KEEPALIVE
+ * flag by exposing platform-supported tuning parameters.
+ *
+ * The following options may be set on the "tcp" protocol:
+ *
+ * - @ref tcp_keepalive : Enable or disable SO_KEEPALIVE.
+ * - @ref tcp_keepidle  : Set idle time (seconds) before sending first probe.
+ * - @ref tcp_keepintvl : Set interval (seconds) between keepalive probes.
+ * - @ref tcp_keepcnt   : Set number of failed probes before declaring dead.
+ *
+ * Notes:
+ *  - Linux and BSD systems expose all parameters.
+ *  - Windows only supports enabling keepalive; tuning values are ignored.
+ *
+ * These options improve robustness of long-lived idle connections and help
+ * detect dead peers sooner than system defaults.
+ */
+
 static const AVOption options[] = {
     { "listen",          "Listen for incoming connections",  OFFSET(listen),   
      AV_OPT_TYPE_INT, { .i64 = 0 },     0,       2,       .flags = D|E },
     { "local_port",      "Local port",                                         
OFFSET(local_port),     AV_OPT_TYPE_STRING, { .str = NULL },     0,       0, 
.flags = D|E },
@@ -64,6 +89,9 @@ 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 },
+    { "tcp_keepidle", "TCP keepalive idle time in seconds", 
OFFSET(tcp_keepidle), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .flags = D|E },
+    { "tcp_keepintvl", "TCP keepalive probe interval in seconds", 
OFFSET(tcp_keepintvl), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .flags = D|E 
},
+    { "tcp_keepcnt", "TCP keepalive probe count", OFFSET(tcp_keepcnt), 
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .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 */
@@ -132,6 +160,58 @@ static int customize_fd(void *ctx, int fd, int family)
         if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &s->tcp_keepalive, 
sizeof(s->tcp_keepalive))) {
             ff_log_net_error(ctx, AV_LOG_WARNING, "setsockopt(SO_KEEPALIVE)");
         }
+        /* Attempt to set keepalive tuning if requested */
+        /* POSIX/Linux/BSD style */
+        #if defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE)
+        if (s->tcp_keepidle > 0) {
+# ifdef TCP_KEEPIDLE
+            int idle = s->tcp_keepidle;
+            if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, 
sizeof(idle))) {
+                ff_log_net_error(ctx, AV_LOG_WARNING, 
"setsockopt(TCP_KEEPIDLE)");
+            }
+# elif defined(TCP_KEEPALIVE)
+            int idle = s->tcp_keepidle;
+            if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &idle, 
sizeof(idle))) {
+                ff_log_net_error(ctx, AV_LOG_WARNING, 
"setsockopt(TCP_KEEPALIVE)");
+            }
+# endif
+        }
+        if (s->tcp_keepintvl > 0) {
+# ifdef TCP_KEEPINTVL
+            int kv = s->tcp_keepintvl;
+            if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &kv, sizeof(kv))) {
+                ff_log_net_error(ctx, AV_LOG_WARNING, 
"setsockopt(TCP_KEEPINTVL)");
+            }
+# endif
+        }
+        if (s->tcp_keepcnt > 0) {
+# ifdef TCP_KEEPCNT
+            int kc = s->tcp_keepcnt;
+            if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &kc, sizeof(kc))) {
+                ff_log_net_error(ctx, AV_LOG_WARNING, 
"setsockopt(TCP_KEEPCNT)");
+            }
+# endif
+        }
+        #endif /* defined(TCP_KEEPIDLE) || defined(TCP_KEEPALIVE) */
+
+        /* Windows SIO_KEEPALIVE_VALS via WSAIoctl */
+        #if HAVE_WINSOCK2_H
+        if (s->tcp_keepidle > 0 || s->tcp_keepintvl > 0) {
+# include <mstcpip.h>
+            /* windows expects milliseconds */
+            struct tcp_keepalive vals;
+            DWORD bytes;
+            vals.onoff = 1;
+            vals.keepalivetime = (DWORD)(s->tcp_keepidle > 0 ? 
(DWORD)s->tcp_keepidle * 1000 : 7200000);
+            vals.keepaliveinterval = (DWORD)(s->tcp_keepintvl > 0 ? 
(DWORD)s->tcp_keepintvl * 1000 : 1000);
+            /* WSAIoctl returns SOCKET_ERROR on failure */
+            if (WSAIoctl((SOCKET)fd, SIO_KEEPALIVE_VALS, &vals, sizeof(vals),
+                         NULL, 0, &bytes, NULL, NULL) == SOCKET_ERROR) {
+                ff_log_net_error(ctx, AV_LOG_WARNING, 
"WSAIoctl(SIO_KEEPALIVE_VALS)");
+            }
+        }
+        #endif /* HAVE_WINSOCK2_H */
+     
     }
 
 #if !HAVE_WINSOCK2_H
-- 
2.34.1

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

Reply via email to