| // 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 "quiche/quic/qbone/bonnet/tun_device_controller.h" |
| |
| #include <linux/if_addr.h> |
| #include <linux/rtnetlink.h> |
| |
| #include "absl/strings/string_view.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/qbone/platform/mock_netlink.h" |
| #include "quiche/quic/qbone/qbone_constants.h" |
| |
| ABSL_DECLARE_FLAG(bool, qbone_tun_device_replace_default_routing_rules); |
| ABSL_DECLARE_FLAG(int, qbone_route_init_cwnd); |
| |
| namespace quic::test { |
| 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; |
| matching_route.init_cwnd = NetlinkInterface::kUnspecifiedInitCwnd; |
| 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, |
| NetlinkInterface::kUnspecifiedInitCwnd)) |
| .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)); |
| |
| absl::SetFlag(&FLAGS_qbone_route_init_cwnd, 32); |
| EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kReplace, |
| QboneConstants::kQboneRouteTableId, |
| IpRangeEq(kIpRange), _, _, kIfindex, |
| absl::GetFlag(FLAGS_qbone_route_init_cwnd))) |
| .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::test |