這個是用Mac下的Network Utility工具實現ping命令,用Wireshark抓取的ICMP數據包:發送ICMP數據包內容接受ICMP數據包內容一.icmp結構要真正瞭解ping命令實現原理,就要瞭解ping命令所使用到的TCP/IP協議。ICMP(Internet Control Me...
這個是用Mac下的Network Utility工具實現ping命令,用Wireshark抓取的ICMP數據包:
一.icmp結構
要真正瞭解ping命令實現原理,就要瞭解ping命令所使用到的TCP/IP協議。
ICMP(Internet Control Message,網際控制報文協議)是為網關和目標主機而提供的一種差錯控制機制,使它們在遇到差錯時能把錯誤報告給報文源發方。ICMP協議是IP層的 一個協議,但是由於差錯報告在發送給報文源發方時可能也要經過若幹子網,因此牽涉到路由選擇等問題,所以ICMP報文需通過IP協議來發送。
ICMP數據報的數據發送前需要兩級封裝:首先添加ICMP報頭形成ICMP報文,再添加IP報頭形成IP數據報
各種ICMP報文的前32bits都是三個長度固定的欄位:type類型欄位(8位)、code代碼欄位(8位)、checksum校驗和欄位(16位)
8bits類型和8bits代碼欄位:一起決定了ICMP報文的類型。常見的有:
類型8、代碼0:回射請求。
類型0、代碼0:回射應答。
類型11、代碼0:超時。
16bits校驗和欄位:包括數據在內的整個ICMP數據包的校驗和,其計算方法和IP頭部校驗和的計算方法是一樣的。
對於ICMP回射請求和應答報文來說,接下來是16bits標識符欄位:用於標識本ICMP進程。
最後是16bits序列號欄位:用於判斷回射應答數據報。
ICMP報文包含在IP數據報中,屬於IP的一個用戶,IP頭部就在ICMP報文的前面
一個ICMP報文包括IP頭部(20位元組)、ICMP頭部(8位元組)和ICMP報文數據部分
ICMP報文格式,在Mac(Unix)下結構包含在ip_icmp.h中:
引入頭文件#include //icmp數據包結構
struct icmp { u_char icmp_type; /* type of message, see below */ u_char icmp_code; /* type sub code */ u_short icmp_cksum; /* ones complement cksum of struct */ union { u_char ih_pptr; /* ICMP_PARAMPROB */ struct in_addr ih_gwaddr; /* ICMP_REDIRECT */ struct ih_idseq { n_short icd_id; n_short icd_seq; } ih_idseq; int ih_void; /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */ struct ih_pmtu { n_short ipm_void; n_short ipm_nextmtu; } ih_pmtu; struct ih_rtradv { u_char irt_num_addrs; u_char irt_wpa; u_int16_t irt_lifetime; } ih_rtradv; } icmp_hun; #define icmp_pptr icmp_hun.ih_pptr #define icmp_gwaddr icmp_hun.ih_gwaddr #define icmp_id icmp_hun.ih_idseq.icd_id #define icmp_seq icmp_hun.ih_idseq.icd_seq #define icmp_void icmp_hun.ih_void #define icmp_pmvoid icmp_hun.ih_pmtu.ipm_void #define icmp_nextmtu icmp_hun.ih_pmtu.ipm_nextmtu #define icmp_num_addrs icmp_hun.ih_rtradv.irt_num_addrs #define icmp_wpa icmp_hun.ih_rtradv.irt_wpa #define icmp_lifetime icmp_hun.ih_rtradv.irt_lifetime union { struct id_ts { n_time its_otime; n_time its_rtime; n_time its_ttime; } id_ts; struct id_ip { struct ip idi_ip; /* options and then 64 bits of data */ } id_ip; struct icmp_ra_addr id_radv; u_int32_t id_mask; char id_data[1]; } icmp_dun; #define icmp_otime icmp_dun.id_ts.its_otime #define icmp_rtime icmp_dun.id_ts.its_rtime #define icmp_ttime icmp_dun.id_ts.its_ttime #define icmp_ip icmp_dun.id_ip.idi_ip #define icmp_radv icmp_dun.id_radv #define icmp_mask icmp_dun.id_mask #define icmp_data icmp_dun.id_data };
IP報頭格式如下圖:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
IP報文格式,在Mac(Unix)下結構包含在ip.h中:
引入頭文件#include //ip數據包結構
struct ip { #ifdef _IP_VHL u_char ip_vhl; /* version << 4 | header length >> 2 */ #else #if BYTE_ORDER == LITTLE_ENDIAN u_int ip_hl:4, /* header length */ ip_v:4; /* version */ #endif #if BYTE_ORDER == BIG_ENDIAN u_int ip_v:4, /* version */ ip_hl:4; /* header length */ #endif #endif /* not _IP_VHL */ u_char ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_char ip_ttl; /* time to live */ u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src,ip_dst; /* source and dest address */ };
二.具體實現代碼
//xSocketPing.c
#include "xSocketPing.h" //statistics void statistics(char* back){ double percent = ((double)sendPacketNumber - (double)recvPacketNumber) / (double)sendPacketNumber * 100; sprintf(back, "---%s ping statistics---\n%d packets trasmitted, %d packet received, %0.1f%% packet loss",inet_ntoa(dstAddr.sin_addr),sendPacketNumber,recvPacketNumber,percent); } //check sum unsigned short checkSum(unsigned short *buffer, int size){ unsigned long checkSum = 0; while (size > 1) { checkSum += *buffer++; size -= sizeof(unsigned short);//unsigned short is 2 bytes = 16 bits } //if size is odd number if (size == 1){ checkSum += *(unsigned short *)buffer; } checkSum = (checkSum >> 16) + (checkSum & 0xFFFF); checkSum += (checkSum >> 16); return ~checkSum; } //calculate time difference double timeSubtract(struct timeval *recvTimeStamp, struct timeval *sendTimeStamp){ //calculate seconds long timevalSec = recvTimeStamp->tv_sec - sendTimeStamp->tv_sec; //calculate microsends long timevalUsec = recvTimeStamp->tv_usec - sendTimeStamp->tv_usec; //if microsends less then zero if (timevalUsec < 0) { timevalSec -= 1; timevalUsec = - timevalUsec; } return (timevalSec * 1000.0 + timevalUsec) / 1000.0; } //fill icmp packet and return size of packet int fillPacket(int packetSequence){ int packetSize = 0; struct icmp *icmpHeader = (struct icmp *)sendBuffer; icmpHeader->icmp_type = ICMP_ECHO; icmpHeader->icmp_code = 0; icmpHeader->icmp_cksum = 0; icmpHeader->icmp_id = pid; icmpHeader->icmp_seq = packetSequence; packetSize = dataSize + 8; tvSend = (struct timeval *)icmpHeader->icmp_data; gettimeofday(tvSend, NULL);//get current of time icmpHeader->icmp_cksum = checkSum((unsigned short *)icmpHeader, packetSize); return packetSize; } //send icmp packet to dstAddr int sendPacket(int packetSequence){ int packSize = 0; packSize = fillPacket(packetSequence); if ((sendto(socketfd, sendBuffer, packSize, 0, (struct sockaddr *)&dstAddr, sizeof(dstAddr))) < 0) { printf("Send icmp packet Error\n"); sendPacketNumber--; recvPacketNumber--; return -1; } return 0; } //setting ip address void settingIP(){ //initialize bzero(&dstAddr,sizeof(dstAddr)); dstAddr.sin_family = AF_INET; dstAddr.sin_addr.s_addr = inet_addr(ipAddr); } //get current process id void getPid(){ pid = getpid(); } //create socket int createSocket(){ //原始套接字SOCK_RAW需要使用root許可權,所以改用SOCK_DGRAM if ((socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0) { printf("Create Socket Error\n"); return -1; } return 0; } //setting socket void settingSocket(int timeout){ int size = 50 * 1024; //setting timeout seconds or you can set it by microseconds struct timeval timeOut; timeOut.tv_sec = timeout; //擴大套接字接收緩衝區到50K這樣做主要為了減小接收緩衝區溢出的可能性,若無意中ping一個廣播地址或多播地址,將會引來大量應答 setsockopt(socketfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); setsockopt(socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeOut, sizeof(timeOut)); } //destory socket void destorySocket(){ close(socketfd); } //unpacket void unPacket(char* packetBuffer,char* back, long size){ struct ip *ipHeader = NULL; struct icmp *icmpHeader = NULL; double rtt;//往返時間 int ipHeaderLength;//ip header length ipHeader = (struct ip *)packetBuffer; ipHeaderLength = ipHeader->ip_hl<<2;//求ip報頭長度,即ip報頭的長度標誌乘4 icmpHeader = (struct icmp *)(packetBuffer + ipHeaderLength);//越過IP頭,point to ICMP header size -= ipHeaderLength; if (size < 8){ back = "Unpacket Error:packet size minmum 8 bytes\n"; } if ((icmpHeader->icmp_type == ICMP_ECHOREPLY) && (icmpHeader->icmp_id == pid)) { tvSend = (struct timeval *)icmpHeader->icmp_data; gettimeofday(&tvRecv, NULL); //以毫秒為單位計算rtt rtt = timeSubtract(&tvRecv, tvSend); sprintf(back,"%ld bytes from %s: icmp_seq=%u ttl=%d time=%.1f ms\n",size,inet_ntoa(recvAddr.sin_addr),icmpHeader->icmp_seq,ipHeader->ip_ttl,rtt); }else{ back = "Unpacket Error\n"; } } //receive packet void receivePacket(char* back){ //claculate packet size int packetSize = sizeof(recvAddr); long size; if ((size = recvfrom(socketfd, recvBuffer, sizeof(recvBuffer), 0, (struct sockaddr *)&recvAddr, (socklen_t *)&packetSize)) < 0) { sprintf(back,"Receive timeout\n"); recvPacketNumber--; }else{ gettimeofday(&tvRecv, NULL); //char temp[100] = {0}; unPacket(recvBuffer, back, size); //printf("%s\n",temp); } } void ping(char *ipAddress, int number, int timeout){ int packetnumber = 0; ipAddr = ipAddress; sendPacketNumber = number; recvPacketNumber = number; settingIP(); getPid(); if (createSocket() != -1){ settingSocket(timeout); printf("PING %s: %d bytes of data.\n",ipAddress,dataSize); while(packetnumber < number){ if (sendPacket(packetnumber) != -1){ char back[100] = {0}; receivePacket(back); printf("%s",back); } sleep(1); packetnumber++; } char back[100] = {0}; statistics(back); printf("%s\n",back); destorySocket(); } }
//xSocketPing.h
#ifndef xSocketPing_h #define xSocketPing_h #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h>//基本系統數據類型 #include <sys/socket.h> #include <netinet/in.h> //get current of time #include <sys/time.h> #include <arpa/inet.h>//inet_ntoa將一個IP轉換成一個互聯網標準點分格式的字元串 #include <unistd.h>//close(int) #include <netdb.h>//定義了與網路有關的結構、變數類型、巨集、函數等 //ip packet structure #include <netinet/ip.h> //icmp packet structure #include <netinet/ip_icmp.h> //time to live int ttl = 64; //icmp data size ,icmp header 8bytes,data size 56bytes,the maximum of packet size 64bytes int dataSize = 56; //packet number int sendPacketNumber; int recvPacketNumber; //ip address char * ipAddr; //send packet of time struct timeval *tvSend; //receive packet of time struct timeval tvRecv; //Socket address, internet style. //the destination address struct sockaddr_in dstAddr; //the receive address struct sockaddr_in recvAddr; //send icmp buffer char sendBuffer[1024] = {0}; //receive icmp replay buffer char recvBuffer[1024] = {0}; //the current process of id int pid; //socket int socketfd = 0; void statistics(char* back); unsigned short checkSum(unsigned short *buffer, int size); double timeSubtract(struct timeval *recvTimeStamp, struct timeval *sendTimeStamp); int fillPacket(int packetSequence); int sendPacket(int packetSequence); void settingIP(); void getPid(); int createSocket(); void settingSocket(int timeout); void destorySocket(); void unPacket(char* packetBuffer,char* back, long size); void receivePacket(char* back); void ping(char *ipAddress, int number, int timeout); #endif /* xSocketPing_h */
//xSocketPing.swift import Foundation public class xSocketPing{ private var ipAddress:String //default packet number 3 or you can setting it by yourself private var packetNumber:Int = 3 private var timeout:Int = 1 //refresh UI weak var delegate:refreshTextDelegate? init(ipAddress:String, delegate:refreshTextDelegate){ self.ipAddress = ipAddress self.delegate = delegate } convenience init(ipAddress:String, packetNumber:Int, delegate:refreshTextDelegate){ self.init(ipAddress: ipAddress,delegate: delegate) self.packetNumber = packetNumber } convenience init(ipAddress:String, packetNumber:Int, timeout:Int, delegate:refreshTextDelegate){ self.init(ipAddress: ipAddress,packetNumber: packetNumber,delegate: delegate) self.timeout = timeout } public func xPing(){ let tempIpAddress:UnsafeMutablePointer = UnsafeMutablePointer<Int8>((ipAddress as NSString).UTF8String) ipAddr = tempIpAddress sendPacketNumber = Int32(packetNumber) recvPacketNumber = Int32(packetNumber) settingIP() getPid() if createSocket() != -1 { settingSocket(Int32(timeout)) let message:String = "PING \(ipAddress): 56 bytes of data.\n" refresh(message, speed: 0.0) //將String轉換為UnsafeMutablePointer<CChar>,相當於char tempmessage[100] let tempmessage:UnsafeMutablePointer = UnsafeMutablePointer<CChar>.alloc(100) var packetsequence:Int = 0 var speed:Float = 0.0 while packetsequence < packetNumber { if sendPacket(Int32(packetsequence)) != -1 { receivePacket(tempmessage) //Calculate percentage speed = (Float(packetNumber) - (Float(packetNumber) - Float(packetsequence))) / Float(packetNumber) //將UnsafeMutablePointer<CChar>轉換為String refresh(String.fromCString(tempmessage)!, speed: speed) } sleep(1); packetsequence++ } statistics(tempmessage) refresh(String.fromCString(tempmessage)!, speed: 1) destorySocket() } } func refresh(text:String, speed:Float){ delegate?.refresh(text, speed: speed) } deinit{ print("xSocketPing destory") } }
//ViewController.swift import UIKit protocol refreshTextDelegate:NSObjectProtocol{ func refresh(text:String, speed:Float) } class ViewController: UIViewController,refreshTextDelegate { @IBOutlet weak var xprogress: UIProgressView! @IBOutlet weak var xip: UITextField! @IBOutlet weak var xnumber: UITextField! @IBOutlet weak var xtext: UITextView! var number:Int = 3 var ip:String = "192.168.1.1" override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. xtext.text = "" } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } @IBAction func xNumberChange(sender: UIStepper) { xnumber.text = "\(Int(sender.value))" self.number = Int(sender.value) } @IBAction func beginPing(sender: UIButton) { sender.enabled = false xip.enabled = false xnumber.enabled = false xprogress.progress = 0 xtext.text = "" let ip = xip.text if ip == ""{ let alert = UIAlertView(title: "Error", message: "No IP Address", delegate: self, cancelButtonTitle: "OK") alert.show() }else{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { let ping:xSocketPing = xSocketPing(ipAddress: "\(ip!)", packetNumber: self.number, delegate: self) ping.xPing() }) } xnumber.enabled = true xip.enabled = true sender.enabled = true } func refresh(text:String, speed:Float) { dispatch_async(dispatch_get_main_queue(), { //更新進度條 self.xprogress.progress = speed //更新UITextView,追加內容並滾動到最下麵 self.xtext.text.appendContentsOf(text) self.xtext.scrollRangeToVisible(NSMakeRange(self.xtext.text.characters.count, 0)) }) } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { self.view.endEditing(true) } }
運行環境OS
X10.10.5,Xcode7.0,Swift2.1,iOS9.0模擬器
真機測試的時候有可能找不到#include
,猜測是蘋果不給在機子上使用icmp數據包,如果只是在自己機子上測試,可以將模擬器的ip_icmp.h文件複製到真機環境,再進行編譯運行.
部分出現的疑惑或者問題,可以看我之前的新浪博客
Wireshark找不到網卡
Swift和C語言交互-String和UnsafeMutablePointer轉換
Swift和c的類型轉換
Swift與c的交互
GitHub項目地址:Swift和C混合Socket編程實現簡單的ping命令
本程式還有很多不足之處,後續會持續更新,並更新GitHub項目,需要改進的,請在博客上留言
抓包的圖片是不更新的,所以會和運行結果圖不一樣.
更新於2016-1-13
修複bug,優化部分顯示,更好,更安全的處理指針類型.
更新於2016-1-12
新增功能:優化顯示,將返回的結果顯示到模擬器中.發送數據包在額外的線程中進行,不會阻塞主線程.由於程式使用的是Swift和C語言混合編程,用到了指針,交互的時候容易crash.後續會繼續修複bug.
更新2016-1-7
新增功能:優化顯示,將返回結果集中到主函數中,方便調用,修複部分bug,添加超時處理,防止程式一直處於阻塞狀態.,