Traceroute(路由追蹤)的原理及實現

2021-09-11 13:30:55 字數 4399 閱讀 1640

現實世界中的網路是由無數的計算機和路由器組成的一張的大網,應用的資料報在傳送到伺服器之前都要經過層層的路由**。而traceroute是一種常規的網路分析工具,用來定位到目標主機之間的所有路由器

在介紹traceroute的原理之前,需要了解幾個技術名詞:

具體到traceroute的實現細節上,有兩種不同的方案:

在基於udp的實現中,客戶端傳送的資料報是通過udp協議來傳輸的,使用了乙個大於30000的埠號,伺服器在收到這個資料報的時候會返回乙個埠不可達的icmp錯誤資訊,客戶端通過判斷收到的錯誤資訊是ttl超時還是埠不可達來判斷資料報是否到達目標主機,具體的流程如圖:

客戶端傳送乙個ttl為1,埠號大於30000的udp資料報,到達第一站路由器之後ttl被減去1,返回了乙個超時的icmp資料報,客戶端得到第一跳路由器的位址。

客戶端傳送乙個ttl為2的資料報,在第二跳的路由器節點處超時,得到第二跳路由器的位址。

客戶端傳送乙個ttl為3的資料報,資料報成功到達目標主機,返回乙個埠不可達錯誤,traceroute結束。

對該網域名稱的ip:115.239.210.27進行traceroute,此時wireshark抓包的結果如下:

注意看紅框處的內容,跟第一張圖對比,可以看到traceroute程式首先通過udp協議向目標位址115.239.210.27傳送了乙個ttl為1的資料報,然後在第乙個路由器中ttl超時,返回乙個錯誤型別為time-to-live exceeded的icmp資料報,此時我們通過該資料報的源位址可知第一站路由器的位址為10.242.0.1。之後只需要不停增加ttl的值就能得到每一跳的位址了。

然而一直跑下去會發現,traceroute並不能到達目的地,當ttl增加到一定大小之後就一直拿不到返回的資料報了:

其實這個時候資料報已經到達目標伺服器了,但是因為安全問題大部分的應用伺服器都不提供udp服務(或者被防火牆擋掉),所以我們拿不到伺服器的任何返回,程式就理所當然的認為還沒有結束,一直嘗試增加資料報的ttl。

目前在網上找到許多開源ios traceroute實現大多都是基於udp的方案,實際用起來並不能達到想要的效果,所以我們需要採用另一種方案來實現。

上述方案失敗的原因是由於伺服器對於udp資料報的處理,所以在這一種實現中我們不使用udp協議,而是直接傳送乙個**icmp回顯請求(echo request)資料報,伺服器在收到回顯請求的時候會向客戶端傳送乙個icmp回顯應答(echo reply)**資料報,在這之後的流程還是跟第一種方案一樣。這樣就避免了我們的traceroute資料報被伺服器的防火牆策略牆掉。

採用這種方案的實現流程如下:

客戶端傳送乙個ttl為1的icmp請求回顯資料報,在第一跳的時候超時並返回乙個icmp超時資料報,得到第一跳的位址。

客戶端傳送乙個ttl為2的icmp請求回顯資料報,得到第二跳的位址。

客戶端傳送乙個ttl為3的icmp請求回顯資料報,到達目標主機,目標主機返回乙個icmp回顯應答,traceroute結束。

可以看出與第一種實現相比,區別主要在傳送的資料報型別以及對於結束的判斷上,大體的流程還是一致的。

值得一提的是在windows系統中也有traceroute程式,它的名字叫做tracerttracert就是用採用這種方法來實現的,感興趣的話可以自行嘗試一下,這裡就不再演示了。

這裡我們主要討論基於icmp的實現,相關的demo已經上傳至github:github.com/l-zephyr/my…

採用這種方案時,icmp資料報的建立、解析、校驗都需要我們自己進行,icmp是封裝在ip資料報的資料段中傳輸的,所以關鍵在於如何建立和傳送icmp資料,以及接收到返回的資料時如何從ip資料報中將icmp解析出來:

icmp資料報頭部的格式如下:

其中的型別字段用來表示訊息的型別,在wiki上可以看到所有型別代表的含義。報文中的識別符號和序列號由傳送端指定,如果這個icmp報文是乙個請求回顯的報文(型別為8,**為0),這兩個欄位會被原封不動的返回。

根據上圖中各個欄位的大小可以定義如下型別:

typedef struct icmppacket  icmppacket;

複製**

其中的type字段指定了這個icmp資料報的型別,是需要重點關注的物件,為此定義乙個報文型別的列舉:

