blob: 8460c94b4d0dc6a395f597dc211574fbc979ccaf [file] [edit]
// Copyright 2018 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/vpn/vpn_service.h"
#include <algorithm>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/check_op.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/net-base/ipv4_address.h>
#include <chromeos/net-base/network_config.h>
#include "shill/dbus/dbus_control.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/profile.h"
#include "shill/static_ip_parameters.h"
#include "shill/store/key_value_store.h"
#include "shill/store/property_accessor.h"
#include "shill/store/store_interface.h"
#include "shill/technology.h"
#include "shill/vpn/vpn_driver.h"
#include "shill/vpn/vpn_metrics.h"
#include "shill/vpn/vpn_provider.h"
#include "shill/vpn/vpn_types.h"
#include "shill/vpn/vpn_util.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kVPN;
static std::string ObjectID(const VPNService* s) {
return s->log_name();
}
} // namespace Logging
namespace {
constexpr uid_t kChronosUid = 1000;
// WireGuardDriver used to use StaticIPConfig to store the local IP address but
// is using a specific property now. This function is for migrating the profile
// data, by the following two actions:
// - Apply the IPv4 address in |static_config| to the WireGuard.IPAddress
// property in |driver|, if |static_config| has an IPv4 address and the
// WireGuard.IPAddress property is empty.
// - Reset IPv4 address (with prefix length) in |static_config|.
//
// Returns whether |static_config| is updated.
bool UpdateWireGuardDriverIPv4Address(net_base::NetworkConfig* static_config,
VPNDriver* driver) {
if (driver->vpn_type() != VPNType::kWireGuard) {
return false;
}
auto static_config_address = static_config->ipv4_address;
if (!static_config_address) {
return false;
}
// No matter whether the parsing result is valid or not, reset the property.
static_config->ipv4_address = std::nullopt;
const auto& current_addrs =
driver->const_args()->Lookup<std::vector<std::string>>(
kWireGuardIPAddress, {});
if (!current_addrs.empty()) {
return true;
}
const std::vector<std::string> addrs_to_set{
static_config_address->address().ToString()};
driver->args()->Set<std::vector<std::string>>(kWireGuardIPAddress,
addrs_to_set);
return true;
}
Service::ConnectFailure VPNEndReasonToServiceFailure(VPNEndReason reason) {
switch (reason) {
case VPNEndReason::kDisconnectRequest:
return Service::kFailureDisconnect;
case VPNEndReason::kNetworkChange:
return Service::kFailureConnect;
case VPNEndReason::kConnectFailureAuthPPP:
return Service::kFailurePPPAuth;
case VPNEndReason::kConnectFailureAuthCert:
// This will be shown as "Authentication certificate rejected by network"
// in UI.
return Service::kFailureIPsecCertAuth;
case VPNEndReason::kConnectFailureAuthUserPassword:
// This will be shown as "Username/password incorrect or EAP-auth failed"
// in UI.
return Service::kFailureEAPAuthentication;
case VPNEndReason::kConnectFailureDNSLookup:
return Service::kFailureDNSLookup;
case VPNEndReason::kConnectTimeout:
return Service::kFailureConnect;
case VPNEndReason::kInvalidConfig:
return Service::kFailureConnect;
case VPNEndReason::kFailureInternal:
return Service::kFailureInternal;
case VPNEndReason::kFailureUnknown:
return Service::kFailureConnect;
}
}
bool IsUsedAsDefaultGateway(const net_base::NetworkConfig& config) {
// If there is no included route, a default route will be installed.
if (config.included_route_prefixes.empty()) {
return true;
}
// If there is no direct information, infer it from the included routes.
return VPNUtil::InferIsUsedAsDefaultGatewayFromIncludedRoutes(
config.included_route_prefixes);
}
// b/328814622: Destroy all Chrome sockets which are bound to the physical
// network to avoid traffic leak.
void DestroyChromeSocketsOnPhysical(VPNDriver* driver,
Service* physical_service,
bool used_as_default_gateway) {
// Skip if the VPN is a Chrome third-party app, since the socket for the VPN
// connection itself will also get destroyed in this case.
if (driver->vpn_type() == VPNType::kThirdParty) {
LOG(INFO) << __func__ << ": Skip since VPN is a Chrome third-party app";
return;
}
if (!physical_service || !physical_service->attached_network()) {
LOG(ERROR) << __func__ << ": Skip since default network is empty";
return;
}
// Skip if the VPN is not intentionally used as default gateway, since it may
// not be expected to destroy them. Ideally we want to do a routing lookup for
// each socket to decide whether it should be destroyed, but it might be too
// complicated.
if (!used_as_default_gateway) {
LOG(INFO) << __func__ << ": Skip since VPN is split-routing";
return;
}
physical_service->attached_network()->DestroySockets(kChronosUid);
}
} // namespace
VPNService::VPNService(Manager* manager, std::unique_ptr<VPNDriver> driver)
: Service(manager, Technology::kVPN), driver_(std::move(driver)) {
DCHECK(driver_);
log_name_ = base::StrCat({"vpn_", VPNTypeEnumToString(driver_->vpn_type()),
"_", base::NumberToString(serial_number())});
SetConnectable(true);
set_save_credentials(false);
mutable_store()->RegisterDerivedString(
kPhysicalTechnologyProperty,
StringAccessor(new CustomAccessor<VPNService, std::string>(
this, &VPNService::GetPhysicalTechnologyProperty, nullptr)));
this->manager()->AddDefaultServiceObserver(this);
}
VPNService::~VPNService() {
manager()->RemoveDefaultServiceObserver(this);
}
void VPNService::OnConnect(Error* error) {
manager()->vpn_provider()->DisconnectAll();
// Note that this must be called after VPNProvider::DisconnectAll. While most
// VPNDrivers create their own Devices, ArcVpnDriver shares the same
// VirtualDevice (VPNProvider::arc_device), so Disconnect()ing an ARC
// VPNService after completing the connection for a new ARC VPNService will
// cause the arc_device to be disabled at the end of this call.
if (manager()->IsTechnologyProhibited(Technology::kVPN)) {
Error::PopulateAndLog(FROM_HERE, error, Error::kPermissionDenied,
"VPN is prohibited.");
return;
}
SetState(ConnectState::kStateAssociating);
driver_->driver_metrics()->ReportConnecting();
// driver_ is owned by VPNService, so this is safe.
base::TimeDelta timeout = driver_->ConnectAsync(this);
StartDriverConnectTimeout(timeout);
}
void VPNService::OnDisconnect(Error* error, const char* reason) {
StopDriverConnectTimeout();
SetState(ConnectState::kStateDisconnecting);
driver_->driver_metrics()->ReportDisconnected(
VPNEndReason::kDisconnectRequest);
driver_->Disconnect();
CleanupDevice();
SetState(ConnectState::kStateIdle);
}
void VPNService::OnDriverConnected(const std::string& if_name, int if_index) {
StopDriverConnectTimeout();
if (!CreateDevice(if_name, if_index)) {
LOG(ERROR) << "Cannot create VPN device for " << if_name;
SetFailure(Service::kFailureInternal);
SetErrorDetails(Service::kErrorDetailsNone);
return;
}
// Note that this is the "driver connected" event instead of "network
// connected", i.e., time to configure network locally won't be included.
driver_->driver_metrics()->ReportConnected();
SetState(ConnectState::kStateConfiguring);
std::unique_ptr<net_base::NetworkConfig> network_config =
driver_->GetNetworkConfig();
// This needs to be done before ConfigureDevice() since we will lose the
// ownership of |network_config| there.
bool used_as_default_gateway = IsUsedAsDefaultGateway(*network_config);
ConfigureDevice(std::move(network_config));
DestroyChromeSocketsOnPhysical(driver(), default_physical_service_.get(),
used_as_default_gateway);
// Report the final NetworkConfig from the Network object attached to this
// service. This NetworkConfig should contains all the network config
// information for this VPN connection (except for the config can be changed
// after the connection is established, currently this should only be name
// servers). The assumption here is ConfigureDevice() above will call
// Network::Start() directly (i.e., without a PostTask()) to finish the setup
// in Network.
DCHECK(attached_network());
driver_->driver_metrics()->ReportNetworkConfig(
attached_network()->GetNetworkConfig());
}
void VPNService::OnDriverFailure(VPNEndReason failure,
std::string_view error_details) {
StopDriverConnectTimeout();
CleanupDevice();
SetErrorDetails(error_details);
SetFailure(VPNEndReasonToServiceFailure(failure));
driver_->driver_metrics()->ReportDisconnected(failure);
}
void VPNService::OnDriverReconnecting(base::TimeDelta timeout) {
driver_->driver_metrics()->ReportReconnecting();
StartDriverConnectTimeout(timeout);
SetState(Service::kStateAssociating);
// If physical network changes before driver connection finished, this could
// be called before device_ was initialized.
if (!device_) {
return;
}
device_->ResetConnection();
}
bool VPNService::CreateDevice(const std::string& if_name, int if_index) {
// Avoids recreating a VirtualDevice if the network interface is not changed.
if (device_ != nullptr && device_->link_name() == if_name &&
device_->interface_index() == if_index) {
return true;
}
// Resets af first to avoid crashing shill in some cases. See
// b/172228079#comment6.
device_ = nullptr;
const bool fixed_ip_params = driver_->vpn_type() == VPNType::kARC;
device_ = new VirtualDevice(manager(), if_name, if_index, Technology::kVPN,
fixed_ip_params);
return device_ != nullptr;
}
void VPNService::CleanupDevice() {
if (!device_) {
return;
}
device_->DropConnection();
device_->SetEnabled(false);
device_ = nullptr;
}
void VPNService::ConfigureDevice(
std::unique_ptr<net_base::NetworkConfig> network_config) {
if (!device_) {
LOG(DFATAL) << "Device not created yet.";
return;
}
device_->SetEnabled(true);
device_->SelectService(this);
device_->UpdateNetworkConfig(std::move(network_config));
}
std::string VPNService::GetStorageIdentifier() const {
return storage_id_;
}
// static
std::string VPNService::CreateStorageIdentifier(const KeyValueStore& args,
Error* error) {
const auto host = args.Lookup<std::string>(kProviderHostProperty, "");
if (host.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidProperty,
"Missing VPN host.");
return "";
}
const auto name = args.Lookup<std::string>(kNameProperty, "");
if (name.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidProperty,
"Missing VPN name.");
return "";
}
return SanitizeStorageIdentifier(
base::StringPrintf("vpn_%s_%s", host.c_str(), name.c_str()));
}
std::string VPNService::GetPhysicalTechnologyProperty(Error* error) {
if (!default_physical_service_) {
error->Populate(Error::kOperationFailed);
return "";
}
return default_physical_service_->GetTechnologyName();
}
RpcIdentifier VPNService::GetDeviceRpcId(Error* error) const {
if (!device_) {
error->Populate(Error::kNotFound, "Not associated with a device");
return DBusControl::NullRpcIdentifier();
}
return device_->GetRpcIdentifier();
}
bool VPNService::Load(const StoreInterface* storage) {
return Service::Load(storage) &&
driver_->Load(storage, GetStorageIdentifier());
}
void VPNService::MigrateDeprecatedStorage(StoreInterface* storage) {
Service::MigrateDeprecatedStorage(storage);
const std::string id = GetStorageIdentifier();
CHECK(storage->ContainsGroup(id));
// Can be removed after the next stepping stone version after M114. Note that
// a VPN service will not be saved automatically if there is no change on
// values, so we need to trigger a Save() on StaticIPParameters here manually.
if (UpdateWireGuardDriverIPv4Address(
mutable_static_ip_parameters()->mutable_config(), driver_.get())) {
mutable_static_ip_parameters()->Save(storage, id);
}
}
bool VPNService::Save(StoreInterface* storage) {
return Service::Save(storage) &&
driver_->Save(storage, GetStorageIdentifier(), save_credentials());
}
bool VPNService::Unload() {
// The base method also disconnects the service.
Service::Unload();
set_save_credentials(false);
driver_->UnloadCredentials();
// Ask the VPN provider to remove us from its list.
manager()->vpn_provider()->RemoveService(this);
return true;
}
void VPNService::InitDriverPropertyStore() {
driver_->InitPropertyStore(mutable_store());
}
bool VPNService::SupportsAlwaysOnVpn() {
// ARC VPNs are not supporting always-on VPN through Shill.
return driver()->vpn_type() != VPNType::kARC;
}
void VPNService::EnableAndRetainAutoConnect() {
// The base EnableAndRetainAutoConnect method also sets auto_connect_ to true
// which is not desirable for VPN services.
RetainAutoConnect();
}
bool VPNService::IsAutoConnectable(const char** reason) const {
if (!Service::IsAutoConnectable(reason)) {
return false;
}
// Don't auto-connect VPN services that have never connected. This improves
// the chances that the VPN service is connectable and avoids dialog popups.
if (!has_ever_connected()) {
*reason = kAutoConnNeverConnected;
return false;
}
// Don't auto-connect a VPN service if another VPN service is already active.
if (manager()->vpn_provider()->HasActiveService()) {
*reason = kAutoConnVPNAlreadyActive;
return false;
}
return true;
}
Service::TetheringState VPNService::GetTethering() const {
if (!IsConnected()) {
return TetheringState::kUnknown;
}
if (!default_physical_service_) {
return TetheringState::kUnknown;
}
return default_physical_service_->GetTethering();
}
bool VPNService::SetNameProperty(const std::string& name, Error* error) {
if (name == friendly_name()) {
return false;
}
LOG(INFO) << "SetNameProperty called for: " << log_name();
KeyValueStore* args = driver_->args();
args->Set<std::string>(kNameProperty, name);
const auto new_storage_id = CreateStorageIdentifier(*args, error);
if (new_storage_id.empty()) {
return false;
}
auto old_storage_id = storage_id_;
DCHECK_NE(old_storage_id, new_storage_id);
SetFriendlyName(name);
// Update the storage identifier before invoking DeleteEntry to prevent it
// from unloading this service.
storage_id_ = new_storage_id;
profile()->DeleteEntry(old_storage_id, nullptr);
profile()->UpdateService(this);
return true;
}
VirtualDeviceRefPtr VPNService::GetVirtualDevice() const {
return device_;
}
void VPNService::OnBeforeSuspend(ResultCallback callback) {
driver_->OnBeforeSuspend(std::move(callback));
}
void VPNService::OnAfterResume() {
driver_->OnAfterResume();
Service::OnAfterResume();
}
void VPNService::OnDefaultPhysicalServiceChanged(
const ServiceRefPtr& physical_service) {
SLOG(this, 2) << __func__ << "("
<< (physical_service ? physical_service->log_name() : "-")
<< ")";
bool default_physical_service_online =
physical_service && physical_service->IsOnline();
bool service_changed =
default_physical_service_.get() != physical_service.get();
if (!last_default_physical_service_online_ &&
default_physical_service_online) {
driver_->OnDefaultPhysicalServiceEvent(
VPNDriver::DefaultPhysicalServiceEvent::kUp);
} else if (last_default_physical_service_online_ &&
!default_physical_service_online) {
// The default physical service is not online, and nothing else is available
// right now. All we can do is wait.
SLOG(this, 2) << __func__ << " - physical service lost or is not online";
driver_->OnDefaultPhysicalServiceEvent(
VPNDriver::DefaultPhysicalServiceEvent::kDown);
} else if (last_default_physical_service_online_ &&
default_physical_service_online && service_changed) {
// The original service is no longer the default, but manager was able
// to find another physical service that is already Online.
driver_->OnDefaultPhysicalServiceEvent(
VPNDriver::DefaultPhysicalServiceEvent::kChanged);
}
last_default_physical_service_online_ = default_physical_service_online;
default_physical_service_ =
physical_service ? physical_service->AsWeakPtr() : nullptr;
}
void VPNService::StartDriverConnectTimeout(base::TimeDelta timeout) {
if (timeout == VPNDriver::kTimeoutNone) {
StopDriverConnectTimeout();
return;
}
LOG(INFO) << "Schedule VPN connect timeout: " << timeout.InSeconds()
<< " seconds.";
driver_connect_timeout_callback_.Reset(BindOnce(
&VPNService::OnDriverConnectTimeout, weak_factory_.GetWeakPtr()));
dispatcher()->PostDelayedTask(
FROM_HERE, driver_connect_timeout_callback_.callback(), timeout);
}
void VPNService::StopDriverConnectTimeout() {
SLOG(this, 2) << __func__;
driver_connect_timeout_callback_.Cancel();
}
void VPNService::OnDriverConnectTimeout() {
LOG(INFO) << "VPN connect timeout.";
driver_->OnConnectTimeout();
StopDriverConnectTimeout();
}
} // namespace shill