//! The machine's interfaces and their addresses, as the kernel reports //! them, and what they say about a next hop: whether it can be reached //! directly, and from which interface. use std::collections::{HashMap, HashSet}; use std::net::IpAddr; use bgp_primitives::constants::IpVersion; use bgp_primitives::ip_prefix::IpPrefix; use crate::netlink::{Address, Link, Message}; /// The interface a neighbor is reached on. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Connected { /// The interface's index. pub interface: u32, /// Our own address in the network the neighbor is in. pub local: IpAddr, } /// The interfaces there are and the addresses they have. #[derive(Debug, Clone, Default)] pub struct Interfaces { links: HashMap, addresses: HashSet
, } /// `address` as the prefix that is only it. fn host(address: IpAddr) -> IpPrefix { match address { IpAddr::V4(v4) => IpPrefix::new(IpVersion::Ipv4, v4.octets(), 32), IpAddr::V6(v6) => IpPrefix::new(IpVersion::Ipv6, v6.octets(), 128), } .expect("an address is a prefix of its own length") } impl Interfaces { /// Takes in what a message from the kernel says about links and /// addresses. Returns whether anything is different for it. pub fn apply(&mut self, message: &Message) -> bool { match message { Message::Link { present: true, link, } => self.links.insert(link.index, link.clone()).as_ref() != Some(link), Message::Link { present: false, link, } => { self.addresses .retain(|address| address.interface != link.index); self.links.remove(&link.index).is_some() } Message::Address { present: true, address, } => self.addresses.insert(*address), Message::Address { present: false, address, } => self.addresses.remove(address), _ => false, } } /// The interfaces, by index. pub fn links(&self) -> impl Iterator { self.links.values() } pub fn link(&self, index: u32) -> Option<&Link> { self.links.get(&index) } /// The addresses of the interface with this index. pub fn addresses_of(&self, interface: u32) -> impl Iterator { self.addresses .iter() .filter(move |address| address.interface == interface) } /// Whether `address` is one of this machine's own. pub fn is_local(&self, address: IpAddr) -> bool { self.addresses.iter().any(|ours| ours.local == address) } /// Whether `prefix` is the network of one of this machine's addresses, /// or inside one. Whether the interface is up does not matter: the /// network is ours either way. pub fn has_network_around(&self, prefix: &IpPrefix) -> bool { self.addresses .iter() .any(|address| address.network.contains(prefix)) } /// Where `neighbor` can be reached without a gateway: the interface /// that is up and has an address in a network `neighbor` is in, the /// most specific such network if there are several. `None` if there is /// none, or if `neighbor` is this machine itself. pub fn connected(&self, neighbor: IpAddr) -> Option { if self.is_local(neighbor) { return None; } let neighbor = host(neighbor); self.addresses .iter() .filter(|address| address.network.contains(&neighbor)) .filter(|address| { self.links .get(&address.interface) .is_some_and(|link| link.up) }) .max_by_key(|address| address.network.length()) .map(|address| Connected { interface: address.interface, local: address.local, }) } } #[cfg(test)] mod tests { use super::*; fn link(index: u32, name: &str, up: bool) -> Message { Message::Link { present: true, link: Link { index, name: name.into(), up, }, } } fn address(interface: u32, local: &str, network: &str) -> Address { Address { interface, local: local.parse().unwrap(), network: IpPrefix::try_from(network).unwrap(), } } fn present(address: Address) -> Message { Message::Address { present: true, address, } } fn some() -> Interfaces { let mut interfaces = Interfaces::default(); for message in [ link(1, "lo", true), link(2, "eth0", true), link(3, "eth1", true), link(4, "eth2", false), present(address(1, "127.0.0.1", "127.0.0.0/8")), present(address(2, "192.0.2.1", "192.0.2.0/24")), present(address(2, "2001:db8::1", "2001:db8::/64")), // A more specific network inside eth0's, on eth1. present(address(3, "192.0.2.129", "192.0.2.128/25")), present(address(4, "198.51.100.1", "198.51.100.0/24")), ] { assert!(interfaces.apply(&message)); } interfaces } fn connected(interfaces: &Interfaces, neighbor: &str) -> Option<(u32, String)> { interfaces .connected(neighbor.parse().unwrap()) .map(|c| (c.interface, c.local.to_string())) } #[test] fn a_neighbor_is_reached_from_the_interface_whose_network_it_is_in() { let interfaces = some(); assert_eq!( connected(&interfaces, "192.0.2.7"), Some((2, "192.0.2.1".into())) ); assert_eq!( connected(&interfaces, "2001:db8::7"), Some((2, "2001:db8::1".into())) ); // The most specific network wins. assert_eq!( connected(&interfaces, "192.0.2.200"), Some((3, "192.0.2.129".into())) ); // Not in any network of ours. assert_eq!(connected(&interfaces, "203.0.113.1"), None); assert_eq!(connected(&interfaces, "2001:db8:1::1"), None); // In the network of an interface that is down. assert_eq!(connected(&interfaces, "198.51.100.7"), None); // Ourselves. assert_eq!(connected(&interfaces, "192.0.2.1"), None); assert!(interfaces.is_local("192.0.2.1".parse().unwrap())); assert!(!interfaces.is_local("192.0.2.7".parse().unwrap())); } #[test] fn what_the_kernel_reports_changes_what_is_reachable() { let mut interfaces = some(); // Told again what is known: nothing changes. assert!(!interfaces.apply(&link(2, "eth0", true))); assert!(!interfaces.apply(&present(address(2, "192.0.2.1", "192.0.2.0/24")))); // An interface comes up. assert!(interfaces.apply(&link(4, "eth2", true))); assert_eq!( connected(&interfaces, "198.51.100.7"), Some((4, "198.51.100.1".into())) ); // An address goes. assert!(interfaces.apply(&Message::Address { present: false, address: address(3, "192.0.2.129", "192.0.2.128/25"), })); assert_eq!( connected(&interfaces, "192.0.2.200"), Some((2, "192.0.2.1".into())) ); // An interface goes, and its addresses with it. assert!(interfaces.apply(&Message::Link { present: false, link: Link { index: 2, name: "eth0".into(), up: true }, })); assert_eq!(connected(&interfaces, "192.0.2.7"), None); assert_eq!(interfaces.addresses_of(2).count(), 0); // What is not about interfaces changes nothing. assert!(!interfaces.apply(&Message::Done)); } }