网络数据包计数:无法从 BPF 套接字筛选器读取数据包数据



我想计算传入的网络数据包,每个TOS值的字节数。我创建了两个映射,第一个包含 256 个条目,其中包含每个 TOS 值的数据包计数,第二个包含数据包字节。所以我写了以下 eBPF 套接字过滤器:

struct bpf_insn prog[]{
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
//we use dgram socket, so packet starts directly from IP header
// BPF_LD_ABS(BPF_H, offsetof(struct ethhdr, h_proto)), // r0 = header type
// BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, ETH_P_IP, 2),        // if (r0 == IPv4) skip 2
// BPF_MOV64_IMM(BPF_REG_0, 0),                         // r0 = 0
// BPF_EXIT_INSN(),                                     // return
//check for IP version, we only interested in v4
BPF_LD_ABS(BPF_B, 0),                           // R0 = ip->vers: offsetof(struct iphdr, version)
BPF_ALU64_IMM(BPF_AND, BPF_REG_0, 0xF0),        // r0 = r0 & 0xF0
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0x40, 2),       // if (r0 == 0x40) goto pc+2
BPF_MOV64_IMM(BPF_REG_0, 0),                    // r0 = 0
BPF_EXIT_INSN(),                                // return
// load packet TOS value
BPF_LD_ABS(BPF_B, offsetof(struct iphdr, tos)), // R0 = ip->tos
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),  // *(u32 *)(fp - 4) = r0
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),           // r2 = fp
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),          // r2 = fp - 4
//first map with packet counters
BPF_LD_MAP_FD(BPF_REG_1, map_cnt_fd),           // r1 = map_fd
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
BPF_FUNC_map_lookup_elem),         // r0 = map_lookup(r1, r2)
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),          // if (r0 == 0) goto pc+2
BPF_MOV64_IMM(BPF_REG_1, 1),                    // r1 = 1
BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW,
BPF_REG_0, BPF_REG_1, 0, 0),       // xadd r0 += r1
BPF_LD_ABS(BPF_B, offsetof(struct iphdr, tos)), // R0 = ip->tos
BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),  // *(u32 *)(fp - 4) = r0
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),          // r2 = fp - 4
//second map with packet bytes
BPF_LD_MAP_FD(BPF_REG_1, map_bytes_fd),         // r1 = map_fd
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
BPF_FUNC_map_lookup_elem),         // r0 = map_lookup(r1, r2)
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),          // if (r0 == 0) goto pc+2
// FIXME big endian
BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6,
offsetof(struct iphdr, tot_len)),   // r1 = tot_len
BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW,
BPF_REG_0, BPF_REG_1, 0, 0),       // xadd r0 += r1
BPF_MOV64_IMM(BPF_REG_0, 0),                    // r0 = 0
BPF_EXIT_INSN(),
};

映射创建没有错误,套接字过滤器程序也创建得很好,数据包计数器部分正常工作。但字节计数器始终为 0。该代码有什么问题?

我试图写一个简单的例子。要编译,您只需要包含 bpf_insn.h。

问题:从套接字缓冲区读取

在程序启动之前放置在BPF_REG_1中的上下文不是指向数据开头的指针。相反,它是指向 UAPI 标头中定义的struct __sk_buff的指针,如下所示:

struct __sk_buff {
__u32 len;
...
}

因此,当您尝试从 IP 标头读取数据时:

BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6, offsetof(struct iphdr, tot_len)),

实际上,您正在从struct __sk_buff读取偏移量为 2 的两个字节(我们将其指针称为skb)。因为您的系统处于小端序中,这对应于skb->len的最高有效位,除非您的数据包大于 2^16 字节(不太可能),否则为 0。

我们在这里有两种可能的解决方案。

解决方案 1:使用绝对负载

我们可以更新您的程序以在正确的位置读取 IP 长度。我相信这在BPF_LDX_MEM()是不可能的,因为套接字过滤器不允许直接数据包访问。解决方法是改用绝对负载。您的程序将变为:

struct bpf_insn prog[]{
BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
// ... packet number counter, skipped for brevity
// Read IP length and store to r7 (preserved during helper calls)
BPF_LD_ABS(BPF_H,
offsetof(struct iphdr, tot_len)),    // r0 = tot_len
BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),            // r7 = r0
// No need to parse ToS a second time here, skipped
BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),          // r2 = fp - 4
//second map with packet bytes
BPF_LD_MAP_FD(BPF_REG_1, map_bytes_fd),         // r1 = map_fd
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0,
BPF_FUNC_map_lookup_elem),         // r0 = map_lookup(r1, r2)
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2),          // if (r0 == 0) goto pc+2
// Now add length to the counter
BPF_STX_XADD(BPF_DW, BPF_REG_0, BPF_REG_7, 0),  // xadd r0 += r7
BPF_MOV64_IMM(BPF_REG_0, 0),                    // r0 = 0
BPF_EXIT_INSN(),
};

解决方案 2:只需使用skb->len

另一种解决方案是从skb,因为内核已经为我们计算了它。这只是固定负载的偏移量和长度的问题,您的BPF_STX_MEM(), BPF_XADD()将变为:

BPF_LDX_MEM(BPF_W, BPF_REG_1, BPF_REG_6,
offsetof(struct __sk_buff, len)),         // r1 = skb->len
BPF_STX_XADD(BPF_DW, BPF_REG_0, BPF_REG_1, 0),        // xadd r0 += r1

相关内容

  • 没有找到相关文章