【網絡編程2】網絡編程基礎-發送ICMP包(Ping程序)


IP協議

網絡地址和主機協議

位於網絡層的協議,主要目的是使得網絡能夠互相通信,該協議使用邏輯地址跨網絡通信,目前有兩個版本IPV4,IPV6。

在IPV4協議中IP地址是一個32位的數備,采用點分四組的表示法便於使用。每個IP地址包含兩個部分,網絡地址和主機地址。

網絡地址和主機地址的划分由子網掩碼來決定。網絡地址用來標示所連接到的局域網,主機地址則標示設備本身,子網掩碼與IP地址等長,被設為1的部分標示IP地址的對應部分為網絡地址,設為0則標示IP對應位為主機地址。

IP格式

image

0-3 版本:IP所使用的版本

4-7 首部長度:IP頭的長度(4字節的整數倍)

8-15 服務類型:路由器使用該字段做流量優先排序

16-18 | 19-31 總長度:IP長度+數據包長度

32 標示符:一個唯一標示,用來區分數據包或數據分片的順序

32 標記:標示數據包是否分片數據

32 分片偏移:分片數據時該字段有效,用於將數據包按順序組合

64 存活時間:定義數據包的生存周期,路由器中轉一次該值減一

64 協議:用來識別數據包上層協議類型

64 首部校驗和:錯誤檢測機制,確認內容是否損壞或被篡改

96 源IP地址:發送方主機的IP地址

128 目的IP地址:接收方主機的IP地址

160 選項:可選

160 or 192+ 數據:使用IP傳遞的實際數據

ICMP協議

IP協議並不是一個可靠的協議,它不保證數據被送達,那么保證數據送達的工作應該由其他的模塊來完成。其中一個重要的模塊就是ICMP(網絡控制報文)協議。

ICMP(internet control message protocol)是internet控制報文協議。是TCP/IP協議族里的一個子協議,用於在IP主機、路由器之間傳遞控制信息。

控制信息是指網絡通不通、主機是否可達、路由是否可用等網絡本身的消息。

ICMP協議大致分為兩類,一種是查詢報文,一種是差錯報文。查詢報文由發送者發出,差錯報文是由出錯的主機返回發給源數據包的發送者。

PING可以說是ICMP的最著名的應用,系統自帶工具當某一個網站上不去的時候。通常會ping一下這個網站,ping會回顯一些有用的信息。

ICMP協議的格式

ICMP頭相對較小並根據用途而改變,ICMP報文的前4個字節是統一的格式,共有三個字段:即類型,代碼和檢驗和。

ICMP報文類型
各種類型的ICMP報文如所示,不現類型由報文中的類型字段和代碼字段來共同決定。

image

常見的類型和代碼有

  • ICMP回顯(echo)請求和輝縣應答報文格式

  • ICMP地址掩碼請求和應答

  • ICMP時間戳請求與應答

  • ICMP不可達報文、ICMP超時報文

  • ICMP重定向報文

  • ICMP路由器請求報文格式

  • ICMP路由器通告報文格式

  • ICMP源站抑制差錯報文格式

等等。。。。。

ICMP地址掩碼請求和應答

image

ICMP時間戳請求與應答

image

編程操作

1、定義結構體

因為ICMP是基於IP協議運行的,所以我們要先定義IP格式的結構體;
以下的是三個結構體分別對應了IP、ICMP請求、ICMP應答的發包格式。。

定義IP首部格式

//定義IP首部格式
typedef struct  _IPHeader
{
	u_char vile;           // 版本和首部長度
	u_char ser;            // 服務類型
	u_short totalLen;      // 總長度
	u_short id;            // 標示符
	u_short flag;          // 標記+分片偏移
	u_char ttl;            // 存活時間
	u_char protocol;       // 協議
	u_short checkSum;      // 首部校驗和
	in_addr srcIP;         // 源IP地址
	in_addr destIP;        // 目的IP地址
}IPHeader, *PIPHDR;

