https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;h=8ccffddc91942dcb4a6417c13b46c5ab5d81a5d9

commit 8ccffddc91942dcb4a6417c13b46c5ab5d81a5d9
Author: Corinna Vinschen <[email protected]>
Date:   Wed Jul 1 20:27:10 2020 +0200

    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 <[email protected]>

Diff:
---
 winsup/cygwin/fhandler.h              |   5 ++
 winsup/cygwin/fhandler_socket_inet.cc | 163 ++++++++++++++++++++++++++++++++--
 winsup/cygwin/include/netinet/tcp.h   |   9 +-
 winsup/cygwin/wincap.cc               |  11 +++
 winsup/cygwin/wincap.h                |   2 +
 5 files changed, 178 insertions(+), 12 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 <w32api/ws2tcpip.h>
 #include <w32api/mswsock.h>
+#include <w32api/mstcpip.h>
 #include <netinet/tcp.h>
 #include <unistd.h>
 #include <asm/byteorder.h>
@@ -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,13 +1767,56 @@ 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)
+             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 (EOPNOTSUPP);
+                 set_errno (EINVAL);
                  return -1;
                }
+             if (set_keepalive (*(int *) optval, tcp_keepcnt, tcp_keepintvl))
+               return -1;
              ignore = true;
-             tcp_fastopen = *(int *) optval ? true : false;
+             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;
 
@@ -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 ()
   {

Reply via email to