程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 使用ICMP實現路由跟蹤

使用ICMP實現路由跟蹤

編輯:關於VC++

代碼運行效果圖如下:

作者簡介:本人是成都理工大學大四學生,學習計算機通信專業,對網絡及編程有著非常濃厚的興趣,希望能與大家共同探討。

[摘要] 本文簡單介紹了ICMP協議和一種利用ICMP在VC++下實現網絡路由跟蹤的方法,並給出一了個詳細的例子。

[關鍵字] ICMP 路由跟蹤 Visual C++6.0

一、概述

計算機在Internet中傳遞信息時,必須要經過路由器進行網絡路由才能找到目的主機,把信息送到目的主機。路由器中都有一張路由表,表中保存了從本路由器到某一主機的路由信息,路由器就是通過該路由表進行網絡尋徑的。兩台主機之間並沒有一條固定的路徑(即路由表並不固定),該路徑隨著網絡的變動而作相應的變動,因而我們並不能直接從某一主機上得到去往另一主機的路徑,要得到本機與網絡上某台主機的網絡路徑就必須要進行路由跟蹤。本文將介紹一種實現路由跟蹤的方法。

二、ICMP簡介

ICMP即Internet控制報文協議是一種用於特殊用途的報文機制,可以使互聯網中的路由器或主機報告差錯或提供有關意外情況的信息。

ICMP報文為兩級封裝,ICMP報文放在IP數據報的數據部分,IP數據報則放在幀的數據中進行網絡傳輸(如下圖1所示)。ICMP報文與其他普通報文一樣,具有相同的路由選擇,並沒有特殊的優先權和增加可靠性。

(圖1)ICMP報文的封裝

在ICMP包頭中包含了三個字段:1字節類型域、1字節代碼域、2字節校驗和。類型域表示了該報文的類型,如:回應請求報文,數據報超時報文等,代碼域表示了該類型的幾種不同情況,如:當類型為11(超時報文)時,代碼為0表示TTL超時,為1表示片重組超時。在實現本文中所述的功能時要發送回應請求報文(類型為8),過程如下:源主機向目的主機發送一個類型為8的回應請求報文,若目的站點收到回應請求報文則把報文IP包頭部中的目的IP與源IP地址交換,將類型8改為回應類型0,計算出新的校驗和再發往源主機。若源主機收到了該回應報文,則不但說明了目的主機可達,而且說明目的主機與源主機之間的路由器工作正常,源主機和目的主機的IP、ICMP軟件運行正常。但若在傳輸過程中了出現了某些問題,如網絡不通等,導致數據被定向到一個無效的目的地,這時相關路由器或目的主機將發回目的不可達報文(類型為3),並在代碼中說明該報文的具體情況:是網絡不可達還是主機不可達等。若請求報文在傳輸過程中超時,即TTL被減為0(報文每經過一個路由器TTL都要減1),則該路由器返回一個TTL超時報文(類型為11),報文IP頭中源IP地址即為本路由器的IP地址。

三、路由跟蹤的實現方法

路由跟蹤的實現就是巧妙地利用了ICMP報文的TTL超時報文。其實現過程如下:源主機先向目的主機發送一個回應請求報文(類型8),TTL值設為1,第一個路由器收到後將TTL減1,這樣TTL變為0,分組被廢除,同時路由器向源主機發送一個TTL超時報文(類型為11),報文的IP包頭中的源IP地址就是第一個路由器的地址,源主機就可以通過對該報文進行分析,得到第一個路由器的地址。接著發送TTL等於2的報文得到第二個路由器地址,再發TTL等於3的報文,如此下去直到收到目的主機的回應應答報文(類型為0)或目的不可達報文(類型為3),或者到了最大跳數(要檢測路由器個數的最大值)。可以看到,對TTL的設置是實現跟蹤的關鍵,使用函數setsockopt(m_Sock, IPPROTO_IP, IP_TTL,(LPSTR)&TTL,sizeof(int)) 可以對其進行設置,m_Sock是所創建的套接字,IP_TTL說明是進行TTL設置,TTL即是要設置的TTL值,為一個整形數值 。其實現流程可用下圖2表示:

(圖2) 流程圖

四、程序實現

本文所介紹的程序是使用了Visual C++6.0編寫,其過程如下:

1、創建一個新的基於對話框的AppWizard工程,並命名為RouteTrace。

2、在stdafx.h中加入#include "winsock2.h"。

3、打開選擇菜單Project->Setting (ALT+F7),進入Project Setting 對話框,在Link下的 Object/library modules 輸入ws2_32.lib,然後點OK。

4、自定義一個ICMP類。點擊菜單中的Insert->New Class,進入New Class對話框,在Class type中選擇Generic Class,在Name中輸入類名CICMP,然後點OK,這樣就新建了一個CICMP類。

5、將對話框設置成如圖3所示的樣子:

(圖3 程序界面)

啟動Class Wizard 為各控件添加響應函數和關聯變量,控件對應的ID及響應函數或變量為:

控件 ID 響應函數 變量 地址組合框 IDC_COMBO   CComboBox m_comb 最大跳數編輯框 IDC_MAXHOT   int m_maxhot 跟蹤按鈕 IDC_TRACE OnTrace()   停止按鈕 IDC_STOP OnStop()   列表框 IDC_LIST   CListCtrl m_list

五、添加代碼

在完成了對各控件的設置和類的添加以後就是對代碼的編寫了,這裡給出了新建類CICMP和RouteTraceDlg.cpp的代碼,詳細代碼請參看源程序。

ICMP.cpp文件代碼:

// ICMP.cpp: implementation of the CICMP class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "RouteTrace.h"
#include "ICMP.h"
#include "ws2tcpip.h" //實現 IP_TTL 設置的關鍵
#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創建失敗!");
    return FALSE;
  }
  int timeout =5000;
  setsockopt(winsock,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,  // 設置接收超時
    sizeof(timeout));
  timeout = 5000;
  setsockopt(winsock,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,  //設置發送超時
    sizeof(timeout));
  return TRUE;
}
void CICMP::Uninitialize()  //釋放Socket
{
  if(winsock)
    closesocket(winsock);
  WSACleanup();
}
USHORT CICMP::CheckSum(USHORT *buffer, int size) //計算校驗和
{
 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);
}
//--------------------發送ICMP回應請求報文-------------------
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數據各項
  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;
  //填充數據
  p_data = ((char *)m_pIcmp + sizeof(ICMP_HEAD));
  memset((char *)p_data,''0'',DEF_PACKET);
  //校驗和
  m_pIcmp->cksum = CheckSum((USHORT *)m_pIcmp,
    DEF_PACKET+sizeof(ICMP_HEAD));

  //發送數據
  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 = "連接超時!(發送)";
    else
      m_strInfo.Format("出現未知發送錯誤!");
    return FALSE;
  }
  if(state <DEF_PACKET) {
    m_strInfo = "發送數據錯誤!";
    return FALSE;
  }

  memcpy((void *)&m_sockAddr,(void *)pAddr,
    sizeof(sockaddr_in));
  return TRUE;
}
//----------------------接收數據----------------------------
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="接收超時,路由跟蹤失敗!";
    routestate=0;
    RouteState="路由跟蹤失敗!";
    }
    else
      m_strInfo = "未知接收錯誤!";
    return FALSE;
  }
  //分析數據
  int ipheadlen;
  ipheadlen = m_pIp->HeadLen * 4 ;
  if (state < (ipheadlen+MIN_PACKET))  {
    m_strInfo = "目的地址的響應數據不正確";
    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字節響應數據,響應

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved