Move QBONE's TunDeviceController to third_party. PiperOrigin-RevId: 344067044 Change-Id: I3db29b202401eb0d8a8ec4e2f2c2a01fa0e85b9c
diff --git a/quic/qbone/bonnet/mock_tun_device_controller.h b/quic/qbone/bonnet/mock_tun_device_controller.h new file mode 100644 index 0000000..2d097ca --- /dev/null +++ b/quic/qbone/bonnet/mock_tun_device_controller.h
@@ -0,0 +1,27 @@ +// 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. + +#ifndef QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_CONTROLLER_H_ +#define QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_CONTROLLER_H_ + +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.h" + +namespace quic { + +class MockTunDeviceController : public TunDeviceController { + public: + MockTunDeviceController() : TunDeviceController("", true, nullptr) {} + + MOCK_METHOD(bool, UpdateAddress, (const IpRange&), (override)); + + MOCK_METHOD(bool, UpdateRoutes, (const IpRange&, const std::vector<IpRange>&), + (override)); + + MOCK_METHOD(QuicIpAddress, current_address, (), (override)); +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_CONTROLLER_H_
diff --git a/quic/qbone/bonnet/tun_device_controller.cc b/quic/qbone/bonnet/tun_device_controller.cc new file mode 100644 index 0000000..255832c --- /dev/null +++ b/quic/qbone/bonnet/tun_device_controller.cc
@@ -0,0 +1,151 @@ +// 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 "net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.h" + +#include <linux/rtnetlink.h> + +#include "absl/time/clock.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" +#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h" + +ABSL_FLAG(bool, qbone_tun_device_replace_default_routing_rules, true, + "If true, will define a rule that points packets sourced from the " + "qbone interface to the qbone table. This is unnecessary in " + "environments with no other ipv6 route."); + +namespace quic { + +bool TunDeviceController::UpdateAddress(const IpRange& desired_range) { + if (!setup_tun_) { + return true; + } + + NetlinkInterface::LinkInfo link_info{}; + if (!netlink_->GetLinkInfo(ifname_, &link_info)) { + return false; + } + + std::vector<NetlinkInterface::AddressInfo> addresses; + if (!netlink_->GetAddresses(link_info.index, 0, &addresses, nullptr)) { + return false; + } + + QuicIpAddress desired_address = desired_range.FirstAddressInRange(); + + for (const auto& address : addresses) { + if (!netlink_->ChangeLocalAddress( + link_info.index, NetlinkInterface::Verb::kRemove, + address.interface_address, address.prefix_length, 0, 0, {})) { + return false; + } + } + + bool address_updated = netlink_->ChangeLocalAddress( + link_info.index, NetlinkInterface::Verb::kAdd, desired_address, + desired_range.prefix_length(), IFA_F_PERMANENT | IFA_F_NODAD, + RT_SCOPE_LINK, {}); + + if (address_updated) { + current_address_ = desired_address; + } + + return address_updated; +} + +bool TunDeviceController::UpdateRoutes( + const IpRange& desired_range, + const std::vector<IpRange>& desired_routes) { + if (!setup_tun_) { + return true; + } + + NetlinkInterface::LinkInfo link_info{}; + if (!netlink_->GetLinkInfo(ifname_, &link_info)) { + QUIC_LOG(ERROR) << "Could not get link info for interface <" << ifname_ + << ">"; + return false; + } + + std::vector<NetlinkInterface::RoutingRule> routing_rules; + if (!netlink_->GetRouteInfo(&routing_rules)) { + QUIC_LOG(ERROR) << "Unable to get route info"; + return false; + } + + for (const auto& rule : routing_rules) { + if (rule.out_interface == link_info.index && + rule.table == QboneConstants::kQboneRouteTableId) { + if (!netlink_->ChangeRoute(NetlinkInterface::Verb::kRemove, + rule.table, rule.destination_subnet, + rule.scope, rule.preferred_source, + rule.out_interface)) { + QUIC_LOG(ERROR) << "Unable to remove old route to <" + << rule.destination_subnet.ToString() << ">"; + return false; + } + } + } + + if (!UpdateRules(desired_range)) { + return false; + } + + QuicIpAddress desired_address = desired_range.FirstAddressInRange(); + + std::vector<IpRange> routes(desired_routes.begin(), desired_routes.end()); + routes.emplace_back(*QboneConstants::TerminatorLocalAddressRange()); + + for (const auto& route : routes) { + if (!netlink_->ChangeRoute(NetlinkInterface::Verb::kReplace, + QboneConstants::kQboneRouteTableId, route, + RT_SCOPE_LINK, desired_address, + link_info.index)) { + QUIC_LOG(ERROR) << "Unable to add route <" << route.ToString() << ">"; + return false; + } + } + + return true; +} + +bool TunDeviceController::UpdateRules(IpRange desired_range) { + if (!absl::GetFlag(FLAGS_qbone_tun_device_replace_default_routing_rules)) { + return true; + } + + std::vector<NetlinkInterface::IpRule> ip_rules; + if (!netlink_->GetRuleInfo(&ip_rules)) { + QUIC_LOG(ERROR) << "Unable to get rule info"; + return false; + } + + for (const auto& rule : ip_rules) { + if (rule.table == QboneConstants::kQboneRouteTableId) { + if (!netlink_->ChangeRule(NetlinkInterface::Verb::kRemove, + rule.table, rule.source_range)) { + QUIC_LOG(ERROR) << "Unable to remove old rule for table <" << rule.table + << "> from source <" << rule.source_range.ToString() + << ">"; + return false; + } + } + } + + if (!netlink_->ChangeRule(NetlinkInterface::Verb::kAdd, + QboneConstants::kQboneRouteTableId, + desired_range)) { + QUIC_LOG(ERROR) << "Unable to add rule for <" << desired_range.ToString() + << ">"; + return false; + } + + return true; +} + +QuicIpAddress TunDeviceController::current_address() { + return current_address_; +} + +} // namespace quic
diff --git a/quic/qbone/bonnet/tun_device_controller.h b/quic/qbone/bonnet/tun_device_controller.h new file mode 100644 index 0000000..0e1662f --- /dev/null +++ b/quic/qbone/bonnet/tun_device_controller.h
@@ -0,0 +1,58 @@ +// 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. + +#ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_CONTROLLER_H_ +#define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_CONTROLLER_H_ + +#include "net/third_party/quiche/src/quic/qbone/bonnet/tun_device.h" +#include "net/third_party/quiche/src/quic/qbone/platform/netlink_interface.h" +#include "net/third_party/quiche/src/quic/qbone/qbone_control.pb.h" +#include "net/third_party/quiche/src/quic/qbone/qbone_control_stream.h" + +namespace quic { + +// TunDeviceController consumes control stream messages from a Qbone server +// and applies the given updates to the TUN device. +class TunDeviceController { + public: + // |ifname| is the interface name of the TUN device to be managed. This does + // not take ownership of |netlink|. + TunDeviceController(std::string ifname, bool setup_tun, + NetlinkInterface* netlink) + : ifname_(std::move(ifname)), setup_tun_(setup_tun), netlink_(netlink) {} + + TunDeviceController(const TunDeviceController&) = delete; + TunDeviceController& operator=(const TunDeviceController&) = delete; + + TunDeviceController(TunDeviceController&&) = delete; + TunDeviceController& operator=(TunDeviceController&&) = delete; + + virtual ~TunDeviceController() = default; + + // Updates the local address of the TUN device to be the first address in the + // given |response.ip_range()|. + virtual bool UpdateAddress(const IpRange& desired_range); + + // Updates the set of routes that the TUN device will provide. All current + // routes for the tunnel that do not exist in the |response| will be removed. + virtual bool UpdateRoutes(const IpRange& desired_range, + const std::vector<IpRange>& desired_routes); + + virtual QuicIpAddress current_address(); + + private: + // Update the IP Rules, this should only be used by UpdateRoutes. + bool UpdateRules(IpRange desired_range); + + const std::string ifname_; + const bool setup_tun_; + + NetlinkInterface* netlink_; + + QuicIpAddress current_address_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_CONTROLLER_H_
diff --git a/quic/qbone/bonnet/tun_device_controller_test.cc b/quic/qbone/bonnet/tun_device_controller_test.cc new file mode 100644 index 0000000..947239a --- /dev/null +++ b/quic/qbone/bonnet/tun_device_controller_test.cc
@@ -0,0 +1,257 @@ +// 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 "net/third_party/quiche/src/quic/qbone/bonnet/tun_device_controller.h" + +#include <linux/if_addr.h> +#include <linux/rtnetlink.h> + +#include "absl/strings/string_view.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_test.h" +#include "net/third_party/quiche/src/quic/qbone/platform/mock_netlink.h" +#include "net/third_party/quiche/src/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
diff --git a/quic/qbone/platform/ip_range.cc b/quic/qbone/platform/ip_range.cc index cb9f4a1..702d081 100644 --- a/quic/qbone/platform/ip_range.cc +++ b/quic/qbone/platform/ip_range.cc
@@ -94,7 +94,7 @@ return true; } -QuicIpAddress IpRange::FirstAddressInRange() { +QuicIpAddress IpRange::FirstAddressInRange() const { return prefix(); }
diff --git a/quic/qbone/platform/ip_range.h b/quic/qbone/platform/ip_range.h index c9f1f34..0cea943 100644 --- a/quic/qbone/platform/ip_range.h +++ b/quic/qbone/platform/ip_range.h
@@ -39,7 +39,7 @@ // Returns the first available IP address in this IpRange. The resulting // address will be uninitialized if there is no available address. - QuicIpAddress FirstAddressInRange(); + QuicIpAddress FirstAddressInRange() const; // The address family of this IpRange. IpAddressFamily address_family() const { return prefix_.address_family(); }