From d73ee111fed6b9c3920a47973c3b9d2d0fdcc287 Mon Sep 17 00:00:00 2001
From: Erich Hoover <ehoover@mines.edu>
Date: Sat, 18 Sep 2010 20:29:42 -0600
Subject: ws2_32: Ensure select does not wake up on packets with an interface mismatch.

---
 dlls/ws2_32/socket.c |  129 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 129 insertions(+), 0 deletions(-)

diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c
index 913f192..2db46d4 100644
--- a/dlls/ws2_32/socket.c
+++ b/dlls/ws2_32/socket.c
@@ -1468,6 +1468,58 @@ static void WS2_unlock_sock(sem_t *lock)
 #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];
+    int ret = FALSE;
+
+    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)
+    {
+        sem_t *lock = NULL;
+        int iface_index;
+
+        iface_index = _get_sock_iface(sock);
+        lock = WS2_lock_sock(fd);
+        if ( recvmsg(fd, &hdr, MSG_PEEK|MSG_DONTWAIT) != -1 )
+        {
+            struct in_pktinfo *pktinfo = WS2_get_pktinfo(&hdr);
+
+            if (pktinfo && pktinfo->ipi_ifindex != iface_index)
+            {
+                if ( recvmsg(fd, &hdr, MSG_DONTWAIT) != -1 )
+                    ret = TRUE;
+                else
+                    WARN("Failed to throw away packet with interface mismatch! %m\n");
+            }
+        }
+        else
+            WARN("Failed to peek at message header! %m\n");
+        WS2_unlock_sock(lock);
+    }
+    return ret;
+}
+#endif
+
+/***********************************************************************
  *              WS2_recv                (INTERNAL)
  *
  * Workhorse for both synchronous and asynchronous recv() operations.
@@ -3351,6 +3403,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,
@@ -3418,6 +3533,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 = FALSE;
+#endif
     struct pollfd *pollfds;
     struct timeval tv1, tv2;
     int torig = 0;
@@ -3432,6 +3551,10 @@ 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)
     {
         torig = (ws_timeout->tv_sec * 1000) + (ws_timeout->tv_usec + 999) / 1000;
@@ -3441,7 +3564,13 @@ int WINAPI WS_select(int nfds, WS_fd_set *ws_readfds,
 
     while ((ret = poll( pollfds, count, timeout )) < 0)
     {
+#ifdef USE_IP_PKTINFO_FILTER
+        if (check_iface != 0 && ret > 0)
+            noevents = check_pktinfo_fds( ws_readfds, pollfds, count );
+        if (errno == EINTR || noevents)
+#else
         if (errno == EINTR)
+#endif
         {
             if (!ws_timeout) continue;
             gettimeofday( &tv2, 0 );
-- 
1.7.0.4

