TCP跟蹤點(diǎn)已經(jīng)到達(dá)Linux!Linux 4.15 增加了其中的五個(gè),4.16(尚未完全發(fā)布)又增加了兩個(gè)(tcp:tcp_probe 和sock:inet_sock_set_state——一個(gè)可用于 TCP 分析的套接字跟蹤點(diǎn))。我們現(xiàn)在有:
# 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]
這包括一個(gè)多功能的:襪子:inet_sock_set_state。它可用于跟蹤內(nèi)核何時(shí)更改 TCP 會(huì)話的狀態(tài),例如從TCP_SYN_SENT更改為TCP_ESTABLISHED。一個(gè)例子是我在開源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
[...]
我在這個(gè)跟蹤點(diǎn)存在之前編寫了 tcplife,所以我不得不使用 tcp_set_state() 內(nèi)核函數(shù)的 kprobes(內(nèi)核動(dòng)態(tài)跟蹤)。這有效,但它依賴于各種內(nèi)核實(shí)現(xiàn)細(xì)節(jié),這些細(xì)節(jié)可能會(huì)從一個(gè)內(nèi)核版本更改為下一個(gè)內(nèi)核版本。為了保持 tcplife 正常工作,每次內(nèi)核更改時(shí)都需要包含不同的代碼,這將變得難以維護(hù)和增強(qiáng)。想象一下,需要在幾個(gè)不同的內(nèi)核版本上測(cè)試更改,因?yàn)?tcplife 為每個(gè)版本都有特殊的代碼!
跟蹤點(diǎn)被認(rèn)為是一個(gè)“穩(wěn)定的API”,因此它們的詳細(xì)信息不應(yīng)該從一個(gè)內(nèi)核版本更改為下一個(gè)內(nèi)核版本,從而使使用它們的程序更容易維護(hù)。我故意說(shuō)“不應(yīng)該”,因?yàn)槲艺J(rèn)為這些是“盡最大努力”而不是“一成不變的”。如果它們被認(rèn)為是一成不變的,那么說(shuō)服內(nèi)核維護(hù)者接受新的跟蹤點(diǎn)將更加困難(有充分的理由)。舉個(gè)例子:tcp:tcp_set_state是在 4.15 中添加的,然后在 4.16 中添加了sock:inet_sock_set_state。由于襪子是超集,因此 tcp 在 4.16 中被禁用,將被移除。我們盡量避免像這樣更改跟蹤點(diǎn),但在這種情況下,它是短暫的,并且在任何人使用它之前就被刪除了。
無(wú)論如何,tcplife 并不是使用跟蹤點(diǎn)的一個(gè)很好的例子,因?yàn)樗谌齻€(gè)地方(tx 和 rx 字節(jié),以及跟蹤點(diǎn)上盡力而為的進(jìn)程上下文)超出了跟蹤點(diǎn) API,因此它可能仍然需要一些維護(hù)。但這是對(duì) kprobes 版本的一個(gè)很大的改進(jìn),其他工具只能堅(jiān)持使用跟蹤點(diǎn) API。
顯示sock:inet_sock_set_state的另一種方法是使用 Sasha Goldshtein 的 bcc 跟蹤工具將其與 tcp_set_state() 上的 kprobes 進(jìn)行比較。第一個(gè)命令使用 kprobes,第二個(gè)命令使用跟蹤點(diǎn):
# 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
我知道,我知道,我應(yīng)該把它添加為查找哈希,然后......過(guò)了一會(huì)兒,這是我剛剛為密件抄送貢獻(xiàn)的一個(gè)新工具 - tcpstate,它進(jìn)行翻譯,并顯示每個(gè)州的持續(xù)時(shí)間:
# 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 上演示了這一點(diǎn),此前 Yafang Shao 編寫了一個(gè)增強(qiáng)功能來(lái)顯示所有狀態(tài)轉(zhuǎn)換,而不僅僅是內(nèi)核實(shí)現(xiàn)的狀態(tài)轉(zhuǎn)換。這是它在 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,這是通過(guò) bcc 的 tplist 工具提供的帶有參數(shù)的當(dāng)前跟蹤點(diǎn)列表:
# 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];
添加的第一個(gè)TCP跟蹤點(diǎn)是Cong Wang(Twitter)的tcp:tcp_retransmit_skb。他引用了我來(lái)自perf-tools的基于kprobe的tcpretrans工具作為示例消費(fèi)者。Song Liu(Facebook)又增加了五個(gè)跟蹤點(diǎn),包括tcp:tcp_set_state現(xiàn)在是sock:inet_sock_set_state。感謝 Cong 和 Song,以及 David S. Miller(網(wǎng)絡(luò)維護(hù)者)接受這些內(nèi)容,并對(duì)正在進(jìn)行的 tcp 跟蹤點(diǎn)工作提供反饋。
在開發(fā)過(guò)程中,我與 Song(和 Alexei Starovoitov)討論了最近添加的內(nèi)容,所以我已經(jīng)知道了它們存在的原因及其用途。當(dāng)前 TCP 跟蹤點(diǎn)的一些粗略說(shuō)明:
- tcp:tcp_retransmit_skb:跟蹤重新傳輸。有助于了解網(wǎng)絡(luò)問題,包括擁塞。將由我的 tcpretrans 工具而不是 kprobes 使用。
- tcp:tcp_retransmit_synack:跟蹤 SYN/ACK 重新傳輸。我想這可以用于一種 DoS 檢測(cè)(SYN 洪水觸發(fā) SYN/ACK,然后重新傳輸)。這與 tcp:tcp_retransmit_skb 是分開的,因?yàn)榇耸录]有 skb。
- tcp:tcp_destroy_sock:任何總結(jié) TCP 會(huì)話內(nèi)存中詳細(xì)信息的程序都需要,該會(huì)話將由襪子地址鍵控。此探測(cè)器可用于知道會(huì)話是否已結(jié)束,以便將重用 sock 地址,并且應(yīng)使用到目前為止的任何匯總數(shù)據(jù),然后刪除。
- tcp:tcp_send_reset:這在有效套接字期間跟蹤 RST 發(fā)送,以診斷這些類型的問題。
- tcp:tcp_receive_reset:跟蹤 RST 接收。
- tcp:tcp_probe:用于 TCP 擁塞窗口跟蹤,這也允許棄用和刪除較舊的 TCP 探測(cè)模塊。這是由Masami Hiramatsu添加的,并合并到Linux 4.16中。
- 襪子:inet_sock_set_state:可用于許多事情。tcplife工具就是其中之一,但我的tcpconnect和tcpaccept bcc工具也可以轉(zhuǎn)換為使用此跟蹤點(diǎn)。我們可以添加單獨(dú)的tcp:tcp_connect和tcp:tcp_accept跟蹤點(diǎn)(或tcp:tcp_active_open和tcp:tcp_passive_open),但sock:inet_sock_set_state可以用于此目的。
我可以想象這些 TCP 跟蹤點(diǎn)將是多么有用,因?yàn)槲叶嗄昵霸O(shè)計(jì)和使用了類似的跟蹤點(diǎn):我在CEC2006 上演示的DTrace TCP 提供程序。我最初將TCP狀態(tài)更改拆分為每個(gè)狀態(tài)的探針,但是當(dāng)它合并時(shí),它變成了一個(gè)單獨(dú)的tcp:::狀態(tài)更改探針,就像我們現(xiàn)在通過(guò)襪子跟蹤點(diǎn)在Linux中一樣。
下一步是什么?tcp:tcp_send和tcp:tcp_receive的跟蹤點(diǎn)可能很方便,但必須特別注意它們可能增加的微小開銷(啟用和特別禁用),因?yàn)榘l(fā)送/接收可能是一個(gè)非常頻繁的活動(dòng)。錯(cuò)誤條件的更多跟蹤點(diǎn)也很有用,例如連接拒絕路徑,這可能有助于分析 DoS 攻擊。
如果您對(duì)添加 TCP 跟蹤點(diǎn)感興趣,我建議您首先編寫一個(gè) kprobe 解決方案作為概念驗(yàn)證,并獲得一些生產(chǎn)經(jīng)驗(yàn)。這就是我之前的kprobe工具所扮演的角色。kprobe 解決方案將顯示跟蹤點(diǎn)是否有用,如果是,則有助于將其包含在 Linux 內(nèi)核維護(hù)者中。