我們一起聊聊TCP 跟踪點

2022.12.10
我們一起聊聊TCP 跟踪點

我可以想像這些TCP 跟踪點將是多麼有用,因為我多年前設計和使用了類似的跟踪點:我在CEC2006 上演示的DTrace TCP 提供程序。我最初將TCP狀態更改拆分為每個狀態的探針,但是當它合併時,它變成了一個單獨的tcp:::狀態更改探針,就像我們現在通過襪子跟踪點在Linux中一樣。

TCP跟踪點已經到達Linux!Linux 4.15 增加了其中的五個,4.16(尚未完全發布)又增加了兩個(tcp:tcp_probe 和sock:inet_sock_set_state——一個可用於TCP 分析的套接字跟踪點)。我們現在有:

# perf list 'tcp:*' 'sock:inet*'

List of pre-defined events (to be used in -e):

  tcp:tcp_destroy_sock                               [Tracepoint event]
  tcp:tcp_probe                                      [Tracepoint event]
  tcp:tcp_receive_reset                              [Tracepoint event]
  tcp:tcp_retransmit_skb                             [Tracepoint event]
  tcp:tcp_retransmit_synack                          [Tracepoint event]
  tcp:tcp_send_reset                                 [Tracepoint event]

  sock:inet_sock_set_state                           [Tracepoint event]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

這包括一個多功能的:襪子:inet_sock_set_state。它可用於跟踪內核何時更改TCP 會話的狀態,例如從TCP_SYN_SENT更改為TCP_ESTABLISHED。一個例子是我在開源bcc集合中的tcplife工具:

# tcplife
PID   COMM       LADDR           LPORT RADDR           RPORT TX_KB RX_KB MS
22597 recordProg 127.0.0.1       46644 127.0.0.1       28527     0     0 0.23
3277  redis-serv 127.0.0.1       28527 127.0.0.1       46644     0     0 0.28
22598 curl       100.66.3.172    61620 52.205.89.26    80        0     1 91.79
22604 curl       100.66.3.172    44400 52.204.43.121   80        0     1 121.38
22624 recordProg 127.0.0.1       46648 127.0.0.1       28527     0     0 0.22
3277  redis-serv 127.0.0.1       28527 127.0.0.1       46648     0     0 0.27
[...]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

我在這個跟踪點存在之前編寫了tcplife,所以我不得不使用tcp_set_state() 內核函數的kprobes(內核動態跟踪)。這有效,但它依賴於各種內核實現細節,這些細節可能會從一個內核版本更改為下一個內核版本。為了保持tcplife 正常工作,每次內核更改時都需要包含不同的代碼,這將變得難以維護和增強。想像一下,需要在幾個不同的內核版本上測試更改,因為tcplife 為每個版本都有特殊的代碼!

跟踪點被認為是一個“穩定的API”,因此它們的詳細信息不應該從一個內核版本更改為下一個內核版本,從而使使用它們的程序更容易維護。我故意說“不應該”,因為我認為這些是“盡最大努力”而不是“一成不變的”。如果它們被認為是一成不變的,那麼說服內核維護者接受新的跟踪點將更加困難(有充分的理由)。舉個例子:tcp:tcp_set_state是在4.15 中添加的,然後在4.16 中添加了sock:inet_sock_set_state。由於襪子是超集,因此tcp 在4.16 中被禁用,將被移除。我們盡量避免像這樣更改跟踪點,但在這種情況下,它是短暫的,並且在任何人使用它之前就被刪除了。

無論如何,tcplife 並不是使用跟踪點的一個很好的例子,因為它在三個地方(tx 和rx 字節,以及跟踪點上盡力而為的進程上下文)超出了跟踪點API,因此它可能仍然需要一些維護。但這是對kprobes 版本的一個很大的改進,其他工具只能堅持使用跟踪點API。

显示sock:inet_sock_set_state的另一种方法是使用 Sasha Goldshtein 的 bcc 跟踪工具将其与 tcp_set_state() 上的 kprobes 进行比较。第一个命令使用 kprobes,第二个命令使用跟踪点:

