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 that serializing and deserializing an `InterfaceSnapshot` preserves its data.
107    #[test]
108    fn test_interface_snapshot_serialize_deserialize() {
109        let snapshot = vec![InterfaceSnapshot {
110            name: "eth0".to_string(),
111            is_up: true,
112            is_loopback: false,
113            is_multicast: true,
114            is_broadcast: true,
115            mac_address: Some("00:1A:2B:3C:4D:5E".to_string()),
116            interface_index: Some(1),
117            ip_addresses: vec![
118                "192.168.1.100".parse().unwrap(),
119                "10.0.0.1".parse().unwrap(),
120            ],
121            subnet_mask: Some("255.255.255.0".parse().unwrap()),
122            gateway: Some("192.168.1.1".parse().unwrap()),
123        }];
124
125        // Serialize
126        let serialized = InterfaceSnapshot::serialize_snapshot(&snapshot).unwrap();
127        assert!(!serialized.is_empty(), "Serialization should produce data");
128
129        // Deserialize
130        let deserialized = InterfaceSnapshot::deserialize_snapshot(&serialized).unwrap();
131        assert_eq!(
132            deserialized, snapshot,
133            "Deserialized snapshot should match original"
134        );
135    }
136
137    /// Test that serializing and deserializing an empty list of `InterfaceSnapshot` works correctly.
138    #[test]
139    fn test_empty_interface_snapshot_serialize_deserialize() {
140        let snapshot: Vec<InterfaceSnapshot> = vec![];
141
142        // Serialize
143        let serialized = InterfaceSnapshot::serialize_snapshot(&snapshot).unwrap();
144        assert!(!serialized.is_empty(), "Serialization should produce data");
145
146        // Deserialize
147        let deserialized = InterfaceSnapshot::deserialize_snapshot(&serialized).unwrap();
148        assert!(
149            deserialized.is_empty(),
150            "Deserialized snapshot should be empty"
151        );
152    }
153
154    /// Test that `take_all()` collects interface data without errors.
155    /// This test **only checks that the function runs without panicking**.
156    #[test]
157    fn test_take_all_interfaces() {
158        let snapshot = InterfaceSnapshot::take_all();
159        assert!(
160            !snapshot.is_empty(),
161            "At least one network interface should be detected"
162        );
163    }
164
165    /// Test serialization and deserialization of `Option<IpAddr>` fields in `InterfaceSnapshot`.
166    #[test]
167    fn test_serde_ipaddr_option_fields() {
168        let snapshot = InterfaceSnapshot {
169            name: "eth0".to_string(),
170            is_up: true,
171            is_loopback: false,
172            is_multicast: true,
173            is_broadcast: true,
174            mac_address: Some("00:1A:2B:3C:4D:5E".to_string()),
175            interface_index: Some(1),
176            ip_addresses: vec!["192.168.1.100".parse().unwrap()],
177            subnet_mask: None, // ✅ Explicitly testing `None`
178            gateway: Some("192.168.1.1".parse().unwrap()),
179        };
180
181        let serialized = bincode::serialize(&snapshot).unwrap();
182        let deserialized: InterfaceSnapshot = bincode::deserialize(&serialized).unwrap();
183
184        assert_eq!(
185            deserialized.subnet_mask, None,
186            "Subnet mask should remain `None` after deserialization"
187        );
188        assert_eq!(
189            deserialized.gateway,
190            Some("192.168.1.1".parse().unwrap()),
191            "Gateway should match after deserialization"
192        );
193    }
194
195    /// Test serialization and deserialization of multiple IP addresses.
196    #[test]
197    fn test_serde_ipaddr_vec_fields() {
198        let snapshot = InterfaceSnapshot {
199            name: "eth0".to_string(),
200            is_up: true,
201            is_loopback: false,
202            is_multicast: true,
203            is_broadcast: true,
204            mac_address: Some("00:1A:2B:3C:4D:5E".to_string()),
205            interface_index: Some(1),
206            ip_addresses: vec![
207                "192.168.1.100".parse().unwrap(),
208                "10.0.0.1".parse().unwrap(),
209            ],
210            subnet_mask: Some("255.255.255.0".parse().unwrap()),
211            gateway: None,
212        };
213
214        let serialized = bincode::serialize(&snapshot).unwrap();
215        let deserialized: InterfaceSnapshot = bincode::deserialize(&serialized).unwrap();
216
217        assert_eq!(
218            deserialized.ip_addresses.len(),
219            2,
220            "There should be exactly 2 IP addresses"
221        );
222        assert_eq!(
223            deserialized.ip_addresses, snapshot.ip_addresses,
224            "IP addresses should match after deserialization"
225        );
226    }
227}