定義ICMP頭部格式

//ICMP頭部
typedef struct _ICMPHeader
{
	u_char type;            // 類型
	u_char code;            // 代碼
	u_short checkSum;       // 校驗和
	u_short id;             // 標示符
	u_short seq;            // 序列號
}ICMPHeader, *PICMPHDR;

ICMP時間戳請求報文格式

//ICMP時間戳請求報文
typedef struct _ECHOREQUEST
{
	ICMPHeader icmpHeader;       //ICMP頭部
	int time;                    //記錄ping時間  
	char data[32];               //數據
}ECHOREQUEST, *pechorequest;

ICMP時間戳應答報文格式

//ICMP時間戳應答報文
typedef struct _ECHORESPONSE
{
	IPHeader ipHeader;
	ECHOREQUEST echoRequest;
	char fill[255];            //接收其他多余的應答數據

}ECHORESPONSE, *PECHORESPONSE;

2、ICMP發送和接收

發送ICMP數據包函數

將請求的包的寬度寫入結構體中,構造出ICMP的請求格式向目標服務器發送數據。

//發送ICMP數據包 sendEchoReQuest
void sendEchoReQuest(SOCKET sock, sockaddr_in dstIP)
{
	static int id = 1;
	static int seq = 1;
	
	//ICMP請求
	ECHOREQUEST echoRequest = { 0 };
	//主要是用來記錄請求應答的時間,當發送ECHO請求時記錄發送時間,當接受到應答數據時,在用GetTickcount()- echoRequest.time這樣就能得到請求應答需要多少時間了。
	echoRequest.time = GetTickCount();   
	echoRequest.icmpHeader.type = 8;
	echoRequest.icmpHeader.code = 0;
	echoRequest.icmpHeader.id = id++;
	echoRequest.icmpHeader.seq = seq++;
	echoRequest.icmpHeader.checkSum =
		checksum((u_short*)&echoRequest, sizeof(echoRequest));

	int re = sendto(sock,
		(char *)&echoRequest,
		sizeof(echoRequest), 
		0, 
		(sockaddr*)&dstIP, 
		sizeof(dstIP));

	if (re == SOCKET_ERROR)
	{
		printf(" send error \n ");
	}
	return;
}

接收ICMP數據包函數

有對應的發送數據包動作,對方服務器如果存活那么會發回來響應包;

//接收ICMP數據包 recvEchoReQuest
void recvEchoReQuest(SOCKET sock,
	ECHORESPONSE * sponse, sockaddr_in *dstIP) 
{
	int size = sizeof(sockaddr);
	int re = recvfrom(sock, (char *)sponse, sizeof(ECHORESPONSE), 0,
		(sockaddr*)dstIP, &size);
	if (re == SOCKET_ERROR)
	{
		printf(" recvfrom error");
	}
	return;
}

附錄A:如何計算檢驗和

// 計算校驗和(16位二進制反碼求和)
u_short checksum(u_short *buffer, int len)
{
	register int nleft = len;
	register u_short *w = buffer;
	register u_short answer;
	register int sum = 0;
	// 使用32bit的累加器,進行16bit的反饋計算
	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}
	// 補全奇數位
	if (nleft == 1) {
		u_short	u = 0;

		*(u_char *)(&u) = *(u_char *)w;
		sum += u;
	}
	// 將反饋的16bit從高位移至地位
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);					/* add carry */
	answer = ~sum;						/* truncate to 16 bits */
	return (answer);
}

ICMP中檢驗和的計算算法為:

1、將檢驗和字段置為0

2、把需校驗的數據看成以16位為單位的數字組成,依次進行二進制反碼求和

3、把得到的結果存入檢驗和字段中

所謂二進制反碼求和,就是:

1、將源數據轉成反碼

2、0+0=0 0+1=1 1+1=0進1

3、若最高位相加后產生進位,則最后得到的結果要加1

