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 }