一、在Windows环境下,实现ping(即发送一个ICMP的echo报文并对目标返回的回应报文进行正确的解析)
二、ICMP(Internet Control Message Protocol,网际控制协议),它允许主机或路由器报告差错情况和提供有关异常情况的报告。
ICMP提供的功能:错误诊断、拥塞控制、路径控制和查询服务
如当一个分组无法到达目的站点或TTL超时后,路由器就会丢弃此分组,并向源站点返回一个目的站点不可到达的ICMP报文。
实现ping主要涉及到ICMP回显请求和应答报文以及ICMP超时报文。回显ICMP协议数据包基本格式如下:
1、8bits类型和8bits代码字段一起决定了ICMP报文的类型:
类型8、代码报文0———-回显请求数据包
类型0、代码报文0———-回显应答数据包
类型11、代码字段为0——TTL超时
类型11、代码字段为1——数据包重组时间超时
2、16bits校验和字段:
包括数据在内的整个ICMP报文的校验和
3、16bits标识符字段:
用于标识本ICMP进程
4、16bits序列号字段:
用于判断回显应答报文
三、Ping程序的实现方法
主机向远程计算机发出ICMP回显请求后,远程计算机会拦截住这个请求,然后生成一条回显应答请求,再通过网络传回给主机。假如因某方面的原因,不能抵达目标主机,就会生成对应的ICMP错误消息(例如:主机不可达),由通信路径上的某台路由器返回。假定与远程主机的物理连接并不存在问题,但远程主机已经关闭或没有设置对应的网络事件作出响应,便需由自己的程序来执行超时鉴定,侦测出这样的情况。
四、ping程序代码
#include
#include
#include
#include
#include
#include
#pragma comment(lib , "Ws2_32.lib")
#define ICMP_ECHO_REQUEST 8 //定义回显请求类型
#define DEF_ICMP_DATA_SIZE 20 //定义发送数据长度
#define DEF_ICMP_PACK_SIZE 32 //定义数据包长度
#define MAX_ICMP_PACKET_SIZE 1024 //定义最大数据包长度
#define DEF_ICMP_TIMEOUT 3000 //定义超时为3秒
#define ICMP_TIMEOUT 11 //ICMP超时报文
#define ICMP_ECHO_REPLY 0 //定义回显应答类型
/*
*IP报头结构
*/
typedef struct
{
byte h_len_ver ; //IP版本号
byte tos ; // 服务类型
unsigned short total_len ; //IP包总长度
unsigned short ident ; // 标识
unsigned short frag_and_flags ; //标志位
byte ttl ; //生存时间
byte proto ; //协议
unsigned short cksum ; //IP首部校验和
unsigned long sourceIP ; //源IP地址
unsigned long destIP ; //目的IP地址
} IP_HEADER ;
/*
*定义ICMP数据类型
*/
typedef struct _ICMP_HEADER
{
byte type ; //类型-----8
byte code ; //代码-----8
unsigned short cksum ; //校验和------16
unsigned short id ; //标识符-------16
unsigned short seq ; //序列号------16
unsigned int choose ; //选项-------32
} ICMP_HEADER ;
typedef struct
{
int usSeqNo ; //记录序列号
DWORD dwRoundTripTime ; //记录当前时间
byte ttl ; //生存时间
in_addr dwIPaddr ; //源IP地址
} DECODE_RESULT ;
/*
*产生网际校验和
*/
unsigned short GenerateChecksum(unsigned short *pBuf , int iSize)
{
unsigned long cksum = 0 ; //开始时将网际校验和初始化为0
while(iSize > 1)
{
cksum += *pBuf++ ; //将待校验的数据每16位逐位相加保存在cksum中
iSize -= sizeof(unsigned short) ; //每16位加完则将带校验数据量减去16
}
//如果待校验的数据为奇数,则循环完之后需将最后一个字节的内容与之前结果相加
if(iSize)
{
cksum += *(unsigned char*)pBuf ;
}
//之前的结果产生了进位,需要把进位也加入最后的结果中
cksum = (cksum >> 16) + (cksum & 0xffff) ;
cksum += (cksum >> 16) ;
return (unsigned short)(~ cksum) ;
}
/*
*对ping应答信息进行解析
*/
boolean DecodeIcmpResponse_Ping(char *pBuf , int iPacketSize , DECODE_RESULT *stDecodeResult)
{
IP_HEADER *pIpHrd = (IP_HEADER*)pBuf ;
int iIphedLen = 20 ;
if(iPacketSize < (int)(iIphedLen + sizeof(ICMP_HEADER)))
{
printf("size error! \n") ;
return 0 ;
}
//指针指向ICMP报文的首地址
ICMP_HEADER *pIcmpHrd = (ICMP_HEADER*)(pBuf + iIphedLen) ;
unsigned short usID , usSeqNo ;
//获得的数据包的type字段为ICMP_ECHO_REPLY,即收到一个回显应答ICMP报文
if(pIcmpHrd->type == ICMP_ECHO_REPLY)
{
usID = pIcmpHrd->id ;
//接收到的是网络字节顺序的seq字段信息 , 需转化为主机字节顺序
usSeqNo = ntohs(pIcmpHrd->seq) ;
}
if(usID != GetCurrentProcessId() || usSeqNo != stDecodeResult->usSeqNo)
{
printf("usID error!\n") ;
return 0 ;
}
//记录对方主机的IP地址以及计算往返的时延RTT
if(pIcmpHrd->type == ICMP_ECHO_REPLY)
{
stDecodeResult->dwIPaddr.s_addr = pIpHrd->sourceIP ;
stDecodeResult->ttl = pIpHrd->ttl ;
stDecodeResult->dwRoundTripTime = GetTickCount() - stDecodeResult->dwRoundTripTime ;
return 1 ;
}
return 0 ;
}
void Ping(char *IP)
{
unsigned long ulDestIP = inet_addr(IP) ; //将IP地址转化为长整形
if(ulDestIP == INADDR_NONE)
{
//转化不成功时按域名解析
HOSTENT *pHostent = gethostbyname(IP) ;
if(pHostent)
{
ulDestIP = (*(IN_ADDR*)pHostent->h_addr).s_addr ; //将HOSTENT转化为长整形
}
else
{
printf("地址解析失败!\n") ;
return ;
}
}
//填充目的Socket地址
SOCKADDR_IN destSockAddr ; //定义目的地址
ZeroMemory(&destSockAddr , sizeof(SOCKADDR_IN)) ; //将目的地址清空
destSockAddr.sin_family = AF_INET ;
destSockAddr.sin_addr.s_addr = ulDestIP ;
destSockAddr.sin_port = htons(0);
//初始化WinSock
WORD wVersionRequested = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(wVersionRequested,&wsaData) != 0)
{
printf("初始化WinSock失败!\n") ;
return ;
}
//使用ICMP协议创建Raw Socket
SOCKET sockRaw = WSASocket(AF_INET , SOCK_RAW , IPPROTO_ICMP , NULL , 0 , WSA_FLAG_OVERLAPPED) ;
if(sockRaw == INVALID_SOCKET)
{
printf("创建Socket失败 !\n") ;
return ;
}
//设置端口属性
int iTimeout = DEF_ICMP_TIMEOUT ;
if(setsockopt(sockRaw , SOL_SOCKET , SO_RCVTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
{
printf("设置参数失败!\n") ;
return ;
}
if(setsockopt(sockRaw , SOL_SOCKET , SO_SNDTIMEO , (char*)&iTimeout , sizeof(iTimeout)) == SOCKET_ERROR)
{
printf("设置参数失败!\n") ;
return ;
}
//定义发送的数据段
char IcmpSendBuf[DEF_ICMP_PACK_SIZE] ;
//填充ICMP数据包个各字段
ICMP_HEADER *pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
pIcmpHeader->type = ICMP_ECHO_REQUEST ;
pIcmpHeader->code = 0 ;
pIcmpHeader->id = (unsigned short)GetCurrentProcessId() ;
memset(IcmpSendBuf + sizeof(ICMP_HEADER) , 'E' , DEF_ICMP_DATA_SIZE) ;
//循环发送四个请求回显icmp数据包
int usSeqNo = 0 ;
DECODE_RESULT stDecodeResult ;
while(usSeqNo <= 3)
{
pIcmpHeader->seq = htons(usSeqNo) ;
pIcmpHeader->cksum = 0 ;
pIcmpHeader->cksum = GenerateChecksum((unsigned short*)IcmpSendBuf , DEF_ICMP_PACK_SIZE) ; //生成校验位
//记录序列号和当前时间
stDecodeResult.usSeqNo = usSeqNo ;
stDecodeResult.dwRoundTripTime = GetTickCount() ;
//发送ICMP的EchoRequest数据包
if(sendto(sockRaw , IcmpSendBuf , DEF_ICMP_PACK_SIZE , 0 , (SOCKADDR*)&destSockAddr , sizeof(destSockAddr)) == SOCKET_ERROR)
{
//如果目的主机不可达则直接退出
if(WSAGetLastError() == WSAEHOSTUNREACH)
{
printf("目的主机不可达!\n") ;
exit(0) ;
}
}
SOCKADDR_IN from ;
int iFromLen = sizeof(from) ;
int iReadLen ;
//定义接收的数据包
char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE] ;
while(1)
{
iReadLen = recvfrom(sockRaw , IcmpRecvBuf , MAX_ICMP_PACKET_SIZE , 0 , (SOCKADDR*)&from , &iFromLen) ;
if(iReadLen != SOCKET_ERROR)
{
if(DecodeIcmpResponse_Ping(IcmpRecvBuf , sizeof(IcmpRecvBuf) , &stDecodeResult))
{
printf("来自 %s 的回复: 字节 = %d 时间 = %dms TTL = %d\n" , inet_ntoa(stDecodeResult.dwIPaddr) ,
iReadLen - 20,stDecodeResult.dwRoundTripTime ,stDecodeResult.ttl) ;
}
break ;
}
else if(WSAGetLastError() == WSAETIMEDOUT)
{
printf("time out ! *****\n") ;
break ;
}
else
{
printf("发生未知错误!\n") ;
break ;
}
}
usSeqNo++ ;
}
//输出屏幕信息
printf("Ping complete...\n") ;
closesocket(sockRaw) ;
WSACleanup() ;
}
int main(int argc , char* argv[])
{
char com[10] , IP[20] ;
while(1){
printf("command>>") ;
scanf("%s %s" , com , IP) ;
if(strcmp(com , "ping") == 0)
{
Ping(IP) ;
}
else
{
printf("command error ! \n") ;
}
}
return 0 ;
}