1、Linux网络编程之原始套接字ping协议实现1.概述PING协议是用来检验本地主机与远程主机是否连接,发送的是ICMP ECHO_REQUEST包。普通的套接字是基于TCP或者是UDP的,无法发送ICMP包,所以必须用原始套接字来实现。PING协议的客户端类型值为8,代码值为0,表示请求。而PING协议的响应端类型值为0,代码值也为0,表示应答. 以太网数据部分的最小值为46字节,而IP首部占20个字节,ICMP的首部占8个字节,所以PING的数据部分至少为4字节。2.实现细节主机端:(1)创建原始套接字 socket(AF_INET,SOCK_RAW,htons(proto),能够直接得到
2、IP包(2)填写ICMP首部和数据部分,即icmp_type(8),icmp_code(0)和icmp_data部分(3)封装后发送ICMP请求包响应端:(1)创建原始套接字socket(AF_INET,SOCK_RAW,htons(proto)(2)填写ICMP首部和数据部分,即icmp_type(0),icmp_code(0),icmp_data(3)发送ICMP响应包主机端收到ICMP响应包之后,即原始的IP包,将收到包的时间减去包的发送时间就可以得到响应时延。3. PING协议的实现例子#include #include #include #include #include #incl
3、ude #include #include #include #include #include #include #include #include /*通过原始套接字发送ICMP回显请求报文来实现ping协议ICMP回显报文的结构struct icmp u_int8_t icmp_type;/消息类型 u_int8_t icmp_code;/消息类型的子码 u_int16_t icmp_cksum;/校验和 union struct ih_idseq/显示数据报 u_int16_t icd_id;/数据报所在进程的ID u_int16_6 icd_seq;/数据报序号ih_idseq;ic
4、mp_hun;#define icmp_id icmp_hun.ih_idseq.icd_id#define icmp_seq icmp_hun.ih_idseq.icd_sequnionu_int8_t id_datai;/数据icmp_dun;#define icmp_data icmp_dun.id_data*/typedef struct pingm_packet struct timeval tv_begin;/发送的时间 struct timeval tv_end;/接收到响应包的时间 short seq;/序号值 int flag;/1表示已经发送但没有接收到回应包,0表示接收到
5、响应包pingm_packet;/保存已经发送包的状态值static pingm_packet pingpacket128;/定义一个包数组static pingm_packet *icmp_findpacket(int seq);static unsigned short icmp_cksum(unsigned char*data,int len);static struct timeval icmp_tvsub(struct timeval end,struct timeval begin);static void icmp_statistics(void);static void icm
6、p_pack(struct icmp*icmph,int seq,struct timeval *tv,int length);static int icmp_unpack(char*buf,int len);static void* icmp_recv(void*argv);static void* icmp_send(void*argv);static void icmp_sigint(int signo);static void icmp_usage();#define K 1024#define BUFFERSIZE 512static unsigned char send_buffB
7、UFFERSIZE;/定义发送缓冲区的大小static unsigned char recv_buff2*K;/定义接收缓冲区的大小,为防止接收端溢出,接收缓冲区稍微大一些static struct sockaddr_in dest;/目的地址static int rawsock=0;/原始套接字描述符static pid_t pid=0;/进程idstatic int alive=0;/是否接收到退出信号static short packet_send=0;/已经发送的数据包数目static short packet_recv=0;/已经接收的数据报数目static char dest_st
8、r80;/目的主机字符串static struct timeval tv_begin,tv_end,tv_interval;/本程序开始发送,结束时间和时间间隔static void icmp_usage()printf(ping aaa.bbb.ccc.dddn);/计算ICMP首部校验和static unsigned short icmp_cksum(unsigned char* data,int len)int sum=0;int odd=len&0x01;unsigned short *value=(unsigned short*)data;while(len&0xfffe) sum+
9、=*(unsigned short*)data; data+=2; len-=2;if(odd) unsigned short tmp=(*data)16)+(sum&0xffff);sum+=(sum16);return sum;/设置ICMP报头static void icmp_pack(struct icmp *icmph,int seq,struct timeval*tv,int length)unsigned char i=0;icmph-icmp_type=ICMP_ECHO;/ICMP回显请求icmph-icmp_code=0;/code为0icmph-icmp_cksum=0;
10、/cksum值icmph-icmp_seq=htons(seq);/数据报的序列号icmph-icmp_id=htons(pid&0xffff);/数据报的IDfor(i=0;iicmp_datai=htons(i);/注意主机字节序转换成网络字节序/发送的数据/计算校验和icmph-icmp_cksum=icmp_cksum(unsigned char*)icmph,length+8);/计算时间差函数static struct timeval icmp_tvsub(struct timeval end,struct timeval begin) struct timeval tv; tv.
11、tv_sec=end.tv_sec-begin.tv_sec; tv.tv_usec=end.tv_usec-begin.tv_usec; if(tv.tv_usec0) tv.tv_sec-; tv.tv_usec+=1000000; return tv;/发送报文static void *icmp_send(void *argv) struct timeval tv; tv.tv_usec=0; tv.tv_sec=1;/每隔一秒发送报文 gettimeofday(&tv_begin,NULL);/保存程序开始发送数据的时间 while(alive) memset(send_buff,0,
12、sizeof(send_buff); int size=0; struct timeval tv; gettimeofday(&tv,NULL);/当前包发送的时间 icmp_pack(struct icmp*)send_buff,packet_send,&tv,203);/packet_send为发送包的序号,发送的数据长度为64个字节,填充ICMP首部信息 size=sendto(rawsock,send_buff,203+8,0,(struct sockaddr*)&dest,sizeof(dest);/dest为ICMP包发送的目的地址 if(sizeseq=packet_send;
13、packet-flag=1; gettimeofday(&packet-tv_begin,NULL); packet_send+;/发送序号1 sleep(1);/寻找一个空闲位置,seq=-1表示空闲位置static pingm_packet* icmp_findpacket(int seq) int i=0; pingm_packet* found=NULL; if(seq=-1) for(i=0;i=0)/查找对应seq的数据包 for(i=0;iip_hl*4;/IP头部长度 icmp=(struct icmp*)(buf+iphdrlen);/ICMP报文的地址 len-=iphdr
14、len;/ICMP报文的长度,ICMP报文至少8个字节 if(lenicmp_type=ICMP_ECHOREPLY)&(icmp-icmp_id=pid) struct timeval tv_interval,tv_recv,tv_send;/在发送数组中查找已经发送的包pingm_packet*packet=icmp_findpacket(ntohs(icmp-icmp_seq);/网络字节序转换成主机字节序if(packet=NULL)return -1;packet-flag=0;/表示已经响应了/本包的发送时间tv_send=packet-tv_begin;/读取收到响应包的时间ge
15、ttimeofday(&tv_recv,NULL);tv_interval=icmp_tvsub(tv_recv,tv_send);/计算往返时延,即RTTrtt=tv_interval.tv_sec*1000+tv_interval.tv_usec/1000;/打印ICMP段长度,源IP,包的序列号,TTL,时间差printf(%d byte from %s:icmp_seq=%u ttl=%d rtt=%d msn,len,inet_ntoa(ip-ip_src),icmp-icmp_seq,ip-ip_ttl,rtt);packet_recv+;/接收包的数量加1elsereturn -
16、1;/接收报文static void *icmp_recv(void*argv)struct timeval tv;tv.tv_usec=200;/轮循时间tv.tv_sec=0;fd_set readfd;while(alive) int ret=0; tv.tv_usec=200;/轮循时间 tv.tv_sec=0; FD_ZERO(&readfd); FD_SET(rawsock,&readfd); ret=select(rawsock+1,&readfd,NULL,NULL,&tv); int fromlen=0; struct sockaddr from; switch(ret)ca
17、se -1:/发生错误break;case 0:/超时/printf(timeoutn);break;default:/收到数据包fromlen=sizeof(from);int size=recvfrom(rawsock,recv_buff,sizeof(recv_buff),0,(struct sockaddr*)&from,&fromlen);/利用原始套接字,原始套接字与IP层网络协议栈核心打交道if(errno=EINTR) perror(recvfrom error);/解包,得到RTTret=icmp_unpack(recv_buff,size);if(ret=-1) conti
18、nue;break;/统计数据结果,成功发送的报文数量,成功接收的报文数量,丢失报文百分比和程序总共运行时间static void icmp_statistics(void) long time=(tv_interval.tv_sec*1000)+(tv_interval.tv_usec/1000); printf(- %s ping statistics -n,dest_str);/目的IP printf(%d packets transmitted, %d recevied, %d%c packet loss, time %d msn,packet_send,packet_recv,(pa
19、cket_send-packet_recv)*100/packet_send,%,time);/信号处理函数static void icmp_sigint(int signo) alive=0;/alive=0程序将会终止 gettimeofday(&tv_end,NULL);/程序结束时间 tv_interval=icmp_tvsub(tv_end,tv_begin);/计算程序一共运行了多长时间 return;/主函数实现int main(int argc,char*argv) struct hostent *host=NULL; struct protoent*protocol=NULL
20、; char protoname=icmp; unsigned long inaddr=1; int size=128*K; int ret;if(argcp_proto);if(rawsockh_addr,host-h_length);elsememcpy(char*)&dest.sin_addr,&inaddr,sizeof(inaddr);inaddr=dest.sin_addr.s_addr;/由于是ICMP不涉及到端口绑定printf(PING %s (%d.%d.%d.%d) 56(84)bytes of data.n,dest_str,(inaddr&0x000000FF)0,(
21、inaddr&0x0000FF00)8,(inaddr&0x00FF0000)16,(inaddr&0xFF000000)24);signal(SIGINT,icmp_sigint);alive=1;/定义两个线程,分别用于发送数据与接收数据pthread_t send_id,recv_id;int err=0;err=pthread_create(&send_id,NULL,icmp_send,NULL);if(err0) return -1;err=pthread_create(&recv_id,NULL,icmp_recv,NULL);if(err0) return -1;pthread
22、_join(send_id,NULL);/等待子线程结束sendpthread_join(recv_id,NULL);/等待子线程的结束recvclose(rawsock);icmp_statistics();return 0;运行结果:PING 222.27.253.1 (222.27.253.1) 56(84)bytes of data.60 byte from 222.27.253.1:icmp_seq=0 ttl=255 rtt=5 ms60 byte from 222.27.253.1:icmp_seq=1 ttl=255 rtt=12 ms60 byte from 222.27.2
23、53.1:icmp_seq=2 ttl=255 rtt=5 ms60 byte from 222.27.253.1:icmp_seq=3 ttl=255 rtt=7 ms60 byte from 222.27.253.1:icmp_seq=4 ttl=255 rtt=2 ms60 byte from 222.27.253.1:icmp_seq=5 ttl=255 rtt=23 ms60 byte from 222.27.253.1:icmp_seq=6 ttl=255 rtt=27 ms60 byte from 222.27.253.1:icmp_seq=7 ttl=255 rtt=10 ms60 byte from 222.27.253.1:icmp_seq=8 ttl=255 rtt=17 ms60 byte from 222.27.253.1:icmp_seq
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1