connect: Add TCP Fast Open support for Windows

This is the former PR #3327, saved by Johannes Schindelin, rebased, squashed
and pushed again.

Requires Windows 10 ver 1607 or newer
diff --git a/lib/config-win32.h b/lib/config-win32.h
index 76b00b9..aac553f 100644
--- a/lib/config-win32.h
+++ b/lib/config-win32.h
@@ -609,6 +609,12 @@
 #  endif
 #endif
 
+/* Define if you have the ConnectEx WinSock 2 extension */
+/* This requires a build target of WinXP or newer */
+#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501)
+#define HAVE_CONNECTEX 1
+#endif
+
 /* ---------------------------------------------------------------- */
 /*                          STRUCT RELATED                          */
 /* ---------------------------------------------------------------- */
diff --git a/lib/connect.c b/lib/connect.c
index ec3cd3a..fb6ea4f 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -770,8 +770,16 @@
     (void)verifyconnect(conn->tempsock[i], NULL);
 #endif
 
-    /* check socket for connect */
-    rc = SOCKET_WRITABLE(conn->tempsock[i], 0);
+#ifdef HAVE_CONNECTEX
+    /* socket isn't actually connected until the first attempted write,
+       so it doesn't appear writable here yet; spoof rc to fall into the
+       good branch (socket errors will be detected after the second pass) */
+    if(conn->bits.tcp_fastopen && !conn->fastopen_connected)
+      rc = 1;
+    else
+#endif
+      /* check socket for connect */
+      rc = SOCKET_WRITABLE(conn->tempsock[i], 0);
 
     if(rc == 0) { /* no connection yet */
       error = 0;
@@ -994,6 +1002,13 @@
   bool is_tcp;
 #ifdef TCP_FASTOPEN_CONNECT
   int optval = 1;
+#elif defined(HAVE_CONNECTEX)
+  int optval = 1;
+  struct sockaddr_storage ss;
+  struct sockaddr_in *si4 = (struct sockaddr_in *)&ss;
+#ifdef ENABLE_IPV6
+  struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&ss;
+#endif
 #endif
 
   *sockp = CURL_SOCKET_BAD;
@@ -1113,7 +1128,45 @@
         rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
       else
         rc = 0; /* Do nothing */
+#elif defined(HAVE_CONNECTEX) /* Windows 10 (ver 1607+) */
+      /* Windows uses ConnectEx() which must be called when the first data */
+      /* is ready to be sent. For now, just set up the socket as needed but */
+      /* don't actually connect */
+      if(setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN,
+                    (void *)&optval, sizeof(optval)) == SOCKET_ERROR) {
+        infof(data, "Failed to enable TCP Fast Open on fd %d, errno: %d\n",
+              sockfd, SOCKERRNO);
+        return CURLE_FAILED_INIT;
+      }
+      else
+        infof(data, "TCP_FASTOPEN set\n");
+
+      /* ConnectEx() requires a bound socket */
+      if(!conn->bits.bound) {
+        memset(&ss, 0, sizeof(ss));
+
+        if(addr.family == AF_INET) {
+          si4->sin_family = AF_INET;
+          si4->sin_addr.s_addr = INADDR_ANY;
+        }
+#if defined(USE_IPV6)
+        else if(addr.family == AF_INET6) {
+          si6->sin6_family = AF_INET6;
+          si6->sin6_addr = in6addr_any;
+        }
 #endif
+        else {
+          infof(data, "Unknown protocol used with TCP Fast Open: %d\n",
+                addr.family);
+          return CURLE_UNSUPPORTED_PROTOCOL;
+        }
+
+        if(bind(sockfd, (struct sockaddr *)&ss, sizeof(ss)) == 0)
+          rc = 0;
+      }
+      else
+        rc = 0;
+#endif /* defined(HAVE_CONNECTEX) */
     }
     else {
       rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
diff --git a/lib/curl_setup.h b/lib/curl_setup.h
index f83e1ea..80e300f 100644
--- a/lib/curl_setup.h
+++ b/lib/curl_setup.h
@@ -249,6 +249,15 @@
 #  include <windows.h>
 #  ifdef HAVE_WINSOCK2_H
 #    include <winsock2.h>
+#    ifdef HAVE_CONNECTEX
+#      include <MSWSock.h>
+#      ifdef WSAID_CONNECTEX
+         typedef LPFN_CONNECTEX curl_ConnectEx_callback;
+         extern curl_ConnectEx_callback Curl_ConnectEx;
+#      else
+#        undef HAVE_CONNECTEX
+#      endif
+#    endif
 #    ifdef HAVE_WS2TCPIP_H
 #      include <ws2tcpip.h>
 #    endif
diff --git a/lib/easy.c b/lib/easy.c
index e592d7a..a3d04e0 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -104,6 +104,12 @@
   WSADATA wsaData;
   int res;
 
+#ifdef HAVE_CONNECTEX
+  curl_socket_t sockfd;
+  GUID guid = WSAID_CONNECTEX;
+  DWORD dummy_val;
+#endif
+
 #if defined(ENABLE_IPV6) && (USE_WINSOCK < 2)
   Error IPV6_requires_winsock2
 #endif
@@ -132,9 +138,31 @@
     return CURLE_FAILED_INIT;
   }
   /* The Windows Sockets DLL is acceptable. Proceed. */
