Featured image of post eBPF:使用ebpf观测HTTP/HTTPS

eBPF:使用ebpf观测HTTP/HTTPS

如何使用eBPF跟踪观测HTTP/HTTPS。

观测HTTP

图片来源: 一文详解用eBPF观测HTTP

关于ebpf观测HTTP的介绍,我想一文详解用eBPF观测HTTP[1]这篇文章已经说明的很详细了,因此不在此叙述。由此文我们可以有以下结论:

  1. 在选择观测HTTP的切入点是,我们有三种选择,kprobe,tracepoint,uprobe.我们选择了tracepoint的原因主要是其是内核已经提供好的切入点,相对其他两者比较稳定,且基于TCP 大多数切入点已经被静态化为tracepoints。

  2. 从上图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

  3. 数据的关联与管理.

    • 将连接信息存储于bpf maps中,以{pid,fd}作为key

    • 采用简单基础的规则进行协议前置推导以过滤数据

    图片来源: pixie_ebpf_protocol_tracer_slides.pdf

  4. 面临的挑战

     图片来源: 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进行关联。

1
2
int SSL_read(SSL *ssl, void *buf, int num);
int SSL_write(SSL *ssl, const void *buf, int num);

不过好在经过分析,我们知道从ssl中是可以获取到sockfd的。(详细介绍见pixie,ecapture源码或docs目录下的的文档)

知道{pid,fd}后,一是可以用来标识连接和数据信息,二是我们需要更新这个socket连接为ssl连接,避免再上报由socket的系统调用采集到的数据。例如假设现在一个HTTPS的客户端的传输过程为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
connect
    --->
        SSL_read entry
            syscall read
        SSL_read return
    --->
        SSL_write entry
            syscall write
        SSL_write return
    ---> 
        close

上述这个过程中,经过SSL_read后的数据已经是加密的,当调用堆栈来到系统调用read时,这时的数据已经是加密的。所以,我们应该在SSL_read return时上报数据而忽略嵌入在SSL_read的系统调用。

GnuTLS

对GnuTLS的处理和OpenSSL相似。我们挂钩的函数是:

1
2
3
4
ssize_t gnutls_record_send(gnutls_session_t session, const void *
       data, size_t data_size);
ssize_t gnutls_record_recv(gnutls_session_t session, void * data,
       size_t data_size);

同样的,我们从gnu_session_t中获取到sockfd.

获取sockfd的另一种方法

在上述OpenSSL和GnuTLS的sockfd获取方法中,我们都是通过在其session入参中找到sockfd字段的。现在还有一种方法也是可以获取到sockfd的,首先让我们现在来看一下SSL_read的一种可能调用过程:

1
2
3
4
5
SSL_read entry
    ---> syscall read entry
    // ssize_t read(int fd, void buf[.count], size_t count);
    ---> syscall read return
SSL_read return

我们可以看到调用堆栈是,系统调用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)

哦吼是一首歌。
Built with Hugo
Theme Stack designed by Jimmy