Linux网络编程---ICMP协议分析及ping程序实现
来源:互联网 发布:问答软件名字 编辑:程序博客网 时间:2024/03/28 17:57
一、IP协议
IP协议是TCP/IP协议族所依赖的传送机制,提供无连接不可靠的数据报服务。IP的无连接特性意味着每个IP报文都是独立寻径的,因此当一个源主机发送多个报文给同一目的主机时,这些报文可能出现错序,丢失或者部分报文产生错误等现象,因此为了保证数据传送的可靠性,必须在IP层之上通过TCP协议提供有序,带确认数据的传输服务。
1.IP协议格式
IP报文由报文头部和数据两部分构成,其中头部信息格式如下图所示,头部占20-60个字节,无选项option时,头部为20字节,最多可以携带40字节选项,报文最大长度为65535字节。
(1)版本(version) 4比特,定义了当前IP协议的版本,目前通常是数字4,即IPV4
(2)头部长度(ihl) 4比特,按4字节单位定义IP报文的头部总长度,因此未携带任何选项的IP报文头部长度为20字节,则ihl值为5(5*4=20),当选项长度达到最大值40字节时,ihl长度为15 (15*4=60)。
(3)服务类型(tos) 8比特,用于指示路由器将如何处理IP报文
(4)总长度(tot_len)16比特,报文头部加数据的总长度,IP报文携带的上层数据长度为:数据长度=总长度-头部长度=总长度-(ihl*4)。之所以需要总长度这个字段,是因为在某些情况下底层协议为了满足最小帧长的限制,会添加填充数据,例如以太协议要求每个数据帧最小必须为46字节,当来自上层的IP报文总长度小于46字节时,将添加填充数据以满足最小帧长,于是必须通过总长度这个字段来记录实际IP层报文的总长度,参考如图所示:
(5)报文标识(id) 16比特,用于标识多个IP分段所对应的原始IP分组的ID。
(6)分段标识(frag)3比特,用于声明一个IP报文是否是某个原始报文的分段,或者声明是否允许一个IP原始报文被分段。
(7)分段偏移(offset) 13比特,标识一个IP分段的数据在原始IP报文中的偏移值,注意该值,必须是8的整数倍。
(8)生存时间(ttl) 8比特, 一个IP报文在网上所允许的最大生存时间,该值实际为最大跳数,当源主机产生一个IP报文后,该字段将填写一个初始值,随后该报文每经过一个路由器则路由器将对该字段值进行减一操作,当该字段值变成0后,路由器将丢弃此报文。
(9)协议(protocol) 8比特,用于标识IP报文承载的上层数据的协议类型,例如可以是TCP,UDP,ICMP和IGMP等。
(10)头部校验和(check) 16比特,IP头部数据的检验和。
(11)源地址(saddr) 32比特,报文的源IP地址。
(12)目的地址(daddr)32比特,报文的目的IP地址。
(13)选项(option) 变长且最大不超过40字节。
2.IP协议头的c语言定义
struct iphdr{#if defined _LITTLE_ENDIAN_BITFIELD //小端机 u8 hlen:4,ver: 4;#elif defined _BIG_ENFIAN_BITFELD //大端机 u8 ver:4,hlen:4;#endif u8 tos; u16 tot_len; u16 id; u16 frag_off; u8 ttl; u8 protocol; u16 check; u32 saddr; u32 daddr;};
二、ICMP协议
(1)ICMP消息类型
ICMP消息分为两大类,错误报告消息和查询消息,这里仅介绍查询消息,每个查询消息类型均包括一对请求和应答消息。
(2)ICMP消息通用格式
ICMP消息包括8字节的头部和变长数据两个部分,其中所有消息类型头部的前4个字节均相同,头部其余4个字节随消息的不同而不同。如图所示:
ICMP消息头部的头4个字节分别是消息类型tye,消息代码code和校验和checksum,其中checksum字段包括头部和数据两部分,而并非仅头部,查询消息的数据部分data包含了用于查询所需要的额外数据。
(3)ICMP查询请求和应答消息格式
ICMP回应请求(echo-request)和应答消息(echo-reply)用于诊断两个系统(主机或路由器)之间是否能够进行通信,当其中一方发送回应请求消息给另一方时,接收到回应请求消息的主机或者路由器将以应答消息进行应答,常用的网络ping命令就是基于此消息类型的,如下图所示 其中type字段为8表示回应请求,0表示应答,code字段暂未是要你管,为0.
(4)ICMP消息格式的C语言定义
struct icmphdr{ u8 type; u8 code; u16 checksum; union { struct { u16 id; u16 sequence; }echo; u32 gateway; struct { u16 unused; u16 mtu; }frag; //pmtu发现 }un; //u32 icmp_timestamp[2];//时间戳 //ICMP数据占位符 u8 data[0];#define icmp_id un.echo.id#define icmp_seq un.echo.sequence};
ping程序实现:
#include<stdio.h>#include<stdlib.h>#include<sys/time.h>#include<unistd.h> #include<string.h>#include<sys/socket.h>#include<sys/types.h>#include<netdb.h>#include<errno.h>#include<arpa/inet.h>#include<signal.h>#include<netinet/in.h>#ifndef _LITTLE_ENDIAN_BITFIELD#define _LITTLE_ENDIAN_BITFIELD#endif#define IP_HSIZE sizeof(struct iphdr) //定义IP_HSIZE为ip头部长度#define IPVERSION 4 //定义IPVERSION为4,指出用ipv4#define ICMP_ECHOREPLY 0 //Echo应答#define ICMP_ECHO 8 //Echo请求#define BUFSIZE 1500 //发送缓存最大值#define DEFAULT_LEN 56 //ping 消息数据默认大小//数据类型别名typedef unsigned char u8;typedef unsigned short u16;typedef unsigned int u32;//ICMP消息头部struct icmphdr{ u8 type; u8 code; u16 checksum; union { struct { u16 id; u16 sequence; }echo; u32 gateway; struct { u16 unused; u16 mtu; }frag; //pmtu发现 }un; u32 icmp_timestamp[2];//时间戳 //ICMP数据占位符 u8 data[0];#define icmp_id un.echo.id#define icmp_seq un.echo.sequence};#define ICMP_HSIZE sizeof(struct icmphdr)struct iphdr{#if defined _LITTLE_ENDIAN_BITFIELD u8 hlen:4,ver: 4;#elif defined _BIG_ENFIAN_BITFELD u8 ver:4,hlen:4;#endif u8 tos; u16 tot_len; u16 id; u16 frag_off; u8 ttl; u8 protocol; u16 check; u32 saddr; u32 daddr;};char hello[]="hello this is a ping test.";char *hostname; //被ping的主机int datalen=DEFAULT_LEN;//ICMP消息携带的数据长度char sendbuf[BUFSIZE];char recvbuf[BUFSIZE];int nsent;//发送的ICMP消息序号int nrecv;pid_t pid;//ping程序的进程pidstruct timeval recvtime; //收到ICMP应答的时间戳int sockfd; //发送和接收原始套接字struct sockaddr_in dest;//被ping主机的ipstruct sockaddr_in from;//发送ping应答消息的主机ipstruct sigaction act_alarm;struct sigaction act_int;//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒struct itimerval val_alarm;//函数原型void alarm_handler(int);//SIGALRM处理程序void int_handler(int);//SIGINT处理程序void set_sighandler();//设置信号处理程序void send_ping();//发送ping消息void recv_reply();//接收ping应答u16 checksum(u8 *buf,int len);//计算校验和int handle_pkt();//ICMP应答消息处理void get_statistics(int ,int);//统计ping命令的检测结果void bail(const char *);//错误报告int main(int argc,char **argv) //argc表示隐形程序命令行中参数的数目,argv是一个指向字符串数组指针,其中每一个字符对应一个参数{ val_alarm.it_interval.tv_sec = 1; val_alarm .it_interval.tv_usec=0; val_alarm .it_value.tv_sec=0; val_alarm .it_value.tv_usec=1; struct hostent *host; //该结构体属于include<netdb.h> int on =1; if((host=gethostbyname(argv[1]))==NULL) { //gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针, perror("can not understand the host name"); //理解不了输入的地址 exit(1); } hostname=argv[1];//取出地址名 memset(&dest,0,sizeof dest); //将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最大内存清零 dest.sin_family=PF_INET; //PF_INET为IPV4,internet协议,在<netinet/in.h>中,地址族 dest.sin_port=ntohs(0); //端口号,ntohs()返回一个以主机字节顺序表达的数。 dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];//host->h_addr_list[0]是地址的指针.返回IP地址,初始化 if((sockfd = socket(PF_INET,SOCK_RAW,IPPROTO_ICMP))<0) { //PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使用协议,调用socket函数来创建一个能够进行网络通信的套接字。这里判断是否创建成功 perror("raw socket created error"); exit(1); } setuid(getuid());//getuid()函数返回一个调用程序的真实用户ID,setuid()是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。 pid=getpid(); //getpid函数用来取得目前进程的进程识别码 printf("PID:%d\n",pid); set_sighandler();//对信号处理 printf("Ping %s(%s): %d bytes data in ICMP packets.\n",argv[1],inet_ntoa(dest.sin_addr),datalen); if((setitimer(ITIMER_REAL,&val_alarm,NULL))==-1) //定时函数 bail("setitimer fails."); recv_reply();//接收ping应答 return 0;}//发送ping消息void send_ping(){ struct iphdr *ip_hdr; //iphdr为IP头部结构体 struct icmphdr *icmp_hdr; //icmphdr为ICMP头部结构体 int len; int len1; icmp_hdr=(struct icmphdr *)(sendbuf); //字符串指针 icmp_hdr->type=ICMP_ECHO; //初始化ICMP消息类型type icmp_hdr->code=0; //初始化消息代码code icmp_hdr->icmp_id=pid; //把进程标识码初始给icmp_id icmp_hdr->icmp_seq=nsent++; //发送的ICMP消息序号赋值给icmp序号 gettimeofday((struct timeval *)icmp_hdr->icmp_timestamp,NULL); // 获取当前时间 memcpy(icmp_hdr->data, hello, strlen(hello)); len=ICMP_HSIZE+strlen(hello); icmp_hdr->checksum=0; //初始化 icmp_hdr->checksum=checksum((u8 *)icmp_hdr,len); //计算校验和 // printf("The send pack checksum is:0x%x\n",icmp_hdr->checksum); sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); //经socket传送数据}//接收程序发出的ping命令的应答void recv_reply(){ int n; socklen_t len; int errno; n=nrecv=0; len=sizeof(from); //发送ping应答消息的主机IP while(nrecv<4) { if((n=recvfrom(sockfd,recvbuf,sizeof recvbuf,0,(struct sockaddr *)&from,&len))<0) { //经socket接收数据,如果正确接收返回接收到的字节数,失败返回0. if(errno==EINTR) //EINTR表示信号中断 continue; bail("recvfrom error"); } gettimeofday(&recvtime,NULL); //记录收到应答的时间 if(handle_pkt()) //接收到错误的ICMP应答信息 continue; nrecv++; } get_statistics(nsent,nrecv); //统计ping命令的检测结果}//计算校验和u16 checksum(u8 *buf,int len){ u32 sum=0; u16 *cbuf; cbuf=(u16 *)buf; while(len>1) { sum+=*cbuf++; len-=2; } if(len) sum+=*(u8 *)cbuf; sum=(sum>>16)+(sum & 0xffff); sum+=(sum>>16); return ~sum;}//ICMP应答消息处理int handle_pkt(){ struct iphdr *ip; struct icmphdr *icmp; int ip_hlen; u16 ip_datalen; //ip数据长度 double rtt; // 往返时间 struct timeval *sendtime; ip=(struct iphdr *)recvbuf; ip_hlen=ip->hlen << 2; ip_datalen=ntohs(ip->tot_len)-ip_hlen; icmp=(struct icmphdr *)(recvbuf+ip_hlen); u16 sum=(u16)checksum((u8 *)icmp,ip_datalen); // printf("The recv pack checksum is:0x%x\n",sum); if(sum) //计算校验和 return -1; if(icmp->icmp_id!=pid) return -1; if(icmp->type!=ICMP_ECHOREPLY) return -1; sendtime=(struct timeval *)icmp->icmp_timestamp; //发送时间 rtt=((&recvtime)->tv_sec-sendtime->tv_sec)*1000+((&recvtime)->tv_usec-sendtime->tv_usec)/1000.0;// 往返时间 //打印结果 printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n", ip_datalen, //IP数据长度 inet_ntoa(from.sin_addr), //目的ip地址 icmp->icmp_seq, //icmp报文序列号 ip->ttl, //生存时间 rtt); //往返时间 return 0;}//设置信号处理程序void set_sighandler(){ act_alarm.sa_handler=alarm_handler; if(sigaction(SIGALRM,&act_alarm,NULL)==-1) //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。 bail("SIGALRM handler setting fails."); act_int.sa_handler=int_handler; if(sigaction(SIGINT,&act_int,NULL)==-1) bail("SIGALRM handler setting fails.");}//统计ping命令的检测结果void get_statistics(int nsent,int nrecv){ printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); //将网络地址转换成“.”点隔的字符串格式。 printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n", nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);}//错误报告void bail(const char * on_what){ fputs(strerror(errno),stderr); //:向指定的文件写入一个字符串(不写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOR(符号常量,其值为-1)。 fputs(":",stderr); fputs(on_what,stderr); fputc('\n',stderr); //送一个字符到一个流中 exit(1);}//SIGINT(中断信号)处理程序void int_handler(int sig){ get_statistics(nsent,nrecv); //统计ping命令的检测结果 close(sockfd); //关闭网络套接字 exit(1);}//SIGALRM(终止进程)处理程序void alarm_handler(int signo){ send_ping(); //发送ping消息}
程序结果(注意这里的PID):
下面我们在对我们写的ping程序进行抓包分析
这是通过抓包抓到的我们发的ICMP请求包,当然还有ICMP应答报,可以先看看后面的id,和seq,后面我们会再次提到,首先我们对其中一个包展开分析:
这里显示的是IP报文字段,可以看到我们前面所展示的IP报头里面所包含的东西,比较简单,就不一一分析了。
然后我们在来看看我我们ICMP报文:
可以看到,搜下是类型type=8,说明是个ICMP请求,code=0,然后是校验和,接下来是标识符,这里怎么有两个呢,其实一个是大端表示,一个是小端表示的结果,这里的标识符是我们的进程ID,可以看我前面ping的时候输出了进程ID,然后是序列号,可以对照几个包分析,开始序列号(大端和小端表示)是为0,然后一次递增,这是我们在程序里面所设定的。接下来是时间戳,开始的时候icmp结构里面我没有放时间戳,这样系统会放在数据位置,我来我单独放了一个时间戳结构这样把它和data分开,关于时间戳大小我也是抓包分析得出来的,是8个字节。接下来便是我们的数据部分,为了很好的区分数据区域和前面的头,特别把数据部分拿出来分析:
可以看到数据部分恰好是从我们自己定义的数据地方开始的,数据前面就是时间戳,开始调试的时候,数据总是被覆盖,后来才找出了里面的时间戳占了8个字节。所以调整后刚好合适。当然不同的类型和代码会导致后面的结构有些不一样,这需要调整。
上面的代码中ICMP和IP都是我们自己定义的。系统中也有提供相应的结构,我们直接调用就可以了,不过必须先查看里面结构是如何定义的。在我这里,ICMP中的数据是单独定义在外面的,不是一起放在里面的,所以,数据部分需要自己声明一个结构,具体名称可以查看自己系统里面多对应的头文件。
我这上面是这样的:
struct icmp_filter *icmp_data;
注意一下下面几句:
icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE); gettimeofday((struct timeval *)icmp_data,NULL); // 获取当前时间 icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE+sizeof(timeval)); //真正数据地方 memcpy(icmp_data, hello, strlen(hello)); icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE); //恢复数据指针
首先找到数据开始相应的位置,然后在开始的地方放时间戳,然后在找到真正数据开始的地方,写入数据,不要随意移动数据头指针,否则结果会有异常的。
下面是其实现:
#include<stdio.h>#include<stdlib.h>#include<sys/time.h>#include<unistd.h>#include<string.h>#include<sys/socket.h>#include<sys/types.h>#include<netdb.h>#include<errno.h>#include<arpa/inet.h>#include<signal.h>#include<netinet/in.h>#include<linux/ip.h>#include <linux/icmp.h>#ifndef _LITTLE_ENDIAN_BITFIELD#define _LITTLE_ENDIAN_BITFIELD#endif#define IP_HSIZE sizeof(struct iphdr) //定义IP_HSIZE为ip头部长度#define IPVERSION 4 //定义IPVERSION为4,指出用ipv4#define ICMP_HSIZE sizeof(struct icmphdr)#define ICMP_ECHOREPLY 0 //Echo应答#define ICMP_ECHO 8 //Echo请求#define BUFSIZE 1500 //发送缓存最大值#define DEFAULT_LEN 56 //ping 消息数据默认大小//数据类型别名typedef unsigned char u8;typedef unsigned short u16;typedef unsigned int u32;char hello[]="hello this is a ping test.";struct icmp_filter *icmp_data;char *hostname; //被ping的主机int datalen=DEFAULT_LEN;//ICMP消息携带的数据长度char sendbuf[BUFSIZE];char recvbuf[BUFSIZE];int nsent;//发送的ICMP消息序号int nrecv;pid_t pid;//ping程序的进程pidstruct timeval recvtime; //收到ICMP应答的时间戳int sockfd; //发送和接收原始套接字struct sockaddr_in dest;//被ping主机的ipstruct sockaddr_in from;//发送ping应答消息的主机ipstruct sigaction act_alarm;struct sigaction act_int;//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒struct itimerval val_alarm;//函数原型void alarm_handler(int);//SIGALRM处理程序void int_handler(int);//SIGINT处理程序void set_sighandler();//设置信号处理程序void send_ping();//发送ping消息void recv_reply();//接收ping应答u16 checksum(u8 *buf,int len);//计算校验和int handle_pkt();//ICMP应答消息处理void get_statistics(int ,int);//统计ping命令的检测结果void bail(const char *);//错误报告int main(int argc,char **argv) //argc表示隐形程序命令行中参数的数目,argv是一个指向字符串数组指针,其中每一个字符对应一个参数{ val_alarm.it_interval.tv_sec = 1; val_alarm .it_interval.tv_usec=0; val_alarm .it_value.tv_sec=0; val_alarm .it_value.tv_usec=1; struct hostent *host; //该结构体属于include<netdb.h> int on =1; if(argc<2){ //判断是否输入了地址 printf("Usage: %s hostname\n",argv[0]); exit(1); } if((host=gethostbyname(argv[1]))==NULL) { //gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针, perror("can not understand the host name"); //理解不了输入的地址 exit(1); } hostname=argv[1];//取出地址名 memset(&dest,0,sizeof dest); //将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最大内存清零 dest.sin_family=PF_INET; //PF_INET为IPV4,internet协议,在<netinet/in.h>中,地址族 dest.sin_port=ntohs(0); //端口号,ntohs()返回一个以主机字节顺序表达的数。 dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];//host->h_addr_list[0]是地址的指针.返回IP地址,初始化 if((sockfd = socket(PF_INET,SOCK_RAW,IPPROTO_ICMP))<0) { //PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使用协议,调用socket函数来创建一个能够进行网络通信的套接字。这里判断是否创建成功 perror("raw socket created error"); exit(1); } setuid(getuid());//getuid()函数返回一个调用程序的真实用户ID,setuid()是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。 pid=getpid(); //getpid函数用来取得目前进程的进程识别码 printf("PID:%d\n",pid); set_sighandler();//对信号处理 printf("Ping %s(%s): %d bytes data in ICMP packets.\n",argv[1],inet_ntoa(dest.sin_addr),datalen); if((setitimer(ITIMER_REAL,&val_alarm,NULL))==-1) //定时函数 bail("setitimer fails."); recv_reply();//接收ping应答 return 0;}//发送ping消息void send_ping(){ struct ip *ip_hdr; //iphdr为IP头部结构体 struct icmphdr *icmp_hdr; //icmphdr为ICMP头部结构体 int len; int len1; icmp_hdr=(struct icmphdr *)(sendbuf); //字符串指针 icmp_hdr->type=ICMP_ECHO;//初始化ICMP消息类型type icmp_hdr->code=0; //初始化消息代码code icmp_hdr->un.echo.id=pid; //把进程标识码初始给icmp_id icmp_hdr->un.echo.sequence=nsent++; //发送的ICMP消息序号赋值给icmp序号 icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE); gettimeofday((struct timeval *)icmp_data,NULL); // 获取当前时间 icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE+sizeof(timeval)); //真正数据地方 memcpy(icmp_data, hello, strlen(hello)); icmp_data=(struct icmp_filter*)(sendbuf+ICMP_HSIZE); //恢复数据指针 len=ICMP_HSIZE+sizeof(icmp_filter)+strlen(hello); icmp_hdr->checksum=0; //初始化 icmp_hdr->checksum=checksum((u8 *)icmp_hdr,len); //计算校验和 printf("The send pack checksum is:0x%x\n",icmp_hdr->checksum); sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); //经socket传送数据}//接收程序发出的ping命令的应答void recv_reply(){ int n; socklen_t len; int errno; n=nrecv=0; len=sizeof(from); //发送ping应答消息的主机IP while(nrecv<4) { if((n=recvfrom(sockfd,recvbuf,sizeof recvbuf,0,(struct sockaddr *)&from,&len))<0) { //经socket接收数据,如果正确接收返回接收到的字节数,失败返回0. if(errno==EINTR) //EINTR表示信号中断 continue; bail("recvfrom error"); } gettimeofday(&recvtime,NULL); //记录收到应答的时间 if(handle_pkt()) //接收到错误的ICMP应答信息 continue; nrecv++; } get_statistics(nsent,nrecv); //统计ping命令的检测结果}//计算校验和u16 checksum(u8 *buf,int len){ u32 sum=0; u16 *cbuf; cbuf=(u16 *)buf; while(len>1) { sum+=*cbuf++; len-=2; } if(len) sum+=*(u8 *)cbuf; sum=(sum>>16)+(sum & 0xffff); sum+=(sum>>16); return ~sum;}//ICMP应答消息处理int handle_pkt(){ struct iphdr *ip; struct icmphdr *icmp; int ip_hlen; u16 ip_datalen; //ip数据长度 double rtt; // 往返时间 struct timeval *sendtime; ip=(struct iphdr *)recvbuf; ip_hlen=ip->ihl << 2; ip_datalen=ntohs(ip->tot_len)-ip_hlen; icmp=(struct icmphdr *)(recvbuf+ip_hlen); u16 sum=(u16)checksum((u8 *)icmp,ip_datalen); printf("The recv pack checksum is:0x%x\n",sum); if(sum) //计算校验和 return -1; if(icmp->un.echo.id!=pid) return -1; if(icmp->type!=ICMP_ECHOREPLY) return -1; sendtime=(struct timeval *)icmp_data; //发送时间 rtt=((&recvtime)->tv_sec-sendtime->tv_sec)*1000+((&recvtime)->tv_usec-sendtime->tv_usec)/1000.0;// 往返时间 //打印结果 printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n", ip_datalen, //IP数据长度 inet_ntoa(from.sin_addr), //目的ip地址 icmp->un.echo.sequence, //icmp报文序列号 ip->ttl, //生存时间 rtt); //往返时间 return 0;}//设置信号处理程序void set_sighandler(){ act_alarm.sa_handler=alarm_handler; if(sigaction(SIGALRM,&act_alarm,NULL)==-1) //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。 bail("SIGALRM handler setting fails."); act_int.sa_handler=int_handler; if(sigaction(SIGINT,&act_int,NULL)==-1) bail("SIGALRM handler setting fails.");}//统计ping命令的检测结果void get_statistics(int nsent,int nrecv){ printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); //将网络地址转换成“.”点隔的字符串格式。 printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n", nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);}//错误报告void bail(const char * on_what){ fputs(strerror(errno),stderr); //:向指定的文件写入一个字符串(不写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOR(符号常量,其值为-1)。 fputs(":",stderr); fputs(on_what,stderr); fputc('\n',stderr); //送一个字符到一个流中 exit(1);}//SIGINT(中断信号)处理程序void int_handler(int sig){ get_statistics(nsent,nrecv); //统计ping命令的检测结果 close(sockfd); //关闭网络套接字 exit(1);}//SIGALRM(终止进程)处理程序void alarm_handler(int signo){ send_ping(); //发送ping消息 }
测试平台:Ubuntu 14.04 +gcc 4.8.4
下面是改良版,删除了没必要的东西,重写了部分代码,看着要好很多,也很好理解:
#include<stdio.h>#include<stdlib.h>#include<sys/time.h>#include<unistd.h>#include<string.h>#include<sys/socket.h>#include<sys/types.h>#include<netdb.h>#include<errno.h>#include<arpa/inet.h>#include<signal.h>#include<netinet/in.h>#include <netinet/ip.h>#include <netinet/ip_icmp.h>#include <netinet/in_systm.h>#define BUFSIZE 1500 //发送缓存最大值//数据类型别名typedef unsigned char u8;typedef unsigned short u16;typedef unsigned int u32;char hello[]="hello this is a ping test.";char *hostname; //被ping的主机int datalen=56;//ICMP消息携带的数据长度char sendbuf[BUFSIZE];char recvbuf[BUFSIZE];int nsent;//发送的ICMP消息序号int nrecv;pid_t pid;//ping程序的进程pidstruct timeval recvtime; //收到ICMP应答的时间戳int sockfd; //发送和接收原始套接字struct sockaddr_in dest;//被ping主机的ipstruct sockaddr_in from;//发送ping应答消息的主机ipstruct sigaction act_alarm;struct sigaction act_int;//设置的时间是一个结构体,倒计时设置,重复倒时,超时值设为1秒struct itimerval val_alarm;//函数原型void alarm_handler(int);//SIGALRM处理程序void int_handler(int);//SIGINT处理程序void set_sighandler();//设置信号处理程序void send_ping();//发送ping消息void recv_reply();//接收ping应答u16 checksum(u8 *buf,int len);//计算校验和int handle_pkt(int len);//ICMP应答消息处理void get_statistics(int ,int);//统计ping命令的检测结果void bail(const char *);//错误报告int main(int argc,char **argv) //argc表示隐形程序命令行中参数的数目,argv是一个指向字符串数组指针,其中每一个字符对应一个参数{ val_alarm.it_interval.tv_sec = 1; val_alarm .it_interval.tv_usec=0; val_alarm .it_value.tv_sec=0; val_alarm .it_value.tv_usec=1; struct hostent *host; //该结构体属于include<netdb.h> int on =1; if(argc<2){ //判断是否输入了地址 printf("Usage: %s hostname\n",argv[0]); exit(1); } if((host=gethostbyname(argv[1]))==NULL) { //gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的结构指针, perror("can not understand the host name"); //理解不了输入的地址 exit(1); } hostname=argv[1];//取出地址名 memset(&dest,0,sizeof dest); //将dest中前sizeof(dest)个字节替换为0并返回s,此处为初始化,给最大内存清零 dest.sin_family=PF_INET; //PF_INET为IPV4,internet协议,在<netinet/in.h>中,地址族 dest.sin_port=ntohs(0); //端口号,ntohs()返回一个以主机字节顺序表达的数。 dest.sin_addr=*(struct in_addr *)host->h_addr_list[0];//host->h_addr_list[0]是地址的指针.返回IP地址,初始化 if((sockfd = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0) { //PF_INEI套接字协议族,SOCK_RAW套接字类型,IPPROTO_ICMP使用协议,调用socket函数来创建一个能够进行网络通信的套接字。这里判断是否创建成功 perror("raw socket created error"); exit(1); } setuid(getuid());//getuid()函数返回一个调用程序的真实用户ID,setuid()是让普通用户可以以root用户的角色运行只有root帐号才能运行的程序或命令。 pid=getpid(); //getpid函数用来取得目前进程的进程识别码 printf("PID:%d\n",pid); set_sighandler();//对信号处理 printf("Ping %s(%s): %d bytes data in ICMP packets.\n",argv[1],inet_ntoa(dest.sin_addr),datalen); if((setitimer(ITIMER_REAL,&val_alarm,NULL))==-1) //定时函数 bail("setitimer fails."); recv_reply();//接收ping应答 return 0;}//发送ping消息void send_ping(){ struct ip *ip; //ip为IP头部结构体 struct icmp *icmp; //icmp为ICMP头部结构体 int len; int len1; icmp=(struct icmp *)(sendbuf); //字符串指针 icmp->icmp_type=ICMP_ECHO;//初始化ICMP消息类型type icmp->icmp_code=0; //初始化消息代码code icmp->icmp_id=pid; //把进程标识码初始给icmp_id icmp->icmp_seq=nsent++; //发送的ICMP消息序号赋值给icmp序号 gettimeofday((struct timeval *)icmp->icmp_data,NULL); // 获取当前时间 memcpy(icmp->icmp_data+sizeof(timeval), hello, strlen(hello)); len=8+sizeof(timeval)+strlen(hello); printf("%d\n",len); icmp->icmp_cksum=0; //初始化 icmp->icmp_cksum=checksum((u8 *)icmp,len); //计算校验和 sendto(sockfd,sendbuf,len,0,(struct sockaddr *)&dest,sizeof (dest)); //经socket传送数据}//接收程序发出的ping命令的应答void recv_reply(){ int n; socklen_t len; int errno; n=nrecv=0; while(nrecv<4) { if((n=recvfrom(sockfd,recvbuf,sizeof recvbuf,0,(struct sockaddr *)&from,&len))<0) { //经socket接收数据,如果正确接收返回接收到的字节数,失败返回0. if(errno==EINTR) //EINTR表示信号中断 continue; bail("recvfrom error"); } gettimeofday(&recvtime,NULL); //记录收到应答的时间 if(handle_pkt(n)) //接收到错误的ICMP应答信息 continue; nrecv++; } get_statistics(nsent,nrecv); //统计ping命令的检测结果}//计算校验和u16 checksum(u8 *buf,int len){ u32 sum=0; u16 *cbuf; cbuf=(u16 *)buf; while(len>1) { sum+=*cbuf++; len-=2; } if(len) sum+=*(u8 *)cbuf; sum=(sum>>16)+(sum & 0xffff); sum+=(sum>>16); return ~sum;}//ICMP应答消息处理int handle_pkt(int len){ struct ip *ip; struct icmp *icmp; int ip_hlen,icmplen; double rtt; // 往返时间 struct timeval *sendtime; ip=(struct ip *)recvbuf; ip_hlen=ip->ip_hl<< 2; icmp=(struct icmp *)(recvbuf+ip_hlen); icmplen=len-ip_hlen; if(icmp->icmp_id!=pid) return -1; if(icmp->icmp_type!=ICMP_ECHOREPLY) return -1; sendtime=(struct timeval *)icmp->icmp_data; //发送时间 if((recvtime.tv_usec-=sendtime->tv_usec)<0) { recvtime.tv_sec--; recvtime.tv_usec+=1000000; } recvtime.tv_sec-=sendtime->tv_sec; rtt=recvtime.tv_sec*1000.0+recvtime.tv_usec/1000.0;// 往返时间 //打印结果 printf("%d bytes from %s:icmp_seq=%u ttl=%d rtt=%.3f ms\n", icmplen, //icmp数据长度 inet_ntoa(from.sin_addr), //目的ip地址 icmp->icmp_seq, //icmp报文序列号 ip->ip_ttl, //生存时间 rtt); //往返时间 return 0;}//设置信号处理程序void set_sighandler(){ act_alarm.sa_handler=alarm_handler; if(sigaction(SIGALRM,&act_alarm,NULL)==-1) //sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum指所要捕获信号或忽略的信号,&act代表新设置的信号共用体,NULL代表之前设置的信号处理结构体。这里判断对信号的处理是否成功。 bail("SIGALRM handler setting fails."); act_int.sa_handler=int_handler; if(sigaction(SIGINT,&act_int,NULL)==-1) bail("SIGALRM handler setting fails.");}//统计ping命令的检测结果void get_statistics(int nsent,int nrecv){ printf("--- %s ping statistics ---\n",inet_ntoa(dest.sin_addr)); //将网络地址转换成“.”点隔的字符串格式。 printf("%d packets transmitted, %d received, %0.0f%% ""packet loss\n", nsent,nrecv,1.0*(nsent-nrecv)/nsent*100);}//错误报告void bail(const char * on_what){ fputs(strerror(errno),stderr); //:向指定的文件写入一个字符串(不写入字符串结束标记符‘\0’)。成功写入一个字符串后,文件的位置指针会自动后移,函数返回值为0;否则返回EOR(符号常量,其值为-1)。 fputs(":",stderr); fputs(on_what,stderr); fputc('\n',stderr); //送一个字符到一个流中 exit(1);}//SIGINT(中断信号)处理程序void int_handler(int sig){ get_statistics(nsent,nrecv); //统计ping命令的检测结果 close(sockfd); //关闭网络套接字 exit(1);}//SIGALRM(终止进程)处理程序void alarm_handler(int signo){ send_ping(); //发送ping消息 }
- Linux网络编程---ICMP协议分析及ping程序实现
- ICMP 协议及 Ping程序的实现
- ICMP协议及ping程序的实现
- ping程序及ICMP协议程序的实现
- Linux用ICMP协议实现简单Ping网络监测功能
- Linux用ICMP协议实现简单Ping网络监测功能
- Linux用ICMP协议实现简单Ping网络监测功能
- 基于ICMP协议的ping程序实现
- Ping及ICMP协议
- ICMP协议的PING程序
- ping 之icmp 协议分析
- ICMP协议之ping实现
- ICMP协议之ping实现
- ICMP协议之ping实现
- ICMP协议之ping实现
- ICMP协议之ping实现
- ICMP协议之ping实现
- 【网络协议】ICMP协议、Ping、Traceroute
- xUtils 源码解析
- HDU 4433 locker
- Letter Combinations of a Phone Number
- igrimaceV8.0.0 IG (三个插件安装方式)
- 达内学习日志Day20:Java核心API(Map初体验)
- Linux网络编程---ICMP协议分析及ping程序实现
- 数组中未出现的最小正整数
- 二级指针
- SparkSQL之数据源
- AFN文档中文翻译以及简单使用说明
- C#学习之多线程开发技术(九)
- 第6周—项目5 后缀表达式
- Java高级_网络编程
- 纠正对Fragment Transaction BackStack的误解