| // 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()) {} | |
| 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_; | |
| 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)); | |
| } | |
| 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)); | |
| } | |
| 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 |