use std::{
io::{self, ErrorKind, Write},
net::Ipv4Addr,
time,
};
use crossterm::event::{read, Event};
use pnet::datalink::{self, Channel::Ethernet, Config, DataLinkReceiver, NetworkInterface};
use tokio::runtime::Runtime;
use crate::{network::dns, os::errors::GetInterfaceError, OsInputOutput};
#[cfg(target_os = "linux")]
use crate::os::linux::get_open_sockets;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
use crate::os::lsof::get_open_sockets;
#[cfg(target_os = "windows")]
use crate::os::windows::get_open_sockets;
pub struct TerminalEvents;
impl Iterator for TerminalEvents {
type Item = Event;
fn next(&mut self) -> Option<Event> {
match read() {
Ok(ev) => Some(ev),
Err(_) => None,
}
}
}
pub(crate) fn get_datalink_channel(
interface: &NetworkInterface,
) -> Result<Box<dyn DataLinkReceiver>, GetInterfaceError> {
let config = Config {
read_timeout: Some(time::Duration::new(1, 0)),
read_buffer_size: 65536,
..Default::default()
};
match datalink::channel(interface, config) {
Ok(Ethernet(_tx, rx)) => Ok(rx),
Ok(_) => Err(GetInterfaceError::OtherError(format!(
"{}: Unsupported interface type",
interface.name
))),
Err(e) => match e.kind() {
ErrorKind::PermissionDenied => Err(GetInterfaceError::PermissionError(
interface.name.to_owned(),
)),
_ => Err(GetInterfaceError::OtherError(format!(
"{}: {e}",
&interface.name
))),
},
}
}
fn get_interface(interface_name: &str) -> Option<NetworkInterface> {
datalink::interfaces()
.into_iter()
.find(|iface| iface.name == interface_name)
}
fn create_write_to_stdout() -> Box<dyn FnMut(String) + Send> {
Box::new({
let mut stdout = io::stdout();
move |output: String| {
writeln!(stdout, "{}", output).unwrap();
}
})
}
#[derive(Debug)]
pub struct UserErrors {
permission: Option<String>,
other: Option<String>,
}
pub fn collect_errors<'a, I>(network_frames: I) -> String
where
I: Iterator<
Item = (
&'a NetworkInterface,
Result<Box<dyn DataLinkReceiver>, GetInterfaceError>,
),
>,
{
let errors = network_frames.fold(
UserErrors {
permission: None,
other: None,
},
|acc, (_, elem)| {
if let Some(iface_error) = elem.err() {
match iface_error {
GetInterfaceError::PermissionError(interface_name) => {
if let Some(prev_interface) = acc.permission {
return UserErrors {
permission: Some(format!("{prev_interface}, {interface_name}")),
..acc
};
} else {
return UserErrors {
permission: Some(interface_name),
..acc
};
}
}
error => {
if let Some(prev_errors) = acc.other {
return UserErrors {
other: Some(format!("{prev_errors} \n {error}")),
..acc
};
} else {
return UserErrors {
other: Some(format!("{error}")),
..acc
};
}
}
};
}
acc
},
);
if let Some(interface_name) = errors.permission {
if let Some(other_errors) = errors.other {
format!(
"\n\n{interface_name}: {} \nAdditional Errors: \n {other_errors}",
eperm_message(),
)
} else {
format!("\n\n{interface_name}: {}", eperm_message())
}
} else {
let other_errors = errors
.other
.expect("asked to collect errors but found no errors");
format!("\n\n {other_errors}")
}
}
pub fn get_input(
interface_name: &Option<String>,
resolve: bool,
dns_server: &Option<Ipv4Addr>,
) -> anyhow::Result<OsInputOutput> {
let network_interfaces = if let Some(name) = interface_name {
match get_interface(name) {
Some(interface) => vec![interface],
None => {
anyhow::bail!("Cannot find interface {name}");
}
}
} else {
datalink::interfaces()
};
#[cfg(target_os = "windows")]
let network_frames = network_interfaces
.iter()
.filter(|iface| !iface.ips.is_empty())
.map(|iface| (iface, get_datalink_channel(iface)));
#[cfg(not(target_os = "windows"))]
let network_frames = network_interfaces
.iter()
.filter(|iface| iface.is_up() && !iface.ips.is_empty())
.map(|iface| (iface, get_datalink_channel(iface)));
let (available_network_frames, network_interfaces) = {
let network_frames = network_frames.clone();
let mut available_network_frames = Vec::new();
let mut available_interfaces: Vec<NetworkInterface> = Vec::new();
for (iface, rx) in network_frames.filter_map(|(iface, channel)| {
if let Ok(rx) = channel {
Some((iface, rx))
} else {
None
}
}) {
available_interfaces.push(iface.clone());
available_network_frames.push(rx);
}
(available_network_frames, available_interfaces)
};
if available_network_frames.is_empty() {
let all_errors = collect_errors(network_frames.clone());
if !all_errors.is_empty() {
anyhow::bail!(all_errors);
}
anyhow::bail!("Failed to find any network interface to listen on.");
}
let keyboard_events = Box::new(TerminalEvents);
let write_to_stdout = create_write_to_stdout();
let dns_client = if resolve {
let runtime = Runtime::new()?;
let resolver = match runtime.block_on(dns::Resolver::new(dns_server)) {
Ok(resolver) => resolver,
Err(err) => anyhow::bail!(
"Could not initialize the DNS resolver. Are you offline?\n\nReason: {err:?}"
),
};
let dns_client = dns::Client::new(resolver, runtime)?;
Some(dns_client)
} else {
None
};
Ok(OsInputOutput {
network_interfaces,
network_frames: available_network_frames,
get_open_sockets,
terminal_events: keyboard_events,
dns_client,
write_to_stdout,
})
}
#[inline]
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
fn eperm_message() -> &'static str {
"Insufficient permissions to listen on network interface(s). Try running with sudo."
}
#[inline]
#[cfg(target_os = "linux")]
fn eperm_message() -> &'static str {
r#"
Insufficient permissions to listen on network interface(s). You can work around
this issue like this:
* Try running `bandwhich` with `sudo`
* Build a `setcap(8)` wrapper for `bandwhich` with the following rules:
`cap_sys_ptrace,cap_dac_read_search,cap_net_raw,cap_net_admin+ep`
"#
}
#[inline]
#[cfg(target_os = "windows")]
fn eperm_message() -> &'static str {
"Insufficient permissions to listen on network interface(s). Try running with administrator rights."
}