[prev in list] [next in list] [prev in thread] [next in thread] 

List:       wine-devel
Subject:    Review Request: Patch to selectively bind to interfaces while still
From:       Erich Hoover <ehoover () mines ! edu>
Date:       2009-09-28 18:55:49
Message-ID: 4c1ac96d0909281155l133c70e8ob940c177bad35f55 () mail ! gmail ! com
[Download RAW message or body]

[Attachment #2 (multipart/alternative)]


Attached is a patch I have been working to fix Bug #7929 (C&C 3 network does
not work).  This patch replaces interface-specific bind requests (on UDP
datagrams only) with a global bind and stores the interface index.  The
patch then uses IP_PKTINFO to set the outgoing interface and filter incoming
packets based on interface (checking against the stored interface index from
the initial bind).  Additionally, the patch peeks at packets in "select" and
asynchronous I/O operations to ensure that spurious wakeups do not occur
when a packet comes in from the wrong interface.

I intend to split the patch into the following pieces:
1) UDP broadcast tests (used for making sure everything works)
2) The "bare essentials" (storing and retrieving the interface index, and
setting/filtering the interface)
3) Ensure "select" does not wake up on packets with an interface mismatch
4) Ensure Async WSARecv does not wake up on packets with an interface
mismatch
5) Ensure Async ReadFile does not wake up on packets with an interface
mismatch

I would greatly appreciate any comments people have on this patch.  Thanks
so much!

Erich Hoover
ehoover@mines.edu

[Attachment #5 (text/html)]

Attached is a patch I have been working to fix Bug #7929 (C&amp;C 3 network does not \
work).  This patch replaces interface-specific bind requests (on UDP datagrams only) \
with a global bind and stores the interface index.  The patch then uses IP_PKTINFO to \
set the outgoing interface and filter incoming packets based on interface (checking \
against the stored interface index from the initial bind).  Additionally, the patch \
peeks at packets in &quot;select&quot; and asynchronous I/O operations to ensure that \
spurious wakeups do not occur when a packet comes in from the wrong interface.<br> \
<br>I intend to split the patch into the following pieces:<br>1) UDP broadcast tests \
(used for making sure everything works)<br> 2) The &quot;bare essentials&quot; \
(storing and retrieving the interface index, and setting/filtering the \
interface)<br>3) Ensure &quot;select&quot; does not wake up on packets with an \
interface mismatch<br>4) Ensure Async WSARecv does not wake up on packets with an \
interface mismatch<br>

5) Ensure Async ReadFile does not wake up on packets with an interface \
mismatch<br><br>I would greatly appreciate any comments people have on this patch.  \
Thanks so much!<br><br>Erich Hoover<br><a \
href="mailto:ehoover@mines.edu">ehoover@mines.edu</a><br>


--00504502c5ffa269470474a7d836--


["cnc3-bind.patch" (text/x-diff)]

diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c
index ab88925..c70dd7a 100644
--- a/dlls/ws2_32/socket.c
+++ b/dlls/ws2_32/socket.c
@@ -155,6 +155,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
@@ -504,6 +507,32 @@ 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
@@ -1173,12 +1202,88 @@ 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
+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;
+}
+#endif
+
+/***********************************************************************
+ *              WS2_pktinfo_check_mismatch                (INTERNAL)
+ *
+ * Use a MSG_PEEK to check for packets that do not match the interface
+ * set in the IP_PKTINFO filter.  This check is intended to occur
+ * before a select or an asynchronous read so that the packet does not
+ * cause a spurious wakeup.  So, if there is a mismatch then throw the
+ * packet away.
+ */
+#ifdef USE_IP_PKTINFO_FILTER
+static int WS2_pktinfo_check_mismatch( SOCKET sock, int fd )
+{
+    unsigned int check_iface, optlen;
+    struct msghdr hdr;
+    char pktbuf[512];
+
+    memset(&hdr, 0, sizeof(struct msghdr));
+    hdr.msg_control = pktbuf;
+    hdr.msg_controllen = sizeof(pktbuf);
+    hdr.msg_flags = 0;
+
+    check_iface = 0;
+    optlen = sizeof(check_iface);
+    getsockopt(fd, IPPROTO_IP, IP_PKTINFO, (void *) &check_iface, &optlen);
+    if (check_iface)
+    {
+        if ( recvmsg(fd, &hdr, MSG_PEEK|MSG_DONTWAIT) != -1 )
+        {
+            struct in_pktinfo *pktinfo = WS2_get_pktinfo(&hdr);
+            int iface_index = _get_sock_iface(sock);
+
+            if (pktinfo && pktinfo->ipi_ifindex != iface_index)
+            {
+                if ( recvmsg(fd, &hdr, MSG_DONTWAIT) != -1 )
+                    return TRUE;
+                else
+                    WARN("Failed to throw away packet with interface mismatch! \
%m\n"); +            }
+        }
+        else
+            WARN("Failed to peek at message header! %m\n");
+    }
+    return FALSE;
+}
+#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;
+    char pktbuf[512];
+#endif
     struct msghdr hdr;
     union generic_unix_sockaddr unix_sockaddr;
     int n;
