启用双标志后BPF_PROG_TYPE_SOCK_OPS无法同时读写TCP选项
同一SOCK_OPS eBPF程序中同时启用TCP选项读写标志后写入回调不触发的问题
我正在编写测试实验性TCP选项的eBPF程序,采用BPF_PROG_TYPE_SOCK_OPS程序类型,为实现需求启用了两个标志:
BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG:用于设置选项长度并写入TCP头部BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG:确保解析头部中的目标选项
随后调用bpf_reserve_hdr_opt和bpf_store_hdr_opt函数分别完成空间预留与选项写入。但遇到以下问题:
- 仅启用
BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG时,可正常写入选项,Wireshark中能观测到结果; - 同时启用两个标志后,
BPF_SOCK_OPS_WRITE_HDR_OPT_CB操作从未触发,仅BPF_SOCK_OPS_PARSE_HDR_OPT_CB被触发。
需要在同一程序中同时实现TCP选项的读写功能,而非拆分两个程序。
无法写入选项的代码
// objective: reading options using TCP SOCK OPS eBPF program type #include <linux/bpf.h> #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <netinet/tcp.h> #define TCP_EXPERIMENT2 42 #define TR_OPTION_ID 254 // experimental option id #define TR_OPTION_LEN 4 // for now small lenght, for testing SEC("sockops") int bgf_dummy_option(struct bpf_sock_ops *ctx) { void *data_end = (void *)(__u64)ctx->skb_data; void *data = (void *)(__u64)ctx->skb_data_end; int rv = 0; // setup flags, by default reserve opt space and write opt are not triggered rv = bpf_sock_ops_cb_flags_set(ctx, BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG); if (rv < 0) { bpf_printk("Failed to setup flag:: BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG, %d", rv); return 0; } // IMPORTANT NOTE: when parse all is activated with the write, we never hook the write rv = bpf_sock_ops_cb_flags_set(ctx, BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG); if (rv < 0) { bpf_printk("Failed to setup flag:: BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG, %d", rv); return 0; } switch (ctx->op) { case BPF_SOCK_OPS_HDR_OPT_LEN_CB: bpf_printk("setting BPF opt len, op == %d", ctx->op); long opt_ptr = bpf_reserve_hdr_opt(ctx, TR_OPTION_LEN, 0); if (opt_ptr < 0) { bpf_printk("Failed to reserve TCP option space\n"); return 0; } return 1; case BPF_SOCK_OPS_WRITE_HDR_OPT_CB: bpf_printk("Writting option, op == %d", ctx->op); // TODO: load option with a struct instead of an array __u8 *tcp_option; tcp_option=(__u8[]){TR_OPTION_ID, TR_OPTION_LEN, 0x01, 0x02}; int ret = bpf_store_hdr_opt(ctx, tcp_option, TR_OPTION_LEN, 0); if (ret < 0) { bpf_printk("Failed to write TCP option (error %d)\n", ret); return 0; } bpf_printk("Adding experimental option\n"); return 1; // only this operation is triggered if both parsing and writing flag are activated case BPF_SOCK_OPS_PARSE_HDR_OPT_CB: bpf_printk("Only reading operation activated"); return 1; default: return 1; } return 1; }
可正常写入选项的代码(未启用读取标志)
// objective: reading options using TCP SOCK OPS eBPF program type #include <linux/bpf.h> #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <netinet/tcp.h> #define TCP_EXPERIMENT2 42 #define TR_OPTION_ID 254 // experimental option id #define TR_OPTION_LEN 4 // for now small lenght, for testing SEC("sockops") int bgf_dummy_option(struct bpf_sock_ops *ctx) { void *data_end = (void *)(__u64)ctx->skb_data; void *data = (void *)(__u64)ctx->skb_data_end; int rv = 0; // setup flags, by default reserve opt space and write opt are not triggered rv = bpf_sock_ops_cb_flags_set(ctx, BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG); if (rv < 0) { bpf_printk("Failed to setup flag:: BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG, %d", rv); return 0; } switch (ctx->op) { case BPF_SOCK_OPS_HDR_OPT_LEN_CB: bpf_printk("setting BPF opt len, op == %d", ctx->op); long opt_ptr = bpf_reserve_hdr_opt(ctx, TR_OPTION_LEN, 0); if (opt_ptr < 0) { bpf_printk("Failed to reserve TCP option space\n"); return 0; } return 1; case BPF_SOCK_OPS_WRITE_HDR_OPT_CB: bpf_printk("Writting option, op == %d", ctx->op); // TODO: load option with a struct instead of an array __u8 *tcp_option; tcp_option=(__u8[]){TR_OPTION_ID, TR_OPTION_LEN, 0x01, 0x02}; int ret = bpf_store_hdr_opt(ctx, tcp_option, TR_OPTION_LEN, 0); if (ret < 0) { bpf_printk("Failed to write TCP option (error %d)\n", ret); return 0; } bpf_printk("Adding experimental option\n"); return 1; // only this operation is triggered if both parsing and writing flag are activated case BPF_SOCK_OPS_PARSE_HDR_OPT_CB: bpf_printk("Only reading operation activated"); return 1; default: return 1; } return 1; }
问题原因与解决方法
问题原因
两次调用bpf_sock_ops_cb_flags_set会覆盖之前设置的标志,而非追加。原代码中先设置WRITE标志,再设置PARSE_ALL标志,最终实际生效的只有PARSE_ALL标志,导致写入相关的回调无法被触发。
此外,内核中SOCK_OPS回调的触发逻辑中,解析回调的优先级较高,若仅生效PARSE_ALL标志,会只触发解析回调。
解决方法
- 使用位或操作一次性设置多个标志:将两个标志通过
|组合,一次调用bpf_sock_ops_cb_flags_set完成设置,避免覆盖。 - 修正代码中的数组定义问题:原代码中
__u8 *tcp_option; tcp_option=(__u8[]){...};的写法存在隐患,改为直接定义数组。
修改后的代码示例:
// objective: reading options using TCP SOCK OPS eBPF program type #include <linux/bpf.h> #include <bpf/bpf_helpers.h> #include <netinet/tcp.h> #define TCP_EXPERIMENT2 42 #define TR_OPTION_ID 254 // experimental option id #define TR_OPTION_LEN 4 // for now small length, for testing SEC("sockops") int bgf_dummy_option(struct bpf_sock_ops *ctx) { void *data_end = (void *)(__u64)ctx->skb_data; void *data = (void *)(__u64)ctx->skb_data_end; int rv = 0; // 一次性设置两个标志,使用位或避免覆盖 rv = bpf_sock_ops_cb_flags_set(ctx, BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG | BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG); if (rv < 0) { bpf_printk("Failed to setup flags, %d", rv); return 0; } switch (ctx->op) { case BPF_SOCK_OPS_HDR_OPT_LEN_CB: bpf_printk("setting BPF opt len, op == %d", ctx->op); long opt_ptr = bpf_reserve_hdr_opt(ctx, TR_OPTION_LEN, 0); if (opt_ptr < 0) { bpf_printk("Failed to reserve TCP option space\n"); return 0; } return 1; case BPF_SOCK_OPS_WRITE_HDR_OPT_CB: bpf_printk("Writing option, op == %d", ctx->op); // 直接定义数组,避免指针赋值隐患 __u8 tcp_option[] = {TR_OPTION_ID, TR_OPTION_LEN, 0x01, 0x02}; int ret = bpf_store_hdr_opt(ctx, tcp_option, TR_OPTION_LEN, 0); if (ret < 0) { bpf_printk("Failed to write TCP option (error %d)\n", ret); return 0; } bpf_printk("Adding experimental option\n"); return 1; case BPF_SOCK_OPS_PARSE_HDR_OPT_CB: bpf_printk("Parsing TCP options"); // 此处添加你的TCP选项解析逻辑 return 1; default: return 1; } return 1; }
内容的提问来源于stack exchange,提问作者hugo rimlinger
相关产品推荐
相关产品推荐

