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)
    }
}

代码说明

  1. 主函数

    • 分别查询TCP和UDP套接字信息
    • 调用query_sockets函数进行实际查询
  2. 查询函数

    • 创建和绑定netlink socket
    • 构建查询请求消息
    • 发送请求并接收响应
    • 处理不同类型的netlink消息
  3. 信息打印

    • print_socket_info函数格式化输出套接字信息
    • 解析本地和远程地址
    • 显示协议类型和状态(对TCP)
  4. 地址解析

    • parse_socket_addr函数处理IPv4和IPv6地址
    • 将网络字节序转换为可读格式

功能扩展

这个完整示例新增了以下功能:

  • 同时支持查询TCP和UDP套接字
  • 更友好的输出格式
  • IPv6地址支持
  • 状态信息显示(针对TCP)
  • 更完善的错误处理

注意事项

  1. 需要root权限运行
  2. 仅适用于Linux系统
  3. 输出信息可能很详细,建议重定向到文件或管道到less查看
  4. 对于大型系统,可能需要增加接收缓冲区大小

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(())
}

这个完整示例提供了一个交互式命令行工具,可以:

  1. 列出所有TCP套接字及其状态
  2. 实时监控已建立的TCP连接
  3. 列出所有UNIX域套接字及其inode和路径

每个功能都使用了netlink-packet-sock-diag库来与Linux内核通信,并格式化输出结果以便于阅读。

回到顶部