| // Copyright 2019 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/routing_table.h" |
| |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <linux/fib_rules.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <net/if.h> // NOLINT - must be included after netinet/ether.h |
| #include <net/if_arp.h> |
| #include <netinet/ether.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include "patchpanel/network/routing_table_entry.h" |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/functional/bind.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/userdb_utils.h> |
| #include <chromeos/net-base/byte_utils.h> |
| #include <chromeos/net-base/ip_address.h> |
| #include <chromeos/net-base/rtnl_handler.h> |
| #include <chromeos/net-base/rtnl_listener.h> |
| |
| namespace patchpanel { |
| |
| namespace { |
| |
| // Amount added to an interface index to come up with the routing table ID for |
| // that interface. Needs to match the kIPFlagPerDeviceRoutingTableForRAEnabled |
| // offset set in net_base::ProcFsStub. |
| constexpr int kInterfaceTableIdIncrement = 1000; |
| static_assert( |
| kInterfaceTableIdIncrement > RT_TABLE_LOCAL, |
| "kInterfaceTableIdIncrement must be greater than RT_TABLE_LOCAL, " |
| "as otherwise some interface's table IDs may collide with system tables."); |
| } // namespace |
| |
| // These don't have named constants in the system header files, but they |
| // are documented in ip-rule(8) and hardcoded in net/ipv4/fib_rules.c. |
| |
| RoutingTable::RoutingTable() |
| : rtnl_handler_(net_base::RTNLHandler::GetInstance()) { |
| VLOG(2) << __func__; |
| } |
| |
| RoutingTable::~RoutingTable() = default; |
| |
| void RoutingTable::Start() { |
| VLOG(2) << __func__; |
| |
| // Initialize kUnreachableTableId as a table to block traffic. |
| auto route = RoutingTableEntry(net_base::IPFamily::kIPv6); |
| route.table = kUnreachableTableId; |
| route.type = RTN_UNREACHABLE; |
| AddRouteToKernelTable(0, route); |
| route = RoutingTableEntry(net_base::IPFamily::kIPv4); |
| route.table = kUnreachableTableId; |
| route.type = RTN_UNREACHABLE; |
| AddRouteToKernelTable(0, route); |
| } |
| |
| bool RoutingTable::AddRoute(int interface_index, |
| const RoutingTableEntry& entry) { |
| // Normal routes (i.e. not blackhole or unreachable) should be sent to a |
| // the interface's per-device table. |
| if (entry.table != GetInterfaceTableId(interface_index) && |
| entry.type != RTN_BLACKHOLE && entry.type != RTN_UNREACHABLE) { |
| LOG(ERROR) << "Can't add route to table " << entry.table |
| << " when the interface's per-device table is " |
| << GetInterfaceTableId(interface_index); |
| return false; |
| } |
| |
| if (!AddRouteToKernelTable(interface_index, entry)) { |
| return false; |
| } |
| tables_[interface_index].push_back(entry); |
| return true; |
| } |
| |
| bool RoutingTable::RemoveRoute(int interface_index, |
| const RoutingTableEntry& entry) { |
| if (!RemoveRouteFromKernelTable(interface_index, entry)) { |
| return false; |
| } |
| RouteTableEntryVector& table = tables_[interface_index]; |
| for (auto nent = table.begin(); nent != table.end(); ++nent) { |
| if (*nent == entry) { |
| table.erase(nent); |
| return true; |
| } |
| } |
| LOG(WARNING) << "Successfully removed routing entry but could not find the " |
| << "corresponding entry in patchpanel's representation of the " |
| << "routing table."; |
| return true; |
| } |
| |
| bool RoutingTable::GetDefaultRouteInternal(int interface_index, |
| net_base::IPFamily family, |
| RoutingTableEntry** entry) { |
| VLOG(2) << __func__ << " index " << interface_index << " family " << family; |
| |
| RouteTables::iterator table = tables_.find(interface_index); |
| if (table == tables_.end()) { |
| VLOG(2) << __func__ << " no table"; |
| return false; |
| } |
| |
| // If there are multiple defaulte routes choose the one with lowest metric. |
| uint32_t lowest_metric = UINT_MAX; |
| for (auto& nent : table->second) { |
| if (nent.dst.IsDefault() && nent.dst.GetFamily() == family && |
| nent.metric < lowest_metric) { |
| *entry = &nent; |
| lowest_metric = nent.metric; |
| } |
| } |
| |
| if (lowest_metric == UINT_MAX) { |
| VLOG(2) << __func__ << " no route"; |
| return false; |
| } else { |
| VLOG(2) << __func__ << ": found" << " gateway " |
| << (*entry)->gateway.ToString() << " metric " << (*entry)->metric; |
| return true; |
| } |
| } |
| |
| bool RoutingTable::SetDefaultRoute(int interface_index, |
| const net_base::IPAddress& gateway_address, |
| uint32_t table_id) { |
| VLOG(2) << __func__ << " index " << interface_index; |
| |
| RoutingTableEntry* old_entry; |
| |
| if (GetDefaultRouteInternal(interface_index, gateway_address.GetFamily(), |
| &old_entry)) { |
| if (old_entry->gateway == gateway_address && old_entry->table == table_id) { |
| return true; |
| } else { |
| if (!RemoveRoute(interface_index, *old_entry)) { |
| LOG(WARNING) << "Failed to remove old default route for interface " |
| << interface_index; |
| } |
| } |
| } |
| |
| RoutingTableEntry entry(gateway_address.GetFamily()); |
| entry.gateway = gateway_address; |
| entry.metric = kDefaultRouteMetric; |
| entry.table = table_id; |
| entry.tag = interface_index; |
| return AddRoute(interface_index, entry); |
| } |
| |
| void RoutingTable::FlushRoutes(int interface_index) { |
| VLOG(2) << __func__; |
| |
| auto table = tables_.find(interface_index); |
| if (table == tables_.end()) { |
| return; |
| } |
| |
| for (const auto& nent : table->second) { |
| RemoveRouteFromKernelTable(interface_index, nent); |
| } |
| table->second.clear(); |
| } |
| |
| void RoutingTable::FlushRoutesWithTag(int tag, net_base::IPFamily family) { |
| VLOG(2) << __func__; |
| |
| for (auto& table : tables_) { |
| for (auto nent = table.second.begin(); nent != table.second.end();) { |
| if (nent->tag == tag && nent->dst.GetFamily() == family) { |
| RemoveRouteFromKernelTable(table.first, *nent); |
| nent = table.second.erase(nent); |
| } else { |
| ++nent; |
| } |
| } |
| } |
| } |
| |
| void RoutingTable::ResetTable(int interface_index) { |
| tables_.erase(interface_index); |
| } |
| |
| bool RoutingTable::AddRouteToKernelTable(int interface_index, |
| const RoutingTableEntry& entry) { |
| VLOG(2) << __func__ << ": " << " index " << interface_index << " " << entry; |
| |
| return ApplyRoute(interface_index, entry, net_base::RTNLMessage::kModeAdd, |
| NLM_F_CREATE | NLM_F_EXCL); |
| } |
| |
| bool RoutingTable::RemoveRouteFromKernelTable(int interface_index, |
| const RoutingTableEntry& entry) { |
| VLOG(2) << __func__ << ": " << " index " << interface_index << " " << entry; |
| |
| return ApplyRoute(interface_index, entry, net_base::RTNLMessage::kModeDelete, |
| 0); |
| } |
| |
| bool RoutingTable::ApplyRoute(int interface_index, |
| const RoutingTableEntry& entry, |
| net_base::RTNLMessage::Mode mode, |
| unsigned int flags) { |
| DCHECK(entry.table != RT_TABLE_UNSPEC && entry.table != RT_TABLE_COMPAT) |
| << "Attempted to apply route: " << entry; |
| |
| VLOG(2) << base::StringPrintf("%s: dst %s index %d mode %d flags 0x%x", |
| __func__, entry.dst.ToString().c_str(), |
| interface_index, mode, flags); |
| |
| auto message = std::make_unique<net_base::RTNLMessage>( |
| net_base::RTNLMessage::kTypeRoute, mode, NLM_F_REQUEST | flags, 0, 0, 0, |
| net_base::ToSAFamily(entry.dst.GetFamily())); |
| message->set_route_status(net_base::RTNLMessage::RouteStatus( |
| static_cast<uint8_t>(entry.dst.prefix_length()), |
| /*src_prefix_in=*/0, |
| entry.table < 256 ? static_cast<uint8_t>(entry.table) : RT_TABLE_COMPAT, |
| entry.protocol, entry.scope, entry.type, 0)); |
| |
| message->SetAttribute(RTA_TABLE, |
| net_base::byte_utils::ToBytes<uint32_t>(entry.table)); |
| message->SetAttribute(RTA_PRIORITY, |
| net_base::byte_utils::ToBytes<uint32_t>(entry.metric)); |
| if (entry.type != RTN_BLACKHOLE) { |
| message->SetAttribute(RTA_DST, entry.dst.address().ToBytes()); |
| } |
| if (!entry.gateway.IsZero()) { |
| message->SetAttribute(RTA_GATEWAY, entry.gateway.ToBytes()); |
| } |
| if (!entry.pref_src.IsZero()) { |
| message->SetAttribute(RTA_PREFSRC, entry.pref_src.ToBytes()); |
| } |
| if (entry.type == RTN_UNICAST) { |
| // Note that RouteMsgHandler will ignore anything without RTA_OIF, |
| // because that is how it looks up the |tables_| vector. But |
| // FlushRoutes() and FlushRoutesWithTag() do not care. |
| message->SetAttribute( |
| RTA_OIF, net_base::byte_utils::ToBytes<int32_t>(interface_index)); |
| } |
| |
| return rtnl_handler_->SendMessage(std::move(message), nullptr); |
| } |
| |
| bool RoutingTable::CreateBlackholeRoute(int interface_index, |
| net_base::IPFamily family, |
| uint32_t metric, |
| uint32_t table_id) { |
| VLOG(2) << base::StringPrintf("%s: family %s metric %d", __func__, |
| net_base::ToString(family).c_str(), metric); |
| |
| auto entry = RoutingTableEntry(family); |
| entry.metric = metric; |
| entry.table = table_id; |
| entry.type = RTN_BLACKHOLE; |
| entry.tag = interface_index; |
| return AddRoute(interface_index, entry); |
| } |
| |
| // static |
| uint32_t RoutingTable::GetInterfaceTableId(int interface_index) { |
| return static_cast<uint32_t>(interface_index + kInterfaceTableIdIncrement); |
| } |
| |
| } // namespace patchpanel |