debian下ping命令向目的主机发送ICMP报文的实现代码

  1 #include <sys/socket.h>
  2 #include <netinet/in.h>
  3 #include <netinet/ip.h>
  4 #include <netinet/ip_icmp.h>
  5 #include <unistd.h>
  6 #include <unistd.h>
  7 #include <signal.h>
  8 #include <arpa/inet.h>
  9 #include <errno.h>
 10 #include <sys/time.h>
 11 #include <stdio.h>
 12 #include <string.h>
 13 #include <netdb.h>
 14 #include <pthread.h>
 15 
 16 //保存已经发送包的状态值
 17 typedef struct pingm_packet
 18 {
 19     struct timeval tv_begin;        //发送的时间
 20     struct timeval tv_end;          //接收到的时间
 21     short seq;                      //序列号
 22     int flag;   //1表示已经发送,但没有接收到回应的包,0表示接收到回应的包
 23 }pingm_packet;
 24 static pingm_packet pingpacket[128];
 25 //函数声明定义
 26 static pingm_packet *icmp_findpacket(int seq);
 27 static unsigned short icmp_cksum(unsigned char *data,int len);
 28 static struct timeval icmp_tvsub(struct timeval end,struct timeval begin);
 29 static void icmp_statistics(void);
 30 static void icmp_pack(struct icmp *icmph,int seq,struct timeval *tv,int length);
 31 static int icmp_unpack(char* buf,int len);
 32 static void *icmp_recv(void* argv);
 33 static void *icmp_send(void* argv);
 34 static void icmp_sigint(int signo);
 35 static void icmp_usage();

 36 

 37 #define K 1024
 38 #define BUFFERSIZE 72           
 39 //发送缓冲区大小
 40 static unsigned char send_buff[BUFFERSIZE];
 41 static unsigned char recv_buff[2*K];//为防止接收溢出,接收缓冲区设置大一点
 42 static struct sockaddr_in dest;     //目的地址
 43 static int rawsock=0;       //发送和接收线程需要的原始socket描述符
 44 static pid_t pid=0;         //进程PID
 45 static int alive=0;         //是否接收到退出信号
 46 static short packet_send=0; //已经发送的数据包有多少
 47 static short packet_recv=0; //已经接收的数据包有多少
 48 static char dest_str[80];   //目的主机字符串
 49 //本程序开始发送、结束和时间间隔
 50 static struct timeval tv_begin,tv_end,tv_interval;
 51 
 52 static void icmp_usage()
 53 {
 54     //ping加IP地址或者域名
 55     printf("ping aaa.bbb.ccc.ddd\n");
 56 }
 57 
 58 //主程序
 59 int main(int argc,char* argv[])
 60 {
 61     struct hostent *host=NULL;
 62     struct protoent *protocol=NULL;
 63     char protoname[]="icmp";
 64     unsigned long inaddr=1;
 65     int size=128*K;
 66     //参数是否数量正确
 67     if(argc<2)
 68     {
 69         icmp_usage();
 70         return -1;
 71     }
 72     //获取协议类型ICMP
 73     protocol=getprotobyname(protoname);

 74     if(protocol==NULL)
 75     {
 76         perror("getprotobyname()");
 77         return -1;
 78     }
 79     //复制目的地址字符串
 80     memcpy(dest_str,argv[1],strlen(argv[1])+1);
 81     memset(pingpacket,0,sizeof(pingm_packet)*128);
 82     //socket初始化
 83     rawsock=socket(AF_INET,SOCK_RAW,protocol->p_proto);
 84     if(rawsock<0)
 85     {
 86         perror("socket()");
 87         return -1;
 88     }
 89     //为了与其他进程的ping程序区别,加入pid
 90     pid=getuid();
 91     //增大接收缓冲区,防止接收的包被覆盖
 92     setsockopt(rawsock,SOL_SOCKET,SO_RCVBUF,&size,sizeof(size));
 93     bzero(&dest,sizeof(dest));
 94 
 95     //获取目的地址的IP地址
 96     dest.sin_family=AF_INET;
 97     //输入的目的地址为字符串IP地址
 98     inaddr=inet_addr(argv[1]);
 99     if(inaddr==INADDR_ANY)
100     {
101         //输入的是DNS地址
102         host=gethostbyname(argv[1]);
103         if(host==NULL)
104         {
105             perror("gethostbyname()");
106             return -1;
107         }
108         //将地址复制到dest中
109         memcpy((char*)&dest.sin_addr,host->h_addr,host->h_length);
110     }

111     else
112     {
113         memcpy((char*)&dest.sin_addr,&inaddr,sizeof(inaddr));
114     }
115 
116     //打印提示
117     inaddr=dest.sin_addr.s_addr;
118     printf("PING is (%d.%d.%d.%d) 56(84) bytes of data.\n",dest_str,(inaddr&0x000000FF)>>0,(inaddr&0x0000FF00)>>8,(inaddr&0x00FF0000)>>1    6,(inaddr&0xFF000000)>>24);
119 
120     //截取信号SIDINT,将icmp_sigint挂接上
121     signal(SIGINT,icmp_sigint);
122 
123     //初始化为可运行
124     alive=1;
125     //建立两个线程,用于接收和发送
126     pthread_t send_id,recv_id;
127     int err=0;
128     err=pthread_create(&send_id,NULL,icmp_send,NULL);   //发送
129     if(err<0)
130     {
131         return -1;
132     }
133     err=pthread_create(&recv_id,NULL,icmp_recv,NULL);   //接收
134     if(err<0)
135     {
136         return -1;
137     }
138     //等待线程结束
139     pthread_join(send_id,NULL);
140     pthread_join(recv_id,NULL);
141     //清理并打印统计结果
142     close(rawsock);
143     icmp_statistics();
144     return 0;
145 }
146 

