Skip to content

教程:Packet02 - 数据包重写

完成 packet01 中的数据包解析课程后,你现在熟悉了如何构建数据包解析、 如何在引用数据包数据之前进行正确的边界检查,以及如何使用返回码决定 最终数据包处置。在本课中,我们在此基础上展示如何修改数据包内容。

本课你将学到的内容

使用直接内存访问重写数据包数据

正如我们在上一课中看到的,验证器会检查所有对数据包数据的内存访问首先 执行正确的边界检查,以确保它不会引用数据包外部的内存。这不仅适用于 数据包数据读取,也适用于写入;这意味着我们可以通过简单地更改它占用的 内存来重写数据包数据。我们将在下面的作业中使用这一点来修改数据包头部字段。

扩大和缩小数据包大小

虽然许多事情可以通过简单地重写现有数据包数据来完成,但有时需要完全 添加或删除内存块,例如执行封装,或从数据包中删除协议头部。内核暴露了 一个 eBPF 辅助函数来实现这一点,称为 bpf_xdp_adjust_head()。此函数 接受 XDP 上下文对象和调整大小作为参数,并将头部指针移动这么多字节 (即正数将缩小数据包数据的大小,而负数将向数据包前面添加那么多字节)。

使用此辅助函数时需要注意几点:

  1. 首先,它可能会失败,因为调整会使数据包数据太小而无法包含至少一个 以太网头部,或者因为数据包开始之前没有足够的内存空间(数据包数据 放置在距内存页开始的固定偏移处)。

  2. 其次,辅助函数只调整数据指针。确保数据包数据之后有效是 XDP 程序的 责任。这通常涉及在数据包的新起始位置重写以太网头部,并调整任何 后续头部字段以匹配。

  3. 最后,验证器将在调整数据包大小后丢弃有关先前边界检查的所有信息。 这意味着 XDP 程序需要在调整数据包大小后执行新的边界检查, 包括重新评估 data 和 data_end 指针

还有一个 bpf_xdp_adjust_tail() 可用于移动数据包数据的末尾。从内核 v5.8 开始,它的功能与 bpf_xdp_adjust_head() 相同,在此之前只能在 尾部缩小数据包,而不能增长。

作业

在本课中,我们将创建两个程序:一个将 TCP 和 UDP 数据包的目标端口号 重写为比原始值小一;另一个如果存在最外层 VLAN 封装头部则删除它, 如果不存在则添加一个新的。

作业 1:重写端口号

对于此作业,你需要解析 TCP 和 UDP 头部,并在传递数据包之前重写端口号。 这些头部分别在 <linux/tcp.h><linux/udp.h> 中定义。

重写只是向头部中正确的字段写入的问题(解析后)。例如:

c
udphdr->dest = bpf_htons(bpf_ntohs(udphdr->dest) - 1);

你可以使用 tcpdump 来验证这是否有效。作为数据包生成器,你可以使用 socat 工具。以下将为你在 stdin 上键入的每一行生成一个到端口 2000 的 UDP 数据包:

bash
$ t exec -- socat - 'udp6:[fc00:dead:cafe:1::1]:2000'

你可以使用 tcpdump 查看这些:

bash
$ t tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on xdptut-3c93, link-type EN10MB (Ethernet), capture size 262144 bytes
12:54:31.085948 d2:9e:c0:4f:3b:7b > 32:71:5a:a4:74:c1, ethertype IPv6 (0x86dd), length 67: fc00:dead:cafe:1::2.35126 > fc00:dead:cafe:1::1.2000: UDP, length 5

当你的程序正常工作时,目标端口(行末尾附近的 2000)应该是 1999。

作业 2:删除最外层 VLAN 标签

在此作业中,我们将开始实现删除最外层 VLAN 标签(如果存在)的程序。 为此,填写 xdp_prog_kern.c 中原型化的 vlan_tag_pop() 函数。 函数原型包含我们解决方案中的变量定义和内联注释,以指导你实现。

实现该函数后,通过设置启用 VLAN 的测试环境(如上一课)来测试它是否有效, 并在一个窗口中运行 t ping --vlan,同时在另一个窗口中查看 t tcpdump 的输出。你应该看到回显请求数据包上没有 VLAN 标签;回显回复仍然会有 VLAN 标签,因为即使它针对的是不同的接口,内核也会回复 ping,并且回复 将被路由到实际具有被 ping 的 IP 地址的接口(即虚拟 VLAN 接口)。

作业 3:添加回缺失的 VLAN 标签

在此作业中,我们将实现与上一个作业相反的操作:即,如果不存在 VLAN 标签 则添加代码。只需将 VLAN ID 硬编码为你选择的值;并以与上一个作业相同的 方式测试程序(但在不带 --vlan 参数的情况下运行 t ping,并验证 ICMP 回显请求数据包确实添加了 VLAN 标签)。