From c58de7ee44f1545c71bbfa76388c628a1b97baf7 Mon Sep 17 00:00:00 2001 From: Erich Hoover Date: Sat, 8 May 2010 16:13:37 -0600 Subject: ws2_32: Patch to selectively bind to interfaces while still allowing broadcast packets. --- dlls/ws2_32/socket.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 213 insertions(+), 3 deletions(-) diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index e6a6973..144ec7a 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -156,6 +156,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 @@ -691,6 +694,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 @@ -1373,12 +1402,42 @@ 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_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; @@ -1395,17 +1454,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. @@ -1471,14 +1560,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; @@ -1513,9 +1688,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; @@ -1733,6 +1939,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