While searching for something else I discovered a bug relevant to the
interface-bound UDP broadcast patches I've been working on.
Apparently, IP_PKTINFO does work on Windows when used with the
WSARecvMsg function (Bug #19493).  I've attached a patch to address
this issue (including tests), and I would appreciate any comments on
it.  This patch approaches the problem by:
1) Implementing WSARecvMsg through WS2_recvfrom
2) Passing the control buffer as an additional WSABUF with an internal
flag (cleared internally to prevent confusing applications)
3) Converting the returned Unix IP_PKTINFO data to the equivalent
Windows version in WS2_recv() by utilizing a temporary buffer

Thanks in advance for your feedback.

Erich Hoover
ehoo...@mines.edu
From 7cf7aa59b7736062464f2e4f9465e165a17867c8 Mon Sep 17 00:00:00 2001
From: Erich Hoover <ehoo...@mines.edu>
Date: Sun, 10 Oct 2010 18:40:11 -0600
Subject: ws2_32: Add support and tests for WSARecvMsg and IP_PKTINFO.

---
 dlls/ws2_32/socket.c     |  127 ++++++++++++++++++++++++++++++++++++++++++++--
 dlls/ws2_32/tests/sock.c |   89 ++++++++++++++++++++++++++++++++
 include/mswsock.h        |   36 ++++++++++---
 include/ws2ipdef.h       |    9 +++
 4 files changed, 249 insertions(+), 12 deletions(-)

diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c
index 22e6138..e3aacb0 100644
--- a/dlls/ws2_32/socket.c
+++ b/dlls/ws2_32/socket.c
@@ -263,6 +263,8 @@ typedef struct ws2_async
     DWORD                               flags;
     unsigned int                        n_iovecs;
     unsigned int                        first_iovec;
+    CHAR                               *control;
+    ULONG                              *controllen;
     struct iovec                        iovec[1];
 } ws2_async;
 
@@ -375,6 +377,7 @@ static const int ws_ip_map[][2] =
 #endif
     MAP_OPTION( IP_TOS ),
     MAP_OPTION( IP_TTL ),
+    MAP_OPTION( IP_PKTINFO ),
 };
 
 static const int ws_ipv6_map[][2] =
@@ -470,6 +473,77 @@ static const int ws_eai_map[][2] =
 
 static const char magic_loopback_addr[] = {127, 12, 34, 56};
 
+#ifndef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
+static inline WSACMSGHDR *create_control_message(int level, int type, WSACMSGHDR *current, ULONG *maxsize, void *data, int len)
+{
+    char *ptr = (char *) current + sizeof(WSACMSGHDR);
+    int newsize = (int)*maxsize;
+
+    /* Make sure there is at least enough room for this entry */
+    newsize -= sizeof(WSACMSGHDR) + CMSG_ALIGN(len);
+    if (newsize < 0)
+        return 0;
+    *maxsize = (ULONG)newsize;
+    /* Fill in the entry */
+    current->cmsg_len = sizeof(WSACMSGHDR) + len;
+    current->cmsg_level = level;
+    current->cmsg_type = type;
+    memcpy(ptr, data, len);
+    /* Return the pointer to where next entry should go */
+    return (WSACMSGHDR *) (ptr + CMSG_ALIGN(len));
+}
+
+static inline int convert_control_headers(struct msghdr *hdr, ULONG *maxsize)
+{
+    void *cmsg = (void *) CMSG_FIRSTHDR(hdr);
+    WSACMSGHDR *cmsg_win, *ptr;
+    struct cmsghdr *cmsg_unix;
+    ULONG ctlsize = *maxsize;
+
+    /* Use a temporary buffer for storing the headers */
+    cmsg_win = HeapAlloc( GetProcessHeap(), 0, *maxsize );
+    memset(cmsg_win, 0x00, sizeof(WSACMSGHDR)); /* Don't use garbage data if no headers are found */
+    /* Loop over all the headers, converting as appropriate */
+    for (ptr = cmsg_win, cmsg_unix = (struct cmsghdr *) cmsg; cmsg_unix != NULL;
+         cmsg_unix = CMSG_NXTHDR(hdr, cmsg_unix))
+    {
+        switch(cmsg_unix->cmsg_level)
+        {
+            case IPPROTO_IP:
+                switch(cmsg_unix->cmsg_type)
+                {
+                    case IP_PKTINFO:
+                    {
+                        /* Convert the Unix IP_PKTINFO structure to the Windows version */
+                        struct in_pktinfo *data_unix = (struct in_pktinfo *) CMSG_DATA(cmsg_unix);
+                        struct WS_in_pktinfo data_win;
+
+                        memcpy(&data_win.ipi_addr,&data_unix->ipi_addr.s_addr,4); /* 4 bytes = 32 address bits */
+                        data_win.ipi_ifindex = data_unix->ipi_ifindex;
+                        ptr = create_control_message(WS_IPPROTO_IP, WS_IP_PKTINFO, ptr, &ctlsize,
+                                                     (void*)&data_win, sizeof(data_win));
+                        if (!ptr) goto error;
+                    }   break;
+                    default:
+                        FIXME("Unhandled IPPROTO_IP message header type %d\n", cmsg_unix->cmsg_type);
+                        break;
+                }
+                break;
+            default:
+                FIXME("Unhandled message header level %d\n", cmsg_unix->cmsg_level);
+                break;
+        }
+    }
+
+error:
+    /* Copy the temporary buffer back to the application buffer */
+    *maxsize = (ptr == NULL ? 0 : (char*)ptr - (char*)cmsg_win);
+    memcpy( cmsg, cmsg_win, *maxsize );
+    HeapFree( GetProcessHeap(), 0, cmsg_win );
+    return (ptr != NULL);
+}
+#endif /* HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */
+
 /* ----------------------------------- error handling */
 
 static NTSTATUS sock_get_ntstatus( int err )
