searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

基于eBPF实现TCP连接监控

2023-06-26 09:57:16
282
0

一. 技术背景

    在云服务器上排查网络连接异常的问题,常用的有几种方式。一个方式是,使用tcpdump抓包,然后分析目标连接的报文。这种方式依赖运维或研发人员的经验。由于tcpdump抓包性能消耗大,不适宜长期开启。所以需要在异常连接发生时段才开启抓包,容易错过定位问题的时机。另一个方式是,在业务日志中对传输层的错误输出采集日志,此方式采集的信息不一定完整,可能无法精确定位问题。

      基于Linux内核协议栈实现异常连接监控,具备精确,低资源消耗的有点。但是这涉及到修改相关的内核模块,并且技术门槛较高。同时,对内核的修改可能需要较长的时间才能部署到生产环境,无法适应当今的devops流程。eBPF技术的出现,为内核安全执行动态代码,提供了革命性的技术。

    eBPF可以看作是内核态的虚拟机。开发人员通过C语言或者其他支持的语言编写eBPF代码,通过编译器编译成eBPF字节码,注入到内核提供的hook点。在相应的内核事件触发时,执行相关的代码。通过eBPF技术,可以动态的往内核添加监控代码,实现高效,精确的监控需求。本文尝试通过几个案例,介绍利用eBPF工具链监控tcp连接异常的方法。

二. eBPF工具简介

2.1 bpftool

下载地址:https://github.com/libbpf/bpftool

bpftool是用来加载,检视eBPF程序,eBPF Map数据的主要工具。

查看已加载的eBPF程序:

# bpftool prog show

3: cgroup_device  tag 3650d9673c54ce30  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 504B  jited 309B  memlock 4096B
 pids systemd(1)
4: cgroup_skb  tag 6deef7357e7b4530  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 64B  jited 54B  memlock 4096B
 pids systemd(1)
5: cgroup_skb  tag 6deef7357e7b4530  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 64B  jited 54B  memlock 4096B
 pids systemd(1)
6: cgroup_device  tag e0428d4db95f3ed4  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 496B  jited 307B  memlock 4096B
 pids systemd(1)
7: cgroup_skb  tag 6deef7357e7b4530  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 64B  jited 54B  memlock 4096B
 pids systemd(1)

查看单个eBPF程序的字节码:

# bpftool prog dump xlated id 3

  0: (61) r2 = *(u32 *)(r1 +0)
   1: (54) w2 &= 65535
   2: (61) r3 = *(u32 *)(r1 +0)
   3: (74) w3 >>= 16
   4: (61) r4 = *(u32 *)(r1 +4)
   5: (61) r5 = *(u32 *)(r1 +8)
   6: (55) if r2 != 0x2 goto pc+3
   7: (55) if r4 != 0x1 goto pc+2
   8: (55) if r5 != 0x3 goto pc+1
   9: (05) goto pc+51
  10: (55) if r2 != 0x2 goto pc+3
  11: (55) if r4 != 0x1 goto pc+2
  12: (55) if r5 != 0x5 goto pc+1
  13: (05) goto pc+47

查看eBPF map:

# bpftool map show

49: lru_hash  name IPV4_FRAG_DATAG  flags 0x0
 key 12B  value 4B  max_entries 8192  memlock 724992B
 btf_id 2603
5761: array  name REDIRECT_MAP  flags 0x0
 key 4B  value 8B  max_entries 65536  memlock 528384B
 btf_id 10847

 

# bpftool map dump id 5761

