nullnet_libconfmon/interface_snapshot/
snapshot.rs

1use super::serde_ext::*;
2use bincode::{deserialize, serialize, Error};
3use get_if_addrs::{get_if_addrs, IfAddr};
4use pnet::datalink;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::net::IpAddr;
8
9/// Represents a snapshot of a network interface
10#[derive(Debug, Serialize, Deserialize, PartialEq)]
11pub struct InterfaceSnapshot {
12    pub name: String,
13    pub is_up: bool,
14    pub is_loopback: bool,
15    pub is_multicast: bool,
16    pub is_broadcast: bool,
17    pub mac_address: Option<String>,
18    pub interface_index: Option<u32>,
19    #[serde(with = "serde_ipaddr_vec")]
20    pub ip_addresses: Vec<IpAddr>,
21    #[serde(with = "serde_ipaddr_option")]
22    pub subnet_mask: Option<IpAddr>,
23    #[serde(with = "serde_ipaddr_option")]
24    pub gateway: Option<IpAddr>,
25}
26
27impl InterfaceSnapshot {
28    /// Serializes a vector of `InterfaceSnapshot` objects into a binary format (`Vec<u8>`).
29    ///
30    /// # Arguments
31    /// - `snapshot`: A reference to a vector of `InterfaceSnapshot` instances.
32    ///
33    /// # Returns
34    /// - `Ok(Vec<u8>)`: Serialized binary data.
35    /// - `Err(Error)`: If serialization fails.
36    pub fn serialize_snapshot(snapshot: &Vec<InterfaceSnapshot>) -> Result<Vec<u8>, Error> {
37        serialize(snapshot)
38    }
39
40    /// Deserializes binary data (`Vec<u8>`) back into a vector of `InterfaceSnapshot` objects.
41    ///
42    /// # Arguments
43    /// - `data`: A byte slice containing serialized `InterfaceSnapshot` data.
44    ///
45    /// # Returns
46    /// - `Ok(Vec<InterfaceSnapshot>)`: The deserialized vector of network interface snapshots.
47    /// - `Err(Error)`: If deserialization fails.
48    pub fn deserialize_snapshot(data: &[u8]) -> Result<Vec<InterfaceSnapshot>, Error> {
49        deserialize(data)
50    }
51
52    /// Captures the current state of all network interfaces available on the system.
53    ///
54    /// - Retrieves interface names, statuses, MAC addresses, and other properties using `pnet::datalink`.
55    /// - Fetches assigned IP addresses and subnet masks using `get_if_addrs`.
56    ///
57    /// # Returns
58    /// - A `Vec<InterfaceSnapshot>` containing details of all detected network interfaces.
59    pub fn take_all() -> Vec<InterfaceSnapshot> {
60        let interfaces = datalink::interfaces();
61        let mut iface_map: HashMap<String, InterfaceSnapshot> = HashMap::new();
62
63        for iface in interfaces {
64            iface_map.insert(
65                iface.name.clone(),
66                InterfaceSnapshot {
67                    name: iface.name.clone(),
68                    is_up: iface.is_up(),
69                    is_loopback: iface.is_loopback(),
70                    is_multicast: iface.is_multicast(),
71                    is_broadcast: iface.is_broadcast(),
72                    mac_address: iface.mac.as_ref().map(|mac| mac.to_string()),
73                    interface_index: Some(iface.index),
74                    ip_addresses: Vec::new(),
75                    subnet_mask: None,
76                    gateway: None,
77                },
78            );
79        }
80
81        if let Ok(if_addrs) = get_if_addrs() {
82            for iface in if_addrs {
83                if let Some(entry) = iface_map.get_mut(&iface.name) {
84                    match iface.addr {
85                        IfAddr::V4(ipv4) => {
86                            entry.ip_addresses.push(IpAddr::V4(ipv4.ip));
87                            entry.subnet_mask = Some(IpAddr::V4(ipv4.netmask));
88                        }
89                        IfAddr::V6(ipv6) => {
90                            entry.ip_addresses.push(IpAddr::V6(ipv6.ip));
91                        }
92                    }
93                }
94            }
95        }
96
97        iface_map.into_values().collect()
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::InterfaceSnapshot;
104    use bincode;
105
106    #[test]
107    fn test_interface_snapshot_serialize_deserialize() {
108        let snapshot = vec![InterfaceSnapshot {
109            name: "eth0".to_string(),
110            is_up: true,
111            is_loopback: false,
112            is_multicast: true,
113            is_broadcast: true,
114            mac_address: Some("00:1A:2B:3C:4D:5E".to_string()),
115            interface_index: Some(1),
116            ip_addresses: vec![
117                "192.168.1.100".parse().unwrap(),
118                "10.0.0.1".parse().unwrap(),
119            ],
120            subnet_mask: Some("255.255.255.0".parse().unwrap()),
121            gateway: Some("192.168.1.1".parse().unwrap()),
122        }];
123
124        let serialized = InterfaceSnapshot::serialize_snapshot(&snapshot).unwrap();
125        assert!(!serialized.is_empty(), "Serialization should produce data");
126
127        let deserialized = InterfaceSnapshot::deserialize_snapshot(&serialized).unwrap();
128        assert_eq!(
129            deserialized, snapshot,
130            "Deserialized snapshot should match original"
131        );
132    }
133
134    #[test]
135    fn test_empty_interface_snapshot_serialize_deserialize() {
136        let snapshot: Vec<InterfaceSnapshot> = vec![];
137
138        let serialized = InterfaceSnapshot::serialize_snapshot(&snapshot).unwrap();
139        assert!(!serialized.is_empty(), "Serialization should produce data");
140
141        let deserialized = InterfaceSnapshot::deserialize_snapshot(&serialized).unwrap();
142        assert!(
143            deserialized.is_empty(),
144            "Deserialized snapshot should be empty"
145        );
146    }
147
148    #[test]
149    fn test_take_all_interfaces() {
150        let snapshot = InterfaceSnapshot::take_all();
151        assert!(
152            !snapshot.is_empty(),
153            "At least one network interface should be detected"
154        );
155    }
156
157    #[test]
158    fn test_serde_ipaddr_option_fields() {
159        let snapshot = InterfaceSnapshot {
160            name: "eth0".to_string(),
161            is_up: true,
162            is_loopback: false,
163            is_multicast: true,
164            is_broadcast: true,
165            mac_address: Some("00:1A:2B:3C:4D:5E".to_string()),
166            interface_index: Some(1),
167            ip_addresses: vec!["192.168.1.100".parse().unwrap()],
168            subnet_mask: None,
169            gateway: Some("192.168.1.1".parse().unwrap()),
170        };
171
172        let serialized = bincode::serialize(&snapshot).unwrap();
173        let deserialized: InterfaceSnapshot = bincode::deserialize(&serialized).unwrap();
174
175        assert_eq!(
176            deserialized.subnet_mask, None,
177            "Subnet mask should remain `None` after deserialization"
178        );
179        assert_eq!(
180            deserialized.gateway,
181            Some("192.168.1.1".parse().unwrap()),
182            "Gateway should match after deserialization"
183        );
184    }
185
186    /// Test serialization and deserialization of multiple IP addresses.
187    #[test]
188    fn test_serde_ipaddr_vec_fields() {
189        let snapshot = InterfaceSnapshot {
190            name: "eth0".to_string(),
191            is_up: true,
192            is_loopback: false,
193            is_multicast: true,
194            is_broadcast: true,
195            mac_address: Some("00:1A:2B:3C:4D:5E".to_string()),
196            interface_index: Some(1),
197            ip_addresses: vec![
198                "192.168.1.100".parse().unwrap(),
199                "10.0.0.1".parse().unwrap(),
200            ],
201            subnet_mask: Some("255.255.255.0".parse().unwrap()),
202            gateway: None,
203        };
204
205        let serialized = bincode::serialize(&snapshot).unwrap();
206        let deserialized: InterfaceSnapshot = bincode::deserialize(&serialized).unwrap();
207
208        assert_eq!(
209            deserialized.ip_addresses.len(),
210            2,
211            "There should be exactly 2 IP addresses"
212        );
213        assert_eq!(
214            deserialized.ip_addresses, snapshot.ip_addresses,
215            "IP addresses should match after deserialization"
216        );
217    }
218}