+
+  /* Init pointer to ConnectEx(), a Winsock 2 function needed */
+  /* for TCP Fast Open support */
+#ifdef HAVE_CONNECTEX
+  Curl_ConnectEx = NULL;
+  sockfd = socket(AF_INET, SOCK_STREAM, 0);
+
+  if(sockfd != CURL_SOCKET_BAD) {
+    if(WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER,
+                &guid, sizeof(guid),
+                &Curl_ConnectEx, sizeof(Curl_ConnectEx),
+                &dummy_val, NULL, NULL)) {
+      DEBUGF(fprintf(stderr,
+                     "WSAIoctl() failed to return ConnectEx() ptr, err #%d\n",
+                     WSAGetLastError()));
+      Curl_ConnectEx = NULL;
+    }
+
+    sclose(sockfd);
+  }
+
+#endif /* HAVE_CONNECTEX */
 #elif defined(USE_LWIPSOCK)
   lwip_init();
-#endif
+#endif /* USE_WINSOCK */
 
 #ifdef USE_WINDOWS_SSPI
   {
@@ -181,6 +209,12 @@
 #if defined(WIN32) && defined(UNICODE)
 curl_wcsdup_callback Curl_cwcsdup = (curl_wcsdup_callback)_wcsdup;
 #endif
+#ifdef HAVE_CONNECTEX
+/*
+ * ConnectEx pointer is initialized by win32_init() after Winsock init
+ */
+curl_ConnectEx_callback Curl_ConnectEx;
+#endif
 #else
 /*
  * Symbian OS doesn't support initialization to code in writable static data.
diff --git a/lib/sendf.c b/lib/sendf.c
index e8598e6..2a4db39 100644
--- a/lib/sendf.c
+++ b/lib/sendf.c
@@ -387,6 +387,37 @@
     conn->bits.tcp_fastopen = FALSE;
   }
   else
+#elif defined(HAVE_CONNECTEX)
+  if(conn->bits.tcp_fastopen && !conn->fastopen_connected) {
+    conn->fastopen_connected = TRUE;
+    bytes_written = 0;
+
+    if(!Curl_ConnectEx(sockfd,
+                       conn->ip_addr->ai_addr, conn->ip_addr->ai_addrlen,
+                       (void *)mem, (DWORD)len,
+                       (LPDWORD)&bytes_written,
+                       &conn->fastopen_state)) {
+      if(SOCKERRNO != WSA_IO_PENDING)
+        bytes_written = -1;
+      else
+        if(!GetOverlappedResult((HANDLE)sockfd, &conn->fastopen_state,
+                                (LPDWORD)&bytes_written, TRUE)) {
+          int err = GetLastError();
+          failf(conn->data, "Send failure: %s",
+                Curl_strerror(conn, err));
+          conn->data->state.os_errno = err;
+          *code = CURLE_SEND_ERROR;
+          return 0;
+        }
+    }
+
+    /* socket in default state; enable previously-set socket params */
+    if(setsockopt(sockfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0))
+      infof(conn->data,
+            "setsockopt() failed after TCP Fast Open for fd %d, errno: %d",
+            sockfd, SOCKERRNO);
+  }
+  else
 #endif
     bytes_written = swrite(sockfd, mem, len);
 
diff --git a/lib/setopt.c b/lib/setopt.c
index 2704676..127d338 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -2547,7 +2547,12 @@
   case CURLOPT_TCP_FASTOPEN:
 #if defined(CONNECT_DATA_IDEMPOTENT) || defined(MSG_FASTOPEN) || \
    defined(TCP_FASTOPEN_CONNECT)
-    data->set.tcp_fastopen = (0 != va_arg(param, long))?TRUE:FALSE;
+    data->set.tcp_fastopen = (0 != va_arg(param, long)) ? TRUE : FALSE;
+#elif defined(HAVE_CONNECTEX)
+    if(Curl_ConnectEx != NULL)
+      data->set.tcp_fastopen = (0 != va_arg(param, long)) ? TRUE : FALSE;
+    else
+      result = CURLE_FAILED_INIT;
 #else
     result = CURLE_NOT_BUILT_IN;
 #endif
diff --git a/lib/urldata.h b/lib/urldata.h
index 8fb2d28..6079a75 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1035,6 +1035,11 @@
   char *unix_domain_socket;
   bool abstract_unix_socket;
 #endif
+
+#ifdef HAVE_CONNECTEX
+  bool fastopen_connected; /* is socket actually connected (via ConnectEx)? */
+  OVERLAPPED fastopen_state; /* state used by a Windows overlapped-I/O func */
+#endif
 };
 
 /* The end of connectdata. */