动手写一个探测网络质量(丢包率/RTT/队形等)的工具
生活随笔
收集整理的這篇文章主要介紹了
动手写一个探测网络质量(丢包率/RTT/队形等)的工具
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
還像往常一樣,本文的內容沒有收斂,依然是隨筆式的備忘,而不是文檔。人在外地,本不該來的,也挺沮喪,不過每周總結總是必不可少。
說到網絡技術,我個人比較關注IP,其次是鏈路設備,然后才是TCP,這可能跟我第一次接觸網絡技術時所遇到的公司有關,它們是華為3Com以及Cisco,而不是Google,Yahoo或者BAT。
??????? 然而能接觸到的大多數的人可能更關注的是TCP,因為這是他們唯一能接觸到的網絡技術,雖然在俠義上,TCP并不屬于網絡,它是典型的一個端到端系統,網絡只是它經由的一個路徑。
??????? 以公共交通系統為例,TCP比較類似票務和調度系統,它關注是否可以賣票,可以賣多少票,始發站是否可以發車,間隔多久發一班等等這種事情。至于車在途徑道路上發生了什么,甚至途徑哪些道路,票務系統并不關心,偶爾,小概率的,坐在公交總站的阿姨會接到電話,路上司機打來的,匯報一些突發情況,車壞了,車翻了,自己被捅了一刀...然后阿姨唯一能做的就是再派一輛車過去,她既無力修車,也無力修路,更無力(事實上也無權)懲罰歹徒...
...
??????? 令人悲哀的痛點在于,一個承諾質量的端到端系統跑在一個根本無法保證質量的統計復用的分組交換網絡里面!請注意,公路也是統計復用的分組交換網路。
???????? 整個網絡是擁塞的,而且隨時都可能擁塞,如果我們想設計一個好的TCP,我們就必須能動態適應網絡的當前狀況,我們必須能夠探知網絡的當前狀況,因此我們需要一個工具。
??????? 在給出這個工具之前,我們先從Wireshark說起。
??????? 因此,我需要一個比較客觀的工具,也就是說它對網絡狀態是無知的,也不奢望獲知網絡狀態,它只是按照自己的參數固定速率發送數據,然后我們通過回應來探測網絡到底發生了什么。
??????? 這個工具自己試一下便知,不多談。事實上,它是基于數據包守恒的,即收到了n個Reply,發出去n個Request,因此它會根據網絡的狀態自動調速,但是它計算的是一種保守狀態的丟包率,
比如下面的序列:
1).發出100個Request,收到100個Reply。
2).發出100個Request,收到80個Reply,假設此時真的發生了擁塞丟包。
3).發出80個Request,收到80個Reply,假設擁塞馬上緩解了。
4).請問怎么可以快速獲知擁塞緩解了??
因此我更希望的是,固定數量發出數據包,然后看回應:
1).發出100個Request,收到100個Reply。
2).發出100個Request,收到80個Reply,假設此時真的發生了擁塞丟包。
3).發出100個Request,收到100個Reply,擁塞緩解馬上被探知。
??????? 我寫這個工具完全是為了自用,而且確實也還可以,這個工具可以完成以下的功能:
??????? 我在這里就不貼圖了,基本就跟ping -f是一樣的。
??????? 以RED隊列為例,丟包往往是緩速隨機的,然后如果擁塞不緩解,丟包就會趨于連續,表現為星號越來越連續且增多,點號的連續性則相反,趨于離散化...我的工具不重傳,只觀測,所以完全可以通過點星的分布來解析隊列的細節,并且很有可能你能把路徑上是否有UDP流氓找出來!即便你無法控制你的路徑從而繞開,不也是可以反制一下么?你可以通過點星分布的變化情況得知擁塞是否由于發送端主動降速而緩解。
./ic.py 183.56.64.62 1 1000 1
./ic.py 14.29.121.206 1 1000 1
...
由于我程序的時間精度不夠,很難探知更詳細的信息,但是這個問題是可以10秒內解決的...為什么不解決,是因為我覺得這已經夠了。
??????? 但是這里也貼一份吧:
#!/usr/local/bin/pythonimport sys import time from time import sleep,ctimeimport signal import threading from scapy.all import *# 指定目標IP地址 target = sys.argv[1] # 指定執行次數 tot = int(sys.argv[2]) # 指定每次發送的包量 tot_per = int(sys.argv[3]) # 指定是否回顯 vl = int(sys.argv[4]) flt = "host " + target + " and icmp"handle = open("/dev/null", 'w')out_list = [] in_list = []def output():all = out_list + in_listall.sort(lambda x,y:cmp(x[3],y[3]))for item in all:print item[0], item[1], item[2], item[3]*10sys.stdout.flush()os._exit(0)def signal_handler(signal, frame):output()class ThreadWraper(threading.Thread):def __init__(self,func,args,name=''):threading.Thread.__init__(self)self.name=nameself.func=funcself.args=argsdef run(self):apply(self.func,self.args)# 將結果輸出到list def printrecv(pktdata):if ICMP in pktdata and pktdata[ICMP]:seq = str(pktdata[ICMP].seq)if seq == tot_per + 2:returnif str(pktdata[IP].dst) == target:handle.write('*')handle.flush()out_list.append(('+', 1, seq, time.clock()))else:if vl == 2:handle.write('.')else:handle.write('\b \b')handle.flush()in_list.append(('-', 0, seq, time.clock()))# 收到seq+2的包就停止抓包并終止程序 def checkstop(pktdata):if ICMP in pktdata and pktdata[ICMP]:seq = str(pktdata[ICMP].seq)if int(seq) == tot_per + 2 and str(pktdata[IP].src) == target:handle.write("\nExit:" + ctime() + '\n')output()return Truereturn False# 發送線程 def send_packet():times = 0while times < tot:times += 1send(IP(dst = target)/ICMP(seq = (0, tot_per))/"test", verbose = 0, loop = 1, count = 1)send(IP(dst = target)/ICMP(seq = tot_per+2)/"bye", verbose = 0)# 接收線程 def recv_packet():sniff(prn = printrecv, store = 1, filter = flt, stop_filter = checkstop)def startup():handle.write("Start:" + ctime() + '\n')send_thread = ThreadWraper(send_packet,(),send_packet.__name__)send_thread.setDaemon(True) send_thread.start()recv_thread = ThreadWraper(recv_packet,(),recv_packet.__name__)recv_thread.setDaemon(True) recv_thread.start()signal.pause()if __name__ == '__main__':if vl != 0:handle.close()handle = sys.stderrsignal.signal(signal.SIGINT, signal_handler)startup()
這個代碼寫的不好,因為我真的不怎么會編程。
??????? 這個代碼使用了令人陶醉的scapy!這個我5年前就接觸過的東西直到今天才用起來。關于scapy的文檔,我覺得比較好的是 這個。
??????? TCP和蝙蝠不同的是,蝙蝠發出的聲波無法被緩存,它永遠直來直去,而TCP發出的數據包卻可以被中間的網絡設備緩存很久,因此,TCP測出的RTT除以2并不一定是數據包到達接收端的時間!如果你不理解我的意思,請考慮以下場景:
0).發送端到接收端的路徑,路徑傳輸用時為10秒,因此不考慮緩存,來回為20秒;
1).時間點A,數據包P發出;
2).到達接收端前,經過了5秒,數據包P在路徑正中間被一個設備緩存了10秒;
3).在時間點A的15秒后數據包P繼續前行,又過了5秒,到達接收端;
4).接收端對數據包P的ACK未緩存經過了10秒返回到發送端。
經過計算得到時間點A發送的數據包P的RTT為5+10+5+10=30秒,除以2就是15秒,請問15秒是發送端到達接收端的時間嗎?顯然不是!如果TCP不是瞎子,那么它肯定知道上述的過程,然而TCP是個瞎子,所以它不知道上面的具體過程。如果數據包P沒有被緩存,顯然只需要10秒就能到達接收端,然而現在卻算出來是15秒,這多出來的5秒發生了什么?TCP不知道,因此傳統上,TCP會認為發生了擁塞排隊。
??????? 但是對于擁塞排隊的細節,TCP真的很容易判斷清楚嗎?
??????? TCP會把RTT的陡增視為發生了擁塞,但是:
A.如果排隊發生在數據包P到達接收端的過程中,數據包到達接收端的總時間為20秒,RTT/2=15秒,此時TCP少算了5秒;
B.如果排隊發生在ACK返回到發送端的過程中,數據包到達接收端的總時間為10秒,RTT/2=15秒,此時TCP多算了5秒。
B-1.如果ACK發生了擁塞排隊或者丟棄,后面的ACK會連帶著代替該ACK去確認已經發送的數據,TCP對待數據包P和ACK是不同的!
因此,RTT陡增(并且持續保持高值)可以比較容易定性擁塞的發生,卻很難從RTT抖動情況去辨析擁塞發生的細節,就更別說去區分偶然的噪聲丟包還是擁塞丟包了...
于是回聲定位技術給了我一些思路,這個思路其實也想了蠻久了。我們可以從如何發現中間網絡設備的流量整形說起...
說到網絡技術,我個人比較關注IP,其次是鏈路設備,然后才是TCP,這可能跟我第一次接觸網絡技術時所遇到的公司有關,它們是華為3Com以及Cisco,而不是Google,Yahoo或者BAT。
??????? 然而能接觸到的大多數的人可能更關注的是TCP,因為這是他們唯一能接觸到的網絡技術,雖然在俠義上,TCP并不屬于網絡,它是典型的一個端到端系統,網絡只是它經由的一個路徑。
??????? 以公共交通系統為例,TCP比較類似票務和調度系統,它關注是否可以賣票,可以賣多少票,始發站是否可以發車,間隔多久發一班等等這種事情。至于車在途徑道路上發生了什么,甚至途徑哪些道路,票務系統并不關心,偶爾,小概率的,坐在公交總站的阿姨會接到電話,路上司機打來的,匯報一些突發情況,車壞了,車翻了,自己被捅了一刀...然后阿姨唯一能做的就是再派一輛車過去,她既無力修車,也無力修路,更無力(事實上也無權)懲罰歹徒...
...
??????? 令人悲哀的痛點在于,一個承諾質量的端到端系統跑在一個根本無法保證質量的統計復用的分組交換網絡里面!請注意,公路也是統計復用的分組交換網路。
???????? 整個網絡是擁塞的,而且隨時都可能擁塞,如果我們想設計一個好的TCP,我們就必須能動態適應網絡的當前狀況,我們必須能夠探知網絡的當前狀況,因此我們需要一個工具。
??????? 在給出這個工具之前,我們先從Wireshark說起。
1.如何看Wireshark里TCP trace圖
用Wireshark打開一個保存TCP流的pcap文件,點擊“統計”-“TCP流圖形”-“時間序列(tcptrace)”,我們可以看到一張圖表,通過該圖表可以探知關于網絡狀況的大部分細節:??????? 因此,我需要一個比較客觀的工具,也就是說它對網絡狀態是無知的,也不奢望獲知網絡狀態,它只是按照自己的參數固定速率發送數據,然后我們通過回應來探測網絡到底發生了什么。
2.基于數據包守恒的ping -f
在寫工具之前,首先看看現成的ping -f是否好用。??????? 這個工具自己試一下便知,不多談。事實上,它是基于數據包守恒的,即收到了n個Reply,發出去n個Request,因此它會根據網絡的狀態自動調速,但是它計算的是一種保守狀態的丟包率,
比如下面的序列:
1).發出100個Request,收到100個Reply。
2).發出100個Request,收到80個Reply,假設此時真的發生了擁塞丟包。
3).發出80個Request,收到80個Reply,假設擁塞馬上緩解了。
4).請問怎么可以快速獲知擁塞緩解了??
因此我更希望的是,固定數量發出數據包,然后看回應:
1).發出100個Request,收到100個Reply。
2).發出100個Request,收到80個Reply,假設此時真的發生了擁塞丟包。
3).發出100個Request,收到100個Reply,擁塞緩解馬上被探知。
3.我的Python工具概述
在上面的篇幅,我給出了不用Wireshark和ping -f的理由:不用Wireshark的理由:
它完全是TCP實現的行為勾畫,在實現的很垃圾的TCP中,根本無法勾勒網絡的狀態,即便是在完美的TCP實現中,它表示的也是TCP如何反應網絡狀況變化的,這個信息絲毫沒有指導意義,我們也很難知道網絡到底發生了什么??傊?#xff0c;不同的TCP實現針對相同的網絡質量會給出不同的數據,畫出不同的圖,而且,它太復雜了!不用ping -f的理由:
很直接的說,ping -f跟tcptrace沒有什么根本的不同,只是它的“擁塞控制”機制更加簡單,完全就是根據數據包守恒來的。不多說了。??????? 我寫這個工具完全是為了自用,而且確實也還可以,這個工具可以完成以下的功能:
1).探測丟包率
這種丟包率是客觀的丟包率,包括噪聲和排隊造成的丟包。我基于ping -f修改了,把根據Reply的調速反饋機制去掉了,因為這樣可以探測出更加詳細的隊形情況以及擁塞對丟包的影響。不然的話,如果使用ping -f,你不得不通過斜率來觀測這種影響,而且當擁塞恢復,探測端無法及時感知。??????? 我在這里就不貼圖了,基本就跟ping -f是一樣的。
2).探測RTT波動
該工具最終會生成一個文件,該文件的截圖如下:3).探測隊列的隊形
和ping -f不同,我的工具可以生成點星文件("."表示收到了回應,"*"表示發送了數據),從點和星的分布,我們可以更加深入的探測隊列細節。如下只是一個例子:動態的圖跟ping -f 幾乎一樣:
??????? 以RED隊列為例,丟包往往是緩速隨機的,然后如果擁塞不緩解,丟包就會趨于連續,表現為星號越來越連續且增多,點號的連續性則相反,趨于離散化...我的工具不重傳,只觀測,所以完全可以通過點星的分布來解析隊列的細節,并且很有可能你能把路徑上是否有UDP流氓找出來!即便你無法控制你的路徑從而繞開,不也是可以反制一下么?你可以通過點星分布的變化情況得知擁塞是否由于發送端主動降速而緩解。
4.如何使用這個工具
首先,你不能盲目的去探測,你首先要有一個大概的拓撲。比如,還是baidu,我們想知道到達baidu的路徑中的情況,利用上古神奇traceroute,我們可以得到很多信息:
為什么用Windows的tracert?因為我的虛擬機是NAT模式,特殊原因不能用Bridge,所以我用Windows...
得到了路徑細節,我們可以逐步探測各個節點了,如下這樣:./ic.py 183.56.64.62 1 1000 1
./ic.py 14.29.121.206 1 1000 1
...
由于我程序的時間精度不夠,很難探知更詳細的信息,但是這個問題是可以10秒內解決的...為什么不解決,是因為我覺得這已經夠了。
5.說到最后,代碼呢?
前面扯了那么多,代碼呢?代碼在 github??????? 但是這里也貼一份吧:
#!/usr/local/bin/pythonimport sys import time from time import sleep,ctimeimport signal import threading from scapy.all import *# 指定目標IP地址 target = sys.argv[1] # 指定執行次數 tot = int(sys.argv[2]) # 指定每次發送的包量 tot_per = int(sys.argv[3]) # 指定是否回顯 vl = int(sys.argv[4]) flt = "host " + target + " and icmp"handle = open("/dev/null", 'w')out_list = [] in_list = []def output():all = out_list + in_listall.sort(lambda x,y:cmp(x[3],y[3]))for item in all:print item[0], item[1], item[2], item[3]*10sys.stdout.flush()os._exit(0)def signal_handler(signal, frame):output()class ThreadWraper(threading.Thread):def __init__(self,func,args,name=''):threading.Thread.__init__(self)self.name=nameself.func=funcself.args=argsdef run(self):apply(self.func,self.args)# 將結果輸出到list def printrecv(pktdata):if ICMP in pktdata and pktdata[ICMP]:seq = str(pktdata[ICMP].seq)if seq == tot_per + 2:returnif str(pktdata[IP].dst) == target:handle.write('*')handle.flush()out_list.append(('+', 1, seq, time.clock()))else:if vl == 2:handle.write('.')else:handle.write('\b \b')handle.flush()in_list.append(('-', 0, seq, time.clock()))# 收到seq+2的包就停止抓包并終止程序 def checkstop(pktdata):if ICMP in pktdata and pktdata[ICMP]:seq = str(pktdata[ICMP].seq)if int(seq) == tot_per + 2 and str(pktdata[IP].src) == target:handle.write("\nExit:" + ctime() + '\n')output()return Truereturn False# 發送線程 def send_packet():times = 0while times < tot:times += 1send(IP(dst = target)/ICMP(seq = (0, tot_per))/"test", verbose = 0, loop = 1, count = 1)send(IP(dst = target)/ICMP(seq = tot_per+2)/"bye", verbose = 0)# 接收線程 def recv_packet():sniff(prn = printrecv, store = 1, filter = flt, stop_filter = checkstop)def startup():handle.write("Start:" + ctime() + '\n')send_thread = ThreadWraper(send_packet,(),send_packet.__name__)send_thread.setDaemon(True) send_thread.start()recv_thread = ThreadWraper(recv_packet,(),recv_packet.__name__)recv_thread.setDaemon(True) recv_thread.start()signal.pause()if __name__ == '__main__':if vl != 0:handle.close()handle = sys.stderrsignal.signal(signal.SIGINT, signal_handler)startup()
這個代碼寫的不好,因為我真的不怎么會編程。
??????? 這個代碼使用了令人陶醉的scapy!這個我5年前就接觸過的東西直到今天才用起來。關于scapy的文檔,我覺得比較好的是 這個。
本文最后,再舉一個例子來聊一下TCP
蝙蝠是個瞎子,TCP也是個瞎子。蝙蝠雖瞎但不會撞墻,依靠的是聲波定位,而TCP雖瞎也能保持平滑發送,依靠的是ACK時鐘流。而這個引發了另一個思路,其效果就是,我們可以采取一些措施撞死蝙蝠。??????? TCP和蝙蝠不同的是,蝙蝠發出的聲波無法被緩存,它永遠直來直去,而TCP發出的數據包卻可以被中間的網絡設備緩存很久,因此,TCP測出的RTT除以2并不一定是數據包到達接收端的時間!如果你不理解我的意思,請考慮以下場景:
0).發送端到接收端的路徑,路徑傳輸用時為10秒,因此不考慮緩存,來回為20秒;
1).時間點A,數據包P發出;
2).到達接收端前,經過了5秒,數據包P在路徑正中間被一個設備緩存了10秒;
3).在時間點A的15秒后數據包P繼續前行,又過了5秒,到達接收端;
4).接收端對數據包P的ACK未緩存經過了10秒返回到發送端。
經過計算得到時間點A發送的數據包P的RTT為5+10+5+10=30秒,除以2就是15秒,請問15秒是發送端到達接收端的時間嗎?顯然不是!如果TCP不是瞎子,那么它肯定知道上述的過程,然而TCP是個瞎子,所以它不知道上面的具體過程。如果數據包P沒有被緩存,顯然只需要10秒就能到達接收端,然而現在卻算出來是15秒,這多出來的5秒發生了什么?TCP不知道,因此傳統上,TCP會認為發生了擁塞排隊。
??????? 但是對于擁塞排隊的細節,TCP真的很容易判斷清楚嗎?
??????? TCP會把RTT的陡增視為發生了擁塞,但是:
A.如果排隊發生在數據包P到達接收端的過程中,數據包到達接收端的總時間為20秒,RTT/2=15秒,此時TCP少算了5秒;
B.如果排隊發生在ACK返回到發送端的過程中,數據包到達接收端的總時間為10秒,RTT/2=15秒,此時TCP多算了5秒。
B-1.如果ACK發生了擁塞排隊或者丟棄,后面的ACK會連帶著代替該ACK去確認已經發送的數據,TCP對待數據包P和ACK是不同的!
因此,RTT陡增(并且持續保持高值)可以比較容易定性擁塞的發生,卻很難從RTT抖動情況去辨析擁塞發生的細節,就更別說去區分偶然的噪聲丟包還是擁塞丟包了...
于是回聲定位技術給了我一些思路,這個思路其實也想了蠻久了。我們可以從如何發現中間網絡設備的流量整形說起...
寫在最后
雖然我是一個典型的IP粉,但我絲毫沒有貶低TCP的意思,事實上我也不常接觸到中間網絡的設備,我之所以總是對TCP表現的不屑一顧是因為我總是發現很多所謂的“精通網絡編程”的人事實上只是“精通編程”而根本不懂網絡,對此的另一個極端,是我在2013到2014年的時候碰到的一些CCIE,他們真的十分精通網絡,但是卻絲毫不懂socket編程,也不懂協議棧的實現,而我在多年的時間內試圖調和兩者。說實話,我鄙視過那些玩弄網絡設備而不懂編程的人,也鄙視過那些在端主機編程但是不懂網絡的人,但其實我更鄙視的是我自己。
??????? tcpdump/Wireshark/tshark抓包工具是利器,但是絕對不是唯一的,碰到問題僅僅依賴分析數據包的人事實上很大的可能是他曾經從事過很長一段時間協議分析和逆向的工作,在這段時間形成了自己的工作方式,如果是一個從事路由器交換機工作的人,他可能就會依賴另外一種工具了。這就是網絡的復雜性之體現。
總結
以上是生活随笔為你收集整理的动手写一个探测网络质量(丢包率/RTT/队形等)的工具的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity自定义文件夹图标颜色 个性化U
- 下一篇: 网站优化中SEO关键词密度到底多少属于合