@@ -1433,14 +1507,23 @@ static int WS2_recv( int fd, struct ws2_async *wsa )
     hdr.msg_accrights = NULL;
     hdr.msg_accrightslen = 0;
 #else
-    hdr.msg_control = NULL;
-    hdr.msg_controllen = 0;
+    hdr.msg_control = wsa->control;
+    hdr.msg_controllen = wsa->controllen ? *wsa->controllen : 0;
     hdr.msg_flags = 0;
 #endif
 
     if ( (n = recvmsg(fd, &hdr, wsa->flags)) == -1 )
         return -1;
 
+#ifndef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
+    if (wsa->control && !convert_control_headers(&hdr, wsa->controllen))
+    {
+        /* Insufficient room for control headers */
+        errno = EINVAL;
+        return -1;
+    }
+#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.
      *
@@ -1964,6 +2047,33 @@ static void WINAPI WS2_GetAcceptExSockaddrs(PVOID buffer, DWORD data_size, DWORD
 }
 
 /***********************************************************************
+ *     WSARecvMsg
+ */
+static int WINAPI WS2_WSARecvMsg( SOCKET s, LPWSAMSG msg, LPDWORD lpNumberOfBytesRecvd,
+                                  LPWSAOVERLAPPED lpOverlapped,
+                                  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine )
+{
+    LPWSABUF buftmp;
+    int ret;
+
+    if (!msg || !(buftmp = HeapAlloc( GetProcessHeap(), 0, (msg->dwBufferCount+1) * sizeof(WSABUF) )))
+    {
+        if (msg) WSASetLastError( WSAEFAULT );
+        return SOCKET_ERROR;
+    }
+
+    msg->dwFlags |= WINE_MSG_HASCTRL;
+    memcpy(buftmp, msg->lpBuffers, msg->dwBufferCount * sizeof(WSABUF));
+    buftmp[msg->dwBufferCount] = msg->Control;
+    ret = WS2_recvfrom( s, buftmp, msg->dwBufferCount, lpNumberOfBytesRecvd,
+                        &msg->dwFlags, msg->name, &msg->namelen,
+                        lpOverlapped, lpCompletionRoutine );
+    msg->Control = buftmp[msg->dwBufferCount];
+    HeapFree( GetProcessHeap(), 0, buftmp );
+    return ret;
+}
+
+/***********************************************************************
  *		bind			(WS2_32.2)
  */
 int WINAPI WS_bind(SOCKET s, const struct WS_sockaddr* name, int namelen)
