blob: f6fe7cda9df1476bde65279773f6b6eea84ab9cd [file] [log] [blame]
// 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 "quic/qbone/platform/netlink.h"
#include <utility>
#include "absl/container/node_hash_set.h"
#include "quic/platform/api/quic_bug_tracker.h"
#include "quic/platform/api/quic_containers.h"
#include "quic/platform/api/quic_test.h"
#include "quic/qbone/platform/mock_kernel.h"
#include "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([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) {
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 = 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);
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 = 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_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_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::kAdd, QboneConstants::kQboneRouteTableId, subnet,
RT_SCOPE_LINK, preferred_ip, egress_interface_index));
}
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));
}
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));
}
} // namespace
} // namespace quic