From: Paul Gofman pgofman@codeweavers.com
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/ntdll/unix/socket.c | 105 ++++++++++++++++++++++++++++++++++++++- server/sock.c | 17 +++++++ 2 files changed, 121 insertions(+), 1 deletion(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index ef3c8494c3d..09a14b75638 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -115,6 +115,7 @@ struct async_recv_ioctl DWORD *ret_flags; int unix_flags; unsigned int count; + BOOL icmp_over_dgram; struct iovec iov[1]; };
@@ -566,6 +567,89 @@ static int wow64_translate_control( const WSABUF *control64, struct afd_wsabuf_3 return 1; }
+struct ip_hdr +{ + BYTE v_hl; /* version << 4 | hdr_len */ + BYTE tos; + UINT16 tot_len; + UINT16 id; + UINT16 frag_off; + BYTE ttl; + BYTE protocol; + UINT16 checksum; + ULONG saddr; + ULONG daddr; +}; + +static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *unix_addr, + ssize_t recv_len, NTSTATUS *status ) +{ + unsigned int tot_len = sizeof(struct ip_hdr) + recv_len; + struct cmsghdr *cmsg; + struct ip_hdr ip_h; + size_t buf_len; + char *buf; + + if (hdr->msg_iovlen != 1) + { + FIXME( "Buffer count %zd is not supported for ICMP fixup.\n", hdr->msg_iovlen ); + return recv_len; + } + + buf = hdr->msg_iov[0].iov_base; + buf_len = hdr->msg_iov[0].iov_len; + + if (recv_len + sizeof(ip_h) > buf_len) + *status = STATUS_BUFFER_OVERFLOW; + + if (buf_len < sizeof(ip_h)) + { + recv_len = buf_len; + } + else + { + recv_len = min( recv_len, buf_len - sizeof(ip_h) ); + memmove( buf + sizeof(ip_h), buf, recv_len ); + recv_len += sizeof(ip_h); + } + memset( &ip_h, 0, sizeof(ip_h) ); + ip_h.v_hl = (4 << 4) | (sizeof(ip_h) >> 2); + ip_h.tot_len = htons( tot_len ); + ip_h.protocol = 1; + ip_h.saddr = unix_addr->in.sin_addr.s_addr; + + for (cmsg = CMSG_FIRSTHDR( hdr ); cmsg; cmsg = CMSG_NXTHDR( hdr, cmsg )) + { + if (cmsg->cmsg_level != IPPROTO_IP) continue; + switch (cmsg->cmsg_type) + { +#if defined(IP_TTL) + case IP_TTL: + ip_h.ttl = *(BYTE *)CMSG_DATA( cmsg ); + break; +#endif +#if defined(IP_TOS) + case IP_TOS: + ip_h.tos = *(BYTE *)CMSG_DATA( cmsg ); + break; +#endif +#if defined(IP_PKTINFO) + case IP_PKTINFO: + { + struct in_pktinfo *info; + + info = (struct in_pktinfo *)CMSG_DATA( cmsg ); + ip_h.daddr = info->ipi_addr.s_addr; + break; + } +#endif + } + } + memcpy( buf, &ip_h, min( sizeof(ip_h), buf_len )); + + return recv_len; +} + static NTSTATUS try_recv( int fd, struct async_recv_ioctl *async, ULONG_PTR *size ) { #ifndef HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS @@ -577,7 +661,7 @@ static NTSTATUS try_recv( int fd, struct async_recv_ioctl *async, ULONG_PTR *siz ssize_t ret;
memset( &hdr, 0, sizeof(hdr) ); - if (async->addr) + if (async->addr || async->icmp_over_dgram) { hdr.msg_name = &unix_addr.addr; hdr.msg_namelen = sizeof(unix_addr); @@ -602,9 +686,14 @@ static NTSTATUS try_recv( int fd, struct async_recv_ioctl *async, ULONG_PTR *siz }
status = (hdr.msg_flags & MSG_TRUNC) ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS; + if (async->icmp_over_dgram) + ret = fixup_icmp_over_dgram( &hdr, &unix_addr, ret, &status );
if (async->control) { + if (async->icmp_over_dgram) + FIXME( "May return extra control headers.\n" ); + if (in_wow64_call()) { char control_buffer64[512]; @@ -675,6 +764,19 @@ static BOOL async_recv_proc( void *user, ULONG_PTR *info, NTSTATUS *status ) return TRUE; }
+static BOOL is_icmp_over_dgram( int fd ) +{ + socklen_t len; + int val; + + len = sizeof(val); + if (getsockopt( fd, SOL_SOCKET, SO_PROTOCOL, (char *)&val, &len ) || val != IPPROTO_ICMP) + return FALSE; + + len = sizeof(val); + return !getsockopt( fd, SOL_SOCKET, SO_TYPE, (char *)&val, &len ) && val == SOCK_DGRAM; +} + static NTSTATUS sock_recv( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user, IO_STATUS_BLOCK *io, int fd, struct async_recv_ioctl *async, int force_async ) { @@ -777,6 +879,7 @@ static NTSTATUS sock_ioctl_recv( HANDLE handle, HANDLE event, PIO_APC_ROUTINE ap async->addr = addr; async->addr_len = addr_len; async->ret_flags = ret_flags; + async->icmp_over_dgram = is_icmp_over_dgram( fd );
return sock_recv( handle, event, apc, apc_user, io, fd, async, force_async ); } diff --git a/server/sock.c b/server/sock.c index 9f872c65637..f3f43c58426 100644 --- a/server/sock.c +++ b/server/sock.c @@ -1622,6 +1622,23 @@ static int init_socket( struct sock *sock, int family, int type, int protocol, u }
sockfd = socket( unix_family, unix_type, unix_protocol ); + +#ifdef linux + if (sockfd == -1 && errno == EPERM && unix_family == AF_INET + && unix_type == SOCK_RAW && unix_protocol == IPPROTO_ICMP) + { + sockfd = socket( unix_family, SOCK_DGRAM, unix_protocol ); + if (sockfd != -1) + { + const int val = 1; + + setsockopt( sockfd, IPPROTO_IP, IP_RECVTTL, (const char *)&val, sizeof(val) ); + setsockopt( sockfd, IPPROTO_IP, IP_RECVTOS, (const char *)&val, sizeof(val) ); + setsockopt( sockfd, IPPROTO_IP, IP_PKTINFO, (const char *)&val, sizeof(val) ); + } + } +#endif + if (sockfd == -1) { if (errno == EINVAL) set_win32_error( WSAESOCKTNOSUPPORT );