利用ICMP實現(xiàn)Traceroute

最近工作中遇到一個需求,就是需要知道我們發(fā)出去的請求經(jīng)過的所有路由IP地址杠愧。查了些資料,主要是用ICMP(Internet控制報文協(xié)議)。

ICMP

ICMP是IP層的一個組成部分纹安,用來傳遞錯誤報文信息的,這個東西運維用得比較多。下圖是ICMP在TCP/IP中的位置厢岂。

14540545202356

ICMP報文是在數(shù)據(jù)報內部被傳輸?shù)墓舛剑袷饺缦聢D:

14540496538467

ICMP報文格式會根據(jù)不同的錯誤類型有不同的格式,但是8位類型塔粒,8位代碼结借,16位校驗和是必不可少的,如下圖:

14540498013133

ICMP報文類型:

14540503478117

ICMP有18種報文類型卒茬,每個類型里面又分不同的code船老。

下面看看幾個常見的報文出錯類型格式。

ICMP地址掩碼請求與應答

14540513973201

ICMP時間戳請求與應答

14540521893396

ICMP不可到達報文

14540522236263

這個報文格式也是我們下面程序實現(xiàn)解析的依據(jù)扬虚。

Ping跟蹤路由的原理

Ping主要用來測試某臺主機能否到達努隙,使用的是ICMP請求回顯報文,但是同樣也提供了IP路由記錄選項功能辜昵。只需要在ping的時候加上參數(shù)-R即可荸镊,如:

14540559349951

當開啟這個RR選項后,IP數(shù)據(jù)報在經(jīng)過路由器的時候堪置,會將IP地址放置IP首部中的選項字段躬存。當數(shù)據(jù)報到達目的端時,IP地址清單復制到ICMP回顯應答中舀锨,當ping收到回顯應答時岭洲,控制臺打印出所有的IP地址。

過程很容易理解坎匿,但是有兩個缺點盾剩。第一,ping的RR選項不是所有系統(tǒng)都支持的替蔬。第二告私、保存的IP地址數(shù)目是有限的。

為什么說保存的IP地址數(shù)目是有限的呢承桥?首先看下IP首部格式:

14540575128044

IP首部的長度有4位首部長度決定驻粟,因此IP首部最大長度為15*32bit,也就是60個字節(jié)凶异。IP首都固定長度為20個字節(jié)蜀撑,所以選項字段的最大長度也只有40個字節(jié)能夠用來保存IP地址。

IP地址在IP首部選項中保存的格式:

14540578013803

開啟RR選項用去3個字節(jié)剩彬,剩下也只有37個字節(jié)可以使用酷麦,每個IP地址占用4個字節(jié),所以最多也就只能保存9個IP地址喉恋。如果我們的數(shù)據(jù)報經(jīng)過的路由器比較多時贴铜,就不準確了粪摘。

Traceroute路由跟蹤

Traceroute也是用來跟蹤IP路由選項的,但是它沒有ping的那些限制绍坝。Traceroute跟蹤路由的原理是通過設置IP數(shù)據(jù)報的TTL(生存周期)。IP數(shù)據(jù)報每經(jīng)過一個路由器的時候苔悦,就將TTL減1轩褐,如果發(fā)現(xiàn)TTL等于0,那么將不會進行再次轉發(fā)玖详,并將數(shù)據(jù)報丟棄把介,并給源地址發(fā)送一個ICMP不可到達報文。而這份ICMP報文中包含了該路由器的信息蟋座。

所以拗踢,Traceroute跟蹤路由的大致流程是先發(fā)送一個TTL為1的數(shù)據(jù)報,當?shù)谝粋€路由器處理時向臀,將TTL值減1巢墅,然后丟棄該數(shù)據(jù)報,并返回一個超時ICMP報文券膀,得到第一個IP地址君纫。然后再發(fā)送一個TTL為2的數(shù)據(jù)報,當?shù)降诙€路由器的時候芹彬,又返回一個IP地址蓄髓。重復以上步驟,我們會不斷得到超時ICMP報文舒帮。那我們如何知道我們的數(shù)據(jù)報何時到達目的主機呢会喝?

Traceroute通過發(fā)送一個UDP包,并且端口號是大于30000的玩郊。如果目的主機沒有任何程序使用該端口肢执,那么主機會產(chǎn)生一份"端口不可到達錯誤"。所以瓦宜,我們程序要做的就是解析兩種情況下的ICMP報文蔚万,一種是超時報文,還有一個是端口不可到達報文临庇。

看下系統(tǒng)的Traceroute運行過程:

終端輸入traceroute 115.239.210.27

