blob: 205f4566fd39cbaf905fc1340f096dc3c5cabb04 [file] [log] [blame]
// Copyright 2021 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/ikev2_driver.h"
#include <array>
#include <memory>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/logging.h>
#include <base/time/time.h>
#include <base/version.h>
#include <chromeos/dbus/shill/dbus-constants.h>
#include "shill/error.h"
#include "shill/ipconfig.h"
#include "shill/manager.h"
#include "shill/metrics.h"
#include "shill/vpn/ipsec_connection.h"
#include "shill/vpn/vpn_service.h"
namespace shill {
namespace {
constexpr base::TimeDelta kConnectTimeout = base::Seconds(30);
std::unique_ptr<IPsecConnection::Config> MakeIPsecConfig(
const KeyValueStore& args, const EapCredentials& eap_credentials) {
auto config = std::make_unique<IPsecConnection::Config>();
config->ike_version = IPsecConnection::Config::IKEVersion::kV2;
config->remote = args.Lookup<std::string>(kProviderHostProperty, "");
config->local_id =
args.GetOptionalValue<std::string>(kIKEv2LocalIdentityProperty);
config->remote_id =
args.GetOptionalValue<std::string>(kIKEv2RemoteIdentityProperty);
config->ca_cert_pem_strings =
args.GetOptionalValue<Strings>(kIKEv2CaCertPemProperty);
const std::string auth_type =
args.Lookup<std::string>(kIKEv2AuthenticationTypeProperty, "");
if (auth_type == kIKEv2AuthenticationTypePSK) {
config->psk = args.GetOptionalValue<std::string>(kIKEv2PskProperty);
if (!config->psk.has_value()) {
LOG(ERROR) << "Auth type is PSK but no PSK value found.";
return nullptr;
}
} else if (auth_type == kIKEv2AuthenticationTypeCert) {
config->client_cert_id =
args.GetOptionalValue<std::string>(kIKEv2ClientCertIdProperty);
config->client_cert_slot =
args.GetOptionalValue<std::string>(kIKEv2ClientCertSlotProperty);
if (!config->client_cert_id.has_value() ||
!config->client_cert_slot.has_value()) {
LOG(ERROR) << "Auth type is emtpy but empty cert id or slot found.";
return nullptr;
}
} else if (auth_type == kIKEv2AuthenticationTypeEAP) {
if (eap_credentials.method() != kEapMethodMSCHAPV2) {
LOG(ERROR) << "Only MSCHAPv2 is supported for EAP in IKEv2 VPN.";
return nullptr;
}
Error err;
config->xauth_user = eap_credentials.identity();
config->xauth_password = eap_credentials.GetEapPassword(&err);
if (err.IsFailure()) {
LOG(ERROR) << err;
return nullptr;
}
} else {
LOG(ERROR) << "Invalid auth type: " << auth_type;
return nullptr;
}
return config;
}
void ReportConnectionEndReason(Metrics* metrics,
Service::ConnectFailure failure) {
metrics->SendEnumToUMA(Metrics::kMetricVpnIkev2EndReason,
Metrics::ConnectFailureToServiceErrorEnum(failure),
Metrics::kMetricVpnIkev2EndReasonMax);
}
} // namespace
const VPNDriver::Property IKEv2Driver::kProperties[] = {
{kIKEv2AuthenticationTypeProperty, 0},
{kIKEv2CaCertPemProperty, Property::kArray},
{kIKEv2ClientCertIdProperty, 0},
{kIKEv2ClientCertSlotProperty, 0},
{kIKEv2PskProperty, Property::kCredential | Property::kWriteOnly},
{kIKEv2LocalIdentityProperty, Property::kCredential},
{kIKEv2RemoteIdentityProperty, Property::kCredential},
{kProviderHostProperty, 0},
{kProviderTypeProperty, 0},
};
// static
bool IKEv2Driver::IsSupported() {
// IKEv2 is currently supported on kernel version >= 4.19, due to the
// availability of XFRM interface.
return VPNUtil::CheckKernelVersion(base::Version("4.19"));
}
IKEv2Driver::IKEv2Driver(Manager* manager, ProcessManager* process_manager)
: VPNDriver(manager,
process_manager,
kProperties,
std::size(kProperties),
/*use_eap=*/true) {}
IKEv2Driver::~IKEv2Driver() {}
base::TimeDelta IKEv2Driver::ConnectAsync(EventHandler* handler) {
event_handler_ = handler;
dispatcher()->PostTask(FROM_HERE,
base::BindOnce(&IKEv2Driver::StartIPsecConnection,
weak_factory_.GetWeakPtr()));
return kConnectTimeout;
}
void IKEv2Driver::StartIPsecConnection() {
if (ipsec_connection_) {
LOG(ERROR) << "The previous IPsecConnection is still running.";
NotifyServiceOfFailure(Service::kFailureInternal);
return;
}
auto callbacks = std::make_unique<IPsecConnection::Callbacks>(
base::BindRepeating(&IKEv2Driver::OnIPsecConnected,
weak_factory_.GetWeakPtr()),
base::BindOnce(&IKEv2Driver::OnIPsecFailure, weak_factory_.GetWeakPtr()),
base::BindOnce(&IKEv2Driver::OnIPsecStopped, weak_factory_.GetWeakPtr()));
auto ipsec_config = MakeIPsecConfig(*const_args(), *eap_credentials());
if (!ipsec_config) {
LOG(ERROR) << "Failed to generate IPsec config";
NotifyServiceOfFailure(Service::kFailureInternal);
return;
}
ipsec_connection_ = CreateIPsecConnection(
std::move(ipsec_config), std::move(callbacks), manager()->device_info(),
manager()->dispatcher(), process_manager());
ipsec_connection_->Connect();
}
std::unique_ptr<VPNConnection> IKEv2Driver::CreateIPsecConnection(
std::unique_ptr<IPsecConnection::Config> config,
std::unique_ptr<VPNConnection::Callbacks> callbacks,
DeviceInfo* device_info,
EventDispatcher* dispatcher,
ProcessManager* process_manager) {
return std::make_unique<IPsecConnection>(
std::move(config), std::move(callbacks), /*l2tp_connection=*/nullptr,
device_info, dispatcher, process_manager);
}
void IKEv2Driver::Disconnect() {
event_handler_ = nullptr;
ReportConnectionEndReason(metrics(), Service::kFailureDisconnect);
if (!ipsec_connection_) {
LOG(ERROR) << "Disconnect() called but IPsecConnection is not running";
return;
}
if (!ipsec_connection_->IsConnectingOrConnected()) {
LOG(ERROR) << "Disconnect() called but IPsecConnection is in "
<< ipsec_connection_->state() << " state";
return;
}
ipsec_connection_->Disconnect();
}
IPConfig::Properties IKEv2Driver::GetIPProperties() const {
return ip_properties_;
}
std::string IKEv2Driver::GetProviderType() const {
return kProviderIKEv2;
}
void IKEv2Driver::OnConnectTimeout() {
LOG(INFO) << "Connect timeout";
if (!ipsec_connection_) {
LOG(ERROR)
<< "OnConnectTimeout() called but IPsecConnection is not running";
return;
}
if (!ipsec_connection_->IsConnectingOrConnected()) {
LOG(ERROR) << "OnConnectTimeout() called but IPsecConnection is in "
<< ipsec_connection_->state() << " state";
return;
}
ipsec_connection_->Disconnect();
NotifyServiceOfFailure(Service::kFailureConnect);
}
// TODO(b/210064468): Check if charon can handle these events.
void IKEv2Driver::OnBeforeSuspend(const ResultCallback& callback) {
if (ipsec_connection_ && ipsec_connection_->IsConnectingOrConnected()) {
ipsec_connection_->Disconnect();
NotifyServiceOfFailure(Service::kFailureDisconnect);
}
callback.Run(Error(Error::kSuccess));
}
// TODO(b/210064468): Check if charon can handle these events.
void IKEv2Driver::OnDefaultPhysicalServiceEvent(
DefaultPhysicalServiceEvent event) {
if (!ipsec_connection_ || !ipsec_connection_->IsConnectingOrConnected()) {
return;
}
switch (event) {
case kDefaultPhysicalServiceUp:
return;
case kDefaultPhysicalServiceDown:
ipsec_connection_->Disconnect();
NotifyServiceOfFailure(Service::kFailureDisconnect);
return;
case kDefaultPhysicalServiceChanged:
ipsec_connection_->Disconnect();
NotifyServiceOfFailure(Service::kFailureDisconnect);
return;
default:
NOTREACHED();
}
}
void IKEv2Driver::NotifyServiceOfFailure(Service::ConnectFailure failure) {
LOG(ERROR) << "Driver failure due to "
<< Service::ConnectFailureToString(failure);
if (event_handler_) {
// Only reports metrics when |event_handler_| exists to ensure reporting
// only once for each connection.
ReportConnectionEndReason(metrics(), failure);
event_handler_->OnDriverFailure(failure, Service::kErrorDetailsNone);
event_handler_ = nullptr;
}
}
void IKEv2Driver::OnIPsecConnected(const std::string& link_name,
int interface_index,
const IPConfig::Properties& ip_properties) {
if (!event_handler_) {
LOG(ERROR) << "OnIPsecConnected() triggered in illegal service state";
return;
}
ReportConnectionMetrics();
ip_properties_ = ip_properties;
event_handler_->OnDriverConnected(link_name, interface_index);
}
void IKEv2Driver::OnIPsecFailure(Service::ConnectFailure failure) {
NotifyServiceOfFailure(failure);
}
void IKEv2Driver::OnIPsecStopped() {
ipsec_connection_ = nullptr;
}
KeyValueStore IKEv2Driver::GetProvider(Error* error) {
KeyValueStore props = VPNDriver::GetProvider(error);
// If the corresponding credential field is empty for an authentication type,
// set kPassphraseRequiredProperty field to true.
bool passphrase_required = false;
const std::string auth_type =
const_args()->Lookup<std::string>(kIKEv2AuthenticationTypeProperty, "");
if (auth_type == kIKEv2AuthenticationTypePSK) {
const std::string psk =
const_args()->Lookup<std::string>(kIKEv2PskProperty, "");
passphrase_required = psk.empty();
} else if (auth_type == kIKEv2AuthenticationTypeEAP) {
passphrase_required = !eap_credentials()->IsConnectableUsingPassphrase();
}
props.Set<bool>(kPassphraseRequiredProperty, passphrase_required);
return props;
}
void IKEv2Driver::ReportConnectionMetrics() {
metrics()->SendEnumToUMA(Metrics::kMetricVpnDriver, Metrics::kVpnDriverIKEv2,
Metrics::kMetricVpnDriverMax);
Metrics::VpnIpsecAuthenticationType auth_type_metrics =
Metrics::kVpnIpsecAuthenticationTypeUnknown;
const std::string auth_type_str =
const_args()->Lookup<std::string>(kIKEv2AuthenticationTypeProperty, "");
if (auth_type_str == kIKEv2AuthenticationTypePSK) {
auth_type_metrics = Metrics::kVpnIpsecAuthenticationTypePsk;
} else if (auth_type_str == kIKEv2AuthenticationTypeEAP) {
auth_type_metrics = Metrics::kVpnIpsecAuthenticationTypeEap;
} else if (auth_type_str == kIKEv2AuthenticationTypeCert) {
auth_type_metrics = Metrics::kVpnIpsecAuthenticationTypeCertificate;
} else {
// We have checked the auth type before connection, but it is still possible
// to reach here if the properties are changed right before the connection
// is established. Still reports this case to keep the numbers consistent.
LOG(ERROR) << "Unexpected auth type: " << auth_type_str;
}
metrics()->SendEnumToUMA(Metrics::kMetricVpnIkev2AuthenticationType,
auth_type_metrics,
Metrics::kMetricVpnIkev2AuthenticationMax);
// To access the methods only defined in the inherited class. The cast will
// only fail in unit tests.
const auto* conn = dynamic_cast<IPsecConnection*>(ipsec_connection_.get());
if (conn) {
// Cipher suite for IKE.
metrics()->SendEnumToUMA(Metrics::kMetricVpnIkev2IkeEncryptionAlgorithm,
conn->ike_encryption_algo(),
Metrics::kMetricVpnIkev2IkeEncryptionAlgorithmMax);
metrics()->SendEnumToUMA(Metrics::kMetricVpnIkev2IkeIntegrityAlgorithm,
conn->ike_integrity_algo(),
Metrics::kMetricVpnIkev2IkeIntegrityAlgorithmMax);
metrics()->SendEnumToUMA(Metrics::kMetricVpnIkev2IkeDHGroup,
conn->ike_dh_group(),
Metrics::kMetricVpnIkev2IkeDHGroupMax);
// Cipher suite for ESP.
metrics()->SendEnumToUMA(Metrics::kMetricVpnIkev2EspEncryptionAlgorithm,
conn->esp_encryption_algo(),
Metrics::kMetricVpnIkev2EspEncryptionAlgorithmMax);
metrics()->SendEnumToUMA(Metrics::kMetricVpnIkev2EspIntegrityAlgorithm,
conn->esp_integrity_algo(),
Metrics::kMetricVpnIkev2EspIntegrityAlgorithmMax);
}
}
} // namespace shill