diff options
author | etoledom <etoledom@icloud.com> | 2019-11-12 11:46:35 +0100 |
---|---|---|
committer | Aram Drevekenin <aram@poor.dev> | 2019-11-12 11:46:35 +0100 |
commit | 68b7a0e7d5ed4ca09d2f694d989b6423da1b4afd (patch) | |
tree | c49bc46e4fe1469c1d148f7e7bb32171fcad502b | |
parent | cab84a0015bd5cd3d817a13dceaca5ebbdf23208 (diff) |
feat(platform): support macos (#8)
* Running on MacOS
* Working on MacOs 🎉
* [MacOS] Adding process name
* Fix cargo procfs dependency for MacOS
* [MacOS] Fix syntax error.
* Fix cargo procfs dependency for Linux. The real one!
* Fix build warnings
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/network/connection.rs | 12 | ||||
-rw-r--r-- | src/os/macos.rs | 155 | ||||
-rw-r--r-- | src/os/mod.rs | 6 |
5 files changed, 176 insertions, 4 deletions
@@ -22,8 +22,9 @@ dns-lookup = "1.0.1" signal-hook = "0.1.10" failure = "0.1.6" chrono = "0.4" +regex = "*" -[target.'cfg(unix)'.dependencies] +[target.'cfg(target_os="linux")'.dependencies] procfs = "0.5.3" [dev-dependencies] diff --git a/src/main.rs b/src/main.rs index bbe3554..0df169b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,9 +49,9 @@ fn main() { } fn try_main() -> Result<(), failure::Error> { - #[cfg(not(target_os = "linux"))] + #[cfg(target_os = "windows")] compile_error!( - "Sorry, no implementations for platforms other than linux yet :( - PRs welcome!" + "Sorry, no implementations for Windows yet :( - PRs welcome!" ); use os::get_input; diff --git a/src/network/connection.rs b/src/network/connection.rs index a5a3b50..75edf86 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -4,12 +4,22 @@ use ::std::net::Ipv4Addr; use ::std::net::SocketAddr; -#[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord)] +#[derive(PartialEq, Hash, Eq, Clone, PartialOrd, Ord, Debug)] pub enum Protocol { Tcp, Udp, } +impl Protocol { + pub fn from_string(string: &String) -> Option<Self> { + match string.as_str() { + "TCP" => Some(Protocol::Tcp), + "UDP" => Some(Protocol::Udp), + _ => None, + } + } +} + impl fmt::Display for Protocol { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { diff --git a/src/os/macos.rs b/src/os/macos.rs new file mode 100644 index 0000000..0f05e37 --- /dev/null +++ b/src/os/macos.rs @@ -0,0 +1,155 @@ +use ::pnet::datalink::Channel::Ethernet; +use ::pnet::datalink::DataLinkReceiver; +use ::pnet::datalink::{self, Config, NetworkInterface}; +use ::std::io::{self, stdin, Write}; +use ::termion::event::Event; +use ::termion::input::TermRead; + +use ::std::collections::HashMap; +use ::std::net::IpAddr; + +use std::process::Command; +use regex::{Regex}; + +use signal_hook::iterator::Signals; + +use crate::network::{Connection, Protocol}; +use crate::OsInputOutput; + +use std::net::{SocketAddr}; + +struct KeyboardEvents; + +impl Iterator for KeyboardEvents { + type Item = Event; + fn next(&mut self) -> Option<Event> { + match stdin().events().next() { + Some(Ok(ev)) => Some(ev), + _ => None, + } + } +} + +fn get_datalink_channel( + interface: &NetworkInterface, +) -> Result<Box<dyn DataLinkReceiver>, failure::Error> { + match datalink::channel(interface, Config::default()) { + Ok(Ethernet(_tx, rx)) => Ok(rx), + Ok(_) => failure::bail!("Unknown interface type"), + Err(e) => failure::bail!("Failed to listen to network interface: {}", e), + } +} + +fn get_interface(interface_name: &str) -> Option<NetworkInterface> { + datalink::interfaces() + .into_iter() + .find(|iface| iface.name == interface_name) +} + +#[derive(Debug)] +struct RawConnection { + ip: String, + local_port: String, + remote_port: String, + protocol: String, + process_name: String, +} + +fn get_open_sockets() -> HashMap<Connection, String> { + let mut open_sockets = HashMap::new(); + + let output = Command::new("lsof") + .args(&["-n","-P", "-i4"])//"4tcp" + .output() + .expect("failed to execute process"); + + let regex = Regex::new(r"([^\s]+).*(TCP|UDP).*:(.*)->(.*):(\d*)(\s|$)").unwrap(); + + let output_string = String::from_utf8(output.stdout).unwrap(); + let lines = output_string.lines(); + + for line in lines { + let raw_connection_iter = regex.captures_iter(line).filter_map(|cap| { + let process_name = String::from(cap.get(1).unwrap().as_str()); + let protocol = String::from(cap.get(2).unwrap().as_str()); + let local_port = String::from(cap.get(3).unwrap().as_str()); + let ip = String::from(cap.get(4).unwrap().as_str()); + let remote_port = String::from(cap.get(5).unwrap().as_str()); + let connection = RawConnection{process_name, ip,local_port, remote_port, protocol}; + Some(connection) + }); + + let raw_connection_vec = raw_connection_iter.map(|m| m).collect::<Vec<_>>(); + + if let Some(raw_connection) = raw_connection_vec.first() { + let protocol = Protocol::from_string(&raw_connection.protocol).unwrap(); + let ip_address = IpAddr::V4(raw_connection.ip.parse().unwrap()); + let remote_port = raw_connection.remote_port.parse::<u16>().unwrap(); + let local_port = raw_connection.local_port.parse::<u16>().unwrap(); + + let socket_addr = SocketAddr::new(ip_address, remote_port); + let connection = Connection::new(socket_addr, local_port, protocol).unwrap(); + + open_sockets.insert(connection, raw_connection.process_name.clone()); + } + } + + return open_sockets; +} + +fn lookup_addr(ip: &IpAddr) -> Option<String> { + ::dns_lookup::lookup_addr(ip).ok() +} + +fn sigwinch() -> (Box<dyn Fn(Box<dyn Fn()>) + Send>, Box<dyn Fn() + Send>) { + let signals = Signals::new(&[signal_hook::SIGWINCH]).unwrap(); + let on_winch = { + let signals = signals.clone(); + move |cb: Box<dyn Fn()>| { + for signal in signals.forever() { + match signal { + signal_hook::SIGWINCH => cb(), + _ => unreachable!(), + } + } + } + }; + let cleanup = move || { + signals.close(); + }; + (Box::new(on_winch), Box::new(cleanup)) +} + +pub fn create_write_to_stdout() -> Box<dyn FnMut(String) + Send> { + Box::new({ + let mut stdout = io::stdout(); + move |output: String| { + writeln!(stdout, "{}", output).unwrap(); + } + }) +} + +pub fn get_input(interface_name: &str) -> Result<OsInputOutput, failure::Error> { + let keyboard_events = Box::new(KeyboardEvents); + let network_interface = match get_interface(interface_name) { + Some(interface) => interface, + None => { + failure::bail!("Cannot find interface {}", interface_name); + } + }; + let network_frames = get_datalink_channel(&network_interface)?; + let lookup_addr = Box::new(lookup_addr); + let write_to_stdout = create_write_to_stdout(); + let (on_winch, cleanup) = sigwinch(); + + Ok(OsInputOutput { + network_interface, + network_frames, + get_open_sockets, + keyboard_events, + lookup_addr, + on_winch, + cleanup, + write_to_stdout, + }) +} diff --git a/src/os/mod.rs b/src/os/mod.rs index d3bd9c3..0a391f5 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -3,3 +3,9 @@ mod linux; #[cfg(target_os = "linux")] pub use linux::*; + +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(target_os = "macos")] +pub use macos::*;
\ No newline at end of file |