| // 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/network_monitor_service.h" |
| |
| #include <linux/rtnetlink.h> |
| #include <net/if.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <base/functional/bind.h> |
| #include <base/logging.h> |
| #include <base/notreached.h> |
| #include <base/strings/strcat.h> |
| #include <base/task/sequenced_task_runner.h> |
| #include <net-base/byte_utils.h> |
| #include <net-base/ipv4_address.h> |
| #include <net-base/ipv6_address.h> |
| #include <net-base/rtnl_handler.h> |
| #include <net-base/rtnl_listener.h> |
| |
| namespace patchpanel { |
| |
| namespace { |
| // The set of states which indicate the neighbor is valid. Copied from |
| // /include/net/neighbour.h in linux kernel. |
| constexpr uint16_t kNUDStateValid = NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE | |
| NUD_PROBE | NUD_STALE | NUD_DELAY; |
| |
| std::string NUDStateToString(uint16_t state) { |
| switch (state) { |
| case NUD_INCOMPLETE: |
| return "NUD_INCOMPLETE"; |
| case NUD_REACHABLE: |
| return "NUD_REACHABLE"; |
| case NUD_STALE: |
| return "NUD_STALE"; |
| case NUD_DELAY: |
| return "NUD_DELAY"; |
| case NUD_PROBE: |
| return "NUD_PROBE"; |
| case NUD_FAILED: |
| return "NUD_FAILED"; |
| case NUD_NOARP: |
| return "NUD_NOARP"; |
| case NUD_PERMANENT: |
| return "NUD_PERMANENT"; |
| case NUD_NONE: |
| return "NUD_NONE"; |
| default: |
| return "Unknown NUD state " + std::to_string(state); |
| } |
| } |
| |
| bool IsIPv6LinkLocalAddress(const net_base::IPAddress& addr) { |
| static const net_base::IPv6CIDR kIPv6LockLocalCIDR = |
| *net_base::IPv6CIDR::CreateFromStringAndPrefix("fe80::", 64); |
| |
| const auto ipv6_addr = addr.ToIPv6Address(); |
| return ipv6_addr && kIPv6LockLocalCIDR.InSameSubnetWith(*ipv6_addr); |
| } |
| |
| // We cannot set the state of an address to NUD_PROBE when the kernel doesn't |
| // know its MAC address, and thus the state should be in NUD_VALID. We don't |
| // probe for the other states in NUD_VALID because: |
| // - NUD_DELAY will soon become NUD_PROBE or NUD_REACHABLE; |
| // - NUD_PROBE means the kernel is already probing; |
| // - NUD_PERMANENT and NUD_NOARP are special states and it will not be |
| // changed. |
| bool NeedProbeForState(uint16_t current_state) { |
| return current_state & (NUD_STALE | NUD_REACHABLE); |
| } |
| |
| } // namespace |
| |
| NeighborLinkMonitor::NeighborLinkMonitor( |
| int ifindex, |
| const std::string& ifname, |
| net_base::RTNLHandler* rtnl_handler, |
| NeighborReachabilityEventHandler* neighbor_event_handler) |
| : ifindex_(ifindex), |
| ifname_(ifname), |
| rtnl_handler_(rtnl_handler), |
| neighbor_event_handler_(neighbor_event_handler) {} |
| |
| NeighborLinkMonitor::WatchingEntry::WatchingEntry( |
| const net_base::IPAddress& addr, NeighborRole role) |
| : addr(addr), role(role) {} |
| |
| std::string NeighborLinkMonitor::NeighborRoleToString( |
| NeighborLinkMonitor::NeighborRole role) { |
| switch (role) { |
| case NeighborLinkMonitor::NeighborRole::kGateway: |
| return "gateway"; |
| case NeighborLinkMonitor::NeighborRole::kDNSServer: |
| return "dns_server"; |
| case NeighborLinkMonitor::NeighborRole::kGatewayAndDNSServer: |
| return "gateway and dns_server"; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| std::string NeighborLinkMonitor::WatchingEntry::ToString() const { |
| return base::StrCat({"{ addr: ", addr.ToString(), |
| ", role: ", NeighborRoleToString(role), |
| ", state: ", NUDStateToString(nud_state), " }"}); |
| } |
| |
| void NeighborLinkMonitor::AddWatchingEntries( |
| const net_base::IPCIDR& local_cidr, |
| const net_base::IPAddress& gateway, |
| const std::vector<std::string>& dns_addrs) { |
| UpdateWatchingEntry(gateway, NeighborRole::kGateway); |
| |
| int watching_dns_num = 0; |
| int skipped_dns_num = 0; |
| for (const auto& dns : dns_addrs) { |
| const auto dns_addr = net_base::IPAddress::CreateFromString(dns); |
| if (!dns_addr) { |
| LOG(ERROR) << "DNS server address is not valid"; |
| return; |
| } |
| if (!local_cidr.InSameSubnetWith(*dns_addr) && |
| !IsIPv6LinkLocalAddress(*dns_addr)) { |
| skipped_dns_num++; |
| continue; |
| } |
| watching_dns_num++; |
| UpdateWatchingEntry(*dns_addr, NeighborRole::kDNSServer); |
| } |
| LOG(INFO) << local_cidr.GetFamily() << " watching entries added on " |
| << ifname_ << ": skipped_dns_num=" << skipped_dns_num |
| << " ,watching_dns_num=" << watching_dns_num; |
| } |
| |
| void NeighborLinkMonitor::UpdateWatchingEntry(const net_base::IPAddress& addr, |
| NeighborRole role) { |
| const auto it = watching_entries_.find(addr); |
| if (it == watching_entries_.end()) { |
| watching_entries_.emplace(std::piecewise_construct, std::make_tuple(addr), |
| std::make_tuple(addr, role)); |
| return; |
| } |
| |
| constexpr uint8_t gateway_flag = static_cast<uint8_t>(NeighborRole::kGateway); |
| constexpr uint8_t dns_server_flag = |
| static_cast<uint8_t>(NeighborRole::kDNSServer); |
| uint8_t current_flags = |
| static_cast<uint8_t>(it->second.role) | static_cast<uint8_t>(role); |
| switch (current_flags) { |
| case gateway_flag: |
| it->second.role = NeighborRole::kGateway; |
| break; |
| case dns_server_flag: |
| it->second.role = NeighborRole::kDNSServer; |
| break; |
| case gateway_flag | dns_server_flag: |
| it->second.role = NeighborRole::kGatewayAndDNSServer; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void NeighborLinkMonitor::OnIPConfigChanged( |
| const ShillClient::IPConfig& ipconfig) { |
| LOG(INFO) << "ipconfigs changed on " << ifname_ |
| << ", update watching entries"; |
| const auto old_watching_entries = std::move(watching_entries_); |
| watching_entries_.clear(); |
| |
| if (ipconfig.ipv4_cidr && ipconfig.ipv4_gateway) { |
| AddWatchingEntries(net_base::IPCIDR(*ipconfig.ipv4_cidr), |
| net_base::IPAddress(*ipconfig.ipv4_gateway), |
| ipconfig.ipv4_dns_addresses); |
| } |
| if (ipconfig.ipv6_cidr && ipconfig.ipv6_gateway) { |
| AddWatchingEntries(net_base::IPCIDR(*ipconfig.ipv6_cidr), |
| net_base::IPAddress(*ipconfig.ipv6_gateway), |
| ipconfig.ipv6_dns_addresses); |
| } |
| |
| if (watching_entries_.empty()) { |
| LOG(INFO) << "Stop due to empty watching list on " << ifname_; |
| Stop(); |
| return; |
| } |
| |
| Start(); |
| |
| // If one address is in our list before, restores its NUD state and does |
| // nothing; otherwise, we need to do a dump. |
| bool has_new_entry = false; |
| for (auto new_it = watching_entries_.begin(); |
| new_it != watching_entries_.end(); new_it++) { |
| const auto old_it = old_watching_entries.find(new_it->first); |
| if (old_it == old_watching_entries.end()) |
| has_new_entry = true; |
| else |
| new_it->second.nud_state = old_it->second.nud_state; |
| } |
| |
| if (has_new_entry) |
| SendNeighborDumpRTNLMessage(); |
| } |
| |
| void NeighborLinkMonitor::Start() { |
| if (listener_ != nullptr) |
| return; |
| |
| listener_ = std::make_unique<net_base::RTNLListener>( |
| net_base::RTNLHandler::kRequestNeighbor, |
| base::BindRepeating(&NeighborLinkMonitor::OnNeighborMessage, |
| base::Unretained(this)), |
| rtnl_handler_); |
| probe_timer_.Start(FROM_HERE, kActiveProbeInterval, this, |
| &NeighborLinkMonitor::ProbeAll); |
| } |
| |
| void NeighborLinkMonitor::Stop() { |
| listener_ = nullptr; |
| probe_timer_.Stop(); |
| } |
| |
| void NeighborLinkMonitor::ProbeAll() { |
| bool has_unknown_entry = false; |
| for (const auto& addr_entry : watching_entries_) { |
| const auto& entry = addr_entry.second; |
| if (entry.nud_state == NUD_NONE) { |
| has_unknown_entry = true; |
| // This could happen for temporary failures, but continuously reaching |
| // here for one entry means that, we have an entry in ipconfig which is |
| // not accessible. |
| LOG(INFO) << "Has an unknown entry on " << ifname_ << " with " |
| << entry.ToString(); |
| } else if (NeedProbeForState(entry.nud_state)) { |
| SendNeighborProbeRTNLMessage(entry); |
| } |
| } |
| |
| if (has_unknown_entry) |
| SendNeighborDumpRTNLMessage(); |
| } |
| |
| void NeighborLinkMonitor::SendNeighborDumpRTNLMessage() { |
| // |seq| will be set by RTNLHandler. |
| // TODO(jiejiang): Specify the family instead of AF_UNSPEC. This optimization |
| // could reduce the amount of data received for each request. |
| auto msg = std::make_unique<net_base::RTNLMessage>( |
| net_base::RTNLMessage::kTypeNeighbor, net_base::RTNLMessage::kModeGet, |
| NLM_F_REQUEST | NLM_F_DUMP, /*seq=*/0, /*pid=*/0, ifindex_, AF_UNSPEC); |
| |
| // TODO(jiejiang): We may get an error of errno=16 (Device or resource busy) |
| // from kernel here. We may need to serialize the DUMP requests. |
| if (!rtnl_handler_->SendMessage(std::move(msg), /*msg_seq=*/nullptr)) |
| LOG(WARNING) << "Failed to send neighbor dump message for on " << ifname_; |
| } |
| |
| void NeighborLinkMonitor::SendNeighborProbeRTNLMessage( |
| const WatchingEntry& entry) { |
| // |seq| will be set by RTNLHandler. |
| auto msg = std::make_unique<net_base::RTNLMessage>( |
| net_base::RTNLMessage::kTypeNeighbor, net_base::RTNLMessage::kModeAdd, |
| NLM_F_REQUEST | NLM_F_REPLACE, /*seq=*/0, /*pid=*/0, ifindex_, |
| net_base::ToSAFamily(entry.addr.GetFamily())); |
| |
| // We don't need to set |ndm_flags| and |ndm_type| for this message. |
| msg->set_neighbor_status(net_base::RTNLMessage::NeighborStatus( |
| NUD_PROBE, /*ndm_flags=*/0, /*ndm_type=*/0)); |
| msg->SetAttribute(NDA_DST, entry.addr.ToBytes()); |
| |
| if (!rtnl_handler_->SendMessage(std::move(msg), /*msg_seq=*/nullptr)) |
| LOG(WARNING) << "Failed to send neighbor probe message for " |
| << entry.ToString() << " on " << ifname_; |
| } |
| |
| void NeighborLinkMonitor::OnNeighborMessage(const net_base::RTNLMessage& msg) { |
| if (msg.interface_index() != ifindex_) |
| return; |
| |
| const auto family = msg.family(); |
| const auto dst = msg.GetAttribute(NDA_DST); |
| const auto addr = net_base::IPAddress::CreateFromBytes(dst); |
| if (!addr || net_base::ToSAFamily(addr->GetFamily()) != family) { |
| LOG(ERROR) << "Got neighbor message with invalid addr which length is " |
| << dst.size(); |
| return; |
| } |
| |
| auto it = watching_entries_.find(*addr); |
| if (it == watching_entries_.end()) |
| return; |
| |
| uint16_t old_nud_state = it->second.nud_state; |
| uint16_t new_nud_state; |
| if (msg.mode() == net_base::RTNLMessage::kModeDelete) |
| new_nud_state = NUD_NONE; |
| else |
| new_nud_state = msg.neighbor_status().state; |
| |
| it->second.nud_state = new_nud_state; |
| |
| // Probes this entry if we know it for the first time (state changed |
| // from NUD_NONE, e.g., the monitor just started, or this entry has been |
| // removed once). |
| if (old_nud_state == NUD_NONE && NeedProbeForState(new_nud_state)) |
| SendNeighborProbeRTNLMessage(it->second); |
| |
| // When the "valid" state (i.e., whether kernel knows the MAC address of a |
| // neighbor) changed from valid to invalid, it doesn't always mean a failure |
| // happens: e.g., an NUD_STALE entry could be removed if it's not been |
| // accessed for a while. But it's still an uncommon case here, because we're |
| // trying to make kernel probing the neighbor periodically. Thus we would |
| // expect the NUD state stays valid if the neighbor is reachable. |
| bool old_nud_state_is_valid = old_nud_state & kNUDStateValid; |
| bool new_nud_state_is_valid = new_nud_state & kNUDStateValid; |
| if (old_nud_state_is_valid != new_nud_state_is_valid) { |
| LOG(INFO) << "NUD state changed on " << ifname_ << " for " |
| << it->second.ToString() |
| << ", old_state=" << NUDStateToString(old_nud_state); |
| } |
| |
| if (new_nud_state == NUD_FAILED) { |
| LOG(WARNING) << "Neighbor becomes NUD_FAILED from " |
| << NUDStateToString(old_nud_state) << " on " << ifname_ << " " |
| << it->second.ToString(); |
| } |
| |
| // NUD_REACHABLE indicates the bidirectional reachability has been confirmed. |
| constexpr auto kReachableState = WatchingEntry::ReachabilityState::kReachable; |
| if (new_nud_state == NUD_REACHABLE && |
| it->second.reachability_state != kReachableState) { |
| it->second.reachability_state = kReachableState; |
| neighbor_event_handler_->Run(ifindex_, it->second.addr, it->second.role, |
| NeighborReachabilityEventSignal::REACHABLE); |
| return; |
| } |
| |
| // NUD_FAILED indicates we have a reachability issue now. |
| constexpr auto kFailedState = WatchingEntry::ReachabilityState::kFailed; |
| if (new_nud_state == NUD_FAILED && |
| it->second.reachability_state != kFailedState) { |
| it->second.reachability_state = kFailedState; |
| neighbor_event_handler_->Run(ifindex_, it->second.addr, it->second.role, |
| NeighborReachabilityEventSignal::FAILED); |
| return; |
| } |
| } |
| |
| NetworkMonitorService::NetworkMonitorService( |
| ShillClient* shill_client, |
| const NeighborLinkMonitor::NeighborReachabilityEventHandler& |
| neighbor_event_handler) |
| : neighbor_event_handler_(neighbor_event_handler), |
| shill_client_(shill_client), |
| rtnl_handler_(net_base::RTNLHandler::GetInstance()) {} |
| |
| void NetworkMonitorService::Start() { |
| // Setups the RTNL socket and listens to neighbor events. This should be |
| // called before creating NeighborLinkMonitors. |
| rtnl_handler_->Start(RTMGRP_NEIGH); |
| |
| // Calls ScanDevices() first to make sure ShillClient knows all existing |
| // shill Devices, and then triggers OnShillDevicesChanged() manually before |
| // registering DevicesChangedHandler to make sure we see each shill Device |
| // exactly once. |
| shill_client_->ScanDevices(); |
| OnShillDevicesChanged(shill_client_->GetDevices(), /*removed=*/{}); |
| shill_client_->RegisterDevicesChangedHandler( |
| base::BindRepeating(&NetworkMonitorService::OnShillDevicesChanged, |
| weak_factory_.GetWeakPtr())); |
| |
| shill_client_->RegisterIPConfigsChangedHandler(base::BindRepeating( |
| &NetworkMonitorService::OnIPConfigsChanged, weak_factory_.GetWeakPtr())); |
| } |
| |
| void NetworkMonitorService::OnShillDevicesChanged( |
| const std::vector<ShillClient::Device>& added, |
| const std::vector<ShillClient::Device>& removed) { |
| System system; |
| for (const auto& device : added) { |
| switch (device.type) { |
| // Link monitoring is possible for physical local area networks on which |
| // neighbor discovery is possible. |
| case ShillClient::Device::Type::kWifi: |
| case ShillClient::Device::Type::kEthernet: |
| case ShillClient::Device::Type::kEthernetEap: |
| break; |
| // Ignore VPN networks, Cellular networks, and other types of |
| // point-to-point networks and internal virtual networks. |
| default: |
| LOG(INFO) << "Skipped creating neighbor monitor for " << device; |
| continue; |
| } |
| |
| auto link_monitor = std::make_unique<NeighborLinkMonitor>( |
| device.ifindex, device.ifname, rtnl_handler_, &neighbor_event_handler_); |
| link_monitor->OnIPConfigChanged(device.ipconfig); |
| neighbor_link_monitors_[device.ifname] = std::move(link_monitor); |
| } |
| |
| for (const auto& device : removed) { |
| neighbor_link_monitors_.erase(device.ifname); |
| } |
| } |
| |
| void NetworkMonitorService::OnIPConfigsChanged( |
| const ShillClient::Device& device) { |
| const auto it = neighbor_link_monitors_.find(device.ifname); |
| if (it == neighbor_link_monitors_.end()) |
| return; |
| |
| it->second->OnIPConfigChanged(device.ipconfig); |
| } |
| |
| } // namespace patchpanel |