blob: 24022c09dd01b87df19035f0296f439d4e57bc8a [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "shill/connection.h"
#include <arpa/inet.h>
#include <linux/rtnetlink.h>
#include <limits>
#include <utility>
#include <base/bind.h>
#include <base/stl_util.h>
#include <base/strings/stringprintf.h>
#include "shill/control_interface.h"
#include "shill/device_info.h"
#include "shill/logging.h"
#include "shill/net/rtnl_handler.h"
#include "shill/resolver.h"
#include "shill/routing_table.h"
#include "shill/routing_table_entry.h"
using std::string;
using std::vector;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kConnection;
static string ObjectID(const Connection* c) {
if (c == nullptr)
return "(connection)";
return c->interface_name();
}
} // namespace Logging
namespace {
// TODO(b/161507671) Use the constants defined in patchpanel::RoutingService at
// platform2/patchpanel/routing_service.cc after the routing layer is migrated
// to patchpanel.
constexpr const uint32_t kFwmarkRoutingMask = 0xffff0000;
RoutingPolicyEntry::FwMark GetFwmarkRoutingTag(int interface_index) {
return {.value = RoutingTable::GetInterfaceTableId(interface_index) << 16,
.mask = kFwmarkRoutingMask};
}
} // namespace
// static
const uint32_t Connection::kDefaultPriority = 10;
// 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.
const uint32_t Connection::kDstRulePriority =
RoutingTable::kRulePriorityMain - 2;
const uint32_t Connection::kCatchallPriority =
RoutingTable::kRulePriorityMain - 1;
// UINT_MAX is also a valid priority, but we reserve this as a sentinel
// value, as in RoutingTable::GetDefaultRouteInternal.
const uint32_t Connection::kLeastPriority =
std::numeric_limits<uint32_t>::max() - 1;
const uint32_t Connection::kPriorityStep = 10;
Connection::Connection(int interface_index,
const std::string& interface_name,
bool fixed_ip_params,
Technology technology,
const DeviceInfo* device_info,
ControlInterface* control_interface)
: weak_ptr_factory_(this),
use_dns_(false),
priority_(kLeastPriority),
is_primary_physical_(false),
has_broadcast_domain_(false),
interface_index_(interface_index),
interface_name_(interface_name),
technology_(technology),
use_if_addrs_(false),
fixed_ip_params_(fixed_ip_params),
table_id_(RoutingTable::GetInterfaceTableId(interface_index)),
blackhole_table_id_(RT_TABLE_UNSPEC),
local_(IPAddress::kFamilyUnknown),
gateway_(IPAddress::kFamilyUnknown),
device_info_(device_info),
resolver_(Resolver::GetInstance()),
routing_table_(RoutingTable::GetInstance()),
rtnl_handler_(RTNLHandler::GetInstance()),
control_interface_(control_interface) {
SLOG(this, 2) << __func__ << "(" << interface_index << ", " << interface_name
<< ", " << technology << ")";
}
Connection::~Connection() {
SLOG(this, 2) << __func__ << " " << interface_name_;
routing_table_->FlushRoutes(interface_index_);
routing_table_->FlushRoutesWithTag(interface_index_);
if (!fixed_ip_params_) {
device_info_->FlushAddresses(interface_index_);
}
routing_table_->FlushRules(interface_index_);
if (blackhole_table_id_ != RT_TABLE_UNSPEC) {
routing_table_->FreeAdditionalTableId(blackhole_table_id_);
}
}
bool Connection::SetupIncludedRoutes(const IPConfig::Properties& properties) {
bool ret = true;
allowed_dsts_.clear();
IPAddress::Family address_family = properties.address_family;
for (const auto& route : properties.routes) {
SLOG(this, 2) << "Installing route:"
<< " Destination: " << route.host
<< " Prefix: " << route.prefix
<< " Gateway: " << route.gateway;
IPAddress destination_address(address_family);
IPAddress source_address(address_family); // Left as default.
IPAddress gateway_address(address_family);
if (!destination_address.SetAddressFromString(route.host)) {
LOG(ERROR) << "Failed to parse host " << route.host;
ret = false;
continue;
}
if (!gateway_address.SetAddressFromString(route.gateway)) {
LOG(ERROR) << "Failed to parse gateway " << route.gateway;
ret = false;
continue;
}
destination_address.set_prefix(route.prefix);
if (!routing_table_->AddRoute(
interface_index_,
RoutingTableEntry::Create(destination_address, source_address,
gateway_address)
.SetMetric(priority_)
.SetTable(table_id_))) {
ret = false;
}
allowed_dsts_.push_back(destination_address);
}
return ret;
}
bool Connection::SetupExcludedRoutes(const IPConfig::Properties& properties,
const IPAddress& gateway) {
// If this connection 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.
IPAddress empty_ip(properties.address_family);
auto entry = RoutingTableEntry::Create(empty_ip, empty_ip, empty_ip)
.SetScope(RT_SCOPE_LINK)
.SetTable(table_id_)
.SetType(RTN_THROW);
for (const auto& excluded_ip : properties.exclusion_list) {
if (!entry.dst.SetAddressAndPrefixFromString(excluded_ip) ||
!entry.dst.IsValid() ||
!routing_table_->AddRoute(interface_index_, entry)) {
LOG(ERROR) << "Unable to setup route for " << excluded_ip << ".";
return false;
}
}
return true;
}
void Connection::UpdateFromIPConfig(const IPConfigRefPtr& config) {
SLOG(this, 2) << __func__ << " " << interface_name_;
const IPConfig::Properties& properties = config->properties();
allowed_uids_ = properties.allowed_uids;
allowed_iifs_ = properties.allowed_iifs;
included_fwmarks_ = properties.included_fwmarks;
use_if_addrs_ =
properties.use_if_addrs || technology_.IsPrimaryConnectivityTechnology();
IPAddress gateway(properties.address_family);
if (!properties.gateway.empty() &&
!gateway.SetAddressFromString(properties.gateway)) {
LOG(ERROR) << "Gateway address " << properties.gateway << " is invalid";
return;
}
IPAddress local(properties.address_family);
if (!local.SetAddressFromString(properties.address)) {
LOG(ERROR) << "Local address " << properties.address << " is invalid";
return;
}
local.set_prefix(properties.subnet_prefix);
IPAddress broadcast(properties.address_family);
if (properties.broadcast_address.empty()) {
if (local.family() == IPAddress::kFamilyIPv4 &&
properties.peer_address.empty()) {
LOG(WARNING) << "Broadcast address is not set. Using default.";
broadcast = local.GetDefaultBroadcast();
}
} else if (!broadcast.SetAddressFromString(properties.broadcast_address)) {
LOG(ERROR) << "Broadcast address " << properties.broadcast_address
<< " is invalid";
return;
}
IPAddress peer(properties.address_family);
if (!properties.peer_address.empty() &&
!peer.SetAddressFromString(properties.peer_address)) {
LOG(ERROR) << "Peer address " << properties.peer_address << " is invalid";
return;
}
if (!SetupExcludedRoutes(properties, gateway)) {
return;
}
if (!FixGatewayReachability(local, &peer, &gateway)) {
LOG(WARNING) << "Expect limited network connectivity.";
}
if (!fixed_ip_params_) {
if (device_info_->HasOtherAddress(interface_index_, local)) {
// The address has changed for this interface. We need to flush
// everything and start over.
LOG(INFO) << __func__ << ": Flushing old addresses and routes.";
routing_table_->FlushRoutes(interface_index_);
device_info_->FlushAddresses(interface_index_);
}
LOG(INFO) << __func__ << ": Installing with parameters:"
<< " local=" << local.ToString()
<< " broadcast=" << broadcast.ToString()
<< " peer=" << peer.ToString()
<< " gateway=" << gateway.ToString();
rtnl_handler_->AddInterfaceAddress(interface_index_, local, broadcast,
peer);
SetMTU(properties.mtu);
}
if (gateway.IsValid() && properties.default_route) {
routing_table_->SetDefaultRoute(interface_index_, gateway, priority_,
table_id_);
}
if (blackhole_table_id_ != RT_TABLE_UNSPEC) {
routing_table_->FreeAdditionalTableId(blackhole_table_id_);
blackhole_table_id_ = RT_TABLE_UNSPEC;
}
blackholed_uids_ = properties.blackholed_uids;
if (!blackholed_uids_.empty()) {
blackhole_table_id_ = routing_table_->RequestAdditionalTableId();
CHECK(blackhole_table_id_);
routing_table_->CreateBlackholeRoute(
interface_index_, IPAddress::kFamilyIPv4, 0, blackhole_table_id_);
routing_table_->CreateBlackholeRoute(
interface_index_, IPAddress::kFamilyIPv6, 0, blackhole_table_id_);
}
if (properties.blackhole_ipv6) {
routing_table_->CreateBlackholeRoute(interface_index_,
IPAddress::kFamilyIPv6, 0, table_id_);
}
if (!SetupIncludedRoutes(properties)) {
LOG(WARNING) << "Failed to set up additional routes";
}
UpdateRoutingPolicy();
// Save a copy of the last non-null DNS config.
if (!config->properties().dns_servers.empty()) {
dns_servers_ = config->properties().dns_servers;
}
if (!config->properties().domain_search.empty()) {
dns_domain_search_ = config->properties().domain_search;
}
if (!config->properties().domain_name.empty()) {
dns_domain_name_ = config->properties().domain_name;
}
ipconfig_rpc_identifier_ = config->GetRpcIdentifier();
PushDNSConfig();
local_ = local;
gateway_ = gateway;
has_broadcast_domain_ = !peer.IsValid();
}
void Connection::UpdateGatewayMetric(const IPConfigRefPtr& config) {
const IPConfig::Properties& properties = config->properties();
IPAddress gateway(properties.address_family);
if (!properties.gateway.empty() &&
!gateway.SetAddressFromString(properties.gateway)) {
return;
}
if (gateway.IsValid() && properties.default_route) {
routing_table_->SetDefaultRoute(interface_index_, gateway, priority_,
table_id_);
routing_table_->FlushCache();
}
}
void Connection::UpdateRoutingPolicy() {
routing_table_->FlushRules(interface_index_);
uint32_t blackhole_offset = 0;
if (blackhole_table_id_ != RT_TABLE_UNSPEC) {
blackhole_offset = 1;
for (const auto& uid : blackholed_uids_) {
auto entry = RoutingPolicyEntry::Create(IPAddress::kFamilyIPv4)
.SetPriority(priority_)
.SetTable(blackhole_table_id_)
.SetUidRange({uid, uid});
routing_table_->AddRule(interface_index_, entry);
routing_table_->AddRule(interface_index_, entry.FlipFamily());
}
}
AllowTrafficThrough(table_id_, priority_ + blackhole_offset);
if (use_if_addrs_ && 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.
auto main_table_rule =
RoutingPolicyEntry::CreateFromSrc(IPAddress(IPAddress::kFamilyIPv4))
.SetPriority(priority_ + blackhole_offset - 1)
.SetTable(RT_TABLE_MAIN);
routing_table_->AddRule(interface_index_, main_table_rule);
routing_table_->AddRule(interface_index_, main_table_rule.FlipFamily());
// Add a default routing rule to use the primary interface if there is
// nothing better.
// TODO(crbug.com/999589) Remove this rule.
auto catch_all_rule =
RoutingPolicyEntry::CreateFromSrc(IPAddress(IPAddress::kFamilyIPv4))
.SetTable(table_id_)
.SetPriority(kCatchallPriority);
routing_table_->AddRule(interface_index_, catch_all_rule);
routing_table_->AddRule(interface_index_, catch_all_rule.FlipFamily());
}
}
void Connection::AllowTrafficThrough(uint32_t table_id,
uint32_t base_priority) {
for (const auto& uid : allowed_uids_) {
auto entry = RoutingPolicyEntry::Create(IPAddress::kFamilyIPv4)
.SetPriority(base_priority)
.SetTable(table_id)
.SetUid(uid);
routing_table_->AddRule(interface_index_, entry);
routing_table_->AddRule(interface_index_, entry.FlipFamily());
}
for (const auto& interface_name : allowed_iifs_) {
auto entry = RoutingPolicyEntry::Create(IPAddress::kFamilyIPv4)
.SetPriority(base_priority)
.SetTable(table_id)
.SetIif(interface_name);
routing_table_->AddRule(interface_index_, entry);
routing_table_->AddRule(interface_index_, entry.FlipFamily());
}
for (const auto& source_address : allowed_srcs_) {
routing_table_->AddRule(interface_index_,
RoutingPolicyEntry::CreateFromSrc(source_address)
.SetPriority(base_priority)
.SetTable(table_id));
}
for (const auto& dst_address : allowed_dsts_) {
routing_table_->AddRule(interface_index_,
RoutingPolicyEntry::CreateFromDst(dst_address)
.SetPriority(kDstRulePriority)
.SetTable(table_id));
}
// Always set a rule for matching traffic tagged with the fwmark routing tag
// corresponding to this network interface.
auto fwmark_routing_entry =
RoutingPolicyEntry::Create(IPAddress::kFamilyIPv4)
.SetPriority(base_priority)
.SetTable(table_id)
.SetFwMark(GetFwmarkRoutingTag(interface_index_));
routing_table_->AddRule(interface_index_, fwmark_routing_entry);
routing_table_->AddRule(interface_index_, fwmark_routing_entry.FlipFamily());
for (const auto& fwmark : included_fwmarks_) {
auto entry = RoutingPolicyEntry::Create(IPAddress::kFamilyIPv4)
.SetPriority(base_priority)
.SetTable(table_id)
.SetFwMark(fwmark);
routing_table_->AddRule(interface_index_, entry);
routing_table_->AddRule(interface_index_, entry.FlipFamily());
}
// Add output interface rule for all interfaces, such that SO_BINDTODEVICE can
// be used without explicitly binding the socket.
auto oif_rule =
RoutingPolicyEntry::CreateFromSrc(IPAddress(IPAddress::kFamilyIPv4))
.SetTable(table_id)
.SetPriority(base_priority)
.SetOif(interface_name_);
routing_table_->AddRule(interface_index_, oif_rule);
routing_table_->AddRule(interface_index_, oif_rule.FlipFamily());
if (use_if_addrs_) {
// Select the per-device table if the outgoing packet's src address matches
// the interface's addresses or the input interface is this interface.
//
// TODO(crbug.com/941597) This may need to change when NDProxy allows guests
// to provision IPv6 addresses.
for (const auto& address : device_info_->GetAddresses(interface_index_)) {
routing_table_->AddRule(interface_index_,
RoutingPolicyEntry::CreateFromSrc(address)
.SetTable(table_id)
.SetPriority(base_priority));
}
auto iif_rule =
RoutingPolicyEntry::CreateFromSrc(IPAddress(IPAddress::kFamilyIPv4))
.SetTable(table_id)
.SetPriority(base_priority)
.SetIif(interface_name_);
routing_table_->AddRule(interface_index_, iif_rule);
routing_table_->AddRule(interface_index_, iif_rule.FlipFamily());
}
}
void Connection::AddInputInterfaceToRoutingTable(
const std::string& interface_name) {
if (base::Contains(allowed_iifs_, interface_name))
return; // interface already allowed
allowed_iifs_.push_back(interface_name);
UpdateRoutingPolicy();
routing_table_->FlushCache();
}
void Connection::RemoveInputInterfaceFromRoutingTable(
const std::string& interface_name) {
if (!base::Contains(allowed_iifs_, interface_name))
return; // interface already removed
base::Erase(allowed_iifs_, interface_name);
UpdateRoutingPolicy();
routing_table_->FlushCache();
}
void Connection::SetPriority(uint32_t priority, bool is_primary_physical) {
SLOG(this, 2) << __func__ << " " << interface_name_ << " (index "
<< interface_index_ << ")" << priority_ << " -> " << priority;
if (priority == priority_) {
return;
}
priority_ = priority;
is_primary_physical_ = is_primary_physical;
routing_table_->SetDefaultMetric(interface_index_, priority);
UpdateRoutingPolicy();
PushDNSConfig();
routing_table_->FlushCache();
}
bool Connection::IsDefault() const {
return priority_ == kDefaultPriority;
}
void Connection::SetUseDNS(bool enable) {
SLOG(this, 2) << __func__ << " " << interface_name_ << " (index "
<< interface_index_ << ")" << use_dns_ << " -> " << enable;
use_dns_ = enable;
}
void Connection::UpdateDNSServers(const vector<string>& dns_servers) {
dns_servers_ = dns_servers;
PushDNSConfig();
}
void Connection::PushDNSConfig() {
if (!use_dns_) {
return;
}
vector<string> domain_search = dns_domain_search_;
if (domain_search.empty() && !dns_domain_name_.empty()) {
SLOG(this, 2) << "Setting domain search to domain name "
<< dns_domain_name_;
domain_search.push_back(dns_domain_name_ + ".");
}
resolver_->SetDNSFromLists(dns_servers_, domain_search);
}
string Connection::GetSubnetName() const {
if (!local().IsValid()) {
return "";
}
return base::StringPrintf(
"%s/%d", local().GetNetworkPart().ToString().c_str(), local().prefix());
}
void Connection::set_allowed_srcs(std::vector<IPAddress> addresses) {
allowed_srcs_ = std::move(addresses);
}
bool Connection::FixGatewayReachability(const IPAddress& local,
IPAddress* peer,
IPAddress* gateway) {
SLOG(nullptr, 2) << __func__ << " local " << local.ToString() << ", peer "
<< peer->ToString() << ", gateway " << gateway->ToString();
if (peer->IsValid()) {
// For a PPP connection:
// 1) Never set a peer (point-to-point) address, because the kernel
// will create an implicit routing rule in RT_TABLE_MAIN rather
// than our preferred routing table. If the peer IP is set to the
// public IP of a VPN gateway (see below) this creates a routing loop.
// If not, it still creates an undesired route.
// 2) Don't bother setting a gateway address either, because it doesn't
// have an effect on a point-to-point link. So `ip route show table 1`
// will just say something like:
// default dev ppp0 metric 10
peer->SetAddressToDefault();
gateway->SetAddressToDefault();
return true;
}
if (!gateway->IsValid()) {
LOG(WARNING) << "No gateway address was provided for this connection.";
return false;
}
// The prefix check will usually fail on IPv6 because IPv6 gateways
// typically use link-local addresses.
if (local.CanReachAddress(*gateway) ||
local.family() == IPAddress::kFamilyIPv6) {
return true;
}
LOG(WARNING) << "Gateway " << gateway->ToString()
<< " is unreachable from local address/prefix "
<< local.ToString() << "/" << local.prefix();
LOG(WARNING) << "Mitigating this by creating a link route to the gateway.";
IPAddress gateway_with_max_prefix(*gateway);
gateway_with_max_prefix.set_prefix(
IPAddress::GetMaxPrefixLength(gateway_with_max_prefix.family()));
IPAddress default_address(gateway->family());
auto entry = RoutingTableEntry::Create(gateway_with_max_prefix,
default_address, default_address)
.SetScope(RT_SCOPE_LINK)
.SetTable(table_id_)
.SetType(RTN_UNICAST);
if (!routing_table_->AddRoute(interface_index_, entry)) {
LOG(ERROR) << "Unable to add link-scoped route to gateway.";
return false;
}
return true;
}
void Connection::SetMTU(int32_t mtu) {
SLOG(this, 2) << __func__ << " " << mtu;
// Make sure the MTU value is valid.
if (mtu == IPConfig::kUndefinedMTU) {
mtu = IPConfig::kDefaultMTU;
} else {
int min_mtu = IsIPv6() ? IPConfig::kMinIPv6MTU : IPConfig::kMinIPv4MTU;
if (mtu < min_mtu) {
SLOG(this, 2) << __func__ << " MTU " << mtu
<< " is too small; adjusting up to " << min_mtu;
mtu = min_mtu;
}
}
rtnl_handler_->SetInterfaceMTU(interface_index_, mtu);
}
bool Connection::IsIPv6() {
return local_.family() == IPAddress::kFamilyIPv6;
}
} // namespace shill