libpnet
libpnet
使用 Rust 提供跨平台的低级网络 API。它包含四个关键组件:
packet
模块,支持数据包的安全构造和操作;
pnet_macros
库,为 packet 模块提供基础设施;
transport
模块,支持传输层协议的实现;
datalink
模块,支持直接发送和接收数据链路层数据包。
libpnet
?使用低级网络编程的原因有很多,使用 Rust 进行低级网络编程的原因更多。这里列出一些。
着手开发新传输层协议通常有两种方式:
用脚本语言,比如 Python,编写;
用 C 编写。
前者非常适合尝试新想法和快速原型设计,但不太适合实际应用。虽然通常可以从这些实现中获得合理的性能,但通常比 C 实现慢得多,不适合任何“繁重的工作”。
另一个选择是用 C 编写 - 这将带来很好的性能,但随之而来的是许多其它问题:
缺乏内存安全性 - 这是基于 C 的网络栈中安全漏洞和其它错误的重要原因。很容易忘记进行边界检查或在释放后使用指针。
缺乏线程安全性 - 必须确保使用正确的锁,以及正确使用。
缺乏高层次抽象 - 脚本语言(比如 Python)的部分吸引力是更高层次的抽象能力,这使得 API 更简单,编程更容易。
使用 libpnet
和 Rust,可以兼具两者的优点。具备高级抽象、内存和线程安全性,以及 C 的性能。
许多网络工具(比如 ping 和 traceroute)依赖于能够操作网络层和传输层头部,但标准网络栈(比如 std::io::net
提供的)不支持。
直接在数据链路层操作数据包有诸多好处,查看数据包在网络上传输时的原始形式。这样做有很多用途,包括网络诊断、数据包捕获和流量整形。
最新构建的文档在 https://docs.rs/pnet/。
为使用 libpnet
,向 Cargo.toml 添加如下内容:
[dependencies.pnet]
version = "0.34.0"
libpnet
应该与最新稳定版本的 Rust 兼容。
在运行测试套件时,一些网络测试可能失败 - 解决该问题的最简单方法是以 root 或管理员用户的身份运行 cargo test
。通常可以避免这种情况,但是需要引入更多的操作。
在 Windows 上构建有三个要求:
必须使用使用 MSVC 工具链的 Rust 版本。
必须安装 WinPcap 或 npcap(测试使用的版本为 WinPcap 4.1.3)(如果使用 npcap,确保使用"在 WinPcap API 兼容模式中,安装 Npcap"安装)
必须将来自 WinPcap Developers pack 的 Packet.lib
放在该仓库的根下名为 lib
的目录中。另外,还可以使用 %LIB%
或 $Env:LIB
环境变量中列出的任何位置。对于 64 位工具链,该文件位于 WpdPack/Lib/x64/Packet.lib
;对于 32 位工具链,位于 WpdPack/Lib/Packet.lib
。
https://github.com/libpnet/libpnet/tree/main/examples
x
use pnet::packet::ip::IpNextHeaderProtocols;
use pnet::packet::ipv4::{checksum, Ipv4Flags, Ipv4Packet, MutableIpv4Packet};
use pnet::packet::tcp::{ipv4_checksum, MutableTcpPacket, TcpFlags};
use pnet::transport::transport_channel;
use pnet::transport::TransportChannelType::Layer3;
use std::io;
use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
pub fn send_reset(
source: SocketAddrV4,
destination: SocketAddrV4,
sequence_number: u32,
acknowledgement_number: u32,
) -> io::Result<usize> {
let protocol = Layer3(IpNextHeaderProtocols::Tcp);
let (mut tx, mut _rx) = match transport_channel(4096, protocol) {
Ok((tx, rx)) => (tx, rx),
Err(e) => {
return Err(e);
}
};
// IP Header 中不包含选项
const IPV4_HEADER_LEN: usize = 20;
// TCP Header 中不包含选项,比如 TSVal 等
const TCP_HEADER_LEN: usize = 20;
const TOTAL_LENGTH: u16 = (IPV4_HEADER_LEN + TCP_HEADER_LEN) as _;
let mut packet = [0u8; IPV4_HEADER_LEN + TCP_HEADER_LEN];
{
let mut ip_header = MutableIpv4Packet::new(&mut packet[..]).unwrap();
ip_header.set_version(4);
ip_header.set_header_length(5);
ip_header.set_total_length(TOTAL_LENGTH);
ip_header.set_identification(257u16.to_be());
ip_header.set_flags(Ipv4Flags::DontFragment);
ip_header.set_fragment_offset(0);
ip_header.set_ttl(64);
ip_header.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
ip_header.set_source(source.ip().to_owned());
ip_header.set_destination(destination.ip().to_owned());
let imm_header = checksum(&ip_header.to_immutable());
ip_header.set_checksum(imm_header);
}
{
let mut tcp_header = MutableTcpPacket::new(&mut packet[IPV4_HEADER_LEN..]).unwrap();
tcp_header.set_source(source.port());
tcp_header.set_destination(destination.port());
tcp_header.set_sequence(sequence_number);
tcp_header.set_acknowledgement(acknowledgement_number);
tcp_header.set_data_offset(5); // 没有任何选项,因此 TCP Header 长度为 5 * 4 字节
tcp_header.set_flags(TcpFlags::RST);
tcp_header.set_window(29200);
let checksum = ipv4_checksum(&tcp_header.to_immutable(), source.ip(), destination.ip());
tcp_header.set_checksum(checksum);
}
match tx.send_to(
Ipv4Packet::new(&packet[..]).unwrap(),
IpAddr::V4(destination.ip().to_owned()),
) {
Ok(n) => Ok(n),
Err(e) => Err(e),
}
}
fn main() {
let n = send_reset(
SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 12345),
SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 45678),
783786044,
2198547145,
)
.unwrap();
println!("send ok[{:?}]", n);
}