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