summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBert Belder <bertbelder@gmail.com>2010-11-30 17:07:09 +0100
committerBert Belder <bertbelder@gmail.com>2010-12-20 23:51:29 +0100
commitbd62203d13769a0422078b6b690f2826d2209c63 (patch)
tree9c2c10053f1aa1dae73c9b3bd195d183dbdef111
parentd66cf5f32cc8bcfa73b32b8a95911cc46c52b6bf (diff)
downloadnode-new-bd62203d13769a0422078b6b690f2826d2209c63.tar.gz
Support for non-overlapped sockets
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.
-rw-r--r--TODO.win323
-rw-r--r--src/node.cc14
-rw-r--r--src/node_net.cc14
-rw-r--r--src/platform_win32.cc1
-rw-r--r--src/platform_win32_winsock.cc331
-rw-r--r--src/platform_win32_winsock.h21
6 files changed, 363 insertions, 21 deletions
diff --git a/TODO.win32 b/TODO.win32
index 09438698ab..b883a8c19c 100644
--- a/TODO.win32
+++ b/TODO.win32
@@ -10,7 +10,8 @@
- Child processes
Should not be too hard using CreatePipe, CreateProcessW and GetExitCodeProcess.
- Hooking up the child to a socket is tricky but it can be done (http://www.spinellis.gr/sw/unix/socketpipe/socketpipe-win.c)
+ Hooking up a child process to a file handle can be done; hooking up to a normal socket won't work;
+ we'd need some sort of pump() mechanism.
Waiting for the child to exit is tricky, probably would require a wait thread to wait for the child, then ev_async notify.
How can we distinguish between the exit code and exception number after calling GetExitCodeProcess?
diff --git a/src/node.cc b/src/node.cc
index 3c81bc9a86..d6a1745036 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -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.
diff --git a/src/node_net.cc b/src/node_net.cc
index 8ba1b7185b..8b9b061bfe 100644
--- a/src/node_net.cc
+++ b/src/node_net.cc
@@ -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) {
diff --git a/src/platform_win32.cc b/src/platform_win32.cc
index c3831c1c4a..3e7029e294 100644
--- a/src/platform_win32.cc
+++ b/src/platform_win32.cc
@@ -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
index 0000000000..6c858e70e1
--- /dev/null
+++ b/src/platform_win32_winsock.cc
@@ -0,0 +1,331 @@
+/*
+ * This file contains all winsock-related stuff.
+ * Socketpair() for winsock is implemented here.
+ * There are also functions to create a non-overlapped socket (which windows normally doesn't do)
+ * and to create a socketpair that has one synchronous and one async socket.
+ * Synchronous sockets are required because async sockets can't be used by child processes.
+ */
+
+
+#include <windows.h>
+#include <winsock2.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <ws2spi.h>
+#include <platform_win32_winsock.h>
+
+
+namespace node {
+
+
+/*
+ * Winsock version data goes here
+ */
+static WSAData winsock_info;
+
+
+/*
+ * Cache for WSAPROTOCOL_INFOW structures for protocols used in node
+ * [0] TCP/IP
+ * [1] UDP/IP
+ * [2] TCP/IPv6
+ * [3] UDP/IPv6
+ */
+static WSAPROTOCOL_INFOW proto_info_cache[4];
+
+
+/*
+ * Does the about the same as perror(), but for winsock errors
+ */
+void wsa_perror(const char *prefix) {
+ DWORD errorno = WSAGetLastError();
+ char *errmsg;
+
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errorno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errmsg, 0, NULL);
+
+ // FormatMessage messages include a newline character
+
+ if (prefix) {
+ fprintf(stderr, "%s: %s", prefix, errmsg);
+ } else {
+ fputs(errmsg, stderr);
+ }
+}
+
+
+/*
+ * Retrieves a pointer to a WSAPROTOCOL_INFOW structure
+ * related to a certain winsock protocol from the cache
+ */
+inline static WSAPROTOCOL_INFOW *wsa_get_cached_proto_info(int af, int type, int proto) {
+ assert(proto == IPPROTO_IP
+ || proto == IPPROTO_TCP
+ || proto == IPPROTO_UDP);
+
+ switch (af) {
+ case AF_INET:
+ switch (type) {
+ case SOCK_STREAM:
+ return &proto_info_cache[0];
+ case SOCK_DGRAM:
+ return &proto_info_cache[1];
+ }
+ break;
+
+ case AF_INET6:
+ switch (type) {
+ case SOCK_STREAM:
+ return &proto_info_cache[2];
+ case SOCK_DGRAM:
+ return &proto_info_cache[3];
+ }
+ break;
+ }
+
+ WSASetLastError(WSAEPROTONOSUPPORT);
+ return NULL;
+}
+
+
+/*
+ * Creates a synchronous, non-overlapped socket.
+ * (The sockets that are created with socket() or accept() are always in overlapped mode.)
+ * Doubles the winsock api, e.g. returns a SOCKET handle, not an FD
+ */
+SOCKET wsa_sync_socket(int af, int type, int proto) {
+ WSAPROTOCOL_INFOW *protoInfo = wsa_get_cached_proto_info(af, type, proto);
+ if (protoInfo == NULL)
+ return INVALID_SOCKET;
+
+ return WSASocketW(af, type, proto, protoInfo, 0, 0);
+}
+
+
+/*
+ * Create a socketpair using the protocol specified
+ * This function uses winsock semantics, it returns SOCKET handles, not FDs
+ * Currently supports TCP/IPv4 socket pairs only
+ */
+int wsa_socketpair(int af, int type, int proto, SOCKET sock[2]) {
+ assert(af == AF_INET
+ && type == SOCK_STREAM
+ && (proto == IPPROTO_IP || proto == IPPROTO_TCP));
+
+ SOCKET listen_sock;
+ SOCKADDR_IN addr1;
+ SOCKADDR_IN addr2;
+ int addr1_len = sizeof (addr1);
+ int addr2_len = sizeof (addr2);
+ sock[1] = INVALID_SOCKET;
+ sock[2] = INVALID_SOCKET;
+
+ if ((listen_sock = socket(af, type, proto)) == INVALID_SOCKET)
+ goto error;
+
+ memset((void*)&addr1, 0, sizeof(addr1));
+ addr1.sin_family = af;
+ addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr1.sin_port = 0;
+
+ if (bind(listen_sock, (SOCKADDR*)&addr1, addr1_len) == SOCKET_ERROR)
+ goto error;
+
+ if (getsockname(listen_sock, (SOCKADDR*)&addr1, &addr1_len) == SOCKET_ERROR)
+ goto error;
+
+ if (listen(listen_sock, 1))
+ goto error;
+
+ if ((sock[0] = socket(af, type, proto)) == INVALID_SOCKET)
+ goto error;
+
+ if (connect(sock[0], (SOCKADDR*)&addr1, addr1_len))
+ goto error;
+
+ if ((sock[1] = accept(listen_sock, 0, 0)) == INVALID_SOCKET)
+ goto error;
+
+ if (getpeername(sock[0], (SOCKADDR*)&addr1, &addr1_len) == INVALID_SOCKET)
+ goto error;
+
+ if (getsockname(sock[1], (SOCKADDR*)&addr2, &addr2_len) == INVALID_SOCKET)
+ goto error;
+
+ if (addr1_len != addr2_len
+ || addr1.sin_addr.s_addr != addr2.sin_addr.s_addr
+ || addr1.sin_port != addr2.sin_port)
+ goto error;
+
+ closesocket(listen_sock);
+
+ return 0;
+
+error:
+ int error = WSAGetLastError();
+
+ if (listen_sock != INVALID_SOCKET)
+ closesocket(listen_sock);
+
+ if (sock[0] != INVALID_SOCKET)
+ closesocket(sock[0]);
+
+ if (sock[1] != INVALID_SOCKET)
+ closesocket(sock[1]);
+
+ WSASetLastError(error);
+
+ return SOCKET_ERROR;
+}
+
+
+/*
+ * Create a sync-async socketpair using the protocol specified,
+ * returning a synchronous socket and an asynchronous socket.
+ * Upon completion asyncSocket is opened with the WSA_FLAG_OVERLAPPED flag set,
+ * syncSocket won't have it set.
+ * Currently supports TCP/IPv4 socket pairs only
+ */
+int wsa_sync_async_socketpair(int af, int type, int proto, SOCKET *syncSocket, SOCKET *asyncSocket) {
+ assert(af == AF_INET
+ && type == SOCK_STREAM
+ && (proto == IPPROTO_IP || proto == IPPROTO_TCP));
+
+ SOCKET listen_sock;
+ SOCKET sock1 = INVALID_SOCKET;
+ SOCKET sock2 = INVALID_SOCKET;
+ SOCKADDR_IN addr1;
+ SOCKADDR_IN addr2;
+ int addr1_len = sizeof (addr1);
+ int addr2_len = sizeof (addr2);
+
+ if ((listen_sock = socket(af, type, proto)) == INVALID_SOCKET)
+ goto error;
+
+ memset((void*)&addr1, 0, sizeof(addr1));
+ addr1.sin_family = af;
+ addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr1.sin_port = 0;
+
+ if (bind(listen_sock, (SOCKADDR*)&addr1, addr1_len) == SOCKET_ERROR)
+ goto error;
+
+ if (getsockname(listen_sock, (SOCKADDR*)&addr1, &addr1_len) == SOCKET_ERROR)
+ goto error;
+
+ if (listen(listen_sock, 1))
+ goto error;
+
+ if ((sock1 = wsa_sync_socket(af, type, proto)) == INVALID_SOCKET)
+ goto error;
+
+ if (connect(sock1, (SOCKADDR*)&addr1, addr1_len))
+ goto error;
+
+ if ((sock2 = accept(listen_sock, 0, 0)) == INVALID_SOCKET)
+ goto error;
+
+ if (getpeername(sock1, (SOCKADDR*)&addr1, &addr1_len) == INVALID_SOCKET)
+ goto error;
+
+ if (getsockname(sock2, (SOCKADDR*)&addr2, &addr2_len) == INVALID_SOCKET)
+ goto error;
+
+ if (addr1_len != addr2_len
+ || addr1.sin_addr.s_addr != addr2.sin_addr.s_addr
+ || addr1.sin_port != addr2.sin_port)
+ goto error;
+
+ closesocket(listen_sock);
+
+ *syncSocket = sock1;
+ *asyncSocket = sock2;
+
+ return 0;
+
+error:
+ int error = WSAGetLastError();
+
+ if (listen_sock != INVALID_SOCKET)
+ closesocket(listen_sock);
+
+ if (sock1 != INVALID_SOCKET)
+ closesocket(sock1);
+
+ if (sock2 != INVALID_SOCKET)
+ closesocket(sock2);
+
+ WSASetLastError(error);
+
+ return SOCKET_ERROR;
+}
+
+
+/*
+ * Retrieves a WSAPROTOCOL_INFOW structure for a certain protocol
+ */
+static void wsa_get_proto_info(int af, int type, int proto, WSAPROTOCOL_INFOW *target) {
+ WSAPROTOCOL_INFOW *info_buffer = NULL;
+ unsigned long info_buffer_length = 0;
+ int protocol_count, i, error;
+
+ if (WSCEnumProtocols(NULL, NULL, &info_buffer_length, &error) != SOCKET_ERROR) {
+ error = WSAEOPNOTSUPP;
+ goto error;
+ }
+
+ info_buffer = (WSAPROTOCOL_INFOW *)malloc(info_buffer_length);
+
+ if ((protocol_count = WSCEnumProtocols(NULL, info_buffer, &info_buffer_length, &error)) == SOCKET_ERROR)
+ goto error;
+
+ for (i = 0; i < protocol_count; i++) {
+ if (af == info_buffer[i].iAddressFamily
+ && type == info_buffer[i].iSocketType
+ && proto == info_buffer[i].iProtocol
+ && info_buffer[i].dwServiceFlags1 & XP1_IFS_HANDLES) {
+ memcpy(target, (WSAPROTOCOL_INFOW*)&info_buffer[i], sizeof(WSAPROTOCOL_INFOW));
+ free(info_buffer);
+ return;
+ }
+ }
+
+ error = WSAEPROTONOSUPPORT;
+
+error:
+ WSASetLastError(error);
+ wsa_perror("Error obtaining winsock protocol information");
+
+ if (info_buffer != NULL) {
+ free(info_buffer);
+ }
+}
+
+
+/*
+ * Initializes (fills) the WSAPROTOCOL_INFOW structure cache
+ */
+static void wsa_init_proto_info_cache() {
+ WSAPROTOCOL_INFOW *cache = (WSAPROTOCOL_INFOW*)&proto_info_cache;
+
+ wsa_get_proto_info(AF_INET, SOCK_STREAM, IPPROTO_TCP, &proto_info_cache[0]);
+ wsa_get_proto_info(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &proto_info_cache[1]);
+ wsa_get_proto_info(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &proto_info_cache[2]);
+ wsa_get_proto_info(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &proto_info_cache[3]);
+}
+
+
+/*
+ * Initializes winsock and winsock-related stuff
+ */
+void wsa_init() {
+ WORD version = MAKEWORD(2, 2);
+ if (WSAStartup(version, &winsock_info)) {
+ wsa_perror("WSAStartup");
+ }
+
+ wsa_init_proto_info_cache();
+}
+
+
+} // namespace node
diff --git a/src/platform_win32_winsock.h b/src/platform_win32_winsock.h
new file mode 100644
index 0000000000..126ee0dd4d
--- /dev/null
+++ b/src/platform_win32_winsock.h
@@ -0,0 +1,21 @@
+#ifndef NODE_PLATFORM_WIN32_WINSOCK_H_
+#define NODE_PLATFORM_WIN32_WINSOCK_H_
+
+#include <windows.h>
+#include <winsock.h>
+
+namespace node {
+
+
+void wsa_init();
+
+void wsa_perror(const char* prefix = "");
+
+SOCKET wsa_sync_socket(int af, int type, int proto);
+int wsa_socketpair(int af, int type, int proto, SOCKET sock[2]);
+int wsa_sync_async_socketpair(int af, int type, int proto, SOCKET *syncSocket, SOCKET *asyncSocket);
+
+
+} // namespace node
+
+#endif // NODE_PLATFORM_WIN32_WINSOCK_H_