@@ -1195,17 +1300,47 @@ 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.
+     */
+    while (!correct_iface)
+    {
+        if ( (n = recvmsg(fd, &hdr, wsa->flags)) == -1 )
+            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); +        }
+    }
+#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.
@@ -1239,6 +1374,17 @@ static NTSTATUS WS2_async_recv( void* user, IO_STATUS_BLOCK* \
                iosb, NTSTATUS stat
         if ((status = wine_server_handle_to_fd( wsa->hSocket, FILE_READ_DATA, &fd, \
NULL ) ))  break;
 
+#ifdef USE_IP_PKTINFO_FILTER
+        /* Check for packets that do not match the IP_PKTINFO filter */
+        if (WS2_pktinfo_check_mismatch(HANDLE2SOCKET(wsa->hSocket), fd))
+        {
+            WARN("Packet not destined for this interface, thrown away.\n");
+            wine_server_release_fd( wsa->hSocket, fd );
+            status = STATUS_PENDING;
+            _enable_event( wsa->hSocket, FD_READ, 0, 0 );
+            break;
+        }
+#endif
         result = WS2_recv( fd, wsa );
         wine_server_release_fd( wsa->hSocket, fd );
         if (result >= 0)
@@ -1271,14 +1417,99 @@ 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;
+    int fd = get_sock_fd( s, 0, NULL );
+    unsigned int sock_type = 0, optlen;
+    struct ifreq ifreqs[20], *ifr;
+    struct ifconf ifc;
+
+    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;
+    }
+    memset(&ifc,0,sizeof(ifc));
+    ifc.ifc_len = sizeof(ifreqs);
+    ifc.ifc_buf = (char *) ifreqs;
+    if (ioctl(fd, SIOCGIFCONF, &ifc) < 0)
+    {
+        FIXME("Failed to get interface list, broadcast packets will not work on this \
socket.\n"); +        goto cleanup;
+    }
+    for (ifr = ifc.ifc_req; (ifr-ifc.ifc_req) < sizeof(ifreqs); ifr++)
+    {
+        /* Currently only coded to handle IPv4 */
+        if (uaddr->addr.sa_family == AF_INET && ifr->ifr_addr.sa_family == AF_INET)
+        {
+            struct sockaddr_in *in_iface = (struct sockaddr_in *) &(ifr->ifr_addr);
+
+            if (in_sock->sin_addr.s_addr == in_iface->sin_addr.s_addr)
+            {
+                int enable = 1;
+
+                if(ioctl(fd, SIOCGIFINDEX, ifr) == -1)
+                {
+                    /* We must re-request the interface index here specifically \
since +                     * SIOCGIFCONF does NOT return the correct ifr_ifindex \
value. If +                     * this request fails for whatever reason then we \
cannot proceed. +                     */
+                    FIXME("obtaining unique interface ID for '%s' failed, broadcast \
packets will not work on this socket.\n", ifr->ifr_name); +                }
+                else 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), ifr->ifr_ifindex); +                    \
_set_sock_iface(s, ifr->ifr_ifindex); +                    in_sock->sin_addr.s_addr = \
htonl(WS_INADDR_ANY); +                }
+                break;
+            }
+        }
+    }
+
+cleanup:
+    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;
@@ -1313,9 +1544,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;
@@ -1534,6 +1796,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;
@@ -2720,6 +2986,69 @@ static struct pollfd *fd_sets_to_poll( const WS_fd_set \
*readfds, const WS_fd_set  return fds;
 }
 
