summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoretoledom <etoledom@icloud.com>2019-11-12 11:46:35 +0100
committerAram Drevekenin <aram@poor.dev>2019-11-12 11:46:35 +0100
commit68b7a0e7d5ed4ca09d2f694d989b6423da1b4afd (patch)
treec49bc46e4fe1469c1d148f7e7bb32171fcad502b
parentcab84a0015bd5cd3d817a13dceaca5ebbdf23208 (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.toml3
-rw-r--r--src/main.rs4
-rw-r--r--src/network/connection.rs12
-rw-r--r--src/os/macos.rs155
-rw-r--r--src/os/mod.rs6
5 files changed, 176 insertions, 4 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 7afc6ba..72b1230 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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