| // 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 "quiche/quic/qbone/bonnet/tun_device_controller.h" |
| |
| #include <linux/rtnetlink.h> |
| |
| #include "absl/time/clock.h" |
| #include "quiche/quic/platform/api/quic_logging.h" |
| #include "quiche/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."); |
| |
| ABSL_FLAG(int, qbone_route_init_cwnd, |
| quic::NetlinkInterface::kUnspecifiedInitCwnd, |
| "If non-zero, will add initcwnd to QBONE routing rules. Setting " |
| "a value below 10 is dangerous and not recommended."); |
| |
| 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, |
| rule.init_cwnd)) { |
| 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, |
| absl::GetFlag(FLAGS_qbone_route_init_cwnd))) { |
| 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 |