Initial commit

This commit is contained in:
Sijmen 2018-07-29 17:48:16 +02:00
commit 91b05ddcf0
4 changed files with 279 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
**/*.rs.bk

68
Cargo.lock generated Normal file
View file

@ -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"

10
Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "netstat-rust"
version = "0.1.0"
authors = ["Sijmen Schoon <me@sijmenschoon.nl>"]
[dependencies]
glob = "0.2.11"
hex = "0.3.2"
byteorder = "1.2.3"
terminal_size = "0.1.7"

199
src/main.rs Normal file
View file

@ -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<String>,
}
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<Process>,
addresses: Vec<Address<'a>>,
}
impl<'a> INode<'a> {
fn new() -> INode<'a> {
INode {
processes: Vec::new(),
addresses: Vec::new(),
}
}
}
fn get_inodes(inodes: &mut HashMap<usize, INode>) {
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<usize, INode>) {
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<usize, INode>) {
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<usize, INode>) {
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<usize, INode> = HashMap::new();
get_inodes(&mut inodes);
get_command_lines(&mut inodes);
get_addresses(&mut inodes);
print_all(&inodes);
}