]> xenbits.xensource.com Git - xen-guest-agent.git/commitdiff
net: keep a cache of network interfaces
authorYann Dirson <yann.dirson@vates.fr>
Thu, 30 Nov 2023 16:45:00 +0000 (17:45 +0100)
committerYann Dirson <yann.dirson@vates.fr>
Fri, 8 Dec 2023 09:59:46 +0000 (10:59 +0100)
Collects in a central place a map of iface indices, their names, and their
toolstack interface if any.

Finally replaces add_vif_info() calls with get_toolstack_interface()
called once to populate the cache.

Finally the RmIp events can be applied when an interface disappears, which
essentially fixes #13.

Though this makes a new problem apparent: recreating the VIF sometimes
does not get caught on Linux, when the iface is created as eth0 and
then renamed by udev as enX0 in a subsequent Newlink message: since we
are now only attempting to identify the toolkit-interface once, if
when we try to access the /sys node for eth0 the renaming already
occured the toolkit-interface is frozen as None.

Signed-off-by: Yann Dirson <yann.dirson@vates.fr>
src/collector_net.rs
src/collector_net_netlink.rs
src/collector_net_pnet.rs
src/datastructs.rs
src/main.rs
src/vif_detect.rs
src/vif_detect_freebsd.rs
src/vif_detect_linux.rs

index f84e50af7f09991578cf0edbf9cdb3a4da88cac6..a998dc2521bbbb2cb3f7e1980ce6ce9527c98e64 100644 (file)
@@ -1,4 +1,4 @@
-use crate::datastructs::NetEvent;
+use crate::datastructs::{NetEvent, NetInterfaceCache};
 use futures::stream::Stream;
 use std::error::Error;
 use std::io;