# trace -t -I net/sock.h 'p::tcp_set_state(struct sock *sk) "%llx: %d -> %d", sk, sk->sk_state, arg2'
TIME     PID     TID     COMM            FUNC             -
2.583525 17320   17320   curl            tcp_set_state    ffff9fd7db588000: 7 -> 2
2.584449 0       0       swapper/5       tcp_set_state    ffff9fd7db588000: 2 -> 1
2.623158 17320   17320   curl            tcp_set_state    ffff9fd7db588000: 1 -> 4
2.623540 0       0       swapper/5       tcp_set_state    ffff9fd7db588000: 4 -> 5
2.623552 0       0       swapper/5       tcp_set_state    ffff9fd7db588000: 5 -> 7
^C
# trace -t 't:sock:inet_sock_set_state "%llx: %d -> %d", args->skaddr, args->oldstate, args->newstate'
TIME     PID     TID     COMM            FUNC             -
1.690191 17308   17308   curl            inet_sock_set_state ffff9fd7db589800: 7 -> 2
1.690798 0       0       swapper/24      inet_sock_set_state ffff9fd7db589800: 2 -> 1
1.727750 17308   17308   curl            inet_sock_set_state ffff9fd7db589800: 1 -> 4
1.728041 0       0       swapper/24      inet_sock_set_state ffff9fd7db589800: 4 -> 5
1.728063 0       0       swapper/24      inet_sock_set_state ffff9fd7db589800: 5 -> 7
^C
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

两者都显示相同的输出。供参考:

1: TCP_ESTABLISHED

2: TCP_SYN_SENT

3: TCP_SYN_RECV

4: TCP_FIN_WAIT1 5: TCP_FIN_WAIT2 6: TCP_TIME_WAIT

7: TCP_CLOSE

8:TCP_CLOSE_WAIT

我知道,我知道,我应该把它添加为查找哈希,然后......过了一会儿,这是我刚刚为密件抄送贡献的一个新工具 - tcpstate,它进行翻译,并显示每个州的持续时间:

# tcpstates
SKADDR           C-PID C-COMM     LADDR           LPORT RADDR         RPORT OLDSTATE    -> NEWSTATE    MS
ffff9fd7e8192000 22384 curl       100.66.100.185  0     52.33.159.26  80    CLOSE       -> SYN_SENT    0.000
ffff9fd7e8192000 0     swapper/5  100.66.100.185  63446 52.33.159.26  80    SYN_SENT    -> ESTABLISHED 1.373
ffff9fd7e8192000 22384 curl       100.66.100.185  63446 52.33.159.26  80    ESTABLISHED -> FIN_WAIT1   176.042
ffff9fd7e8192000 0     swapper/5  100.66.100.185  63446 52.33.159.26  80    FIN_WAIT1   -> FIN_WAIT2   0.536
ffff9fd7e8192000 0     swapper/5  100.66.100.185  63446 52.33.159.26  80    FIN_WAIT2   -> CLOSE       0.006
^C
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

我在 Linux 4.16 上演示了这一点,此前 Yafang Shao 编写了一个增强功能来显示所有状态转换,而不仅仅是内核实现的状态转换。这是它在 4.15 上的样子:

 trace -I net/sock.h -t 'p::tcp_set_state(struct sock *sk) "%llx: %d -> %d", sk, sk->sk_state, arg2'
TIME     PID    TID    COMM         FUNC             -
3.275865 29039  29039  curl         tcp_set_state    ffff8803a8213800: 7 -> 2
3.277447 0      0      swapper/1    tcp_set_state    ffff8803a8213800: 2 -> 1
3.786203 29039  29039  curl         tcp_set_state    ffff8803a8213800: 1 -> 8
3.794016 29039  29039  curl         tcp_set_state    ffff8803a8213800: 8 -> 7
^C
# trace -t 't:tcp:tcp_set_state "%llx: %d -> %d", args->skaddr, args->oldstate, args->newstate'
TIME     PID    TID    COMM         FUNC             -
2.031911 29042  29042  curl         tcp_set_state    ffff8803a8213000: 7 -> 2
2.035019 0      0      swapper/1    tcp_set_state    ffff8803a8213000: 2 -> 1
2.746864 29042  29042  curl         tcp_set_state    ffff8803a8213000: 1 -> 8
2.754193 29042  29042  curl         tcp_set_state    ffff8803a8213000: 8 -> 7
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

回到 4.16,这是通过 bcc 的 tplist 工具提供的带有参数的当前跟踪点列表:

# tplist -v 'tcp:*'
tcp:tcp_retransmit_skb
    const void * skbaddr;
    const void * skaddr;
    __u16 sport;
    __u16 dport;
    __u8 saddr[4];
    __u8 daddr[4];
    __u8 saddr_v6[16];
    __u8 daddr_v6[16];
tcp:tcp_send_reset
    const void * skbaddr;
    const void * skaddr;
    __u16 sport;
    __u16 dport;
    __u8 saddr[4];
    __u8 daddr[4];
    __u8 saddr_v6[16];
    __u8 daddr_v6[16];
tcp:tcp_receive_reset
    const void * skaddr;
    __u16 sport;
    __u16 dport;
    __u8 saddr[4];
    __u8 daddr[4];
    __u8 saddr_v6[16];
    __u8 daddr_v6[16];