+/* Check to see if any of the read sockets have IP_PKTINFO enabled,
+ * if this option is enabled and "select" is called then it
+ * may cause the application to attempt to collect data on an
+ * interface for which the socket is not bound (which will fail).
+ */
+#ifdef USE_IP_PKTINFO_FILTER
+static int fd_sets_with_pktinfo( struct pollfd *fds, int count )
+{
+    unsigned int i, check_iface, optlen;
+    int sets = 0;
+
+    optlen = sizeof(check_iface);
+    for (i = 0; i < count; i++)
+    {
+        if (fds[i].events == POLLIN)
+        {
+            int fd = fds[i].fd;
+
+            check_iface = 0;
+            getsockopt(fd, IPPROTO_IP, IP_PKTINFO, (void *) &check_iface, &optlen);
+            if (check_iface)
+                sets++;
+        }
+    }
+    return sets;
+}
+#endif
+
+/* A poll succeeded, if any of the sockets have IP_PKTINFO enabled
+ * then peek the data from that socket and throw the packet away
+ * if there is an interface mismatch.
+ */
+#ifdef USE_IP_PKTINFO_FILTER
+static int check_pktinfo_fds( WS_fd_set *readfds, struct pollfd *fds, int count )
+{
+    unsigned int i;
+    int events = 0;
+
+    for (i = 0; i < count; i++)
+    {
+        if (fds[i].events == POLLIN && fds[i].revents)
+        {
+            SOCKET sock = readfds->fd_array[i];
+            int fd = fds[i].fd;
+
+            if (WS2_pktinfo_check_mismatch(sock, fd))
+            {
+                WARN("Packet not destined for this interface, thrown away.\n");
+                fds[i].revents = 0;
+            }
+            else
+                events++;
+        }
+        else
+        {
+            if (fds[i].revents)
+                events++;
+        }
+    }
+    return (events == 0);
+}
+#endif
+
 /* release the file descriptor obtained in fd_sets_to_poll */
 /* must be called with the original fd_set arrays, before calling get_poll_results \
*/  static void release_poll_fds( const WS_fd_set *readfds, const WS_fd_set \
*writefds, @@ -2787,6 +3116,10 @@ int WINAPI WS_select(int nfds, WS_fd_set \
*ws_readfds,  WS_fd_set *ws_writefds, WS_fd_set *ws_exceptfds,
                      const struct WS_timeval* ws_timeout)
 {
+#ifdef USE_IP_PKTINFO_FILTER
+    unsigned int check_iface = 0;
+    unsigned int noevents = TRUE;
+#endif
     struct pollfd *pollfds;
     int count, ret, timeout = -1;
 
@@ -2799,9 +3132,28 @@ int WINAPI WS_select(int nfds, WS_fd_set *ws_readfds,
         return SOCKET_ERROR;
     }
 
+#ifdef USE_IP_PKTINFO_FILTER
+    check_iface = fd_sets_with_pktinfo( pollfds, count );
+#endif
+
     if (ws_timeout) timeout = (ws_timeout->tv_sec * 1000) + (ws_timeout->tv_usec + \
999) / 1000;  
+#ifndef USE_IP_PKTINFO_FILTER
     ret = poll( pollfds, count, timeout );
+#else
+    while (noevents)
+    {
+        ret = poll( pollfds, count, timeout );
+
+        noevents = FALSE;
+        /* If there is an IP_PKTINFO filter set on any of the sockets
+         * then ensure that the select only wakes up if the the packet
+         * is destined for the appropriate interface.
+         */
+        if (check_iface != 0 && ret > 0)
+            noevents = check_pktinfo_fds( ws_readfds, pollfds, count );
+    }
+#endif
     release_poll_fds( ws_readfds, ws_writefds, ws_exceptfds, pollfds );
 
     if (ret == -1) SetLastError(wsaErrno());
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c
index 3d470c6..626d208 100644
--- a/dlls/ws2_32/tests/sock.c
+++ b/dlls/ws2_32/tests/sock.c
@@ -64,6 +64,26 @@ static PCSTR  (WINAPI  *pInetNtop)(INT,LPVOID,LPSTR,ULONG) = 0;
 
 /**************** Structs and typedefs ***************/
 
+/* UDP select thread information */
+struct udp_select {
+    SOCKET socket;
+    int responses;
+};
+
+/* UDP IOCP thread information */
+struct udp_iocp {
+    HANDLE iocp;
+    SOCKET socket;
+    int responses;
+};
+
+/* IOCP key for retrieving buffer information */
+struct iocp_key {
+    SOCKET socket;
+    int num_buffers;
+    WSABUF *buffers;
+};
+
 typedef struct thread_info
 {
     HANDLE thread;
@@ -1175,8 +1195,151 @@ static test_setup tests [NUM_TESTS] =
     }
 };
 
+/* Perform a timed "select" (100 ms) and read from a socket,
+ * does not block so if the "select" times out then this fails.
+ */
+int select_recv(SOCKET sock, char *buffer, int len)
+{
+    struct timeval tv = {0, 100000};
+    unsigned long enable = 1;
+    fd_set fdset;
+    int result;
+
+    FD_ZERO(&fdset);
+    FD_SET(sock, &fdset);
+    ioctlsocket(sock, FIONBIO, &enable);
+    result = select(sock+1, &fdset, NULL, NULL, &tv);
+    if (result < 0)
+        return -1;
+    else if (result > 0 && FD_ISSET(sock, &fdset))
+    {
+        result = recv(sock, buffer, len, 0);
+        return result;
+    }
+    return -2;
+}
+
+/* Perform a timed "IOCP" operation (100 ms) and read from a socket,
+ * does not block so if the "select" times out then this fails.
+ */
+int iocp_recv(HANDLE iocp, char *buffer, unsigned int len)
+{
+    struct iocp_key *key = NULL;
+    LPOVERLAPPED o = NULL;
+    unsigned int bytes, i;
+    int result;
+
+    result = GetQueuedCompletionStatus(iocp, &bytes, (PULONG_PTR) &key, &o, 100);
+    if (result && bytes > 0)
+    {
+        for (i=0; i < key->num_buffers; i++)
+        {
+            if (key->buffers[i].buf[0] != '\0')
+            {
+                memcpy(buffer, key->buffers[i].buf, bytes);
+                key->buffers[i].buf[0] = '\0';
+            }
+        }
+
+        return bytes;
+    }
+    return -1;
+}
+
+/* Loop through waiting for UDP datagrams (using "select")
+ * and count the number successfully received.
+ */
+static void WINAPI udp_select_thread( struct udp_select *udpsel )
+{
+    int tries = 0;
+
+    while (tries < 10)
+    {
+        char buffer[100];
+        int ret;
+
+        buffer[0] = '\0';
+        ret = select_recv(udpsel->socket, buffer, sizeof(buffer));
+        if (ret == sizeof(struct sockaddr_in))
+        {
+            struct sockaddr_in *addr = (struct sockaddr_in *) buffer;
+
+            if (ntohl(addr->sin_addr.s_addr) == INADDR_LOOPBACK)
+                (udpsel->responses)++;
+        }
+        tries++;
+    }
+}
+
+/* Loop through waiting for UDP datagrams (using IOCP)
+ * and count the number successfully received.
+ */
+static void WINAPI udp_iocp_thread( struct udp_iocp *udpiocp )
+{
+    int tries = 0;
+
+    while (tries < 10)
+    {
+        char buffer[100];
+        int ret;
+
+        buffer[0] = '\0';
+        ret = iocp_recv(udpiocp->iocp, buffer, sizeof(buffer));
+        if (ret == sizeof(struct sockaddr_in))
+        {
+            struct sockaddr_in *addr = (struct sockaddr_in *) buffer;
+
+            if (ntohl(addr->sin_addr.s_addr) == INADDR_LOOPBACK)
+                (udpiocp->responses)++;
+        }
+        tries++;
+    }
+}
+
+void start_iocp_recv( struct iocp_key *iocp_key, OVERLAPPED *o, int buffer_size, int \
count, int use_readfile ) +{
+    unsigned int flags = 0;
+    int i, ret;
+
+    iocp_key->num_buffers = count;
+    iocp_key->buffers = malloc(count*sizeof(WSABUF));
+    for(i = 0; i < count; i++)
+    {
+        iocp_key->buffers[i].len = buffer_size;
+        iocp_key->buffers[i].buf = malloc(buffer_size);
+        iocp_key->buffers[i].buf[0] = '\0';
+        if (use_readfile)
+        {
+            ret = ReadFile ((HANDLE)iocp_key->socket, iocp_key->buffers[i].buf, \
iocp_key->buffers[i].len, NULL ,o);  +            ok ( ret == 0 && WSAGetLastError() \
== ERROR_IO_PENDING, "UDP: failed to start IOCP read!\n" ); +        }
+        else
+        {
+            ret = WSARecv(iocp_key->socket, &(iocp_key->buffers[i]), 1, NULL, \
&flags, o, NULL); +            ok ( ret == SOCKET_ERROR && WSAGetLastError() == \
ERROR_IO_PENDING, "UDP: failed to start IOCP read!\n" ); +        }
+    }
+}
+
+void cleanup_iocp_recv( struct iocp_key *iocp_key )
+{
+    int i;
+
+    for(i = 0; i < iocp_key->num_buffers; i++)
+        free(iocp_key->buffers[i].buf);
+    free(iocp_key->buffers);
+}
+
 static void test_UDP(void)
 {
+    SOCKET broadcast_sock, write_sock, read_sock;
+    struct udp_iocp udpiocp = {0, 0, 0};
+    struct udp_select udpsel = {0, 0};
+    HANDLE thread_handle, iocp;
+    struct sockaddr_in addr;
+    int run_num;
+    DWORD id;
+
     /* This function tests UDP sendto() and recvfrom(). UDP is unreliable, so it is
        possible that this test fails due to dropped packets. */
 
@@ -1221,6 +1384,120 @@ static void test_UDP(void)
         ok ( n_recv == sizeof(buf), "UDP: recvfrom() received wrong amount of data \
                or socket error: %d\n", n_recv );
         ok ( memcmp ( &peer[0].peer.sin_port, buf, sizeof(peer[0].addr.sin_port) ) \
== 0, "UDP: port numbers do not match\n" );  }
+
+    for ( i = 1; i < NUM_UDP_PEERS; i++ )
+        closesocket ( peer[i].s );
+
+    read_sock = socket( AF_INET, SOCK_DGRAM, 0 );
+    write_sock = socket( AF_INET, SOCK_DGRAM, 0 );
+    broadcast_sock = socket( AF_INET, SOCK_DGRAM, 0 );
+    udpsel.responses = 0;
+    if (read_sock != INVALID_SOCKET && write_sock != INVALID_SOCKET
+        && broadcast_sock != INVALID_SOCKET)
+    {
+        unsigned int enable = 1;
+        int ret;
+
+        /* setup the necessary sockets */
+        addr.sin_family         = AF_INET;
+        addr.sin_addr.s_addr    = htonl ( INADDR_LOOPBACK );
+        addr.sin_port           = htons ( 2000 );
+        do_bind ( read_sock, (struct sockaddr *) &addr, sizeof(addr) );
+        ret = setsockopt ( read_sock, SOL_SOCKET, SO_BROADCAST, (LPVOID) &enable, \
sizeof (enable) ); +        ok ( ret == 0, "UDP: failed to set broadcast flag!\n" );
+        ret = setsockopt ( broadcast_sock, SOL_SOCKET, SO_BROADCAST, (LPVOID) \
&enable, sizeof (enable) ); +        ok ( ret == 0, "UDP: failed to set broadcast \
flag!\n" ); +
+        /* test the "select" operation on UDP sockets */
+        udpsel.socket = read_sock;
+        thread_handle = CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) \
&udp_select_thread, &udpsel, 0, &id ); +
+        /* send test packets to the thread waiting on "select" */
+        memcpy ( buf, &addr, sizeof(addr) );
+        ret = sendto ( write_sock, buf, sizeof(buf), 0, (struct sockaddr*) &addr, \
sizeof(addr) ); +        ok ( ret == sizeof(buf), "UDP: failed to send data!\n" );
+        addr.sin_addr.s_addr    = htonl ( INADDR_BROADCAST );
+        ret = sendto ( broadcast_sock, buf, sizeof(buf), 0, (struct sockaddr*) \
&addr, sizeof(addr) ); +        addr.sin_addr.s_addr    = htonl ( INADDR_LOOPBACK );
+        if (ret == -1 && WSAGetLastError() == WSAENETUNREACH)
+            skip ( "A connected network interface is required to run this test.\n" \
); +        else
+            ok ( ret == sizeof(buf), "UDP: failed to send data!\n" );
+        ret = sendto ( write_sock, buf, sizeof(buf), 0, (struct sockaddr*) &addr, \
sizeof(addr) ); +        ok ( ret == sizeof(buf), "UDP: failed to send data!\n" );
+        /* send a test packet <out> on the "receive" interface (which is valid) */
+        ret = sendto ( read_sock, buf, sizeof(buf), 0, (struct sockaddr*) &addr, \
sizeof(addr) ); +        ok ( ret == sizeof(buf), "UDP: failed to send data!\n" );
+
+        /* wait for the thread to exit and check the replies */
+        WaitForSingleObject ( thread_handle, TEST_TIMEOUT * 1000 );
+        ok ( udpsel.responses == 3, "UDP: expected three replies (received %d).\n", \
udpsel.responses); +
+        /* test IOCP with both WSARecv and ReadFile */
+        for (run_num = 0; run_num <= 1; run_num++)
+        {
+            /* create an IOCP handle and associate it with our read socket */
+            iocp = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL,0,3);
+            if (iocp)
+            {
+                struct iocp_key iocp_key;
+                OVERLAPPED o;
+
+                /* reset the read socket (make sure IOCP runs are separate) */
+                closesocket ( read_sock );
+                read_sock = socket( AF_INET, SOCK_DGRAM, 0 );
+                do_bind ( read_sock, (struct sockaddr *) &addr, sizeof(addr) );
+
+                /* initialize the IO Completion port */
+                iocp_key.socket = read_sock;
+                CreateIoCompletionPort ( (HANDLE)read_sock, iocp, (ULONG_PTR) \
&iocp_key, 1 ); +                memset ( &o, 0, sizeof(OVERLAPPED) );
+                o.hEvent = CreateEvent ( NULL, TRUE, TRUE, NULL );
+                udpiocp.responses = 0;
+                start_iocp_recv ( &iocp_key, &o, sizeof(addr), 10, run_num );
+
+                /* test the "select" operation on UDP sockets */
+                udpiocp.iocp = iocp;
+                udpiocp.socket = read_sock;
+                thread_handle = CreateThread ( NULL, 0, (LPTHREAD_START_ROUTINE) \
&udp_iocp_thread, &udpiocp, 0, &id ); +
+                /* send test packets to the thread waiting on the IO Completion */
+                memcpy ( buf, &addr, sizeof(addr) );
+                ret = sendto ( write_sock, buf, sizeof(buf), 0, (struct sockaddr*) \
&addr, sizeof(addr) ); +                ok ( ret == sizeof(buf), "UDP: failed to send \
data!\n" ); +                addr.sin_addr.s_addr    = htonl ( INADDR_BROADCAST );
+                ret = sendto ( broadcast_sock, buf, sizeof(buf), 0, (struct \
sockaddr*) &addr, sizeof(addr) ); +                addr.sin_addr.s_addr    = htonl ( \
INADDR_LOOPBACK ); +                if (ret == -1 && WSAGetLastError() == \
WSAENETUNREACH) +                    skip ( "A connected network interface is \
required to run this test.\n" ); +                else
+                    ok ( ret == sizeof(buf), "UDP: failed to send data!\n" );
+                ret = sendto ( write_sock, buf, sizeof(buf), 0, (struct sockaddr*) \
&addr, sizeof(addr) ); +                ok ( ret == sizeof(buf), "UDP: failed to send \
data!\n" ); +                /* send a test packet <out> on the "receive" interface \
(which is valid) */ +                ret = sendto ( read_sock, buf, sizeof(buf), 0, \
(struct sockaddr*) &addr, sizeof(addr) ); +                ok ( ret == sizeof(buf), \
"UDP: failed to send data!\n" ); +
+                /* wait for the thread to exit, check the replies, cleanup the \
buffers */ +                WaitForSingleObject ( thread_handle, TEST_TIMEOUT * 1000 \
); +                ok ( udpiocp.responses == 3, "UDP: expected three replies \
(received %d).\n", udpiocp.responses); +                cleanup_iocp_recv ( &iocp_key \
); +
+                CloseHandle(iocp);
+            }
+            else
+                skip ( "Several tests skipped, failed to create IO completion \
port!\n" ); +        }
+    }
+    else
+        skip ( "Several tests skipped, failed to create necessary sockets!\n" );
+    /* cleanup socket select test */
+    if (read_sock != INVALID_SOCKET);
+        closesocket ( read_sock );
+    if (write_sock != INVALID_SOCKET);
+        closesocket ( write_sock );
+    if (broadcast_sock != INVALID_SOCKET);
+        closesocket ( broadcast_sock );
 }
 
 static void WINAPI do_getservbyname( HANDLE *starttest )
