813 lines
25 KiB
Rust
813 lines
25 KiB
Rust
// Don't throw clippy warnings for manual string stripping.
|
|
// The suggested fix with `strip_prefix` removes support for Rust 1.33 and 1.38
|
|
#![allow(clippy::manual_strip)]
|
|
|
|
//! Information about the networking layer.
|
|
//!
|
|
//! This module corresponds to the `/proc/net` directory and contains various information about the
|
|
//! networking layer.
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! Here's an example that will print out all of the open and listening TCP sockets, and their
|
|
//! corresponding processes, if know. This mimics the "netstat" utility, but for TCP only. You
|
|
//! can run this example yourself with:
|
|
//!
|
|
//! > cargo run --example=netstat
|
|
//!
|
|
//! ```rust
|
|
//! # use procfs::process::{FDTarget, Stat};
|
|
//! # use std::collections::HashMap;
|
|
//! let all_procs = procfs::process::all_processes().unwrap();
|
|
//!
|
|
//! // build up a map between socket inodes and process stat info:
|
|
//! let mut map: HashMap<u64, Stat> = HashMap::new();
|
|
//! for p in all_procs {
|
|
//! let process = p.unwrap();
|
|
//! if let (Ok(stat), Ok(fds)) = (process.stat(), process.fd()) {
|
|
//! for fd in fds {
|
|
//! if let FDTarget::Socket(inode) = fd.unwrap().target {
|
|
//! map.insert(inode, stat.clone());
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//! }
|
|
//!
|
|
//! // get the tcp table
|
|
//! let tcp = procfs::net::tcp().unwrap();
|
|
//! let tcp6 = procfs::net::tcp6().unwrap();
|
|
//! println!("{:<26} {:<26} {:<15} {:<8} {}", "Local address", "Remote address", "State", "Inode", "PID/Program name");
|
|
//! for entry in tcp.into_iter().chain(tcp6) {
|
|
//! // find the process (if any) that has an open FD to this entry's inode
|
|
//! let local_address = format!("{}", entry.local_address);
|
|
//! let remote_addr = format!("{}", entry.remote_address);
|
|
//! let state = format!("{:?}", entry.state);
|
|
//! if let Some(stat) = map.get(&entry.inode) {
|
|
//! println!("{:<26} {:<26} {:<15} {:<12} {}/{}", local_address, remote_addr, state, entry.inode, stat.pid, stat.comm);
|
|
//! } else {
|
|
//! // We might not always be able to find the process associated with this socket
|
|
//! println!("{:<26} {:<26} {:<15} {:<12} -", local_address, remote_addr, state, entry.inode);
|
|
//! }
|
|
//! }
|
|
use crate::from_iter;
|
|
use crate::ProcResult;
|
|
use std::collections::HashMap;
|
|
|
|
use crate::FileWrapper;
|
|
use bitflags::bitflags;
|
|
use byteorder::{ByteOrder, NativeEndian, NetworkEndian};
|
|
use std::io::{BufRead, BufReader, Read};
|
|
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
|
use std::{path::PathBuf, str::FromStr};
|
|
|
|
#[cfg(feature = "serde1")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub enum TcpState {
|
|
Established = 1,
|
|
SynSent,
|
|
SynRecv,
|
|
FinWait1,
|
|
FinWait2,
|
|
TimeWait,
|
|
Close,
|
|
CloseWait,
|
|
LastAck,
|
|
Listen,
|
|
Closing,
|
|
NewSynRecv,
|
|
}
|
|
|
|
impl TcpState {
|
|
pub fn from_u8(num: u8) -> Option<TcpState> {
|
|
match num {
|
|
0x01 => Some(TcpState::Established),
|
|
0x02 => Some(TcpState::SynSent),
|
|
0x03 => Some(TcpState::SynRecv),
|
|
0x04 => Some(TcpState::FinWait1),
|
|
0x05 => Some(TcpState::FinWait2),
|
|
0x06 => Some(TcpState::TimeWait),
|
|
0x07 => Some(TcpState::Close),
|
|
0x08 => Some(TcpState::CloseWait),
|
|
0x09 => Some(TcpState::LastAck),
|
|
0x0A => Some(TcpState::Listen),
|
|
0x0B => Some(TcpState::Closing),
|
|
0x0C => Some(TcpState::NewSynRecv),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_u8(&self) -> u8 {
|
|
match self {
|
|
TcpState::Established => 0x01,
|
|
TcpState::SynSent => 0x02,
|
|
TcpState::SynRecv => 0x03,
|
|
TcpState::FinWait1 => 0x04,
|
|
TcpState::FinWait2 => 0x05,
|
|
TcpState::TimeWait => 0x06,
|
|
TcpState::Close => 0x07,
|
|
TcpState::CloseWait => 0x08,
|
|
TcpState::LastAck => 0x09,
|
|
TcpState::Listen => 0x0A,
|
|
TcpState::Closing => 0x0B,
|
|
TcpState::NewSynRecv => 0x0C,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub enum UdpState {
|
|
Established = 1,
|
|
Close = 7,
|
|
}
|
|
|
|
impl UdpState {
|
|
pub fn from_u8(num: u8) -> Option<UdpState> {
|
|
match num {
|
|
0x01 => Some(UdpState::Established),
|
|
0x07 => Some(UdpState::Close),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_u8(&self) -> u8 {
|
|
match self {
|
|
UdpState::Established => 0x01,
|
|
UdpState::Close => 0x07,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub enum UnixState {
|
|
UNCONNECTED = 1,
|
|
CONNECTING = 2,
|
|
CONNECTED = 3,
|
|
DISCONNECTING = 4,
|
|
}
|
|
|
|
impl UnixState {
|
|
pub fn from_u8(num: u8) -> Option<UnixState> {
|
|
match num {
|
|
0x01 => Some(UnixState::UNCONNECTED),
|
|
0x02 => Some(UnixState::CONNECTING),
|
|
0x03 => Some(UnixState::CONNECTED),
|
|
0x04 => Some(UnixState::DISCONNECTING),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_u8(&self) -> u8 {
|
|
match self {
|
|
UnixState::UNCONNECTED => 0x01,
|
|
UnixState::CONNECTING => 0x02,
|
|
UnixState::CONNECTED => 0x03,
|
|
UnixState::DISCONNECTING => 0x04,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An entry in the TCP socket table
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct TcpNetEntry {
|
|
pub local_address: SocketAddr,
|
|
pub remote_address: SocketAddr,
|
|
pub state: TcpState,
|
|
pub rx_queue: u32,
|
|
pub tx_queue: u32,
|
|
pub inode: u64,
|
|
}
|
|
|
|
/// An entry in the UDP socket table
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct UdpNetEntry {
|
|
pub local_address: SocketAddr,
|
|
pub remote_address: SocketAddr,
|
|
pub state: UdpState,
|
|
pub rx_queue: u32,
|
|
pub tx_queue: u32,
|
|
pub inode: u64,
|
|
}
|
|
|
|
/// An entry in the Unix socket table
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct UnixNetEntry {
|
|
/// The number of users of the socket
|
|
pub ref_count: u32,
|
|
/// The socket type.
|
|
///
|
|
/// Possible values are `SOCK_STREAM`, `SOCK_DGRAM`, or `SOCK_SEQPACKET`. These constants can
|
|
/// be found in the libc crate.
|
|
pub socket_type: u16,
|
|
/// The state of the socket
|
|
pub state: UnixState,
|
|
/// The inode number of the socket
|
|
pub inode: u64,
|
|
/// The bound pathname (if any) of the socket.
|
|
///
|
|
/// Sockets in the abstract namespace are included, and are shown with a path that commences
|
|
/// with the '@' character.
|
|
pub path: Option<PathBuf>,
|
|
}
|
|
|
|
/// Parses an address in the form 00010203:1234
|
|
///
|
|
/// Also supports IPv6
|
|
fn parse_addressport_str(s: &str) -> ProcResult<SocketAddr> {
|
|
let mut las = s.split(':');
|
|
let ip_part = expect!(las.next(), "ip_part");
|
|
let port = expect!(las.next(), "port");
|
|
let port = from_str!(u16, port, 16);
|
|
|
|
if ip_part.len() == 8 {
|
|
let bytes = expect!(hex::decode(ip_part));
|
|
let ip_u32 = NetworkEndian::read_u32(&bytes);
|
|
|
|
let ip = Ipv4Addr::new(
|
|
(ip_u32 & 0xff) as u8,
|
|
((ip_u32 & 0xff << 8) >> 8) as u8,
|
|
((ip_u32 & 0xff << 16) >> 16) as u8,
|
|
((ip_u32 & 0xff << 24) >> 24) as u8,
|
|
);
|
|
|
|
Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)))
|
|
} else if ip_part.len() == 32 {
|
|
let bytes = expect!(hex::decode(ip_part));
|
|
|
|
let ip_a = NativeEndian::read_u32(&bytes[0..]);
|
|
let ip_b = NativeEndian::read_u32(&bytes[4..]);
|
|
let ip_c = NativeEndian::read_u32(&bytes[8..]);
|
|
let ip_d = NativeEndian::read_u32(&bytes[12..]);
|
|
|
|
let ip = Ipv6Addr::new(
|
|
((ip_a >> 16) & 0xffff) as u16,
|
|
(ip_a & 0xffff) as u16,
|
|
((ip_b >> 16) & 0xffff) as u16,
|
|
(ip_b & 0xffff) as u16,
|
|
((ip_c >> 16) & 0xffff) as u16,
|
|
(ip_c & 0xffff) as u16,
|
|
((ip_d >> 16) & 0xffff) as u16,
|
|
(ip_d & 0xffff) as u16,
|
|
);
|
|
|
|
Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)))
|
|
} else {
|
|
Err(build_internal_error!(format!(
|
|
"Unable to parse {:?} as an address:port",
|
|
s
|
|
)))
|
|
}
|
|
}
|
|
|
|
/// Reads TCP socket table from the provided `reader`.
|
|
pub fn read_tcp_table<R: Read>(reader: BufReader<R>) -> ProcResult<Vec<TcpNetEntry>> {
|
|
let mut vec = Vec::new();
|
|
|
|
// first line is a header we need to skip
|
|
for line in reader.lines().skip(1) {
|
|
let line = line?;
|
|
let mut s = line.split_whitespace();
|
|
s.next();
|
|
let local_address = expect!(s.next(), "tcp::local_address");
|
|
let rem_address = expect!(s.next(), "tcp::rem_address");
|
|
let state = expect!(s.next(), "tcp::st");
|
|
let mut tx_rx_queue = expect!(s.next(), "tcp::tx_queue:rx_queue").splitn(2, ':');
|
|
let tx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::tx_queue"), 16);
|
|
let rx_queue = from_str!(u32, expect!(tx_rx_queue.next(), "tcp::rx_queue"), 16);
|
|
s.next(); // skip tr and tm->when
|
|
s.next(); // skip retrnsmt
|
|
s.next(); // skip uid
|
|
s.next(); // skip timeout
|
|
let inode = expect!(s.next(), "tcp::inode");
|
|
|
|
vec.push(TcpNetEntry {
|
|
local_address: parse_addressport_str(local_address)?,
|
|
remote_address: parse_addressport_str(rem_address)?,
|
|
rx_queue,
|
|
tx_queue,
|
|
state: expect!(TcpState::from_u8(from_str!(u8, state, 16))),
|
|
inode: from_str!(u64, inode),
|
|
});
|
|
}
|
|
|
|
Ok(vec)
|
|
}
|
|
|
|
/// Reads UDP socket table from the provided `reader`.
|
|
pub fn read_udp_table<R: Read>(reader: BufReader<R>) -> ProcResult<Vec<UdpNetEntry>> {
|
|
let mut vec = Vec::new();
|
|
|
|
// first line is a header we need to skip
|
|
for line in reader.lines().skip(1) {
|
|
let line = line?;
|
|
let mut s = line.split_whitespace();
|
|
s.next();
|
|
let local_address = expect!(s.next(), "udp::local_address");
|
|
let rem_address = expect!(s.next(), "udp::rem_address");
|
|
let state = expect!(s.next(), "udp::st");
|
|
let mut tx_rx_queue = expect!(s.next(), "udp::tx_queue:rx_queue").splitn(2, ':');
|
|
let tx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::tx_queue"), 16);
|
|
let rx_queue: u32 = from_str!(u32, expect!(tx_rx_queue.next(), "udp::rx_queue"), 16);
|
|
s.next(); // skip tr and tm->when
|
|
s.next(); // skip retrnsmt
|
|
s.next(); // skip uid
|
|
s.next(); // skip timeout
|
|
let inode = expect!(s.next(), "udp::inode");
|
|
|
|
vec.push(UdpNetEntry {
|
|
local_address: parse_addressport_str(local_address)?,
|
|
remote_address: parse_addressport_str(rem_address)?,
|
|
rx_queue,
|
|
tx_queue,
|
|
state: expect!(UdpState::from_u8(from_str!(u8, state, 16))),
|
|
inode: from_str!(u64, inode),
|
|
});
|
|
}
|
|
|
|
Ok(vec)
|
|
}
|
|
|
|
/// Reads the tcp socket table
|
|
pub fn tcp() -> ProcResult<Vec<TcpNetEntry>> {
|
|
let file = FileWrapper::open("/proc/net/tcp")?;
|
|
|
|
read_tcp_table(BufReader::new(file))
|
|
}
|
|
|
|
/// Reads the tcp6 socket table
|
|
pub fn tcp6() -> ProcResult<Vec<TcpNetEntry>> {
|
|
let file = FileWrapper::open("/proc/net/tcp6")?;
|
|
|
|
read_tcp_table(BufReader::new(file))
|
|
}
|
|
|
|
/// Reads the udp socket table
|
|
pub fn udp() -> ProcResult<Vec<UdpNetEntry>> {
|
|
let file = FileWrapper::open("/proc/net/udp")?;
|
|
|
|
read_udp_table(BufReader::new(file))
|
|
}
|
|
|
|
/// Reads the udp6 socket table
|
|
pub fn udp6() -> ProcResult<Vec<UdpNetEntry>> {
|
|
let file = FileWrapper::open("/proc/net/udp6")?;
|
|
|
|
read_udp_table(BufReader::new(file))
|
|
}
|
|
|
|
/// Reads the unix socket table
|
|
pub fn unix() -> ProcResult<Vec<UnixNetEntry>> {
|
|
let file = FileWrapper::open("/proc/net/unix")?;
|
|
let reader = BufReader::new(file);
|
|
|
|
let mut vec = Vec::new();
|
|
|
|
// first line is a header we need to skip
|
|
for line in reader.lines().skip(1) {
|
|
let line = line?;
|
|
let mut s = line.split_whitespace();
|
|
s.next(); // skip table slot number
|
|
let ref_count = from_str!(u32, expect!(s.next()), 16);
|
|
s.next(); // skip protocol, always zero
|
|
s.next(); // skip internal kernel flags
|
|
let socket_type = from_str!(u16, expect!(s.next()), 16);
|
|
let state = from_str!(u8, expect!(s.next()), 16);
|
|
let inode = from_str!(u64, expect!(s.next()));
|
|
let path = s.next().map(PathBuf::from);
|
|
|
|
vec.push(UnixNetEntry {
|
|
ref_count,
|
|
socket_type,
|
|
inode,
|
|
state: expect!(UnixState::from_u8(state)),
|
|
path,
|
|
});
|
|
}
|
|
|
|
Ok(vec)
|
|
}
|
|
|
|
/// An entry in the ARP table
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct ARPEntry {
|
|
/// IPv4 address
|
|
pub ip_address: Ipv4Addr,
|
|
/// Hardware type
|
|
///
|
|
/// This will almost always be ETHER (or maybe INFINIBAND)
|
|
pub hw_type: ARPHardware,
|
|
/// Internal kernel flags
|
|
pub flags: ARPFlags,
|
|
/// MAC Address
|
|
pub hw_address: Option<[u8; 6]>,
|
|
/// Device name
|
|
pub device: String,
|
|
}
|
|
|
|
bitflags! {
|
|
/// Hardware type for an ARP table entry.
|
|
// source: include/uapi/linux/if_arp.h
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct ARPHardware: u32 {
|
|
/// NET/ROM pseudo
|
|
const NETROM = 0;
|
|
/// Ethernet
|
|
const ETHER = 1;
|
|
/// Experimental ethernet
|
|
const EETHER = 2;
|
|
/// AX.25 Level 2
|
|
const AX25 = 3;
|
|
/// PROnet token ring
|
|
const PRONET = 4;
|
|
/// Chaosnet
|
|
const CHAOS = 5;
|
|
/// IEEE 802.2 Ethernet/TR/TB
|
|
const IEEE802 = 6;
|
|
/// Arcnet
|
|
const ARCNET = 7;
|
|
/// APPLEtalk
|
|
const APPLETLK = 8;
|
|
/// Frame Relay DLCI
|
|
const DLCI = 15;
|
|
/// ATM
|
|
const ATM = 19;
|
|
/// Metricom STRIP
|
|
const METRICOM = 23;
|
|
//// IEEE 1394 IPv4 - RFC 2734
|
|
const IEEE1394 = 24;
|
|
/// EUI-64
|
|
const EUI64 = 27;
|
|
/// InfiniBand
|
|
const INFINIBAND = 32;
|
|
}
|
|
}
|
|
|
|
bitflags! {
|
|
/// Flags for ARP entries
|
|
// source: include/uapi/linux/if_arp.h
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct ARPFlags: u32 {
|
|
/// Completed entry
|
|
const COM = 0x02;
|
|
/// Permanent entry
|
|
const PERM = 0x04;
|
|
/// Publish entry
|
|
const PUBL = 0x08;
|
|
/// Has requested trailers
|
|
const USETRAILERS = 0x10;
|
|
/// Want to use a netmask (only for proxy entries)
|
|
const NETMASK = 0x20;
|
|
// Don't answer this address
|
|
const DONTPUB = 0x40;
|
|
}
|
|
}
|
|
|
|
/// Reads the ARP table
|
|
pub fn arp() -> ProcResult<Vec<ARPEntry>> {
|
|
let file = FileWrapper::open("/proc/net/arp")?;
|
|
let reader = BufReader::new(file);
|
|
|
|
let mut vec = Vec::new();
|
|
|
|
// First line is a header we need to skip
|
|
for line in reader.lines().skip(1) {
|
|
// Check if there might have been an IO error.
|
|
let line = line?;
|
|
let mut line = line.split_whitespace();
|
|
let ip_address = expect!(Ipv4Addr::from_str(expect!(line.next())));
|
|
let hw = from_str!(u32, &expect!(line.next())[2..], 16);
|
|
let hw = ARPHardware::from_bits_truncate(hw);
|
|
let flags = from_str!(u32, &expect!(line.next())[2..], 16);
|
|
let flags = ARPFlags::from_bits_truncate(flags);
|
|
|
|
let mac = expect!(line.next());
|
|
let mut mac: Vec<Result<u8, _>> = mac.split(':').map(|s| Ok(from_str!(u8, s, 16))).collect();
|
|
|
|
let mac = if mac.len() == 6 {
|
|
let mac_block_f = mac.pop().unwrap()?;
|
|
let mac_block_e = mac.pop().unwrap()?;
|
|
let mac_block_d = mac.pop().unwrap()?;
|
|
let mac_block_c = mac.pop().unwrap()?;
|
|
let mac_block_b = mac.pop().unwrap()?;
|
|
let mac_block_a = mac.pop().unwrap()?;
|
|
if mac_block_a == 0
|
|
&& mac_block_b == 0
|
|
&& mac_block_c == 0
|
|
&& mac_block_d == 0
|
|
&& mac_block_e == 0
|
|
&& mac_block_f == 0
|
|
{
|
|
None
|
|
} else {
|
|
Some([
|
|
mac_block_a,
|
|
mac_block_b,
|
|
mac_block_c,
|
|
mac_block_d,
|
|
mac_block_e,
|
|
mac_block_f,
|
|
])
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// mask is always "*"
|
|
let _mask = expect!(line.next());
|
|
let dev = expect!(line.next());
|
|
|
|
vec.push(ARPEntry {
|
|
ip_address,
|
|
hw_type: hw,
|
|
flags,
|
|
hw_address: mac,
|
|
device: dev.to_string(),
|
|
})
|
|
}
|
|
|
|
Ok(vec)
|
|
}
|
|
|
|
/// General statistics for a network interface/device
|
|
///
|
|
/// For an example, see the [interface_stats.rs](https://github.com/eminence/procfs/tree/master/examples)
|
|
/// example in the source repo.
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct DeviceStatus {
|
|
/// Name of the interface
|
|
pub name: String,
|
|
/// Total bytes received
|
|
pub recv_bytes: u64,
|
|
/// Total packets received
|
|
pub recv_packets: u64,
|
|
/// Bad packets received
|
|
pub recv_errs: u64,
|
|
/// Packets dropped
|
|
pub recv_drop: u64,
|
|
/// Fifo overrun
|
|
pub recv_fifo: u64,
|
|
/// Frame alignment errors
|
|
pub recv_frame: u64,
|
|
/// Number of compressed packets received
|
|
pub recv_compressed: u64,
|
|
/// Number of multicast packets received
|
|
pub recv_multicast: u64,
|
|
|
|
/// Total bytes transmitted
|
|
pub sent_bytes: u64,
|
|
/// Total packets transmitted
|
|
pub sent_packets: u64,
|
|
/// Number of transmission errors
|
|
pub sent_errs: u64,
|
|
/// Number of packets dropped during transmission
|
|
pub sent_drop: u64,
|
|
pub sent_fifo: u64,
|
|
/// Number of collisions
|
|
pub sent_colls: u64,
|
|
/// Number of packets not sent due to carrier errors
|
|
pub sent_carrier: u64,
|
|
/// Number of compressed packets transmitted
|
|
pub sent_compressed: u64,
|
|
}
|
|
|
|
impl DeviceStatus {
|
|
fn from_str(s: &str) -> ProcResult<DeviceStatus> {
|
|
let mut split = s.split_whitespace();
|
|
let name: String = expect!(from_iter(&mut split));
|
|
let recv_bytes = expect!(from_iter(&mut split));
|
|
let recv_packets = expect!(from_iter(&mut split));
|
|
let recv_errs = expect!(from_iter(&mut split));
|
|
let recv_drop = expect!(from_iter(&mut split));
|
|
let recv_fifo = expect!(from_iter(&mut split));
|
|
let recv_frame = expect!(from_iter(&mut split));
|
|
let recv_compressed = expect!(from_iter(&mut split));
|
|
let recv_multicast = expect!(from_iter(&mut split));
|
|
let sent_bytes = expect!(from_iter(&mut split));
|
|
let sent_packets = expect!(from_iter(&mut split));
|
|
let sent_errs = expect!(from_iter(&mut split));
|
|
let sent_drop = expect!(from_iter(&mut split));
|
|
let sent_fifo = expect!(from_iter(&mut split));
|
|
let sent_colls = expect!(from_iter(&mut split));
|
|
let sent_carrier = expect!(from_iter(&mut split));
|
|
let sent_compressed = expect!(from_iter(&mut split));
|
|
|
|
Ok(DeviceStatus {
|
|
name: name.trim_end_matches(':').to_owned(),
|
|
recv_bytes,
|
|
recv_packets,
|
|
recv_errs,
|
|
recv_drop,
|
|
recv_fifo,
|
|
recv_frame,
|
|
recv_compressed,
|
|
recv_multicast,
|
|
sent_bytes,
|
|
sent_packets,
|
|
sent_errs,
|
|
sent_drop,
|
|
sent_fifo,
|
|
sent_colls,
|
|
sent_carrier,
|
|
sent_compressed,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Returns basic network device statistics for all interfaces
|
|
///
|
|
/// This data is from the `/proc/net/dev` file.
|
|
///
|
|
/// For an example, see the [interface_stats.rs](https://github.com/eminence/procfs/tree/master/examples)
|
|
/// example in the source repo.
|
|
pub fn dev_status() -> ProcResult<HashMap<String, DeviceStatus>> {
|
|
let file = FileWrapper::open("/proc/net/dev")?;
|
|
let buf = BufReader::new(file);
|
|
let mut map = HashMap::new();
|
|
// the first two lines are headers, so skip them
|
|
for line in buf.lines().skip(2) {
|
|
let dev = DeviceStatus::from_str(&line?)?;
|
|
map.insert(dev.name.clone(), dev);
|
|
}
|
|
|
|
Ok(map)
|
|
}
|
|
|
|
/// An entry in the ipv4 route table
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
|
|
pub struct RouteEntry {
|
|
/// Interface to which packets for this route will be sent
|
|
pub iface: String,
|
|
/// The destination network or destination host
|
|
pub destination: Ipv4Addr,
|
|
pub gateway: Ipv4Addr,
|
|
pub flags: u16,
|
|
/// Number of references to this route
|
|
pub refcnt: u16,
|
|
/// Count of lookups for the route
|
|
pub in_use: u16,
|
|
/// The 'distance' to the target (usually counted in hops)
|
|
pub metrics: u32,
|
|
pub mask: Ipv4Addr,
|
|
/// Default maximum transmission unit for TCP connections over this route
|
|
pub mtu: u32,
|
|
/// Default window size for TCP connections over this route
|
|
pub window: u32,
|
|
/// Initial RTT (Round Trip Time)
|
|
pub irtt: u32,
|
|
}
|
|
|
|
/// Reads the ipv4 route table
|
|
///
|
|
/// This data is from the `/proc/net/route` file
|
|
pub fn route() -> ProcResult<Vec<RouteEntry>> {
|
|
let file = FileWrapper::open("/proc/net/route")?;
|
|
let reader = BufReader::new(file);
|
|
|
|
let mut vec = Vec::new();
|
|
|
|
// First line is a header we need to skip
|
|
for line in reader.lines().skip(1) {
|
|
// Check if there might have been an IO error.
|
|
let line = line?;
|
|
let mut line = line.split_whitespace();
|
|
// network interface name, e.g. eth0
|
|
let iface = expect!(line.next());
|
|
let destination = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into();
|
|
let gateway = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into();
|
|
let flags = from_str!(u16, expect!(line.next()), 16);
|
|
let refcnt = from_str!(u16, expect!(line.next()), 10);
|
|
let in_use = from_str!(u16, expect!(line.next()), 10);
|
|
let metrics = from_str!(u32, expect!(line.next()), 10);
|
|
let mask = from_str!(u32, expect!(line.next()), 16).to_ne_bytes().into();
|
|
let mtu = from_str!(u32, expect!(line.next()), 10);
|
|
let window = from_str!(u32, expect!(line.next()), 10);
|
|
let irtt = from_str!(u32, expect!(line.next()), 10);
|
|
vec.push(RouteEntry {
|
|
iface: iface.to_string(),
|
|
destination,
|
|
gateway,
|
|
flags,
|
|
refcnt,
|
|
in_use,
|
|
metrics,
|
|
mask,
|
|
mtu,
|
|
window,
|
|
irtt,
|
|
});
|
|
}
|
|
|
|
Ok(vec)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::net::IpAddr;
|
|
|
|
#[test]
|
|
fn test_parse_ipaddr() {
|
|
use std::str::FromStr;
|
|
|
|
let addr = parse_addressport_str("0100007F:1234").unwrap();
|
|
assert_eq!(addr.port(), 0x1234);
|
|
match addr.ip() {
|
|
IpAddr::V4(addr) => assert_eq!(addr, Ipv4Addr::new(127, 0, 0, 1)),
|
|
_ => panic!("Not IPv4"),
|
|
}
|
|
|
|
// When you connect to [2a00:1450:4001:814::200e]:80 (ipv6.google.com) the entry with
|
|
// 5014002A14080140000000000E200000:0050 remote endpoint is created in /proc/net/tcp6
|
|
// on Linux 4.19.
|
|
let addr = parse_addressport_str("5014002A14080140000000000E200000:0050").unwrap();
|
|
assert_eq!(addr.port(), 80);
|
|
match addr.ip() {
|
|
IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2a00:1450:4001:814::200e").unwrap()),
|
|
_ => panic!("Not IPv6"),
|
|
}
|
|
|
|
// IPv6 test case from https://stackoverflow.com/questions/41940483/parse-ipv6-addresses-from-proc-net-tcp6-python-2-7/41948004#41948004
|
|
let addr = parse_addressport_str("B80D01200000000067452301EFCDAB89:0").unwrap();
|
|
assert_eq!(addr.port(), 0);
|
|
match addr.ip() {
|
|
IpAddr::V6(addr) => assert_eq!(addr, Ipv6Addr::from_str("2001:db8::123:4567:89ab:cdef").unwrap()),
|
|
_ => panic!("Not IPv6"),
|
|
}
|
|
|
|
let addr = parse_addressport_str("1234:1234");
|
|
assert!(addr.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_tcpstate_from() {
|
|
assert_eq!(TcpState::from_u8(0xA).unwrap(), TcpState::Listen);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tcp() {
|
|
for entry in tcp().unwrap() {
|
|
println!("{:?}", entry);
|
|
assert_eq!(entry.state, TcpState::from_u8(entry.state.to_u8()).unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_tcp6() {
|
|
for entry in tcp6().unwrap() {
|
|
println!("{:?}", entry);
|
|
assert_eq!(entry.state, TcpState::from_u8(entry.state.to_u8()).unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_udp() {
|
|
for entry in udp().unwrap() {
|
|
println!("{:?}", entry);
|
|
assert_eq!(entry.state, UdpState::from_u8(entry.state.to_u8()).unwrap());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_udp6() {
|
|
for entry in udp6().unwrap() {
|
|
println!("{:?}", entry);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_unix() {
|
|
for entry in unix().unwrap() {
|
|
println!("{:?}", entry);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_dev_status() {
|
|
let status = dev_status().unwrap();
|
|
println!("{:#?}", status);
|
|
}
|
|
|
|
#[test]
|
|
fn test_arp() {
|
|
for entry in arp().unwrap() {
|
|
println!("{:?}", entry);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_route() {
|
|
for entry in route().unwrap() {
|
|
println!("{:?}", entry);
|
|
}
|
|
}
|
|
}
|