在 iOS 平臺實現Ping 和 traceroute

ping 命令

Ping是爲了測試另外一臺主機是否可達,如今已經成爲一種經常使用的網絡狀態檢查工具。linux

常見的ping命令:ios

/**** 往目的追擊發送固定包數 ****/
ping -c 3 www.baidu.com   // ping百度發送3個包

/**** 設置兩次發包之間的等待時間 ****/
ping -i 5 www.baidu.com   // 兩包之間的時間間隔爲5s
ping -i 0.1 www.baidu.com // 兩包之間的時間間隔爲0.1s

/**** 檢查本地網絡接口是否已經啓動並正在運行  ****/
ping 127.0.0.1  (linux: ping 0) 
ping localhost 

/**** 超級用戶能夠利用 -f 幾秒鐘發送數十萬個包給主服務形成壓力 *****/
sudo ping -f www.baidu.com 

/**** 讓電腦發出蜂鳴聲: 響應包到達目時,會發出聲音  ****/
ping -a www.baidu.com 

/**** 只打印ping的彙總結果  ****/
ping -c 5 -q www.baidu.com

/**** 修改ping包(icmp包)的大小 ****/
ping -s 100 -c 5 www.baidu.com




複製代碼

示例:c++

macdeiMac:PhoneNetSDK ethan$ ping www.baidu.com

PING www.a.shifen.com (61.135.169.121): 56 data bytes
64 bytes from 61.135.169.121: icmp_seq=0 ttl=49 time=32.559 ms
64 bytes from 61.135.169.121: icmp_seq=1 ttl=49 time=32.413 ms
64 bytes from 61.135.169.121: icmp_seq=2 ttl=49 time=32.489 ms
^C
--- www.a.shifen.com ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 32.413/32.487/32.559/0.060 ms
macdeiMac:PhoneNetSDK ethan$ 
複製代碼

分析以上結果:git

  • 發送端信息github

    • www.a.shifen.com (61.135.169.121): 對域名作了自動DNS解析
    • 56 data bytes: 向該主機發送大小是56字節的數據包。
  • 主機響應的信息安全

    • icmp_seq: 響應包的序列號。
    • ttl: ip數據報的ttl值。
    • time:請求往返耗時。
    • 64 bytes:響應數據包的大小是64個字節。
  • 統計總結信息bash

    • 0.0% packet loss: 總共發了3個包丟包率是0%
    • min/avg/max = 32.413/32.487/32.559:最小/平均/最大往返時間32.413/32.487/32.559

TTL(Time to live): IP數據報的生存時間,單位是hop(跳)。好比64,每過一個路由器就把該值減1,若是減到0 就表示路由已經太長了仍然找不到目的主機的網絡,就丟棄該包。服務器

問題:在發包時,爲何發送的是56字節的包,主機響應的倒是64字節的包? 在這裏的56和64是同一個概念嗎?網絡

icmp

互聯網控制消息協議(英語:Internet Control Message Protocol,縮寫:ICMP)是互聯網協議族的核心協議之一。它是TCP/IP協議族的一個子協議,它用於TCP/IP網絡中發送控制消息,提供可能發生在通訊環境中的各類問題反饋,經過這些信息,使管理者能夠對所發生的問題做出診斷,而後採起適當的措施解決。併發

控制消息有:目的不可達下次,超時信息,重定向消息,時間戳請求和時間戳響應消息,回顯請求和回顯應答消息。

ICMP [1]依靠IP來完成它的任務,它是IP的主要部分。它與傳輸協議(如TCP和UDP)顯著不一樣:它通常不用於在兩點間傳輸數據。它一般不禁網絡程序直接使用,除了ping和traceroute這兩個特別的例子。 IPv4中的ICMP被稱做ICMPv4,IPv6中的ICMP則被稱做ICMPv6。

icmp技術細節

CMP是在RFC 792中定義的互聯網協議族之一。一般用於返回的錯誤信息或是分析路由。ICMP錯誤消息老是包括了源數據並返回給發送者。 ICMP錯誤消息的例子之一是TTL值過時。每一個路由器在轉發數據報的時候都會把IP包頭中的TTL值減1。若是TTL值爲0,「TTL在傳輸中過時」的消息將會回報給源地址。 每一個ICMP消息都是直接封裝在一個IP數據包中的,所以,和UDP同樣,ICMP是不可靠的。

