對ICMP路由跟蹤的研究
在ICMP協(xié)議的使用中,我們來介紹一下路由跟蹤的應(yīng)用。那么,在掌握了ICMP協(xié)議的一些基本介紹后,我們來對具體的路由跟蹤的實(shí)現(xiàn)進(jìn)行一下分析和講解,希望對大家有所幫助。
一、概述
計(jì)算機(jī)在Internet中傳遞信息時(shí),必須要經(jīng)過路由器進(jìn)行網(wǎng)絡(luò)路由才能找到目的主機(jī),把信息送到目的主機(jī).路由器中都有一張路由表,表中保存了從本路由器到某一主機(jī)的路由信息,路由器就是通過該路由表進(jìn)行網(wǎng)絡(luò)尋徑的.兩臺(tái)主機(jī)之間并沒有一條固定的路徑(即路由表并不固定),該路徑隨著網(wǎng)絡(luò)的變動(dòng)而作相應(yīng)的變動(dòng),因而我們并不能直接從某一主機(jī)上得到去往另一主機(jī)的路徑,要得到本機(jī)與網(wǎng)絡(luò)上某臺(tái)主機(jī)的網(wǎng)絡(luò)路徑就必須要進(jìn)行路由跟蹤.本文將介紹一種實(shí)現(xiàn)路由跟蹤的方法.
二、ICMP簡介
ICMP即Internet控制報(bào)文協(xié)議是一種用于特殊用途的報(bào)文機(jī)制,可以使互聯(lián)網(wǎng)中的路由器或主機(jī)報(bào)告差錯(cuò)或提供有關(guān)意外情況的信息.
ICMP報(bào)文為兩級(jí)封裝,ICMP報(bào)文放在IP數(shù)據(jù)報(bào)的數(shù)據(jù)部分,IP數(shù)據(jù)報(bào)則放在幀的數(shù)據(jù)中進(jìn)行網(wǎng)絡(luò)傳輸(如下圖1所示).ICMP報(bào)文與其他普通報(bào)文一樣,具有相同的路由選擇,并沒有特殊的優(yōu)先權(quán)和增加可靠性.
(圖1)ICMP報(bào)文的封裝
在ICMP包頭中包含了三個(gè)字段:1字節(jié)類型域、1字節(jié)代碼域、2字節(jié)校驗(yàn)和.類型域表示了該報(bào)文的類型,如:回應(yīng)請求報(bào)文,數(shù)據(jù)報(bào)超時(shí)報(bào)文等,代碼域表示了該類型的幾種不同情況,如:當(dāng)類型為11(超時(shí)報(bào)文)時(shí),代碼為0表示TTL超時(shí),為1表示片重組超時(shí).在實(shí)現(xiàn)本文中所述的功能時(shí)要發(fā)送回應(yīng)請求報(bào)文(類型為8),過程如下:源主機(jī)向目的主機(jī)發(fā)送一個(gè)類型為8的回應(yīng)請求報(bào)文,若目的站點(diǎn)收到回應(yīng)請求報(bào)文則把報(bào)文IP包頭部中的目的IP與源IP地址交換,將類型8改為回應(yīng)類型0,計(jì)算出新的校驗(yàn)和再發(fā)往源主機(jī).若源主機(jī)收到了該回應(yīng)報(bào)文,則不但說明了目的主機(jī)可達(dá),而且說明目的主機(jī)與源主機(jī)之間的路由器工作正常,源主機(jī)和目的主機(jī)的IP、ICMP軟件運(yùn)行正常.但若在傳輸過程中了出現(xiàn)了某些問題,如網(wǎng)絡(luò)不通等,導(dǎo)致數(shù)據(jù)被定向到一個(gè)無效的目的地,這時(shí)相關(guān)路由器或目的主機(jī)將發(fā)回目的不可達(dá)報(bào)文(類型為3),并在代碼中說明該報(bào)文的具體情況:是網(wǎng)絡(luò)不可達(dá)還是主機(jī)不可達(dá)等.若請求報(bào)文在傳輸過程中超時(shí),即TTL被減為0(報(bào)文每經(jīng)過一個(gè)路由器TTL都要減1),則該路由器返回一個(gè)TTL超時(shí)報(bào)文(類型為11),報(bào)文IP頭中源IP地址即為本路由器的IP地址.
三、路由跟蹤的實(shí)現(xiàn)方法
路由跟蹤的實(shí)現(xiàn)就是巧妙地利用了ICMP報(bào)文的TTL超時(shí)報(bào)文.其實(shí)現(xiàn)過程如下:源主機(jī)先向目的主機(jī)發(fā)送一個(gè)回應(yīng)請求報(bào)文(類型8),TTL值設(shè)為1,第一個(gè)路由器收到后將TTL減1,這樣TTL變?yōu)?,分組被廢除,同時(shí)路由器向源主機(jī)發(fā)送一個(gè)TTL超時(shí)報(bào)文(類型為11),報(bào)文的IP包頭中的源IP地址就是第一個(gè)路由器的地址,源主機(jī)就可以通過對該報(bào)文進(jìn)行分析,得到第一個(gè)路由器的地址.接著發(fā)送TTL等于2的報(bào)文得到第二個(gè)路由器地址,再發(fā)TTL等于3的報(bào)文,如此下去直到收到目的主機(jī)的回應(yīng)應(yīng)答報(bào)文(類型為0)或目的不可達(dá)報(bào)文(類型為3),或者到了最大跳數(shù)(要檢測路由器個(gè)數(shù)的最大值).可以看到,對TTL的設(shè)置是實(shí)現(xiàn)跟蹤的關(guān)鍵,使用函數(shù)setsockopt(m_Sock, IPPROTO_IP, IP_TTL,(LPSTR)&TTL,sizeof(int)) 可以對其進(jìn)行設(shè)置,m_Sock是所創(chuàng)建的套接字,IP_TTL說明是進(jìn)行TTL設(shè)置,TTL即是要設(shè)置的TTL值,為一個(gè)整形數(shù)值 .其實(shí)現(xiàn)流程可用下圖2表示:
(圖2) 流程圖
四、路由跟蹤程序?qū)崿F(xiàn)
本文所介紹的程序是使用了Visual C++6.0編寫,其過程如下:
1、創(chuàng)建一個(gè)新的基于對話框的AppWizard工程,并命名為RouteTrace.
2、在stdafx.h中加入#include "winsock2.h".
3、打開選擇菜單Project->Setting (ALT+F7),進(jìn)入Project Setting 對話框,在Link下的 Object/library modules 輸入ws2_32.lib,然后點(diǎn)OK.
4、自定義一個(gè)ICMP類.點(diǎn)擊菜單中的Insert->New Class,進(jìn)入New Class對話框,在Class type中選擇Generic Class,在Name中輸入類名CICMP,然后點(diǎn)OK,這樣就新建了一個(gè)CICMP類.
5、將對話框設(shè)置成如圖3所示的樣子:
(圖3 程序界面)
啟動(dòng)Class Wizard 為各控件添加響應(yīng)函數(shù)和關(guān)聯(lián)變量,控件對應(yīng)的ID及響應(yīng)函數(shù)或變量為:
控件 | ID | 響應(yīng)函數(shù) | 變量 |
地址組合框 | IDC_COMBO | CComboBox m_comb | |
最大跳數(shù)編輯框 | IDC_MAXHOT | int m_maxhot | |
跟蹤按鈕 | IDC_TRACE | OnTrace() | |
停止按鈕 | IDC_STOP | OnStop() | |
列表框 | IDC_LIST | CListCtrl m_list |
#p#
五、路由跟蹤代碼
在完成了對各控件的設(shè)置和類的添加以后就是對代碼的編寫了,這里給出了新建類CICMP和RouteTraceDlg.cpp的代碼,詳細(xì)代碼請參看源程序。
ICMP.cpp文件代碼:
// ICMP.cpp: implementation of the CICMP class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "RouteTrace.h"
#include "ICMP.h"
#include "ws2tcpip.h" //實(shí)現(xiàn) IP_TTL 設(shè)置的關(guān)鍵
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CICMP::CICMP()
{
winsock = 0;
m_pIp = NULL;
m_pIcmp = NULL;
m_pIp = (IP_HEAD *)new BYTE[MAX_PACKET];
m_pIcmp = (ICMP_HEAD *)new BYTE[MAX_PACKET];
}
CICMP::~CICMP()
{
delete [] m_pIp;
delete [] m_pIcmp;
}
BOOL CICMP::Initialize()
{
WSADATA wsadata;
if( WSAStartup(MAKEWORD(2, 1),&wsadata) )
{
AfxMessageBox("WSAStartup初始化失敗!");
return FALSE;
}
winsock= WSASocket (AF_INET, //建立socket
SOCK_RAW,
IPPROTO_ICMP,
NULL, 0,0);
if(!winsock) {
AfxMessageBox( "Socket創(chuàng)建失敗!");
return FALSE;
}
int timeout =5000;
setsockopt(winsock,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout, // 設(shè)置接收超時(shí)
sizeof(timeout));
timeout = 5000;
setsockopt(winsock,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout, //設(shè)置發(fā)送超時(shí)
sizeof(timeout));
return TRUE;
}
void CICMP::Uninitialize() //釋放Socket
{
if(winsock)
closesocket(winsock);
WSACleanup();
}
USHORT CICMP::CheckSum(USHORT *buffer, int size) //計(jì)算校驗(yàn)和
{
unsigned long cksum = 0;
while(size > 1) {
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if(size ) {
cksum += *(UCHAR*)buffer;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >>16);
return (USHORT)(~cksum);
}
//--------------------發(fā)送ICMP回應(yīng)請求報(bào)文-------------------
BOOL CICMP::SendICMPPack(char *pAddr)
{
sockaddr_in sockAddr;
memset((void *)&sockAddr,0,sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = 0;
sockAddr.sin_addr.S_un.S_addr=inet_addr(pAddr);
return SendICMPPack(&sockAddr);
}
BOOL CICMP::SendICMPPack(sockaddr_in *pAddr)
{
//填充ICMP數(shù)據(jù)各項(xiàng)
int state;
char *p_data;
m_pIcmp->type = ICMP_ECHO;
m_pIcmp->code = 0;
m_pIcmp->ID = (USHORT)GetCurrentProcessId();
m_pIcmp->number = 0;
m_pIcmp->time = GetTickCount();
m_pIcmp->cksum = 0;
//填充數(shù)據(jù)
p_data = ((char *)m_pIcmp + sizeof(ICMP_HEAD));
memset((char *)p_data,''0'',DEF_PACKET);
//校驗(yàn)和
m_pIcmp->cksum = CheckSum((USHORT *)m_pIcmp,
DEF_PACKET+sizeof(ICMP_HEAD));
//發(fā)送數(shù)據(jù)
state = sendto(winsock,(char *)m_pIcmp,
DEF_PACKET+sizeof(ICMP_HEAD),
NULL,(struct sockaddr *)pAddr,sizeof(sockaddr));
if(state == SOCKET_ERROR) {
if(GetLastError()==WSAETIMEDOUT)
m_strInfo = "連接超時(shí)!(發(fā)送)";
else
m_strInfo.Format("出現(xiàn)未知發(fā)送錯(cuò)誤!");
return FALSE;
}
if(state <DEF_PACKET) {
m_strInfo = "發(fā)送數(shù)據(jù)錯(cuò)誤!";
return FALSE;
}
memcpy((void *)&m_sockAddr,(void *)pAddr,
sizeof(sockaddr_in));
return TRUE;
}
//----------------------接收數(shù)據(jù)----------------------------
BOOL CICMP::RecvICMPPack()
{
int state;
int len = sizeof(sockaddr_in);
char * addr;
struct hostent *lpHostent = NULL;
addr = inet_ntoa(m_sockAddr.sin_addr);
state = recvfrom(winsock,(char *)m_pIp,MAX_PACKET,0,
(struct sockaddr*)&m_sockAddr,&len);
if (state == SOCKET_ERROR) {
if (WSAGetLastError() == WSAETIMEDOUT)
{ m_strInfo="接收超時(shí),路由跟蹤失敗!";
routestate=0;
RouteState="路由跟蹤失敗!";
}
else
m_strInfo = "未知接收錯(cuò)誤!";
return FALSE;
}
//分析數(shù)據(jù)
int ipheadlen;
ipheadlen = m_pIp->HeadLen * 4 ;
if (state < (ipheadlen+MIN_PACKET)) {
m_strInfo = "目的地址的響應(yīng)數(shù)據(jù)不正確";
return FALSE;
}
ICMP_HEAD * p_icmprev;
p_icmprev = (ICMP_HEAD*)((char *)m_pIp + ipheadlen);
switch (p_icmprev->type)
{
case ICMP_ECHOREPLY: //收到正常回顯
{
m_strInfo.Format("接收到%s %d字節(jié)響應(yīng)數(shù)據(jù),響應(yīng)時(shí)間:%dms.",
inet_ntoa(m_sockAddr.sin_addr),len,GetTickCount()-p_icmprev->time);
routeaddr=addr;
routestate=0;
RouteState="到達(dá)目的主機(jī)!";
return TRUE;
break;
}
case ICMP_TTLOUT: // TTL超時(shí)
{ routeaddr=inet_ntoa(m_sockAddr.sin_addr);
routestate=1;
RouteState="測試到路由器!";
return TRUE;
break;
}
case ICMP_DESUNREACH: //目的不可達(dá)
{ m_strInfo = "目的不可達(dá)!";
routestate=0;
RouteState="目的不可達(dá)!";
return TRUE;
break;
}
default :{ routestate=0;
m_strInfo="未知錯(cuò)誤!";
RouteState="不明狀態(tài)!";
}
}
return TRUE;
}
//----------------設(shè)置TTL--------------------
int CICMP::SetTTL(int TTL)
{
int nRet=setsockopt(winsock, IPPROTO_IP, IP_TTL,(LPSTR)&TTL,sizeof(int));
if(nRet==SOCKET_ERROR)
{ CString ttlerr;
ttlerr.Format("設(shè)置 TTL 錯(cuò)誤!");
AfxMessageBox(ttlerr);
return 0;
}
return 1;
}RouteTraceDlg.cpp文件代碼:
// RouteTraceDlg.cpp : implementation file
//
#include "stdafx.h"
#include "RouteTrace.h"
#include "RouteTraceDlg.h"
#include "afxmt.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About
struct SubThreadInfo
{
CDialog* pDialog;
CListCtrl* list;
CStatic* state;
CString IPStr;
int Maxhot;
} Info;
CEvent eventStopRoute;
//-----------------路由跟蹤線程---------------
UINT ThreadRoute(LPVOID pParam)
{
SubThreadInfo* pInfo = (SubThreadInfo*)pParam;
CRouteTraceDlg* pThreadDlg = (CRouteTraceDlg*)pInfo->pDialog;
CICMP m_icmp;
CString IPStr=pInfo->IPStr;
CString sTTL;
int nTtl;
m_icmp.Initialize();
for(nTtl=1;nTtl<=pInfo->Maxhot;nTtl++)
{
if(m_icmp.SetTTL(nTtl)==0)
return 0;
sTTL.Format("%d",nTtl);
if(m_icmp.SendICMPPack((char *)(LPCSTR)IPStr))
m_icmp.RecvICMPPack();
{
int i=pInfo->list->InsertItem(0,sTTL);
pInfo->list->SetItemText(i,1,m_icmp.routeaddr);
pInfo->list->SetItemText(i,2,m_icmp.RouteState);
pInfo->state->SetWindowText(m_icmp.m_strInfo);
Sleep(100);
}
if(m_icmp.routestate==0) //收到非TTL超時(shí)報(bào)文則跳出循環(huán)
break;
if(WaitForSingleObject(eventStopRoute.m_hObject, 0) == WAIT_OBJECT_0)
break; //收到停止信號(hào)則跳出循環(huán)
}
pThreadDlg->Routeflag=TRUE;
return 0;
}
…… //系統(tǒng)代碼
BOOL CRouteTraceDlg::OnInitDialog()
{
CDialog::OnInitDialog();
…… //系統(tǒng)代碼
// TODO: Add extra initialization here
m_list.InsertColumn(0,"標(biāo)號(hào)",LVCFMT_CENTER,60,0);
m_list.InsertColumn(1,"路由器地址",HDF_CENTER,200,0);
m_list.InsertColumn(2,"狀態(tài)",HDF_CENTER,100,0);
ListView_SetExtendedListViewStyleEx(m_list.m_hWnd, LVS_EX_FULLROWSELECT, 0xFFFFFFFF);
return TRUE; // return TRUE unless you set the focus to a control
}
…… //系統(tǒng)代碼
void CRouteTraceDlg::OnTrace()
{
CString str;
UpdateData(TRUE);
CWnd * pWnd;
pWnd = GetDlgItem(IDC_COMBO);
pWnd->GetWindowText(str);
if(str.IsEmpty()) {
MessageBox("請輸入地址!");
pWnd->SetFocus();
return;
}
if (m_comb.FindStringExact(-1, str) == CB_ERR)
m_comb.AddString(str);
m_list.DeleteAllItems();
if(Routeflag)
{
Routeflag=FALSE;
Info.IPStr=str;
Info.pDialog=this;
Info.Maxhot=m_maxhot;
Info.list=(&m_list);
Info.state=(&m_statectl);
AfxBeginThread(ThreadRoute, &Info); //在線程中實(shí)現(xiàn)路由跟蹤
}
}
void CRouteTraceDlg::OnStop()
{
if(!Routeflag)
{
eventStopRoute.SetEvent(); // 發(fā)出停止信號(hào)
}
}
void CRouteTraceDlg::OnDestroy()
{
CDialog::OnDestroy();
// TODO: Add your message handler code here
m_icmp.Uninitialize();
}
代碼運(yùn)行效果圖如下:
六、結(jié)束語
本文所述程序在 Windows 98 下 Visual C++6.0 中調(diào)試通過,并在寬帶網(wǎng)中測試成功。讀者可根據(jù)實(shí)際需要修改程序中的參數(shù),以實(shí)現(xiàn)更強(qiáng)大的功能,比如可以把超時(shí)值設(shè)置成更合適的值,或設(shè)置成可動(dòng)態(tài)輸入的形式。