blob: 63669ac9d82d9f7833120e96d115e3f981c2eea0 [file] [log] [blame] [edit]
// Copyright 2021 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/ipsec_connection.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/containers/fixed_flat_map.h>
#include <base/files/file_path_watcher.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/strcat.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/files/file_util.h>
#include <chromeos/dbus/service_constants.h>
#include <net-base/ip_address.h>
#include <net-base/ipv4_address.h>
#include <net-base/network_config.h>
#include <net-base/process_manager.h>
#include <re2/re2.h>
#include "shill/metrics.h"
#include "shill/vpn/vpn_end_reason.h"
#include "shill/vpn/vpn_util.h"
namespace shill {
namespace {
constexpr char kBaseRunDir[] = "/run/ipsec";
constexpr char kStrongSwanConfFileName[] = "strongswan.conf";
constexpr char kSwanctlConfFileName[] = "swanctl.conf";
constexpr char kResolvConfFilename[] = "resolv.conf";
constexpr char kSwanctlPath[] = "/usr/sbin/swanctl";
constexpr char kCharonPath[] = "/usr/libexec/ipsec/charon";
constexpr char kViciSocketPath[] = "/run/ipsec/charon.vici";
constexpr char kSmartcardModuleName[] = "crypto_module";
constexpr char kPIDPath[] = "/run/ipsec/charon.pid";
// aes128-sha256-modp3072: new strongSwan default
// aes128-sha1-modp2048: old strongSwan default
// 3des-sha1-modp1536: strongSwan fallback
// 3des-sha1-modp1024: for compatibility with Windows RRAS, which requires
// using the modp1024 dh-group
constexpr char kL2TPIPsecDefaultIKEProposals[] =
"aes128-sha256-modp3072,aes128-sha1-modp2048,3des-sha1-modp1536,3des-sha1-"
"modp1024,default";
// Cisco ASA L2TP/IPsec setup instructions indicate using md5 for authentication
// for the IPsec SA. Default StrongS/WAN setup is to only propose SHA1.
constexpr char kL2TPIPsecDefaultESPProposals[] =
"aes128gcm16,aes128-sha256,aes128-sha1,3des-sha1,3des-md5,default";
// The default proposals used by strongSwan 5.9.2, removing 3DES and SHA1. Each
// string contains two proposals: the AEAD one and non-AEAD one, they have to be
// specified separately in IKEv2.
constexpr char kIKEv2DefaultIKEProposals[] =
// non-AEAD encryption algorithms
"aes128-aes192-aes256-camellia128-camellia192-camellia256"
// integrity algorithms
"-aesxcbc-aescmac-sha256-sha384-sha512"
// DH groups
"-ecp256-ecp384-ecp521-ecp256bp-ecp384bp-ecp512bp-curve25519-curve448-"
"modp3072-modp4096-modp6144-modp8192-modp2048,"
// AEAD encryption algorithms
"aes128gcm16-aes192gcm16-aes256gcm16-chacha20poly1305-aes128gcm12-"
"aes192gcm12-aes256gcm12-aes128gcm8-aes192gcm8-aes256gcm8"
// PRF functions
"-prfsha256-prfsha384-prfsha512-prfaesxcbc-prfaescmac"
// DH groups
"-ecp256-ecp384-ecp521-ecp256bp-ecp384bp-ecp512bp-curve25519-curve448-"
"modp3072-modp4096-modp6144-modp8192-modp2048";
constexpr char kIKEv2DefaultESPProposals[] =
"aes128gcm16-aes192gcm16-aes256gcm16," // AEAD algorithms
"aes128-aes192-aes256" // encryption algorithms
"-sha256-sha384-sha512-aesxcbc"; // integrity algorithms
constexpr char kChildSAName[] = "managed";
// The interface identifier set for the XFRM interface. This id connects the
// IPsec policies and the interface. Only used in IKEv2 connections.
constexpr int kXFRMInterfaceID = 1;
// Only used in IKEv2 connection.
constexpr char kXFRMInterfaceName[] = "xfrm0";
// The time interval between two checks for if the vici socket is connectable.
constexpr base::TimeDelta kCheckViciConnectableInterval =
base::Milliseconds(300);
// The maximum number of attempts to check if the vici socket is connectable
// before returning a failure.
constexpr int kCheckViciConnectableMaxAttempts = 10;
// The default timeout value used in `swanctl --initiate`.
constexpr base::TimeDelta kIPsecTimeout = base::Seconds(30);
// The PIN value does not have any real effects. Use the default value here.
// See platform2/chaps/README.md
constexpr char kTPMDefaultPin[] = "111111";
// Represents a section in the format used by strongswan.conf and swanctl.conf.
// We use this class only for formatting swanctl.conf since the contents of
// strongswan.conf generated by this class are fixed. The basic syntax is:
// section := name { settings }
// settings := (section|keyvalue)*
// keyvalue := key = value\n
// Also see the following link for more details.
// https://wiki.strongswan.org/projects/strongswan/wiki/Strongswanconf
class StrongSwanConfSection {
public:
explicit StrongSwanConfSection(std::string_view name) : name_(name) {}
StrongSwanConfSection* AddSection(std::string_view name) {
auto section = new StrongSwanConfSection(name);
sections_.emplace_back(section);
return section;
}
void AddKeyValue(std::string_view key, std::string_view value) {
key_values_.insert_or_assign(std::string(key), std::string(value));
}
std::string Format(int indent_base = 0) const {
std::vector<std::string> lines;
const std::string indent_str(indent_base, ' ');
lines.push_back(base::StrCat({indent_str, name_, " {"}));
for (const auto& [k, v] : key_values_) {
lines.push_back(
base::StrCat({indent_str, " ", k, " = ", FormatValue(v)}));
}
for (const auto& section : sections_) {
lines.push_back(section->Format(indent_base + 2));
}
lines.push_back(base::StrCat({indent_str, "}"}));
return base::JoinString(lines, "\n");
}
private:
// Wraps the value in quotation marks and encodes control chars to make sure
// the whole value will be read as a single string.
static std::string FormatValue(std::string_view input) {
std::string output;
output.reserve(input.size() + 2);
output.append("\"");
for (char c : input) {
switch (c) {
case '\b':
output.append("\\b");
break;
case '\f':
output.append("\\f");
break;
case '\n':
output.append("\\n");
break;
case '\r':
output.append("\\r");
break;
case '\t':
output.append("\\t");
break;
case '"':
output.append("\\\"");
break;
case '\\':
output.append("\\\\");
break;
default:
output.push_back(c);
break;
}
}
output.append("\"");
return output;
}
std::string name_;
std::vector<std::unique_ptr<StrongSwanConfSection>> sections_;
std::map<std::string, std::string> key_values_;
};
// Parsing the encryption algorithm output by swanctl, which may contain two
// parts: the algorithm name and an optional key size. See the following src
// files in the strongswan project for how the name is output:
// - libstrongswan/crypto/crypters/crypter.c
// - swanctl/commands/list-sas.c
Metrics::VpnIpsecEncryptionAlgorithm ParseEncryptionAlgorithm(
std::string_view input) {
// The name and the key size is concated with "-". Changes them into "_" for
// simplicity.
std::string algo_str;
base::ReplaceChars(input, "-", "_", &algo_str);
static constexpr auto str2enum = base::MakeFixedFlatMap<
std::string_view, Metrics::VpnIpsecEncryptionAlgorithm>({
{"AES_CBC_128", Metrics::kVpnIpsecEncryptionAlgorithm_AES_CBC_128},
{"AES_CBC_192", Metrics::kVpnIpsecEncryptionAlgorithm_AES_CBC_192},
{"AES_CBC_256", Metrics::kVpnIpsecEncryptionAlgorithm_AES_CBC_256},
{"CAMELLIA_CBC_128",
Metrics::kVpnIpsecEncryptionAlgorithm_CAMELLIA_CBC_128},
{"CAMELLIA_CBC_192",
Metrics::kVpnIpsecEncryptionAlgorithm_CAMELLIA_CBC_192},
{"CAMELLIA_CBC_256",
Metrics::kVpnIpsecEncryptionAlgorithm_CAMELLIA_CBC_256},
{"3DES_CBC", Metrics::kVpnIpsecEncryptionAlgorithm_3DES_CBC},
{"AES_GCM_16_128", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_16_128},
{"AES_GCM_16_192", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_16_192},
{"AES_GCM_16_256", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_16_256},
{"AES_GCM_12_128", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_12_128},
{"AES_GCM_12_192", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_12_192},
{"AES_GCM_12_256", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_12_256},
{"AES_GCM_8_128", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_8_128},
{"AES_GCM_8_192", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_8_192},
{"AES_GCM_8_256", Metrics::kVpnIpsecEncryptionAlgorithm_AES_GCM_8_256},
});
const auto it = str2enum.find(algo_str);
if (it == str2enum.end()) {
return Metrics::kVpnIpsecEncryptionAlgorithmUnknown;
}
return it->second;
}
// Parsing the integrity algorithm output by swanctl, which may contain two
// parts: the algorithm name and an optional key size. See the following src
// files in the strongswan project for how the name is output:
// - libstrongswan/crypto/signers/signer.c
// - swanctl/commands/list-sas.c
Metrics::VpnIpsecIntegrityAlgorithm ParseIntegrityAlgorithm(
std::string_view input) {
// The name and the key size is concated with "-". Changes them into "_" for
// simplicity.
std::string algo_str;
base::ReplaceChars(input, "-", "_", &algo_str);
static constexpr auto str2enum =
base::MakeFixedFlatMap<std::string_view,
Metrics::VpnIpsecIntegrityAlgorithm>({
{"HMAC_SHA2_256_128",
Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA2_256_128},
{"HMAC_SHA2_384_192",
Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA2_384_192},
{"HMAC_SHA2_512_256",
Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA2_512_256},
{"HMAC_SHA1_96", Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA1_96},
{"AES_XCBC_96", Metrics::kVpnIpsecIntegrityAlgorithm_AES_XCBC_96},
{"AES_CMAC_96", Metrics::kVpnIpsecIntegrityAlgorithm_AES_CMAC_96},
});
const auto it = str2enum.find(algo_str);
if (it == str2enum.end()) {
return Metrics::kVpnIpsecIntegrityAlgorithmUnknown;
}
return it->second;
}
// Parsing the DH group output by swanctl. See the following src files in the
// strongswan project for the names:
// - libstrongswan/crypto/diffie_hellman.c
Metrics::VpnIpsecDHGroup ParseDHGroup(std::string_view input) {
static constexpr auto str2enum =
base::MakeFixedFlatMap<std::string_view, Metrics::VpnIpsecDHGroup>({
{"ECP_256", Metrics::kVpnIpsecDHGroup_ECP_256},
{"ECP_384", Metrics::kVpnIpsecDHGroup_ECP_384},
{"ECP_521", Metrics::kVpnIpsecDHGroup_ECP_521},
{"ECP_256_BP", Metrics::kVpnIpsecDHGroup_ECP_256_BP},
{"ECP_384_BP", Metrics::kVpnIpsecDHGroup_ECP_384_BP},
{"ECP_512_BP", Metrics::kVpnIpsecDHGroup_ECP_512_BP},
{"CURVE_25519", Metrics::kVpnIpsecDHGroup_CURVE_25519},
{"CURVE_448", Metrics::kVpnIpsecDHGroup_CURVE_448},
{"MODP_1024", Metrics::kVpnIpsecDHGroup_MODP_1024},
{"MODP_1536", Metrics::kVpnIpsecDHGroup_MODP_1536},
{"MODP_2048", Metrics::kVpnIpsecDHGroup_MODP_2048},
{"MODP_3072", Metrics::kVpnIpsecDHGroup_MODP_3072},
{"MODP_4096", Metrics::kVpnIpsecDHGroup_MODP_4096},
{"MODP_6144", Metrics::kVpnIpsecDHGroup_MODP_6144},
{"MODP_8192", Metrics::kVpnIpsecDHGroup_MODP_8192},
});
const auto it = str2enum.find(input);
if (it == str2enum.end()) {
return Metrics::kVpnIpsecDHGroupUnknown;
}
return it->second;
}
// Returns whether the pathname UNIX socket pointed by |path| is connect()-able.
bool TestUnixSocketConnectable(const base::FilePath& path) {
base::ScopedFD fd(socket(AF_UNIX, SOCK_STREAM, 0));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to open UNIX socket";
return false;
}
struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path.value().c_str());
if (HANDLE_EINTR(connect(fd.get(), (struct sockaddr*)&addr, sizeof(addr))) <
0) {
PLOG(WARNING) << "Failed to connect to UNIX socket file: " << path;
return false;
}
return true;
}
// Parses and returns the remote traffic selectors (remote_ts) from the output
// of `swanctl --list-sas`. The parsed information will be used for routing
// setup (only traffic sending to destinations in the remote_ts should be routed
// by VPN).
std::vector<net_base::IPCIDR> ParseRemoteTrafficSelectors(
const std::vector<std::string_view>& swanctl_output) {
// remote_ts information is on the last line, but there might be some empty
// lines in the output. Skip them at first.
std::string_view remote_ts_line;
for (auto it = swanctl_output.rbegin(); it != swanctl_output.rend(); it++) {
if (it->empty()) {
continue;
}
remote_ts_line = *it;
break;
}
if (remote_ts_line.empty()) {
LOG(ERROR) << __func__ << ": swanctl output is empty";
return {};
}
// The line will look like ` remote 0.0.0.0/0 ::/0`. The first token must
// be "remote", and the following tokens are the CIDR strings. There are no
// other tokens.
auto tokens = base::SplitStringPiece(
remote_ts_line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (tokens.size() == 0) {
LOG(ERROR) << __func__ << ": the last line is empty";
return {};
}
if (tokens[0] != "remote") {
LOG(ERROR) << __func__ << ": expect first token \"remote\", got \""
<< tokens[0] << "\"";
return {};
}
std::vector<net_base::IPCIDR> ret;
base::span<std::string_view> tokens_span = tokens;
for (std::string_view t : tokens_span.subspan(1)) {
if (auto cidr = net_base::IPCIDR::CreateFromCIDRString(t)) {
ret.push_back(*cidr);
} else {
// Continue on failure to parse in the include route in a best-effort way.
LOG(ERROR) << __func__ << ": invalid CIDR string " << t;
}
}
return ret;
}
} // namespace
constexpr char IPsecConnection::kOpensslConfFilename[] =
"/etc/ssl/openssl.cnf.compat";
// static
IPsecConnection::CipherSuite IPsecConnection::ParseCipherSuite(
std::string_view input) {
constexpr auto kInvalidResults =
std::make_tuple(Metrics::kVpnIpsecEncryptionAlgorithmUnknown,
Metrics::kVpnIpsecIntegrityAlgorithmUnknown,
Metrics::kVpnIpsecDHGroupUnknown);
auto [encryption_algo, integrity_algo, dh_group] = kInvalidResults;
const std::vector<std::string_view> names = base::SplitStringPiece(
input, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const std::string_view name : names) {
// Tries parsing the name as an encryption algorithm.
auto parsed_encryption_algo = ParseEncryptionAlgorithm(name);
if (parsed_encryption_algo !=
Metrics::kVpnIpsecEncryptionAlgorithmUnknown) {
if (encryption_algo != Metrics::kVpnIpsecEncryptionAlgorithmUnknown) {
// This means |input| contains algorithm names with a certain type
// multiple times. This is not expected, discards the results.
LOG(ERROR) << "The input contains multiple encryption algorithm: "
<< input;
return kInvalidResults;
}
encryption_algo = parsed_encryption_algo;
continue;
}
// Tries parsing the name as an integrity algorithm.
auto parsed_integrity_algo = ParseIntegrityAlgorithm(name);
if (parsed_integrity_algo != Metrics::kVpnIpsecIntegrityAlgorithmUnknown) {
if (integrity_algo != Metrics::kVpnIpsecIntegrityAlgorithmUnknown) {
LOG(ERROR) << "The input contains multiple integrity algorithm: "
<< input;
return kInvalidResults;
}
integrity_algo = parsed_integrity_algo;
continue;
}
// Tries parsing the name as a DH group.
auto parsed_dh_group = ParseDHGroup(name);
if (parsed_dh_group != Metrics::kVpnIpsecDHGroupUnknown) {
if (dh_group != Metrics::kVpnIpsecDHGroupUnknown) {
LOG(ERROR) << "The input contains multiple DH group: " << input;
return kInvalidResults;
}
dh_group = parsed_dh_group;
continue;
}
}
return {encryption_algo, integrity_algo, dh_group};
}
IPsecConnection::IPsecConnection(std::unique_ptr<Config> config,
std::unique_ptr<Callbacks> callbacks,
std::unique_ptr<VPNConnection> l2tp_connection,
DeviceInfo* device_info,
EventDispatcher* dispatcher,
net_base::ProcessManager* process_manager)
: VPNConnection(std::move(callbacks), dispatcher),
config_(std::move(config)),
l2tp_connection_(std::move(l2tp_connection)),
vici_socket_path_(kViciSocketPath),
device_info_(device_info),
process_manager_(process_manager),
vpn_util_(VPNUtil::New()) {
if (l2tp_connection_) {
CHECK(config_->ike_version == Config::IKEVersion::kV1);
l2tp_connection_->ResetCallbacks(std::make_unique<VPNConnection::Callbacks>(
base::BindRepeating(&IPsecConnection::OnL2TPConnected,
weak_factory_.GetWeakPtr()),
base::BindOnce(&IPsecConnection::OnL2TPFailure,
weak_factory_.GetWeakPtr()),
base::BindOnce(&IPsecConnection::OnL2TPStopped,
weak_factory_.GetWeakPtr())));
} else {
CHECK(config_->ike_version == Config::IKEVersion::kV2);
}
}
IPsecConnection::~IPsecConnection() {
if (state() == State::kIdle || state() == State::kStopped) {
return;
}
// This is unexpected but cannot be fully avoided, e.g., shill stops or
// restarts (or the corresponding service is removed) while the
// IPsecConnection is still connected or connecting (this is very likely to
// happen in the tast tests). Call StopCharon() to make sure that we at least
// send a SIGTERM to the charon process.
LOG(WARNING) << "Destructor called but the current state is " << state();
StopCharon();
}
void IPsecConnection::OnConnect() {
temp_dir_ = vpn_util_->CreateScopedTempDir(base::FilePath(kBaseRunDir));
if (!temp_dir_.IsValid()) {
NotifyFailure(VPNEndReason::kFailureInternal,
"Failed to create temp dir for IPsec");
return;
}
// Create dirs required by `swanctl --load-all`. They don't have any
// functional role, but just for avoiding error logs when running `swanctl`.
for (const std::string_view dir :
{"x509", "x509ca", "x509ocsp", "x509aa", "x509ac", "x509crl", "pubkey",
"private", "rsa", "ecdsa", "bliss", "pkcs8", "pkcs12"}) {
base::FilePath full_path = temp_dir_.GetPath().Append(dir);
// Ignore the return value since 1) the function itself will log the
// failures and 2) the failure here won't affect the execution.
vpn_util_->PrepareConfigDirectory(full_path);
}
ScheduleConnectTask(ConnectStep::kStart);
}
void IPsecConnection::ScheduleConnectTask(ConnectStep step) {
switch (step) {
case ConnectStep::kStart:
WriteStrongSwanConfig();
return;
case ConnectStep::kStrongSwanConfigWritten:
CheckPreviousCharonProcess(true);
return;
case ConnectStep::kStartCharon:
StartCharon();
return;
case ConnectStep::kCharonStarted:
WriteSwanctlConfig();
return;
case ConnectStep::kSwanctlConfigWritten:
SwanctlLoadConfig();
return;
case ConnectStep::kSwanctlConfigLoaded:
SwanctlInitiateConnection();
return;
case ConnectStep::kIPsecConnected:
SwanctlListSAs();
return;
case ConnectStep::kIPsecStatusRead:
if (l2tp_connection_) {
l2tp_connection_->Connect();
} else {
ParseDNSServers();
CreateXFRMInterface();
}
return;
default:
NOTREACHED();
}
}
void IPsecConnection::WriteStrongSwanConfig() {
strongswan_conf_path_ = temp_dir_.GetPath().Append(kStrongSwanConfFileName);
// See the following link for the format and descriptions for each field:
// https://wiki.strongswan.org/projects/strongswan/wiki/strongswanconf
static constexpr char kTemplate[] =
"charon {\n"
" accept_unencrypted_mainmode_messages = yes\n"
" ignore_routing_tables = 0\n"
" install_routes = no\n"
" install_virtual_ip = no\n" // b/263688887
// Avoid that charon install a rule for 220 table. See b/277999673.
" routing_table = 0\n"
" syslog {\n"
" daemon {\n"
" ike = 2\n" // Logs some traffic selector info.
" cfg = 2\n" // Logs algorithm proposals.
" knl = 2\n" // Logs high-level xfrm crypto parameters.
" }\n"
" }\n"
" plugins {\n"
" pkcs11 {\n"
" modules {\n"
" %s {\n"
" path = " PKCS11_LIB
"\n"
" }\n"
" }\n"
" }\n"
" resolve {\n"
" file = %s\n"
" }\n"
" }\n"
"}";
const auto contents =
base::StringPrintf(kTemplate, kSmartcardModuleName,
StrongSwanResolvConfPath().value().c_str());
if (!vpn_util_->WriteConfigFile(strongswan_conf_path_, contents)) {
NotifyFailure(VPNEndReason::kFailureInternal,
base::StrCat({"Failed to write ", kStrongSwanConfFileName}));
return;
}
ScheduleConnectTask(ConnectStep::kStrongSwanConfigWritten);
}
// The swanctl.conf which we generate here will look like:
// connections {
// vpn { // A connection named "vpn".
// ... // Parameters used in the IKE phase.
// local-1 { ... } // First round of authentication in local or remote.
// remote-1 { ... }
// local-2 { ... } // Second round of authentication (if exists).
// remote-2 { ... }
// managed { // A CHILD_SA named "managed".
// ... // Parameters for SA negotiation.
// }
// }
// }
// secrets {
// ... // secrets used in IKE (e.g., PSK).
// }
// For the detailed meanings of each field, see
// https://wiki.strongswan.org/projects/strongswan/wiki/Swanctlconf
void IPsecConnection::WriteSwanctlConfig() {
swanctl_conf_path_ = temp_dir_.GetPath().Append(kSwanctlConfFileName);
using Section = StrongSwanConfSection;
Section connections_section("connections");
Section secrets_section("secrets");
Section* vpn_section = connections_section.AddSection("vpn");
vpn_section->AddKeyValue("local_addrs", "0.0.0.0/0,::/0");
vpn_section->AddKeyValue("remote_addrs", config_->remote);
const std::string kIfIdString = base::NumberToString(kXFRMInterfaceID);
switch (config_->ike_version) {
case Config::IKEVersion::kV1:
vpn_section->AddKeyValue("version", "1"); // IKEv1
vpn_section->AddKeyValue("proposals", kL2TPIPsecDefaultIKEProposals);
break;
case Config::IKEVersion::kV2:
vpn_section->AddKeyValue("version", "2"); // IKEv2
vpn_section->AddKeyValue("proposals", kIKEv2DefaultIKEProposals);
vpn_section->AddKeyValue("vips", "0.0.0.0,::");
vpn_section->AddKeyValue("if_id_in", kIfIdString);
vpn_section->AddKeyValue("if_id_out", kIfIdString);
break;
default:
NOTREACHED();
}
// Fields for PSK.
if (config_->psk.has_value()) {
Section* local = vpn_section->AddSection("local-psk");
Section* remote = vpn_section->AddSection("remote-psk");
local->AddKeyValue("auth", "psk");
remote->AddKeyValue("auth", "psk");
auto* psk_section = secrets_section.AddSection("ike-1");
psk_section->AddKeyValue("secret", config_->psk.value());
if (config_->local_id.has_value()) {
local->AddKeyValue("id", config_->local_id.value());
}
if (config_->remote_id.has_value()) {
remote->AddKeyValue("id", config_->remote_id.value());
}
if (config_->tunnel_group.has_value()) {
// Aggressive mode is insecure but required by the legacy Cisco VPN here.
// See https://crbug.com/199004 .
vpn_section->AddKeyValue("aggressive", "yes");
// Sets local id.
const std::string tunnel_group = config_->tunnel_group.value();
const std::string hex_tunnel_id =
base::HexEncode(tunnel_group.c_str(), tunnel_group.length());
const std::string local_id =
base::StringPrintf("@#%s", hex_tunnel_id.c_str());
local->AddKeyValue("id", local_id);
}
}
// Fields for local pubkey.
if (config_->client_cert_id.has_value()) {
if (!config_->ca_cert_pem_strings.has_value() ||
!config_->client_cert_slot.has_value()) {
NotifyFailure(VPNEndReason::kInvalidConfig,
"Expect cert auth but some required fields are empty");
return;
}
Section* local_cert = vpn_section->AddSection("local-pubkey");
local_cert->AddKeyValue("auth", "pubkey");
if (config_->local_id.has_value()) {
local_cert->AddKeyValue("id", config_->local_id.value());
}
Section* cert = local_cert->AddSection("cert");
cert->AddKeyValue("handle", config_->client_cert_id.value());
cert->AddKeyValue("slot", config_->client_cert_slot.value());
cert->AddKeyValue("module", kSmartcardModuleName);
Section* token = secrets_section.AddSection("token-1");
token->AddKeyValue("module", kSmartcardModuleName);
token->AddKeyValue("handle", config_->client_cert_id.value());
token->AddKeyValue("slot", config_->client_cert_slot.value());
token->AddKeyValue("pin", kTPMDefaultPin);
}
// Fields for remote pubkey.
if (config_->ca_cert_pem_strings) {
Section* remote = vpn_section->AddSection("remote-pubkey");
remote->AddKeyValue("auth", "pubkey");
if (config_->remote_id.has_value()) {
remote->AddKeyValue("id", config_->remote_id.value());
}
// Writes server CA to a file and references this file in the config.
server_ca_.set_root_directory(temp_dir_.GetPath());
server_ca_path_ =
server_ca_.CreatePEMFromStrings(config_->ca_cert_pem_strings.value());
remote->AddKeyValue("cacerts", server_ca_path_.value());
}
// Fields for Xauth/EAP-MSCHAPv2. This will be used as the second round in
// L2TP/IPsec VPN or the first round in IKEv2 VPN.
if (config_->xauth_user.has_value() || config_->xauth_password.has_value()) {
if (!config_->xauth_user.has_value()) {
NotifyFailure(VPNEndReason::kInvalidConfig, "Only Xauth password is set");
return;
}
if (!config_->xauth_password.has_value()) {
NotifyFailure(VPNEndReason::kInvalidConfig, "Only Xauth user is set");
return;
}
Section* local = vpn_section->AddSection("local-xauth");
if (config_->local_id.has_value()) {
local->AddKeyValue("id", config_->local_id.value());
}
switch (config_->ike_version) {
case Config::IKEVersion::kV1:
local->AddKeyValue("auth", "xauth");
local->AddKeyValue("xauth_id", config_->xauth_user.value());
break;
case Config::IKEVersion::kV2:
local->AddKeyValue("auth", "eap-mschapv2");
local->AddKeyValue("eap_id", config_->xauth_user.value());
break;
default:
NOTREACHED();
}
Section* xauth_section = secrets_section.AddSection("xauth-1");
xauth_section->AddKeyValue("id", config_->xauth_user.value());
xauth_section->AddKeyValue("secret", config_->xauth_password.value());
}
// Fields for CHILD_SA.
Section* children_section = vpn_section->AddSection("children");
Section* child_section = children_section->AddSection(kChildSAName);
switch (config_->ike_version) {
case Config::IKEVersion::kV1:
child_section->AddKeyValue(
"local_ts",
base::StrCat({"dynamic[", config_->local_proto_port, "]"}));
child_section->AddKeyValue(
"remote_ts",
base::StrCat({"dynamic[", config_->remote_proto_port, "]"}));
child_section->AddKeyValue("esp_proposals",
kL2TPIPsecDefaultESPProposals);
child_section->AddKeyValue("mode", "transport");
break;
case Config::IKEVersion::kV2:
child_section->AddKeyValue("local_ts", "dynamic");
child_section->AddKeyValue("remote_ts", "0.0.0.0/0,::/0");
child_section->AddKeyValue("esp_proposals", kIKEv2DefaultESPProposals);
child_section->AddKeyValue("mode", "tunnel");
break;
default:
NOTREACHED();
}
child_section->AddKeyValue("set_mark_out",
"0x500"); // TrafficSource::HOST_VPN
// Writes to file.
const std::string contents = base::StrCat(
{connections_section.Format(), "\n", secrets_section.Format()});
if (!vpn_util_->WriteConfigFile(swanctl_conf_path_, contents)) {
NotifyFailure(
VPNEndReason::kFailureInternal,
base::StrCat({"Failed to write swanctl.conf", kSwanctlConfFileName}));
return;
}
ScheduleConnectTask(ConnectStep::kSwanctlConfigWritten);
}
void IPsecConnection::CheckPreviousCharonProcess(bool wait_if_alive) {
std::optional<bool> is_charon_alive =
process_manager_->IsTerminating(base::FilePath(kPIDPath));
if (!is_charon_alive || !*is_charon_alive) {
// If we failed to get pid of the previous charon process, we will also
// continue starting charon. It could be possible that charon process leaves
// the pid file into a weird state, e.g., file exists but it's empty. The
// charon will just exit itself if there is any problem and we can detect
// the issue there.
ScheduleConnectTask(ConnectStep::kStartCharon);
return;
}
if (!wait_if_alive) {
NotifyFailure(VPNEndReason::kFailureInternal, "Charon is still running");
return;
}
LOG(INFO) << "Old charon is alive. wait for 2 seconds.";
dispatcher()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IPsecConnection::CheckPreviousCharonProcess,
weak_factory_.GetWeakPtr(), false),
net_base::ProcessManager::kTerminationTimeout);
return;
}
void IPsecConnection::StartCharon() {
// We should make sure there is no socket file before starting charon, since
// we rely on its existence to know if charon is ready.
if (base::PathExists(vici_socket_path_)) {
// This could happen if something unexpected happened in the previous run,
// e.g., shill crashed.
LOG(WARNING) << "vici socket exists before starting charon";
if (!brillo::DeleteFile(vici_socket_path_)) {
const std::string reason = "Failed to delete vici socket file";
PLOG(ERROR) << reason;
NotifyFailure(VPNEndReason::kFailureInternal, reason);
return;
}
}
// TODO(b/165170125): Check the behavior when shill crashes (if charon is
// still running).
std::vector<std::string> args = {};
std::map<std::string, std::string> env = {
{"STRONGSWAN_CONF", strongswan_conf_path_.value()},
{"OPENSSL_CONF", IPsecConnection::kOpensslConfFilename},
};
constexpr uint64_t kCapMask = CAP_TO_MASK(CAP_NET_ADMIN) |
CAP_TO_MASK(CAP_NET_BIND_SERVICE) |
CAP_TO_MASK(CAP_NET_RAW);
auto minijail_options = VPNUtil::BuildMinijailOptions(kCapMask);
// Charon can have a quite large VmSize/VmPeak despite not using much resident
// memory. This can be partially reduced by lowering charon.threads, but in
// any case, Charon cannot rely on inheriting shill's RLIMIT_AS. See
// crbug/961519.
//
// Additionally, address space limits do not work well with allocators
// that mmap large amounts of space up front such as ASAN, Scudo,
// PartitionAlloc. Allow disabling it through a build configuration.
#ifndef DISABLE_CHARON_RLIMIT_AS
constexpr rlim_t kCharonRlimitAS = 1500 * 1024 * 1024; // 1500MB
minijail_options.rlimit_as_soft = kCharonRlimitAS;
#endif
charon_pid_ = process_manager_->StartProcessInMinijail(
FROM_HERE, base::FilePath(kCharonPath), args, env, minijail_options,
base::BindOnce(&IPsecConnection::OnCharonExitedUnexpectedly,
weak_factory_.GetWeakPtr()));
if (charon_pid_ == -1) {
NotifyFailure(VPNEndReason::kFailureInternal, "Failed to start charon");
return;
}
LOG(INFO) << "Charon started";
if (!base::PathExists(vici_socket_path_)) {
vici_socket_watcher_ = std::make_unique<base::FilePathWatcher>();
auto callback = base::BindRepeating(&IPsecConnection::OnViciSocketPathEvent,
weak_factory_.GetWeakPtr(),
kCheckViciConnectableMaxAttempts);
if (!vici_socket_watcher_->Watch(vici_socket_path_,
base::FilePathWatcher::Type::kNonRecursive,
callback)) {
NotifyFailure(VPNEndReason::kFailureInternal,
"Failed to set up FilePathWatcher for the vici socket");
return;
}
} else {
LOG(INFO) << "vici socket is already here";
ScheduleConnectTask(ConnectStep::kCharonStarted);
}
}
void IPsecConnection::SwanctlLoadConfig() {
const std::vector<std::string> args = {"--load-all", "--file",
swanctl_conf_path_.value()};
RunSwanctl(args,
base::BindOnce(&IPsecConnection::SwanctlNextStep,
weak_factory_.GetWeakPtr(),
ConnectStep::kSwanctlConfigLoaded),
VPNEndReason::kFailureInternal, "Failed to load swanctl.conf");
}
void IPsecConnection::SwanctlInitiateConnection() {
// This is a blocking call: if the execution returns with 0, then it means the
// IPsec connection has been established.
const std::string timeout_str =
base::NumberToString(kIPsecTimeout.InSeconds());
const std::vector<std::string> args = {"--initiate", "-c", kChildSAName,
"--timeout", timeout_str};
RunSwanctl(
args,
base::BindOnce(&IPsecConnection::SwanctlNextStep,
weak_factory_.GetWeakPtr(), ConnectStep::kIPsecConnected),
VPNEndReason::kFailureUnknown, "Failed to initiate IPsec connection");
}
void IPsecConnection::SwanctlListSAs() {
const std::vector<std::string> args = {"--list-sas"};
RunSwanctl(args,
base::BindOnce(&IPsecConnection::OnSwanctlListSAsDone,
weak_factory_.GetWeakPtr()),
VPNEndReason::kFailureInternal, "Failed to get SA information");
}
void IPsecConnection::CreateXFRMInterface() {
// We use the lo interface as the underlying interface of the created
// xfrm interface. This field is mandatory but does not really matter in
// our use case: it does matter if the outbound interface of the IPsec
// policies is configured. See the following link for more details:
// https://wiki.strongswan.org/projects/strongswan/wiki/RouteBasedVPN
int lo_index = device_info_->GetIndex("lo");
if (lo_index == -1) {
NotifyFailure(VPNEndReason::kFailureInternal, "Failed to get index of lo");
return;
}
const std::string err_msg = "Failed to create XFRM interface";
if (!device_info_->CreateXFRMInterface(
kXFRMInterfaceName, lo_index, kXFRMInterfaceID,
base::BindOnce(&IPsecConnection::OnXFRMInterfaceReady,
weak_factory_.GetWeakPtr()),
base::BindOnce(&IPsecConnection::NotifyFailure,
weak_factory_.GetWeakPtr(),
VPNEndReason::kFailureInternal, err_msg))) {
NotifyFailure(VPNEndReason::kFailureInternal, err_msg);
}
return;
}
void IPsecConnection::OnViciSocketPathEvent(int remaining_attempts,
const base::FilePath& /*path*/,
bool error) {
if (state() != State::kConnecting) {
LOG(WARNING) << "OnViciSocketPathEvent triggered on state " << state();
return;
}
if (error) {
NotifyFailure(VPNEndReason::kFailureInternal,
"FilePathWatcher error for the vici socket");
return;
}
if (!base::PathExists(vici_socket_path_)) {
// This is kind of unexpected, since the first event should be the creation
// of this file. Waits for the next event.
LOG(WARNING) << "vici socket is still not ready";
return;
}
vici_socket_watcher_ = nullptr;
if (remaining_attempts <= 0) {
NotifyFailure(VPNEndReason::kFailureInternal,
"Failed to wait for vici socket ready.");
return;
}
if (!TestUnixSocketConnectable(vici_socket_path_)) {
LOG(WARNING) << "vici socket is not connectable";
dispatcher()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IPsecConnection::OnViciSocketPathEvent,
weak_factory_.GetWeakPtr(), remaining_attempts - 1,
vici_socket_path_, false),
kCheckViciConnectableInterval);
return;
}
LOG(INFO) << "vici socket is ready";
ScheduleConnectTask(ConnectStep::kCharonStarted);
}
void IPsecConnection::OnCharonExitedUnexpectedly(int exit_code) {
charon_pid_ = -1;
NotifyFailure(VPNEndReason::kFailureInternal,
base::StringPrintf(
"charon exited unexpectedly with exit code %d", exit_code));
return;
}
void IPsecConnection::OnSwanctlListSAsDone(const std::string& stdout_str) {
// Note that any failure in parsing the cipher suite is unexpected but will
// not block the connection. We only leave a log for such failures.
const std::vector<std::string_view> lines = base::SplitStringPiece(
stdout_str, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (!l2tp_connection_) {
ParseLocalVirtualIPs(lines);
if (!local_virtual_ipv4_.has_value() && !local_virtual_ipv6_.has_value()) {
NotifyFailure(VPNEndReason::kFailureInternal,
"Failed to get local virtual IP");
return;
}
remote_traffic_selectors_ = ParseRemoteTrafficSelectors(lines);
if (remote_traffic_selectors_.empty()) {
// remote_ts must not be empty, otherwise there is no valid route.
NotifyFailure(VPNEndReason::kFailureInternal, "Failed to get remote_ts");
return;
}
}
ParseIKECipherSuite(lines);
ParseESPCipherSuite(lines);
ScheduleConnectTask(ConnectStep::kIPsecStatusRead);
}
void IPsecConnection::RunSwanctl(const std::vector<std::string>& args,
SwanctlCallback on_success,
VPNEndReason reason_on_failure,
const std::string& message_on_failure) {
std::map<std::string, std::string> env = {
{"STRONGSWAN_CONF", strongswan_conf_path_.value()},
};
constexpr uint64_t kCapMask = 0;
pid_t pid = process_manager_->StartProcessInMinijailWithStdout(
FROM_HERE, base::FilePath(kSwanctlPath), args, env,
VPNUtil::BuildMinijailOptions(kCapMask),
base::BindOnce(&IPsecConnection::OnSwanctlExited,
weak_factory_.GetWeakPtr(), std::move(on_success),
reason_on_failure, message_on_failure));
if (pid == -1) {
NotifyFailure(VPNEndReason::kFailureInternal,
message_on_failure + ": failed to run swanctl in minijail");
}
}
void IPsecConnection::OnSwanctlExited(SwanctlCallback on_success,
VPNEndReason reason_on_failure,
const std::string& message_on_failure,
int exit_code,
const std::string& stdout_str) {
if (exit_code == 0) {
std::move(on_success).Run(stdout_str);
} else {
NotifyFailure(reason_on_failure,
base::StringPrintf("%s, exit_code=%d",
message_on_failure.c_str(), exit_code));
}
}
void IPsecConnection::SwanctlNextStep(ConnectStep step, const std::string&) {
ScheduleConnectTask(step);
}
void IPsecConnection::ParseLocalVirtualIPs(
const std::vector<std::string_view>& swanctl_output) {
ClearVirtualIPs();
// The index of the line which contains the virtual IP information in
// |swanctl_output|.
constexpr int kVIPLineNumber = 1;
if (swanctl_output.size() <= kVIPLineNumber) {
LOG(ERROR) << "Failed to parse the virtual IP, output only contains "
<< swanctl_output.size() << " lines";
return;
}
// Example: local '192.168.1.245' @ 192.168.1.245[4500] [10.10.10.2]
// We need to match the IP address(es) in the last bracket ("[10.10.10.2]").
static constexpr LazyRE2 kVIPLine = {R"(\s*local.*@.*\s+\[(.*)\]\s*$)"};
const std::string_view line = swanctl_output[kVIPLineNumber];
// Checks if the part for virtual IP addresses exists.
std::string matched_part;
if (!RE2::FullMatch(line, *kVIPLine, &matched_part)) {
LOG(ERROR) << "Failed to parse the virtual IP, the line is: " << line;
return;
}
// Parses the string for the virtual IP (list).
// Example output of `swanctl --list-sas`:
// - when configured with an IPv4 address:
// local '192.168.1.245' @ 192.168.1.245[4500] [10.10.10.2]
// - when configured with an IPv6 address:
// local '192.168.1.245' @ 192.168.1.245[4500] [fec1::1]
// - when configured with an IPv4 address and IPv6 one:
// local '192.168.1.245' @ 192.168.1.245[4500] [10.10.10.2 fec1::1]
// Note: In the dual stack case, swanctl will always put IPv4 address
// at first.
for (std::string_view part : base::SplitStringPiece(
matched_part, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
const auto addr = net_base::IPAddress::CreateFromString(part);
if (!addr.has_value()) {
ClearVirtualIPs();
LOG(ERROR) << "Failed to parse the virtual IPs, the line is " << line;
return;
}
switch (addr->GetFamily()) {
case net_base::IPFamily::kIPv4:
if (local_virtual_ipv4_.has_value()) {
ClearVirtualIPs();
LOG(ERROR)
<< "At most one IPv4 address should be configured as the virtual "
"IP, the line is "
<< line;
return;
}
local_virtual_ipv4_ = addr->ToIPv4Address();
break;
case net_base::IPFamily::kIPv6:
if (local_virtual_ipv6_.has_value()) {
ClearVirtualIPs();
LOG(ERROR)
<< "At most one IPv6 address should be configured as the virtual "
"IP, the line is "
<< line;
return;
}
local_virtual_ipv6_ = addr->ToIPv6Address();
break;
}
}
}
void IPsecConnection::ParseIKECipherSuite(
const std::vector<std::string_view>& swanctl_output) {
ike_encryption_algo_ = Metrics::kVpnIpsecEncryptionAlgorithmUnknown;
ike_integrity_algo_ = Metrics::kVpnIpsecIntegrityAlgorithmUnknown;
ike_dh_group_ = Metrics::kVpnIpsecDHGroupUnknown;
// The index of the line which contains the cipher suite information for IKE
// in |swanctl_output|.
constexpr int kIKECipherSuiteLineNumber = 3;
if (swanctl_output.size() <= kIKECipherSuiteLineNumber) {
LOG(ERROR) << "Failed to parse the IKE cipher suite, the number of line is "
<< swanctl_output.size();
return;
}
// Example: AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_3072
// See `swanctl/commands/list-sas.c:ike_sa()` in the strongswan project for
// the format.
static constexpr LazyRE2 kIKECipherSuiteLine = {
R"(^\s*((?:[^/\s]+)(?:/[^/\s]+)*)\s*$)"};
const std::string_view line = swanctl_output[kIKECipherSuiteLineNumber];
std::string matched_part;
if (!RE2::FullMatch(line, *kIKECipherSuiteLine, &matched_part)) {
LOG(ERROR) << "Failed to parse the IKE cipher suite, the line is: " << line;
return;
}
std::tie(ike_encryption_algo_, ike_integrity_algo_, ike_dh_group_) =
ParseCipherSuite(matched_part);
if (ike_encryption_algo_ == Metrics::kVpnIpsecEncryptionAlgorithmUnknown ||
ike_integrity_algo_ == Metrics::kVpnIpsecIntegrityAlgorithmUnknown ||
ike_dh_group_ == Metrics::kVpnIpsecDHGroupUnknown) {
LOG(ERROR) << "The output does not contain a valid cipher suite for IKE: "
<< matched_part;
}
}
void IPsecConnection::ParseESPCipherSuite(
const std::vector<std::string_view>& swanctl_output) {
esp_encryption_algo_ = Metrics::kVpnIpsecEncryptionAlgorithmUnknown;
esp_integrity_algo_ = Metrics::kVpnIpsecIntegrityAlgorithmUnknown;
// The index of the line which contains the cipher suite information for ESP
// in |swanctl_output|.
constexpr int kESPCipherSuiteLineNumber = 5;
if (swanctl_output.size() <= kESPCipherSuiteLineNumber) {
LOG(ERROR) << "Failed to parse the ESP cipher suite, the number of line is "
<< swanctl_output.size();
return;
}
// This line does not only contains the cipher suite for ESP. Example:
// managed: #1, reqid 1, INSTALLED, TUNNEL, ESP:AES_CBC-128/HMAC_SHA2_256_128
// See `swanctl/commands/list-sas.c:child_sas()` in the strongswan project
// for the format.
static constexpr LazyRE2 kESPCipherSuiteLine = {
R"(^.*ESP:((?:[^/\s]+)(?:/[^/\s]+)*)\s*$)"};
const std::string_view line = swanctl_output[kESPCipherSuiteLineNumber];
std::string matched_part;
if (!RE2::FullMatch(line, *kESPCipherSuiteLine, &matched_part)) {
LOG(ERROR) << "Failed to parse the ESP cipher suite, the line is: " << line;
return;
}
const auto parsed_results = ParseCipherSuite(matched_part);
esp_encryption_algo_ = std::get<0>(parsed_results);
esp_integrity_algo_ = std::get<1>(parsed_results);
if (esp_encryption_algo_ == Metrics::kVpnIpsecEncryptionAlgorithmUnknown ||
esp_integrity_algo_ == Metrics::kVpnIpsecIntegrityAlgorithmUnknown) {
LOG(ERROR) << "The output does not contain a valid cipher suite for ESP: "
<< matched_part;
}
}
// The file to be parsed is in resolv.conf format. Example of its contents:
// nameserver 1.2.3.4 # by strongSwan
// nameserver 1.2.3.5 # by strongSwan
// TODO(b/229918180): Add a fuzzer test for this function.
void IPsecConnection::ParseDNSServers() {
dns_servers_.clear();
const base::FilePath path = StrongSwanResolvConfPath();
if (!base::PathExists(path)) {
LOG(INFO) << "No DNS servers found";
return;
}
std::string contents;
if (!base::ReadFileToString(path, &contents)) {
LOG(ERROR) << "Failed to read " << path.value();
return;
}
// TODO(jiejiang): Support IPv6 name servers.
static constexpr LazyRE2 kNameServerLine = {
R"(^nameserver\s+(\d+\.\d+\.\d+\.\d+).*)"};
const std::vector<std::string_view> lines = base::SplitStringPiece(
contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (std::string_view line : lines) {
std::string matched_part;
if (RE2::FullMatch(line, *kNameServerLine, &matched_part)) {
std::optional<net_base::IPAddress> dns =
net_base::IPAddress::CreateFromString(matched_part);
if (!dns.has_value()) {
LOG(WARNING) << "Ignoring invalid DNS server " << matched_part;
continue;
}
dns_servers_.push_back(*dns);
}
}
LOG(INFO) << "Received " << dns_servers_.size() << " DNS server entries";
return;
}
void IPsecConnection::OnL2TPConnected(
const std::string& interface_name,
int interface_index,
std::unique_ptr<net_base::NetworkConfig> network_config) {
if (state() != State::kConnecting) {
// This is possible, e.g., the upper layer called Disconnect() right before
// this callback is triggered.
LOG(WARNING) << "OnL2TPConnected() called but the IPsec layer is "
<< state();
return;
}
NotifyConnected(interface_name, interface_index, std::move(network_config));
}
void IPsecConnection::OnDisconnect() {
if (!l2tp_connection_) {
StopCharon();
return;
}
switch (l2tp_connection_->state()) {
case State::kIdle:
StopCharon();
return;
case State::kConnecting:
case State::kConnected:
l2tp_connection_->Disconnect();
return;
case State::kDisconnecting:
// StopCharon() called in the stopped callback.
return;
case State::kStopped:
// If |l2tp_connection_| is in stopped state but has not been destroyed,
// the stopped callback must be in the queue, so StopCharon() will be
// called later.
return;
default:
NOTREACHED();
}
}
void IPsecConnection::OnL2TPFailure(VPNEndReason reason) {
switch (state()) {
case State::kDisconnecting:
// If the IPsec layer is disconnecting, it could mean the failure happens
// in the IPsec layer, and the failure must have been propagated to the
// upper layer.
return;
case State::kConnecting:
case State::kConnected:
NotifyFailure(reason, "L2TP layer failure");
return;
default:
// Other states are unexpected.
LOG(DFATAL) << "OnL2TPFailure() called but the IPsec layer is "
<< state();
}
}
void IPsecConnection::OnL2TPStopped() {
l2tp_connection_ = nullptr;
if (state() != State::kDisconnecting) {
LOG(DFATAL) << "OnL2TPStopped() called but the IPsec layer is " << state();
// Does the cleanup anyway.
}
StopCharon();
}
void IPsecConnection::OnXFRMInterfaceReady(const std::string& ifname,
int ifindex) {
xfrm_interface_index_ = ifindex;
auto network_config = std::make_unique<net_base::NetworkConfig>();
if (local_virtual_ipv4_.has_value()) {
network_config->ipv4_address =
net_base::IPv4CIDR::CreateFromAddressAndPrefix(
*local_virtual_ipv4_, net_base::IPv4CIDR::kMaxPrefixLength);
network_config->ipv6_blackhole_route = true;
}
if (local_virtual_ipv6_.has_value()) {
network_config->ipv6_addresses.push_back(
*net_base::IPv6CIDR::CreateFromAddressAndPrefix(
*local_virtual_ipv6_, net_base::IPv6CIDR::kMaxPrefixLength));
network_config->ipv6_blackhole_route = false;
}
// Default routes are always included in remote_ts, if they exists.
network_config->included_route_prefixes = remote_traffic_selectors_;
network_config->dns_servers = dns_servers_;
network_config->mtu = net_base::NetworkConfig::kMinIPv6MTU;
NotifyConnected(ifname, ifindex, std::move(network_config));
}
void IPsecConnection::StopCharon() {
if (charon_pid_ != -1) {
process_manager_->StopProcess(charon_pid_);
charon_pid_ = -1;
}
// Removes the vici socket file, since the charon process will not do that by
// itself. Note that brillo::DeleteFile() will return true if the file does
// not exist.
if (!brillo::DeleteFile(vici_socket_path_)) {
PLOG(ERROR) << "Failed to delete the vici socket file";
}
// Removes the XFRM interface if it has been created.
if (xfrm_interface_index_.has_value()) {
device_info_->DeleteInterface(xfrm_interface_index_.value());
xfrm_interface_index_.reset();
}
// This function can be called directly from the destructor, and in that case
// the state may not be kDisconnecting.
if (state() == State::kDisconnecting) {
// Currently we do not wait for charon fully stopped to send out this
// signal.
NotifyStopped();
}
}
base::FilePath IPsecConnection::StrongSwanResolvConfPath() const {
return temp_dir_.GetPath().Append(kResolvConfFilename);
}
} // namespace shill