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]