diff --git a/server/protocol.def b/server/protocol.def
index 9748931..1e09aa0 100644
--- a/server/protocol.def
+++ b/server/protocol.def
@@ -1103,6 +1103,21 @@ enum server_fd_type
 @END
 
 
+/* Set socket interface ID */
+@REQ(set_socket_iface)
+    obj_handle_t handle;        /* handle to the socket */
+    int iface;                  /* interface ID */
+@END
+
+
+/* Get socket interface ID */
+@REQ(get_socket_iface)
+    obj_handle_t handle;        /* handle to the socket */
+@REPLY
+    int iface;                  /* interface ID */
+@END
+
+
 /* Reenable pending socket events */
 @REQ(enable_socket_event)
     obj_handle_t handle;        /* handle to the socket */
diff --git a/server/sock.c b/server/sock.c
index 71d8ccb..46c4665 100644
--- a/server/sock.c
+++ b/server/sock.c
@@ -38,6 +38,9 @@
 #ifdef HAVE_SYS_SOCKET_H
 # include <sys/socket.h>
 #endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
 #ifdef HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
 #endif
@@ -65,6 +68,10 @@
 #define USE_WS_PREFIX
 #include "winsock2.h"
 
+#if defined(HAVE_NETINET_IN_H) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
+#    define USE_IP_PKTINFO_FILTER
+#endif
+
 struct sock
 {
     struct object       obj;         /* object header */
@@ -85,6 +92,7 @@ struct sock
     struct sock        *deferred;    /* socket that waits for a deferred accept */
     struct async_queue *read_q;      /* queue for asynchronous reads */
     struct async_queue *write_q;     /* queue for asynchronous writes */
+    int                 iface_index; /* interface ID for interface-specific sockets \
*/  };
 
 static void sock_dump( struct object *obj, int verbose );