雖然ICMP是包含在IP數據包中的,可是對ICMP消息一般會特殊處理,會和通常IP數據包的處理不一樣,而不是做爲IP的一個子協議來處理。在不少時候,須要去查看ICMP消息的內容,而後發送過當的錯誤消息到那個原來產生IP數據包的程序,即那個致使ICMP信息被傳送的IP數據包。

不少經常使用的工具是基於ICMP消息的。traceroute是經過發送包含有特殊的TTL的包,而後接收ICMP超超消息和目標不可達消息來實現的。ping則是用ICMP的」Echo request」(類別代碼:8)和」Echo reply」(類別代碼:0)消息來實現的。

icmp報文結構

報頭

ICMP報頭從IP報頭的第160位開始(ip首部20字節)

  • Type: ICMP的類型,標識生成的錯誤報文
  • Code: 進一步割分ICMP的類型,該字段用來查找產生錯誤的緣由;例如ICMP的目標不可達類型能夠把這個位設置爲1-15等來表示不一樣的意思。
  • Checksum : 校驗碼部分,這個字段包含有從ICMP報頭和數據部分計算得來,用於檢查錯誤的數據,其中此校驗碼字段的值視爲0
  • ID :這個字段包含了ID值,在Echo Reply類型的消息中要返回這個字段
  • Sequence : 這個字段包含一個序號,一樣要在Echo Reply類型的消息中要返回這個字段

填充數據

填充的數據緊接在ICMP報頭的後面(以8位爲一組):

  • Linux的ping工具填充的ICMP除了8個8位元組的報頭之外,默認狀況下還另外填充數據使得總大小位64字節。
  • Windows的ping.exe填充的ICMP除了8個8位元組的報頭之外,默認狀況下還另外填充數據使得總大小位40字節。

ping

ping實現原理

Ping是爲了測試另外一臺主機是否可達,如今已經成爲一種經常使用的網絡狀態檢查工具。該程序發送一份 ICMP回顯請求報文給遠程主機,並等待返回 ICMP回顯應答。

ping 使用的是ICMP協議,它發送icmp回送請求消息給目的主機。ICMP協議規定:目的主機必須返回ICMP回送應答消息給源主機。若是源主機在必定時間內收到應答,則認爲主機可達。大多數的 TCP/IP 實現都在內核中直接支持Ping服務器,ICMP回顯請求和回顯應答報文以下圖所示。

image

ping的原理:

ping的原理是用類型碼爲8的ICMP發請求,收到請求的主機則用類型碼爲0的ICMP迴應。經過計算ICMP應答報文數量和與接受與發送報文之間的時間差,判斷當前的網絡狀態。這個往返時間的計算方法是:ping命令在發送ICMP報文時將當前的時間值存儲在ICMP報文中發出,當應答報文返回時,使用當前時間值減去存放在ICMP報文數據中存放發送請求的時間值來計算往返時間。ping返回接收到的數據報文字節大小、TTL值以及往返時間。

利用wireshark查看ping

我在命令行中ping www.baidu.com 如下是顯示結果:

image

image

如上圖所示,icmp包的type是8 , 是request請求; icmp的包type是0 ,是reply.

計算機網絡基礎知識

TCP/IP協議棧與數據包封裝

OSI七層模型以及TCP/IP模型:

兩臺計算機經過TCP/IP的通訊過程以下:

傳輸層及其如下的機制由內核提供,應用層由用戶進程提供,應用程序對通信數據的含義進行解釋,而傳輸層及其如下處理通信的細節,將數據從一臺計算機經過必定的路徑發送到另外一臺計算機。應用層數據經過協議棧發到網絡上時,每層協議都要加上一個數據首部(header),稱爲封裝(Encapsulation)。

TCP/IP數據包的封裝:

目的主機收到數據包後,通過各層協議棧最後到達應用程序。

