blob: 501571c34837b6f6b29c8350397103d0717930eb [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/vpn/ipsec_connection.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/run_loop.h>
#include <base/strings/string_util.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "shill/mock_control.h"
#include "shill/mock_device_info.h"
#include "shill/mock_manager.h"
#include "shill/mock_metrics.h"
#include "shill/mock_process_manager.h"
#include "shill/test_event_dispatcher.h"
#include "shill/vpn/fake_vpn_util.h"
#include "shill/vpn/vpn_connection_under_test.h"
namespace shill {
class IPsecConnectionUnderTest : public IPsecConnection {
public:
IPsecConnectionUnderTest(std::unique_ptr<Config> config,
std::unique_ptr<Callbacks> callbacks,
std::unique_ptr<VPNConnection> l2tp_connection,
DeviceInfo* device_info,
EventDispatcher* dispatcher,
ProcessManager* process_manager)
: IPsecConnection(std::move(config),
std::move(callbacks),
std::move(l2tp_connection),
device_info,
dispatcher,
process_manager) {
vpn_util_ = std::make_unique<FakeVPNUtil>();
}
IPsecConnectionUnderTest(const IPsecConnectionUnderTest&) = delete;
IPsecConnectionUnderTest& operator=(const IPsecConnectionUnderTest&) = delete;
base::FilePath SetTempDir() {
CHECK(temp_dir_.CreateUniqueTempDir());
return temp_dir_.GetPath();
}
void InvokeScheduleConnectTask(ConnectStep step) {
IPsecConnection::ScheduleConnectTask(step);
}
void set_config(std::unique_ptr<Config> config) {
config_ = std::move(config);
}
void set_strongswan_conf_path(const base::FilePath& path) {
strongswan_conf_path_ = path;
}
void set_swanctl_conf_path(const base::FilePath& path) {
swanctl_conf_path_ = path;
}
void set_charon_pid(pid_t pid) { charon_pid_ = pid; }
void set_vici_socket_path(const base::FilePath& path) {
vici_socket_path_ = path;
}
void set_state(State state) { state_ = state; }
void set_l2tp_connection(std::unique_ptr<VPNConnection> l2tp_in) {
l2tp_connection_ = std::move(l2tp_in);
}
std::string local_virtual_ip() { return local_virtual_ip_; }
std::vector<std::string> dns_servers() { return dns_servers_; }
MOCK_METHOD(void, ScheduleConnectTask, (ConnectStep), (override));
};
namespace {
using ConnectStep = IPsecConnection::ConnectStep;
using testing::_;
using testing::AllOf;
using testing::DoAll;
using testing::Return;
using testing::WithArg;
constexpr char kExpectedStrongSwanConf[] = R"(charon {
accept_unencrypted_mainmode_messages = yes
ignore_routing_tables = 0
install_routes = no
routing_table = 0
syslog {
daemon {
ike = 2
cfg = 2
knl = 2
}
}
plugins {
pkcs11 {
modules {
crypto_module {
path = $1
}
}
}
resolve {
file = $2
}
}
})";
// Expected contents of swanctl.conf in WriteSwanctlConfigL2TPIPsec test.
constexpr char kExpectedSwanctlConfL2TPIPsecPSK[] = R"(connections {
vpn {
local_addrs = "0.0.0.0/0,::/0"
proposals = "aes128-sha256-modp3072,aes128-sha1-modp2048,3des-sha1-modp1536,3des-sha1-modp1024,default"
remote_addrs = "10.0.0.1"
version = "1"
local-psk {
auth = "psk"
}
remote-psk {
auth = "psk"
}
local-xauth {
auth = "xauth"
xauth_id = "xauth_user"
}
children {
managed {
esp_proposals = "aes128gcm16,aes128-sha256,aes128-sha1,3des-sha1,3des-md5,default"
local_ts = "dynamic[17/1701]"
mode = "transport"
remote_ts = "dynamic[17/1701]"
}
}
}
}
secrets {
ike-1 {
secret = "this is psk"
}
xauth-1 {
id = "xauth_user"
secret = "xauth_password"
}
})";
// Expected contents of swanctl.conf in WriteSwanctlConfigIKEv2 test.
constexpr char kExpectedSwanctlConfIKEv2EAP[] = R"(connections {
vpn {
if_id_in = "1"
if_id_out = "1"
local_addrs = "0.0.0.0/0,::/0"
proposals = "aes128-aes192-aes256-camellia128-camellia192-camellia256-aesxcbc-aescmac-sha256-sha384-sha512-ecp256-ecp384-ecp521-ecp256bp-ecp384bp-ecp512bp-curve25519-curve448-modp3072-modp4096-modp6144-modp8192-modp2048,aes128gcm16-aes192gcm16-aes256gcm16-chacha20poly1305-aes128gcm12-aes192gcm12-aes256gcm12-aes128gcm8-aes192gcm8-aes256gcm8-prfsha256-prfsha384-prfsha512-prfaesxcbc-prfaescmac-ecp256-ecp384-ecp521-ecp256bp-ecp384bp-ecp512bp-curve25519-curve448-modp3072-modp4096-modp6144-modp8192-modp2048"
remote_addrs = "10.0.0.1"
version = "2"
vips = "0.0.0.0"
local-xauth {
auth = "eap-mschapv2"
eap_id = "xauth_user"
id = "local_id"
}
children {
managed {
esp_proposals = "aes128gcm16-aes192gcm16-aes256gcm16,aes128-aes192-aes256-sha256-sha384-sha512-aesxcbc"
local_ts = "dynamic"
mode = "tunnel"
remote_ts = "0.0.0.0/0"
}
}
}
}
secrets {
xauth-1 {
id = "xauth_user"
secret = "xauth_password"
}
})";
// Output of `swanctl --list-sas` used in SwanctlListSAsL2TP test.
constexpr char kSwanctlListSAsL2TPOutput[] =
R"(vpn: #1, ESTABLISHED, IKEv1, d182735e14966467_i* ff9e514adb77bea8_r
local 'CN=10.1.1.10' @ 192.168.1.2[4500]
remote '10.1.1.10' @ 1.2.3.4[4500]
AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_3072
established 186s ago, rekeying in 12983s
managed: #1, reqid 1, INSTALLED, TRANSPORT-in-UDP, ESP:AES_CBC-128/HMAC_SHA2_256_128
installed 186s ago, rekeying in 3144s, expires in 3775s
in cd474435, 7657 bytes, 83 packets, 0s ago
out c16887e7, 141075 bytes, 1614 packets, 0s ago
local 192.168.1.2/32[udp/l2tp]
remote 1.2.3.4/32[udp/l2tp]
)";
constexpr char kSwanctlListSAsIKEv2Output[] =
R"(vpn: #1, ESTABLISHED, IKEv2, f32cfa4a3b007894_i* 7cc2f86218f11619_r
local '192.168.1.2' @ 192.168.1.2[4500] [10.10.10.2]
remote '192.168.1.3' @ 192.168.1.3[4500]
AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_3072
established 56s ago, rekeying in 14192s, reauth in 8601s
managed: #1, reqid 1, INSTALLED, TUNNEL, ESP:AES_CBC-128/HMAC_SHA2_256_128
installed 56s ago, rekeying in 3266s, expires in 3904s
in c13d6df5 (-|0x00000001), 21701 bytes, 66 packets, 0s ago
out c78f93a7 (-|0x00000001), 11293 bytes, 95 packets, 0s ago
local 10.10.10.2/32
remote 0.0.0.0/0)";
// Creates the UNIX socket at |path|, and listens on it if |start_listen| is
// true. Returns the fd of this socket.
base::ScopedFD CreateUnixSocketAt(const base::FilePath& path,
bool start_listen) {
base::ScopedFD fd(socket(AF_UNIX, SOCK_STREAM, 0));
CHECK(fd.is_valid());
struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", path.value().c_str());
CHECK_EQ(bind(fd.get(), (struct sockaddr*)&addr, sizeof(addr)), 0);
if (start_listen) {
CHECK_EQ(listen(fd.get(), 1), 0);
}
return fd;
}
class MockCallbacks {
public:
MOCK_METHOD(void,
OnConnected,
(const std::string& link_name,
int interface_index,
const IPConfig::Properties& ip_properties));
MOCK_METHOD(void, OnFailure, (Service::ConnectFailure));
MOCK_METHOD(void, OnStopped, ());
};
class IPsecConnectionTest : public testing::Test {
public:
IPsecConnectionTest()
: manager_(&control_, &dispatcher_, &metrics_), device_info_(&manager_) {
auto callbacks = std::make_unique<VPNConnection::Callbacks>(
base::BindRepeating(&MockCallbacks::OnConnected,
base::Unretained(&callbacks_)),
base::BindOnce(&MockCallbacks::OnFailure,
base::Unretained(&callbacks_)),
base::BindOnce(&MockCallbacks::OnStopped,
base::Unretained(&callbacks_)));
auto l2tp_tmp =
std::make_unique<VPNConnectionUnderTest>(nullptr, &dispatcher_);
l2tp_connection_ = l2tp_tmp.get();
ipsec_connection_ = std::make_unique<IPsecConnectionUnderTest>(
std::make_unique<IPsecConnection::Config>(), std::move(callbacks),
std::move(l2tp_tmp), &device_info_, &dispatcher_, &process_manager_);
}
protected:
void SetAsIKEv2Connection() {
auto config = std::make_unique<IPsecConnection::Config>();
config->ike_version = IPsecConnection::Config::IKEVersion::kV2;
ipsec_connection_->set_config(std::move(config));
ipsec_connection_->set_l2tp_connection(nullptr);
}
MockControl control_;
EventDispatcherForTest dispatcher_;
MockMetrics metrics_;
MockManager manager_;
MockCallbacks callbacks_;
MockDeviceInfo device_info_;
MockProcessManager process_manager_;
std::unique_ptr<IPsecConnectionUnderTest> ipsec_connection_;
VPNConnectionUnderTest* l2tp_connection_; // owned by ipsec_connection_;
};
TEST_F(IPsecConnectionTest, WriteStrongSwanConfig) {
base::FilePath temp_dir = ipsec_connection_->SetTempDir();
// Signal should be send out at the end of the execution.
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kStrongSwanConfigWritten));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kStart);
// IPsecConnection should write the config to the `strongswan.conf` file under
// the temp dir it created.
base::FilePath expected_path = temp_dir.Append("strongswan.conf");
ASSERT_TRUE(base::PathExists(expected_path));
std::string actual_content;
ASSERT_TRUE(base::ReadFileToString(expected_path, &actual_content));
EXPECT_EQ(actual_content,
base::ReplaceStringPlaceholders(
kExpectedStrongSwanConf,
{PKCS11_LIB, temp_dir.Append("resolv.conf").value()}, nullptr));
// The file should be deleted after destroying the IPsecConnection object.
ipsec_connection_ = nullptr;
ASSERT_FALSE(base::PathExists(expected_path));
}
TEST_F(IPsecConnectionTest, StartCharon) {
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
const base::FilePath kStrongSwanConfPath("/tmp/strongswan.conf");
ipsec_connection_->set_strongswan_conf_path(kStrongSwanConfPath);
// Prepares the file path under the scoped temp dir. The actual file will be
// created later to simulate the case that it is created by the charon
// process.
const auto tmp_dir = ipsec_connection_->SetTempDir();
const base::FilePath kViciSocketPath = tmp_dir.Append("charon.vici");
ipsec_connection_->set_vici_socket_path(kViciSocketPath);
// Expects call for starting charon process.
const base::FilePath kExpectedProgramPath("/usr/libexec/ipsec/charon");
const std::vector<std::string> kExpectedArgs = {};
const std::map<std::string, std::string> kExpectedEnv = {
{"STRONGSWAN_CONF", kStrongSwanConfPath.value()}};
constexpr uint64_t kExpectedCapMask = CAP_TO_MASK(CAP_NET_ADMIN) |
CAP_TO_MASK(CAP_NET_BIND_SERVICE) |
CAP_TO_MASK(CAP_NET_RAW);
EXPECT_CALL(process_manager_,
StartProcessInMinijail(
_, kExpectedProgramPath, kExpectedArgs, kExpectedEnv,
AllOf(MinijailOptionsMatchUserGroup("vpn", "vpn"),
MinijailOptionsMatchCapMask(kExpectedCapMask),
MinijailOptionsMatchInheritSupplumentaryGroup(true),
MinijailOptionsMatchCloseNonstdFDs(true)),
_))
.WillOnce(Return(123));
// Triggers the task.
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kStrongSwanConfigWritten);
// Creates the socket file, and then IPsecConnection should be notified and
// forward the step. We use a RunLoop here instead of RunUtilIdle() since it
// cannot be guaranteed that FilePathWatcher posted the task before
// RunUtilIdle() is called.
base::ScopedFD vici_server_fd =
CreateUnixSocketAt(kViciSocketPath, /*start_listen=*/true);
base::RunLoop run_loop;
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kCharonStarted))
.WillOnce([&](ConnectStep) { run_loop.Quit(); });
run_loop.Run();
}
TEST_F(IPsecConnectionTest, StartCharonFailWithStartProcess) {
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _))
.WillOnce(Return(-1));
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kStrongSwanConfigWritten);
EXPECT_CALL(callbacks_, OnFailure(_));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, StartCharonFailWithCharonExited) {
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
base::OnceCallback<void(int)> exit_cb;
EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _))
.WillOnce(
WithArg<5>([&exit_cb](base::OnceCallback<void(int)> exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kStrongSwanConfigWritten);
std::move(exit_cb).Run(1);
EXPECT_CALL(callbacks_, OnFailure(_));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, StartCharonFailWithSocketNotListening) {
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
const auto tmp_dir = ipsec_connection_->SetTempDir();
const base::FilePath kViciSocketPath = tmp_dir.Append("charon.vici");
ipsec_connection_->set_vici_socket_path(kViciSocketPath);
base::OnceCallback<void(int)> exit_cb;
EXPECT_CALL(process_manager_, StartProcessInMinijail(_, _, _, _, _, _))
.WillOnce(
WithArg<5>([&exit_cb](base::OnceCallback<void(int)> exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kStrongSwanConfigWritten);
base::ScopedFD vici_server_fd =
CreateUnixSocketAt(kViciSocketPath, /*start_listen=*/false);
base::RunLoop run_loop;
EXPECT_CALL(callbacks_, OnFailure(_)).WillOnce([&](Service::ConnectFailure) {
run_loop.Quit();
});
run_loop.Run();
}
TEST_F(IPsecConnectionTest, WriteSwanctlConfigL2TPIPsec) {
base::FilePath temp_dir = ipsec_connection_->SetTempDir();
// Creates a config with PSK. Cert will be covered by tast tests.
auto config = std::make_unique<IPsecConnection::Config>();
config->ike_version = IPsecConnection::Config::IKEVersion::kV1;
config->remote = "10.0.0.1";
config->local_proto_port = "17/1701";
config->remote_proto_port = "17/1701";
config->psk = "this is psk";
config->xauth_user = "xauth_user";
config->xauth_password = "xauth_password";
ipsec_connection_->set_config(std::move(config));
// Signal should be sent out at the end of the execution.
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kSwanctlConfigWritten));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kCharonStarted);
// IPsecConnection should write the config to the `swanctl.conf` file under
// the temp dir it created.
base::FilePath expected_path = temp_dir.Append("swanctl.conf");
ASSERT_TRUE(base::PathExists(expected_path));
std::string actual_content;
ASSERT_TRUE(base::ReadFileToString(expected_path, &actual_content));
EXPECT_EQ(actual_content, kExpectedSwanctlConfL2TPIPsecPSK);
// The file should be deleted after destroying the IPsecConnection object.
ipsec_connection_ = nullptr;
ASSERT_FALSE(base::PathExists(expected_path));
}
TEST_F(IPsecConnectionTest, WriteSwanctlConfigIKEv2) {
base::FilePath temp_dir = ipsec_connection_->SetTempDir();
// Creates a config with PSK. Cert will be covered by tast tests.
auto config = std::make_unique<IPsecConnection::Config>();
config->ike_version = IPsecConnection::Config::IKEVersion::kV2;
config->remote = "10.0.0.1";
config->local_id = "local_id";
config->xauth_user = "xauth_user";
config->xauth_password = "xauth_password";
ipsec_connection_->set_config(std::move(config));
// Signal should be sent out at the end of the execution.
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kSwanctlConfigWritten));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kCharonStarted);
// IPsecConnection should write the config to the `swanctl.conf` file under
// the temp dir it created.
base::FilePath expected_path = temp_dir.Append("swanctl.conf");
ASSERT_TRUE(base::PathExists(expected_path));
std::string actual_content;
ASSERT_TRUE(base::ReadFileToString(expected_path, &actual_content));
EXPECT_EQ(actual_content, kExpectedSwanctlConfIKEv2EAP);
// The file should be deleted after destroying the IPsecConnection object.
ipsec_connection_ = nullptr;
ASSERT_FALSE(base::PathExists(expected_path));
}
TEST_F(IPsecConnectionTest, SwanctlLoadConfig) {
const base::FilePath kStrongSwanConfPath("/tmp/strongswan.conf");
ipsec_connection_->set_strongswan_conf_path(kStrongSwanConfPath);
const base::FilePath kSwanctlConfPath("/tmp/swanctl.conf");
ipsec_connection_->set_swanctl_conf_path(kSwanctlConfPath);
// Expects call for starting swanctl process.
ProcessManager::ExitWithStdoutCallback exit_cb;
const base::FilePath kExpectedProgramPath("/usr/sbin/swanctl");
const std::vector<std::string> kExpectedArgs = {"--load-all", "--file",
kSwanctlConfPath.value()};
const std::map<std::string, std::string> kExpectedEnv = {
{"STRONGSWAN_CONF", kStrongSwanConfPath.value()}};
constexpr uint64_t kExpectedCapMask = 0;
EXPECT_CALL(process_manager_,
StartProcessInMinijailWithStdout(
_, kExpectedProgramPath, kExpectedArgs, kExpectedEnv,
AllOf(MinijailOptionsMatchUserGroup("vpn", "vpn"),
MinijailOptionsMatchCapMask(kExpectedCapMask),
MinijailOptionsMatchInheritSupplumentaryGroup(true),
MinijailOptionsMatchCloseNonstdFDs(true)),
_))
.WillOnce(WithArg<5>(
[&exit_cb](ProcessManager::ExitWithStdoutCallback exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kSwanctlConfigWritten);
// Signal should be sent out if swanctl exits with 0.
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kSwanctlConfigLoaded));
std::move(exit_cb).Run(0, "");
}
TEST_F(IPsecConnectionTest, SwanctlLoadConfigFailExecution) {
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
EXPECT_CALL(process_manager_,
StartProcessInMinijailWithStdout(_, _, _, _, _, _))
.WillOnce(Return(-1));
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kSwanctlConfigWritten);
EXPECT_CALL(callbacks_, OnFailure(_));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, SwanctlLoadConfigFailExitCodeNonZero) {
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
ProcessManager::ExitWithStdoutCallback exit_cb;
EXPECT_CALL(process_manager_,
StartProcessInMinijailWithStdout(_, _, _, _, _, _))
.WillOnce(WithArg<5>(
[&exit_cb](ProcessManager::ExitWithStdoutCallback exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kSwanctlConfigWritten);
std::move(exit_cb).Run(1, "");
EXPECT_CALL(callbacks_, OnFailure(_));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, SwanctlInitiateConnection) {
const base::FilePath kStrongSwanConfPath("/tmp/strongswan.conf");
ipsec_connection_->set_strongswan_conf_path(kStrongSwanConfPath);
const base::FilePath kSwanctlConfPath("/tmp/swanctl.conf");
ipsec_connection_->set_swanctl_conf_path(kSwanctlConfPath);
// Expects call for starting swanctl process.
ProcessManager::ExitWithStdoutCallback exit_cb;
const base::FilePath kExpectedProgramPath("/usr/sbin/swanctl");
const std::vector<std::string> kExpectedArgs = {"--initiate", "-c", "managed",
"--timeout", "30"};
const std::map<std::string, std::string> kExpectedEnv = {
{"STRONGSWAN_CONF", kStrongSwanConfPath.value()}};
constexpr uint64_t kExpectedCapMask = 0;
EXPECT_CALL(process_manager_,
StartProcessInMinijailWithStdout(
_, kExpectedProgramPath, kExpectedArgs, kExpectedEnv,
AllOf(MinijailOptionsMatchUserGroup("vpn", "vpn"),
MinijailOptionsMatchCapMask(kExpectedCapMask),
MinijailOptionsMatchInheritSupplumentaryGroup(true),
MinijailOptionsMatchCloseNonstdFDs(true)),
_))
.WillOnce(WithArg<5>(
[&exit_cb](ProcessManager::ExitWithStdoutCallback exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(
ConnectStep::kSwanctlConfigLoaded);
// Signal should be sent out if swanctl exits with 0.
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kIPsecConnected));
std::move(exit_cb).Run(0, "");
}
TEST_F(IPsecConnectionTest, SwanctlListSAsL2TP) {
const base::FilePath kStrongSwanConfPath("/tmp/strongswan.conf");
ipsec_connection_->set_strongswan_conf_path(kStrongSwanConfPath);
ProcessManager::ExitWithStdoutCallback exit_cb;
const base::FilePath kExpectedProgramPath("/usr/sbin/swanctl");
const std::vector<std::string> kExpectedArgs = {"--list-sas"};
const std::map<std::string, std::string> kExpectedEnv = {
{"STRONGSWAN_CONF", kStrongSwanConfPath.value()}};
constexpr uint64_t kExpectedCapMask = 0;
EXPECT_CALL(process_manager_,
StartProcessInMinijailWithStdout(
_, kExpectedProgramPath, kExpectedArgs, kExpectedEnv,
AllOf(MinijailOptionsMatchUserGroup("vpn", "vpn"),
MinijailOptionsMatchCapMask(kExpectedCapMask),
MinijailOptionsMatchInheritSupplumentaryGroup(true),
MinijailOptionsMatchCloseNonstdFDs(true)),
_))
.WillOnce(WithArg<5>(
[&exit_cb](ProcessManager::ExitWithStdoutCallback exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecConnected);
// Signal should be sent out if swanctl exits with 0.
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kIPsecStatusRead));
std::move(exit_cb).Run(0, kSwanctlListSAsL2TPOutput);
// Checks the parsed cipher suites.
EXPECT_EQ(ipsec_connection_->ike_encryption_algo(),
Metrics::kVpnIpsecEncryptionAlgorithm_AES_CBC_128);
EXPECT_EQ(ipsec_connection_->ike_integrity_algo(),
Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA2_256_128);
EXPECT_EQ(ipsec_connection_->ike_dh_group(),
Metrics::kVpnIpsecDHGroup_MODP_3072);
EXPECT_EQ(ipsec_connection_->esp_encryption_algo(),
Metrics::kVpnIpsecEncryptionAlgorithm_AES_CBC_128);
EXPECT_EQ(ipsec_connection_->esp_integrity_algo(),
Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA2_256_128);
}
TEST_F(IPsecConnectionTest, SwanctlListSAsIKEv2) {
SetAsIKEv2Connection();
ProcessManager::ExitWithStdoutCallback exit_cb;
EXPECT_CALL(process_manager_,
StartProcessInMinijailWithStdout(_, _, _, _, _, _))
.WillOnce(WithArg<5>(
[&exit_cb](ProcessManager::ExitWithStdoutCallback exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecConnected);
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kIPsecStatusRead));
std::move(exit_cb).Run(0, kSwanctlListSAsIKEv2Output);
// Checks the parsed virtual ip.
EXPECT_EQ(ipsec_connection_->local_virtual_ip(), "10.10.10.2");
// Checks the parsed cipher suites.
EXPECT_EQ(ipsec_connection_->ike_encryption_algo(),
Metrics::kVpnIpsecEncryptionAlgorithm_AES_CBC_128);
EXPECT_EQ(ipsec_connection_->ike_integrity_algo(),
Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA2_256_128);
EXPECT_EQ(ipsec_connection_->ike_dh_group(),
Metrics::kVpnIpsecDHGroup_MODP_3072);
EXPECT_EQ(ipsec_connection_->esp_encryption_algo(),
Metrics::kVpnIpsecEncryptionAlgorithm_AES_CBC_128);
EXPECT_EQ(ipsec_connection_->esp_integrity_algo(),
Metrics::kVpnIpsecIntegrityAlgorithm_HMAC_SHA2_256_128);
}
TEST_F(IPsecConnectionTest, SwanctlListSAsIKEv2ParseVIPFailed) {
SetAsIKEv2Connection();
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
ProcessManager::ExitWithStdoutCallback exit_cb;
EXPECT_CALL(process_manager_,
StartProcessInMinijailWithStdout(_, _, _, _, _, _))
.WillOnce(WithArg<5>(
[&exit_cb](ProcessManager::ExitWithStdoutCallback exit_callback) {
exit_cb = std::move(exit_callback);
return 123;
}));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecConnected);
EXPECT_CALL(*ipsec_connection_,
ScheduleConnectTask(ConnectStep::kIPsecStatusRead))
.Times(0);
std::move(exit_cb).Run(0, ""); // Passes an empty string.
EXPECT_CALL(callbacks_, OnFailure(Service::kFailureInternal));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, StartL2TPLayerAndConnected) {
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
// L2TP connect.
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecStatusRead);
EXPECT_CALL(*l2tp_connection_, OnConnect());
dispatcher_.task_environment().RunUntilIdle();
// L2TP connected.
const std::string kIfName = "ppp0";
constexpr int kIfIndex = 123;
const IPConfig::Properties kIPProperties;
l2tp_connection_->TriggerConnected(kIfName, kIfIndex, kIPProperties);
EXPECT_CALL(callbacks_, OnConnected(kIfName, kIfIndex, _));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, OnL2TPFailure) {
ipsec_connection_->set_state(VPNConnection::State::kConnected);
l2tp_connection_->set_state(VPNConnection::State::kConnecting);
l2tp_connection_->TriggerFailure(Service::kFailureInternal, "");
EXPECT_CALL(callbacks_, OnFailure(Service::kFailureInternal));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, OnL2TPStopped) {
ipsec_connection_->set_state(VPNConnection::State::kDisconnecting);
l2tp_connection_->set_state(VPNConnection::State::kDisconnecting);
l2tp_connection_->TriggerStopped();
// If charon is still running, it should be stopped.
constexpr pid_t kCharonPid = 123;
ipsec_connection_->set_charon_pid(kCharonPid);
EXPECT_CALL(process_manager_, StopProcess(kCharonPid));
EXPECT_CALL(callbacks_, OnStopped());
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, ParseDNSServers) {
SetAsIKEv2Connection();
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
// Simulates that charon creates the file.
const auto tmp_dir = ipsec_connection_->SetTempDir();
const base::FilePath kResolvConfPath = tmp_dir.Append("resolv.conf");
CHECK(
base::WriteFile(kResolvConfPath,
base::JoinString({"nameserver 1.2.3.4 # by strongSwan",
"nameserver 1.2.3.5 # by strongSwan"},
"\n")));
EXPECT_CALL(device_info_, CreateXFRMInterface(_, _, _, _, _))
.WillRepeatedly(Return(true));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecStatusRead);
const auto acutal_dns_servers = ipsec_connection_->dns_servers();
EXPECT_EQ(acutal_dns_servers.size(), 2);
EXPECT_EQ(acutal_dns_servers[0], "1.2.3.4");
EXPECT_EQ(acutal_dns_servers[1], "1.2.3.5");
}
TEST_F(IPsecConnectionTest, ParseDNSServersEmptyFile) {
SetAsIKEv2Connection();
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
// Simulates that charon does not create the file.
const auto tmp_dir = ipsec_connection_->SetTempDir();
EXPECT_CALL(device_info_, CreateXFRMInterface(_, _, _, _, _))
.WillRepeatedly(Return(true));
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecStatusRead);
const auto acutal_dns_servers = ipsec_connection_->dns_servers();
EXPECT_EQ(acutal_dns_servers.size(), 0);
}
TEST_F(IPsecConnectionTest, CreateXFRMInterfaceAndNotifyConnected) {
SetAsIKEv2Connection();
ipsec_connection_->SetTempDir();
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
constexpr int kLoIndex = 10;
constexpr int kIfIndex = 123;
std::string actual_if_name;
std::string actual_if_id;
DeviceInfo::LinkReadyCallback registered_link_ready_cb;
EXPECT_CALL(device_info_, GetIndex("lo")).WillOnce(Return(kLoIndex));
EXPECT_CALL(device_info_, CreateXFRMInterface(_, kLoIndex, _, _, _))
.WillOnce([&](const std::string& if_name, int, int if_id,
DeviceInfo::LinkReadyCallback link_ready_cb,
base::OnceClosure failure_cb) {
actual_if_name = if_name;
actual_if_id = if_id;
registered_link_ready_cb = std::move(link_ready_cb);
return true;
});
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecStatusRead);
std::move(registered_link_ready_cb).Run(actual_if_name, kIfIndex);
EXPECT_CALL(callbacks_, OnConnected(actual_if_name, kIfIndex, _));
dispatcher_.task_environment().RunUntilIdle();
}
TEST_F(IPsecConnectionTest, CreateXFRMInterfaceFailed) {
SetAsIKEv2Connection();
ipsec_connection_->SetTempDir();
ipsec_connection_->set_state(VPNConnection::State::kConnecting);
constexpr int kLoIndex = 10;
base::OnceClosure registered_failure_cb;
EXPECT_CALL(device_info_, GetIndex("lo")).WillOnce(Return(kLoIndex));
EXPECT_CALL(device_info_, CreateXFRMInterface(_, kLoIndex, _, _, _))
.WillOnce(
[&registered_failure_cb](const std::string& if_name, int, int if_id,
DeviceInfo::LinkReadyCallback link_ready_cb,
base::OnceClosure failure_cb) {
registered_failure_cb = std::move(failure_cb);
return true;
});
ipsec_connection_->InvokeScheduleConnectTask(ConnectStep::kIPsecStatusRead);
std::move(registered_failure_cb).Run();
EXPECT_CALL(callbacks_, OnFailure(Service::kFailureInternal));
dispatcher_.task_environment().RunUntilIdle();
}
} // namespace
} // namespace shill