tcp:tcp_destroy_sock
    const void * skaddr;
    __u16 sport;
    __u16 dport;
    __u8 saddr[4];
    __u8 daddr[4];
    __u8 saddr_v6[16];
    __u8 daddr_v6[16];
tcp:tcp_retransmit_synack
    const void * skaddr;
    const void * req;
    __u16 sport;
    __u16 dport;
    __u8 saddr[4];
    __u8 daddr[4];
    __u8 saddr_v6[16];
    __u8 daddr_v6[16];
tcp:tcp_probe
    __u8 saddr[sizeof(struct sockaddr_in6)];
    __u8 daddr[sizeof(struct sockaddr_in6)];
    __u16 sport;
    __u16 dport;
    __u32 mark;
    __u16 length;
    __u32 snd_nxt;
    __u32 snd_una;
    __u32 snd_cwnd;
    __u32 ssthresh;
    __u32 snd_wnd;
    __u32 srtt;
    __u32 rcv_wnd;
# tplist -v sock:inet_sock_set_state
sock:inet_sock_set_state
    const void * skaddr;
    int oldstate;
    int newstate;
    __u16 sport;
    __u16 dport;
    __u16 family;
    __u8 protocol;
    __u8 saddr[4];
    __u8 daddr[4];
    __u8 saddr_v6[16];
    __u8 daddr_v6[16];
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.

添加的第一个TCP跟踪点是Cong Wang(Twitter)的tcp:tcp_retransmit_skb。他引用了我来自perf-tools的基于kprobe的tcpretrans工具作为示例消费者。Song Liu(Facebook)又增加了五个跟踪点,包括tcp:tcp_set_state现在是sock:inet_sock_set_state。感谢 Cong 和 Song,以及 David S. Miller(网络维护者)接受这些内容,并对正在进行的 tcp 跟踪点工作提供反馈。

在开发过程中,我与 Song(和 Alexei Starovoitov)讨论了最近添加的内容,所以我已经知道了它们存在的原因及其用途。当前 TCP 跟踪点的一些粗略说明:

  • tcp:tcp_retransmit_skb:跟踪重新传输。有助于了解网络问题,包括拥塞。将由我的 tcpretrans 工具而不是 kprobes 使用。
  • tcp:tcp_retransmit_synack:跟踪 SYN/ACK 重新传输。我想这可以用于一种 DoS 检测(SYN 洪水触发 SYN/ACK,然后重新传输)。这与 tcp:tcp_retransmit_skb 是分开的,因为此事件没有 skb。
  • tcp:tcp_destroy_sock:任何总结 TCP 会话内存中详细信息的程序都需要,该会话将由袜子地址键控。此探测器可用于知道会话是否已结束,以便将重用 sock 地址,并且应使用到目前为止的任何汇总数据,然后删除。
  • tcp:tcp_send_reset:这在有效套接字期间跟踪 RST 发送,以诊断这些类型的问题。
  • tcp:tcp_receive_reset:跟踪 RST 接收。
  • tcp:tcp_probe:用于 TCP 拥塞窗口跟踪,这也允许弃用和删除较旧的 TCP 探测模块。这是由Masami Hiramatsu添加的,并合并到Linux 4.16中。
  • 袜子:inet_sock_set_state:可用于许多事情。tcplife工具就是其中之一,但我的tcpconnect和tcpaccept bcc工具也可以转换为使用此跟踪点。我们可以添加单独的tcp:tcp_connect和tcp:tcp_accept跟踪点(或tcp:tcp_active_open和tcp:tcp_passive_open),但sock:inet_sock_set_state可以用于此目的。

我可以想象这些 TCP 跟踪点将是多么有用,因为我多年前设计和使用了类似的跟踪点:我在CEC2006 上演示的DTrace TCP 提供程序。我最初将TCP状态更改拆分为每个状态的探针,但是当它合并时,它变成了一个单独的tcp:::状态更改探针,就像我们现在通过袜子跟踪点在Linux中一样。

下一步是什么?tcp:tcp_send和tcp:tcp_receive的跟踪点可能很方便,但必须特别注意它们可能增加的微小开销(启用和特别禁用),因为发送/接收可能是一个非常频繁的活动。错误条件的更多跟踪点也很有用,例如连接拒绝路径,这可能有助于分析 DoS 攻击。

如果您对添加 TCP 跟踪点感兴趣,我建议您首先编写一个 kprobe 解决方案作为概念验证,并获得一些生产经验。这就是我之前的kprobe工具所扮演的角色。kprobe 解决方案将显示跟踪点是否有用,如果是,则有助于将其包含在 Linux 内核维护者中。