commit 91b05ddcf0b32f135390b2c0d1dbd89ad87ec3e5 Author: Sijmen Schoon Date: Sun Jul 29 17:48:16 2018 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1042c71 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,68 @@ +[[package]] +name = "byteorder" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "libc" +version = "0.2.42" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "netstat-rust" +version = "0.1.0" +dependencies = [ + "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "terminal_size 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "terminal_size" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9" +"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" +"checksum terminal_size 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef4f7fdb2a063032d361d9a72539380900bc3e0cd9ffc9ca8b677f8c855bae0f" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..631feb2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "netstat-rust" +version = "0.1.0" +authors = ["Sijmen Schoon "] + +[dependencies] +glob = "0.2.11" +hex = "0.3.2" +byteorder = "1.2.3" +terminal_size = "0.1.7" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..880350a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,199 @@ +extern crate glob; +extern crate hex; +extern crate byteorder; +extern crate terminal_size; + +use std::fs::{File, read_link}; +use std::io::{BufRead, BufReader}; +use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::fmt; + +use byteorder::{ByteOrder, LittleEndian}; +use glob::glob; +use terminal_size::{Width, terminal_size}; + + +fn parse_ipv4(ip: &str) -> IpAddr { + let bytes = hex::decode(ip).unwrap(); + let ipv4 = Ipv4Addr::new(bytes[3], bytes[2], bytes[1], bytes[0]); + IpAddr::V4(ipv4) +} + + +fn parse_ipv6(ip: &str) -> IpAddr { + let bytes = hex::decode(ip).unwrap(); + let mut words = [0; 8]; + + LittleEndian::read_u16_into(&bytes, &mut words); + + let ipv6 = Ipv6Addr::new(words[7], words[6], words[5], words[4], + words[3], words[2], words[1], words[0]); + IpAddr::V6(ipv6) +} + + +enum Protocol { + Tcp, + Udp, +} + +impl fmt::Display for Protocol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", match self { + Protocol::Tcp => "tcp", + Protocol::Udp => "udp", + }) + } +} + +struct Process { + pid: usize, + command_line: Option, +} + +struct Address<'a> { + ip: IpAddr, + port: u16, + protocol: &'a Protocol, +} + +impl<'a> fmt::Display for Address<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.ip.is_ipv4() { + true => write!(f, "{} {}:{}", self.protocol, self.ip, self.port), + false => write!(f, "{} [{}]:{}", self.protocol, self.ip, self.port) + } + } +} + +struct INode<'a> { + processes: Vec, + addresses: Vec>, +} + +impl<'a> INode<'a> { + fn new() -> INode<'a> { + INode { + processes: Vec::new(), + addresses: Vec::new(), + } + } +} + + +fn get_inodes(inodes: &mut HashMap) { + for fd in glob("/proc/*/fd/*").unwrap() { + let path = match fd { + Ok(value) => value, + Err(_) => continue, + }; + let path_str = path.to_str().unwrap(); + + let target = match read_link(path_str) { + Ok(value) => value, + Err(_) => continue, + }; + + let target_str = target.to_str().unwrap(); + let pid = path_str.split("/").nth(2).unwrap().parse(); + if !pid.is_ok() { + continue; + } + + if target_str.starts_with("socket") { + let inode_str = target_str.splitn(2, ":").nth(1).unwrap(); + let inode = inode_str[1..(inode_str.len() - 1)].parse().unwrap(); + + let mut inode = inodes.entry(inode).or_insert(INode::new()); + let process = Process { pid: pid.unwrap(), command_line: None }; + inode.processes.push(process); + } + } +} + + +fn get_command_lines(inodes: &mut HashMap) { + for inode in inodes.values_mut() { + for process in inode.processes.iter_mut() { + let filename = format!("/proc/{}/cmdline", process.pid); + let file = File::open(filename).unwrap(); + let mut buffer = BufReader::new(file); + + let mut command_line = String::new(); + buffer.read_line(&mut command_line).unwrap(); + + process.command_line = Some(command_line); + } + } +} + + +fn get_addresses(inodes: &mut HashMap) { + for protocol in [Protocol::Tcp, Protocol::Udp].iter() { + for version in ["", "6"].iter() { + let filename = format!("/proc/net/{}{}", protocol, version); + let file = File::open(filename).unwrap(); + let buffer = BufReader::new(file); + + let connections = buffer.lines().skip(1).map(|line| line.unwrap()); + for connection in connections { + let fields: Vec<&str> = connection.split_whitespace().collect(); + + let state = fields[3]; + if state != "0A" && state != "07" { + // Skip if the connection isn't listening. + continue; + } + + let address: Vec<&str> = fields[1].split(":").collect(); + let ip = match version { &"6" => parse_ipv6, _ => parse_ipv4 }(address[0]); + let port = u16::from_str_radix(address[1], 16).unwrap(); + + let inode_id = fields[9].parse().unwrap(); + let mut inode = match inodes.get_mut(&inode_id) { + Some(value) => value, + None => continue, + }; + inode.addresses.push(Address { ip, port, protocol }); + } + } + } +} + + +fn print_all(inodes: &HashMap) { + let columns = match terminal_size() { + Some((Width(value), _)) => usize::from(value), + None => 80, + }; + + let mut values: Vec<&INode> = inodes.values().collect(); + values.sort_unstable_by(|a, b| a.processes[0].pid.cmp(&b.processes[0].pid)); + + for inode in values { + for address in &inode.addresses { + for (i, process) in inode.processes.iter().enumerate() { + let command_line = process.command_line.clone().unwrap_or("".to_owned()); + let address_str = if i == 0 { format!("{}", address) } else { String::new() }; + + let mut output = format!( + "{: <45} {: >6} {}", address_str, process.pid, command_line); + output.truncate(columns); + + println!("{}", output); + } + } + } +} + + +fn main() { + let mut inodes: HashMap = HashMap::new(); + + get_inodes(&mut inodes); + get_command_lines(&mut inodes); + get_addresses(&mut inodes); + + print_all(&inodes); +}