我們一起聊聊TCP 跟踪點
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 内核维护者中。