Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9727086
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-03-13 18:36:16

代码运行效果图如下:


作者简介:本人是成都理工大学大四学生,学习计算机通信专业,对网络及编程有着非常浓厚的兴趣,希望能与大家共同探讨。

[摘要] 本文简单介绍了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 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字节响应数据,响应时间:%dms.",
  inet_ntoa(m_sockAddr.sin_addr),len,GetTickCount()-p_icmprev->time);
  routeaddr=addr;
  routestate=0; 
     RouteState="到达目的主机!";
     return TRUE;
   break; 
  }
  case ICMP_TTLOUT:   // TTL超时
  {   routeaddr=inet_ntoa(m_sockAddr.sin_addr);
   routestate=1;
   RouteState="测试到路由器!";
   return TRUE;
   break;
  }
  
  case ICMP_DESUNREACH:  //目的不可达
  { m_strInfo = "目的不可达!";
      routestate=0;
   RouteState="目的不可达!";
   return TRUE;
   break;
  }
    
  default :{  routestate=0;
     m_strInfo="未知错误!";
     RouteState="不明状态!";
     }
  }
  return TRUE;

}
//----------------设置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("设置 TTL 错误!");
  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超时报文则跳出循环
         break;
if(WaitForSingleObject(eventStopRoute.m_hObject, 0) == WAIT_OBJECT_0) 
    break;          //收到停止信号则跳出循环
 }
pThreadDlg->Routeflag=TRUE;

  return 0;
}

……   //系统代码

BOOL CRouteTraceDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

…… //系统代码
 
 // TODO: Add extra initialization here
 m_list.InsertColumn(0,"标号",LVCFMT_CENTER,60,0);
 m_list.InsertColumn(1,"路由器地址",HDF_CENTER,200,0);
 m_list.InsertColumn(2,"状态",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
}

…… //系统代码

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);  //在线程中实现路由跟踪
  }
}

void CRouteTraceDlg::OnStop() 
{
     if(!Routeflag)
  { 
    eventStopRoute.SetEvent();    // 发出停止信号
  }
}

void CRouteTraceDlg::OnDestroy() 
{
 CDialog::OnDestroy();
 
 // TODO: Add your message handler code here
 m_icmp.Uninitialize();
}
六、结束语
本文所述程序在 Windows 98 下 Visual C++6.0 中调试通过,并在宽带网中测试成功。读者可根据实际需要修改程序中的参数,以实现更强大的功能,比如可以把超时值设置成更合适的值,或设置成可动态输入的形式,示例程序请看本文所附源码,也可以本人主页中下载。

七、参考文献
1. 胡晓军、邓波、高宏伟 编著 Visual C++高级开发范例解析 2002.1 电子工业出版社
2. 周明天、汪文勇 编著 TCP/IP网络原理与技术  1993.12 清华大学出版社

阅读(2330) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~