blob: 12376468154d30ba9b5ada29c0feef6d22ea1825 [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/vpn/vpn_service.h"
#include <algorithm>
#include <utility>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include "shill/connection.h"
#include "shill/control_interface.h"
#include "shill/key_value_store.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/ppp_device.h"
#include "shill/profile.h"
#include "shill/property_accessor.h"
#include "shill/store_interface.h"
#include "shill/technology.h"
#include "shill/vpn/vpn_driver.h"
#include "shill/vpn/vpn_provider.h"
using base::StringPrintf;
using std::string;
namespace shill {
const char VPNService::kAutoConnNeverConnected[] = "never connected";
const char VPNService::kAutoConnVPNAlreadyActive[] = "vpn already active";
VPNService::VPNService(Manager* manager, std::unique_ptr<VPNDriver> driver)
: Service(manager, Technology::kVPN), driver_(std::move(driver)) {
if (driver_) {
set_log_name("vpn_" + driver_->GetProviderType() + "_" +
base::NumberToString(serial_number()));
} else {
// |driver| may be null in tests.
set_log_name("vpn_" + base::NumberToString(serial_number()));
}
SetConnectable(true);
set_save_credentials(false);
mutable_store()->RegisterDerivedString(
kPhysicalTechnologyProperty,
StringAccessor(new CustomAccessor<VPNService, string>(
this, &VPNService::GetPhysicalTechnologyProperty, nullptr)));
}
VPNService::~VPNService() = default;
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.
SetState(ConnectState::kStateAssociating);
switch (driver_->GetIfType()) {
case VPNDriver::kTunnel:
if (!manager()->device_info()->CreateTunnelInterface(base::BindOnce(
&VPNService::OnLinkReady, weak_factory_.GetWeakPtr()))) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInternalError,
"Could not create tunnel interface.");
SetFailure(Service::kFailureInternal);
SetErrorDetails(Service::kErrorDetailsNone);
return;
}
// Flow continues in OnLinkReady().
break;
case VPNDriver::kArcBridge:
device_ = manager()->vpn_provider()->arc_device();
if (!device_) {
Error::PopulateAndLog(FROM_HERE, error, Error::kNotFound,
"arc_device is not found");
SetFailure(Service::kFailureInternal);
SetErrorDetails(Service::kErrorDetailsNone);
return;
}
FALLTHROUGH;
case VPNDriver::kPPP:
driver_->ConnectAsync(base::BindRepeating(&VPNService::OnDriverEvent,
weak_factory_.GetWeakPtr()));
// Flow continues in OnDriverEvent(kEventConnectionSuccess).
break;
default:
NOTREACHED();
}
}
void VPNService::OnDisconnect(Error* error, const char* reason) {
SetState(ConnectState::kStateDisconnecting);
driver_->Disconnect();
CleanupDevice();
SetState(ConnectState::kStateIdle);
}
void VPNService::OnLinkReady(const string& link_name, int interface_index) {
switch (driver_->GetIfType()) {
case VPNDriver::kTunnel:
CHECK(!device_);
device_ = new VirtualDevice(manager(), link_name, interface_index,
Technology::kVPN);
driver_->set_interface_name(link_name);
driver_->ConnectAsync(base::BindRepeating(&VPNService::OnDriverEvent,
weak_factory_.GetWeakPtr()));
// Flow continues in OnDriverEvent(kEventConnectionSuccess).
break;
case VPNDriver::kPPP:
// Only get called when driver notification arrives earlier than RTNL
// notification - continues flow from
// OnDriverEvent(kEventConnectionSuccess).
device_ = new PPPDevice(manager(), link_name, interface_index);
SetState(ConnectState::kStateConfiguring);
ConfigureDevice();
SetState(ConnectState::kStateConnected);
SetState(ConnectState::kStateOnline);
break;
default:
NOTREACHED();
}
}
void VPNService::OnDriverEvent(DriverEvent event,
ConnectFailure failure,
const std::string& error_details) {
switch (event) {
case kEventConnectionSuccess:
if (driver_->GetIfType() == VPNDriver::kPPP) {
string link_name = driver_->interface_name();
int interface_index = manager()->device_info()->GetIndex(link_name);
if (interface_index < 0) {
// To handle the potential race when the RTNL notification about the
// new PPP device has not been received yet. Register a callback where
// the remaining steps can be continued.
manager()->device_info()->AddVirtualInterfaceReadyCallback(
link_name, base::BindOnce(&VPNService::OnLinkReady,
weak_factory_.GetWeakPtr()));
return;
}
device_ = new PPPDevice(manager(), link_name, interface_index);
}
SetState(ConnectState::kStateConfiguring);
ConfigureDevice();
SetState(ConnectState::kStateConnected);
SetState(ConnectState::kStateOnline);
break;
case kEventDriverFailure:
CleanupDevice();
SetErrorDetails(error_details);
SetFailure(failure);
break;
case kEventDriverReconnecting:
if (device_) {
SetState(Service::kStateAssociating);
device_->ResetConnection();
}
// Await further OnDriverEvent(kEventConnectionSuccess).
break;
}
}
void VPNService::CleanupDevice() {
if (!device_)
return;
int interface_index = device_->interface_index();
device_->DropConnection();
device_->SetEnabled(false);
device_ = nullptr;
if (driver_->GetIfType() == VPNDriver::kTunnel) {
manager()->device_info()->DeleteInterface(interface_index);
}
}
void VPNService::ConfigureDevice() {
if (!device_) {
LOG(DFATAL) << "Device not created yet.";
return;
}
IPConfig::Properties ip_properties = driver_->GetIPProperties();
// TODO(taoyl): Remove routing policy from IPProperties.
manager()->vpn_provider()->SetDefaultRoutingPolicy(&ip_properties);
// Remove vpn virtual device from allow_iifs list to avoid route loop.
// This is mainly for ARC bridge on ARC VPN (note ARC bridge needs to
// be in allow_iifs for non-ARC VPNs). PPP and tunnel interface should
// never be in allow_iifs.
// TODO(taoyl): Remove this as part of routing policy migration.
base::Erase(ip_properties.allowed_iifs, device_->link_name());
device_->SetEnabled(true);
device_->SelectService(this);
device_->UpdateIPConfig(ip_properties);
device_->SetLooseRouting(true);
}
string VPNService::GetStorageIdentifier() const {
return storage_id_;
}
bool VPNService::IsAlwaysOnVpn(const string& package) const {
// For ArcVPN connections, the driver host is set to the package name of the
// Android app that is creating the VPN connection.
return driver_->GetProviderType() == string(kProviderArcVpn) &&
driver_->GetHost() == package;
}
// static
string VPNService::CreateStorageIdentifier(const KeyValueStore& args,
Error* error) {
string host = args.Lookup<string>(kProviderHostProperty, "");
if (host.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidProperty,
"Missing VPN host.");
return "";
}
string name = args.Lookup<string>(kNameProperty, "");
if (name.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported,
"Missing VPN name.");
return "";
}
return SanitizeStorageIdentifier(
StringPrintf("vpn_%s_%s", host.c_str(), name.c_str()));
}
string VPNService::GetPhysicalTechnologyProperty(Error* error) {
ConnectionConstRefPtr underlying_connection = GetUnderlyingConnection();
if (!underlying_connection) {
error->Populate(Error::kOperationFailed);
return "";
}
return underlying_connection->technology().GetName();
}
RpcIdentifier VPNService::GetDeviceRpcId(Error* error) const {
if (!device_) {
error->Populate(Error::kNotFound, "Not associated with a device");
return control_interface()->NullRpcIdentifier();
}
return device_->GetRpcIdentifier();
}
ConnectionConstRefPtr VPNService::GetUnderlyingConnection() const {
// TODO(crbug.com/941597) Policy routing should be used to enforce that VPN
// traffic can only exit the interface it is supposed to. The VPN driver
// should also be informed of changes in underlying connection.
ServiceRefPtr underlying_service = manager()->GetPrimaryPhysicalService();
if (!underlying_service) {
return nullptr;
}
return underlying_service->connection();
}
bool VPNService::Load(const StoreInterface* storage) {
return Service::Load(storage) &&
driver_->Load(storage, GetStorageIdentifier());
}
void VPNService::MigrateDeprecatedStorage(StoreInterface* storage) {
Service::MigrateDeprecatedStorage(storage);
const string id = GetStorageIdentifier();
CHECK(storage->ContainsGroup(id));
driver_->MigrateDeprecatedStorage(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());
}
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;
}
string VPNService::GetTethering(Error* error) const {
ConnectionConstRefPtr underlying_connection = GetUnderlyingConnection();
string tethering;
if (underlying_connection) {
tethering = underlying_connection->tethering();
if (!tethering.empty()) {
return tethering;
}
// The underlying service may not have a Tethering property. This is
// not strictly an error, so we don't print an error message. Populating
// an error here just serves to propagate the lack of a property in
// GetProperties().
error->Populate(Error::kNotSupported);
} else {
error->Populate(Error::kOperationFailed);
}
return "";
}
bool VPNService::SetNameProperty(const string& name, Error* error) {
if (name == friendly_name()) {
return false;
}
LOG(INFO) << "SetNameProperty called for: " << log_name();
KeyValueStore* args = driver_->args();
args->Set<string>(kNameProperty, name);
string new_storage_id = CreateStorageIdentifier(*args, error);
if (new_storage_id.empty()) {
return false;
}
string 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;
}
void VPNService::OnBeforeSuspend(const ResultCallback& callback) {
driver_->OnBeforeSuspend(callback);
}
void VPNService::OnAfterResume() {
driver_->OnAfterResume();
Service::OnAfterResume();
}
void VPNService::OnDefaultServiceStateChanged(const ServiceRefPtr& service) {
driver_->OnDefaultServiceStateChanged(service);
}
} // namespace shill