Skip to main content

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,
7    util::MacAddr,
8};
9use std::{
10    error::Error,
11    io,
12    net::{Ipv4Addr, TcpListener},
13    str::FromStr,
14};
15
16/// Represents a network interface on current host
17pub struct NetworkInterface {
18    /// The name of the network interface i.e. "en0"
19    pub name: String,
20    /// A description of the network interface
21    pub description: String,
22    /// The cidr block associated with interface
23    pub cidr: String,
24    /// The assigned IPV4 address on the interface
25    pub ipv4: Ipv4Addr,
26    /// The IpNetwork of the interface
27    pub ips: Vec<IpNetwork>,
28    /// The MAC address of the interface
29    pub mac: MacAddr,
30    /// Any defined flags on the interface
31    pub flags: u32,
32    /// The index of the interface
33    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
71/// Finds and returns a NetworkInterface by name for current host
72pub 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
79/// Finds and returns the default NetworkInterface for current host
80pub 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
87/// Finds an available port on the current host. This is useful when setting the
88/// listening port on a scanner where packets will be received.
89pub 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;