From abe9d9f67f2c2c696ef26690fa8518dfc4e28728 Mon Sep 17 00:00:00 2001 From: Ghanan Gowripalan Date: Wed, 6 Jan 2021 11:39:14 -0800 Subject: Support add/remove IPv6 multicast group sock opt IPv4 was always supported but UDP never supported joining/leaving IPv6 multicast groups via socket options. Add: IPPROTO_IPV6, IPV6_JOIN_GROUP/IPV6_ADD_MEMBERSHIP Remove: IPPROTO_IPV6, IPV6_LEAVE_GROUP/IPV6_DROP_MEMBERSHIP Test: integration_test.TestUDPAddRemoveMembershipSocketOption PiperOrigin-RevId: 350396072 --- test/syscalls/BUILD | 5 ++ test/syscalls/linux/BUILD | 54 ++++++++++++- .../socket_ip_udp_unbound_external_networking.cc | 59 ++++++++++++++ .../socket_ip_udp_unbound_external_networking.h | 46 +++++++++++ .../socket_ipv4_udp_unbound_external_networking.cc | 52 ------------- .../socket_ipv4_udp_unbound_external_networking.h | 21 +---- .../socket_ipv6_udp_unbound_external_networking.cc | 90 ++++++++++++++++++++++ .../socket_ipv6_udp_unbound_external_networking.h | 31 ++++++++ ...et_ipv6_udp_unbound_external_networking_test.cc | 39 ++++++++++ test/syscalls/linux/socket_test_util.cc | 11 +++ test/syscalls/linux/socket_test_util.h | 1 + 11 files changed, 336 insertions(+), 73 deletions(-) create mode 100644 test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc create mode 100644 test/syscalls/linux/socket_ip_udp_unbound_external_networking.h create mode 100644 test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc create mode 100644 test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h create mode 100644 test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc (limited to 'test/syscalls') diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index a5b9233f7..7fe7db5cb 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -681,6 +681,11 @@ syscall_test( test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_test", ) +syscall_test( + size = "medium", + test = "//test/syscalls/linux:socket_ipv6_udp_unbound_loopback_test", +) + syscall_test( size = "medium", add_hostinet = True, diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 4e0c8a574..017f997de 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -20,6 +20,7 @@ exports_files( "socket_ip_udp_loopback_nonblock.cc", "socket_ip_unbound.cc", "socket_ipv4_udp_unbound_external_networking_test.cc", + "socket_ipv6_udp_unbound_external_networking_test.cc", "socket_ipv4_udp_unbound_loopback.cc", "socket_ipv6_udp_unbound_loopback.cc", "socket_ipv4_udp_unbound_loopback_nogotsan.cc", @@ -2510,6 +2511,23 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "socket_ip_udp_unbound_external_networking", + testonly = 1, + srcs = [ + "socket_ip_udp_unbound_external_networking.cc", + ], + hdrs = [ + "socket_ip_udp_unbound_external_networking.h", + ], + deps = [ + ":ip_socket_test_util", + ":socket_test_util", + "//test/util:test_util", + ], + alwayslink = 1, +) + cc_library( name = "socket_ipv4_udp_unbound_external_networking_test_cases", testonly = 1, @@ -2520,10 +2538,24 @@ cc_library( "socket_ipv4_udp_unbound_external_networking.h", ], deps = [ - ":ip_socket_test_util", - ":socket_test_util", + ":socket_ip_udp_unbound_external_networking", + gtest, + ], + alwayslink = 1, +) + +cc_library( + name = "socket_ipv6_udp_unbound_external_networking_test_cases", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound_external_networking.cc", + ], + hdrs = [ + "socket_ipv6_udp_unbound_external_networking.h", + ], + deps = [ + ":socket_ip_udp_unbound_external_networking", gtest, - "//test/util:test_util", ], alwayslink = 1, ) @@ -2722,6 +2754,22 @@ cc_binary( ], ) +cc_binary( + name = "socket_ipv6_udp_unbound_external_networking_test", + testonly = 1, + srcs = [ + "socket_ipv6_udp_unbound_external_networking_test.cc", + ], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + ":socket_ipv6_udp_unbound_external_networking_test_cases", + ":socket_test_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + cc_binary( name = "socket_bind_to_device_test", testonly = 1, diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc new file mode 100644 index 000000000..fdbb2216b --- /dev/null +++ b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.cc @@ -0,0 +1,59 @@ +// Copyright 2020 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 "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" + +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +void IPUDPUnboundExternalNetworkingSocketTest::SetUp() { + // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its + // IPv4 address on eth0. + found_net_interfaces_ = false; + + // Get interface list. + ASSERT_NO_ERRNO(if_helper_.Load()); + std::vector if_names = if_helper_.InterfaceList(AF_INET); + if (if_names.size() != 2) { + return; + } + + // Figure out which interface is where. + std::string lo = if_names[0]; + std::string eth = if_names[1]; + if (lo != "lo") std::swap(lo, eth); + if (lo != "lo") return; + + lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(lo)); + auto lo_if_addr = if_helper_.GetAddr(AF_INET, lo); + if (lo_if_addr == nullptr) { + return; + } + lo_if_addr_ = *reinterpret_cast(lo_if_addr); + + eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(eth)); + auto eth_if_addr = if_helper_.GetAddr(AF_INET, eth); + if (eth_if_addr == nullptr) { + return; + } + eth_if_addr_ = *reinterpret_cast(eth_if_addr); + + found_net_interfaces_ = true; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h new file mode 100644 index 000000000..e5287addb --- /dev/null +++ b/test/syscalls/linux/socket_ip_udp_unbound_external_networking.h @@ -0,0 +1,46 @@ +// Copyright 2020 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. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ + +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_test_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to unbound IP UDP sockets in a sandbox +// with external networking support. +class IPUDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest { + protected: + void SetUp() override; + + IfAddrHelper if_helper_; + + // found_net_interfaces_ is set to false if SetUp() could not obtain + // all interface infos that we need. + bool found_net_interfaces_; + + // Interface infos. + int lo_if_idx_; + int eth_if_idx_; + sockaddr_in lo_if_addr_; + sockaddr_in eth_if_addr_; +}; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc index 2eecb0866..940289d15 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc @@ -14,23 +14,6 @@ #include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h" -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/test_util.h" - namespace gvisor { namespace testing { @@ -41,41 +24,6 @@ TestAddress V4EmptyAddress() { return t; } -void IPv4UDPUnboundExternalNetworkingSocketTest::SetUp() { - // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its - // IPv4 address on eth0. - found_net_interfaces_ = false; - - // Get interface list. - ASSERT_NO_ERRNO(if_helper_.Load()); - std::vector if_names = if_helper_.InterfaceList(AF_INET); - if (if_names.size() != 2) { - return; - } - - // Figure out which interface is where. - std::string lo = if_names[0]; - std::string eth = if_names[1]; - if (lo != "lo") std::swap(lo, eth); - if (lo != "lo") return; - - lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(lo)); - auto lo_if_addr = if_helper_.GetAddr(AF_INET, lo); - if (lo_if_addr == nullptr) { - return; - } - lo_if_addr_ = *reinterpret_cast(lo_if_addr); - - eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(eth)); - auto eth_if_addr = if_helper_.GetAddr(AF_INET, eth); - if (eth_if_addr == nullptr) { - return; - } - eth_if_addr_ = *reinterpret_cast(eth_if_addr); - - found_net_interfaces_ = true; -} - // Verifies that a broadcast UDP packet will arrive at all UDP sockets with // the destination port number. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h index 0e9e70e8e..20922ac1f 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h @@ -15,30 +15,15 @@ #ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ #define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ -#include "test/syscalls/linux/ip_socket_test_util.h" -#include "test/syscalls/linux/socket_test_util.h" +#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" namespace gvisor { namespace testing { // Test fixture for tests that apply to unbound IPv4 UDP sockets in a sandbox // with external networking support. -class IPv4UDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest { - protected: - void SetUp(); - - IfAddrHelper if_helper_; - - // found_net_interfaces_ is set to false if SetUp() could not obtain - // all interface infos that we need. - bool found_net_interfaces_; - - // Interface infos. - int lo_if_idx_; - int eth_if_idx_; - sockaddr_in lo_if_addr_; - sockaddr_in eth_if_addr_; -}; +using IPv4UDPUnboundExternalNetworkingSocketTest = + IPUDPUnboundExternalNetworkingSocketTest; } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc new file mode 100644 index 000000000..7364a1ea5 --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.cc @@ -0,0 +1,90 @@ +// Copyright 2020 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 "test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h" + +namespace gvisor { +namespace testing { + +TEST_P(IPv6UDPUnboundExternalNetworkingSocketTest, TestJoinLeaveMulticast) { + SKIP_IF(!found_net_interfaces_); + + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + auto receiver_addr = V6Any(); + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + + // Register to receive multicast packets. + auto multicast_addr = V6Multicast(); + ipv6_mreq group_req = { + .ipv6mr_multiaddr = + reinterpret_cast(&multicast_addr.addr)->sin6_addr, + .ipv6mr_interface = + (unsigned int)ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")), + }; + ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, + &group_req, sizeof(group_req)), + SyscallSucceeds()); + + // Set the sender to the loopback interface. + auto sender_addr = V6Loopback(); + ASSERT_THAT( + bind(sender->get(), reinterpret_cast(&sender_addr.addr), + sender_addr.addr_len), + SyscallSucceeds()); + + // Send a multicast packet. + auto send_addr = multicast_addr; + reinterpret_cast(&send_addr.addr)->sin6_port = + reinterpret_cast(&receiver_addr.addr)->sin6_port; + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, + reinterpret_cast(&send_addr.addr), + send_addr.addr_len), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we received the multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); + + // Leave the group and make sure we don't receive its multicast traffic. + ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, + &group_req, sizeof(group_req)), + SyscallSucceeds()); + RandomizeBuffer(send_buf, sizeof(send_buf)); + ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, + reinterpret_cast(&send_addr.addr), + send_addr.addr_len), + SyscallSucceedsWithValue(sizeof(send_buf))); + ASSERT_THAT(RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), + MSG_DONTWAIT), + SyscallFailsWithErrno(EAGAIN)); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h new file mode 100644 index 000000000..731ae0a1f --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h @@ -0,0 +1,31 @@ +// Copyright 2020 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. + +#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ + +#include "test/syscalls/linux/socket_ip_udp_unbound_external_networking.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to unbound IPv6 UDP sockets in a sandbox +// with external networking support. +using IPv6UDPUnboundExternalNetworkingSocketTest = + IPUDPUnboundExternalNetworkingSocketTest; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6yy_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc new file mode 100644 index 000000000..5c764b8fd --- /dev/null +++ b/test/syscalls/linux/socket_ipv6_udp_unbound_external_networking_test.cc @@ -0,0 +1,39 @@ +// Copyright 2020 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 "test/syscalls/linux/socket_ipv6_udp_unbound_external_networking.h" + +#include + +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { +namespace { + +std::vector GetSockets() { + return ApplyVec( + IPv6UDPUnboundSocket, + AllBitwiseCombinations(List{0, SOCK_NONBLOCK})); +} + +INSTANTIATE_TEST_SUITE_P(IPv6UDPUnboundSockets, + IPv6UDPUnboundExternalNetworkingSocketTest, + ::testing::ValuesIn(GetSockets())); + +} // namespace +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc index a760581b5..26dacc95e 100644 --- a/test/syscalls/linux/socket_test_util.cc +++ b/test/syscalls/linux/socket_test_util.cc @@ -860,6 +860,17 @@ TestAddress V6Loopback() { return t; } +TestAddress V6Multicast() { + TestAddress t("V6Multicast"); + t.addr.ss_family = AF_INET6; + t.addr_len = sizeof(sockaddr_in6); + EXPECT_EQ( + 1, + inet_pton(AF_INET6, "ff05::1234", + reinterpret_cast(&t.addr)->sin6_addr.s6_addr)); + return t; +} + // Checksum computes the internet checksum of a buffer. uint16_t Checksum(uint16_t* buf, ssize_t buf_size) { // Add up the 16-bit values in the buffer. diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h index 5e205339f..75c0d4735 100644 --- a/test/syscalls/linux/socket_test_util.h +++ b/test/syscalls/linux/socket_test_util.h @@ -502,6 +502,7 @@ TestAddress V4MappedLoopback(); TestAddress V4Multicast(); TestAddress V6Any(); TestAddress V6Loopback(); +TestAddress V6Multicast(); // Compute the internet checksum of an IP header. uint16_t IPChecksum(struct iphdr ip); -- cgit v1.2.3