|  | // 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/rtnetlink.h> | 
|  |  | 
|  | #include "absl/time/clock.h" | 
|  | #include "quic/platform/api/quic_logging.h" | 
|  | #include "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; | 
|  |  | 
|  | for (const auto& cb : address_update_cbs_) { | 
|  | cb(current_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::UpdateRoutesWithRetries( | 
|  | const IpRange& desired_range, | 
|  | const std::vector<IpRange>& desired_routes, | 
|  | int retries) { | 
|  | while (retries-- > 0) { | 
|  | if (UpdateRoutes(desired_range, desired_routes)) { | 
|  | return true; | 
|  | } | 
|  | absl::SleepFor(absl::Milliseconds(100)); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | 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_; | 
|  | } | 
|  |  | 
|  | void TunDeviceController::RegisterAddressUpdateCallback( | 
|  | const std::function<void(QuicIpAddress)>& cb) { | 
|  | address_update_cbs_.push_back(cb); | 
|  | } | 
|  |  | 
|  | }  // namespace quic |