void Icmp_pack (struct icmp* icmphdr, int seq, int length) { int i = 0; Icmphdr->icmp_type = Icmp_echo; Type fill loopback request icmphdr->icmp_code = 0; icmphdr->icmp_cksum = 0; Note that it is important to fill in the 0 first! icmphdr->icmp_seq = seq; Here's the serial number we fill 1,2,3,4 .... icmphdr->icmp_id = pid & 0xFFFF; We use PID as icmp_id,icmp_id only 2 bytes, and PID has 4 bytes for (i=0;i<length;i++) { Icmphdr->icmp_data[i] = i; Populate the data segment so that the ICMP message is greater than 64B } icmphdr->icmp_cksum = Cal_chksum ((unsigned short*) icmphdr, length);//Checksum calculation}
Here again, Icmp_cksum must first fill in the 0 and then perform the checksum algorithm calculation, or ping the other host will be due to checksum calculation error and discard the request packet, resulting in a ping failure. A colleague of mine once had a long time for such a mistake, please remember the lesson of blood.
Here is a brief introduction to the checksum (checksum).
Computer network communication, in order to verify the data transmission process in the event of an error, usually in the transmission of data with a checksum a piece of transmission, when the receiving end of the data will be calculated from the new checksum, if the difference with the original checksum is considered an error, discard the packet, and return the ICMP message.
The basic idea of the algorithm:
The checksum algorithms for IP/ICMP/IGMP/TCP/UDP and other protocols are identical, and the data flow is treated as a 16-bit integer stream for repeated overlay computations. To calculate the test and the test, first set the inspection and field to 0. Then, for each 16 bits in the valid data range, the binary inverse code is summed, the result exists in the test and field, if the data length is an odd number of one byte 0. When the data is received, the sum of binary codes is also summed for each 16-digit number in the valid data range. Since the receiver has included in the calculation process the sender's presence in the header of the test and, therefore, if the first in the transmission process without any errors, then the receiver calculates the result should be full 0 or all 1 (specifically, the implementation, essentially the same). If the result is not full 0 or full 1, then the data error is indicated.
/* 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 the ICMP header binary data in 2-byte increments * /while (nleft>1) { sum+=*w++; nleft-=2; } /* If the ICMP header is an odd number of bytes, the last byte is left. Consider the last byte as a high byte of 2 bytes of data, the low byte of this 2 byte data is 0, 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;}
(3) packet unpacking of ICMP packetsKnowing how to package the package, the unpacking is not difficult, it is important to note that you receive an ICMP packet, we do not think that this package is our outgoing ICMP echo reply packet, we need to add a layer of code to determine whether the ID of the ICMP message and the SEQ field is consistent with the set of ICMP messages we send, To verify the correctness of the ICMP reply packet.
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); Causes the pointer to skip over the IP header to point to the ICMP header Len-=iphdr_len; ICMP packet length if (Len < 8)//Determine whether the length is ICMP packet length {fprintf (stderr, "Invalid ICMP packet. Its length was less than 8\n "); return-1; }//To determine if the packet is an ICMP echo Reply packet and the package is the IF ((Icmp->icmp_type = = icmp_echoreply) && (icmp->icmp_id = = (PID & 0x FFFF)) {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 that the packet was emitted gettimeofday (&recv_time, NULL); Offset_time = Cal_time_offset (Begin_time,Recv_time); RTT = offset_time.tv_sec*1000 + offset_time.tv_usec/1000; Ms printf ("%d bytes from%s:icmp_seq=%u ttl=%d rtt=%d ms\n", Len, Inet_ntoa (IP_HDR->IP_SRC), ICM P->icmp_seq, Ip_hdr->ip_ttl, RTT); } else {fprintf (stderr, "Invalid ICMP packet! Its ID isn't matched!\n "); return-1; } return 0;}
Second, the construction of the contract threadAccording to the framework of the PING program, we need to set up a thread for the ping packet Send, my idea is this: using SendTo for the package, the packet rate we maintain in 1 seconds 1, we need to use a global variable to record the time of the first ping packet, in addition, We also need a global variable to record the number of pings we send out, and these two variables are used to calculate the data after the Ping packet reply is received later.
void Ping_send () { char send_buf[128]; memset (send_buf, 0, sizeof (SEND_BUF)); Gettimeofday (&start_time, NULL); Records the time when the first ping packet was issued while (alive) { int size = 0; Gettimeofday (& (Ping_packet[send_count].begin_time), NULL); Ping_packet[send_count].flag = 1; Mark this as set to the package sent Icmp_pack ((struct icmp*) send_buf, Send_count, 64);//encapsulation ICMP packet size = sendto (Rawsock, Send_buf , 0, (struct sockaddr*) &dest, sizeof (dest)); send_count++; Record the number of ping packets issued if (size < 0) { fprintf (stderr, "Send ICMP packet fail!\n"); Continue; } Sleep (1);} }
third, the collection thread is set up
We also create a thread to receive the package, where we use the Select function to collect the packet, and set the time-out for the Select function to 200us, and then the next loop if a timeout occurs. Similarly, we need a global variable to record the number of Ping reply packets that were successfully received.
void Ping_recv () {struct Timeval TV; Tv.tv_usec = 200; The time-out for setting the Select function is 200us 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); The received packet is unpacked if (ret = =-1)//not its own ICMP packet, discard does not process {continue; } recv_count++; Receive packet count} break; } }}
iv. interruption of processingWe have specified that a ping sends a maximum of 64 packets and stops sending if the value is exceeded. As a user of ping, we usually send only a few packets, and if these packages return smoothly, we crtl+c interrupt ping. The code here is to write an interrupt handler for the interrupt signal, set the global variable of alive to 0, which will stop the loop of sending the ping packets and end the program.
void Icmp_sigint (int signo) { alive = 0; Gettimeofday (&end_time, NULL); Time_interval = Cal_time_offset (start_time, end_time);} Signal (SIGINT, icmp_sigint);
v. Overall realizationThe modules are finished, and the complete code is now posted.
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> #include <string.h> #include <netdb.h> #include <PTHREAD.H&G T #define PACKET_SEND_MAX_NUM, typedef struct PING_PACKET_STATUS (Timeval begin_time; the struct timeval end_time; int flag; Send flag, 1 for the sent int seq; The serial number of the package is}ping_packet_status; Ping_packet_status Ping_packet[packet_send_max_num]; Alive INT; Rawsock int; Send_count int; Recv_count int; pid_t pid; Sockaddr_in dest of the struct; Timeval start_time of the struct; Timeval end_time of the struct; Timeval time_interval of Panax notoginseng struct; 38 39/* CHECKSUM algorithm */unsigned short cal_chksum (unsigned short *addr,int len), int nleft=len;= 0; unsigned short *w=addr; unsigned short answer=0; 45 46/* Add the ICMP header binary data in 2-byte increments */(nleft>1) sum+=*w++; 50 nleft-=2; 51} 52/* If the ICMP header is an odd number of bytes, the last byte is left. Consider the last byte as a high byte of 2 bytes of data, the low byte of this 2 byte data is 0, continue to accumulate */nleft==1 (unsigned char *) (& Amp;answer) =* (unsigned char *) W; Sum+=answer; Sum= (sum>>16) + (SUM&0XFFFF); sum+= (SUM>>16); Answer=~sum; Answer return; The Timeval cal_time_offset (struct timeval begin, struct timeval end), and the 67 struct timeval ans; Ans.tv_sec = end.tv_sec-begin.tv_sec; Ans.tv_usec = end.tv_usec-begin.tv_usec; if (Ans.tv_usec < 0)//If the receiving time of USEC is less than usec of the sending time, then borrow to the SEC field ans.tv_sec-- 1000000; The return ans; Icmp_pack (StrucT icmp* icmphdr, int seq, int length) (0) (int) i = Icmphdr->icmp_type; n-bay = Icmp_echo; >icmp_code = 0; icmphdr->icmp_cksum = 0; Icmphdr->icmp_seq = seq; icmphdr->icmp_id = pid & 0xFFFF; for (i=0;i<length;i++) icmphdr->icmp_data[i {n] = i; icmphdr->icmp_cks Um = Cal_chksum ((unsigned short*) icmphdr, length); The 94 int icmp_unpack (char* buf, int len)-----------------Iphdr_len, Timeval Fset_time; 98 int RTT; Round trip time ip* struct IP_HDR = (struct IP *) buf;101 iphdr_len = ip_hdr->ip_hl*4;102 struct IC mp* ICMP = (struct icmp*) (Buf+iphdr_len); 103 Len-=iphdr_len; The ICMP packet length 104 if (Len < 8)//Determines whether the length is ICMP packet length of 106 {fprintf (stderr, "Invalid ICMP packet. Its length was less than 8\n "); 107 return-1;108}109 110//Determine that the packet is an ICMP echo reply packet and that the package is the 111 if ((Icmp->ic MP_type = = icmp_echoreply) && (icmp->icmp_id = = (PID & 0xFFFF)) (113) (Icmp->icmp_seq < 0) | | (Icmp->icmp_seq > Packet_send_max_num)) fprintf {stderr, "ICMP packet seq is out of range!\n"); 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; Milliseconds is 126 printf ("%d bytes 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); 129}130 else131 {fprintf (stderr, "Invalid ICMP packet! Its ID was not matched!\n "); 133 return-1;134}135 return 0;136}137 138 void Ping_send () 139 {$ char Send_buf[128];141 memset (send_buf, 0, sizeof (SEND_BUF)); 142 Gettimeofday (&start_time, NULL); Records the time that the first ping packet was issued 143 while (alive) 144 {145 int size = 0;146 gettimeofday (& (Ping_packet[send_cou Nt].begin_time), NULL); 147 Ping_packet[send_count].flag = 1; Mark this as set as the package has been sent 148 149 icmp_pack ((struct icmp*) send_buf, Send_count, 64); Encapsulate ICMP Packet size = sendto (Rawsock, Send_buf, 0, (struct sockaddr*) &dest, sizeof (dest)); 151 Send_c ount++; Record the number of ping packets issued 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 t v;165 tv.tv_usec = 200; Set the time-out for the Select function to 200us166 tv.tv_sec = 0;167 fd_set read_fd;168 char recv_buf[512];169 memset (recv_buf, 0 , sizeof (RECV_BUF)), 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); Unpack the received Packet 193 if (ret = =-1)//Not own ICMP packet, discard does not process 194 {195 continue;196}197 recv_count++; Receive packet Count 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, en d_time);}211 212 void Ping_stats_show () 213 {214 Long time = time_interval.tv_sec*1000+time_interval.tv_usec/1000;2 15/* Note that the divisor cannot be zero, here Send_count may be zero, so the runtime prompt error */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, 221 int main (int argc, char* argv[]) 222 {223 int size = 128*1024;//128k224 struct protoent* protocol = null;225 Char Dest_ addr_str[80];226 memset (dest_addr_str, 0, N); 227 unsigned int inaddr = 1;228 struct hostent* host = null;229 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"); Gets the protocol type ICMP239 if (protocol = = NULL) for the {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 {+ printf ("Fail to Create socket!\n "); 251 return-1;252}253 254 pid = Getpid (); 255 setsockopt (Rawsock, Sol_socket, So_rcvbuf, &size, sizeof (size)); Increase the receive buffer to 128k257 258 bzero (&dest,sizeof (dest)); 259 260 dest.sin_family = af_inet;261 262 inaddr = Inet_ad Dr (Argv[1]); 263 if (inaddr = = Inaddr_none)//Determine if the user entered the IP address or the domain name 264 {265//input is the domain name address 266 host = Geth Ostbyname (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 else276 {277 memcpy ((char*) &dest.sin_addr, &inaddr, sizeof (INADDR));//Input IP address 278}279 inaddr = dest.sin_addr.s_addr;280 printf ("PING%s, (%d.%d.%d.%d) bytes of data.\n", Dest_addr_str , 281 (INADDR&0X000000FF), (inaddr&0x0000ff00) >>8, 282 (inaddr&0x00ff0000) >>16, (in addr&0xff000000) >>24); 283 284 alive = 1; Controls the send and receive of pings 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 (pthr Ead_create (&recv_id, NULL, (void*) ping_recv, NULL)) 295 {296 printf ("Fail to create ping recv thread!\n"); 2 }299 return-1;298 pthread_join (send_id, NULL);//wait for the Send ping thread to end after the process ends 301 Pthread_join (recv_id, NULL);//wait for the recv ping thread to end after the process ends 302 303 ping_stats_show (); 304 305 Close (Rawsock); 306 return 0;307 308}
The compilation and experimental phenomena are as follows:
My experimental environment is two servers, the host to Ping is 172.0.5.183, the host being ping is 172.0.5.182, the following is my two experiments (ping ip and ping domain name).
Special attention:
Only the root user can use the socket () function to generate the original socket, in order for the general Linux users to execute the above program, the following special operation is required: login with root, compile the above program Gcc-lpthread-o Ping ping.c
Experimental phenomenon can be seen, ping is successful, indicating that the network between the two hosts is through, all the ping packets issued received a reply. Here is the Linux system comes with the ping program, we can compare the ping program we designed with the system's own ping program is different.
Implementation of Ping for Linux programming