[{
        "key": 0,
        "value": {
            "daddr": 0,
            "dport": 0,
            "padding": 0
        }
    },{
        "key": 1,
        "value": {
            "daddr": 0,
            "dport": 0,
            "padding": 0
        }

2.2 bpftrace

bpftrace 是基于eBPF开发的高级动态追踪工具。bpftrace提供类脚本语言的前端,利用LLVM后端编译成eBPF代码。利用bpftrace,只需要很少的代码,就可以追踪CPU使用率,协议栈,系统调用等丰富的内核事件,例如:追踪进程读取的字节数

 

# bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret/ { @[comm] = sum(args->ret); }'

@[containerd]: 4
@[BgSchPool]: 331
@[sshd]: 938
@[cat]: 2511
@[TraceCollector]: 3936
@[bash]: 5174
@[vmtoolsd]: 14233

三. 追踪TCP连接案例

3.1 追踪tcp连接socket关闭调用栈:

编辑如下代码,保存为tcpdrop.bt

#!/usr/bin/env bpftrace
/*
 * tcpdrop.bt   Trace TCP kernel-dropped packets/segments.
 *              For Linux, uses bpftrace and eBPF.
 *
 * USAGE: tcpdrop.bt
 *
 * This is a bpftrace version of the bcc tool of the same name.
 * It is limited to ipv4 addresses, and cannot show tcp flags.
 *
 * This provides information such as packet details, socket state, and kernel
 * stack trace for packets/segments that were dropped via kfree_skb.
 *
 * For Linux 5.17+ (see tools/old for script for lower versions).
 *
 * Copyright (c) 2018 Dale Hamel.
 * Licensed under the Apache License, Version 2.0 (the "License")
 *
 * 23-Nov-2018 Dale Hamel created this.
 * 01-Oct-2022 Rong Tao use tracepoint:skb:kfree_skb
 */
 
#ifndef BPFTRACE_HAVE_BTF
#include <linux/socket.h>
#include <net/sock.h>
#else
#include <sys/socket.h>
#endif
 
BEGIN
{
  printf("Tracing tcp drops. Hit Ctrl-C to end.\n");
  printf("%-8s %-8s %-16s %-21s %-21s %-8s\n", "TIME", "PID", "COMM", "SADDR:SPORT", "DADDR:DPORT", "STATE");
 
  // See https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h
  @tcp_states[1] = "ESTABLISHED";
  @tcp_states[2] = "SYN_SENT";
  @tcp_states[3] = "SYN_RECV";
  @tcp_states[4] = "FIN_WAIT1";
  @tcp_states[5] = "FIN_WAIT2";
  @tcp_states[6] = "TIME_WAIT";
  @tcp_states[7] = "CLOSE";
  @tcp_states[8] = "CLOSE_WAIT";
  @tcp_states[9] = "LAST_ACK";
  @tcp_states[10] = "LISTEN";
  @tcp_states[11] = "CLOSING";
  @tcp_states[12] = "NEW_SYN_RECV";
}
 
tracepoint:skb:consume_skb
{
  $skb = (struct sk_buff *)args->skbaddr;
  $sk = ((struct sock *) $skb->sk);
  $inet_family = $sk->__sk_common.skc_family;
 
  if ($inet_family == AF_INET || $inet_family == AF_INET6) {
    if ($inet_family == AF_INET) {
      $daddr = ntop($sk->__sk_common.skc_daddr);
      $saddr = ntop($sk->__sk_common.skc_rcv_saddr);
    } else {
      $daddr = ntop($sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8);
      $saddr = ntop($sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8);
    }
    $lport = $sk->__sk_common.skc_num;
    $dport = $sk->__sk_common.skc_dport;
 
    // Destination port is big endian, it must be flipped
    $dport = bswap($dport);
 
    $state = $sk->__sk_common.skc_state;
    $statestr = @tcp_states[$state];
    if ($state <= 3) {
return;
    }
 
    time("%H:%M:%S ");
    printf("%-8d %-16s ", pid, comm);
    printf("%39s:%-6d %39s:%-6d %-10s\n", $saddr, $lport, $daddr, $dport, $statestr);
    printf("%s\n", kstack);
  }
}
 
END
{
  clear(@tcp_states);
}

执行:bpftrace tcpdrop.bt,输出

16:09:51 0        swapper/1                                        0.0.0.0:6                                      0.0.0.0:0      CLOSE

        napi_consume_skb+134
        napi_consume_skb+134
        e1000_unmap_and_free_tx_resource+75
        e1000_clean+285
        __napi_poll+41
        net_rx_action+674
        __softirqentry_text_start+232
        __irq_exit_rcu+201
        common_interrupt+184
        asm_common_interrupt+34
        native_safe_halt+11
        acpi_safe_halt+31
        acpi_idle_enter+257
        cpuidle_enter_state+134
        cpuidle_enter+41
        do_idle+478
        cpu_startup_entry+25
        start_secondary+274
        secondary_startup_64_no_verify+211

这段代码跟踪内核函数consume_skb,此函数在连接关闭,释放skb时调用。统计并输出连接五元组信息及调用栈。

0条评论
0 / 1000
洪****煌
1文章数
0粉丝数
洪****煌
1 文章 | 0 粉丝
洪****煌
1文章数
0粉丝数
洪****煌
1 文章 | 0 粉丝
原创

基于eBPF实现TCP连接监控

2023-06-26 09:57:16
282
0

一. 技术背景

    在云服务器上排查网络连接异常的问题,常用的有几种方式。一个方式是,使用tcpdump抓包,然后分析目标连接的报文。这种方式依赖运维或研发人员的经验。由于tcpdump抓包性能消耗大,不适宜长期开启。所以需要在异常连接发生时段才开启抓包,容易错过定位问题的时机。另一个方式是,在业务日志中对传输层的错误输出采集日志,此方式采集的信息不一定完整,可能无法精确定位问题。

      基于Linux内核协议栈实现异常连接监控,具备精确,低资源消耗的有点。但是这涉及到修改相关的内核模块,并且技术门槛较高。同时,对内核的修改可能需要较长的时间才能部署到生产环境,无法适应当今的devops流程。eBPF技术的出现,为内核安全执行动态代码,提供了革命性的技术。

    eBPF可以看作是内核态的虚拟机。开发人员通过C语言或者其他支持的语言编写eBPF代码,通过编译器编译成eBPF字节码,注入到内核提供的hook点。在相应的内核事件触发时,执行相关的代码。通过eBPF技术,可以动态的往内核添加监控代码,实现高效,精确的监控需求。本文尝试通过几个案例,介绍利用eBPF工具链监控tcp连接异常的方法。

二. eBPF工具简介

2.1 bpftool

下载地址:https://github.com/libbpf/bpftool

bpftool是用来加载,检视eBPF程序,eBPF Map数据的主要工具。

查看已加载的eBPF程序:

# bpftool prog show

3: cgroup_device  tag 3650d9673c54ce30  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 504B  jited 309B  memlock 4096B
 pids systemd(1)
4: cgroup_skb  tag 6deef7357e7b4530  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 64B  jited 54B  memlock 4096B
 pids systemd(1)
5: cgroup_skb  tag 6deef7357e7b4530  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 64B  jited 54B  memlock 4096B
 pids systemd(1)
6: cgroup_device  tag e0428d4db95f3ed4  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 496B  jited 307B  memlock 4096B
 pids systemd(1)
7: cgroup_skb  tag 6deef7357e7b4530  gpl
 loaded_at 2023-06-26T14:47:34+0800  uid 0
 xlated 64B  jited 54B  memlock 4096B
 pids systemd(1)

查看单个eBPF程序的字节码:

# bpftool prog dump xlated id 3

  0: (61) r2 = *(u32 *)(r1 +0)
   1: (54) w2 &= 65535
   2: (61) r3 = *(u32 *)(r1 +0)
   3: (74) w3 >>= 16
   4: (61) r4 = *(u32 *)(r1 +4)
   5: (61) r5 = *(u32 *)(r1 +8)
   6: (55) if r2 != 0x2 goto pc+3
   7: (55) if r4 != 0x1 goto pc+2
   8: (55) if r5 != 0x3 goto pc+1
   9: (05) goto pc+51
  10: (55) if r2 != 0x2 goto pc+3
  11: (55) if r4 != 0x1 goto pc+2
  12: (55) if r5 != 0x5 goto pc+1
  13: (05) goto pc+47

查看eBPF map:

# bpftool map show

49: lru_hash  name IPV4_FRAG_DATAG  flags 0x0
 key 12B  value 4B  max_entries 8192  memlock 724992B
 btf_id 2603
5761: array  name REDIRECT_MAP  flags 0x0
 key 4B  value 8B  max_entries 65536  memlock 528384B
 btf_id 10847

 

# bpftool map dump id 5761

[{
        "key": 0,
        "value": {
            "daddr": 0,
            "dport": 0,
            "padding": 0
        }
    },{
        "key": 1,
        "value": {
            "daddr": 0,
            "dport": 0,
            "padding": 0
        }

2.2 bpftrace

bpftrace 是基于eBPF开发的高级动态追踪工具。bpftrace提供类脚本语言的前端,利用LLVM后端编译成eBPF代码。利用bpftrace,只需要很少的代码,就可以追踪CPU使用率,协议栈,系统调用等丰富的内核事件,例如:追踪进程读取的字节数

 

# bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret/ { @[comm] = sum(args->ret); }'

@[containerd]: 4
@[BgSchPool]: 331
@[sshd]: 938
@[cat]: 2511
@[TraceCollector]: 3936
@[bash]: 5174
@[vmtoolsd]: 14233

三. 追踪TCP连接案例

3.1 追踪tcp连接socket关闭调用栈:

编辑如下代码,保存为tcpdrop.bt

#!/usr/bin/env bpftrace
/*
 * tcpdrop.bt   Trace TCP kernel-dropped packets/segments.
 *              For Linux, uses bpftrace and eBPF.
 *
 * USAGE: tcpdrop.bt
 *
 * This is a bpftrace version of the bcc tool of the same name.
 * It is limited to ipv4 addresses, and cannot show tcp flags.
 *
 * This provides information such as packet details, socket state, and kernel
 * stack trace for packets/segments that were dropped via kfree_skb.
 *
 * For Linux 5.17+ (see tools/old for script for lower versions).
 *
 * Copyright (c) 2018 Dale Hamel.
 * Licensed under the Apache License, Version 2.0 (the "License")
 *
 * 23-Nov-2018 Dale Hamel created this.
 * 01-Oct-2022 Rong Tao use tracepoint:skb:kfree_skb
 */
 
#ifndef BPFTRACE_HAVE_BTF
#include <linux/socket.h>
#include <net/sock.h>
#else
#include <sys/socket.h>
#endif
 
BEGIN
{
  printf("Tracing tcp drops. Hit Ctrl-C to end.\n");
  printf("%-8s %-8s %-16s %-21s %-21s %-8s\n", "TIME", "PID", "COMM", "SADDR:SPORT", "DADDR:DPORT", "STATE");
 
  // See https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h
  @tcp_states[1] = "ESTABLISHED";
  @tcp_states[2] = "SYN_SENT";
  @tcp_states[3] = "SYN_RECV";
  @tcp_states[4] = "FIN_WAIT1";
  @tcp_states[5] = "FIN_WAIT2";
  @tcp_states[6] = "TIME_WAIT";
  @tcp_states[7] = "CLOSE";
  @tcp_states[8] = "CLOSE_WAIT";
  @tcp_states[9] = "LAST_ACK";
  @tcp_states[10] = "LISTEN";
  @tcp_states[11] = "CLOSING";
  @tcp_states[12] = "NEW_SYN_RECV";
}
 
tracepoint:skb:consume_skb
{
  $skb = (struct sk_buff *)args->skbaddr;
  $sk = ((struct sock *) $skb->sk);
  $inet_family = $sk->__sk_common.skc_family;
 
  if ($inet_family == AF_INET || $inet_family == AF_INET6) {
    if ($inet_family == AF_INET) {
      $daddr = ntop($sk->__sk_common.skc_daddr);
      $saddr = ntop($sk->__sk_common.skc_rcv_saddr);
    } else {
      $daddr = ntop($sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8);
      $saddr = ntop($sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8);
    }
    $lport = $sk->__sk_common.skc_num;
    $dport = $sk->__sk_common.skc_dport;
 
    // Destination port is big endian, it must be flipped
    $dport = bswap($dport);
 
    $state = $sk->__sk_common.skc_state;
    $statestr = @tcp_states[$state];
    if ($state <= 3) {
return;
    }
 
    time("%H:%M:%S ");
    printf("%-8d %-16s ", pid, comm);
    printf("%39s:%-6d %39s:%-6d %-10s\n", $saddr, $lport, $daddr, $dport, $statestr);
    printf("%s\n", kstack);
  }
}
 
END
{
  clear(@tcp_states);
}

执行:bpftrace tcpdrop.bt,输出

16:09:51 0        swapper/1                                        0.0.0.0:6                                      0.0.0.0:0      CLOSE

        napi_consume_skb+134
        napi_consume_skb+134
        e1000_unmap_and_free_tx_resource+75
        e1000_clean+285
        __napi_poll+41
        net_rx_action+674
        __softirqentry_text_start+232
        __irq_exit_rcu+201
        common_interrupt+184
        asm_common_interrupt+34
        native_safe_halt+11
        acpi_safe_halt+31
        acpi_idle_enter+257
        cpuidle_enter_state+134
        cpuidle_enter+41
        do_idle+478
        cpu_startup_entry+25
        start_secondary+274
        secondary_startup_64_no_verify+211

这段代码跟踪内核函数consume_skb,此函数在连接关闭,释放skb时调用。统计并输出连接五元组信息及调用栈。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0