前文采纳 tracepoint 和 kprobe 等追踪伎俩画出了 tcp 状态转移的时序图,仔细的读者可能留神到,文中的时序仿佛有点问题:
ts:2220445792791:client:CLOSE:SYN_SENTts:2220446761789:client:SYN_SENT:ESTABLISHEDts:2220447626787:server:LISTEN:SYN_RECVts:2220448026786:server:SYN_RECV:ESTABLISHEDts:2220454118771:client:ESTABLISHED:FIN_WAIT1ts:2220455075769:server:ESTABLISHED:CLOSE_WAITts:2220455593768:server:CLOSE_WAIT:LAST_ACKts:2220456264766:client:FIN_WAIT1:FIN_WAIT2ts:2220456525766:client:FIN_WAIT2:TIME_WAITts:2220456623765:client:FIN_WAIT2:CLOSEts:2220456768765:server:LAST_ACK:CLOSEts:2282464966825:client:TIME_WAIT:CLOSE
为什么 client
的 ESTABLISHED
在 server
的 SYN_RECV
后面?
事实上,我的测试内核版本是 6.1.11,它蕴含了这个commit [https://github.com/torvalds/linux/commit/10feb428a5045d5eb18a5d755fbb8f0cc9645626], 及其系列 [https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linu...]。
所以测试内核中 TCP_SYN_RECV
曾经不是原始 TCP 协定中 server
收到第一个 syn
包的状态了,取而代之的是 TCP_NEW_SYN_RECV
,TCP_SYN_RECV
自身次要被用于反对 fastopen
个性了。
既然这样,咱们怎么还原协定中的状态呢?通过一番检索,发现了这个办法:inet_reqsk_alloc
,该办法是内核收到第一个 syn
包后,为连贯调配 struct request_sock
这个轻量级数据结构表征的中央,于是咱们能够进行追踪:
SEC("fexit/inet_reqsk_alloc")int BPF_PROG(inet_reqsk_alloc,const struct request_sock_ops *ops, struct sock *sk_listener, bool attach_listener,struct request_sock *req ) { __u64 ts = bpf_ktime_get_boot_ns(); const struct sock_common skc1 = BPF_CORE_READ(sk_listener,__sk_common); const struct sock_common skc = BPF_CORE_READ(req,__req_common); const int family = BPF_CORE_READ(&skc,skc_family); if(family != AF_INET){ return 0; } const char oldstate = (BPF_CORE_READ(&skc1,skc_state)); const char newstate = (BPF_CORE_READ(&skc,skc_state)); __u32 dip = (BPF_CORE_READ(&skc1,skc_daddr)); __u16 dport = (BPF_CORE_READ(&skc1,skc_dport)); __u32 sip = (BPF_CORE_READ(&skc1,skc_rcv_saddr)); __u16 sport = bpf_htons(BPF_CORE_READ(&skc1,skc_num)); return judge_side(ctx,ts,(long long)req,dip, dport, sip, sport,oldstate,newstate);}
前面三步握手实现,为连贯建设 struct sock
重量级数据结构表征的中央:tcp_v4_syn_recv_sock->tcp_create_openreq_child->inet_csk_clone_lock
。
通过这个办法,咱们能够将 inet_reqsk_alloc
中的状态转移和前文的 sock
关联起来:
SEC("fexit/inet_csk_clone_lock")int BPF_PROG(inet_csk_clone_lock,const struct sock *sk, const struct request_sock *req, const gfp_t priority,struct sock * newsk) { bpf_printk("csk_clone,lskaddr:%u,reqaddr:%u,skaddr:%u", sk,req,newsk); return 0;}
这样就残缺了。能够失去下图,是不是就合乎咱们预期了:
注:内核中不反对批改 timewait 工夫,图中将其放大才不便展现,其余状态如实展现。