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]
這包括一個多功能的:襪子: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
[...]
我在這個跟蹤點存在之前編寫了 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: 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
我在 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
回到 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];
添加的第一個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 內核維護者中。