blob: 807536de079746a917ef48ef0d26ab2faab8cc5a [file] [log] [blame]
// Copyright 2018 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_policy_entry.h"
#include "shill/routing_table.h"
#include "shill/routing_table_entry.h"
using std::vector;
using std::string;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kConnection;
static string ObjectID(Connection* c) {
if (c == nullptr)
return "(connection)";
return c->interface_name();
}
} // namespace Logging
// static
const uint32_t Connection::kDefaultMetric = 10;
// static
//
// UINT_MAX is also a valid priority metric, but we reserve this as a sentinel
// value, as in RoutingTable::GetDefaultRouteInternal.
const uint32_t Connection::kLowestPriorityMetric =
std::numeric_limits<uint32_t>::max() - 1;
// static
const uint32_t Connection::kMetricIncrement = 10;
Connection::Binder::Binder(const string& name,
const base::Closure& disconnect_callback)
: name_(name),
client_disconnect_callback_(disconnect_callback) {}
Connection::Binder::~Binder() {
Attach(nullptr);
}
void Connection::Binder::Attach(const ConnectionRefPtr& to_connection) {
if (connection_) {
connection_->DetachBinder(this);
LOG(INFO) << name_ << ": unbound from connection: "
<< connection_->interface_name();
connection_.reset();
}
if (to_connection) {
connection_ = to_connection->weak_ptr_factory_.GetWeakPtr();
connection_->AttachBinder(this);
LOG(INFO) << name_ << ": bound to connection: "
<< connection_->interface_name();
}
}
void Connection::Binder::OnDisconnect() {
LOG(INFO) << name_ << ": bound connection disconnected: "
<< connection_->interface_name();
connection_.reset();
if (!client_disconnect_callback_.is_null()) {
SLOG(connection_.get(), 2) << "Running client disconnect callback.";
client_disconnect_callback_.Run();
}
}
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),
metric_(kLowestPriorityMetric),
is_primary_physical_(false),
has_broadcast_domain_(false),
routing_request_count_(0),
interface_index_(interface_index),
interface_name_(interface_name),
technology_(technology),
use_if_addrs_(false),
blackholed_addrs_(nullptr),
fixed_ip_params_(fixed_ip_params),
table_id_(RT_TABLE_MAIN),
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_;
NotifyBindersOnDisconnect();
DCHECK(!routing_request_count_);
routing_table_->FlushRoutes(interface_index_);
routing_table_->FlushRoutesWithTag(interface_index_);
if (!fixed_ip_params_) {
device_info_->FlushAddresses(interface_index_);
}
routing_table_->FlushRules(interface_index_);
routing_table_->FreeTableId(table_id_);
if (blackhole_table_id_ != RT_TABLE_UNSPEC) {
routing_table_->FreeTableId(blackhole_table_id_);
}
}
bool Connection::SetupExcludedRoutes(const IPConfig::Properties& properties,
const IPAddress& gateway) {
excluded_ips_cidr_ = properties.exclusion_list;
// 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 : excluded_ips_cidr_) {
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;
use_if_addrs_ =
properties.use_if_addrs || technology_.IsPrimaryConnectivityTechnology();
if (table_id_ == RT_TABLE_MAIN) {
table_id_ = routing_table_->AllocTableId();
CHECK_NE(table_id_, RT_TABLE_UNSPEC);
routing_table_->SetPerDeviceTable(interface_index_, table_id_);
}
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,
metric_,
table_id_);
}
if (blackhole_table_id_ != RT_TABLE_UNSPEC) {
routing_table_->FreeTableId(blackhole_table_id_);
blackhole_table_id_ = RT_TABLE_UNSPEC;
}
blackholed_uids_ = properties.blackholed_uids;
blackholed_addrs_ = properties.blackholed_addrs;
bool has_blackholed_addrs =
blackholed_addrs_ && !blackholed_addrs_->IsEmpty();
if (!blackholed_uids_.empty() || has_blackholed_addrs) {
blackhole_table_id_ = routing_table_->AllocTableId();
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_);
}
UpdateRoutingPolicy();
// Install any explicitly configured routes at the default metric.
routing_table_->ConfigureRoutes(interface_index_, config, kDefaultMetric,
table_id_);
if (properties.blackhole_ipv6) {
routing_table_->CreateBlackholeRoute(interface_index_,
IPAddress::kFamilyIPv6,
0,
table_id_);
}
// 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,
metric_,
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(metric_)
.SetTable(blackhole_table_id_)
.SetUidRange({uid, uid});
routing_table_->AddRule(interface_index_, entry);
routing_table_->AddRule(interface_index_, entry.FlipFamily());
}
if (blackholed_addrs_) {
blackholed_addrs_->Apply(base::Bind(
[](Connection* connection, const IPAddress& addr) {
// Add |addr| to blackhole table.
auto entry = RoutingPolicyEntry::CreateFromSrc(addr)
.SetPriority(connection->metric_)
.SetTable(connection->blackhole_table_id_);
connection->routing_table_->AddRule(connection->interface_index_,
entry);
},
base::Unretained(this)));
}
}
for (const auto& uid : allowed_uids_) {
auto entry = RoutingPolicyEntry::Create(IPAddress::kFamilyIPv4)
.SetPriority(metric_ + blackhole_offset)
.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(metric_ + blackhole_offset)
.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_addrs_) {
routing_table_->AddRule(interface_index_,
RoutingPolicyEntry::CreateFromSrc(source_address)
.SetPriority(metric_ + blackhole_offset)
.SetTable(table_id_));
}
// 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(metric_ + blackhole_offset)
.SetOif(interface_name_);
routing_table_->AddRule(interface_index_, oif_rule);
routing_table_->AddRule(interface_index_, oif_rule.FlipFamily());
if (use_if_addrs_) {
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.
auto main_table_rule =
RoutingPolicyEntry::CreateFromSrc(IPAddress(IPAddress::kFamilyIPv4))
.SetPriority(metric_ + 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.
auto catch_all_rule = RoutingPolicyEntry::CreateFromSrc(
IPAddress(IPAddress::kFamilyIPv4))
.SetTable(table_id_)
.SetPriority(RoutingTable::kRulePriorityMain - 1);
routing_table_->AddRule(interface_index_, catch_all_rule);
routing_table_->AddRule(interface_index_, catch_all_rule.FlipFamily());
}
// Otherwise, only 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.
std::vector<DeviceInfo::AddressData> addr_data;
bool ok = device_info_->GetAddresses(interface_index_, &addr_data);
DCHECK(ok);
for (const auto& data : addr_data) {
routing_table_->AddRule(interface_index_,
RoutingPolicyEntry::CreateFromSrc(data.address)
.SetTable(table_id_)
.SetPriority(metric_ + blackhole_offset));
}
auto iif_rule = RoutingPolicyEntry::CreateFromSrc(
IPAddress(IPAddress::kFamilyIPv4))
.SetTable(table_id_)
.SetPriority(metric_ + blackhole_offset)
.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::ContainsValue(allowed_iifs_, interface_name))
return; // interface already whitelisted
allowed_iifs_.push_back(interface_name);
UpdateRoutingPolicy();
routing_table_->FlushCache();
}
void Connection::RemoveInputInterfaceFromRoutingTable(
const std::string& interface_name) {
if (!base::ContainsValue(allowed_iifs_, interface_name))
return; // interface already removed
base::Erase(allowed_iifs_, interface_name);
UpdateRoutingPolicy();
routing_table_->FlushCache();
}
void Connection::SetMetric(uint32_t metric, bool is_primary_physical) {
SLOG(this, 2) << __func__ << " " << interface_name_
<< " (index " << interface_index_ << ")"
<< metric_ << " -> " << metric;
if (metric == metric_) {
return;
}
metric_ = metric;
is_primary_physical_ = is_primary_physical;
UpdateRoutingPolicy();
PushDNSConfig();
if (metric == kDefaultMetric) {
DeviceRefPtr device = device_info_->GetDevice(interface_index_);
if (device) {
device->RequestPortalDetection();
}
}
routing_table_->FlushCache();
}
bool Connection::IsDefault() const {
return metric_ == kDefaultMetric;
}
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);
}
void Connection::RequestRouting() {
if (routing_request_count_++ == 0) {
DeviceRefPtr device = device_info_->GetDevice(interface_index_);
DCHECK(device.get());
if (!device) {
LOG(ERROR) << "Device is NULL!";
return;
}
device->SetLooseRouting(true);
}
}
void Connection::ReleaseRouting() {
DCHECK_GT(routing_request_count_, 0);
if (--routing_request_count_ == 0) {
DeviceRefPtr device = device_info_->GetDevice(interface_index_);
DCHECK(device.get());
if (!device) {
LOG(ERROR) << "Device is NULL!";
return;
}
device->SetLooseRouting(false);
// Clear any cached routes that might have accumulated while reverse-path
// filtering was disabled.
routing_table_->FlushCache();
}
}
string Connection::GetSubnetName() const {
if (!local().IsValid()) {
return "";
}
return base::StringPrintf("%s/%d",
local().GetNetworkPart().ToString().c_str(),
local().prefix());
}
void Connection::set_allowed_addrs(std::vector<IPAddress> addresses) {
allowed_addrs_ = 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);
}
void Connection::NotifyBindersOnDisconnect() {
// Note that this method may be invoked by the destructor.
SLOG(this, 2) << __func__ << " @ " << interface_name_;
while (!binders_.empty()) {
// Pop the binder first and then notify it to ensure that each binder is
// notified only once.
Binder* binder = binders_.front();
binders_.pop_front();
binder->OnDisconnect();
}
}
void Connection::AttachBinder(Binder* binder) {
SLOG(this, 2) << __func__ << "(" << binder->name() << ")" << " @ "
<< interface_name_;
binders_.push_back(binder);
}
void Connection::DetachBinder(Binder* binder) {
SLOG(this, 2) << __func__ << "(" << binder->name() << ")" << " @ "
<< interface_name_;
for (auto it = binders_.begin(); it != binders_.end(); ++it) {
if (binder == *it) {
binders_.erase(it);
return;
}
}
}
bool Connection::IsIPv6() {
return local_.family() == IPAddress::kFamilyIPv6;
}
} // namespace shill