@@ -296,6 +304,57 @@ static inline int sock_error( struct fd *fd )
     return optval ? sock_get_error(optval) : 0;
 }
 
+/* Test for IP_PKTINFO filtered packets that are requested using
+ * ReadFile instead of WSARecv.  If the packet does not match
+ * for this socket's interface then throw it away.
+ */
+static int accept_read_poll( struct fd *fd )
+{
+#ifdef USE_IP_PKTINFO_FILTER
+    struct sock *sock = get_fd_user( fd );
+    unsigned int check_iface, optlen;
+    int handle = get_unix_fd ( fd );
+    struct msghdr hdr;
+    char pktbuf[512];
+
+    memset(&hdr, 0, sizeof(struct msghdr));
+    hdr.msg_control = pktbuf;
+    hdr.msg_controllen = sizeof(pktbuf);
+    hdr.msg_flags = 0;
+
+    check_iface = 0;
+    optlen = sizeof(check_iface);
+    getsockopt(handle, IPPROTO_IP, IP_PKTINFO, (void *) &check_iface, &optlen);
+    if (check_iface)
+    {
+        /* Peek at the packet to check its interface */
+        if ( recvmsg(handle, &hdr, MSG_PEEK|MSG_DONTWAIT) != -1 )
+        {
+            int iface_index = sock->iface_index;
+            struct in_pktinfo *pktinfo = NULL;
+            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)
+                    pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);
+            }
+            if (pktinfo && pktinfo->ipi_ifindex != iface_index)
+            {
+                if ( recvmsg(handle, &hdr, MSG_DONTWAIT) != -1 )
+                    return FALSE;
+                else
+                    fprintf(stderr, "Failed to throw away packet with interface \
mismatch! %m\n"); +            }
+        }
+        else
+            fprintf(stderr, "Failed to peek at message header! %m\n");
+    }
+#endif /* USE_IP_PKTINFO_FILTER */
+    return TRUE;
+}
+
 static void sock_poll_event( struct fd *fd, int event )
 {
     struct sock *sock = get_fd_user( fd );
@@ -388,9 +447,12 @@ static void sock_poll_event( struct fd *fd, int event )
         }
         else if ( event & POLLIN ) /* POLLIN for non-stream socket */
         {
-            sock->pmask |= FD_READ;
-            sock->hmask |= (FD_READ|FD_CLOSE);
-            sock->errors[FD_READ_BIT] = 0;
+            if (accept_read_poll(fd))
+            {
+                sock->pmask |= FD_READ;
+                sock->hmask |= (FD_READ|FD_CLOSE);
+                sock->errors[FD_READ_BIT] = 0;
+            }
             if (debug_level)
                 fprintf(stderr, "socket %p is readable\n", sock );
 
@@ -612,6 +674,7 @@ static struct object *create_socket( int family, int type, int \
protocol, unsigne  sock->deferred = NULL;
     sock->read_q  = NULL;
     sock->write_q = NULL;
+    sock->iface_index = 0;
     if (!(sock->fd = create_anonymous_fd( &sock_fd_ops, sockfd, &sock->obj,
                             (flags & WSA_FLAG_OVERLAPPED) ? 0 : \
FILE_SYNCHRONOUS_IO_NONALERT )))  {
@@ -682,6 +745,7 @@ static struct sock *accept_socket( obj_handle_t handle )
         acceptsock->deferred = NULL;
         acceptsock->read_q  = NULL;
         acceptsock->write_q = NULL;
+        acceptsock->iface_index = 0;
         if (!(acceptsock->fd = create_anonymous_fd( &sock_fd_ops, acceptfd, \
                &acceptsock->obj,
                                                     get_fd_options( sock->fd ) )))
         {
@@ -799,6 +863,33 @@ DECL_HANDLER(accept_socket)
     }
 }
 
+/* set socket interface */
+DECL_HANDLER(set_socket_iface)
+{
+    struct sock *sock;
+
+    if (!(sock = (struct sock *)get_handle_obj( current->process, req->handle,
+                                                FILE_WRITE_ATTRIBUTES, &sock_ops))) \
return; +    sock->iface_index = req->iface;
+    release_object( &sock->obj );
+}
+
+/* get socket interface */
+DECL_HANDLER(get_socket_iface)
+{
+    struct sock *sock;
+
+    sock = (struct sock *)get_handle_obj( current->process, req->handle, \
FILE_READ_ATTRIBUTES, &sock_ops ); +    if (!sock)
+    {
+        reply->iface = 0;
+        set_error( WSAENOTSOCK );
+        return;
+    }
+    reply->iface = sock->iface_index;
+    release_object( &sock->obj );
+}
+
 /* set socket event parameters */
 DECL_HANDLER(set_socket_event)
 {





[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic