9 个版本
0.6.1 | 2024年5月19日 |
---|---|
0.4.1 | 2022年3月10日 |
0.4.0 | 2022年2月9日 |
0.2.4 | 2021年7月10日 |
0.1.0 |
|
#120 in Unix APIs
239 monthly downloads
100KB
2K SLoC
xsk-rs
使用 libxdp 的 Rust 接口,用于 Linux AF_XDP 套接字。
最初受 Jesse DuMond 的 OCaml 实现 的启发。
示例
一些示例可以在 examples
目录中找到。在 examples/hello_xdp.rs
中可以找到一个简单的示例,该示例通过 veth 对展示在两个套接字之间移动字节,而一个稍微复杂一些的示例(也通过 veth 对)在 examples/dev2_to_dev1.rs
中发送和接收 eth 帧,其中包含单线程和多线程实现。请注意,由于将套接字绑定到 veth 对,因此这些示例将不会反映实际性能。
一个使用共享 UMEM 的示例在 examples/shared_umem.rs
中。
运行测试/示例
运行测试或示例可能需要 root 权限,因为它们需要设置 veth 对。但是,为了避免在 root
下运行 cargo,最好首先构建测试/示例,然后直接运行二进制文件。
# tests
cargo build --tests
sudo run_all_tests.sh
# examples
cargo build --examples --release
sudo target/release/examples/hello_xdp
sudo target/release/examples/dev1_to_dev2 -- [FLAGS] [OPTIONS]
兼容性
在运行 Linux 内核版本 6.5.0 的 64 位机器上进行了测试。
安全性
在使用此库时,涉及到相当多的不安全操作,因此存在灾难性的可能性。然而,如果您记住以下内容,则应该很少有灾难性的途径
- 当一个帧/地址被提交到填充队列或 tx 环时,不要再次使用它,直到您已从完成队列或 rx 环中消费它。
- 不要使用一个 UMEM 的帧描述符来访问另一个不同 UMEM 的帧。
用法
以下示例从一个接口向另一个接口发送数据包。
use std::{convert::TryInto, io::Write};
use xsk_rs::{
config::{SocketConfig, UmemConfig},
socket::Socket,
umem::Umem,
};
const ETHERNET_PACKET: [u8; 42] = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf6, 0xe0, 0xf6, 0xc9, 0x60, 0x0a, 0x08, 0x06, 0x00, 0x01,
0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0xf6, 0xe0, 0xf6, 0xc9, 0x60, 0x0a, 0xc0, 0xa8, 0x45, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x45, 0xfe,
];
fn main() {
// Create a UMEM for dev1 with 32 frames, whose sizes are
// specified via the `UmemConfig` instance.
let (dev1_umem, mut dev1_descs) =
Umem::new(UmemConfig::default(), 32.try_into().unwrap(), false)
.expect("failed to create UMEM");
// Bind an AF_XDP socket to the interface named `xsk_dev1`, on
// queue 0.
let (mut dev1_tx_q, _dev1_rx_q, _dev1_fq_and_cq) = unsafe {
Socket::new(
SocketConfig::default(),
&dev1_umem,
&"xsk_dev1".parse().unwrap(),
0,
)
}
.expect("failed to create dev1 socket");
// Create a UMEM for dev2. Another option is to use the same UMEM
// as dev1 - to do that we'd just pass `dev1_umem` to the
// `Socket::new` call. In this case the UMEM would be shared, and
// so `dev1_descs` could be used in either context, but each
// socket would have its own completion queue and fill queue.
let (dev2_umem, mut dev2_descs) =
Umem::new(UmemConfig::default(), 32.try_into().unwrap(), false)
.expect("failed to create UMEM");
// Bind an AF_XDP socket to the interface named `xsk_dev2`, on
// queue 0.
let (_dev2_tx_q, mut dev2_rx_q, dev2_fq_and_cq) = unsafe {
Socket::new(
SocketConfig::default(),
&dev2_umem,
&"xsk_dev2".parse().unwrap(),
0,
)
}
.expect("failed to create dev2 socket");
let (mut dev2_fq, _dev2_cq) = dev2_fq_and_cq.expect("missing dev2 fill queue and comp queue");
// 1. Add frames to dev2's fill queue so we are ready to receive
// some packets.
unsafe {
dev2_fq.produce(&dev2_descs);
}
// 2. Write to dev1's UMEM.
let pkt = "Hello, world!".as_bytes();
unsafe {
dev1_umem
.data_mut(&mut dev1_descs[0])
.cursor()
.write_all(pkt)
.expect("failed writing packet to frame")
}
// 3. Submit the frame to the kernel for transmission.
println!("sending packet");
unsafe {
dev1_tx_q.produce_and_wakeup(&dev1_descs[..1]).unwrap();
}
// 4. Read on dev2.
let pkts_recvd = unsafe { dev2_rx_q.poll_and_consume(&mut dev2_descs, 100).unwrap() };
// 5. Confirm that one of the packets we received matches what we expect.
for recv_desc in dev2_descs.iter().take(pkts_recvd) {
let data = unsafe { dev2_umem.data(recv_desc) };
if data.contents() == ÐERNET_PACKET {
println!("received packet!");
return;
}
}
panic!("no matching packets received")
}
依赖关系
~3-5MB
~100K SLoC