blob: bc38e235328cef0a8e21a9a80f9e97f89da0f951 [file] [log] [blame]
// Copyright (c) 2012 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 "shill/device_info.h"
#include "shill/logging.h"
#include "shill/resolver.h"
#include "shill/routing_table.h"
#include "shill/rtnl_handler.h"
using base::Bind;
using base::Closure;
using base::Unretained;
using std::deque;
using std::string;
using std::vector;
namespace shill {
// static
const uint32 Connection::kDefaultMetric = 1;
// static
const uint32 Connection::kNonDefaultMetricBase = 10;
Connection::Binder::Binder(const string &name,
const Closure &disconnect_callback)
: name_(name),
client_disconnect_callback_(disconnect_callback) {}
Connection::Binder::~Binder() {
Attach(NULL);
}
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, 2) << "Running client disconnect callback.";
client_disconnect_callback_.Run();
}
}
Connection::Connection(int interface_index,
const std::string& interface_name,
Technology::Identifier technology,
const DeviceInfo *device_info,
bool is_short_dns_timeout_enabled)
: weak_ptr_factory_(this),
is_default_(false),
has_broadcast_domain_(false),
routing_request_count_(0),
interface_index_(interface_index),
interface_name_(interface_name),
technology_(technology),
local_(IPAddress::kFamilyUnknown),
gateway_(IPAddress::kFamilyUnknown),
lower_binder_(
interface_name_,
// Connection owns a single instance of |lower_binder_| so it's safe
// to use an Unretained callback.
Bind(&Connection::OnLowerDisconnect, Unretained(this))),
dns_timeout_parameters_(Resolver::kDefaultTimeout),
device_info_(device_info),
resolver_(Resolver::GetInstance()),
routing_table_(RoutingTable::GetInstance()),
rtnl_handler_(RTNLHandler::GetInstance()) {
SLOG(Connection, 2) << __func__ << "(" << interface_index << ", "
<< interface_name << ", "
<< Technology::NameFromIdentifier(technology) << ")";
if (is_short_dns_timeout_enabled) {
dns_timeout_parameters_ = Resolver::kShortTimeout;
}
}
Connection::~Connection() {
SLOG(Connection, 2) << __func__ << " " << interface_name_;
NotifyBindersOnDisconnect();
DCHECK(!routing_request_count_);
routing_table_->FlushRoutes(interface_index_);
routing_table_->FlushRoutesWithTag(interface_index_);
device_info_->FlushAddresses(interface_index_);
}
void Connection::UpdateFromIPConfig(const IPConfigRefPtr &config) {
SLOG(Connection, 2) << __func__ << " " << interface_name_;
const IPConfig::Properties &properties = config->properties();
if (!properties.trusted_ip.empty() && !PinHostRoute(properties)) {
LOG(ERROR) << "Unable to pin host route to " << properties.trusted_ip;
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 (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 gateway(properties.address_family);
if (!properties.gateway.empty() &&
!gateway.SetAddressFromString(properties.gateway)) {
LOG(ERROR) << "Gateway address " << properties.peer_address
<< " is invalid";
return;
}
if (!FixGatewayReachability(&local, &peer, gateway)) {
LOG(WARNING) << "Expect limited network connectivity.";
}
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);
if (gateway.IsValid()) {
routing_table_->SetDefaultRoute(interface_index_, gateway,
GetMetric(is_default_));
}
// Install any explicitly configured routes at the default metric.
routing_table_->ConfigureRoutes(interface_index_, config, kDefaultMetric);
if (properties.blackhole_ipv6) {
routing_table_->CreateBlackholeRoute(interface_index_,
IPAddress::kFamilyIPv6,
kDefaultMetric);
}
// 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();
if (is_default_) {
PushDNSConfig();
}
local_ = local;
gateway_ = gateway;
has_broadcast_domain_ = !peer.IsValid();
}
void Connection::SetIsDefault(bool is_default) {
SLOG(Connection, 2) << __func__ << " " << interface_name_
<< " (index " << interface_index_ << ") "
<< is_default_ << " -> " << is_default;
if (is_default == is_default_) {
return;
}
routing_table_->SetDefaultMetric(interface_index_, GetMetric(is_default));
is_default_ = is_default;
if (is_default) {
PushDNSConfig();
DeviceRefPtr device = device_info_->GetDevice(interface_index_);
if (device) {
device->RequestPortalDetection();
}
}
routing_table_->FlushCache();
}
void Connection::PushDNSConfig() {
vector<string> domain_search = dns_domain_search_;
if (domain_search.empty() && !dns_domain_name_.empty()) {
SLOG(Connection, 2) << "Setting domain search to domain name "
<< dns_domain_name_;
domain_search.push_back(dns_domain_name_ + ".");
}
resolver_->SetDNSFromLists(dns_servers_, domain_search,
dns_timeout_parameters_);
}
void Connection::RequestRouting() {
if (routing_request_count_++ == 0) {
DeviceRefPtr device = device_info_->GetDevice(interface_index_);
DCHECK(device.get());
if (!device.get()) {
LOG(ERROR) << "Device is NULL!";
return;
}
device->DisableReversePathFilter();
}
}
void Connection::ReleaseRouting() {
DCHECK(routing_request_count_ > 0);
if (--routing_request_count_ == 0) {
DeviceRefPtr device = device_info_->GetDevice(interface_index_);
DCHECK(device.get());
if (!device.get()) {
LOG(ERROR) << "Device is NULL!";
return;
}
device->EnableReversePathFilter();
// Clear any cached routes that might have accumulated while reverse-path
// filtering was disabled.
routing_table_->FlushCache();
}
}
bool Connection::RequestHostRoute(const IPAddress &address) {
// Set the prefix to be the entire address size.
IPAddress address_prefix(address);
address_prefix.set_prefix(address_prefix.GetLength() * 8);
// Do not set interface_index_ since this may not be the default route through
// which this destination can be found. However, we should tag the created
// route with our interface index so we can clean this route up when this
// connection closes. Also, add route query callback to determine the lower
// connection and bind to it.
if (!routing_table_->RequestRouteToHost(
address_prefix,
-1,
interface_index_,
Bind(&Connection::OnRouteQueryResponse,
weak_ptr_factory_.GetWeakPtr()))) {
LOG(ERROR) << "Could not request route to " << address.ToString();
return false;
}
return true;
}
// static
bool Connection::FixGatewayReachability(IPAddress *local,
IPAddress *peer,
const IPAddress &gateway) {
if (!gateway.IsValid()) {
LOG(WARNING) << "No gateway address was provided for this connection.";
return false;
}
if (peer->IsValid()) {
if (gateway.Equals(*peer)) {
return true;
}
LOG(WARNING) << "Gateway address "
<< gateway.ToString()
<< " does not match peer address "
<< peer->ToString();
return false;
}
if (local->CanReachAddress(gateway)) {
return true;
}
LOG(WARNING) << "Gateway "
<< gateway.ToString()
<< " is unreachable from local address/prefix "
<< local->ToString() << "/" << local->prefix();
bool found_new_prefix = false;
size_t original_prefix = local->prefix();
// Only try to expand the netmask if the configured prefix is
// less than "all ones". This special-cases the "all-ones"
// prefix as a forced conversion to point-to-point networking.
if (local->prefix() < IPAddress::GetMaxPrefixLength(local->family())) {
size_t prefix = original_prefix - 1;
for (; prefix >= local->GetMinPrefixLength(); --prefix) {
local->set_prefix(prefix);
if (local->CanReachAddress(gateway)) {
found_new_prefix = true;
break;
}
}
}
if (!found_new_prefix) {
// Restore the original prefix since we cannot find a better one.
local->set_prefix(original_prefix);
DCHECK(!peer->IsValid());
LOG(WARNING) << "Assuming point-to-point configuration.";
*peer = gateway;
return true;
}
LOG(WARNING) << "Mitigating this by setting local prefix to "
<< local->prefix();
return true;
}
uint32 Connection::GetMetric(bool is_default) {
// If this is not the default route, assign a metric based on the interface
// index. This way all non-default routes (even to the same gateway IP) end
// up with unique metrics so they do not collide.
return is_default ? kDefaultMetric : kNonDefaultMetricBase + interface_index_;
}
bool Connection::PinHostRoute(const IPConfig::Properties &properties) {
SLOG(Connection, 2) << __func__;
if (properties.gateway.empty() || properties.trusted_ip.empty()) {
LOG_IF(ERROR, properties.gateway.empty())
<< "No gateway -- unable to pin host route.";
LOG_IF(ERROR, properties.trusted_ip.empty())
<< "No trusted IP -- unable to pin host route.";
return false;
}
IPAddress trusted_ip(properties.address_family);
if (!trusted_ip.SetAddressFromString(properties.trusted_ip)) {
LOG(ERROR) << "Failed to parse trusted_ip "
<< properties.trusted_ip << "; ignored.";
return false;
}
return RequestHostRoute(trusted_ip);
}
void Connection::OnRouteQueryResponse(int interface_index,
const RoutingTableEntry &entry) {
SLOG(Connection, 2) << __func__ << "(" << interface_index << ", "
<< entry.tag << ")" << " @ " << interface_name_;
lower_binder_.Attach(NULL);
DeviceRefPtr device = device_info_->GetDevice(interface_index);
if (!device) {
LOG(ERROR) << "Unable to lookup device for index " << interface_index;
return;
}
ConnectionRefPtr connection = device->connection();
if (!connection) {
LOG(ERROR) << "Device " << interface_index << " has no connection.";
return;
}
lower_binder_.Attach(connection);
connection->CreateGatewayRoute();
}
bool Connection::CreateGatewayRoute() {
// Ensure that the gateway for the lower connection remains reachable,
// since we may create routes that conflict with it.
if (!has_broadcast_domain_) {
return false;
}
// It is not worth keeping track of this route, since it is benign,
// and only pins persistent state that was already true of the connection.
// If DHCP parameters change later (without the connection having been
// destroyed and recreated), the binding processes will likely terminate
// and restart, causing a new link route to be created.
return routing_table_->CreateLinkRoute(interface_index_, local_, gateway_);
}
void Connection::OnLowerDisconnect() {
SLOG(Connection, 2) << __func__ << " @ " << interface_name_;
// Ensures that |this| instance doesn't get destroyed in the middle of
// notifying the binders. This method needs to be separate from
// NotifyBindersOnDisconnect because the latter may be invoked by Connection's
// destructor when |this| instance's reference count is already 0.
ConnectionRefPtr connection(this);
connection->NotifyBindersOnDisconnect();
}
void Connection::NotifyBindersOnDisconnect() {
// Note that this method may be invoked by the destructor.
SLOG(Connection, 2) << __func__ << " @ " << interface_name_;
// Unbinds the lower connection before notifying the binders. This ensures
// correct behavior in case of circular binding.
lower_binder_.Attach(NULL);
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(Connection, 2) << __func__ << "(" << binder->name() << ")" << " @ "
<< interface_name_;
binders_.push_back(binder);
}
void Connection::DetachBinder(Binder *binder) {
SLOG(Connection, 2) << __func__ << "(" << binder->name() << ")" << " @ "
<< interface_name_;
for (deque<Binder *>::iterator it = binders_.begin();
it != binders_.end(); ++it) {
if (binder == *it) {
binders_.erase(it);
return;
}
}
}
} // namespace shill