Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 502, comments - 13, trackbacks - 0, articles - 1

          導航

          公告


            ① 即時通訊開發社區
            地址: 52im.net
            專業的資料、社區

            ② 關注我的公眾號:

            讓技術不再封閉

            ③ 我的Github
            地址: 點此進入
            好代碼,與大家分享
          <2024年6月>
          2627282930311
          2345678
          9101112131415
          16171819202122
          23242526272829
          30123456

          常用鏈接

          留言簿(288)

          隨筆檔案

          文章檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          60天內閱讀排行

          本文由騰訊技術kernel分享,原題“TCP經典異常問題探討與解決”,下文進行了排版和內容優化等。

          1、引言

          TCP的經典異常問題無非就是丟包和連接中斷,在這里我打算與各位聊一聊TCP的RST到底是什么?現網中的RST問題有哪些模樣?我們如何去應對和解決?

          本文將從TCP的RST技術原理、排查手段、現網痛難點案例三個方面,自上而下、循序漸進地給讀者帶來一套完整的分析方法和解決思路。

          2、系列文章

          本文是系列文章中的第15篇,本系列文章的大綱如下:

          不為人知的網絡編程(一):淺析TCP協議中的疑難雜癥(上篇)

          不為人知的網絡編程(二):淺析TCP協議中的疑難雜癥(下篇)

          不為人知的網絡編程(三):關閉TCP連接時為什么會TIME_WAIT、CLOSE_WAIT

          不為人知的網絡編程(四):深入研究分析TCP的異常關閉

          不為人知的網絡編程(五):UDP的連接性和負載均衡

          不為人知的網絡編程(六):深入地理解UDP協議并用好它

          不為人知的網絡編程(七):如何讓不可靠的UDP變的可靠?

          不為人知的網絡編程(八):從數據傳輸層深度解密HTTP

          不為人知的網絡編程(九):理論聯系實際,全方位深入理解DNS

          不為人知的網絡編程(十):深入操作系統,從內核理解網絡包的接收過程(Linux篇)

          不為人知的網絡編程(十一):從底層入手,深度分析TCP連接耗時的秘密

          不為人知的網絡編程(十二):徹底搞懂TCP協議層的KeepAlive保活機制

          不為人知的網絡編程(十三):深入操作系統,徹底搞懂127.0.0.1本機網絡通信

          不為人知的網絡編程(十四):拔掉網線再插上,TCP連接還在嗎?一文即懂!

          不為人知的網絡編程(十五):深入操作系統,一文搞懂Socket到底是什么

          不為人知的網絡編程(十六):深入分析與解決TCP的RST經典異常問題》(* 本文)

          3、問題背景

          最近一年的時間里,現網碰到RST問題屢屢出現,一旦TCP連接中收到了RST包,大概率會導致連接中止或用戶異常。

          如何正確解決RST異常是較為棘手的問題。

          本文關注的不是細節,而是方法論,也確實方法更為重要。筆者始終相信,一百個人眼中的哈姆雷特最終還是一個具體的人物形象,一百個RST異常最終也會是一個簡短的小問題。

          4、技術原理

          4.1概述

          首先:我們需要確定的RST問題一定就是問題嗎?如果RST發生了你會如何去解決?

          讀者可以嘗試問下自己并解答這個問題,這里“停頓、停頓、停頓”來給大家一點時間思考。。。

          好了,時間到,我們繼續往下看。

          RST分為兩種:

          • 1)一種是active rst;
          • 2)一種是passive rst。

          前者:多半是指的符合預期的reset行為,此種情況多半是屬于機器自己主動觸發,更具有先前意識,且和協議棧本身的細節關聯性不強;后者:多半是指的機器也不清楚后面會發生什么,走一步看一步,如果不符合協議棧的if-else實現的RFC中條條杠杠的規則的情況下,那就只能reset重置了。

          這里貼上RFC 793最經典的最初對RST包的解釋:

          4.2active rst

          那具體什么是active rst?

          如果從tcpdump抓包上來看表現就是(如下圖)RST的報文中含有了一串Ack標識。

          這個對應的內核代碼為(如果感興趣):

          tcp_send_active_reset()

              -> skb = alloc_skb(MAX_TCP_HEADER, priority);

              -> tcp_init_nondata_skb(skb, tcp_acceptable_seq(sk), TCPHDR_ACK | TCPHDR_RST);

              -> tcp_transmit_skb()

          通常發生active rst的有幾種情況:

          • 1)主動方調用close()的時候:上層卻沒有取走完數據(這個屬于上層user自己犯下的錯)。
          • 2)主動方調用close()的時候:setsockopt設置了linger(這個標識代表我既然設置了這個,那close就趕快結束吧)。
          • 3)主動方調用close()的時候:發現全局的tcp可用的內存不夠了(這個可以sysctl調整tcp mem第三個參數),或,發現已經有太多的orphans了,這時候系統就是擺爛的意思:我也沒轍了”,那就只能干脆點長痛不如短痛,結束吧。這個案例可以搜索(dmesg日志)“too many orphaned sockets”或“out of memory -- consider tuning tcp_mem”,匹配其中一個就容易中rst。

          注:這里省略其他使用diag相關(如ss命令)的RST問題。上述三類是主要的active rst問題的情況。

          4.3passive rst

          現在繼續說說另一種passive rst吧。

          如果從抓包上來看表現就是(如下圖)rst的報文中無ack標識,而且RST的seq等于它否定的報文的ack號(紅色框的rst否定的黃色框的ack),當然還有另一種極小概率出現的特殊情況的表現我這里不貼出來了,它的表現形式就是RST的Ack號為1。

          這個對應的內核代碼為(如果感興趣):

          tcp_v4_send_reset()

                  if (th->ack) {

                          // 這里對應的就是上圖中為何出現Seq==Ack

                          rep.th.seq = th->ack_seq;

                  } else {

                          // 極小概率,如果出現,那么RST包的就沒有Seq序列號

                          rep.th.ack = 1;                                                  

                          rep.th.ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin +   

                                                 skb->len - (th->doff << 2));              

                  }

          通常發生passive rst的有哪些情況呢?這個遠比active rst更復雜,場景更多。具體的需要看TCP的收、發的協議,文字的描述可以參考rfc 793即可。

          5、輔助工具

          5.1概述

          我們針對線上這么多的rst如何去分析呢?

          首先tcpdump的抓捕是一定需要的,這個可以在整體流程上給我們縮小排查范圍。其次是,必須要手寫抓捕異常調用rst的點,文末我會分享一些源碼出來供參考。

          那如何抓調用RST的點?這里只提供下思路。

          5.2active rst

          使用bpf*相關的工具抓捕tcp_send_active_reset()函數并打印堆棧即可,通過crash現場機器并輸入“dis -l [addr]”可以得到具體的函數位置,比對源碼就可以得知了。

          可以使用bpftrace進行快速抓捕:

          sudo bpftrace -e 'k:tcp_send_active_reset { @[kstack()] = count(); }'

          堆棧結果如圖:

          我們可以根據堆棧信息推算上下文。

          5.3passive rst

          使用bpf*相關的工具抓捕抓捕tcp_v4_send_reset()和其他若干小的地方即可,原理同上。

          sudo bpftrace -e 'k:tcp_v4_send_reset { @[kstack()] = count(); }'

          效果如圖:

          當然:無論哪種,我們抓到了堆棧后依然需要輸出很多的關于skb和sk的信息,這個讀者自行考慮即可。再補充一些抓捕小技巧,如果現網機器的rst數量較多時候,盡量使用匹配固定的ip+port方式或其它關鍵字來減少打印輸出,否則會消耗資源過多!

          注:切記不能去抓捕reset tracepoint(具體函數:trace_tcp_send_reset()),這個tracepoint實現是有問題的,這個問題已經在社區內核中存在了7年之久!目前我正在修復中。

          6、案例分析

          本章節我將用現網實際碰到的三個”離譜“而且讓我非常”咬牙切齒“的case作為案例分析,當時在查這些問題的時候我提前告知業務“不保證有能力解決 

          ”,不過最終還是用時間磨贏了bug。

          對內核不感興趣的同學可以不用糾結具體的細節,只需要知道一個過程即可。

          對內核感興趣的同學不妨可以一起構造RST然后自己再抓取的試試。

          7、 案例1:小試牛刀—— close階段RST

          背景:這是線上出現概率/次數較多的一種類型的RST,業務總是抱怨為何我的連接莫名其妙的又沒了。

          我們先使用網絡異常檢測中最常用的工具:tcpdump。如下抓包的圖片再結合前文對RST的兩種分類(active && passive)可知,這是active rst。

          好,既然知道了是active rst,我們就針對性的在線上對關鍵函數抓捕,如下圖所示。

          通過crash命令找到了對應的源碼,如下:

          這時候便知是用戶設置了linger,主動預期內的行為觸發的rst,所以本例就解決了。不過插曲是,用戶并不認為他設置了linger,這個怎么辦?那就再抓一次sk->sk_lingertime值就好咯。

          如下圖所示:

          計算:socket的flag是784,第5位(從右往左)是1,這個是SO_LINGER位置位成功,但是同時linger_time為0。這個條件默認(符合預期)觸發:上層用戶退出時候,不走四次揮手,直接RST結束。

          結論:linger的默認機制觸發了加速結束TCP連接從而RST報文發出。

          8、 案例2:TCP 兩個bug —— 握手與揮手的RS

          8.1概述

          背景:某重點業務報告他們的某重點用戶出現了莫名其妙的RST問題,而且每一次都是出現在三次握手階段,復現概率約為——“按請求數來算的話差不多百萬級別分之1的概率,概率極”(這是來自業務的原話)。

          這里需要劇透一點的是,后文提到的兩個場景下的rst的bug,都是由于相同的race condition導致的。rcu保護關注的是reader&writer的安全性(不會踩錯地址),而不保護數據的實時性,這個很重要。所以當rcu與hashtable結合的時候,對整個表的增刪和讀如何保證數據的絕對的同步顯得很重要!

          8.2握手階段的TCP bug

          問題的表象是:三次握手完畢后client端給server端發送了數據,結果server端卻發送了rst拒絕了。

          分析:注意看上圖最左邊的第4和5這兩行的時間間隔非常短,只有11微妙,11微妙是什么概念?查一次tcp socket的hash表可能都是幾十微妙,這點時間完全可能會停頓在一個函數上。

          當server端看到第三行的ack的時候幾乎同時也看到了第四行的數據。

          詳細來說:這時候server端在握手最后一個環節,會在socket的hash表中刪除一個老的socket(我們叫req sk),再插入一個新的socket(我們叫full sk),在刪除和插入之間的這短暫的幾微妙發生的時候,server收第行的數據的時候需要去到這個hash表中尋找(根據五元組)對應的socket來接受這個報文,結果在這個空檔期間沒有匹配到應該找到的socket,這時候沒辦法只能把當時上層最初監聽的listener拿出來接收,這樣就出現了錯誤,違背了協議棧的基本的設計:對于listener socket接收到了數據包,那么這個數據包是非預期的,應該發送RST!

             CPU 0                           CPU 1

             -----                           -----

          tcp_v4_rcv()                  syn_recv_sock()

                                      inet_ehash_insert()

                                      -> sk_nulls_del_node_init_rcu(osk)

          __inet_lookup_established()

                                      -> __sk_nulls_add_node_rcu(sk, list)

          對應上圖的cpu0就是server的第四行的讀者,cpu1就是寫者,對于cpu0而言,讀到的數據可能是三種情況:

          • 1)讀到老的sk;
          • 2)讀到新的sk;
          • 3)誰也讀不到。

          前兩個都是可以接收,但是最后一個就是bug了——我們必須要找到兩者之一!如下就是一種場景,無法正確找到new或者old。

          那如何修復這個問題?在排查完整個握手規則后,發現只需要先插入新的sk到hash桶的尾部,再刪除老的sk即可。

          這樣就會有幾種情況:

          • 1)兩個同時都在,一定能匹配到其中一個;
          • 2)匹配到新的。

          如下圖:無論reader在哪里都能保證可以讀到一個。

          如下是正確的:

           

          結論:第3行(client給server發生了握手最后一次ack)和第4行(client端給server發送了第一組數據)出現的并發問題。

          8.3揮手階段的bug

          這個問題根因同上:rcu+hash表的使用問題,在揮手階段發起close()的一方競爭的亂序的收到了一個ack和一個fin ack觸發,導致socket在最后接收fin ack時候沒有匹配到任何一個socket,又只能拿出最初監聽的listener來收包的時候,這時候出現了錯誤。但是這個原始代碼中,是先插入新的sk再刪除了老的sk,乍一聽沒有任何問題,但是實際上插入新的sk出現了問題,源碼中插入到頭部,這里需要插入到尾部才行!

          出現問題的情景如下圖:

           

          結論:這個是原生內核長達十多年的一個實現上的BUG,即為了性能考慮使用的RCU機制,由此必然引入的不準確性導致并發的問題,我定位并分析出這個問題的并發的根因,由此提交了一份bugfix patch到社區被接收(點此鏈接查看)。

          9、 案例3:netfilter兩個bug —— 數據傳輸RST

          9.1概述

          背景:用戶報告有以下兩個痛點問題。

          偶發性出現:

          • 1)根本無法完成三次握手連接;
          • 2)在傳輸數據的階段突然被RST異常中止。

          分析:我們很容易的通過TCP的設計推測到這種情況一定不是正常的、符合預期的行為。我抓取了passive rst后發現原因是TCP層無法通過收到的skb包尋找到對應的socket,要知道socket是最核心的TCP連接通信的基站,它保存了TCP應有的信息(wscale、seq、buf等等),如果skb無法找到socket,那么就像小時候的故事小蝌蚪找媽媽但是找不到回家的路一樣。

          那為什么會出現找不到socket?

          經過排查發現線上配置了DNAT規則,如下例子,凡是到達server端的1111端口或1112端口的都被轉發到80端口接收。

          // iptables A port -> B port

          iptables ... -p tcp --port 1111 -j REDIRECT --to-ports 80

          iptables ... -p tcp --port 1112 -j REDIRECT --to-ports 80

          DNAT+netfilter的流程是什么樣?

          那么:有了DNAT之后,凡是進入到server端的A port會被直接轉發到B port,最后TCP完成接收。

          完整的邏輯是這樣:DNAT的端口映射在ip層收包時候先進入prerouting流程,修改skb的dst_ip:dst_port為真正的最后映射的信息,而后由ip early demux機制針對skb中的原始信息src_ip:src_port(也就是A port)修改為dst_ip:dst_port(也就是使用B port),由此4元組hash選擇一個sk,繼而成功由TCP接收才對。

          9.2兩條流沖突觸發的bug

          如下:如果這時候有兩條流量想要TCP建連,二者都是由同一個client端相同的ip和port發起連接,這時候第1條連接首先發起握手那么肯定可以順利進行,而當第2條連接發起的時候抵達到server端的1112端口最終被轉化為80端口,但是根據80端口可以發現我們已經建立了連接,所以第2條流三次握手直接失敗。

          1. saddr:12345 -> daddr:80 // 正常連接

          2. saddr:12345 -> daddr:1112 -> daddr:80 // NAT參與轉化

          (對內核細節不感興趣的同學可以跳過此段)

          我需要補充的信息是:NAT轉化port分為兩次,對于上述第二條流,第一次轉化1112為80,第二次轉化12345為1112,最終此流變為[saddr:1112 -> daddr:80]。

          1)第一條流:skb對應的sk是[saddr:12345 -> daddr:80],這個沒有NAT參與。

          2)第二條流:skb在ip層這時候NAT剛完成第一次port(修改dport 1112為dport 80),然后進入了early demux機制,此時的4元組是[saddr:12345 -> daddr:80],所以這時候匹配上了第一條流的sk,但是系統并不知情有問題了,緊接著NAT第二次改變skb的port,變為[saddr:1112 -> daddr:80],這個也是后續TCP層延續使用的,雖然這個4元組信息是對的,但是已經沒有用了,因為early demux階段已經獲取、保存socket了。

          注:內核修復后,對于第二條流就是放棄early demux階段選擇的4元組,而是安心等待NAT完成兩輪port的轉化之后,使用[saddr:1112 -> daddr:80]來匹配socket,這時候發現沒有對應的socket,就找到了listener socket,從而完成三次連接。

          結論:這個是early demux+DNAT的bug,它未能解決沖突問題,導致了異常RST的發生。

          9.3特殊skb觸發的bug

          注:在這個場景里面多了一個中間的gateway。

          在本例中:我發現依然是熟知的一幕,skb無法lookup尋找到對應的socket,此時我們要相信一定不會lookup算法出錯,因為此算法僅僅是做簡單的4元組的hash計算與匹配。所以追溯異常的skb和socket的四元組信息是頭等事情,經過對比果然發現skb的端口信息未能成功被iptables轉化為B port,所以使用了含有A port的四元組信息去找socket,而socket當初的建立是使用了B port,所以skb與sk的相遇就這么擦身而過了。

          對內核細節不感興趣的同學可以跳過后面大段

          那么為什么會DNAT無法轉化?

          我們先看下,異常未被轉化的skb和應當能接收的socket的4元組信息:

          // 2.2.2.2是去敏后的server端ip地址,另外兩個是client的ip

          sk info: 1.1.1.1:1111 <-> 2.2.2.2:80 // 我們可以知道真實的socket的建立是使用了80端口

          skb info: 1.1.1.2:2222 <-> 2.2.2.2:1112 // 異常的skb未成功將1112端口轉化為80端口

          client->gw->server的流程中,由于gw側發送了一些unknown skb再加上client端發送了一些out-of-window的包,導致進入到server的netfilter階段會被識別出來INVALID異常,這個異常被識別后直接清除netfilter保持的該有的流信息,繼而異常的skb抵達DNAT階段后無法轉化端口(因為判斷轉化的流信息沒有了),最終skb無法成功轉化port端口號。

          這個是netfilter+DNAT的設計上的bug。

          我認為:無論是否有netfilter,都不應當是TCP的行為被改變,所以如果netfilter識別到了問題所在:

          • 1)要么忽視,直接傳給TCP,交給TCP處理;
          • 2)要么丟棄,這樣也能避免RST的發生。

          但是:就這么一個小小的細節上,我和社區的幾個維護者拉鋸戰的battle了三百回合(點此查看),可惜雖然有一個維護者ACK了我的補丁,但是另外的維護者考慮netfilter不適合用于丟包功能,所以讓用戶去使用iptables --log功能、檢測出invalid異常包、繼而用iptables配置主動丟棄。就憑這點,我認為嚴重違背了user friendly的初衷,這些應該是default默認功能才對。此時的我雖然表面打不過,但是在內心世界里很顯然我battle贏了...

          結論:netfilter識別異常的skb未能成功保留DNAT信息,導致最后port端口不能成功被轉化,從而觸發了TCP的RST行為。

          10、 本文小結

          RST問題并不可怕,只要思路理清楚,先判斷類型,再抓取對應代碼,繼而翻出RFC協議,最后分析源碼就能搞定,僅僅四步就可以了 :)。

          希望這篇文章對大家有用。

          11、 附錄:bcc的工具源碼

          這里列一下bcc的工具源碼,感興趣的同學可以自行查閱。

          如下是針對4.14內核寫的,如果是更高版本需要調整一些python與c對照的格式問題。

          #!/usr/bin/env python

           

          from __future__ import print_function

          from bcc import BPF

          import argparse

          from time import strftime

          from socket import inet_ntop, AF_INET, AF_INET6

          from struct import pack

          import ctypes as ct

          from time import sleep

          from bcc import tcp

           

          # arguments

          examples = """examples:

              ./tcpdrop           # trace kernel TCP drops

          """

          parser = argparse.ArgumentParser(

              description="Trace TCP drops by the kernel",

              formatter_class=argparse.RawDescriptionHelpFormatter,

              epilog=examples)

          parser.add_argument("--ebpf", action="store_true",

              help=argparse.SUPPRESS)

          args = parser.parse_args()

          debug = 0

           

          # define BPF program

          bpf_text = """

          #include <uapi/linux/ptrace.h>

          #include <uapi/linux/tcp.h>

          #include <uapi/linux/ip.h>

          #include <net/sock.h>

          #include <bcc/proto.h>

           

          BPF_STACK_TRACE(stack_traces, 1024);

           

          struct ipv4_data_t {

              u32 pid;

              u64 is_sknull;

              u32 saddr;

              u32 daddr;

              u16 sport;

              u16 dport;

              u8 state;

              u8 tcpflags;

              u32 stack_id;

          };

          BPF_PERF_OUTPUT(ipv4_events);

           

          struct active_data_t {

              u32 pid;

              u32 saddr;

              u32 daddr;

              u16 sport;

              u16 dport;

              u32 stack_id;

          };

          BPF_PERF_OUTPUT(active_events);

           

          static struct tcphdr *skb_to_tcphdr(const struct sk_buff *skb)

          {

              // unstable API. verify logic in tcp_hdr() -> skb_transport_header().

              return (struct tcphdr *)(skb->head + skb->transport_header);

          }

           

          static inline struct iphdr *skb_to_iphdr(const struct sk_buff *skb)

          {

              // unstable API. verify logic in ip_hdr() -> skb_network_header().

              return (struct iphdr *)(skb->head + skb->network_header);

          }

           

          // from include/net/tcp.h:

          #ifndef tcp_flag_byte

          #define tcp_flag_byte(th) (((u_int8_t *)th)[13])

          #endif

           

          int trace_tcp_v4_send_reset(struct pt_regs *ctx, struct sock *sk, struct sk_buff *skb)

          {

              u8 is_sk_null = sk ? 0 : 1;

              u8 state = sk ? (u8)sk->__sk_common.skc_state : 1;

              u32 pid = bpf_get_current_pid_tgid();

              struct iphdr *ip = skb_to_iphdr(skb);

              u32 daddr = ip->daddr;

              u32 saddr = ip->saddr;

           

              // pull in details from the packet headers and the sock struct

              u16 family = sk->__sk_common.skc_family;

              u16 sport = 0, dport = 0;

              struct tcphdr *tcp = skb_to_tcphdr(skb);

              u8 tcpflags = ((u_int8_t *)tcp)[13];

              sport = tcp->source;

              dport = tcp->dest;

              sport = ntohs(sport);

              dport = ntohs(dport);

           

              if (family == AF_INET &&

                  (saddr == 16777343 && daddr == 16777343) &&

                  (sport == 8004 || dport == 8004)) {

                  struct ipv4_data_t data4 = {};

                  data4.pid = pid;

                  data4.saddr = saddr;

                  data4.daddr = daddr;

                  data4.dport = dport;

                  data4.sport = sport;

                  data4.state = state;

                  data4.tcpflags = tcpflags;

                  data4.stack_id = stack_traces.get_stackid(ctx, 0);

                  ipv4_events.perf_submit(ctx, &data4, sizeof(data4));

           

              }

           

              return 0;

          }

           

          int trace_tcp_send_active_reset(struct pt_regs *ctx, struct sock *sk, unsigned int priority)

          {

              u32 pid = bpf_get_current_pid_tgid() >> 32;

              u32 saddr = 0, daddr = 0;

              u16 family = AF_INET;

              u16 sport = 0, dport = 0;

           

              // sport is not right

              sport = sk->__sk_common.skc_num;

              dport = sk->__sk_common.skc_dport;

              dport = ntohs(dport);

           

              saddr = sk->__sk_common.skc_rcv_saddr;

              daddr = sk->__sk_common.skc_daddr;

           

              if (family == AF_INET && (saddr == 16777343 && daddr == 16777343)) {

                  struct active_data_t data4 = {};

                  data4.pid = pid;

                  data4.saddr = saddr;

                  data4.daddr = daddr;

                  data4.dport = dport;

                  data4.sport = sport;

                  data4.stack_id = stack_traces.get_stackid(ctx, 0);

                  active_events.perf_submit(ctx, &data4, sizeof(data4));

              }

           

              return 0;

          }

          """

           

          if debug or args.ebpf:

              print(bpf_text)

              if args.ebpf:

                  exit()

           

          # event data

          class Data_ipv4(ct.Structure):

              _fields_ = [

                  ("pid", ct.c_uint),

                  ("is_sknull", ct.c_ulonglong),

                  ("saddr", ct.c_uint),

                  ("daddr", ct.c_uint),

                  ("sport", ct.c_ushort),

                  ("dport", ct.c_ushort),

                  ("state", ct.c_ubyte),

                  ("tcpflags", ct.c_ubyte),

                  ("stack_id", ct.c_ulong)

              ]

           

          class Data_active(ct.Structure):

              _fields_ = [

                  ("pid", ct.c_uint),

                  ("saddr", ct.c_uint),

                  ("daddr", ct.c_uint),

                  ("sport", ct.c_ushort),

                  ("dport", ct.c_ushort),

                  ("stack_id", ct.c_ulong)

              ]

           

          # process event

          def print_ipv4_event(cpu, data, size):

              event = ct.cast(data, ct.POINTER(Data_ipv4)).contents

              if event.is_sknull is 1:

                  print("%-8s %-7d %-20s > %-20s %s (%s)" % (

                      strftime("%H:%M:%S"), event.pid,

                      "%s:%d" % (inet_ntop(AF_INET, pack('I', event.saddr)), event.sport),

                      "%s:%s" % (inet_ntop(AF_INET, pack('I', event.daddr)), event.dport),

                      "sk-is-null", tcp.flags2str(event.tcpflags)))

              else:

                  print("%-8s %-7d %-20s > %-20s %s (%s)" % (

                      strftime("%H:%M:%S"), event.pid,

                      "%s:%d" % (inet_ntop(AF_INET, pack('I', event.saddr)), event.sport),

                      "%s:%s" % (inet_ntop(AF_INET, pack('I', event.daddr)), event.dport),

                      tcp.tcpstate[event.state], tcp.flags2str(event.tcpflags)))

              for addr in stack_traces.walk(event.stack_id):

                  sym = b.ksym(addr, show_offset=True)

                  print("\t%s" % sym)

              print("")

           

          def print_active_event(cpu, data, size):

              event = ct.cast(data, ct.POINTER(Data_active)).contents

              print("%-8s %-7d %-20s > %-20s" % (

                  strftime("%H:%M:%S"), event.pid,

                  "%s:%d" % (inet_ntop(AF_INET, pack('I', event.saddr)), event.sport),

                  "%s:%d" % (inet_ntop(AF_INET, pack('I', event.daddr)), event.dport)))

           

              for addr in stack_traces.walk(event.stack_id):

                  sym = b.ksym(addr, show_offset=True)

                  print("\t%s" % sym)

              print("")

           

          # initialize BPF

          b = BPF(text=bpf_text)

          if b.get_kprobe_functions(b"tcp_v4_send_reset"):

              b.attach_kprobe(event="tcp_v4_send_reset", fn_name="trace_tcp_v4_send_reset")

          else:

              print("ERROR: tcp_drop() kernel function not found or traceable. "

                  "Older kernel versions not supported.")

              exit()

           

          if b.get_kprobe_functions(b"tcp_send_active_reset"):

              b.attach_kprobe(event="tcp_send_active_reset", fn_name="trace_tcp_send_active_reset")

          else:

              print("ERROR: tcp_v4_send_reset() kernel function")

              exit()

           

          stack_traces = b.get_table("stack_traces")

           

          # header

          print("%-8s %-6s %-2s %-20s > %-20s %s (%s)" % ("TIME", "PID", "IP",

              "SADDR:SPORT", "DADDR:DPORT", "STATE", "FLAGS"))

           

          # read events

          b["ipv4_events"].open_perf_buffer(print_ipv4_event)

          #b["active_events"].open_perf_buffer(print_active_event)

          while 1:

              try:

                  b.perf_buffer_poll()

              except KeyboardInterrupt:

                  exit()

          12、參考資料

          [1] RFC 793

          [2] TCP/IP詳解 - 第17章·TCP:傳輸控制協議

          [3] 網絡編程入門從未如此簡單(二):假如你來設計TCP協議,會怎么做?

          [4] 通俗易懂-深入理解TCP協議(上):理論基礎

          [5] 理論經典:TCP協議的3次握手與4次揮手過程詳解

          [6] 腦殘式網絡編程入門(一):跟著動畫來學TCP三次握手和四次揮手

          [7] 網絡編程懶人入門(一):快速理解網絡通信協議(上篇)

          [8] 網絡編程懶人入門(二):快速理解網絡通信協議(下篇)

          [9] 不為人知的網絡編程(一):淺析TCP協議中的疑難雜癥(上篇)

          [10] 不為人知的網絡編程(二):淺析TCP協議中的疑難雜癥(下篇)


          (本文已同步發布于:http://www.52im.net/thread-4668-1-1.html



          作者:Jack Jiang (點擊作者姓名進入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 三都| 铜梁县| 罗定市| 湟中县| 道孚县| 方山县| 沙坪坝区| 开化县| 泸西县| 红桥区| 郑州市| 剑川县| 康保县| 五华县| 剑河县| 且末县| 阿拉善盟| 沙河市| 巴里| 青阳县| 五常市| 河北省| 丰都县| 襄樊市| 巴里| 穆棱市| 石嘴山市| 阳山县| 延吉市| 大关县| 永泰县| 田阳县| 顺昌县| 永福县| 滦平县| 阳高县| 邹城市| 永顺县| 广安市| 罗定市| 涟源市|