1use itertools::Itertools;
5use pnet::{
6 datalink::NetworkInterface as PNetNetworkInterface, ipnetwork::IpNetwork,
7 util::MacAddr,
8};
9use std::{
10 error::Error,
11 io,
12 net::{Ipv4Addr, TcpListener},
13 str::FromStr,
14};
15
16pub struct NetworkInterface {
18 pub name: String,
20 pub description: String,
22 pub cidr: String,
24 pub ipv4: Ipv4Addr,
26 pub ips: Vec<IpNetwork>,
28 pub mac: MacAddr,
30 pub flags: u32,
32 pub index: u32,
34}
35
36impl TryFrom<PNetNetworkInterface> for NetworkInterface {
37 type Error = Box<dyn Error>;
38
39 fn try_from(value: PNetNetworkInterface) -> Result<Self, Self::Error> {
40 let mac = value.mac.ok_or("failed to get mac address for interface")?;
41 let (ip, cidr) = get_interface_ipv4_and_cidr(&value)
42 .ok_or("failed to get ip and cidr for interface")?;
43 let ipv4 = Ipv4Addr::from_str(ip.as_str())?;
44
45 Ok(Self {
46 name: value.name,
47 description: value.description,
48 flags: value.flags,
49 index: value.index,
50 mac,
51 ips: value.ips,
52 cidr,
53 ipv4,
54 })
55 }
56}
57
58impl From<&NetworkInterface> for PNetNetworkInterface {
59 fn from(value: &NetworkInterface) -> Self {
60 Self {
61 name: value.name.clone(),
62 flags: value.flags,
63 description: value.description.clone(),
64 index: value.index,
65 ips: value.ips.clone(),
66 mac: Some(value.mac),
67 }
68 }
69}
70
71pub fn get_interface(name: &str) -> Option<NetworkInterface> {
73 let iface = pnet::datalink::interfaces()
74 .into_iter()
75 .find(|i| i.name == name)?;
76 NetworkInterface::try_from(iface).ok()
77}
78
79pub fn get_default_interface() -> Option<NetworkInterface> {
81 let iface = pnet::datalink::interfaces().into_iter().find(|e| {
82 e.is_up() && !e.is_loopback() && e.ips.iter().any(|i| i.is_ipv4())
83 })?;
84 NetworkInterface::try_from(iface).ok()
85}
86
87pub fn get_available_port() -> Result<u16, io::Error> {
90 let listener = TcpListener::bind(("127.0.0.1", 0))?;
91 let addr = listener.local_addr()?;
92 Ok(addr.port())
93}
94
95fn get_interface_ipv4_and_cidr(
96 interface: &PNetNetworkInterface,
97) -> Option<(String, String)> {
98 let ipnet = interface.ips.iter().find(|i| i.is_ipv4())?;
99 let host_ip = ipnet.ip().to_string();
100 let first_ip = ipnet
101 .iter()
102 .find_or_first(|p| p.is_ipv4() && !p.to_string().ends_with(".0"));
103 let base = first_ip
104 .map(|i| i.to_string())
105 .unwrap_or_else(|| ipnet.network().to_string());
106 let prefix = ipnet.prefix().to_string();
107 let cidr = format!("{base}/{prefix}");
108 Some((host_ip, cidr))
109}
110
111#[cfg(test)]
112#[path = "./network_tests.rs"]
113mod tests;