From 323689a8b9527107faede810f71e1f6dcb6a6271 Mon Sep 17 00:00:00 2001 From: Daniel Lenski Date: Tue, 18 Jan 2022 10:02:39 -0800 Subject: [PATCH] Attempt to use AF_UNIX sockets instead of AF_INET on Windows 10+ This PR is based on the improvements we made in OpenConnect in https://gitlab.com/openconnect/openconnect/-/merge_requests/320. Unfortunately, and maddeningly, it's possible for the local IPv4 routes (127.0.0.0/8) to be deleted on Windows; this will prevent dumb_socketpair() from working in its current form. Using AF_UNIX sockets - AF_UNIX/SOCK_STREAM became available in Windows 10: https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows - There appears to be no other documentation whatsoever of Windows's support for AF_UNIX sockets. - Contrary to the claims of that blog post, abstract sockets (filesystem-independent) are not implemented: https://github.com/microsoft/WSL/issues/4240#issuecomment-549663217 In order to deal with the above weaknesses, the approach taken here is to first try creating an AF_UNIX socketpair, and then to fallback to AF_INET if it fails. Furthermore, because the AF_UNIX socketpair requires a real, writable filesystem path, it tries binding to each of the following before giving up on AF_UNIX: 1. GetTempDir() \ (filename including PID and 64-bit time ticks) 2. GetWindowsDirectory() \ Temp \ (...) 3. C:\TEMP \ (...) 4. . \ (...) Based on testing so far, this implementation appears to be robust and to avoid the problems with deleted IPv4 localhost routes. Also adds a stub for the , which defines `UNIX_PATH_MAX` and `struct sockaddr_un`. MinGW lacks this header, but other FLOSS projects show how to embed the needed definitions: - https://github.com/MisterDA/ocaml/commit/5855ce5ffd931a2802d5b9a5b2987ab0b276fd0a - https://github.com/curl/curl/blob/curl-7_74_0/lib/config-win32.h#L725-L734 --- dummy_headers/afunix.h | 6 ++ socketpair.c | 144 ++++++++++++++++++++++++++++++----------- 2 files changed, 114 insertions(+), 36 deletions(-) create mode 100644 dummy_headers/afunix.h diff --git a/dummy_headers/afunix.h b/dummy_headers/afunix.h new file mode 100644 index 0000000..fbee6f5 --- /dev/null +++ b/dummy_headers/afunix.h @@ -0,0 +1,6 @@ +#define UNIX_PATH_MAX 108 +struct sockaddr_un +{ + ADDRESS_FAMILY sun_family; /* AF_UNIX */ + char sun_path[UNIX_PATH_MAX]; /* pathname */ +} SOCKADDR_UN, *PSOCKADDR_UN;; diff --git a/socketpair.c b/socketpair.c index ededc9f..8b3e441 100644 --- a/socketpair.c +++ b/socketpair.c @@ -49,11 +49,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include +#include #ifdef WIN32 # include /* socklen_t, et al (MSVC20xx) */ # include # include +# include #else # include # include @@ -73,12 +75,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. int dumb_socketpair(SOCKET socks[2], int make_overlapped) { union { + struct sockaddr_un unaddr; struct sockaddr_in inaddr; struct sockaddr addr; } a; SOCKET listener; - int e; - socklen_t addrlen = sizeof(a.inaddr); + int e, ii; + int domain = AF_UNIX; + socklen_t addrlen = sizeof(a.unaddr); DWORD flags = (make_overlapped ? WSA_FLAG_OVERLAPPED : 0); int reuse = 1; @@ -88,52 +92,120 @@ int dumb_socketpair(SOCKET socks[2], int make_overlapped) } socks[0] = socks[1] = -1; - listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (listener == -1) - return SOCKET_ERROR; - - memset(&a, 0, sizeof(a)); - a.inaddr.sin_family = AF_INET; - a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - a.inaddr.sin_port = 0; - - for (;;) { - if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, - (char*) &reuse, (socklen_t) sizeof(reuse)) == -1) - break; - if (bind(listener, &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) - break; + /* AF_UNIX/SOCK_STREAM became available in Windows 10 + * ( https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows ) + * + * We will attempt to use AF_UNIX, but fallback to using AF_INET if + * setting up AF_UNIX socket fails in any other way, which it surely will + * on earlier versions of Windows. + */ + for (ii = 0; ii < 2; ii++) { + listener = socket(domain, SOCK_STREAM, domain == AF_INET ? IPPROTO_TCP : 0); + if (listener == INVALID_SOCKET) + goto fallback; memset(&a, 0, sizeof(a)); - if (getsockname(listener, &a.addr, &addrlen) == SOCKET_ERROR) - break; - // win32 getsockname may only set the port number, p=0.0005. - // ( http://msdn.microsoft.com/library/ms738543.aspx ): - a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - a.inaddr.sin_family = AF_INET; + if (domain == AF_UNIX) { + /* Abstract sockets (filesystem-independent) don't work, contrary to + * the claims of the aforementioned blog post: + * ( https://github.com/microsoft/WSL/issues/4240#issuecomment-549663217 ) + * + * So we must use a named path, and that comes with all the attendant + * problems of permissions and collisions. Trying various temporary + * directories and putting high-res time and PID in the filename, that + * seems like a less-bad option. + */ + LARGE_INTEGER ticks; + DWORD n; + int bind_try = 0; + + for (;;) { + switch (bind_try++) { + case 0: + /* "The returned string ends with a backslash" */ + n = GetTempPath(UNIX_PATH_MAX, a.unaddr.sun_path); + break; + case 1: + /* Heckuva job with API consistency, Microsoft! Reversed argument order, and + * "This path does not end with a backslash unless the Windows directory is the root directory.." + */ + n = GetWindowsDirectory(a.unaddr.sun_path, UNIX_PATH_MAX); + n += snprintf(a.unaddr.sun_path + n, UNIX_PATH_MAX - n, "\\Temp\\"); + break; + case 2: + n = snprintf(a.unaddr.sun_path, UNIX_PATH_MAX, "C:\\Temp\\"); + break; + case 3: + n = 0; /* Current directory */ + break; + case 4: + goto fallback; + } + + /* GetTempFileName could be used here. + * ( https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea ) + * However it only adds 16 bits of time-based random bits, + * fails if there isn't room for a 14-character filename, and + * seems to offers no other apparent advantages. So we will + * use high-res timer ticks and PID for filename. + */ + QueryPerformanceCounter(&ticks); + snprintf(a.unaddr.sun_path + n, UNIX_PATH_MAX - n, + "%"PRIx64"-%"PRId32".$$$", ticks.QuadPart, GetCurrentProcessId()); + a.unaddr.sun_family = AF_UNIX; + + if (bind(listener, &a.addr, addrlen) != SOCKET_ERROR) + break; + } + } else { + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0; + + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, + (char *) &reuse, (socklen_t) sizeof(reuse)) == -1) + goto fallback;; + + if (bind(listener, &a.addr, addrlen) == SOCKET_ERROR) + goto fallback; + + memset(&a, 0, sizeof(a)); + if (getsockname(listener, &a.addr, &addrlen) == SOCKET_ERROR) + goto fallback; + + // win32 getsockname may only set the port number, p=0.0005. + // ( https://docs.microsoft.com/windows/win32/api/winsock/nf-winsock-getsockname ): + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_family = AF_INET; + } if (listen(listener, 1) == SOCKET_ERROR) - break; + goto fallback; - socks[0] = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, flags); - if (socks[0] == -1) - break; - if (connect(socks[0], &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) - break; + socks[0] = WSASocket(domain, SOCK_STREAM, 0, NULL, 0, flags); + if (socks[0] == INVALID_SOCKET) + goto fallback; + if (connect(socks[0], &a.addr, addrlen) == SOCKET_ERROR) + goto fallback; socks[1] = accept(listener, NULL, NULL); - if (socks[1] == -1) - break; + if (socks[1] == INVALID_SOCKET) + goto fallback; closesocket(listener); return 0; + + fallback: + domain = AF_INET; + addrlen = sizeof(a.inaddr); + + e = WSAGetLastError(); + closesocket(listener); + closesocket(socks[0]); + closesocket(socks[1]); + WSASetLastError(e); } - e = WSAGetLastError(); - closesocket(listener); - closesocket(socks[0]); - closesocket(socks[1]); - WSASetLastError(e); socks[0] = socks[1] = -1; return SOCKET_ERROR; }