c语言实现icmp协议ping命令,利用ICMP协议实现ping命令

一、实现原理

ping利用ICMP协议包来侦测另一个主机是否可达。Ping的原理是使用了类型码为8的ICMP回送请求包,收到请求的主机则用类型码为0的ICMP回应报文。如果应答包和请求包的标示号、序号和内容相同的话,则证明能够ping通,网络设备可用。ping程序计算间隔时间,并计算有多少个包被送达,用户就可以判断网络大致的情况。

系统提供的Ping还列出来了传送的时间和TTL等数据,命令提供了多种参数。下面的程序用ICMP协议实现了简单的Ping功能。由于用的是ICMP协议,因此,我们不能够通过建立一个SOCK_STREAM或SOCK_DGRAM来发送这个包,只能够使用SOCK_RAW来自己构造数据包。

二、ping程序的工作流程

a4c26d1e5885305701be709a3d33442f.png

三、主要函数和数据结构

//定义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程序运行结果

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png

带有发送次数参数的操作:

a4c26d1e5885305701be709a3d33442f.png

你可能感兴趣的:(c语言实现icmp协议ping命令,利用ICMP协议实现ping命令)