TraceRoute程序:
原理:向目的地发送一个UDP数据包,并重复递增IP的"存活时间"(TTL)值.最开始的时候。TTL等于1,也就是说,一旦它抵达路途中的第一个路由器,TTL便会超时(变成0),这样就会造成路由器生成一个ICMP"超时"数据包。随后,最初的TTL值递增1,以便UDP包能继续传到下一个路由器,而生成的ICMP超时包会自第一个路由器返回。只需将返回的每一条ICMP消息都收集下来,便能为中途经过的路由器IP地址勾勒出一个清晰的轮廓,直到最终抵达目标主机。一旦TTL的值递增得足够大,可以实际抵达目标位置,通常便会返回一条ICMP"端口访问不到"消息,因为再接受端主机上,并没有进程在等待这条消息
有两个办法实现TraceRoute程序:
1: 可以使用UDP包发送数据报,连续递增更改TTL的值。TTL每一次“超时”,都返回一条ICMP消息,这种方法要求使用安装了UDP协议的一个套接字来发送数据,同时用安装了ICMP协议的另一个套接字来接收数据(读取返回的消息),UDP套接字是普通的套接字,而ICMP套接字的类型比较特殊.是SOCK_RAW(原始套接字),并设定了IPPROTO_ICMP协议。UDP套接字的TTL值需要通过IP_TTL套接字选项加以控制.此外,也可以创建一个UDP套接字,并使用IP_HDRINCL选项,在IP头内对TTL进行人工设置
2:将ICMP数据报简单发给目的地,同时连续递增更改TTL的值,在"超时"的时候,返回一条ICMP错误消息(它只要使用一个套接字(安装ICMP协议))
2的例子:
步骤:
1 将RraceRoute的功能封装在一个类CTranceRoute中
#include <winsock2.h>
#include <ws2tcpip.h>
//定义ICMP消息类型
#define ICMP_ECHOREPLY 0 //目的地址返回的ICMP
#define ICMP_DESTUNREACH 3 //不能够到达目的地
#define ICMP_SRCQUENCH 4
#define ICMP_REDIRECT 5
#define ICMP_ECHO 8
#define ICMP_TIMEOUT 11 //沿着这条链路返回的路由
#define ICMP_PARMERR 12
//定义最大节点数
#define MAX_HOPS 30
#define ICMP_MIN 8 //每个ICMP数据包头部最少8字节
//IP 头结构
typedef struct iphdr
{
unsigned int h_len:4; //头部长度
unsigned int version:4; //IP版本
unsigned char tos; //服务类型
unsigned short total_len; //数据报总长度
unsigned short ident; //唯一的标志
unsigned short frag_and_flags;//标志
unsigned char ttl; //生命周期
unsigned char proto; //协议类型
unsigned short checksum; //IP校验
unsigned int sourceIP; //源地址
unsigned int destIP; //目的地址
}IpHeader;
//ICMP头结构
typedef struct _ihdr
{
BYTE i_type; //ICMP消息类型
BYTE i_code; //子码
USHORT i_cksum; //校验和
USHORT i_id; //唯一的标识
USHORT i_seq; //序号
ULONG timestamp; //时间戳
}IcmpHeader;
#define DEF_PACKET_SIZE 32 //缺省数据报长度
#define MAX_PACKET 1024 //数据报最大长度
class CTraceRouteDlg ;
class CTranceRoute
{
public:
CTranceRoute(CTraceRouteDlg *dlg);
virtual ~CTranceRoute();
public:
//解析返回的数据包
int decode_resp(char *buf,int bytes,SOCKADDR_IN*from,int ttl);
//清除数据
void Cleanup();
//数据校验
USHORT checksum(USHORT*buffer,int size);
//设置TTL
int set_ttl(SOCKET s,int nTimeToLive);
//填充ICMP数据包
void fill_icmp_data(char*icmp_data,int datasize);
//连接到主机
void ConnectToHost(char* strHost);
CTraceRouteDlg *m_dlg;
SOCKET m_socketRaw;
SOCKADDR_IN m_addrDest;
SOCKADDR_IN m_addrFrom;
int m_nDatasize;
int m_nMaxhops;//最大节点数
int m_nTTL;
char* m_IcmpData;
char* m_RcvBuffer;
int m_nSeqno;
int m_nTimeout;
BOOL m_bDone;
};
2 初始化:
CTranceRoute::CTranceRoute(CTraceRouteDlg *dlg)
{
m_dlg=dlg;
m_nTTL=1;
m_nMaxhops=MAX_HOPS;
m_socketRaw=INVALID_SOCKET;
m_RcvBuffer=NULL;
m_IcmpData=NULL;
m_nTimeout=1000;
m_bDone=FALSE;
m_nSeqno=0;
//初始化socket
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
{
AfxMessageBox("socket cannot load dll");
}
}
3:功能函数:
//该函数负责连接到主机,它首先创建一个Socket,这个Socket的类型必须设成SOCK_RAW,即原始套接字,必须采用IPPORTO_ICMP
//协议,接下来,设置Socket的接收和发送的超时参数,如果用户输入的地址是主机名,则还需要将其解析成一个IP地址
//接着,为ICMP数据报分配空间,最后是创建和发送ICMP数据包,并解析返回的ICMP包
void CTranceRoute::ConnectToHost(char*strHost)
{
//----------------------------------------------------------------------------------------------------------------------
//创建socket 这个Socket的类型必须设成SOCK_RAW,即原始套接字,必须采用IPPORTO_ICMP协议
m_socketRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);
if(m_socketRaw==INVALID_SOCKET)
{
AfxMessageBox("socket 创建失败");
return ;
}
//-------------------------------------------------------------------------------------------------------------------------
//设置发送和接收超时参数
int ret=setsockopt(m_socketRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&m_nTimeout,sizeof(m_nTimeout));
if(ret==SOCKET_ERROR)
{
AfxMessageBox("设置接收超时参数失败");
return ;
}
ret=setsockopt(m_socketRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&m_nTimeout,sizeof(m_nTimeout));
if(ret==SOCKET_ERROR)
{
AfxMessageBox("设置接收超时参数失败");
return ;
}
//-------------------------------------------------------------------------------------------------------------------------
//解析地址
m_addrDest.sin_family=AF_INET;
if((m_addrDest.sin_addr.S_un.S_addr=inet_addr(strHost))==INADDR_NONE)
{
HOSTENT* hp;
hp=gethostbyname(strHost);
if(hp)
memcpy(&(m_addrDest.sin_addr.S_un.S_addr ),hp->h_addr_list[0],hp->h_length );
else
{
AfxMessageBox("输入的主机不存在");
return ;
}
}
//-------------------------------------------------------------------------------------------------------------------------
//设置路由选项
int bOpt=TRUE;
if(setsockopt(m_socketRaw,SOL_SOCKET,SO_DONTROUTE,(char *)&bOpt,sizeof(BOOL))==SOCKET_ERROR)
{
AfxMessageBox("设置socket参数失败");
return ;
}
//-------------------------------------------------------------------------------------------------------------------------
//为ICMP数据报分配空间
m_nDatasize=DEF_PACKET_SIZE;
m_nDatasize+=sizeof(IcmpHeader);
//为ICMP数据包分配发送和接收缓冲区
m_IcmpData=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
m_RcvBuffer=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
if((!m_IcmpData)|(!m_RcvBuffer))
{
AfxMessageBox("堆分配数据失败");
return ;
}
//-----------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------
//创建ICMP数据包
memset(m_IcmpData,0,MAX_PACKET);
CString temp;
temp.Format("Tracing route to %s over a maximum of %d hops: /r/n",strHost,m_nMaxhops);
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result );
//------------------------------------------------
fill_icmp_data(m_IcmpData,m_nDatasize);//填充ICMP数据
//------------------------------------------------
for(m_nTTL=1;((m_nTTL<m_nMaxhops)&&(!m_bDone));m_nTTL++)
{
int bwrote;
//设置socket生命周期
set_ttl(m_socketRaw,m_nTTL);
//往ICMP头部添加信息
((IcmpHeader*)m_IcmpData)->i_cksum=0;
((IcmpHeader*)m_IcmpData)->timestamp=GetTickCount();
((IcmpHeader*)m_IcmpData)->i_seq=m_nSeqno++;
((IcmpHeader*)m_IcmpData)->i_cksum=checksum((USHORT*)m_IcmpData,m_nDatasize);
//发送ICMP包到目的端
bwrote=sendto(m_socketRaw,m_IcmpData,m_nDatasize,0,(SOCKADDR*)&m_addrDest,sizeof(m_addrDest));
if(bwrote==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAETIMEDOUT)
{
temp.Format("%2d Send request timed out ./t/n",m_nTTL);
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result );
continue ;
}
temp.Format("发送数据函数出错!/r/n");
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result);
return ;
}
//从目的端读取数据包
int fromlen;
fromlen=sizeof(SOCKADDR);
ret=recvfrom(m_socketRaw,m_RcvBuffer,MAX_PACKET,0,(struct sockaddr*)&m_addrFrom,&fromlen);
if(ret==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAETIMEDOUT)
{
temp.Format("%2d Recvive Request timed out ./r/n",m_nTTL);
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result );
continue;
}
temp.Format("recvfrom 函数出错!/n");
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result );
return ;
}
//解析返回的ICMP数据报信息
m_bDone=decode_resp(m_RcvBuffer,ret,&m_addrFrom,m_nTTL);
Sleep(1000);
}
}
其它代码:
void CTranceRoute::Cleanup()
{
HeapFree(GetProcessHeap(), 0, m_RcvBuffer);
HeapFree(GetProcessHeap(), 0, m_IcmpData);
if (m_socketRaw != NULL)
closesocket(m_socketRaw);
// WSACleanup();
}
//负责对返回的ICMP数据进行解析
int CTranceRoute::decode_resp(char*buf,int bytes,SOCKADDR_IN*from,int ttl)
{
IpHeader *iphdr=NULL;
IcmpHeader *icmphdr=NULL;
unsigned short iphdrlen;
struct hostent*IpHostent=NULL;
struct in_addr inaddr=from->sin_addr;
iphdr=(IpHeader*)buf;
//Number of 32-bit words*4=bytes
iphdrlen=iphdr->h_len*4;
CString temp;
if(bytes<iphdrlen+ICMP_MIN)
{
temp.Format("Too few bytes from %s /r/n",inet_ntoa(from->sin_addr));
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result);
}
icmphdr=(IcmpHeader*)(buf+iphdrlen);
switch(icmphdr->i_type )
{
case ICMP_ECHOREPLY: //目的地址返回的ICMP
IpHostent=gethostbyaddr((const char*)&from->sin_addr,AF_INET,sizeof(struct in_addr));
if(IpHostent!=NULL)
{
temp.Format("%2d %s (%s) /r/n", ttl, IpHostent->h_name,inet_ntoa(inaddr));
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result);
}
return 1;
break;
case ICMP_TIMEOUT: //沿着这条链路返回的路由
temp.Format("%2d %s /r/n",ttl,inet_ntoa(inaddr));
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result );
return 0;
break;
case ICMP_DESTUNREACH://不能够到达目的地
temp.Format("%2d %s reports: Host is unreachable /r/n", ttl,inet_ntoa(inaddr));
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result);
return 1;
break;
default:
temp.Format("non-echo type %d recvd (找不到匹配)/n", icmphdr->i_type);
m_dlg->m_result+=temp;
m_dlg->SetDlgItemText(IDC_RESULT,m_dlg->m_result);
return 1;
break;
}
return 0;
}
//负责设置ICMP包的声明周期
int CTranceRoute::set_ttl(SOCKET s,int nTimeToLive)
{
int nRet;
nRet=setsockopt(s,IPPROTO_IP,IP_TTL,(LPSTR)&nTimeToLive,sizeof(int));
if(nRet==SOCKET_ERROR)
{
AfxMessageBox("设置socket选项失败!");
return 0;
}
return 1;
}
//负责填充ICMP数据
void CTranceRoute::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);
//memset(datapart,'E',datasize-sizeof(IcmpHeader));
}
USHORT CTranceRoute::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);
}
4 按钮
void CTraceRouteDlg::OnTracert()
{
CTranceRoute route(this);
char host[100];
GetDlgItemText(IDC_HOST,host,100);
route.m_nMaxhops=GetDlgItemInt(IDC_ROUTE_COUNT);
route.ConnectToHost(host);
m_result+="Trace 结束!/r/n";
SetDlgItemText(IDC_RESULT,m_result);
route.Cleanup();
}
void CTraceRouteDlg::OnDeltaposSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_UPDOWN* pNMUpDown = (NM_UPDOWN*)pNMHDR;
int max=GetDlgItemInt(IDC_ROUTE_COUNT);
if(max>=MAX_HOPS&&pNMUpDown->iDelta<0)
return ;
if(max<2&&pNMUpDown->iDelta>0)
return ;
max-=pNMUpDown->iDelta;
SetDlgItemInt(IDC_ROUTE_COUNT,max);
*pResult = 0;
}