blob: eaee6e041f981a40cc67c58d12ef78431b15d6b8 [file] [log] [blame] [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/network/address_service.h"
#include <map>
#include <optional>
#include <set>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <chromeos/net-base/ip_address.h>
#include "patchpanel/network/routing_table.h"
namespace patchpanel {
AddressService::AddressService(RoutingTable* routing_table)
: routing_table_(routing_table),
rtnl_handler_(net_base::RTNLHandler::GetInstance()) {}
AddressService::~AddressService() = default;
// static
std::unique_ptr<AddressService> AddressService::CreateForTesting(
net_base::RTNLHandler* rtnl_handler, RoutingTable* routing_table) {
// Using `new` to access a non-public constructor.
auto ptr = base::WrapUnique(new AddressService(routing_table));
ptr->rtnl_handler_ = rtnl_handler;
return ptr;
}
void AddressService::FlushAddress(int interface_index) {
ClearIPv4Address(interface_index);
SetIPv6Addresses(interface_index, {});
}
void AddressService::ClearIPv4Address(int interface_index) {
auto current = added_ipv4_address_.find(interface_index);
if (current == added_ipv4_address_.end()) {
return;
}
rtnl_handler_->RemoveInterfaceAddress(interface_index,
net_base::IPCIDR(current->second));
auto route = RoutingTableEntry(net_base::IPFamily::kIPv4);
route.dst = net_base::IPCIDR(current->second.GetPrefixCIDR());
route.pref_src = net_base::IPAddress(current->second.address());
route.scope = RT_SCOPE_LINK;
route.table = RoutingTable::GetInterfaceTableId(interface_index);
routing_table_->RemoveRoute(interface_index, route);
added_ipv4_address_.erase(interface_index);
}
void AddressService::SetIPv4Address(
int interface_index,
const net_base::IPv4CIDR& local,
const std::optional<net_base::IPv4Address>& broadcast) {
auto current = added_ipv4_address_.find(interface_index);
if (current != added_ipv4_address_.end()) {
if (current->second == local) {
return;
}
LOG(INFO) << __func__ << ": removing existing address " << current->second;
rtnl_handler_->RemoveInterfaceAddress(interface_index,
net_base::IPCIDR(current->second));
auto route = RoutingTableEntry(net_base::IPFamily::kIPv4);
route.dst = net_base::IPCIDR(current->second.GetPrefixCIDR());
route.pref_src = net_base::IPAddress(current->second.address());
route.scope = RT_SCOPE_LINK;
route.table = RoutingTable::GetInterfaceTableId(interface_index);
routing_table_->RemoveRoute(interface_index, route);
}
if (!rtnl_handler_->AddInterfaceAddress(interface_index,
net_base::IPCIDR(local), broadcast)) {
LOG(ERROR) << __func__ << ": fail to add " << local.ToString()
<< ", broadcast: "
<< (broadcast.has_value() ? broadcast->ToString() : "default");
} else {
LOG(INFO) << __func__ << ": adding new address " << local;
}
added_ipv4_address_[interface_index] = local;
// Move kernel-added local IPv4 route from main table to per-network table.
// Note that for IPv6 kernel directly adds those routes into per-device table
// thanks to accept_ra_rt_table.
auto route = RoutingTableEntry(net_base::IPFamily::kIPv4);
route.dst = net_base::IPCIDR(local.GetPrefixCIDR());
route.pref_src = net_base::IPAddress(local.address());
route.scope = RT_SCOPE_LINK;
route.table = RoutingTable::GetInterfaceTableId(interface_index);
if (!routing_table_->AddRoute(interface_index, route)) {
LOG(ERROR) << __func__ << ": fail to add local route " << route
<< " to per-network table, keeping the kernel-added route in "
"main table";
return;
}
route.protocol = RTPROT_KERNEL;
route.table = RT_TABLE_MAIN;
routing_table_->RemoveRoute(interface_index, route);
}
void AddressService::SetIPv6Addresses(
int interface_index, const std::vector<net_base::IPv6CIDR>& addresses) {
std::set<net_base::IPv6CIDR> to_add(addresses.begin(), addresses.end());
auto current_addresses = added_ipv6_addresses_.find(interface_index);
if (current_addresses != added_ipv6_addresses_.end()) {
for (auto iter = current_addresses->second.begin();
iter != current_addresses->second.end();
/*no-op*/) {
if (to_add.contains(*iter)) {
to_add.erase(*iter);
++iter;
} else {
LOG(INFO) << __func__ << ": removing existing address " << *iter;
rtnl_handler_->RemoveInterfaceAddress(interface_index,
net_base::IPCIDR(*iter));
iter = current_addresses->second.erase(iter);
}
}
}
for (const auto& address : to_add) {
if (!rtnl_handler_->AddInterfaceAddress(interface_index,
net_base::IPCIDR(address),
/*broadcast=*/std::nullopt)) {
LOG(ERROR) << __func__ << ": fail to add " << address.ToString();
} else {
LOG(INFO) << __func__ << ": adding new address " << address;
}
added_ipv6_addresses_[interface_index].push_back(address);
}
}
} // namespace patchpanel