ebpf 研究之 XDP 技术

########################################################
如下内容摘自 《Linux内核观测技术BPF》第 7 章!

什么是 XDP?

XDP 是 Linux 网络数据路径上内核集成的数据包处理器,具有安全、可编程、高性能的特点。 XDP 程序可以在最早的时间点,对接收到的数据包进行丢弃、修改、允许等操作

快速运行 XDP,还需要考虑下面几个因素:

  1. 使用 XDP 进行数据包处理时,没有内存分配。
  2. XDP 程序仅适用于线性的、无碎片的数据包,并且有数据包的头指针和尾指针
  3. XDP 程序无法访问完整的数据包元数据,这种程序接收的输入上下文是 xdp_buff 类型,而不是 sk_buff 结构。
  4. 由于 XDP 程序是 eBPF 程序,在网络管道中 XDP 程序的使用开销是固定的,所以 XDP 程序的执行时间是有限的。

XDP 程序概述

本质上 XDP 程序所做的是对接收的数据包进行决策,然后对接收的数据包内容进行编辑或者仅返回一个结果码。结果码用于决定对数据包进行的操作。你可以丢弃数据包,或将其发送到同一个接口,也可以将其传递给其余的网络栈。此外,XDP 程序可与网络栈协作推送和拉取数据包头。例如,如果当前内核不支持封装格式或协议,XDP 程序可以对其进行解包或转换协议,然后将结果发送到内核进行处理。

操作模式

XDP 有三种操作模式:

  • 原生 XDP
  • 卸载 XDP
  • 通用 XDP

原生 XDP

默认模式。在这种模式下,XDP 的 BPF 程序在网络驱动程序的早期接收路径之外运行。使用此模式时需要检查驱动程序是否支持此模式。

通过执行如下命令查询 linux 5.0 内核支持 xdp 的网卡驱动,得到了如下信息:

$ git grep -l XDP_SETUP_PROG drivers/
drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c
drivers/net/ethernet/cavium/thunder/nicvf_main.c
drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c
drivers/net/ethernet/intel/i40e/i40e_main.c
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c
drivers/net/ethernet/mellanox/mlx4/en_netdev.c
drivers/net/ethernet/mellanox/mlx5/core/en_main.c
drivers/net/ethernet/netronome/nfp/nfp_net_common.c
drivers/net/ethernet/qlogic/qede/qede_filter.c
drivers/net/netdevsim/bpf.c
drivers/net/tun.c
drivers/net/veth.c
drivers/net/virtio_net.c

可以看到,linux 5.0 内核中 intel ixgbe、i40e、tun、virtio 等网卡都支持原生的 XDP。

卸载 XDP

在这种模式下,XDP 的 BPF 程序可以直接卸载到网卡上,而不是主机 CPU 上执行。因为将执行从 CPU 上移出,这种模式比原生 XDP 具有更高的性能。

这一模式目前较少网卡支持!

通用 XDP

通过 XDP 是一种测试模式,它不依赖网卡驱动与网卡硬件支持。内核版本从 4.12 开始支持通用 XDP。

XDP 结果码

使用 XDP 进行数据包处理,有 5 个返回码指示网络驱动处理数据包,列举如下:

  • 丢弃(XDP_DROP)
  • 转发(XDP_TX)
  • 重定向(XDP_REDIRECT)
  • 传递(XDP_PASS)
  • 错误(XDP_ABORTED)

摘录内容结束!
#################################################

使用 ip 命令测试 XDP 功能

ip 命令能够充当 XDP 前端,可以将 XDP 程序编译为 ELF 文件并加载它。

加载 XDP 程序的语法:

# ip link set dev eth0 xdp obj program.o sec mysection

xdp obj program.o 表示从 program.o ELF 文件中加载 XDP 程序。sec mysection 指定 ELF 文件中 XDP 程序所在的 section 名称。

丢弃 TCP 报文的 XDP 使用 demo

如下 XDP demo 能够用来丢弃网卡接收到的 TCP 报文:

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>


#define SEC(NAME) __attribute__((section(NAME), used))

