Windows 10 [received support](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/) for AF_UNIX sockets in Insider Build 17063. This merge request adds basic support for AF_UNIX sockets to ws2_32 and wineserver.
Of particular note is the difficulty in handling `sun_path`. Most of the functions that allow for translating Windows paths to Unix paths are not accessible from ws2_32. I considered the following options: * Pass the Windows path to wineserver and do the conversion there. * This is, as far as I can tell, not possible without major rearchitecting. wineserver does not have functions to translate Windows paths to Unix paths, for obvious reasons. * Obtain the current working directory of the requesting process and temporarily change directories to there. * This only handles relative paths and fails for absolute paths, UNC paths, etc. * Conditionally change directories based on whether the path is relative or not. * This is error-prone and wineserver does not have the requisite functions to do this cleanly.
I ultimately decided to pass the translated Unix path to wineserver, which changes directories to `dirname(path)`. It then provides `bind` and `connect` with `basename(path)`. This is not threadsafe, but wineserver is not (currently) multithreaded.
Abstract sockets are supported by this patch.
-- v14: ws2_32/tests: Add test for AF_UNIX sockets. server + ntdll/unix: Move getpeername() implementation to server. server: Introduce error when attempting to connect() to abstract AF_UNIX sockets. server: Allow for deletion of socket files. ws2_32: Add support for AF_UNIX sockets. ntdll/unix: Add support for AF_UNIX sockets to multiple functions. ws2_32: Add afunix.h header.
From: Ally Sommers dropbear.sh@gmail.com
This header is needed for support of AF_UNIX sockets. --- include/Makefile.in | 1 + include/afunix.h | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 include/afunix.h
diff --git a/include/Makefile.in b/include/Makefile.in index e9f0aa8d5fb..fcdeabd2d72 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -11,6 +11,7 @@ SOURCES = \ adshlp.h \ advpub.h \ af_irda.h \ + afunix.h \ amaudio.h \ amsi.idl \ amstream.idl \ diff --git a/include/afunix.h b/include/afunix.h new file mode 100644 index 00000000000..8118dda271b --- /dev/null +++ b/include/afunix.h @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Ally Sommers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef _WS2AFUNIX_ +#define _WS2AFUNIX_ + +#include "windef.h" +#include "ws2def.h" + +#ifdef USE_WS_PREFIX +# define WS(x) WS_##x +#else +# define WS(x) x +#endif + +#define UNIX_PATH_MAX 108 + +typedef struct WS(sockaddr_un) { + USHORT sun_family; + char sun_path[UNIX_PATH_MAX]; +} SOCKADDR_UN, *PSOCKADDR_UN; + +#endif /* _WS2AFUNIX_ */ \ No newline at end of file
From: Ally Sommers dropbear.sh@gmail.com
--- dlls/ntdll/unix/socket.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 4e706323a0a..2f8733c7592 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -66,6 +66,8 @@ # define HAS_IRDA #endif
+#include <sys/un.h> + #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" @@ -77,6 +79,7 @@ #include "ws2tcpip.h" #include "wsipx.h" #include "af_irda.h" +#include "afunix.h" #include "wine/afd.h"
#include "unix_private.h" @@ -105,6 +108,7 @@ union unix_sockaddr #ifdef HAS_IRDA struct sockaddr_irda irda; #endif + struct sockaddr_un un; };
struct async_recv_ioctl @@ -289,6 +293,17 @@ static socklen_t sockaddr_to_unix( const struct WS_sockaddr *wsaddr, int wsaddrl return sizeof(uaddr->in6); }
+ case WS_AF_UNIX: + { + struct WS_sockaddr_un win = {0}; + + if (wsaddrlen < sizeof(win)) return 0; + memcpy( &win, wsaddr, sizeof(win) ); + uaddr->un.sun_family = AF_UNIX; + memcpy( uaddr->un.sun_path, win.sun_path, sizeof(win.sun_path) ); + return sizeof(uaddr->un); + } + default: FIXME( "unknown address family %u\n", wsaddr->sa_family ); return 0; @@ -361,6 +376,17 @@ static int sockaddr_from_unix( const union unix_sockaddr *uaddr, struct WS_socka } #endif
+ case AF_UNIX: + { + struct WS_sockaddr_un win = {0}; + + if (wsaddrlen < sizeof(win)) return -1; + win.sun_family = WS_AF_UNIX; + memcpy( win.sun_path, uaddr->un.sun_path, sizeof(win.sun_path) ); + memcpy( wsaddr, &win, sizeof(win) ); + return sizeof(win); + } + case AF_UNSPEC: return 0;
From: Ally Sommers dropbear.sh@gmail.com
This commit additionally modifies wineserver's sock_ioctl to handle the provided pathname by changing directories and then returning after the native call. This is NOT threadsafe, but wineserver is not multithreaded. --- dlls/ws2_32/socket.c | 68 +++++++++++++++++++++++++++++++--- dlls/ws2_32/ws2_32_private.h | 14 +++++++ server/sock.c | 72 +++++++++++++++++++++++++++++++++++- 3 files changed, 148 insertions(+), 6 deletions(-)
diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index 710ec8cbb26..72c0d177ffc 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -167,6 +167,19 @@ static const WSAPROTOCOL_INFOW supported_protocols[] = .dwMessageSize = UINT_MAX, .szProtocol = L"SPX II", }, + { + .dwServiceFlags1 = XP1_GUARANTEED_DELIVERY | XP1_GUARANTEED_ORDER | XP1_IFS_HANDLES, + .dwProviderFlags = PFL_MATCHES_PROTOCOL_ZERO, + .ProviderId = {0xa00943d9, 0x9c2e, 0x4633, {0x9b, 0x59, 0x00, 0x57, 0xa3, 0x16, 0x09, 0x94}}, + .dwCatalogEntryId = 1007, + .ProtocolChain.ChainLen = 1, + .iVersion = 2, + .iAddressFamily = AF_UNIX, + .iMaxSockAddr = sizeof(struct sockaddr_un), + .iMinSockAddr = sizeof(struct sockaddr_un) - sizeof((struct sockaddr_un){}.sun_path), + .iSocketType = SOCK_STREAM, + .szProtocol = L"AF_UNIX", + } };
DECLARE_CRITICAL_SECTION(cs_socket_list); @@ -226,6 +239,11 @@ const char *debugstr_sockaddr( const struct sockaddr *a ) addr, ((const SOCKADDR_IRDA *)a)->irdaServiceName); } + case AF_UNIX: + { + return wine_dbg_sprintf("{ family AF_UNIX, path %s }", + ((const SOCKADDR_UN *)a)->sun_path); + } default: return wine_dbg_sprintf("{ family %d }", a->sa_family); } @@ -1106,6 +1124,9 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) HANDLE sync_event; NTSTATUS status;
+ char *unix_path = NULL; + int unix_path_len = 0; + TRACE( "socket %#Ix, addr %s\n", s, debugstr_sockaddr(addr) );
if (!addr) @@ -1148,6 +1169,14 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) } break;
+ case AF_UNIX: + if (len < sizeof(struct sockaddr_un)) + { + SetLastError( WSAEFAULT ); + return -1; + } + break; + default: FIXME( "unknown protocol %u\n", addr->sa_family ); SetLastError( WSAEAFNOSUPPORT ); @@ -1156,8 +1185,18 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len )
if (!(sync_event = get_sync_event())) return -1;
- params = malloc( sizeof(int) + len ); - ret_addr = malloc( len ); + if (addr->sa_family == AF_UNIX && *addr->sa_data) + { + WCHAR *sun_pathW = strdupAtoW(addr->sa_data); + unix_path = wine_get_unix_file_name(sun_pathW); + free(sun_pathW); + if (!unix_path) + return SOCKET_ERROR; + unix_path_len = strlen(unix_path) + 1; + } + + params = malloc( sizeof(int) + len + unix_path_len ); + ret_addr = malloc( len + unix_path_len ); if (!params || !ret_addr) { free( params ); @@ -1167,9 +1206,11 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len ) } params->unknown = 0; memcpy( ¶ms->addr, addr, len ); + if (unix_path) + memcpy( (char *)¶ms->addr + len, unix_path, unix_path_len );
status = NtDeviceIoControlFile( (HANDLE)s, sync_event, NULL, NULL, &io, IOCTL_AFD_BIND, - params, sizeof(int) + len, ret_addr, len ); + params, sizeof(int) + len + unix_path_len, ret_addr, len + unix_path_len ); if (status == STATUS_PENDING) { if (WaitForSingleObject( sync_event, INFINITE ) == WAIT_FAILED) @@ -1182,6 +1223,8 @@ int WINAPI bind( SOCKET s, const struct sockaddr *addr, int len )
free( params ); free( ret_addr ); + if (unix_path) + free( unix_path );
SetLastError( NtStatusToWSAError( status ) ); return status ? -1 : 0; @@ -1222,11 +1265,24 @@ int WINAPI connect( SOCKET s, const struct sockaddr *addr, int len ) HANDLE sync_event; NTSTATUS status;
+ char *unix_path = NULL; + int unix_path_len = 0; + TRACE( "socket %#Ix, addr %s, len %d\n", s, debugstr_sockaddr(addr), len );
if (!(sync_event = get_sync_event())) return -1;
- if (!(params = malloc( sizeof(*params) + len ))) + if (addr->sa_family == AF_UNIX && *addr->sa_data) + { + WCHAR *sun_pathW = strdupAtoW(addr->sa_data); + unix_path = wine_get_unix_file_name(sun_pathW); + free(sun_pathW); + if (!unix_path) + return SOCKET_ERROR; + unix_path_len = strlen(unix_path) + 1; + } + + if (!(params = malloc( sizeof(*params) + len + unix_path_len ))) { SetLastError( ERROR_NOT_ENOUGH_MEMORY ); return -1; @@ -1234,9 +1290,11 @@ int WINAPI connect( SOCKET s, const struct sockaddr *addr, int len ) params->addr_len = len; params->synchronous = TRUE; memcpy( params + 1, addr, len ); + if (unix_path_len) + memcpy( (char *)(params + 1) + len, unix_path, unix_path_len );
status = NtDeviceIoControlFile( (HANDLE)s, sync_event, NULL, NULL, &io, IOCTL_AFD_WINE_CONNECT, - params, sizeof(*params) + len, NULL, 0 ); + params, sizeof(*params) + len + unix_path_len, NULL, 0 ); free( params ); if (status == STATUS_PENDING) { diff --git a/dlls/ws2_32/ws2_32_private.h b/dlls/ws2_32/ws2_32_private.h index 41d9e512488..8e5da235801 100644 --- a/dlls/ws2_32/ws2_32_private.h +++ b/dlls/ws2_32/ws2_32_private.h @@ -46,6 +46,7 @@ #include "mstcpip.h" #include "af_irda.h" #include "winnt.h" +#include "afunix.h" #define USE_WC_PREFIX /* For CMSG_DATA */ #include "iphlpapi.h" #include "ip2string.h" @@ -73,6 +74,19 @@ static inline char *strdupWtoA( const WCHAR *str ) return ret; }
+static inline WCHAR *strdupAtoW( const char *str ) +{ + WCHAR *ret = NULL; + if (str) + { + DWORD len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); + if ((ret = malloc( len * sizeof(WCHAR) ))) + MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len); + } + return ret; +} + + static const char magic_loopback_addr[] = {127, 12, 34, 56};
const char *debugstr_sockaddr( const struct sockaddr *addr ) DECLSPEC_HIDDEN; diff --git a/server/sock.c b/server/sock.c index 088e6d63079..35cc755840e 100644 --- a/server/sock.c +++ b/server/sock.c @@ -53,6 +53,7 @@ #include <time.h> #include <unistd.h> #include <limits.h> +#include <libgen.h> #ifdef HAVE_LINUX_FILTER_H # include <linux/filter.h> #endif @@ -83,6 +84,8 @@ # define HAS_IRDA #endif
+#include <sys/un.h> + #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" @@ -93,6 +96,7 @@ #include "ws2tcpip.h" #include "wsipx.h" #include "af_irda.h" +#include "afunix.h" #include "wine/afd.h" #include "wine/rbtree.h"
@@ -116,6 +120,7 @@ union win_sockaddr struct WS_sockaddr_in6 in6; struct WS_sockaddr_ipx ipx; SOCKADDR_IRDA irda; + struct WS_sockaddr_un un; };
union unix_sockaddr @@ -129,6 +134,7 @@ union unix_sockaddr #ifdef HAS_IRDA struct sockaddr_irda irda; #endif + struct sockaddr_un un; };
static struct list poll_list = LIST_INIT( poll_list ); @@ -546,6 +552,17 @@ static int sockaddr_from_unix( const union unix_sockaddr *uaddr, struct WS_socka } #endif
+ case AF_UNIX: + { + struct WS_sockaddr_un win = {0}; + + if (wsaddrlen < sizeof(win)) return -1; + win.sun_family = WS_AF_UNIX; + memcpy( win.sun_path, uaddr->un.sun_path, sizeof(win.sun_path) ); + memcpy( wsaddr, &win, sizeof(win) ); + return sizeof(win); + } + case AF_UNSPEC: return 0;
@@ -646,6 +663,17 @@ static socklen_t sockaddr_to_unix( const struct WS_sockaddr *wsaddr, int wsaddrl return sizeof(uaddr->in6); }
+ case WS_AF_UNIX: + { + struct WS_sockaddr_un win = {0}; + + if (wsaddrlen < sizeof(win.sun_family) + strlen( wsaddr->sa_data ) + 1) return 0; + memcpy( &win, wsaddr, sizeof(win) ); + uaddr->un.sun_family = AF_UNIX; + memcpy( uaddr->un.sun_path, win.sun_path, sizeof(win.sun_path) ); + return sizeof(uaddr->un); + } + default: return 0; } @@ -672,6 +700,9 @@ static socklen_t get_unix_sockaddr_any( union unix_sockaddr *uaddr, int ws_famil uaddr->irda.sir_family = AF_IRDA; return sizeof(uaddr->irda); #endif + case WS_AF_UNIX: + uaddr->un.sun_family = AF_UNIX; + return sizeof(uaddr->un); default: return 0; } @@ -1757,6 +1788,7 @@ static int get_unix_family( int family ) #ifdef AF_IRDA case WS_AF_IRDA: return AF_IRDA; #endif + case WS_AF_UNIX: return AF_UNIX; case WS_AF_UNSPEC: return AF_UNSPEC; default: return -1; } @@ -2607,6 +2639,19 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) if (unix_addr.addr.sa_family == AF_INET && !memcmp( &unix_addr.in.sin_addr, magic_loopback_addr, 4 )) unix_addr.in.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
+ if (sock->family == WS_AF_UNIX && *((char *)(params + 1) + params->addr_len)) + { + char *unix_path = (char *)(params + 1) + params->addr_len; + char unix_path_copy[PATH_MAX]; + strcpy(unix_path_copy, unix_path); + if (chdir( dirname( unix_path_copy ) ) == -1) + { + set_error( sock_get_ntstatus( errno ) ); + return; + } + memcpy( unix_addr.un.sun_path, basename( unix_path ), sizeof(unix_addr.un.sun_path) ); + } + ret = connect( unix_fd, &unix_addr.addr, unix_len ); if (ret < 0 && errno == ECONNABORTED) { @@ -2619,6 +2664,9 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) ret = connect( unix_fd, &unix_addr.addr, unix_len ); }
+ if (sock->family == WS_AF_UNIX && *(char *)(params + 1)) + fchdir(server_dir_fd); + if (ret < 0 && errno != EINPROGRESS) { set_error( sock_get_ntstatus( errno ) ); @@ -2878,7 +2926,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) case IOCTL_AFD_BIND: { const struct afd_bind_params *params = get_req_data(); - union unix_sockaddr unix_addr, bind_addr; + union unix_sockaddr unix_addr, bind_addr, win_addr; data_size_t in_size; socklen_t unix_len; int v6only = 1; @@ -2943,12 +2991,29 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) if (check_addr_usage( sock, &bind_addr, v6only )) return;
+ if (sock->family == WS_AF_UNIX && *params->addr.sa_data) + { + char *unix_path = (char *)¶ms->addr + sizeof(unix_addr.un); + char unix_path_copy[PATH_MAX]; + strcpy(unix_path_copy, unix_path); + if (chdir( dirname( unix_path_copy ) ) == -1) + { + set_error( sock_get_ntstatus( errno ) ); + return; + } + + win_addr = bind_addr; + memcpy( bind_addr.un.sun_path, basename( unix_path ), sizeof(bind_addr.un.sun_path) ); + } + if (bind( unix_fd, &bind_addr.addr, unix_len ) < 0) { if (errno == EADDRINUSE && sock->reuseaddr) errno = EACCES;
set_error( sock_get_ntstatus( errno ) ); + if (sock->family == WS_AF_UNIX && *(char *)(params + 1)) + fchdir(server_dir_fd); return; }
@@ -2961,6 +3026,8 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) * actual unix address */ if (bind_addr.addr.sa_family == AF_INET) bind_addr.in.sin_addr = unix_addr.in.sin_addr; + if (bind_addr.addr.sa_family == AF_UNIX) + bind_addr = win_addr; sock->addr_len = sockaddr_from_unix( &bind_addr, &sock->addr.addr, sizeof(sock->addr) ); }
@@ -2968,6 +3035,9 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async )
if (get_reply_max_size() >= sock->addr_len) set_reply_data( &sock->addr, sock->addr_len ); + + if (sock->family == WS_AF_UNIX && *(char *)(params + 1)) + fchdir(server_dir_fd); return; }
From: Ally Sommers dropbear.sh@gmail.com
Deleting the socket file is a common pattern with AF_UNIX sockets, and is analogous to unbinding. --- server/fd.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+)
diff --git a/server/fd.c b/server/fd.c index eaebe044f37..55d3725b2e2 100644 --- a/server/fd.c +++ b/server/fd.c @@ -1957,6 +1957,24 @@ struct fd *open_fd( struct fd *root, const char *name, struct unicode_str nt_nam fd->unix_fd = open( name, O_RDONLY | (flags & ~(O_TRUNC | O_CREAT | O_EXCL)), *mode ); }
+ /* POSIX requires that open(2) throws EOPNOTSUPP when `path` is a UNIX + * socket. *BSD throws EOPNOTSUPP in this case and the additional case of + * O_SHLOCK or O_EXLOCK being passed when `path` resides on a filesystem + * without lock support. + * + * Contrary to POSIX, Linux returns ENXIO in this case, so we also check + * that error code here. */ + if ((errno == EOPNOTSUPP || errno == ENXIO) && !stat( name, &st ) && S_ISSOCK(st.st_mode)) + { + if ((access & DELETE) && unlink( name )) + { + file_set_error(); + goto error; + } + else + fd->unix_fd = open( name, O_CREAT | O_RDWR, S_IRWXU ); + } + if (fd->unix_fd == -1) { /* check for trailing slash on file path */
From: Ally Sommers dropbear.sh@gmail.com
--- server/sock.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/server/sock.c b/server/sock.c index 35cc755840e..eb6167b78dd 100644 --- a/server/sock.c +++ b/server/sock.c @@ -2639,17 +2639,28 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) if (unix_addr.addr.sa_family == AF_INET && !memcmp( &unix_addr.in.sin_addr, magic_loopback_addr, 4 )) unix_addr.in.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
- if (sock->family == WS_AF_UNIX && *((char *)(params + 1) + params->addr_len)) + if (sock->family == WS_AF_UNIX) { - char *unix_path = (char *)(params + 1) + params->addr_len; - char unix_path_copy[PATH_MAX]; - strcpy(unix_path_copy, unix_path); - if (chdir( dirname( unix_path_copy ) ) == -1) + if (*(char *)(params + 1)) { - set_error( sock_get_ntstatus( errno ) ); + char *unix_path = (char *)(params + 1) + params->addr_len; + char unix_path_copy[PATH_MAX]; + strcpy(unix_path_copy, unix_path); + if (chdir( dirname( unix_path_copy ) ) == -1) + { + set_error( sock_get_ntstatus( errno ) ); + return; + } + memcpy( unix_addr.un.sun_path, basename( unix_path ), sizeof(unix_addr.un.sun_path) ); + } + else + { + /* Contrary to documentation, Windows does not currently support abstract Unix + * sockets. connect() throws WSAEINVAL if sun_family is AF_UNIX and sun_path + * begins with '\0', even though bind() will succeed. */ + set_win32_error( WSAEINVAL ); return; } - memcpy( unix_addr.un.sun_path, basename( unix_path ), sizeof(unix_addr.un.sun_path) ); }
ret = connect( unix_fd, &unix_addr.addr, unix_len );
From: Ally Sommers dropbear.sh@gmail.com
In order to allow for emulating AF_UNIX sockets' addresses, getpeername() is moved to the server to line up with the implementation of getsockname(). --- dlls/ntdll/unix/socket.c | 24 ++----------- server/sock.c | 75 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 27 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 2f8733c7592..ff5a881567a 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -1938,30 +1938,10 @@ NTSTATUS sock_ioctl( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc }
case IOCTL_AFD_WINE_GETPEERNAME: - { - union unix_sockaddr unix_addr; - socklen_t unix_len = sizeof(unix_addr); - int len; - - if ((status = server_get_unix_fd( handle, 0, &fd, &needs_close, NULL, NULL ))) - return status; - - if (getpeername( fd, &unix_addr.addr, &unix_len ) < 0) - { - status = sock_errno_to_status( errno ); - break; - } + if (in_size) FIXME( "unexpected input size %u\n", in_size );
- len = sockaddr_from_unix( &unix_addr, out_buffer, out_size ); - if (out_size < len) - { - status = STATUS_BUFFER_TOO_SMALL; - break; - } - io->Information = len; - status = STATUS_SUCCESS; + status = STATUS_BAD_DEVICE_TYPE; break; - }
case IOCTL_AFD_WINE_GET_SO_BROADCAST: return do_getsockopt( handle, io, SOL_SOCKET, SO_BROADCAST, out_buffer, out_size ); diff --git a/server/sock.c b/server/sock.c index eb6167b78dd..1ccf39e9ee7 100644 --- a/server/sock.c +++ b/server/sock.c @@ -245,6 +245,8 @@ struct sock struct poll_req *main_poll; /* main poll */ union win_sockaddr addr; /* socket name */ int addr_len; /* socket name length */ + union win_sockaddr peer_addr; /* peer name */ + int peer_addr_len; /* peer name length */ unsigned int rcvbuf; /* advisory recv buffer size */ unsigned int sndbuf; /* advisory send buffer size */ unsigned int rcvtimeo; /* receive timeout in ms */ @@ -1749,6 +1751,8 @@ static struct sock *create_socket(void) sock->main_poll = NULL; memset( &sock->addr, 0, sizeof(sock->addr) ); sock->addr_len = 0; + memset( &sock->peer_addr, 0, sizeof(sock->peer_addr) ); + sock->peer_addr_len = 0; sock->rd_shutdown = 0; sock->wr_shutdown = 0; sock->wr_shutdown_pending = 0; @@ -2052,7 +2056,24 @@ static struct sock *accept_socket( struct sock *sock ) } unix_len = sizeof(unix_addr); if (!getsockname( acceptfd, &unix_addr.addr, &unix_len )) - acceptsock->addr_len = sockaddr_from_unix( &unix_addr, &acceptsock->addr.addr, sizeof(acceptsock->addr) ); + { + unix_len = sizeof(unix_addr); + if (sock->family == WS_AF_UNIX) + { + acceptsock->addr_len = sock->addr_len; + acceptsock->addr.un = sock->addr.un; + } + else + { + acceptsock->addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->addr.addr, + sizeof(acceptsock->addr) ); + if (!getpeername( acceptfd, &unix_addr.addr, &unix_len )) + acceptsock->peer_addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->peer_addr.addr, + sizeof(acceptsock->peer_addr) ); + } + } } clear_error(); sock->pending_events &= ~AFD_POLL_ACCEPT; @@ -2108,7 +2129,23 @@ static int accept_into_socket( struct sock *sock, struct sock *acceptsock )
unix_len = sizeof(unix_addr); if (!getsockname( get_unix_fd( newfd ), &unix_addr.addr, &unix_len )) - acceptsock->addr_len = sockaddr_from_unix( &unix_addr, &acceptsock->addr.addr, sizeof(acceptsock->addr) ); + { + if (sock->family == WS_AF_UNIX) + { + acceptsock->addr_len = sock->addr_len; + acceptsock->addr.un = sock->addr.un; + } + else + { + acceptsock->addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->addr.addr, + sizeof(acceptsock->addr) ); + if (!getpeername( get_unix_fd( newfd ), &unix_addr.addr, &unix_len )) + acceptsock->peer_addr_len = sockaddr_from_unix( &unix_addr, + &acceptsock->peer_addr.addr, + sizeof(acceptsock->peer_addr) ); + } + }
clear_error(); sock->pending_events &= ~AFD_POLL_ACCEPT; @@ -2577,7 +2614,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) { const struct afd_connect_params *params = get_req_data(); const struct WS_sockaddr *addr; - union unix_sockaddr unix_addr; + union unix_sockaddr unix_addr, win_addr; struct connect_req *req; socklen_t unix_len; int send_len, ret; @@ -2639,6 +2676,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) if (unix_addr.addr.sa_family == AF_INET && !memcmp( &unix_addr.in.sin_addr, magic_loopback_addr, 4 )) unix_addr.in.sin_addr.s_addr = htonl( INADDR_LOOPBACK );
+ memcpy( &win_addr, &unix_addr, sizeof(unix_addr) ); if (sock->family == WS_AF_UNIX) { if (*(char *)(params + 1)) @@ -2651,6 +2689,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) set_error( sock_get_ntstatus( errno ) ); return; } + win_addr.un = unix_addr.un; memcpy( unix_addr.un.sun_path, basename( unix_path ), sizeof(unix_addr.un.sun_path) ); } else @@ -2689,7 +2728,11 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async )
unix_len = sizeof(unix_addr); if (!getsockname( unix_fd, &unix_addr.addr, &unix_len )) - sock->addr_len = sockaddr_from_unix( &unix_addr, &sock->addr.addr, sizeof(sock->addr) ); + { + if (sock->family != WS_AF_UNIX) + sock->addr_len = sockaddr_from_unix( &unix_addr, &sock->addr.addr, sizeof(sock->addr) ); + sock->peer_addr_len = sockaddr_from_unix( &win_addr, &sock->peer_addr.addr, sizeof(sock->peer_addr)); + } sock->bound = 1;
if (!ret) @@ -3013,7 +3056,7 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) return; }
- win_addr = bind_addr; + win_addr.un = bind_addr.un; memcpy( bind_addr.un.sun_path, basename( unix_path ), sizeof(bind_addr.un.sun_path) ); }
@@ -3068,6 +3111,28 @@ static void sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async ) set_reply_data( &sock->addr, sock->addr_len ); return;
+ case IOCTL_AFD_WINE_GETPEERNAME: + if (sock->state != SOCK_CONNECTED && sock->state != SOCK_CONNECTIONLESS) + { + set_error( STATUS_INVALID_CONNECTION ); + return; + } + + if (!sock->peer_addr_len && sock->type == WS_SOCK_DGRAM) + { + set_error( STATUS_INVALID_CONNECTION ); + return; + } + + if (get_reply_max_size() < sock->peer_addr_len) + { + set_error( STATUS_BUFFER_TOO_SMALL ); + return; + } + + set_reply_data( &sock->peer_addr, sock->peer_addr_len); + return; + case IOCTL_AFD_WINE_DEFER: { const obj_handle_t *handle = get_req_data();
From: Ally Sommers dropbear.sh@gmail.com
--- dlls/ws2_32/tests/sock.c | 228 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 219 insertions(+), 9 deletions(-)
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index cff4acb0b3c..e09265eeb3a 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -29,6 +29,7 @@ #include <iphlpapi.h> #include <ws2tcpip.h> #include <wsipx.h> +#include <afunix.h> #include <wsnwlink.h> #include <mswsock.h> #include <mstcpip.h> @@ -836,7 +837,12 @@ static VOID WINAPI simple_mixed_client ( client_params *par ) ((struct sockaddr_in*)&test)->sin_addr.s_addr = inet_addr("0.0.0.0");
/* Receive data echoed back & check it */ - n_recvd = do_synchronous_recvfrom ( mem->s, mem->recv_buf, n_expected, 0, &test, &fromLen, + n_recvd = do_synchronous_recvfrom ( mem->s, + mem->recv_buf, + n_expected, + 0, + (struct sockaddr *)&test, + &fromLen, par->buflen ); ok ( n_recvd == n_expected, "simple_client (%x): received less data than expected: %d of %d\n", id, n_recvd, n_expected ); @@ -9158,16 +9164,16 @@ static void test_shutdown(void)
addrlen = sizeof(addr); ret = getpeername(client, (struct sockaddr *)&addr, &addrlen); - todo_wine ok(!ret, "got error %u\n", WSAGetLastError()); - todo_wine ok(!memcmp(&addr, &server_addr, sizeof(server_addr)), "address didn't match\n"); + ok(!ret, "got error %u\n", WSAGetLastError()); + ok(!memcmp(&addr, &server_addr, sizeof(server_addr)), "address didn't match\n");
addrlen = sizeof(client_addr); ret = getsockname(client, (struct sockaddr *)&client_addr, &addrlen); ok(!ret, "got error %u\n", WSAGetLastError()); addrlen = sizeof(addr); ret = getpeername(server, (struct sockaddr *)&addr, &addrlen); - todo_wine ok(!ret, "got error %u\n", WSAGetLastError()); - todo_wine ok(!memcmp(&addr, &client_addr, sizeof(addr)), "address didn't match\n"); + ok(!ret, "got error %u\n", WSAGetLastError()); + ok(!memcmp(&addr, &client_addr, sizeof(addr)), "address didn't match\n");
WSASetLastError(0xdeadbeef); ret = connect(client, (struct sockaddr *)&server_addr, sizeof(server_addr)); @@ -9226,16 +9232,16 @@ static void test_shutdown(void)
addrlen = sizeof(addr); ret = getpeername(client, (struct sockaddr *)&addr, &addrlen); - todo_wine ok(!ret, "got error %u\n", WSAGetLastError()); - todo_wine ok(!memcmp(&addr, &server_addr, sizeof(server_addr)), "address didn't match\n"); + ok(!ret, "got error %u\n", WSAGetLastError()); + ok(!memcmp(&addr, &server_addr, sizeof(server_addr)), "address didn't match\n");
addrlen = sizeof(client_addr); ret = getsockname(client, (struct sockaddr *)&client_addr, &addrlen); ok(!ret, "got error %u\n", WSAGetLastError()); addrlen = sizeof(addr); ret = getpeername(server, (struct sockaddr *)&addr, &addrlen); - todo_wine ok(!ret, "got error %u\n", WSAGetLastError()); - todo_wine ok(!memcmp(&addr, &client_addr, sizeof(addr)), "address didn't match\n"); + ok(!ret, "got error %u\n", WSAGetLastError()); + ok(!memcmp(&addr, &client_addr, sizeof(addr)), "address didn't match\n");
closesocket(client); closesocket(server); @@ -13785,6 +13791,209 @@ static void test_connect_udp(void) closesocket(client); }
+struct afunix_thread_param +{ + int sockfd; + SOCKADDR_UN addr; +}; + +static DWORD WINAPI test_afunix_client_connect_thread(void *param) +{ + struct afunix_thread_param *in = param; + ok(!connect(in->sockfd, (SOCKADDR *)&in->addr, sizeof(in->addr)), "Could not connect to Unix socket: %lu\n", + GetLastError()); + return 0; +} + +static void test_afunix(void) +{ + SOCKET listener, client, server; + SOCKADDR_UN addr = { AF_UNIX, "test_afunix.sock" }; + HANDLE clientThread; + const char serverMsg[] = "ws2_32/AF_UNIX socket test"; + char clientBuf[sizeof(serverMsg)] = { 0 }; + + char paths[4][sizeof(addr.sun_path)] = { "./tmp.sock", "../tmp.sock" }; + char dosPath[sizeof(addr.sun_path)]; + WCHAR dosWidePath[sizeof(addr.sun_path)]; + UNICODE_STRING ntPath = { 0 }; + SOCKADDR_UN outAddr = { 0 }; + int ret; + + /* Test connection and send/recv */ + listener = socket(AF_UNIX, SOCK_STREAM, 0); + if (listener == INVALID_SOCKET && GetLastError() == WSAEAFNOSUPPORT) + { + win_skip("AF_UNIX sockets are unsupported, skipping...\n"); + return; + } + + ok(listener != INVALID_SOCKET, "Could not create Unix socket: %lu\n", + GetLastError()); + + ret = bind(listener, (SOCKADDR *)&addr, sizeof(SOCKADDR_UN)); + ok(!ret, "Could not bind Unix socket: %lu\n", GetLastError()); + + ret = listen(listener, 1); + ok(!ret, "Could not listen on Unix socket: %lu\n", GetLastError()); + + client = socket(AF_UNIX, SOCK_STREAM, 0); + ok(client != INVALID_SOCKET, "Failed to create second Unix socket: %lu\n", + GetLastError()); + + clientThread = CreateThread(NULL, 0, test_afunix_client_connect_thread, + &(struct afunix_thread_param){ client, addr }, 0, NULL); + ok(clientThread != NULL, "CreateThread failed unexpectedly: %ld\n", GetLastError()); + server = accept(listener, NULL, NULL); + ok(server != INVALID_SOCKET, "Could not accept Unix socket connection: %lu\n", + GetLastError()); + + WaitForSingleObject(clientThread, 1000); + + ret = send(server, serverMsg, sizeof(serverMsg), 0); + ok(ret == sizeof(serverMsg), "Incorrect return value from send: %d\n", ret); + ret = recv(client, clientBuf, sizeof(serverMsg), 0); + ok(ret == sizeof(serverMsg), "Incorrect return value from recv: %d\n", ret); + ok(!memcmp(serverMsg, clientBuf, sizeof(serverMsg)), "Data mismatch over Unix socket\n"); + + memset(clientBuf, 0, sizeof(clientBuf)); + + ret = sendto(server, serverMsg, sizeof(serverMsg), 0, NULL, 0); + ok(ret == sizeof(serverMsg), "Incorrect return value from sendto: %d\n", ret); + ret = recvfrom(client, clientBuf, sizeof(serverMsg), 0, NULL, 0); + ok(ret == sizeof(serverMsg), "Incorrect return value from recvfrom: %d\n", ret); + ok(!memcmp(serverMsg, clientBuf, sizeof(serverMsg)), "Data mismatch over Unix socket\n"); + + closesocket(listener); + closesocket(client); + closesocket(server); + + /* Test socket file deletion */ + ret = DeleteFileA("test_afunix.sock"); + ok(ret, "DeleteFileA on socket file failed: %lu\n", GetLastError()); + ok(GetFileAttributesA("test_afunix.sock") == INVALID_FILE_ATTRIBUTES && + GetLastError() == ERROR_FILE_NOT_FOUND, + "Failed to delete socket file at path '%s'\n", + addr.sun_path); + + /* Test failure modes */ + listener = socket(AF_UNIX, SOCK_STREAM, 0); + ok(listener != INVALID_SOCKET, "Could not create Unix socket: %lu\n", + GetLastError()); + ret = bind(listener, (SOCKADDR *)&addr, sizeof(SOCKADDR_UN)); + ok(!ret, "Could not bind Unix socket to path '%s': %lu\n", addr.sun_path, GetLastError()); + closesocket(listener); + listener = socket(AF_UNIX, SOCK_STREAM, 0); + ok(listener != INVALID_SOCKET, "Could not create Unix socket: %lu\n", + GetLastError()); + ret = bind(listener, (SOCKADDR *)&addr, sizeof(SOCKADDR_UN)); + ok(ret && GetLastError() == WSAEADDRINUSE, + "Bound Unix socket to path '%s' despite existing socket file: %lu\n", + addr.sun_path, + GetLastError()); + closesocket(listener); + ret = DeleteFileA(addr.sun_path); + ok(ret, "DeleteFileA on socket file failed: %lu\n", GetLastError()); + ok(GetFileAttributesA("test_afunix.sock") == INVALID_FILE_ATTRIBUTES && + GetLastError() == ERROR_FILE_NOT_FOUND, + "Failed to delete socket file at path '%s'\n", + addr.sun_path); + + /* Test different path types (relative, NT, etc.) */ + GetTempPathA(sizeof(paths[0]) - 1, paths[2]); + strcat(paths[2], "tmp.sock"); + MultiByteToWideChar(CP_ACP, 0, paths[2], -1, dosWidePath, sizeof(dosPath)); + RtlDosPathNameToNtPathName_U(dosWidePath, &ntPath, NULL, NULL); + RtlUnicodeToMultiByteN(paths[3], sizeof(addr.sun_path) - 1, NULL, ntPath.Buffer, ntPath.Length); + + for (int i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) + { + memcpy(addr.sun_path, paths[i], sizeof(paths[i])); + + listener = socket(AF_UNIX, SOCK_STREAM, 0); + ok(listener != INVALID_SOCKET, "Could not create Unix socket: %lu\n", + GetLastError()); + + ret = bind(listener, (SOCKADDR *)&addr, sizeof(SOCKADDR_UN)); + ok(!ret, "Could not bind Unix socket to path '%s': %lu\n", addr.sun_path, GetLastError()); + + ret = listen(listener, 1); + ok(!ret, "Could not listen on Unix socket: %lu\n", GetLastError()); + + client = socket(AF_UNIX, SOCK_STREAM, 0); + ok(client != INVALID_SOCKET, "Failed to create second Unix socket: %lu\n", + GetLastError()); + + clientThread = CreateThread(NULL, 0, test_afunix_client_connect_thread, + &(struct afunix_thread_param){ client, addr }, 0, NULL); + ok(clientThread != NULL, "CreateThread failed unexpectedly: %ld\n", GetLastError()); + server = accept(listener, NULL, NULL); + ok(server != INVALID_SOCKET, "Could not accept Unix socket connection: %lu\n", + GetLastError()); + + WaitForSingleObject(clientThread, 1000); + + memset(&outAddr, 0, sizeof(outAddr)); + ret = getsockname(listener, (SOCKADDR *)&outAddr, &(int){sizeof(outAddr)}); + ok(!ret, "Could not get info on Unix socket: %lu\n", GetLastError()); + ok(!memcmp(addr.sun_path, outAddr.sun_path, sizeof(addr.sun_path)), + "getsockname returned incorrect path '%s' for provided path '%s'\n", + outAddr.sun_path, + addr.sun_path); + + memset(&outAddr, 0, sizeof(outAddr)); + ret = getsockname(client, (SOCKADDR *)&outAddr, &(int){sizeof(outAddr)}); + ok(!ret, "Could not get info on Unix socket: %lu\n", GetLastError()); + ok(!memcmp((char[108]){0}, outAddr.sun_path, sizeof(addr.sun_path)), + "getsockname returned incorrect path '%s' for provided path '%s'\n", + outAddr.sun_path, + addr.sun_path); + + memset(&outAddr, 0, sizeof(outAddr)); + ret = getsockname(server, (SOCKADDR *)&outAddr, &(int){sizeof(outAddr)}); + ok(!ret, "Could not get info on Unix socket: %lu\n", GetLastError()); + ok(!memcmp(addr.sun_path, outAddr.sun_path, sizeof(addr.sun_path)), + "getsockname returned incorrect path '%s' for provided path '%s'\n", + outAddr.sun_path, + addr.sun_path); + + memset(&outAddr, 0, sizeof(outAddr)); + ret = getpeername(listener, (SOCKADDR *)&outAddr, &(int){sizeof(outAddr)}); + ok(ret == -1, "Got info on Unix socket: %lu\n", GetLastError()); + ok(!memcmp((char[108]){0}, outAddr.sun_path, sizeof(addr.sun_path)), + "getpeername returned incorrect path '%s' for provided path '%s'\n", + outAddr.sun_path, + addr.sun_path); + + memset(&outAddr, 0, sizeof(outAddr)); + ret = getpeername(client, (SOCKADDR *)&outAddr, &(int){sizeof(outAddr)}); + ok(!ret, "Could not get info on Unix socket: %lu\n", GetLastError()); + ok(!memcmp(addr.sun_path, outAddr.sun_path, sizeof(addr.sun_path)), + "getpeername returned incorrect path '%s' for provided path '%s'\n", + outAddr.sun_path, + addr.sun_path); + + memset(&outAddr, 0, sizeof(outAddr)); + ret = getpeername(server, (SOCKADDR *)&outAddr, &(int){sizeof(outAddr)}); + ok(!ret, "Could not get info on Unix socket: %lu\n", GetLastError()); + ok(!memcmp((char[108]){0}, outAddr.sun_path, sizeof(addr.sun_path)), + "getpeername returned incorrect path '%s' for provided path '%s'\n", + outAddr.sun_path, + addr.sun_path); + + closesocket(listener); + closesocket(client); + closesocket(server); + + ret = DeleteFileA(addr.sun_path); + ok(ret, "DeleteFileA on socket file failed: %lu\n", GetLastError()); + ok(GetFileAttributesA(addr.sun_path) == INVALID_FILE_ATTRIBUTES && + GetLastError() == ERROR_FILE_NOT_FOUND, + "Failed to delete socket file at path '%s'\n", + addr.sun_path); + } +} + START_TEST( sock ) { int i; @@ -13866,6 +14075,7 @@ START_TEST( sock ) test_tcp_reset(); test_icmp(); test_connect_udp(); + test_afunix();
/* this is an io heavy test, do it at the end so the kernel doesn't start dropping packets */ test_send();
On Fri May 19 16:50:44 2023 +0000, Zebediah Figura wrote:
I still don't understand the issue. The sockaddr we pass to the server is already variable-sized. It's passed as variable-sized input data, using the "ioctl" request, with the code IOCTL_AFD_BIND. Why is sockaddr_un any different from sockaddr_in?
The sockaddr passed to the server is indeed variably-sized, but the size for it is set in ws2_32, not ntdll. I am sure that it could be done in ntdll, but as every other function that calls through to `sock_ioctl` sets the vararg size itself, I feel that it is best handled in ws2_32.
On Sat Jun 3 22:54:20 2023 +0000, Ally Sommers wrote:
The sockaddr passed to the server is indeed variably-sized, but the size for it is set in ws2_32, not ntdll. I am sure that it could be done in ntdll, but as every other function that calls through to `sock_ioctl` sets the vararg size itself, I feel that it is best handled in ws2_32.
Well, sure, ws2_32 sets the size everywhere else, because there aren't any other instances where ntdll needs to manhandle the arguments.
That said, it probably does make the most sense to do the path conversion from ws2, since converting an ioctl does strike me as awkward, and I don't think there's a good reason to avoid it at this rate.
On Sat Jun 3 22:38:31 2023 +0000, Ally Sommers wrote:
Windows indeed does not modify the path. `getpeername` and `getsockname` return the original path, even if it is relative or prefixed. Notably, Windows itself does not implement `accept` correctly, as it does not fill in the path but does set `sun_family` to `AF_UNIX`. I think punting on `getpeername` with regard to Windows <-> Unix sockets is fine since there is absolutely no precedent there and Wine doesn't even have the infrastructure to test a resolving implementation, but `getpeername` correctly returning the path of Windows-created AF_UNIX sockets should be implemented, in this or a following merge request. Given this, I reaffirm my opinion that forwarding `getpeername` to `sock_ioctl` is the cleanest way to handle this, and my next push will include an implementation of that. I can split it off into its own merge request if the changes made are deemed too extensive to be included here.
Wow, that is awkward.
I would opine that getpeername() should be stubbed out for all AF_UNIX sockets, regardless of who created the peer. Even if it's possible to trace the peer back to a struct sock, it strikes me as an extra bit of complication that probably won't be used.
On Mon Jun 5 04:55:14 2023 +0000, Zebediah Figura wrote:
Wow, that is awkward. I would opine that getpeername() should be stubbed out for all AF_UNIX sockets, regardless of who created the peer. Even if it's possible to trace the peer back to a struct sock, it strikes me as an extra bit of complication that probably won't be used.
I feel that having correct `getpeername` functionality is worthwhile, and the implementation does not cause regressions elsewhere. While it does require a somewhat significant restructuring, it fixed a few unrelated TODOs in other tests.
That being said, since little that was specific to Unix sockets needed to be changed to achieve correct `getpeername` functionality, I think it would be best to explore this further in a separate merge request more focused on `getpeername` itself.
I will push a new revision without the changes to `getpeername` and wineserver's `struct sock`, and without the new tests of `getpeername` in `test_afunix` (this will be almost identical to the revision before the most recent one).