blob: 4f680af35eec2ba7127d0e062769783bcbc276b6 [file] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/dhcp_server_controller.h"
#include <linux/capability.h>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include "patchpanel/system.h"
namespace patchpanel {
namespace {
constexpr char kDnsmasqPath[] = "/usr/sbin/dnsmasq";
constexpr char kLeaseTime[] = "12h"; // 12 hours
} // namespace
using Config = DHCPServerController::Config;
// static
std::optional<Config> Config::Create(
const shill::IPv4CIDR& host_cidr,
const shill::IPv4Address& start_ip,
const shill::IPv4Address& end_ip,
const std::vector<shill::IPv4Address>& dns_servers,
const std::vector<std::string>& domain_searches,
const std::optional<int>& mtu) {
// The start_ip and end_ip should be in the same subnet as host_cidr.
if (!(host_cidr.InSameSubnetWith(start_ip) &&
host_cidr.InSameSubnetWith(end_ip))) {
return std::nullopt;
}
// end_ip should not be smaller than or start_ip.
if (end_ip < start_ip) {
return std::nullopt;
}
// Transform std::vector<IPv4Address> to std::vector<std::string>.
std::vector<std::string> dns_server_strs;
for (const auto& ip : dns_servers) {
dns_server_strs.push_back(ip.ToString());
}
const std::string mtu_str = (mtu) ? std::to_string(*mtu) : "";
return Config(host_cidr.address().ToString(),
host_cidr.ToNetmask().ToString(), start_ip.ToString(),
end_ip.ToString(), base::JoinString(dns_server_strs, ","),
base::JoinString(domain_searches, ","), mtu_str);
}
Config::Config(const std::string& host_ip,
const std::string& netmask,
const std::string& start_ip,
const std::string& end_ip,
const std::string& dns_servers,
const std::string& domain_searches,
const std::string& mtu)
: host_ip_(host_ip),
netmask_(netmask),
start_ip_(start_ip),
end_ip_(end_ip),
dns_servers_(dns_servers),
domain_searches_(domain_searches),
mtu_(mtu) {}
std::ostream& operator<<(std::ostream& os, const Config& config) {
os << "{host_ip: " << config.host_ip() << ", netmask: " << config.netmask()
<< ", start_ip: " << config.start_ip() << ", end_ip: " << config.end_ip()
<< "}";
return os;
}
DHCPServerController::DHCPServerController(const std::string& ifname)
: ifname_(ifname), process_manager_(shill::ProcessManager::GetInstance()) {}
DHCPServerController::~DHCPServerController() {
Stop();
}
bool DHCPServerController::Start(const Config& config,
ExitCallback exit_callback) {
if (IsRunning()) {
LOG(ERROR) << "DHCP server is still running: " << ifname_
<< ", old config=" << *config_;
return false;
}
LOG(INFO) << "Starting DHCP server at: " << ifname_ << ", config: " << config;
std::vector<std::string> dnsmasq_args = {
"--dhcp-authoritative", // dnsmasq is the only DHCP server on a network.
"--keep-in-foreground", // Use foreground mode to prevent forking.
"--log-dhcp", // Log the DHCP event.
"--no-ping", // (b/257377981): Speed up the negotiation.
"--port=0", // Disable DNS.
"--leasefile-ro", // Do not use leasefile.
base::StringPrintf("--interface=%s", ifname_.c_str()),
base::StringPrintf("--dhcp-range=%s,%s,%s,%s", config.start_ip().c_str(),
config.end_ip().c_str(), config.netmask().c_str(),
kLeaseTime),
base::StringPrintf("--dhcp-option=option:netmask,%s",
config.netmask().c_str()),
base::StringPrintf("--dhcp-option=option:router,%s",
config.host_ip().c_str()),
};
if (!config.dns_servers().empty()) {
dnsmasq_args.push_back(base::StringPrintf(
"--dhcp-option=option:dns-server,%s", config.dns_servers().c_str()));
}
if (!config.domain_searches().empty()) {
dnsmasq_args.push_back(
base::StringPrintf("--dhcp-option=option:domain-search,%s",
config.domain_searches().c_str()));
}
if (!config.mtu().empty()) {
dnsmasq_args.push_back(base::StringPrintf("--dhcp-option=option:mtu,%s",
config.mtu().c_str()));
}
shill::ProcessManager::MinijailOptions minijail_options = {};
minijail_options.user = kPatchpaneldUser;
minijail_options.group = kPatchpaneldGroup;
minijail_options.capmask = CAP_TO_MASK(CAP_NET_ADMIN) |
CAP_TO_MASK(CAP_NET_BIND_SERVICE) |
CAP_TO_MASK(CAP_NET_RAW);
const pid_t pid = process_manager_->StartProcessInMinijail(
FROM_HERE, base::FilePath(kDnsmasqPath), dnsmasq_args, /*environment=*/{},
minijail_options,
base::BindOnce(&DHCPServerController::OnProcessExitedUnexpectedly,
weak_ptr_factory_.GetWeakPtr()));
if (pid < 0) {
LOG(ERROR) << "Failed to start the DHCP server: " << ifname_;
return false;
}
pid_ = pid;
config_ = config;
exit_callback_ = std::move(exit_callback);
return true;
}
void DHCPServerController::Stop() {
if (!IsRunning()) {
return;
}
LOG(INFO) << "Stopping DHCP server at: " << ifname_;
process_manager_->StopProcess(*pid_);
pid_ = std::nullopt;
config_ = std::nullopt;
exit_callback_.Reset();
}
bool DHCPServerController::IsRunning() const {
return pid_.has_value();
}
void DHCPServerController::OnProcessExitedUnexpectedly(int exit_status) {
LOG(ERROR) << "dnsmasq exited unexpectedly, status: " << exit_status;
pid_ = std::nullopt;
config_ = std::nullopt;
std::move(exit_callback_).Run(exit_status);
}
} // namespace patchpanel