[PATCH v2 2/7] server: Partially implement IOCTL_AFD_BIND.

Zebediah Figura z.figura12 at gmail.com
Tue Jun 15 23:13:27 CDT 2021


Signed-off-by: Zebediah Figura <z.figura12 at gmail.com>
---
 dlls/ntdll/unix/socket.c |  10 ++
 include/wine/afd.h       |   7 +
 server/sock.c            | 302 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 318 insertions(+), 1 deletion(-)

diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c
index 3b8206474f0..0d77e417148 100644
--- a/dlls/ntdll/unix/socket.c
+++ b/dlls/ntdll/unix/socket.c
@@ -1157,6 +1157,16 @@ NTSTATUS sock_ioctl( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc
 
     switch (code)
     {
+        case IOCTL_AFD_BIND:
+        {
+            const struct afd_bind_params *params = in_buffer;
+
+            if (params->unknown) FIXME( "bind: got unknown %#x\n", params->unknown );
+
+            status = STATUS_BAD_DEVICE_TYPE;
+            break;
+        }
+
         case IOCTL_AFD_LISTEN:
         {
             const struct afd_listen_params *params = in_buffer;
diff --git a/include/wine/afd.h b/include/wine/afd.h
index 35d047c59d8..87da44ecca7 100644
--- a/include/wine/afd.h
+++ b/include/wine/afd.h
@@ -32,6 +32,7 @@
 # define WS(x)    x
 #endif
 
+#define IOCTL_AFD_BIND                      CTL_CODE(FILE_DEVICE_BEEP, 0x800, METHOD_NEITHER,  FILE_ANY_ACCESS)
 #define IOCTL_AFD_LISTEN                    CTL_CODE(FILE_DEVICE_BEEP, 0x802, METHOD_NEITHER,  FILE_ANY_ACCESS)
 #define IOCTL_AFD_RECV                      CTL_CODE(FILE_DEVICE_BEEP, 0x805, METHOD_NEITHER,  FILE_ANY_ACCESS)
 #define IOCTL_AFD_POLL                      CTL_CODE(FILE_DEVICE_BEEP, 0x809, METHOD_BUFFERED, FILE_ANY_ACCESS)
@@ -70,6 +71,12 @@ enum afd_poll_bit
 #define AFD_POLL_UNK1           0x0200
 #define AFD_POLL_UNK2           0x0400
 
+struct afd_bind_params
+{
+    int unknown;
+    struct WS(sockaddr) addr; /* variable size */
+};
+
 struct afd_listen_params
 {
     int unknown1;
diff --git a/server/sock.c b/server/sock.c
index a3062cff8ab..425c07566ac 100644
--- a/server/sock.c
+++ b/server/sock.c
@@ -30,6 +30,12 @@
 #include <string.h>
 #include <stdlib.h>
 #include <errno.h>
+#ifdef HAVE_IFADDRS_H
+# include <ifaddrs.h>
+#endif
+#ifdef HAVE_NET_IF_H
+# include <net/if.h>
+#endif
 #ifdef HAVE_NETINET_IN_H
 # include <netinet/in.h>
 #endif
@@ -50,6 +56,9 @@
 #include <time.h>
 #include <unistd.h>
 #include <limits.h>
+#ifdef HAVE_LINUX_FILTER_H
+# include <linux/filter.h>
+#endif
 #ifdef HAVE_LINUX_RTNETLINK_H
 # include <linux/rtnetlink.h>
 #endif
@@ -96,6 +105,10 @@
 #include "request.h"
 #include "user.h"
 
+#if defined(linux) && !defined(IP_UNICAST_IF)
+#define IP_UNICAST_IF 50
+#endif
+
 static struct list poll_list = LIST_INIT( poll_list );
 
 struct poll_req
@@ -338,6 +351,108 @@ static int sockaddr_from_unix( const union unix_sockaddr *uaddr, struct WS_socka
     }
 }
 
+static socklen_t sockaddr_to_unix( const struct WS_sockaddr *wsaddr, int wsaddrlen, union unix_sockaddr *uaddr )
+{
+    memset( uaddr, 0, sizeof(*uaddr) );
+
+    switch (wsaddr->sa_family)
+    {
+    case WS_AF_INET:
+    {
+        struct WS_sockaddr_in win = {0};
+
+        if (wsaddrlen < sizeof(win)) return 0;
+        memcpy( &win, wsaddr, sizeof(win) );
+        uaddr->in.sin_family = AF_INET;
+        uaddr->in.sin_port = win.sin_port;
+        memcpy( &uaddr->in.sin_addr, &win.sin_addr, sizeof(win.sin_addr) );
+        return sizeof(uaddr->in);
+    }
+
+    case WS_AF_INET6:
+    {
+        struct WS_sockaddr_in6 win = {0};
+
+        if (wsaddrlen < sizeof(struct WS_sockaddr_in6_old)) return 0;
+        if (wsaddrlen < sizeof(struct WS_sockaddr_in6))
+            memcpy( &win, wsaddr, sizeof(struct WS_sockaddr_in6_old) );
+        else
+            memcpy( &win, wsaddr, sizeof(struct WS_sockaddr_in6) );
+
+        uaddr->in6.sin6_family = AF_INET6;
+        uaddr->in6.sin6_port = win.sin6_port;
+        uaddr->in6.sin6_flowinfo = win.sin6_flowinfo;
+        memcpy( &uaddr->in6.sin6_addr, &win.sin6_addr, sizeof(win.sin6_addr) );
+#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
+        if (wsaddrlen >= sizeof(struct WS_sockaddr_in6))
+            uaddr->in6.sin6_scope_id = win.sin6_scope_id;
+#endif
+        return sizeof(uaddr->in6);
+    }
+
+#ifdef HAS_IPX
+    case WS_AF_IPX:
+    {
+        struct WS_sockaddr_ipx win = {0};
+
+        if (wsaddrlen < sizeof(win)) return 0;
+        memcpy( &win, wsaddr, sizeof(win) );
+        uaddr->ipx.sipx_family = AF_IPX;
+        memcpy( &uaddr->ipx.sipx_network, win.sa_netnum, sizeof(win.sa_netnum) );
+        memcpy( &uaddr->ipx.sipx_node, win.sa_nodenum, sizeof(win.sa_nodenum) );
+        uaddr->ipx.sipx_port = win.sa_socket;
+        return sizeof(uaddr->ipx);
+    }
+#endif
+
+#ifdef HAS_IRDA
+    case WS_AF_IRDA:
+    {
+        SOCKADDR_IRDA win = {0};
+        unsigned int lsap_sel;
+
+        if (wsaddrlen < sizeof(win)) return 0;
+        memcpy( &win, wsaddr, sizeof(win) );
+        uaddr->irda.sir_family = AF_IRDA;
+        if (sscanf( win.irdaServiceName, "LSAP-SEL%u", &lsap_sel ) == 1)
+            uaddr->sir_lsap_sel = lsap_sel;
+        else
+        {
+            uaddr->sir_lsap_sel = LSAP_ANY;
+            memcpy( uaddr->irda.sir_name, win.irdaServiceName, sizeof(win.irdaServiceName) );
+        }
+        memcpy( &uaddr->irda.sir_addr, win.irdaDeviceID, sizeof(win.irdaDeviceID) );
+        return sizeof(uaddr->irda);
+    }
+#endif
+
+    case WS_AF_UNSPEC:
+        switch (wsaddrlen)
+        {
+        default: /* likely an ipv4 address */
+        case sizeof(struct WS_sockaddr_in):
+            return sizeof(uaddr->in);
+
+#ifdef HAS_IPX
+        case sizeof(struct WS_sockaddr_ipx):
+            return sizeof(uaddr->ipx);
+#endif
+
+#ifdef HAS_IRDA
+        case sizeof(SOCKADDR_IRDA):
+            return sizeof(uaddr->irda);
+#endif
+
+        case sizeof(struct WS_sockaddr_in6):
+        case sizeof(struct WS_sockaddr_in6_old):
+            return sizeof(uaddr->in6);
+        }
+
+    default:
+        return 0;
+    }
+}
+
 /* some events are generated at the same time but must be sent in a particular
  * order (e.g. CONNECT must be sent before READ) */
 static const enum afd_poll_bit event_bitorder[] =
@@ -1551,6 +1666,137 @@ static int accept_into_socket( struct sock *sock, struct sock *acceptsock )
     return TRUE;
 }
 
+#ifdef IP_BOUND_IF
+
+static int bind_to_index( int fd, in_addr_t bind_addr, unsigned int index )
+{
+    return setsockopt( fd, IPPROTO_IP, IP_BOUND_IF, &index, sizeof(index) );
+}
+
+#elif defined(IP_UNICAST_IF) && defined(SO_ATTACH_FILTER)
+
+struct interface_filter
+{
+    struct sock_filter iface_memaddr;
+    struct sock_filter iface_rule;
+    struct sock_filter ip_memaddr;
+    struct sock_filter ip_rule;
+    struct sock_filter return_keep;
+    struct sock_filter return_dump;
+};
+# define FILTER_JUMP_DUMP(here)  (u_char)(offsetof(struct interface_filter, return_dump) \
+                                 -offsetof(struct interface_filter, here)-sizeof(struct sock_filter)) \
+                                 /sizeof(struct sock_filter)
+# define FILTER_JUMP_KEEP(here)  (u_char)(offsetof(struct interface_filter, return_keep) \
+                                 -offsetof(struct interface_filter, here)-sizeof(struct sock_filter)) \
+                                 /sizeof(struct sock_filter)
+# define FILTER_JUMP_NEXT()      (u_char)(0)
+# define SKF_NET_DESTIP          16 /* offset in the network header to the destination IP */
+static struct interface_filter generic_interface_filter =
+{
+    /* This filter rule allows incoming packets on the specified interface, which works for all
+     * remotely generated packets and for locally generated broadcast packets. */
+    BPF_STMT(BPF_LD+BPF_W+BPF_ABS, SKF_AD_OFF+SKF_AD_IFINDEX),
+    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0xdeadbeef, FILTER_JUMP_KEEP(iface_rule), FILTER_JUMP_NEXT()),
+    /* This rule allows locally generated packets targeted at the specific IP address of the chosen
+     * adapter (local packets not destined for the broadcast address do not have IFINDEX set) */
+    BPF_STMT(BPF_LD+BPF_W+BPF_ABS, SKF_NET_OFF+SKF_NET_DESTIP),
+    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0xdeadbeef, FILTER_JUMP_KEEP(ip_rule), FILTER_JUMP_DUMP(ip_rule)),
+    BPF_STMT(BPF_RET+BPF_K, (u_int)-1), /* keep packet */
+    BPF_STMT(BPF_RET+BPF_K, 0)          /* dump packet */
+};
+
+static int bind_to_index( int fd, in_addr_t bind_addr, unsigned int index )
+{
+    in_addr_t ifindex = htonl( index );
+    struct interface_filter specific_interface_filter;
+    struct sock_fprog filter_prog;
+    int ret;
+
+    if ((ret = setsockopt( fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex) )) < 0)
+        return ret;
+
+    specific_interface_filter = generic_interface_filter;
+    specific_interface_filter.iface_rule.k = index;
+    specific_interface_filter.ip_rule.k = htonl( bind_addr );
+    filter_prog.len = sizeof(generic_interface_filter) / sizeof(struct sock_filter);
+    filter_prog.filter = (struct sock_filter *)&specific_interface_filter;
+    return setsockopt( fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog) );
+}
+
+#else
+
+static int bind_to_index( int fd, in_addr_t bind_addr, unsigned int index )
+{
+    errno = EOPNOTSUPP;
+    return -1;
+}
+
+#endif /* LINUX_BOUND_IF */
+
+/* Take bind() calls on any name corresponding to a local network adapter and
+ * restrict the given socket to operating only on the specified interface. This
+ * restriction consists of two components:
+ *  1) An outgoing packet restriction suggesting the egress interface for all
+ *     packets.
+ *  2) An incoming packet restriction dropping packets not meant for the
+ *     interface.
+ * If the function succeeds in placing these restrictions, then the name for the
+ * bind() may safely be changed to INADDR_ANY, permitting the transmission and
+ * receipt of broadcast packets on the socket. This behavior is only relevant to
+ * UDP sockets and is needed for applications that expect to be able to receive
+ * broadcast packets on a socket that is bound to a specific network interface.
+ */
+static int bind_to_interface( struct sock *sock, const struct sockaddr_in *addr )
+{
+    in_addr_t bind_addr = addr->sin_addr.s_addr;
+    struct ifaddrs *ifaddrs, *ifaddr;
+    int fd = get_unix_fd( sock->fd );
+    static const int enable = 1;
+    unsigned int index;
+
+    if (bind_addr == htonl( INADDR_ANY ) || bind_addr == htonl( INADDR_LOOPBACK ))
+        return 0;
+    if (sock->type != WS_SOCK_DGRAM)
+        return 0;
+
+    if (getifaddrs( &ifaddrs ) < 0) return 0;
+
+    for (ifaddr = ifaddrs; ifaddr != NULL; ifaddr = ifaddr->ifa_next)
+    {
+        if (ifaddr->ifa_addr && ifaddr->ifa_addr->sa_family == AF_INET
+                && ((struct sockaddr_in *)ifaddr->ifa_addr)->sin_addr.s_addr == bind_addr)
+        {
+            index = if_nametoindex( ifaddr->ifa_name );
+            if (!index)
+            {
+                if (debug_level)
+                    fprintf( stderr, "Unable to look up interface index for %s: %s\n",
+                             ifaddr->ifa_name, strerror( errno ) );
+                continue;
+            }
+
+            freeifaddrs( ifaddrs );
+
+            if (bind_to_index( fd, bind_addr, index ) < 0)
+            {
+                if (debug_level)
+                    fprintf( stderr, "failed to bind to interface: %s\n", strerror( errno ) );
+                return 0;
+            }
+
+            if (setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable) ) < 0)
+            {
+                if (debug_level)
+                    fprintf( stderr, "failed to reuse address: %s\n", strerror( errno ) );
+                return 0;
+            }
+            return 1;
+        }
+    }
+    return 0;
+}
+
 /* return an errno value mapped to a WSA error */
 static unsigned int sock_get_error( int err )
 {
@@ -1646,7 +1892,7 @@ static int sock_get_ntstatus( int err )
         case ENOPROTOOPT:       return STATUS_INVALID_PARAMETER;
         case EOPNOTSUPP:        return STATUS_NOT_SUPPORTED;
         case EADDRINUSE:        return STATUS_SHARING_VIOLATION;
-        case EADDRNOTAVAIL:     return STATUS_INVALID_PARAMETER;
+        case EADDRNOTAVAIL:     return STATUS_INVALID_ADDRESS_COMPONENT;
         case ECONNREFUSED:      return STATUS_CONNECTION_REFUSED;
         case ESHUTDOWN:         return STATUS_PIPE_DISCONNECTED;
         case ENOTCONN:          return STATUS_INVALID_CONNECTION;
@@ -2102,6 +2348,60 @@ static int sock_ioctl( struct fd *fd, ioctl_code_t code, struct async *async )
         return 1;
     }
 
