Rust网络诊断库netlink-packet-sock-diag的使用:Linux套接字状态监控与网络连接分析
Rust网络诊断库netlink-packet-sock-diag的使用:Linux套接字状态监控与网络连接分析
简介
netlink-packet-sock-diag
是一个用于处理Linux sock_diag子协议的Rust netlink数据包类型库。它允许开发者通过Linux的netlink接口监控套接字状态和分析网络连接。
安装
在Cargo.toml中添加以下依赖:
netlink-packet-sock-diag = "0.4.2"
或者运行以下命令:
cargo add netlink-packet-sock-diag
使用示例
下面是内容中提供的示例代码:
use netlink_packet_core::{
NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST,
};
use netlink_packet_sock_diag::{
constants::SOCK_DIAG_BY_FAMILY as SOCK_DIAG_MSG,
InetRequest, SocketId, SockDiagMessage,
};
use netlink_sys::{protocols::NETLINK_INET_DIAG, Socket, SocketAddr};
fn main() -> Result<(), String> {
// 创建netlink socket
let mut socket = Socket::new(NETLINK_INET_DIAG)
.map_err(|e| format!("Failed to create socket: {}", e))?;
// 绑定socket
socket.bind(&SocketAddr::new(0, 0))
.map_err(|e| format!("Failed to bind socket: {}", e))?;
// 准备请求消息
let mut msg = NetlinkMessage::from(SockDiagMessage::InetRequest(InetRequest {
header: SOCK_DIAG_MSG,
flags: NLM_F_REQUEST | NLM_F_DUMP,
// 查询所有TCP套接字
family: libc::AF_INET as u8,
protocol: libc::IPPROTO_TCP as u8,
ext: 0,
states: 0xffffffff, // 所有状态
id: SocketId::default(),
}));
// 设置消息头
msg.header.flags = NLM_F_REQUEST | NLM_F_DUMP;
msg.header.sequence_number = 1;
// 发送请求
socket.send(&msg, &SocketAddr::new(0, 0))
.map_err(|e| format!("Failed to send message: {}", e))?;
// 接收响应
let mut receive_buffer = vec![0; 4096];
let mut offset = 0;
loop {
let size = socket.recv(&mut receive_buffer[offset..], 0)
.map_err(|e| format!("Failed to receive message: {}", e))?;
if size == 0 {
break;
}
offset += size;
// 解析接收到的消息
while offset >= NetlinkMessage::<SockDiagMessage>::header_len() {
let bytes = &receive_buffer[..offset];
let parsed = NetlinkMessage::<SockDiagMessage>::deserialize(bytes)
.map_err(|e| format!("Failed to parse message: {}", e))?;
match parsed.payload {
NetlinkPayload::InnerMessage(SockDiagMessage::InetResponse(response)) => {
println!("Found socket: {:?}", response);
}
NetlinkPayload::Error(err) => {
return Err(format!("Received error message: {:?}", err));
}
NetlinkPayload::Done(_) => {
println!("Done receiving messages");
return Ok(());
}
_ => {}
}
// 移除已处理的消息
let consumed = parsed.header.length as usize;
receive_buffer.drain(..consumed);
offset -= consumed;
}
}
Ok(())
}
完整示例代码
下面是一个更完整的示例,增加了对UDP套接字的查询和更详细的输出:
use netlink_packet_core::{NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST};
use netlink_packet_sock_diag::{
constants::{SOCK_DIAG_BY_FAMILY as SOCK_DIAG_MSG, TCP_ESTABLISHED, TCP_LISTEN},
InetRequest, InetResponse, SocketId, SockDiagMessage,
};
use netlink_sys::{protocols::NETLINK_INET_DIAG, Socket, SocketAddr};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr as StdSocketAddr};
fn main() -> Result<(), String> {
// 查询TCP套接字
println!("=== TCP Sockets ===");
query_sockets(libc::IPPROTO_TCP)?;
// 查询UDP套接字
println!("\n=== UDP Sockets ===");
query_sockets(libc::IPPROTO_UDP)?;
Ok(())
}
fn query_sockets(protocol: i32) -> Result<(), String> {
// 创建并绑定socket
let mut socket = Socket::new(NETLINK_INET_DIAG)
.map_err(|e| format!("Failed to create socket: {}", e))?;
socket.bind(&SocketAddr::new(0, 0))
.map_err(|e| format!("Failed to bind socket: {}", e))?;
// 构建请求消息
let mut msg = NetlinkMessage::from(SockDiagMessage::InetRequest(InetRequest {
header: SOCK_DIAG_MSG,
flags: NLM_F_REQUEST | NLM_F_DUMP,
family: libc::AF_INET as u8, // IPv4
protocol: protocol as u8,
ext: 0,
states: 0xffffffff, // 所有状态
id: SocketId::default(),
}));
msg.header.flags = NLM_F_REQUEST | NLM_F_DUMP;
msg.header.sequence_number = 1;
// 发送请求
socket.send(&msg, &SocketAddr::new(0, 0))
.map_err(|e| format!("Failed to send message: {}", e))?;
// 接收和处理响应
let mut buf = vec![0; 4096];
let mut offset = 0;
loop {
let size = socket.recv(&mut buf[offset..], 0)
.map_err(|e| format!("Failed to receive message: {}", e))?;
if size == 0 { break; }
offset += size;
while offset >= NetlinkMessage::<SockDiagMessage>::header_len() {
let bytes = &buf[..offset];
let parsed = NetlinkMessage::<SockDiagMessage>::deserialize(bytes)
.map_err(|e| format!("Failed to parse message: {}", e))?;
match parsed.payload {
NetlinkPayload::InnerMessage(SockDiagMessage::InetResponse(res)) => {
print_socket_info(&res);
}
NetlinkPayload::Error(err) => {
return Err(format!("Error: {:?}", err));
}
NetlinkPayload::Done(_) => return Ok(()),
_ => {}
}
let consumed = parsed.header.length as usize;
buf.drain(..consumed);
offset -= consumed;
}
}
Ok(())
}
fn print_socket_info(res: &InetResponse) {
let protocol = match res.protocol {
libc::IPPROTO_TCP => "TCP",
libc::IPPROTO_UDP => "UDP",
_ => "UNKNOWN",
};
let state = if res.protocol == libc::IPPROTO_TCP {
match res.state {
TCP_ESTABLISHED => "ESTABLISHED",
TCP_LISTEN => "LISTEN",
_ => "OTHER",
}
} else {
""
};
let local_addr = parse_socket_addr(&res.id.source_port, &res.id.source);
let remote_addr = parse_socket_addr(&res.id.destination_port, &res.id.destination);
println!("Protocol: {} {}", protocol, state);
println!("Local: {}", local_addr);
println!("Remote: {}", remote_addr);
println!("Inode: {}", res.id.inode);
println!("---");
}
fn parse_socket_addr(port: &u16, addr: &[u8; 8]) -> String {
let port = u16::from_be(*port);
// IPv4地址
if addr[4..8].iter().any(|&b| b != 0) {
let ip = Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]);
format!("{}:{}", ip, port)
}
// IPv6地址
else {
let ip = Ipv6Addr::from([
addr[0], addr[1], addr[2], addr[3],
addr[4], addr[5], addr[6], addr[7]
]);
format!("{}:{}", ip, port)
}
}
代码说明
-
主函数:
- 分别查询TCP和UDP套接字信息
- 调用
query_sockets
函数进行实际查询
-
查询函数:
- 创建和绑定netlink socket
- 构建查询请求消息
- 发送请求并接收响应
- 处理不同类型的netlink消息
-
信息打印:
print_socket_info
函数格式化输出套接字信息- 解析本地和远程地址
- 显示协议类型和状态(对TCP)
-
地址解析:
parse_socket_addr
函数处理IPv4和IPv6地址- 将网络字节序转换为可读格式
功能扩展
这个完整示例新增了以下功能:
- 同时支持查询TCP和UDP套接字
- 更友好的输出格式
- IPv6地址支持
- 状态信息显示(针对TCP)
- 更完善的错误处理
注意事项
- 需要root权限运行
- 仅适用于Linux系统
- 输出信息可能很详细,建议重定向到文件或管道到less查看
- 对于大型系统,可能需要增加接收缓冲区大小
1 回复
Rust网络诊断库netlink-packet-sock-diag的使用:Linux套接字状态监控与网络连接分析
netlink-packet-sock-diag
是一个Rust库,用于通过Linux的netlink接口访问套接字诊断信息。它允许开发者以编程方式获取Linux系统中套接字的状态和统计信息,非常适合网络监控、诊断工具和连接分析应用的开发。
主要功能
- 查询系统中所有打开的套接字
- 获取TCP/UDP/UNIX域套接字的详细信息
- 监控套接字状态变化
- 分析网络连接统计信息
使用方法
添加依赖
首先在Cargo.toml中添加依赖:
[dependencies]
netlink-packet-sock-diag = "0.2"
netlink-sys = "0.8"
基本示例:列出所有TCP套接字
use netlink_packet_sock_diag::{
constants::*,
inet::{InetRequest, SocketId},
SockDiagMessage,
};
use netlink_sys::{protocols::NETLINK_SOCK_DIAG, Socket, SocketAddr};
fn list_tcp_sockets() -> Result<(), String> {
// 创建netlink套接字
let mut socket = Socket::new(NETLINK_SOCK_DIAG)
.map_err(|e| format!("Failed to create socket: {}", e))?;
// 连接到内核
let kernel_addr = SocketAddr::new(0, 0);
socket.bind(&kernel_addr)
.map_err(|e| format!("Failed to bind: {}", e))?;
// 构建请求消息
let mut msg = SockDiagMessage::new_inet_request(InetRequest {
header: SockDiagHeader {
family极问题,建议适当过滤
- 某些信息可能因内核版本不同而有所变化
通过`netlink-packet-sock-diag`库,Rust开发者可以方便地构建强大的网络诊断工具,深入分析Linux系统的网络连接状态。
## 完整示例代码
下面是一个完整的网络连接监控工具示例,结合了上述所有功能:
```rust
use netlink_packet_sock_diag::{
constants::*,
inet::{InetRequest, InetResponse, SocketId},
unix::{UnixRequest, UnixResponse},
SockDiagHeader, SockDiagMessage,
};
use netlink_sys::{protocols::NETLINK_SOCK_DIAG, Socket, SocketAddr};
use std::process;
fn main() {
println!("Linux Socket Monitor Tool");
println!("1. List all TCP sockets");
println!("2. Monitor established TCP connections");
println!("3. List UNIX domain sockets");
println!("4. Exit");
loop {
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.expect("Failed to read input");
match input.trim() {
"1" => {
if let Err(e) = list_tcp_sockets() {
eprintln!("Error: {}", e);
}
}
"2" => {
if let Err(e) = monitor_established_connections() {
eprintln!("Error: {}", e);
}
}
"3" => {
if let Err(e) = list_unix_sockets() {
eprintln!("Error: {}", e);
}
}
"4" => process::exit(0),
_ => println!("Invalid option"),
}
}
}
fn list_tcp_sockets() -> Result<(), String> {
let mut socket = Socket::new(NETLINK_SOCK_DIAG)
.map_err(|e| format!("Failed to create socket: {}", e))?;
let kernel_addr = SocketAddr::new(0, 0);
socket.bind(&kernel_addr)
.map_err(|e| format!("Failed to bind: {}", e))?;
let mut msg = SockDiagMessage::new_inet_request(InetRequest {
header: SockDiagHeader {
family: AF_INET as u8,
protocol: IPPROTO_TCP as u8,
states: (1 << TCP_ESTABLISHED) | (1 << TCP_LISTEN) | (1 << TCP_CLOSE_WAIT),
..Default::default()
},
socket_id: SocketId::default(),
});
socket.send(&msg)
.map_err(|e| format!("Failed to send request: {}", e))?;
println!("TCP Sockets:");
println!("{:<20} {:<10} {:<20} {:<10} {:<15}", "Source IP", "Port", "Dest IP", "Port", "State");
loop {
let mut buf = vec![0; 4096];
let len = match socket.recv(&mut buf, 0) {
Ok(len) => len,
Err(_) => break, // 接收结束
};
let response = SockDiagMessage::parse(&buf[..len])
.map_err(|e| format!("Failed to parse response: {}", e))?;
if let SockDiagMessage::InetResponse(InetResponse { header, socket_id, .. }) = response {
let state = match header.states {
s if s == (1 << TCP_ESTABLISHED) => "ESTABLISHED",
s if s == (1 << TCP_LISTEN) => "LISTEN",
s if s == (1 << TCP_CLOSE_WAIT) => "CLOSE_WAIT",
_ => "UNKNOWN",
};
println!(
"{:<20} {:<10} {:<20} {:<10} {:<15}",
socket_id.source.ip(),
socket_id.source.port(),
socket_id.destination.ip(),
socket_id.destination.port(),
state
);
}
}
Ok(())
}
fn monitor_established_connections() -> Result<(), String> {
let mut socket = Socket::new(NETLINK_SOCK_DIAG)
.map_err(|e| format!("Failed to create socket: {}", e))?;
let kernel_addr = SocketAddr::new(0, 0);
socket.bind(&kernel_addr)
.map_err(|e| format!("Failed to bind: {}", e))?;
let mut msg = SockDiagMessage::new_inet_request(InetRequest {
header: SockDiagHeader {
family: AF_INET as u8,
protocol: IPPROTO_TCP as u8,
states: 1 << TCP_ESTABLISHED,
..Default::default()
},
socket_id: SocketId::default(),
});
socket.send(&msg)
.map_err(|e| format!("Failed to send request: {}", e))?;
println!("Monitoring established TCP connections...");
println!("Press Ctrl+C to stop");
println!("{:<20} {:<10} {:<20} {:<10}", "Source IP", "Port", "Dest IP", "Port");
loop {
let mut buf = vec![0; 4096];
let len = socket.recv(&mut buf, 0)
.map_err(|e| format!("Failed to receive: {}", e))?;
let response = SockDiagMessage::parse(&buf[..len])
.map_err(|e| format!("Failed to parse: {}", e))?;
if let SockDiagMessage::InetResponse(InetResponse { socket_id, .. }) = response {
println!(
"{:<20} {:<10} {:<20} {:<10}",
socket_id.source.ip(),
socket_id.source.port(),
socket_id.destination.ip(),
socket_id.destination.port()
);
}
}
}
fn list_unix_sockets() -> Result<(), String> {
let mut socket = Socket::new(NETLINK_SOCK_DIAG)
.map_err(|e| format!("Failed to create socket: {}", e))?;
let kernel_addr = SocketAddr::new(0, 0);
socket.bind(&kernel_addr)
.map_err(|e| format!("Failed to bind: {}", e))?;
let mut msg = SockDiagMessage::new_unix_request(UnixRequest {
header: SockDiagHeader {
family: AF_UNIX as u8,
..Default::default()
},
states: 0xffffffff,
});
socket.send(&msg)
.map_err(|e| format!("Failed to send request: {}", e))?;
println!("UNIX Domain Sockets:");
println!("{:<15} {:<50}", "Inode", "Path");
loop {
let mut buf = vec![0; 4096];
let len = match socket.recv(&mut buf, 0) {
Ok(len) => len,
Err(_) => break,
};
let response = SockDiagMessage::parse(&buf[..len])
.map_err(|e| format!("Failed to parse response: {}", e))?;
if let SockDiagMessage::UnixResponse(UnixResponse { attributes, .. }) = response {
let path = attributes.path.as_deref().unwrap_or("");
println!("{:<15} {:<50}", attributes.inode.unwrap_or(0), path);
}
}
Ok(())
}
这个完整示例提供了一个交互式命令行工具,可以:
- 列出所有TCP套接字及其状态
- 实时监控已建立的TCP连接
- 列出所有UNIX域套接字及其inode和路径
每个功能都使用了netlink-packet-sock-diag
库来与Linux内核通信,并格式化输出结果以便于阅读。