Initial commit
This commit is contained in:
commit
91b05ddcf0
4 changed files with 279 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
**/*.rs.bk
|
68
Cargo.lock
generated
Normal file
68
Cargo.lock
generated
Normal 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
10
Cargo.toml
Normal 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
199
src/main.rs
Normal 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);
|
||||||
|
}
|
Loading…
Reference in a new issue