+    case IOCTL_AFD_BIND:
+    {
+        const struct afd_bind_params *params = get_req_data();
+        union unix_sockaddr unix_addr;
+        data_size_t in_size;
+        socklen_t unix_len;
+
+        /* the ioctl is METHOD_NEITHER, so ntdll gives us the output buffer as
+         * input */
+        if (get_req_data_size() < get_reply_max_size())
+        {
+            set_error( STATUS_BUFFER_TOO_SMALL );
+            return 0;
+        }
+        in_size = get_req_data_size() - get_reply_max_size();
+        if (in_size < offsetof(struct afd_bind_params, addr.sa_data)
+                || get_reply_max_size() < sizeof(struct WS_sockaddr))
+        {
+            set_error( STATUS_INVALID_PARAMETER );
+            return 0;
+        }
+
+        unix_len = sockaddr_to_unix( &params->addr, in_size - sizeof(int), &unix_addr );
+        if (!unix_len)
+        {
+            set_error( STATUS_INVALID_ADDRESS );
+            return 0;
+        }
+
+        if (unix_addr.addr.sa_family == WS_AF_INET)
+        {
+            static const char magic_loopback_addr[] = {127, 12, 34, 56};
+
+            if (!memcmp( &unix_addr.in.sin_addr, magic_loopback_addr, 4 )
+                    || bind_to_interface( sock, &unix_addr.in ))
+                unix_addr.in.sin_addr.s_addr = htonl( INADDR_ANY );
+        }
+
+        if (bind( unix_fd, &unix_addr.addr, unix_len ) < 0)
+        {
+            if (errno == EADDRINUSE)
+            {
+                int reuse;
+                socklen_t len = sizeof(reuse);
+
+                if (!getsockopt( unix_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, &len ) && reuse)
+                    errno = EACCES;
+            }
+
+            set_error( sock_get_ntstatus( errno ) );
+        }
+        return 1;
+    }
+
     default:
         set_error( STATUS_NOT_SUPPORTED );
         return 0;
-- 
2.30.2




More information about the wine-devel mailing list