blob: d97329cb706b0455798a02e31f7ef054b1fbc483 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/network/network.h"
#include <algorithm>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/notreached.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/patchpanel/dbus/client.h>
#include "shill/connection.h"
#include "shill/event_dispatcher.h"
#include "shill/logging.h"
#include "shill/metrics.h"
#include "shill/net/ip_address.h"
#include "shill/net/rtnl_handler.h"
#include "shill/network/proc_fs_stub.h"
#include "shill/network/slaac_controller.h"
#include "shill/routing_table.h"
#include "shill/routing_table_entry.h"
#include "shill/service.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kDevice;
} // namespace Logging
namespace {
// Constant string advertised in DHCP Vendor option 43 by Android devices
// sharing a metered network (typically a Cellular network) via tethering
// over a WiFi hotspot or a USB ethernet connection.
constexpr char kAndroidMeteredHotspotVendorOption[] = "ANDROID_METERED";
} // namespace
Network::Network(int interface_index,
const std::string& interface_name,
Technology technology,
bool fixed_ip_params,
ControlInterface* control_interface,
EventDispatcher* dispatcher,
Metrics* metrics)
: interface_index_(interface_index),
interface_name_(interface_name),
technology_(technology),
logging_tag_(interface_name),
fixed_ip_params_(fixed_ip_params),
proc_fs_(std::make_unique<ProcFsStub>(interface_name_)),
control_interface_(control_interface),
dispatcher_(dispatcher),
metrics_(metrics),
dhcp_provider_(DHCPProvider::GetInstance()),
routing_table_(RoutingTable::GetInstance()),
rtnl_handler_(RTNLHandler::GetInstance()) {}
Network::~Network() {
for (auto* ev : event_handlers_) {
ev->OnNetworkDestroyed(interface_index_);
}
}
void Network::RegisterEventHandler(EventHandler* handler) {
if (std::find(event_handlers_.begin(), event_handlers_.end(), handler) !=
event_handlers_.end()) {
return;
}
event_handlers_.push_back(handler);
}
void Network::UnregisterEventHandler(EventHandler* handler) {
auto it = std::find(event_handlers_.begin(), event_handlers_.end(), handler);
if (it != event_handlers_.end()) {
event_handlers_.erase(it);
}
}
void Network::Start(const Network::StartOptions& opts) {
ignore_link_monitoring_ = opts.ignore_link_monitoring;
ipv4_gateway_found_ = false;
ipv6_gateway_found_ = false;
probing_configuration_ = opts.probing_configuration;
// accept_ra and link_protocol_ipv6 should not be set at the same time.
DCHECK(!(opts.accept_ra && link_protocol_ipv6_properties_));
// TODO(b/232177767): Log the StartOptions and other parameters.
if (state_ != State::kIdle) {
LOG(INFO) << logging_tag_
<< ": Network has been started, stop it before starting with the "
"new options";
StopInternal(/*is_failure=*/false, /*trigger_callback=*/false);
}
EnableARPFiltering();
// If the execution of this function fails, StopInternal() will be called and
// turn the state to kIdle.
state_ = State::kConfiguring;
bool ipv6_started = false;
if (opts.accept_ra) {
slaac_controller_ = CreateSLAACController();
slaac_controller_->RegisterCallback(
base::BindRepeating(&Network::OnUpdateFromSLAAC, AsWeakPtr()));
slaac_controller_->Start();
ipv6_started = true;
}
if (ipv6_static_properties_) {
dispatcher_->PostTask(
FROM_HERE,
base::BindOnce(&Network::ConfigureStaticIPv6Address, AsWeakPtr()));
ipv6_started = true;
}
if (link_protocol_ipv6_properties_) {
proc_fs_->SetIPFlag(IPAddress::kFamilyIPv6, ProcFsStub::kIPFlagDisableIPv6,
"0");
set_ip6config(
std::make_unique<IPConfig>(control_interface_, interface_name_));
ip6config_->set_properties(*link_protocol_ipv6_properties_);
dispatcher_->PostTask(FROM_HERE,
base::BindOnce(&Network::SetupConnection, AsWeakPtr(),
ip6config_.get()));
ipv6_started = true;
}
// Note that currently, the existence of ipconfig_ indicates if the IPv4 part
// of Network has been started.
bool dhcp_started = false;
if (opts.dhcp) {
auto dhcp_opts = *opts.dhcp;
if (static_network_config_.ipv4_address_cidr) {
dhcp_opts.use_arp_gateway = false;
}
dhcp_controller_ = dhcp_provider_->CreateController(interface_name_,
dhcp_opts, technology_);
dhcp_controller_->RegisterCallbacks(
base::BindRepeating(&Network::OnIPConfigUpdatedFromDHCP, AsWeakPtr()),
base::BindRepeating(&Network::OnDHCPDrop, AsWeakPtr()));
set_ipconfig(std::make_unique<IPConfig>(control_interface_, interface_name_,
IPConfig::kTypeDHCP));
dhcp_started = dhcp_controller_->RequestIP();
} else if (link_protocol_ipv4_properties_) {
set_ipconfig(
std::make_unique<IPConfig>(control_interface_, interface_name_));
ipconfig_->set_properties(*link_protocol_ipv4_properties_);
} else {
// This could happen on IPv6-only networks.
DCHECK(ipv6_started);
}
if (link_protocol_ipv4_properties_ ||
static_network_config_.ipv4_address_cidr) {
// If the parameters contain an IP address, apply them now and bring the
// interface up. When DHCP information arrives, it will supplement the
// static information.
dispatcher_->PostTask(
FROM_HERE, base::BindOnce(&Network::OnIPv4ConfigUpdated, AsWeakPtr()));
} else if (!dhcp_started && !ipv6_started) {
// Neither v4 nor v6 is running, trigger the failure callback directly.
LOG(WARNING) << logging_tag_ << ": Failed to start IP provisioning";
dispatcher_->PostTask(
FROM_HERE,
base::BindOnce(&Network::StopInternal, AsWeakPtr(),
/*is_failure=*/true, /*trigger_callback=*/true));
}
LOG(INFO) << logging_tag_ << ": Started IP provisioning, dhcp: "
<< (dhcp_started ? "started" : "no")
<< ", accept_ra: " << std::boolalpha << opts.accept_ra;
if (static_network_config_.ipv4_address_cidr.has_value()) {
LOG(INFO) << logging_tag_ << ": has IPv4 static config "
<< static_network_config_;
}
if (link_protocol_ipv4_properties_) {
LOG(INFO) << logging_tag_ << ": has IPv4 link properties "
<< *link_protocol_ipv4_properties_;
}
if (ipv6_static_properties_) {
LOG(INFO) << logging_tag_ << ": has IPv6 static properties "
<< *ipv6_static_properties_;
}
if (link_protocol_ipv6_properties_) {
LOG(INFO) << logging_tag_ << ": has IPv6 link properties "
<< *link_protocol_ipv6_properties_;
}
}
std::unique_ptr<SLAACController> Network::CreateSLAACController() {
auto slaac_controller = std::make_unique<SLAACController>(
interface_index_, proc_fs_.get(), rtnl_handler_, dispatcher_);
return slaac_controller;
}
void Network::SetupConnection(IPConfig* ipconfig) {
DCHECK(ipconfig);
LOG(INFO) << logging_tag_ << ": Setting "
<< IPAddress::GetAddressFamilyName(
ipconfig->properties().address_family)
<< " connection";
if (connection_ == nullptr) {
connection_ = CreateConnection();
}
connection_->UpdateFromIPConfig(ipconfig->properties());
connection_->UpdateRoutingPolicy(GetAddresses());
if (state_ != State::kConnected && technology_ != Technology::kVPN) {
// The Network becomes connected, wait for 30 seconds to report its IP type.
// Skip VPN since it's already reported separately in VPNService.
dispatcher_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Network::ReportIPType,
weak_factory_for_connection_.GetWeakPtr()),
base::Seconds(30));
}
state_ = State::kConnected;
ConfigureStaticIPv6Address();
for (auto* ev : event_handlers_) {
ev->OnConnectionUpdated(interface_index_);
}
const bool ipconfig_changed = current_ipconfig_ != ipconfig;
current_ipconfig_ = ipconfig;
if (ipconfig_changed && !current_ipconfig_change_handler_.is_null()) {
current_ipconfig_change_handler_.Run();
}
}
std::unique_ptr<Connection> Network::CreateConnection() const {
return std::make_unique<Connection>(interface_index_, interface_name_,
fixed_ip_params_, technology_);
}
void Network::Stop() {
StopInternal(/*is_failure=*/false, /*trigger_callback=*/true);
}
void Network::StopInternal(bool is_failure, bool trigger_callback) {
std::stringstream ss;
if (ipconfig()) {
ss << ", IPv4 config: " << *ipconfig();
}
if (ip6config()) {
ss << ", IPv6 config: " << *ip6config();
}
LOG(INFO) << logging_tag_ << ": Stopping "
<< (is_failure ? " after failure" : " normally") << ss.str();
weak_factory_for_connection_.InvalidateWeakPtrs();
network_validation_result_.reset();
StopPortalDetection();
StopConnectionDiagnostics();
const bool should_trigger_callback =
state_ != State::kIdle && trigger_callback;
bool ipconfig_changed = false;
if (dhcp_controller_) {
dhcp_controller_->ReleaseIP(DHCPController::kReleaseReasonDisconnect);
dhcp_controller_ = nullptr;
}
if (ipconfig()) {
set_ipconfig(nullptr);
link_protocol_ipv4_properties_ = {};
ipconfig_changed = true;
}
if (slaac_controller_) {
slaac_controller_->Stop();
slaac_controller_ = nullptr;
}
if (ip6config()) {
set_ip6config(nullptr);
link_protocol_ipv6_properties_ = {};
ipconfig_changed = true;
}
// Emit updated IP configs if there are any changes.
if (ipconfig_changed) {
for (auto* ev : event_handlers_) {
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
}
if (current_ipconfig_) {
current_ipconfig_ = nullptr;
if (!current_ipconfig_change_handler_.is_null()) {
current_ipconfig_change_handler_.Run();
}
}
state_ = State::kIdle;
connection_ = nullptr;
if (should_trigger_callback) {
for (auto* ev : event_handlers_) {
ev->OnNetworkStopped(interface_index_, is_failure);
}
}
}
void Network::InvalidateIPv6Config() {
SLOG(2) << logging_tag_ << ": " << __func__;
if (!ip6config_) {
return;
}
SLOG(2) << logging_tag_ << "Waiting for new IPv6 configuration";
if (slaac_controller_) {
slaac_controller_->Stop();
slaac_controller_->Start();
}
set_ip6config(nullptr);
for (auto* ev : event_handlers_) {
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
}
void Network::OnIPv4ConfigUpdated() {
if (!ipconfig()) {
return;
}
saved_network_config_ =
ipconfig()->ApplyNetworkConfig(static_network_config_);
if (static_network_config_.ipv4_address_cidr.has_value() &&
dhcp_controller_) {
// If we are using a statically configured IP address instead of a leased IP
// address, release any acquired lease so it may be used by others. This
// allows us to merge other non-leased parameters (like DNS) when they're
// available from a DHCP server and not overridden by static parameters, but
// at the same time we avoid taking up a dynamic IP address the DHCP server
// could assign to someone else who might actually use it.
dhcp_controller_->ReleaseIP(DHCPController::kReleaseReasonStaticIP);
}
SetupConnection(ipconfig());
for (auto* ev : event_handlers_) {
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
}
void Network::OnStaticIPConfigChanged(const NetworkConfig& config) {
static_network_config_ = config;
if (state_ == State::kIdle) {
// This can happen after service is selected but before the Network starts.
return;
}
if (ipconfig() == nullptr) {
LOG(WARNING)
<< interface_name_
<< " is not configured with IPv4. Skip applying static IP config";
return;
}
LOG(INFO) << logging_tag_ << ": static IPv4 config update " << config;
// Clear the previously applied static IP parameters. The new config will be
// applied in ConfigureStaticIPTask().
ipconfig()->ApplyNetworkConfig(saved_network_config_);
saved_network_config_ = {};
// TODO(b/232177767): Apply the static IP parameters no matter if there is a
// valid IPv4 in it.
if (config.ipv4_address_cidr.has_value()) {
dispatcher_->PostTask(
FROM_HERE, base::BindOnce(&Network::OnIPv4ConfigUpdated, AsWeakPtr()));
}
if (dhcp_controller_) {
// Trigger DHCP renew.
dhcp_controller_->RenewIP();
}
}
void Network::RegisterCurrentIPConfigChangeHandler(
base::RepeatingClosure handler) {
current_ipconfig_change_handler_ = handler;
}
IPConfig* Network::GetCurrentIPConfig() const {
// Make sure that the |current_ipconfig_| is still valid.
if (current_ipconfig_ == ipconfig_.get()) {
return current_ipconfig_;
}
if (current_ipconfig_ == ip6config_.get()) {
return current_ipconfig_;
}
return nullptr;
}
void Network::OnIPConfigUpdatedFromDHCP(const IPConfig::Properties& properties,
bool new_lease_acquired) {
// |dhcp_controller_| cannot be empty when the callback is invoked.
DCHECK(dhcp_controller_);
DCHECK(ipconfig());
LOG(INFO) << logging_tag_ << ": DHCP lease "
<< (new_lease_acquired ? "acquired " : "update ") << properties;
if (new_lease_acquired) {
for (auto* ev : event_handlers_) {
ev->OnGetDHCPLease(interface_index_);
}
}
ipconfig()->UpdateProperties(properties);
OnIPv4ConfigUpdated();
// TODO(b/232177767): OnIPv4ConfiguredWithDHCPLease() should be called inside
// Network::OnIPv4ConfigUpdated() and only if SetupConnection() happened as a
// result of the new lease. The current call pattern reproduces the same
// conditions as before crrev/c/3840983.
if (new_lease_acquired) {
for (auto* ev : event_handlers_) {
ev->OnIPv4ConfiguredWithDHCPLease(interface_index_);
}
}
}
void Network::OnDHCPDrop(bool is_voluntary) {
LOG(INFO) << logging_tag_ << ": " << __func__
<< ": is_voluntary = " << is_voluntary;
if (!is_voluntary) {
for (auto* ev : event_handlers_) {
ev->OnGetDHCPFailure(interface_index_);
}
}
// |dhcp_controller_| cannot be empty when the callback is invoked.
DCHECK(dhcp_controller_);
DCHECK(ipconfig());
if (static_network_config_.ipv4_address_cidr.has_value()) {
// Consider three cases:
//
// 1. We're here because DHCP failed while starting up. There
// are two subcases:
// a. DHCP has failed, and Static IP config has _not yet_
// completed. It's fine to do nothing, because we'll
// apply the static config shortly.
// b. DHCP has failed, and Static IP config has _already_
// completed. It's fine to do nothing, because we can
// continue to use the static config that's already
// been applied.
//
// 2. We're here because a previously valid DHCP configuration
// is no longer valid. There's still a static IP config,
// because the condition in the if clause evaluated to true.
// Furthermore, the static config includes an IP address for
// us to use.
//
// The current configuration may include some DHCP
// parameters, overridden by any static parameters
// provided. We continue to use this configuration, because
// the only configuration element that is leased to us (IP
// address) will be overridden by a static parameter.
//
// TODO(b/261681299): When this function is triggered by a renew failure,
// the current IPConfig can be a mix of DHCP and static IP. We need to
// revert the DHCP part.
return;
}
ipconfig()->ResetProperties();
for (auto* ev : event_handlers_) {
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
// Fallback to IPv6 if possible.
if (ip6config() && ip6config()->properties().HasIPAddressAndDNS()) {
LOG(INFO) << logging_tag_ << ": operating in IPv6-only because of "
<< (is_voluntary ? "receiving DHCP option 108" : "DHCP failure");
if (!connection_ || !connection_->IsIPv6()) {
// Destroy the IPv4 connection (if exists) to clear the state in kernel at
// first. It is possible that this function is called when we have a valid
// DHCP lease now (e.g., triggered by a renew failure). We need to
// withdraw the effect of the previous IPv4 lease at first. Static IP is
// handled above so it's guaranteed that there is no valid IPv4 lease.
// Also see b/261681299.
connection_ = nullptr;
SetupConnection(ip6config());
}
return;
}
if (is_voluntary) {
if (state_ == State::kConfiguring) {
// DHCPv4 reports to prefer v6 only. Continue to wait for SLAAC.
return;
} else {
LOG(ERROR) << logging_tag_
<< ": DHCP option 108 received but no valid IPv6 network is "
"usable. Likely a network configuration error.";
}
}
StopInternal(/*is_failure=*/true, /*trigger_callback=*/true);
}
bool Network::RenewDHCPLease() {
if (!dhcp_controller_) {
return false;
}
SLOG(2) << logging_tag_ << ": renewing DHCP lease";
// If RenewIP() fails, DHCPController will output a ERROR log.
return dhcp_controller_->RenewIP();
}
void Network::DestroyDHCPLease(const std::string& name) {
dhcp_provider_->DestroyLease(name);
}
std::optional<base::TimeDelta> Network::TimeToNextDHCPLeaseRenewal() {
if (!dhcp_controller_) {
return std::nullopt;
}
return dhcp_controller_->TimeToLeaseExpiry();
}
void Network::ConfigureStaticIPv6Address() {
if (!ipv6_static_properties_ || ipv6_static_properties_->address.empty()) {
return;
}
const auto local = IPAddress::CreateFromStringAndPrefix(
ipv6_static_properties_->address, ipv6_static_properties_->subnet_prefix,
IPAddress::kFamilyIPv6);
if (!local.has_value()) {
LOG(ERROR) << logging_tag_ << ": Local address "
<< ipv6_static_properties_->address << " is invalid";
return;
}
LOG(INFO) << logging_tag_ << ": configuring static IPv6 address " << *local;
rtnl_handler_->AddInterfaceAddress(interface_index_, *local,
local->GetDefaultBroadcast());
}
void Network::OnUpdateFromSLAAC(SLAACController::UpdateType update_type) {
if (update_type == SLAACController::UpdateType::kAddress) {
OnIPv6AddressChanged();
} else if (update_type == SLAACController::UpdateType::kRDNSS) {
OnIPv6DnsServerAddressesChanged();
}
}
void Network::OnIPv6AddressChanged() {
auto slaac_addresses = slaac_controller_->GetAddresses();
if (slaac_addresses.size() == 0) {
if (ip6config()) {
LOG(INFO) << logging_tag_ << ": Removing all observed IPv6 addresses";
set_ip6config(nullptr);
for (auto* ev : event_handlers_) {
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
// TODO(b/232177767): We may lose the whole IP connectivity here (if there
// is no IPv4).
}
return;
}
const auto& primary_address = slaac_addresses[0];
CHECK_EQ(primary_address.family(), IPAddress::kFamilyIPv6);
IPConfig::Properties properties;
if (!primary_address.IntoString(&properties.address)) {
LOG(ERROR) << logging_tag_
<< ": Unable to convert IPv6 address into a string";
return;
}
properties.subnet_prefix = primary_address.prefix();
RoutingTableEntry default_route(IPAddress::kFamilyIPv6);
if (routing_table_->GetDefaultRouteFromKernel(interface_index_,
&default_route)) {
if (!default_route.gateway.IntoString(&properties.gateway)) {
LOG(ERROR) << logging_tag_
<< ": Unable to convert IPv6 gateway into a string";
return;
}
} else {
// The kernel normally populates the default route before it performs
// a neighbor solicitation for the new address, so it shouldn't be
// missing at this point.
LOG(WARNING) << logging_tag_
<< ": No default route for global IPv6 address "
<< properties.address;
}
// No matter whether the primary address changes, any address change will
// need to trigger address-based routing rule to be updated.
if (connection_) {
connection_->UpdateRoutingPolicy(GetAddresses());
}
std::string addresses_str;
std::string sep;
for (const auto& addr : slaac_addresses) {
addresses_str += sep;
addresses_str += addr.ToString();
sep = ",";
}
LOG(INFO) << logging_tag_ << ": Updating IPv6 addresses to [" << addresses_str
<< "]";
if (!ip6config()) {
set_ip6config(
std::make_unique<IPConfig>(control_interface_, interface_name_));
} else if (properties.address == ip6config()->properties().address &&
properties.subnet_prefix ==
ip6config()->properties().subnet_prefix &&
properties.gateway == ip6config()->properties().gateway) {
SLOG(2) << logging_tag_ << ": " << __func__ << ": primary address for "
<< interface_name_ << " is unchanged";
return;
}
properties.address_family = IPAddress::kFamilyIPv6;
properties.method = kTypeIPv6;
// It is possible for device to receive DNS server notification before IP
// address notification, so preserve the saved DNS server if it exist.
properties.dns_servers = ip6config()->properties().dns_servers;
if (ipv6_static_properties_ &&
!ipv6_static_properties_->dns_servers.empty()) {
properties.dns_servers = ipv6_static_properties_->dns_servers;
}
ip6config()->set_properties(properties);
for (auto* ev : event_handlers_) {
ev->OnGetSLAACAddress(interface_index_);
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
OnIPv6ConfigUpdated();
// TODO(b/232177767): OnIPv6ConfiguredWithSLAACAddress() should be called
// inside Network::OnIPv6ConfigUpdated() and only if SetupConnection()
// happened as a result of the new address (ignoring IPv4 and assuming Network
// is fully dual-stack). The current call pattern reproduces the same
// conditions as before crrev/c/3840983.
for (auto* ev : event_handlers_) {
ev->OnIPv6ConfiguredWithSLAACAddress(interface_index_);
}
}
void Network::OnIPv6ConfigUpdated() {
if (!ip6config()) {
LOG(WARNING) << logging_tag_ << ": " << __func__
<< " called but |ip6config_| is empty";
return;
}
// Apply search domains from StaticIPConfig, if the list is not empty and
// there is a change. This is a workaround to apply search domains from policy
// on IPv6-only network (b/265680125), since StaticIPConfig is only applied to
// IPv4 now. This workaround can be removed after we unify IPv4 and IPv6
// config into a single object. Since currently we don't update it in
// OnStaticIPConfigChanged() (because it will make the code more tricky to
// handle IPv6 in that code path), SearchDomains change will not take effect
// on a connected network. This limitation should be acceptable that it cannot
// be changed via UI, but only through policy.
const auto& search_domains = static_network_config_.dns_search_domains;
if (search_domains.has_value() && !search_domains->empty() &&
ip6config()->properties().domain_search != *search_domains) {
ip6config()->UpdateSearchDomains(*search_domains);
}
// Setup connection using IPv6 configuration only if the IPv6 configuration is
// ready for connection (contained both IP address and DNS servers), and there
// is no existing IPv4 connection. We always prefer IPv4 configuration over
// IPv6.
if (ip6config()->properties().HasIPAddressAndDNS() &&
(!IsConnected() || connection_->IsIPv6())) {
SetupConnection(ip6config());
}
}
void Network::OnIPv6DnsServerAddressesChanged() {
std::vector<IPAddress> rdnss = slaac_controller_->GetRDNSSAddresses();
if (rdnss.size() == 0) {
if (!ip6config()) {
return;
}
LOG(INFO) << logging_tag_ << ": Removing all observed IPv6 DNS addresses";
ip6config()->UpdateDNSServers(std::vector<std::string>());
for (auto* ev : event_handlers_) {
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
return;
}
if (!ip6config()) {
set_ip6config(
std::make_unique<IPConfig>(control_interface_, interface_name_));
}
std::vector<std::string> addresses_str;
for (const auto& ip : rdnss) {
std::string address_str;
if (!ip.IntoString(&address_str)) {
LOG(ERROR) << interface_name_
<< ": Unable to convert IPv6 address into a string!";
continue;
}
addresses_str.push_back(address_str);
}
// Done if no change in server addresses.
if (ip6config()->properties().dns_servers == addresses_str) {
SLOG(2) << logging_tag_ << ": " << __func__ << " IPv6 DNS server list for "
<< interface_name_ << " is unchanged.";
return;
}
LOG(INFO) << logging_tag_ << ": Updating DNS IPv6 addresses to ["
<< base::JoinString(addresses_str, ",") << "]";
ip6config()->UpdateDNSServers(std::move(addresses_str));
for (auto* ev : event_handlers_) {
ev->OnIPConfigsPropertyUpdated(interface_index_);
}
OnIPv6ConfigUpdated();
}
void Network::EnableARPFiltering() {
proc_fs_->SetIPFlag(IPAddress::kFamilyIPv4, ProcFsStub::kIPFlagArpAnnounce,
ProcFsStub::kIPFlagArpAnnounceBestLocal);
proc_fs_->SetIPFlag(IPAddress::kFamilyIPv4, ProcFsStub::kIPFlagArpIgnore,
ProcFsStub::kIPFlagArpIgnoreLocalOnly);
}
void Network::SetPriority(uint32_t priority, bool is_primary_physical) {
if (!connection_) {
LOG(WARNING) << logging_tag_ << ": " << __func__
<< " called but no connection exists";
return;
}
connection_->SetPriority(priority, is_primary_physical);
}
bool Network::IsDefault() const {
if (!connection_) {
return false;
}
return connection_->IsDefault();
}
void Network::SetUseDNS(bool enable) {
if (!connection_) {
LOG(WARNING) << logging_tag_ << ": " << __func__
<< " called but no connection exists";
return;
}
connection_->SetUseDNS(enable);
}
std::vector<IPAddress> Network::GetAddresses() const {
std::vector<IPAddress> result;
if (slaac_controller_) {
result = slaac_controller_->GetAddresses();
}
const auto insert_addr = [&result](const std::string& addr_str, int prefix) {
auto addr = IPAddress::CreateFromStringAndPrefix(addr_str, prefix);
if (!addr.has_value()) {
LOG(ERROR) << "Invalid IP address: " << addr_str << "/" << prefix;
return;
}
result.push_back(std::move(*addr));
};
if (link_protocol_ipv6_properties_ &&
link_protocol_ipv6_properties_->subnet_prefix > 0) {
insert_addr(link_protocol_ipv6_properties_->address,
link_protocol_ipv6_properties_->subnet_prefix);
}
if (ipconfig() && ipconfig()->properties().subnet_prefix > 0) {
insert_addr(ipconfig()->properties().address,
ipconfig()->properties().subnet_prefix);
}
// link_protocol_ipv4_properties_ should already be reflected in ipconfig_
return result;
}
void Network::OnNeighborReachabilityEvent(
const patchpanel::Client::NeighborReachabilityEvent& event) {
using Role = patchpanel::Client::NeighborRole;
using Status = patchpanel::Client::NeighborStatus;
const auto ip_address = IPAddress::CreateFromString(event.ip_addr);
if (!ip_address.has_value()) {
LOG(ERROR) << logging_tag_ << ": " << __func__ << ": invalid IP address "
<< event.ip_addr;
return;
}
switch (event.status) {
case Status::kFailed:
case Status::kReachable:
break;
default:
LOG(ERROR) << logging_tag_ << ": " << __func__ << ": invalid event "
<< event;
return;
}
if (event.status == Status::kFailed) {
metrics_->NotifyNeighborLinkMonitorFailure(
technology_, ip_address->family(), event.role);
}
if (state_ == State::kIdle) {
LOG(INFO) << logging_tag_ << ": " << __func__ << ": Idle state, ignoring "
<< event;
return;
}
if (ignore_link_monitoring_) {
LOG(INFO) << logging_tag_ << ": " << __func__
<< " link monitor events ignored, ignoring " << event;
return;
}
if (event.role == Role::kGateway ||
event.role == Role::kGatewayAndDnsServer) {
IPConfig* ipconfig;
bool* gateway_found;
if (ip_address->family() == IPAddress::kFamilyIPv4) {
ipconfig = ipconfig_.get();
gateway_found = &ipv4_gateway_found_;
} else if (ip_address->family() == IPAddress::kFamilyIPv6) {
ipconfig = ip6config_.get();
gateway_found = &ipv6_gateway_found_;
} else {
NOTREACHED();
return;
}
// It is impossible to observe a reachability event for the current gateway
// before Network knows the IPConfig for the current connection: patchpanel
// would not emit reachability event for the correct connection yet.
if (!ipconfig) {
LOG(INFO) << logging_tag_ << ": " << __func__ << ": "
<< IPAddress::GetAddressFamilyName(ip_address->family())
<< " not configured, ignoring neighbor reachability event "
<< event;
return;
}
// Ignore reachability events related to a prior connection.
if (ipconfig->properties().gateway != event.ip_addr) {
LOG(INFO) << logging_tag_ << ": " << __func__
<< ": ignored neighbor reachability event with conflicting "
"gateway address "
<< event;
return;
}
*gateway_found = true;
}
for (auto* ev : event_handlers_) {
ev->OnNeighborReachabilityEvent(interface_index_, *ip_address, event.role,
event.status);
}
}
std::vector<std::string> Network::dns_servers() const {
if (!connection_) {
return {};
}
return connection_->dns_servers();
}
const IPAddress* Network::local() const {
if (!connection_) {
return nullptr;
}
return &connection_->local();
}
bool Network::StartPortalDetection(bool reset) {
if (!IsConnected()) {
LOG(INFO) << logging_tag_
<< ": Cannot start portal detection: Network is not connected";
return false;
}
if (!reset && IsPortalDetectionInProgress()) {
LOG(INFO) << logging_tag_ << ": Portal detection is already running.";
return true;
}
DCHECK(connection_ != nullptr);
portal_detector_ = CreatePortalDetector();
if (!portal_detector_->Start(interface_name_, connection_->local(),
connection_->dns_servers(), logging_tag_)) {
LOG(ERROR) << logging_tag_ << ": Portal detection failed to start.";
portal_detector_.reset();
return false;
}
LOG(INFO) << logging_tag_ << ": Portal detection started.";
for (auto* ev : event_handlers_) {
ev->OnNetworkValidationStart(interface_index_);
}
return true;
}
bool Network::RestartPortalDetection() {
if (!portal_detector_) {
LOG(ERROR) << logging_tag_
<< ": Portal detection was not started, cannot restart";
return false;
}
DCHECK(connection_ != nullptr);
if (!portal_detector_->Restart(interface_name_, connection_->local(),
connection_->dns_servers(), logging_tag_)) {
LOG(ERROR) << logging_tag_ << ": Portal detection failed to restart.";
StopPortalDetection();
return false;
}
LOG(INFO) << logging_tag_ << ": Portal detection restarted.";
// TODO(b/216351118): this ignores the portal detection retry delay. The
// callback should be triggered when the next attempt starts, not when it
// is scheduled.
for (auto* ev : event_handlers_) {
ev->OnNetworkValidationStart(interface_index_);
}
return true;
}
void Network::StopPortalDetection() {
if (IsPortalDetectionInProgress()) {
LOG(INFO) << logging_tag_ << ": Portal detection stopped.";
for (auto* ev : event_handlers_) {
ev->OnNetworkValidationStop(interface_index_);
}
}
portal_detector_.reset();
}
bool Network::IsPortalDetectionInProgress() const {
return portal_detector_ && portal_detector_->IsInProgress();
}
std::unique_ptr<PortalDetector> Network::CreatePortalDetector() {
return std::make_unique<PortalDetector>(
dispatcher_, probing_configuration_,
base::BindRepeating(&Network::OnPortalDetectorResult, AsWeakPtr()));
}
void Network::OnPortalDetectorResult(const PortalDetector::Result& result) {
std::string previous_validation_state = "unevaluated";
if (network_validation_result_) {
previous_validation_state = PortalDetector::ValidationStateToString(
network_validation_result_->GetValidationState());
}
LOG(INFO) << logging_tag_
<< ": OnPortalDetectorResult: " << previous_validation_state
<< " -> " << result.GetValidationState();
int portal_status = Metrics::PortalDetectionResultToEnum(result);
metrics_->SendEnumToUMA(Metrics::kMetricPortalResult, technology_,
portal_status);
if (!IsConnected()) {
LOG(INFO) << logging_tag_
<< ": Portal detection completed but Network is not connected";
return;
}
network_validation_result_ = result;
for (auto* ev : event_handlers_) {
ev->OnNetworkValidationResult(interface_index_, result);
}
// If portal detection was not conclusive, also start additional connection
// diagnostics for the current network connection.
switch (result.GetValidationState()) {
case PortalDetector::ValidationState::kNoConnectivity:
case PortalDetector::ValidationState::kPartialConnectivity:
StartConnectionDiagnostics();
break;
case PortalDetector::ValidationState::kInternetConnectivity:
// Conclusive result that allows the Service to transition to the
// "online" state.
case PortalDetector::ValidationState::kPortalRedirect:
// Conclusive result that allows to start the portal detection sign-in
// flow.
break;
}
}
void Network::StartConnectionDiagnostics() {
if (!IsConnected()) {
LOG(INFO) << logging_tag_
<< ": Not connected, cannot start connection diagnostics";
return;
}
DCHECK(connection_ != nullptr);
connection_diagnostics_ = CreateConnectionDiagnostics(*connection_);
if (!connection_diagnostics_->Start(probing_configuration_.portal_http_url)) {
connection_diagnostics_.reset();
LOG(WARNING) << logging_tag_ << ": Failed to start connection diagnostics";
return;
}
LOG(INFO) << logging_tag_ << ": Connection diagnostics started";
}
void Network::StopConnectionDiagnostics() {
LOG(INFO) << logging_tag_ << ": Connection diagnostics stopping";
connection_diagnostics_.reset();
}
std::unique_ptr<ConnectionDiagnostics> Network::CreateConnectionDiagnostics(
const Connection& connection) {
return std::make_unique<ConnectionDiagnostics>(
interface_name(), interface_index(), connection.local(),
connection.gateway(), connection.dns_servers(), dispatcher_, metrics_,
base::DoNothing());
}
bool Network::IsConnectedViaTether() const {
if (!ipconfig_) {
return false;
}
const auto& vendor_option =
ipconfig_->properties().vendor_encapsulated_options;
if (vendor_option.size() != strlen(kAndroidMeteredHotspotVendorOption)) {
return false;
}
return memcmp(kAndroidMeteredHotspotVendorOption, vendor_option.data(),
vendor_option.size()) == 0;
}
bool Network::HasInternetConnectivity() const {
return network_validation_result_.has_value() &&
network_validation_result_->GetValidationState() ==
PortalDetector::ValidationState::kInternetConnectivity;
}
void Network::ReportIPType() {
const bool has_ipv4 = ipconfig() && !ipconfig()->properties().address.empty();
const bool has_ipv6 =
ip6config() && !ip6config()->properties().address.empty();
Metrics::IPType ip_type = Metrics::kIPTypeUnknown;
if (has_ipv4 && has_ipv6) {
ip_type = Metrics::kIPTypeDualStack;
} else if (has_ipv4) {
ip_type = Metrics::kIPTypeIPv4Only;
} else if (has_ipv6) {
ip_type = Metrics::kIPTypeIPv6Only;
}
metrics_->SendEnumToUMA(Metrics::kMetricIPType, technology_, ip_type);
}
} // namespace shill