以太網驅動程序首先根據以太網首部中的「上層協議」字段肯定該數據幀的有效載荷是IP、ARP仍是RARP協議的數據報,而後交給相應的協議處理。假如是IP數據報,IP協議再根據IP首部中的「上層協議」字段肯定該數據報的有效載荷是TCP、UDP、ICMP仍是IGMP,而後交給相應的協議處理。假如是TCP段或UDP段,TCP或UDP協議再根據TCP首部或UDP首部的「端口號」字段肯定應該將應用層數據交給哪一個用戶進程。IP地址是標識網絡中不一樣主機的地址,而端口號就是同一臺主機上標識不一樣進程的地址,IP地址和端口號合起來標識網絡中惟一的進程。

注意,雖然IP、ARP和RARP數據報都須要以太網驅動程序來封裝成幀,可是從功能上劃分,ARP和RARP屬於鏈路層,IP屬於網絡層。雖然ICMP、IGMP、TCP、UDP的數據都須要IP協議來封裝成數據報,可是從功能上劃分,ICMP、IGMP與IP同屬於網絡層,TCP和UDP屬於傳輸層。

IP數據報格式

IPv4數據包格式以下:

關於首部長度:

根據IP數據報,判斷當前包是不是IPv4

version佔4位,首部長度佔4位,version = 4(IPv4), ipheader=20.
因爲首部長度是以4字節爲單位的-> version: 0100 ; 首部長度:0101

獲取version:  0100 0101 & 0xFO(11110000) = 01000000 = 0x40
獲取首部長度:  0100 0101 & 0x0F(00001111) = 0000 0101 = 5個4字節 = 20 Byte

複製代碼

ping實現(c++&oc)

技術預研與構思

根據ping的結果,咱們須要解決如下問題:

macdeiMac:PhoneNetSDK ethan$ ping www.baidu.com

PING www.a.shifen.com (61.135.169.121): 56 data bytes
64 bytes from 61.135.169.121: icmp_seq=0 ttl=49 time=32.559 ms
64 bytes from 61.135.169.121: icmp_seq=1 ttl=49 time=32.413 ms
64 bytes from 61.135.169.121: icmp_seq=2 ttl=49 time=32.489 ms
^C
--- www.a.shifen.com ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 32.413/32.487/32.559/0.060 ms
macdeiMac:PhoneNetSDK ethan$ 
複製代碼
  • DNS解析(域名->ip)
  • 本地終端接收到的每一個icmp包來自哪一個主機
  • icmp_seq
  • ttl
  • time

以上問題解決方案以下:

  • DNS解析: socket支持
  • 本地終端接收到的每一個icmp包來自哪一個主機: ip包中的source
  • icmp_seq: icmp包中的 sequence number
  • ttl: ip包中的Time to live
  • time: 發送包和接收到包時的時間差

具體實現

IP包定義:

typedef struct PNetIPHeader {
    uint8_t versionAndHeaderLength;
    uint8_t differentiatedServices;
    uint16_t totalLength;
    uint16_t identification;
    uint16_t flagsAndFragmentOffset;
    uint8_t timeToLive;
    uint8_t protocol;
    uint16_t headerChecksum;
    uint8_t sourceAddress[4];
    uint8_t destinationAddress[4];
    // options...
    // data...
}PNetIPHeader;
複製代碼

ICMP包定義:

/*
 use linux style . totals 64B
 */
typedef struct UICMPPacket
{
    uint8_t type;
    uint8_t code;
    uint16_t checksum;
    uint16_t identifier;
    uint16_t seq;
    char fills[56];  // data
}UICMPPacket;
複製代碼

構造ICMP包:

+ (UICMPPacket *)constructPacketWithSeq:(uint16_t)seq andIdentifier:(uint16_t)identifier
{
    UICMPPacket *packet = (UICMPPacket *)malloc(sizeof(UICMPPacket));
    packet->type  = ENU_U_ICMPType_EchoRequest;
    packet->code = 0;
    packet->checksum = 0;
    packet->identifier = OSSwapHostToBigInt16(identifier);
    packet->seq = OSSwapHostToBigInt16(seq);
    memset(packet->fills, 65, 56);
    packet->checksum = [self in_cksumWithBuffer:packet andSize:sizeof(UICMPPacket)];
    return packet;
}
複製代碼

