blob: 53e5b3c14c2313dfe40816546d6bece6af785321 [file] [log] [blame]
// Copyright (c) 2020 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/bonnet/tun_device_controller.h"
#include <linux/if_addr.h>
#include <linux/rtnetlink.h>
#include "absl/strings/string_view.h"
#include "quic/platform/api/quic_test.h"
#include "quic/qbone/platform/mock_netlink.h"
#include "quic/qbone/qbone_constants.h"
ABSL_DECLARE_FLAG(bool, qbone_tun_device_replace_default_routing_rules);
namespace quic {
namespace {
using ::testing::Eq;
constexpr int kIfindex = 42;
constexpr char kIfname[] = "qbone0";
const IpRange kIpRange = []() {
IpRange range;
QCHECK(range.FromString("2604:31c0:2::/64"));
return range;
}();
constexpr char kOldAddress[] = "1.2.3.4";
constexpr int kOldPrefixLen = 24;
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;
MATCHER_P(IpRangeEq, range,
absl::StrCat("expected IpRange to equal ", range.ToString())) {
return arg == range;
}
class TunDeviceControllerTest : public QuicTest {
public:
TunDeviceControllerTest()
: controller_(kIfname, true, &netlink_),
link_local_range_(*QboneConstants::TerminatorLocalAddressRange()) {
controller_.RegisterAddressUpdateCallback(
[this](QuicIpAddress address) { notified_address_ = address; });
}
protected:
void ExpectLinkInfo(const std::string& interface_name, int ifindex) {
EXPECT_CALL(netlink_, GetLinkInfo(interface_name, _))
.WillOnce(
Invoke([ifindex](absl::string_view ifname,
NetlinkInterface::LinkInfo* link_info) {
link_info->index = ifindex;
return true;
}));
}
MockNetlink netlink_;
TunDeviceController controller_;
QuicIpAddress notified_address_;
IpRange link_local_range_;
};
TEST_F(TunDeviceControllerTest, AddressAppliedWhenNoneExisted) {
ExpectLinkInfo(kIfname, kIfindex);
EXPECT_CALL(netlink_, GetAddresses(kIfindex, _, _, _)).WillOnce(Return(true));
EXPECT_CALL(netlink_,
ChangeLocalAddress(
kIfindex, NetlinkInterface::Verb::kAdd,
kIpRange.FirstAddressInRange(), kIpRange.prefix_length(),
IFA_F_PERMANENT | IFA_F_NODAD, RT_SCOPE_LINK, _))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateAddress(kIpRange));
EXPECT_THAT(notified_address_, Eq(kIpRange.FirstAddressInRange()));
}
TEST_F(TunDeviceControllerTest, OldAddressesAreRemoved) {
ExpectLinkInfo(kIfname, kIfindex);
EXPECT_CALL(netlink_, GetAddresses(kIfindex, _, _, _))
.WillOnce(
Invoke([](int interface_index, uint8_t unwanted_flags,
std::vector<NetlinkInterface::AddressInfo>* addresses,
int* num_ipv6_nodad_dadfailed_addresses) {
NetlinkInterface::AddressInfo info{};
info.interface_address.FromString(kOldAddress);
info.prefix_length = kOldPrefixLen;
addresses->emplace_back(info);
return true;
}));
QuicIpAddress old_address;
old_address.FromString(kOldAddress);
EXPECT_CALL(netlink_, ChangeLocalAddress(
kIfindex, NetlinkInterface::Verb::kRemove,
old_address, kOldPrefixLen, _, _, _))
.WillOnce(Return(true));
EXPECT_CALL(netlink_,
ChangeLocalAddress(
kIfindex, NetlinkInterface::Verb::kAdd,
kIpRange.FirstAddressInRange(), kIpRange.prefix_length(),
IFA_F_PERMANENT | IFA_F_NODAD, RT_SCOPE_LINK, _))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateAddress(kIpRange));
EXPECT_THAT(notified_address_, Eq(kIpRange.FirstAddressInRange()));
}
TEST_F(TunDeviceControllerTest, UpdateRoutesRemovedOldRoutes) {
ExpectLinkInfo(kIfname, kIfindex);
const int num_matching_routes = 3;
EXPECT_CALL(netlink_, GetRouteInfo(_))
.WillOnce(Invoke(
[](std::vector<NetlinkInterface::RoutingRule>* routing_rules) {
NetlinkInterface::RoutingRule non_matching_route;
non_matching_route.table = QboneConstants::kQboneRouteTableId;
non_matching_route.out_interface = kIfindex + 1;
routing_rules->push_back(non_matching_route);
NetlinkInterface::RoutingRule matching_route;
matching_route.table = QboneConstants::kQboneRouteTableId;
matching_route.out_interface = kIfindex;
for (int i = 0; i < num_matching_routes; i++) {
routing_rules->push_back(matching_route);
}
NetlinkInterface::RoutingRule non_matching_table;
non_matching_table.table =
QboneConstants::kQboneRouteTableId + 1;
non_matching_table.out_interface = kIfindex;
routing_rules->push_back(non_matching_table);
return true;
}));
EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kRemove,
QboneConstants::kQboneRouteTableId, _,
_, _, kIfindex))
.Times(num_matching_routes)
.WillRepeatedly(Return(true));
EXPECT_CALL(netlink_, GetRuleInfo(_)).WillOnce(Return(true));
EXPECT_CALL(netlink_, ChangeRule(NetlinkInterface::Verb::kAdd,
QboneConstants::kQboneRouteTableId,
IpRangeEq(kIpRange)))
.WillOnce(Return(true));
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
IpRangeEq(link_local_range_), _, _, kIfindex))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {}));
}
TEST_F(TunDeviceControllerTest, UpdateRoutesAddsNewRoutes) {
ExpectLinkInfo(kIfname, kIfindex);
EXPECT_CALL(netlink_, GetRouteInfo(_)).WillOnce(Return(true));
EXPECT_CALL(netlink_, GetRuleInfo(_)).WillOnce(Return(true));
EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
IpRangeEq(kIpRange), _, _, kIfindex))
.Times(2)
.WillRepeatedly(Return(true))
.RetiresOnSaturation();
EXPECT_CALL(netlink_, ChangeRule(NetlinkInterface::Verb::kAdd,
QboneConstants::kQboneRouteTableId,
IpRangeEq(kIpRange)))
.WillOnce(Return(true));
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
IpRangeEq(link_local_range_), _, _, kIfindex))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {kIpRange, kIpRange}));
}
TEST_F(TunDeviceControllerTest, EmptyUpdateRouteKeepsLinkLocalRoute) {
ExpectLinkInfo(kIfname, kIfindex);
EXPECT_CALL(netlink_, GetRouteInfo(_)).WillOnce(Return(true));
EXPECT_CALL(netlink_, GetRuleInfo(_)).WillOnce(Return(true));
EXPECT_CALL(netlink_, ChangeRule(NetlinkInterface::Verb::kAdd,
QboneConstants::kQboneRouteTableId,
IpRangeEq(kIpRange)))
.WillOnce(Return(true));
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
IpRangeEq(link_local_range_), _, _, kIfindex))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {}));
}
TEST_F(TunDeviceControllerTest, DisablingRoutingRulesSkipsRuleCreation) {
absl::SetFlag(&FLAGS_qbone_tun_device_replace_default_routing_rules, false);
ExpectLinkInfo(kIfname, kIfindex);
EXPECT_CALL(netlink_, GetRouteInfo(_)).WillOnce(Return(true));
EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
IpRangeEq(kIpRange), _, _, kIfindex))
.Times(2)
.WillRepeatedly(Return(true))
.RetiresOnSaturation();
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
IpRangeEq(link_local_range_), _, _, kIfindex))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {kIpRange, kIpRange}));
}
class DisabledTunDeviceControllerTest : public QuicTest {
public:
DisabledTunDeviceControllerTest()
: controller_(kIfname, false, &netlink_),
link_local_range_(
*QboneConstants::TerminatorLocalAddressRange()) {}
StrictMock<MockNetlink> netlink_;
TunDeviceController controller_;
IpRange link_local_range_;
};
TEST_F(DisabledTunDeviceControllerTest, UpdateRoutesIsNop) {
EXPECT_THAT(controller_.UpdateRoutes(kIpRange, {}), Eq(true));
}
TEST_F(DisabledTunDeviceControllerTest, UpdateAddressIsNop) {
EXPECT_THAT(controller_.UpdateAddress(kIpRange), Eq(true));
}
} // namespace
} // namespace quic