blob: 7e99c167c0d84ef64f5e073537ca5851e559e8b1 [file] [log] [blame]
// Copyright 2022 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/guest_ipv6_service.h"
#include <net/ethernet.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/signal.h>
#include <algorithm>
#include <iostream>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#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_util.h>
#include <base/time/time.h>
#include <brillo/files/file_util.h>
#include <brillo/process/process.h>
#include <net-base/ipv6_address.h>
#include "patchpanel/shill_client.h"
namespace patchpanel {
namespace {
constexpr char kRadvdRunDir[] = "/run/radvd";
constexpr char kRadvdPath[] = "/usr/sbin/radvd";
constexpr char kRadvdConfigFilePrefix[] = "radvd.conf.";
constexpr char kRadvdPidFilePrefix[] = "radvd.pid.";
constexpr base::TimeDelta kTimeoutForSIGTERM = base::Seconds(2);
constexpr base::TimeDelta kTimeoutForSIGKILL = base::Seconds(1);
GuestIPv6Service::ForwardMethod GetForwardMethodByDeviceType(
ShillClient::Device::Type type) {
switch (type) {
case ShillClient::Device::Type::kEthernet:
case ShillClient::Device::Type::kEthernetEap:
case ShillClient::Device::Type::kWifi:
// b/246444885: Make guests consider physical network off-link to reduce
// amount of NS/NA sent to the physical network.
return GuestIPv6Service::ForwardMethod::kMethodNDProxyInjectingRA;
case ShillClient::Device::Type::kCellular:
return GuestIPv6Service::ForwardMethod::kMethodRAServer;
default:
return GuestIPv6Service::ForwardMethod::kMethodUnknown;
}
}
// Helper struct local to this file for logging an uplink / downlink interface
// pair.
struct LinkPair {
std::string_view uplink;
std::string_view downlink;
};
std::ostream& operator<<(std::ostream& stream, const LinkPair& pair) {
return stream << "uplink: " << pair.uplink << ", downlink: " << pair.downlink;
}
} // namespace
// TODO(b/228585272): Support prefix larger than /64
// static
net_base::IPv6CIDR GuestIPv6Service::IPAddressTo64BitPrefix(
const net_base::IPv6Address& addr) {
const int prefix_length = 64;
return net_base::IPv6CIDR::CreateFromAddressAndPrefix(addr, prefix_length)
->GetPrefixCIDR();
}
GuestIPv6Service::GuestIPv6Service(SubprocessController* nd_proxy,
Datapath* datapath,
System* system)
: nd_proxy_(nd_proxy), datapath_(datapath), system_(system) {}
void GuestIPv6Service::Start() {
nd_proxy_->RegisterFeedbackMessageHandler(base::BindRepeating(
&GuestIPv6Service::OnNDProxyMessage, weak_factory_.GetWeakPtr()));
nd_proxy_->Listen();
}
void GuestIPv6Service::StartForwarding(
const ShillClient::Device& upstream_shill_device,
const std::string& ifname_downlink,
const std::optional<int>& mtu,
const std::optional<int>& hop_limit,
bool downlink_is_tethering) {
const std::string& ifname_uplink = upstream_shill_device.ifname;
const LinkPair pair = {.uplink = ifname_uplink, .downlink = ifname_downlink};
LOG(INFO) << __func__ << ": " << pair
<< ", mtu: " << (mtu ? std::to_string(*mtu) : "default")
<< ", hop_limit: "
<< (hop_limit ? std::to_string(*hop_limit) : "default");
int if_id_uplink = system_->IfNametoindex(ifname_uplink);
if (if_id_uplink == 0) {
PLOG(ERROR) << "Get interface index failed on " << ifname_uplink;
return;
}
if_cache_[ifname_uplink] = if_id_uplink;
int if_id_downlink = system_->IfNametoindex(ifname_downlink);
if (if_id_downlink == 0) {
PLOG(ERROR) << __func__ << ": " << pair
<< ", Get interface index failed on " << ifname_downlink;
return;
}
if_cache_[ifname_downlink] = if_id_downlink;
// Lookup ForwardEntry for the specified uplink. If it does not exist, create
// a new one based on its device type.
ForwardMethod forward_method;
if (forward_record_.find(ifname_uplink) != forward_record_.end()) {
forward_method = forward_record_[ifname_uplink].method;
forward_record_[ifname_uplink].downstream_ifnames.insert(ifname_downlink);
} else if (forward_method_override_.find(ifname_uplink) !=
forward_method_override_.end()) {
forward_method = forward_method_override_[ifname_uplink];
forward_record_[ifname_uplink] = {
forward_method, {ifname_downlink}, std::nullopt, std::nullopt};
} else {
forward_method = GetForwardMethodByDeviceType(upstream_shill_device.type);
if (forward_method == ForwardMethod::kMethodUnknown) {
LOG(INFO) << __func__ << ": " << pair
<< ", IPv6 forwarding not supported on device type";
return;
}
forward_record_[ifname_uplink] = {
forward_method, {ifname_downlink}, std::nullopt, std::nullopt};
}
// Set the MTU and CurHopLimit value to |forward_record_|.
if (mtu && forward_record_[ifname_uplink].mtu != *mtu) {
forward_record_[ifname_uplink].mtu = *mtu;
}
if (hop_limit && forward_record_[ifname_uplink].hop_limit != *hop_limit) {
forward_record_[ifname_uplink].hop_limit = *hop_limit;
}
if (!datapath_->MaskInterfaceFlags(ifname_uplink, IFF_ALLMULTI)) {
LOG(WARNING) << __func__ << ": " << pair
<< ", Failed to setup all multicast mode";
}
if (!datapath_->MaskInterfaceFlags(ifname_downlink, IFF_ALLMULTI)) {
LOG(WARNING) << __func__ << ": " << pair
<< ", Failed to setup all multicast mode";
}
switch (forward_method) {
case ForwardMethod::kMethodNDProxy:
SendNDProxyControl(NDProxyControlMessage::START_NS_NA_RS_RA, if_id_uplink,
if_id_downlink);
break;
case ForwardMethod::kMethodNDProxyInjectingRA:
SendNDProxyControl(
NDProxyControlMessage::START_NS_NA_RS_RA_MODIFYING_ROUTER_ADDRESS,
if_id_uplink, if_id_downlink);
break;
case ForwardMethod::kMethodRAServer:
// No need of proxying between downlink and uplink for RA server.
SendNDProxyControl(NDProxyControlMessage::START_NEIGHBOR_MONITOR,
if_id_downlink, 0);
break;
case ForwardMethod::kMethodUnknown:
NOTREACHED();
}
// Start NA proxying between the new downlink and existing downlinks, if any.
CHECK(forward_record_.find(ifname_uplink) != forward_record_.end());
for (const auto& another_downlink :
forward_record_[ifname_uplink].downstream_ifnames) {
if (another_downlink != ifname_downlink) {
int32_t if_id_downlink2 = if_cache_[another_downlink];
SendNDProxyControl(NDProxyControlMessage::START_NS_NA, if_id_downlink,
if_id_downlink2);
}
}
const auto uplink_ip = GetUplinkIp(ifname_uplink);
if (!uplink_ip) {
return;
}
// Allow IPv6 address on uplink to be resolvable on the downlink
if (!datapath_->AddIPv6NeighborProxy(ifname_downlink, *uplink_ip)) {
LOG(WARNING) << __func__ << ": " << pair
<< ", Failed to setup the IPv6 neighbor {" << *uplink_ip
<< "} proxy on dev " << ifname_downlink;
}
if (forward_method == ForwardMethod::kMethodRAServer) {
if (!StartRAServer(ifname_downlink, IPAddressTo64BitPrefix(*uplink_ip),
uplink_dns_[ifname_uplink],
forward_record_[ifname_uplink].mtu,
forward_record_[ifname_uplink].hop_limit)) {
LOG(WARNING) << __func__ << ": " << pair
<< ", Failed to start RA server on downlink with uplink IP {"
<< *uplink_ip << "}";
}
}
}
void GuestIPv6Service::StopForwarding(
const ShillClient::Device& upstream_shill_device,
const std::string& ifname_downlink) {
const std::string& ifname_uplink = upstream_shill_device.ifname;
const LinkPair pair = {.uplink = ifname_uplink, .downlink = ifname_downlink};
LOG(INFO) << __func__ << ": " << pair;
const auto it = forward_record_.find(ifname_uplink);
if (it == forward_record_.end()) {
return;
}
auto& forward_record = it->second;
if (forward_record.downstream_ifnames.find(ifname_downlink) ==
forward_record.downstream_ifnames.end()) {
return;
}
if (forward_record.method != ForwardMethod::kMethodRAServer) {
SendNDProxyControl(NDProxyControlMessage::STOP_PROXY,
if_cache_[ifname_uplink], if_cache_[ifname_downlink]);
}
// Remove proxying between specified downlink and all other downlinks in the
// same group.
for (const auto& another_downlink : forward_record.downstream_ifnames) {
if (another_downlink != ifname_downlink) {
SendNDProxyControl(NDProxyControlMessage::STOP_PROXY,
if_cache_[ifname_downlink],
if_cache_[another_downlink]);
}
}
// Remove ip neigh proxy entry
const auto uplink_ip = GetUplinkIp(ifname_uplink);
if (uplink_ip) {
datapath_->RemoveIPv6NeighborProxy(ifname_downlink, *uplink_ip);
}
// Remove downlink /128 routes
for (const auto& neighbor_ip : downstream_neighbors_[ifname_downlink]) {
datapath_->RemoveIPv6HostRoute(
*net_base::IPv6CIDR::CreateFromAddressAndPrefix(neighbor_ip, 128));
}
downstream_neighbors_[ifname_downlink].clear();
if (forward_record.method == ForwardMethod::kMethodRAServer) {
SendNDProxyControl(NDProxyControlMessage::STOP_NEIGHBOR_MONITOR,
if_cache_[ifname_downlink], 0);
if (uplink_ip) {
StopRAServer(ifname_downlink);
}
}
forward_record.downstream_ifnames.erase(ifname_downlink);
if (forward_record.downstream_ifnames.empty()) {
forward_record_.erase(it);
}
}
void GuestIPv6Service::StopUplink(
const ShillClient::Device& upstream_shill_device) {
const std::string& ifname_uplink = upstream_shill_device.ifname;
LOG(INFO) << __func__ << ": uplink: " << ifname_uplink;
if (forward_record_.find(ifname_uplink) == forward_record_.end()) {
return;
}
// Remove proxying between specified uplink and all downlinks.
if (forward_record_[ifname_uplink].method != ForwardMethod::kMethodRAServer) {
for (const auto& ifname_downlink :
forward_record_[ifname_uplink].downstream_ifnames) {
SendNDProxyControl(NDProxyControlMessage::STOP_PROXY,
if_cache_[ifname_uplink], if_cache_[ifname_downlink]);
}
}
// Remove proxying between all downlink pairs in the forward group.
const auto& downlinks = forward_record_[ifname_uplink].downstream_ifnames;
for (auto it1 = downlinks.begin(); it1 != downlinks.end(); it1++) {
for (auto it2 = std::next(it1); it2 != downlinks.end(); it2++) {
SendNDProxyControl(NDProxyControlMessage::STOP_PROXY,
if_cache_[it1->c_str()], if_cache_[it2->c_str()]);
}
}
const auto uplink_ip = GetUplinkIp(ifname_uplink);
for (const auto& ifname_downlink :
forward_record_[ifname_uplink].downstream_ifnames) {
// Remove ip neigh proxy entry
if (uplink_ip) {
datapath_->RemoveIPv6NeighborProxy(ifname_downlink, *uplink_ip);
}
// Remove downlink /128 routes
for (const auto& neighbor_ip : downstream_neighbors_[ifname_downlink]) {
datapath_->RemoveIPv6HostRoute(
*net_base::IPv6CIDR::CreateFromAddressAndPrefix(neighbor_ip, 128));
}
downstream_neighbors_[ifname_downlink].clear();
}
if (forward_record_[ifname_uplink].method == ForwardMethod::kMethodRAServer) {
for (const auto& ifname_downlink :
forward_record_[ifname_uplink].downstream_ifnames) {
SendNDProxyControl(NDProxyControlMessage::STOP_NEIGHBOR_MONITOR,
if_cache_[ifname_downlink], 0);
if (uplink_ip) {
StopRAServer(ifname_downlink);
}
}
}
forward_record_.erase(ifname_uplink);
}
void GuestIPv6Service::OnUplinkIPv6Changed(
const ShillClient::Device& upstream_shill_device) {
const std::string& ifname = upstream_shill_device.ifname;
const auto old_uplink_ip = GetUplinkIp(ifname);
if (!upstream_shill_device.ipconfig.ipv6_cidr) {
LOG(INFO) << __func__ << ": uplink: " << ifname << ", {"
<< ((old_uplink_ip) ? old_uplink_ip->ToString() : "")
<< "} to {}";
uplink_ips_.erase(ifname);
if (!old_uplink_ip ||
forward_record_.find(ifname) == forward_record_.end()) {
return;
}
for (const auto& ifname_downlink :
forward_record_[ifname].downstream_ifnames) {
// Remove ip neigh proxy entry
datapath_->RemoveIPv6NeighborProxy(ifname_downlink, *old_uplink_ip);
// Remove downlink /128 routes
for (const auto& neighbor_ip : downstream_neighbors_[ifname_downlink]) {
datapath_->RemoveIPv6HostRoute(
*net_base::IPv6CIDR::CreateFromAddressAndPrefix(neighbor_ip, 128));
}
downstream_neighbors_[ifname_downlink].clear();
// Stop RA servers
if (forward_record_[ifname].method == ForwardMethod::kMethodRAServer) {
StopRAServer(ifname_downlink);
}
}
return;
}
const auto new_uplink_ip =
upstream_shill_device.ipconfig.ipv6_cidr->address();
LOG(INFO) << __func__ << ": uplink: " << ifname << ", {"
<< ((old_uplink_ip) ? old_uplink_ip->ToString() : "") << "} to {"
<< new_uplink_ip << "}";
if (old_uplink_ip == new_uplink_ip) {
return;
}
if (forward_record_.find(ifname) != forward_record_.end()) {
// Note that the order of StartForwarding() and OnUplinkIPv6Changed() is not
// certain so the `ip neigh proxy` and /128 route changes need to be handled
// in both code paths. When an uplink is newly connected to,
// StartForwarding() get called first and then we received
// OnUplinkIPv6Changed() when uplink get an IPv6 address. When default
// network switches to an existing uplink, StartForwarding() is after
// OnUplinkIPv6Changed() (which was already called when it was not default
// yet).
for (const auto& ifname_downlink :
forward_record_[ifname].downstream_ifnames) {
const LinkPair pair = {.uplink = ifname, .downlink = ifname_downlink};
// Update ip neigh proxy entries
if (old_uplink_ip) {
datapath_->RemoveIPv6NeighborProxy(ifname_downlink, *old_uplink_ip);
}
if (!datapath_->AddIPv6NeighborProxy(ifname_downlink, new_uplink_ip)) {
LOG(WARNING) << __func__ << ": " << pair
<< ", Failed to setup the IPv6 neighbor {" << new_uplink_ip
<< "} proxy on dev " << ifname_downlink;
}
// Update downlink /128 routes source IP. Note AddIPv6HostRoute uses `ip
// route replace` so we don't need to remove the old one first.
for (const auto& neighbor_ip : downstream_neighbors_[ifname_downlink]) {
LOG(INFO) << __func__ << ": " << pair << ", update /128 route to "
<< ifname_downlink << " for existing neighbor IP {"
<< neighbor_ip << "} because of new uplink IP {"
<< new_uplink_ip << "} on " << ifname;
if (!datapath_->AddIPv6HostRoute(
ifname_downlink,
*net_base::IPv6CIDR::CreateFromAddressAndPrefix(neighbor_ip,
128),
new_uplink_ip)) {
LOG(WARNING) << __func__ << ": " << pair
<< ": Failed to setup the IPv6 route {" << neighbor_ip
<< "} dev " << ifname << " src {" << new_uplink_ip
<< "}";
}
}
if (forward_record_[ifname].method == ForwardMethod::kMethodRAServer) {
const auto new_prefix = IPAddressTo64BitPrefix(new_uplink_ip);
if (old_uplink_ip) {
if (IPAddressTo64BitPrefix(*old_uplink_ip) == new_prefix) {
continue;
}
StopRAServer(ifname_downlink);
}
if (!StartRAServer(ifname_downlink, new_prefix, uplink_dns_[ifname],
forward_record_[ifname].mtu,
forward_record_[ifname].hop_limit)) {
LOG(WARNING)
<< __func__ << ": " << pair
<< ", Failed to start RA server on downlink with uplink IP {"
<< new_uplink_ip << "}";
}
}
}
}
uplink_ips_[ifname] = new_uplink_ip;
}
void GuestIPv6Service::UpdateUplinkIPv6DNS(
const ShillClient::Device& upstream_shill_device) {
const std::string& ifname = upstream_shill_device.ifname;
const auto& old_dns = uplink_dns_[ifname];
VLOG(1) << __func__ << ": " << ifname << ", {"
<< base::JoinString(old_dns, ",") << "} to {"
<< base::JoinString(upstream_shill_device.ipconfig.ipv6_dns_addresses,
",")
<< "}";
// Check if the new dns list is identical with the old one.
auto sorted_dns = upstream_shill_device.ipconfig.ipv6_dns_addresses;
std::sort(sorted_dns.begin(), sorted_dns.end());
bool identical = true;
if (old_dns.size() == sorted_dns.size()) {
for (size_t i = 0; i < old_dns.size(); ++i) {
if (old_dns[i] != sorted_dns[i]) {
identical = false;
break;
}
}
} else {
identical = false;
}
if (identical) {
return;
}
if (auto it = forward_record_.find(ifname);
it != forward_record_.end() &&
it->second.method == ForwardMethod::kMethodRAServer) {
for (const auto& ifname_downlink : it->second.downstream_ifnames) {
const auto uplink_ip = GetUplinkIp(ifname);
if (uplink_ip) {
const auto prefix = IPAddressTo64BitPrefix(*uplink_ip);
StopRAServer(ifname_downlink);
if (!StartRAServer(ifname_downlink, prefix, sorted_dns, it->second.mtu,
it->second.hop_limit)) {
const LinkPair pair = {.uplink = ifname, .downlink = ifname_downlink};
LOG(WARNING)
<< __func__ << ": " << pair
<< ", Failed to start RA server on downlink with uplink IP {"
<< *uplink_ip << "}";
}
}
}
}
uplink_dns_[ifname] = sorted_dns;
}
void GuestIPv6Service::StartLocalHotspot(
const std::string& ifname_hotspot_link,
const std::string& prefix,
const std::vector<std::string>& rdnss,
const std::vector<std::string>& dnssl) {
NOTIMPLEMENTED();
}
void GuestIPv6Service::StopLocalHotspot(
const std::string& ifname_hotspot_link) {
NOTIMPLEMENTED();
}
void GuestIPv6Service::SetForwardMethod(
const ShillClient::Device& upstream_shill_device, ForwardMethod method) {
forward_method_override_[upstream_shill_device.ifname] = method;
const auto it = forward_record_.find(upstream_shill_device.ifname);
if (it != forward_record_.end()) {
// Need a copy here since StopUplink() will modify the record
auto downlinks = it->second.downstream_ifnames;
auto mtu = it->second.mtu;
auto hop_limit = it->second.hop_limit;
StopUplink(upstream_shill_device);
for (const auto& downlink : downlinks) {
StartForwarding(upstream_shill_device, downlink, mtu, hop_limit);
}
}
}
void GuestIPv6Service::SendNDProxyControl(
NDProxyControlMessage::NDProxyRequestType type,
int32_t if_id_primary,
int32_t if_id_secondary) {
VLOG(4) << "Sending NDProxyControlMessage: " << type << ": " << if_id_primary
<< "<->" << if_id_secondary;
NDProxyControlMessage msg;
msg.set_type(type);
msg.set_if_id_primary(if_id_primary);
msg.set_if_id_secondary(if_id_secondary);
ControlMessage cm;
*cm.mutable_ndproxy_control() = msg;
nd_proxy_->SendControlMessage(cm);
}
void GuestIPv6Service::OnNDProxyMessage(const FeedbackMessage& fm) {
if (!fm.has_ndproxy_signal()) {
LOG(ERROR) << "Unexpected feedback message type";
return;
}
const NDProxySignalMessage& msg = fm.ndproxy_signal();
if (msg.has_neighbor_detected_signal()) {
const auto& inner_msg = msg.neighbor_detected_signal();
const auto ip = net_base::IPv6Address::CreateFromBytes(inner_msg.ip());
if (!ip) {
LOG(ERROR) << "Failed to create IPv6Address from NeighborDetectedSignal,"
<< " size=" << inner_msg.ip().size() << " instead of "
<< net_base::IPv6Address::kAddressLength;
return;
}
std::string ifname = system_->IfIndextoname(inner_msg.if_id());
RegisterDownstreamNeighborIP(ifname, *ip);
return;
}
if (msg.has_router_detected_signal()) {
// This event is currently not used.
return;
}
LOG(ERROR) << "Unknown NDProxy event ";
NOTREACHED();
}
void GuestIPv6Service::RegisterDownstreamNeighborIP(
const std::string& ifname_downlink, const net_base::IPv6Address& ip) {
if (downstream_neighbors_[ifname_downlink].count(ip) != 0) {
return;
}
downstream_neighbors_[ifname_downlink].insert(ip);
const auto& uplink = DownlinkToUplink(ifname_downlink);
if (!uplink) {
LOG(INFO) << __func__ << ": downlink: " << ifname_downlink
<< ", neighbor IP {" << ip
<< "}, no corresponding uplink IP yet, postponing /128 route";
return;
}
const LinkPair pair = {.uplink = uplink.value(), .downlink = ifname_downlink};
const auto uplink_ip = GetUplinkIp(uplink.value());
const std::string uplink_ip_str = uplink_ip ? uplink_ip->ToString() : "";
LOG(INFO) << __func__ << ": " << pair << ", neighbor IP {" << ip
<< "}, uplink IP {" << uplink_ip_str << "}";
if (!datapath_->AddIPv6HostRoute(
ifname_downlink,
*net_base::IPv6CIDR::CreateFromAddressAndPrefix(ip, 128),
uplink_ip)) {
LOG(WARNING) << __func__ << ": " << pair
<< ", Failed to setup the IPv6 route: " << ip << " dev "
<< ifname_downlink << " src {" << uplink_ip_str << "}";
}
}
std::optional<std::string> GuestIPv6Service::DownlinkToUplink(
const std::string& downlink) {
for (const auto& [upstream_ifname, forward_record] : forward_record_) {
if (forward_record.downstream_ifnames.find(downlink) !=
forward_record.downstream_ifnames.end()) {
return upstream_ifname;
}
}
return std::nullopt;
}
const std::set<std::string>& GuestIPv6Service::UplinkToDownlinks(
const std::string& uplink) {
static std::set<std::string> empty_set;
const auto it = forward_record_.find(uplink);
if (it != forward_record_.end()) {
return it->second.downstream_ifnames;
}
return empty_set;
}
bool GuestIPv6Service::StartRAServer(const std::string& ifname,
const net_base::IPv6CIDR& prefix,
const std::vector<std::string>& rdnss,
const std::optional<int>& mtu,
const std::optional<int>& hop_limit) {
base::FilePath run_path(kRadvdRunDir);
if (!base::DirectoryExists(run_path)) {
LOG(ERROR) << "Configuration directory " << kRadvdRunDir
<< " is not available.";
return false;
}
return CreateConfigFile(ifname, prefix, rdnss, mtu, hop_limit) &&
StartRadvd(ifname);
}
bool GuestIPv6Service::StopRAServer(const std::string& ifname) {
const base::FilePath& pid_file_path =
base::FilePath(kRadvdRunDir)
.Append(std::string(kRadvdPidFilePrefix) + ifname);
std::string pid_str;
pid_t pid;
if (!base::ReadFileToString(pid_file_path, &pid_str) ||
!base::TrimString(pid_str, "\n", &pid_str) ||
!base::StringToInt(pid_str, &pid)) {
LOG(WARNING) << "Invalid radvd pid file " << pid_file_path;
return false;
}
if (!brillo::Process::ProcessExists(pid)) {
LOG(WARNING) << "radvd[" << pid << "] already stopped for interface "
<< ifname;
return true;
}
brillo::ProcessImpl process;
process.Reset(pid);
if (process.Kill(SIGTERM, kTimeoutForSIGTERM.InSeconds())) {
brillo::DeleteFile(pid_file_path);
return true;
}
LOG(WARNING) << "Not able to gracefully stop radvd[" << pid
<< "] for interface " << ifname << ", trying to force stop";
if (process.Kill(SIGKILL, kTimeoutForSIGKILL.InSeconds())) {
brillo::DeleteFile(pid_file_path);
return true;
}
LOG(ERROR) << "Cannot stop radvd[" << pid << "] for interface " << ifname;
return false;
}
namespace {
constexpr char kRadvdConfTemplate[] = R"(interface $1 {
AdvSendAdvert on;
prefix $2 {
AdvOnLink off;
AdvAutonomous on;
};
$3
$4
$5
};
)";
} // namespace
bool GuestIPv6Service::CreateConfigFile(const std::string& ifname,
const net_base::IPv6CIDR& prefix,
const std::vector<std::string>& rdnss,
const std::optional<int>& mtu,
const std::optional<int>& hop_limit) {
const std::string contents = base::ReplaceStringPlaceholders(
kRadvdConfTemplate,
{
/*$1=*/ifname,
/*$2=*/prefix.ToString(),
/*$3=*/(mtu ? base::StringPrintf("AdvLinkMTU %d;", *mtu) : ""),
/*$4=*/
(hop_limit ? base::StringPrintf("AdvCurHopLimit %d;", *hop_limit)
: ""),
/*$5=*/
(!rdnss.empty()
? base::StrCat({"RDNSS ", base::JoinString(rdnss, " "), " {};"})
: ""),
},
nullptr);
const base::FilePath& conf_file_path =
base::FilePath(kRadvdRunDir)
.Append(std::string(kRadvdConfigFilePrefix) + ifname);
return system_->WriteConfigFile(conf_file_path, contents);
}
bool GuestIPv6Service::StartRadvd(const std::string& ifname) {
const base::FilePath& conf_file_path =
base::FilePath(kRadvdRunDir)
.Append(std::string(kRadvdConfigFilePrefix) + ifname);
const base::FilePath& pid_file_path =
base::FilePath(kRadvdRunDir)
.Append(std::string(kRadvdPidFilePrefix) + ifname);
std::vector<std::string> argv = {
kRadvdPath, "-n",
"-C", conf_file_path.value(),
"-p", pid_file_path.value(),
"-m", "syslog",
};
auto mj = brillo::Minijail::GetInstance();
minijail* jail = mj->New();
mj->DropRoot(jail, kPatchpaneldUid, kPatchpaneldGid);
constexpr uint64_t kNetRawCapMask = CAP_TO_MASK(CAP_NET_RAW);
mj->UseCapabilities(jail, kNetRawCapMask);
std::vector<char*> args;
for (const auto& arg : argv) {
args.push_back(const_cast<char*>(arg.c_str()));
}
args.push_back(nullptr);
pid_t pid;
bool ran = mj->RunAndDestroy(jail, args, &pid);
return ran;
}
const std::optional<net_base::IPv6Address> GuestIPv6Service::GetUplinkIp(
const std::string& ifname) const {
const auto it = uplink_ips_.find(ifname);
if (it == uplink_ips_.end()) {
return std::nullopt;
}
return it->second;
}
} // namespace patchpanel