观测HTTP
图片来源: 一文详解用eBPF观测HTTP
关于ebpf观测HTTP的介绍,我想一文详解用eBPF观测HTTP[1]这篇文章已经说明的很详细了,因此不在此叙述。由此文我们可以有以下结论:
-
在选择观测HTTP的切入点是,我们有三种选择,kprobe,tracepoint,uprobe.我们选择了tracepoint的原因主要是其是内核已经提供好的切入点,相对其他两者比较稳定,且基于TCP 大多数切入点已经被静态化为tracepoints。
-
从上图TCP的建立、传输、关闭过程,我们可以选择以下tracepoints切入点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
SEC("tracepoint/syscalls/sys_enter_connect") SEC("tracepoint/syscalls/sys_exit_connect") SEC("tracepoint/syscalls/sys_enter_accept") SEC("tracepoint/syscalls/sys_exit_accept") SEC("tracepoint/syscalls/sys_enter_accept4") SEC("tracepoint/syscalls/sys_exit_accept4") SEC("tracepoint/syscalls/sys_enter_close") SEC("tracepoint/syscalls/sys_exit_close") SEC("tracepoint/syscalls/sys_enter_write") SEC("tracepoint/syscalls/sys_exit_write") SEC("tracepoint/syscalls/sys_enter_read") SEC("tracepoint/syscalls/sys_exit_read") SEC("tracepoint/syscalls/sys_enter_sendmsg") SEC("tracepoint/syscalls/sys_exit_sendmsg") SEC("tracepoint/syscalls/sys_enter_recvmsg") SEC("tracepoint/syscalls/sys_exit_recvmsg")
一个比较基础的过程可以是:
-
Client :connect —>read/write —> close
-
Sever : accept —>read/write —>close
-
-
数据的关联与管理.
-
将连接信息存储于bpf maps中,以{pid,fd}作为key
-
采用简单基础的规则进行协议前置推导以过滤数据
图片来源: pixie_ebpf_protocol_tracer_slides.pdf
-
-
面临的挑战
图片来源: pixie_ebpf_protocol_tracer_slides.pdf
-
所有的文件I/O都会调用read/write,不仅仅是socket,因此就会可能处理很多无关网络的数据.
-
accept的addr参数可能会为NULL(这一点我们已经遇到了),为NULL的时候我们就没法拿到远端地址和端口.
-
sendmsg/recvmsg可能会有多个数据块
-
应用可能在我们部署之前就已经运行,因此必然存在很多已经建立的连接。当我们切入时,并不可能是每次都是
accept-->read/write --->close
这样完美的过程,我们可能会在中间某个点切入.所以对于长连接来说我们是无法知道远端地址和端口的。一种解决方式是在用户态根据pid和fd找到inode然后根据netlink得到。
-
图片来源: pixie_ebpf_protocol_tracer_slides.pdf
观测HTTPS
其实,Pixie那篇博客3介绍得也差不多,因此这只是做补充。
由下图我们可以知道得是,从应用层到socket层,HTTPS比HTTP多经过了一层SSL/TLS的加密之后到socket这一层已经是密文了,要想拿到HTTPS的明文必然是在socket层之前的。
图片来源: pixie_ebpf_protocol_tracer_slides.pdf
SSL/TLS
对HTTPS的观测其实就是对SSL/TLS的观测,而SSL/TLS协议的实现有OpenSSL、GnuTLS、GoTLS,当然还有从OpenSSL fork的BoringSSL、LibreSSL等。
比如对于OPenSSL的跟踪实际上就是对libssl.so库中SSL_read/SSL_write函数的挂钩,在应用调用这两个函数时,捕获其入参。
图片来源: Pixie Team Blogs, Debugging with eBPF Part 3: Tracing SSL/TLS connections.
OpenSSL
前面我们提到过,我们需要{pid,fd}去做为key来存储标识socket连接信息。对于OpenSSL我们就是在SSL_read/SS_write处挂上uprobe来捕获名为的,然而从从SSL_read/SSL_write的原型我们可以看出,单纯的从这两个函数的入参时没法拿到fd的。不过,既然SSL/TLS时建立在socket层之上,那么就必然在某个地方与sockfd进行关联。
|
|
不过好在经过分析,我们知道从ssl中是可以获取到sockfd的。(详细介绍见pixie,ecapture源码或docs目录下的的文档)
知道{pid,fd}后,一是可以用来标识连接和数据信息,二是我们需要更新这个socket连接为ssl连接,避免再上报由socket的系统调用采集到的数据。例如假设现在一个HTTPS的客户端的传输过程为:
|
|
上述这个过程中,经过SSL_read后的数据已经是加密的,当调用堆栈来到系统调用read时,这时的数据已经是加密的。所以,我们应该在SSL_read return时上报数据而忽略嵌入在SSL_read的系统调用。
GnuTLS
对GnuTLS的处理和OpenSSL相似。我们挂钩的函数是:
|
|
同样的,我们从gnu_session_t中获取到sockfd.
获取sockfd的另一种方法
在上述OpenSSL和GnuTLS的sockfd获取方法中,我们都是通过在其session入参中找到sockfd字段的。现在还有一种方法也是可以获取到sockfd的,首先让我们现在来看一下SSL_read的一种可能调用过程:
|
|
我们可以看到调用堆栈是,系统调用read会嵌在SSL_read之中,而对于read而言我们是能显示地从入参就能知道fd的。若我们能知道SSL_read调用时内嵌的read,那么就能找到sockfd,我们可以使用pid+tid将二者进行关联。
参考
一文详解用eBPF观测HTTP - 知乎 (zhihu.com)
Pixie Protocol Parsing (LPC 2021)
Debugging with eBPF Part 3: Tracing SSL/TLS connections | Pixie Labs Blog (px.dev)
Debugging with eBPF Part 2: Tracing full body HTTP request/responses | Pixie Labs Blog (px.dev)