原理簡介:
--------
通過前四節(jié)的介紹, 可能大家對ICMP的應(yīng)用有了初步的了解. 不過開始本節(jié)之前我對ICMP協(xié)議再從宏觀上做些介紹. 大家都知道ICMP是為于ISO的第三層---網(wǎng)絡(luò)層。 既是它同ip協(xié)議為于同一層, 然而大家可能也只到,ICMP協(xié)議要用到IP協(xié)議, 所以有一些書上說ICMP位ISO的第四層, 那是錯誤的。 同樣這樣那些書上這樣畫的的例子也是錯誤的, 我就發(fā)現(xiàn)某外資通訊公司的資料上有這樣兩種錯誤的畫法
--------------------------
ICMP TCP(SCTP)
--------------------------
IP
--------------------------
---------------------------
... TCP(SCTP)
---------------------------
ICMP IP
----------------------------
其實(shí)如上的畫法是錯誤的, 正確地畫法應(yīng)為:
---------------------
... TCP(SCTP)
---------------------
ICMP
----------
IP
---------------------
接下來,讓我們來說明怎樣實(shí)現(xiàn)追蹤路由的功能, 大家通過我的第一節(jié)的閱讀可能已經(jīng)了解了超時報文的具體內(nèi)容(參見透析ICMP協(xié)議(一): 協(xié)議原理), 它在假如網(wǎng)關(guān)在處理數(shù)據(jù)報時發(fā)現(xiàn)生存周期域(ttl)為零,此數(shù)據(jù)報必須拋棄。網(wǎng)關(guān)同時必須通過超時信息通知源主機(jī)。這是它的報文的具體結(jié)構(gòu):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Type(11) Code(0/1) Checksum
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
unused
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Internet Header + 64 bits of Original Data Datagram
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
通過利用setsockopt()函數(shù)設(shè)置ICMP包的IP包頭中的ttl字段便可以達(dá)到這種效果。 具體過程如下, 假設(shè)你的IP到達(dá)目標(biāo)地址需要過n個路由器(n>1)。 則
1. 初始化第一個ICMP包,并設(shè)置IP包頭中的TTL為1, 則得到第一個數(shù)據(jù)路由器發(fā)回的超時報文
2. 一般情況下:初始化第i(i<n)個ICMP包,并設(shè)置IP包頭中的TTL為i, 則得到第一個數(shù)據(jù)路由器發(fā)回的超時報文
剩下的問題為如何確定超時ICMP報文的路由器IP地址得到它的機(jī)器名的信息。 這個問題可能很多讀者都會求, 用gethostbyaddr()可以得到答案。
經(jīng)過理論的論證后, 讓我們看看如何實(shí)現(xiàn)。
具體實(shí)現(xiàn):(具體如何初試化ICMP的數(shù)據(jù)包上節(jié)已有具體的介紹,這里只是補(bǔ)充路由追蹤的代碼)
--------
主要代碼如下:
unsigned long ipback = 0; //超時報文的IP的初試值
unsigned long ms = 0; //超時值
strUCt hostent *hHost;
char m_address[256];
//直到找到目標(biāo)主機(jī), 或達(dá)到最大跳數(shù)(HOPS)
while (ipback != ipfinal){
hHost = 0;
//對到目標(biāo)主機(jī)中間的某個路由器發(fā)放ping的報文(ttl為1~N-1之間)
if (Ping(m_address,ttl,ipback,ms))
{
sin.sin_family = AF_INET;
sin.sin_addr.S_un.S_addr = ipback; // 由函數(shù)返回的IP地址
// 查找主機(jī)名
hHost = gethostbyaddr((char*)&sin.sin_addr, 4, PF_INET);
//這里可以輸出hHost的內(nèi)容
}
ttl++;
if (ttl > MAX_HOPS) //達(dá)到最大跳數(shù)
{
break;
}
}
==================
ping函數(shù)的代碼
==================
int Ping(const char * host, int ttl, unsigned long& ipback, unsigned long& ms)
{
SOCKET sockRaw;
struct sockaddr_in dest,from;
struct hostent * hp;
int bread,datasize;
int fromlen = sizeof(from);
int timeout = 100;
char *dest_ip;
char *icmp_data;
char *recvbuf;
unsigned int addr=0;
const int MAX_PACKET = 1024;
//初始化Socket
sockRaw = WSASocket (AF_INET,
SOCK_RAW,
IPPROTO_ICMP,
NULL, 0, WSA_FLAG_OVERLAPPED);
if (sockRaw == INVALID_SOCKET)
{
// 錯誤
}
// 設(shè)置IP包頭的ttl字段
bread = setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(int));
if(bread == SOCKET_ERROR)
{
// 錯誤
}
// 設(shè)置接受超時為100ms
bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));
if(bread == SOCKET_ERROR)
{
// 錯誤
}
//禁止用Nagle算法緩存數(shù)據(jù)
bread = setsockopt(sockRaw, SOL_SOCKET, TCP_NODELAY, (const char*)&killnagle, sizeof(int));
if (bread == SOCKET_ERROR)
{
// 錯誤
}
timeout = 1000;
// 設(shè)置發(fā)送超時為100ms
bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,
sizeof(timeout));
if(bread == SOCKET_ERROR)
{
// 錯誤
}
//下面的代碼生成ICMP包
memset(&dest,0,sizeof(dest));
hp = gethostbyname(host);
if (!hp)
{
addr = inet_addr(host);
}
if ((!hp) && (addr == INADDR_NONE) )
{
// 錯誤
}
if (hp != NULL)
memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
else
dest.sin_addr.s_addr = addr;
//初始化dest
if (hp)
dest.sin_family = hp->h_addrtype;
else
dest.sin_family = AF_INET;
dest_ip = inet_ntoa(dest.sin_addr);
// 設(shè)置包長度
datasize = DEF_PACKET_SIZE;
// 計算包大小
datasize += sizeof(IcmpHeader);
icmp_data = (char *)new[MAX_PACKET]; //分配內(nèi)存,可以用new 和 delete
recvbuf = (char *)new[MAX_PACKET];
if (!icmp_data)
{
// 釋放內(nèi)存,退出
}
if (!recvbuf)
{
// 釋放內(nèi)存,退出 }
}
memset(icmp_data,0,MAX_PACKET);
fill_icmp_data(icmp_data,datasize); // 這個函數(shù)用來填充ICMP的數(shù)據(jù)包
int bwrote;
((IcmpHeader*)icmp_data)->i_cksum = 0;
((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); // 存入當(dāng)前時間值
((IcmpHeader*)icmp_data)->i_seq = seq_no++;
// 計算校驗(yàn)和
((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);
// 為了最后計算ICMP包回來的總時間
unsigned long tc = GetTickCount();
//發(fā)送數(shù)據(jù)包
bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest, sizeof(dest));
if (bwrote == SOCKET_ERROR)
{
// 錯誤
}
if (bwrote < datasize ) //發(fā)送字節(jié)數(shù)對否
{
}
// 接受數(shù)據(jù)包
bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,
&fromlen);
//計算總時間
ms = GetTickCount() - tc;
if (bread == SOCKET_ERROR)
{
// 錯誤
}
// 得到返回的路由器
ipback = from.sin_addr.s_addr;
return 1;
}
===============================
函數(shù)fill_icmp_data()的源代碼
===============================
//這個結(jié)構(gòu)下面將用到
typedef struct _ihdr {
BYTE i_type;
BYTE i_code;
USHORT i_cksum;
USHORT i_id;
USHORT i_seq;
ULONG timestamp; /* 這不是ICMP包的一部分, 只是為了計算時間 */
}IcmpHeader;
void fill_icmp_data(char * icmp_data, int datasize){
IcmpHeader *icmp_hdr;
char *datapart;
icmp_hdr = (IcmpHeader*)icmp_data;
icmp_hdr->i_type = ICMP_ECHO;
icmp_hdr->i_code = 0;
icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
icmp_hdr->i_cksum = 0;
icmp_hdr->i_seq = 0;
datapart = icmp_data + sizeof(IcmpHeader); //計算數(shù)據(jù)域的開始地址
// 初試化數(shù)據(jù)域
memset(datapart,'E', datasize - sizeof(IcmpHeader));
}
新聞熱點(diǎn)
疑難解答
圖片精選