From 8ccffddc91942dcb4a6417c13b46c5ab5d81a5d9 Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Wed, 1 Jul 2020 20:27:10 +0200 Subject: [PATCH] Cygwin: tcp: Support TCP_KEEPIDLE, TCP_KEEPCNT, TCP_KEEPINTVL Use WSAIoctl(SIO_KEEPALIVE_VALS) on older systems. Make sure that keep-alive timeout is equivalent to TCP_KEEPIDLE + TCP_KEEPCNT * TCP_KEEPINTVL on older systems, even with TCP_KEEPCNT being a fixed value on those systems. Signed-off-by: Corinna Vinschen --- winsup/cygwin/fhandler.h | 5 + winsup/cygwin/fhandler_socket_inet.cc | 167 ++++++++++++++++++++++++-- winsup/cygwin/include/netinet/tcp.h | 9 +- winsup/cygwin/wincap.cc | 11 ++ winsup/cygwin/wincap.h | 2 + 5 files changed, 180 insertions(+), 14 deletions(-) diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h index 57f282105..24184dacc 100644 --- a/winsup/cygwin/fhandler.h +++ b/winsup/cygwin/fhandler.h @@ -720,6 +720,11 @@ class fhandler_socket_inet: public fhandler_socket_wsock private: bool oobinline; /* True if option SO_OOBINLINE is set */ bool tcp_fastopen; /* True if TCP_FASTOPEN is set on older systems */ + int tcp_keepidle; /* TCP_KEEPIDLE value in secs on older systems */ + int tcp_keepcnt; /* TCP_KEEPCNT value on older systems */ + int tcp_keepintvl; /* TCP_KEEPINTVL value in secs on older systems */ + + int set_keepalive (int keepidle, int keepcnt, int keepintvl); protected: int af_local_connect () { return 0; } diff --git a/winsup/cygwin/fhandler_socket_inet.cc b/winsup/cygwin/fhandler_socket_inet.cc index 1e837d72c..9bdaece5a 100644 --- a/winsup/cygwin/fhandler_socket_inet.cc +++ b/winsup/cygwin/fhandler_socket_inet.cc @@ -23,6 +23,7 @@ #endif #include #include +#include #include #include #include @@ -692,7 +693,10 @@ fhandler_socket_wsock::set_socket_handle (SOCKET sock, int af, int type, fhandler_socket_inet::fhandler_socket_inet () : fhandler_socket_wsock (), oobinline (false), - tcp_fastopen (false) + tcp_fastopen (false), + tcp_keepidle (7200), /* WinSock default */ + tcp_keepcnt (10), /* WinSock default */ + tcp_keepintvl (1) /* WinSock default */ { } @@ -1572,6 +1576,63 @@ fhandler_socket_wsock::writev (const struct iovec *const iov, const int iovcnt, return send_internal (&wsamsg, 0); } +#define MAX_TCP_KEEPIDLE 32767 +#define MAX_TCP_KEEPCNT 255 +#define MAX_TCP_KEEPINTVL 32767 + +#define FIXED_WSOCK_TCP_KEEPCNT 10 + +int +fhandler_socket_inet::set_keepalive (int keepidle, int keepcnt, int keepintvl) +{ + struct tcp_keepalive tka; + int so_keepalive = 0; + int len = sizeof so_keepalive; + int ret; + DWORD dummy; + + /* Per MSDN, + https://docs.microsoft.com/en-us/windows/win32/winsock/sio-keepalive-vals + the subsequent keep-alive settings in struct tcp_keepalive are only used + if the onoff member is != 0. Request the current state of SO_KEEPALIVE, + then set the keep-alive options with onoff set to 1. On success, if + SO_KEEPALIVE was 0, restore to the original SO_KEEPALIVE setting. Per + the above MSDN doc, the SIO_KEEPALIVE_VALS settings are persistent + across switching SO_KEEPALIVE. */ + ret = ::getsockopt (get_socket (), SOL_SOCKET, SO_KEEPALIVE, + (char *) &so_keepalive, &len); + if (ret == SOCKET_ERROR) + debug_printf ("getsockopt (SO_KEEPALIVE) failed, %u\n", WSAGetLastError ()); + tka.onoff = 1; + tka.keepalivetime = keepidle * MSPERSEC; + /* WinSock TCP_KEEPCNT is fixed. But we still want that the keep-alive + times out after TCP_KEEPIDLE + TCP_KEEPCNT * TCP_KEEPINTVL secs. + To that end, we set keepaliveinterval so that + + keepaliveinterval * FIXED_WSOCK_TCP_KEEPCNT == TCP_KEEPINTVL * TCP_KEEPCNT + + FIXME? Does that make sense? + + Sidenote: Given the max values, the entire operation fits into an int. */ + tka.keepaliveinterval = MSPERSEC / FIXED_WSOCK_TCP_KEEPCNT * keepcnt + * keepintvl; + if (WSAIoctl (get_socket (), SIO_KEEPALIVE_VALS, (LPVOID) &tka, sizeof tka, + NULL, 0, &dummy, NULL, NULL) == SOCKET_ERROR) + { + set_winsock_errno (); + return -1; + } + if (!so_keepalive) + { + ret = ::setsockopt (get_socket (), SOL_SOCKET, SO_KEEPALIVE, + (const char *) &so_keepalive, sizeof so_keepalive); + if (ret == SOCKET_ERROR) + debug_printf ("setsockopt (SO_KEEPALIVE) failed, %u\n", + WSAGetLastError ()); + } + return 0; +} + int fhandler_socket_inet::setsockopt (int level, int optname, const void *optval, socklen_t optlen) @@ -1686,6 +1747,14 @@ fhandler_socket_inet::setsockopt (int level, int optname, const void *optval, break; case IPPROTO_TCP: + /* Check for stream socket early on, so we don't have to do this for + every option. Also, WinSock returns EINVAL. */ + if (type != SOCK_STREAM) + { + set_errno (EOPNOTSUPP); + return -1; + } + switch (optname) { case TCP_MAXSEG: @@ -1698,16 +1767,59 @@ fhandler_socket_inet::setsockopt (int level, int optname, const void *optval, /* Fake FastOpen on older systems. */ if (!wincap.has_tcp_fastopen ()) { - if (type != SOCK_STREAM) - { - set_errno (EOPNOTSUPP); - return -1; - } ignore = true; tcp_fastopen = *(int *) optval ? true : false; } break; + case TCP_KEEPIDLE: + /* Handle TCP_KEEPIDLE on older systems. */ + if (!wincap.has_linux_tcp_keepalive_sockopts ()) + { + if (*(int *) optval < 1 || *(int *) optval > MAX_TCP_KEEPIDLE) + { + set_errno (EINVAL); + return -1; + } + if (set_keepalive (*(int *) optval, tcp_keepcnt, tcp_keepintvl)) + return -1; + ignore = true; + tcp_keepidle = *(int *) optval; + } + break; + + case TCP_KEEPCNT: + /* Fake TCP_KEEPCNT on older systems. */ + if (!wincap.has_linux_tcp_keepalive_sockopts ()) + { + if (*(int *) optval < 1 || *(int *) optval > MAX_TCP_KEEPCNT) + { + set_errno (EINVAL); + return -1; + } + if (set_keepalive (tcp_keepidle, *(int *) optval, tcp_keepintvl)) + return -1; + ignore = true; + tcp_keepcnt = *(int *) optval; + } + break; + + case TCP_KEEPINTVL: + /* Handle TCP_KEEPINTVL on older systems. */ + if (!wincap.has_linux_tcp_keepalive_sockopts ()) + { + if (*(int *) optval < 1 || *(int *) optval > MAX_TCP_KEEPINTVL) + { + set_errno (EINVAL); + return -1; + } + if (set_keepalive (tcp_keepidle, tcp_keepcnt, *(int *) optval)) + return -1; + ignore = true; + tcp_keepintvl = *(int *) optval; + } + break; + default: break; } @@ -1841,23 +1953,56 @@ fhandler_socket_inet::getsockopt (int level, int optname, const void *optval, break; case IPPROTO_TCP: + /* Check for stream socket early on, so we don't have to do this for + every option. Also, WinSock returns EINVAL. */ + if (type != SOCK_STREAM) + { + set_errno (EOPNOTSUPP); + return -1; + } + switch (optname) { case TCP_FASTOPEN: /* Fake FastOpen on older systems */ if (!wincap.has_tcp_fastopen ()) { - if (type != SOCK_STREAM) - { - set_errno (EOPNOTSUPP); - return -1; - } *(int *) optval = tcp_fastopen ? 1 : 0; *optlen = sizeof (int); return 0; } break; + case TCP_KEEPIDLE: + /* Use stored value on older systems */ + if (!wincap.has_linux_tcp_keepalive_sockopts ()) + { + *(int *) optval = tcp_keepidle; + *optlen = sizeof (int); + return 0; + } + break; + + case TCP_KEEPCNT: + /* Use stored value on older systems */ + if (!wincap.has_linux_tcp_keepalive_sockopts ()) + { + *(int *) optval = tcp_keepcnt; + *optlen = sizeof (int); + return 0; + } + break; + + case TCP_KEEPINTVL: + /* Use stored value on older systems */ + if (!wincap.has_linux_tcp_keepalive_sockopts ()) + { + *(int *) optval = tcp_keepintvl; + *optlen = sizeof (int); + return 0; + } + break; + default: break; } diff --git a/winsup/cygwin/include/netinet/tcp.h b/winsup/cygwin/include/netinet/tcp.h index ab1cd12bf..9c2e90eaa 100644 --- a/winsup/cygwin/include/netinet/tcp.h +++ b/winsup/cygwin/include/netinet/tcp.h @@ -123,8 +123,11 @@ struct tcphdr { /* * User-settable options (used with setsockopt). */ -#define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ -#define TCP_MAXSEG 0x04 /* get maximum segment size (r/o on windows) */ -#define TCP_FASTOPEN 0x0f /* enable FastOpen on listeners */ +#define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */ +#define TCP_KEEPIDLE 0x03 /* start keepalives after this period */ +#define TCP_MAXSEG 0x04 /* get maximum segment size (r/o on windows) */ +#define TCP_FASTOPEN 0x0f /* enable FastOpen on listeners */ +#define TCP_KEEPCNT 0x10 /* number of keepalives before death */ +#define TCP_KEEPINTVL 0x11 /* interval between keepalives */ #endif diff --git a/winsup/cygwin/wincap.cc b/winsup/cygwin/wincap.cc index be6d71d12..323c5a368 100644 --- a/winsup/cygwin/wincap.cc +++ b/winsup/cygwin/wincap.cc @@ -47,6 +47,7 @@ wincaps wincap_vista __attribute__((section (".cygwin_dll_common"), shared)) = { has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:false, + has_linux_tcp_keepalive_sockopts:false, }, }; @@ -79,6 +80,7 @@ wincaps wincap_7 __attribute__((section (".cygwin_dll_common"), shared)) = { has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:false, + has_linux_tcp_keepalive_sockopts:false, }, }; @@ -111,6 +113,7 @@ wincaps wincap_8 __attribute__((section (".cygwin_dll_common"), shared)) = { has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:false, + has_linux_tcp_keepalive_sockopts:false, }, }; @@ -143,6 +146,7 @@ wincaps wincap_8_1 __attribute__((section (".cygwin_dll_common"), shared)) = { has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:false, + has_linux_tcp_keepalive_sockopts:false, }, }; @@ -175,6 +179,7 @@ wincaps wincap_10_1507 __attribute__((section (".cygwin_dll_common"), shared)) has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:false, + has_linux_tcp_keepalive_sockopts:false, }, }; @@ -207,6 +212,7 @@ wincaps wincap_10_1607 __attribute__((section (".cygwin_dll_common"), shared)) has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:true, + has_linux_tcp_keepalive_sockopts:false, }, }; @@ -239,6 +245,7 @@ wincaps wincap_10_1703 __attribute__((section (".cygwin_dll_common"), shared)) = has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:true, + has_linux_tcp_keepalive_sockopts:false, }, }; @@ -271,6 +278,7 @@ wincaps wincap_10_1709 __attribute__((section (".cygwin_dll_common"), shared)) = has_con_esc_rep:false, has_extended_mem_api:false, has_tcp_fastopen:true, + has_linux_tcp_keepalive_sockopts:true, }, }; @@ -303,6 +311,7 @@ wincaps wincap_10_1803 __attribute__((section (".cygwin_dll_common"), shared)) = has_con_esc_rep:false, has_extended_mem_api:true, has_tcp_fastopen:true, + has_linux_tcp_keepalive_sockopts:true, }, }; @@ -335,6 +344,7 @@ wincaps wincap_10_1809 __attribute__((section (".cygwin_dll_common"), shared)) = has_con_esc_rep:false, has_extended_mem_api:true, has_tcp_fastopen:true, + has_linux_tcp_keepalive_sockopts:true, }, }; @@ -367,6 +377,7 @@ wincaps wincap_10_1903 __attribute__((section (".cygwin_dll_common"), shared)) = has_con_esc_rep:true, has_extended_mem_api:true, has_tcp_fastopen:true, + has_linux_tcp_keepalive_sockopts:true, }, }; diff --git a/winsup/cygwin/wincap.h b/winsup/cygwin/wincap.h index 54a880af7..26c4c01ef 100644 --- a/winsup/cygwin/wincap.h +++ b/winsup/cygwin/wincap.h @@ -41,6 +41,7 @@ struct wincaps unsigned has_con_esc_rep : 1; unsigned has_extended_mem_api : 1; unsigned has_tcp_fastopen : 1; + unsigned has_linux_tcp_keepalive_sockopts : 1; }; }; @@ -105,6 +106,7 @@ public: bool IMPLEMENT (has_con_esc_rep) bool IMPLEMENT (has_extended_mem_api) bool IMPLEMENT (has_tcp_fastopen) + bool IMPLEMENT (has_linux_tcp_keepalive_sockopts) void disable_case_sensitive_dirs () {