2 个稳定版本
1.0.1 | 2024 年 4 月 8 日 |
---|---|
1.0.0 | 2024 年 4 月 4 日 |
#250 in Unix APIs
26 monthly downloads
4MB
101K SLoC
Packetvisor(PV)
系统依赖
- clang 版本 >= 11
- llvm 版本 >= 11
bpftool & XDP-tools (submodules)
PV 使用 libxdp
在 XDP-tools 中。
构建库
您可以使用 cargo build -r
命令来构建 PV 库,库文件将位于 target/release/
。
入门指南
本指南将引导您通过使用 PV 库编写的示例源代码进行编译和使用的过程。
以下说明将基于 Echo 示例。
请先安装依赖包。
# Install build dependencies
$ sudo apt-get install llvm clang libelf-dev gcc-multilib libpcap-dev m4 make curl
# Install Rust
$ curl https://sh.rustup.rs -sSf | sh
$ source "$HOME/.cargo/env"
然后编译示例源代码。
# Compile all examples.
$ cargo build -r --examples
# Compile the specific example.
$ cargo build -r --example echo
编译后的示例二进制文件位于 target/release/examples/
目录。
在运行 Echo 示例之前,您需要创建一个 Linux 网络命名空间。
我们为此提供了两个脚本
examples/set_veth.sh
: 创建两个 Linux 网络命名空间。examples/unset_veth.sh
: 删除之前创建的两个 Linux 网络命名空间。
运行 examples/set_veth.sh
脚本在主机内部创建了两个命名空间(test1 和 test2)。
# Host veth0 is connected to veth1 in the test1 namespace.
# Host veth2 is connected to veth3 in the test2 namespace.
+-----------+ +-----------+
| test1 | | test2 |
+--[veth1]--+ +--[veth3]--+
| |
+--[veth0]----------------[veth2]--+
| Host |
+----------------------------------+
在 Echo 示例测试中只使用了 test1 命名空间。
现在,打开两个终端。
- 在一个终端(TERM1)中,在 test1 命名空间的 veth1 接口上运行 Echo。
- 在另一个终端(TERM2)中,从主机发送 ARP、ICMP 和 UDP 数据包到 test1。
总结来说,主机和 test1 之间执行 ARP、ICMP 和 UDP Echo(Ping-Pong)。
请在 TERM1 和 TERM2 上执行以下命令。
# Working directory on TERM1 is packetvisor/.
(TERM1) $ sudo ip netns exec test1 /bin/bash
(TERM1) $ ./target/release/examples/echo veth1
-------------------------------------------------
(TERM2) $ sudo apt-get install arping 2ping
# ARP echo test
(TERM2) $ sudo arping 10.0.0.5
# ICMP echo test
(TERM2) $ ping 10.0.0.5
# UDP echo test
(TERM2) $ 2ping 10.0.0.5 --port 7
现在您可以在 TERM1 的日志中看到 ARP、ICMP 和 UDP(端口 7)数据包在 test1 命名空间中被回显了!
关于 XSK(XDP Socket)的描述
XSK 的元素
- TX 环:包含要发送的包的描述符
- RX 环:包含已接收的包的描述符
- 完成环(= CQ,完成队列):用于保存成功发送的数据包块
- 填充环(= FQ,填充队列):用于分配接收数据包的块
RX 端过程
用户应在接收数据包并放入之前,预先分配空块或准备使用的块。以下步骤描述了如何通过 XSK
接收数据包。
- 用户通过
xsk_ring_prod__reserve()
通知内核在FQ
中预留多少个槽位,然后该函数将返回预留槽位的数量和index
值,即预留槽位在环中的第一个索引。 - 用户将
index
放入xsk_ring_prod__fill_addr()
作为参数,函数将根据index
返回槽位的指针。然后,用户通过迭代将块地址放入指针,就像*xsk_ring_prod__fill_addr() = chunk_addr
这样,以与预留槽位的数量相同的方式进行分配。 - 在分配块之后,
xsk_ring_prod__submit()
将通知内核有多少块被分配到FQ
中的槽位。 - 当接收到一些数据包时,内核将根据
FQ
的分配信息将数据包复制到块中,并在RX Ring
中创建数据包的描述符。 - 用户可以通过
xsk_ring_cons__peek()
了解接收了多少个数据包。该函数将返回接收到的数据包数量和index
值,即接收到的槽位在环中的第一个索引。 - 通过
xsk_ring_cons__rx_desc()
,用户可以通过将index
放入函数作为参数来获取接收到的数据包的信息。 - 在获取所有接收到的数据包信息之后,用户应通过
xsk_ring_cons__release()
通知内核在RX Ring
中消耗了多少个数据包。这样,内核将移动RX 环的尾部,以便为未来数据包的描述符保存空间。
TX 端过程
与 RX 端过程相反,在通过 XSK
发送数据包之前,CQ
应有足够的空槽位,因为 CQ
会在数据包成功发送后被分配。如果在 CQ
中没有可分配的槽位,内核可能无法发送数据包。用户可以通过 CQ
了解哪些数据包已成功发送。以下步骤描述了如何通过 XSK
发送数据包。
- 要发送的数据包已在块中准备好。
- 用户应通过
xsk_ring_prod__reserve()
函数预留TX Ring
的槽位。该函数将返回预留的槽位数和索引,其中索引表示预留槽位的第一索引。 - 当用户将
index
作为参数传递给xsk_ring_prod__tx_desc()
函数时,该函数将返回与索引对应的描述符。然后,用户将待发送数据包的有效载荷地址和有效载荷长度放入描述符中。 - 所有描述符关于数据包的信息完成后,用户应通过
xsk_ring_prod__submit()
函数通知内核将发送多少个数据包。 - 单独调用
xsk_ring_prod__submit()
实际上不会发送任何数据包。调用sendto()
将使内核发送这些数据包。 - 如果数据包发送成功,内核将在
CQ
中为已发送的数据包分配块。 - 用户可以通过将
CQ
作为参数传递给xsk_ring_cons__peek()
函数,通过CQ
了解发送了多少个数据包。该函数将返回发送的数据包数量和索引。索引表示CQ
中第一个槽位的索引。 xsk_ring_cons__comp_addr()
将返回在CQ
中分配的块地址。然后,用户可以在下次重用该块。- 最后,用户应通过
xsk_ring_cons__release()
函数通知内核已消耗CQ
中的多少个槽位。这样内核就会移动CQ
的tail
,以便将下一个数据包的块保存到环中。
许可证
本软件根据GPLv3或任何后续版本分发。
如果您需要除了GPLv3之外的其他许可证用于专有用途或专业支持,请通过contact at tsnlab dot com联系我们。
待办事项
制作一个安装程序,包括更改应用程序功能选项。使用以下命令:sudo setcap CAP_SYS_ADMIN,CAP_NET_ADMIN,CAP_NET_RAW,CAP_DAC_OVERRIDE+ep target/release/pv3_rust
依赖关系
~4.5–7.5MB
~142K SLoC