使用eBPF追踪OpenSSL TLS流量时如何获取真实套接字文件描述符
解决OpenSSL 1.1.1 eBPF追踪中获取真实套接字FD的问题
你遇到的核心问题是:从SSL结构体的rbio/wbio中直接读取的num字段是上层过滤BIO的内部标识,而非底层真实的套接字文件描述符。OpenSSL 1.1.1的BIO采用链式分层设计,SSL对象关联的BIO通常是缓冲、加密等功能型BIO,而非直接对接套接字的原始BIO。
原因分析
OpenSSL的BIO链结构逻辑:
- 应用建立TLS连接时,会先创建套接字BIO,再在其上层叠加缓冲BIO、SSL加密BIO等功能节点,形成完整的处理链。
- 你当前获取的
num=5是上层过滤BIO的内部编号,并非套接字FD,因此调用fstat、getsockname会返回非套接字错误。
解决方案:遍历BIO链定位套接字BIO
需要顺着BIO的next指针遍历整个链,找到类型为BIO_TYPE_SOCKET的BIO节点,它的num字段才是真实的套接字FD。
1. 定义必要的结构体片段
在eBPF程序中定义适配OpenSSL 1.1.1的结构体片段,保留关键字段:
// BIO结构体核心片段(需匹配目标OpenSSL库的字段偏移) struct bio_st { int type; // BIO类型标识 struct bio_st *next; // 链式结构的下一个节点指针 int num; // 套接字BIO的num字段对应真实FD }; // SSL结构体核心片段 struct ssl_st { struct bio_st *rbio; // 读操作关联的BIO链头 struct bio_st *wbio; // 写操作关联的BIO链头 };
2. 在eBPF探针中遍历BIO链
以SSL_read的uprobe为例,实现真实FD的获取逻辑:
SEC("uprobe/SSL_read") int BPF_KPROBE(ssl_read, struct ssl_st *ssl) { struct bio_st *bio = NULL; bpf_core_read(&bio, sizeof(bio), &ssl->rbio); // 遍历BIO链查找套接字类型节点 while (bio != NULL) { int bio_type; bpf_core_read(&bio_type, sizeof(bio_type), &bio->type); // BIO_TYPE_SOCKET的宏定义值为1(可参考OpenSSL头文件确认) if (bio_type == 1) { int real_fd; bpf_core_read(&real_fd, sizeof(real_fd), &bio->num); bpf_printk("Real socket FD: %d", real_fd); // 后续可将real_fd与进程PID关联,传递到用户空间处理 break; } // 移动到下一个BIO节点 struct bio_st *next_bio; bpf_core_read(&next_bio, sizeof(next_bio), &bio->next); bio = next_bio; } return 0; }
3. 关键注意事项
- 字段偏移适配:不同编译环境下OpenSSL结构体的字段偏移可能存在差异,建议使用
pahole工具分析目标OpenSSL库的结构体布局,或用bpf_core_read(libbpf原生支持)安全读取字段,避免因偏移错误导致的读取异常。 - 命名空间问题:用户空间调用
getsockname/getpeername时,需确保当前进程与目标进程(如wget、curl)处于同一PID命名空间,否则FD无法直接复用。可通过eBPF程序关联FD与进程PID,再在用户空间通过nsenter进入目标进程命名空间后执行操作。
验证方法
修改测试代码,拿到真实FD后再次调用fstat和print_socket_details,应能正确识别为套接字并输出端点信息。
内容的提问来源于stack exchange,提问作者Kyle Dong
相关产品推荐
相关产品推荐

