zond_engine/system/interface/
lan.rs1use super::os::{is_physical, is_wireless};
8use crate::info;
9use pnet::datalink::NetworkInterface;
10use pnet::ipnetwork::{IpNetwork, Ipv4Network};
11
12#[derive(Debug, PartialEq, Eq, Clone, Copy)]
14pub enum ViabilityError {
15 IsDown,
17 NotPhysical,
19 NoMacAddress,
21 NotBroadcast,
23 IsPointToPoint,
25 NoValidLanIp,
27}
28
29pub fn get_lan_network() -> anyhow::Result<Option<Ipv4Network>> {
33 let interfaces: Vec<NetworkInterface> = pnet::datalink::interfaces();
34 get_lan_network_with(interfaces)
35}
36
37pub(crate) fn get_lan_network_with(
39 interfaces: Vec<NetworkInterface>,
40) -> anyhow::Result<Option<Ipv4Network>> {
41 let interfaces_str: &str = match interfaces.len() {
42 1 => "interface",
43 _ => "interfaces",
44 };
45
46 info!(
47 verbosity = 1,
48 "Identified {} network {}, picking the best one...",
49 interfaces.len(),
50 interfaces_str
51 );
52
53 let interfaces: Vec<NetworkInterface> = interfaces
54 .into_iter()
55 .filter_map(
56 |interface| match is_viable_lan_interface(&interface, is_physical) {
57 Ok(()) => Some(interface),
58 Err(_) => None,
59 },
60 )
61 .collect();
62
63 let interface: NetworkInterface =
64 if let Some(interface) = select_best_lan_interface(interfaces, is_wired) {
65 info!(
66 verbosity = 1,
67 "Performing LAN scan on interface {}", interface.name
68 );
69 interface
70 } else {
71 anyhow::bail!("No interfaces available for LAN discovery");
72 };
73 let private_v4_net: Option<Ipv4Network> = interface.ips.iter().find_map(|net| match net {
74 IpNetwork::V4(v4) if v4.ip().is_private() => Some(*v4),
75 _ => None,
76 });
77 Ok(private_v4_net)
78}
79
80fn is_viable_lan_interface(
81 interface: &NetworkInterface,
82 is_physical: impl Fn(&NetworkInterface) -> bool,
83) -> Result<(), ViabilityError> {
84 if !interface.is_up() {
85 return Err(ViabilityError::IsDown);
86 }
87 if !is_physical(interface) {
88 return Err(ViabilityError::NotPhysical);
89 }
90 if interface.is_loopback() {
91 return Err(ViabilityError::NotPhysical);
92 }
93 if interface.mac.is_none() {
94 return Err(ViabilityError::NoMacAddress);
95 }
96 if !interface.is_broadcast() {
97 return Err(ViabilityError::NotBroadcast);
98 }
99 if interface.is_point_to_point() {
100 return Err(ViabilityError::IsPointToPoint);
101 }
102 let has_valid_ip = interface.ips.iter().any(|net| match net {
103 IpNetwork::V4(ipv4) => ipv4.ip().is_private(),
104 IpNetwork::V6(ipv6) => ipv6.ip().is_unicast_link_local(),
105 });
106 if !has_valid_ip {
107 return Err(ViabilityError::NoValidLanIp);
108 }
109
110 Ok(())
111}
112
113fn select_best_lan_interface(
114 interfaces: Vec<NetworkInterface>,
115 is_wired: impl Fn(&NetworkInterface) -> bool,
116) -> Option<NetworkInterface> {
117 match interfaces.len() {
118 0 => None,
119 1 => Some(interfaces[0].clone()),
120 _ => interfaces
121 .iter()
122 .find(|&interface| is_wired(interface))
123 .cloned()
124 .or(Some(interfaces[0].clone())),
125 }
126}
127
128pub fn is_wired(interface: &NetworkInterface) -> bool {
132 is_physical(interface) && !is_wireless(interface)
133}
134
135#[cfg(test)]
145mod tests {
146 use super::*;
147 use pnet::datalink::MacAddr;
148 use pnet::ipnetwork::IpNetwork;
149 use std::net::Ipv4Addr;
150
151 fn mock_interface(
152 up: bool,
153 mac: bool,
154 broadcast: bool,
155 p2p: bool,
156 loopback: bool,
157 ip: bool,
158 ) -> NetworkInterface {
159 NetworkInterface {
160 name: "test0".to_string(),
161 description: "".to_string(),
162 index: 0,
163 mac: if mac {
164 Some(MacAddr::new(1, 2, 3, 4, 5, 6))
165 } else {
166 None
167 },
168 ips: if ip {
169 vec![IpNetwork::V4(
170 Ipv4Network::new(Ipv4Addr::new(192, 168, 1, 100), 24).unwrap(),
171 )]
172 } else {
173 vec![]
174 },
175 flags: {
176 let mut flags = 0;
177 if up {
178 flags |= 1;
179 }
180 if broadcast {
181 flags |= 2;
182 }
183 if p2p {
184 flags |= 16;
185 }
186 if loopback {
187 flags |= 8;
188 } flags
190 },
191 }
192 }
193
194 #[test]
195 fn is_viable_down() {
196 let intf = mock_interface(false, true, true, false, false, true);
197 assert_eq!(
198 is_viable_lan_interface(&intf, |_| true),
199 Err(ViabilityError::IsDown)
200 );
201 }
202
203 #[test]
204 fn is_viable_not_physical() {
205 let intf = mock_interface(true, true, true, false, false, true);
206 assert_eq!(
207 is_viable_lan_interface(&intf, |_| false),
208 Err(ViabilityError::NotPhysical)
209 );
210 }
211
212 #[test]
213 fn is_viable_no_mac() {
214 let intf = mock_interface(true, false, true, false, false, true);
215 assert_eq!(
216 is_viable_lan_interface(&intf, |_| true),
217 Err(ViabilityError::NoMacAddress)
218 );
219 }
220
221 #[test]
222 fn is_viable_success() {
223 let intf = mock_interface(true, true, true, false, false, true);
224 assert_eq!(is_viable_lan_interface(&intf, |_| true), Ok(()));
225 }
226}