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