c linux ping 实现
摘自:https://blog.csdn.net/weibo1230123/article/details/79891018
ping的实现和代码分析
一.介绍
ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命令的工作原理是:
向网络上的另一个主机系统发送ICMP报文,如果指定系统得到了报文,它将把报文一模一样地传回给发送者,这有点象潜水艇声纳系统中使用的发声装置。 例如,在Linux终端上执行ping如下:
二.分析
由上面的执行结果可以看到,ping命令执行后显示出被测试系统主机名和相应IP地址、返回给当前主机的ICMP报文顺序号、ttl生存时间和往返时间rtt(单位是毫秒,即千分之一秒)。要写一个模拟ping命令,这些信息有启示作用。要真正了解ping命令实现原理,就要了解ping命令所使用到的TCP/IP协议。 ICMP(Internet Control Message,网际控制报文协议)是为网关和目标主机而提供的一种差错控制机制,使它们在遇到差错时能把错误报告给报文源发方。ICMP协议是IP层的一个协议,但是由于差错报告在发送给报文源发方时可能也要经过若干子网,因此牵涉到路由选择等问题,所以ICMP报文需通过IP协议来发送。ICMP数据报的数据发送前需要两级封装:首先添加ICMP报头形成ICMP报文,再添加IP报头形成IP数据报。由于IP层协议是一种点对点的协议,而非端对端的协议,它提供无连接的数据报服务,没有端口的概念,因此很少使用bind()和connect()函数,若有使用也只是用于设置IP地址。明白了工作原理,我们就可以写我们自己的ping命令:myping:
代码1:
1 /* 2 * 名称: myping 3 * 程序应用: ping命令是向目的主机发送ICMP报文,检验本地主机和远程的目的主机是否连接 4 * 5 * 6 */ /*ICMP必须使用原始套接字进行设计,要手动设置IP的头部和ICMP的头部并行校验*/ 7 /***********主函数********************************************* 8 myping.c*/ 9 #include <sys/socket.h> 10 #include <netinet/in.h> 11 #include <netinet/ip.h> 12 #include <netinet/ip_icmp.h> 13 #include <unistd.h> 14 #include <signal.h> 15 #include <arpa/inet.h> 16 #include <errno.h> 17 #include <sys/time.h> 18 #include <stdio.h> 19 #include <string.h> /* bzero */ 20 #include <netdb.h> 21 #include <pthread.h> 22 //保存发送包的状态值 23 typedef struct pingm_pakcet{ 24 struct timeval tv_begin; //发送时间 25 struct timeval tv_end; //接收到的时间 26 short seq; //序列号 27 int flag; //1,表示已经发送但是没有接收到回应,0,表示接收到回应 28 }pingm_pakcet; 29 static pingm_pakcet *icmp_findpacket(int seq); 30 static unsigned short icmp_cksum(unsigned char *data, int len); 31 static struct timeval icmp_tvsub(struct timeval end, struct timeval begin); 32 static void icmp_statistics(void); 33 static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv,int length); 34 static int icmp_unpack(char *buf,int len); 35 static void *icmp_recv(void *argv); 36 static void icmp_sigint(int signo); 37 static void icmp_usage(); 38 static pingm_pakcet pingpacket[128]; 39 #define K 1024 40 #define BUFFERSIZE 72 //发送缓冲区的大小 41 static unsigned char send_buff[BUFFERSIZE]; 42 static unsigned char recv_buff[2*K]; //防止接收溢出,设置大一些 43 static struct sockaddr_in dest; //目的地址 44 static int rawsock = 0; //发送和接收线程需要的socket描述符 45 static pid_t pid; //进程PID 46 static int alive = 0; //是否接收到退出信号 47 static short packet_send = 0; //已经发送的数据包数量 48 static short packet_recv = 0; //已经接收的数据包数量 49 static char dest_str[80]; //目的主机字符串 50 static struct timeval tv_begin, tv_end, tv_interval; 51 52 //2.计算发送和接收的时间 53 static void icmp_usage() 54 { 55 //ping加IP地址或者域名 56 printf("ping aaa.bbb.ccc.ddd\n"); 57 } 58 /*终端信号处理函数SIGINT*/ 59 static void icmp_sigint(int signo) 60 { 61 alive = 0; 62 gettimeofday(&tv_end,NULL); 63 tv_interval = icmp_tvsub(tv_end, tv_begin); 64 return; 65 } 66 67 //3.统计数据结果 68 /*统计数据结果函数****************************************** 69 打印全部ICMP发送的接收统计结果*/ 70 static void icmp_statistics(void) 71 { 72 long time = (tv_interval.tv_sec * 1000) + (tv_interval.tv_usec/1000); 73 printf("--- %s ping statistics ---\n", dest_str); 74 printf("%d packets transmitted, %d received, %d%c packet loss, time %ld ms\n", 75 packet_send,packet_recv,(packet_send-packet_recv)*100/packet_send,'%',time); 76 } 77 /*************查找数组中的标识函数*********************** 78 查找合适的包的位置 79 当seq为1时,表示查找空包 80 其他值表示查找seq对应的包*/ 81 static pingm_pakcet *icmp_findpacket(int seq) 82 { 83 int i; 84 pingm_pakcet *found = NULL; 85 //查找包的位置 86 if(seq == -1){ 87 for(i=0;i<128;i++){ 88 if(pingpacket[i].flag == 0){ 89 found = &pingpacket[i]; 90 break; 91 } 92 } 93 } 94 else if(seq >= 0){ 95 for(i =0 ;i< 128;i++){ 96 if(pingpacket[i].seq == seq){ 97 found = &pingpacket[i]; 98 break; 99 } 100 } 101 } 102 return found; 103 } 104 105 //4.校验和函数 106 /*************校验和函数***************************** 107 TCP/IP协议栈使用的校验算法是比较经典的,对16位的数据进行累加计算,并返回计算结果, 108 109 CRC16校验和计算icmp_cksum 110 参数: 111 data:数据 112 len:数据长度 113 返回值: 114 计算结果,short类型 115 */ 116 static unsigned short icmp_cksum(unsigned char *data, int len) 117 { 118 int sum = 0; //计算结果 119 int odd = len & 0x01; //是否为奇数 120 /*将数据按照2字节为单位累加起来*/ 121 while(len & 0xfffe){ 122 sum += *(unsigned short*)data; 123 data += 2; 124 len -= 2; 125 } 126 /*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一个字节*/ 127 if(odd){ 128 unsigned short tmp = ((*data)<<8)&0xff00; 129 sum += tmp; 130 } 131 sum = (sum >> 16) + (sum & 0xffff); //高地位相加 132 sum += (sum >> 16); //将溢出位加入 133 134 return ~sum; //返回取反值 135 } 136 137 //5.ICMP头部校验打包和拆包 138 /**********进行ICMP头部校验********************/ 139 //设置ICMP报头 140 static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length) 141 { 142 unsigned char i = 0; 143 //设置报头 144 icmph->icmp_type = ICMP_ECHO; //ICMP回显请求 145 icmph->icmp_code = 0; //code的值为0 146 icmph->icmp_cksum = 0; //先将cksum的值填为0,便于以后的cksum计算 147 icmph->icmp_seq = seq; //本报的序列号 148 icmph->icmp_id = pid & 0xffff; //填写PID 149 for(i=0; i< length; i++) 150 icmph->icmp_data[i] = i; //计算校验和 151 icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length); 152 } 153 154 /*解压接收到的包,并打印信息*/ 155 static int icmp_unpack(char *buf, int len) 156 { 157 int i,iphdrlen; 158 struct ip *ip = NULL; 159 struct icmp *icmp = NULL; 160 int rtt; 161 162 ip = (struct ip *)buf; //IP报头 163 iphdrlen = ip->ip_hl * 4; //IP头部长度 164 icmp = (struct icmp *)(buf+iphdrlen); //ICMP段的地址 165 len -= iphdrlen; 166 //判断长度是否为ICMP包 167 if(len < 8){ 168 printf("ICMP packets\'s length is less than 8\n"); 169 return -1; 170 } 171 //ICMP类型为ICMP_ECHOREPLY并且为本进程的PID 172 if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)){ 173 struct timeval tv_interval,tv_recv,tv_send; 174 //在发送表格中查找已经发送的包,按照seq 175 pingm_pakcet *packet = icmp_findpacket(icmp->icmp_seq); 176 if(packet == NULL) 177 return -1; 178 packet->flag = 0; //取消标志 179 tv_send = packet->tv_begin; //获取本包的发送时间 180 181 gettimeofday(&tv_recv,NULL); //读取此时间,计算时间差 182 tv_interval = icmp_tvsub(tv_recv,tv_send); 183 rtt = tv_interval.tv_sec * 1000 + tv_interval.tv_usec/1000; 184 /*打印结果包含 185 ICMP段的长度 186 源IP地址 187 包的序列号 188 TTL 189 时间差 190 */ 191 printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n", 192 len,inet_ntoa(ip->ip_src),icmp->icmp_seq,ip->ip_ttl,rtt); 193 packet_recv ++; //接收包数量加1 194 } 195 else { 196 return -1; 197 } 198 } 199 200 //6.计算时间差函数 201 /************计算时间差time_sub************************ 202 参数: 203 end:接收到时间 204 begin:开始发送的时间 205 返回值: 206 使用的时间 207 */ 208 static struct timeval icmp_tvsub(struct timeval end, struct timeval begin) 209 { 210 struct timeval tv; 211 //计算差值 212 tv.tv_sec = end.tv_sec - begin.tv_sec; 213 tv.tv_usec = end.tv_usec - begin.tv_usec; 214 //如果接收的时间的usec值小于发送时的usec,从uesc域借位 215 if(tv.tv_usec < 0){ 216 tv.tv_sec --; 217 tv.tv_usec += 1000000; 218 } 219 220 return tv; 221 } 222 223 //7.发送报文函数 224 //**********发送报文*************************** 225 static void *icmp_send(void *argv) 226 { 227 //保存程序开始发送数据的时间 228 gettimeofday(&tv_begin, NULL); 229 while(alive){ 230 int size = 0; 231 struct timeval tv; 232 gettimeofday(&tv, NULL); //当前包的发送时间 233 //在发送包状态数组中找到一个空闲位置 234 pingm_pakcet *packet = icmp_findpacket(-1); 235 if(packet){ 236 packet->seq = packet_send; 237 packet->flag = 1; 238 gettimeofday(&packet->tv_begin,NULL); 239 } 240 icmp_pack((struct icmp *)send_buff,packet_send,&tv, 64); 241 //打包数据 242 size = sendto(rawsock, send_buff,64,0,(struct sockaddr *)&dest, sizeof(dest)); 243 if(size < 0){ 244 perror("sendto error"); 245 continue; 246 } 247 packet_send ++; 248 //每隔1s发送一个ICMP回显请求包 249 sleep(1); 250 } 251 } 252 253 //8.接收目的主机的回复函数 254 /***********接收ping目的主机的回复***********/ 255 static void *icmp_recv(void *argv) 256 { 257 //轮询等待时间 258 struct timeval tv; 259 tv.tv_usec = 200; 260 tv.tv_sec = 0; 261 fd_set readfd; 262 //当没有信号发出一直接收数据 263 while(alive){ 264 int ret = 0; 265 FD_ZERO(&readfd); 266 FD_SET(rawsock,&readfd); 267 ret = select(rawsock+1,&readfd,NULL,NULL,&tv); 268 switch(ret) 269 { 270 case -1: 271 //错误发生 272 break; 273 case 0: 274 //超时 275 break; 276 default : 277 { 278 //收到一个包 279 int fromlen = 0; 280 struct sockaddr from; 281 //接收数据 282 int size = recv(rawsock,recv_buff,sizeof(recv_buff),0); 283 if(errno == EINTR){ 284 perror("recvfrom error"); 285 continue; 286 } 287 //解包 288 ret = icmp_unpack(recv_buff,size); 289 if(ret == 1){ 290 continue; 291 } 292 } 293 break; 294 } 295 } 296 } 297 298 //9.设置ICMP头部(程序中不需要,这里只是作为了解) 299 /**********设置ICMP发送报文的头部********************************* 300 回显请求的ICMP报文 301 */ 302 /*struct icmp 303 { 304 u_int8_t icmp_type; //消息类型 305 u_int8_t icmp_code; //消息类型的子码 306 u_int16_t icmp_cksum; //校验和 307 union 308 { 309 struct ih_idseq //显示数据报 310 { 311 u_int16_t icd_id; //数据报ID 312 u_int16_t icd_seq; //数据报的序号 313 }ih_idseq; 314 }icmp_hun; 315 #define icmp_id icmp_hun.ih_idseq.icd_id; 316 #define icmp_seq icmp_hun.ih_idseq.icd_seq; 317 union 318 { 319 u_int8_t id_data[1]; //数据 320 }icmp_dun; 321 #define icmp_data icmp_dun.id_data; 322 }; */ 323 324 //10.主函数 325 //主程序 326 int main(int argc, char const *argv[]) 327 { 328 struct hostent *host = NULL; 329 struct protoent *protocol = NULL; 330 char protoname[] = "icmp"; 331 unsigned long inaddr = 1; 332 int size = 128*K; 333 334 if(argc < 2) //参数是否数量正确 335 { 336 icmp_usage(); 337 return -1; 338 } 339 //获取协议类型 340 protocol = getprotobyname(protoname); 341 if(protocol == NULL) 342 { 343 perror("getprotobyname()"); 344 return -1; 345 } 346 //复制目的地址字符串 347 memcpy(dest_str, argv[1],strlen(argv[1])+1); 348 memset(pingpacket, 0, sizeof(pingm_pakcet) * 128); 349 //socket初始化 350 rawsock = socket(AF_INET, SOCK_RAW, protocol->p_proto); 351 if(rawsock < 0){ 352 perror("socket"); 353 return -1; 354 } 355 356 pid = getuid(); //为与其他线程区别,加入pid 357 //增大接收缓冲区,防止接收包被覆盖 358 setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); 359 bzero(&dest, sizeof(dest)); 360 //获取目的地址的IP地址 361 dest.sin_family = AF_INET; 362 //输入的目的地址为字符串IP地址 363 inaddr = inet_addr(argv[1]); 364 if(inaddr == INADDR_NONE){ //输入的是DNS地址 365 host = gethostbyname(argv[1]); 366 if(host == NULL){ 367 perror("gethostbyname"); 368 return -1; 369 } 370 //将地址复制到dest 371 memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length); 372 } //IP地址字符串 373 else { 374 memcpy((char *)&dest.sin_addr, &inaddr,sizeof(inaddr)); 375 } 376 //打印提示 377 inaddr = dest.sin_addr.s_addr; 378 printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n", 379 dest_str,(inaddr&0x000000ff)>>0,(inaddr&0x0000ff00)>>8,(inaddr&0x00ff0000)>>16,(inaddr&0xff000000)>>24); 380 //截取信号SIGINT,将icmp_sigint挂接上 381 signal(SIGINT,icmp_sigint); 382 383 /*发送数据并接收回应 384 建立两个线程,一个用于发数据,另一个用于接收响应数据,主程序等待两个线程运行完毕后再进行 385 下一步,最后对结果进行统计并打印 386 */ 387 alive = 1; //初始化可运行 388 pthread_t send_id, recv_id; //建立两个线程,用于发送和接收 389 int err = 0; 390 err = pthread_create(&send_id, NULL, icmp_send, NULL); //发送 391 if(err < 0){ 392 return -1; 393 } 394 err = pthread_create(&recv_id, NULL, icmp_recv, NULL); //接收 395 if(err < 0){ 396 return -1; 397 } 398 //等待线程结束 399 pthread_join(send_id, NULL); 400 pthread_join(recv_id, NULL); 401 //清理并打印统计结果 402 close(rawsock); 403 icmp_statistics(); 404 return 0; 405 }
三.程序运行方法
由于在程序中用到了<pthread.h>多线程,所以在编译时要加上-lpthread,并且想要运行的话要在root权限下,一般的用户是没有权限的此程序编译方法为:
gcc -o myping myping.c -lpthread
./myping www.baidu.com
代码2:
1 #include "stdafx.h" 2 #include<stdio.h> 3 #include<windows.h> 4 #include<process.h> 5 6 #pragma comment( lib, "ws2_32.lib" ) 7 8 #define SEND_SIZE 32 9 #define PACKET_SIZE 4096 10 #define ICMP_ECHO 8 11 #define ICMP_ECHOREPLY 0 12 13 struct icmp 14 { 15 unsigned char icmp_type; 16 unsigned char icmp_code; 17 unsigned short icmp_cksum; 18 unsigned short icmp_id; 19 unsigned short icmp_seq; 20 unsigned long icmp_data; 21 }; 22 23 struct ip 24 { 25 unsigned char ip_hl:4; 26 unsigned char ip_v:4; 27 unsigned char ip_tos; 28 unsigned short ip_len; 29 unsigned short ip_id; 30 unsigned short ip_off; 31 unsigned char ip_ttl; 32 unsigned char ip_p; 33 unsigned short ip_sum; 34 unsigned long ip_src; 35 unsigned long ip_dst; 36 }; 37 38 /*unsigned */char sendpacket[PACKET_SIZE]; 39 /*unsigned */char recvpacket[PACKET_SIZE]; 40 struct sockaddr_in dest_addr; 41 struct sockaddr_in from_addr; 42 int sockfd; 43 int pid; 44 45 unsigned short cal_chksum(unsigned short *addr,int len); 46 int pack(int pack_no); 47 int unpack(/*unsigned*/ char *buf,int len); 48 void send_packet(void); 49 void recv_packet(void); 50 51 void main(int argc,char *argv[]) 52 { 53 struct hostent *host; 54 struct protoent *protocol; 55 WSADATA wsaData; 56 int timeout=1000; 57 int SEND_COUNT=4; 58 int i; 59 char* par_host = "www.baidu.com"; 60 61 /* 62 par_host=argv[argc-1]; 63 switch(argc) 64 { 65 case 2: break; 66 case 3: if(strcmp(argv[1],"-t")==0) 67 { 68 SEND_COUNT=10000; 69 break; 70 } 71 //fall through 72 default: 73 printf("usage: %s [-t] Host name or IP address\n",argv[0]); 74 exit(1); 75 }*/ 76 77 78 if(WSAStartup(0x1010,&wsaData)!=0) 79 { 80 printf("wsastartup error\n"); 81 exit(1); 82 } 83 if( (protocol=getprotobyname("icmp") )==NULL) 84 { 85 printf("getprotobyname error\n"); 86 exit(1); 87 } 88 if( (sockfd=socket(AF_INET,SOCK_RAW,protocol->p_proto) )<0) 89 { 90 printf("socket error\n"); 91 exit(1); 92 } 93 if(setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout))<0) 94 fprintf(stderr,"failed to set recv timeout: %d\n",WSAGetLastError()); 95 if(setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout))<0) 96 fprintf(stderr,"failed to set send timeout: %d\n",WSAGetLastError()); 97 98 memset(&dest_addr,0,sizeof(dest_addr)); 99 dest_addr.sin_family=AF_INET; 100 if(host=gethostbyname(par_host) ) 101 { 102 memcpy( (char *)&dest_addr.sin_addr,host->h_addr,host->h_length); 103 //resolve address to hostname 104 if(host=gethostbyaddr(host->h_addr,4,PF_INET)) 105 par_host=host->h_name; 106 } 107 else if( dest_addr.sin_addr.s_addr=inet_addr(par_host)==INADDR_NONE) 108 { 109 printf("Unkown host %s\n",par_host); 110 exit(1); 111 } 112 113 114 pid = _getpid(); 115 printf("Pinging %s [%s]: with %d bytes of data:\n\n",par_host,inet_ntoa(dest_addr.sin_addr),SEND_SIZE); 116 for(i=0;i<SEND_COUNT;i++) 117 { 118 send_packet(); 119 recv_packet(); 120 Sleep(1000); 121 } 122 } 123 124 //this algorithm is referenced from other's 125 unsigned short cal_chksum(unsigned short *addr,int len)d 126 //打包 127 int pack(int pack_no) 128 { 129 int packsize; 130 struct icmp *icmp; 131 132 packsize=8+SEND_SIZE; 133 icmp=(struct icmp*)sendpacket; 134 icmp->icmp_type=ICMP_ECHO; 135 icmp->icmp_code=0; 136 icmp->icmp_cksum=0; 137 icmp->icmp_seq=pack_no; 138 icmp->icmp_id=pid; 139 icmp->icmp_data=GetTickCount(); 140 icmp->icmp_cksum=cal_chksum( (unsigned short *)icmp,packsize); /*校验算法*/ 141 return packsize; 142 } 143 144 //解包 145 int unpack(/*unsigned*/ char *buf,int len) 146 { 147 struct ip *ip; 148 struct icmp *icmp; 149 double rtt; 150 int iphdrlen; 151 152 ip=(struct ip *)buf; 153 iphdrlen=ip->ip_hl*4; 154 icmp=(struct icmp *)(buf+iphdrlen); 155 if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id==pid) ) 156 { 157 len=len-iphdrlen-8; 158 rtt=GetTickCount()-icmp->icmp_data; 159 printf("Reply from %s: bytes=%d time=%.0fms TTL=%d icmp_seq=%u\n", 160 inet_ntoa(from_addr.sin_addr), 161 len, 162 rtt, 163 ip->ip_ttl, 164 icmp->icmp_seq); 165 return 1; 166 } 167 return 0; 168 } 169 170 //发送 171 void send_packet() 172 { 173 int packetsize; 174 static int pack_no=0; 175 176 packetsize=pack(pack_no++); 177 if( sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr *)&dest_addr,sizeof(dest_addr) )<0 ) 178 printf("Destination host unreachable.\n"); 179 // printf("send NO %d\n",pack_no-1); 180 } 181 182 //接收 183 void recv_packet() 184 { 185 int n,fromlen; 186 int success; 187 188 fromlen=sizeof(from_addr); 189 do 190 { 191 if( (n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr *)&from_addr,&fromlen)) >=0) 192 success=unpack(recvpacket,n); 193 else if (WSAGetLastError() == WSAETIMEDOUT) 194 { 195 printf("Request timed out.\n"); 196 return; 197 } 198 }while(!success); 199 200 }
gcc -o myping myping.c -lpthread
./myping www.baidu.com
gcc -o myping myping.c -lpthread
./myping www.baidu.com
---------------------
作者:魏波-
来源:CSDN
原文:https://blog.csdn.net/weibo1230123/article/details/79891018
版权声明:本文为博主原创文章,转载请附上博文链接!