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