在上一篇文章中,我们使用XDP的eBPF Map与用户空间进行了数据交换。
在前一篇文章中,我们写了一个程序,通过XDP程序丢弃所有数据包。
这次我们将编写一个程序来回复ICMP Echo Request的ICMP Echo Reply。
返回ICMP Echo Reply
为了回复ICMP Echo Request的ICMP Echo Reply,需要以下步骤。
- 接收ICMP Echo Request的数据包
- 将ICMP Echo Reply的标志更改
- 交换源和目标的IP地址
- 交换以太网头的源和目标MAC地址
- 发送数据包
只丢弃ICMP Echo Request
首先,我们希望能够处理ICMP Echo Request,因此编写一个只丢弃ICMP Echo Request的程序。
- xdp_echo_request_drop.c
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <linux/if_ether.h> #include <linux/ip.h> #include <linux/icmp.h> #include <arpa/inet.h> SEC("xdp") int xdp_drop_icmp(struct xdp_md *ctx) { void *data_end = (void *)(unsigned long)ctx->data_end; void *data = (void *)(unsigned long)ctx->data; // 获取以太网头指针 struct ethhdr *eth = data; if ((void *)eth + sizeof(*eth) > data_end) return XDP_PASS; // 检查以太网帧是否包含IP数据包 if (eth->h_proto != htons(ETH_P_IP)) return XDP_PASS; // 获取IP头的指针 struct iphdr *ip = data + sizeof(*eth); if ((void *)ip + sizeof(*ip) > data_end) return XDP_PASS; // 检查协议是否为ICMP if (ip->protocol != IPPROTO_ICMP) return XDP_PASS; // 获取ICMP头的指针 struct icmphdr *icmp = (void *)ip + sizeof(*ip); if ((void *)icmp + sizeof(*icmp) > data_end) return XDP_PASS; // 允许非ICMP Echo Request的数据包通过 if (icmp->type != ICMP_ECHO) return XDP_PASS; // 丢弃ICMP Echo Request的数据包 return XDP_DROP; } char _license[] SEC("license") = "MIT";
这段代码与之前在手っ取り早くやってみる 超XDP入門中创建的程序几乎相同。
// 允许非ICMP Echo Request的数据包通过
if (icmp->type != ICMP_ECHO)
return XDP_PASS;
在这里,除了ICMP Echo Request以外的数据包都会被允许通过。
将ICMP Echo改为Reply并返回
接下来,我们将编写一个程序来回复ICMP Echo Request的ICMP Echo Reply。
- 将ICMP Echo Reply的标志更改
// 将ICMP Echo Reply的标志更改
icmp->type = ICMP_ECHOREPLY;
将类型从ICMP_ECHO (8)更改为ICMP_ECHOREPLY (0)。
- 交换源和目标的IP地址
// 交换源和目标的IP地址
__u32 tmp = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = tmp;
- 交换以太网头的源和目标MAC地址
// 交换以太网头的源和目标
unsigned char tmp_mac[ETH_ALEN];
__builtin_memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
__builtin_memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
__builtin_memcpy(eth->h_source, tmp_mac, ETH_ALEN);
- 通过XDP_TX发送数据包
// 发送数据包
return XDP_TX;
到此为止,ICMP Echo Reply的回复程序编写完成。
SEC("xdp")
int xdp_echo_reply(struct xdp_md *ctx)
{
void *data_end = (void *)(unsigned long)ctx->data_end;
void *data = (void *)(unsigned long)ctx->data;
// 获取以太网头指针
struct ethhdr *eth = data;
if ((void *)eth + sizeof(*eth) > data_end)
return XDP_PASS;
// 检查以太网帧是否包含IP数据包
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
// 获取IP头的指针
struct iphdr *ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) > data_end)
return XDP_PASS;
// 检查协议是否为ICMP
if (ip->protocol != IPPROTO_ICMP)
return XDP_PASS;
// 获取ICMP头的指针
struct icmphdr *icmp = (void *)ip + sizeof(*ip);
if ((void *)icmp + sizeof(*icmp) > data_end)
return XDP_PASS;
// 允许非ICMP Echo Request的数据包通过
if (icmp->type != ICMP_ECHO)
return XDP_PASS;
// 将ICMP Echo Reply的标志更改
icmp->type = ICMP_ECHOREPLY;
// 交换IP头的源和目标地址
__u32 tmp = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = tmp;
// 交换以太网头的源和目标
unsigned char tmp_mac[ETH_ALEN];
__builtin_memcpy(tmp_mac, eth->h_dest, ETH_ALEN);
__builtin_memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
__builtin_memcpy(eth->h_source, tmp_mac, ETH_ALEN);
// 丢弃ICMP Echo Request的数据包
// return XDP_DROP;
return XDP_TX;
}
将程序加载到NIC中
将程序编译并加载到NIC中。
$ clang -O2 -target bpf -c xdp_echo_reply.c -o xdp_echo_reply.o
$ sudo ip link set dev eth0 xdp obj xdp_echo_reply.o
验证运行结果
从客户端发送ping
$ ping 192.168.XXX.XXX
在服务器上运行tcpdump,确认未接收到ICMP Request。 因为XDP直接处理数据包,所以tcpdump无法确认,而Linux内核未回复Echo Reply。
$ sudo tcpdump -i eth0 icmp
从客户端发送ping,确认可以收到ICMP Echo Reply。
$ sudo tcpdump -i eth0 icmp -vvv XX:XX:XX.160507 IP (tos 0x0, ttl 64, id 53521, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.XXX.1 > 192.168.XXX.2: ICMP echo request, id 58371, seq 684, length 64 XX:XX:XX.161394 IP (tos 0x0, ttl 64, id 53521, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.XXX.2 > 192.168.XXX.1: ICMP echo reply, id 58371, seq 684, length 64 (wrong icmp cksum 767b (->7e7b)!)
确认ICMP Echo Reply已返回。
然而,显示wrong icmp cksum
,这表明ICMP头的校验和计算不正确。
要计算ICMP的校验和并正确回复,应如何处理?如果写作的动力不减,我会继续撰写相关文章。
总结
- 我们使用XDP实现了一个针对ICMP Echo Request返回ICMP Echo Reply的XDP程序。
- 步骤包括检测ICMP Echo Request,生成回复数据包并交换MAC地址和IP地址,最后通过XDP_TX直接回复而不经过内核。
- 最终出现了wrong icmp cksum的错误,确认了需要重新计算ICMP校验和。