From 348d5eb22d4311688f778c42ef238c1ba2161ecc Mon Sep 17 00:00:00 2001
From: Erich Hoover <ehoover@mines.edu>
Date: Sat, 18 Sep 2010 20:29:32 -0600
Subject: ws2_32: Patch to selectively bind to interfaces while still allowing broadcast packets.

---
 dlls/ws2_32/Makefile.in |    2 +-
 dlls/ws2_32/socket.c    |  245 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 243 insertions(+), 4 deletions(-)

diff --git a/dlls/ws2_32/Makefile.in b/dlls/ws2_32/Makefile.in
index 6321759..947d484 100644
--- a/dlls/ws2_32/Makefile.in
+++ b/dlls/ws2_32/Makefile.in
@@ -6,7 +6,7 @@ VPATH     = @srcdir@
 MODULE    = ws2_32.dll
 IMPORTLIB = ws2_32
 DELAYIMPORTS = iphlpapi user32
-EXTRALIBS = @LIBPOLL@
+EXTRALIBS = @LIBPOLL@ @LIBPTHREAD@
 
 C_SRCS = \
 	async.c \
diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c
index 0335d41..913f192 100644
--- a/dlls/ws2_32/socket.c
+++ b/dlls/ws2_32/socket.c
@@ -30,6 +30,7 @@
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
+#include <semaphore.h>
 #include <sys/types.h>
 #ifdef HAVE_SYS_IPC_H
 # include <sys/ipc.h>
@@ -156,6 +157,9 @@
 # include "wsnwlink.h"
 #endif
 
+#if defined(HAVE_NETINET_IN_H) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
+#    define USE_IP_PKTINFO_FILTER
+#endif
 
 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
 # define sipx_network    sipx_addr.x_net
@@ -710,6 +714,33 @@ static unsigned int _get_sock_mask(SOCKET s)
     return ret;
 }
 
