diff options
author | Bert Belder <bertbelder@gmail.com> | 2010-11-30 17:07:09 +0100 |
---|---|---|
committer | Bert Belder <bertbelder@gmail.com> | 2010-12-20 23:51:29 +0100 |
commit | bd62203d13769a0422078b6b690f2826d2209c63 (patch) | |
tree | 9c2c10053f1aa1dae73c9b3bd195d183dbdef111 | |
parent | d66cf5f32cc8bcfa73b32b8a95911cc46c52b6bf (diff) | |
download | node-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.win32 | 3 | ||||
-rw-r--r-- | src/node.cc | 14 | ||||
-rw-r--r-- | src/node_net.cc | 14 | ||||
-rw-r--r-- | src/platform_win32.cc | 1 | ||||
-rw-r--r-- | src/platform_win32_winsock.cc | 331 | ||||
-rw-r--r-- | src/platform_win32_winsock.h | 21 |
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_
|