在實際實現的過程中,比較常見的代碼寫法是:

1、將檢驗和字段置為0

2、把需校驗的數據看成以16位為單位的數字組成,依次進行求和,並存到32位的整型中

3、把求和結果中的高16位(進位)加到低16位上,如果還有進位,重復第3步[實際上,這一步最多會執行2次]

4、將這個32位的整型按位取反,並強制轉換為16位整型(截斷)后返回

附錄B VS2015 UAC設置

使用原始套接字編程調試的時候運行權限不夠,是無法運行的。

可以在VS里做設置,在需要提升權限的時候自動重啟VS提升權限。

點擊項目右鍵->屬性->配置屬性->鏈接器->清單文件->UAC執行級別->requireAdministrator(選擇)。

image

實例代碼

將上面的代碼與思路整合就成了下面的實例代碼;

#include "stdafx.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include "data.h"


//發送ICMP數據包 sendEchoReQuest
void sendEchoReQuest(SOCKET sock, sockaddr_in dstIP)
{
	static int id = 1;
	static int seq = 1;
	
	//ICMP請求
	ECHOREQUEST echoRequest = { 0 };
	//主要是用來記錄請求應答的時間,當發送ECHO請求時記錄發送時間,當接受到應答數據時,在用GetTickcount()- echoRequest.time這樣就能得到請求應答需要多少時間了。
	echoRequest.time = GetTickCount();   
	echoRequest.icmpHeader.type = 8;
	echoRequest.icmpHeader.code = 0;
	echoRequest.icmpHeader.id = id++;
	echoRequest.icmpHeader.seq = seq++;
	echoRequest.icmpHeader.checkSum =
		checksum((u_short*)&echoRequest, sizeof(echoRequest));

	int re = sendto(sock,
		(char *)&echoRequest,
		sizeof(echoRequest), 
		0, 
		(sockaddr*)&dstIP, 
		sizeof(dstIP));

	if (re == SOCKET_ERROR)
	{
		printf(" send error \n ");
	}
	return;
}


//接收ICMP數據包 recvEchoReQuest
void recvEchoReQuest(SOCKET sock,
	ECHORESPONSE * sponse, sockaddr_in *dstIP) 
{
	int size = sizeof(sockaddr);
	int re = recvfrom(sock, (char *)sponse, sizeof(ECHORESPONSE), 0,
		(sockaddr*)dstIP, &size);
	if (re == SOCKET_ERROR)
	{
		printf(" recvfrom error");
	}
	return;
}


//ping函數
void Ping(char * host) 
{
	//2.創建套接字
	SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);


	//獲取域名對應的IP
	HOSTENT * lpHost = gethostbyname(host);

	sockaddr_in dstIP = { 0 };
	dstIP.sin_family = AF_INET;
	dstIP.sin_addr.S_un.S_addr = *(u_long*)lpHost->h_addr;

	for (int i=0;i<4;i++)
	{
		//發送ICMP數據
		sendEchoReQuest(sock, dstIP);

		//接收ICMP數據
		ECHORESPONSE sponse;
		recvEchoReQuest(sock, &sponse, &dstIP);

		printf("來自 %s 的回復: 字節=32 時間=%dms TTL=%d \n",
			inet_ntoa(dstIP.sin_addr),
			GetTickCount() - sponse.echoRequest.time,
			sponse.ipHeader.ttl
		);
	}
}

int main()
{
	//1.初始化winsock環境
	WSADATA wsa;
	WSAStartup(MAKEWORD(2, 2),&wsa);

	while (true)
	{
		Ping("www.baidu.com");
		char in = getchar();
		if (in == 'q')
		{
			break;
		}

	}
	//2.釋放環境
	WSACleanup();

    return 0;
}

參考:
C++實現Ping
http://www.cnblogs.com/goagent/p/4078940.html
給VS程序添加管理員權限等
http://www.voidcn.com/blog/wokaowokaowokao12345/article/p-5973906.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM