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