Skip to main content

zond_engine/system/interface/
ext.rs

1// Copyright (c) 2026 Erik Lening (hollowpointer) and Contributors
2//
3// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4// If a copy of the MPL was not distributed with this file, You can obtain one at
5// https://mozilla.org/MPL/2.0/.
6
7use pnet::datalink::NetworkInterface;
8use pnet::ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
9
10/// Adds IP network extractor utilities to `NetworkInterface`.
11pub trait NetworkInterfaceExtension {
12    /// Collects all IPv4 properties from the interface.
13    fn get_ipv4_nets(&self) -> Vec<Ipv4Network>;
14    /// Collects all IPv6 properties from the interface.
15    fn get_ipv6_nets(&self) -> Vec<Ipv6Network>;
16    /// Extracts a singular, primary non-loopback IPv4 network if present.
17    fn get_ipv4_range(&self) -> Option<Ipv4Network>;
18}
19
20impl NetworkInterfaceExtension for NetworkInterface {
21    fn get_ipv4_nets(&self) -> Vec<Ipv4Network> {
22        self.ips
23            .iter()
24            .filter_map(|ip| {
25                if let IpNetwork::V4(ipv4) = ip {
26                    Some(*ipv4)
27                } else {
28                    None
29                }
30            })
31            .collect()
32    }
33
34    fn get_ipv6_nets(&self) -> Vec<Ipv6Network> {
35        self.ips
36            .iter()
37            .filter_map(|ip| {
38                if let IpNetwork::V6(ipv6) = ip {
39                    Some(*ipv6)
40                } else {
41                    None
42                }
43            })
44            .collect()
45    }
46
47    fn get_ipv4_range(&self) -> Option<Ipv4Network> {
48        // Simple heuristic: pick the first non-loopback IPv4
49        self.get_ipv4_nets()
50            .into_iter()
51            .find(|net| !net.ip().is_loopback())
52    }
53}
54
55// ╔════════════════════════════════════════════╗
56// ║ ████████╗███████╗███████╗████████╗███████╗ ║
57// ║ ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝██╔════╝ ║
58// ║    ██║   █████╗  ███████╗   ██║   ███████╗ ║
59// ║    ██║   ██╔══╝  ╚════██║   ██║   ╚════██║ ║
60// ║    ██║   ███████╗███████║   ██║   ███████║ ║
61// ║    ╚═╝   ╚══════╝╚══════╝   ╚═╝   ╚══════╝ ║
62// ╚════════════════════════════════════════════╝
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use std::net::{Ipv4Addr, Ipv6Addr};
68
69    fn mock_interface() -> NetworkInterface {
70        NetworkInterface {
71            name: "test0".to_string(),
72            description: "".to_string(),
73            index: 0,
74            mac: None,
75            ips: vec![
76                IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(127, 0, 0, 1), 8).unwrap()),
77                IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(192, 168, 1, 100), 24).unwrap()),
78                IpNetwork::V6(
79                    Ipv6Network::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 128).unwrap(),
80                ),
81            ],
82            flags: 0,
83        }
84    }
85
86    #[test]
87    fn get_ipv4_nets() {
88        let intf = mock_interface();
89        let v4s = intf.get_ipv4_nets();
90        assert_eq!(v4s.len(), 2);
91        assert_eq!(v4s[0].ip(), Ipv4Addr::new(127, 0, 0, 1));
92        assert_eq!(v4s[1].ip(), Ipv4Addr::new(192, 168, 1, 100));
93    }
94
95    #[test]
96    fn get_ipv6_nets() {
97        let intf = mock_interface();
98        let v6s = intf.get_ipv6_nets();
99        assert_eq!(v6s.len(), 1);
100        assert_eq!(v6s[0].ip(), Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
101    }
102
103    #[test]
104    fn get_ipv4_range_ignores_loopback() {
105        let intf = mock_interface();
106        let best_range = intf.get_ipv4_range();
107        assert!(best_range.is_some());
108        assert_eq!(best_range.unwrap().ip(), Ipv4Addr::new(192, 168, 1, 100));
109    }
110}