From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/unix/socket.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 5156bbfee10..0c53585443e 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -588,12 +588,12 @@ struct icmp_hdr } un; };
-/* rfc 1071 checksum */ -static unsigned short chksum(BYTE *data, unsigned int count) +static unsigned int chksum_add( BYTE *data, unsigned int count, unsigned int sum ) { - unsigned int sum = 0, carry = 0; - unsigned short check, s; + unsigned int carry = 0; + unsigned short s;
+ assert( !(count % 2) ); while (count > 1) { s = *(unsigned short *)data; @@ -604,9 +604,18 @@ static unsigned short chksum(BYTE *data, unsigned int count) count -= 2; } sum += carry; /* This won't produce another carry */ + return sum; +} + +/* rfc 1071 checksum */ +static unsigned short chksum( BYTE *data, unsigned int count, unsigned int sum ) +{ + unsigned short check; + + sum = chksum_add( data, count & ~1u, sum ); sum = (sum & 0xffff) + (sum >> 16);
- if (count) sum += *data; /* LE-only */ + if (count % 2) sum += data[count - 1]; /* LE-only */
sum = (sum & 0xffff) + (sum >> 16); /* fold in any carry */ @@ -700,10 +709,10 @@ static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *u if (!fixup_status) { icmp_h->checksum = 0; - icmp_h->checksum = chksum( (BYTE *)icmp_h, recv_len - sizeof(ip_h) ); + icmp_h->checksum = chksum( (BYTE *)icmp_h, recv_len - sizeof(ip_h), 0 ); } } - ip_h.checksum = chksum( (BYTE *)&ip_h, sizeof(ip_h) ); + ip_h.checksum = chksum( (BYTE *)&ip_h, sizeof(ip_h), 0 ); memcpy( buf, &ip_h, min( sizeof(ip_h), buf_len ));
return recv_len;
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ntdll/unix/socket.c | 60 +++++++++++++++++++++++++++++++++++++++- server/sock.c | 22 +++++++++++---- 2 files changed, 75 insertions(+), 7 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 0c53585443e..c4960852592 100644 --- a/dlls/ntdll/unix/socket.c +++ b/dlls/ntdll/unix/socket.c @@ -573,6 +573,15 @@ struct ip_hdr ULONG daddr; };
+struct ipv6_pseudo_header +{ + struct in6_addr src; + struct in6_addr dst; + UINT32 next_len; /* incapsulated packet length in network byte order */ + BYTE zero[3]; + BYTE next_header; +}; + struct icmp_hdr { BYTE type; @@ -625,6 +634,52 @@ static unsigned short chksum( BYTE *data, unsigned int count, unsigned int sum ) return check; }
+static ssize_t fixup_icmpv6_over_dgram( struct msghdr *hdr, void *buf, union unix_sockaddr *unix_addr, + HANDLE handle, ssize_t recv_len ) +{ + struct icmp_hdr *icmp_h = buf; + struct ipv6_pseudo_header ip_h; + unsigned int fixup_status; + struct in6_pktinfo *info; + struct cmsghdr *cmsg; + unsigned int sum; + + if (recv_len < sizeof(*icmp_h)) return recv_len; + + memset( &ip_h, 0, sizeof(ip_h) ); + ip_h.src = unix_addr->in6.sin6_addr; + for (cmsg = CMSG_FIRSTHDR( hdr ); cmsg; cmsg = CMSG_NXTHDR( hdr, cmsg )) + { + if (cmsg->cmsg_level != IPPROTO_IPV6) continue; +#ifdef IPV6_PKTINFO + if (cmsg->cmsg_type != IPV6_PKTINFO) continue; + info = (struct in6_pktinfo *)CMSG_DATA( cmsg ); + ip_h.dst = info->ipi6_addr; +#endif + break; + } + ip_h.next_len = htonl( recv_len ); + ip_h.next_header = IPPROTO_ICMPV6; + + 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; + } + SERVER_END_REQ; + + if (fixup_status) WARN( "socket_get_icmp_id returned %#x.\n", fixup_status ); + else + { + sum = chksum_add( (BYTE *)&ip_h, sizeof(ip_h), 0 ); + icmp_h->checksum = 0; + icmp_h->checksum = chksum( (BYTE *)icmp_h, recv_len, sum ); + } + return recv_len; +} + static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *unix_addr, HANDLE handle, ssize_t recv_len, NTSTATUS *status ) { @@ -645,6 +700,9 @@ static ssize_t fixup_icmp_over_dgram( struct msghdr *hdr, union unix_sockaddr *u buf = hdr->msg_iov[0].iov_base; buf_len = hdr->msg_iov[0].iov_len;
+ if (unix_addr->addr.sa_family == AF_INET6) + return fixup_icmpv6_over_dgram( hdr, buf, unix_addr, handle, recv_len ); + if (recv_len + sizeof(ip_h) > buf_len) *status = STATUS_BUFFER_OVERFLOW;
@@ -836,7 +894,7 @@ static BOOL is_icmp_over_dgram( int fd ) int val;
len = sizeof(val); - if (getsockopt( fd, SOL_SOCKET, SO_PROTOCOL, (char *)&val, &len ) || val != IPPROTO_ICMP) + if (getsockopt( fd, SOL_SOCKET, SO_PROTOCOL, (char *)&val, &len ) || (val != IPPROTO_ICMP && val != IPPROTO_ICMPV6)) return FALSE;
len = sizeof(val); diff --git a/server/sock.c b/server/sock.c index 7ea708be8af..7785d3c7706 100644 --- a/server/sock.c +++ b/server/sock.c @@ -1876,6 +1876,7 @@ static int get_unix_protocol( int family, int protocol ) switch (protocol) { case WS_IPPROTO_ICMP: return IPPROTO_ICMP; + case WS_IPPROTO_ICMPV6: return IPPROTO_ICMPV6; case WS_IPPROTO_IGMP: return IPPROTO_IGMP; case WS_IPPROTO_IP: return IPPROTO_IP; case WS_IPPROTO_IPV4: return IPPROTO_IPIP; @@ -1947,19 +1948,28 @@ static int init_socket( struct sock *sock, int family, int type, int protocol ) }
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) + if (sockfd == -1 && errno == EPERM && unix_type == SOCK_RAW + && ((unix_family == AF_INET && unix_protocol == IPPROTO_ICMP) + || (unix_family == AF_INET6 && unix_protocol == IPPROTO_ICMPV6))) { 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) ); + if (unix_family == AF_INET6) + { +#ifdef IPV6_RECVPKTINFO + setsockopt( sockfd, IPPROTO_IPV6, IPV6_RECVPKTINFO, (const char *)&val, sizeof(val) ); +#endif + } + else + { + 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
From: Paul Gofman pgofman@codeweavers.com
--- dlls/ws2_32/tests/sock.c | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+)
diff --git a/dlls/ws2_32/tests/sock.c b/dlls/ws2_32/tests/sock.c index a3fb6744235..77bc3beeabf 100644 --- a/dlls/ws2_32/tests/sock.c +++ b/dlls/ws2_32/tests/sock.c @@ -14096,6 +14096,83 @@ static void test_icmp(void) closesocket(s); }
+struct ipv6_pseudo_header +{ + struct in6_addr src; + struct in6_addr dst; + UINT32 next_len; /* incapsulated packet length in network byte order */ + BYTE zero[3]; + BYTE next_header; +}; + +static void test_icmpv6(void) +{ + static const unsigned int ping_data = 0xdeadbeef; + + BYTE send_buf[sizeof(struct icmp_hdr) + sizeof(ping_data)]; + struct ipv6_pseudo_header *ip_h; + UINT16 recv_checksum, checksum; + struct icmp_hdr *icmp_h; + unsigned int reply_data; + struct sockaddr_in6 sa; + BYTE chksum_buf[256]; + BYTE recv_buf[256]; + SOCKET s; + int ret; + + s = WSASocketA(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, 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; + } + + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + ret = inet_pton( AF_INET6, "::1", &sa.sin6_addr); + ok(ret, "got error %u.\n", WSAGetLastError()); + + icmp_h = (struct icmp_hdr *)send_buf; + icmp_h->type = ICMP6_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 = 2; + *(unsigned int *)(icmp_h + 1) = ping_data; + icmp_h->checksum = 0; + + 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(send_buf), "got %d\n", ret); + + icmp_h = (struct icmp_hdr *)recv_buf; + reply_data = *(unsigned int *)(icmp_h + 1); + + ok(icmp_h->type == ICMP6_ECHO_REPLY, "got type %#x.\n", icmp_h->type); + ok(!icmp_h->code, "got code %#x.\n", icmp_h->code); + 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; + ip_h = (struct ipv6_pseudo_header *)chksum_buf; + memset(ip_h, 0, sizeof(*ip_h)); + ip_h->dst = sa.sin6_addr; + ip_h->src = sa.sin6_addr; + ip_h->next_len = htonl(sizeof(send_buf)); + ip_h->next_header = IPPROTO_ICMPV6; + icmp_h->checksum = 0; + memcpy(ip_h + 1, icmp_h, sizeof(send_buf)); + checksum = chksum((BYTE *)ip_h, sizeof(*ip_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); +} + static void test_connect_time(void) { struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK)}; @@ -14601,6 +14678,7 @@ START_TEST( sock ) test_timeout(); test_tcp_reset(); test_icmp(); + test_icmpv6(); test_connect_udp(); test_tcp_sendto_recvfrom(); test_broadcast();
IPv6 over dgram basic support is a bit simpler than IPv4 (which was implemented some time ago) because the data sent to and received from raw ipv6 socket doesn't include ip header (and only the payload, ICMP packet in this case). In fact, it is possible to enable the raw packet outgoing data with IPV6_HDRINCL (works on Windows while requires admin privileges). But this is out of scope for now (and we don't support this option at all currently even for real raw sockets).
Alfred Agrell (@Alcaro) commented about dlls/ntdll/unix/socket.c:
ULONG daddr;
};
+struct ipv6_pseudo_header +{
- struct in6_addr src;
- struct in6_addr dst;
- UINT32 next_len; /* incapsulated packet length in network byte order */
That word is usually spelled encapsulated.
Alfred Agrell (@Alcaro) commented about dlls/ntdll/unix/socket.c:
- struct in6_pktinfo *info;
- struct cmsghdr *cmsg;
- unsigned int sum;
- if (recv_len < sizeof(*icmp_h)) return recv_len;
- memset( &ip_h, 0, sizeof(ip_h) );
- ip_h.src = unix_addr->in6.sin6_addr;
- for (cmsg = CMSG_FIRSTHDR( hdr ); cmsg; cmsg = CMSG_NXTHDR( hdr, cmsg ))
- {
if (cmsg->cmsg_level != IPPROTO_IPV6) continue;
+#ifdef IPV6_PKTINFO
if (cmsg->cmsg_type != IPV6_PKTINFO) continue;
info = (struct in6_pktinfo *)CMSG_DATA( cmsg );
ip_h.dst = info->ipi6_addr;
+#endif
Feels like some of those #ifdef should have a #else with an ERR or FIXME or something. Or at least a comment saying it's not necessary.
Alfred Agrell (@Alcaro) commented about dlls/ws2_32/tests/sock.c:
- BYTE send_buf[sizeof(struct icmp_hdr) + sizeof(ping_data)];
- struct ipv6_pseudo_header *ip_h;
- UINT16 recv_checksum, checksum;
- struct icmp_hdr *icmp_h;
- unsigned int reply_data;
- struct sockaddr_in6 sa;
- BYTE chksum_buf[256];
- BYTE recv_buf[256];
- SOCKET s;
- int ret;
- s = WSASocketA(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, NULL, 0, 0);
- if (s == INVALID_SOCKET)
- {
ret = WSAGetLastError();
ok(ret == WSAEACCES, "Expected 10013, received %d\n", ret);
IMO `ok(ret == WSAEACCES, "Expected %d, received %d\n", WSAEACCES, ret);` would be slightly cleaner.
On Tue Aug 12 22:41:25 2025 +0000, Alfred Agrell wrote:
IMO `ok(ret == WSAEACCES, "Expected %d, received %d\n", WSAEACCES, ret);` would be slightly cleaner.
I disagree; in fact I don't think printing the expected value is useful at all when it's a constant.
On Tue Aug 12 22:41:25 2025 +0000, Alfred Agrell wrote:
Feels like some of those #ifdef should have a #else with an ERR or FIXME or something. Or at least a comment saying it's not necessary.
We have similar checks without the FIXMEs in the same source file. If we really want a FIXME here that should probably for the case when IPV6_PKTINFO is not found for whatever reason, but we also don't have any FIXMEs on failures resulting in ICMP over dgram not working at all (e. g., not allowed, that also can be not permitted). So if we want to start with that I think it would make sense to start from earlier failing fallback.
On Tue Aug 12 22:47:52 2025 +0000, Paul Gofman wrote:
We have similar checks without the FIXMEs in the same source file. If we really want a FIXME here that should probably for the case when IPV6_PKTINFO is not found for whatever reason, but we also don't have any FIXMEs on failures resulting in ICMP over dgram not working at all (e. g., not allowed, that also can be not permitted). So if we want to start with that I think it would make sense to start from earlier failing fallback.
In fact, what I surely missed here is that if IPV6_PKTINFO is not defined then struct in6_pktinfo also should not be defined, I will resend with that changed.