Most people use the ping command just a simple way to see if the network connection of another system is normal. In this article, the author will introduce how to write a program that simulates the ping command function with the C language.
The PING command is used to see if a network connection of another host system on the network is normal. The working principle of the ping command is to send ICMP packets to another host system on the network. If the specified system gets a message, it will transmit the message to the sender, which is a bit like submarine The sound device used is used. For example, executing the ping localhost command on the Linux terminal will see the following results:
Ping localhost.localdomain (127.0.0.1) from 127.0.0.1: 56 (84) bytes of data.
64 bytes from localhost.localdomain (127.0.0.1): ICMP_SEQ = 0 TTL = 255 TIME = 112 USEC
64 bytes from localhost.localdomain (127.0.0.1): ICMP_SEQ = 1 TTL = 255 TIME = 79 USEC
64 bytes from localhost.localdomain (127.0.0.1): ICMP_SEQ = 2 TTL = 255 TIME = 78 USEC
64 Bytes from localhost.localdomain (127.0.0.1): ICMP_SEQ = 3 TTL = 255 TIME = 82 USEC
--- Localhost.localdomain ping statistics ----
4 Packets Transmitted, 4 Packets Received, 0% Packet LOSS
Round-trip min / avg / max / mdev = 0.078 / 0.087 / 0.112 / 0.018 ms
As can be seen from the above execution, it can be seen that the PING command is executed after the test system hostname and the corresponding IP address, return to the current host's ICMP message sequence, TTL survival time and round-trip time RTT (unit is millisecond, ie thousand points One second). To write an analog ping command, this information has a revelation. To truly understand the principle of ping command implementation, you must understand the TCP / IP protocol used by the ping command. ICMP (Internet Control Message, internet protocol) is an error control mechanism provided for gateways and target hosts, enabling them to report the error to the packet source when encountered errors. The ICMP protocol is an IP layer protocol, but since the error report may also pass a few subnets when sending to the packet source, the ICMP message is involved in routing, so ICMP packets are sent via the IP protocol. Two level packages need to be added before the data transmission of ICMP datagram: First add ICMP header to form ICMP packets, add IP header to form an IP datagram. As shown below
IP header ICMP header ICMP Datasheet IP header format Since the IP layer protocol is a point-to-point protocol, not the end-to-end protocol, it provides connectionless datagram services, there is no port concept, so it is rarely used bind () and Connect () function, if used, is just used to set IP addresses. Sending data Use the sendto () function to receive data using the RECVFROM () function. The IP header format is as follows:
In Linux, the IP header format data structure (
Struct IP
{
#if __byte_order == __little_endian
Unsigned int ip_hl: 4; / * header length * /
Unsigned int ip_v: 4; / * version * / # endif
#if __byte_order == __big_endian
Unsigned int ip_v: 4; / * version * /
Unsigned int ip_hl: 4; / * header length * /
#ENDIF
U_INT8_T IP_TOS; / * TYPE OF Service * /
u_short ip_len; / * Total length * /
U_SHORT IP_ID; / * IDENTIFICATION * /
U_SHORT IP_OFF; / * FRAGMENT OFFSET FIELD * /
#define ip_rf 0x8000 / * Reserved Fragment Flag * /
#define ip_df 0x4000 / * DONT FRAGMENT FLAG * /
#define ip_mf 0x2000 / * more Fragments flag * /
#define ip_offmask 0x1fff / * Mask for fragmenting bits * /
U_INT8_T IP_TTL; / * TIME TO LIVE * /
U_INT8_T IP_P; / * Protocol * /
u_short ip_sum; / * checksum * /
Struct in_addr ip_src, ip_dst; / * source and dest address * /
}; Where the PING program only uses the following data:
IP header length IHL (Internet Header Length) - The length of the IP header is recorded in 4 bytes, which is the IP_HL variable of the above IP data structure. Survival time TTL (Time to Live) - in seconds, pointing to the maximum time of IP data taps to stay on the network, its value is set by the sender, and minus one when the routing is When this value is 0, the datagram will be discarded, which is the IP_TTL variable of the above IP data structure. ICMP header format ICMP packets are divided into two, one is the error report message, and the other is the query message. Each ICMP header contains type, encoding, and checksum, with a length of 8 bits, 8-bit, and 16th, and the remaining options are different from ICMP features. The ping command only uses two of the many ICMP packets: "Request to return '(ICMP_ECHO) and" Request response' (ICMP_ECHOREPLY). Defined in Linux as follows:
#define ICMP_echo 0
#define ICMP_ECHOREPLY 8 These two ICMP type header formats are as follows:
The ICMP data structure (
Struct ICMP
{
U_INT8_T ICMP_TYPE; / * TYPE OF MESSAGE, SEE BEE BELOW * /
U_INT8_T ICMP_CODE; / * TYPE SUB CODE * /
U_INT16_T ICMP_CKSUM; / * ONES Complement Checksum of Struct * /
union
{
U_CHAR IH_PPTR; / * ICMP_PARAMPROB * /
Struct in_addr h_gwaddr; / * Gateway Address * / Struct IH_IDSEQ / * Echo DataGram * /
{
u_INT16_T ICD_ID;
U_INT16_T ICD_SEQ;
} IH_IDSEQ;
U_INT32_T IH_VOID;
/ * ICMP_UNREACH_NEDFRAG - Path MTU Discovery (RFC1191) * /
Struct IH_PMTU
{
U_INT16_T IPM_VOID;
U_INT16_T IPM_NEXTMTU;
} IH_PMTU;
Struct IH_RTRADV
{
U_INT8_T IRT_NUM_ADDRS;
U_INT8_T IRT_WPA;
U_INT16_T IRT_LIFETIME;
} IH_RTRADV;
} ICMP_HUN;
#define ICMP_pptr icmp_hun.ih_pptr
#define icmp_gwaddr icmp_hun.ih_gwaddr
#define ICMP_ID ICMP_HUN.IH_IDSEQ.ICD_ID
#define ICMP_SEQ ICMP_HUN.IH_IDSEQ.ICD_SEQ
#define ICMP_VOID ICMP_HUN.IH_VOID
#define icmp_pmvoid ICMP_HUN.IH_PMTU.IPM_VOID
#define ICMP_NEXTMTU ICMP_HUN.IH_PMTU.IPM_NEXTMTU
#define ICMP_NUM_ADDRS ICMP_HUN.IH_RTRADV.IRT_NUM_ADDRS
#define ICMP_WPA ICMP_HUN.IH_RTRADV.IRT_WPA
#define icmp_lifetime ICMP_HUN.IH_RTRADV.IRT_LIFETIME
union
{
Struct
{
U_INT32_T ITS_OTIME;
U_INT32_T ITS_RTIME;
U_INT32_T ITS_TTIME;
} ID_TS;
Struct
{
Struct IP IDI_IP;
/ * Options and the 64 bits of data * /
} ID_IP;
Struct ICMP_RA_ADDR ID_RADV;
U_INT32_T ID_MASK;
U_INT8_T ID_DATA [1];
} ICMP_DUN;
#define icmp_otime ICMP_Dun.id_ts.its_otime
#define icmp_rtime ICMP_Dun.id_ts.its_rtime
#define ICMP_TTIME ICMP_Dun.ID_TS.ITS_TTIME
#define ICMP_IP ICMP_Dun.id_ip.idi_ip
#define ICMP_RADV ICMP_Dun.ID_Radv
#define ICMP_MASK ICMP_DUN.ID_MASK
#define ICMP_DATA ICMP_DUN.ID_DATA
}
Using macro definitions makes it more concise, where the ICMP header is 8 bytes, the data report is up to 64K bytes.
Calibration and algorithm - This algorithm is called an internet check and algorithm, and the 16 digits of the verified data are accumulated, then take the reverse code, if the data byte length is odd, the data tail is added to one byte 0 To make up even. This algorithm applies to IPv4, ICMPv4, IGMPv4, ICMPv6, UDP, and TCP checksums. For more detailed information, please refer to the RFC 1071, the checksum field for the ICMP_CKSUM variable of the above ICMP data structure. Identifier - For unique identity ICMP packets, the variables referred to in the ICMP_ID macro of the above ICMP data structure. The sequence number -pring command ICMP_SEQ is read here, representing the parameter of the ICMP data structure of the ICMP data structure, represents the variable of the ICMP data structure of the ICMP data structure. ICMP datagram ping commands need to be displayed, including ICMP_SEQ and TTLs have implemented methods, but it is also deficient to RTT round trip time. In order to achieve this, ICMP datagram can be used to bring a timestamp. Use the following function to generate timestamp: #include
INT getTimeOfDay (Struct Timeval * TP, VOID * TZP)
The TimeVal structure is as follows:
Struct Timeval {
Long TV_sec;
Long TV_usec;
} Where tv_sec is a number of seconds, TV_USEC microseconds. Generate two TIMEVAL structures by getTimeOfDay when sending and receiving packets, the difference between the two, that is, the time difference of the ICMP packet transmission and reception, and the TIMEVAL structure is carried by the ICMP datagram, and the TZP pointer represents the time zone, generally Not used, assign a null value. The ping command comes with the data statistics system When it passes all ICMP packets, all transmission and all received ICMP packets are statistically calculate the ratio of ICMP packets. To reach the purpose, two global variables are defined: the receiving counter and send counters are used to record ICMP packet acceptance and transmission. Loss Number = Total Send Total - Total Number of Receives, Loss Ratio = Loss Number / Total Number. The code for the simulation ping program is now as follows:
/ ************************************************** **********
* Author: Liang Junhui *
* Time: October 2001 *
* Name: myping.c *
* Description: This program is used to demonstrate the principle of implementation of the ping command *
*********************************************************** ********* /
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define packet_size 4096
#define max_wait_time 5
#define max_no_packets 3
Char sendpacket [packet_size];
Char recapacket [packet_size];
INT SOCKFD, DATALEN = 56;
INT NSEND = 0, NRECEIVED = 0;
Struct SockAddr_in dest_addr;
PID_T PID;
Struct socketdr_in from;
Struct TimeVal TVRecv;
Void Statistics (Int Signo);
Unsigned short ca_chksum (unsigned short * addr, int LEN);
INT PACK (int pack_no);
Void Send_Packet (Void);
Void Recv_packet (void);
INT UNPACK (Char * BUF, INT LEN);
Void TV_SUB (Struct Timeval * OUT, STRUCT TIMEVAL * IN);
Void Statistics (int Signo)
{printf ("/ n ------------------ ping statistics ------------------ / n" );
Printf ("% d packets transmitted,% d received, %%% D Lost / N", NSEND, NRECEIVED,
(NSEND-NRECEIVED) / NSEND * 100);
Close (SockFD);
Exit (1);
}
/ * Calibration Algorithm * /
Unsigned short cal_chksum (unsigned short * addr, int LEN)
{Int nLEFT = LEN;
INT SUM = 0;
UNSIGNED SHORT * W = AddR;
UNSIGNED SHORT ANSWER = 0;
/ * Tired of ICMP header binary data in 2 bytes * /
While (NLEFT> 1)
{SUM = * W ;
NLEFT- = 2;
}
/ * If the ICMP header is an odd byte, the last byte is left. Treat the last byte as a high byte of a 2-byte data, the low byte of this 2-byte data is 0, continue to be accumulated * /
IF (NLEFT == 1)
{* (UNSIGNED Char *) (& Answer) = * (unsigned char *) W;
SUM = answer;
}
SUM = (SUM >> 16) (SUM & 0xFFF);
SUM = (SUM >> 16);
Answer = ~ SUM;
Return Answer;
}
/ * Set ICMP header * /
INT PACK (int Pack_no)
{INT I, PACKSIZE;
Struct ICMP * ICMP;
Struct TimeVal * TVAL;
ICMP = (struct icmp *) Sendpacket;
ICMP-> ICMP_TYPE = ICMP_ECHO;
ICMP-> ICMP_CODE = 0;
ICMP-> ICMP_CKSUM = 0;
ICMP-> ICMP_SEQ = PACK_NO;
ICMP-> ICMP_ID = PID;
Packsize = 8 Datalen;
Tval = (struct timeval *) ICMP-> ICMP_DATA;
GetTimeOfDay (TVAL, NULL); / * Record send time * /
ICMP-> ICMP_CKSUM = CAL_CHKSUM ((unsigned short *) ICMP, PACKSIZE); / * Calibration Algorithm * /
Return Packsize;
}
/ * Send three ICMP messages * /
void send_packet ()
{INT PACKETSIZE;
While (nsend {NSEND ; Packetsize = PACK (NSEND); / * Set ICMP header * / IF (SENDTO (Sockfd, Sendpacket, Packetsize, 0, (Struct SockAddr *) & dest_addr, sizeof (dest_addr)) <0) {PERROR ("Sendto Error"); CONTINUE; } Sleep (1); / * Send an ICMP message every other second * / } } / * Receive all ICMP packets * / Void recv_packet () {INT N, Fromlen; EXTERN INT errno; Signal (SIGALRM, STATISTICS); Fromlen = sizeof (from); While (NReceived {alarm (max_wait_time); IF ((n = recvfrom (sockfd, recvpacket, sizeof (recvpacket), 0, (struct sockaddr *) & from, & fromlease) <0) {IF (errno == eintr) Continue; Perror ("Recvfrom Error"); CONTINUE; } GetTimeOfDay (& TVRecv, null); / * Record reception time * / IF (unpack (recvpacket, n) == - 1) Continue; NRECEIVED ; } } / * Striping ICMP header * / INT unpack (char * buf, int LEN) {INT I, IPHDRLEN; Struct ip * IP; Struct ICMP * ICMP; Struct TimeVal * Tvsend; Double RTT; IP = (struct ip *) BUF; IPHDRLEN = IP-> ip_hl << 2; / * Seeking IP header length, ie the length flag of IP headers multiplied by 4 * / ICMP = (struct icmp *) (buf iphdrlen); / * Crossing the IP header, pointing to ICMP header * / LEN- = iphdrlen; / * ICMP header and ICMP dataginary total length * / IF (Len <8) / * is less than ICMP header length is unreasonable * / {Printf ("ICMP Packets / 'S Length IS LESS THAN 8 / N"); Return -1; } / * Make sure the received ICMP response I have sent * / IF ((ICMP-> ICMP_TYPE == ICMP_ECHOREPLY) && (ICMP-> ICMP_ID == PID)) {tvsend = (struct timeval *) ICMP-> ICMP_DATA; TV_SUB (& TVRecv, TVsend); / * Receive and send time difference * / RTT = TVRecv.tv_sec * 1000 TVRecv.tv_usec / 1000; / * calculate rtt * // * display related information in milliseconds * / Printf ("% D Byte from% S: ICMP_SEQ =% U TTL =% D =%. 3F ms / n", Len, INET_NTOA (from.sin_addr), ICMP-> ICMP_SEQ, IP-> ip_ttl, RTT); } Else Return -1; } Main (int Argc, char * argv []) {struct hostent * host; Struct Protoent * protocol; Unsigned long inaddr = 0L; INT waittime = max_wait_time; INT size = 50 * 1024; IF (Argc <2) {Printf ("USAGE:% s hostname / ip address / n", argv [0]); Exit (1); } IF ((protocol = getprotobyname ("ICMP")) == null) {Perror ("getprotobyname"); Exit (1); } / * Generate the original socket using ICMP, this socket is only root to generate * / IF ((Sockfd = Socket (AF_INET, SOCK_RAW, Protocol-> P_Proto) <0) {PERROR ("socket error"); Exit (1); } / * Recycle root permissions, set current user rights * / SetUID (GetUID ()); / * Expand the socket receiving buffer to 50K to do this, to reduce the receiving buffer overflow The possibility, if there is unintentionally ping a broadcast address or multicast address, it will attract a lot of answers * / Setsockopt (SockFD, SOL_Socket, SO_RCVBUF, & SIZE, SIZEOF (SIZE)); Bzero (& Dest_addr, SIZEOF (DEST_ADDR)); dest_addr.sin_family = af_INet; / * Judging is the host name or IP address * / IF (INADDR = INET_ADDR (Argv [1]) == INADDR_NONE) {IF ((Host = gethostByname) == NULL) / * is host name * / {Perror ("gethostbyname error); Exit (1); } Memcpy ((char *) & dest_addr.sin_addr, host-> h_addr, host-> h_length; } ELSE / * is an IP address * / Memcpy (CHAR *) & DEST_ADDR, (CHAR *) & INADDR, HOST-> H_LENGTH; / * Get main process ID for setting ICMP flag * / PID = getPid (); Printf ("ping% s):% d bytes data in icmp packets./n" ,arv[1], inet_ntoa (dest_addr.sin_addr), datalent; Send_packet (); / * Send all ICMP messages * / Recv_packet (); / * Receive all ICMP packets * / Statistics (SIGALRM); / * Conduct statistics * / Return 0; } / * Two TIMEVAL structures subtract * / Void TV_sub (struct timeval * out, struct timeval * in) {if ((out-> tv_usec- = in-> TV_USEC) <0) {--Out-> TV_sec; OUT-> TV_USEC = 1000000; } OUT-> TV_sec- = in-> TV_SEC; } / * ------------- The end ----------- * / Special attention only has root users to generate raw sockets using the socket () function, let Linux general users can perform the above programs, need to perform the following special operation: login with root, compile the program: gcc -o myping myping.c It is a 2: one is compiled, and the other is to let Myping belong to the root user. Execute CHMOD U S Myping, the purpose is to set myping program to the property of the SUID. Exit root, use the general user to log in, execute ./myping www.cn.ibm.com, have the following execution results: PING www.cn.ibm.com (202.95.2.148): 56 bytes Data in ICMP Packets. 64 Byte from 202.95.2.148: ICMP_SEQ = 1 TTL = 242 rtt = 3029.000 ms 64 Byte from 202.95.2.148: ICMP_SEQ = 2 TTL = 242 rtt = 2020.000 ms 64 Byte from 202.95.2.148: ICMP_SEQ = 3 TTL = 242 rtt = 1010.000 ms -------------------- Ping Statistics ------------------- 3 Packets Transmitted, 3 Received,% 0 Lost Since myping.c is to receive all ICMP packets, the round trip time of the first, second and third ICMP messages is 3 seconds, 2 seconds, 1 second, and the RTT information is positive in the above results. Reflect this fact. The author briefly Liang Junhui, has a strong interest in Linux's network applications and programming, and focuses on this study, published in the IBM DeveloperWorks - Linux area.