网络数据包计数:无法从 BPF 套接字过滤器读取数据包数据
Network packet counting: Failure to read packet data from a BPF socket filter
我想计算传入的网络数据包,每个 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。该代码有什么问题?
我试着写了一个简单的example. To compile you just need to include bpf_insn.h。
问题:从套接字缓冲区读取
程序启动前放在BPF_REG_1
的上下文不是指向数据开头的指针。相反,它是指向 struct __sk_buff
定义的 in the UAPI headers 的指针,如下所示:
struct __sk_buff {
__u32 len;
...
}
因此,当您尝试从您的 IP header 读取数据时:
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()
是不可能的,因为套接字过滤器 do not permit direct packet access。解决方法是改用绝对负载。您的程序将变为:
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
我想计算传入的网络数据包,每个 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。该代码有什么问题?
我试着写了一个简单的example. To compile you just need to include bpf_insn.h。
问题:从套接字缓冲区读取
程序启动前放在BPF_REG_1
的上下文不是指向数据开头的指针。相反,它是指向 struct __sk_buff
定义的 in the UAPI headers 的指针,如下所示:
struct __sk_buff {
__u32 len;
...
}
因此,当您尝试从您的 IP header 读取数据时:
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()
是不可能的,因为套接字过滤器 do not permit direct packet access。解决方法是改用绝对负载。您的程序将变为:
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