简介
pcap与libpcap
可以理解为, pcap是一种文件格式(其实是一种接口格式),其名称来源于“抓包”(p acket cap ture)
而libpcap是 类Unix系统中的一个函数库, 可以解析和处理pcap格式的文件. Windows上有类似的实现(WinPcap,npcap)
(pcap 是早期的网络抓包格式, 下一代抓包格式叫pcapng,二者可以通tcpdump或wireshark互转, 使用libpcap解析pcap和pcapng文件 )
很多语言写的网络工具,底层都需要调用机器上的libpcap库,比如之前用过的流量回放工具goreplay
网络相关的命令行工具汇
流量回放工
pnet
pnet是Rust语言中的一个网络库,主要用于构建网络应用程序。
pnet的主要功能和作用包括:
Rust的libpnet库底层使用了libpcap库来实现网络数据包捕获和处理的功能。
libpnet是一个基于Rust语言的网络编程库,提供了对网络协议的解析、构建和发送功能。它建立在libpcap(或者Windows上的WinPcap)之上,通过调用libpcap提供的底层功能来进行网络数据包捕获。
libpcap(Packet Capture Library)是一个跨平台的网络数据包捕获库,广泛用于网络分析和网络安全领域。它提供了一组API,允许开发人员在应用程序中以编程方式捕获和处理网络数据包。
libpnet库在其底层实现中使用libpcap来访问网络接口、捕获数据包、解析协议以及构建和发送数据包。这使得libpnet能够提供强大的网络编程功能,并且可以与现有的网络工具和库进行集成。
使用libpnet库时,需要确保安装了libpcap库及其开发包,以便在编译和运行时能够正确地链接和使用libpcap。
使用
github.com/libpnet/libpnet
Rust中非常多的网络工具依赖于pnet ,
例如,鸟窝老师写的一个类似ping的工具: 使用rust重写: 和Go版本mping比较
Rust 黑客编程 - ICMP 协议 ping 的简单实现
Rust初探: 实现一个Ping
获取本机活跃的网口名称(其实就是网卡,有实体的,也有虚拟的)
本部分内容参考自 rust 使用pnet获取本地活动的网卡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 use std::net::Ipv4Addr; use pnet::datalink; use pnet::ipnetwork; fn main () { let interfaces = datalink::interfaces (); for interface in interfaces { let ip : Vec <Ipv4Addr> = interface.ips.iter ().map (|ip| match ip { ipnetwork::IpNetwork::V4 (ref ipv4) => Ok (ipv4.ip ()), _ => Err ("" ), }).filter_map (Result ::ok).collect (); #[cfg(unix)] if !ip.is_empty () && !interface.is_loopback () && interface.is_running () && interface.is_up () { println! ("{}" , interface.name); } #[cfg(not(unix))] if !ip.is_empty () && !interface.is_loopback () && interface.is_running () && interface.is_up () { println! ("{}" , interface.name); } } }
上面这段代码的作用是获取本地计算机上的网络接口信息,并打印出满足特定条件的接口的名称。
使用datalink::interfaces()函数获取本地计算机上的所有网络接口信息,并将其存储在interfaces变量中。
针对每个网络接口进行迭代处理。
对于每个接口,提取其中的IPv4地址,并将其存储在ip变量中。
根据操作系统类型(Unix或非Unix),在满足以下条件的情况下打印接口的名称:
接口的IPv4地址列表非空。
接口不是回环接口(Loopback)。
接口正在运行且处于启用状态。
如果满足条件,将打印出满足条件的接口的名称。
用于获取本地计算机上的活跃网络接口,并输出满足特定条件的接口的名称。这在诸如网络监控、网络配置等应用场景非常有用。
关于”eth0”和”tun3”,这是两种不同类型的网络接口,简言之,”eth0”是一种物理以太网接口,通常用于常规的网络通信,而”tun3”是一种虚拟网络接口,通常用于建立安全的隧道连接。
二者详细的不同功能和特点:
eth0:
“eth0”是一种以太网接口,通常用于连接本地计算机与局域网或广域网的物理网络连接。
它是基于以太网协议(Ethernet)的网络接口,支持传输各种类型的数据包,如IP、TCP、UDP等。
“eth0”通常用于常规的网络通信,如通过网络访问互联网、与其他计算机进行通信等。
tun3:
“tun3”是一种虚拟网络接口,通常用于建立虚拟私有网络(VPN)或隧道连接。
它是在操作系统内核中创建的虚拟接口,可用于在公共网络上创建安全的、私密的通信通道。
“tun3”接口通过将数据包封装在其他协议中(如IPsec、OpenVPN等)来实现安全的通信。
“tun3”通常用于远程访问、跨网络连接、保护敏感数据等场景。
监听指定网络接口上的网络流量,并对接收到的数据包进行解析和处理
本部分内容参考自 007 Rust 网络编程,libpnet 库介绍
使用pnet库来实现网络数据包的捕获和解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 use pnet::datalink::Channel::Ethernet; use pnet::datalink::{self , NetworkInterface}; use pnet::packet::ethernet::{EtherTypes, EthernetPacket}; use pnet::packet::ip::IpNextHeaderProtocols; use pnet::packet::ipv4::Ipv4Packet; use pnet::packet::tcp::TcpPacket; use pnet::packet::Packet; use std::env; fn handle_packet (ethernet: &EthernetPacket) { match ethernet.get_ethertype () { EtherTypes::Ipv4 => { let header = Ipv4Packet::new (ethernet.payload ()); if let Some (header) = header { match header.get_next_level_protocol () { IpNextHeaderProtocols::Tcp => { let tcp = TcpPacket::new (header.payload ()); if let Some (tcp) = tcp { println! ( "Got a TCP packet {}:{} to {}:{}" , header.get_source (), tcp.get_source (), header.get_destination (), tcp.get_destination () ); } } _ => println! ("Ignoring non TCP packet" ), } } } _ => println! ("Ignoring non IPv4 packet" ), } } fn main () { let interface_name = env::args ().nth (1 ).unwrap (); let interfaces = datalink::interfaces (); let interface = interfaces .into_iter () .filter (|iface: &NetworkInterface| iface.name == interface_name) .next () .expect ("Error getting interface" ); let (_tx, mut rx) = match datalink::channel (&interface, Default ::default ()) { Ok (Ethernet (tx, rx)) => (tx, rx), Ok (_) => panic! ("Unhandled channel type" ), Err (e) => panic! ( "An error occurred when creating the datalink channel: {}" , e ), }; loop { match rx.next () { Ok (packet) => { let packet = EthernetPacket::new (packet).unwrap (); handle_packet (&packet); } Err (e) => { panic! ("An error occurred while reading: {}" , e); } } } }
执行sudo cargo run en0(网卡名),可以看到如下输出:
其中en0是要监听的网卡名称,可以通过ifconfig命令,或者第一部分的代码拿到
代码的执行流程如下:
导入所需的库和模块。
定义了一个handle_packet函数,用于处理接收到的数据包。在函数内部,它首先检查数据包的以太网类型,如果是IPv4数据包,则进一步解析IPv4头部。如果是TCP协议的数据包,则解析TCP头部,并打印源IP地址、源端口、目的IP地址和目的端口。
在main函数中,获取命令行参数中指定的网络接口名称。
调用datalink::interfaces()函数获取所有可用的网络接口列表,并根据指定的接口名称过滤出匹配的接口。
使用过滤得到的接口,调用datalink::channel函数创建一个以太网通道,用于接收数据包。
进入一个无限循环,在循环中不断接收数据包并调用handle_packet函数进行处理。
如果在接收数据包或处理过程中发生错误,将打印错误消息并退出程序。
通过这些,该代码就可以用于实时监听和分析指定网络接口上的TCP流量。
但是如果用来监听openvpn创建的虚拟网卡,则会报错:
1 2 pnet_datalink-0.34.0/src/bpf.rs:416:44: misaligned pointer dereference: address must be a multiple of 0x4 but is 0x11f809e0e
实现一个ping
本部分内容参考自 Rust 黑客编程 - ICMP 协议 ping 的简单实现
ping是最常用的网络诊断工具之一,用于测试机器之间的连通性。其通过向目标主机发送ICMP(Internet Control Message Protocol)回显请求消息,并等待目标主机返回回显应答消息来判断主机之间是否能够相互通信。ping命令在网络故障排除、网络性能测试以及测量网络延迟和丢包率等方面非常有用。
ICMP是一种网络层协议,在网络协议栈中位于IP协议的上层。因此,ping命令作用在网络层(第3层,网络层).
其实准确来说,是3.5层,ICMP协议的报头从IP报头的第160位开始(IP首部20字节)
ICMP是包含在IP数据包中的,但是对ICMP消息通常会特殊处理,会和一般IP数据包的处理不同,而不是作为IP的一个子协议来处理
图片来自 Rust 黑客编程 - ICMP 协议 ping 的简单实现
关于ICMP,更多参考 互联网控制消息协议
ping使用ICMP消息作为通信的载体,通过向目标主机发送ICMP Echo Request消息,并等待目标主机返回ICMP Echo Reply消息来测试网络连通性。
很多常用的工具是基于ICMP消息的。ping 和 traceroute 是两个典型.
traceroute 是通过发送包含有特殊的TTL的包,然后接收ICMP超时消息和目标不可达消息来实现的。
ping 则是用ICMP的”Echo request”(类别代码:8)和”Echo reply”(类别代码:0)消息来实现的。
另外mtr其实相当于增强版的traceroute:
My Traceroute (MTR) 是一个结合了traceroute 和ping 的工具,这是测试网络连接和速度的另一个常用方法。 除了网络路径上的跃点外,MTR 还显示到目的地的路线中不断更新的延迟和丢包信息。 可以实时看到路径上发生的情况,协助排除网络问题
什么是 My Traceroute (MTR)?
下面这些代码的作用,是创建和发送 ICMP Echo 请求(通常被称为 ping)并接收响应。程序使用 pnet 库来处理网络通信
程序会不断发送 ICMP Echo 请求到指定的 IP 地址,并等待接收回复。收到回复后,它会打印出从发送到接收回复的往返时间(RTT)。相当于自己实现了常见的网络诊断工具ping,用于测试网络连接的质量和速度。
Cargo.toml:
1 2 3 4 5 6 7 8 9 10 11 12 [package] name = "pnet" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.79" pnet = "0.34.0" pnet_transport = "0.34.0" rand = "0.8.5"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 use pnet::packet::{ icmp::{ echo_reply::EchoReplyPacket, echo_request::{IcmpCodes, MutableEchoRequestPacket}, IcmpTypes, }, ip::IpNextHeaderProtocols, util, Packet, }; use pnet_transport::icmp_packet_iter;use pnet_transport::TransportChannelType::Layer4;use pnet_transport::{transport_channel, TransportProtocol};use rand::random;use std::{ env, net::IpAddr, sync::{Arc, RwLock}, time::{Duration, Instant}, }; const ICMP_SIZE: usize = 64 ; fn main () -> anyhow::Result <()> { let args : Vec <String > = env::args ().collect (); if args.len () < 2 { panic! ("Usage: icmp-demo target_ip" ); } let target_ip : IpAddr = args[1 ].parse ().unwrap (); println! ("icpm echo request to target ip:{:#?}" , target_ip); let protocol = Layer4 (TransportProtocol::Ipv4 (IpNextHeaderProtocols::Icmp)); let (mut tx, mut rx) = match transport_channel (4096 , protocol) { Ok ((tx, rx)) => (tx, rx), Err (e) => return Err (e.into ()), }; let mut iter = icmp_packet_iter (&mut rx); loop { let mut icmp_header : [u8 ; ICMP_SIZE] = [0 ; ICMP_SIZE]; let icmp_packet = create_icmp_packet (&mut icmp_header); let timer = Arc::new (RwLock::new (Instant::now ())); tx.send_to (icmp_packet, target_ip)?; match iter.next () { Ok ((packet, addr)) => match EchoReplyPacket::new (packet.packet ()) { Some (echo_reply) => { if packet.get_icmp_type () == IcmpTypes::EchoReply { let start_time = timer.read ().unwrap (); let rtt = Instant::now ().duration_since (*start_time); println! ( "ICMP EchoReply received from {:?}: {:?} , Time:{:?}" , addr, packet.get_icmp_type (), rtt ); } else { println! ( "ICMP type other than reply (0) received from {:?}: {:?}" , addr, packet.get_icmp_type () ); } } None => {} }, Err (e) => { println! ("An error occurred while reading: {}" , e); } } std::thread::sleep (Duration::from_millis (500 )); } Ok (()) } fn create_icmp_packet <'a >(icmp_header: &'a mut [u8 ]) -> MutableEchoRequestPacket<'a > { let mut icmp_packet = MutableEchoRequestPacket::new (icmp_header).unwrap (); icmp_packet.set_icmp_type (IcmpTypes::EchoRequest); icmp_packet.set_icmp_code (IcmpCodes::NoCode); icmp_packet.set_identifier (random::<u16 >()); icmp_packet.set_sequence_number (1 ); let checksum = util::checksum (icmp_packet.packet (), 1 ); icmp_packet.set_checksum (checksum); icmp_packet }
1 2 3 cargo build sudo ./target/debug/pnet 8.8.8.8
因为之前wireshark侦听的是eth0这个网卡,如果”ping” 127.0.0.1,就看不到任何数据包了.
而修改wireshark的网卡为lo这个网卡后,再”ping” 127.0.0.1,就可以看到数据包
原文链接: https://dashen.tech/2017/01/09/Rust-pnet库的使用/
版权声明: 转载请注明出处.