blob: 78e1cfa42ffa0a799cbc20575e069ed9dde30c4d [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 <set>
#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"
using base::Bind;
using base::Closure;
using base::Unretained;
using std::set;
using std::string;
using std::vector;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kConnection;
static string ObjectID(Connection* c) {
if (c == nullptr)
return "(connection)";
return c->interface_name();
}
}
// 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 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::Identifier technology,
const DeviceInfo* device_info,
ControlInterface* control_interface)
: weak_ptr_factory_(this),
use_dns_(false),
metric_(kLowestPriorityMetric),
has_broadcast_domain_(false),
routing_request_count_(0),
interface_index_(interface_index),
interface_name_(interface_name),
technology_(technology),
per_device_routing_(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::NameFromIdentifier(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,
IPAddress* trusted_ip) {
excluded_ips_cidr_ = properties.exclusion_list;
// TODO(crbug.com/941597) Remove this CHECK when per device routing is used
// for all devices.
CHECK(excluded_ips_cidr_.empty() || per_device_routing_);
// 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);
RoutingTableEntry entry(empty_ip,
empty_ip,
empty_ip,
0,
RT_SCOPE_LINK,
false,
table_id_,
RTN_THROW,
RoutingTableEntry::kDefaultTag);
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();
if (!properties.allowed_uids.empty() || !properties.allowed_iifs.empty() ||
properties.blackhole_ipv6) {
per_device_routing_ = true;
allowed_uids_ = properties.allowed_uids;
allowed_iifs_ = properties.allowed_iifs;
// For per-device routing, |table_id_| uses the interface index
// (as a simple way to assign a per-device ID) and the route priority
// uses |metric_| which is set by Manager's service sort.
routing_table_->FreeTableId(table_id_);
table_id_ = routing_table_->AllocTableId();
CHECK(table_id_);
} else {
table_id_ = RT_TABLE_MAIN;
}
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;
}
IPAddress trusted_ip(IPAddress::kFamilyUnknown);
if (!SetupExcludedRoutes(properties, gateway, &trusted_ip)) {
return;
}
if (!FixGatewayReachability(local, &peer, &gateway, trusted_ip)) {
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_);
bool rule_created = false;
uint32_t blackhole_offset = 0;
if (blackhole_table_id_ != RT_TABLE_UNSPEC) {
blackhole_offset = 1;
for (const auto& uid : blackholed_uids_) {
RoutingPolicyEntry entry(
IPAddress::kFamilyIPv4, metric_, blackhole_table_id_, uid, uid);
routing_table_->AddRule(interface_index_, entry);
entry.family = IPAddress::kFamilyIPv6;
routing_table_->AddRule(interface_index_, entry);
rule_created = true;
}
if (blackholed_addrs_) {
blackholed_addrs_->Apply(base::Bind(
[](Connection* connection, const IPAddress& addr) {
// Add |addr| to blackhole table.
RoutingPolicyEntry entry(addr.family(), connection->metric_,
connection->blackhole_table_id_);
entry.src = addr;
connection->routing_table_->AddRule(connection->interface_index_,
entry);
},
base::Unretained(this)));
rule_created = rule_created || !blackholed_addrs_->IsEmpty();
}
}
for (const auto& uid : allowed_uids_) {
RoutingPolicyEntry entry(IPAddress::kFamilyIPv4,
metric_ + blackhole_offset, table_id_, uid, uid);
routing_table_->AddRule(interface_index_, entry);
entry.family = IPAddress::kFamilyIPv6;
routing_table_->AddRule(interface_index_, entry);
rule_created = true;
}
for (const auto& interface_name : allowed_iifs_) {
RoutingPolicyEntry entry(IPAddress::kFamilyIPv4,
metric_ + blackhole_offset, table_id_, interface_name);
routing_table_->AddRule(interface_index_, entry);
entry.family = IPAddress::kFamilyIPv6;
routing_table_->AddRule(interface_index_, entry);
rule_created = true;
}
if (!rule_created) {
// No restrictions.
RoutingPolicyEntry entry(
IPAddress::kFamilyIPv4, metric_ + blackhole_offset, table_id_);
routing_table_->AddRule(interface_index_, entry);
entry.family = IPAddress::kFamilyIPv6;
routing_table_->AddRule(interface_index_, entry);
}
}
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;
}
if (!per_device_routing_) {
routing_table_->SetDefaultMetric(interface_index_, metric);
}
metric_ = metric;
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());
}
bool Connection::FixGatewayReachability(const IPAddress& local,
IPAddress* peer,
IPAddress* gateway,
const IPAddress& trusted_ip) {
SLOG(nullptr, 2) << __func__
<< " local " << local.ToString()
<< ", peer " << peer->ToString()
<< ", gateway " << gateway->ToString()
<< ", trusted_ip " << trusted_ip.ToString();
if (per_device_routing_ && peer->IsValid()) {
// If per-device routing tables are used 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;
}
if (peer->IsValid()) {
if (!gateway->HasSameAddressAs(*peer)) {
LOG(WARNING) << "Gateway address "
<< gateway->ToString()
<< " does not match peer address "
<< peer->ToString();
return false;
}
if (gateway->HasSameAddressAs(trusted_ip)) {
// In order to send outgoing traffic in a point-to-point network,
// the gateway IP address isn't of significance. As opposed to
// broadcast networks, we never ARP for the gateway IP address,
// but just send the IP packet addressed to the recipient. As
// such, since using the external trusted IP address as the
// gateway or peer wreaks havoc on the routing rules, we choose
// not to supply a gateway address. Here's an example:
//
// Client <-> Internet <-> VPN Gateway <-> Internal Network
// 192.168.1.2 10.0.1.25 172.16.5.0/24
//
// In this example, a client connects to a VPN gateway on its
// public IP address 10.0.1.25. It gets issued an IP address
// from the VPN internal pool. For some VPN gateways, this
// results in a pushed-down PPP configuration which specifies:
//
// Client local address: 172.16.5.13
// Client peer address: 10.0.1.25
// Client default gateway: 10.0.1.25
//
// If we take this literally, we need to resolve the fact that
// 10.0.1.25 is now listed as the default gateway and interface
// peer address for the point-to-point interface. However, in
// order to route tunneled packets to the VPN gateway we must
// use the external route through the physical interface and
// not the tunnel, or else we end up in an infinite loop
// re-entering the tunnel trying to route towards the VPN server.
//
// We can do this by pinning a route, but we would need to wait
// for the pinning process to complete before assigning this
// address. Currently this process is asynchronous and will
// complete only after returning to the event loop. Additionally,
// since there's no metric associated with assigning an address
// to an interface, it's always possible that having the peer
// address of the interface might still trump a host route.
//
// To solve this problem, we reset the peer and gateway
// addresses. Neither is required in order to perform the
// underlying routing task. A gateway route can be specified
// without an IP endpoint on point-to-point links, and simply
// specify the outbound interface index. Similarly, a peer
// IP address is not necessary either, and will be assigned
// the same IP address as the local IP. This approach
// simplifies routing and doesn't change the desired
// functional behavior.
//
LOG(INFO) << "Removing gateway and peer addresses to preserve "
<< "routability to trusted IP address.";
peer->SetAddressToDefault();
gateway->SetAddressToDefault();
}
return true;
}
// 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();
IPAddress gateway_with_max_prefix(*gateway);
gateway_with_max_prefix.set_prefix(
IPAddress::GetMaxPrefixLength(gateway_with_max_prefix.family()));
IPAddress default_address(gateway->family());
RoutingTableEntry entry(gateway_with_max_prefix,
default_address,
default_address,
0,
RT_SCOPE_LINK,
false,
table_id_,
RTN_UNICAST,
RoutingTableEntry::kDefaultTag);
if (!routing_table_->AddRoute(interface_index_, entry)) {
LOG(ERROR) << "Unable to add link-scoped route to gateway.";
return false;
}
LOG(WARNING) << "Mitigating this by creating a link route to the gateway.";
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