diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc index 6aac04610..dc9635cae 100644 --- a/test/syscalls/linux/ip_socket_test_util.cc +++ b/test/syscalls/linux/ip_socket_test_util.cc @@ -24,6 +24,8 @@ namespace gvisor { namespace testing { +using ::testing::NotNull; + uint32_t IPFromInetSockaddr(const struct sockaddr* addr) { auto* in_addr = reinterpret_cast(addr); return in_addr->sin_addr.s_addr; @@ -250,13 +252,13 @@ namespace { template void RecvCmsg(int sock, int cmsg_level, int cmsg_type, char buf[], size_t* buf_size, T* out_cmsg_value) { - struct iovec iov = { + iovec iov = { iov.iov_base = buf, iov.iov_len = *buf_size, }; // Add an extra byte to confirm we only read what we expected. char control[CMSG_SPACE(sizeof(*out_cmsg_value)) + 1]; - struct msghdr msg = { + msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = control, @@ -276,15 +278,52 @@ void RecvCmsg(int sock, int cmsg_level, int cmsg_type, char buf[], std::copy_n(CMSG_DATA(cmsg), sizeof(*out_cmsg_value), reinterpret_cast(out_cmsg_value)); } + +template +void SendCmsg(int sock, int cmsg_level, int cmsg_type, char buf[], + size_t buf_size, T cmsg_value) { + iovec iov = { + .iov_base = buf, + .iov_len = buf_size, + }; + + std::vector control(CMSG_SPACE(sizeof(cmsg_value))); + msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = control.data(), + .msg_controllen = CMSG_LEN(sizeof(cmsg_value)), + }; + + // Manually add control message. + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + ASSERT_THAT(cmsg, NotNull()); + cmsg->cmsg_len = CMSG_LEN(sizeof(cmsg_value)); + cmsg->cmsg_level = cmsg_level; + cmsg->cmsg_type = cmsg_type; + std::copy_n(reinterpret_cast(&cmsg_value), sizeof(cmsg_value), + CMSG_DATA(cmsg)); + + ASSERT_THAT(RetryEINTR(sendmsg)(sock, &msg, 0), + SyscallSucceedsWithValue(buf_size)); +} } // namespace void RecvTOS(int sock, char buf[], size_t* buf_size, uint8_t* out_tos) { RecvCmsg(sock, SOL_IP, IP_TOS, buf, buf_size, out_tos); } +void SendTOS(int sock, char buf[], size_t buf_size, uint8_t tos) { + SendCmsg(sock, SOL_IP, IP_TOS, buf, buf_size, tos); +} + void RecvTClass(int sock, char buf[], size_t* buf_size, int* out_tclass) { RecvCmsg(sock, SOL_IPV6, IPV6_TCLASS, buf, buf_size, out_tclass); } +void SendTClass(int sock, char buf[], size_t buf_size, int tclass) { + SendCmsg(sock, SOL_IPV6, IPV6_TCLASS, buf, buf_size, tclass); +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h index ba370575d..d8a727d43 100644 --- a/test/syscalls/linux/ip_socket_test_util.h +++ b/test/syscalls/linux/ip_socket_test_util.h @@ -130,12 +130,20 @@ std::string GetAddrStr(const sockaddr* a); // received. The buffer must already be allocated with at least buf_size size. void RecvTOS(int sock, char buf[], size_t* buf_size, uint8_t* out_tos); +// SendTOS sends a message using buf as payload and tos as IP_TOS control +// message. +void SendTOS(int sock, char buf[], size_t buf_size, uint8_t tos); + // RecvTClass attempts to read buf_size bytes into buf, and then update buf_size // with the numbers of bytes actually read. It expects the IPV6_TCLASS cmsg to // be received. The buffer must already be allocated with at least buf_size // size. void RecvTClass(int sock, char buf[], size_t* buf_size, int* out_tclass); +// SendTClass sends a message using buf as payload and tclass as IP_TOS control +// message. +void SendTClass(int sock, char buf[], size_t buf_size, int tclass); + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc index a29f5ebd6..99a1b06bf 100644 --- a/test/syscalls/linux/udp_socket.cc +++ b/test/syscalls/linux/udp_socket.cc @@ -1737,296 +1737,6 @@ TEST_P(UdpSocketTest, TimestampIoctlPersistence) { ASSERT_EQ(tv.tv_usec, tv2.tv_usec); } -// TOS and TCLASS values may be different but IPv6 sockets with IPv4-mapped-IPv6 -// addresses use TOS (IPv4), not TCLASS (IPv6). -TEST_P(UdpSocketTest, DifferentTOSAndTClass) { - const int kFamily = GetParam(); - constexpr int kToS = IPTOS_LOWDELAY; - constexpr int kTClass = IPTOS_THROUGHPUT; - ASSERT_NE(kToS, kTClass); - - if (kFamily == AF_INET6) { - ASSERT_THAT(setsockopt(sock_.get(), SOL_IPV6, IPV6_TCLASS, &kTClass, - sizeof(kTClass)), - SyscallSucceeds()); - - // Marking an IPv6 socket as IPv6 only should not affect the ability to - // configure IPv4 socket options as the V6ONLY flag may later be disabled so - // that applications may use the socket to send/receive IPv4 packets. - constexpr int on = 1; - ASSERT_THAT(setsockopt(sock_.get(), SOL_IPV6, IPV6_V6ONLY, &on, sizeof(on)), - SyscallSucceeds()); - } - - ASSERT_THAT(setsockopt(sock_.get(), SOL_IP, IP_TOS, &kToS, sizeof(kToS)), - SyscallSucceeds()); - - if (kFamily == AF_INET6) { - int got_tclass; - socklen_t got_tclass_len = sizeof(got_tclass); - ASSERT_THAT(getsockopt(sock_.get(), SOL_IPV6, IPV6_TCLASS, &got_tclass, - &got_tclass_len), - SyscallSucceeds()); - ASSERT_EQ(got_tclass_len, sizeof(got_tclass)); - EXPECT_EQ(got_tclass, kTClass); - } - - { - int got_tos; - socklen_t got_tos_len = sizeof(got_tos); - ASSERT_THAT(getsockopt(sock_.get(), SOL_IP, IP_TOS, &got_tos, &got_tos_len), - SyscallSucceeds()); - ASSERT_EQ(got_tos_len, sizeof(got_tos)); - EXPECT_EQ(got_tos, kToS); - } - - auto test_send = [this](sockaddr_storage addr, - std::function cb) { - FileDescriptor bind = ASSERT_NO_ERRNO_AND_VALUE( - Socket(addr.ss_family, SOCK_DGRAM, IPPROTO_UDP)); - ASSERT_NO_ERRNO(BindSocket(bind.get(), reinterpret_cast(&addr))); - ASSERT_THAT(setsockopt(bind.get(), SOL_IP, IP_RECVTOS, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - if (addr.ss_family == AF_INET6) { - ASSERT_THAT(setsockopt(bind.get(), SOL_IPV6, IPV6_RECVTCLASS, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - } - - char sent_data[1024]; - iovec sent_iov = { - .iov_base = sent_data, - .iov_len = sizeof(sent_data), - }; - msghdr sent_msg = { - .msg_name = &addr, - .msg_namelen = sizeof(addr), - .msg_iov = &sent_iov, - .msg_iovlen = 1, - }; - ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - char received_data[sizeof(sent_data) + 1]; - iovec received_iov = { - .iov_base = received_data, - .iov_len = sizeof(received_data), - }; - std::vector received_cmsgbuf(CMSG_SPACE(sizeof(int8_t))); - msghdr received_msg = { - .msg_iov = &received_iov, - .msg_iovlen = 1, - .msg_control = received_cmsgbuf.data(), - .msg_controllen = static_cast(received_cmsgbuf.size()), - }; - ASSERT_THAT(RetryEINTR(recvmsg)(bind.get(), &received_msg, 0), - SyscallSucceedsWithValue(sizeof(sent_data))); - - cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_NO_FATAL_FAILURE(cb(cmsg)); - EXPECT_EQ(CMSG_NXTHDR(&received_msg, cmsg), nullptr); - }; - - if (kFamily == AF_INET6) { - SCOPED_TRACE( - "Send IPv4 loopback packet using IPv6 socket via IPv4-mapped-IPv6"); - - constexpr int off = 0; - ASSERT_THAT( - setsockopt(sock_.get(), SOL_IPV6, IPV6_V6ONLY, &off, sizeof(off)), - SyscallSucceeds()); - - // Send a packet and make sure that the ToS value in the IPv4 header is - // the configured IPv4 ToS Value and not the IPv6 Traffic Class value even - // though we use an IPv6 socket to send an IPv4 packet. - ASSERT_NO_FATAL_FAILURE( - test_send(V4MappedLoopback().addr, [kToS](const cmsghdr* cmsg) { - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int8_t))); - EXPECT_EQ(cmsg->cmsg_level, SOL_IP); - EXPECT_EQ(cmsg->cmsg_type, IP_TOS); - int8_t received; - memcpy(&received, CMSG_DATA(cmsg), sizeof(received)); - EXPECT_EQ(received, kToS); - })); - } - - { - SCOPED_TRACE("Send loopback packet"); - - ASSERT_NO_FATAL_FAILURE(test_send( - InetLoopbackAddr(), [kFamily, kTClass, kToS](const cmsghdr* cmsg) { - switch (kFamily) { - case AF_INET: { - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int8_t))); - EXPECT_EQ(cmsg->cmsg_level, SOL_IP); - EXPECT_EQ(cmsg->cmsg_type, IP_TOS); - int8_t received; - memcpy(&received, CMSG_DATA(cmsg), sizeof(received)); - EXPECT_EQ(received, kToS); - } break; - case AF_INET6: { - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(int32_t))); - EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6); - EXPECT_EQ(cmsg->cmsg_type, IPV6_TCLASS); - int32_t received; - memcpy(&received, CMSG_DATA(cmsg), sizeof(received)); - EXPECT_EQ(received, kTClass); - } break; - } - })); - } -} - -// Test that a socket with IP_TOS or IPV6_TCLASS set will set the TOS byte on -// outgoing packets, and that a receiving socket with IP_RECVTOS or -// IPV6_RECVTCLASS will create the corresponding control message. -TEST_P(UdpSocketTest, SetAndReceiveTOS) { - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Allow socket to receive control message. - int recv_level = SOL_IP; - int recv_type = IP_RECVTOS; - if (GetParam() == AF_INET6) { - recv_level = SOL_IPV6; - recv_type = IPV6_RECVTCLASS; - } - ASSERT_THAT(setsockopt(bind_.get(), recv_level, recv_type, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - // Set socket TOS. - int sent_level = recv_level; - int sent_type = IP_TOS; - if (sent_level == SOL_IPV6) { - sent_type = IPV6_TCLASS; - } - int sent_tos = IPTOS_LOWDELAY; // Choose some TOS value. - ASSERT_THAT(setsockopt(sock_.get(), sent_level, sent_type, &sent_tos, - sizeof(sent_tos)), - SyscallSucceeds()); - - // Prepare message to send. - constexpr size_t kDataLength = 1024; - struct msghdr sent_msg = {}; - struct iovec sent_iov = {}; - char sent_data[kDataLength]; - sent_iov.iov_base = &sent_data[0]; - sent_iov.iov_len = kDataLength; - sent_msg.msg_iov = &sent_iov; - sent_msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - // Receive message. - struct msghdr received_msg = {}; - struct iovec received_iov = {}; - char received_data[kDataLength]; - received_iov.iov_base = &received_data[0]; - received_iov.iov_len = kDataLength; - received_msg.msg_iov = &received_iov; - received_msg.msg_iovlen = 1; - size_t cmsg_data_len = sizeof(int8_t); - if (sent_type == IPV6_TCLASS) { - cmsg_data_len = sizeof(int); - } - std::vector received_cmsgbuf(CMSG_SPACE(cmsg_data_len)); - received_msg.msg_control = &received_cmsgbuf[0]; - received_msg.msg_controllen = received_cmsgbuf.size(); - ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &received_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); - EXPECT_EQ(cmsg->cmsg_level, sent_level); - EXPECT_EQ(cmsg->cmsg_type, sent_type); - int8_t received_tos = 0; - memcpy(&received_tos, CMSG_DATA(cmsg), sizeof(received_tos)); - EXPECT_EQ(received_tos, sent_tos); -} - -// Test that sendmsg with IP_TOS and IPV6_TCLASS control messages will set the -// TOS byte on outgoing packets, and that a receiving socket with IP_RECVTOS or -// IPV6_RECVTCLASS will create the corresponding control message. -TEST_P(UdpSocketTest, SendAndReceiveTOS) { - // TODO(b/146661005): Setting TOS via cmsg not supported for netstack. - SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet()); - - ASSERT_NO_ERRNO(BindLoopback()); - ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds()); - - // Allow socket to receive control message. - int recv_level = SOL_IP; - int recv_type = IP_RECVTOS; - if (GetParam() == AF_INET6) { - recv_level = SOL_IPV6; - recv_type = IPV6_RECVTCLASS; - } - int recv_opt = kSockOptOn; - ASSERT_THAT(setsockopt(bind_.get(), recv_level, recv_type, &recv_opt, - sizeof(recv_opt)), - SyscallSucceeds()); - - // Prepare message to send. - constexpr size_t kDataLength = 1024; - int sent_level = recv_level; - int sent_type = IP_TOS; - int sent_tos = IPTOS_LOWDELAY; // Choose some TOS value. - - struct msghdr sent_msg = {}; - struct iovec sent_iov = {}; - char sent_data[kDataLength]; - sent_iov.iov_base = &sent_data[0]; - sent_iov.iov_len = kDataLength; - sent_msg.msg_iov = &sent_iov; - sent_msg.msg_iovlen = 1; - size_t cmsg_data_len = sizeof(int8_t); - if (sent_level == SOL_IPV6) { - sent_type = IPV6_TCLASS; - cmsg_data_len = sizeof(int); - } - std::vector sent_cmsgbuf(CMSG_SPACE(cmsg_data_len)); - sent_msg.msg_control = &sent_cmsgbuf[0]; - sent_msg.msg_controllen = CMSG_LEN(cmsg_data_len); - - // Manually add control message. - struct cmsghdr* sent_cmsg = CMSG_FIRSTHDR(&sent_msg); - sent_cmsg->cmsg_len = CMSG_LEN(cmsg_data_len); - sent_cmsg->cmsg_level = sent_level; - sent_cmsg->cmsg_type = sent_type; - *(int8_t*)CMSG_DATA(sent_cmsg) = sent_tos; - - ASSERT_THAT(RetryEINTR(sendmsg)(sock_.get(), &sent_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - // Receive message. - struct msghdr received_msg = {}; - struct iovec received_iov = {}; - char received_data[kDataLength]; - received_iov.iov_base = &received_data[0]; - received_iov.iov_len = kDataLength; - received_msg.msg_iov = &received_iov; - received_msg.msg_iovlen = 1; - std::vector received_cmsgbuf(CMSG_SPACE(cmsg_data_len)); - received_msg.msg_control = &received_cmsgbuf[0]; - received_msg.msg_controllen = CMSG_LEN(cmsg_data_len); - ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &received_msg, 0), - SyscallSucceedsWithValue(kDataLength)); - - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg); - ASSERT_NE(cmsg, nullptr); - EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(cmsg_data_len)); - EXPECT_EQ(cmsg->cmsg_level, sent_level); - EXPECT_EQ(cmsg->cmsg_type, sent_type); - int8_t received_tos = 0; - memcpy(&received_tos, CMSG_DATA(cmsg), sizeof(received_tos)); - EXPECT_EQ(received_tos, sent_tos); -} - TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) { // Discover minimum buffer size by setting it to zero. constexpr int kRcvBufSz = 0; @@ -2260,6 +1970,183 @@ TEST_P(UdpSocketTest, ConnectToZeroPortConnected) { INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest, ::testing::Values(AF_INET, AF_INET6)); +class UdpSocketControlMessagesTest + : public ::testing::TestWithParam { + protected: + void SetUp() override { + switch (GetParam()) { + case AddressFamily::kIpv4: { + server_ = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); + TestAddress addr = V4Loopback().WithPort(port_); + ASSERT_THAT( + bind(server_.get(), reinterpret_cast(&addr.addr), + addr.addr_len), + SyscallSucceeds()); + + client_ = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT(connect(client_.get(), + reinterpret_cast(&addr.addr), + addr.addr_len), + SyscallSucceeds()); + break; + } + + case AddressFamily::kIpv6: { + server_ = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)); + TestAddress addr = V6Loopback().WithPort(port_); + ASSERT_THAT( + bind(server_.get(), reinterpret_cast(&addr.addr), + addr.addr_len), + SyscallSucceeds()); + + client_ = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)); + ASSERT_THAT(connect(client_.get(), + reinterpret_cast(&addr.addr), + addr.addr_len), + SyscallSucceeds()); + break; + } + + case AddressFamily::kDualStack: { + server_ = ASSERT_NO_ERRNO_AND_VALUE( + Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)); + + TestAddress bind_addr = V4MappedLoopback().WithPort(port_); + ASSERT_THAT(bind(server_.get(), + reinterpret_cast(&bind_addr.addr), + bind_addr.addr_len), + SyscallSucceeds()); + + client_ = + ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)); + TestAddress connect_addr = V4Loopback().WithPort(port_); + ASSERT_THAT( + connect(client_.get(), + reinterpret_cast(&connect_addr.addr), + connect_addr.addr_len), + SyscallSucceeds()); + break; + } + + default: + FAIL() << "unknown address family: " << static_cast(GetParam()); + } + } + + int ClientAddressFamily() const { + if (GetParam() == AddressFamily::kIpv6) { + return AF_INET6; + } + return AF_INET; + } + + int ServerAddressFamily() const { + if (GetParam() == AddressFamily::kIpv4) { + return AF_INET; + } + return AF_INET6; + } + + FileDescriptor server_; + FileDescriptor client_; + + private: + static constexpr uint16_t port_ = 1337; +}; + +TEST_P(UdpSocketControlMessagesTest, SetAndReceiveTOSOrTClass) { + // Enable receiving TOS and maybe TClass on the receiver. + ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_RECVTOS, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + if (ServerAddressFamily() == AF_INET6) { + ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVTCLASS, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + } + + // Set custom TOS and maybe TClass on the sender. + constexpr int kTOS = IPTOS_LOWDELAY; + constexpr int kTClass = IPTOS_THROUGHPUT; + ASSERT_NE(kTOS, kTClass); + + ASSERT_THAT(setsockopt(client_.get(), SOL_IP, IP_TOS, &kTOS, sizeof(kTOS)), + SyscallSucceeds()); + if (ClientAddressFamily() == AF_INET6) { + ASSERT_THAT(setsockopt(client_.get(), SOL_IPV6, IPV6_TCLASS, &kTClass, + sizeof(kTClass)), + SyscallSucceeds()); + } + + constexpr size_t kArbitrarySendSize = 1042; + constexpr char sent_data[kArbitrarySendSize] = {}; + ASSERT_THAT(RetryEINTR(send)(client_.get(), sent_data, sizeof(sent_data), 0), + SyscallSucceedsWithValue(sizeof(sent_data))); + + char recv_data[sizeof(sent_data) + 1]; + size_t recv_data_len = sizeof(recv_data); + + if (ClientAddressFamily() == AF_INET) { + uint8_t tos; + ASSERT_NO_FATAL_FAILURE( + RecvTOS(server_.get(), recv_data, &recv_data_len, &tos)); + EXPECT_EQ(static_cast(tos), kTOS); + } else { + int tclass; + ASSERT_NO_FATAL_FAILURE( + RecvTClass(server_.get(), recv_data, &recv_data_len, &tclass)); + EXPECT_EQ(tclass, kTClass); + } + EXPECT_EQ(recv_data_len, sizeof(sent_data)); +} + +TEST_P(UdpSocketControlMessagesTest, SendAndReceiveTOSorTClass) { + // TODO(b/146661005): Setting TOS via sendmsg is not supported by netstack. + SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet()); + + // Enable receiving TOS and maybe TClass on the receiver. + ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_RECVTOS, &kSockOptOn, + sizeof(kSockOptOn)), + SyscallSucceeds()); + if (ServerAddressFamily() == AF_INET6) { + ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVTCLASS, + &kSockOptOn, sizeof(kSockOptOn)), + SyscallSucceeds()); + } + + constexpr uint8_t kSendCmsgValue = IPTOS_LOWDELAY; + constexpr size_t kArbitrarySendSize = 1024; + char sent_data[kArbitrarySendSize]; + char recv_data[sizeof(sent_data) + 1]; + size_t recv_data_len = sizeof(recv_data); + + if (ClientAddressFamily() == AF_INET) { + ASSERT_NO_FATAL_FAILURE(SendTOS(client_.get(), sent_data, + size_t(sizeof(sent_data)), kSendCmsgValue)); + uint8_t tos; + ASSERT_NO_FATAL_FAILURE( + RecvTOS(server_.get(), recv_data, &recv_data_len, &tos)); + EXPECT_EQ(static_cast(tos), kSendCmsgValue); + } else { + ASSERT_NO_FATAL_FAILURE(SendTClass( + client_.get(), sent_data, size_t(sizeof(sent_data)), kSendCmsgValue)); + int tclass; + ASSERT_NO_FATAL_FAILURE( + RecvTClass(server_.get(), recv_data, &recv_data_len, &tclass)); + EXPECT_EQ(tclass, kSendCmsgValue); + } + EXPECT_EQ(recv_data_len, sizeof(sent_data)); +} + +INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketControlMessagesTest, + ::testing::Values(AddressFamily::kIpv4, + AddressFamily::kIpv6, + AddressFamily::kDualStack)); + TEST(UdpInet6SocketTest, ConnectInet4Sockaddr) { // glibc getaddrinfo expects the invariant expressed by this test to be held. const sockaddr_in connect_sockaddr = {