在BPF_CGROUP_INET_INGRESS钩子获取入站数据包数据及eBPF程序推荐
针对CGroup入站数据包/套接字数据获取的eBPF方案推荐
你当前使用的BPF_PROG_TYPE_CGROUP_SKB配合BPF_CGROUP_INET_INGRESS钩子,仅能访问skb线性缓冲区的数据(通常对应网络协议头部),若数据包存在非线性分片(比如TCP载荷存储在skb frags中),bpf_skb_load_bytes无法直接读取这部分内容。以下是几种更适合获取完整数据包/套接字数据的eBPF程序类型:
1. BPF_PROG_TYPE_SOCKET_FILTER(套接字过滤器)
- 挂载方式:可绑定到单个套接字,或通过
BPF_CGROUP_INET_SOCK挂载到CGroup,对CGroup内所有新建套接字生效 - 核心优势:能访问完整的套接字数据(包括TCP/UDP载荷),内核5.1及以上版本支持通过
bpf_skb_load_bytes读取线性区和分片数据 - 适用场景:需要针对CGroup内特定套接字或全量套接字的流量做内容分析的场景
2. BPF_PROG_TYPE_XDP(eXpress Data Path)
- 挂载方式:绑定到指定网络接口(支持
XDP_ATTACH_MODE_SKB或XDP_ATTACH_MODE_DRV两种模式) - 核心优势:在网络栈最早期处理数据包,可获取完整原始数据包(包含所有分片),性能远高于传统网络钩子
- CGroup适配:若需限定CGroup范围,可在XDP程序中通过
bpf_get_current_cgroup_id()获取当前进程所属CGroup,再做流量筛选
3. BPF_PROG_TYPE_SOCK_OPS(套接字操作钩子)
- 触发时机:内核5.8及以上版本支持
BPF_SOCK_OPS_TCP_DATA_READ/BPF_SOCK_OPS_TCP_DATA_WRITE事件,对应TCP数据的读写操作 - 核心优势:直接关联到套接字的CGroup上下文,可精准捕获套接字层面的数据收发事件,配合
bpf_skb_load_bytes读取数据包内容
4. BPF_PROG_TYPE_TRACEPOINT(跟踪点)
- 可用跟踪点:
net/netif_receive_skb(数据包进入网络栈时触发)、tcp/tcp_revmsg(TCP接收消息完成时触发)等 - 核心优势:可在网络栈任意阶段捕获数据包,通过
bpf_get_current_cgroup_id()过滤目标CGroup的流量,能访问完整的skb结构体及数据
优化后的CGroup SKB程序示例(读取TCP载荷)
如果希望继续使用BPF_PROG_TYPE_CGROUP_SKB,可通过解析协议头部定位载荷位置,读取线性区内的载荷数据(非线性分片仍无法读取):
#include <linux/in.h> #include <linux/tcp.h> #include <linux/if_ether.h> #include <linux/bpf.h> #include <bpf/bpf_endian.h> #include <bpf/bpf_helpers.h> char LICENSE[] SEC("license") = "GPL"; SEC("cgroup_skb/ingress") int bpf_extract_tcp_payload(struct __sk_buff *skb) { long err; char payload_buf[100] = {0}; struct ethhdr eth; struct iphdr ip; struct tcphdr tcp; int ip_hdr_len, tcp_hdr_len, payload_offset; // 读取以太网头部 err = bpf_skb_load_bytes(skb, 0, ð, sizeof(eth)); if (err < 0) { bpf_printk("Failed to read eth header: %ld\n", err); return SK_PASS; } if (bpf_ntohs(eth.h_proto) != ETH_P_IP) { return SK_PASS; // 仅处理IPv4流量 } // 读取IP头部 err = bpf_skb_load_bytes(skb, sizeof(eth), &ip, sizeof(ip)); if (err < 0) { bpf_printk("Failed to read ip header: %ld\n", err); return SK_PASS; } ip_hdr_len = ip.ihl * 4; // 读取TCP头部 err = bpf_skb_load_bytes(skb, sizeof(eth) + ip_hdr_len, &tcp, sizeof(tcp)); if (err < 0) { bpf_printk("Failed to read tcp header: %ld\n", err); return SK_PASS; } tcp_hdr_len = tcp.doff * 4; payload_offset = sizeof(eth) + ip_hdr_len + tcp_hdr_len; // 读取TCP载荷(仅线性区部分) if (skb->len > payload_offset + sizeof(payload_buf)) { err = bpf_skb_load_bytes(skb, payload_offset, payload_buf, sizeof(payload_buf)); if (err >= 0) { bpf_printk("TCP Payload (partial): %s\n", payload_buf); } else { bpf_printk("Failed to read payload: %ld\n", err); } } return SK_PASS; }
内容的提问来源于stack exchange,提问作者diviquery
相关产品推荐
相关产品推荐

