blob: 7be21c1cfada99f914dcb0f8bcea8536fcbb88e3 [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/l2tp_connection.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
#include <base/callback.h>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/strings/strcat.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include "shill/ppp_daemon.h"
#include "shill/ppp_device.h"
#include "shill/vpn/vpn_util.h"
namespace shill {
namespace {
// TODO(b/165170125): Consider using /run/xl2tpd folder.
constexpr char kRunDir[] = "/run/l2tpipsec_vpn";
constexpr char kXl2tpdPath[] = "/usr/sbin/xl2tpd";
constexpr char kL2TPDConfigFileName[] = "l2tpd.conf";
constexpr char kL2TPDControlFileName[] = "l2tpd.control";
constexpr char kPPPDConfigFileName[] = "pppd.conf";
// Environment variable available to ppp plugin to know the resolved address
// of the L2TP server.
const char kLnsAddress[] = "LNS_ADDRESS";
} // namespace
L2TPConnection::L2TPConnection(std::unique_ptr<Config> config,
std::unique_ptr<Callbacks> callbacks,
ControlInterface* control_interface,
DeviceInfo* device_info,
EventDispatcher* dispatcher,
ProcessManager* process_manager)
: VPNConnection(std::move(callbacks), dispatcher),
config_(std::move(config)),
control_interface_(control_interface),
device_info_(device_info),
process_manager_(process_manager),
vpn_util_(VPNUtil::New()) {}
L2TPConnection::~L2TPConnection() {
if (state() == State::kIdle || state() == State::kStopped) {
return;
}
// This is unexpected but cannot be fully avoided. Call OnDisconnect() to make
// sure resources are released.
LOG(WARNING) << "Destructor called but the current state is " << state();
OnDisconnect();
}
void L2TPConnection::OnConnect() {
temp_dir_ = vpn_util_->CreateScopedTempDir(base::FilePath(kRunDir));
if (!WritePPPDConfig()) {
NotifyFailure(Service::kFailureInternal,
"Failed to write pppd config file");
return;
}
if (!WriteL2TPDConfig()) {
NotifyFailure(Service::kFailureInternal,
"Failed to write xl2tpd config file");
return;
}
StartXl2tpd();
}
void L2TPConnection::GetLogin(std::string* user, std::string* password) {
LOG(INFO) << "Login requested.";
if (config_->user.empty()) {
LOG(ERROR) << "User not set.";
return;
}
// TODO(b/165170125): Add support for using login password.
if (config_->password.empty()) {
LOG(ERROR) << "Password not set.";
return;
}
*user = config_->user;
*password = config_->password;
}
void L2TPConnection::Notify(const std::string& reason,
const std::map<std::string, std::string>& dict) {
// TODO(b/165170125): On failure, check the reason (e.g., if it is an
// authentication failure).
if (reason == kPPPReasonAuthenticating || reason == kPPPReasonAuthenticated) {
// These are uninteresting intermediate states that do not indicate failure.
return;
}
if (reason != kPPPReasonConnect) {
if (!IsConnectingOrConnected()) {
// We have notified the upper layer, or the disconnect is triggered by the
// upper layer. In both cases, we don't need call NotifyFailure().
LOG(INFO) << "pppd notifies us of " << reason << ", the current state is "
<< state();
return;
}
NotifyFailure(Service::kFailureInternal, "pppd disconnected");
return;
}
// The message is kPPPReasonConnect. Checks if we are in the connecting state
// at first.
if (state() != State::kConnecting) {
LOG(WARNING) << "pppd notifies us of " << reason
<< ", the current state is " << state();
return;
}
std::string interface_name = PPPDevice::GetInterfaceName(dict);
IPConfig::Properties ip_properties = PPPDevice::ParseIPConfiguration(dict);
// There is no IPv6 support for L2TP/IPsec VPN at this moment, so create a
// blackhole route for IPv6 traffic after establishing a IPv4 VPN.
ip_properties.blackhole_ipv6 = true;
// Reduce MTU to the minimum viable for IPv6, since the IPsec layer consumes
// some variable portion of the payload. Although this system does not yet
// support IPv6, it is a reasonable value to start with, since the minimum
// IPv6 packet size will plausibly be a size any gateway would support, and
// is also larger than the IPv4 minimum size.
ip_properties.mtu = IPConfig::kMinIPv6MTU;
ip_properties.method = kTypeVPN;
// Notify() could be invoked either before or after the creation of the ppp
// interface. We need to make sure that the interface is ready (by checking
// DeviceInfo) before invoking the connected callback here.
int interface_index = device_info_->GetIndex(interface_name);
if (interface_index != -1) {
NotifyConnected(interface_name, interface_index, ip_properties);
} else {
device_info_->AddVirtualInterfaceReadyCallback(
interface_name,
base::BindOnce(&L2TPConnection::OnLinkReady, weak_factory_.GetWeakPtr(),
ip_properties));
}
}
void L2TPConnection::OnDisconnect() {
// TODO(b/165170125): Terminate the connection before stopping xl2tpd.
external_task_ = nullptr;
if (state() == State::kDisconnecting) {
NotifyStopped();
}
}
bool L2TPConnection::WritePPPDConfig() {
pppd_config_path_ = temp_dir_.GetPath().Append(kPPPDConfigFileName);
// TODO(b/200636771): Use proper mtu and mru.
std::vector<std::string> lines = {
"ipcp-accept-local",
"ipcp-accept-remote",
"refuse-eap",
"noccp",
"noauth",
"crtscts",
"mtu 1410",
"mru 1410",
"lock",
"connect-delay 5000",
"nodefaultroute",
"nosystemconfig",
"usepeerdns",
};
if (config_->lcp_echo) {
lines.push_back("lcp-echo-failure 4");
lines.push_back("lcp-echo-interval 30");
}
lines.push_back(base::StrCat({"plugin ", PPPDaemon::kShimPluginPath}));
std::string contents = base::JoinString(lines, "\n");
return vpn_util_->WriteConfigFile(pppd_config_path_, contents);
}
bool L2TPConnection::WriteL2TPDConfig() {
l2tpd_config_path_ = temp_dir_.GetPath().Append(kL2TPDConfigFileName);
// TODO(b/165170125): Fill in contents.
return vpn_util_->WriteConfigFile(l2tpd_config_path_, "");
}
void L2TPConnection::StartXl2tpd() {
const base::FilePath l2tpd_control_path =
temp_dir_.GetPath().Append(kL2TPDControlFileName);
std::vector<std::string> args = {
"-c", l2tpd_config_path_.value(), "-C", l2tpd_control_path.value(),
"-D" // prevents xl2tpd from detaching from the terminal and daemonizing
};
// TODO(b/165170125): Add remote IP here.
std::map<std::string, std::string> env = {
{kLnsAddress, ""},
};
auto external_task_local = std::make_unique<ExternalTask>(
control_interface_, process_manager_, weak_factory_.GetWeakPtr(),
base::BindRepeating(&L2TPConnection::OnXl2tpdExitedUnexpectedly,
weak_factory_.GetWeakPtr()));
Error error;
constexpr uint64_t kCapMask = CAP_TO_MASK(CAP_NET_ADMIN);
if (!external_task_local->StartInMinijail(
base::FilePath(kXl2tpdPath), &args, env, VPNUtil::kVPNUser,
VPNUtil::kVPNGroup, kCapMask, /*inherit_supplementary_groups=*/true,
/*close_nonstd_fds=*/true, &error)) {
NotifyFailure(Service::kFailureInternal,
base::StrCat({"Failed to start xl2tpd: ", error.message()}));
return;
}
external_task_ = std::move(external_task_local);
}
void L2TPConnection::OnLinkReady(const IPConfig::Properties& ip_properties,
const std::string& if_name,
int if_index) {
if (state() != State::kConnecting) {
// Needs to do nothing here. The ppp interface is managed by the pppd
// process so we don't need to remove it here.
LOG(WARNING) << "OnLinkReady() called but the current state is " << state();
return;
}
NotifyConnected(if_name, if_index, ip_properties);
}
void L2TPConnection::OnXl2tpdExitedUnexpectedly(pid_t pid, int exit_code) {
const std::string message =
base::StringPrintf("xl2tpd exited unexpectedly with code=%d", exit_code);
if (!IsConnectingOrConnected()) {
LOG(WARNING) << message;
return;
}
NotifyFailure(Service::kFailureInternal, message);
}
} // namespace shill