// icmpv4報文型別

typedef enum icmpv4type icmpv4type;

複製**

在傳送資料的時系統會自動加上ip頭部不需要自己處理,如此一來我們只需要建立乙個icmppacket資料報並通過socket傳送到目標伺服器就可以了。

接下來就是要接收伺服器向我們返回的icmp資料了,我們接收到的是帶有ip頭部的原始資料,所以必須先進行一些處理將icmp從ip資料報中提取出來,ip資料報由兩部分組成:資料報頭部資訊部分以及實際的資料部分。下圖是ipv4資料報的結構:

一眼看上去是不是感覺很混亂,其實這裡面只有用紅框圈出來的這這三個字段需要我們關心:版本表示該資料報是ipv4還是ipv6;之前說過icmp協議是通過ip協議來傳輸的,如果該資料報傳輸的是icmp協議則協議欄位會被設定為1;由於ipv4資料報帶有可選的選項字段,所以其頭部的長度是可變的,此時需要根據首部長度欄位來獲取具體的資料。

根據上面的結構可以定義型別:

typedef struct ipv4header  ipv4header;

複製**

提取icmp資料報的方法如下:

+ (icmppacket *)unpackicmpv4packet:(char *)packet len:(int)len 

const struct ipv4header *ipptr = (const ipv4header *)packet;

if ((ipptr->versionandheaderlength & 0xf0) != 0x40 || // ipv4

ipptr->protocol != 1)

// 獲取ip頭部長度

size_t ipheaderlength = (ipptr->versionandheaderlength & 0x0f) * sizeof(uint32_t);

if (len < ipheaderlength + sizeof(icmppacket))

// 返回資料部分的icmp

return (icmppacket *)((char *)packet + ipheaderlength);

}複製**

其**現的如ipptr->versionandheaderlength & 0xf0的判斷是因為版本號和首部長度各自只佔4個bit,在結構中直接定義了乙個1位元組的uint8_t型別來表示,所以只能通過位運算子&來獲取各自的值。

有了上面的兩步,剩下的事情就很簡單了,下面是整體流程的偽**:

// 1. 建立乙個套接字

int sock = socket(af_inet, sock_dgram, ipproto_icmp);

// 2. 最多嘗試30跳

int ttl = 1;

for (0...30)

}複製**

socket的型別採用了sock_dgram,有些小夥伴可能會感到疑惑:用sock_dgram建立套接字不還是傳送udp資料麼?

確實在許多系統的實現中要直接傳送icmp的話需要使用原始套接字(型別為sock_raw),這在ios系統中是不被允許使用的,但是查閱資料中了解到macos支援一種使用引數sock_dgramipproto_icmp來直接建立icmp套接字方式,嘗試之下果然ios也支援這種用法。不過在使用中發現了乙個問題:使用ipv4套接字的時候接收到的資料報是帶有原始ip頭部的,而使用ipv6套接字的時候收到的資料報卻沒有ip頭部,這個問題讓我比較疑惑,各位大佬如果有對這一塊了解的話還望賜教。

demo中的示例程式已經在模擬器和真機環境經過測試,可以看到,現在traceroute已經能夠正常的工作了:

有些路由器會隱藏的自己的位置,不讓icmp timeout的訊息通過,結果就是在那一跳上始終會顯示星號,此外伺服器也可以偽造traceroute路徑的,不過一般應用伺服器也沒有理由這麼做,所以traceroute的結果還是能夠為網路分析提供一些參考的。

traceroute 路由跟蹤

traceroute是乙個通用的tcp ip工具,它能顯示出資料報從本地機到達目標機時經過的所有路由器。由於traceroute會人為地給網路造成一些通訊負擔,所有它主要被系統或網路管理員,作為乙個簡單而有效的網路手工查錯工具使用,普通使用者最好少用。儘管如此,traceroute對於使用者了解in...

Traceroute路由跟蹤技術

一 traceroute概述 traceroute是一種將資料報途經路由,ip位址回顯的指令,基於icmp協議實現,通過不斷傳送不可交付的ip資料報,獲取從源主機到目標主機的路由ip。二 traceroute工作原理 traceroute工具從源主機向目的主機傳送一連串的ip資料報,資料報中封裝的是...

關於路由跟蹤指令 traceroute

我們都用過ping命令來檢查主機與目標位址是否連通,自己的主機與目標位址的通訊包通訊速率,所謂的通訊包也就是那些什麼tcp ip,udp包。原理 關於路由跟蹤 路由跟蹤指令對linux來說是traceroute,在windows則是tracert,這次就主要來說traceroute。tracerou...