發送icmp包:

UICMPPacket *packet = [PhoneNetDiagnosisHelper constructPacketWithSeq:index andIdentifier:identifier];
        _sendDate = [NSDate date];
        ssize_t sent = sendto(socket_client, packet, sizeof(UICMPPacket), 0, (struct sockaddr *)&remote_addr, (socklen_t)sizeof(struct sockaddr));
        if (sent < 0) {
            log4cplus_warn("PhoneNetPing", "ping %s , send icmp packet error..\n",[self.host UTF8String]);
        }
複製代碼

接收icmp包:

size_t bytesRead = recvfrom(socket_client, buffer, 65535, 0, (struct sockaddr *)&ret_addr, &addrLen);
  if ((int)bytesRead < 0) {
            [self reporterPingResWithSorceIp:self.host ttl:0 timeMillSecond:0 seq:0 icmpId:0 dataSize:0 pingStatus:PhoneNetPingStatusDidTimeout];
            res = YES;
        }else if(bytesRead == 0){
            log4cplus_warn("PhoneNetPing", "ping %s , receive icmp packet error , bytesRead=0",[self.host UTF8String]);
        }else{
            
            if ([PhoneNetDiagnosisHelper isValidPingResponseWithBuffer:(char *)buffer len:(int)bytesRead]) {
                
                UICMPPacket *icmpPtr = (UICMPPacket *)[PhoneNetDiagnosisHelper icmpInpacket:(char *)buffer andLen:(int)bytesRead];
                
                int seq = OSSwapBigToHostInt16(icmpPtr->seq);
                
                NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:_sendDate];
                
                int ttl = ((PNetIPHeader *)buffer)->timeToLive;
                int size = (int)(bytesRead-sizeof(PNetIPHeader));
                NSString *sorceIp = self.host;
                
                
//                NSLog(@"PhoneNetPing, ping %@ , receive icmp packet..\n",self.host );
                [self reporterPingResWithSorceIp:sorceIp  ttl:ttl timeMillSecond:duration*1000 seq:seq icmpId:OSSwapBigToHostInt16(icmpPtr->identifier) dataSize:size pingStatus:PhoneNetPingStatusDidReceivePacket];
                res = YES;
            }
複製代碼

從接收到的buffer中分離icmp包:

/* 從 ipv4 數據包中解析出icmp */
+ (char *)icmpInpacket:(char *)packet andLen:(int)len
{
    if (len < (sizeof(PNetIPHeader) + sizeof(UICMPPacket))) {
        return NULL;
    }
    const struct PNetIPHeader *ipPtr = (const PNetIPHeader *)packet;
    if ((ipPtr->versionAndHeaderLength & 0xF0) != 0x40 // IPv4
        ||
        ipPtr->protocol != 1) { //ICMP
        return NULL;
    }
    size_t ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t);
    
    if (len < ipHeaderLength + sizeof(UICMPPacket)) {
        return NULL;
    }
    
    return (char *)packet + ipHeaderLength;
}
複製代碼

校驗接收到的icmp包:

+ (BOOL)isValidPingResponseWithBuffer:(char *)buffer len:(int)len
{
    UICMPPacket *icmpPtr = (UICMPPacket *)[self icmpInpacket:buffer andLen:len];
    if (icmpPtr == NULL) {
        return NO;
    }
    uint16_t receivedChecksum = icmpPtr->checksum;
    icmpPtr->checksum = 0;
    uint16_t calculatedChecksum = [self in_cksumWithBuffer:icmpPtr andSize:len-((char*)icmpPtr - buffer)];
    
    return receivedChecksum == calculatedChecksum &&
    icmpPtr->type == ENU_U_ICMPType_EchoReplay &&
    icmpPtr->code == 0 &&
    OSSwapBigToHostInt16(icmpPtr->identifier)>=KPingIcmpIdBeginNum;;
}
複製代碼

TCP ping

當有些服務器禁ping時,能夠選擇TCP ping。

TCP ping原理

經過和目的主機及其端口創建TCP鏈接的方式計算其鏈接耗時。

