blob: 4bc14a612938315c1610233fb84f86bfcde28a20 [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/new_l2tp_ipsec_driver.h"
#include <memory>
#include <string>
#include <utility>
#include <arpa/inet.h> // for inet_ntop
#include <netdb.h> // for getaddrinfo
#include <base/bind.h>
#include <brillo/type_list.h>
#include <base/logging.h>
#include <chromeos/dbus/service_constants.h>
#include "shill/error.h"
#include "shill/ipconfig.h"
#include "shill/manager.h"
#include "shill/vpn/ipsec_connection.h"
#include "shill/vpn/l2tp_connection.h"
#include "shill/vpn/vpn_service.h"
namespace shill {
namespace {
const char kL2TPIPsecLeftProtoPortProperty[] = "L2TPIPsec.LeftProtoPort";
const char kL2TPIPsecLengthBitProperty[] = "L2TPIPsec.LengthBit";
const char kL2TPIPsecRefusePapProperty[] = "L2TPIPsec.RefusePap";
const char kL2TPIPsecRequireAuthProperty[] = "L2TPIPsec.RequireAuth";
const char kL2TPIPsecRequireChapProperty[] = "L2TPIPsec.RequireChap";
const char kL2TPIPsecRightProtoPortProperty[] = "L2TPIPsec.RightProtoPort";
// Returns an empty string on error.
std::string ConvertSockAddrToIPString(const sockaddr_storage& address) {
char str[INET6_ADDRSTRLEN] = {0};
switch (address.ss_family) {
case AF_INET:
if (!inet_ntop(
AF_INET,
&(reinterpret_cast<const sockaddr_in*>(&address)->sin_addr), str,
sizeof(str))) {
PLOG(ERROR) << "inet_ntop failed";
return "";
}
break;
case AF_INET6:
if (!inet_ntop(
AF_INET6,
&(reinterpret_cast<const sockaddr_in6*>(&address)->sin6_addr),
str, sizeof(str))) {
PLOG(ERROR) << "inet_ntop failed";
return "";
}
break;
default:
LOG(ERROR) << "Unknown address family: " << address.ss_family;
return "";
}
return str;
}
// Returns an empty string on error.
std::string ResolveNameToIP(const std::string& name) {
addrinfo* address_info = nullptr;
// This function is called when the VPN service is connecting, and it makes
// sense to let the query go through the dnsproxy.
int s = getaddrinfo(name.c_str(), nullptr, nullptr, &address_info);
if (s != 0) {
LOG(ERROR) << "getaddrinfo failed: " << gai_strerror(s);
return "";
}
sockaddr_storage address;
memcpy(&address, address_info->ai_addr, address_info->ai_addrlen);
freeaddrinfo(address_info);
return ConvertSockAddrToIPString(address);
}
// Gets a value from KeyValueStore in std::optional. Returns std::nullopt if the
// key does not exist or the value is empty.
using ContainerTypes = brillo::TypeList<std::string, Strings>;
template <typename T, typename = brillo::EnableIfIsOneOf<T, ContainerTypes>>
std::optional<T> GetOptionalValue(const KeyValueStore& args,
const std::string& key) {
if (args.Lookup<T>(key, T{}).empty()) {
return std::nullopt;
}
return args.Get<T>(key);
}
std::unique_ptr<IPsecConnection::Config> MakeIPsecConfig(
const std::string& remote_ip, const KeyValueStore& args) {
auto config = std::make_unique<IPsecConnection::Config>();
config->remote = remote_ip;
config->psk = GetOptionalValue<std::string>(args, kL2TPIPsecPskProperty);
config->ca_cert_pem_strings =
GetOptionalValue<Strings>(args, kL2TPIPsecCaCertPemProperty);
config->client_cert_id =
GetOptionalValue<std::string>(args, kL2TPIPsecClientCertIdProperty);
config->client_cert_slot =
GetOptionalValue<std::string>(args, kL2TPIPsecClientCertSlotProperty);
config->client_cert_pin =
GetOptionalValue<std::string>(args, kL2TPIPsecPinProperty);
config->xauth_user =
GetOptionalValue<std::string>(args, kL2TPIPsecXauthUserProperty);
config->xauth_password =
GetOptionalValue<std::string>(args, kL2TPIPsecXauthPasswordProperty);
config->tunnel_group =
GetOptionalValue<std::string>(args, kL2TPIPsecTunnelGroupProperty);
// 17 = UDP, 1701 = L2TP.
config->local_proto_port =
args.Lookup<std::string>(kL2TPIPsecLeftProtoPortProperty, "17/1701");
config->remote_proto_port =
args.Lookup<std::string>(kL2TPIPsecRightProtoPortProperty, "17/1701");
return config;
}
// KeyValueStore stores bool value as string "true" or "false". This function
// converts it to bool type, or returns |default_value|.
bool GetBool(const KeyValueStore& args,
const std::string& key,
bool default_value) {
if (args.Contains<std::string>(key)) {
return args.Get<std::string>(key) == "true";
}
return default_value;
}
std::unique_ptr<L2TPConnection::Config> MakeL2TPConfig(
const std::string& remote_ip, const KeyValueStore& args) {
auto config = std::make_unique<L2TPConnection::Config>();
config->remote_ip = remote_ip;
// Fields for xl2tpd.
config->refuse_pap = GetBool(args, kL2TPIPsecRefusePapProperty, false);
config->require_auth = GetBool(args, kL2TPIPsecRequireAuthProperty, true);
config->require_chap = GetBool(args, kL2TPIPsecRequireChapProperty, true);
config->length_bit = GetBool(args, kL2TPIPsecLengthBitProperty, true);
// Fields for pppd.
config->lcp_echo = GetBool(args, kL2TPIPsecLcpEchoDisabledProperty, true);
config->user = args.Lookup<std::string>(kL2TPIPsecUserProperty, "");
config->password = args.Lookup<std::string>(kL2TPIPsecPasswordProperty, "");
config->use_login_password =
GetBool(args, kL2TPIPsecUseLoginPasswordProperty, false);
return config;
}
} // namespace
const VPNDriver::Property NewL2TPIPsecDriver::kProperties[] = {
{kL2TPIPsecClientCertIdProperty, 0},
{kL2TPIPsecClientCertSlotProperty, 0},
{kL2TPIPsecPasswordProperty, Property::kCredential | Property::kWriteOnly},
{kL2TPIPsecPinProperty, Property::kCredential},
{kL2TPIPsecPskProperty, Property::kCredential | Property::kWriteOnly},
{kL2TPIPsecUseLoginPasswordProperty, 0},
{kL2TPIPsecUserProperty, 0},
{kProviderHostProperty, 0},
{kProviderTypeProperty, 0},
{kL2TPIPsecCaCertPemProperty, Property::kArray},
{kL2TPIPsecTunnelGroupProperty, 0},
{kL2TPIPsecLeftProtoPortProperty, 0},
{kL2TPIPsecLengthBitProperty, 0},
{kL2TPIPsecRefusePapProperty, 0},
{kL2TPIPsecRequireAuthProperty, 0},
{kL2TPIPsecRequireChapProperty, 0},
{kL2TPIPsecRightProtoPortProperty, 0},
{kL2TPIPsecXauthUserProperty, Property::kCredential | Property::kWriteOnly},
{kL2TPIPsecXauthPasswordProperty,
Property::kCredential | Property::kWriteOnly},
{kL2TPIPsecLcpEchoDisabledProperty, 0},
};
NewL2TPIPsecDriver::NewL2TPIPsecDriver(Manager* manager,
ProcessManager* process_manager)
: VPNDriver(
manager, process_manager, kProperties, base::size(kProperties)) {}
NewL2TPIPsecDriver::~NewL2TPIPsecDriver() {}
base::TimeDelta NewL2TPIPsecDriver::ConnectAsync(EventHandler* handler) {
event_handler_ = handler;
dispatcher()->PostTask(
FROM_HERE, base::BindOnce(&NewL2TPIPsecDriver::StartIPsecConnection,
weak_factory_.GetWeakPtr()));
return base::TimeDelta::FromSeconds(60);
}
void NewL2TPIPsecDriver::StartIPsecConnection() {
if (ipsec_connection_) {
LOG(ERROR) << "The previous IPsecConnection is still running.";
NotifyServiceOfFailure(Service::kFailureInternal);
return;
}
const std::string remote_ip = ResolveNameToIP(
const_args()->Lookup<std::string>(kProviderHostProperty, ""));
if (remote_ip.empty()) {
LOG(ERROR) << "Failed to resolve host property to IP.";
NotifyServiceOfFailure(Service::kFailureInternal);
return;
}
auto l2tp_connection = CreateL2TPConnection(
MakeL2TPConfig(remote_ip, *const_args()), control_interface(),
manager()->device_info(), manager()->dispatcher(), process_manager());
auto callbacks = std::make_unique<IPsecConnection::Callbacks>(
base::BindRepeating(&NewL2TPIPsecDriver::OnIPsecConnected,
weak_factory_.GetWeakPtr()),
base::BindOnce(&NewL2TPIPsecDriver::OnIPsecFailure,
weak_factory_.GetWeakPtr()),
base::BindOnce(&NewL2TPIPsecDriver::OnIPsecStopped,
weak_factory_.GetWeakPtr()));
ipsec_connection_ = CreateIPsecConnection(
MakeIPsecConfig(remote_ip, *const_args()), std::move(callbacks),
std::move(l2tp_connection), manager()->dispatcher(), process_manager());
ipsec_connection_->Connect();
}
std::unique_ptr<VPNConnection> NewL2TPIPsecDriver::CreateIPsecConnection(
std::unique_ptr<IPsecConnection::Config> config,
std::unique_ptr<VPNConnection::Callbacks> callbacks,
std::unique_ptr<VPNConnection> l2tp_connection,
EventDispatcher* dispatcher,
ProcessManager* process_manager) {
return std::make_unique<IPsecConnection>(
std::move(config), std::move(callbacks), std::move(l2tp_connection),
dispatcher, process_manager);
}
std::unique_ptr<VPNConnection> NewL2TPIPsecDriver::CreateL2TPConnection(
std::unique_ptr<L2TPConnection::Config> config,
ControlInterface* control_interface,
DeviceInfo* device_info,
EventDispatcher* dispatcher,
ProcessManager* process_manager) {
// Callbacks for L2TP will be set and handled in IPsecConnection.
return std::make_unique<L2TPConnection>(
std::move(config), /*callbacks=*/nullptr, control_interface, device_info,
dispatcher, process_manager);
}
void NewL2TPIPsecDriver::Disconnect() {
event_handler_ = nullptr;
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 NewL2TPIPsecDriver::GetIPProperties() const {
return ip_properties_;
}
std::string NewL2TPIPsecDriver::GetProviderType() const {
return kProviderL2tpIpsec;
}
void NewL2TPIPsecDriver::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);
}
void NewL2TPIPsecDriver::OnBeforeSuspend(const ResultCallback& callback) {
if (ipsec_connection_ && ipsec_connection_->IsConnectingOrConnected()) {
ipsec_connection_->Disconnect();
NotifyServiceOfFailure(Service::kFailureDisconnect);
}
callback.Run(Error(Error::kSuccess));
}
void NewL2TPIPsecDriver::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 NewL2TPIPsecDriver::NotifyServiceOfFailure(
Service::ConnectFailure failure) {
LOG(ERROR) << "Driver failure due to "
<< Service::ConnectFailureToString(failure);
if (event_handler_) {
event_handler_->OnDriverFailure(failure, Service::kErrorDetailsNone);
event_handler_ = nullptr;
}
}
void NewL2TPIPsecDriver::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 NewL2TPIPsecDriver::OnIPsecFailure(Service::ConnectFailure failure) {
NotifyServiceOfFailure(failure);
}
void NewL2TPIPsecDriver::OnIPsecStopped() {
ipsec_connection_ = nullptr;
}
KeyValueStore NewL2TPIPsecDriver::GetProvider(Error* error) {
const bool require_passphrase =
args()->Lookup<std::string>(kL2TPIPsecPasswordProperty, "").empty();
const bool psk_empty =
args()->Lookup<std::string>(kL2TPIPsecPskProperty, "").empty();
const bool cert_empty =
args()->Lookup<std::string>(kL2TPIPsecClientCertIdProperty, "").empty();
const bool require_psk = psk_empty && cert_empty;
KeyValueStore props = VPNDriver::GetProvider(error);
props.Set<bool>(kPassphraseRequiredProperty, require_passphrase);
props.Set<bool>(kL2TPIPsecPskRequiredProperty, require_psk);
return props;
}
void NewL2TPIPsecDriver::ReportConnectionMetrics() {
metrics()->SendEnumToUMA(Metrics::kMetricVpnDriver,
Metrics::kVpnDriverL2tpIpsec,
Metrics::kMetricVpnDriverMax);
// We output an enum for each of the authentication types specified,
// even if more than one is set at the same time.
bool has_remote_authentication = false;
if (args()->Contains<Strings>(kL2TPIPsecCaCertPemProperty) &&
!args()->Get<Strings>(kL2TPIPsecCaCertPemProperty).empty()) {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnRemoteAuthenticationType,
Metrics::kVpnRemoteAuthenticationTypeL2tpIpsecCertificate,
Metrics::kMetricVpnRemoteAuthenticationTypeMax);
has_remote_authentication = true;
}
if (args()->Lookup<std::string>(kL2TPIPsecPskProperty, "") != "") {
metrics()->SendEnumToUMA(Metrics::kMetricVpnRemoteAuthenticationType,
Metrics::kVpnRemoteAuthenticationTypeL2tpIpsecPsk,
Metrics::kMetricVpnRemoteAuthenticationTypeMax);
has_remote_authentication = true;
}
if (!has_remote_authentication) {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnRemoteAuthenticationType,
Metrics::kVpnRemoteAuthenticationTypeL2tpIpsecDefault,
Metrics::kMetricVpnRemoteAuthenticationTypeMax);
}
bool has_user_authentication = false;
if (args()->Lookup<std::string>(kL2TPIPsecClientCertIdProperty, "") != "") {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeL2tpIpsecCertificate,
Metrics::kMetricVpnUserAuthenticationTypeMax);
has_user_authentication = true;
}
if (args()->Lookup<std::string>(kL2TPIPsecPasswordProperty, "") != "" ||
GetBool(*args(), kL2TPIPsecUseLoginPasswordProperty, false)) {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeL2tpIpsecUsernamePassword,
Metrics::kMetricVpnUserAuthenticationTypeMax);
has_user_authentication = true;
}
if (!has_user_authentication) {
metrics()->SendEnumToUMA(Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeL2tpIpsecNone,
Metrics::kMetricVpnUserAuthenticationTypeMax);
}
// Reports whether tunnel group is set or not (b/201478824).
const auto tunnel_group_usage =
args()->Lookup<std::string>(kL2TPIPsecTunnelGroupProperty, "") != ""
? Metrics::kVpnL2tpIpsecTunnelGroupUsageYes
: Metrics::kVpnL2tpIpsecTunnelGroupUsageNo;
metrics()->SendEnumToUMA(Metrics::kMetricVpnL2tpIpsecTunnelGroupUsage,
tunnel_group_usage,
Metrics::kMetricVpnL2tpIpsecTunnelGroupUsageMax);
}
} // namespace shill