Support for non-overlapped sockets
authorBert Belder <bertbelder@gmail.com>
Tue, 30 Nov 2010 16:07:09 +0000 (17:07 +0100)
committerBert Belder <bertbelder@gmail.com>
Mon, 20 Dec 2010 22:51:29 +0000 (23:51 +0100)
By default windows creates sockets with the WSA_FLAG_OVERLAPPED flag set.
Because child processes don't expect to have overlapped stdio (it never happens)
it won't work with them.

TODO.win32
src/node.cc
src/node_net.cc
src/platform_win32.cc
src/platform_win32_winsock.cc [new file with mode: 0644]
src/platform_win32_winsock.h [new file with mode: 0644]

index 09438698abc0a51edd854b7b271564606ee0d055..b883a8c19c6e44a1cc5f5d21b328d3b4c4ce112a 100644 (file)
@@ -10,7 +10,8 @@
 \r
 - Child processes\r
   Should not be too hard using CreatePipe, CreateProcessW and GetExitCodeProcess.\r
-  Hooking up the child to a socket is tricky but it can be done (http://www.spinellis.gr/sw/unix/socketpipe/socketpipe-win.c)\r
+  Hooking up a child process to a file handle can be done; hooking up to a normal socket won't work;\r
+  we'd need some sort of pump() mechanism.\r
   Waiting for the child to exit is tricky, probably would require a wait thread to wait for the child, then ev_async notify.\r
   How can we distinguish between the exit code and exception number after calling GetExitCodeProcess?\r
 \r
index 3c81bc9a86eb4d2884156eb71e524eabf9026247..d6a1745036007959710e11c47bf8bb25a808a0cc 100644 (file)
@@ -17,7 +17,8 @@
 #include "platform.h"
 
 #ifdef __MINGW32__
-# include "platform_win32.h" /* winapi_perror() */
+# include <platform_win32.h> /* winapi_perror() */
+# include <platform_win32_winsock.h> /* wsa_init() */
 #else // __POSIX__
 # include <dlfcn.h> /* dlopen(), dlsym() */
 # include <pwd.h> /* getpwnam() */
@@ -96,10 +97,6 @@ static ev_async eio_want_poll_notifier;
 static ev_async eio_done_poll_notifier;
 static ev_idle  eio_poller;
 
-#ifdef __MINGW32__
-WSAData winsockData;
-#endif
-
 // Buffer for getpwnam_r(), getgrpam_r() and other misc callers; keep this
 // scoped at file-level rather than method-level to avoid excess stack usage.
 static char getbuf[PATH_MAX + 1];
@@ -1952,11 +1949,8 @@ int Start(int argc, char *argv[]) {
 #endif // __POSIX__
 
 #ifdef __MINGW32__
-  // On windows, to use winsock it must be initialized
-  WORD winsockVersion = MAKEWORD(2, 2);
-  if (WSAStartup(winsockVersion, &winsockData)) {
-    winapi_perror("WSAStartup");
-  }
+  // Initialize winsock and soem related caches
+  wsa_init();
 #endif // __MINGW32__
 
   // Initialize the default ev loop.
index 8ba1b7185bc3d477810f62f6ba0fc20f27d86528..8b9b061bfe866c32364071412b3f2409dc8cee66 100644 (file)
@@ -716,11 +716,8 @@ static Handle<Value> Read(const Arguments& args) {
     return ThrowException(ErrnoException(errno, "read"));
   }
 #else // __MINGW32__
-  /*
-   * read() _should_ work for sockets in mingw, but always gives EINVAL;
-   * someone should really file a bug about it.
-   * We'll use recv() for sockets however, it's faster as well.
-   */
+   // read() doesn't work for overlapped sockets (the only usable 
+   // type of sockets) so recv() is used here.
   ssize_t bytes_read = recv(_get_osfhandle(fd), (char*)buffer_data + off, len, 0);
 
   if (bytes_read < 0) {
@@ -938,11 +935,8 @@ static Handle<Value> Write(const Arguments& args) {
     return ThrowException(ErrnoException(errno, "write"));
   }
 #else // __MINGW32__
-  /*
-   * write() _should_ work for sockets in mingw, but always gives EINVAL;
-   * someone should really file a bug about it.
-   * We'll use send() for sockets however, it's faster as well.
-   */
+  // write() doesn't work for overlapped sockets (the only usable 
+  // type of sockets) so send() is used.
   ssize_t written = send(_get_osfhandle(fd), buffer_data + off, len, 0);
 
   if (written < 0) {
index c3831c1c4a3c0714d1eb4b1dc028a28de231e2cf..3e7029e294b81ef989009d0ca9353aae1283137a 100644 (file)
@@ -7,6 +7,7 @@
 #include <unistd.h> // getpagesize
 #include <windows.h>
 
+#include "platform_win32_winsock.cc"
 
 namespace node {
 
diff --git a/src/platform_win32_winsock.cc b/src/platform_win32_winsock.cc
new file mode 100644 (file)
index 0000000..6c858e7
--- /dev/null
@@ -0,0 +1,331 @@
+/*\r
+ * This file contains all winsock-related stuff.\r
+ * Socketpair() for winsock is implemented here.\r
+ * There are also functions to create a non-overlapped socket (which windows normally doesn't do)\r
+ * and to create a socketpair that has one synchronous and one async socket.\r
+ * Synchronous sockets are required because async sockets can't be used by child processes.\r
+ */\r
+\r
+\r
+#include <windows.h>\r
+#include <winsock2.h>\r
+#include <mswsock.h>\r
+#include <ws2tcpip.h>\r
+#include <ws2spi.h>\r
+#include <platform_win32_winsock.h>\r
+\r
+\r
+namespace node {\r
+\r
+\r
+/*\r
+ * Winsock version data goes here\r
+ */\r
+static WSAData winsock_info;\r
+\r
+\r
+/*\r
+ * Cache for WSAPROTOCOL_INFOW structures for protocols used in node\r
+ * [0] TCP/IP\r
+ * [1] UDP/IP\r
+ * [2] TCP/IPv6\r
+ * [3] UDP/IPv6\r
+ */\r
+static WSAPROTOCOL_INFOW proto_info_cache[4];\r
+\r
+\r
+/*\r
+ * Does the about the same as perror(), but for winsock errors\r
+ */\r
+void wsa_perror(const char *prefix) {\r
+  DWORD errorno = WSAGetLastError();\r
+  char *errmsg;\r
+\r
+  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,\r
+                NULL, errorno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errmsg, 0, NULL);\r
+\r
+  // FormatMessage messages include a newline character\r
+\r
+  if (prefix) {\r
+    fprintf(stderr, "%s: %s", prefix, errmsg);\r
+  } else {\r
+    fputs(errmsg, stderr);\r
+  }\r
+}\r
+\r
+\r
+/*\r
+ * Retrieves a pointer to a WSAPROTOCOL_INFOW structure\r
+ * related to a certain winsock protocol from the cache\r
+ */\r
+inline static WSAPROTOCOL_INFOW *wsa_get_cached_proto_info(int af, int type, int proto) {\r
+  assert(proto == IPPROTO_IP\r
+      || proto == IPPROTO_TCP\r
+      || proto == IPPROTO_UDP);\r
+\r
+  switch (af) {\r
+    case AF_INET:\r
+      switch (type) {\r
+        case SOCK_STREAM:\r
+          return &proto_info_cache[0];\r
+        case SOCK_DGRAM:\r
+          return &proto_info_cache[1];\r
+      }\r
+      break;\r
+\r
+    case AF_INET6:\r
+      switch (type) {\r
+        case SOCK_STREAM:\r
+          return &proto_info_cache[2];\r
+        case SOCK_DGRAM:\r
+          return &proto_info_cache[3];\r
+      }\r
+      break;\r
+  }\r
+\r
+  WSASetLastError(WSAEPROTONOSUPPORT);\r
+  return NULL;\r
+}\r
+\r
+\r
+/*\r
+ * Creates a synchronous, non-overlapped socket.\r
+ * (The sockets that are created with socket() or accept() are always in overlapped mode.)\r
+ * Doubles the winsock api, e.g. returns a SOCKET handle, not an FD\r
+ */\r
+SOCKET wsa_sync_socket(int af, int type, int proto) {\r
+  WSAPROTOCOL_INFOW *protoInfo = wsa_get_cached_proto_info(af, type, proto);\r
+  if (protoInfo == NULL)\r
+    return INVALID_SOCKET;\r
+\r
+  return WSASocketW(af, type, proto, protoInfo, 0, 0);\r
+}\r
+\r
+\r
+/*\r
+ * Create a socketpair using the protocol specified\r
+ * This function uses winsock semantics, it returns SOCKET handles, not FDs\r
+ * Currently supports TCP/IPv4 socket pairs only\r
+ */\r
+int wsa_socketpair(int af, int type, int proto, SOCKET sock[2]) {\r
+  assert(af == AF_INET\r
+      && type == SOCK_STREAM\r
+      && (proto == IPPROTO_IP || proto == IPPROTO_TCP));\r
+\r
+  SOCKET listen_sock;\r
+  SOCKADDR_IN addr1;\r
+  SOCKADDR_IN addr2;\r
+  int addr1_len = sizeof (addr1);\r
+  int addr2_len = sizeof (addr2);\r
+  sock[1] = INVALID_SOCKET;\r
+  sock[2] = INVALID_SOCKET;\r
+\r
+  if ((listen_sock = socket(af, type, proto)) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  memset((void*)&addr1, 0, sizeof(addr1));\r
+  addr1.sin_family = af;\r
+  addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\r
+  addr1.sin_port = 0;\r
+\r
+  if (bind(listen_sock, (SOCKADDR*)&addr1, addr1_len) == SOCKET_ERROR)\r
+    goto error;\r
+\r
+  if (getsockname(listen_sock, (SOCKADDR*)&addr1, &addr1_len) == SOCKET_ERROR)\r
+    goto error;\r
+\r
+  if (listen(listen_sock, 1))\r
+    goto error;\r
+\r
+  if ((sock[0] = socket(af, type, proto)) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (connect(sock[0], (SOCKADDR*)&addr1, addr1_len))\r
+    goto error;\r
+\r
+  if ((sock[1] = accept(listen_sock, 0, 0)) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (getpeername(sock[0], (SOCKADDR*)&addr1, &addr1_len) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (getsockname(sock[1], (SOCKADDR*)&addr2, &addr2_len) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (addr1_len != addr2_len\r
+      || addr1.sin_addr.s_addr != addr2.sin_addr.s_addr\r
+      || addr1.sin_port        != addr2.sin_port)\r
+    goto error;\r
+\r
+  closesocket(listen_sock);\r
+\r
+  return 0;\r
+\r
+error:\r
+  int error = WSAGetLastError();\r
+\r
+  if (listen_sock != INVALID_SOCKET)\r
+    closesocket(listen_sock);\r
+\r
+  if (sock[0] != INVALID_SOCKET)\r
+    closesocket(sock[0]);\r
+\r
+  if (sock[1] != INVALID_SOCKET)\r
+    closesocket(sock[1]);\r
+\r
+  WSASetLastError(error);\r
+\r
+  return SOCKET_ERROR;\r
+}\r
+\r
+\r
+/*\r
+ * Create a sync-async socketpair using the protocol specified,\r
+ * returning a synchronous socket and an asynchronous socket.\r
+ * Upon completion asyncSocket is opened with the WSA_FLAG_OVERLAPPED flag set,\r
+ * syncSocket won't have it set.\r
+ * Currently supports TCP/IPv4 socket pairs only\r
+ */\r
+int wsa_sync_async_socketpair(int af, int type, int proto, SOCKET *syncSocket, SOCKET *asyncSocket) {\r
+  assert(af == AF_INET\r
+      && type == SOCK_STREAM\r
+      && (proto == IPPROTO_IP || proto == IPPROTO_TCP));\r
+\r
+  SOCKET listen_sock;\r
+  SOCKET sock1 = INVALID_SOCKET;\r
+  SOCKET sock2 = INVALID_SOCKET;\r
+  SOCKADDR_IN addr1;\r
+  SOCKADDR_IN addr2;\r
+  int addr1_len = sizeof (addr1);\r
+  int addr2_len = sizeof (addr2);\r
+\r
+  if ((listen_sock = socket(af, type, proto)) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  memset((void*)&addr1, 0, sizeof(addr1));\r
+  addr1.sin_family = af;\r
+  addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\r
+  addr1.sin_port = 0;\r
+\r
+  if (bind(listen_sock, (SOCKADDR*)&addr1, addr1_len) == SOCKET_ERROR)\r
+    goto error;\r
+\r
+  if (getsockname(listen_sock, (SOCKADDR*)&addr1, &addr1_len) == SOCKET_ERROR)\r
+    goto error;\r
+\r
+  if (listen(listen_sock, 1))\r
+    goto error;\r
+\r
+  if ((sock1 = wsa_sync_socket(af, type, proto)) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (connect(sock1, (SOCKADDR*)&addr1, addr1_len))\r
+    goto error;\r
+\r
+  if ((sock2 = accept(listen_sock, 0, 0)) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (getpeername(sock1, (SOCKADDR*)&addr1, &addr1_len) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (getsockname(sock2, (SOCKADDR*)&addr2, &addr2_len) == INVALID_SOCKET)\r
+    goto error;\r
+\r
+  if (addr1_len != addr2_len\r
+      || addr1.sin_addr.s_addr != addr2.sin_addr.s_addr\r
+      || addr1.sin_port        != addr2.sin_port)\r
+    goto error;\r
+\r
+  closesocket(listen_sock);\r
+\r
+  *syncSocket = sock1;\r
+  *asyncSocket = sock2;\r
+\r
+  return 0;\r
+\r
+error:\r
+  int error = WSAGetLastError();\r
+\r
+  if (listen_sock != INVALID_SOCKET)\r
+    closesocket(listen_sock);\r
+\r
+  if (sock1 != INVALID_SOCKET)\r
+    closesocket(sock1);\r
+\r
+  if (sock2 != INVALID_SOCKET)\r
+    closesocket(sock2);\r
+\r
+  WSASetLastError(error);\r
+\r
+  return SOCKET_ERROR;\r
+}\r
+\r
+\r
+/*\r
+ * Retrieves a WSAPROTOCOL_INFOW structure for a certain protocol\r
+ */\r
+static void wsa_get_proto_info(int af, int type, int proto, WSAPROTOCOL_INFOW *target) {\r
+  WSAPROTOCOL_INFOW *info_buffer = NULL;\r
+  unsigned long info_buffer_length = 0;\r
+  int protocol_count, i, error;\r
+\r
+  if (WSCEnumProtocols(NULL, NULL, &info_buffer_length, &error) != SOCKET_ERROR) {\r
+    error = WSAEOPNOTSUPP;\r
+    goto error;\r
+  }\r
+\r
+  info_buffer = (WSAPROTOCOL_INFOW *)malloc(info_buffer_length);\r
+\r
+  if ((protocol_count = WSCEnumProtocols(NULL, info_buffer, &info_buffer_length, &error)) == SOCKET_ERROR)\r
+    goto error;\r
+\r
+  for (i = 0; i < protocol_count; i++) {\r
+    if (af == info_buffer[i].iAddressFamily\r
+        && type == info_buffer[i].iSocketType\r
+        && proto == info_buffer[i].iProtocol\r
+        && info_buffer[i].dwServiceFlags1 & XP1_IFS_HANDLES) {\r
+      memcpy(target, (WSAPROTOCOL_INFOW*)&info_buffer[i], sizeof(WSAPROTOCOL_INFOW));\r
+      free(info_buffer);\r
+      return;\r
+    }\r
+  }\r
+\r
+  error = WSAEPROTONOSUPPORT;\r
+\r
+error:\r
+  WSASetLastError(error);\r
+  wsa_perror("Error obtaining winsock protocol information");\r
+\r
+  if (info_buffer != NULL) {\r
+    free(info_buffer);\r
+  }\r
+}\r
+\r
+\r
+/*\r
+ * Initializes (fills) the WSAPROTOCOL_INFOW structure cache\r
+ */\r
+static void wsa_init_proto_info_cache() {\r
+  WSAPROTOCOL_INFOW *cache = (WSAPROTOCOL_INFOW*)&proto_info_cache;\r
+\r
+  wsa_get_proto_info(AF_INET,  SOCK_STREAM, IPPROTO_TCP, &proto_info_cache[0]);\r
+  wsa_get_proto_info(AF_INET,  SOCK_DGRAM,  IPPROTO_UDP, &proto_info_cache[1]);\r
+  wsa_get_proto_info(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &proto_info_cache[2]);\r
+  wsa_get_proto_info(AF_INET6, SOCK_DGRAM,  IPPROTO_UDP, &proto_info_cache[3]);\r
+}\r
+\r
+\r
+/*\r
+ * Initializes winsock and winsock-related stuff\r
+ */\r
+void wsa_init() {\r
+  WORD version = MAKEWORD(2, 2);\r
+  if (WSAStartup(version, &winsock_info)) {\r
+    wsa_perror("WSAStartup");\r
+  }\r
+\r
+  wsa_init_proto_info_cache();\r
+}\r
+\r
+\r
+} // namespace node\r
diff --git a/src/platform_win32_winsock.h b/src/platform_win32_winsock.h
new file mode 100644 (file)
index 0000000..126ee0d
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef NODE_PLATFORM_WIN32_WINSOCK_H_\r
+#define NODE_PLATFORM_WIN32_WINSOCK_H_\r
+\r
+#include <windows.h>\r
+#include <winsock.h>\r
+\r
+namespace node {\r
+\r
+\r
+void wsa_init();\r
+\r
+void wsa_perror(const char* prefix = "");\r
+\r
+SOCKET wsa_sync_socket(int af, int type, int proto);\r
+int wsa_socketpair(int af, int type, int proto, SOCKET sock[2]);\r
+int wsa_sync_async_socketpair(int af, int type, int proto, SOCKET *syncSocket, SOCKET *asyncSocket);\r
+\r
+\r
+} // namespace node\r
+\r
+#endif  // NODE_PLATFORM_WIN32_WINSOCK_H_\r