如何使用golang實現(xiàn)traceroute
Traceroute 概念
traceroute是一種網(wǎng)絡(luò)診斷工具,通過traceroute可以診斷出本機到目的地IP之間的路由情況,例如路由跳數(shù)、延遲、是否可達(dá)等信息。該工具在linux環(huán)境下的命令是traceroute
或者tracepath
,在windows下命令是tracert
。
工作原理
traceroute在linux系列的操作系統(tǒng),默認(rèn)通過發(fā)送UDP請求到目的地IP,UDP的端口使用的是33434到33545之間。除了UDP的協(xié)議,可選用ICMP或者TCP(TCP SYN包)。使用33434到33534之間到端口是因為大部分linux系統(tǒng)的該范圍內(nèi)的端口是不可用的。正常情況下如果我們對一個目的地主機發(fā)起UDP請求,并且該端口不存在就會直接返回端口或者主機不可大的信息,這樣是無法獲取到中途的路由節(jié)點。此時需要引入一個TTL的概念。
TTL即Time-To-Live,更多的被理解為路由跳數(shù),該值存于IP頭,經(jīng)過路由轉(zhuǎn)發(fā)時會將該值減1,當(dāng)ttl值為0時,路由就會回復(fù)一個ICMP消息"Time Exceeded",表示跳數(shù)已經(jīng)達(dá)到最大值,無法進(jìn)行轉(zhuǎn)發(fā)。
TTL在ipv4和ipv6頭有不同的定義,在ipv4頭用8位來存該數(shù)值,且命名為“Time to Live”,而在ipv6的頭則叫做“Hop Limit”。
不管是Time to Live還是Hop Limit,其實都是相同的邏輯,路由轉(zhuǎn)發(fā)一次就減1,并且該值為0時則無法轉(zhuǎn)發(fā)。
我們來看一下traceroute的發(fā)包過程:
第一步:主機A往目的主機B發(fā)送UDP包,包頭需要設(shè)置TTL=1,并且設(shè)置目的端口為33434。
第二步:主機A的最近的路由A收到UDP包以后,將TTL減1,此時TTL=0,路由A就將該包丟棄,并且回復(fù)主機A一條ICMP信息:“Time Exceeded”。
第三步:主機A收到ICMP的消息以后即可記錄ICMP發(fā)送主機的地址,該地址就是路由IP,并且主機A設(shè)置TTL=2,再次發(fā)送UDP包到目的主機B的33434端口。
第四步:以此類推,直到TTL超過設(shè)置的最大值或者收到目的主機返回的消息時停止發(fā)包,這樣就得到了一個路由地址列表,同時也能拿到發(fā)送到路由之間的消息延遲,如果路由超過設(shè)定的時間內(nèi)沒有相應(yīng),則置該跳數(shù)的路由地址為“*”。
traceroute-go代碼實現(xiàn)
由于go語言是高級語言,將udp以及tcp的包頭都封裝完整,無法定制設(shè)置ttl。好在golang提供了syscall庫,該庫提供依稀了linux下的函數(shù)調(diào)用,因此可以利用該包的方法達(dá)到設(shè)置ttl的目的。在1.4之前可以使用標(biāo)準(zhǔn)庫syscall
,但因為該庫已經(jīng)被棄用,可以使用golang.org/x/sys
庫,該庫是syscall
的擴展,提供更加豐富的系統(tǒng)調(diào)用方法。
有庫的支持,我們則需要了解一下C語言的知識,即用C語言發(fā)送udp包和接受icmp的信息,因此這里需要涉及到幾個函數(shù):
socket
函數(shù),創(chuàng)建一個socke的文件描述,用于發(fā)送udp以及接收icmp的消息,golang對應(yīng)的函數(shù)為func Socket(domain, typ, proto int) (fd int, err error)
setsockopt
函數(shù),該函數(shù)可以用于設(shè)定IP的頭信息,我們要設(shè)定TTL就是利用該函數(shù),同時該函數(shù)可以設(shè)定socket的請求或者接收消息的超時時間,golang對應(yīng)的函數(shù)為func SetsockoptInt(fd, level, opt int, value int) (err error)
sendto
函數(shù),用于發(fā)送udp消息,golang對應(yīng)的函數(shù)為func Sendto(fd int, p []byte, flags int, to Sockaddr) (err error)
recvfrom
函數(shù),用于接收icmp消息,golang對應(yīng)的函數(shù)為func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error)
函數(shù)準(zhǔn)備好以后就可以開工編寫golang版本的traceroute庫了。
首先,創(chuàng)建sendSocket,用于發(fā)送UDP包,注意內(nèi)部的參數(shù) unix.IPPROTO_UDP
表示使用ipv4的udp協(xié)議,這個與ipv6協(xié)議是有區(qū)別的,可以通過命令man socket
查看函數(shù)說明,然后創(chuàng)建一個recvSocket的socket文件描述符,用于接收ICMP的消息,這里調(diào)用了函數(shù)SetsockoptTimeval
,用于設(shè)定接收消息的超時時間。
然后在for循環(huán)內(nèi)循環(huán)發(fā)送udp消息并且接收icmp消息:
代碼中SetsockoptInt
函數(shù)設(shè)定ipv4的頭TTL,初始化ttl=1,通過Sendto
函數(shù)將消息發(fā)送到目的地址和目的端口,這里目的端口從33434開始,會在33434到33534區(qū)間內(nèi)循環(huán)。
發(fā)送消息以后,通過Recvfrom
接收消息,此時會判斷接收消息是否報錯,如果報錯則直接退出循環(huán)并結(jié)束traceroute操作;如果沒有報錯,則需要解析返回的ICMP消息,由于ipv4的Header包頭長度最小是20字節(jié),最大是60字節(jié),會出現(xiàn)浮動,因此需要拿到實際的ipv4頭長度,這里使用ipv4
庫的ParseHeader
函數(shù)解析拿到ipv4的包頭結(jié)構(gòu),然后將收到的消息截取ipHeader.Len
長度就得到我們的ICMP消息結(jié)構(gòu)體,拿到ICMP消息結(jié)構(gòu)以后既可以根據(jù)Type判定消息類型,由于我們只關(guān)注ICMPTypeTimeExceeded
和ICMPTypeDestinationUnreachable
類型的消息,因此其他消息我們都會丟棄,并且如果收到的是ICMPTypeTimeExceeded
,則需要將發(fā)送方的地址(路由地址)存下來,并且將ttl+1,然后再次循環(huán)發(fā)送udp消息到目的地。
如果收到的ICMP消息類型是ICMPTypeDestinationUnreachable
或者ttl超過了最大的ttl設(shè)定或者接受的的ICMP消息來自于目的地址,則結(jié)束發(fā)包,并輸出結(jié)果。
當(dāng)然,如果接收到報錯的消息,該消息可能是路由不通或者發(fā)包超時,因此我們需要將該跳的路由地址設(shè)置為“*”,同時判定重試次數(shù),以及是否超過了最大TTL。
最后每次循環(huán)都將目的端口值+1,并且超過了最大的端口33534是又從最小端口開始,保障端口范圍一直在33434到33534之間。
結(jié)果輸出:
以下是我們自己的程序結(jié)果輸出:
以下是系統(tǒng)自帶的traceroute輸出:
總結(jié)
traceroute工具原理不難,但要實現(xiàn)這個過程需要涉及到一些基本知識,如ip的報文組成、udp、icmp協(xié)議的一些基本知識,另外就是需要知道路由跳數(shù)的基本原理,通過實現(xiàn)這個過程也可以加深這些基礎(chǔ)知識,同時是對這些知識的運用。
完整代碼已經(jīng)上傳到github,地址為:https://github.com/Kseleven/traceroute-go,歡迎大家star,當(dāng)然如有紕漏或者講解不正確的地方,歡迎指正。
參考文獻(xiàn)
- ipv4 rfc 791
- ipv6 rfc 2460
- icmp rfc 792
- traceroute rfc 1393
- linux man page-traceroute
- traceroute wiki
- icmp wiki
- golang sys庫
到此這篇關(guān)于如何使用golang實現(xiàn)traceroute的文章就介紹到這了,更多相關(guān)golang實現(xiàn)traceroute內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言普通指針unsafe.Pointer?uintpt之間的關(guān)系及指針運算
這篇文章主要為大家介紹了Go語言普通指針unsafe.Pointer?uintpt之間的關(guān)系及指針運算示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Golang執(zhí)行g(shù)o get私有庫提示"410 Gone" 的問題及解決辦法
這篇文章主要介紹了Golang執(zhí)行g(shù)o get私有庫提示”410 Gone“ 解決辦法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02