[netstack] Support sending `IPV6_PKTINFO` cmsg
Fixed: 102222
Change-Id: I9f6c303c06b10affde5adc11102a044e9d51417e
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/691224
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Fuchsia-Auto-Submit: Ghanan Gowripalan <ghanan@google.com>
Reviewed-by: Nick Brown <nickbrow@google.com>
Reviewed-by: Tamir Duberstein <tamird@google.com>
Reviewed-by: Bruno Dal Bo <brunodalbo@google.com>
API-Review: Bruno Dal Bo <brunodalbo@google.com>
diff --git a/sdk/fidl/fuchsia.posix.socket/socket.fidl b/sdk/fidl/fuchsia.posix.socket/socket.fidl
index 8d134fb..00e4e56 100644
--- a/sdk/fidl/fuchsia.posix.socket/socket.fidl
+++ b/sdk/fidl/fuchsia.posix.socket/socket.fidl
@@ -533,6 +533,25 @@
/// The Hop Limit value to set in the IPv6 header of an outgoing
/// packet.
2: hoplimit uint8;
+
+ /// Information controlling the local interface and/or address used when
+ /// sending an IPv6 packet.
+ //
+ // This is a structure instead of a table as it is meant to match
+ // `in6_pktinfo` which is not expected to grow.
+ 3: pktinfo @generated_name("Ipv6PktInfoSendControlData") struct {
+ /// The interface index from which the IPv6 packet should be sent.
+ ///
+ /// 0 indicates that the local interface is unspecified and the
+ /// stack may choose an appropriate interface.
+ iface fuchsia.net.interface_id;
+ /// The source address from which the IPv6 packet should be sent.
+ ///
+ /// All zeroes indicates that the local address is unspecified and
+ /// the stack may choose an appropriate address (i.e. the local
+ /// address to which the socket is bound).
+ local_addr fuchsia.net.Ipv6Address;
+ };
};
};
diff --git a/sdk/lib/fdio/socket.cc b/sdk/lib/fdio/socket.cc
index ce94bac..ae660a4 100644
--- a/sdk/lib/fdio/socket.cc
+++ b/sdk/lib/fdio/socket.cc
@@ -295,8 +295,9 @@
}
}
-int16_t ParseIpv6LevelControlMessage(fsocket::wire::Ipv6SendControlData& fidl_ipv6, int type,
- const void* data, socklen_t data_len) {
+int16_t ParseIpv6LevelControlMessage(fsocket::wire::Ipv6SendControlData& fidl_ipv6,
+ fidl::AnyArena& allocator, int type, const void* data,
+ socklen_t data_len) {
switch (type) {
case IPV6_HOPLIMIT: {
int hoplimit;
@@ -315,6 +316,21 @@
}
return 0;
}
+ case IPV6_PKTINFO: {
+ in6_pktinfo pktinfo;
+ if (data_len != sizeof(pktinfo)) {
+ return EINVAL;
+ }
+ memcpy(&pktinfo, data, sizeof(pktinfo));
+ fsocket::wire::Ipv6PktInfoSendControlData fidl_pktinfo{
+ .iface = static_cast<uint64_t>(pktinfo.ipi6_ifindex),
+ };
+ static_assert(sizeof(pktinfo.ipi6_addr) == sizeof(fidl_pktinfo.local_addr.addr),
+ "mismatch between size of FIDL and in6_pktinfo IPv6 addresses");
+ memcpy(fidl_pktinfo.local_addr.addr.data(), &pktinfo.ipi6_addr, sizeof(pktinfo.ipi6_addr));
+ fidl_ipv6.set_pktinfo(allocator, fidl_pktinfo);
+ return 0;
+ }
default:
// TODO(https://fxbug.dev/88984): Validate unsupported SOL_IPV6 control messages.
return 0;
@@ -350,7 +366,7 @@
if (!fidl_net.has_ipv6()) {
fidl_net.set_ipv6(allocator, fsocket::wire::Ipv6SendControlData(allocator));
}
- return ParseIpv6LevelControlMessage(fidl_net.ipv6(), type, data, data_len);
+ return ParseIpv6LevelControlMessage(fidl_net.ipv6(), allocator, type, data, data_len);
default:
return 0;
}
diff --git a/src/connectivity/network/netstack/fuchsia_posix_socket.go b/src/connectivity/network/netstack/fuchsia_posix_socket.go
index 8a722fc..5e5bbba 100644
--- a/src/connectivity/network/netstack/fuchsia_posix_socket.go
+++ b/src/connectivity/network/netstack/fuchsia_posix_socket.go
@@ -1943,10 +1943,18 @@
if in.HasIpv6() {
inIpv6 := in.GetIpv6()
if inIpv6.HasHoplimit() {
- hoplimit := inIpv6.GetHoplimit()
- out.HopLimit = hoplimit
+ out.HopLimit = inIpv6.GetHoplimit()
out.HasHopLimit = true
}
+
+ if inIpv6.HasPktinfo() {
+ pktInfo := inIpv6.GetPktinfo()
+ out.IPv6PacketInfo = tcpip.IPv6PacketInfo{
+ NIC: tcpip.NICID(pktInfo.Iface),
+ Addr: toTcpIpAddressDroppingUnspecifiedv6(pktInfo.LocalAddr),
+ }
+ out.HasIPv6PacketInfo = true
+ }
}
return 0
diff --git a/src/connectivity/network/tests/multi_nic/BUILD.gn b/src/connectivity/network/tests/multi_nic/BUILD.gn
index 55139c9..7112cb0 100644
--- a/src/connectivity/network/tests/multi_nic/BUILD.gn
+++ b/src/connectivity/network/tests/multi_nic/BUILD.gn
@@ -7,7 +7,10 @@
test("bin") {
output_name = "multi_nic_test_client"
- sources = [ "main.cc" ]
+ sources = [
+ "main.cc",
+ "socket_test.cc",
+ ]
deps = [
"//src/connectivity/network/testing/netemul/sync-manager/fidl:sync_llcpp",
diff --git a/src/connectivity/network/tests/multi_nic/constants.h b/src/connectivity/network/tests/multi_nic/constants.h
new file mode 100644
index 0000000..eeeb1a7
--- /dev/null
+++ b/src/connectivity/network/tests/multi_nic/constants.h
@@ -0,0 +1,23 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_CONNECTIVITY_NETWORK_TESTS_MULTI_NIC_CONSTANTS_H_
+#define SRC_CONNECTIVITY_NETWORK_TESTS_MULTI_NIC_CONSTANTS_H_
+
+#include <stdint.h>
+
+constexpr char kClientNic1Name[] = "client-ep-1";
+constexpr char kClientNic2Name[] = "client-ep-2";
+
+constexpr char kClientIpv4Addr1[] = "192.168.0.1";
+constexpr char kClientIpv4Addr2[] = "192.168.0.2";
+constexpr char kServerIpv4Addr[] = "192.168.0.254";
+
+constexpr char kClientIpv6Addr1[] = "a::1";
+constexpr char kClientIpv6Addr2[] = "a::2";
+constexpr char kServerIpv6Addr[] = "a::ffff";
+
+constexpr uint16_t kServerPort = 1234;
+
+#endif // SRC_CONNECTIVITY_NETWORK_TESTS_MULTI_NIC_CONSTANTS_H_
diff --git a/src/connectivity/network/tests/multi_nic/main.cc b/src/connectivity/network/tests/multi_nic/main.cc
index d880e0d..b5a27a3 100644
--- a/src/connectivity/network/tests/multi_nic/main.cc
+++ b/src/connectivity/network/tests/multi_nic/main.cc
@@ -12,20 +12,14 @@
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
+#include "constants.h"
+
namespace {
constexpr char kBusName[] = "test-bus";
constexpr char kTestClientName[] = "client";
constexpr char kTestServerName[] = "server";
-constexpr char kClientIpv4Addr1[] = "192.168.0.1";
-constexpr char kClientIpv4Addr2[] = "192.168.0.2";
-constexpr char kServerIpv4Addr[] = "192.168.0.254";
-constexpr char kClientIpv6Addr1[] = "a::1";
-constexpr char kClientIpv6Addr2[] = "a::2";
-constexpr char kServerIpv6Addr[] = "a::ffff";
-constexpr uint16_t kServerPort = 1234;
-
void TestUdpPing(int domain, const sockaddr* bind_addr, socklen_t bind_addr_len,
const sockaddr* connect_addr, socklen_t connect_addr_len) {
fbl::unique_fd s;
diff --git a/src/connectivity/network/tests/multi_nic/socket_test.cc b/src/connectivity/network/tests/multi_nic/socket_test.cc
new file mode 100644
index 0000000..458bbf4
--- /dev/null
+++ b/src/connectivity/network/tests/multi_nic/socket_test.cc
@@ -0,0 +1,259 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <algorithm>
+
+#include <fbl/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "constants.h"
+
+namespace {
+
+struct SendIpv6PacketInfoSuccessTestCase {
+ std::string test_name;
+ std::optional<std::string> send_local_addr_str;
+ std::optional<std::string> send_local_if_str;
+ std::string expected_recv_addr_str;
+};
+
+class SendIpv6PacketInfoSuccessTest
+ : public testing::TestWithParam<SendIpv6PacketInfoSuccessTestCase> {};
+
+TEST_P(SendIpv6PacketInfoSuccessTest, SendAndRecv) {
+ fbl::unique_fd s;
+ ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
+
+ constexpr int kOne = 1;
+ ASSERT_EQ(setsockopt(s.get(), SOL_IPV6, IPV6_RECVPKTINFO, &kOne, sizeof(kOne)), 0)
+ << strerror(errno);
+
+ const sockaddr_in6 bind_addr{
+ .sin6_family = AF_INET6,
+ };
+ ASSERT_EQ(bind(s.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)), 0)
+ << strerror(errno);
+
+ sockaddr_in6 to_addr = {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(kServerPort),
+ };
+ ASSERT_EQ(inet_pton(to_addr.sin6_family, kServerIpv6Addr, &to_addr.sin6_addr), 1);
+
+ const auto [test_name, send_local_addr_str, send_local_if_str, expected_recv_addr_str] =
+ GetParam();
+ in6_addr expected_recv_addr;
+ ASSERT_EQ(inet_pton(AF_INET6, expected_recv_addr_str.c_str(), &expected_recv_addr), 1);
+ in6_pktinfo send_pktinfo = {};
+ if (send_local_addr_str.has_value()) {
+ ASSERT_EQ(inet_pton(AF_INET6, send_local_addr_str->c_str(), &send_pktinfo.ipi6_addr), 1);
+ }
+ if (send_local_if_str.has_value()) {
+ send_pktinfo.ipi6_ifindex = if_nametoindex(send_local_if_str->c_str());
+ }
+
+ char send_buf[] = "hello";
+ iovec send_iovec = {
+ .iov_base = send_buf,
+ .iov_len = sizeof(send_buf),
+ };
+ char send_control[CMSG_SPACE(sizeof(send_pktinfo)) + 1];
+ msghdr send_msghdr = {
+ .msg_name = &to_addr,
+ .msg_namelen = sizeof(to_addr),
+ .msg_iov = &send_iovec,
+ .msg_iovlen = 1,
+ .msg_control = send_control,
+ .msg_controllen = sizeof(send_control),
+ };
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&send_msghdr);
+ ASSERT_NE(cmsg, nullptr);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(send_pktinfo));
+ cmsg->cmsg_level = SOL_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ memcpy(CMSG_DATA(cmsg), &send_pktinfo, sizeof(send_pktinfo));
+ ASSERT_EQ(sendmsg(s.get(), &send_msghdr, 0), static_cast<ssize_t>(sizeof(send_buf)))
+ << strerror(errno);
+
+ constexpr char kExpectedRecvBuf[] = "Response: hello";
+ in6_pktinfo recv_pktinfo;
+ char recv_buf[sizeof(kExpectedRecvBuf) + 1];
+ iovec recv_iovec = {
+ .iov_base = recv_buf,
+ .iov_len = sizeof(recv_buf),
+ };
+ char recv_control[CMSG_SPACE(sizeof(recv_pktinfo)) + 1];
+ msghdr recv_msghdr = {
+ .msg_iov = &recv_iovec,
+ .msg_iovlen = 1,
+ .msg_control = recv_control,
+ .msg_controllen = sizeof(recv_control),
+ };
+
+ ASSERT_EQ(recvmsg(s.get(), &recv_msghdr, 0), static_cast<ssize_t>(sizeof(kExpectedRecvBuf)))
+ << strerror(errno);
+ EXPECT_EQ(std::string_view(kExpectedRecvBuf, sizeof(kExpectedRecvBuf)),
+ std::string_view(recv_buf, sizeof(kExpectedRecvBuf)));
+ cmsg = CMSG_FIRSTHDR(&recv_msghdr);
+ ASSERT_NE(cmsg, nullptr);
+ EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(recv_pktinfo)));
+ EXPECT_EQ(cmsg->cmsg_level, SOL_IPV6);
+ EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO);
+ memcpy(&recv_pktinfo, CMSG_DATA(cmsg), sizeof(recv_pktinfo));
+ EXPECT_EQ(memcmp(&recv_pktinfo.ipi6_addr, &expected_recv_addr, sizeof(expected_recv_addr)), 0);
+}
+
+INSTANTIATE_TEST_SUITE_P(SocketTests, SendIpv6PacketInfoSuccessTest,
+ testing::Values(
+ SendIpv6PacketInfoSuccessTestCase{
+ .test_name = "NIC1 local address",
+ .send_local_addr_str = kClientIpv6Addr1,
+ .expected_recv_addr_str = kClientIpv6Addr1,
+ },
+ SendIpv6PacketInfoSuccessTestCase{
+ .test_name = "NIC1 local interface",
+ .send_local_if_str = kClientNic1Name,
+ .expected_recv_addr_str = kClientIpv6Addr1,
+ },
+ SendIpv6PacketInfoSuccessTestCase{
+ .test_name = "NIC1 local address and interface",
+ .send_local_addr_str = kClientIpv6Addr1,
+ .send_local_if_str = kClientNic1Name,
+ .expected_recv_addr_str = kClientIpv6Addr1,
+ },
+ SendIpv6PacketInfoSuccessTestCase{
+ .test_name = "NIC2 local address",
+ .send_local_addr_str = kClientIpv6Addr2,
+ .expected_recv_addr_str = kClientIpv6Addr2,
+ },
+ SendIpv6PacketInfoSuccessTestCase{
+ .test_name = "NIC2 local interface",
+ .send_local_if_str = kClientNic2Name,
+ .expected_recv_addr_str = kClientIpv6Addr2,
+ },
+ SendIpv6PacketInfoSuccessTestCase{
+ .test_name = "NIC2 local address and interface",
+ .send_local_addr_str = kClientIpv6Addr2,
+ .send_local_if_str = kClientNic2Name,
+ .expected_recv_addr_str = kClientIpv6Addr2,
+ }),
+ [](const testing::TestParamInfo<SendIpv6PacketInfoSuccessTestCase>& info) {
+ std::string test_name(info.param.test_name);
+ std::replace(test_name.begin(), test_name.end(), ' ', '_');
+ return test_name;
+ });
+
+struct SendIpv6PacketInfoFailureTestCase {
+ std::string test_name;
+ std::string bind_addr_str;
+ std::optional<std::string> bind_to_device;
+ std::optional<std::string> send_local_addr_str;
+ std::optional<std::string> send_local_if_str;
+ ssize_t expected_errno;
+};
+
+class SendIpv6PacketInfoFailureTest
+ : public testing::TestWithParam<SendIpv6PacketInfoFailureTestCase> {};
+
+TEST_P(SendIpv6PacketInfoFailureTest, CheckError) {
+ fbl::unique_fd s;
+ ASSERT_TRUE(s = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
+
+ const auto [test_name, bind_addr_str, bind_to_device, send_local_addr_str, send_local_if_str,
+ expected_errno] = GetParam();
+
+ if (bind_to_device.has_value()) {
+ ASSERT_EQ(setsockopt(s.get(), SOL_SOCKET, SO_BINDTODEVICE, bind_to_device->c_str(),
+ static_cast<socklen_t>(bind_to_device->size())),
+ 0)
+ << strerror(errno);
+ }
+
+ sockaddr_in6 bind_addr{
+ .sin6_family = AF_INET6,
+ };
+ ASSERT_EQ(inet_pton(bind_addr.sin6_family, bind_addr_str.c_str(), &bind_addr.sin6_addr), 1);
+ ASSERT_EQ(bind(s.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)), 0)
+ << strerror(errno);
+
+ sockaddr_in6 to_addr = {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(kServerPort),
+ };
+ ASSERT_EQ(inet_pton(to_addr.sin6_family, kServerIpv6Addr, &to_addr.sin6_addr), 1);
+
+ in6_pktinfo send_pktinfo = {};
+ if (send_local_addr_str.has_value()) {
+ ASSERT_EQ(inet_pton(AF_INET6, send_local_addr_str->c_str(), &send_pktinfo.ipi6_addr), 1);
+ }
+ if (send_local_if_str.has_value()) {
+ send_pktinfo.ipi6_ifindex = if_nametoindex(send_local_if_str->c_str());
+ }
+
+ char send_buf[] = "hello";
+ iovec send_iovec = {
+ .iov_base = send_buf,
+ .iov_len = sizeof(send_buf),
+ };
+ char send_control[CMSG_SPACE(sizeof(send_pktinfo)) + 1];
+ msghdr send_msghdr = {
+ .msg_name = &to_addr,
+ .msg_namelen = sizeof(to_addr),
+ .msg_iov = &send_iovec,
+ .msg_iovlen = 1,
+ .msg_control = send_control,
+ .msg_controllen = sizeof(send_control),
+ };
+ cmsghdr* cmsg = CMSG_FIRSTHDR(&send_msghdr);
+ ASSERT_NE(cmsg, nullptr);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(send_pktinfo));
+ cmsg->cmsg_level = SOL_IPV6;
+ cmsg->cmsg_type = IPV6_PKTINFO;
+ memcpy(CMSG_DATA(cmsg), &send_pktinfo, sizeof(send_pktinfo));
+ ASSERT_EQ(sendmsg(s.get(), &send_msghdr, 0), -1);
+ EXPECT_EQ(errno, expected_errno) << strerror(errno);
+}
+
+constexpr char kIpv6UnspecifiedAddr[] = "::";
+
+INSTANTIATE_TEST_SUITE_P(SocketTests, SendIpv6PacketInfoFailureTest,
+ testing::Values(
+ SendIpv6PacketInfoFailureTestCase{
+ .test_name = "Local interface and bound device mismatch",
+ .bind_addr_str = kIpv6UnspecifiedAddr,
+ .bind_to_device = kClientNic2Name,
+ .send_local_if_str = kClientNic1Name,
+ .expected_errno = EHOSTUNREACH,
+ },
+ SendIpv6PacketInfoFailureTestCase{
+ .test_name = "Local address and bound device mismatch",
+ .bind_addr_str = kIpv6UnspecifiedAddr,
+ .bind_to_device = kClientNic2Name,
+ .send_local_addr_str = kClientIpv6Addr1,
+ .expected_errno = EADDRNOTAVAIL,
+ },
+ SendIpv6PacketInfoFailureTestCase{
+ .test_name = "Local addr and interface mismatch",
+ .bind_addr_str = kIpv6UnspecifiedAddr,
+ .send_local_addr_str = kClientIpv6Addr2,
+ .send_local_if_str = kClientNic1Name,
+ .expected_errno = EADDRNOTAVAIL,
+ },
+ SendIpv6PacketInfoFailureTestCase{
+ .test_name = "Bound address and local interface mismatch",
+ .bind_addr_str = kClientIpv6Addr1,
+ .send_local_if_str = kClientNic2Name,
+ .expected_errno = EADDRNOTAVAIL}),
+ [](const testing::TestParamInfo<SendIpv6PacketInfoFailureTestCase>& info) {
+ std::string test_name(info.param.test_name);
+ std::replace(test_name.begin(), test_name.end(), ' ', '_');
+ return test_name;
+ });
+
+} // namespace