blob: fcc6b16c99fa971da2df9ecfbc25c19fdf47a144 [file] [edit]
// Copyright 2020 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/routing_service.h"
#include <iostream>
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
#include <base/containers/fixed_flat_map.h>
#include <base/functional/callback.h>
#include <base/functional/callback_helpers.h>
#include <base/logging.h>
#include <base/strings/strcat.h>
namespace patchpanel {
// Make sure that the compiler is not doing padding.
static_assert(sizeof(Fwmark) == sizeof(uint32_t));
RoutingService::RoutingService(System* system,
LifelineFDService* lifeline_fd_service)
: system_(system), lifeline_fd_svc_(lifeline_fd_service) {}
RoutingService::~RoutingService() = default;
int RoutingService::GetSockopt(
int sockfd, int level, int optname, void* optval, socklen_t* optlen) {
return getsockopt(sockfd, level, optname, optval, optlen);
}
int RoutingService::SetSockopt(
int sockfd, int level, int optname, const void* optval, socklen_t optlen) {
return setsockopt(sockfd, level, optname, optval, optlen);
}
bool RoutingService::SetFwmark(int sockfd, Fwmark mark, Fwmark mask) {
uint32_t fwmark_value = 0;
socklen_t fwmark_len = sizeof(fwmark_value);
if (GetSockopt(sockfd, SOL_SOCKET, SO_MARK, &fwmark_value, &fwmark_len) < 0) {
PLOG(ERROR) << "SetFwmark mark=" << mark.ToString()
<< " mask=" << mask.ToString()
<< " getsockopt SOL_SOCKET SO_MARK failed";
return false;
}
fwmark_value = (mark & mask).Value() | (fwmark_value & ~mask.Value());
fwmark_len = sizeof(fwmark_value);
if (SetSockopt(sockfd, SOL_SOCKET, SO_MARK, &fwmark_value, fwmark_len) < 0) {
PLOG(ERROR) << "SetFwmark mark=" << mark.ToString()
<< " mask=" << mask.ToString()
<< " setsockopt SOL_SOCKET SO_MARK failed";
return false;
}
return true;
}
bool RoutingService::TagSocket(
int sockfd,
std::optional<int> network_id,
VPNRoutingPolicy vpn_policy,
std::optional<TrafficAnnotationId> annotation_id) {
if (vpn_policy == VPNRoutingPolicy::kRouteOnVPN && network_id.has_value()) {
LOG(ERROR) << __func__
<< ": route_on_vpn policy and network_id should not be set at "
"the same time";
return false;
}
if (annotation_id.has_value()) {
// TODO(b/331744250): add fwmark to mark the socket as audited.
return true;
}
// TODO(b/322083502): Do some basic verification that this socket is not
// connected.
Fwmark mark = {.fwmark = 0};
if (network_id.has_value()) {
std::optional<Fwmark> routing_fwmark = GetRoutingFwmark(*network_id);
if (routing_fwmark) {
mark.rt_table_id = routing_fwmark->rt_table_id;
}
}
switch (vpn_policy) {
case VPNRoutingPolicy::kDefault:
break;
case VPNRoutingPolicy::kRouteOnVPN:
mark = mark | kFwmarkRouteOnVpn;
break;
case VPNRoutingPolicy::kBypassVPN:
mark = mark | kFwmarkBypassVpn;
break;
}
const auto mask = kFwmarkRoutingMask | kFwmarkVpnMask;
LOG(INFO) << "SetFwmark mark=" << mark.ToString()
<< " mask=" << mask.ToString();
return SetFwmark(sockfd, mark, mask);
}
int RoutingService::AllocateNetworkID() {
CHECK_LT(0, next_network_id_);
return next_network_id_++;
}
bool RoutingService::AssignInterfaceToNetwork(int network_id,
std::string_view ifname,
base::ScopedFD client_fd) {
if (auto it = network_ids_to_interfaces_.find(network_id);
it != network_ids_to_interfaces_.end()) {
LOG(ERROR) << __func__ << ": " << it->second << " already assigned to "
<< network_id;
return false;
}
if (auto it = interfaces_to_network_ids_.find(ifname);
it != interfaces_to_network_ids_.end()) {
LOG(ERROR) << __func__ << ": " << ifname << " already assigned to "
<< it->second;
return false;
}
base::ScopedClosureRunner cancel_lifeline_fd =
lifeline_fd_svc_->AddLifelineFD(
std::move(client_fd),
base::BindOnce(&RoutingService::ForgetNetworkID,
weak_factory_.GetWeakPtr(), network_id));
if (!cancel_lifeline_fd) {
LOG(ERROR) << __func__ << ": Failed to create lifeline fd";
return false;
}
// TODO(b/345417108): synchronize the interface/network_id maps with the
// RoutingService instance in use in the SocketService daemon.
LOG(INFO) << __func__ << ": " << network_id << " <-> " << ifname;
network_ids_to_interfaces_[network_id] = ifname;
interfaces_to_network_ids_.emplace(ifname, network_id);
cancel_lifeline_fds_.emplace(network_id, std::move(cancel_lifeline_fd));
return true;
}
void RoutingService::ForgetNetworkID(int network_id) {
const auto it = network_ids_to_interfaces_.find(network_id);
if (it == network_ids_to_interfaces_.end()) {
LOG(ERROR) << __func__ << ": Unknown " << network_id;
return;
}
LOG(INFO) << __func__ << ": " << network_id << " <-> " << it->second;
interfaces_to_network_ids_.erase(it->second);
network_ids_to_interfaces_.erase(it);
cancel_lifeline_fds_.erase(network_id);
}
const std::string* RoutingService::GetInterface(int network_id) const {
const auto it = network_ids_to_interfaces_.find(network_id);
if (it == network_ids_to_interfaces_.end()) {
return nullptr;
}
return &it->second;
}
std::optional<Fwmark> RoutingService::GetRoutingFwmark(int network_id) const {
const std::string* ifname = GetInterface(network_id);
if (!ifname) {
return std::nullopt;
}
int ifindex = system_->IfNametoindex(*ifname);
if (ifindex == 0) {
return std::nullopt;
}
return Fwmark::FromIfIndex(ifindex);
}
std::optional<int> RoutingService::GetNetworkID(std::string_view ifname) const {
const auto it = interfaces_to_network_ids_.find(ifname);
if (it == interfaces_to_network_ids_.end()) {
return std::nullopt;
}
return it->second;
}
std::vector<int> RoutingService::GetNetworkIDs() const {
std::vector<int> network_ids;
network_ids.reserve(network_ids_to_interfaces_.size());
for (const auto& [network_id, _] : network_ids_to_interfaces_) {
network_ids.push_back(network_id);
}
return network_ids;
}
std::string QoSFwmarkWithMask(QoSCategory category) {
auto mark = Fwmark::FromQoSCategory(category);
return base::StrCat(
{mark.ToString(), "/", kFwmarkQoSCategoryMask.ToString()});
}
std::string SourceFwmarkWithMask(TrafficSource source) {
auto mark = Fwmark::FromSource(source);
return base::StrCat({mark.ToString(), "/", kFwmarkAllSourcesMask.ToString()});
}
std::string_view TrafficSourceName(TrafficSource source) {
static constexpr auto kTrafficSourceNames =
base::MakeFixedFlatMap<TrafficSource, std::string_view>({
{kArc, "ARC"},
{kArcVpn, "ARC_VPN"},
{kBorealisVM, "BOREALIS_VM"},
{kBruschettaVM, "BRUSCHETTA_VM"},
{kChrome, "CHROME"},
{kCrostiniVM, "CROSTINI_VM"},
{kHostVpn, "HOST_VPN"},
{kParallelsVM, "PARALLELS_VM"},
{kSystem, "SYSTEM"},
{kTetherDownstream, "TETHER_DOWNSTREAM"},
{kUnknown, "UNKNOWN"},
{kUpdateEngine, "UPDATE_ENGINE"},
{kUser, "USER"},
{kWiFiDirect, "WIFI_DIRECT"},
{kWiFiLOHS, "WIFI_LOHS"},
});
const auto it = kTrafficSourceNames.find(source);
return it != kTrafficSourceNames.end() ? it->second : "UNKNOWN";
}
std::ostream& operator<<(std::ostream& stream, const LocalSourceSpecs& source) {
return stream << "{source: " << TrafficSourceName(source.source_type)
<< ", uid: " << (source.uid_name ? source.uid_name : "")
<< ", classid: " << source.classid
<< ", is_on_vpn: " << (source.is_on_vpn ? "true" : "false")
<< "}";
}
} // namespace patchpanel