r_lanlib/
network.rs

1//! Provides helpers for selecting a network interface on the current host
2//! through which to preform network scanning
3
4use itertools::Itertools;
5use pnet::{
6    datalink::NetworkInterface as PNetNetworkInterface, ipnetwork::IpNetwork, util::MacAddr,
7};
8use std::{
9    error::Error,
10    io,
11    net::{Ipv4Addr, TcpListener},
12    str::FromStr,
13};
14
15/// Represents a network interface on current host
16pub struct NetworkInterface {
17    /// The name of the network interface i.e. "en0"
18    pub name: String,
19    /// A description of the network interface
20    pub description: String,
21    /// The cidr block associated with interface
22    pub cidr: String,
23    /// The assigned IPV4 address on the interface
24    pub ipv4: Ipv4Addr,
25    /// The IpNetwork of the interface
26    pub ips: Vec<IpNetwork>,
27    /// The MAC address of the interface
28    pub mac: MacAddr,
29    /// Any defined flags on the interface
30    pub flags: u32,
31    /// The index of the interface
32    pub index: u32,
33}
34
35impl TryFrom<PNetNetworkInterface> for NetworkInterface {
36    type Error = Box<dyn Error>;
37
38    fn try_from(value: PNetNetworkInterface) -> Result<Self, Self::Error> {
39        let mac = value.mac.ok_or("failed to get mac address for interface")?;
40        let (ip, cidr) =
41            get_interface_ipv4_and_cidr(&value).ok_or("failed to get ip and cidr for interface")?;
42        let ipv4 = Ipv4Addr::from_str(ip.as_str())?;
43
44        Ok(Self {
45            name: value.name,
46            description: value.description,
47            flags: value.flags,
48            index: value.index,
49            mac,
50            ips: value.ips,
51            cidr,
52            ipv4,
53        })
54    }
55}
56
57impl From<&NetworkInterface> for PNetNetworkInterface {
58    fn from(value: &NetworkInterface) -> Self {
59        Self {
60            name: value.name.clone(),
61            flags: value.flags,
62            description: value.description.clone(),
63            index: value.index,
64            ips: value.ips.clone(),
65            mac: Some(value.mac),
66        }
67    }
68}
69
70/// Finds and returns a NetworkInterface by name for current host
71pub fn get_interface(name: &str) -> Option<NetworkInterface> {
72    let iface = pnet::datalink::interfaces()
73        .into_iter()
74        .find(|i| i.name == name)?;
75    NetworkInterface::try_from(iface).ok()
76}
77
78/// Finds and returns the default NetworkInterface for current host
79pub fn get_default_interface() -> Option<NetworkInterface> {
80    let iface = pnet::datalink::interfaces()
81        .into_iter()
82        .find(|e| e.is_up() && !e.is_loopback() && e.ips.iter().any(|i| i.is_ipv4()))?;
83    NetworkInterface::try_from(iface).ok()
84}
85
86/// Finds an available port on the current host. This is useful when setting the
87/// listening port on a scanner where packets will be received.
88pub fn get_available_port() -> Result<u16, io::Error> {
89    let listener = TcpListener::bind(("127.0.0.1", 0))?;
90    let addr = listener.local_addr()?;
91    Ok(addr.port())
92}
93
94fn get_interface_ipv4_and_cidr(interface: &PNetNetworkInterface) -> Option<(String, String)> {
95    let ipnet = interface.ips.iter().find(|i| i.is_ipv4())?;
96    let host_ip = ipnet.ip().to_string();
97    let first_ip = ipnet
98        .iter()
99        .find_or_first(|p| p.is_ipv4() && !p.to_string().ends_with(".0"));
100    let base = first_ip
101        .map(|i| i.to_string())
102        .unwrap_or_else(|| ipnet.network().to_string());
103    let prefix = ipnet.prefix().to_string();
104    let cidr = format!("{base}/{prefix}");
105    Some((host_ip, cidr))
106}
107
108#[cfg(test)]
109#[path = "./network_tests.rs"]
110mod tests;