ICMP over SOCK_DGRAM is already used in ndispproxy.sys. But apps may also use ICMP directly over SOCK_RAW (Hardspace: Shipbreaker is an example) and that requires admin privileges on Linux (or setcap cap_net_raw+ep on wineserver).
AF_INET / SOCK_RAW / IPPROTO_ICMP works on Windows without admin privileges.
Comments: - IP, ICMP header structures and chksum() functions are taken from ndisproxy.sys:icmp_echo.c; - ICMP over DGRAM, while designed specifically to allow ICMP without admin permissions, still needs some setup Linux, /proc/sys/net/ipv4/ping_group_range controls that. Such socket creation fails on Testbot Debian machines so I left skip() path in test_icmp(); - Linux substitute local port number instead of provided ICMP packet id, while the referenced game depends on reply id matching the id it provided in request. So the last part performs the fixup.
-- v2: ntdll: Compute checksum for ICMP over SOCK_DGRAM IP header. ntdll: Fixup ICMP packet id if SOCK_DGRAM fallback is used. ntdll: Support SOCK_RAW / IPPROTO_ICMP fallback over SOCK_DGRAM. ws2_32/tests: Test ICMP. ws2_32: Add ipv4 raw socket protocol info.
From: Paul Gofman pgofman@codeweavers.com
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/ws2_32/socket.c | 30 ++++++++++++++++++++++++------ dlls/ws2_32/tests/protocol.c | 2 ++ dlls/ws2_32/tests/sock.c | 23 +++++++++++++++++++++-- 3 files changed, 47 insertions(+), 8 deletions(-)
diff --git a/dlls/ws2_32/socket.c b/dlls/ws2_32/socket.c index 9cc75aacf20..d3c93e15a47 100644 --- a/dlls/ws2_32/socket.c +++ b/dlls/ws2_32/socket.c @@ -70,6 +70,23 @@ static const WSAPROTOCOL_INFOW supported_protocols[] = .dwMessageSize = 0xffbb, .szProtocol = L"UDP/IP", }, + { + .dwServiceFlags1 = XP1_IFS_HANDLES | XP1_SUPPORT_BROADCAST + | XP1_SUPPORT_MULTIPOINT | XP1_MESSAGE_ORIENTED | XP1_CONNECTIONLESS, + .dwProviderFlags = PFL_MATCHES_PROTOCOL_ZERO | PFL_HIDDEN, + .ProviderId = {0xe70f1aa0, 0xab8b, 0x11cf, {0x8c, 0xa3, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}}, + .dwCatalogEntryId = 1003, + .ProtocolChain.ChainLen = 1, + .iVersion = 2, + .iAddressFamily = AF_INET, + .iMaxSockAddr = sizeof(struct sockaddr_in), + .iMinSockAddr = sizeof(struct sockaddr_in), + .iSocketType = SOCK_RAW, + .iProtocol = 0, + .iProtocolMaxOffset = 255, + .dwMessageSize = 0x8000, + .szProtocol = L"MSAFD Tcpip [RAW/IP]", + }, { .dwServiceFlags1 = XP1_IFS_HANDLES | XP1_EXPEDITED_DATA | XP1_GRACEFUL_CLOSE | XP1_GUARANTEED_ORDER | XP1_GUARANTEED_DELIVERY, @@ -4097,12 +4114,13 @@ int WINAPI WSARecvDisconnect( SOCKET s, WSABUF *data ) }
-static BOOL protocol_matches_filter( const int *filter, int protocol ) +static BOOL protocol_matches_filter( const int *filter, unsigned int index ) { + if (supported_protocols[index].dwProviderFlags & PFL_HIDDEN) return FALSE; if (!filter) return TRUE; while (*filter) { - if (protocol == *filter++) return TRUE; + if (supported_protocols[index].iProtocol == *filter++) return TRUE; } return FALSE; } @@ -4120,7 +4138,7 @@ int WINAPI WSAEnumProtocolsA( int *filter, WSAPROTOCOL_INFOA *protocols, DWORD *
for (i = 0; i < ARRAY_SIZE(supported_protocols); ++i) { - if (protocol_matches_filter( filter, supported_protocols[i].iProtocol )) + if (protocol_matches_filter( filter, i )) ++count; }
@@ -4134,7 +4152,7 @@ int WINAPI WSAEnumProtocolsA( int *filter, WSAPROTOCOL_INFOA *protocols, DWORD * count = 0; for (i = 0; i < ARRAY_SIZE(supported_protocols); ++i) { - if (protocol_matches_filter( filter, supported_protocols[i].iProtocol )) + if (protocol_matches_filter( filter, i )) { memcpy( &protocols[count], &supported_protocols[i], offsetof( WSAPROTOCOL_INFOW, szProtocol ) ); WideCharToMultiByte( CP_ACP, 0, supported_protocols[i].szProtocol, -1, @@ -4190,7 +4208,7 @@ int WINAPI WSAEnumProtocolsW( int *filter, WSAPROTOCOL_INFOW *protocols, DWORD *
for (i = 0; i < ARRAY_SIZE(supported_protocols); ++i) { - if (protocol_matches_filter( filter, supported_protocols[i].iProtocol )) + if (protocol_matches_filter( filter, i )) ++count; }
@@ -4204,7 +4222,7 @@ int WINAPI WSAEnumProtocolsW( int *filter, WSAPROTOCOL_INFOW *protocols, DWORD * count = 0; for (i = 0; i < ARRAY_SIZE(supported_protocols); ++i) { - if (protocol_matches_filter( filter, supported_protocols[i].iProtocol )) + if (protocol_matches_filter( filter, i )) protocols[count++] = supported_protocols[i]; } return count; diff --git a/dlls/ws2_32/tests/protocol.c b/dlls/ws2_32/tests/protocol.c index 9a6c8610718..0b14543606a 100644 --- a/dlls/ws2_32/tests/protocol.c +++ b/dlls/ws2_32/tests/protocol.c @@ -107,6 +107,7 @@ static void test_WSAEnumProtocolsA(void) for (i = 0; i < ret; i++) { ok( strlen( buffer[i].szProtocol ), "No protocol name found\n" ); + ok( !(buffer[i].dwProviderFlags & PFL_HIDDEN), "Found a protocol with PFL_HIDDEN.\n" ); test_service_flags( buffer[i].iAddressFamily, buffer[i].iVersion, buffer[i].iSocketType, buffer[i].iProtocol, buffer[i].dwServiceFlags1); @@ -174,6 +175,7 @@ static void test_WSAEnumProtocolsW(void) for (i = 0; i < ret; i++) { ok( lstrlenW( buffer[i].szProtocol ), "No protocol name found\n" ); + ok( !(buffer[i].dwProviderFlags & PFL_HIDDEN), "Found a protocol with PFL_HIDDEN.\n" ); test_service_flags( buffer[i].iAddressFamily, buffer[i].iVersion, buffer[i].iSocketType, buffer[i].iProtocol, buffer[i].dwServiceFlags1); diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index 3ec0ab67040..f611fe6ed9d 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -2937,18 +2937,38 @@ static void test_WSASocket(void) } else { + WSAPROTOCOL_INFOW info; + size = sizeof(socktype); socktype = 0xdead; err = getsockopt(sock, SOL_SOCKET, SO_TYPE, (char *) &socktype, &size); ok(!err, "getsockopt failed with %d\n", WSAGetLastError()); ok(socktype == SOCK_RAW, "Wrong socket type, expected %d received %d\n", SOCK_RAW, socktype); + + size = sizeof(info); + err = getsockopt(sock, SOL_SOCKET, SO_PROTOCOL_INFOW, (char *) &info, &size); + ok(!err,"got error %d\n", WSAGetLastError()); + ok(!wcscmp(info.szProtocol, L"MSAFD Tcpip [RAW/IP]") + || broken(!wcscmp(info.szProtocol, L"MSAFD-Tcpip [RAW/IP]")) /* Some Win7 machines. */, + "got szProtocol %s.\n", debugstr_w(info.szProtocol)); + ok(info.iAddressFamily == AF_INET, "got iAddressFamily %d.\n", info.iAddressFamily); + ok(info.iSocketType == SOCK_RAW, "got iSocketType %d.\n", info.iSocketType); + ok(info.iMaxSockAddr == 0x10, "got iMaxSockAddr %d.\n", info.iMaxSockAddr); + ok(info.iMinSockAddr == 0x10, "got iMinSockAddr %d.\n", info.iMinSockAddr); + todo_wine ok(!info.iProtocol, "got iProtocol %d.\n", info.iProtocol); + ok(info.iProtocolMaxOffset == 255, "got iProtocol %d.\n", info.iProtocolMaxOffset); + ok(info.dwProviderFlags == (PFL_MATCHES_PROTOCOL_ZERO | PFL_HIDDEN), "got dwProviderFlags %#lx.\n", + info.dwProviderFlags); + ok(info.dwServiceFlags1 == (XP1_IFS_HANDLES | XP1_SUPPORT_BROADCAST | XP1_SUPPORT_MULTIPOINT + | XP1_MESSAGE_ORIENTED | XP1_CONNECTIONLESS), "got dwServiceFlags1 %#lx.\n", + info.dwServiceFlags1); + closesocket(sock);
sock = WSASocketA(0, 0, IPPROTO_RAW, NULL, 0, 0); if (sock != INVALID_SOCKET) { - todo_wine { size = sizeof(socktype); socktype = 0xdead; err = getsockopt(sock, SOL_SOCKET, SO_TYPE, (char *) &socktype, &size); @@ -2956,7 +2976,6 @@ static void test_WSASocket(void) ok(socktype == SOCK_RAW, "Wrong socket type, expected %d received %d\n", SOCK_RAW, socktype); closesocket(sock); - }
sock = WSASocketA(AF_INET, SOCK_RAW, IPPROTO_TCP, NULL, 0, 0); ok(sock != INVALID_SOCKET, "Failed to create socket: %d\n",
From: Paul Gofman pgofman@codeweavers.com
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/ws2_32/tests/sock.c | 150 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+)
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index f611fe6ed9d..d24556ccdd2 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -12816,6 +12816,155 @@ static void test_tcp_reset(void) CloseHandle(overlapped.hEvent); }
+struct icmp_hdr +{ + BYTE type; + BYTE code; + UINT16 checksum; + union + { + struct + { + UINT16 id; + UINT16 sequence; + } echo; + } un; +}; + +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; +}; + +/* rfc 1071 checksum */ +static unsigned short chksum(BYTE *data, unsigned int count) +{ + unsigned int sum = 0, carry = 0; + unsigned short check, s; + + while (count > 1) + { + s = *(unsigned short *)data; + data += 2; + sum += carry; + sum += s; + carry = s > sum; + count -= 2; + } + sum += carry; /* This won't produce another carry */ + sum = (sum & 0xffff) + (sum >> 16); + + if (count) sum += *data; /* LE-only */ + + sum = (sum & 0xffff) + (sum >> 16); + /* fold in any carry */ + sum = (sum & 0xffff) + (sum >> 16); + + check = ~sum; + return check; +} + +static void test_icmp(void) +{ + static const unsigned int ping_data = 0xdeadbeef; + struct icmp_hdr *icmp_h; + BYTE send_buf[sizeof(struct icmp_hdr) + sizeof(ping_data)]; + UINT16 recv_checksum, checksum; + unsigned int reply_data; + struct sockaddr_in sa; + struct ip_hdr *ip_h; + struct in_addr addr; + BYTE recv_buf[256]; + SOCKET s; + int ret; + + s = WSASocketA(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0); + if (s == INVALID_SOCKET) + { + ret = WSAGetLastError(); + ok(ret == WSAEACCES, "Expected 10013, received %d\n", ret); + skip("SOCK_RAW is not supported\n"); + return; + } + + icmp_h = (struct icmp_hdr *)send_buf; + icmp_h->type = ICMP4_ECHO_REQUEST; + icmp_h->code = 0; + icmp_h->checksum = 0; + icmp_h->un.echo.id = 0xbeaf; /* will be overwritten for linux ping socks */ + icmp_h->un.echo.sequence = 1; + *(unsigned int *)(icmp_h + 1) = ping_data; + icmp_h->checksum = chksum(send_buf, sizeof(send_buf)); + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = 0; + sa.sin_addr.s_addr = inet_addr("127.0.0.1"); + + ret = sendto(s, (char *)send_buf, sizeof(send_buf), 0, (struct sockaddr*)&sa, sizeof(sa)); + ok(ret == sizeof(send_buf), "got %d, error %d.\n", ret, WSAGetLastError()); + + ret = recv(s, (char *)recv_buf, sizeof(struct ip_hdr) + sizeof(send_buf) - 1, 0); + ok(ret == -1, "got %d\n", ret); + ok(WSAGetLastError() == WSAEMSGSIZE, "got %d\n", WSAGetLastError()); + + icmp_h->un.echo.sequence = 2; + icmp_h->checksum = 0; + icmp_h->checksum = chksum(send_buf, sizeof(send_buf)); + + ret = sendto(s, (char *)send_buf, sizeof(send_buf), 0, (struct sockaddr*)&sa, sizeof(sa)); + ok(ret != SOCKET_ERROR, "got error %d.\n", WSAGetLastError()); + + memset(recv_buf, 0xcc, sizeof(recv_buf)); + ret = recv(s, (char *)recv_buf, sizeof(recv_buf), 0); + ok(ret == sizeof(struct ip_hdr) + sizeof(send_buf), "got %d\n", ret); + + ip_h = (struct ip_hdr *)recv_buf; + icmp_h = (struct icmp_hdr *)(ip_h + 1); + reply_data = *(unsigned int *)(icmp_h + 1); + + ok(ip_h->v_hl == ((4 << 4) | (sizeof(*ip_h) >> 2)), "got v_hl %#x.\n", ip_h->v_hl); + ok(ntohs(ip_h->tot_len) == sizeof(struct ip_hdr) + sizeof(send_buf), + "got tot_len %#x.\n", ntohs(ip_h->tot_len)); + + recv_checksum = ip_h->checksum; + ip_h->checksum = 0; + checksum = chksum((BYTE *)ip_h, sizeof(*ip_h)); + /* Checksum is 0 for localhost ping on Windows but not for remote host ping. */ + ok(recv_checksum == checksum || !recv_checksum, "got checksum %#x, expected %#x.\n", recv_checksum, checksum); + + ok(!ip_h->frag_off, "got id %#x.\n", ip_h->frag_off); + addr.s_addr = ip_h->saddr; + ok(ip_h->saddr == sa.sin_addr.s_addr, "got saddr %s.\n", inet_ntoa(addr)); + addr.s_addr = ip_h->daddr; + ok(!!ip_h->daddr, "got daddr %s.\n", inet_ntoa(addr)); + + ok(ip_h->protocol == 1, "got protocol %#x.\n", ip_h->protocol); + + ok(icmp_h->type == ICMP4_ECHO_REPLY, "got type %#x.\n", icmp_h->type); + ok(!icmp_h->code, "got code %#x.\n", icmp_h->code); + todo_wine ok(icmp_h->un.echo.id == 0xbeaf, "got echo id %#x.\n", icmp_h->un.echo.id); + ok(icmp_h->un.echo.sequence == 2, "got echo sequence %#x.\n", icmp_h->un.echo.sequence); + + recv_checksum = icmp_h->checksum; + icmp_h->checksum = 0; + checksum = chksum((BYTE *)icmp_h, sizeof(send_buf)); + ok(recv_checksum == checksum, "got checksum %#x, expected %#x.\n", recv_checksum, checksum); + + ok(reply_data == ping_data, "got reply_data %#x.\n", reply_data); + + closesocket(s); +} + START_TEST( sock ) { int i; @@ -12892,6 +13041,7 @@ START_TEST( sock ) test_empty_recv(); test_timeout(); test_tcp_reset(); + test_icmp();
/* this is an io heavy test, do it at the end so the kernel doesn't start dropping packets */ test_send();
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 );
On 7/14/22 17:40, Paul Gofman wrote:
+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;
SO_PROTOCOL is nonstandard (I believe Mac specifically doesn't have it). You may want to guard out this whole function with "#ifdef linux".
- len = sizeof(val);
- return !getsockopt( fd, SOL_SOCKET, SO_TYPE, (char *)&val, &len ) && val == SOCK_DGRAM;
+}
From: Paul Gofman pgofman@codeweavers.com
Signed-off-by: Paul Gofman pgofman@codeweavers.com --- dlls/ntdll/unix/socket.c | 98 +++++++++++++++++++++++++++++++++++++++- dlls/ws2_32/tests/sock.c | 2 +- server/protocol.def | 18 ++++++++ server/sock.c | 54 ++++++++++++++++++++++ 4 files changed, 169 insertions(+), 3 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 09a14b75638..10ad033f385 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -581,10 +581,55 @@ struct ip_hdr ULONG daddr; };
+struct icmp_hdr +{ + BYTE type; + BYTE code; + UINT16 checksum; + union + { + struct + { + UINT16 id; + UINT16 sequence; + } echo; + } un; +}; + +/* rfc 1071 checksum */ +static unsigned short chksum(BYTE *data, unsigned int count) +{ + unsigned int sum = 0, carry = 0; + unsigned short check, s; + + while (count > 1) + { + s = *(unsigned short *)data; + data += 2; + sum += carry; + sum += s; + carry = s > sum; + count -= 2; + } + sum += carry; /* This won't produce another carry */ + sum = (sum & 0xffff) + (sum >> 16); + + if (count) sum += *data; /* LE-only */ + + sum = (sum & 0xffff) + (sum >> 16); + /* fold in any carry */ + sum = (sum & 0xffff) + (sum >> 16); + + check = ~sum; + return check; +} + static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *unix_addr, - ssize_t recv_len, NTSTATUS *status ) + HANDLE handle, ssize_t recv_len, NTSTATUS *status ) { unsigned int tot_len = sizeof(struct ip_hdr) + recv_len; + struct icmp_hdr *icmp_h = NULL; + NTSTATUS fixup_status; struct cmsghdr *cmsg; struct ip_hdr ip_h; size_t buf_len; @@ -610,6 +655,8 @@ static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *u { recv_len = min( recv_len, buf_len - sizeof(ip_h) ); memmove( buf + sizeof(ip_h), buf, recv_len ); + if (recv_len >= sizeof(struct icmp_hdr)) + icmp_h = (struct icmp_hdr *)(buf + sizeof(ip_h)); recv_len += sizeof(ip_h); } memset( &ip_h, 0, sizeof(ip_h) ); @@ -645,6 +692,25 @@ static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *u #endif } } + if (icmp_h) + { + SERVER_START_REQ( socket_get_icmp_id ) + { + req->handle = wine_server_obj_handle( handle ); + req->icmp_seq = icmp_h->un.echo.sequence; + if (!(fixup_status = wine_server_call( req ))) + icmp_h->un.echo.id = reply->icmp_id; + else + WARN( "socket_get_fixup_data returned %#x.\n", fixup_status ); + } + SERVER_END_REQ; + + if (!fixup_status) + { + icmp_h->checksum = 0; + icmp_h->checksum = chksum( (BYTE *)icmp_h, recv_len - sizeof(ip_h) ); + } + } memcpy( buf, &ip_h, min( sizeof(ip_h), buf_len ));
return recv_len; @@ -687,7 +753,7 @@ 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 ); + ret = fixup_icmp_over_dgram( &hdr, &unix_addr, async->io.handle, ret, &status );
if (async->control) { @@ -997,6 +1063,31 @@ static BOOL async_send_proc( void *user, ULONG_PTR *info, NTSTATUS *status ) return TRUE; }
+static void sock_save_icmp_id( struct async_send_ioctl *async ) +{ + unsigned short id, seq; + struct icmp_hdr *h; + + if (async->count != 1 || async->iov[0].iov_len < sizeof(*h)) + { + FIXME( "ICMP over DGRAM fixup is not supported for count %u, len %zu.\n", async->count, async->iov[0].iov_len ); + return; + } + + h = async->iov[0].iov_base; + id = h->un.echo.id; + seq = h->un.echo.sequence; + SERVER_START_REQ( socket_send_icmp_id ) + { + req->handle = wine_server_obj_handle( async->io.handle ); + req->icmp_id = id; + req->icmp_seq = seq; + if (wine_server_call( req )) + WARN( "socket_fixup_send_data failed.\n" ); + } + SERVER_END_REQ; +} + static NTSTATUS sock_send( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, void *apc_user, IO_STATUS_BLOCK *io, int fd, struct async_send_ioctl *async, int force_async ) { @@ -1017,6 +1108,9 @@ static NTSTATUS sock_send( HANDLE handle, HANDLE event, PIO_APC_ROUTINE apc, voi } SERVER_END_REQ;
+ if (!NT_ERROR(status) && is_icmp_over_dgram( fd )) + sock_save_icmp_id( async ); + alerted = status == STATUS_ALERTED; if (alerted) { diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index d24556ccdd2..32872e1b683 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -12952,7 +12952,7 @@ static void test_icmp(void)
ok(icmp_h->type == ICMP4_ECHO_REPLY, "got type %#x.\n", icmp_h->type); ok(!icmp_h->code, "got code %#x.\n", icmp_h->code); - todo_wine ok(icmp_h->un.echo.id == 0xbeaf, "got echo id %#x.\n", icmp_h->un.echo.id); + ok(icmp_h->un.echo.id == 0xbeaf, "got echo id %#x.\n", icmp_h->un.echo.id); ok(icmp_h->un.echo.sequence == 2, "got echo sequence %#x.\n", icmp_h->un.echo.sequence);
recv_checksum = icmp_h->checksum; diff --git a/server/protocol.def b/server/protocol.def index 6cb6275f2f2..a8044582ef8 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1470,6 +1470,24 @@ enum server_fd_type @END
+/* Store ICMP id for ICMP over datagram fixup */ +@REQ(socket_send_icmp_id) + obj_handle_t handle; /* socket handle */ + unsigned short icmp_id; /* ICMP packet id */ + unsigned short icmp_seq; /* ICMP packed sequence */ +@REPLY +@END + + +/* Get ICMP id for ICMP over datagram fixup */ +@REQ(socket_get_icmp_id) + obj_handle_t handle; /* socket handle */ + unsigned short icmp_seq; /* ICMP packed sequence */ +@REPLY + unsigned short icmp_id; /* ICMP packet id */ +@END + + /* Retrieve the next pending console ioctl request */ @REQ(get_next_console_request) obj_handle_t handle; /* console server handle */ diff --git a/server/sock.c b/server/sock.c index f3f43c58426..f413c45ef23 100644 --- a/server/sock.c +++ b/server/sock.c @@ -168,6 +168,8 @@ enum connection_state SOCK_CONNECTIONLESS, };
+#define MAX_ICMP_HISTORY_LENGTH 8 + struct sock { struct object obj; /* object header */ @@ -216,6 +218,13 @@ struct sock unsigned int sndbuf; /* advisory send buffer size */ unsigned int rcvtimeo; /* receive timeout in ms */ unsigned int sndtimeo; /* send timeout in ms */ + struct + { + unsigned short icmp_id; + unsigned short icmp_seq; + } + icmp_fixup_data[MAX_ICMP_HISTORY_LENGTH]; /* Sent ICMP packets history used to fixup reply id. */ + unsigned int icmp_fixup_data_len; /* Sent ICMP packets history length. */ unsigned int rd_shutdown : 1; /* is the read end shut down? */ unsigned int wr_shutdown : 1; /* is the write end shut down? */ unsigned int wr_shutdown_pending : 1; /* is a write shutdown pending? */ @@ -1506,6 +1515,7 @@ static struct sock *create_socket(void) sock->sndbuf = 0; sock->rcvtimeo = 0; sock->sndtimeo = 0; + sock->icmp_fixup_data_len = 0; init_async_queue( &sock->read_q ); init_async_queue( &sock->write_q ); init_async_queue( &sock->ifchange_q ); @@ -3594,3 +3604,47 @@ DECL_HANDLER(send_socket) } release_object( sock ); } + +DECL_HANDLER(socket_send_icmp_id) +{ + struct sock *sock = (struct sock *)get_handle_obj( current->process, req->handle, 0, &sock_ops ); + + if (!sock) return; + + if (sock->icmp_fixup_data_len == MAX_ICMP_HISTORY_LENGTH) + { + memmove( sock->icmp_fixup_data, sock->icmp_fixup_data + 1, + sizeof(*sock->icmp_fixup_data) * (MAX_ICMP_HISTORY_LENGTH - 1) ); + --sock->icmp_fixup_data_len; + } + + sock->icmp_fixup_data[sock->icmp_fixup_data_len].icmp_id = req->icmp_id; + sock->icmp_fixup_data[sock->icmp_fixup_data_len].icmp_seq = req->icmp_seq; + ++sock->icmp_fixup_data_len; + + release_object( sock ); +} + +DECL_HANDLER(socket_get_icmp_id) +{ + struct sock *sock = (struct sock *)get_handle_obj( current->process, req->handle, 0, &sock_ops ); + unsigned int i; + + if (!sock) return; + + for (i = 0; i < sock->icmp_fixup_data_len; ++i) + { + if (sock->icmp_fixup_data[i].icmp_seq == req->icmp_seq) + { + reply->icmp_id = sock->icmp_fixup_data[i].icmp_id; + --sock->icmp_fixup_data_len; + memmove( &sock->icmp_fixup_data[i], &sock->icmp_fixup_data[i + 1], + (sock->icmp_fixup_data_len - i) * sizeof(*sock->icmp_fixup_data) ); + release_object( sock ); + return; + } + } + + set_error( STATUS_NOT_FOUND ); + release_object( sock ); +}
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/unix/socket.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 10ad033f385..be0e65fa47c 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -711,6 +711,8 @@ static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *u icmp_h->checksum = chksum( (BYTE *)icmp_h, recv_len - sizeof(ip_h) ); } } + ip_h.checksum = 0; + ip_h.checksum = chksum( (BYTE *)&ip_h, sizeof(ip_h) ); memcpy( buf, &ip_h, min( sizeof(ip_h), buf_len ));
return recv_len;
v2: - Test: don't pass server id as a parameter, check send length exactly; - Guess icmp over datagram mode in ntdll based on native socket protocol and type instead of getting that from server; - Move icmp over datagram fixup handling to helper functions; - Use NT_ERROR() instead of open coding status checks in sock_send().