一、实现原理
ping利用ICMP协议包来侦测另一个主机是否可达。Ping的原理是使用了类型码为8的ICMP回送请求包,收到请求的主机则用类型码为0的ICMP回应报文。如果应答包和请求包的标示号、序号和内容相同的话,则证明能够ping通,网络设备可用。ping程序计算间隔时间,并计算有多少个包被送达,用户就可以判断网络大致的情况。
系统提供的Ping还列出来了传送的时间和TTL等数据,命令提供了多种参数。下面的程序用ICMP协议实现了简单的Ping功能。由于用的是ICMP协议,因此,我们不能够通过建立一个SOCK_STREAM或SOCK_DGRAM来发送这个包,只能够使用SOCK_RAW来自己构造数据包。
二、ping程序的工作流程
三、主要函数和数据结构
//定义IP头部
typedef struct iphdr {
unsigned int h_len:4; // 头部长
unsigned int version:4; // 版本号
unsigned char tos; // 服务类型
unsigned short total_len; // 总长度
unsigned short ident; // 标识
unsigned short frag_and_flags; //标志
unsigned char ttl; //生存时间
unsigned char proto; // 上层协议
unsigned short checksum; // 校验和
unsigned int sourceIP; //源IP
unsigned int destIP; //目的IP
unsigned long
RoundTripTime; // RTT in milliseconds
}IpHeader;
// 定义ICMP 头部
typedef struct icmphdr {
BYTE i_type; //类型
BYTE i_code; //代码
USHORT i_cksum; //校验和
USHORT i_id; //标识
USHORT i_seq; //序列号
ULONG timestamp; //数据
}IcmpHeader;
#define STATUS_FAILED 0xFFFF
#define DEF_PACKET_SIZE
32
//默认数据包长度
#define DEF_PACKET_NUMBER 4
//默认发送ICMP请求的次数
#define MAX_PACKET 1024
//数据包最大长度
#define xmalloc(s)
HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))
//分配内存
#define xfree(p) HeapFree (GetProcessHeap(),0,(p))
//释放内存
//填充icmp数据包
void fill_icmp_data(char *, int);
//计算校验和
USHORT checksum(USHORT *, int);
//收到数据后解码
int decode_resp(char *,int ,struct sockaddr_in
*);
//计算接收耗费的时间的最小,最大和平均值
void PX(int *input, int inputlen, int
*output);
四、ping程序的具体实现
#define ICMP_ECHO 8 //ICMP回显请求
#define ICMP_ECHOREPLY 0 //ICMP回显应答
#define ICMP_MIN 8 //ICMP数据包最短为8个字节
//定义IP头部
typedef struct iphdr {
unsigned int h_len:4; // 头部长
unsigned int version:4; // 版本号
unsigned char tos; // 服务类型
unsigned short total_len; // 总长度
unsigned short ident; // 标识
unsigned short frag_and_flags; //标志
unsigned char ttl; //生存时间
unsigned char proto; // 上层协议
unsigned short checksum; // 校验和
unsigned int sourceIP; //源IP
unsigned int destIP; //目的IP
unsigned long
RoundTripTime; // RTT in milliseconds
}IpHeader;
// 定义ICMP 头部
typedef struct icmphdr {
BYTE i_type; //类型
BYTE i_code; //代码
USHORT i_cksum; //校验和
USHORT i_id; //标识
USHORT i_seq; //序列号
ULONG timestamp; //数据
}IcmpHeader;
#define STATUS_FAILED 0xFFFF
#define DEF_PACKET_SIZE
32
//默认数据包长度
#define DEF_PACKET_NUMBER 4
//默认发送ICMP请求的次数
#define MAX_PACKET 1024
//数据包最大长度
#define xmalloc(s)
HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))
//分配内存
#define xfree(p) HeapFree (GetProcessHeap(),0,(p))
//释放内存
//填充icmp数据包
void fill_icmp_data(char *, int);
//计算校验和
USHORT checksum(USHORT *, int);
//收到数据后解码
int decode_resp(char *,int ,struct sockaddr_in
*);
//计算接收耗费的时间的最小,最大和平均值
void PX(int *input, int inputlen, int
*output);
//记录时间的变量
int temptime=0;
//提示用户该程序使用方法
void Usage(char *progname){
fprintf(stderr,"Usage:\n");
fprintf(stderr,"%s [number of packets]
[data_size]\n",progname);
fprintf(stderr,"datasize can be up to 1Kb\n");
ExitProcess(STATUS_FAILED);
}
int main(int argc, char **argv)
{
WSADATA wsaData; //初始化windows socket需要的参数
SOCKET sockRaw; //原始套接字
struct sockaddr_in dest,from; //源、目的IP地址
struct hostent * hp; //指针指向包含主机名、地址列表等信息的结构体
int bread,datasize,times;
int fromlen = sizeof(from);
int timeout = 1000; //超时时间1000ms
int statistic = 0; // 用于统计
char *dest_ip;
char *icmp_data;
char *recvbuf;
unsigned int addr=0;
USHORT seq_no = 0;
int num=4;
if(argc>2)
{
num=atoi(argv[2]);
if(num == 0)
num=DEF_PACKET_NUMBER;
}
int
*TIME=(int*)malloc(num*sizeof(int));
for(int
k=0;k
{
TIME[k]=0;
}
int
ST[3]={0};
if (WSAStartup(MAKEWORD(2,1),&wsaData) !=
0){
fprintf(stderr,"WSAStartup failed:
%d\n",GetLastError());
ExitProcess(STATUS_FAILED);
}
//使用方法不对时显示提示信息
if (argc <2 ) {
Usage(argv[0]);
}
//创建原始套接字
sockRaw = WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,
0,WSA_FLAG_OVERLAPPED);
//注:为了使用发送接收超时设置(即设置SO_RCVTIMEO, SO_SNDTIMEO),
//
必须将标志位设为WSA_FLAG_OVERLAPPED !
// 创建原始套接字不成功
if (sockRaw == INVALID_SOCKET) {
fprintf(stderr,"WSASocket() failed:
%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
//设定发送超时时间
timeout = 1000;
bread =
setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,
sizeof(timeout));
if(bread == SOCKET_ERROR) {
fprintf(stderr,"failed to set send timeout:
%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
//设定接收数据超时时间
bread =
setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,
sizeof(timeout));
if(bread == SOCKET_ERROR) {
fprintf(stderr,"failed to set recv timeout:
%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
memset(&dest,0,sizeof(dest));
//解析用户输入的目标地址
hp = gethostbyname(argv[1]);
if (!hp){
addr = inet_addr(argv[1]);
}
//非法输入
if ((!hp) && (addr == INADDR_NONE)
) {
fprintf(stderr,"Unable to resolve %s\n",argv[1]);
ExitProcess(STATUS_FAILED);
}
//记录目标主机信息的结构体
//地址
if (hp != NULL)
memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
else
dest.sin_addr.s_addr = addr;
//协议族
if (hp)
dest.sin_family = hp->h_addrtype;
else
dest.sin_family = AF_INET;
//目标IP
dest_ip = inet_ntoa(dest.sin_addr);
//除了目标地址,还给出了Ping的次数
if(argc>2)
{
times=atoi(argv[2]);
if(times == 0)
times=DEF_PACKET_NUMBER;
}
else
times=DEF_PACKET_NUMBER;
//还给出了数据大小
if (argc >3)
{
datasize = atoi(argv[3]);
//给的是0,则用默认数据包大小
if (datasize == 0)
datasize = DEF_PACKET_SIZE;
//用户给出的数据包大小太大
if (datasize >1024)
{
fprintf(stderr,"WARNING : data_size is too large !\n");
datasize = DEF_PACKET_SIZE;
}
}
else
datasize = DEF_PACKET_SIZE;
datasize += sizeof(IcmpHeader);
icmp_data = (char*)xmalloc(MAX_PACKET);
recvbuf = (char*)xmalloc(MAX_PACKET);
if (!icmp_data) {
fprintf(stderr,"HeapAlloc failed %d\n",GetLastError());
ExitProcess(STATUS_FAILED);
}
memset(icmp_data,0,MAX_PACKET);
//填充ICMP数据包,类型、代码、标识等
fill_icmp_data(icmp_data,datasize);
//提示正在ping目标主机
fprintf(stdout,"\nPinging %s ....\n\n",dest_ip);
//Ping多次
for(int i=0;i
{
int bwrote;
//准备ICMP包头部数据
((IcmpHeader*)icmp_data)->i_cksum = 0;
//取得以毫秒为单位的计算机启动后经历的时间间隔
((IcmpHeader*)icmp_data)->timestamp =
GetTickCount();
((IcmpHeader*)icmp_data)->i_seq = seq_no++;
//序列号递增
((IcmpHeader*)icmp_data)->i_cksum =
checksum((USHORT*)icmp_data,datasize);//计算校验和
//发送ICMP数据包
bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct
sockaddr*)&dest,sizeof(dest));
//发送失败
if (bwrote == SOCKET_ERROR){
if (WSAGetLastError() == WSAETIMEDOUT) {
printf("Request timed out.\n");
continue;
}
fprintf(stderr,"sendto failed: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
if (bwrote < datasize ) {
fprintf(stdout,"Wrote %d bytes\n",bwrote);
}
//接收应答数据
bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct
sockaddr*)&from,&fromlen);
//接收失败
if (bread == SOCKET_ERROR){
if (WSAGetLastError() == WSAETIMEDOUT) {
printf("Request timed out.\n");
continue;
}
fprintf(stderr,"recvfrom failed:
%d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
//成功解码
if(!decode_resp(recvbuf,bread,&from))
statistic++; //记录成功接收响应数据包的次数
TIME[i] = temptime;
Sleep(1000);
}
PX(TIME, times,
ST);
//统计运行Ping命令的统计结果
fprintf(stdout,"\nPing statistics for %s \n",dest_ip);
fprintf(stdout,"
Packets: Sent = %d,Received = %d, Lost = %d (%2.0f%%
loss)\n",times,
statistic,(times-statistic),(float)(times-statistic)/times*100);
printf("Approximate
round trip times in milli-seconds:\n" );
printf("
Minimum = %dms, Maximum = %dms, Average =
%dms\n",ST[1],ST[0],ST[2]);
// 关闭,回收资源
WSACleanup();
return 0;
}
//收到响应IP数据包后,对其进行解码
int decode_resp(char *buf, int bytes,struct
sockaddr_in *from)
{
IpHeader *iphdr;
IcmpHeader *icmphdr;
unsigned short iphdrlen;
iphdr = (IpHeader *)buf;
iphdrlen = (iphdr->h_len) * 4 ;
//头部占几个节字节
// 报文太短
if (bytes < iphdrlen + ICMP_MIN) {
printf("Too few bytes from
%s\n",inet_ntoa(from->sin_addr));
}
//找到ICMP数据包开始的地方
icmphdr = (IcmpHeader*)(buf + iphdrlen);
// 是否是回复报文类型
if (icmphdr->i_type != ICMP_ECHOREPLY) {
fprintf(stderr,"non-echo type %d
recvd\n",icmphdr->i_type);
return 1;
}
//是不是发给本程序的数据包
if (icmphdr->i_id != (USHORT)GetCurrentProcessId())
{
fprintf(stderr,"someone else's packet!\n");
return 1;
}
temptime=GetTickCount()-icmphdr->timestamp;
printf("Reply from
%s:", inet_ntoa(from->sin_addr));
printf(" bytes=%d ", bytes);
printf(" time= %d ms ", temptime); //发送到接收过程的经历的时间
printf(" TTL=%d
",iphdr->ttl);
printf(" icmp_seq = %d
",icmphdr->i_seq);
printf("\n");
return 0;
}
//计算校验和
USHORT 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数据包
void 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,17, datasize - sizeof(IcmpHeader));
}
void PX(int *input, int inputlen, int
*output)
{
int I;
int
min=input[0];
int max=input[0];
int
Num=input[0];
for(I=1;
I
{
if(min
> input[I])
{
min
= input[I];
}
if(max
< input[I])
{
max
= input[I];
}
Num = Num +
input[I];
}
output[0] =
max;
output[1] = min;
output[2] =
(Num/inputlen);
}
五、ping程序运行结果
带有发送次数参数的操作: