559 lines
20 KiB
C++
559 lines
20 KiB
C++
//
|
|
// Copyright (C) 2012 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
// The term "L2TP / IPSec" refers to a pair of layered protocols used
|
|
// together to establish a tunneled VPN connection. First, an "IPSec"
|
|
// link is created, which secures a single IP traffic pair between the
|
|
// client and server. For this link to complete, one or two levels of
|
|
// authentication are performed. The first, inner mandatory authentication
|
|
// ensures the two parties establishing the IPSec link are correct. This
|
|
// can use a certificate exchange or a less secure "shared group key"
|
|
// (PSK) authentication. An optional outer IPSec authentication can also be
|
|
// performed, which is not fully supported by shill's implementation.
|
|
// In order to support "tunnel groups" from some vendor VPNs shill supports
|
|
// supplying the authentication realm portion during the outer authentication.
|
|
// Notably, XAUTH and other forms of user authentication on this outer link
|
|
// are not supported.
|
|
//
|
|
// When IPSec authentication completes, traffic is tunneled through a
|
|
// layer 2 tunnel, called "L2TP". Using the secured link, we tunnel a
|
|
// PPP link, through which a second layer of authentication is performed,
|
|
// using the provided "user" and "password" properties.
|
|
|
|
#include "shill/vpn/l2tp_ipsec_driver.h"
|
|
|
|
#include <base/bind.h>
|
|
#include <base/files/file_util.h>
|
|
#include <base/strings/string_util.h>
|
|
#include <base/strings/stringprintf.h>
|
|
#if defined(__ANDROID__)
|
|
#include <dbus/service_constants.h>
|
|
#else
|
|
#include <chromeos/dbus/service_constants.h>
|
|
#endif // __ANDROID__
|
|
#include <vpn-manager/service_error.h>
|
|
|
|
#include "shill/certificate_file.h"
|
|
#include "shill/device_info.h"
|
|
#include "shill/error.h"
|
|
#include "shill/external_task.h"
|
|
#include "shill/ipconfig.h"
|
|
#include "shill/logging.h"
|
|
#include "shill/manager.h"
|
|
#include "shill/ppp_daemon.h"
|
|
#include "shill/ppp_device.h"
|
|
#include "shill/ppp_device_factory.h"
|
|
#include "shill/process_manager.h"
|
|
#include "shill/vpn/vpn_service.h"
|
|
|
|
using base::Bind;
|
|
using base::Closure;
|
|
using base::FilePath;
|
|
using std::map;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
namespace shill {
|
|
|
|
namespace Logging {
|
|
static auto kModuleLogScope = ScopeLogger::kVPN;
|
|
static string ObjectID(L2TPIPSecDriver* l) {
|
|
return l->GetServiceRpcIdentifier();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
const char kL2TPIPSecIPSecTimeoutProperty[] = "L2TPIPsec.IPsecTimeout";
|
|
const char kL2TPIPSecLeftProtoPortProperty[] = "L2TPIPsec.LeftProtoPort";
|
|
const char kL2TPIPSecLengthBitProperty[] = "L2TPIPsec.LengthBit";
|
|
const char kL2TPIPSecPFSProperty[] = "L2TPIPsec.PFS";
|
|
const char kL2TPIPSecRefusePapProperty[] = "L2TPIPsec.RefusePap";
|
|
const char kL2TPIPSecRekeyProperty[] = "L2TPIPsec.Rekey";
|
|
const char kL2TPIPSecRequireAuthProperty[] = "L2TPIPsec.RequireAuth";
|
|
const char kL2TPIPSecRequireChapProperty[] = "L2TPIPsec.RequireChap";
|
|
const char kL2TPIPSecRightProtoPortProperty[] = "L2TPIPsec.RightProtoPort";
|
|
} // namespace
|
|
|
|
// static
|
|
const char L2TPIPSecDriver::kL2TPIPSecVPNPath[] = "/usr/sbin/l2tpipsec_vpn";
|
|
// static
|
|
const VPNDriver::Property L2TPIPSecDriver::kProperties[] = {
|
|
{ kL2tpIpsecAuthenticationType, 0 },
|
|
{ kL2tpIpsecCaCertNssProperty, 0 },
|
|
{ kL2tpIpsecClientCertIdProperty, 0 },
|
|
{ kL2tpIpsecClientCertSlotProperty, 0 },
|
|
{ kL2tpIpsecIkeVersion, 0 },
|
|
{ kL2tpIpsecPasswordProperty, Property::kCredential | Property::kWriteOnly },
|
|
{ kL2tpIpsecPinProperty, Property::kCredential },
|
|
{ kL2tpIpsecPskProperty, Property::kCredential | Property::kWriteOnly },
|
|
{ kL2tpIpsecUserProperty, 0 },
|
|
{ kProviderHostProperty, 0 },
|
|
{ kProviderTypeProperty, 0 },
|
|
{ kL2tpIpsecCaCertPemProperty, Property::kArray },
|
|
{ kL2tpIpsecTunnelGroupProperty, 0 },
|
|
{ kL2TPIPSecIPSecTimeoutProperty, 0 },
|
|
{ kL2TPIPSecLeftProtoPortProperty, 0 },
|
|
{ kL2TPIPSecLengthBitProperty, 0 },
|
|
{ kL2TPIPSecPFSProperty, 0 },
|
|
{ kL2TPIPSecRefusePapProperty, 0 },
|
|
{ kL2TPIPSecRekeyProperty, 0 },
|
|
{ kL2TPIPSecRequireAuthProperty, 0 },
|
|
{ kL2TPIPSecRequireChapProperty, 0 },
|
|
{ kL2TPIPSecRightProtoPortProperty, 0 },
|
|
{ kL2tpIpsecXauthUserProperty, Property::kCredential | Property::kWriteOnly },
|
|
{ kL2tpIpsecXauthPasswordProperty,
|
|
Property::kCredential | Property::kWriteOnly },
|
|
{ kL2tpIpsecLcpEchoDisabledProperty, 0 },
|
|
};
|
|
|
|
L2TPIPSecDriver::L2TPIPSecDriver(ControlInterface* control,
|
|
EventDispatcher* dispatcher,
|
|
Metrics* metrics,
|
|
Manager* manager,
|
|
DeviceInfo* device_info,
|
|
ProcessManager* process_manager)
|
|
: VPNDriver(dispatcher, manager, kProperties, arraysize(kProperties)),
|
|
control_(control),
|
|
metrics_(metrics),
|
|
device_info_(device_info),
|
|
process_manager_(process_manager),
|
|
ppp_device_factory_(PPPDeviceFactory::GetInstance()),
|
|
certificate_file_(new CertificateFile()),
|
|
weak_ptr_factory_(this) {}
|
|
|
|
L2TPIPSecDriver::~L2TPIPSecDriver() {
|
|
IdleService();
|
|
}
|
|
|
|
std::string L2TPIPSecDriver::GetServiceRpcIdentifier() {
|
|
if (service_ == nullptr)
|
|
return "(l2tp_ipsec_driver)";
|
|
return service_->GetRpcIdentifier();
|
|
}
|
|
|
|
bool L2TPIPSecDriver::ClaimInterface(const string& link_name,
|
|
int interface_index) {
|
|
// TODO(petkov): crbug.com/212446.
|
|
NOTIMPLEMENTED();
|
|
return false;
|
|
}
|
|
|
|
void L2TPIPSecDriver::Connect(const VPNServiceRefPtr& service, Error* error) {
|
|
StartConnectTimeout(kDefaultConnectTimeoutSeconds);
|
|
service_ = service;
|
|
service_->SetState(Service::kStateConfiguring);
|
|
if (!SpawnL2TPIPSecVPN(error)) {
|
|
FailService(Service::kFailureInternal);
|
|
}
|
|
}
|
|
|
|
void L2TPIPSecDriver::Disconnect() {
|
|
SLOG(this, 2) << __func__;
|
|
IdleService();
|
|
}
|
|
|
|
void L2TPIPSecDriver::OnConnectionDisconnected() {
|
|
LOG(INFO) << "Underlying connection disconnected.";
|
|
IdleService();
|
|
}
|
|
|
|
void L2TPIPSecDriver::OnConnectTimeout() {
|
|
VPNDriver::OnConnectTimeout();
|
|
FailService(Service::kFailureConnect);
|
|
}
|
|
|
|
string L2TPIPSecDriver::GetProviderType() const {
|
|
return kProviderL2tpIpsec;
|
|
}
|
|
|
|
void L2TPIPSecDriver::IdleService() {
|
|
Cleanup(Service::kStateIdle, Service::kFailureUnknown);
|
|
}
|
|
|
|
void L2TPIPSecDriver::FailService(Service::ConnectFailure failure) {
|
|
Cleanup(Service::kStateFailure, failure);
|
|
}
|
|
|
|
void L2TPIPSecDriver::Cleanup(Service::ConnectState state,
|
|
Service::ConnectFailure failure) {
|
|
SLOG(this, 2) << __func__ << "("
|
|
<< Service::ConnectStateToString(state) << ", "
|
|
<< Service::ConnectFailureToString(failure) << ")";
|
|
StopConnectTimeout();
|
|
DeleteTemporaryFiles();
|
|
external_task_.reset();
|
|
if (device_) {
|
|
device_->DropConnection();
|
|
device_->SetEnabled(false);
|
|
device_ = nullptr;
|
|
}
|
|
if (service_) {
|
|
if (state == Service::kStateFailure) {
|
|
service_->SetFailure(failure);
|
|
} else {
|
|
service_->SetState(state);
|
|
}
|
|
service_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void L2TPIPSecDriver::DeleteTemporaryFile(base::FilePath* temporary_file) {
|
|
if (!temporary_file->empty()) {
|
|
base::DeleteFile(*temporary_file, false);
|
|
temporary_file->clear();
|
|
}
|
|
}
|
|
|
|
void L2TPIPSecDriver::DeleteTemporaryFiles() {
|
|
DeleteTemporaryFile(&psk_file_);
|
|
DeleteTemporaryFile(&xauth_credentials_file_);
|
|
}
|
|
|
|
bool L2TPIPSecDriver::SpawnL2TPIPSecVPN(Error* error) {
|
|
SLOG(this, 2) << __func__;
|
|
std::unique_ptr<ExternalTask> external_task_local(
|
|
new ExternalTask(control_,
|
|
process_manager_,
|
|
weak_ptr_factory_.GetWeakPtr(),
|
|
Bind(&L2TPIPSecDriver::OnL2TPIPSecVPNDied,
|
|
weak_ptr_factory_.GetWeakPtr())));
|
|
|
|
vector<string> options;
|
|
map<string, string> environment; // No env vars passed.
|
|
if (!InitOptions(&options, error)) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << "L2TP/IPSec VPN process options: "
|
|
<< base::JoinString(options, " ");
|
|
|
|
if (external_task_local->Start(
|
|
FilePath(kL2TPIPSecVPNPath), options, environment, true, error)) {
|
|
external_task_ = std::move(external_task_local);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool L2TPIPSecDriver::InitOptions(vector<string>* options, Error* error) {
|
|
string vpnhost = args()->LookupString(kProviderHostProperty, "");
|
|
if (vpnhost.empty()) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInvalidArguments, "VPN host not specified.");
|
|
return false;
|
|
}
|
|
|
|
if (!InitPSKOptions(options, error)) {
|
|
return false;
|
|
}
|
|
|
|
if (!InitXauthOptions(options, error)) {
|
|
return false;
|
|
}
|
|
|
|
options->push_back(base::StringPrintf("--remote_host=%s", vpnhost.c_str()));
|
|
options->push_back(base::StringPrintf("--pppd_plugin=%s",
|
|
PPPDaemon::kShimPluginPath));
|
|
// Disable pppd from configuring IP addresses, routes, DNS.
|
|
options->push_back("--nosystemconfig");
|
|
|
|
// Accept a PEM CA certificate.
|
|
InitPEMOptions(options);
|
|
|
|
AppendValueOption(kL2tpIpsecClientCertIdProperty,
|
|
"--client_cert_id", options);
|
|
AppendValueOption(kL2tpIpsecClientCertSlotProperty,
|
|
"--client_cert_slot", options);
|
|
AppendValueOption(kL2tpIpsecPinProperty, "--user_pin", options);
|
|
AppendValueOption(kL2tpIpsecUserProperty, "--user", options);
|
|
AppendValueOption(kL2TPIPSecIPSecTimeoutProperty, "--ipsec_timeout", options);
|
|
AppendValueOption(kL2TPIPSecLeftProtoPortProperty,
|
|
"--leftprotoport", options);
|
|
AppendFlag(kL2TPIPSecPFSProperty, "--pfs", "--nopfs", options);
|
|
AppendFlag(kL2TPIPSecRekeyProperty, "--rekey", "--norekey", options);
|
|
AppendValueOption(kL2TPIPSecRightProtoPortProperty,
|
|
"--rightprotoport", options);
|
|
AppendFlag(kL2TPIPSecRequireChapProperty,
|
|
"--require_chap", "--norequire_chap", options);
|
|
AppendFlag(kL2TPIPSecRefusePapProperty,
|
|
"--refuse_pap", "--norefuse_pap", options);
|
|
AppendFlag(kL2TPIPSecRequireAuthProperty,
|
|
"--require_authentication", "--norequire_authentication", options);
|
|
AppendFlag(kL2TPIPSecLengthBitProperty,
|
|
"--length_bit", "--nolength_bit", options);
|
|
AppendFlag(kL2tpIpsecLcpEchoDisabledProperty,
|
|
"--noppp_lcp_echo", "--ppp_lcp_echo", options);
|
|
AppendValueOption(kL2tpIpsecTunnelGroupProperty, "--tunnel_group", options);
|
|
if (SLOG_IS_ON(VPN, 0)) {
|
|
options->push_back("--debug");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool L2TPIPSecDriver::InitPSKOptions(vector<string>* options, Error* error) {
|
|
string psk = args()->LookupString(kL2tpIpsecPskProperty, "");
|
|
if (!psk.empty()) {
|
|
if (!base::CreateTemporaryFileInDir(manager()->run_path(), &psk_file_) ||
|
|
chmod(psk_file_.value().c_str(), S_IRUSR | S_IWUSR) ||
|
|
base::WriteFile(psk_file_, psk.data(), psk.size()) !=
|
|
static_cast<int>(psk.size())) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInternalError, "Unable to setup psk file.");
|
|
return false;
|
|
}
|
|
options->push_back(base::StringPrintf("--psk_file=%s",
|
|
psk_file_.value().c_str()));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool L2TPIPSecDriver::InitPEMOptions(vector<string>* options) {
|
|
vector<string> ca_certs;
|
|
if (args()->ContainsStrings(kL2tpIpsecCaCertPemProperty)) {
|
|
ca_certs = args()->GetStrings(kL2tpIpsecCaCertPemProperty);
|
|
}
|
|
if (ca_certs.empty()) {
|
|
return false;
|
|
}
|
|
FilePath certfile = certificate_file_->CreatePEMFromStrings(ca_certs);
|
|
if (certfile.empty()) {
|
|
LOG(ERROR) << "Unable to extract certificates from PEM string.";
|
|
return false;
|
|
}
|
|
options->push_back(base::StringPrintf("--server_ca_file=%s",
|
|
certfile.value().c_str()));
|
|
return true;
|
|
}
|
|
|
|
bool L2TPIPSecDriver::InitXauthOptions(vector<string>* options, Error* error) {
|
|
string user = args()->LookupString(kL2tpIpsecXauthUserProperty, "");
|
|
string password = args()->LookupString(kL2tpIpsecXauthPasswordProperty, "");
|
|
if (user.empty() && password.empty()) {
|
|
// Xauth credentials not configured.
|
|
return true;
|
|
}
|
|
if (user.empty() || password.empty()) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInvalidArguments,
|
|
"XAUTH credentials are partially configured.");
|
|
return false;
|
|
}
|
|
string xauth_credentials = user + "\n" + password + "\n";
|
|
if (!base::CreateTemporaryFileInDir(manager()->run_path(),
|
|
&xauth_credentials_file_) ||
|
|
chmod(xauth_credentials_file_.value().c_str(), S_IRUSR | S_IWUSR) ||
|
|
base::WriteFile(xauth_credentials_file_, xauth_credentials.data(),
|
|
xauth_credentials.size()) !=
|
|
static_cast<int>(xauth_credentials.size())) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInternalError,
|
|
"Unable to setup XAUTH credentials file.");
|
|
return false;
|
|
}
|
|
options->push_back(base::StringPrintf("--xauth_credentials_file=%s",
|
|
xauth_credentials_file_.value().c_str()));
|
|
return true;
|
|
}
|
|
|
|
bool L2TPIPSecDriver::AppendValueOption(
|
|
const string& property, const string& option, vector<string>* options) {
|
|
string value = args()->LookupString(property, "");
|
|
if (!value.empty()) {
|
|
options->push_back(base::StringPrintf("%s=%s", option.c_str(),
|
|
value.c_str()));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool L2TPIPSecDriver::AppendFlag(const string& property,
|
|
const string& true_option,
|
|
const string& false_option,
|
|
vector<string>* options) {
|
|
string value = args()->LookupString(property, "");
|
|
if (!value.empty()) {
|
|
options->push_back(value == "true" ? true_option : false_option);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void L2TPIPSecDriver::OnL2TPIPSecVPNDied(pid_t /*pid*/, int status) {
|
|
FailService(TranslateExitStatusToFailure(status));
|
|
// TODO(petkov): Figure if we need to restart the connection.
|
|
}
|
|
|
|
// static
|
|
Service::ConnectFailure L2TPIPSecDriver::TranslateExitStatusToFailure(
|
|
int status) {
|
|
if (!WIFEXITED(status)) {
|
|
return Service::kFailureInternal;
|
|
}
|
|
switch (WEXITSTATUS(status)) {
|
|
case vpn_manager::kServiceErrorResolveHostnameFailed:
|
|
return Service::kFailureDNSLookup;
|
|
case vpn_manager::kServiceErrorIpsecConnectionFailed:
|
|
case vpn_manager::kServiceErrorL2tpConnectionFailed:
|
|
case vpn_manager::kServiceErrorPppConnectionFailed:
|
|
return Service::kFailureConnect;
|
|
case vpn_manager::kServiceErrorIpsecPresharedKeyAuthenticationFailed:
|
|
return Service::kFailureIPSecPSKAuth;
|
|
case vpn_manager::kServiceErrorIpsecCertificateAuthenticationFailed:
|
|
return Service::kFailureIPSecCertAuth;
|
|
case vpn_manager::kServiceErrorPppAuthenticationFailed:
|
|
return Service::kFailurePPPAuth;
|
|
default:
|
|
break;
|
|
}
|
|
return Service::kFailureUnknown;
|
|
}
|
|
|
|
void L2TPIPSecDriver::GetLogin(string* user, string* password) {
|
|
LOG(INFO) << "Login requested.";
|
|
string user_property =
|
|
args()->LookupString(kL2tpIpsecUserProperty, "");
|
|
if (user_property.empty()) {
|
|
LOG(ERROR) << "User not set.";
|
|
return;
|
|
}
|
|
string password_property =
|
|
args()->LookupString(kL2tpIpsecPasswordProperty, "");
|
|
if (password_property.empty()) {
|
|
LOG(ERROR) << "Password not set.";
|
|
return;
|
|
}
|
|
*user = user_property;
|
|
*password = password_property;
|
|
}
|
|
|
|
void L2TPIPSecDriver::Notify(
|
|
const string& reason, const map<string, string>& dict) {
|
|
LOG(INFO) << "IP configuration received: " << reason;
|
|
|
|
if (reason == kPPPReasonAuthenticating ||
|
|
reason == kPPPReasonAuthenticated) {
|
|
// These are uninteresting intermediate states that do not indicate failure.
|
|
return;
|
|
}
|
|
|
|
if (reason != kPPPReasonConnect) {
|
|
DCHECK_EQ(kPPPReasonDisconnect, reason);
|
|
// DestroyLater, rather than while on stack.
|
|
external_task_.release()->DestroyLater(dispatcher());
|
|
FailService(Service::kFailureUnknown);
|
|
return;
|
|
}
|
|
|
|
DeleteTemporaryFiles();
|
|
|
|
string interface_name = PPPDevice::GetInterfaceName(dict);
|
|
int interface_index = device_info_->GetIndex(interface_name);
|
|
if (interface_index < 0) {
|
|
// TODO(petkov): Consider handling the race when the RTNL notification about
|
|
// the new PPP device has not been received yet. We can keep the IP
|
|
// configuration and apply it when ClaimInterface is
|
|
// invoked. crbug.com/212446.
|
|
NOTIMPLEMENTED() << ": No device info for " << interface_name << ".";
|
|
return;
|
|
}
|
|
|
|
// There is no IPv6 support for L2TP/IPsec VPN at this moment, so create a
|
|
// blackhole route for IPv6 traffic after establishing a IPv4 VPN.
|
|
// TODO(benchan): Generalize this when IPv6 support is added.
|
|
bool blackhole_ipv6 = true;
|
|
|
|
if (!device_) {
|
|
device_ = ppp_device_factory_->CreatePPPDevice(
|
|
control_, dispatcher(), metrics_, manager(), interface_name,
|
|
interface_index);
|
|
}
|
|
device_->SetEnabled(true);
|
|
device_->SelectService(service_);
|
|
|
|
// Reduce MTU to the minimum viable for IPv6, since the IPSec layer consumes
|
|
// some variable portion of the payload. Although this system does not yet
|
|
// support IPv6, it is a reasonable value to start with, since the minimum
|
|
// IPv6 packet size will plausibly be a size any gateway would support, and
|
|
// is also larger than the IPv4 minimum size.
|
|
device_->UpdateIPConfigFromPPPWithMTU(
|
|
dict, blackhole_ipv6, IPConfig::kMinIPv6MTU);
|
|
|
|
ReportConnectionMetrics();
|
|
StopConnectTimeout();
|
|
}
|
|
|
|
bool L2TPIPSecDriver::IsPskRequired() const {
|
|
return
|
|
const_args()->LookupString(kL2tpIpsecPskProperty, "").empty() &&
|
|
const_args()->LookupString(kL2tpIpsecClientCertIdProperty, "").empty();
|
|
}
|
|
|
|
KeyValueStore L2TPIPSecDriver::GetProvider(Error* error) {
|
|
SLOG(this, 2) << __func__;
|
|
KeyValueStore props = VPNDriver::GetProvider(error);
|
|
props.SetBool(kPassphraseRequiredProperty,
|
|
args()->LookupString(kL2tpIpsecPasswordProperty, "").empty());
|
|
props.SetBool(kL2tpIpsecPskRequiredProperty, IsPskRequired());
|
|
return props;
|
|
}
|
|
|
|
void L2TPIPSecDriver::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()->LookupString(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()->LookupString(kL2tpIpsecClientCertIdProperty,
|
|
"") != "") {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeL2tpIpsecCertificate,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
has_user_authentication = true;
|
|
}
|
|
if (args()->LookupString(kL2tpIpsecPasswordProperty, "") != "") {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeL2tpIpsecUsernamePassword,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
has_user_authentication = true;
|
|
}
|
|
if (!has_user_authentication) {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeL2tpIpsecNone,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
}
|
|
}
|
|
|
|
} // namespace shill
|