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 2020年10月1日

#120 in Unix APIs

Download history 30/week @ 2024-05-07 330/week @ 2024-05-14 66/week @ 2024-05-21 181/week @ 2024-05-28 134/week @ 2024-06-04 83/week @ 2024-06-11 48/week @ 2024-06-18 215/week @ 2024-06-25 180/week @ 2024-07-02 143/week @ 2024-07-09 64/week @ 2024-07-16 11/week @ 2024-07-23 72/week @ 2024-07-30 85/week @ 2024-08-06

239 monthly downloads

MIT 许可证

100KB
2K SLoC

xsk-rs

使用 libxdp 的 Rust 接口,用于 Linux AF_XDP 套接字。

API 文档.

有关更多信息,请参阅网络文档或更详细的概述

最初受 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() == &ETHERNET_PACKET {
            println!("received packet!");
            return;
        }
    }

    panic!("no matching packets received")
}

依赖关系

~3-5MB
~100K SLoC