Skip to main content

zond_engine/system/interface/
utils.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 crate::core::models::ip::set::IpSet;
8use pnet::datalink::NetworkInterface;
9
10/// Resolves a list of prioritized network interfaces (e.g. wired interfaces first).
11///
12/// Under the hood, this iterates over `pnet::datalink::interfaces()` directly.
13pub fn get_prioritized_interfaces(limit: usize) -> anyhow::Result<Vec<NetworkInterface>> {
14    let interfaces: Vec<NetworkInterface> = pnet::datalink::interfaces()
15        .into_iter()
16        .filter(|i| i.is_up() && !i.is_loopback() && !i.ips.is_empty())
17        .collect();
18
19    Ok(get_prioritized_interfaces_with(limit, interfaces))
20}
21
22/// Core prioritization logic, decoupled from OS interface dependencies for testing.
23pub(crate) fn get_prioritized_interfaces_with(
24    limit: usize,
25    mut interfaces: Vec<NetworkInterface>,
26) -> Vec<NetworkInterface> {
27    interfaces.sort_by_key(|i| if i.name.starts_with("e") { 0 } else { 1 });
28    interfaces.into_iter().take(limit).collect()
29}
30
31/// Determines if the interface is capable of Layer 2 operations (like ARP).
32///
33/// An interface is capable if it is not point-to-point, not a loopback, and holds a MAC address.
34pub fn is_layer_2_capable(intf: &NetworkInterface) -> bool {
35    !intf.is_point_to_point() && !intf.is_loopback() && intf.mac.is_some()
36}
37
38/// Validates whether the entire set of targets exists on the exact same layer 2 link as the interface.
39pub fn is_on_link(intf: &NetworkInterface, ips: &mut IpSet) -> bool {
40    for range in ips.v4() {
41        let mut range_covered = false;
42        for iface_ipnet in &intf.ips {
43            if let pnet::ipnetwork::IpNetwork::V4(network) = iface_ipnet
44                && network.contains(range.start_addr)
45                && network.contains(range.end_addr)
46            {
47                range_covered = true;
48                break;
49            }
50        }
51        if !range_covered {
52            return false;
53        }
54    }
55    true
56}
57
58// ╔════════════════════════════════════════════╗
59// ║ ████████╗███████╗███████╗████████╗███████╗ ║
60// ║ ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝██╔════╝ ║
61// ║    ██║   █████╗  ███████╗   ██║   ███████╗ ║
62// ║    ██║   ██╔══╝  ╚════██║   ██║   ╚════██║ ║
63// ║    ██║   ███████╗███████║   ██║   ███████║ ║
64// ║    ╚═╝   ╚══════╝╚══════╝   ╚═╝   ╚══════╝ ║
65// ╚════════════════════════════════════════════╝
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use pnet::datalink::MacAddr;
71
72    fn mock_interface(name: &str, p2p: bool, loopback: bool, mac: bool) -> NetworkInterface {
73        NetworkInterface {
74            name: name.to_string(),
75            description: "".to_string(),
76            index: 0,
77            mac: if mac {
78                Some(MacAddr::new(1, 2, 3, 4, 5, 6))
79            } else {
80                None
81            },
82            ips: vec![],
83            flags: {
84                let mut flags = 0;
85                if p2p {
86                    flags |= 16;
87                }
88                if loopback {
89                    flags |= 8;
90                }
91                flags
92            },
93        }
94    }
95
96    #[test]
97    fn test_is_layer_2_capable() {
98        assert!(is_layer_2_capable(&mock_interface(
99            "eth0", false, false, true
100        )));
101        assert!(!is_layer_2_capable(&mock_interface(
102            "eth0", true, false, true
103        )));
104        assert!(!is_layer_2_capable(&mock_interface(
105            "eth0", false, true, true
106        )));
107        assert!(!is_layer_2_capable(&mock_interface(
108            "eth0", false, false, false
109        )));
110    }
111
112    #[test]
113    fn test_sorting_prioritizes_eth_interfaces() {
114        let interfaces = vec![
115            mock_interface("wlan0", false, false, true),
116            mock_interface("eth0", false, false, true),
117        ];
118
119        let sorted = get_prioritized_interfaces_with(10, interfaces);
120        assert_eq!(sorted[0].name, "eth0");
121        assert_eq!(sorted[1].name, "wlan0");
122    }
123}