The Chinese name of PING (Packet InterNet Groper) is Internet Packet Explorer, which is a tool used to check whether the network connection of another host system on the network is normal. The ping command works by sending an ICMP message to another host system on the network. If a message is received by the designated system, it will send the message back to the sender, which is a bit like the sound generator used in the submarine sonar system. So, we want to know if this host can communicate with another one. First of all, we need to confirm whether the network between our two hosts is connected or not, that is, whether what I said can be transmitted to you, which is the premise of communication between the two hosts. The methods and phenomena of using instruction Ping under Linux are as follows:
The implementation of PING does not seem complicated. I want to write my own code to implement this function. What kind of knowledge is needed? Let me briefly list:
- Understanding of ICMP Protocol
- RAW socket
- Network Packeting and Unpacking Skills
- Encapsulation and Unencapsulation of ICMP Packets
- Create a thread for sending ICMP packages
- Create a thread for receiving ICMP packets
- Primitive socket programming
Because the format of ICMP message varies according to the type of ICMP message, the format of our ping package is as follows:
void icmp_pack(struct icmp* icmphdr, int seq, int length) { int i = 0; icmphdr->icmp_type = ICMP_ECHO; //Type Fill Back Request icmphdr->icmp_code = 0; icmphdr->icmp_cksum = 0; //Note that it's important to fill in 0 first! icmphdr->icmp_seq = seq; //Here's the serial number. Let's fill in 1.,2,3,4.... icmphdr->icmp_id = pid & 0xffff; //We use pid Act as icmp_id,icmp_id It's only 2 bytes, and pid There are 4 bytes. for(i=0;i<length;i++) { icmphdr->icmp_data[i] = i; //Fill in data segments to make ICMP Messages greater than 64 B } icmphdr->icmp_cksum = cal_chksum((unsigned short*)icmphdr, length); //check sum calculator }
Here's a brief introduction to checksum.
When communicating with computer network, in order to check whether the data is wrong in the process of data transmission, the data is usually transmitted together with the checksum. When receiving the data, the checksum will be calculated again. If the checksum is different from the original one, it will be regarded as an error. The data packet will be discarded and the icmp message will be returned.
The checksum algorithms of IP/ICMP/IGMP/TCP/UDP and other protocols are the same, and the data stream is regarded as 16-bit integer stream for repeated overlay calculation. In order to calculate the checksum, the checksum and field are set to 0. Then, the binary inverse sum of each 16 bits in the valid data range is performed. The result exists in the test and field. If the length of the data is odd, a byte of 0 is added. When data is received, the sum of binary inverse codes for each 16 digits in the valid data range is also performed. Since the receiver includes the checksum of the sender's presence in the header in the calculation process, if there are no errors in the header in the transmission process, then the result of the receiver's calculation should be all 0 or all 1 (see the implementation in detail, the essence is the same). If the result is not all 0 or all 1, then the data is incorrect.
/*Checksum algorithm*/ unsigned short cal_chksum(unsigned short *addr,int len) { int nleft=len; int sum=0; unsigned short *w=addr; unsigned short answer=0; /*Add up ICMP header binary data in 2 bytes*/ while(nleft>1) { sum+=*w++; nleft-=2; } /*If the ICMP header is odd bytes, the last byte is left. Consider the last byte as a high byte of 2-byte data, the low byte of 2-byte data is 0, and continue to accumulate.*/ if( nleft==1) { *(unsigned char *)(&answer)=*(unsigned char *)w; sum+=answer; } sum=(sum>>16)+(sum&0xffff); sum+=(sum>>16); answer=~sum; return answer; }
int icmp_unpack(char* buf, int len) { int iphdr_len; struct timeval begin_time, recv_time, offset_time; int rtt; //round trip time struct ip* ip_hdr = (struct ip *)buf; iphdr_len = ip_hdr->ip_hl*4; struct icmp* icmp = (struct icmp*)(buf+iphdr_len); //Skip the pointer IP Head pointing ICMP head len-=iphdr_len; //icmp Packet length if(len < 8) //Determine whether the length is ICMP Packet length { fprintf(stderr, "Invalid icmp packet.Its length is less than 8\n"); return -1; } //Judge that the package is ICMP Return the reply package and it was sent out by us. if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)) { if((icmp->icmp_seq < 0) || (icmp->icmp_seq > PACKET_SEND_MAX_NUM)) { fprintf(stderr, "icmp packet seq is out of range!\n"); return -1; } ping_packet[icmp->icmp_seq].flag = 0; begin_time = ping_packet[icmp->icmp_seq].begin_time; //Remove the time of delivery of the package gettimeofday(&recv_time, NULL); offset_time = cal_time_offset(begin_time, recv_time); rtt = offset_time.tv_sec*1000 + offset_time.tv_usec/1000; //In milliseconds printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n", len, inet_ntoa(ip_hdr->ip_src), icmp->icmp_seq, ip_hdr->ip_ttl, rtt); } else { fprintf(stderr, "Invalid ICMP packet! Its id is not matched!\n"); return -1; } return 0; }
void ping_send() { char send_buf[128]; memset(send_buf, 0, sizeof(send_buf)); gettimeofday(&start_time, NULL); //Record the first ping Time of Pack Delivery while(alive) { int size = 0; gettimeofday(&(ping_packet[send_count].begin_time), NULL); ping_packet[send_count].flag = 1; //Mark this as set that the package has been sent icmp_pack((struct icmp*)send_buf, send_count, 64); //encapsulation icmp package size = sendto(rawsock, send_buf, 64, 0, (struct sockaddr*)&dest, sizeof(dest)); send_count++; //Record issue ping Number of packages if(size < 0) { fprintf(stderr, "send icmp packet fail!\n"); continue; } sleep(1); } }
3. Construction of Packet Receiving Threads
We also set up a thread to receive the package. Here we use slect as a non-congestion function to receive the package, and set the timeout time for the slect function to 200 us. If the timeout occurs, the next loop will be carried out. Similarly, we need a global variable to record the number of ping reply packets received successfully.
void ping_recv() { struct timeval tv; tv.tv_usec = 200; //Set up select The timeout time of the function is 200 us tv.tv_sec = 0; fd_set read_fd; char recv_buf[512]; memset(recv_buf, 0 ,sizeof(recv_buf)); while(alive) { int ret = 0; FD_ZERO(&read_fd); FD_SET(rawsock, &read_fd); ret = select(rawsock+1, &read_fd, NULL, NULL, &tv); switch(ret) { case -1: fprintf(stderr,"fail to select!\n"); break; case 0: break; default: { int size = recv(rawsock, recv_buf, sizeof(recv_buf), 0); if(size < 0) { fprintf(stderr,"recv data fail!\n"); continue; } ret = icmp_unpack(recv_buf, size); //Unencapsulation of received packets if(ret == -1) //Not your own icmp Pack, discarded and untreated { continue; } recv_count++; //Receiving Packet Counting } break; } } }
void icmp_sigint(int signo) { alive = 0; gettimeofday(&end_time, NULL); time_interval = cal_time_offset(start_time, end_time); } signal(SIGINT, icmp_sigint);
1 #include <stdio.h> 2 #include <netinet/in.h> 3 #include <netinet/ip.h> 4 #include <netinet/ip_icmp.h> 5 #include <unistd.h> 6 #include <signal.h> 7 #include <arpa/inet.h> 8 #include <errno.h> 9 #include <sys/time.h> 10 #include <string.h> 11 #include <netdb.h> 12 #include <pthread.h> 13 14 15 #define PACKET_SEND_MAX_NUM 64 16 17 typedef struct ping_packet_status 18 { 19 struct timeval begin_time; 20 struct timeval end_time; 21 int flag; //Send sign,1 Send for 22 int seq; //Sequence number of package 23 }ping_packet_status; 24 25 26 27 ping_packet_status ping_packet[PACKET_SEND_MAX_NUM]; 28 29 int alive; 30 int rawsock; 31 int send_count; 32 int recv_count; 33 pid_t pid; 34 struct sockaddr_in dest; 35 struct timeval start_time; 36 struct timeval end_time; 37 struct timeval time_interval; 38 39 /*Checksum algorithm*/ 40 unsigned short cal_chksum(unsigned short *addr,int len) 41 { int nleft=len; 42 int sum=0; 43 unsigned short *w=addr; 44 unsigned short answer=0; 45 46 /*Add up ICMP header binary data in 2 bytes*/ 47 while(nleft>1) 48 { 49 sum+=*w++; 50 nleft-=2; 51 } 52 /*If the ICMP header is odd bytes, the last byte is left. Consider the last byte as a high byte of 2-byte data, the low byte of 2-byte data is 0, and continue to accumulate.*/ 53 if( nleft==1) 54 { 55 *(unsigned char *)(&answer)=*(unsigned char *)w; 56 sum+=answer; 57 } 58 sum=(sum>>16)+(sum&0xffff); 59 sum+=(sum>>16); 60 answer=~sum; 61 return answer; 62 } 63 64 struct timeval cal_time_offset(struct timeval begin, struct timeval end) 65 { 66 struct timeval ans; 67 ans.tv_sec = end.tv_sec - begin.tv_sec; 68 ans.tv_usec = end.tv_usec - begin.tv_usec; 69 if(ans.tv_usec < 0) //If receiving time usec Less than the sending time usec,Then to sec Domain borrow 70 { 71 ans.tv_sec--; 72 ans.tv_usec+=1000000; 73 } 74 return ans; 75 } 76 77 void icmp_pack(struct icmp* icmphdr, int seq, int length) 78 { 79 int i = 0; 80 81 icmphdr->icmp_type = ICMP_ECHO; 82 icmphdr->icmp_code = 0; 83 icmphdr->icmp_cksum = 0; 84 icmphdr->icmp_seq = seq; 85 icmphdr->icmp_id = pid & 0xffff; 86 for(i=0;i<length;i++) 87 { 88 icmphdr->icmp_data[i] = i; 89 } 90 91 icmphdr->icmp_cksum = cal_chksum((unsigned short*)icmphdr, length); 92 } 93 94 int icmp_unpack(char* buf, int len) 95 { 96 int iphdr_len; 97 struct timeval begin_time, recv_time, offset_time; 98 int rtt; //round trip time 99 100 struct ip* ip_hdr = (struct ip *)buf; 101 iphdr_len = ip_hdr->ip_hl*4; 102 struct icmp* icmp = (struct icmp*)(buf+iphdr_len); 103 len-=iphdr_len; //icmp Packet length 104 if(len < 8) //Determine whether the length is ICMP Packet length 105 { 106 fprintf(stderr, "Invalid icmp packet.Its length is less than 8\n"); 107 return -1; 108 } 109 110 //Judge that the package is ICMP Return the reply package and it was sent out by us. 111 if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid)) 112 { 113 if((icmp->icmp_seq < 0) || (icmp->icmp_seq > PACKET_SEND_MAX_NUM)) 114 { 115 fprintf(stderr, "icmp packet seq is out of range!\n"); 116 return -1; 117 } 118 119 ping_packet[icmp->icmp_seq].flag = 0; 120 begin_time = ping_packet[icmp->icmp_seq].begin_time; 121 gettimeofday(&recv_time, NULL); 122 123 offset_time = cal_time_offset(begin_time, recv_time); 124 rtt = offset_time.tv_sec*1000 + offset_time.tv_usec/1000; //In milliseconds 125 126 printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n", 127 len, inet_ntoa(ip_hdr->ip_src), icmp->icmp_seq, ip_hdr->ip_ttl, rtt); 128 129 } 130 else 131 { 132 fprintf(stderr, "Invalid ICMP packet! Its id is not matched!\n"); 133 return -1; 134 } 135 return 0; 136 } 137 138 void ping_send() 139 { 140 char send_buf[128]; 141 memset(send_buf, 0, sizeof(send_buf)); 142 gettimeofday(&start_time, NULL); //Record the first ping Time of Pack Delivery 143 while(alive) 144 { 145 int size = 0; 146 gettimeofday(&(ping_packet[send_count].begin_time), NULL); 147 ping_packet[send_count].flag = 1; //Mark this as set that the package has been sent 148 149 icmp_pack((struct icmp*)send_buf, send_count, 64); //encapsulation icmp package 150 size = sendto(rawsock, send_buf, 64, 0, (struct sockaddr*)&dest, sizeof(dest)); 151 send_count++; //Record issue ping Number of packages 152 if(size < 0) 153 { 154 fprintf(stderr, "send icmp packet fail!\n"); 155 continue; 156 } 157 158 sleep(1); 159 } 160 } 161 162 void ping_recv() 163 { 164 struct timeval tv; 165 tv.tv_usec = 200; //Set up select The timeout time of the function is 200 us 166 tv.tv_sec = 0; 167 fd_set read_fd; 168 char recv_buf[512]; 169 memset(recv_buf, 0 ,sizeof(recv_buf)); 170 while(alive) 171 { 172 int ret = 0; 173 FD_ZERO(&read_fd); 174 FD_SET(rawsock, &read_fd); 175 ret = select(rawsock+1, &read_fd, NULL, NULL, &tv); 176 switch(ret) 177 { 178 case -1: 179 fprintf(stderr,"fail to select!\n"); 180 break; 181 case 0: 182 break; 183 default: 184 { 185 int size = recv(rawsock, recv_buf, sizeof(recv_buf), 0); 186 if(size < 0) 187 { 188 fprintf(stderr,"recv data fail!\n"); 189 continue; 190 } 191 192 ret = icmp_unpack(recv_buf, size); //Unencapsulation of received packets 193 if(ret == -1) //Not your own icmp Pack, discarded and untreated 194 { 195 continue; 196 } 197 recv_count++; //Receiving Packet Counting 198 } 199 break; 200 } 201 202 } 203 } 204 205 void icmp_sigint(int signo) 206 { 207 alive = 0; 208 gettimeofday(&end_time, NULL); 209 time_interval = cal_time_offset(start_time, end_time); 210 } 211 212 void ping_stats_show() 213 { 214 long time = time_interval.tv_sec*1000+time_interval.tv_usec/1000; 215 /*Note that the divisor cannot be zero, where send_count may be zero, so the runtime prompts for errors*/ 216 printf("%d packets transmitted, %d recieved, %d%c packet loss, time %ldms\n", 217 send_count, recv_count, (send_count-recv_count)*100/send_count, '%', time); 218 } 219 220 221 int main(int argc, char* argv[]) 222 { 223 int size = 128*1024;//128k 224 struct protoent* protocol = NULL; 225 char dest_addr_str[80]; 226 memset(dest_addr_str, 0, 80); 227 unsigned int inaddr = 1; 228 struct hostent* host = NULL; 229 230 pthread_t send_id,recv_id; 231 232 if(argc < 2) 233 { 234 printf("Invalid IP ADDRESS!\n"); 235 return -1; 236 } 237 238 protocol = getprotobyname("icmp"); //Get the protocol type ICMP 239 if(protocol == NULL) 240 { 241 printf("Fail to getprotobyname!\n"); 242 return -1; 243 } 244 245 memcpy(dest_addr_str, argv[1], strlen(argv[1])+1); 246 247 rawsock = socket(AF_INET,SOCK_RAW,protocol->p_proto); 248 if(rawsock < 0) 249 { 250 printf("Fail to create socket!\n"); 251 return -1; 252 } 253 254 pid = getpid(); 255 256 setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); //Increase the receiving buffer to 128 K 257 258 bzero(&dest,sizeof(dest)); 259 260 dest.sin_family = AF_INET; 261 262 inaddr = inet_addr(argv[1]); 263 if(inaddr == INADDR_NONE) //Determine whether user input is IP Address or Domain Name 264 { 265 //The domain name address is entered 266 host = gethostbyname(argv[1]); 267 if(host == NULL) 268 { 269 printf("Fail to gethostbyname!\n"); 270 return -1; 271 } 272 273 memcpy((char*)&dest.sin_addr, host->h_addr, host->h_length); 274 } 275 else 276 { 277 memcpy((char*)&dest.sin_addr, &inaddr, sizeof(inaddr));//The input is IP address 278 } 279 inaddr = dest.sin_addr.s_addr; 280 printf("PING %s, (%d.%d.%d.%d) 56(84) bytes of data.\n",dest_addr_str, 281 (inaddr&0x000000ff), (inaddr&0x0000ff00)>>8, 282 (inaddr&0x00ff0000)>>16, (inaddr&0xff000000)>>24); 283 284 alive = 1; //control ping Sending and receiving 285 286 signal(SIGINT, icmp_sigint); 287 288 if(pthread_create(&send_id, NULL, (void*)ping_send, NULL)) 289 { 290 printf("Fail to create ping send thread!\n"); 291 return -1; 292 } 293 294 if(pthread_create(&recv_id, NULL, (void*)ping_recv, NULL)) 295 { 296 printf("Fail to create ping recv thread!\n"); 297 return -1; 298 } 299 300 pthread_join(send_id, NULL);//wait for send ping The process ends after the thread has finished 301 pthread_join(recv_id, NULL);//wait for recv ping The process ends after the thread has finished 302 303 ping_stats_show(); 304 305 close(rawsock); 306 return 0; 307 308 }
My experimental environment is two servers, the host of Ping is 172.0.5.183, and the host of Ping is 172.0.5.182. The following are my two experimental phenomena (ping IP and Ping domain name).
Only root users can use socket() function to generate the original socket. To enable ordinary users of Linux to execute the above programs, the following special operations are needed: log in with root and compile the above programs gcc-lpthread-o ping.c.