基于eBPF/XDP扩展程序检测OpenConnect流量并提取内层IP目的地址
扩展XDP程序实现OpenConnect流量解析与内层IP捕获
1. 识别OpenConnect流量
OpenConnect基于TCP传输,默认使用443端口,可先从端口过滤入手:
- 在eBPF/XDP程序中添加TCP目标端口为443的过滤规则;
- 若需更精准识别,可结合TLS Client Hello中的特征(比如OpenConnect客户端会携带特定SNI或
cisco-anyconnectALPN协议标识),新手阶段优先用端口过滤快速验证。
2. 解封装内层IP包(核心步骤)
OpenConnect的流量分两种场景,处理方式不同:
场景A:监听OpenConnect虚拟网卡(推荐)
在客户端/服务器主机上,OpenConnect会创建tun0(或自定义名称)虚拟网卡,内层IP包直接从该设备流出,无需处理加密:
- 修改eBPF程序的附加逻辑,让它绑定到OpenConnect创建的tun设备(可通过用户态传入设备名参数);
- 在XDP程序中直接解析IP层,提取目的IP地址(tun设备的数据包无以太网帧头,直接从IP层开始)。
场景B:处理物理网卡上的加密流量
如果必须在物理网卡上捕获加密后的OpenConnect流量,需要先解密:
- 在eBPF层标记符合端口/特征的TCP流,将TCP payload转发到用户态;
- 用户态通过TLS会话密钥解密payload(可通过
SSLKEYLOGFILE环境变量从OpenConnect客户端导出密钥),再解析内层IP包。
3. 整合到现有Go用户态程序
- 修改eBPF的事件结构体,新增内层目的IP字段,将提取到的IP地址发送到用户态;
- 在Go代码中扩展解析逻辑:将uint32类型的IP地址转换为字符串格式;
- 复用现有gRPC上报逻辑,把内层目的IP作为新增字段加入上报消息。
代码修改示例
eBPF层(xdp_dump.c)
添加IP解析与事件发送逻辑:
#include <linux/if_ether.h> #include <linux/ip.h> // 定义事件结构体 struct event { __be32 dst_ip; // 保留原有字段... }; // 解析IPv4包,提取目的地址 static __always_inline void parse_inner_ipv4(struct xdp_md *ctx, void *data_end, void *data) { struct iphdr *iph = data; if ((void *)iph + sizeof(*iph) > data_end) return; struct event e = {}; e.dst_ip = iph->daddr; // 发送事件到用户态 xdp_send_event(ctx, &e); } SEC("xdp") int xdp_dump_prog(struct xdp_md *ctx) { void *data_end = (void *)(long)ctx->data_end; void *data = (void *)(long)ctx->data; struct ethhdr *eth = data; if ((void *)eth + sizeof(*eth) > data_end) return XDP_PASS; // 处理物理网卡的以太网帧,或tun设备的直接IP包 if (eth->h_proto == htons(ETH_P_IP)) { // 物理网卡:跳过以太网帧头解析IP parse_inner_ipv4(ctx, data_end, data + sizeof(*eth)); } else { // tun设备:直接解析IP(无以太网帧头) parse_inner_ipv4(ctx, data_end, data); } // 保留原有TCP连接捕获逻辑 // ... return XDP_PASS; }
Go用户态层
扩展事件解析与上报:
import "net" type Event struct { DstIP uint32 `bpf:"dst_ip"` // 原有字段... } // 将uint32格式IP转换为字符串 func (e *Event) DstIPString() string { return net.IP{ byte(e.DstIP), byte(e.DstIP >> 8), byte(e.DstIP >> 16), byte(e.DstIP >> 24), }.String() } // 在gRPC上报函数中添加内层IP字段 func reportEvent(e *Event) { req := &pb.ReportRequest{ InnerDstIp: e.DstIPString(), // 原有上报字段... } // 调用gRPC上报 // ... }
注意事项
- 优先选择场景A(监听虚拟网卡),避免TLS解密的复杂逻辑,更适合新手;
- 若使用场景B,需确保能获取TLS会话密钥,OpenConnect客户端支持通过环境变量导出密钥;
- XDP层尽量只做轻量过滤与提取,复杂解析(如解密)放到用户态,避免影响网络性能。
内容的提问来源于stack exchange,提问作者NobinPegasus
相关产品推荐
相关产品推荐

