You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

启用双标志后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标志,会只触发解析回调。

解决方法

  1. 使用位或操作一次性设置多个标志:将两个标志通过|组合,一次调用bpf_sock_ops_cb_flags_set完成设置,避免覆盖。
  2. 修正代码中的数组定义问题:原代码中__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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:20:02