714 lines
24 KiB
C++
714 lines
24 KiB
C++
//
|
|
// Copyright (C) 2012 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#include "shill/connection.h"
|
|
|
|
#include <arpa/inet.h>
|
|
#include <linux/rtnetlink.h>
|
|
|
|
#include <set>
|
|
|
|
#include <base/strings/stringprintf.h>
|
|
|
|
#include "shill/control_interface.h"
|
|
#include "shill/device_info.h"
|
|
#include "shill/firewall_proxy_interface.h"
|
|
#include "shill/logging.h"
|
|
#include "shill/net/rtnl_handler.h"
|
|
#include "shill/routing_table.h"
|
|
|
|
#if !defined(__ANDROID__)
|
|
#include "shill/resolver.h"
|
|
#else
|
|
#include "shill/dns_server_proxy.h"
|
|
#include "shill/dns_server_proxy_factory.h"
|
|
#endif // __ANDROID__
|
|
|
|
using base::Bind;
|
|
using base::Closure;
|
|
using base::Unretained;
|
|
using std::deque;
|
|
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();
|
|
}
|
|
}
|
|
|
|
#if defined(__ANDROID__)
|
|
namespace {
|
|
const char* kGoogleDNSServers[] = {
|
|
"8.8.4.4",
|
|
"8.8.8.8"
|
|
};
|
|
} // namespace
|
|
#endif // __ANDROID__
|
|
|
|
// static
|
|
const uint32_t Connection::kDefaultMetric = 1;
|
|
// static
|
|
const uint32_t Connection::kNonDefaultMetricBase = 10;
|
|
// static
|
|
const uint32_t Connection::kMarkForUserTraffic = 0x1;
|
|
// static
|
|
const uint8_t Connection::kSecondaryTableId = 0x1;
|
|
|
|
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,
|
|
Technology::Identifier technology,
|
|
const DeviceInfo* device_info,
|
|
ControlInterface* control_interface)
|
|
: 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),
|
|
user_traffic_only_(false),
|
|
table_id_(RT_TABLE_MAIN),
|
|
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))),
|
|
device_info_(device_info),
|
|
#if !defined(__ANDROID__)
|
|
resolver_(Resolver::GetInstance()),
|
|
#else
|
|
dns_server_proxy_factory_(DNSServerProxyFactory::GetInstance()),
|
|
#endif // __ANDROID__
|
|
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_);
|
|
device_info_->FlushAddresses(interface_index_);
|
|
TearDownIptableEntries();
|
|
}
|
|
|
|
void Connection::UpdateFromIPConfig(const IPConfigRefPtr& config) {
|
|
SLOG(this, 2) << __func__ << " " << interface_name_;
|
|
|
|
const IPConfig::Properties& properties = config->properties();
|
|
user_traffic_only_ = properties.user_traffic_only;
|
|
table_id_ = user_traffic_only_ ? kSecondaryTableId : (uint8_t)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;
|
|
}
|
|
|
|
excluded_ips_cidr_ = properties.exclusion_list;
|
|
|
|
IPAddress trusted_ip(properties.address_family);
|
|
if (!excluded_ips_cidr_.empty()) {
|
|
const std::string first_excluded_ip = excluded_ips_cidr_[0];
|
|
excluded_ips_cidr_.erase(excluded_ips_cidr_.begin());
|
|
// A VPN connection can currently be bound to exactly one lower connection
|
|
// such as eth0 or wan0. The excluded IPs are pinned to the gateway of
|
|
// that connection. Setting up the routing table this way ensures that when
|
|
// the lower connection goes offline, the associated entries in the routing
|
|
// table are removed. On the flip side, when there are multiple connections
|
|
// such as eth0 and wan0 and some IPs can be reached quickly over one
|
|
// connection and the others over a different connection, all routes are
|
|
// still pinned to a connection.
|
|
//
|
|
// The optimal connection to reach the first excluded IP is found below.
|
|
// When this is found the route for the remaining excluded IPs are pinned in
|
|
// the method PinPendingRoutes below.
|
|
if (!trusted_ip.SetAddressAndPrefixFromString(first_excluded_ip)) {
|
|
LOG(ERROR) << "Trusted IP address "
|
|
<< first_excluded_ip << " is invalid";
|
|
return;
|
|
}
|
|
if (!PinHostRoute(trusted_ip, gateway)) {
|
|
LOG(ERROR) << "Unable to pin host route to " << first_excluded_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;
|
|
}
|
|
|
|
if (!FixGatewayReachability(local, &peer, &gateway, trusted_ip)) {
|
|
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() && properties.default_route) {
|
|
routing_table_->SetDefaultRoute(interface_index_, gateway,
|
|
GetMetric(is_default_),
|
|
table_id_);
|
|
}
|
|
|
|
if (user_traffic_only_) {
|
|
SetupIptableEntries();
|
|
}
|
|
|
|
// Install any explicitly configured routes at the default metric.
|
|
routing_table_->ConfigureRoutes(interface_index_, config, kDefaultMetric,
|
|
table_id_);
|
|
|
|
SetMTU(properties.mtu);
|
|
|
|
if (properties.blackhole_ipv6) {
|
|
routing_table_->CreateBlackholeRoute(interface_index_,
|
|
IPAddress::kFamilyIPv6,
|
|
kDefaultMetric,
|
|
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 defined(__ANDROID__)
|
|
// Default to Google's DNS server if it is not provided through DHCP.
|
|
if (config->properties().dns_servers.empty()) {
|
|
LOG(INFO) << "Default to use Google DNS servers";
|
|
dns_servers_ =
|
|
vector<string>(std::begin(kGoogleDNSServers),
|
|
std::end(kGoogleDNSServers));
|
|
}
|
|
#endif // __ANDROID__
|
|
|
|
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();
|
|
}
|
|
|
|
bool Connection::SetupIptableEntries() {
|
|
if (!firewall_proxy_) {
|
|
firewall_proxy_.reset(control_interface_->CreateFirewallProxy());
|
|
}
|
|
|
|
std::vector<std::string> user_names;
|
|
user_names.push_back("chronos");
|
|
user_names.push_back("debugd");
|
|
|
|
if (!firewall_proxy_->RequestVpnSetup(user_names, interface_name_)) {
|
|
LOG(ERROR) << "VPN iptables setup request failed.";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Connection::TearDownIptableEntries() {
|
|
return firewall_proxy_ ? firewall_proxy_->RemoveVpnSetup() : true;
|
|
}
|
|
|
|
void Connection::SetIsDefault(bool is_default) {
|
|
SLOG(this, 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;
|
|
|
|
PushDNSConfig();
|
|
if (is_default) {
|
|
DeviceRefPtr device = device_info_->GetDevice(interface_index_);
|
|
if (device) {
|
|
device->RequestPortalDetection();
|
|
}
|
|
}
|
|
routing_table_->FlushCache();
|
|
}
|
|
|
|
void Connection::UpdateDNSServers(const vector<string>& dns_servers) {
|
|
dns_servers_ = dns_servers;
|
|
PushDNSConfig();
|
|
}
|
|
|
|
void Connection::PushDNSConfig() {
|
|
if (!is_default_) {
|
|
#if defined(__ANDROID__)
|
|
// Stop DNS server proxy to avoid having multiple instances of it running.
|
|
// Only run DNS server proxy for the current default connection.
|
|
dns_server_proxy_.reset();
|
|
#endif // __ANDROID__
|
|
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_ + ".");
|
|
}
|
|
#if !defined(__ANDROID__)
|
|
resolver_->SetDNSFromLists(dns_servers_, domain_search);
|
|
#else
|
|
dns_server_proxy_.reset(
|
|
dns_server_proxy_factory_->CreateDNSServerProxy(dns_servers_));
|
|
dns_server_proxy_->Start();
|
|
#endif // __ANDROID__
|
|
}
|
|
|
|
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->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.get()) {
|
|
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();
|
|
}
|
|
}
|
|
|
|
bool Connection::RequestHostRoute(const IPAddress& address) {
|
|
// 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,
|
|
-1,
|
|
interface_index_,
|
|
Bind(&Connection::OnRouteQueryResponse,
|
|
weak_ptr_factory_.GetWeakPtr()),
|
|
table_id_)) {
|
|
LOG(ERROR) << "Could not request route to " << address.ToString();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Connection::PinPendingRoutes(int interface_index,
|
|
RoutingTableEntry entry) {
|
|
// The variable entry is locally modified, hence is passed by value in the
|
|
// second argument above.
|
|
for (auto excluded_ip = excluded_ips_cidr_.begin();
|
|
excluded_ip != excluded_ips_cidr_.end(); ++excluded_ip) {
|
|
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 true;
|
|
}
|
|
|
|
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 (!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;
|
|
}
|
|
|
|
if (local.CanReachAddress(*gateway)) {
|
|
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_,
|
|
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;
|
|
}
|
|
|
|
uint32_t 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 IPAddress& trusted_ip,
|
|
const IPAddress& gateway) {
|
|
SLOG(this, 2) << __func__;
|
|
if (!trusted_ip.IsValid()) {
|
|
LOG(ERROR) << "No trusted IP -- unable to pin host route.";
|
|
return false;
|
|
}
|
|
|
|
if (!gateway.IsValid()) {
|
|
// Although we cannot pin a host route, we are also not going to create
|
|
// a gateway route that will interfere with our primary connection, so
|
|
// it is okay to return success here.
|
|
LOG(WARNING) << "No gateway -- unable to pin host route.";
|
|
return true;
|
|
}
|
|
|
|
return RequestHostRoute(trusted_ip);
|
|
}
|
|
|
|
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::OnRouteQueryResponse(int interface_index,
|
|
const RoutingTableEntry& entry) {
|
|
SLOG(this, 2) << __func__ << "(" << interface_index << ", "
|
|
<< entry.tag << ")" << " @ " << interface_name_;
|
|
lower_binder_.Attach(nullptr);
|
|
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;
|
|
}
|
|
if (connection == this) {
|
|
LOG(ERROR) << "Avoiding a connection bind loop for " << interface_name();
|
|
return;
|
|
}
|
|
lower_binder_.Attach(connection);
|
|
connection->CreateGatewayRoute();
|
|
device->OnConnectionUpdated();
|
|
PinPendingRoutes(interface_index, entry);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// If there is no gateway, don't try to create a route to it.
|
|
if (!gateway_.IsValid()) {
|
|
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_,
|
|
table_id_);
|
|
}
|
|
|
|
void Connection::OnLowerDisconnect() {
|
|
SLOG(this, 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(this, 2) << __func__ << " @ " << interface_name_;
|
|
|
|
// Unbinds the lower connection before notifying the binders. This ensures
|
|
// correct behavior in case of circular binding.
|
|
lower_binder_.Attach(nullptr);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
ConnectionRefPtr Connection::GetCarrierConnection() {
|
|
SLOG(this, 2) << __func__ << " @ " << interface_name_;
|
|
set<Connection*> visited;
|
|
ConnectionRefPtr carrier = this;
|
|
while (carrier->GetLowerConnection()) {
|
|
if (ContainsKey(visited, carrier.get())) {
|
|
LOG(ERROR) << "Circular connection chain starting at: "
|
|
<< carrier->interface_name();
|
|
// If a loop is detected return a NULL value to signal that the carrier
|
|
// connection is unknown.
|
|
return nullptr;
|
|
}
|
|
visited.insert(carrier.get());
|
|
carrier = carrier->GetLowerConnection();
|
|
}
|
|
SLOG(this, 2) << "Carrier connection: " << carrier->interface_name()
|
|
<< " @ " << interface_name_;
|
|
return carrier;
|
|
}
|
|
|
|
bool Connection::IsIPv6() {
|
|
return local_.family() == IPAddress::kFamilyIPv6;
|
|
}
|
|
|
|
} // namespace shill
|