| // 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 |