计算机网络 课程设计指导书
附录2、Tracert程序源代码
/* 程序名称:路由追踪(Tracert)程序
实现原理:Tracert程序关键是对IP头部生存时间(time to live)TTL字段的使用,程序实现时是向目地主机发送一个ICMP回显请求消息,初始时TTL等于1,这样当该数据报抵达途中的第一个路由器时,TTL的值就被减为0,导致发生超时错误,因此该路由生成一份ICMP超时差错报文返回给源主机。随后,主机将数据报的TTL值递增1,以便IP报能传送到下一个路由器,并由下一个路由器生成ICMP超时差错报文返回给源主机。不断重复这个过程,直到数据报达到最终的目地主机,此时目地主机将返回ICMP回显应答消息。这样,源主机只需对返回的每一份ICMP报文进行解析处理,就可以掌握数据报从源主机到达目地主机途中所经过的路由信息。
*/
#include
#pragma comment(lib, \ //IP报头 typedef struct {
unsigned char hdr_len:4; //4位头部长度 unsigned char version:4; //4位版本号 unsigned char tos; //8位服务类型 unsigned short total_len; //16位总长度 unsigned short identifier; //16位标识符
unsigned short frag_and_flags; //3位标志加13位片偏移 unsigned char ttl; //8位生存时间 unsigned char protocol; //8位上层协议号 unsigned short checksum; //16位校验和 unsigned long sourceIP; //32位源IP地址 unsigned long destIP; //32位目的IP地址 } IP_HEADER;
//ICMP报头 typedef struct {
10
计算机网络 课程设计指导书
BYTE type; //8位类型字段 BYTE code; //8位代码字段 USHORT cksum; //16位校验和 USHORT id; //16位标识符 USHORT seq; //16位序列号 } ICMP_HEADER;
//报文解码结构 typedef struct {
USHORT usSeqNo; //序列号 DWORD dwRoundTripTime; //往返时间 in_addr dwIPaddr; //返回报文的IP地址 }DECODE_RESULT;
//计算网际校验和函数
USHORT checksum(USHORT *pBuf,int iSize) {
unsigned long cksum=0; while(iSize>1) {
cksum+=*pBuf++; iSize-=sizeof(USHORT); } if(iSize) {
cksum+=*(UCHAR *)pBuf; }
cksum=(cksum>>16)+(cksum&0xffff); cksum+=(cksum>>16); return (USHORT)(~cksum); }
//对数据包进行解码
11
计算机网络 课程设计指导书
BOOL DecodeIcmpResponse(char * pBuf,int iPacketSize,DECODE_RESULT &DecodeResult,BYTE ICMP_ECHO_REPLY,BYTE ICMP_TIMEOUT)
{
//检查数据报大小的合法性
IP_HEADER* pIpHdr = (IP_HEADER*)pBuf; int iIpHdrLen = pIpHdr->hdr_len * 4;
if (iPacketSize < (int)(iIpHdrLen+sizeof(ICMP_HEADER))) return FALSE;
//根据ICMP报文类型提取ID字段和序列号字段
ICMP_HEADER *pIcmpHdr=(ICMP_HEADER *)(pBuf+iIpHdrLen); USHORT usID,usSquNo;
if(pIcmpHdr->type==ICMP_ECHO_REPLY) //ICMP回显应答报文 {
usID=pIcmpHdr->id; //报文ID usSquNo=pIcmpHdr->seq; //报文序列号 }
else if(pIcmpHdr->type==ICMP_TIMEOUT)//ICMP超时差错报文 {
char * pInnerIpHdr=pBuf+iIpHdrLen+sizeof(ICMP_HEADER); //载荷中的IP头 int iInnerIPHdrLen=((IP_HEADER *)pInnerIpHdr)->hdr_len*4; //载荷中的IP头长 ICMP_HEADER * pInnerIcmpHdr=(ICMP_HEADER *)(pInnerIpHdr+iInnerIPHdrLen);//载荷中的ICMP头
usID=pInnerIcmpHdr->id; //报文ID usSquNo=pInnerIcmpHdr->seq; //序列号 } else {
return false; }
//检查ID和序列号以确定收到期待数据报
if(usID!=(USHORT)GetCurrentProcessId()||usSquNo!=DecodeResult.usSeqNo) {
return false; }
12
计算机网络 课程设计指导书
//记录IP地址并计算往返时间
DecodeResult.dwIPaddr.s_addr=pIpHdr->sourceIP;
DecodeResult.dwRoundTripTime=GetTickCount()-DecodeResult.dwRoundTripTime;
//处理正确收到的ICMP数据报
if (pIcmpHdr->type == ICMP_ECHO_REPLY ||pIcmpHdr->type == ICMP_TIMEOUT) {
//输出往返时间信息
if(DecodeResult.dwRoundTripTime)
cout<<\ \ else
cout<<\ \ } return true; }
void main() {
//初始化Windows sockets网络环境 WSADATA wsa;
WSAStartup(MAKEWORD(2,2),&wsa); char IpAddress[255];
cout<<\请输入一个IP地址或域名:\ cin>>IpAddress; //得到IP地址
u_long ulDestIP=inet_addr(IpAddress); //转换不成功时按域名解析 if(ulDestIP==INADDR_NONE) {
hostent * pHostent=gethostbyname(IpAddress); if(pHostent) {
ulDestIP=(*(in_addr*)pHostent->h_addr).s_addr; } else
13
计算机网络 课程设计指导书
{
cout<<\输入的IP地址或域名无效!\ WSACleanup(); return; } }
cout<<\ //填充目地端socket地址 sockaddr_in destSockAddr;
ZeroMemory(&destSockAddr,sizeof(sockaddr_in)); destSockAddr.sin_family=AF_INET; destSockAddr.sin_addr.s_addr=ulDestIP; //创建原始套接字
SOCKET sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0, WSA_FLAG_OVERLAPPED);
//超时时间 int iTimeout=3000; //接收超时
setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char *)&iTimeout,sizeof(iTimeout)); //发送超时
setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char *)&iTimeout,sizeof(iTimeout));
//构造ICMP回显请求消息,并以TTL递增的顺序发送报文 //ICMP类型字段
const BYTE ICMP_ECHO_REQUEST=8; //请求回显 const BYTE ICMP_ECHO_REPLY=0; //回显应答 const BYTE ICMP_TIMEOUT=11; //传输超时
//其他常量定义
const int DEF_ICMP_DATA_SIZE=32; //ICMP报文默认数据字段长度 const int MAX_ICMP_PACKET_SIZE=1024;//ICMP报文最大长度(包括报头) const DWORD DEF_ICMP_TIMEOUT=3000; //回显应答超时时间 const int DEF_MAX_HOP=30; //最大跳站数
//填充ICMP报文中每次发送时不变的字段
14