tcpkeepalive: distinguish OS versions and use proper time units (#1669)

DragonFly BSD changed the time unit for TCP keep-alive from milliseconds to seconds since v5.8 and Solaris 11.4 added `TCP_KEEPIDLE`, `TCP_KEEPINTVL`, and `TCP_KEEPCNT` with time units in second while Solaris <11.4 still use `TCP_KEEPALIVE_THRESHOLD` and `TCP_KEEPALIVE_ABORT_THRESHOLD` with time units in millisecond.

Currently, we don't differentiate among DragonFly BSD versions but set the keepalive options with seconds, which will result in unexpected behaviors on DragonFlyBSD <5.8. This PR intends to fix the wrong usage of time units of TCP keepalive options on DragonFly BSD <5.8 and consolidate the logic of time units conversion for TCP keepalive across platforms.

In addition, this PR introduces a new custom macro for determining Solaris 11.4. This macro is expected to help us implement some new features for `libuv` using some abilities that only exist on Solaris 11.4 and other mainstream platforms in the future, considering that Oracle developed and released Solaris 11.4 to replenish plenty of features on Solaris that have already been implemented on other UNIX-like OSs but missing from Solaris <11.4, also bring a good deal of new features.
### References

- [Change tcp keepalive options from ms to seconds (DISRUPTIVE)](https://lists.dragonflybsd.org/pipermail/commits/2019-July/719125.html)
- [DragonFly BSD 5.8 release notes](https://www.dragonflybsd.org/release58/)
- [DragonFly TCP](https://man.dragonflybsd.org/?command=tcp&section=4)
- [Solaris 11.3 TCP](https://docs.oracle.com/cd/E86824_01/html/E54777/tcp-7p.html)
- [Solaris 11.4 TCP](https://docs.oracle.com/cd/E88353_01/html/E37851/tcp-4p.html)
- [Solaris 11.4 release notes](https://docs.oracle.com/cd/E37838_01/html/E60973/)

Signed-off-by: Andy Pan <i@andypan.me>
This commit is contained in:
Andy Pan 2024-06-26 12:10:00 +08:00 committed by GitHub
parent 90b9520f3c
commit 96c259f1d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 37 additions and 10 deletions

View File

@ -502,7 +502,7 @@ evutil_make_listen_socket_reuseable_port(evutil_socket_t sock)
#elif (defined(__linux__) || \
defined(_AIX73) || \
(defined(__DragonFly__) && __DragonFly_version >= 300600) || \
(defined(__sun) && defined(SO_FLOW_NAME))) && \
(defined(EVENT__SOLARIS_11_4) && EVENT__SOLARIS_11_4)) && \
defined(SO_REUSEPORT)
int enabled = 1;
/* SO_REUSEPORT on Linux 3.9+ means, "Multiple servers (processes or
@ -519,10 +519,6 @@ evutil_make_listen_socket_reuseable_port(evutil_socket_t sock)
* Solaris 11 supported SO_REUSEPORT, but it's implemented only for
* binding to the same address and port, without load balancing.
* Solaris 11.4 extended SO_REUSEPORT with the capability of load balancing.
*
* Since it's impossible to detect the Solaris 11.4 version via OS macros,
* so we check the presence of the socket option SO_FLOW_NAME that was first
* introduced to Solaris 11.4.
*/
return setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void*)&enabled,
(ev_socklen_t)sizeof(enabled));
@ -3135,6 +3131,15 @@ evutil_free_globals_(void)
evutil_free_sock_err_globals();
}
#if (defined(EVENT__SOLARIS_11_4) && !EVENT__SOLARIS_11_4) || \
(defined(__DragonFly__) && __DragonFly_version < 500702) || \
(defined(_WIN32) && !defined(TCP_KEEPIDLE))
/* DragonFlyBSD <500702, Solaris <11.4, and Windows <10.0.16299
* require millisecond units for TCP keepalive options. */
#define EVENT_KEEPALIVE_FACTOR(x) (x *= 1000)
#else
#define EVENT_KEEPALIVE_FACTOR(x)
#endif
int
evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
{
@ -3164,6 +3169,10 @@ evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
intvl = idle/3;
if (intvl == 0)
intvl = 1;
EVENT_KEEPALIVE_FACTOR(idle);
EVENT_KEEPALIVE_FACTOR(intvl);
/* The three options TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT are not available until
* Windows 10 version 1709, but let's gamble here.
*/
@ -3183,8 +3192,8 @@ evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
#elif defined(SIO_KEEPALIVE_VALS)
struct tcp_keepalive keepalive;
keepalive.onoff = on;
keepalive.keepalivetime = idle * 1000; /* the kernel expects milliseconds */
keepalive.keepaliveinterval = intvl * 1000; /* ditto */
keepalive.keepalivetime = idle;
keepalive.keepaliveinterval = intvl;
/* On Windows Vista and later, the number of keep-alive probes (data retransmissions)
* is set to 10 and cannot be changed.
* On Windows Server 2003, Windows XP, and Windows 2000, the default setting for
@ -3228,6 +3237,8 @@ evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
if (idle > 10*24*60*60)
idle = 10*24*60*60;
EVENT_KEEPALIVE_FACTOR(idle);
/* `TCP_KEEPIDLE`, `TCP_KEEPINTVL`, and `TCP_KEEPCNT` were not available on Solaris
* until version 11.4, but let's gamble here.
*/
@ -3239,6 +3250,7 @@ evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
/* Kernel expects at least 10 seconds. */
if (intvl < 10)
intvl = 10;
EVENT_KEEPALIVE_FACTOR(intvl);
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)))
return -1;
@ -3249,7 +3261,6 @@ evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
/* Fall back to the first implementation of tcp-alive mechanism for older Solaris,
* simulate the tcp-alive mechanism on other platforms via `TCP_KEEPALIVE_THRESHOLD` + `TCP_KEEPALIVE_ABORT_THRESHOLD`.
*/
idle *= 1000; /* kernel expects milliseconds */
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, &idle, sizeof(idle)))
return -1;
@ -3263,13 +3274,13 @@ evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
#else /* !__sun */
#ifdef TCP_KEEPIDLE
idle = timeout;
EVENT_KEEPALIVE_FACTOR(idle);
#ifdef TCP_KEEPIDLE
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)))
return -1;
#elif defined(TCP_KEEPALIVE)
/* Darwin/macOS uses TCP_KEEPALIVE in place of TCP_KEEPIDLE. */
idle = timeout;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &idle, sizeof(idle)))
return -1;
#endif
@ -3282,6 +3293,7 @@ evutil_set_tcp_keepalive(evutil_socket_t fd, int on, int timeout)
intvl = timeout/3;
if (intvl == 0)
intvl = 1;
EVENT_KEEPALIVE_FACTOR(intvl);
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)))
return -1;
#endif

View File

@ -76,6 +76,21 @@ extern "C" {
#include <time.h>
#ifdef __sun
#ifdef SO_FLOW_NAME
/* Since it's impossible to detect the Solaris 11.4 version via OS macros,
* so we check the presence of the socket option SO_FLOW_NAME that was first
* introduced to Solaris 11.4 and define a custom macro for determining 11.4.
*
* Note that this might be a false positive if the code is compiled on a system
* but run on another system with an older version of Solaris.
*/
#define EVENT__SOLARIS_11_4 (1)
#else
#define EVENT__SOLARIS_11_4 (0)
#endif
#endif
/* Some openbsd autoconf versions get the name of this macro wrong. */
#if defined(EVENT__SIZEOF_VOID__) && !defined(EVENT__SIZEOF_VOID_P)
#define EVENT__SIZEOF_VOID_P EVENT__SIZEOF_VOID__