SEC("mysection")
int myprogram(struct xdp_md *ctx) {
  int ipsize = 0;
  void *data = (void *)(long)ctx->data;
  void *data_end = (void *)(long)ctx->data_end;
  struct ethhdr *eth = data;
  struct iphdr *ip;

  ipsize = sizeof(*eth);
  ip = data + ipsize;
  ipsize += sizeof(struct iphdr);
  if (data + ipsize > data_end) {
    return XDP_DROP;
  }

  if (ip->protocol == IPPROTO_TCP) {
    return XDP_DROP;
  }

  return XDP_PASS;
}

myprogram 函数中解析报文的 ip 头部,判断协议类型,为 TCP 的时候则返回 XDP_DROP 表示丢弃数据包。

编译命令如下:

clang -g -c -O2 -target bpf -c program.c -o program.o

加载命令如下:

$ sudo ip link set dev wlp0s20f3 xdp obj program.o sec mysection

wlp0s20f3 是我笔记本上无线网卡对应的网络接口名称。加载成功后可以执行 ip 命令查看,记录如下:

$ ip a show wlp0s20f3
3: wlp0s20f3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric/id:99 qdisc mq state UP group default qlen 1000
    link/ether 84:fd:d1:7d:9b:7d brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.233/24 brd 192.168.2.255 scope global dynamic noprefixroute wlp0s20f3
       valid_lft 81747sec preferred_lft 81747sec
    inet6 fe80::fd97:de1e:33f4:999/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

xdpgeneric/id:99 表明使用的 xdp 类型为通用 XDP,id:99 表示 XDP 程序的 ID 为 99。

这时访问网页就会失败,确认 tcp 报文被丢弃。执行 ip link set dev wlp0s20f3 xdp off后网页可以正常访问。

使用 bcc 加载 XDP 程序

XDP 程序也可以使用 BCC 编译,使用如下 XDP 程序进行测试:

#define KBUILD_MODNAME "program"
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/ip.h>

BPF_TABLE("percpu_array", uint32_t, long, packetcnt, 256);

int myprogram(struct xdp_md *ctx) {
  int ipsize = 0;
  void *data = (void *)(long)ctx->data;
  void *data_end = (void *)(long)ctx->data_end;
  struct ethhdr *eth = data;
  struct iphdr *ip;
  long *cnt;
  __u32 idx;

  ipsize = sizeof(*eth);
  ip = data + ipsize;
  ipsize += sizeof(struct iphdr);

  if (data + ipsize > data_end) {
    return XDP_DROP;
  }

  idx = ip->protocol;
  cnt = packetcnt.lookup(&idx);
  if (cnt) {
    *cnt += 1;
  }

  if (ip->protocol == IPPROTO_TCP) {
    return XDP_DROP;
  }

  return XDP_PASS;
}

这个程序比第一个 XDP 程序增加了统计功能,其它的完全一致。

bcc 加载脚本源码如下:

#!/usr/bin/python3

from bcc import BPF
import time
import sys

device = "wlp0s20f3"
b = BPF(src_file="program.c")
fn = b.load_func("myprogram", BPF.XDP)
b.attach_xdp(device, fn, 0)
packetcnt = b.get_table("packetcnt")

prev = [0] * 256
print("Printing packet counts per IP protocol-number, hit CTRL+C to stop")
while 1:
    try:
        for k in packetcnt.keys():
            val = packetcnt.sum(k).value
            i = k.value
            if val:
                delta = val - prev[i]
                prev[i] = val
                print("{}: {} pkt/s".format(i, delta))
        time.sleep(1)
    except KeyboardInterrupt:
        print("Removing filter from device")
        break

b.remove_xdp(device, 0)

测试记录如下:

$ sudo python ./loader.py 
Printing packet counts per IP protocol-number, hit CTRL+C to stop
17: 16 pkt/s
6: 22 pkt/s
17: 20 pkt/s
6: 19 pkt/s
17: 0 pkt/s
6: 9 pkt/s
17: 0 pkt/s
6: 9 pkt/s
17: 0 pkt/s
6: 6 pkt/s
17: 0 pkt/s
6: 6 pkt/s
17: 0 pkt/s

总结

XDP 技术是 bpf 技术的一个应用,它支持多种模式,目前原生模式已经为几个主流的网卡所支持,这是一个很好的消息。

卸载 XDP 模式支持的网卡还非常少,但是随着智能网卡需求的不断提高,相信未来会有更多支持卸载 XDP 模式的网卡。与这个功能类似的有网卡 flow-directory 功能,这些功能的实现意味着性能优化从 CPU 迈入了网卡侧,这是未来需要重点关注的内容。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值