@@ -2676,6 +2786,7 @@ INT WINAPI WS_getsockopt(SOCKET s, INT level,
         case WS_IP_MULTICAST_LOOP:
         case WS_IP_MULTICAST_TTL:
         case WS_IP_OPTIONS:
+        case WS_IP_PKTINFO:
         case WS_IP_TOS:
         case WS_IP_TTL:
             if ( (fd = get_sock_fd( s, 0, NULL )) == -1)
@@ -3201,7 +3312,8 @@ INT WINAPI WSAIoctl(SOCKET s,
         }
         else if ( IsEqualGUID(&wsarecvmsg_guid, lpvInBuffer) )
         {
-            FIXME("SIO_GET_EXTENSION_FUNCTION_POINTER: unimplemented WSARecvMsg\n");
+            *(LPFN_WSARECVMSG *)lpbOutBuffer = WS2_WSARecvMsg;
+            return 0;
         }
         else if ( IsEqualGUID(&wsasendmsg_guid, lpvInBuffer) )
         {
@@ -4137,6 +4249,7 @@ int WINAPI WS_setsockopt(SOCKET s, int level, int optname,
         case WS_IP_MULTICAST_LOOP:
         case WS_IP_MULTICAST_TTL:
         case WS_IP_OPTIONS:
+        case WS_IP_PKTINFO:
         case WS_IP_TOS:
         case WS_IP_TTL:
             convert_sockopt(&level, &optname);
@@ -5538,7 +5651,7 @@ static int WS2_recvfrom( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
 
     if (fd == -1) return SOCKET_ERROR;
 
-    if (!(wsa = HeapAlloc( GetProcessHeap(), 0, FIELD_OFFSET(struct ws2_async, iovec[dwBufferCount]) )))
+    if (!(wsa = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, FIELD_OFFSET(struct ws2_async, iovec[dwBufferCount]) )))
     {
         err = WSAEFAULT;
         goto error;
@@ -5550,6 +5663,12 @@ static int WS2_recvfrom( SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
     wsa->addrlen.ptr = lpFromlen;
     wsa->n_iovecs    = dwBufferCount;
     wsa->first_iovec = 0;
+    if (*lpFlags & WINE_MSG_HASCTRL)
+    {
+        *lpFlags &= ~WINE_MSG_HASCTRL;
+        wsa->control = lpBuffers[dwBufferCount].buf;
+        wsa->controllen = &lpBuffers[dwBufferCount].len;
+    }
     for (i = 0; i < dwBufferCount; i++)
     {
         /* check buffer first to trigger write watches */
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c
index e0f9ccf..f84ecb7 100644
--- a/dlls/ws2_32/tests/sock.c
+++ b/dlls/ws2_32/tests/sock.c
@@ -56,6 +56,8 @@
 static void   (WINAPI  *pFreeAddrInfoW)(PADDRINFOW) = 0;
 static int    (WINAPI  *pGetAddrInfoW)(LPCWSTR,LPCWSTR,const ADDRINFOW *,PADDRINFOW *) = 0;
 static PCSTR  (WINAPI  *pInetNtop)(INT,LPVOID,LPSTR,ULONG) = 0;
+static int    (WINAPI  *pWSARecvMsg)(SOCKET,LPWSAMSG,LPDWORD,LPWSAOVERLAPPED,LPWSAOVERLAPPED_COMPLETION_ROUTINE) = 0;
+GUID WSARecvMsg_GUID = WSAID_WSARECVMSG;
 
 /**************** Structs and typedefs ***************/
 
@@ -871,12 +873,19 @@ static void Init (void)
     WORD ver = MAKEWORD (2, 2);
     WSADATA data;
     HMODULE hws2_32 = GetModuleHandle("ws2_32.dll");
+    DWORD dwBytes;
+    SOCKET s;
 
     pFreeAddrInfoW = (void *)GetProcAddress(hws2_32, "FreeAddrInfoW");
     pGetAddrInfoW = (void *)GetProcAddress(hws2_32, "GetAddrInfoW");
     pInetNtop = (void *)GetProcAddress(hws2_32, "inet_ntop");
 
     ok ( WSAStartup ( ver, &data ) == 0, "WSAStartup failed\n" );
+    s = socket( AF_INET, SOCK_STREAM, 0 );
+    WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &WSARecvMsg_GUID, sizeof(WSARecvMsg_GUID),
+             &pWSARecvMsg, sizeof(pWSARecvMsg), &dwBytes, NULL, NULL);
+    if (!pWSARecvMsg) skip("WSARecvMsg is unsupported, some tests will be skipped.\n");
+    closesocket( s );
     tls = TlsAlloc();
 }
 
@@ -1085,6 +1094,85 @@ static void test_so_reuseaddr(void)
     closesocket(s2);
 }
 
+static void test_ip_pktinfo(void)
+{
+    ULONG addresses[2] = {inet_addr("127.0.0.1"), htonl(INADDR_ANY)};
+    char recvbuf[10], pktbuf[512], msg[] = "HELLO";
+    struct sockaddr_in s1addr, s2addr, s3addr;
+    unsigned int rc, foundhdr, yes = 1;
+    WSAOVERLAPPED ov;
+    WSACMSGHDR *cmsg;
+    WSABUF iovec[1];
+    SOCKET s1, s2;
+    WSAMSG hdr;
+    DWORD size;
+    int i;
+
+    ov.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+    s1addr.sin_family      = AF_INET;
+    s1addr.sin_port        = htons(9375);
+    s2addr.sin_family      = AF_INET;
+    s2addr.sin_port        = htons(9375);
+    s2addr.sin_addr.s_addr = addresses[0];
+    iovec[0].buf = recvbuf;
+    iovec[0].len = sizeof(recvbuf);
+    memset(&hdr, 0, sizeof(hdr));
+    hdr.name = (struct sockaddr*)&s3addr;
+    hdr.namelen = sizeof(s3addr);
+    hdr.lpBuffers = &iovec[0];
+    hdr.dwBufferCount = 1;
+    hdr.Control.buf = pktbuf;
+    hdr.dwFlags = 0;
+
+    for (i=0;i<sizeof(addresses)/sizeof(UINT32);i++)
+    {
+        s1addr.sin_addr.s_addr = addresses[i];
+        hdr.Control.len = sizeof(pktbuf);
+
+        /* Build "server" side socket */
+        s1=socket(AF_INET, SOCK_DGRAM, 0);
+        ok(s1!=INVALID_SOCKET, "socket() failed error: %d\n", WSAGetLastError());
+        rc=bind(s1, (struct sockaddr*)&s1addr, sizeof(s1addr));
+        ok(rc!=SOCKET_ERROR, "bind() failed error: %d\n", WSAGetLastError());
+        rc=setsockopt(s1, IPPROTO_IP, IP_PKTINFO, (const char*)&yes, sizeof(yes));
+        ok(rc==0, "failed to set IPPROTO_IP flag IP_PKTINFO!\n");
+        /* Build "client" side socket */
+        s2=socket(AF_INET, SOCK_DGRAM, 0);
+        ok(s2!=INVALID_SOCKET, "socket() failed error: %d\n", WSAGetLastError());
+
+        /* Send a packet from the client to the server */
+        rc=sendto(s2, msg, sizeof(msg), 0, (struct sockaddr*)&s2addr, sizeof(s2addr));
+        ok(rc==sizeof(msg), "send() failed error: %d\n", WSAGetLastError());
+
+        /*
+         * Receive the packet on the server side and check that
+         * the returned packet matches what was sent.
+         */
+        rc=pWSARecvMsg(s1, &hdr, &size, NULL, NULL);
+        ok(rc==0, "recvmsg() failed error: %d\n", WSAGetLastError());
+        ok(strncmp(iovec[0].buf, msg, sizeof(msg)) == 0,
+           "recvmsg() buffer does not match transmitted data!\n");
+
+        /* Test for the expected IP_PKTINFO return information. */
+        foundhdr = FALSE;
+        for (cmsg = WSA_CMSG_FIRSTHDR(&hdr); cmsg != NULL; cmsg = WSA_CMSG_NXTHDR(&hdr, cmsg))
+        {
+            if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO)
+            {
+                struct in_pktinfo *pi = (struct in_pktinfo *)WSA_CMSG_DATA(cmsg);
+
+                ok(pi->ipi_addr.s_addr == s2addr.sin_addr.s_addr, "destination ip mismatch!\n");
+                foundhdr = TRUE;
+            }
+        }
+        ok(foundhdr, "IP_PKTINFO header information was not returned!\n");
+
+        closesocket(s2);
+        closesocket(s1);
+    }
+}
+
 /************* Array containing the tests to run **********/
 
 #define STD_STREAM_SOCKET \
@@ -4351,6 +4439,7 @@ START_TEST( sock )
 
     test_set_getsockopt();
     test_so_reuseaddr();
+    if (pWSARecvMsg) test_ip_pktinfo();
     test_extendedSocketOptions();
 
     for (i = 0; i < NUM_TESTS; i++)
diff --git a/include/mswsock.h b/include/mswsock.h
index 322ab20..5749e59 100644
--- a/include/mswsock.h
+++ b/include/mswsock.h
@@ -91,16 +91,17 @@ extern "C" {
 #define DE_REUSE_SOCKET TF_REUSE_SOCKET
 
 #ifndef USE_WS_PREFIX
-#define MSG_TRUNC   0x0100
-#define MSG_CTRUNC  0x0200
-#define MSG_BCAST   0x0400
-#define MSG_MCAST   0x0800
+#define MSG_TRUNC        0x00000100
+#define MSG_CTRUNC       0x00000200
+#define MSG_BCAST        0x00000400
+#define MSG_MCAST        0x00000800
 #else
-#define WS_MSG_TRUNC   0x0100
-#define WS_MSG_CTRUNC  0x0200
-#define WS_MSG_BCAST   0x0400
-#define WS_MSG_MCAST   0x0800
+#define WS_MSG_TRUNC     0x00000100
+#define WS_MSG_CTRUNC    0x00000200
+#define WS_MSG_BCAST     0x00000400
+#define WS_MSG_MCAST     0x00000800
 #endif
+#define WINE_MSG_HASCTRL 0x10000000
 
 #define TF_DISCONNECT          0x01
 #define TF_REUSE_SOCKET        0x02
@@ -185,6 +186,25 @@ VOID WINAPI GetAcceptExSockaddrs(PVOID, DWORD, DWORD, DWORD, struct WS(sockaddr)
 BOOL WINAPI TransmitFile(SOCKET, HANDLE, DWORD, DWORD, LPOVERLAPPED, LPTRANSMIT_FILE_BUFFERS, DWORD);
 INT  WINAPI WSARecvEx(SOCKET, char *, INT, INT *);
 
+/*
+ * Macros for retrieving control message data returned by WSARecvMsg()
+ */
+#define WSA_CMSG_DATA(cmsg)     ((UCHAR*)((WSACMSGHDR*)(cmsg)+1))
+#define WSA_CMSG_FIRSTHDR(mhdr) \
+        ((size_t) (mhdr)->Control.len >= sizeof(WSACMSGHDR)		      \
+         ? (WSACMSGHDR *) (mhdr)->Control.buf : (WSACMSGHDR *) 0)
+#define WSA_CMSG_ALIGN(len)     (((len) + sizeof (size_t) - 1) \
+                                 & (size_t) ~(sizeof (size_t) - 1))
+static inline WSACMSGHDR *WSA_CMSG_NXTHDR(WSAMSG *msg, WSACMSGHDR *cmsg)
+{
+    if ((size_t)(msg)->Control.len<sizeof(WSACMSGHDR)) return 0;
+    cmsg = (WSACMSGHDR*)((unsigned char*)cmsg + WSA_CMSG_ALIGN(cmsg->cmsg_len));
+    if ((unsigned char*)(cmsg+1) > ((unsigned char*)msg->Control.buf + msg->Control.len)
+        || ((unsigned char*)cmsg + WSA_CMSG_ALIGN(cmsg->cmsg_len) > ((unsigned char *)msg->Control.buf + msg->Control.len)) )
+        return 0;
+    return cmsg;
+}
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/ws2ipdef.h b/include/ws2ipdef.h
index 11b3689..ec0b85a 100644
--- a/include/ws2ipdef.h
+++ b/include/ws2ipdef.h
@@ -153,6 +153,15 @@ typedef struct WS(ip_msfilter) {
     struct WS(in_addr) imsf_slist[1];
 } WS(IP_MSFILTER), *WS(PIP_MSFILTER);
 
+/*
+ * Windows IP_PKTINFO packet header information, the
+ * Unix version of this structure is quite different.
+ */
+typedef struct WS(in_pktinfo) {
+  IN_ADDR ipi_addr;
+  UINT    ipi_ifindex;
+} WS(in_pktinfo);
+
 #ifndef USE_WS_PREFIX
 #define IPV6_OPTIONS                    1
 #define IPV6_HDRINCL                    2
-- 
1.7.0.4



Reply via email to