| // 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 "quiche/quic/qbone/platform/netlink.h" |
| |
| #include <utility> |
| |
| #include "absl/container/node_hash_set.h" |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/qbone/platform/mock_kernel.h" |
| #include "quiche/quic/qbone/qbone_constants.h" |
| |
| namespace quic::test { |
| 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([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)); |
| |
| std::string buf; |
| for (int i = 0; i < msg->msg_iovlen; i++) { |
| buf.append( |
| std::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()); |
| } |
| |
| QUICHE_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); |
| QUICHE_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 std::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) { |
| QUICHE_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(quic_bug_11034_1) |
| << 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, |
| int init_cwnd) { |
| 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)); |
| |
| // Add initcwnd |
| if (init_cwnd > 0) { |
| char data[RTA_LENGTH(sizeof(uint32_t))]; |
| struct rtattr* rta = reinterpret_cast<struct rtattr*>(data); |
| rta->rta_len = sizeof(data); |
| rta->rta_type = RTA_METRICS; |
| *reinterpret_cast<uint32_t*>(RTA_DATA(rta)) = init_cwnd; |
| AddRTA(nlm, RTA_METRICS, data, sizeof(data)); |
| } |
| } |
| |
| TEST_F(NetlinkTest, GetLinkInfoWorks) { |
| auto netlink = std::make_unique<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, |
| [&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 = std::make_unique<Netlink>(&mock_kernel_); |
| |
| absl::node_hash_set<std::string> addresses = { |
| QuicIpAddress::Any4().ToString(), QuicIpAddress::Any6().ToString()}; |
| |
| ExpectNetlinkPacket( |
| RTM_GETADDR, NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST, |
| [&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 = std::make_unique<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 = std::make_unique<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 = std::make_unique<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, 0); |
| 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); |
| EXPECT_EQ(0, routing_rules[0].init_cwnd); |
| } |
| |
| TEST_F(NetlinkTest, ChangeRouteAdd) { |
| auto netlink = std::make_unique<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; |
| uint32_t init_cwnd = 32; |
| 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, init_cwnd](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_GATEWAY: { |
| 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(*QboneConstants::GatewayAddress(), 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; |
| } |
| case RTA_METRICS: { |
| struct rtattr* rtax = |
| reinterpret_cast<struct rtattr*>(RTA_DATA(rta)); |
| ASSERT_EQ(rtax->rta_type, RTAX_INITCWND); |
| ASSERT_EQ(rtax->rta_len, RTA_LENGTH(sizeof(uint32_t))); |
| ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rtax)), |
| init_cwnd); |
| break; |
| } |
| default: |
| EXPECT_TRUE(false) << "Seeing rtattr that should not be sent"; |
| } |
| ++num_rta; |
| } |
| EXPECT_EQ(6, num_rta); |
| }); |
| EXPECT_TRUE(netlink->ChangeRoute( |
| Netlink::Verb::kAdd, QboneConstants::kQboneRouteTableId, subnet, |
| RT_SCOPE_LINK, preferred_ip, egress_interface_index, init_cwnd)); |
| } |
| |
| TEST_F(NetlinkTest, ChangeRouteRemove) { |
| auto netlink = std::make_unique<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, |
| Netlink::kUnspecifiedInitCwnd)); |
| } |
| |
| TEST_F(NetlinkTest, ChangeRouteReplace) { |
| auto netlink = std::make_unique<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_GATEWAY: { |
| 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(*QboneConstants::GatewayAddress(), 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(5, num_rta); |
| }); |
| EXPECT_TRUE(netlink->ChangeRoute( |
| Netlink::Verb::kReplace, QboneConstants::kQboneRouteTableId, subnet, |
| RT_SCOPE_LINK, preferred_ip, egress_interface_index, |
| Netlink::kUnspecifiedInitCwnd)); |
| } |
| |
| } // namespace |
| } // namespace quic::test |