From 075693975376be05d228bc45b321b1f6d08021cd Mon Sep 17 00:00:00 2001 From: remgodow Date: Thu, 10 Sep 2020 18:52:30 +0200 Subject: feat(platform): windows build and run support (#180) * Remove connections vector from OpenSockets, use common OpenSockets implementation based on sysinfo and netstat2. * Replace termion backend with crossterm, which works on Windows as well. * More fixes for windows build. * Remove tui default-features (termion), update unit tests for crossterm. * Windows compilation fixes. * Remove unused get_open_sockets implementations for linux and mac. Fix formatting. * Add build.rs for windows to download and extract Packet.lib from npcap SDK. * Resolve Cargo.lock after merging main. * fix(tests): adjust snapshots new location of the dns resolution * style(clippy): clippy * style(clippy): remove dead code * style(clippy): use write_all in build.rs * style(clippy): remove unused import added by Intellij * style(review): use String instead of str * fix(build): run build.rs only once * fix(regression): skip iface_is_up() filter only for Windows * fix(review): restore per os implementation of get_open_sockets() * fix(cargo): add missing os specific packages * fix: conditional compilation of windows module * fix: compilation errors * fix: missing Protocol::from_str() implementation * style(clippy): remove unused methods Co-authored-by: Aram Drevekenin --- src/main.rs | 17 ++----- src/os/linux.rs | 39 ++++++++------- src/os/lsof.rs | 24 ++++----- src/os/lsof_utils.rs | 34 ------------- src/os/mod.rs | 3 ++ src/os/shared.rs | 26 ++++++++++ src/os/windows.rs | 58 ++++++++++++++++++++++ .../raw_mode__traffic_with_host_names.snap | 8 +-- .../snapshots/ui__traffic_with_host_names-2.snap | 8 +-- .../snapshots/ui__traffic_with_host_names.snap | 8 +-- .../snapshots/ui__truncate_long_hostnames-2.snap | 8 +-- .../snapshots/ui__truncate_long_hostnames.snap | 8 +-- src/tests/fakes/fake_input.rs | 1 - 13 files changed, 141 insertions(+), 101 deletions(-) create mode 100644 src/os/windows.rs (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 966e871..82fd65b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ mod tests; use display::{elapsed_time, RawTerminalBackend, Ui}; use network::{ dns::{self, IpTable}, - Connection, LocalSocket, Sniffer, Utilization, + LocalSocket, Sniffer, Utilization, }; use os::OnSigWinch; @@ -75,9 +75,6 @@ fn main() { } fn try_main() -> Result<(), failure::Error> { - #[cfg(target_os = "windows")] - compile_error!("Sorry, no implementations for Windows yet :( - PRs welcome!"); - use os::get_input; let opts = Opt::from_args(); let os_input = get_input(&opts.interface, !opts.no_resolve)?; @@ -101,7 +98,6 @@ fn try_main() -> Result<(), failure::Error> { pub struct OpenSockets { sockets_to_procs: HashMap, - connections: Vec, } pub struct OsInputOutput { @@ -190,19 +186,16 @@ where while running.load(Ordering::Acquire) { let render_start_time = Instant::now(); let utilization = { network_utilization.lock().unwrap().clone_and_reset() }; - let OpenSockets { - sockets_to_procs, - connections, - } = get_open_sockets(); + let OpenSockets { sockets_to_procs } = get_open_sockets(); let mut ip_to_host = IpTable::new(); if let Some(dns_client) = dns_client.as_mut() { ip_to_host = dns_client.cache(); - let unresolved_ips = connections - .iter() + let unresolved_ips = utilization + .connections + .keys() .filter(|conn| !ip_to_host.contains_key(&conn.remote_socket.ip)) .map(|conn| conn.remote_socket.ip) .collect::>(); - dns_client.resolve(unresolved_ips); } { diff --git a/src/os/linux.rs b/src/os/linux.rs index ada04fb..a28c5ac 100644 --- a/src/os/linux.rs +++ b/src/os/linux.rs @@ -2,12 +2,11 @@ use ::std::collections::HashMap; use ::procfs::process::FDTarget; -use crate::network::{Connection, Protocol}; +use crate::network::{LocalSocket, Protocol}; use crate::OpenSockets; pub(crate) fn get_open_sockets() -> OpenSockets { let mut open_sockets = HashMap::new(); - let mut connections = std::vec::Vec::new(); let mut inode_to_procname = HashMap::new(); if let Ok(all_procs) = procfs::process::all_processes() { @@ -28,14 +27,15 @@ pub(crate) fn get_open_sockets() -> OpenSockets { tcp.append(&mut tcp6); } for entry in tcp.into_iter() { - let local_port = entry.local_address.port(); - let local_ip = entry.local_address.ip(); - if let (connection, Some(procname)) = ( - Connection::new(entry.remote_address, local_ip, local_port, Protocol::Tcp), - inode_to_procname.get(&entry.inode), - ) { - open_sockets.insert(connection.local_socket, procname.clone()); - connections.push(connection); + if let Some(procname) = inode_to_procname.get(&entry.inode) { + open_sockets.insert( + LocalSocket { + ip: entry.local_address.ip(), + port: entry.local_address.port(), + protocol: Protocol::Tcp, + }, + procname.clone(), + ); }; } } @@ -45,19 +45,20 @@ pub(crate) fn get_open_sockets() -> OpenSockets { udp.append(&mut udp6); } for entry in udp.into_iter() { - let local_port = entry.local_address.port(); - let local_ip = entry.local_address.ip(); - if let (connection, Some(procname)) = ( - Connection::new(entry.remote_address, local_ip, local_port, Protocol::Udp), - inode_to_procname.get(&entry.inode), - ) { - open_sockets.insert(connection.local_socket, procname.clone()); - connections.push(connection); + if let Some(procname) = inode_to_procname.get(&entry.inode) { + open_sockets.insert( + LocalSocket { + ip: entry.local_address.ip(), + port: entry.local_address.port(), + protocol: Protocol::Udp, + }, + procname.clone(), + ); }; } } + OpenSockets { sockets_to_procs: open_sockets, - connections, } } diff --git a/src/os/lsof.rs b/src/os/lsof.rs index 31e3266..bb17948 100644 --- a/src/os/lsof.rs +++ b/src/os/lsof.rs @@ -1,10 +1,9 @@ use ::std::collections::HashMap; -use crate::network::Connection; +use crate::network::LocalSocket; use crate::OpenSockets; use super::lsof_utils; -use std::net::SocketAddr; #[derive(Debug)] struct RawConnection { @@ -17,26 +16,21 @@ struct RawConnection { pub(crate) fn get_open_sockets() -> OpenSockets { let mut open_sockets = HashMap::new(); - let mut connections_vec = std::vec::Vec::new(); let connections = lsof_utils::get_connections(); for raw_connection in connections { - let protocol = raw_connection.get_protocol(); - let remote_ip = raw_connection.get_remote_ip(); - let local_ip = raw_connection.get_local_ip(); - let remote_port = raw_connection.get_remote_port(); - let local_port = raw_connection.get_local_port(); - - let socket_addr = SocketAddr::new(remote_ip, remote_port); - let connection = Connection::new(socket_addr, local_ip, local_port, protocol); - - open_sockets.insert(connection.local_socket, raw_connection.process_name.clone()); - connections_vec.push(connection); + open_sockets.insert( + LocalSocket { + ip: raw_connection.get_local_ip(), + port: raw_connection.get_local_port(), + protocol: raw_connection.get_protocol(), + }, + raw_connection.process_name.clone(), + ); } OpenSockets { sockets_to_procs: open_sockets, - connections: connections_vec, } } diff --git a/src/os/lsof_utils.rs b/src/os/lsof_utils.rs index fa51fa1..3d715b8 100644 --- a/src/os/lsof_utils.rs +++ b/src/os/lsof_utils.rs @@ -106,14 +106,6 @@ impl RawConnection { Protocol::from_str(&self.protocol).unwrap() } - pub fn get_remote_ip(&self) -> IpAddr { - self.remote_ip.parse().unwrap() - } - - pub fn get_remote_port(&self) -> u16 { - self.remote_port.parse::().unwrap() - } - pub fn get_local_ip(&self) -> IpAddr { self.local_ip.parse().unwrap() } @@ -203,19 +195,6 @@ com.apple 590 etoledom 204u IPv4 0x28ffb9c04111253f 0t0 TCP 192.168.1. assert!(connection.is_none()); } - #[test] - fn test_raw_connection_parse_remote_port_ipv4() { - test_raw_connection_parse_remote_port(LINE_RAW_OUTPUT); - } - #[test] - fn test_raw_connection_parse_remote_port_ipv6() { - test_raw_connection_parse_remote_port(IPV6_LINE_RAW_OUTPUT); - } - fn test_raw_connection_parse_remote_port(raw_output: &str) { - let connection = RawConnection::new(raw_output).unwrap(); - assert_eq!(connection.get_remote_port(), 2222); - } - #[test] fn test_raw_connection_parse_local_port_ipv4() { test_raw_connection_parse_local_port(LINE_RAW_OUTPUT); @@ -229,19 +208,6 @@ com.apple 590 etoledom 204u IPv4 0x28ffb9c04111253f 0t0 TCP 192.168.1. assert_eq!(connection.get_local_port(), 1111); } - #[test] - fn test_raw_connection_parse_ip_address_ipv4() { - test_raw_connection_parse_ip_address(LINE_RAW_OUTPUT, "198.252.206.25"); - } - #[test] - fn test_raw_connection_parse_ip_address_ipv6() { - test_raw_connection_parse_ip_address(IPV6_LINE_RAW_OUTPUT, "fe80:4::aede:48ff:fe33:4455"); - } - fn test_raw_connection_parse_ip_address(raw_output: &str, ip: &str) { - let connection = RawConnection::new(raw_output).unwrap(); - assert_eq!(connection.get_remote_ip().to_string(), String::from(ip)); - } - #[test] fn test_raw_connection_parse_protocol_ipv4() { test_raw_connection_parse_protocol(LINE_RAW_OUTPUT); diff --git a/src/os/mod.rs b/src/os/mod.rs index 056d44e..ec04ecd 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -7,6 +7,9 @@ pub(self) mod lsof; #[cfg(any(target_os = "macos", target_os = "freebsd"))] mod lsof_utils; +#[cfg(target_os = "windows")] +pub(self) mod windows; + mod errors; mod shared; diff --git a/src/os/shared.rs b/src/os/shared.rs index 7c252bf..4cc0c24 100644 --- a/src/os/shared.rs +++ b/src/os/shared.rs @@ -9,12 +9,16 @@ use ::tokio::runtime::Runtime; use ::std::time; use crate::os::errors::GetInterfaceErrorKind; +#[cfg(not(target_os = "windows"))] use signal_hook::iterator::Signals; #[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; + use crate::{network::dns, OsInputOutput}; pub type OnSigWinch = dyn Fn(Box) + Send; @@ -63,6 +67,7 @@ fn get_interface(interface_name: &str) -> Option { .find(|iface| iface.name == interface_name) } +#[cfg(not(target_os = "windows"))] fn sigwinch() -> (Box, Box) { let signals = Signals::new(&[signal_hook::SIGWINCH]).unwrap(); let on_winch = { @@ -82,6 +87,15 @@ fn sigwinch() -> (Box, Box) { (Box::new(on_winch), Box::new(cleanup)) } +#[cfg(any(target_os = "windows"))] +fn sigwinch() -> (Box, Box) { + let on_winch = { move |_cb: Box| {} }; + let cleanup = move || { + println!("Fake signal cleanup"); + }; + (Box::new(on_winch), Box::new(cleanup)) +} + fn create_write_to_stdout() -> Box { Box::new({ let mut stdout = io::stdout(); @@ -180,6 +194,12 @@ pub fn get_input( datalink::interfaces() }; + #[cfg(any(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()) @@ -257,3 +277,9 @@ fn eperm_message() -> &'static str { `cap_sys_ptrace,cap_dac_read_search,cap_net_raw,cap_net_admin+ep` "# } + +#[inline] +#[cfg(any(target_os = "windows"))] +fn eperm_message() -> &'static str { + "Insufficient permissions to listen on network interface(s). Try running with administrator rights." +} diff --git a/src/os/windows.rs b/src/os/windows.rs new file mode 100644 index 0000000..69adfde --- /dev/null +++ b/src/os/windows.rs @@ -0,0 +1,58 @@ +use ::std::collections::HashMap; + +use ::sysinfo::ProcessExt; +use netstat2::*; + +use crate::network::{LocalSocket, Protocol}; +use crate::OpenSockets; +use sysinfo::{Pid, System, SystemExt}; + +pub(crate) fn get_open_sockets() -> OpenSockets { + let mut open_sockets = HashMap::new(); + + let mut sysinfo = System::new_all(); + sysinfo.refresh_processes(); + + let af_flags = AddressFamilyFlags::IPV4 | AddressFamilyFlags::IPV6; + let proto_flags = ProtocolFlags::TCP | ProtocolFlags::UDP; + let sockets_info = get_sockets_info(af_flags, proto_flags); + + if let Ok(sockets_info) = sockets_info { + for si in sockets_info { + let mut procname = String::new(); + for pid in si.associated_pids { + if let Some(process) = sysinfo.get_process(pid as Pid) { + procname = String::from(process.name()); + break; + } + } + + match si.protocol_socket_info { + ProtocolSocketInfo::Tcp(tcp_si) => { + open_sockets.insert( + LocalSocket { + ip: tcp_si.local_addr, + port: tcp_si.local_port, + protocol: Protocol::Tcp, + }, + procname, + ); + } + ProtocolSocketInfo::Udp(udp_si) => { + open_sockets.insert( + LocalSocket { + ip: udp_si.local_addr, + port: udp_si.local_port, + protocol: Protocol::Udp, + }, + procname, + ); + } + } + } + } + + OpenSockets { + sockets_to_procs: open_sockets, + } +} diff --git a/src/tests/cases/snapshots/raw_mode__traffic_with_host_names.snap b/src/tests/cases/snapshots/raw_mode__traffic_with_host_names.snap index fa34c0f..70398ca 100644 --- a/src/tests/cases/snapshots/raw_mode__traffic_with_host_names.snap +++ b/src/tests/cases/snapshots/raw_mode__traffic_with_host_names.snap @@ -8,10 +8,10 @@ Refreshing: Refreshing: process: "1" up/down Bps: 28/30 connections: 1 process: "5" up/down Bps: 17/18 connections: 1 -connection: :443 => one.one.one.one:12345 (tcp) up/down Bps: 28/30 process: "1" -connection: :4435 => three.three.three.three:1337 (tcp) up/down Bps: 17/18 process: "5" -remote_address: one.one.one.one up/down Bps: 28/30 connections: 1 -remote_address: three.three.three.three up/down Bps: 17/18 connections: 1 +connection: :443 => 1.1.1.1:12345 (tcp) up/down Bps: 28/30 process: "1" +connection: :4435 => 3.3.3.3:1337 (tcp) up/down Bps: 17/18 process: "5" +remote_address: 1.1.1.1 up/down Bps: 28/30 connections: 1 +remote_address: 3.3.3.3 up/down Bps: 17/18 connections: 1 Refreshing: process: "1" up/down Bps: 31/32 connections: 1 diff --git a/src/tests/cases/snapshots/ui__traffic_with_host_names-2.snap b/src/tests/cases/snapshots/ui__traffic_with_host_names-2.snap index a620f65..18a9a28 100644 --- a/src/tests/cases/snapshots/ui__traffic_with_host_names-2.snap +++ b/src/tests/cases/snapshots/ui__traffic_with_host_names-2.snap @@ -6,8 +6,8 @@ expression: "&terminal_draw_events_mirror[2]" - 31 2 31 2 - 22 27 22 27 + 31 2 one one.one.one 31 2 + 22 27 three three.three.three 22 27 @@ -30,8 +30,8 @@ expression: "&terminal_draw_events_mirror[2]" - 31 2 - 22 27 + one one.one.one:12345 (tcp) 31 2 + three three.three.three:1337 (tcp) 22 27 diff --git a/src/tests/cases/snapshots/ui__traffic_with_host_names.snap b/src/tests/cases/snapshots/ui__traffic_with_host_names.snap index 2057262..6e3a0ec 100644 --- a/src/tests/cases/snapshots/ui__traffic_with_host_names.snap +++ b/src/tests/cases/snapshots/ui__traffic_with_host_names.snap @@ -6,8 +6,8 @@ expression: "&terminal_draw_events_mirror[1]" - 1 1 28Bps / 30Bps one.one.one.one 1 28Bps / 30Bps - 5 1 17Bps / 18Bps three.three.three.three 1 17Bps / 18Bps + 1 1 28Bps / 30Bps 1.1.1.1 1 28Bps / 30Bps + 5 1 17Bps / 18Bps 3.3.3.3 1 17Bps / 18Bps @@ -30,8 +30,8 @@ expression: "&terminal_draw_events_mirror[1]" - :443 => one.one.one.one:12345 (tcp) 1 28Bps / 30Bps - :4435 => three.three.three.three:1337 (tcp) 5 17Bps / 18Bps + :443 => 1.1.1.1:12345 (tcp) 1 28Bps / 30Bps + :4435 => 3.3.3.3:1337 (tcp) 5 17Bps / 18Bps diff --git a/src/tests/cases/snapshots/ui__truncate_long_hostnames-2.snap b/src/tests/cases/snapshots/ui__truncate_long_hostnames-2.snap index a620f65..61dd48d 100644 --- a/src/tests/cases/snapshots/ui__truncate_long_hostnames-2.snap +++ b/src/tests/cases/snapshots/ui__truncate_long_hostnames-2.snap @@ -6,8 +6,8 @@ expression: "&terminal_draw_events_mirror[2]" - 31 2 31 2 - 22 27 22 27 + 31 2 i am.not.too.long 31 2 + 22 27 i am.an.obno[...]really.i.ask 22 27 @@ -30,8 +30,8 @@ expression: "&terminal_draw_events_mirror[2]" - 31 2 - 22 27 + i am.not.too.long:12345 (tcp) 31 2 + i am.an.obnoxiosuly.lo[...]hy.would.anyone.do.this.really.i.ask:1337 (tcp) 22 27 diff --git a/src/tests/cases/snapshots/ui__truncate_long_hostnames.snap b/src/tests/cases/snapshots/ui__truncate_long_hostnames.snap index ff2bcd8..6e3a0ec 100644 --- a/src/tests/cases/snapshots/ui__truncate_long_hostnames.snap +++ b/src/tests/cases/snapshots/ui__truncate_long_hostnames.snap @@ -6,8 +6,8 @@ expression: "&terminal_draw_events_mirror[1]" - 1 1 28Bps / 30Bps i.am.not.too.long 1 28Bps / 30Bps - 5 1 17Bps / 18Bps i.am.an.obno[...]really.i.ask 1 17Bps / 18Bps + 1 1 28Bps / 30Bps 1.1.1.1 1 28Bps / 30Bps + 5 1 17Bps / 18Bps 3.3.3.3 1 17Bps / 18Bps @@ -30,8 +30,8 @@ expression: "&terminal_draw_events_mirror[1]" - :443 => i.am.not.too.long:12345 (tcp) 1 28Bps / 30Bps - :4435 => i.am.an.obnoxiosuly.lo[...]hy.would.anyone.do.this.really.i.ask:1337 (tcp) 5 17Bps / 18Bps + :443 => 1.1.1.1:12345 (tcp) 1 28Bps / 30Bps + :4435 => 3.3.3.3:1337 (tcp) 5 17Bps / 18Bps diff --git a/src/tests/fakes/fake_input.rs b/src/tests/fakes/fake_input.rs index ac6c246..5e9e913 100644 --- a/src/tests/fakes/fake_input.rs +++ b/src/tests/fakes/fake_input.rs @@ -141,7 +141,6 @@ pub fn get_open_sockets() -> OpenSockets { OpenSockets { sockets_to_procs: local_socket_to_procs, - connections, } } -- cgit v1.2.3