IP協議
網絡地址和主機協議
位於網絡層的協議,主要目的是使得網絡能夠互相通信,該協議使用邏輯地址跨網絡通信,目前有兩個版本IPV4,IPV6。
在IPV4協議中IP地址是一個32位的數備,采用點分四組的表示法便於使用。每個IP地址包含兩個部分,網絡地址和主機地址。
網絡地址和主機地址的划分由子網掩碼來決定。網絡地址用來標示所連接到的局域網,主機地址則標示設備本身,子網掩碼與IP地址等長,被設為1的部分標示IP地址的對應部分為網絡地址,設為0則標示IP對應位為主機地址。
IP格式
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報文如所示,不現類型由報文中的類型字段和代碼字段來共同決定。
常見的類型和代碼有
-
ICMP回顯(echo)請求和輝縣應答報文格式
-
ICMP地址掩碼請求和應答
-
ICMP時間戳請求與應答
-
ICMP不可達報文、ICMP超時報文
-
ICMP重定向報文
-
ICMP路由器請求報文格式
-
ICMP路由器通告報文格式
-
ICMP源站抑制差錯報文格式
等等。。。。。
ICMP地址掩碼請求和應答
ICMP時間戳請求與應答
編程操作
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(選擇)。
實例代碼
將上面的代碼與思路整合就成了下面的實例代碼;
#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