AF-XDP:如何从内核获取 `ctx->data_meta` 到用户-space?
AF-XDP: How do I get `ctx->data_meta` from kernel into user-space?
我想测量我的 AF-XDP 程序的数据包延迟。我在看这个参考:https://github.com/xdp-project/xdp-project/blob/master/areas/arm64/xdp_for_tsn.org
并将其改编为:
SEC("xdp_sock")
int xdp_sock_prog(struct xdp_md *ctx) {
int index = ctx->rx_queue_index;
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
const unsigned long long kstamp = bpf_ktime_get_ns();
if(data - sizeof(unsigned long long) <= data_end) {
ctx->data_meta = ctx->data - sizeof(unsigned long long);
memcpy(&ctx->data_meta, &kstamp, sizeof(unsigned long long));
}
...
}
在用户中访问-space然后发生这样的事情:
static bool process_packet(struct xsk_socket_info *xsk, uint64_t addr, uint32_t len) {
uint8_t *pkt = xsk_umem__get_data(xsk->umem->buffer, addr);
uint8_t *pkt_meta = xsk_umem__get_data(xsk->umem->buffer, addr - sizeof(unsigned long long));
const unsigned long long kstamp = (uint64_t) pkt_meta[7] << 56 | (uint64_t) pkt_meta[6] << 48 | (uint64_t) pkt_meta[5] << 40
| (uint64_t) pkt_meta[4] << 32 | (uint64_t) pkt_meta[3] << 24 | (uint64_t) pkt_meta[2] << 16
| (uint64_t) pkt_meta[1] << 8 | (uint64_t) pkt_meta[0];
...
}
但是我无法将 XDP 程序加载到内核中:
Load XDP program...
libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf:
0: (bf) r6 = r1
1: (61) r1 = *(u32 *)(r6 +16)
2: (63) *(u32 *)(r10 -4) = r1
3: (61) r7 = *(u32 *)(r6 +4)
4: (61) r8 = *(u32 *)(r6 +0)
5: (85) call bpf_ktime_get_ns#5
6: (bf) r1 = r8
7: (07) r1 += -8
8: (2d) if r1 > r7 goto pc+3
R0_w=inv(id=0) R1_w=pkt(id=0,off=-8,r=0,imm=0) R6_w=ctx(id=0,off=0,imm=0) R7_w=pkt_end(id=0,off=0,imm=0) R8_w=pkt(id=0,off=0,r=0,imm=0) R10=fp0 fp-8=mmmm????
9: (63) *(u32 *)(r6 +8) = r0
invalid bpf_context access off=8 size=4
processed 10 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
libbpf: -- END LOG --
我的意思是,这不可能 - 对吧?因为 XDP 完全是关于有效的内存访问,为什么在定义的边界 data
和 data_end
之外的访问是有效的?
恕我直言,如果 ctx->data_meta
保持不变,它可能会工作 - 但我在用户 space 中遇到了问题,因为我不知道 data_meta
位于何处。是否有任何 libbpf 辅助函数来访问数据包元数据?
也可能是访问有效,但我的范围检查错误...
您使用的片段在教程中被描述为伪代码,它不是一个完整的功能示例。让我们看看验证者日志:
Load XDP program...
libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf:
0: (bf) r6 = r1 // r6 points to ctx
1: (61) r1 = *(u32 *)(r6 +16) // [let's ignore that]
2: (63) *(u32 *)(r10 -4) = r1 // [and that]
3: (61) r7 = *(u32 *)(r6 +4) // r7 points to ctx->data_end
4: (61) r8 = *(u32 *)(r6 +0) // r8 points to ctx->data
5: (85) call bpf_ktime_get_ns#5 // r0 now contains timestamp
6: (bf) r1 = r8 // r1 points to ctx->data
7: (07) r1 += -8 // r1 = data - sizeof(unsigned long long)
8: (2d) if r1 > r7 goto pc+3 // if(data-sizeof(unsigned long long)>data_end) jump;
现在是有趣的部分。我们努力做到:
ctx->data_meta = ctx->data - sizeof(unsigned long long);
memcpy(&ctx->data_meta, &kstamp, sizeof(unsigned long long));
它产生:
R0_w=inv(id=0) R1_w=pkt(id=0,off=-8,r=0,imm=0) R6_w=ctx(id=0,off=0,imm=0)
R7_w=pkt_end(id=0,off=0,imm=0) R8_w=pkt(id=0,off=0,r=0,imm=0) R10=fp0 fp-8=mmmm????
9: (63) *(u32 *)(r6 +8) = r0
invalid bpf_context access off=8 size=4
r6
指向 struct xdp_md
类型的 ctx
,所以 r6 +8
是 ctx->data - sizeof(unsigned long long)
。这是因为,默认情况下,ctx->data_meta
points to ctx->data
。因此,当您尝试将时间戳复制到 ctx->data_meta
时,您实际上是在尝试将其写入数据包开始之前的 8 个字节。您没有在那里保留任何特定的 space 来放置元数据,因此这是一个越界访问:验证者抱怨这是无效的。
但是我们如何才能为元数据分配一些空间呢?这要归功于 bpf_xdp_adjust_meta()
BPF helper, as in the sample you referred to.
但是,并非所有 NIC 驱动程序都支持 XDP 元数据。他们中的一些人用-ENOTSUPP
调用函数xdp_set_data_meta_invalid()
internally, that sets ctx->data_meta
to ctx->data + 1
instead of ctx->data
. If bpf_xdp_adjust_meta()
, called from the BPF program, detects that this adjustment has been performed, it errors out并拒绝调整ctx->data_meta
。根据评论中的讨论,您的情况似乎是这样。
如果您不支持 XDP 元数据,您可以尝试在数据包的另一个字段中对时间戳进行编码。 BPF 映射也可能是一个选项,但每个数据包需要一个条目,因此不确定对 memory/performance.
的影响
我想测量我的 AF-XDP 程序的数据包延迟。我在看这个参考:https://github.com/xdp-project/xdp-project/blob/master/areas/arm64/xdp_for_tsn.org
并将其改编为:
SEC("xdp_sock")
int xdp_sock_prog(struct xdp_md *ctx) {
int index = ctx->rx_queue_index;
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
const unsigned long long kstamp = bpf_ktime_get_ns();
if(data - sizeof(unsigned long long) <= data_end) {
ctx->data_meta = ctx->data - sizeof(unsigned long long);
memcpy(&ctx->data_meta, &kstamp, sizeof(unsigned long long));
}
...
}
在用户中访问-space然后发生这样的事情:
static bool process_packet(struct xsk_socket_info *xsk, uint64_t addr, uint32_t len) {
uint8_t *pkt = xsk_umem__get_data(xsk->umem->buffer, addr);
uint8_t *pkt_meta = xsk_umem__get_data(xsk->umem->buffer, addr - sizeof(unsigned long long));
const unsigned long long kstamp = (uint64_t) pkt_meta[7] << 56 | (uint64_t) pkt_meta[6] << 48 | (uint64_t) pkt_meta[5] << 40
| (uint64_t) pkt_meta[4] << 32 | (uint64_t) pkt_meta[3] << 24 | (uint64_t) pkt_meta[2] << 16
| (uint64_t) pkt_meta[1] << 8 | (uint64_t) pkt_meta[0];
...
}
但是我无法将 XDP 程序加载到内核中:
Load XDP program...
libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf:
0: (bf) r6 = r1
1: (61) r1 = *(u32 *)(r6 +16)
2: (63) *(u32 *)(r10 -4) = r1
3: (61) r7 = *(u32 *)(r6 +4)
4: (61) r8 = *(u32 *)(r6 +0)
5: (85) call bpf_ktime_get_ns#5
6: (bf) r1 = r8
7: (07) r1 += -8
8: (2d) if r1 > r7 goto pc+3
R0_w=inv(id=0) R1_w=pkt(id=0,off=-8,r=0,imm=0) R6_w=ctx(id=0,off=0,imm=0) R7_w=pkt_end(id=0,off=0,imm=0) R8_w=pkt(id=0,off=0,r=0,imm=0) R10=fp0 fp-8=mmmm????
9: (63) *(u32 *)(r6 +8) = r0
invalid bpf_context access off=8 size=4
processed 10 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
libbpf: -- END LOG --
我的意思是,这不可能 - 对吧?因为 XDP 完全是关于有效的内存访问,为什么在定义的边界 data
和 data_end
之外的访问是有效的?
恕我直言,如果 ctx->data_meta
保持不变,它可能会工作 - 但我在用户 space 中遇到了问题,因为我不知道 data_meta
位于何处。是否有任何 libbpf 辅助函数来访问数据包元数据?
也可能是访问有效,但我的范围检查错误...
您使用的片段在教程中被描述为伪代码,它不是一个完整的功能示例。让我们看看验证者日志:
Load XDP program...
libbpf: load bpf program failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
libbpf:
0: (bf) r6 = r1 // r6 points to ctx
1: (61) r1 = *(u32 *)(r6 +16) // [let's ignore that]
2: (63) *(u32 *)(r10 -4) = r1 // [and that]
3: (61) r7 = *(u32 *)(r6 +4) // r7 points to ctx->data_end
4: (61) r8 = *(u32 *)(r6 +0) // r8 points to ctx->data
5: (85) call bpf_ktime_get_ns#5 // r0 now contains timestamp
6: (bf) r1 = r8 // r1 points to ctx->data
7: (07) r1 += -8 // r1 = data - sizeof(unsigned long long)
8: (2d) if r1 > r7 goto pc+3 // if(data-sizeof(unsigned long long)>data_end) jump;
现在是有趣的部分。我们努力做到:
ctx->data_meta = ctx->data - sizeof(unsigned long long);
memcpy(&ctx->data_meta, &kstamp, sizeof(unsigned long long));
它产生:
R0_w=inv(id=0) R1_w=pkt(id=0,off=-8,r=0,imm=0) R6_w=ctx(id=0,off=0,imm=0)
R7_w=pkt_end(id=0,off=0,imm=0) R8_w=pkt(id=0,off=0,r=0,imm=0) R10=fp0 fp-8=mmmm????
9: (63) *(u32 *)(r6 +8) = r0
invalid bpf_context access off=8 size=4
r6
指向 struct xdp_md
类型的 ctx
,所以 r6 +8
是 ctx->data - sizeof(unsigned long long)
。这是因为,默认情况下,ctx->data_meta
points to ctx->data
。因此,当您尝试将时间戳复制到 ctx->data_meta
时,您实际上是在尝试将其写入数据包开始之前的 8 个字节。您没有在那里保留任何特定的 space 来放置元数据,因此这是一个越界访问:验证者抱怨这是无效的。
但是我们如何才能为元数据分配一些空间呢?这要归功于 bpf_xdp_adjust_meta()
BPF helper, as in the sample you referred to.
但是,并非所有 NIC 驱动程序都支持 XDP 元数据。他们中的一些人用-ENOTSUPP
调用函数xdp_set_data_meta_invalid()
internally, that sets ctx->data_meta
to ctx->data + 1
instead of ctx->data
. If bpf_xdp_adjust_meta()
, called from the BPF program, detects that this adjustment has been performed, it errors out并拒绝调整ctx->data_meta
。根据评论中的讨论,您的情况似乎是这样。
如果您不支持 XDP 元数据,您可以尝试在数据包的另一个字段中对时间戳进行编码。 BPF 映射也可能是一个选项,但每个数据包需要一个条目,因此不确定对 memory/performance.
的影响