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