147 //CRC16校验和计算icmp_cksum
148 static unsigned short icmp_cksum(unsigned char *data,int len)
149 {
150     int sum=0;      //计算结果 
151     int odd=len&0x01;       //是否为奇数
152     unsigned short *value=(unsigned short*)data;
153     //将数据按照2字节为单位累加起来
154     while(len&0xfffe)
155     {
156         sum+=*(unsigned short*)data;
157         data+=2;
158         len-=2;
159     }
160     //判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一个字节
161     if(odd)
162     {
163         unsigned short tmp=((*data)<<8)&0xff00;
164         sum+=tmp;
165     }
166     sum=(sum>>16)+(sum&0xffff);     //高低位相加
167     sum+=(sum>>16);                 //将溢出位加入
168 
169     return ~sum;                    //返回取反值
170 }
171 
172 //设置ICMP报头
173 static void icmp_pack(struct icmp *icmph,int seq,struct timeval *tv,int length)
174 {
175     unsigned char i=0;
176     //设置报头
177     icmph->icmp_type=ICMP_ECHO;     //ICMP回显请求
178     icmph->icmp_code=0;             //code值为0
179     icmph->icmp_cksum=0;            //先将cksum值填写0,便于之后的cksum计算
180     icmph->icmp_seq=seq;            //本报的序列号
181     icmph->icmp_id=pid&0xffff;      //填写PID
182     for(i=0;i<length;i++)
183         icmph->icmp_data[i]=i;

184     //计算校验和
185     icmph->icmp_cksum=icmp_cksum((unsigned char*)icmph,length);
186 }
187 
188 //解压接收到的包,并打印信息
189 static int icmp_unpack(char* buf,int len)
190 {
191     int i,iphdrlen;
192     struct ip* ip=NULL;
193     struct icmp* icmp=NULL;
194     int rrt;
195     ip=(struct ip*)buf;     //IP头部
196     iphdrlen=ip->ip_hl*4;   //IP头部地址
197     icmp=(struct icmp*)(buf+iphdrlen);      //ICMP段的地址
198     len-=iphdrlen;
199     //判定长度是否为ICMP包
200     if(len<8)
201     {
202         printf("ICMP packets\'s length is less than 8\n");
203         return -1;
204     }
205     //ICMP类型为ICMP_ECHOREPLY并且为本进程的PID
206     if((icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid))
207     {
208         struct timeval tv_internel,tv_recv,tv_send;
209         //在发送表格中查找已经发送的包,按照seq
210         pingm_packet* packet=icmp_findpacket(icmp->icmp_seq);
211         if(packet==NULL)
212             return -1;
213         packet->flag=0;     //取消标志
214         tv_send=packet->tv_begin;       //获取本包的发送时间
215         gettimeofday(&tv_recv,NULL);    //读取此时间,计算时间差
216         tv_internel=icmp_tvsub(tv_recv,tv_send);
217         rrt=tv_internel.tv_sec*1000+tv_internel.tv_usec/1000;
218         //打印结果,包含ICMP段长度,源IP地址,包的序列号,TTL,时间差
219         printf("%d byte from %s :icmp_seq=%u ttl=%d rrt=%d ms\n",len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rrt);
220         packet_recv++;      //接收包数量加1

221     }
222     else
223     {
224         return -1;
225     }
226 }
227 
228 //计算时间差icmp_tvsub(),end接收到的时间,begin开始发送的时间
229 static struct timeval icmp_tvsub(struct timeval end,struct timeval begin)
230 {
231     struct timeval tv;
232     //计算差值
233     tv.tv_sec=end.tv_sec-begin.tv_sec;
234     tv.tv_usec=end.tv_usec-begin.tv_usec;
235     //如果接收时间的usec值小于发送的usec值,从sec域借位
236     if(tv.tv_usec<0)
237     {
238         tv.tv_sec--;
239         tv.tv_usec=tv.tv_usec+1000000;
240     }
241     return tv;
242 }
243 
244 //发送ICMP回显请求包
245 static void* icmp_send(void* argv)
246 {
247     struct timeval tv;
248     tv.tv_usec=0;
249     tv.tv_sec=1;
250     //保存程序开始发送数据的时间
251     gettimeofday(&tv_begin,NULL);
252     while(alive)
253     {
254         int size=0;
255         struct timeval tv;
256         gettimeofday(&tv,NULL);     //当前包的发送时间
257         icmp_pack((struct icmp*)send_buff,packet_send,&tv,64);  //打包数据

258         size=sendto(rawsock,send_buff,64,0,(struct sockaddr*)&dest,sizeof(dest));       //发送给目标地址
259         if(size<0)
260         {
261             perror("sendto error");
262             continue;
263         }
264         else
265         {
266             //在发送包状态数组中找一个空闲位置
267             pingm_packet* packet=icmp_findpacket(-1);
268             if(packet)
269             {
270                 packet->seq=packet_send;        //设置seq
271                 packet->flag=1;             //已经使用
272                 gettimeofday(&packet->tv_begin,NULL);       //发送时间
273                 packet_send++;
274             }
275         }
276         //每隔1s,发送一个ICMP回显请求
277         sleep(1);
278     }
279 }
280 
281 //接收ping目的主机的回复
282 static void* icmp_recv(void* argv)
283 {
284     //轮询等待时间
285     struct timeval tv;
286     tv.tv_usec=200;
287     tv.tv_sec=0;
288     fd_set readfd;
289     //当没有信号发出,一直接收数据
290     while(alive)
291     {
292         int ret=0;
293         FD_ZERO(&readfd);
294         FD_SET(rawsock,&readfd);

295         ret=select(rawsock+1,&readfd,NULL,NULL,&tv);
296         switch(ret)
297         {
298             case -1:
299                 break;      //错误发生
300             case 0:
301                 break;      //超时
302             default:
303             {
304                 //收到一个包
305                 int fromlen=0;
306                 struct sockaddr from;
307                 //接收数据
308                 int size=recv(rawsock,recv_buff,sizeof(recv_buff),0);
309                 if(errno==EINTR)
310                 {
311                     perror("recvfrom error");
312                     continue;
313                 }
314                 //解包,并设置相关变量
315                 ret=icmp_unpack(recv_buff,size);
316                 if(ret==-1)
317                 {
318                     continue;
319                 }
320             }
321             break;
322         }
323     }
324 }
325 
326 //经典信号处理函数SIGINT
327 static void icmp_sigint(int signo)
328 {
329     alive=0;        //告诉接收和发送线程结束程序
330     gettimeofday(&tv_end,NULL);     //读取程序结束时间
331     tv_interval=icmp_tvsub(tv_end,tv_begin);    //计算以下总共所用时间

332     return ;
333 }
334 
335 //查找数组中的标示函数icmp_findpacket()
336 //查找一个合适的包位置,seq为-1时,表示查找空包,其他值表示查找seq对应的包
337 static pingm_packet* icmp_findpacket(int seq)
338 {
339     int i=0;
340     pingm_packet* found=NULL;
341     //查找包的位置
342     if(seq==-1)             //查找空包的位置
343     {
344         for(i=0;i<128;i++)
345         {
346             if(pingpacket[i].flag==0)
347             {
348                 found=&pingpacket[i];
349                 break;
350             }
351         }
352     }
353     else if(seq>=0)     //查找对应的seq包
354     {
355         for(i=0;i<128;i++)
356         {
357             if(pingpacket[i].seq==seq)
358             {
359                 found=&pingpacket[i];
360                 break;
361             }
362         }
363     }
364     return found;
365 }
366 
367 //打印全部ICMP发送接收统计结果
368 static void icmp_statistics(void)

369{
370     long time=(tv_interval.tv_sec*1000)+(tv_interval.tv_usec/1000);
371     printf("--- %s ping statistics ---\n",dest_str);    //目的IP地址
372     printf("%d packets transmitted,%d received, %d%c packet loss,time %d ms\n",packet_send,packet_recv,(packet_send-packet_recv)*100/pac    ket_send,'%',time);     //发送包数、接收包数、丢失百分比、时间
373 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值