nullnet_libconfmon/interface_snapshot/
snapshot.rs1use 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#[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 pub fn serialize_snapshot(snapshot: &Vec<InterfaceSnapshot>) -> Result<Vec<u8>, Error> {
37 serialize(snapshot)
38 }
39
40 pub fn deserialize_snapshot(data: &[u8]) -> Result<Vec<InterfaceSnapshot>, Error> {
49 deserialize(data)
50 }
51
52 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]
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 let serialized = InterfaceSnapshot::serialize_snapshot(&snapshot).unwrap();
127 assert!(!serialized.is_empty(), "Serialization should produce data");
128
129 let deserialized = InterfaceSnapshot::deserialize_snapshot(&serialized).unwrap();
131 assert_eq!(
132 deserialized, snapshot,
133 "Deserialized snapshot should match original"
134 );
135 }
136
137 #[test]
139 fn test_empty_interface_snapshot_serialize_deserialize() {
140 let snapshot: Vec<InterfaceSnapshot> = vec![];
141
142 let serialized = InterfaceSnapshot::serialize_snapshot(&snapshot).unwrap();
144 assert!(!serialized.is_empty(), "Serialization should produce data");
145
146 let deserialized = InterfaceSnapshot::deserialize_snapshot(&serialized).unwrap();
148 assert!(
149 deserialized.is_empty(),
150 "Deserialized snapshot should be empty"
151 );
152 }
153
154 #[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]
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, 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]
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}