gfe-relnote: Default-initialize QUIC BBRv2 loss event threshold for exiting STARTUP from a flag. Protected by --gfe2_reloadable_flag_quic_default_to_bbr_v2.
PiperOrigin-RevId: 264298542
Change-Id: I304ab19e4820dec51d3f8ef53762a393f6b175fd
diff --git a/quic/qbone/platform/netlink_test.cc b/quic/qbone/platform/netlink_test.cc
new file mode 100644
index 0000000..024e0fb
--- /dev/null
+++ b/quic/qbone/platform/netlink_test.cc
@@ -0,0 +1,763 @@
+// Copyright (c) 2019 The Chromium 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 "net/third_party/quiche/src/quic/qbone/platform/netlink.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/mock_kernel.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+
+namespace quic {
+namespace {
+
+using ::testing::_;
+using ::testing::Contains;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::Unused;
+
+const int kSocketFd = 101;
+
+class NetlinkTest : public QuicTest {
+ protected:
+ NetlinkTest() {
+ ON_CALL(mock_kernel_, socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE))
+ .WillByDefault(Invoke([this](Unused, Unused, Unused) {
+ EXPECT_CALL(mock_kernel_, close(kSocketFd)).WillOnce(Return(0));
+ return kSocketFd;
+ }));
+ }
+
+ void ExpectNetlinkPacket(
+ uint16_t type,
+ uint16_t flags,
+ const std::function<ssize_t(void* buf, size_t len, int seq)>&
+ recv_callback,
+ const std::function<void(const void* buf, size_t len)>& send_callback =
+ nullptr) {
+ static int seq = -1;
+ InSequence s;
+
+ EXPECT_CALL(mock_kernel_, sendmsg(kSocketFd, _, _))
+ .WillOnce(Invoke([this, type, flags, send_callback](
+ Unused, const struct msghdr* msg, int) {
+ EXPECT_EQ(sizeof(struct sockaddr_nl), msg->msg_namelen);
+ auto* nl_addr =
+ reinterpret_cast<const struct sockaddr_nl*>(msg->msg_name);
+ EXPECT_EQ(AF_NETLINK, nl_addr->nl_family);
+ EXPECT_EQ(0, nl_addr->nl_pid);
+ EXPECT_EQ(0, nl_addr->nl_groups);
+
+ EXPECT_GE(msg->msg_iovlen, 1);
+ EXPECT_GE(msg->msg_iov[0].iov_len, sizeof(struct nlmsghdr));
+
+ string buf;
+ for (int i = 0; i < msg->msg_iovlen; i++) {
+ buf.append(string(reinterpret_cast<char*>(msg->msg_iov[i].iov_base),
+ msg->msg_iov[i].iov_len));
+ }
+
+ auto* netlink_message =
+ reinterpret_cast<const struct nlmsghdr*>(buf.c_str());
+ EXPECT_EQ(type, netlink_message->nlmsg_type);
+ EXPECT_EQ(flags, netlink_message->nlmsg_flags);
+ EXPECT_GE(buf.size(), netlink_message->nlmsg_len);
+
+ if (send_callback != nullptr) {
+ send_callback(buf.c_str(), buf.size());
+ }
+
+ CHECK_EQ(seq, -1);
+ seq = netlink_message->nlmsg_seq;
+ return buf.size();
+ }));
+
+ EXPECT_CALL(mock_kernel_,
+ recvfrom(kSocketFd, _, 0, MSG_PEEK | MSG_TRUNC, _, _))
+ .WillOnce(Invoke([this, recv_callback](Unused, Unused, Unused, Unused,
+ struct sockaddr* src_addr,
+ socklen_t* addrlen) {
+ auto* nl_addr = reinterpret_cast<struct sockaddr_nl*>(src_addr);
+ nl_addr->nl_family = AF_NETLINK;
+ nl_addr->nl_pid = 0; // from kernel
+ nl_addr->nl_groups = 0; // no multicast
+
+ int ret = recv_callback(reply_packet_, sizeof(reply_packet_), seq);
+ CHECK_LE(ret, sizeof(reply_packet_));
+ return ret;
+ }));
+
+ EXPECT_CALL(mock_kernel_, recvfrom(kSocketFd, _, _, _, _, _))
+ .WillOnce(Invoke([recv_callback](Unused, void* buf, size_t len, Unused,
+ struct sockaddr* src_addr,
+ socklen_t* addrlen) {
+ auto* nl_addr = reinterpret_cast<struct sockaddr_nl*>(src_addr);
+ nl_addr->nl_family = AF_NETLINK;
+ nl_addr->nl_pid = 0; // from kernel
+ nl_addr->nl_groups = 0; // no multicast
+
+ int ret = recv_callback(buf, len, seq);
+ EXPECT_GE(len, ret);
+ seq = -1;
+ return ret;
+ }));
+ }
+
+ char reply_packet_[4096];
+ MockKernel mock_kernel_;
+};
+
+void AddRTA(struct nlmsghdr* netlink_message,
+ uint16_t type,
+ const void* data,
+ size_t len) {
+ auto* next_header_ptr = reinterpret_cast<char*>(netlink_message) +
+ NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ auto* rta = reinterpret_cast<struct rtattr*>(next_header_ptr);
+ rta->rta_type = type;
+ rta->rta_len = RTA_LENGTH(len);
+ memcpy(RTA_DATA(rta), data, len);
+
+ netlink_message->nlmsg_len =
+ NLMSG_ALIGN(netlink_message->nlmsg_len) + RTA_LENGTH(len);
+}
+
+void CreateIfinfomsg(struct nlmsghdr* netlink_message,
+ const string& interface_name,
+ uint16_t type,
+ int index,
+ unsigned int flags,
+ unsigned int change,
+ uint8_t address[],
+ int address_len,
+ uint8_t broadcast[],
+ int broadcast_len) {
+ auto* interface_info =
+ reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(netlink_message));
+ interface_info->ifi_family = AF_UNSPEC;
+ interface_info->ifi_type = type;
+ interface_info->ifi_index = index;
+ interface_info->ifi_flags = flags;
+ interface_info->ifi_change = change;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+
+ // Add address
+ AddRTA(netlink_message, IFLA_ADDRESS, address, address_len);
+
+ // Add broadcast address
+ AddRTA(netlink_message, IFLA_BROADCAST, broadcast, broadcast_len);
+
+ // Add name
+ AddRTA(netlink_message, IFLA_IFNAME, interface_name.c_str(),
+ interface_name.size());
+}
+
+struct nlmsghdr* CreateNetlinkMessage(void* buf, // NOLINT
+ struct nlmsghdr* previous_netlink_message,
+ uint16_t type,
+ int seq) {
+ auto* next_header_ptr = reinterpret_cast<char*>(buf);
+ if (previous_netlink_message != nullptr) {
+ next_header_ptr = reinterpret_cast<char*>(previous_netlink_message) +
+ NLMSG_ALIGN(previous_netlink_message->nlmsg_len);
+ }
+ auto* netlink_message = reinterpret_cast<nlmsghdr*>(next_header_ptr);
+ netlink_message->nlmsg_len = NLMSG_LENGTH(0);
+ netlink_message->nlmsg_type = type;
+ netlink_message->nlmsg_flags = NLM_F_MULTI;
+ netlink_message->nlmsg_pid = 0; // from the kernel
+ netlink_message->nlmsg_seq = seq;
+
+ return netlink_message;
+}
+
+void CreateIfaddrmsg(struct nlmsghdr* nlm,
+ int interface_index,
+ unsigned char prefixlen,
+ unsigned char flags,
+ unsigned char scope,
+ QuicIpAddress ip) {
+ CHECK(ip.IsInitialized());
+ unsigned char family;
+ switch (ip.address_family()) {
+ case IpAddressFamily::IP_V4:
+ family = AF_INET;
+ break;
+ case IpAddressFamily::IP_V6:
+ family = AF_INET6;
+ break;
+ default:
+ QUIC_BUG << absl::StrCat("unexpected address family: ",
+ ip.address_family());
+ family = AF_UNSPEC;
+ }
+ auto* msg = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlm));
+ msg->ifa_family = family;
+ msg->ifa_prefixlen = prefixlen;
+ msg->ifa_flags = flags;
+ msg->ifa_scope = scope;
+ msg->ifa_index = interface_index;
+ nlm->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+
+ // Add local address
+ AddRTA(nlm, IFA_LOCAL, ip.ToPackedString().c_str(),
+ ip.ToPackedString().size());
+}
+
+void CreateRtmsg(struct nlmsghdr* nlm,
+ unsigned char family,
+ unsigned char destination_length,
+ unsigned char source_length,
+ unsigned char tos,
+ unsigned char table,
+ unsigned char protocol,
+ unsigned char scope,
+ unsigned char type,
+ unsigned int flags,
+ QuicIpAddress destination,
+ int interface_index) {
+ auto* msg = reinterpret_cast<struct rtmsg*>(NLMSG_DATA(nlm));
+ msg->rtm_family = family;
+ msg->rtm_dst_len = destination_length;
+ msg->rtm_src_len = source_length;
+ msg->rtm_tos = tos;
+ msg->rtm_table = table;
+ msg->rtm_protocol = protocol;
+ msg->rtm_scope = scope;
+ msg->rtm_type = type;
+ msg->rtm_flags = flags;
+ nlm->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+
+ // Add destination
+ AddRTA(nlm, RTA_DST, destination.ToPackedString().c_str(),
+ destination.ToPackedString().size());
+
+ // Add egress interface
+ AddRTA(nlm, RTA_OIF, &interface_index, sizeof(interface_index));
+}
+
+TEST_F(NetlinkTest, GetLinkInfoWorks) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ uint8_t hwaddr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
+ uint8_t bcaddr[] = {'c', 'b', 'a', 'f', 'e', 'd'};
+
+ ExpectNetlinkPacket(
+ RTM_GETLINK, NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ [this, &hwaddr, &bcaddr](void* buf, size_t len, int seq) {
+ int ret = 0;
+
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, RTM_NEWLINK, seq);
+ CreateIfinfomsg(netlink_message, "tun0", /* type = */ 1,
+ /* index = */ 7,
+ /* flags = */ 0,
+ /* change = */ 0xFFFFFFFF, hwaddr, 6, bcaddr, 6);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ netlink_message =
+ CreateNetlinkMessage(buf, netlink_message, NLMSG_DONE, seq);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ return ret;
+ });
+
+ Netlink::LinkInfo link_info;
+ EXPECT_TRUE(netlink->GetLinkInfo("tun0", &link_info));
+
+ EXPECT_EQ(7, link_info.index);
+ EXPECT_EQ(1, link_info.type);
+
+ for (int i = 0; i < link_info.hardware_address_length; ++i) {
+ EXPECT_EQ(hwaddr[i], link_info.hardware_address[i]);
+ }
+ for (int i = 0; i < link_info.broadcast_address_length; ++i) {
+ EXPECT_EQ(bcaddr[i], link_info.broadcast_address[i]);
+ }
+}
+
+TEST_F(NetlinkTest, GetAddressesWorks) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicUnorderedSet<std::string> addresses = {QuicIpAddress::Any4().ToString(),
+ QuicIpAddress::Any6().ToString()};
+
+ ExpectNetlinkPacket(
+ RTM_GETADDR, NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ [this, &addresses](void* buf, size_t len, int seq) {
+ int ret = 0;
+
+ struct nlmsghdr* nlm = nullptr;
+
+ for (const auto& address : addresses) {
+ QuicIpAddress ip;
+ ip.FromString(address);
+ nlm = CreateNetlinkMessage(buf, nlm, RTM_NEWADDR, seq);
+ CreateIfaddrmsg(nlm, /* interface_index = */ 7, /* prefixlen = */ 24,
+ /* flags = */ 0, /* scope = */ RT_SCOPE_UNIVERSE, ip);
+
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+ }
+
+ // Create IPs with unwanted flags.
+ {
+ QuicIpAddress ip;
+ ip.FromString("10.0.0.1");
+ nlm = CreateNetlinkMessage(buf, nlm, RTM_NEWADDR, seq);
+ CreateIfaddrmsg(nlm, /* interface_index = */ 7, /* prefixlen = */ 16,
+ /* flags = */ IFA_F_OPTIMISTIC, /* scope = */
+ RT_SCOPE_UNIVERSE, ip);
+
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+
+ ip.FromString("10.0.0.2");
+ nlm = CreateNetlinkMessage(buf, nlm, RTM_NEWADDR, seq);
+ CreateIfaddrmsg(nlm, /* interface_index = */ 7, /* prefixlen = */ 16,
+ /* flags = */ IFA_F_TENTATIVE, /* scope = */
+ RT_SCOPE_UNIVERSE, ip);
+
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+ }
+
+ nlm = CreateNetlinkMessage(buf, nlm, NLMSG_DONE, seq);
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+
+ return ret;
+ });
+
+ std::vector<Netlink::AddressInfo> reported_addresses;
+ int num_ipv6_nodad_dadfailed_addresses = 0;
+ EXPECT_TRUE(netlink->GetAddresses(7, IFA_F_TENTATIVE | IFA_F_OPTIMISTIC,
+ &reported_addresses,
+ &num_ipv6_nodad_dadfailed_addresses));
+
+ for (const auto& reported_address : reported_addresses) {
+ EXPECT_TRUE(reported_address.local_address.IsInitialized());
+ EXPECT_FALSE(reported_address.interface_address.IsInitialized());
+ EXPECT_THAT(addresses, Contains(reported_address.local_address.ToString()));
+ addresses.erase(reported_address.local_address.ToString());
+
+ EXPECT_EQ(24, reported_address.prefix_length);
+ }
+
+ EXPECT_TRUE(addresses.empty());
+}
+
+TEST_F(NetlinkTest, ChangeLocalAddressAdd) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress ip = QuicIpAddress::Any6();
+ ExpectNetlinkPacket(
+ RTM_NEWADDR, NLM_F_ACK | NLM_F_REQUEST,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [ip](const void* buf, size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* ifa = reinterpret_cast<const struct ifaddrmsg*>(
+ NLMSG_DATA(netlink_message));
+ EXPECT_EQ(19, ifa->ifa_prefixlen);
+ EXPECT_EQ(RT_SCOPE_UNIVERSE, ifa->ifa_scope);
+ EXPECT_EQ(IFA_F_PERMANENT, ifa->ifa_flags);
+ EXPECT_EQ(7, ifa->ifa_index);
+ EXPECT_EQ(AF_INET6, ifa->ifa_family);
+
+ const struct rtattr* rta;
+ int payload_length = IFA_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = IFA_RTA(ifa); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case IFA_LOCAL: {
+ EXPECT_EQ(ip.ToPackedString().size(), RTA_PAYLOAD(rta));
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(ip, address);
+ break;
+ }
+ case IFA_CACHEINFO: {
+ EXPECT_EQ(sizeof(struct ifa_cacheinfo), RTA_PAYLOAD(rta));
+ const auto* cache_info =
+ reinterpret_cast<const struct ifa_cacheinfo*>(RTA_DATA(rta));
+ EXPECT_EQ(8, cache_info->ifa_prefered); // common_typos_disable
+ EXPECT_EQ(6, cache_info->ifa_valid);
+ EXPECT_EQ(4, cache_info->cstamp);
+ EXPECT_EQ(2, cache_info->tstamp);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not exist";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(2, num_rta);
+ });
+
+ struct {
+ struct rtattr rta;
+ struct ifa_cacheinfo cache_info;
+ } additional_rta;
+
+ additional_rta.rta.rta_type = IFA_CACHEINFO;
+ additional_rta.rta.rta_len = RTA_LENGTH(sizeof(struct ifa_cacheinfo));
+ additional_rta.cache_info.ifa_prefered = 8;
+ additional_rta.cache_info.ifa_valid = 6;
+ additional_rta.cache_info.cstamp = 4;
+ additional_rta.cache_info.tstamp = 2;
+
+ EXPECT_TRUE(netlink->ChangeLocalAddress(7, Netlink::Verb::kAdd, ip, 19,
+ IFA_F_PERMANENT, RT_SCOPE_UNIVERSE,
+ {&additional_rta.rta}));
+}
+
+TEST_F(NetlinkTest, ChangeLocalAddressRemove) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress ip = QuicIpAddress::Any4();
+ ExpectNetlinkPacket(
+ RTM_DELADDR, NLM_F_ACK | NLM_F_REQUEST,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [ip](const void* buf, size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* ifa = reinterpret_cast<const struct ifaddrmsg*>(
+ NLMSG_DATA(netlink_message));
+ EXPECT_EQ(32, ifa->ifa_prefixlen);
+ EXPECT_EQ(RT_SCOPE_UNIVERSE, ifa->ifa_scope);
+ EXPECT_EQ(0, ifa->ifa_flags);
+ EXPECT_EQ(7, ifa->ifa_index);
+ EXPECT_EQ(AF_INET, ifa->ifa_family);
+
+ const struct rtattr* rta;
+ int payload_length = IFA_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = IFA_RTA(ifa); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case IFA_LOCAL: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(in_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(ip, address);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not exist";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(1, num_rta);
+ });
+
+ EXPECT_TRUE(netlink->ChangeLocalAddress(7, Netlink::Verb::kRemove, ip, 32, 0,
+ RT_SCOPE_UNIVERSE, {}));
+}
+
+TEST_F(NetlinkTest, GetRouteInfoWorks) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress destination;
+ ASSERT_TRUE(destination.FromString("f800::2"));
+ ExpectNetlinkPacket(RTM_GETROUTE, NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ [destination](void* buf, size_t len, int seq) {
+ int ret = 0;
+ struct nlmsghdr* netlink_message = CreateNetlinkMessage(
+ buf, nullptr, RTM_NEWROUTE, seq);
+ CreateRtmsg(netlink_message, AF_INET6, 48, 0, 0,
+ RT_TABLE_MAIN, RTPROT_STATIC, RT_SCOPE_LINK,
+ RTN_UNICAST, 0, destination, 7);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ netlink_message = CreateNetlinkMessage(
+ buf, netlink_message, NLMSG_DONE, seq);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ QUIC_LOG(INFO) << "ret: " << ret;
+ return ret;
+ });
+
+ std::vector<Netlink::RoutingRule> routing_rules;
+ EXPECT_TRUE(netlink->GetRouteInfo(&routing_rules));
+
+ ASSERT_EQ(1, routing_rules.size());
+ EXPECT_EQ(RT_SCOPE_LINK, routing_rules[0].scope);
+ EXPECT_EQ(IpRange(destination, 48).ToString(),
+ routing_rules[0].destination_subnet.ToString());
+ EXPECT_FALSE(routing_rules[0].preferred_source.IsInitialized());
+ EXPECT_EQ(7, routing_rules[0].out_interface);
+}
+
+TEST_F(NetlinkTest, ChangeRouteAdd) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress preferred_ip;
+ preferred_ip.FromString("ff80:dead:beef::1");
+ IpRange subnet;
+ subnet.FromString("ff80:dead:beef::/48");
+ int egress_interface_index = 7;
+ ExpectNetlinkPacket(
+ RTM_NEWROUTE, NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [preferred_ip, subnet, egress_interface_index](const void* buf,
+ size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* rtm =
+ reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(AF_INET6, rtm->rtm_family);
+ EXPECT_EQ(48, rtm->rtm_dst_len);
+ EXPECT_EQ(0, rtm->rtm_src_len);
+ EXPECT_EQ(RT_TABLE_MAIN, rtm->rtm_table);
+ EXPECT_EQ(RTPROT_STATIC, rtm->rtm_protocol);
+ EXPECT_EQ(RT_SCOPE_LINK, rtm->rtm_scope);
+ EXPECT_EQ(RTN_UNICAST, rtm->rtm_type);
+
+ const struct rtattr* rta;
+ int payload_length = RTM_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = RTM_RTA(rtm); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_PREFSRC: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(preferred_ip, address);
+ break;
+ }
+ case RTA_OIF: {
+ ASSERT_EQ(sizeof(int), RTA_PAYLOAD(rta));
+ const auto* interface_index =
+ reinterpret_cast<const int*>(RTA_DATA(rta));
+ EXPECT_EQ(egress_interface_index, *interface_index);
+ break;
+ }
+ case RTA_DST: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(subnet.ToString(),
+ IpRange(address, rtm->rtm_dst_len).ToString());
+ break;
+ }
+ case RTA_TABLE: {
+ ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rta)),
+ QboneConstants::kQboneRouteTableId);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not be sent";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(4, num_rta);
+ });
+ EXPECT_TRUE(netlink->ChangeRoute(
+ Netlink::Verb::kAdd, QboneConstants::kQboneRouteTableId, subnet,
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+}
+
+TEST_F(NetlinkTest, ChangeRouteRemove) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress preferred_ip;
+ preferred_ip.FromString("ff80:dead:beef::1");
+ IpRange subnet;
+ subnet.FromString("ff80:dead:beef::/48");
+ int egress_interface_index = 7;
+ ExpectNetlinkPacket(
+ RTM_DELROUTE, NLM_F_ACK | NLM_F_REQUEST,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [preferred_ip, subnet, egress_interface_index](const void* buf,
+ size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* rtm =
+ reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(AF_INET6, rtm->rtm_family);
+ EXPECT_EQ(48, rtm->rtm_dst_len);
+ EXPECT_EQ(0, rtm->rtm_src_len);
+ EXPECT_EQ(RT_TABLE_MAIN, rtm->rtm_table);
+ EXPECT_EQ(RTPROT_UNSPEC, rtm->rtm_protocol);
+ EXPECT_EQ(RT_SCOPE_LINK, rtm->rtm_scope);
+ EXPECT_EQ(RTN_UNICAST, rtm->rtm_type);
+
+ const struct rtattr* rta;
+ int payload_length = RTM_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = RTM_RTA(rtm); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_PREFSRC: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(preferred_ip, address);
+ break;
+ }
+ case RTA_OIF: {
+ ASSERT_EQ(sizeof(int), RTA_PAYLOAD(rta));
+ const auto* interface_index =
+ reinterpret_cast<const int*>(RTA_DATA(rta));
+ EXPECT_EQ(egress_interface_index, *interface_index);
+ break;
+ }
+ case RTA_DST: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(subnet.ToString(),
+ IpRange(address, rtm->rtm_dst_len).ToString());
+ break;
+ }
+ case RTA_TABLE: {
+ ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rta)),
+ QboneConstants::kQboneRouteTableId);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not be sent";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(4, num_rta);
+ });
+ EXPECT_TRUE(netlink->ChangeRoute(
+ Netlink::Verb::kRemove, QboneConstants::kQboneRouteTableId, subnet,
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+}
+
+TEST_F(NetlinkTest, ChangeRouteReplace) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress preferred_ip;
+ preferred_ip.FromString("ff80:dead:beef::1");
+ IpRange subnet;
+ subnet.FromString("ff80:dead:beef::/48");
+ int egress_interface_index = 7;
+ ExpectNetlinkPacket(
+ RTM_NEWROUTE, NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [preferred_ip, subnet, egress_interface_index](const void* buf,
+ size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* rtm =
+ reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(AF_INET6, rtm->rtm_family);
+ EXPECT_EQ(48, rtm->rtm_dst_len);
+ EXPECT_EQ(0, rtm->rtm_src_len);
+ EXPECT_EQ(RT_TABLE_MAIN, rtm->rtm_table);
+ EXPECT_EQ(RTPROT_STATIC, rtm->rtm_protocol);
+ EXPECT_EQ(RT_SCOPE_LINK, rtm->rtm_scope);
+ EXPECT_EQ(RTN_UNICAST, rtm->rtm_type);
+
+ const struct rtattr* rta;
+ int payload_length = RTM_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = RTM_RTA(rtm); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_PREFSRC: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(preferred_ip, address);
+ break;
+ }
+ case RTA_OIF: {
+ ASSERT_EQ(sizeof(int), RTA_PAYLOAD(rta));
+ const auto* interface_index =
+ reinterpret_cast<const int*>(RTA_DATA(rta));
+ EXPECT_EQ(egress_interface_index, *interface_index);
+ break;
+ }
+ case RTA_DST: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(subnet.ToString(),
+ IpRange(address, rtm->rtm_dst_len).ToString());
+ break;
+ }
+ case RTA_TABLE: {
+ ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rta)),
+ QboneConstants::kQboneRouteTableId);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not be sent";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(4, num_rta);
+ });
+ EXPECT_TRUE(netlink->ChangeRoute(
+ Netlink::Verb::kReplace, QboneConstants::kQboneRouteTableId, subnet,
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+}
+
+} // namespace
+} // namespace quic