Ping 使用 Internet
控制消息協(xié)議(ICMP
)來測試主機(jī)之間的連接。當(dāng)用戶發(fā)送一個(gè) ping
請求時(shí),則對應(yīng)的發(fā)送一個(gè) ICMP Echo
請求消息到目標(biāo)主機(jī),并等待目標(biāo)主機(jī)回復(fù)一個(gè) ICMP Echo
回應(yīng)消息。如果目標(biāo)主機(jī)接收到請求并且網(wǎng)絡(luò)連接正常,則會返回一個(gè)回應(yīng)消息,表示主機(jī)之間的網(wǎng)絡(luò)連接是正常的。如果目標(biāo)主機(jī)沒有收到請求消息或網(wǎng)絡(luò)連接不正常,則不會有回應(yīng)消息返回。
Ping 工作的步驟如下:
- Ping發(fā)送一個(gè)
ICMP Echo
請求消息到目標(biāo)主機(jī)。 - 目標(biāo)主機(jī)接收到請求消息后,檢查消息中的目標(biāo)
IP
地址是否正確,并回復(fù)一個(gè)ICMP Echo
回應(yīng)消息表示收到請求。 - Ping接收到回應(yīng)消息后,并計(jì)算從發(fā)送到接收的時(shí)延(即往返時(shí)間 RTT)和丟包率等統(tǒng)計(jì)信息,然后輸出到命令行上。
- Ping不斷進(jìn)行第1到第3步的操作,直到達(dá)到指定的停止條件(如發(fā)送一定數(shù)量的請求或持續(xù)一定的時(shí)間等)為止。
Ping的實(shí)現(xiàn)依賴于ICMP
協(xié)議,Internet控制消息協(xié)議(Internet Control Message Protocol,簡稱 ICMP)是一種在IP
網(wǎng)絡(luò)上發(fā)送控制消息的協(xié)議。主要是用于在 IP
網(wǎng)絡(luò)上進(jìn)行錯(cuò)誤處理和診斷。ICMP協(xié)議是運(yùn)行在網(wǎng)絡(luò)層的協(xié)議,它的主要作用是向源主機(jī)和目標(biāo)主機(jī)發(fā)送控制消息,幫助網(wǎng)絡(luò)診斷和監(jiān)控。這些控制消息通常是由網(wǎng)絡(luò)設(shè)備(如路由器、交換機(jī)、防火墻等)生成或捕獲,并在整個(gè)網(wǎng)絡(luò)傳輸。
ICMP協(xié)議的消息格式通常由兩個(gè)部分組成:消息頭和數(shù)據(jù)。其中,消息頭包含以下字段:
- 消息類型(Type):指示消息的類型(如 Echo 請求、Echo 回應(yīng)、目標(biāo)不可達(dá)、重定向等)
- 代碼(Code):指示消息的子類型或錯(cuò)誤代碼
- 校驗(yàn)和(Checksum):用于檢查消息是否被篡改
- 消息體(Payload):包含特定類型消息所需的數(shù)據(jù),如 IP 數(shù)據(jù)報(bào)片段、Echo 請求消息等
ICMP 協(xié)議中常見的消息類型包括:
- Echo 請求(Ping)和 Echo 回應(yīng):用于測試主機(jī)之間的連通性和計(jì)算往返時(shí)間(RTT)
- 目標(biāo)不可達(dá):通知源主機(jī)無法到達(dá)某個(gè)目標(biāo)主機(jī)或網(wǎng)絡(luò)
- 重定向:用于通知主機(jī)更改路由器或網(wǎng)關(guān)
- 時(shí)間超時(shí):通知主機(jī)數(shù)據(jù)包已超過了最大存活期
- 地址掩碼請求和地址掩碼回應(yīng):用于向主機(jī)查詢和設(shè)置子網(wǎng)掩碼
在Windows
平臺下要實(shí)現(xiàn)Ping
命令有多種方法,首先我們先來講解第一種實(shí)現(xiàn)方式,通過自己構(gòu)造ICMP
數(shù)據(jù)包并發(fā)包實(shí)現(xiàn),首先該功能的實(shí)現(xiàn)需要定義一個(gè)icmp_header
頭部,并定義好所需要的發(fā)送與回應(yīng)定義,如下所示;
// ICMP頭部定義部分
struct icmp_header
{
unsigned char icmp_type; // 消息類型
unsigned char icmp_code; // 代碼
unsigned short icmp_checksum; // 校驗(yàn)和
unsigned short icmp_id; // ICMP唯一ID
unsigned short icmp_sequence; // 序列號
unsigned long icmp_timestamp; // 時(shí)間戳
};
// 計(jì)算出ICMP頭部長度
#define ICMP_HEADER_SIZE sizeof(icmp_header)
// ICMP回送請求消息代碼
#define ICMP_ECHO_REQUEST 0x08
// ICMP回送響應(yīng)消息代碼
#define ICMP_ECHO_REPLY 0x00
當(dāng)有了結(jié)構(gòu)體定義那么接著就需要實(shí)現(xiàn)一個(gè)ICMP
校驗(yàn)和的計(jì)算方法,ICMP
報(bào)文檢驗(yàn)和是一種用于檢測 ICMP
報(bào)文數(shù)據(jù)正確性的校驗(yàn)和。它是 ICMP
協(xié)議中一種重要的錯(cuò)誤檢測機(jī)制,用于驗(yàn)證發(fā)送和接收的 ICMP
報(bào)文的數(shù)據(jù)是否完整、正確。
校驗(yàn)和計(jì)算方法如下:
- 將要計(jì)算校驗(yàn)和的數(shù)據(jù)(即 ICMP 報(bào)文)按照16位為一組進(jìn)行分組
- 把所有的 16 位數(shù)字相加并加上進(jìn)位,得到一個(gè)數(shù)
- 若上一步和的高位不為零,則把進(jìn)位加到低位上,重復(fù)步驟 2
- 對累加后的結(jié)果進(jìn)行二進(jìn)制反轉(zhuǎn)
- 得到校驗(yàn)和值,將其放置于 ICMP 報(bào)文的校驗(yàn)和字段中
當(dāng) ICMP
接收到 ICMP
報(bào)文時(shí),將立即計(jì)算校驗(yàn)和,比對接收到的校驗(yàn)和值與計(jì)算所得的校驗(yàn)和值是否相同,從而決定 ICMP
報(bào)文是否正確接收及響應(yīng)。這樣做的好處是可以有效地檢測數(shù)據(jù)在傳輸過程中的誤碼、中間路由設(shè)備的錯(cuò)誤操作等問題,保障 ICMP
報(bào)文的正確性。
根據(jù)上述描述,計(jì)算校驗(yàn)和CheckSum
函數(shù),首先對報(bào)文的數(shù)據(jù)進(jìn)行分組,并依次計(jì)算每個(gè)16
位數(shù)字的和。當(dāng)相加的結(jié)果有進(jìn)位時(shí),將進(jìn)位加到低位上,并將進(jìn)位部分加到下一組中。處理完所有數(shù)字之后,還需要對結(jié)果進(jìn)行二進(jìn)制反轉(zhuǎn),得到最終的校驗(yàn)和值。
// 計(jì)算校驗(yàn)和
unsigned short CheckSum(struct icmp_header *picmp, int len)
{
long sum = 0;
unsigned short *pusicmp = (unsigned short *)picmp;
// 將數(shù)據(jù)按16位分組,相鄰的兩個(gè)16位取出并相加,直到處理完所有數(shù)據(jù)
while (len > 1)
{
sum += *(pusicmp++);
// 如果相加的結(jié)果有進(jìn)位,則將進(jìn)位加到低16位上
if (sum & 0x80000000)
{
sum = (sum & 0xffff) + (sum >> 16);
}
// 減去已經(jīng)處理完的字節(jié)數(shù)
len -= 2;
}
// 如果數(shù)據(jù)的字節(jié)數(shù)為奇數(shù),則將最后一個(gè)字節(jié)視為16位,高8位設(shè)為0,低8位取余部分。
if (len)
{
sum += (unsigned short)*(unsigned char *)pusicmp;
}
// 如果計(jì)算完校驗(yàn)和后還有進(jìn)位,則將進(jìn)位加到低16位上
while (sum >> 16)
{
sum = (sum & 0xffff) + (sum >> 16);
}
// 取反得到最終的校驗(yàn)和
return (unsigned short)~sum;
}
接著就是實(shí)現(xiàn)ICMP測試函數(shù),如下函數(shù)首先進(jìn)行初始化,并創(chuàng)建原始套接字,然后構(gòu)造 ICMP
報(bào)文,計(jì)算報(bào)文的校驗(yàn)和。接著發(fā)送 ICMP
報(bào)文,并接收 ICMP
回復(fù)報(bào)文,解析其中的信息,判斷延遲超時(shí),最后返回 ping
測試結(jié)果。
發(fā)送 ICMP
報(bào)文使用 sendto
函數(shù),第一個(gè)參數(shù)是原始套接字,第二個(gè)參數(shù)是 ICMP
報(bào)文數(shù)據(jù)緩存區(qū),第三個(gè)參數(shù)是緩存區(qū)的長度,第四個(gè)參數(shù)是標(biāo)志,第五個(gè)參數(shù)是目的地址信息。接收 ICMP
回復(fù)報(bào)文使用 recvfrom
函數(shù),第一個(gè)參數(shù)和第五個(gè)參數(shù)與 sendto
函數(shù)相同。函數(shù)返回時(shí),判斷接收到的 IP
地址是否與發(fā)送 ICMP
報(bào)文的 IP
地址相同,如果相同,解析 ICMP
回復(fù)報(bào)文中的信息并返回 true
,否則返回 false
。
在 ICMP
報(bào)文構(gòu)造中,使用了 Winsock
函數(shù)庫中的 inet_addr
將 IP
地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。在計(jì)算 ICMP
報(bào)文的校驗(yàn)和時(shí),調(diào)用了 CheckSum
函數(shù)。
BOOL MyPing(char *szDestIp)
{
BOOL bRet = TRUE;
WSADATA wsaData;
int nTimeOut = 1000;
char szBuff[ICMP_HEADER_SIZE + 32] = { 0 };
icmp_header *pIcmp = (icmp_header *)szBuff;
char icmp_data[32] = { 0 };
// 初始化Winsock動態(tài)鏈接庫
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 創(chuàng)建原始套接字
SOCKET s = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
// 設(shè)置接收超時(shí)
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char const*)&nTimeOut, sizeof(nTimeOut));
// 設(shè)置目的地址
sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.S_un.S_addr = inet_addr(szDestIp);
dest_addr.sin_port = htons(0);
// 構(gòu)造ICMP封包
pIcmp->icmp_type = ICMP_ECHO_REQUEST;
pIcmp->icmp_code = 0;
pIcmp->icmp_id = (USHORT)::GetCurrentProcessId();
pIcmp->icmp_sequence = 0;
pIcmp->icmp_timestamp = 0;
pIcmp->icmp_checksum = 0;
// 拷貝ICMP協(xié)議中附帶的數(shù)據(jù)
memcpy((szBuff + ICMP_HEADER_SIZE), "abcdefghijklmnopqrstuvwabcdefghi", 32);
// 計(jì)算校驗(yàn)和
pIcmp->icmp_checksum = CheckSum((struct icmp_header *)szBuff, sizeof(szBuff));
// 接收ping返回的ICMP數(shù)據(jù)包
sockaddr_in from_addr;
char szRecvBuff[1024];
int nLen = sizeof(from_addr);
// 發(fā)送UDP數(shù)據(jù)包
sendto(s, szBuff, sizeof(szBuff), 0, (SOCKADDR *)&dest_addr, sizeof(SOCKADDR));
// 等待響應(yīng)
recvfrom(s, szRecvBuff, MAXBYTE, 0, (SOCKADDR *)&from_addr, &nLen);
// 判斷接收到的是否是自己請求的地址
if (lstrcmp(inet_ntoa(from_addr.sin_addr), szDestIp))
{
bRet = FALSE;
}
else
{
// 如果是自己請求的地址,則解析 ICMP 回復(fù)報(bào)文中的信息
struct icmp_header *pIcmp1 = (icmp_header *)(szRecvBuff + 20);
printf("%s \r\n", inet_ntoa(from_addr.sin_addr));
}
return bRet;
}
當(dāng)讀者有了上述函數(shù)封裝那么實(shí)現(xiàn)Ping測試將變得很容易,首先如下調(diào)用實(shí)例中,通過GetHostByName
函數(shù)獲取到對應(yīng)域名的IP地址信息返回字符串,并將該字符串傳入MyPing
函數(shù)內(nèi),該函數(shù)會測試當(dāng)前主機(jī)是否可通信,如果可以返回狀態(tài)值1,否則返回0。
int main(int argc, char **argv)
{
// 獲得指定網(wǎng)址的IP地址
char * ptr = GetHostByName("www.lyshark.com");
// 開始測試
for (size_t i = 0; i < 5; i++)
{
int ret = MyPing(ptr);
printf("測試結(jié)果 = %d \n", ret);
}
system("pause");
return 0;
}
運(yùn)行代碼后讀者可看到如下圖所示的提示信息;
除了通過自己封裝接口外,Windows系統(tǒng)中還為我們提供了一個(gè)專用函數(shù)IcmpSendEcho
,該函數(shù)用于通過 ICMP
協(xié)議向遠(yuǎn)程主機(jī)發(fā)送 Echo
請求并接收 Echo
回復(fù)。如果發(fā)送 Echo
請求并成功接收 Echo
回復(fù),則函數(shù)返回值為非零,否則為零。
該函數(shù)的聲明如下:
BOOL IcmpSendEcho
(
HANDLE IcmpHandle,
IPAddr DestinationAddress,
LPVOID RequestData,
WORD RequestSize,
PIP_OPTION_INFORMATION RequestOptions,
LPVOID ReplyBuffer,
DWORD ReplySize,
DWORD Timeout
);
函數(shù)參數(shù):
- IcmpHandle:一個(gè)有效的 ICMP 句柄
- DestinationAddress:目標(biāo)地址,可以是 IP 地址(IPAddr)或主機(jī)名(LPCSTR)
- RequestData:指向要發(fā)送的數(shù)據(jù)的指針
- RequestSize:要發(fā)送的數(shù)據(jù)的大?。ㄒ宰止?jié)為單位)
- RequestOptions:指向 IP 選項(xiàng)的信息(IP_OPTION_INFORMATION)
- ReplyBuffer:指向緩沖區(qū),該緩沖區(qū)將用于存儲接收到的回復(fù)
- ReplySize:存儲在回復(fù)緩沖區(qū)中的數(shù)據(jù)的大?。ㄒ宰止?jié)為單位)
- Timeout:請求超時(shí)之前等待回復(fù)的時(shí)間(以毫秒為單位)
如下函數(shù)則是通過IcmpCreateFile
和IcmpSendEcho
函數(shù)實(shí)現(xiàn)的Ping測試,函數(shù)首先將 IP
地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,創(chuàng)建 ICMP
句柄并初始化 IP
選項(xiàng)信息。然后,設(shè)置要發(fā)送的 ICMP
數(shù)據(jù)報(bào)文,和接收 ICMP
數(shù)據(jù)報(bào)文的大小和緩沖區(qū)。接著發(fā)送 ICMP
數(shù)據(jù)報(bào)文,等待接收回復(fù),并將回復(fù)解析為 ICMP_ECHO_REPLY
結(jié)構(gòu)體。最后,判斷回復(fù)的狀態(tài),如果不為 0
則返回失敗,否則輸出回復(fù)信息并返回成功。
// 調(diào)用API實(shí)現(xiàn)ping
bool IcmpPing(char *Address)
{
// 設(shè)置超時(shí)為1000ms
DWORD timeOut = 1000;
// IP地址轉(zhuǎn)為網(wǎng)絡(luò)字節(jié)序
ULONG hAddr = inet_addr(Address);
HANDLE handle = IcmpCreateFile();
IP_OPTION_INFORMATION ipoi;
memset(&ipoi, 0, sizeof(IP_OPTION_INFORMATION));
// Time-To-Live
ipoi.Ttl = 64;
// 設(shè)置發(fā)送數(shù)據(jù)包
unsigned char SendData[32] = { "send icmp pack" };
int repSize = sizeof(ICMP_ECHO_REPLY)+32;
// 設(shè)置接收數(shù)據(jù)包
unsigned char pReply[128];
ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)pReply;
// 發(fā)送ICMP數(shù)據(jù)報(bào)文
DWORD nPackets = IcmpSendEcho(handle, hAddr, SendData, sizeof(SendData), &ipoi, pReply, repSize, timeOut);
if (pEchoReply->Status != 0)
{
IcmpCloseHandle(handle);
return false;
}
in_addr inAddr;
inAddr.s_addr = pEchoReply->Address;
printf("回復(fù)地址: %13s 狀態(tài): %1d 初始TTL: %3d 回復(fù): TTL: %3d \n",
inet_ntoa(inAddr), pEchoReply->Status, ipoi.Ttl, pEchoReply->Options.Ttl);
return true;
}
該段代碼的調(diào)用與上述一致,讀者只需要傳入主機(jī)IP地址的字符串即可,具體調(diào)用實(shí)現(xiàn)如下所示;
int main(int argc, char *argv[])
{
// 解析域名
char * HostAddress = GetHostByName("www.lyshark.com");
printf("網(wǎng)站IP地址 = %s \n", HostAddress);
// 調(diào)用Ping
for (int x = 0; x < 3; x++)
{
IcmpPing(HostAddress);
Sleep(1000);
}
system("pause");
return 0;
}
運(yùn)行代碼后讀者可看到如下圖所示的提示信息;
通過使用Ping
命令我們還可以實(shí)現(xiàn)針對主機(jī)路由的追蹤功能,路由追蹤功能的原理是,它實(shí)際上是發(fā)送一系列ICMP
數(shù)據(jù)包,數(shù)據(jù)包每經(jīng)過一個(gè)路由節(jié)點(diǎn)則TTL值會減去1,假設(shè)TTL
值等于0時(shí)數(shù)據(jù)包還沒有到達(dá)目標(biāo)主機(jī),那么該路由則會回復(fù)給目標(biāo)主機(jī)一個(gè)數(shù)據(jù)包不可達(dá),由此我們就可以獲取到目標(biāo)主機(jī)的IP地址。
其跟蹤原理如下:
- 1.一開始發(fā)送一個(gè)
TTL
為1的包,這樣到達(dá)第一個(gè)路由器的時(shí)候就已經(jīng)超時(shí)了,第一個(gè)路由器就會返回一個(gè)ICMP
通知,該通知包含了對端的IP
地址,這樣就能夠記錄下所經(jīng)過的第一個(gè)路由器的IP。 - 2.然后將
TTL
加1,讓其能夠安全的通過第一個(gè)路由器,而第二個(gè)路由器的的處理過程會自動丟包,發(fā)通知說包超時(shí)了,這樣記錄下第二個(gè)路由器IP,由此能夠一直進(jìn)行下去,直到這個(gè)數(shù)據(jù)包到達(dá)目標(biāo)主機(jī),由此打印出全部經(jīng)過的路由器。
由上述流程并配合使用IcmpSendEcho
函數(shù)設(shè)置默認(rèn)最大跳數(shù)為64,通過不間斷的循環(huán)即可輸出本機(jī)數(shù)據(jù)包到達(dá)目標(biāo)之間的所有路由信息,代碼片段如下所示;
// 實(shí)現(xiàn)路由跟中
void Tracert(char *Address)
{
ULONG hAddr = inet_addr(Address);
HANDLE handle = IcmpCreateFile();
IP_OPTION_INFORMATION ipoi;
memset(&ipoi, 0, sizeof(IP_OPTION_INFORMATION));
unsigned char SendData[32] = { "send ttl pack" };
int repSize = sizeof(ICMP_ECHO_REPLY)+32;
unsigned char pReply[128];
ICMP_ECHO_REPLY* pEchoReply = (ICMP_ECHO_REPLY*)pReply;
for (int ttl = 1; ttl < 64; ttl++)
{
ipoi.Ttl = ttl;
DWORD nPackets = IcmpSendEcho(handle, hAddr, SendData, sizeof(SendData), &ipoi, pReply, repSize, 1000);
if (pEchoReply->Status != 0)
{
in_addr inAddr;
inAddr.s_addr = pEchoReply->Address;
printf("-> 第 %2d 跳 --> 地址: %15s -> TTL: %2d \n", ttl, inet_ntoa(inAddr), pEchoReply->Options.Ttl);
}
}
IcmpCloseHandle(handle);
}
上述代碼讀者可自行運(yùn)行并傳入Tracert(HostAddress)
被測試主機(jī)IP地址,即可輸出當(dāng)前經(jīng)過路由的完整信息,如果路由TTL
為0則可能是對端路由過濾掉了ICMP
請求,如下圖所示;文章來源:http://www.zghlxwxcb.cn/news/detail-710859.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-710859.html
到了這里,關(guān)于15.2 主機(jī)探測與路由追蹤的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!