traceroute

traceroute命令

/**** 設置每一個路由發送的包數 ****/
traceroute -q 5 baidu.com

/**** 設置最大路由跳數 ****/
traceroute -m 5 baidu.com

/**** 不作DNS解析 ****/
traceroute -n baidu.com

/**** 繞過路由表直接發送到目的治具 ****/
traceroute -r baidu.com

/**** 使用ICMP包取代UDP包 ****/
traceroute -I baidu.com
複製代碼

traceroute原理

tacceroute是利用增長存活時間(TTL)值來實現功能的。每當一個icmp包通過一個路由器時,其存活時間值就會減1,當其存活時間爲0時,路由器便會取消包發送,併發送一個ICMP TTL封包給原封包發出者。

traceroute過程

主叫方首先發出TTL = 1 的數據包,第一個路由器將 TTL 減1得0後就再也不繼續轉發此數據包,而是返回一個ICMP超時報文,主叫方從超時報文中便可提取出數據包所通過的第一個路由器的地址。而後又發出一個TTL=2的ICMP數據包,可得到第二個路由器的地址,依次增長TTL便獲取了沿途全部路由器位地址。

須要注意的是,並非全部路由器都會如實返回ICMP超時報文。出於安全性考慮,大多數防火牆以及啓動了防火牆功能的路由器缺省配置爲不返回各類ICMP報文,其路由器或交換機也可被管理員主動修改配置變爲不返回ICMP報文。所以Traceroute程序不必定能拿全全部沿途路由器地址。因此當某個TTL值的數據包得不到響應是,並不能中止這一追蹤過程,程序仍然會把TTL遞增而發出下一個數據包。一直達到預設或用於參數制定的追蹤限制時才結束追蹤。

依據上述原理,利用了UDP數據包的Traceroute程序在數據包到達真正的目的主機時,就可能由於該主機沒有提供UDP服務而簡單將數據包丟棄,並不返回任何信息。爲了解決這個問題,Traceroute故意使用了一個大於30000的端口號,因UDP協議規定端口號必須小於30000,因此目標主機收到數據包後惟一能作的事就是返回一個"端口不可達"的ICMP報文,因而主叫方就將端口不可達報文當作跟蹤結束標誌。

利用wireshark查看traceroute

我在命令行中traceroute www.baidu.com 如下是顯示結果:

image

如上圖所示,UDP請求,第一個請求的端口是33435 , 接下來的UDP請求,端口會遞增。

當到達目的地址時,目的地址會replay類型爲3的包.

image

如上圖所示,是路由器返回的ICMP包,type是11。

UDP traceroute的實現

發送udp包,接收ip+icmp包,過濾route ip計算時間。

github.com/mediaios/ne…

UDP traceroute存在的問題

使用 UDP 的 traceroute,失敗仍是比較常見的。這經常是因爲,在運營商的路由器上,UDP 與 ICMP 的待遇大不相同。爲了利於 troubleshooting,ICMP 的request 和 replay 是不會封的,而 UDP 則不一樣。UDP 常被用來作網絡攻擊,由於 UDP 無需鏈接,於是沒有任何狀態約束它,比較方便攻擊者僞造源 IP、僞造目的端口發送任意多的 UDP 包,長度自定義。因此運營商爲安全考慮,對於 UDP 端口經常採用白名單 ACL,就是隻有 ACL 容許的端口才能夠經過,沒有明確容許的則通通丟棄。好比容許 DNS/DHCP/SNMP 等。

icmp traceroute

發送icmp包,類型爲8,每一個路由返回的icmp包類型是11的超時包,當到達目的地址時,目的地址會replay類型爲0的包

icmp traceroute的實現

github.com/mediaios/ne…

net-diagnosis(ios平臺下網絡診斷SDK)

net-diagnosis是ios平臺下的網絡診斷SDK,提供的功能有:

  • ping
  • tcp ping
  • traceroute
  • icmp traceroute
  • nslookup
  • port scan

項目地址: github

後續更多關於網絡診斷的功能會不斷開發完善,歡迎提交issue

另,歡迎fork和star !

相關文章
相關標籤/搜索