Document directory
- Ping Command Implementation Program
- Ping Running Effect
25.1. Overview
The original set of interfaces provides the following three functions that are not generally provided by TCP and UDP sets of interfaces.
1. Use the original interface to read and write ICMPv4, igmpv4, and ICMPv6 groups. For example, the Ping program uses the original interface to send an ICMP echo request and accept the ICMP echo response. Mrouted, a daemon for multicast routing, also uses the original set of interfaces to send and receive igmpv4 groups. The above function also allows applications constructed using ICMP or IGMP to complete processing as user processes without adding too much kernel encoding. For example, a vro discovers a daemon (in. rdisc under Solaris 2. x, and Appendix F of tcpv1 describes how to obtain publicly available source code. It processes two ICMP messages that are completely unknown to the kernel (router announcement and router solicitation ).
2. Use the original interface to read and write special IPv4 datagram. The kernel does not process the IPv4 protocol fields of these datagram.
3. Using the original interface set, it seems that you can use the ip_hdrincl interface set Option to construct your own IPv4 header.
25.2. Create the original set of interfaces
The following steps are involved in creating an original interface:
1. When the second parameter is sock_raw, call the socket function to create an original interface. The third parameter (Protocol) should not be 0. For example, to create an IPv4 original interface, we can write as follows:
Int sockfd;
Sockfd = socket (af_inet, sock_raw, Protocol );
The Protocol parameter value is a common value in the form of ipproto_xxx, defined by the header file <netinet/in. h>, such as ipproto_igmp. However, the header file defines a protocol name, such as ipproto_egp, which does not mean that the kernel must support it.
To prevent normal users from writing their own IP data packets to the network, only Super Users have the right to create the original set of interfaces.
2. You can set ip_hdrincl Interface Options:
Const int on = 1;
If (setsockopt (sockfd, ipproto_ip, ip_hdrincl, & on, sizeof (on) <0)
We will introduce the effects of this option in the next section.
3. You can call the BIND function on the original interface, but it is not commonly used. This function is only used to set the local address: For an original set of interfaces, the port number is meaningless. When outputting data, bind sets the source IP address that will be used in the datagram sent on the original interface (only when the ip_hdrincl interface option is not set). If BIND is not called, the kernel sets the source IP address to the primary IP address of the outbound interface.
4. You can call the connect function on the original interface, but it is not commonly used. The connect function only sets the destination address and re-applies it again: the port number is meaningless for the original interface. For the output, we can call write or send instead of sendto because the destination address has been specified after connect is called.
25.3. Original set interface output
The output of the original set of interfaces follows the following rules:
1. normal output is completed by calling sendto or sendmsg and specifying the destination IP address. If the set of interfaces has been connected, you can also call write, writev, or send
2. If the ip_hdrincl option is not set, the start address of the data written by the kernel is the first byte after the IP header. In this case, the kernel constructs an IP header and installs it before the data from the process. The kernel sets the protocol field in the IPv4 header to the third parameter that the user gives when calling the socket function.
3. If the ip_hdrincl option is set, the start address of the data written by the kernel is the first byte in the IP header. The data size value provided by the user must include the number of bytes in the header. In this case, the process constructs the entire IP header except for the following two items: (a) IPv4 Id field can be set to 0, the kernel is required to set this value. (B) The IPv4 header checksum is calculated and stored by the kernel.
4. For groups that exceed the MTU of the outbound interface, the kernel will split them.
The original IPv6 interface is different from IPv4:
1. All fields in the protocol header sent and received by the original IPv6 interface use the network byte sequence.
2. IPv6 does not have a set of Interface Options similar to ip_hdrincl in IPv4. Using the original IPv6 port, you cannot read or write the entire IPv6 group (including the extended header). However, using the set Interface Options and auxiliary data, you can obtain almost all fields and Extended Headers of the IPv6 Header. However, to read and write the entire IPv6 datagram, you must still use the data link for access.
3. There are differences in the validation and processing of the original IPv6 Interface
For the original ICMPv6 interface, it is always calculated by the kernel and Its checksum is stored in the ICMPv6 header. This is different from the original interface of ICMPv4. The original interface of ICMPv4 is calculated by the application process and the checksum is stored.
25.4. Original set interface input
For the input of the original set of interfaces, the first question we want to answer is: which IP groups will be passed to the original set of interfaces. Zhejiang follows the following rules:
1. The received TCP group and UDP group will never be passed to any original interface. If a process wants to read IP data packets including TCP or UDP groups, they must be read at the data link layer.
2. After the kernel processes ICMP messages, most ICMP groups will be passed to the original interface. For implementations originating from Berkeley, all received ICMP groups will be passed to an original set of interfaces except for the bounce request, the timestamp request and the address mask request will be completely processed by the kernel.
3. After the kernel completes IGMP message processing, all IGMP groups will be passed to an original set of interfaces.
4. All IP datagram with unrecognizable protocol fields from the kernel will be transmitted to an original port. The kernel only checks certain fields in the IP header for these groups: IP version, IPv4 header checksum, header length, and destination IP address.
5. If the data report arrives in the form of a segment, the group will pass the packet to the original set of interfaces only after all the segments arrive and are reorganized.
After the kernel has prepared a data packet to be transmitted, the kernel checks the original sets of interfaces of all processes to find all matching sets of interfaces. Each matching set of interfaces will receive a copy of the IP datagram. The following are three tests for each original set of APIs. The datagram is delivered to this set of Apis only when all three tests are true.
1. If the Protocol Parameter specified during the creation of the original interface is not zero (the third parameter of socket), the Protocol field of the received datagram should match the value. Otherwise, the datagram is not delivered to this interface.
2. if a local IP address is bound to the original interface, the destination IP address of the received datagram should match the bound IP address. Otherwise, the datagram will not be delivered to the interface.
3. if the original interface specifies an IP address of the other party by calling connect, the source IP address of the received datagram should match the connection address, otherwise, the datagram is not delivered to this interface.
NOTE: If an original socket port is created using the Protocol parameter 0 and is used to call connect and bind, for each original datagram transmitted by the kernel to the original socket interface, this interface will receive a copy. In addition, when a received datagram is transmitted to the original IPv4 port, the entire datagram is transmitted to the process. For the original IPv6 interface, the entire datagram content outside the outbound extension header is transmitted to the interface.
In the IPv4 header passed to the application process, ip_len, ip_off, and ip_id are in the host's byte sequence, and other fields are in the network's byte sequence. In Linux, all fields are in the network byte order.
The original ICMPv6 interface receives most ICMPv4 messages received by the kernel, but ICMPv6 is a superset of ICMPv4, including ARP and IGMP functions. The number of groups to be filtered. The filter uses the struct icmp6_filter data type declaration, which is defined by <netinet/icmp6.h>. The current filter of an original ICMPv6 interface can be set and obtained using setsockopt and getsockopt. The level parameter is ipproto_cimpv6, And the optname parameter is icmp6_filter.
The following are six macros that operate the icmp6_filter structure:
# Include <netinet/icmp6.h> void Merge (struct icmp6_filter * filt); void Merge (struct icmp6_filter * filt); void merge (INT msgtype, struct icmp6_filter * filt ); void merge (INT msgtype, struct icmp6_filter * filt); int reverse (INT msgtype, const struct icmp6_filter * filt); int reverse (INT msgtype, const struct icmp6_filter * filt ); /* return value: if the corresponding message type of the filter transmission (blocking) is 1, otherwise it is 0 */
The filt parameter in the preceding macro call is a pointer to an icmp6_filter variable. The first four macros modify the icmp6_filter variable and the last two macros check it. The msgtype value ranges from 0 ~ The value range is 255. The ICMP message type is specified.
The setpassall macro sets that all message types can be passed to the application process. The setblockall macro sets no message type to be passed. By default, when an ICMPv6 original interface is created, all ICMPv6 message types can be passed to the application process.
The setpass macro opens the delivery of a message type to the application process, while the setblock macro blocks the delivery of a message type. If the given message type can be passed by the filter, willpass macro returns 1; otherwise, 0 is returned. If the given message type is blocked by the filter, willblock macro returns 1; otherwise, 0 is returned.
As an example, suppose an application that only receives the ICMPv6 Router:
struct icmp6_filter myfilt;fd = Socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);ICMP6_FILTER_SETBLOCKALL(&myfilt);ICMP6_FILTER_SETPASS(NO_ROUTER_ADVERT, &myfilt);Setsockopt(fd, IPPROTO_IMCPV6, ICMP6_FILTER, &myfilt, sizeof(myfilt));
In this example, the transmission of all message types is blocked first, and then the vro notification message is delivered.
25.5. Ping program
In this chapter, we will develop a Ping program that supports both IPv4 and IPv6. This program is different from the public available version. First, this program ignores a large number of publicly available options supported by the Ping program. Secondly, this program supports both IPv6 and IPv4, while the open Ping program only supports IPv4
The ping operation is very simple. Send an ICMP echo request to some IP addresses, and then the node returns an ICMP echo response. The two ICMP messages are supported in both IPv4 and IPv6. Figure 25.1 shows the ICMP Message format:
Figure 25.3 shows an overview of the functions that constitute the entire Ping program.
Ping Command Implementation Program
Ping. h file
/* Includes the basic header files of IPv4 and ICMPv4, defining partial full-Process Variables and function prototype */# include <netinet/in_sy1_.h> # include <netinet/IP. h >#include <netinet/ip_icmp.h> # define bufsize 1500/* globals */Char recvbuf [bufsize]; char sendbuf [bufsize]; int datalen;/* # bytes of data, following ICMP header */char * Host; int nsent;/* Add 1 for each sendto () */pid_t PID;/* Our PID */INT sockfd; int verbose; /* function prototypes */void proc_v4 (char *, SSI Ze_t, struct timeval *); void proc_v6 (char *, ssize_t, struct timeval *); void send_v4 (void); void send_v6 (void); void readloop (void ); void sig_alrm (INT); void TV _sub (struct timeval *, struct timeval *);/* we use the proto structure to handle the differences between IPv4 and IPv6. This structure contains two function pointers, two interface address result pointers, the size of the two interface address structures, and ICMP protocol values. The full-process variable pointer PR will point to a proto structure initialized for IPv4 or IPv6 */struct proto {void (* fproc) (char *, ssize_t, struct timeval *); void (* fsend) (void); struct sockaddr * sasend;/* sockaddr {} For send, from getaddrinfo */struct sockaddr * sarecv; /* sockaddr {} for sorting */socklen_t Salen;/* length of sockaddr {} s */INT icmpproto;/* ipproto_xxx value for ICMP */} * PR;
Main file, Main. c
# Include <stdio. h> # include <errno. h> # include <signal. h> # include <unistd. h> # include <sys/types. h> # include <sys/socket. h> # include <netdb. h> # include <stdlib. h> # include <stdarg. h> # include "Ping. H "# UNDEF IPv6 // printf (" % d bytes from % s: TYPE = % d, code = % d \ n ", icmplen, sock_ntop_host (pr-> sarecv, pr-> salen), ICMP-> icmp_type, ICMP-> icmp_code); # define gettimeofday # define sock_ntop_host (x, y) I Net_ntop (af_inet, & (struct sockaddr_in *) (x)-> sin_addr), strip, Y) Char Strip [128]; err_doit (INT errnoflag, const char * FMT, va_list AP) {int errno_save; char Buf [1, 255]; errno_save = errno;/* value caller might want printed */vsprintf (BUF, FMT, AP ); if (errnoflag) sprintf (BUF + strlen (BUF), ": % s", strerror (errno_save); strcat (BUF, "\ n"); fflush (stdout ); /* In case stdout and stderr are the same */ Fputs (BUF, stderr); fflush (stderr);/* SunOS 4. 1. * doesn't grok null argument */return;} err_sys (const char * FMT ,...) {va_list AP; va_start (AP, FMT); err_doit (1, FMT, AP); va_end (AP); exit (1 );} err_quit (const char * FMT ,...) {va_list AP; va_start (AP, FMT); err_doit (0, FMT, AP); va_end (AP); exit (1);} void proc_v4 (char * PTR, ssize_t Len, struct timeval * tvrecv) {int hlen1, icmplen; double RTT; Str Uct ip * IP; struct ICMP * ICMP; struct timeval * tvsend;/* The IPv4 header length field is counted in 4 bytes, multiply it by 4 to the header length in bytes. This allows us to correctly set ICMP to point to the starting address of the ICMP header */IP = (struct IP *) PTR; /* Start of IP header */hlen1 = IP-> ip_hl <2;/* length of IP header */ICMP = (struct ICMP *) (PTR + hlen1 ); /* Start of ICMP header */If (icmplen = len-hlen1) <8) err_quit ("icmplen (% d) <8", icmplen ); /* If the received message is an ICMP echo response, we must check the Identifier Field to see if the response is a response to the request sent by our process. If the process on this host is run multiple times, each process will obtain a copy of the ICMP message received by the host */If (ICMP-> icmp_type = ICMP_ECHOREPLY) {If (ICMP-> icmp_id! = PID) return;/* not a response to our echo_request */If (icmplen <16) err_quit ("icmplen (% d) <16", icmplen ); /* subtract the message sending time from the current time to calculate RTT */tvsend = (struct timeval *) ICMP-> icmp_data; TV _sub (tvrecv, tvsend ); RTT = tvrecv-> TV _sec x 1000.0 + tvrecv-> TV _usec/1000.0; printf ("% d bytes from % s: seq = % u, TTL = % d, RTT = %. 3f Ms \ n ", icmplen, sock_ntop_host (pr-> sarecv, pr-> salen), ICMP-> icmp_seq, IP-> ip_ttl, RTT);} else if (verbose)/* If the-V command line option is specified, this program will output the type fields and code fields of all received ICMP messages */{printf ("% d bytes from % s: TYPE = % d, code = % d \ n ", icmplen, sock_ntop_host (pr-> sarecv, pr-> salen), ICMP-> icmp_type, ICMP-> icmp_code );}} /* define the proto structure for IPv4 and IPv6 respectively. Because I do not know whether IPv4 or IPv6 is used, the interface address structure pointer is initialized to null */struct proto proto_v4 = {proc_v4, send_v4, null, null, 0, ipproto_icmp}; # ifdef defaults 6struct proto proto_v6 = {proc _ V6, send_v6, null, null, 0, ipproto_icmp}; # endif/* sets the length of optional data sent along with the echo request to 56 bytes. In this way, IPv4 datagram is generated, the total length is 84 bytes (20-byte IPv4 header, 8-byte ICMP header). If an IPv6 datagram is generated, the total length is 104 bytes. Any data contained in the bounce request must be returned by the bounce response. In this example, we will store the time when the callback request was sent in the first eight bytes of the callback request data area, so that it can be used to calculate and output RTT */INT datalen = 56 after receiving the reply; /* data that goes with ICMP Echo Request */void TV _sub (struct timeval * Out, struct timeval * In) {If (out-> TV _usec-= In-> TV _usec) <0)/* Out-= In */{-- Out-> TV _sec; out-> TV _usec + = 1000000;} Out-> TV _sec-= In-> TV _sec ;} unsigned shortin_cksum (unsigned short * ADDR, int Len) {int nleft = Len; int sum = 0; unsigned Short * w = ADDR; unsigned short answer = 0;/** our algorithm is simple, using a 32 bit accumulator (SUM), * we add sequential 16 bit words to it, and at the end, * Fold back all the carry bits from the top 16 bits into the lower 16 bits */while (nleft> 1) {sum + = * w ++; nleft-= 2;} If (nleft = 1) {* (unsigned char *) (& answer) = * (unsigned char *) W; sum + = answer ;} /* Add back carry outs fro M Top 16 bits to low 16 bits */SUM = (sum> 16) + (sum & 0 xFFFF ); /* Add Hi 16 to low 16 */SUM + = (sum> 16);/* add carry */answer = ~ SUM;/* truncate to 16 bits */Return (answer);} void send_v4 (void) {int Len; struct ICMP * ICMP;/* construct ICMPv4 messages, set the Identifier Field with the process ID, set the serial number with the full variable nsent, and add nsent to the next group by 1. The current time is saved to the data part of the ICMP Message */ICMP = (struct ICMP *) sendbuf; ICMP-> icmp_type = ICMP_Echo; ICMP-> icmp_code = 0; ICMP-> icmp_id = PID; ICMP-> icmp_seq = nsent ++; gettimeofday (struct timeval *) ICMP-> icmp_data, null);/* to calculate the ICMP checksum, we first set the checksum field to 0, and then call the in_cksu function. M to calculate the checksum and save the result to the checksum field. */Len = 8 + datalen;/* checksum ICMP header and Data */ICMP-> icmp_cksum = 0; ICMP-> icmp_cksum = in_cksum (u_short *) ICMP, Len ); sendto (sockfd, sendbuf, Len, 0, pr-> sasend, pr-> salen);/* Send an ICMP message through the original interface. Since ip_hdrincl is not used, the kernel constructs the IPv4 group header for us and installs it before our buffer */} void sig_alrm (INT signo) {(* Pr-> fsend) (); Alarm (1); return;/* probably interrupts recvfrom () */} void readloop (void) {int size; char recvbuf [bufsize]; socklen_t Len; ssize_t N; struct timeval tval;/* create an original set of interfaces with appropriate protocols */sockfd = socket (pr-> sasend-> sa_family, sock_raw, pr-> icmpproto ); if (sockfd <0) {perror ("socket:"); exit (-1) ;}/ * sets the valid user ID of the process to the actual user ID of the process. User ID */setuid (getuid ();/* dont need special permissions any more * // * sets the receiver buffer of the Set interface to 61440 bytes, which is much larger than the default value. To reduce the possibility of receiving buffer overflow */size = 60*1024;/* OK if setsockopt fails * // */setsockopt (sockfd, sol_socket, so_rcvbuf, & size, sizeof (size);/* call the signal processing program to send a group and schedule the next sigalrm for one second. */Sig_alrm (sigalrm);/* Send first packet * // * The main loop of the program is an infinite loop, it reads and returns to each group of the ICMP original interface */For (;) {Len = Pr-> Salen; n = recvfrom (sockfd, recvbuf, sizeof (recvbuf ), 0, pr-> sarecv, & Len); If (n <0) {If (errno = eintr) continue; else err_sys ("recvfrom error ");} gettimeofday (& tval, null); (* Pr-> fproc) (recvbuf, N, & tval) ;}} struct addrinfo * host_serv (const char * host, const char * serv, int family, int Socktype) {int N; struct addrinfo hints, * res; bzero (& hints, sizeof (struct addrinfo); hints. ai_flags = ai_canonname;/* always return canonical name */hints. ai_family = family;/* 0, af_inet, af_inet6, etc. */hints. ai_socktype = socktype;/* 0, sock_stream, sock_dgram, etc. */If (n = getaddrinfo (host, serv, & hints, & res ))! = 0) err_quit ("host_serv error for % s, % s: % s", (host = NULL )? "(No hostname)": Host, (SERV = NULL )? "(No service name)": Serv, gai_strerror (n); Return (RES);/* return pointer to first on linked list */} int main (INT argc, char ** argv) {int C; struct addrinfo * Ai;/* The only command line option of this program is-V, which determines whether to output most ICMP messages received (of course, we do not output a reply to another running Ping process ). */Opterr = 0;/* dont want getopt () writing to stderr */while (C = getopt (argc, argv, "V ")! =-1) {Switch (c) {Case 'V': verbose ++; break; Case '? ': Err_quit ("unrecongized option: % C", c) ;}} if (optind! = Argc-1) err_quit ("Usage: Ping [-v]
Ping Running Effect
[Root @ localhost raw_sock] #./A. Out-V 10.33.28.254
Ping 10.33.28.254 (10.33.28.254): 56 data bytes
64 bytes from 10.33.28.254: seq = 0, TTL = 255, RTT = 1.733 MS
64 bytes from 10.33.28.254: seq = 1, TTL = 255, RTT = 1.668 MS
64 bytes from 10.33.28.254: seq = 2, TTL = 255, RTT = 0.863 MS
64 bytes from 10.33.28.254: seq = 3, TTL = 255, RTT = 0.990 MS
64 bytes from 10.33.28.254: seq = 4, TTL = 255, RTT = 1.239 MS
64 bytes from 10.33.28.254: seq = 5, TTL = 255, RTT = 4.184 MS
Reference: http://www.cnblogs.com/s7vens/archive/2012/04/16/2451635.html