14540685145024

這個是Wireshark抓包反璃,看到Traceroute運行的過程:

14540684535657

我們可以看到系統(tǒng)的traceroute命令實現(xiàn)是使用采用的UDP,并且發(fā)送的端口是大于30000的假夺,并且每次都是端口加1淮蜈,用來防止端口被目的主機占用的可能,返回的是ICMP報文已卷。

traceroute不能保證每次路由都是一致的梧田,可能會因為路由的選擇,結果可能不一定一致,但是大致是相似的裁眯。

程序實現(xiàn)

首先看下UDP不可到達格式鹉梨,下面的代碼解析也是根據(jù)這個來的:

14540695051510

可以看到IP數(shù)據(jù)報格式,由20字節(jié)IP首部+ICMP首部+產(chǎn)生差錯的數(shù)據(jù)報IP首部+UDP首部8字節(jié)穿稳。


struct hostent *_host = gethostbyname([host UTF8String]);
    
    if (_host == NULL) {
        //域名解析失敶嬖怼!
        return;
    }
    
    struct in_addr *addr = (struct in_addr *)_host->h_addr_list[0];
    char *ip_addr = inet_ntoa(*addr);
    
    struct sockaddr_in destAddr, fromAddr;
    memset(&destAddr, 0, sizeof(destAddr));
    destAddr.sin_family = AF_INET;
    destAddr.sin_addr.s_addr = inet_addr(ip_addr);
    destAddr.sin_port = htons(_sourePort);
    //發(fā)送采用UDP
    if ((send_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        NSLog(@"fail to create send_socket:%s", strerror(errno));
        return;
    }
    //接受ICMP
    if ((recv_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0) {
        NSLog(@"fail to create recv_socket:%s", strerror(errno));
        return;
    }
    
    struct timeval timeout;
    memset(&timeout, 0, sizeof(timeout));
    timeout.tv_sec = 0;
    timeout.tv_usec = _timeout;
    //設置超時時間
    if (setsockopt(send_sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) {
        NSLog(@"fail to set socket option:%s", strerror(errno));
        return;
    }
    //設置超時時間
    if (setsockopt(recv_sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0) {
        NSLog(@"fail to set socket option:%s", strerror(errno));
        return;
    }
    
    char recvBuf[1024];
    int ttl = 1;
    char sendBuf[100];
    memset(sendBuf, 0, sizeof(sendBuf));
    
    while (ttl < _maxTTL) {
        //設置TTL
        if (setsockopt(send_sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
            NSLog(@"fail to set socket option:%s", strerror(errno));
            return;
        }
        //開始發(fā)送
        for (int i = 0; i < _maxAttempts; i++) {
            destAddr.sin_port = htons(_sourePort++);
            if (sendto(send_sock, sendBuf, 0, 0, (struct sockaddr *) &destAddr, sizeof(destAddr)) < 0) {
                NSLog(@"fail to send data:%s", strerror(errno));
                continue;
            }
            
            ssize_t recv;
            
            memset(&fromAddr, 0, sizeof(fromAddr));
            memset(&recvBuf, 0, sizeof(recvBuf));
            socklen_t len = sizeof(fromAddr);
            
            if ((recv = recvfrom(recv_sock, recvBuf, sizeof(recvBuf), 0, (struct sockaddr *)&fromAddr, &len)) < 0) {
                NSLog(@"fail to recv data:code:%d  %s", errno,strerror(errno));
                
                if (i == _maxAttempts - 1) {
                    //超過最大嘗試次數(shù)后就不再發(fā)送了
                    break;
                }
                continue;
            }
            else {
                //以下只是數(shù)據(jù)報的解析了
                
                struct ip *ip = (struct ip*)recvBuf;
                int ipLen = ip->ip_hl<<2;
                struct icmp *icmp = (struct icmp*)(recvBuf + ipLen);
                //整個ICMP報文長度:ICMP首部 + 產(chǎn)生出錯的ip首部 + UDP首部8字節(jié)
                int icmpLen = recv - ipLen;
                
                if (icmpLen < 8) {
                    continue;
                }
                
                if (icmp->icmp_type == ICMP_TIMXCEED
                    && icmp->icmp_code == ICMP_TIMXCEED_INTRANS) {
                    //獲取產(chǎn)生出錯的ip首部 + UDP首部8字節(jié)
                    if (icmpLen < 8 + sizeof(struct ip)) {
                        continue;
                    }
                    
                    struct ip *errorIP = (struct ip *)(recvBuf + ipLen + 8);
                    
                    int errorIPLength = errorIP->ip_hl<<2;
                    
                    if (icmpLen < 8 + errorIPLength + 8) {
                        continue;
                    }
                    struct udphdr *udp = (struct udphdr *)(recvBuf + ipLen + 8 + errorIPLength);
//                    u_short port = htons(_sourePort);
//                    u_short po = htons(_sourePort);
//                    u_char ip_p = errorIP->ip_p;
//                    errorIP->ip_p == IPPROTO_UDP
                        char address[16];
                        memset(&address, 0, sizeof(address));
                        
                        inet_ntop(AF_INET, &fromAddr.sin_addr.s_addr, address, sizeof (address));
                        NSString *hostAddress = [NSString stringWithFormat:@"%s",address];
                        //打印IP地址
                        NSLog(@"====address:%@", hostAddress);
            
                        break;
                }
                else if (icmp->icmp_type == ICMP_UNREACH
                         && icmp->icmp_code == ICMP_UNREACH_PORT) {
                    //發(fā)生端口不可到達
                    break;
                }
                else {
                    NSLog(@"====%d===%d", icmp->icmp_type, icmp->icmp_code);
                }
            }
        }
        ttl++;
    }

以上代碼在真機上是跑不了的逢艘,只能在模擬器上旦袋。因為iPhone的sdk里面把解析數(shù)據(jù)報的幾個頭文件給去掉了。它改。不過不影響我們對IP獲取的需求疤孕。實際運行發(fā)現(xiàn),端口不可到達這個報文央拖,不是立馬就能得到的祭阀,包括系統(tǒng)的traceroute命令也是,系統(tǒng)會不斷的發(fā)送UDP包爬泥,過了好久有可能收到柬讨。。袍啡。

參考:
TCP/IP協(xié)議詳解
http://www.cnblogs.com/aLittleBitCool/archive/2011/09/20/2182760.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末踩官,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖臂容,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辩越,居然都是意外死亡,警方通過查閱死者的電腦和手機信粮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門黔攒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人强缘,你說我怎么就攤上這事督惰。” “怎么了旅掂?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵赏胚,是天一觀的道長。 經(jīng)常有香客問我商虐,道長觉阅,這世上最難降的妖魔是什么崖疤? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮典勇,結果婚禮上劫哼,老公的妹妹穿的比我還像新娘。我一直安慰自己割笙,他們只是感情好沦偎,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咳蔚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搔驼。 梳的紋絲不亂的頭發(fā)上谈火,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音舌涨,去河邊找鬼糯耍。 笑死,一個胖子當著我的面吹牛囊嘉,可吹牛的內容都是我干的温技。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼扭粱,長吁一口氣:“原來是場噩夢啊……” “哼舵鳞!你這毒婦竟也來了?” 一聲冷哼從身側響起琢蛤,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蜓堕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后博其,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體套才,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年慕淡,在試婚紗的時候發(fā)現(xiàn)自己被綠了背伴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡峰髓,死狀恐怖傻寂,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情儿普,我是刑警寧澤崎逃,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站眉孩,受9級特大地震影響个绍,放射性物質發(fā)生泄漏勒葱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一巴柿、第九天 我趴在偏房一處隱蔽的房頂上張望凛虽。 院中可真熱鬧,春花似錦广恢、人聲如沸凯旋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽至非。三九已至,卻和暖如春糠聪,著一層夾襖步出監(jiān)牢的瞬間荒椭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工舰蟆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趣惠,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓身害,卻偏偏與公主長得像味悄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子塌鸯,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容

  • 8.1 引言 由Van Jacobson編寫的Traceroute程序是一個能更深入探索TCP/IP協(xié)議的方便可用...
    張芳濤閱讀 1,661評論 0 3
  • 個人認為侍瑟,Goodboy1881先生的TCP /IP 協(xié)議詳解學習博客系列博客是一部非常精彩的學習筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,054評論 0 8
  • 1.這篇文章不是本人原創(chuàng)的界赔,只是個人為了對這部分知識做一個整理和系統(tǒng)的輸出而編輯成的丢习,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,063評論 6 174
  • 11.1 引言 UDP是一個簡單的面向數(shù)據(jù)報的運輸層協(xié)議:進程的每個輸出操作都正好產(chǎn)生一個UDP數(shù)據(jù)報,并組裝成一...
    張芳濤閱讀 2,808評論 1 6
  • 接了新的班級淮悼,又要認識一批新的一年級的孩子咐低,以往是貼桌簽,一目了然一周的時間保證認識一班的孩子袜腥。此次见擦,放慢認識...
    心童夕惕閱讀 336評論 0 1