blob: d182c8120b213b6de240b725fe9f32833496fcd2 [file] [log] [blame] [edit]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/vpn/openvpn_driver.h"
#include <arpa/inet.h>
#include <algorithm>
#include <iterator>
#include <limits>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/containers/fixed_flat_map.h>
#include <base/containers/flat_map.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <brillo/files/file_util.h>
#include <chromeos/dbus/service_constants.h>
#include <net-base/ip_address.h>
#include <net-base/ip_address_utils.h>
#include <net-base/ipv4_address.h>
#include <net-base/ipv6_address.h>
#include <net-base/network_config.h>
#include <net-base/process_manager.h>
#include "shill/certificate_file.h"
#include "shill/device_info.h"
#include "shill/error.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/rpc_task.h"
#include "shill/vpn/openvpn_management_server.h"
#include "shill/vpn/vpn_end_reason.h"
#include "shill/vpn/vpn_service.h"
#include "shill/vpn/vpn_types.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kVPN;
} // namespace Logging
namespace {
constexpr char kChromeOSReleaseName[] = "CHROMEOS_RELEASE_NAME";
constexpr char kChromeOSReleaseVersion[] = "CHROMEOS_RELEASE_VERSION";
constexpr char kOpenVPNForeignOptionPrefix[] = "foreign_option_";
constexpr char kOpenVPNIfconfigLocal[] = "ifconfig_local";
constexpr char kOpenVPNIfconfigNetmask[] = "ifconfig_netmask";
constexpr char kOpenVPNIfconfigRemote[] = "ifconfig_remote";
constexpr char kOpenVPNIfconfigIPv6Local[] = "ifconfig_ipv6_local";
constexpr char kOpenVPNIfconfigIPv6Netbits[] = "ifconfig_ipv6_netbits";
constexpr char kOpenVPNRedirectGateway[] = "redirect_gateway";
constexpr char kOpenVPNTunMTU[] = "tun_mtu";
// Typically OpenVPN will set environment variables for IPv4 like:
// route_net_gateway=<existing default LAN gateway>
// route_vpn_gateway=10.8.0.1
// route_gateway_1=10.8.0.1
// route_netmask_1=255.255.255.0
// route_network_1=192.168.10.0
// This example shows a split include route of 192.168.10.0/24, and
// 10.8.0.1 is the ifconfig_remote (remote peer) address.
//
// For IPv6, they will be like:
// ifconfig_ipv6_local: fdfd::1000
// ifconfig_ipv6_netbits: 64
// ifconfig_ipv6_remote: fdfd::1
// route_ipv6_gateway_1: fdfd::1
// route_ipv6_network_1: ::/3
// Different from IPv4, for a route entry, there are only two variables for it
// in IPv6, and the network variable will be a prefix string.
constexpr std::string_view kOpenVPNRouteOptionPrefix = "route_";
constexpr std::string_view kOpenVPNRouteIPv6OptionPrefix = "route_ipv6_";
constexpr char kOpenVPNRouteNetGateway[] = "route_net_gateway";
constexpr char kOpenVPNRouteVPNGateway[] = "route_vpn_gateway";
constexpr char kOpenVPNRouteNetworkPrefix[] = "network_";
constexpr char kOpenVPNRouteNetmaskPrefix[] = "netmask_";
constexpr char kOpenVPNRouteGatewayPrefix[] = "gateway_";
constexpr 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.
constexpr char kSuspectedNetmaskPrefix[] = "255.";
constexpr char kOpenVPNPath[] = "/usr/sbin/openvpn";
constexpr char kOpenVPNScript[] = SHIMDIR "/openvpn-script";
// Directory where OpenVPN configuration files are exported while the
// process is running.
constexpr char kDefaultOpenVPNConfigurationDirectory[] =
RUNDIR "/openvpn_config";
} // namespace
// static
const VPNDriver::Property OpenVPNDriver::kProperties[] = {
{kOpenVPNAuthNoCacheProperty, 0},
{kOpenVPNAuthProperty, 0},
{kOpenVPNAuthRetryProperty, 0},
{kOpenVPNAuthUserPassProperty, 0},
{kOpenVPNCipherProperty, 0},
{kOpenVPNClientCertIdProperty, Property::kCredential},
{kOpenVPNCompLZOProperty, 0},
{kOpenVPNCompNoAdaptProperty, 0},
{kOpenVPNCompressProperty, 0},
{kOpenVPNExtraHostsProperty, Property::kArray},
{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},
{kOpenVPNPushPeerInfoProperty, 0},
{kOpenVPNRemoteCertEKUProperty, 0},
{kOpenVPNRemoteCertKUProperty, 0},
{kOpenVPNRemoteCertTLSProperty, 0},
{kOpenVPNRenegSecProperty, 0},
{kOpenVPNServerPollTimeoutProperty, 0},
{kOpenVPNShaperProperty, 0},
{kOpenVPNStaticChallengeProperty, 0},
{kOpenVPNTLSAuthContentsProperty, 0},
{kOpenVPNTLSRemoteProperty, 0},
{kOpenVPNTLSVersionMinProperty, 0},
{kOpenVPNTokenProperty,
Property::kEphemeral | Property::kCredential | Property::kWriteOnly},
{kOpenVPNUserProperty, 0},
{kProviderHostProperty, 0},
{kProviderTypeProperty, 0},
{kOpenVPNCaCertPemProperty, Property::kArray},
{kOpenVPNExtraCertPemProperty, Property::kArray},
{kOpenVPNPingExitProperty, 0},
{kOpenVPNPingProperty, 0},
{kOpenVPNPingRestartProperty, 0},
{kOpenVPNTLSAuthProperty, 0},
{kOpenVPNVerbProperty, 0},
{kOpenVPNVerifyHashProperty, 0},
{kOpenVPNVerifyX509NameProperty, 0},
{kOpenVPNVerifyX509TypeProperty, 0},
{kVPNMTUProperty, 0},
};
OpenVPNDriver::OpenVPNDriver(Manager* manager,
net_base::ProcessManager* process_manager)
: VPNDriver(manager,
process_manager,
VPNType::kOpenVPN,
kProperties,
std::size(kProperties)),
management_server_(new OpenVPNManagementServer(this)),
certificate_file_(new CertificateFile()),
extra_certificates_file_(new CertificateFile()),
lsb_release_file_(kLSBReleaseFile),
openvpn_config_directory_(kDefaultOpenVPNConfigurationDirectory),
pid_(0),
vpn_util_(VPNUtil::New()) {}
OpenVPNDriver::~OpenVPNDriver() {
Cleanup();
}
void OpenVPNDriver::FailService(VPNEndReason failure,
std::string_view error_details) {
SLOG(2) << __func__ << "(" << error_details << ")";
Cleanup();
if (event_handler_) {
event_handler_->OnDriverFailure(failure, error_details);
event_handler_ = nullptr;
}
}
void OpenVPNDriver::Cleanup() {
// 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::DoNothing());
}
management_server_->Stop();
if (!tls_auth_file_.empty()) {
brillo::DeleteFile(tls_auth_file_);
tls_auth_file_.clear();
}
if (!openvpn_config_file_.empty()) {
brillo::DeleteFile(openvpn_config_file_);
openvpn_config_file_.clear();
}
rpc_task_.reset();
params_.clear();
network_config_ = std::nullopt;
if (pid_) {
process_manager()->StopProcessAndBlock(pid_);
pid_ = 0;
}
if (!interface_name_.empty()) {
manager()->device_info()->DeleteInterface(interface_index_);
interface_name_.clear();
interface_index_ = -1;
}
}
// static
std::string OpenVPNDriver::JoinOptions(
const std::vector<std::vector<std::string>>& options, char separator) {
std::vector<std::string> option_strings;
for (const auto& option : options) {
std::vector<std::string> quoted_option;
for (const auto& argument : option) {
if (argument.find(' ') != std::string::npos ||
argument.find('\t') != std::string::npos ||
argument.find('"') != std::string::npos ||
argument.find(separator) != std::string::npos) {
std::string quoted_argument(argument);
const char separator_chars[] = {separator, '\0'};
base::ReplaceChars(argument, separator_chars, " ", &quoted_argument);
base::ReplaceChars(quoted_argument, "\\", "\\\\", &quoted_argument);
base::ReplaceChars(quoted_argument, "\"", "\\\"", &quoted_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, std::string{separator});
}
bool OpenVPNDriver::WriteConfigFile(
const std::vector<std::vector<std::string>>& options,
base::FilePath* config_file) {
if (!vpn_util_->PrepareConfigDirectory(openvpn_config_directory_)) {
LOG(ERROR) << "Unable to setup OpenVPN config directory.";
return false;
}
std::string contents = JoinOptions(options, '\n');
contents.push_back('\n');
if (!base::CreateTemporaryFileInDir(openvpn_config_directory_, config_file) ||
!vpn_util_->WriteConfigFile(*config_file, contents)) {
LOG(ERROR) << "Unable to setup OpenVPN config file.";
return false;
}
return true;
}
bool OpenVPNDriver::SpawnOpenVPN() {
SLOG(2) << __func__ << "(" << interface_name_ << ")";
std::vector<std::vector<std::string>> options;
Error error;
pid_t openvpn_pid;
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_);
const std::vector<std::string> args = GetCommandLineArgs();
LOG(INFO) << "OpenVPN command line args: " << base::JoinString(args, " ");
// OpenSSL compatibility settings.
// TODO(crbug.com/1047146): Drop these stop-gaps after addressing the
// underlying problems described in the bug.
const std::map<std::string, std::string> kEnv = {
{"OPENSSL_CONF", "/etc/ssl/openssl.cnf.compat"},
{"OPENSSL_CHROMIUM_SKIP_TRUSTED_PURPOSE_CHECK", "1"},
{"OPENSSL_CHROMIUM_GENERATE_METRICS", "1"},
};
net_base::ProcessManager::MinijailOptions minijail_options;
minijail_options.user = "vpn";
minijail_options.group = "vpn";
// openvpn needs CAP_NET_ADMIN for several operations, e.g, set SO_MARK on the
// socket and set tx queue length.
minijail_options.capmask = CAP_TO_MASK(CAP_NET_ADMIN);
minijail_options.inherit_supplementary_groups = true;
openvpn_pid = process_manager()->StartProcessInMinijail(
FROM_HERE, base::FilePath(kOpenVPNPath), args, kEnv, minijail_options,
base::BindOnce(&OpenVPNDriver::OnOpenVPNDied, base::Unretained(this)));
if (openvpn_pid == -1) {
LOG(ERROR) << "Minijail couldn't run our child process";
return false;
}
pid_ = openvpn_pid;
return true;
}
void OpenVPNDriver::OnOpenVPNDied(int exit_status) {
SLOG(2) << __func__ << "(" << pid_ << ", " << exit_status << ")";
pid_ = 0;
FailService(VPNEndReason::kFailureInternal, Service::kErrorDetailsNone);
// TODO(petkov): Figure if we need to restart the connection.
}
void OpenVPNDriver::GetLogin(std::string* /*user*/, std::string* /*password*/) {
NOTREACHED();
}
void OpenVPNDriver::Notify(const std::string& reason,
const std::map<std::string, std::string>& dict) {
LOG(INFO) << "IP configuration received: " << reason;
// We only registered "--up" script so this should be the only
// reason we get notified here. Note that "--up-restart" is set
// so we will get notification also upon reconnection.
if (reason != "up") {
LOG(DFATAL) << "Unexpected notification reason";
return;
}
// On restart/reconnect, update the existing dict, and generate IP
// configurations from it.
for (const auto& [k, v] : dict) {
params_[k] = v;
}
network_config_ = ParseNetworkConfig(
params_,
const_args()->Contains<std::string>(kOpenVPNIgnoreDefaultRouteProperty));
if (!network_config_.has_value()) {
FailService(VPNEndReason::kFailureInternal, "No valid IP config");
return;
}
ReportConnectionMetrics();
if (event_handler_) {
event_handler_->OnDriverConnected(interface_name_, interface_index_);
} else {
LOG(DFATAL) << "Missing service callback";
}
}
std::unique_ptr<net_base::NetworkConfig> OpenVPNDriver::GetNetworkConfig()
const {
if (!network_config_.has_value()) {
return nullptr;
}
return std::make_unique<net_base::NetworkConfig>(*network_config_);
}
// static
std::optional<net_base::NetworkConfig> OpenVPNDriver::ParseNetworkConfig(
const std::map<std::string, std::string>& configuration,
bool ignore_redirect_gateway) {
// Values parsed from |configuration|.
ForeignOptions foreign_options;
int mtu = 0;
std::optional<net_base::IPv4Address> ipv4_local;
std::optional<int> ipv4_prefix = std::nullopt;
std::optional<net_base::IPv4Address> ipv4_remote;
bool ipv4_redirect_gateway = false;
std::optional<net_base::IPv6Address> ipv6_local;
std::optional<int> ipv6_prefix = std::nullopt;
for (const auto& [key, value] : configuration) {
SLOG(2) << "Processing: " << key << " -> " << value;
if (base::EqualsCaseInsensitiveASCII(key, kOpenVPNIfconfigLocal)) {
ipv4_local = net_base::IPv4Address::CreateFromString(value);
if (!ipv4_local.has_value()) {
LOG(WARNING) << "Failed to parse IPv4 local address from " << value;
}
} else if (base::EqualsCaseInsensitiveASCII(key, kOpenVPNIfconfigNetmask)) {
const auto netmask = net_base::IPv4Address::CreateFromString(value);
if (netmask) {
ipv4_prefix = net_base::IPv4CIDR::GetPrefixLength(*netmask);
}
if (!ipv4_prefix) {
LOG(WARNING) << "Failed to get prefix length from " << value;
}
} else if (base::EqualsCaseInsensitiveASCII(key, kOpenVPNIfconfigRemote)) {
ipv4_remote = net_base::IPv4Address::CreateFromString(value);
if (!ipv4_remote.has_value()) {
LOG(WARNING) << "Failed to parse IPv4 remote address from " << value;
} else 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, we unset |ipv4_remote|.
// NetworkApplier::ApplyRoute() 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.
ipv4_prefix = net_base::IPv4CIDR::GetPrefixLength(*ipv4_remote);
if (ipv4_prefix) {
ipv4_remote = std::nullopt;
} else {
LOG(WARNING) << "Failed to get prefix length from " << value;
}
}
} else if (base::EqualsCaseInsensitiveASCII(key, kOpenVPNRedirectGateway)) {
if (ignore_redirect_gateway) {
LOG(INFO) << "Ignoring default route parameter as requested by "
<< "configuration.";
} else {
ipv4_redirect_gateway = true;
}
} else if (base::EqualsCaseInsensitiveASCII(key,
kOpenVPNIfconfigIPv6Local)) {
ipv6_local = net_base::IPv6Address::CreateFromString(value);
if (!ipv6_local.has_value()) {
LOG(WARNING) << "Failed to parse IPv6 local address from " << value;
}
} else if (base::EqualsCaseInsensitiveASCII(key,
kOpenVPNIfconfigIPv6Netbits)) {
int prefix = 0;
if (!base::StringToInt(value, &prefix) ||
!net_base::IPv6CIDR::IsValidPrefixLength(prefix)) {
LOG(ERROR) << "IPv6 netbits ignored, value=" << value;
} else {
ipv6_prefix = prefix;
}
} else if (base::EqualsCaseInsensitiveASCII(key, kOpenVPNTunMTU)) {
if (!base::StringToInt(value, &mtu)) {
LOG(ERROR) << "Failed to parse MTU " << value;
}
} else if (base::StartsWith(key, kOpenVPNForeignOptionPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
const auto 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)) {
// These options will be parsed later in |ParseIPv4RouteOptions| and
// |ParseIPv6RouteOptions|.
} else {
SLOG(2) << "Key ignored.";
}
}
const bool has_ipv4 = ipv4_local.has_value();
const bool has_ipv6 = ipv6_local.has_value();
if (!has_ipv4 && !has_ipv6) {
return std::nullopt;
}
auto network_config = std::make_optional<net_base::NetworkConfig>();
std::vector<std::string> search_domains;
std::vector<net_base::IPAddress> dns_servers;
if (!foreign_options.empty()) {
ParseForeignOptions(foreign_options, &network_config->dns_search_domains,
&network_config->dns_servers);
} else {
LOG(INFO) << "No foreign option provided";
}
if (mtu != 0) {
const int min_mtu = has_ipv6 ? net_base::NetworkConfig::kMinIPv6MTU
: net_base::NetworkConfig::kMinIPv4MTU;
if (mtu < min_mtu) {
LOG(ERROR) << "MTU value " << mtu << " ignored";
} else {
network_config->mtu = mtu;
}
}
// Notes on `redirect-gateway`:
//
// In openvpn configuration, the user can add a `ipv6` flag to the
// `redirect-gateway` option to indicate a default route for IPv6, but in the
// context of environment variables passed from openvpn, `redirect-gateway` is
// an IPv4-only option: for IPv6, openvpn client will translate it into routes
// and set them in the variables. So at the server side, suppose there is no
// route configured directly, there are 4 cases:
// - No `redirect-gateway`: indicates no default route for both v4 and v6;
// openvpn client will set neither `redirect-gateway` nor routes in env
// variables.
// - `redirect-gateway (def1)?`: indicates IPv4-only default route; openvpn
// client will set only `redirect-gateway` but no route in env variables.
// - `redirect-gateway ipv6 !ipv4`: indicates IPv6-only default route; openvpn
// client will set only routes (for IPv6) but no `redirect-gateway` in env
// variables.
// - `redirect-gateway ipv6`: indicates default route for both v4 and v6;
// openvpn client will set both `redirect-gateway` and routes (for IPv6) in
// env variables.
if (has_ipv4) {
network_config->ipv4_address =
net_base::IPv4CIDR::CreateFromAddressAndPrefix(
*ipv4_local,
ipv4_prefix.value_or(net_base::IPv4CIDR::kMaxPrefixLength));
if (ipv4_remote.has_value()) {
// --topology net30 or p2p will set ifconfig_remote
// Setting a point-to-point address in the kernel will create a route
// in RT_TABLE_MAIN instead of our per-device table. To avoid this,
// create an explicit host route here.
network_config->included_route_prefixes.push_back(
*net_base::IPCIDR::CreateFromAddressAndPrefix(
net_base::IPAddress(*ipv4_remote),
net_base::IPv4CIDR::kMaxPrefixLength));
} else if (ipv4_prefix.has_value() &&
ipv4_prefix != net_base::IPv4CIDR::kMaxPrefixLength) {
// --topology subnet will set ifconfig_netmask instead
network_config->included_route_prefixes.push_back(
net_base::IPCIDR(network_config->ipv4_address->GetPrefixCIDR()));
}
if (ipv4_redirect_gateway) {
network_config->included_route_prefixes.push_back(
net_base::IPCIDR(net_base::IPFamily::kIPv4));
}
network_config->ipv6_blackhole_route = ipv4_redirect_gateway && !has_ipv6;
}
if (has_ipv6) {
const net_base::IPv6CIDR ipv6_addr =
net_base::IPv6CIDR::CreateFromAddressAndPrefix(
*ipv6_local,
ipv6_prefix.value_or(net_base::IPv6CIDR::kMaxPrefixLength))
.value();
network_config->ipv6_addresses.push_back(ipv6_addr);
if (ipv6_prefix.has_value() &&
ipv6_prefix != net_base::IPv6CIDR::kMaxPrefixLength) {
// --topology subnet will set ifconfig_netmask instead
network_config->included_route_prefixes.push_back(
net_base::IPCIDR(network_config->ipv6_addresses[0].GetPrefixCIDR()));
}
}
// Parse IPv4 and IPv6 routes from |configuration|.
std::vector<net_base::IPCIDR> ipv4_routes =
ParseIPv4RouteOptions(configuration);
std::vector<net_base::IPCIDR> ipv6_routes =
ParseIPv6RouteOptions(configuration);
// Add routes to |included_route_prefixes|.
std::copy(ipv4_routes.begin(), ipv4_routes.end(),
std::back_inserter(network_config->included_route_prefixes));
std::copy(ipv6_routes.begin(), ipv6_routes.end(),
std::back_inserter(network_config->included_route_prefixes));
return network_config;
}
namespace {
bool ParseForeignOption(const std::string& option,
std::vector<std::string>* domain_search,
std::vector<net_base::IPAddress>* dns_servers) {
SLOG(2) << __func__ << "(" << option << ")";
const auto tokens = base::SplitStringPiece(option, " ", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
if (tokens.size() != 3 ||
!base::EqualsCaseInsensitiveASCII(tokens[0], "dhcp-option")) {
return false;
}
if (base::EqualsCaseInsensitiveASCII(tokens[1], "domain")) {
domain_search->push_back(std::string(tokens[2]));
return true;
} else if (base::EqualsCaseInsensitiveASCII(tokens[1], "dns")) {
const std::optional<net_base::IPAddress> dns =
net_base::IPAddress::CreateFromString(tokens[2]);
if (!dns.has_value()) {
LOG(WARNING) << "Failed to parse DNS " << tokens[2];
return false;
}
dns_servers->push_back(*dns);
return true;
}
return false;
}
} // namespace
// static
void OpenVPNDriver::ParseForeignOptions(
const ForeignOptions& options,
std::vector<std::string>* domain_search,
std::vector<net_base::IPAddress>* dns_servers) {
domain_search->clear();
dns_servers->clear();
for (const auto& option_map : options) {
if (!ParseForeignOption(option_map.second, domain_search, dns_servers)) {
LOG(INFO) << "Ignore foreign option " << option_map.second;
}
}
}
namespace {
struct RouteOption {
std::string_view prefix;
int index;
};
// Tries to parse a key as a route option. If the key follow the format
// {network,netmask,prefix}_<index>, then {network_,netmask_,prefix_} will be
// returned as |prefix| and <index> will be returned as |index|. Otherwise
// std::nullopt will be returned.
std::optional<RouteOption> ParseKeyAsRouteOption(std::string_view key) {
RouteOption ret;
for (const auto& prefix :
{kOpenVPNRouteNetworkPrefix, kOpenVPNRouteNetmaskPrefix,
kOpenVPNRouteGatewayPrefix}) {
if (base::StartsWith(key, prefix, base::CompareCase::INSENSITIVE_ASCII)) {
ret.prefix = prefix;
break;
}
}
if (ret.prefix.empty() ||
!base::StringToInt(key.substr(ret.prefix.size()), &ret.index)) {
return std::nullopt;
}
return std::make_optional(ret);
}
} // namespace
// static
std::vector<net_base::IPCIDR> OpenVPNDriver::ParseIPv4RouteOptions(
const std::map<std::string, std::string>& configuration) {
struct IPv4Route {
std::optional<net_base::IPv4Address> network;
int prefix_length = 0;
std::optional<net_base::IPv4Address> gateway;
};
// Temporarily store the parsed routes here. The key is the route index.
base::flat_map<int, IPv4Route> routes;
for (const auto& [key, value] : configuration) {
// Keys for IPv4 routes start with route_ while those for IPv6 routes starts
// with route_ipv6_. As we are parsing IPv4 routes here, we need to drop
// those for IPv6 routes.
if (!base::StartsWith(key, kOpenVPNRouteOptionPrefix,
base::CompareCase::INSENSITIVE_ASCII) ||
base::StartsWith(key, kOpenVPNRouteIPv6OptionPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
continue;
}
// These options are unused. Catch them here so that they don't get passed
// to ParseKeyAsRouteOption().
if (base::EqualsCaseInsensitiveASCII(key, kOpenVPNRouteNetGateway) ||
base::EqualsCaseInsensitiveASCII(key, kOpenVPNRouteVPNGateway)) {
continue;
}
// The format of keys for IPv4 routes is
// route_{network,netmask,gateway}_<index>.
std::optional<RouteOption> route_option =
ParseKeyAsRouteOption(key.substr(kOpenVPNRouteOptionPrefix.size()));
if (!route_option.has_value()) {
LOG(WARNING) << "Route option ignored: " << key;
continue;
}
const std::optional<net_base::IPv4Address> addr =
net_base::IPv4Address::CreateFromString(value);
if (!addr.has_value()) {
LOG(WARNING) << "Failed to get address from " << value
<< " for route option " << key;
continue;
}
IPv4Route& route = routes[route_option->index];
if (route_option->prefix == kOpenVPNRouteNetworkPrefix) {
route.network = addr;
} else if (route_option->prefix == kOpenVPNRouteNetmaskPrefix) {
route.prefix_length =
net_base::IPv4CIDR::GetPrefixLength(*addr).value_or(0);
if (route.prefix_length == 0) {
LOG(WARNING) << "Failed to get prefix length from " << value;
}
} else {
// route_option->prefix == kOpenVPNRouteGatewayPrefix.
route.gateway = addr;
}
}
// Build routes with the temporary |routes|.
std::vector<net_base::IPCIDR> ret;
for (const auto& [index, route] : routes) {
if (!route.network.has_value() || !route.gateway.has_value()) {
LOG(WARNING) << "Ignoring incomplete route: " << index;
continue;
}
const std::optional<net_base::IPCIDR> cidr =
net_base::IPCIDR::CreateFromAddressAndPrefix(
net_base::IPAddress(*route.network), route.prefix_length);
if (!cidr.has_value()) {
LOG(WARNING) << "Ignoring invalid route: " << *route.network << "/"
<< route.prefix_length;
continue;
}
ret.push_back(*cidr);
}
return ret;
}
// static
std::vector<net_base::IPCIDR> OpenVPNDriver::ParseIPv6RouteOptions(
const std::map<std::string, std::string>& configuration) {
struct IPv6Route {
std::optional<net_base::IPv6CIDR> network;
std::optional<net_base::IPv6Address> gateway;
};
// Temporarily store the parsed routes here. The key is the route index.
base::flat_map<int, IPv6Route> routes;
for (const auto& [key, value] : configuration) {
if (!base::StartsWith(key, kOpenVPNRouteIPv6OptionPrefix,
base::CompareCase::INSENSITIVE_ASCII)) {
continue;
}
// The format of keys for IPv6 routes is
// route_ipv6_{network,gateway}_<index>.
std::optional<RouteOption> route_option =
ParseKeyAsRouteOption(key.substr(kOpenVPNRouteIPv6OptionPrefix.size()));
if (!route_option.has_value()) {
LOG(WARNING) << "Route option ignored: " << key;
continue;
}
IPv6Route& route = routes[route_option->index];
if (route_option->prefix == kOpenVPNRouteNetworkPrefix) {
route.network = net_base::IPv6CIDR::CreateFromCIDRString(value);
if (!route.network.has_value()) {
LOG(WARNING) << "Failed to get network from " << value;
}
} else if (route_option->prefix == kOpenVPNRouteGatewayPrefix) {
route.gateway = net_base::IPv6Address::CreateFromString(value);
if (!route.gateway.has_value()) {
LOG(WARNING) << "Failed to get gateway from " << value;
}
} else {
// route_option->prefix == kOpenVPNRouteNetmaskPrefix, which should not
// exist for IPv6 routes.
LOG(WARNING) << "Route option ignored: " << key;
}
}
// Build routes with the temporary |routes|.
std::vector<net_base::IPCIDR> ret;
for (const auto& [index, route] : routes) {
if (!route.network.has_value() || !route.gateway.has_value()) {
LOG(WARNING) << "Ignoring incomplete route: " << index;
continue;
}
ret.push_back(net_base::IPCIDR(*route.network));
}
return ret;
}
// static
bool OpenVPNDriver::SplitPortFromHost(std::string_view host,
std::string* name,
std::string* port) {
const auto tokens = base::SplitStringPiece(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;
}
base::TimeDelta OpenVPNDriver::ConnectAsync(EventHandler* handler) {
event_handler_ = handler;
if (!manager()->device_info()->CreateTunnelInterface(base::BindOnce(
&OpenVPNDriver::OnLinkReady, weak_factory_.GetWeakPtr()))) {
dispatcher()->PostTask(
FROM_HERE,
base::BindOnce(&OpenVPNDriver::FailService, weak_factory_.GetWeakPtr(),
VPNEndReason::kFailureInternal,
"Could not create tunnel interface."));
return kTimeoutNone;
}
return kConnectTimeout;
}
void OpenVPNDriver::OnLinkReady(const std::string& link_name,
int interface_index) {
if (!event_handler_) {
LOG(ERROR) << "event_handler_ is not set";
return;
}
interface_name_ = link_name;
interface_index_ = interface_index;
rpc_task_.reset(new RpcTask(control_interface(), this));
if (!SpawnOpenVPN()) {
FailService(VPNEndReason::kFailureInternal, Service::kErrorDetailsNone);
}
}
void OpenVPNDriver::InitOptions(std::vector<std::vector<std::string>>* options,
Error* error) {
const auto vpnhost = args()->Lookup<std::string>(kProviderHostProperty, "");
if (vpnhost.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
"VPN host not specified.");
return;
}
AppendOption("client", options);
AppendOption("tls-client", options);
AppendRemoteOption(vpnhost, options);
if (args()->Contains<std::vector<std::string>>(kOpenVPNExtraHostsProperty)) {
for (const auto& host :
args()->Get<std::vector<std::string>>(kOpenVPNExtraHostsProperty)) {
AppendRemoteOption(host, options);
}
}
AppendOption("mark", "1280",
options); // 0x500: source type = 5 (Built-in VPN)
AppendOption("nobind", options);
AppendOption("persist-key", options);
AppendOption("persist-tun", options);
if (interface_name_.empty()) {
LOG(DFATAL) << "Tunnel interface name needs to be set before connecting.";
Error::PopulateAndLog(FROM_HERE, error, Error::kInternalError,
"Invalid tunnel interface");
return;
}
AppendOption("dev", interface_name_, 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);
{
const auto contents =
args()->Lookup<std::string>(kOpenVPNTLSAuthContentsProperty, "");
if (!contents.empty()) {
if (!vpn_util_->PrepareConfigDirectory(openvpn_config_directory_) ||
!base::CreateTemporaryFileInDir(openvpn_config_directory_,
&tls_auth_file_) ||
!vpn_util_->WriteConfigFile(tls_auth_file_, contents)) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInternalError,
"Unable to setup tls-auth file.");
return;
}
AppendOption("tls-auth", tls_auth_file_.value(), options);
}
}
if (args()->Contains<std::string>(kOpenVPNTLSVersionMinProperty)) {
AppendOption("tls-version-min",
args()->Get<std::string>(kOpenVPNTLSVersionMinProperty),
options);
}
const auto tls_remote =
args()->Lookup<std::string>(kOpenVPNTLSRemoteProperty, "");
if (!tls_remote.empty()) {
AppendOption("verify-x509-name", tls_remote, "name-prefix", 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);
AppendValueOption(kOpenVPNCompressProperty, "compress", 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 support.
auto remote_cert_tls =
args()->Lookup<std::string>(kOpenVPNRemoteCertTLSProperty, "");
if (remote_cert_tls.empty()) {
remote_cert_tls = "server";
}
if (remote_cert_tls != "none") {
AppendOption("remote-cert-tls", remote_cert_tls, options);
}
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().value(), options);
AppendOption("setenv", kRpcTaskPathVariable,
rpc_task_->GetRpcIdentifier().value(), 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);
// The default tx queue length set by openvpn (100) MAY be too small. We used
// to use the default value set by Linux (500) before, so explicitly set it
// here to avoid potential performance regression (also see
// b/313521559#comment2).
AppendOption("txqueuelen", "500", options);
}
bool OpenVPNDriver::InitCAOptions(
std::vector<std::vector<std::string>>* options, Error* error) {
std::vector<std::string> ca_cert_pem;
if (args()->Contains<std::vector<std::string>>(kOpenVPNCaCertPemProperty)) {
ca_cert_pem =
args()->Get<std::vector<std::string>>(kOpenVPNCaCertPemProperty);
}
if (ca_cert_pem.empty()) {
// Use default CAs if no CA certificate is provided.
AppendOption("ca", kDefaultCACertificates, options);
return true;
}
const base::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;
}
void OpenVPNDriver::InitCertificateVerifyOptions(
std::vector<std::vector<std::string>>* options) {
AppendValueOption(kOpenVPNVerifyHashProperty, "verify-hash", options);
const auto x509_name =
args()->Lookup<std::string>(kOpenVPNVerifyX509NameProperty, "");
if (!x509_name.empty()) {
const auto x509_type =
args()->Lookup<std::string>(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(
std::vector<std::vector<std::string>>* options, Error* error) {
if (!args()->Contains<std::vector<std::string>>(
kOpenVPNExtraCertPemProperty)) {
// It's okay for this parameter to be unspecified.
return true;
}
const auto extra_certs =
args()->Get<std::vector<std::string>>(kOpenVPNExtraCertPemProperty);
if (extra_certs.empty()) {
// It's okay for this parameter to be empty.
return true;
}
const base::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(
std::vector<std::vector<std::string>>* options) {
const auto id = args()->Lookup<std::string>(kOpenVPNClientCertIdProperty, "");
if (!id.empty()) {
AppendOption("pkcs11-providers", kDefaultPKCS11Provider, options);
AppendOption("pkcs11-id", id, options);
}
}
void OpenVPNDriver::InitClientAuthOptions(
std::vector<std::vector<std::string>>* options) {
// If the AuthUserPass property is set, or the User property is non-empty, or
// a client cert was not provided, specify user-password client
// authentication.
if (args()->Contains<std::string>(kOpenVPNAuthUserPassProperty) ||
!args()->Lookup<std::string>(kOpenVPNUserProperty, "").empty() ||
args()->Lookup<std::string>(kOpenVPNClientCertIdProperty, "").empty()) {
AppendOption("auth-user-pass", options);
}
}
bool OpenVPNDriver::InitManagementChannelOptions(
std::vector<std::vector<std::string>>* options, Error* error) {
if (!management_server_->Start(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(
std::vector<std::vector<std::string>>* options) {
AppendOption("syslog", options);
const auto verb = args()->Lookup<std::string>(kOpenVPNVerbProperty, "");
if (!verb.empty()) {
AppendOption("verb", verb, options);
return;
}
if (SLOG_IS_ON(VPN, 6)) {
// Maximum output:
// --verb 9 enables PKCS11 debug, TCP stream, link read/write
// --verb 8 enables event waits, scheduler, tls_session
AppendOption("verb", "9", options);
} else if (SLOG_IS_ON(VPN, 5)) {
// --verb 7 enables data channel encryption keys, routing,
// pkcs11 actions, pings, push/pull debug
AppendOption("verb", "7", options);
} else if (SLOG_IS_ON(VPN, 4)) {
// --verb 6 enables tcp/udp reads/writes (short), tun/tap reads/writes
// --verb 5 enables printing 'R' or 'W' per packet to stdout
AppendOption("verb", "6", options);
} else if (SLOG_IS_ON(VPN, 3)) {
// --verb 4 enables logging packet drops, options
AppendOption("verb", "4", options);
} else if (SLOG_IS_ON(VPN, 0)) {
// --verb 3 is the old default for `ff_debug +vpn`
AppendOption("verb", "3", options);
}
}
void OpenVPNDriver::AppendOption(
std::string_view option, std::vector<std::vector<std::string>>* options) {
options->push_back({std::string(option)});
}
void OpenVPNDriver::AppendOption(
std::string_view option,
std::string_view value,
std::vector<std::vector<std::string>>* options) {
options->push_back({std::string(option), std::string(value)});
}
void OpenVPNDriver::AppendOption(
std::string_view option,
std::string_view value0,
std::string_view value1,
std::vector<std::vector<std::string>>* options) {
options->push_back(
{std::string(option), std::string(value0), std::string(value1)});
}
void OpenVPNDriver::AppendRemoteOption(
std::string_view host, std::vector<std::vector<std::string>>* options) {
std::string host_name, host_port;
if (SplitPortFromHost(host, &host_name, &host_port)) {
DCHECK(!host_name.empty());
DCHECK(!host_port.empty());
AppendOption("remote", host_name, host_port, options);
} else {
AppendOption("remote", host, options);
}
}
bool OpenVPNDriver::AppendValueOption(
std::string_view property,
std::string_view option,
std::vector<std::vector<std::string>>* options) {
const auto value = args()->Lookup<std::string>(property, "");
if (!value.empty()) {
AppendOption(option, value, options);
return true;
}
return false;
}
bool OpenVPNDriver::AppendDelimitedValueOption(
const std::string& property,
const std::string& option,
char delimiter,
std::vector<std::vector<std::string>>* options) {
const auto value = args()->Lookup<std::string>(property, "");
if (!value.empty()) {
auto parts = base::SplitString(value, std::string{delimiter},
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
parts.insert(parts.begin(), option);
options->push_back(std::move(parts));
return true;
}
return false;
}
bool OpenVPNDriver::AppendFlag(const std::string& property,
const std::string& option,
std::vector<std::vector<std::string>>* options) {
if (args()->Contains<std::string>(property)) {
AppendOption(option, options);
return true;
}
return false;
}
void OpenVPNDriver::Disconnect() {
SLOG(2) << __func__;
Cleanup();
event_handler_ = nullptr;
}
void OpenVPNDriver::OnConnectTimeout() {
VPNEndReason failure =
management_server_->state() == OpenVPNManagementServer::kStateResolve
? VPNEndReason::kConnectFailureDNSLookup
: VPNEndReason::kConnectTimeout;
FailService(failure, Service::kErrorDetailsNone);
}
void OpenVPNDriver::OnReconnecting(ReconnectReason reason) {
LOG(INFO) << __func__ << "(" << reason << ")";
if (!event_handler_) {
LOG(ERROR) << "event_handler_ is not set";
return;
}
base::TimeDelta timeout = GetReconnectTimeout(reason);
event_handler_->OnDriverReconnecting(timeout);
}
// static
base::TimeDelta OpenVPNDriver::GetReconnectTimeout(ReconnectReason reason) {
switch (reason) {
case kReconnectReasonOffline:
return kReconnectOfflineTimeout;
case kReconnectReasonTLSError:
return kReconnectTLSErrorTimeout;
default:
return kConnectTimeout;
}
}
KeyValueStore OpenVPNDriver::GetProvider(Error* error) {
SLOG(2) << __func__;
KeyValueStore props = VPNDriver::GetProvider(error);
props.Set<bool>(
kPassphraseRequiredProperty,
args()->Lookup<std::string>(kOpenVPNPasswordProperty, "").empty() &&
args()->Lookup<std::string>(kOpenVPNTokenProperty, "").empty());
return props;
}
std::vector<std::string> OpenVPNDriver::GetCommandLineArgs() {
SLOG(2) << __func__ << "(" << lsb_release_file_.value() << ")";
std::vector<std::string> args = {"--config", openvpn_config_file_.value()};
std::string contents;
if (!base::ReadFileToString(lsb_release_file_, &contents)) {
LOG(ERROR) << "Unable to read the lsb-release file: "
<< lsb_release_file_.value();
return args;
}
const auto lines = base::SplitStringPiece(
contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
for (const std::string_view line : lines) {
const size_t assign = line.find('=');
if (assign == std::string::npos) {
continue;
}
const auto key = line.substr(0, assign);
const std::string value(line.substr(assign + 1));
if (key == kChromeOSReleaseName) {
args.push_back("--setenv");
args.push_back("UV_PLAT");
args.push_back(value);
} else if (key == kChromeOSReleaseVersion) {
args.push_back("--setenv");
args.push_back("UV_PLAT_REL");
args.push_back(value);
}
// Other LSB release values are irrelevant.
}
return args;
}
void OpenVPNDriver::OnDefaultPhysicalServiceEvent(
DefaultPhysicalServiceEvent event) {
if (!event_handler_)
return;
// When this happens, it means the service is connecting but the management
// server and the OpenVPN client have not been started yet. We don't need to
// do anything in this case:
// 1) For the service-down event, a new started client will be automatically
// on hold and we will check if the default service is connected before
// releasing the hold (see InitManagementChannelOptions()), and then the
// following service-up event will release the hold.
// 2) For the other two events, it will just set up the VPN connection on the
// new physical service.
if (!management_server_->IsStarted()) {
LOG(INFO) << "Default physical service event comes before management "
"server starts.";
return;
}
switch (event) {
case DefaultPhysicalServiceEvent::kUp:
management_server_->ReleaseHold();
event_handler_->OnDriverReconnecting(
GetReconnectTimeout(kReconnectReasonOffline));
break;
case DefaultPhysicalServiceEvent::kDown:
management_server_->Hold();
management_server_->Restart();
event_handler_->OnDriverReconnecting(kTimeoutNone);
break;
case DefaultPhysicalServiceEvent::kChanged:
// Ask the management server to reconnect immediately.
management_server_->ReleaseHold();
management_server_->Restart();
event_handler_->OnDriverReconnecting(
GetReconnectTimeout(kReconnectReasonOffline));
break;
}
}
void OpenVPNDriver::ReportConnectionMetrics() {
if (args()->Contains<std::vector<std::string>>(kOpenVPNCaCertPemProperty) &&
!args()
->Get<std::vector<std::string>>(kOpenVPNCaCertPemProperty)
.empty()) {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnRemoteAuthenticationType,
Metrics::kVpnRemoteAuthenticationTypeOpenVpnCertificate);
} else {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnRemoteAuthenticationType,
Metrics::kVpnRemoteAuthenticationTypeOpenVpnDefault);
}
bool has_user_authentication = false;
if (args()->Lookup<std::string>(kOpenVPNTokenProperty, "") != "") {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeOpenVpnUsernameToken);
has_user_authentication = true;
}
if (args()->Lookup<std::string>(kOpenVPNOTPProperty, "") != "") {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePasswordOtp);
has_user_authentication = true;
}
if (args()->Lookup<std::string>(kOpenVPNAuthUserPassProperty, "") != "" ||
args()->Lookup<std::string>(kOpenVPNUserProperty, "") != "") {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeOpenVpnUsernamePassword);
has_user_authentication = true;
}
if (args()->Lookup<std::string>(kOpenVPNClientCertIdProperty, "") != "") {
metrics()->SendEnumToUMA(
Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeOpenVpnCertificate);
has_user_authentication = true;
}
if (!has_user_authentication) {
metrics()->SendEnumToUMA(Metrics::kMetricVpnUserAuthenticationType,
Metrics::kVpnUserAuthenticationTypeOpenVpnNone);
}
}
void OpenVPNDriver::ReportCipherMetrics(std::string_view cipher) {
static constexpr auto str2enum =
base::MakeFixedFlatMap<std::string_view, Metrics::VpnOpenVPNCipher>({
{"BF-CBC", Metrics::kVpnOpenVPNCipher_BF_CBC},
{"AES-256-GCM", Metrics::kVpnOpenVPNCipher_AES_256_GCM},
{"AES-128-GCM", Metrics::kVpnOpenVPNCipher_AES_128_GCM},
});
Metrics::VpnOpenVPNCipher metric = Metrics::kVpnOpenVPNCipherUnknown;
const auto it = str2enum.find(cipher);
if (it != str2enum.end()) {
metric = it->second;
}
metrics()->SendEnumToUMA(Metrics::kMetricVpnOpenVPNCipher, metric);
}
} // namespace shill