@@ -7,7 +7,7 @@ pub struct NetworkSource {
 }
 
 impl NetworkSource {
-    pub fn new() -> io::Result<NetworkSource> {
+    pub fn new(_cache: &'static mut NetInterfaceCache) -> io::Result<NetworkSource> {
         Ok(NetworkSource {})
     }
 
index c585c224b2aacc260eeac465b29396a73863f812..a052acb05c7af8c481dc2e1ab895a45d75a76e27 100644 (file)
@@ -1,6 +1,5 @@
 use async_stream::try_stream;
-use crate::datastructs::{NetEvent, NetEventOp, NetInterface, ToolstackNetInterface};
-use crate::unix_helpers::interface_name;
+use crate::datastructs::{NetEvent, NetEventOp, NetInterface, NetInterfaceCache};
 use futures::channel::mpsc::UnboundedReceiver;
 use futures::stream::{Stream, StreamExt};
 use netlink_packet_core::{
@@ -24,19 +23,22 @@ use rtnetlink::constants::{
     RTMGRP_IPV6_IFADDR,
     RTMGRP_LINK,
     };
+use std::collections::hash_map;
 use std::convert::TryInto;
 use std::error::Error;
 use std::io;
 use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+use std::rc::Rc;
 use std::vec::Vec;
 
 pub struct NetworkSource {
     handle: netlink_proto::ConnectionHandle<RtnlMessage>,
     messages: UnboundedReceiver<(NetlinkMessage<RtnlMessage>, SocketAddr)>,
+    iface_cache: &'static mut NetInterfaceCache,
 }
 
 impl NetworkSource {
-    pub fn new() -> io::Result<NetworkSource> {
+    pub fn new(iface_cache: &'static mut NetInterfaceCache) -> io::Result<NetworkSource> {
         let (mut connection, handle, messages) = new_connection(NETLINK_ROUTE)?;
         // What kinds of broadcast messages we want to listen for.
         let nl_mgroup_flags = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
@@ -47,7 +49,7 @@ impl NetworkSource {
             .bind(&nl_addr)
             .expect("failed to bind");
         tokio::spawn(connection);
-        Ok(NetworkSource { handle, messages })
+        Ok(NetworkSource { handle, messages, iface_cache })
     }
 
     pub async fn collect_current(&mut self) -> Result<Vec<NetEvent>, Box<dyn Error>> {
@@ -129,7 +131,7 @@ impl NetworkSource {
         Ok(event)
     }
 
-    fn nl_linkmessage_decode(&mut self, msg: &LinkMessage) -> io::Result<(NetInterface, String)> {
+    fn nl_linkmessage_decode(&mut self, msg: &LinkMessage) -> io::Result<(Rc<NetInterface>, String)> {
         let LinkMessage{header, nlas, ..} = msg;
 
         // extract fields of interest
@@ -148,18 +150,17 @@ impl NetworkSource {
                                             .map(|b| format!("{b:02x}"))
                                             .collect::<Vec<String>>().join(":"));
 
-        let iface = NetInterface { index: header.index,
-                                   name: iface_name.unwrap_or(String::from("")),
-                                   toolstack_iface: ToolstackNetInterface::None,
-        };
+        let iface = self.iface_cache
+            .entry(header.index)
+            .or_insert_with_key(|index| NetInterface::new(*index, iface_name).into());
 
         match mac_address {
-            Some(mac_address) => Ok((iface, mac_address)),
-            None => Ok((iface, "".to_string())), // FIXME ad-hoc ugly, use Option<String> instead
+            Some(mac_address) => Ok((iface.clone(), mac_address)),
+            None => Ok((iface.clone(), "".to_string())), // FIXME ad-hoc ugly, use Option<String> instead
         }
     }
 
-    fn nl_addressmessage_decode(&mut self, msg: &AddressMessage) -> io::Result<(NetInterface, IpAddr)> {
+    fn nl_addressmessage_decode(&mut self, msg: &AddressMessage) -> io::Result<(Rc<NetInterface>, IpAddr)> {
         let AddressMessage{header, nlas, ..} = msg;
 
         // extract fields of interest
@@ -193,15 +194,18 @@ impl NetworkSource {
             _ => None,
         };
 
-        let iface = NetInterface { index: header.index,
-                                   name: interface_name(header.index),
-                                   toolstack_iface: ToolstackNetInterface::None,
+        let iface = match self.iface_cache.entry(header.index) {
+            hash_map::Entry::Occupied(entry) => { entry.get().clone() },
+            hash_map::Entry::Vacant(_) => {
+                return Err(io::Error::new(io::ErrorKind::InvalidData,
+                                          format!("unknown interface for index {}", header.index)));
+            },
         };
 
         match address {
-            Some(address) => Ok((iface, address)),
+            Some(address) => Ok((iface.clone(), address)),
             None => Err(io::Error::new(io::ErrorKind::InvalidData, "unknown address")),
         }
     }
-
 }
+
index cfe117a3ff9ed51da122f1910ee1dd964d19a829..ddbe3c112f287f77dc99afad7363908b6b105216 100644 (file)
@@ -1,9 +1,9 @@
 use async_stream::try_stream;
-use crate::datastructs::{NetEvent, NetEventOp, NetInterface, ToolstackNetInterface};
+use crate::datastructs::{NetEvent, NetEventOp, NetInterface, NetInterfaceCache};
 use futures::stream::Stream;
 use ipnetwork::IpNetwork;
 use pnet_base::MacAddr;
-use std::collections::{HashMap, HashSet};
+use std::collections::{HashMap, HashSet, hash_map};
 use std::error::Error;
 use std::io;
 use std::time::Duration;
@@ -29,11 +29,12 @@ impl InterfaceInfo {
 type AddressesState = HashMap<u32, InterfaceInfo>;
 pub struct NetworkSource {
     addresses_cache: AddressesState,
+    iface_cache: &'static mut NetInterfaceCache,
 }
 
 impl NetworkSource {
-    pub fn new() -> io::Result<NetworkSource> {
-        Ok(NetworkSource {addresses_cache: AddressesState::new()})
+    pub fn new(iface_cache: &'static mut NetInterfaceCache) -> io::Result<NetworkSource> {
+        Ok(NetworkSource {addresses_cache: AddressesState::new(), iface_cache})
     }
 
     pub async fn collect_current(&mut self) -> Result<Vec<NetEvent>, Box<dyn Error>> {
@@ -81,9 +82,15 @@ impl NetworkSource {
 
         // disappearing addresses
         for (cached_iface_index, cached_info) in self.addresses_cache.iter() {
-            let iface = NetInterface { index: *cached_iface_index,
-                                       name: cached_info.name.to_string(),
-                                       toolstack_iface: ToolstackNetInterface::None,
+            // iface object from iface_cache
+            let iface = match self.iface_cache.entry(*cached_iface_index) {
+                hash_map::Entry::Occupied(entry) => { entry.get().clone() },
+                hash_map::Entry::Vacant(_) => {
+                    return Err(io::Error::new(
+                        io::ErrorKind::InvalidData,
+                        format!("disappearing interface with index {} not in iface_cache",
+                                cached_iface_index)));
+                },
             };
             let iface_adresses =
                 if let Some(iface_info) = current_addresses.get(cached_iface_index) {
@@ -102,10 +109,12 @@ impl NetworkSource {
         }
         // appearing addresses
         for (iface_index, iface_info) in current_addresses.iter() {
-            let iface = NetInterface { index: *iface_index,
-                                       name: iface_info.name.to_string(),
-                                       toolstack_iface: ToolstackNetInterface::None,
-            };
+            let iface = self.iface_cache
+                .entry(*iface_index)
+                .or_insert_with_key(|index| {
+                    NetInterface::new(*index, Some(iface_info.name.clone())).into()
+                })
+                .clone();
             let cache_adresses =
                 if let Some(cache_info) = self.addresses_cache.get(iface_index) {
                     &cache_info.addresses
index 381e06992b516d60c47e91334b3675425d418deb..25e365500d9576461902851a5db4bed537659292 100644 (file)
@@ -1,4 +1,6 @@
+use std::collections::HashMap;
 use std::net::IpAddr;
+use std::rc::Rc;
 
 pub struct KernelInfo {
     pub release: String,
@@ -30,6 +32,30 @@ pub struct NetInterface {
     pub toolstack_iface: ToolstackNetInterface,
 }
 
+impl NetInterface {
+    pub fn new(index: u32, name: Option<String>) -> NetInterface {
+        let name = match name {
+            Some(string) => { string },
+            None => {
+                log::error!("new interface with index {index} has no name");
+                String::from("") // this is not valid, but user will now be aware
+            },
+        };
+        NetInterface { index,
+                       name: name.clone(),
+                       toolstack_iface: crate::vif_detect::get_toolstack_interface(&name),
+        }
+    }
+}
+
+// The cache of currently-known network interfaces.  We have to use
+// reference counting on the cached items, as we want on one hand to
+// use references to those items from NetEvent, and OTOH we want to
+// remove interfaces from here once unplugged.  And Rust won't let us
+// use `&'static NetInterface` because we can do the latter, which is
+// good in the end.
+pub type NetInterfaceCache = HashMap<u32, Rc<NetInterface>>;
+
 #[derive(Debug)]
 pub enum NetEventOp {
     AddMac(String),
@@ -40,6 +66,6 @@ pub enum NetEventOp {
 
 #[derive(Debug)]
 pub struct NetEvent {
-    pub iface: NetInterface,
+    pub iface: Rc<NetInterface>,
     pub op: NetEventOp,
 }
index 7c3dd13efe16b2f52a06a69223ef656f81b7be21..5adf922bab7dcd0b6e07a8a1aac97e04dee43e9e 100644 (file)
@@ -23,7 +23,7 @@ mod vif_detect;
 
 use clap::Parser;
 
-use crate::datastructs::KernelInfo;
+use crate::datastructs::{KernelInfo, NetInterfaceCache};
 use crate::publisher::Publisher;
 use crate::collector_net::NetworkSource;
 use crate::collector_memory::MemorySource;
@@ -64,9 +64,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
     let mut timer_stream = tokio::time::interval(Duration::from_secs(MEM_PERIOD_SECONDS));
 
     // network events
-    let mut collector_net = NetworkSource::new()?;
-    for mut event in collector_net.collect_current().await? {
-        vif_detect::add_vif_info(&mut event);
+    let network_cache = Box::leak(Box::new(NetInterfaceCache::new()));
+    let mut collector_net = NetworkSource::new(network_cache)?;
+    for event in collector_net.collect_current().await? {
         if REPORT_INTERNAL_NICS || ! event.iface.toolstack_iface.is_none() {
             publisher.publish_netevent(&event)?;
         }
@@ -79,8 +79,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
         select! {
             event = netevent_stream.try_next().fuse() => {
                 match event? {
-                    Some(mut event) => {
-                        vif_detect::add_vif_info(&mut event);
+                    Some(event) => {
                         if REPORT_INTERNAL_NICS || ! event.iface.toolstack_iface.is_none() {
                             publisher.publish_netevent(&event)?;
                         } else {
index 4b17587531f11705d2a52caa5c33742185c5f844..15907191063f9a5de4f2c968d1821271e053adf1 100644 (file)
@@ -3,6 +3,3 @@ use crate::datastructs::{NetEvent, ToolstackNetInterface};
 pub fn get_toolstack_interface(iface_name: &str) -> ToolstackNetInterface {
     return ToolstackNetInterface::None;
 }
-
-pub fn add_vif_info(_event: &mut NetEvent) -> () {
-}
index 04dd334c9468fc85b0b7f000182e0219682766c0..f1c90c9e683496bb529637795278178919a1082d 100644 (file)
@@ -1,4 +1,4 @@
-use crate::datastructs::{NetEvent, ToolstackNetInterface};
+use crate::datastructs::ToolstackNetInterface;
 
 // identifies a VIF as named "xn%ID"
 
@@ -16,7 +16,3 @@ pub fn get_toolstack_interface(iface_name: &str) -> ToolstackNetInterface {
         },
     }
 }
-
-pub fn add_vif_info(event: &mut NetEvent) -> () {
-    event.iface.toolstack_iface = get_toolstack_interface(&event.iface.name);
-}
index 73629faa0036362aefb14ff826eac83f441ab855..d909a401fabfac45966091306f67d08c0ce46c7c 100644 (file)
@@ -1,4 +1,4 @@
-use crate::datastructs::{NetEvent, ToolstackNetInterface};
+use crate::datastructs::ToolstackNetInterface;
 use std::fs;
 
 // identifies a VIF from sysfs as devtype="vif", and take the VIF id
@@ -39,7 +39,3 @@ pub fn get_toolstack_interface(iface_name: &str) -> ToolstackNetInterface {
         },
     }
 }
-
-pub fn add_vif_info(event: &mut NetEvent) {
-    event.iface.toolstack_iface = get_toolstack_interface(&event.iface.name);
-}