+/* Set and retrieve the interface for IP_PKTINFO filtered sockets */
+#ifdef USE_IP_PKTINFO_FILTER
+static void _set_sock_iface(SOCKET s, int index)
+{
+    SERVER_START_REQ( set_socket_iface )
+    {
+        req->handle = wine_server_obj_handle( SOCKET2HANDLE(s) );
+        req->iface = index;
+        wine_server_call( req );
+    }
+    SERVER_END_REQ;
+}
+static int _get_sock_iface(SOCKET s)
+{
+    int ret;
+
+    SERVER_START_REQ( get_socket_iface )
+    {
+        req->handle = wine_server_obj_handle( SOCKET2HANDLE(s) );
+        wine_server_call( req );
+        ret = reply->iface;
+    }
+    SERVER_END_REQ;
+    return ret;
+}
+#endif /* USE_IP_PKTINFO_FILTER */
+
 static void _sync_sock_state(SOCKET s)
 {
     /* do a dummy wineserver request in order to let
@@ -1392,12 +1423,64 @@ static void WINAPI ws2_async_apc( void *arg, IO_STATUS_BLOCK *iosb, ULONG reserv
 }
 
 /***********************************************************************
+ *              WS2_get_pktinfo                (INTERNAL)
+ *
+ * check_iface is enabled, so this is a socket that might receive broadcast
+ * packets and it is bound to a particular network interface. Therefore, we
+ * need to check the IP_PKTINFO interface to make sure that the datagram
+ * is meant for this interface and throw it out if it is not.
+ */
+#ifdef USE_IP_PKTINFO_FILTER
+static struct in_pktinfo *WS2_get_pktinfo(struct msghdr *hdr)
+{
+    struct cmsghdr *cmsg;
+
+    for (cmsg = CMSG_FIRSTHDR(hdr); cmsg != NULL;
+         cmsg = CMSG_NXTHDR(hdr, cmsg))
+    {
+        if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
+            return (struct in_pktinfo *)CMSG_DATA(cmsg);
+    }
+    WARN("Failed to get IP_PKTINFO structure for a socket with a filter enabled.\n");
+    return NULL;
+}
+
+static sem_t *WS2_lock_sock(int fd)
+{
+    char lockname[100];
+    sem_t *lock;
+
+    snprintf( lockname, sizeof(lockname), "/wine-ifacelock-%d", fd );
+    lock = sem_open( lockname, 0 );
+    if (!lock)
+        return NULL; /* no lock on this file descriptor */
+    sem_wait( lock );
+    return lock;
+}
+
+static void WS2_unlock_sock(sem_t *lock)
+{
+    if (!lock)
+        return;
+    sem_post( lock );
+    sem_close( lock );
+}
+#endif
+
+/***********************************************************************
  *              WS2_recv                (INTERNAL)
  *
  * Workhorse for both synchronous and asynchronous recv() operations.
  */
 static int WS2_recv( int fd, struct ws2_async *wsa )
 {
+#ifdef USE_IP_PKTINFO_FILTER
+    unsigned int check_iface = 0, optlen;
+    int correct_iface = FALSE;
+    int iface_index = 0;
+    sem_t *lock = NULL;
+    char pktbuf[512];
+#endif
     struct msghdr hdr;
     union generic_unix_sockaddr unix_sockaddr;
     int n;
@@ -1414,17 +1497,52 @@ static int WS2_recv( int fd, struct ws2_async *wsa )
 
     hdr.msg_iov = wsa->iovec + wsa->first_iovec;
     hdr.msg_iovlen = wsa->n_iovecs - wsa->first_iovec;
-#ifdef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
+#if defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
     hdr.msg_accrights = NULL;
     hdr.msg_accrightslen = 0;
+#elif defined(USE_IP_PKTINFO_FILTER)
+    hdr.msg_control = pktbuf;
+    hdr.msg_controllen = sizeof(pktbuf);
+    hdr.msg_flags = 0;
+
+    optlen = sizeof(check_iface);
+    getsockopt(fd, IPPROTO_IP, IP_PKTINFO, (void *) &check_iface, &optlen);
+    if (check_iface)
+        iface_index = _get_sock_iface(HANDLE2SOCKET(wsa->hSocket));
 #else
     hdr.msg_control = NULL;
     hdr.msg_controllen = 0;
     hdr.msg_flags = 0;
 #endif
 
+#ifndef USE_IP_PKTINFO_FILTER
     if ( (n = recvmsg(fd, &hdr, wsa->flags)) == -1 )
         return -1;
+#else
+    /* If IP_PKTINFO interface filtering is enabled then throw out any packet
+     * that is not meant for the interface associated with this socket.
+     */
+    lock = WS2_lock_sock(fd);
+    while (!correct_iface)
+    {
+        if ( (n = recvmsg(fd, &hdr, wsa->flags)) == -1 )
+        {
+            WS2_unlock_sock(lock);
+            return -1;
+        }
+        correct_iface = TRUE;
+        if (check_iface)
+        {
+            struct in_pktinfo *pktinfo = WS2_get_pktinfo(&hdr);
+
+            if(pktinfo && pktinfo->ipi_ifindex == iface_index)
+                correct_iface = TRUE;
+            else
+                WARN("Packet not destined for this interface (%d != %d), thrown away.\n", pktinfo->ipi_ifindex, iface_index);
+        }
+    }
+    WS2_unlock_sock(lock);
+#endif
 
     /* if this socket is connected and lpFrom is not NULL, Linux doesn't give us
      * msg_name and msg_namelen from recvmsg, but it does set msg_namelen to zero.
@@ -1490,14 +1608,100 @@ static NTSTATUS WS2_async_recv( void* user, IO_STATUS_BLOCK* iosb, NTSTATUS stat
 }
 
 /***********************************************************************
+ *               WS2_fix_interface_bind          (INTERNAL)
+ *
+ * Replace the interface bind with an IP_PKTINFO filter.
+ */
+#ifdef USE_IP_PKTINFO_FILTER
+void WS2_fix_interface_bind (SOCKET s, union generic_unix_sockaddr *uaddr)
+{
+    struct sockaddr_in *in_sock = (struct sockaddr_in *) uaddr;
+    PIP_ADAPTER_INFO adapters = NULL, adapter;
+    int fd = get_sock_fd( s, 0, NULL );
+    unsigned int sock_type = 0, optlen;
+    DWORD adap_size;
+
+    if (in_sock->sin_addr.s_addr == htonl(WS_INADDR_ANY))
+    {
+        /* Not binding to specific interface, uses default route */
+        goto cleanup;
+    }
+    optlen = sizeof(sock_type);
+    if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &optlen) == -1
+        || sock_type != SOCK_DGRAM)
+    {
+        /* IP_PKTINFO filtering is only valid for UDP datagrams. */
+        goto cleanup;
+    }
+    /* Obtain the size of the IPv4 adapter list, also allocate memory */
+    if (GetAdaptersInfo(NULL, &adap_size) != ERROR_BUFFER_OVERFLOW)
+    {
+        FIXME("Failed to get the size of the adapter list, bound broadcast packets will not work on this socket.\n");
+        goto cleanup;
+    }
+    adapters = HeapAlloc(GetProcessHeap(), 0, adap_size);
+    if (adapters == NULL)
+    {
+        FIXME("Failed to allocate the adapter list, bound broadcast packets will not work on this socket.\n");
+        goto cleanup;
+    }
+    /* Obtain the IPv4 adapter list */
+    if (GetAdaptersInfo(adapters, &adap_size) != NO_ERROR)
+    {
+        FIXME("Failed to obtain the adapter list, bound broadcast packets will not work on this socket.\n");
+        goto cleanup;
+    }
+    /* Search the IPv4 adapter list for the appropriate binding IP */
+    for (adapter = adapters; adapter != NULL; adapter = adapter->Next)
+    {
+        char *ip = adapter->IpAddressList.IpAddress.String;
+        in_addr_t adapter_addr = (in_addr_t) inet_addr(ip);
+
+        if (in_sock->sin_addr.s_addr == adapter_addr)
+        {
+            int enable = 1;
+
+            if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable)) < 0)
+            {
+                /* If we are unable to enable IP_PKTINFO filtering then we cannot
+                 * switch over to INADDR_ANY or we will handle packets destined
+                 * for the wrong interfaces.
+                 */
+                FIXME("setsockopt: setting IP_PKTINFO failed, broadcast packets will not work on this socket.\n");
+            }
+            else
+            {
+                /* Success! Store the interface ID to the socket for proper
+                 * filtering later on and bind this socket to INADDR_ANY.
+                 */
+                TRACE("Bind to local address %s corresponds to interface %d.\n", inet_ntoa(in_sock->sin_addr), adapter->Index);
+                _set_sock_iface(s, adapter->Index);
+                in_sock->sin_addr.s_addr = htonl(WS_INADDR_ANY);
+            }
+            break;
+        }
+    }
+
+cleanup:
+    HeapFree(GetProcessHeap(), 0, adapters);
+    release_sock_fd( s, fd );
+}
+#endif
+
+/***********************************************************************
  *              WS2_send                (INTERNAL)
  *
  * Workhorse for both synchronous and asynchronous send() operations.
  */
 static int WS2_send( int fd, struct ws2_async *wsa )
 {
-    struct msghdr hdr;
+#ifdef USE_IP_PKTINFO_FILTER
+    unsigned int force_iface = 0, optlen;
+    char *buf = NULL, pktbuf[512];
+    unsigned int buflen = 0;
+#endif
     union generic_unix_sockaddr unix_addr;
+    struct msghdr hdr;
 
     hdr.msg_name = NULL;
     hdr.msg_namelen = 0;
@@ -1532,9 +1736,40 @@ static int WS2_send( int fd, struct ws2_async *wsa )
 
     hdr.msg_iov = wsa->iovec + wsa->first_iovec;
     hdr.msg_iovlen = wsa->n_iovecs - wsa->first_iovec;
-#ifdef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
+#if defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
     hdr.msg_accrights = NULL;
     hdr.msg_accrightslen = 0;
+#elif defined(USE_IP_PKTINFO_FILTER)
+    hdr.msg_control = pktbuf;
+    hdr.msg_controllen = sizeof(pktbuf);
+
+    optlen = sizeof(force_iface);
+    getsockopt(fd, IPPROTO_IP, IP_PKTINFO, (void *) &force_iface, &optlen);
+    /* If force_iface is enabled then this is a socket that might send broadcast
+     * packets and is bound to a particular network interface. Therefore, we
+     * must setup the packet routing to use the correct interface.
+     */
+    if (force_iface)
+    {
+        struct in_pktinfo pktinfo;
+        struct cmsghdr *cmsg;
+
+        memset(&pktinfo, 0, sizeof(pktinfo));
+        pktinfo.ipi_ifindex = _get_sock_iface(HANDLE2SOCKET(wsa->hSocket));
+        cmsg = CMSG_FIRSTHDR(&hdr);
+        cmsg->cmsg_level = IPPROTO_IP;
+        cmsg->cmsg_type = IP_PKTINFO;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(pktinfo));
+        memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo));
+        buf = pktbuf;
+        buflen = cmsg->cmsg_len;
+    }
+    else
+        buf = NULL;
+
+    hdr.msg_control = buf;
+    hdr.msg_controllen = buflen;
+    hdr.msg_flags = 0;
 #else
     hdr.msg_control = NULL;
     hdr.msg_controllen = 0;
@@ -1752,6 +1987,10 @@ int WINAPI WS_bind(SOCKET s, const struct WS_sockaddr* name, int namelen)
                     }
                 }
 #endif
+#ifdef USE_IP_PKTINFO_FILTER
+                /* Replace interface-specific bind with a filter using IP_PKTINFO. */
+                WS2_fix_interface_bind(s, &uaddr);
+#endif
                 if (name->sa_family == WS_AF_INET)
                 {
                     struct sockaddr_in *in4 = (struct sockaddr_in*) &uaddr;
-- 
1.7.0.4

