-- v2: ws2_32/tests: Test ICMPv6 ping. ntdll: Support SOCK_RAW / IPPROTO_ICMPV6 fallback over SOCK_DGRAM.
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 | 68 +++++++++++++++++++++++++++++++++++++++- server/sock.c | 22 +++++++++---- 2 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/dlls/ntdll/unix/socket.c b/dlls/ntdll/unix/socket.c index 0c53585443e..dd9fe27d0ab 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,60 @@ static unsigned short chksum( BYTE *data, unsigned int count, unsigned int sum ) return check; }
+static void set_ipv6_addr_from_pktinfo( struct msghdr *hdr, struct in6_addr *addr ) +{ +#ifdef IPV6_PKTINFO + struct in6_pktinfo *info; + struct cmsghdr *cmsg; + + for (cmsg = CMSG_FIRSTHDR( hdr ); cmsg; cmsg = CMSG_NXTHDR( hdr, cmsg )) + { + if (cmsg->cmsg_level != IPPROTO_IPV6) continue; + if (cmsg->cmsg_type != IPV6_PKTINFO) continue; + info = (struct in6_pktinfo *)CMSG_DATA( cmsg ); + *addr = info->ipi6_addr; + return; + } +#endif +} + +static ssize_t fixup_icmpv6_over_dgram( struct msghdr *hdr, void *buf, union unix_sockaddr *unix_addr, + HANDLE handle, ssize_t recv_len ) +{ + struct ipv6_pseudo_header ip_h; + struct icmp_hdr *icmp_h = buf; + unsigned int fixup_status; + unsigned int sum; + + if (recv_len < sizeof(*icmp_h)) return recv_len; + + 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 ); + return recv_len; + } + + memset( &ip_h, 0, sizeof(ip_h) ); + ip_h.src = unix_addr->in6.sin6_addr; + set_ipv6_addr_from_pktinfo( hdr, &ip_h.dst ); + ip_h.next_len = htonl( recv_len ); + ip_h.next_header = IPPROTO_ICMPV6; + 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 +708,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 +902,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();