blob: 2ca59ba35506ece86b0fbdc2b3c5d15c87e65e40 [file]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/network/network_applier.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <chromeos/net-base/ip_address.h>
#include <chromeos/net-base/ipv4_address.h>
#include <chromeos/net-base/network_priority.h>
#include <chromeos/net-base/proc_fs_stub.h>
#include "patchpanel/network/address_service.h"
#include "patchpanel/network/routing_policy_service.h"
#include "patchpanel/network/routing_table.h"
#include "patchpanel/network/routing_table_entry.h"
#include "patchpanel/routing_service.h"
namespace patchpanel {
namespace {
// TODO(b/161507671) Use the constants defined in patchpanel::RoutingService at
// platform2/patchpanel/routing_service.cc.
constexpr const uint32_t kFwmarkRoutingMask = 0xffff0000;
// kCrosVmFwmark = {.value = 0x2100, .mask = 0x3f00} should be the preferred
// method to match traffic from crosvm. This is a workaround before b/300033608
// is fixed.
// From patchpanel/address_manager.cc:
// 100.115.92.24 - 100.115.92.127 for CrosVM;
// 100.115.92.192 - 100.115.92.255 for Crostini containers.
const auto kCrosVmSrcIP = {*net_base::IPv4CIDR::CreateFromAddressAndPrefix(
net_base::IPv4Address(100, 115, 92, 24), 29),
*net_base::IPv4CIDR::CreateFromAddressAndPrefix(
net_base::IPv4Address(100, 115, 92, 32), 27),
*net_base::IPv4CIDR::CreateFromAddressAndPrefix(
net_base::IPv4Address(100, 115, 92, 64), 26),
*net_base::IPv4CIDR::CreateFromAddressAndPrefix(
net_base::IPv4Address(100, 115, 92, 192), 26)};
RoutingPolicyEntry::FwMark GetFwmarkRoutingTag(int interface_index) {
return {.value = RoutingTable::GetInterfaceTableId(interface_index) << 16,
.mask = kFwmarkRoutingMask};
}
// The routing rule priority used for the default service, whether physical or
// VPN.
constexpr uint32_t kDefaultPriority = 10;
// Space between the priorities of services. The Nth highest priority service
// (starting from N=0) will have a rule priority of
// |kDefaultPriority| + N*|kPriorityStep|.
constexpr uint32_t kPriorityStep = 10;
// An offset added to the priority of non-VPN services, so their rules comes
// after the main table rule.
constexpr uint32_t kPhysicalPriorityOffset = 1000;
// Priority for rules corresponding to IPConfig::Properties::routes.
// Allowed dsts rules are added right before the catchall rule. In this way,
// existing traffic from a different interface will not be "stolen" by these
// rules and sent out of the wrong interface, but the routes added to
// |table_id| will not be ignored.
constexpr uint32_t kDstRulePriority =
RoutingPolicyService::kRulePriorityMain - 5;
// Priority for rules routing traffic from certain VMs through CLAT.
constexpr uint32_t kClatRulePriority =
RoutingPolicyService::kRulePriorityMain - 4;
// Priority for rules routing traffic with BYPASS_VPN mark to the default
// physical network.
constexpr uint32_t kBypassVpnRulePriority =
RoutingPolicyService::kRulePriorityMain - 3;
// Priority for VPN rules routing traffic or specific uids with the routing
// table of a VPN connection.
constexpr uint32_t kVpnUidRulePriority =
RoutingPolicyService::kRulePriorityMain - 2;
// Priority for the rule sending any remaining traffic to the default physical
// interface.
constexpr uint32_t kCatchallPriority =
RoutingPolicyService::kRulePriorityMain - 1;
} // namespace
NetworkApplier::NetworkApplier()
: rule_table_(std::make_unique<RoutingPolicyService>()),
routing_table_(std::make_unique<RoutingTable>()),
address_service_(std::make_unique<AddressService>(routing_table_.get())),
rtnl_handler_(net_base::RTNLHandler::GetInstance()),
proc_fs_(std::make_unique<net_base::ProcFsStub>("")) {}
NetworkApplier::~NetworkApplier() = default;
// static
NetworkApplier* NetworkApplier::GetInstance() {
static base::NoDestructor<NetworkApplier> instance;
return instance.get();
}
// static
std::unique_ptr<NetworkApplier> NetworkApplier::CreateForTesting(
std::unique_ptr<RoutingTable> routing_table,
std::unique_ptr<RoutingPolicyService> rule_table,
std::unique_ptr<AddressService> address_service,
net_base::RTNLHandler* rtnl_handler,
std::unique_ptr<net_base::ProcFsStub> proc_fs) {
// Using `new` to access a non-public constructor.
auto ptr = base::WrapUnique(new NetworkApplier());
ptr->routing_table_ = std::move(routing_table);
ptr->rule_table_ = std::move(rule_table);
ptr->address_service_ = std::move(address_service);
ptr->rtnl_handler_ = rtnl_handler;
ptr->proc_fs_ = std::move(proc_fs);
return ptr;
}
void NetworkApplier::Start() {
routing_table_->Start();
}
void NetworkApplier::Clear(int interface_index) {
rule_table_->FlushRules(interface_index);
routing_table_->FlushRoutes(interface_index);
routing_table_->FlushRoutesWithTag(interface_index,
net_base::IPFamily::kIPv4);
routing_table_->FlushRoutesWithTag(interface_index,
net_base::IPFamily::kIPv6);
address_service_->FlushAddress(interface_index);
proc_fs_->FlushRoutingCache();
rtnl_handler_->SetInterfaceMTU(interface_index,
net_base::NetworkConfig::kDefaultMTU);
}
void NetworkApplier::ApplyDNS(
net_base::NetworkPriority priority,
const std::vector<net_base::IPAddress>& dns_servers,
const std::vector<std::string>& dns_search_domains) {
// TODO(b/259354228): Notify dnsproxy when DNS changes. Note that currently
// dnsproxy is getting the information from itself subscribing to patchpanel
// Device/Service event API instead.
}
void NetworkApplier::ApplyRoutingPolicy(
int interface_index,
const std::string& interface_name,
Technology technology,
net_base::NetworkPriority priority,
const std::vector<net_base::IPCIDR>& all_addresses,
const std::vector<net_base::IPv4CIDR>& rfc3442_dsts) {
uint32_t rule_priority =
kDefaultPriority + priority.ranking_order * kPriorityStep;
uint32_t table_id = RoutingTable::GetInterfaceTableId(interface_index);
bool is_primary_physical = priority.is_primary_physical;
rule_table_->FlushRules(interface_index);
// Add rules just before the default rule to route to the VPN interface for
// certain traffic. These rules are necessary for consistency between source
// IP address selection algorithm that ignores iptables fwmark tagging rules,
// and the actual routing of packets that have been tagged in iptables
// PREROUTING or OUTPUT.
if (technology == Technology::kVPN) {
// b/177620923 Add uid rules any untagged traffic owner by a uid routed
// through VPN connections.
for (const auto& uid : rule_table_->GetUserTrafficUids()) {
for (const auto family : net_base::kIPFamilies) {
auto entry = RoutingPolicyEntry(family);
entry.priority = kVpnUidRulePriority;
entry.table = table_id;
entry.uid_range = uid.second;
rule_table_->AddRule(interface_index, entry);
}
}
// Add rules for packets already tagged with ROUTE_ON_VPN.
for (const auto family : net_base::kIPFamilies) {
auto entry = RoutingPolicyEntry(family);
entry.priority = kVpnUidRulePriority;
entry.table = table_id;
entry.fw_mark = RoutingPolicyEntry::FwMark{
.value = kFwmarkRouteOnVpn.fwmark,
.mask = kFwmarkVpnMask.fwmark,
};
rule_table_->AddRule(interface_index, entry);
}
}
if (is_primary_physical) {
// Main routing table contains kernel-added routes for source address
// selection. Sending traffic there before all other rules for physical
// interfaces (but after any VPN rules) ensures that physical interface
// rules are not inadvertently too aggressive. Since this rule is static,
// add it as interface index -1 so it never get removed by FlushRules().
// Note that this rule could be added multiple times when default network
// changes, but since the rule itself is identical, there will only be one
// instance added into kernel.
for (const auto family : net_base::kIPFamilies) {
auto main_table_rule = RoutingPolicyEntry(family);
main_table_rule.priority = kPhysicalPriorityOffset;
main_table_rule.table = RT_TABLE_MAIN;
rule_table_->AddRule(-1, main_table_rule);
}
// Add a default routing rule to use the primary interface if there is
// nothing better.
// TODO(crbug.com/999589) Remove this rule.
for (const auto family : net_base::kIPFamilies) {
auto catch_all_rule = RoutingPolicyEntry(family);
catch_all_rule.priority = kCatchallPriority;
catch_all_rule.table = table_id;
rule_table_->AddRule(interface_index, catch_all_rule);
}
// Add a rule right before the VPN uid rules to match packets with
// BYPASS_VPN mark and point them to the default table. Similar to the VPN
// uid rules, the main purpose of this rule is for src ip selection,
// otherwise a packet from a user socket with BYPASS_VPN will be matched by
// the VPN uid rules. This rule has to have a lower priority than the rules
// for routing tags.
for (const auto family : net_base::kIPFamilies) {
auto rule = RoutingPolicyEntry(family);
rule.priority = kBypassVpnRulePriority;
rule.table = table_id;
rule.fw_mark = RoutingPolicyEntry::FwMark{
.value = kFwmarkBypassVpn.fwmark,
.mask = kFwmarkVpnMask.fwmark,
};
rule_table_->AddRule(interface_index, rule);
}
}
if (priority.is_primary_logical) {
// Add a routing rule for IPv4 traffic to look up CLAT table first before it
// get to catch-all rule.
for (const auto& src : kCrosVmSrcIP) {
auto clat_table_rule = RoutingPolicyEntry(net_base::IPFamily::kIPv4);
clat_table_rule.priority = kClatRulePriority;
clat_table_rule.table = RoutingTable::kClatRoutingTableId;
clat_table_rule.src = net_base::IPCIDR(src);
rule_table_->AddRule(-1, clat_table_rule);
}
}
if (technology != Technology::kVPN) {
rule_priority += kPhysicalPriorityOffset;
}
// Allow for traffic corresponding to this Connection to match with
// |table_id|. Note that this does *not* necessarily imply that the traffic
// will actually be routed through a route in |table_id|. For example, if the
// traffic matches one of the excluded destination addresses set up in
// SetupExcludedRoutes, then no routes in the per-Device table for this
// Connection will be used for that traffic.
for (const auto& dst_address : rfc3442_dsts) {
auto dst_addr_rule = RoutingPolicyEntry(net_base::IPFamily::kIPv4);
dst_addr_rule.dst = net_base::IPCIDR(dst_address);
dst_addr_rule.priority = kDstRulePriority;
dst_addr_rule.table = table_id;
rule_table_->AddRule(interface_index, dst_addr_rule);
}
// b/180521518: Add an explicit rule to block user IPv6 traffic for a Cellular
// connection that is not the primary physical connection. This prevents
// Chrome from accidentally using the Cellular network and causing data
// charges with IPv6 traffic when the primary physical connection is IPv4
// only.
bool chronos_no_ipv6 =
technology == Technology::kCellular && !is_primary_physical;
if (chronos_no_ipv6) {
auto chrome_uid = rule_table_->GetChromeUid();
for (const auto& address : all_addresses) {
if (address.GetFamily() != net_base::IPFamily::kIPv6) {
continue;
}
auto blackhole_chronos_ipv6_rule =
RoutingPolicyEntry(net_base::IPFamily::kIPv6);
blackhole_chronos_ipv6_rule.priority = rule_priority - 1;
blackhole_chronos_ipv6_rule.src = address;
blackhole_chronos_ipv6_rule.table = RoutingTable::kUnreachableTableId;
blackhole_chronos_ipv6_rule.uid_range = chrome_uid;
rule_table_->AddRule(interface_index, blackhole_chronos_ipv6_rule);
}
}
// Always set a rule for matching traffic tagged with the fwmark routing tag
// corresponding to this network interface.
for (const auto family : net_base::kIPFamilies) {
auto fwmark_routing_entry = RoutingPolicyEntry(family);
fwmark_routing_entry.priority = rule_priority;
fwmark_routing_entry.table = table_id;
fwmark_routing_entry.fw_mark = GetFwmarkRoutingTag(interface_index);
rule_table_->AddRule(interface_index, fwmark_routing_entry);
}
// Add output interface rule for all interfaces, such that SO_BINDTODEVICE can
// be used without explicitly binding the socket.
for (const auto family : net_base::kIPFamilies) {
auto oif_rule = RoutingPolicyEntry(family);
oif_rule.priority = rule_priority;
oif_rule.table = table_id;
oif_rule.oif_name = interface_name;
rule_table_->AddRule(interface_index, oif_rule);
}
if (technology != Technology::kVPN) {
// Select the per-device table if the outgoing packet's src address matches
// the interface's addresses, dst address is in the interface's prefix, or
// the input interface is this interface.
for (const auto& address : all_addresses) {
auto if_addr_rule = RoutingPolicyEntry(address.GetFamily());
if_addr_rule.src = *net_base::IPCIDR::CreateFromAddressAndPrefix(
address.address(),
net_base::IPCIDR::GetMaxPrefixLength(address.GetFamily()));
if_addr_rule.table = table_id;
if_addr_rule.priority = rule_priority;
rule_table_->AddRule(interface_index, if_addr_rule);
if_addr_rule = RoutingPolicyEntry(address.GetFamily());
if_addr_rule.dst = address;
if_addr_rule.table = table_id;
if_addr_rule.priority = rule_priority;
rule_table_->AddRule(interface_index, if_addr_rule);
}
for (const auto family : net_base::kIPFamilies) {
auto iif_rule = RoutingPolicyEntry(family);
iif_rule.priority = rule_priority;
iif_rule.table = table_id;
iif_rule.iif_name = interface_name;
rule_table_->AddRule(interface_index, iif_rule);
}
}
proc_fs_->FlushRoutingCache();
}
void NetworkApplier::ApplyMTU(int interface_index, int mtu) {
rtnl_handler_->SetInterfaceMTU(interface_index, static_cast<uint32_t>(mtu));
}
void NetworkApplier::ApplyRoute(
int interface_index,
net_base::IPFamily family,
const std::optional<net_base::IPAddress>& gateway,
bool fix_gateway_reachability,
bool default_route,
bool blackhole_ipv6,
const std::vector<net_base::IPCIDR>& excluded_routes,
const std::vector<net_base::IPCIDR>& included_routes,
const std::vector<std::pair<net_base::IPv4CIDR, net_base::IPv4Address>>&
rfc3442_routes) {
if (gateway && gateway->GetFamily() != family) {
LOG(DFATAL) << "Gateway address [" << *gateway << "] unmatched with family "
<< family;
return;
}
const uint32_t table_id = RoutingTable::GetInterfaceTableId(interface_index);
auto empty_ip = net_base::IPCIDR(family);
// 0. Flush existing routes set by patchpanel.
routing_table_->FlushRoutesWithTag(interface_index, family);
// 1. Fix gateway reachability (add an on-link /32 route to the gateway) if
// the gateway is not currently on-link. Note this only applies for IPv4 as
// IPv6 uses the link local address for gateway.
if (fix_gateway_reachability) {
CHECK(gateway);
CHECK(gateway->GetFamily() == net_base::IPFamily::kIPv4);
auto entry = RoutingTableEntry(net_base::IPFamily::kIPv4);
entry.dst = *net_base::IPCIDR::CreateFromAddressAndPrefix(*gateway, 32);
entry.scope = RT_SCOPE_LINK;
entry.table = table_id;
entry.type = RTN_UNICAST;
entry.tag = interface_index;
if (!routing_table_->AddRoute(interface_index, entry)) {
LOG(ERROR) << "Unable to add link-scoped route to gateway " << entry
<< ", if " << interface_index;
}
}
// 2. Default route
if (default_route) {
if (!routing_table_->SetDefaultRoute(
interface_index, gateway.value_or(empty_ip.address()), table_id)) {
LOG(ERROR) << "Unable to add default route via "
<< (gateway ? gateway->ToString() : "onlink") << ", if "
<< interface_index;
}
}
// 3. Excluded Routes
// Since each Network has its own dedicated routing table, exclusion is as
// simple as adding an RTN_THROW entry for each item on the list. Traffic that
// matches the RTN_THROW entry will cause the kernel to stop traversing our
// routing table and try the next rule in the list.
bool has_ipv6_default_excluded_prefix = false;
for (const auto& excluded_prefix : excluded_routes) {
if (excluded_prefix.GetFamily() != family) {
continue;
}
if (excluded_prefix.prefix_length() == 0 &&
family == net_base::IPFamily::kIPv6) {
has_ipv6_default_excluded_prefix = true;
}
auto entry = RoutingTableEntry(family);
entry.scope = RT_SCOPE_LINK;
entry.table = table_id;
entry.type = RTN_THROW;
entry.tag = interface_index;
entry.dst = excluded_prefix;
if (!routing_table_->AddRoute(interface_index, entry)) {
LOG(WARNING) << "Unable to setup excluded route " << entry << ", if "
<< interface_index;
}
}
// 4. Included Routes and IPv6 Blackhole Routes
for (const auto& included_prefix : included_routes) {
if (included_prefix.GetFamily() != family) {
continue;
}
auto entry = RoutingTableEntry(family);
entry.dst = included_prefix;
if (gateway) {
entry.gateway = *gateway;
}
entry.table = table_id;
entry.tag = interface_index;
if (family == net_base::IPFamily::kIPv6 && blackhole_ipv6) {
entry.type = RTN_BLACKHOLE;
}
if (!routing_table_->AddRoute(interface_index, entry)) {
LOG(WARNING) << "Unable to setup included route " << entry << ", if "
<< interface_index;
}
}
if (family == net_base::IPFamily::kIPv6 && blackhole_ipv6 &&
!has_ipv6_default_excluded_prefix) {
if (!routing_table_->CreateBlackholeRoute(
interface_index, net_base::IPFamily::kIPv6, 0, table_id)) {
LOG(ERROR) << "Unable to add IPv6 blackhole route, if "
<< interface_index;
}
}
// 5. RFC 3442 Static Classless Routes from DHCPv4
for (const auto& [route_prefix, route_gateway] : rfc3442_routes) {
auto entry = RoutingTableEntry(net_base::IPFamily::kIPv4);
entry.dst = net_base::IPCIDR(route_prefix);
entry.gateway = net_base::IPAddress(route_gateway);
entry.table = table_id;
entry.tag = interface_index;
if (!routing_table_->AddRoute(interface_index, entry)) {
LOG(WARNING) << "Unable to setup static classless route " << entry
<< ", if " << interface_index;
}
}
}
void NetworkApplier::ApplyNetworkConfig(
int interface_index,
const std::string& interface_name,
Area area,
const net_base::NetworkConfig& network_config,
net_base::NetworkPriority priority,
Technology technology) {
if (area & Area::kClear) {
Clear(interface_index);
}
if (area & Area::kIPv4Address) {
if (network_config.ipv4_address) {
address_service_->SetIPv4Address(interface_index,
*network_config.ipv4_address,
network_config.ipv4_broadcast);
} else {
address_service_->ClearIPv4Address(interface_index);
}
}
if (area & Area::kIPv4Route) {
bool default_route = (area & Area::kIPv4DefaultRoute) &&
network_config.included_route_prefixes.empty();
// Check if an IPv4 gateway is on-link, and add a /32 on-link route to the
// gateway if not. Note that IPv6 uses link local address for gateway so
// this is not needed.
bool fix_gateway_reachability =
network_config.ipv4_gateway && network_config.ipv4_address &&
!network_config.ipv4_address->InSameSubnetWith(
*network_config.ipv4_gateway);
if (fix_gateway_reachability) {
LOG(WARNING)
<< interface_name << ": Gateway " << *network_config.ipv4_gateway
<< " is unreachable from local address/prefix "
<< *network_config.ipv4_address
<< ", mitigating this by creating a link route to the gateway.";
}
std::optional<net_base::IPAddress> gateway = std::nullopt;
if (network_config.ipv4_gateway) {
gateway = net_base::IPAddress(*network_config.ipv4_gateway);
}
ApplyRoute(interface_index, net_base::IPFamily::kIPv4, gateway,
fix_gateway_reachability, default_route,
/*blackhole_ipv6=*/false, network_config.excluded_route_prefixes,
network_config.included_route_prefixes,
network_config.rfc3442_routes);
}
if (area & Area::kIPv6Address) {
address_service_->SetIPv6Addresses(interface_index,
network_config.ipv6_addresses);
}
if (area & Area::kIPv6Route) {
bool default_route = (area & Area::kIPv6DefaultRoute) &&
network_config.included_route_prefixes.empty() &&
!network_config.ipv6_blackhole_route;
std::optional<net_base::IPAddress> gateway = std::nullopt;
if (network_config.ipv6_gateway) {
gateway = net_base::IPAddress(*network_config.ipv6_gateway);
}
ApplyRoute(interface_index, net_base::IPFamily::kIPv6, gateway,
/*fix_gateway_reachability=*/false, default_route,
network_config.ipv6_blackhole_route,
network_config.excluded_route_prefixes,
network_config.included_route_prefixes, {});
}
if (area & Area::kRoutingPolicy) {
std::vector<net_base::IPCIDR> all_addresses;
if (network_config.ipv4_address) {
all_addresses.emplace_back(*network_config.ipv4_address);
}
for (const auto& item : network_config.ipv6_addresses) {
all_addresses.emplace_back(item);
}
std::vector<net_base::IPv4CIDR> rfc3442_dsts;
for (const auto& item : network_config.rfc3442_routes) {
rfc3442_dsts.push_back(item.first);
}
ApplyRoutingPolicy(interface_index, interface_name, technology, priority,
all_addresses, rfc3442_dsts);
}
if (area & Area::kDNS) {
ApplyDNS(priority, network_config.dns_servers,
network_config.dns_search_domains);
}
if (area & Area::kMTU) {
ApplyMTU(interface_index,
network_config.mtu.value_or(net_base::NetworkConfig::kDefaultMTU));
}
}
} // namespace patchpanel