1123 lines
40 KiB
C++
1123 lines
40 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.
|
|
//
|
|
|
|
#include "shill/vpn/openvpn_driver.h"
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <base/files/file_util.h>
|
|
#include <base/strings/string_number_conversions.h>
|
|
#include <base/strings/string_split.h>
|
|
#include <base/strings/string_util.h>
|
|
#if defined(__ANDROID__)
|
|
#include <dbus/service_constants.h>
|
|
#else
|
|
#include <chromeos/dbus/service_constants.h>
|
|
#endif // __ANDROID__
|
|
|
|
#include "shill/certificate_file.h"
|
|
#include "shill/connection.h"
|
|
#include "shill/device_info.h"
|
|
#include "shill/error.h"
|
|
#include "shill/ipconfig.h"
|
|
#include "shill/logging.h"
|
|
#include "shill/manager.h"
|
|
#include "shill/net/sockets.h"
|
|
#include "shill/process_manager.h"
|
|
#include "shill/rpc_task.h"
|
|
#include "shill/virtual_device.h"
|
|
#include "shill/vpn/openvpn_management_server.h"
|
|
#include "shill/vpn/vpn_service.h"
|
|
|
|
using base::Closure;
|
|
using base::FilePath;
|
|
using base::SplitString;
|
|
using base::Unretained;
|
|
using base::WeakPtr;
|
|
using std::map;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
namespace shill {
|
|
|
|
namespace Logging {
|
|
static auto kModuleLogScope = ScopeLogger::kVPN;
|
|
static string ObjectID(const OpenVPNDriver* o) {
|
|
return o->GetServiceRpcIdentifier();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
const char kChromeOSReleaseName[] = "CHROMEOS_RELEASE_NAME";
|
|
const char kChromeOSReleaseVersion[] = "CHROMEOS_RELEASE_VERSION";
|
|
const char kOpenVPNEnvVarPlatformName[] = "IV_PLAT";
|
|
const char kOpenVPNEnvVarPlatformVersion[] = "IV_PLAT_REL";
|
|
const char kOpenVPNForeignOptionPrefix[] = "foreign_option_";
|
|
const char kOpenVPNIfconfigBroadcast[] = "ifconfig_broadcast";
|
|
const char kOpenVPNIfconfigLocal[] = "ifconfig_local";
|
|
const char kOpenVPNIfconfigNetmask[] = "ifconfig_netmask";
|
|
const char kOpenVPNIfconfigRemote[] = "ifconfig_remote";
|
|
const char kOpenVPNRedirectGateway[] = "redirect_gateway";
|
|
const char kOpenVPNRedirectPrivate[] = "redirect_private";
|
|
const char kOpenVPNRouteOptionPrefix[] = "route_";
|
|
const char kOpenVPNRouteVPNGateway[] = "route_vpn_gateway";
|
|
const char kOpenVPNTrustedIP[] = "trusted_ip";
|
|
const char kOpenVPNTunMTU[] = "tun_mtu";
|
|
|
|
const char kDefaultPKCS11Provider[] = "libchaps.so";
|
|
|
|
// Some configurations pass the netmask in the ifconfig_remote property.
|
|
// This is due to some servers not explicitly indicating that they are using
|
|
// a "broadcast mode" network instead of peer-to-peer. See
|
|
// http://crbug.com/241264 for an example of this issue.
|
|
const char kSuspectedNetmaskPrefix[] = "255.";
|
|
|
|
void DoNothingWithExitStatus(int exit_status) {
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
const char OpenVPNDriver::kDefaultCACertificates[] =
|
|
"/etc/ssl/certs/ca-certificates.crt";
|
|
// static
|
|
const char OpenVPNDriver::kOpenVPNPath[] = "/usr/sbin/openvpn";
|
|
// static
|
|
const char OpenVPNDriver::kOpenVPNScript[] = SHIMDIR "/openvpn-script";
|
|
// static
|
|
const VPNDriver::Property OpenVPNDriver::kProperties[] = {
|
|
{ kOpenVPNAuthNoCacheProperty, 0 },
|
|
{ kOpenVPNAuthProperty, 0 },
|
|
{ kOpenVPNAuthRetryProperty, 0 },
|
|
{ kOpenVPNAuthUserPassProperty, 0 },
|
|
{ kOpenVPNCaCertNSSProperty, 0 },
|
|
{ kOpenVPNCaCertProperty, 0 },
|
|
{ kOpenVPNCipherProperty, 0 },
|
|
{ kOpenVPNClientCertIdProperty, Property::kCredential },
|
|
{ kOpenVPNCompLZOProperty, 0 },
|
|
{ kOpenVPNCompNoAdaptProperty, 0 },
|
|
{ kOpenVPNIgnoreDefaultRouteProperty, 0 },
|
|
{ kOpenVPNKeyDirectionProperty, 0 },
|
|
{ kOpenVPNNsCertTypeProperty, 0 },
|
|
{ kOpenVPNOTPProperty,
|
|
Property::kEphemeral | Property::kCredential | Property::kWriteOnly },
|
|
{ kOpenVPNPasswordProperty, Property::kCredential | Property::kWriteOnly },
|
|
{ kOpenVPNPinProperty, Property::kCredential },
|
|
{ kOpenVPNPortProperty, 0 },
|
|
{ kOpenVPNProtoProperty, 0 },
|
|
{ kOpenVPNProviderProperty, 0 },
|
|
{ kOpenVPNPushPeerInfoProperty, 0 },
|
|
{ kOpenVPNRemoteCertEKUProperty, 0 },
|
|
{ kOpenVPNRemoteCertKUProperty, 0 },
|
|
{ kOpenVPNRemoteCertTLSProperty, 0 },
|
|
{ kOpenVPNRenegSecProperty, 0 },
|
|
{ kOpenVPNServerPollTimeoutProperty, 0 },
|
|
{ kOpenVPNShaperProperty, 0 },
|
|
{ kOpenVPNStaticChallengeProperty, 0 },
|
|
{ kOpenVPNTLSAuthContentsProperty, 0 },
|
|
{ kOpenVPNTLSRemoteProperty, 0 },
|
|
{ kOpenVPNTokenProperty,
|
|
Property::kEphemeral | Property::kCredential | Property::kWriteOnly },
|
|
{ kOpenVPNUserProperty, 0 },
|
|
{ kProviderHostProperty, 0 },
|
|
{ kProviderTypeProperty, 0 },
|
|
{ kOpenVPNCaCertPemProperty, Property::kArray },
|
|
{ kOpenVPNCertProperty, 0 },
|
|
{ kOpenVPNExtraCertPemProperty, Property::kArray },
|
|
{ kOpenVPNKeyProperty, 0 },
|
|
{ kOpenVPNPingExitProperty, 0 },
|
|
{ kOpenVPNPingProperty, 0 },
|
|
{ kOpenVPNPingRestartProperty, 0 },
|
|
{ kOpenVPNTLSAuthProperty, 0 },
|
|
{ kOpenVPNVerbProperty, 0 },
|
|
{ kOpenVPNVerifyHashProperty, 0 },
|
|
{ kOpenVPNVerifyX509NameProperty, 0 },
|
|
{ kOpenVPNVerifyX509TypeProperty, 0 },
|
|
{ kVPNMTUProperty, 0 },
|
|
};
|
|
|
|
const char OpenVPNDriver::kLSBReleaseFile[] = "/etc/lsb-release";
|
|
|
|
// Directory where OpenVPN configuration files are exported while the
|
|
// process is running.
|
|
const char OpenVPNDriver::kDefaultOpenVPNConfigurationDirectory[] =
|
|
RUNDIR "/openvpn_config";
|
|
|
|
const int OpenVPNDriver::kReconnectOfflineTimeoutSeconds = 2 * 60;
|
|
const int OpenVPNDriver::kReconnectTLSErrorTimeoutSeconds = 20;
|
|
|
|
OpenVPNDriver::OpenVPNDriver(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),
|
|
management_server_(new OpenVPNManagementServer(this)),
|
|
certificate_file_(new CertificateFile()),
|
|
extra_certificates_file_(new CertificateFile()),
|
|
lsb_release_file_(kLSBReleaseFile),
|
|
openvpn_config_directory_(kDefaultOpenVPNConfigurationDirectory),
|
|
pid_(0),
|
|
default_service_callback_tag_(0) {}
|
|
|
|
OpenVPNDriver::~OpenVPNDriver() {
|
|
IdleService();
|
|
}
|
|
|
|
void OpenVPNDriver::IdleService() {
|
|
Cleanup(Service::kStateIdle,
|
|
Service::kFailureUnknown,
|
|
Service::kErrorDetailsNone);
|
|
}
|
|
|
|
void OpenVPNDriver::FailService(Service::ConnectFailure failure,
|
|
const string& error_details) {
|
|
Cleanup(Service::kStateFailure, failure, error_details);
|
|
}
|
|
|
|
void OpenVPNDriver::Cleanup(Service::ConnectState state,
|
|
Service::ConnectFailure failure,
|
|
const string& error_details) {
|
|
SLOG(this, 2) << __func__ << "(" << Service::ConnectStateToString(state)
|
|
<< ", " << error_details << ")";
|
|
StopConnectTimeout();
|
|
// Disconnecting the management interface will terminate the openvpn
|
|
// process. Ensure this is handled robustly by first unregistering
|
|
// the callback for OnOpenVPNDied, and then terminating and reaping
|
|
// the process with StopProcess().
|
|
if (pid_) {
|
|
process_manager_->UpdateExitCallback(
|
|
pid_, base::Bind(DoNothingWithExitStatus));
|
|
}
|
|
management_server_->Stop();
|
|
if (!tls_auth_file_.empty()) {
|
|
base::DeleteFile(tls_auth_file_, false);
|
|
tls_auth_file_.clear();
|
|
}
|
|
if (!openvpn_config_file_.empty()) {
|
|
base::DeleteFile(openvpn_config_file_, false);
|
|
openvpn_config_file_.clear();
|
|
}
|
|
if (default_service_callback_tag_) {
|
|
manager()->DeregisterDefaultServiceCallback(default_service_callback_tag_);
|
|
default_service_callback_tag_ = 0;
|
|
}
|
|
rpc_task_.reset();
|
|
int interface_index = -1;
|
|
if (device_) {
|
|
interface_index = device_->interface_index();
|
|
device_->DropConnection();
|
|
device_->SetEnabled(false);
|
|
device_ = nullptr;
|
|
}
|
|
if (pid_) {
|
|
if (interface_index >= 0) {
|
|
// NB: |callback| must be bound to a static method, as
|
|
// |callback| may be called after our dtor completes.
|
|
const auto callback(
|
|
Bind(OnOpenVPNExited, device_info_->AsWeakPtr(), interface_index));
|
|
interface_index = -1;
|
|
process_manager_->UpdateExitCallback(pid_, callback);
|
|
}
|
|
process_manager_->StopProcess(pid_);
|
|
pid_ = 0;
|
|
}
|
|
if (interface_index >= 0) {
|
|
device_info_->DeleteInterface(interface_index);
|
|
}
|
|
tunnel_interface_.clear();
|
|
if (service_) {
|
|
if (state == Service::kStateFailure) {
|
|
service_->SetErrorDetails(error_details);
|
|
service_->SetFailure(failure);
|
|
} else {
|
|
service_->SetState(state);
|
|
}
|
|
service_ = nullptr;
|
|
}
|
|
ip_properties_ = IPConfig::Properties();
|
|
}
|
|
|
|
// static
|
|
string OpenVPNDriver::JoinOptions(const vector<vector<string>>& options,
|
|
char separator) {
|
|
vector<string> option_strings;
|
|
for (const auto& option : options) {
|
|
vector<string> quoted_option;
|
|
for (const auto& argument : option) {
|
|
if (argument.find(' ') != string::npos ||
|
|
argument.find('\t') != string::npos ||
|
|
argument.find('"') != string::npos ||
|
|
argument.find(separator) != string::npos) {
|
|
string quoted_argument(argument);
|
|
const char separator_chars[] = { separator, '\0' };
|
|
base::ReplaceChars(argument, separator_chars, " ", "ed_argument);
|
|
base::ReplaceChars(quoted_argument, "\\", "\\\\", "ed_argument);
|
|
base::ReplaceChars(quoted_argument, "\"", "\\\"", "ed_argument);
|
|
quoted_option.push_back("\"" + quoted_argument + "\"");
|
|
} else {
|
|
quoted_option.push_back(argument);
|
|
}
|
|
}
|
|
option_strings.push_back(base::JoinString(quoted_option, " "));
|
|
}
|
|
return base::JoinString(option_strings, string{separator});
|
|
}
|
|
|
|
bool OpenVPNDriver::WriteConfigFile(
|
|
const vector<vector<string>>& options,
|
|
FilePath* config_file) {
|
|
if (!base::DirectoryExists(openvpn_config_directory_)) {
|
|
if (!base::CreateDirectory(openvpn_config_directory_)) {
|
|
LOG(ERROR) << "Unable to create configuration directory "
|
|
<< openvpn_config_directory_.value();
|
|
return false;
|
|
}
|
|
if (chmod(openvpn_config_directory_.value().c_str(), S_IRWXU)) {
|
|
LOG(ERROR) << "Failed to set permissions on "
|
|
<< openvpn_config_directory_.value();
|
|
base::DeleteFile(openvpn_config_directory_, true);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
string contents = JoinOptions(options, '\n');
|
|
contents.push_back('\n');
|
|
if (!base::CreateTemporaryFileInDir(openvpn_config_directory_, config_file) ||
|
|
base::WriteFile(*config_file, contents.data(), contents.size()) !=
|
|
static_cast<int>(contents.size())) {
|
|
LOG(ERROR) << "Unable to setup OpenVPN config file.";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool OpenVPNDriver::SpawnOpenVPN() {
|
|
SLOG(this, 2) << __func__ << "(" << tunnel_interface_ << ")";
|
|
|
|
vector<vector<string>> options;
|
|
Error error;
|
|
InitOptions(&options, &error);
|
|
if (error.IsFailure()) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << "OpenVPN process options: " << JoinOptions(options, ',');
|
|
if (!WriteConfigFile(options, &openvpn_config_file_)) {
|
|
return false;
|
|
}
|
|
|
|
// TODO(quiche): This should be migrated to use ExternalTask.
|
|
// (crbug.com/246263).
|
|
CHECK(!pid_);
|
|
pid_t pid = process_manager_->StartProcess(
|
|
FROM_HERE, FilePath(kOpenVPNPath),
|
|
vector<string>{"--config", openvpn_config_file_.value()},
|
|
GetEnvironment(),
|
|
false, // Do not terminate with parent.
|
|
base::Bind(&OpenVPNDriver::OnOpenVPNDied, base::Unretained(this)));
|
|
if (pid < 0) {
|
|
LOG(ERROR) << "Unable to spawn: " << kOpenVPNPath;
|
|
return false;
|
|
}
|
|
|
|
pid_ = pid;
|
|
return true;
|
|
}
|
|
|
|
void OpenVPNDriver::OnOpenVPNDied(int exit_status) {
|
|
SLOG(nullptr, 2) << __func__ << "(" << pid_ << ", " << exit_status << ")";
|
|
pid_ = 0;
|
|
FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
// TODO(petkov): Figure if we need to restart the connection.
|
|
}
|
|
|
|
// static
|
|
void OpenVPNDriver::OnOpenVPNExited(const WeakPtr<DeviceInfo>& device_info,
|
|
int interface_index,
|
|
int /* exit_status */) {
|
|
if (device_info) {
|
|
LOG(INFO) << "Deleting interface " << interface_index;
|
|
device_info->DeleteInterface(interface_index);
|
|
}
|
|
}
|
|
|
|
bool OpenVPNDriver::ClaimInterface(const string& link_name,
|
|
int interface_index) {
|
|
if (link_name != tunnel_interface_) {
|
|
return false;
|
|
}
|
|
|
|
SLOG(this, 2) << "Claiming " << link_name << " for OpenVPN tunnel";
|
|
|
|
CHECK(!device_);
|
|
device_ = new VirtualDevice(control_, dispatcher(), metrics_, manager(),
|
|
link_name, interface_index, Technology::kVPN);
|
|
device_->SetEnabled(true);
|
|
|
|
rpc_task_.reset(new RPCTask(control_, this));
|
|
if (SpawnOpenVPN()) {
|
|
default_service_callback_tag_ =
|
|
manager()->RegisterDefaultServiceCallback(
|
|
Bind(&OpenVPNDriver::OnDefaultServiceChanged, Unretained(this)));
|
|
} else {
|
|
FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void OpenVPNDriver::GetLogin(string* /*user*/, string* /*password*/) {
|
|
NOTREACHED();
|
|
}
|
|
|
|
void OpenVPNDriver::Notify(const string& reason,
|
|
const map<string, string>& dict) {
|
|
LOG(INFO) << "IP configuration received: " << reason;
|
|
if (reason != "up") {
|
|
device_->DropConnection();
|
|
return;
|
|
}
|
|
// On restart/reconnect, update the existing IP configuration.
|
|
ParseIPConfiguration(dict, &ip_properties_);
|
|
device_->SelectService(service_);
|
|
device_->UpdateIPConfig(ip_properties_);
|
|
ReportConnectionMetrics();
|
|
StopConnectTimeout();
|
|
}
|
|
|
|
void OpenVPNDriver::ParseIPConfiguration(
|
|
const map<string, string>& configuration,
|
|
IPConfig::Properties* properties) const {
|
|
ForeignOptions foreign_options;
|
|
RouteOptions routes;
|
|
bool is_gateway_route_required = false;
|
|
|
|
properties->address_family = IPAddress::kFamilyIPv4;
|
|
if (!properties->subnet_prefix) {
|
|
properties->subnet_prefix =
|
|
IPAddress::GetMaxPrefixLength(properties->address_family);
|
|
}
|
|
for (const auto& configuration_map : configuration) {
|
|
const string& key = configuration_map.first;
|
|
const string& value = configuration_map.second;
|
|
SLOG(this, 2) << "Processing: " << key << " -> " << value;
|
|
if (base::LowerCaseEqualsASCII(key, kOpenVPNIfconfigLocal)) {
|
|
properties->address = value;
|
|
} else if (base::LowerCaseEqualsASCII(key, kOpenVPNIfconfigBroadcast)) {
|
|
properties->broadcast_address = value;
|
|
} else if (base::LowerCaseEqualsASCII(key, kOpenVPNIfconfigNetmask)) {
|
|
properties->subnet_prefix =
|
|
IPAddress::GetPrefixLengthFromMask(properties->address_family, value);
|
|
} else if (base::LowerCaseEqualsASCII(key, kOpenVPNIfconfigRemote)) {
|
|
if (base::StartsWith(value, kSuspectedNetmaskPrefix,
|
|
base::CompareCase::INSENSITIVE_ASCII)) {
|
|
LOG(WARNING) << "Option " << key << " value " << value
|
|
<< " looks more like a netmask than a peer address; "
|
|
<< "assuming it is the former.";
|
|
// In this situation, the "peer_address" value will be left
|
|
// unset and Connection::UpdateFromIPConfig() will treat the
|
|
// interface as if it were a broadcast-style network. The
|
|
// kernel will, automatically set the peer address equal to
|
|
// the local address.
|
|
properties->subnet_prefix =
|
|
IPAddress::GetPrefixLengthFromMask(properties->address_family,
|
|
value);
|
|
} else {
|
|
properties->peer_address = value;
|
|
}
|
|
} else if (base::LowerCaseEqualsASCII(key, kOpenVPNRedirectGateway) ||
|
|
base::LowerCaseEqualsASCII(key, kOpenVPNRedirectPrivate)) {
|
|
is_gateway_route_required = true;
|
|
} else if (base::LowerCaseEqualsASCII(key, kOpenVPNRouteVPNGateway)) {
|
|
properties->gateway = value;
|
|
} else if (base::LowerCaseEqualsASCII(key, kOpenVPNTrustedIP)) {
|
|
size_t prefix = IPAddress::GetMaxPrefixLength(properties->address_family);
|
|
properties->exclusion_list.push_back(value + "/" +
|
|
base::SizeTToString(prefix));
|
|
|
|
} else if (base::LowerCaseEqualsASCII(key, kOpenVPNTunMTU)) {
|
|
int mtu = 0;
|
|
if (base::StringToInt(value, &mtu) && mtu >= IPConfig::kMinIPv4MTU) {
|
|
properties->mtu = mtu;
|
|
} else {
|
|
LOG(ERROR) << "MTU " << value << " ignored.";
|
|
}
|
|
} else if (base::StartsWith(key, kOpenVPNForeignOptionPrefix,
|
|
base::CompareCase::INSENSITIVE_ASCII)) {
|
|
const string suffix = key.substr(strlen(kOpenVPNForeignOptionPrefix));
|
|
int order = 0;
|
|
if (base::StringToInt(suffix, &order)) {
|
|
foreign_options[order] = value;
|
|
} else {
|
|
LOG(ERROR) << "Ignored unexpected foreign option suffix: " << suffix;
|
|
}
|
|
} else if (base::StartsWith(key, kOpenVPNRouteOptionPrefix,
|
|
base::CompareCase::INSENSITIVE_ASCII)) {
|
|
ParseRouteOption(key.substr(strlen(kOpenVPNRouteOptionPrefix)),
|
|
value, &routes);
|
|
} else {
|
|
SLOG(this, 2) << "Key ignored.";
|
|
}
|
|
}
|
|
ParseForeignOptions(foreign_options, properties);
|
|
SetRoutes(routes, properties);
|
|
|
|
if (const_args()->ContainsString(kOpenVPNIgnoreDefaultRouteProperty)) {
|
|
if (is_gateway_route_required) {
|
|
LOG(INFO) << "Configuration request to ignore default route is "
|
|
<< "overridden by the remote server.";
|
|
} else {
|
|
SLOG(this, 2) << "Ignoring default route parameter as requested by "
|
|
<< "configuration.";
|
|
properties->gateway.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
// static
|
|
void OpenVPNDriver::ParseForeignOptions(const ForeignOptions& options,
|
|
IPConfig::Properties* properties) {
|
|
vector<string> domain_search;
|
|
vector<string> dns_servers;
|
|
for (const auto& option_map : options) {
|
|
ParseForeignOption(option_map.second, &domain_search, &dns_servers);
|
|
}
|
|
if (!domain_search.empty()) {
|
|
properties->domain_search.swap(domain_search);
|
|
}
|
|
LOG_IF(WARNING, properties->domain_search.empty())
|
|
<< "No search domains provided.";
|
|
if (!dns_servers.empty()) {
|
|
properties->dns_servers.swap(dns_servers);
|
|
}
|
|
LOG_IF(WARNING, properties->dns_servers.empty())
|
|
<< "No DNS servers provided.";
|
|
}
|
|
|
|
// static
|
|
void OpenVPNDriver::ParseForeignOption(const string& option,
|
|
vector<string>* domain_search,
|
|
vector<string>* dns_servers) {
|
|
SLOG(nullptr, 2) << __func__ << "(" << option << ")";
|
|
vector<string> tokens = SplitString(option, " ", base::TRIM_WHITESPACE,
|
|
base::SPLIT_WANT_ALL);
|
|
if (tokens.size() != 3 ||
|
|
!base::LowerCaseEqualsASCII(tokens[0], "dhcp-option")) {
|
|
return;
|
|
}
|
|
if (base::LowerCaseEqualsASCII(tokens[1], "domain")) {
|
|
domain_search->push_back(tokens[2]);
|
|
} else if (base::LowerCaseEqualsASCII(tokens[1], "dns")) {
|
|
dns_servers->push_back(tokens[2]);
|
|
}
|
|
}
|
|
|
|
// static
|
|
IPConfig::Route* OpenVPNDriver::GetRouteOptionEntry(
|
|
const string& prefix, const string& key, RouteOptions* routes) {
|
|
int order = 0;
|
|
if (!base::StartsWith(key, prefix, base::CompareCase::INSENSITIVE_ASCII) ||
|
|
!base::StringToInt(key.substr(prefix.size()), &order)) {
|
|
return nullptr;
|
|
}
|
|
return&(*routes)[order];
|
|
}
|
|
|
|
// static
|
|
void OpenVPNDriver::ParseRouteOption(
|
|
const string& key, const string& value, RouteOptions* routes) {
|
|
IPConfig::Route* route = GetRouteOptionEntry("network_", key, routes);
|
|
if (route) {
|
|
route->host = value;
|
|
return;
|
|
}
|
|
route = GetRouteOptionEntry("netmask_", key, routes);
|
|
if (route) {
|
|
route->netmask = value;
|
|
return;
|
|
}
|
|
route = GetRouteOptionEntry("gateway_", key, routes);
|
|
if (route) {
|
|
route->gateway = value;
|
|
return;
|
|
}
|
|
LOG(WARNING) << "Unknown route option ignored: " << key;
|
|
}
|
|
|
|
// static
|
|
void OpenVPNDriver::SetRoutes(const RouteOptions& routes,
|
|
IPConfig::Properties* properties) {
|
|
vector<IPConfig::Route> new_routes;
|
|
for (const auto& route_map : routes) {
|
|
const IPConfig::Route& route = route_map.second;
|
|
if (route.host.empty() || route.netmask.empty() || route.gateway.empty()) {
|
|
LOG(WARNING) << "Ignoring incomplete route: " << route_map.first;
|
|
continue;
|
|
}
|
|
new_routes.push_back(route);
|
|
}
|
|
if (!new_routes.empty()) {
|
|
properties->routes.swap(new_routes);
|
|
}
|
|
LOG_IF(WARNING, properties->routes.empty()) << "No routes provided.";
|
|
}
|
|
|
|
// static
|
|
bool OpenVPNDriver::SplitPortFromHost(
|
|
const string& host, string* name, string* port) {
|
|
vector<string> tokens = SplitString(host, ":", base::TRIM_WHITESPACE,
|
|
base::SPLIT_WANT_ALL);
|
|
int port_number = 0;
|
|
if (tokens.size() != 2 || tokens[0].empty() || tokens[1].empty() ||
|
|
!base::IsAsciiDigit(tokens[1][0]) ||
|
|
!base::StringToInt(tokens[1], &port_number) ||
|
|
port_number > std::numeric_limits<uint16_t>::max()) {
|
|
return false;
|
|
}
|
|
*name = tokens[0];
|
|
*port = tokens[1];
|
|
return true;
|
|
}
|
|
|
|
void OpenVPNDriver::Connect(const VPNServiceRefPtr& service, Error* error) {
|
|
StartConnectTimeout(kDefaultConnectTimeoutSeconds);
|
|
service_ = service;
|
|
service_->SetState(Service::kStateConfiguring);
|
|
if (!device_info_->CreateTunnelInterface(&tunnel_interface_)) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInternalError,
|
|
"Could not create tunnel interface.");
|
|
FailService(Service::kFailureInternal, Service::kErrorDetailsNone);
|
|
}
|
|
// Wait for the ClaimInterface callback to continue the connection process.
|
|
}
|
|
|
|
void OpenVPNDriver::InitOptions(vector<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;
|
|
}
|
|
AppendOption("client", options);
|
|
AppendOption("tls-client", options);
|
|
|
|
string host_name, host_port;
|
|
if (SplitPortFromHost(vpnhost, &host_name, &host_port)) {
|
|
DCHECK(!host_name.empty());
|
|
DCHECK(!host_port.empty());
|
|
AppendOption("remote", host_name, host_port, options);
|
|
} else {
|
|
AppendOption("remote", vpnhost, options);
|
|
}
|
|
|
|
AppendOption("nobind", options);
|
|
AppendOption("persist-key", options);
|
|
AppendOption("persist-tun", options);
|
|
|
|
CHECK(!tunnel_interface_.empty());
|
|
AppendOption("dev", tunnel_interface_, options);
|
|
AppendOption("dev-type", "tun", options);
|
|
|
|
InitLoggingOptions(options);
|
|
|
|
AppendValueOption(kVPNMTUProperty, "mtu", options);
|
|
AppendValueOption(kOpenVPNProtoProperty, "proto", options);
|
|
AppendValueOption(kOpenVPNPortProperty, "port", options);
|
|
AppendValueOption(kOpenVPNTLSAuthProperty, "tls-auth", options);
|
|
{
|
|
string contents =
|
|
args()->LookupString(kOpenVPNTLSAuthContentsProperty, "");
|
|
if (!contents.empty()) {
|
|
if (!base::CreateTemporaryFile(&tls_auth_file_) ||
|
|
base::WriteFile(tls_auth_file_, contents.data(), contents.size()) !=
|
|
static_cast<int>(contents.size())) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInternalError,
|
|
"Unable to setup tls-auth file.");
|
|
return;
|
|
}
|
|
AppendOption("tls-auth", tls_auth_file_.value(), options);
|
|
}
|
|
}
|
|
AppendValueOption(kOpenVPNTLSRemoteProperty, "tls-remote", options);
|
|
AppendValueOption(kOpenVPNCipherProperty, "cipher", options);
|
|
AppendValueOption(kOpenVPNAuthProperty, "auth", options);
|
|
AppendFlag(kOpenVPNAuthNoCacheProperty, "auth-nocache", options);
|
|
AppendValueOption(kOpenVPNAuthRetryProperty, "auth-retry", options);
|
|
AppendFlag(kOpenVPNCompLZOProperty, "comp-lzo", options);
|
|
AppendFlag(kOpenVPNCompNoAdaptProperty, "comp-noadapt", options);
|
|
AppendFlag(kOpenVPNPushPeerInfoProperty, "push-peer-info", options);
|
|
AppendValueOption(kOpenVPNRenegSecProperty, "reneg-sec", options);
|
|
AppendValueOption(kOpenVPNShaperProperty, "shaper", options);
|
|
AppendValueOption(kOpenVPNServerPollTimeoutProperty,
|
|
"server-poll-timeout", options);
|
|
|
|
if (!InitCAOptions(options, error)) {
|
|
return;
|
|
}
|
|
|
|
// Additional remote certificate verification options.
|
|
InitCertificateVerifyOptions(options);
|
|
if (!InitExtraCertOptions(options, error)) {
|
|
return;
|
|
}
|
|
|
|
// Client-side ping support.
|
|
AppendValueOption(kOpenVPNPingProperty, "ping", options);
|
|
AppendValueOption(kOpenVPNPingExitProperty, "ping-exit", options);
|
|
AppendValueOption(kOpenVPNPingRestartProperty, "ping-restart", options);
|
|
|
|
AppendValueOption(kOpenVPNNsCertTypeProperty, "ns-cert-type", options);
|
|
|
|
InitClientAuthOptions(options);
|
|
InitPKCS11Options(options);
|
|
|
|
// TLS suport.
|
|
string remote_cert_tls =
|
|
args()->LookupString(kOpenVPNRemoteCertTLSProperty, "");
|
|
if (remote_cert_tls.empty()) {
|
|
remote_cert_tls = "server";
|
|
}
|
|
if (remote_cert_tls != "none") {
|
|
AppendOption("remote-cert-tls", remote_cert_tls, options);
|
|
}
|
|
|
|
// This is an undocumented command line argument that works like a .cfg file
|
|
// entry. TODO(sleffler): Maybe roll this into the "tls-auth" option?
|
|
AppendValueOption(kOpenVPNKeyDirectionProperty, "key-direction", options);
|
|
AppendValueOption(kOpenVPNRemoteCertEKUProperty, "remote-cert-eku", options);
|
|
AppendDelimitedValueOption(kOpenVPNRemoteCertKUProperty,
|
|
"remote-cert-ku", ' ', options);
|
|
|
|
if (!InitManagementChannelOptions(options, error)) {
|
|
return;
|
|
}
|
|
|
|
// Setup openvpn-script options and RPC information required to send back
|
|
// Layer 3 configuration.
|
|
AppendOption("setenv", kRPCTaskServiceVariable,
|
|
rpc_task_->GetRpcConnectionIdentifier(), options);
|
|
AppendOption("setenv", kRPCTaskServiceVariable,
|
|
rpc_task_->GetRpcConnectionIdentifier(), options);
|
|
AppendOption("setenv", kRPCTaskPathVariable, rpc_task_->GetRpcIdentifier(),
|
|
options);
|
|
AppendOption("script-security", "2", options);
|
|
AppendOption("up", kOpenVPNScript, options);
|
|
AppendOption("up-restart", options);
|
|
|
|
// Disable openvpn handling since we do route+ifconfig work.
|
|
AppendOption("route-noexec", options);
|
|
AppendOption("ifconfig-noexec", options);
|
|
|
|
// Drop root privileges on connection and enable callback scripts to send
|
|
// notify messages.
|
|
AppendOption("user", "openvpn", options);
|
|
AppendOption("group", "openvpn", options);
|
|
}
|
|
|
|
bool OpenVPNDriver::InitCAOptions(
|
|
vector<vector<string>>* options, Error* error) {
|
|
string ca_cert =
|
|
args()->LookupString(kOpenVPNCaCertProperty, "");
|
|
vector<string> ca_cert_pem;
|
|
if (args()->ContainsStrings(kOpenVPNCaCertPemProperty)) {
|
|
ca_cert_pem = args()->GetStrings(kOpenVPNCaCertPemProperty);
|
|
}
|
|
|
|
int num_ca_cert_types = 0;
|
|
if (!ca_cert.empty())
|
|
num_ca_cert_types++;
|
|
if (!ca_cert_pem.empty())
|
|
num_ca_cert_types++;
|
|
if (num_ca_cert_types == 0) {
|
|
// Use default CAs if no CA certificate is provided.
|
|
AppendOption("ca", kDefaultCACertificates, options);
|
|
return true;
|
|
} else if (num_ca_cert_types > 1) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInvalidArguments,
|
|
"Can't specify more than one of CACert and CACertPEM.");
|
|
return false;
|
|
}
|
|
string cert_file;
|
|
if (!ca_cert_pem.empty()) {
|
|
DCHECK(ca_cert.empty());
|
|
FilePath certfile = certificate_file_->CreatePEMFromStrings(ca_cert_pem);
|
|
if (certfile.empty()) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE,
|
|
error,
|
|
Error::kInvalidArguments,
|
|
"Unable to extract PEM CA certificates.");
|
|
return false;
|
|
}
|
|
AppendOption("ca", certfile.value(), options);
|
|
return true;
|
|
}
|
|
DCHECK(!ca_cert.empty() && ca_cert_pem.empty());
|
|
AppendOption("ca", ca_cert, options);
|
|
return true;
|
|
}
|
|
|
|
void OpenVPNDriver::InitCertificateVerifyOptions(
|
|
std::vector<std::vector<std::string>>* options) {
|
|
AppendValueOption(kOpenVPNVerifyHashProperty, "verify-hash", options);
|
|
string x509_name = args()->LookupString(kOpenVPNVerifyX509NameProperty, "");
|
|
if (!x509_name.empty()) {
|
|
string x509_type = args()->LookupString(kOpenVPNVerifyX509TypeProperty, "");
|
|
if (x509_type.empty()) {
|
|
AppendOption("verify-x509-name", x509_name, options);
|
|
} else {
|
|
AppendOption("verify-x509-name", x509_name, x509_type, options);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool OpenVPNDriver::InitExtraCertOptions(
|
|
vector<vector<string>>* options, Error* error) {
|
|
if (!args()->ContainsStrings(kOpenVPNExtraCertPemProperty)) {
|
|
// It's okay for this parameter to be unspecified.
|
|
return true;
|
|
}
|
|
|
|
vector<string> extra_certs = args()->GetStrings(kOpenVPNExtraCertPemProperty);
|
|
if (extra_certs.empty()) {
|
|
// It's okay for this parameter to be empty.
|
|
return true;
|
|
}
|
|
|
|
FilePath certfile =
|
|
extra_certificates_file_->CreatePEMFromStrings(extra_certs);
|
|
if (certfile.empty()) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE,
|
|
error,
|
|
Error::kInvalidArguments,
|
|
"Unable to extract extra PEM CA certificates.");
|
|
return false;
|
|
}
|
|
|
|
AppendOption("extra-certs", certfile.value(), options);
|
|
return true;
|
|
}
|
|
|
|
void OpenVPNDriver::InitPKCS11Options(vector<vector<string>>* options) {
|
|
string id = args()->LookupString(kOpenVPNClientCertIdProperty, "");
|
|
if (!id.empty()) {
|
|
string provider =
|
|
args()->LookupString(kOpenVPNProviderProperty, "");
|
|
if (provider.empty()) {
|
|
provider = kDefaultPKCS11Provider;
|
|
}
|
|
AppendOption("pkcs11-providers", provider, options);
|
|
AppendOption("pkcs11-id", id, options);
|
|
}
|
|
}
|
|
|
|
void OpenVPNDriver::InitClientAuthOptions(vector<vector<string>>* options) {
|
|
bool has_cert = AppendValueOption(kOpenVPNCertProperty, "cert", options) ||
|
|
!args()->LookupString(kOpenVPNClientCertIdProperty, "").empty();
|
|
bool has_key = AppendValueOption(kOpenVPNKeyProperty, "key", options);
|
|
// If the AuthUserPass property is set, or the User property is non-empty, or
|
|
// there's neither a key, nor a cert available, specify user-password client
|
|
// authentication.
|
|
if (args()->ContainsString(kOpenVPNAuthUserPassProperty) ||
|
|
!args()->LookupString(kOpenVPNUserProperty, "").empty() ||
|
|
(!has_cert && !has_key)) {
|
|
AppendOption("auth-user-pass", options);
|
|
}
|
|
}
|
|
|
|
bool OpenVPNDriver::InitManagementChannelOptions(
|
|
vector<vector<string>>* options, Error* error) {
|
|
if (!management_server_->Start(dispatcher(), &sockets_, options)) {
|
|
Error::PopulateAndLog(
|
|
FROM_HERE, error, Error::kInternalError,
|
|
"Unable to setup management channel.");
|
|
return false;
|
|
}
|
|
// If there's a connected default service already, allow the openvpn client to
|
|
// establish connection as soon as it's started. Otherwise, hold the client
|
|
// until an underlying service connects and OnDefaultServiceChanged is
|
|
// invoked.
|
|
if (manager()->IsConnected()) {
|
|
management_server_->ReleaseHold();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void OpenVPNDriver::InitLoggingOptions(vector<vector<string>>* options) {
|
|
AppendOption("syslog", options);
|
|
|
|
string verb = args()->LookupString(kOpenVPNVerbProperty, "");
|
|
if (verb.empty() && SLOG_IS_ON(VPN, 0)) {
|
|
verb = "3";
|
|
}
|
|
if (!verb.empty()) {
|
|
AppendOption("verb", verb, options);
|
|
}
|
|
}
|
|
|
|
void OpenVPNDriver::AppendOption(
|
|
const string& option, vector<vector<string>>* options) {
|
|
options->push_back(vector<string>{ option });
|
|
}
|
|
|
|
void OpenVPNDriver::AppendOption(
|
|
const string& option,
|
|
const string& value,
|
|
vector<vector<string>>* options) {
|
|
options->push_back(vector<string>{ option, value });
|
|
}
|
|
|
|
void OpenVPNDriver::AppendOption(
|
|
const string& option,
|
|
const string& value0,
|
|
const string& value1,
|
|
vector<vector<string>>* options) {
|
|
options->push_back(vector<string>{ option, value0, value1 });
|
|
}
|
|
|
|
bool OpenVPNDriver::AppendValueOption(
|
|
const string& property,
|
|
const string& option,
|
|
vector<vector<string>>* options) {
|
|
string value = args()->LookupString(property, "");
|
|
if (!value.empty()) {
|
|
AppendOption(option, value, options);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OpenVPNDriver::AppendDelimitedValueOption(
|
|
const string& property,
|
|
const string& option,
|
|
char delimiter,
|
|
vector<vector<string>>* options) {
|
|
string value = args()->LookupString(property, "");
|
|
if (!value.empty()) {
|
|
vector<string> parts = SplitString(
|
|
value, std::string{delimiter}, base::TRIM_WHITESPACE,
|
|
base::SPLIT_WANT_ALL);
|
|
parts.insert(parts.begin(), option);
|
|
options->push_back(parts);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool OpenVPNDriver::AppendFlag(
|
|
const string& property,
|
|
const string& option,
|
|
vector<vector<string>>* options) {
|
|
if (args()->ContainsString(property)) {
|
|
AppendOption(option, options);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
string OpenVPNDriver::GetServiceRpcIdentifier() const {
|
|
if (service_ == nullptr)
|
|
return "(openvpn_driver)";
|
|
return service_->GetRpcIdentifier();
|
|
}
|
|
|
|
void OpenVPNDriver::Disconnect() {
|
|
SLOG(this, 2) << __func__;
|
|
IdleService();
|
|
}
|
|
|
|
void OpenVPNDriver::OnConnectionDisconnected() {
|
|
LOG(INFO) << "Underlying connection disconnected.";
|
|
// Restart the OpenVPN client forcing a reconnect attempt.
|
|
management_server_->Restart();
|
|
// Indicate reconnect state right away to drop the VPN connection and start
|
|
// the connect timeout. This ensures that any miscommunication between shill
|
|
// and openvpn will not lead to a permanently stale connectivity state. Note
|
|
// that a subsequent invocation of OnReconnecting due to a RECONNECTING
|
|
// message will essentially be a no-op.
|
|
OnReconnecting(kReconnectReasonOffline);
|
|
}
|
|
|
|
void OpenVPNDriver::OnConnectTimeout() {
|
|
VPNDriver::OnConnectTimeout();
|
|
Service::ConnectFailure failure =
|
|
management_server_->state() == OpenVPNManagementServer::kStateResolve ?
|
|
Service::kFailureDNSLookup : Service::kFailureConnect;
|
|
FailService(failure, Service::kErrorDetailsNone);
|
|
}
|
|
|
|
void OpenVPNDriver::OnReconnecting(ReconnectReason reason) {
|
|
LOG(INFO) << __func__ << "(" << reason << ")";
|
|
int timeout_seconds = GetReconnectTimeoutSeconds(reason);
|
|
if (reason == kReconnectReasonTLSError &&
|
|
timeout_seconds < connect_timeout_seconds()) {
|
|
// Reconnect due to TLS error happens during connect so we need to cancel
|
|
// the original connect timeout first and then reduce the time limit.
|
|
StopConnectTimeout();
|
|
}
|
|
StartConnectTimeout(timeout_seconds);
|
|
// On restart/reconnect, drop the VPN connection, if any. The openvpn client
|
|
// might be in hold state if the VPN connection was previously established
|
|
// successfully. The hold will be released by OnDefaultServiceChanged when a
|
|
// new default service connects. This ensures that the client will use a fully
|
|
// functional underlying connection to reconnect.
|
|
if (device_) {
|
|
device_->DropConnection();
|
|
}
|
|
if (service_) {
|
|
service_->SetState(Service::kStateAssociating);
|
|
}
|
|
}
|
|
|
|
// static
|
|
int OpenVPNDriver::GetReconnectTimeoutSeconds(ReconnectReason reason) {
|
|
switch (reason) {
|
|
case kReconnectReasonOffline:
|
|
return kReconnectOfflineTimeoutSeconds;
|
|
case kReconnectReasonTLSError:
|
|
return kReconnectTLSErrorTimeoutSeconds;
|
|
default:
|
|
break;
|
|
}
|
|
return kDefaultConnectTimeoutSeconds;
|
|
}
|
|
|
|
string OpenVPNDriver::GetProviderType() const {
|
|
return kProviderOpenVpn;
|
|
}
|
|
|
|
KeyValueStore OpenVPNDriver::GetProvider(Error* error) {
|
|
SLOG(this, 2) << __func__;
|
|
KeyValueStore props = VPNDriver::GetProvider(error);
|
|
props.SetBool(kPassphraseRequiredProperty,
|
|
args()->LookupString(kOpenVPNPasswordProperty, "").empty() &&
|
|
args()->LookupString(kOpenVPNTokenProperty, "").empty());
|
|
return props;
|
|
}
|
|
|
|
map<string, string> OpenVPNDriver::GetEnvironment() {
|
|
SLOG(this, 2) << __func__ << "(" << lsb_release_file_.value() << ")";
|
|
map<string, string> environment;
|
|
string contents;
|
|
if (!base::ReadFileToString(lsb_release_file_, &contents)) {
|
|
LOG(ERROR) << "Unable to read the lsb-release file: "
|
|
<< lsb_release_file_.value();
|
|
return environment;
|
|
}
|
|
vector<string> lines = SplitString(contents, "\n", base::TRIM_WHITESPACE,
|
|
base::SPLIT_WANT_ALL);
|
|
for (const auto& line : lines) {
|
|
const size_t assign = line.find('=');
|
|
if (assign == string::npos) {
|
|
continue;
|
|
}
|
|
const string key = line.substr(0, assign);
|
|
const string value = line.substr(assign + 1);
|
|
if (key == kChromeOSReleaseName) {
|
|
environment[kOpenVPNEnvVarPlatformName] = value;
|
|
} else if (key == kChromeOSReleaseVersion) {
|
|
environment[kOpenVPNEnvVarPlatformVersion] = value;
|
|
}
|
|
// Other LSB release values are irrelevant.
|
|
}
|
|
return environment;
|
|
}
|
|
|
|
void OpenVPNDriver::OnDefaultServiceChanged(const ServiceRefPtr& service) {
|
|
SLOG(this, 2) << __func__
|
|
<< "(" << (service ? service->unique_name() : "-") << ")";
|
|
// Allow the openvpn client to connect/reconnect only over a connected
|
|
// underlying default service. If there's no default connected service, hold
|
|
// the openvpn client until an underlying connection is established. If the
|
|
// default service is our VPN service, hold the openvpn client on reconnect so
|
|
// that the VPN connection can be torn down fully before a new connection
|
|
// attempt is made over the underlying service.
|
|
if (service && service != service_ && service->IsConnected()) {
|
|
management_server_->ReleaseHold();
|
|
} else {
|
|
management_server_->Hold();
|
|
}
|
|
}
|
|
|
|
void OpenVPNDriver::ReportConnectionMetrics() {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnDriver,
|
|
Metrics::kVpnDriverOpenVpn,
|
|
Metrics::kMetricVpnDriverMax);
|
|
|
|
if (args()->LookupString(kOpenVPNCaCertProperty, "") != "" ||
|
|
(args()->ContainsStrings(kOpenVPNCaCertPemProperty) &&
|
|
!args()->GetStrings(kOpenVPNCaCertPemProperty).empty())) {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnRemoteAuthenticationType,
|
|
Metrics::kVpnRemoteAuthenticationTypeOpenVpnCertificate,
|
|
Metrics::kMetricVpnRemoteAuthenticationTypeMax);
|
|
} else {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnRemoteAuthenticationType,
|
|
Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault,
|
|
Metrics::kMetricVpnRemoteAuthenticationTypeMax);
|
|
}
|
|
|
|
bool has_user_authentication = false;
|
|
if (args()->LookupString(kOpenVPNTokenProperty, "") != "") {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeOpenVpnUsernameToken,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
has_user_authentication = true;
|
|
}
|
|
if (args()->LookupString(kOpenVPNOTPProperty, "") != "") {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePasswordOtp,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
has_user_authentication = true;
|
|
}
|
|
if (args()->LookupString(kOpenVPNAuthUserPassProperty, "") != "" ||
|
|
args()->LookupString(kOpenVPNUserProperty, "") != "") {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePassword,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
has_user_authentication = true;
|
|
}
|
|
if (args()->LookupString(kOpenVPNClientCertIdProperty, "") != "" ||
|
|
args()->LookupString(kOpenVPNCertProperty, "") != "") {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeOpenVpnCertificate,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
has_user_authentication = true;
|
|
}
|
|
if (!has_user_authentication) {
|
|
metrics_->SendEnumToUMA(
|
|
Metrics::kMetricVpnUserAuthenticationType,
|
|
Metrics::kVpnUserAuthenticationTypeOpenVpnNone,
|
|
Metrics::kMetricVpnUserAuthenticationTypeMax);
|
|
}
|
|
}
|
|
|
|
} // namespace shill
|