From 9a2f1d041a7b21c9446f65bdcb8cfcf8262f25e4 Mon Sep 17 00:00:00 2001 From: Alex Konradi Date: Wed, 31 May 2023 11:28:29 -0700 Subject: [PATCH] Add a test for receiving UDP packet with src port 0 Add a test that validates that when a UDP packet is received with a source port value of 0, it is delivered to a listening socket. PiperOrigin-RevId: 536773375 --- test/syscalls/BUILD | 5 + test/syscalls/linux/BUILD | 18 ++++ test/syscalls/linux/udp_raw_socket.cc | 131 ++++++++++++++++++++++++++ test/syscalls/linux/udp_socket.cc | 15 +-- test/util/socket_util.cc | 49 ++++++++++ test/util/socket_util.h | 7 ++ 6 files changed, 211 insertions(+), 14 deletions(-) create mode 100644 test/syscalls/linux/udp_raw_socket.cc diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 004b8ac13..bac32de7d 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -1108,6 +1108,11 @@ syscall_test( test = "//test/syscalls/linux:udp_socket_test", ) +syscall_test( + add_hostinet = True, + test = "//test/syscalls/linux:udp_raw_socket_test", +) + syscall_test( test = "//test/syscalls/linux:uidgid_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 75d113904..5efec8fb2 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -4052,6 +4052,7 @@ cc_binary( deps = [ ":ip_socket_test_util", ":unix_domain_socket_test_util", + "//test/util:capability_util", "//test/util:socket_util", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/strings:str_format", @@ -4065,6 +4066,23 @@ cc_binary( ], ) +cc_binary( + name = "udp_raw_socket_test", + testonly = 1, + srcs = ["udp_raw_socket.cc"], + defines = select_system(), + linkstatic = 1, + deps = [ + gtest, + "//test/util:capability_util", + "//test/util:file_descriptor", + "//test/util:posix_error", + "//test/util:socket_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + cc_binary( name = "udp_bind_test", testonly = 1, diff --git a/test/syscalls/linux/udp_raw_socket.cc b/test/syscalls/linux/udp_raw_socket.cc new file mode 100644 index 000000000..c2ce2ac0c --- /dev/null +++ b/test/syscalls/linux/udp_raw_socket.cc @@ -0,0 +1,131 @@ +// Copyright 2023 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#include +#endif // __linux__ +#include +#include +#include +#include +#include + +#ifndef SIOCGSTAMP +#include +#endif + +#include "gtest/gtest.h" +#include "test/util/capability_util.h" +#include "test/util/file_descriptor.h" +#include "test/util/posix_error.h" +#include "test/util/socket_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +// Tests for UDP that require raw socket access. +class UdpSocketRawTest : public ::testing::TestWithParam {}; + +TEST_P(UdpSocketRawTest, ReceiveWithZeroSourcePort) { + // UDP sockets can't bind to port 0, so send a UDP packet via a raw IP + // socket instead. If those aren't available, skip the test. + if (!ASSERT_NO_ERRNO_AND_VALUE(HaveRawIPSocketCapability())) { + GTEST_SKIP(); + } + + FileDescriptor udp_socket = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, 0)); + sockaddr_storage bind_addr = InetLoopbackAddr(GetParam()); + ASSERT_THAT(bind(udp_socket.get(), AsSockAddr(&bind_addr), sizeof(bind_addr)), + SyscallSucceeds()); + socklen_t bind_addr_len = sizeof(bind_addr); + ASSERT_THAT( + getsockname(udp_socket.get(), AsSockAddr(&bind_addr), &bind_addr_len), + SyscallSucceeds()); + uint16_t udp_port = + ASSERT_NO_ERRNO_AND_VALUE(AddrPort(GetParam(), bind_addr)); + + constexpr absl::string_view kMessage = "hi"; + + // Set up the UDP body. + struct udphdr udphdr = { + .source = 0, + .dest = udp_port, + .len = htons(sizeof(udphdr) + kMessage.size()), + .check = 0, + }; + + if (GetParam() == AF_INET) { + udphdr.check = UDPChecksum( + iphdr{ + .saddr = htonl(INADDR_LOOPBACK), + .daddr = htonl(INADDR_LOOPBACK), + }, + udphdr, kMessage.data(), kMessage.size()); + } else { + udphdr.check = UDPChecksum( + ip6_hdr{ + .ip6_src = in6addr_loopback, + .ip6_dst = in6addr_loopback, + }, + udphdr, kMessage.data(), kMessage.size()); + } + // Copy the header and the payload into our packet buffer. + char send_buf[sizeof(udphdr) + kMessage.size()]; + memcpy(send_buf, &udphdr, sizeof(udphdr)); + memcpy(send_buf + sizeof(udphdr), kMessage.data(), kMessage.size()); + + { + // Send the packet out a raw socket. + struct sockaddr_storage raw_socket_addr = InetLoopbackAddr(GetParam()); + FileDescriptor raw_socket = + ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_RAW, IPPROTO_UDP)); + ASSERT_THAT(sendto(raw_socket.get(), send_buf, sizeof(send_buf), 0, + reinterpret_cast(&raw_socket_addr), + sizeof(raw_socket_addr)), + SyscallSucceedsWithValue(sizeof(send_buf))); + } + + // Receive and validate the data. + char received[kMessage.size() + 1]; + struct sockaddr_storage src; + socklen_t addr2len = sizeof(src); + EXPECT_THAT(recvfrom(udp_socket.get(), received, sizeof(received), 0, + AsSockAddr(&src), &addr2len), + SyscallSucceedsWithValue(kMessage.size())); + ASSERT_EQ(src.ss_family, GetParam()); + ASSERT_EQ(ASSERT_NO_ERRNO_AND_VALUE(AddrPort(GetParam(), src)), 0); + ASSERT_EQ(absl::string_view(received, kMessage.size()), kMessage); +} + +INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketRawTest, + ::testing::Values(AF_INET, AF_INET6)); + +} // namespace +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index 315709ff4..db44ec4ff 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -219,20 +219,7 @@ sockaddr_storage UdpSocketTest::InetAnyAddr() { } sockaddr_storage UdpSocketTest::InetLoopbackAddr() { - struct sockaddr_storage addr; - memset(&addr, 0, sizeof(addr)); - AsSockAddr(&addr)->sa_family = GetParam(); - - if (GetParam() == AF_INET) { - auto sin = reinterpret_cast(&addr); - sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sin->sin_port = htons(0); - return addr; - } - auto sin6 = reinterpret_cast(&addr); - sin6->sin6_addr = in6addr_loopback; - sin6->sin6_port = htons(0); - return addr; + return gvisor::testing::InetLoopbackAddr(GetParam()); } void UdpSocketTest::Disconnect(int sockfd) { diff --git a/test/util/socket_util.cc b/test/util/socket_util.cc index c07309def..7ffbc6379 100644 --- a/test/util/socket_util.cc +++ b/test/util/socket_util.cc @@ -937,6 +937,8 @@ struct udp_pseudo_hdr { uint16_t udplen; }; +static_assert(sizeof(udp_pseudo_hdr) == 12); + uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr, const char* payload, ssize_t payload_len) { struct udp_pseudo_hdr phdr = {}; @@ -957,6 +959,36 @@ uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr, return csum; } +// IPv6 pseudo-header for UDP checksum calculation. +struct udpv6_pseudo_hdr { + in6_addr srcip; + in6_addr destip; + char zero; + char protocol; + uint16_t udplen; +}; +static_assert(sizeof(udpv6_pseudo_hdr) == 36); + +uint16_t UDPChecksum(struct ip6_hdr iphdr, struct udphdr udphdr, + const char* payload, ssize_t payload_len) { + struct udpv6_pseudo_hdr phdr = {}; + phdr.srcip = iphdr.ip6_src; + phdr.destip = iphdr.ip6_dst; + phdr.zero = 0; + phdr.protocol = IPPROTO_UDP; + phdr.udplen = udphdr.len; + + ssize_t buf_size = sizeof(phdr) + sizeof(udphdr) + payload_len; + char* buf = static_cast(malloc(buf_size)); + memcpy(buf, &phdr, sizeof(phdr)); + memcpy(buf + sizeof(phdr), &udphdr, sizeof(udphdr)); + memcpy(buf + sizeof(phdr) + sizeof(udphdr), payload, payload_len); + + uint16_t csum = Checksum(reinterpret_cast(buf), buf_size); + free(buf); + return csum; +} + uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload, ssize_t payload_len) { ssize_t buf_size = sizeof(icmphdr) + payload_len; @@ -997,6 +1029,23 @@ PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port) { } } +sockaddr_storage InetLoopbackAddr(int family) { + struct sockaddr_storage addr; + memset(&addr, 0, sizeof(addr)); + AsSockAddr(&addr)->sa_family = family; + + if (family == AF_INET) { + auto sin = reinterpret_cast(&addr); + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sin->sin_port = htons(0); + return addr; + } + auto sin6 = reinterpret_cast(&addr); + sin6->sin6_addr = in6addr_loopback; + sin6->sin6_port = htons(0); + return addr; +} + void SetupTimeWaitClose(const TestAddress* listener, const TestAddress* connector, bool reuse, bool accept_close, sockaddr_storage* listen_addr, diff --git a/test/util/socket_util.h b/test/util/socket_util.h index 7f2a566d7..68acba0d8 100644 --- a/test/util/socket_util.h +++ b/test/util/socket_util.h @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -546,6 +547,10 @@ uint16_t IPChecksum(struct iphdr ip); uint16_t UDPChecksum(struct iphdr iphdr, struct udphdr udphdr, const char* payload, ssize_t payload_len); +// Compute the internet checksum of a UDPv6 header. +uint16_t UDPChecksum(struct ip6_hdr iphdr, struct udphdr udphdr, + const char* payload, ssize_t payload_len); + // Compute the internet checksum of an ICMP header. uint16_t ICMPChecksum(struct icmphdr icmphdr, const char* payload, ssize_t payload_len); @@ -580,6 +585,8 @@ PosixErrorOr AddrPort(int family, sockaddr_storage const& addr); PosixError SetAddrPort(int family, sockaddr_storage* addr, uint16_t port); +sockaddr_storage InetLoopbackAddr(int family); + // setupTimeWaitClose sets up a socket endpoint in TIME_WAIT state. // Callers can choose to perform active close on either ends of the connection // and also specify if they want to enabled SO_REUSEADDR.