Home
小杰的博客 Prev Page Prev Page
?
Main Page
Table of content
Copyright
Addison-Wesley Professional Computing Series
Foreword
Preface
Introduction
Changes from the Second Edition
Using This Book
Source Code and Errata Availability
Acknowledgments
Part 1: Introduction and TCP/IP
Chapter 1. Introduction
1.1 Introduction
1.2 A Simple Daytime Client
1.3 Protocol Independence
1.4 Error Handling: Wrapper Functions
1.5 A Simple Daytime Server
1.6 Roadmap to Client/Server Examples in the Text
1.7 OSI Model
1.8 BSD Networking History
1.9 Test Networks and Hosts
1.10 Unix Standards
1.11 64-Bit Architectures
1.12 Summary
Exercises
Chapter 2. The Transport Layer: TCP, UDP, and SCTP
2.1 Introduction
2.2 The Big Picture
2.3 User Datagram Protocol (UDP)
2.4 Transmission Control Protocol (TCP)
2.5 Stream Control Transmission Protocol (SCTP)
2.6 TCP Connection Establishment and Termination
2.7 TIME_WAIT State
2.8 SCTP Association Establishment and Termination
2.9 Port Numbers
2.10 TCP Port Numbers and Concurrent Servers
2.11 Buffer Sizes and Limitations
2.12 Standard Internet Services
2.13 Protocol Usage by Common Internet Applications
2.14 Summary
Exercises
Part 2: Elementary Sockets
Chapter 3. Sockets Introduction
3.1 Introduction
3.2 Socket Address Structures
3.3 Value-Result Arguments
3.4 Byte Ordering Functions
3.5 Byte Manipulation Functions
3.6 'inet_aton', 'inet_addr', and 'inet_ntoa' Functions
3.7 'inet_pton' and 'inet_ntop' Functions
3.8 'sock_ntop' and Related Functions
3.9 'readn', 'writen', and 'readline' Functions
3.10 Summary
Exercises
Chapter 4. Elementary TCP Sockets
4.1 Introduction
4.2 'socket' Function
4.3 'connect' Function
4.4 'bind' Function
4.5 'listen' Function
4.6 'accept' Function
4.7 'fork' and 'exec' Functions
4.8 Concurrent Servers
4.9 'close' Function
4.10 'getsockname' and 'getpeername' Functions
4.11 Summary
Exercises
Chapter 5. TCP Client/Server Example
5.1 Introduction
5.2 TCP Echo Server: 'main' Function
5.3 TCP Echo Server: 'str_echo' Function
5.4 TCP Echo Client: 'main' Function
5.5 TCP Echo Client: 'str_cli' Function
5.6 Normal Startup
5.7 Normal Termination
5.8 POSIX Signal Handling
5.9 Handling 'SIGCHLD' Signals
5.10 'wait' and 'waitpid' Functions
5.11 Connection Abort before 'accept' Returns
5.12 Termination of Server Process
5.13 'SIGPIPE' Signal
5.14 Crashing of Server Host
5.15 Crashing and Rebooting of Server Host
5.16 Shutdown of Server Host
5.17 Summary of TCP Example
5.18 Data Format
5.19 Summary
Exercises
Chapter 6. I/O Multiplexing: The 'select' and 'poll' Functions
6.1 Introduction
6.2 I/O Models
6.3 'select' Function
6.4 'str_cli' Function (Revisited)
6.5 Batch Input and Buffering
6.6 'shutdown' Function
6.7 'str_cli' Function (Revisited Again)
6.8 TCP Echo Server (Revisited)
6.9 'pselect' Function
6.10 'poll' Function
6.11 TCP Echo Server (Revisited Again)
6.12 Summary
Exercises
Chapter 7. Socket Options
7.1 Introduction
7.2 'getsockopt' and 'setsockopt' Functions
7.3 Checking if an Option Is Supported and Obtaining the Default
7.4 Socket States
7.5 Generic Socket Options
7.6 IPv4 Socket Options
7.7 ICMPv6 Socket Option
7.8 IPv6 Socket Options
7.9 TCP Socket Options
7.10 SCTP Socket Options
7.11 'fcntl' Function
7.12 Summary
Exercises
Chapter 8. Elementary UDP Sockets
8.1 Introduction
8.2 'recvfrom' and 'sendto' Functions
8.3 UDP Echo Server: 'main' Function
8.4 UDP Echo Server: 'dg_echo' Function
8.5 UDP Echo Client: 'main' Function
8.6 UDP Echo Client: 'dg_cli' Function
8.7 Lost Datagrams
8.8 Verifying Received Response
8.9 Server Not Running
8.10 Summary of UDP Example
8.11 'connect' Function with UDP
8.12 'dg_cli' Function (Revisited)
8.13 Lack of Flow Control with UDP
8.14 Determining Outgoing Interface with UDP
8.15 TCP and UDP Echo Server Using 'select'
8.16 Summary
Exercises
Chapter 9. Elementary SCTP Sockets
9.1 Introduction
9.2 Interface Models
9.3 'sctp_bindx' Function
9.4 'sctp_connectx' Function
9.5 'sctp_getpaddrs' Function
9.6 'sctp_freepaddrs' Function
9.7 'sctp_getladdrs' Function
9.8 'sctp_freeladdrs' Function
9.9 'sctp_sendmsg' Function
9.10 'sctp_recvmsg' Function
9.11 'sctp_opt_info' Function
9.12 'sctp_peeloff' Function
9.13 'shutdown' Function
9.14 Notifications
9.15 Summary
Exercises
Chapter 10. SCTP Client/Server Example
10.1 Introduction
10.2 SCTP One-to-Many-Style Streaming Echo Server: 'main' Function
10.3 SCTP One-to-Many-Style Streaming Echo Client: 'main' Function
10.4 SCTP Streaming Echo Client: 'str_cli' Function
10.5 Exploring Head-of-Line Blocking
10.6 Controlling the Number of Streams
10.7 Controlling Termination
10.8 Summary
Exercises
Chapter 11. Name and Address Conversions
11.1 Introduction
11.2 Domain Name System (DNS)
11.3 'gethostbyname' Function
11.4 'gethostbyaddr' Function
11.5 'getservbyname' and 'getservbyport' Functions
11.6 'getaddrinfo' Function
11.7 'gai_strerror' Function
11.8 'freeaddrinfo' Function
11.9 'getaddrinfo' Function: IPv6
11.10 'getaddrinfo' Function: Examples
11.11 'host_serv' Function
11.12 'tcp_connect' Function
11.13 'tcp_listen' Function
11.14 'udp_client' Function
11.15 'udp_connect' Function
11.16 'udp_server' Function
11.17 'getnameinfo' Function
11.18 Re-entrant Functions
11.19 'gethostbyname_r' and 'gethostbyaddr_r' Functions
11.20 Obsolete IPv6 Address Lookup Functions
11.21 Other Networking Information
11.22 Summary
Exercises
Part 3: Advanced Sockets
Chapter 12. IPv4 and IPv6 Interoperability
12.1 Introduction
12.2 IPv4 Client, IPv6 Server
12.3 IPv6 Client, IPv4 Server
12.4 IPv6 Address-Testing Macros
12.5 Source Code Portability
12.6 Summary
Exercises
Chapter 13. Daemon Processes and the 'inetd' Superserver
13.1 Introduction
13.2 'syslogd' Daemon
13.3 'syslog' Function
13.4 'daemon_init' Function
13.5 'inetd' Daemon
13.6 'daemon_inetd' Function
13.7 Summary
Exercises
Chapter 14. Advanced I/O Functions
14.1 Introduction
14.2 Socket Timeouts
14.3 'recv' and 'send' Functions
14.4 'readv' and 'writev' Functions
14.5 'recvmsg' and 'sendmsg' Functions
14.6 Ancillary Data
14.7 How Much Data Is Queued?
14.8 Sockets and Standard I/O
14.9 Advanced Polling
14.10 Summary
Exercises
Chapter 15. Unix Domain Protocols
15.1 Introduction
15.2 Unix Domain Socket Address Structure
15.3 'socketpair' Function
15.4 Socket Functions
15.5 Unix Domain Stream Client/Server
15.6 Unix Domain Datagram Client/Server
15.7 Passing Descriptors
15.8 Receiving Sender Credentials
15.9 Summary
Exercises
Chapter 16. Nonblocking I/O
16.1 Introduction
16.2 Nonblocking Reads and Writes: 'str_cli' Function (Revisited)
16.3 Nonblocking 'connect'
16.4 Nonblocking 'connect:' Daytime Client
16.5 Nonblocking 'connect:' Web Client
16.6 Nonblocking 'accept'
16.7 Summary
Exercises
Chapter 17. 'ioctl' Operations
17.1 Introduction
17.2 'ioctl' Function
17.3 Socket Operations
17.4 File Operations
17.5 Interface Configuration
17.6 'get_ifi_info' Function
17.7 Interface Operations
17.8 ARP Cache Operations
17.9 Routing Table Operations
17.10 Summary
Exercises
Chapter 18. Routing Sockets
18.1 Introduction
18.2 Datalink Socket Address Structure
18.3 Reading and Writing
18.4 'sysctl' Operations
18.5 'get_ifi_info' Function (Revisited)
18.6 Interface Name and Index Functions
18.7 Summary
Exercises
Chapter 19. Key Management Sockets
19.1 Introduction
19.2 Reading and Writing
19.3 Dumping the Security Association Database (SADB)
19.4 Creating a Static Security Association (SA)
19.5 Dynamically Maintaining SAs
19.6 Summary
Exercises
Chapter 20. Broadcasting
20.1 Introduction
20.2 Broadcast Addresses
20.3 Unicast versus Broadcast
20.4 'dg_cli' Function Using Broadcasting
20.5 Race Conditions
20.6 Summary
Exercises
Chapter 21. Multicasting
21.1 Introduction
21.2 Multicast Addresses
21.3 Multicasting versus Broadcasting on a LAN
21.4 Multicasting on a WAN
21.5 Source-Specific Multicast
21.6 Multicast Socket Options
21.7 'mcast_join' and Related Functions
21.8 'dg_cli' Function Using Multicasting
21.9 Receiving IP Multicast Infrastructure Session Announcements
21.10 Sending and Receiving
21.11 Simple Network Time Protocol (SNTP)
21.12 Summary
Exercises
Chapter 22. Advanced UDP Sockets
22.1 Introduction
22.2 Receiving Flags, Destination IP Address, and Interface Index
22.3 Datagram Truncation
22.4 When to Use UDP Instead of TCP
22.5 Adding Reliability to a UDP Application
22.6 Binding Interface Addresses
22.7 Concurrent UDP Servers
22.8 IPv6 Packet Information
22.9 IPv6 Path MTU Control
22.10 Summary
Exercises
Chapter 23. Advanced SCTP Sockets
23.1 Introduction
23.2 An Autoclosing One-to-Many-Style Server
23.3 Partial Delivery
23.4 Notifications
23.5 Unordered Data
23.6 Binding a Subset of Addresses
23.7 Determining Peer and Local Address Information
23.8 Finding an Association ID Given an IP Address
23.9 Heartbeating and Address Failure
23.10 Peeling Off an Association
23.11 Controlling Timing
23.12 When to Use SCTP Instead of TCP
23.13 Summary
Exercises
Chapter 24. Out-of-Band Data
24.1 Introduction
24.2 TCP Out-of-Band Data
24.3 'sockatmark' Function
24.4 TCP Out-of-Band Data Recap
24.5 Summary
Exercises
Chapter 25. Signal-Driven I/O
25.1 Introduction
25.2 Signal-Driven I/O for Sockets
25.3 UDP Echo Server Using 'SIGIO'
25.4 Summary
Exercises
Chapter 26. Threads
26.1 Introduction
26.2 Basic Thread Functions: Creation and Termination
26.3 'str_cli' Function Using Threads
26.4 TCP Echo Server Using Threads
26.5 Thread-Specific Data
26.6 Web Client and Simultaneous Connections (Continued)
26.7 Mutexes: Mutual Exclusion
26.8 Condition Variables
26.9 Web Client and Simultaneous Connections (Continued)
26.10 Summary
Exercises
Chapter 27. IP Options
27.1 Introduction
27.2 IPv4 Options
27.3 IPv4 Source Route Options
27.4 IPv6 Extension Headers
27.5 IPv6 Hop-by-Hop Options and Destination Options
27.6 IPv6 Routing Header
27.7 IPv6 Sticky Options
27.8 Historical IPv6 Advanced API
27.9 Summary
Exercises
Chapter 28. Raw Sockets
28.1 Introduction
28.2 Raw Socket Creation
28.3 Raw Socket Output
28.4 Raw Socket Input
28.5 'ping' Program
28.6 'traceroute' Program
28.7 An ICMP Message Daemon
28.8 Summary
Exercises
Chapter 29. Datalink Access
29.1 Introduction
29.2 BSD Packet Filter (BPF)
29.3 Datalink Provider Interface (DLPI)
29.4 Linux: 'SOCK_PACKET' and 'PF_PACKET'
29.5 'libpcap': Packet Capture Library
29.6 'libnet': Packet Creation and Injection Library
29.7 Examining the UDP Checksum Field
29.8 Summary
Exercises
Chapter 30. Client/Server Design Alternatives
30.1 Introduction
30.2 TCP Client Alternatives
30.3 TCP Test Client
30.4 TCP Iterative Server
30.5 TCP Concurrent Server, One Child per Client
30.6 TCP Preforked Server, No Locking Around 'accept'
30.7 TCP Preforked Server, File Locking Around 'accept'
30.8 TCP Preforked Server, Thread Locking Around 'accept'
30.9 TCP Preforked Server, Descriptor Passing
30.10 TCP Concurrent Server, One Thread per Client
30.11 TCP Prethreaded Server, per-Thread 'accept'
30.12 TCP Prethreaded Server, Main Thread 'accept'
30.13 Summary
Exercises
Chapter 31. Streams
31.1 Introduction
31.2 Overview
31.3 'getmsg' and 'putmsg' Functions
31.4 'getpmsg' and 'putpmsg' Functions
31.5 'ioctl' Function
31.6 Transport Provider Interface (TPI)
31.7 Summary
Exercises
Appendix A. IPv4, IPv6, ICMPv4, and ICMPv6
A.1 Introduction
A.2 IPv4 Header
A.3 IPv6 Header
A.4 IPv4 Addresses
A.5 IPv6 Addresses
A.6 Internet Control Message Protocols (ICMPv4 and ICMPv6)
Appendix B. Virtual Networks
B.1 Introduction
B.2 The MBone
B.3 The 6bone
B.4 IPv6 Transition: 6to4
Appendix C. Debugging Techniques
C.1 System Call Tracing
C.2 Standard Internet Services
C.3 'sock' Program
C.4 Small Test Programs
C.5 'tcpdump' Program
C.6 'netstat' Program
C.7 'lsof' Program
Appendix D. Miscellaneous Source Code
D.1 'unp.h' Header
D.2 'config.h' Header
D.3 Standard Error Functions
Appendix E. Solutions to Selected Exercises
Chapter 1
Chapter 2
Chapter 3
Chapter 4
Chapter 5
Chapter 6
Chapter 7
Chapter 8
Chapter 9
Chapter 10
Chapter 11
Chapter 12
Chapter 13
Chapter 14
Chapter 15
Chapter 16
Chapter 17
Chapter 18
Chapter 20
Chapter 21
Chapter 22
Chapter 24
Chapter 25
Chapter 26
Chapter 27
Chapter 28
Chapter 29
Chapter 30
Chapter 31
Bibliography
?
[ Team LiB ] Previous Section Next Section

28.5 ping Program

In this section, we will develop and present a version of the ping program that works with both IPv4 and IPv6. We will develop our own program instead of presenting the publicly available source code for two reasons. First, the publicly available ping program suffers from a common programming disease known as creeping featurism: It supports a dozen different options. Our goal in examining a ping program is to understand the network programming concepts and techniques without being distracted by all these options. Our version of ping supports only one option and is about five times smaller than the public version. Second, the public version works only with IPv4 and we want to show a version that also supports IPv6.

The operation of ping is extremely simple: An ICMP echo request is sent to some IP address and that node responds with an ICMP echo reply. These two ICMP messages are supported under both IPv4 and IPv6. Figure 28.1 shows the format of the ICMP messages.

Figure 28.1. Format of ICMPv4 and ICMPv6 echo request and echo reply messages.

graphics/28fig01.gif

Figure A.15 and A.16 show the type values for these messages and also show that the code is 0. We will see that we set the identifier to the PID of the ping process and we increment the sequence number by one for each packet we send. We store the 8-byte timestamp of when the packet is sent as the optional data. The rules of ICMP require that the identifier, sequence number, and any optional data be returned in the echo reply. Storing the timestamp in the packet lets us calculate the RTT when the reply is received.

Figure 28.2 shows some examples of our program. The first uses IPv4 and the second uses IPv6. Note that we made our ping program set-user-ID, as it takes superuser privileges to create a raw socket.

Figure 28.2 Sample output from our ping program.
freebsd % ping www.google.com
PING www.google.com (216.239.57.99): 56 data bytes
64 bytes from 216.239.57.99: seq=0, ttl=53, rtt=5.611 ms
64 bytes from 216.239.57.99: seq=1, ttl=53, rtt=5.562 ms
64 bytes from 216.239.57.99: seq=2, ttl=53, rtt=5.589 ms
64 bytes from 216.239.57.99: seq=3, ttl=53, rtt=5.910 ms

freebsd % ping www.kame.net
PING orange.kame.net (2001:200:0:4819:203:47ff:fea5:3085): 56 data bytes
64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=0, hlim=52, rtt=422.066 ms
64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=1, hlim=52, rtt=417.398 ms
64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=2, hlim=52, rtt=416.528 ms
64 bytes from 2001:200:0:4819:203:47ff:fea5:3085: seq=3, hlim=52, rtt=429.192 ms

Figure 28.3 is an overview of the functions that comprise our ping program.

Figure 28.3. Overview of the functions in our ping program.

graphics/28fig03.gif

The program operates in two parts: One half reads everything received on a raw socket, printing the ICMP echo replies, and the other half sends an ICMP echo request once per second. The second half is driven by a SIGALRM signal once per second.

Figure 28.4 shows our ping.h header that is included by all our program files.

Figure 28.4 ping.h header.

ping/ping.h

 1 #include    "unp.h"
 2 #include    <netinet/in_systm.h>
 3 #include    <netinet/ip.h>
 4 #include    <netinet/ip_icmp.h>

 5 #define BUFSIZE     1500

 6             /* globals */
 7 char    sendbuf[BUFSIZE];

 8 int     datalen;                /* #bytes of data following ICMP header */
 9 char   *host;
10 int     nsent;                  /* add 1 for each sendto() */
11 pid_t   pid;                    /* our PID */
12 int     sockfd;
13 int     verbose;

14             /* function prototypes */
15 void    init_v6(void);
16 void    proc_v4(char *, ssize_t, struct msghdr *, struct timeval *);
17 void    proc_v6(char *, ssize_t, struct msghdr *, struct timeval *);
18 void    send_v4(void);
19 void    send_v6(void);
20 void    readloop(void);
21 void    sig_alrm(int);
22 void    tv_sub(struct timeval *, struct timeval *);

23 struct proto {
24     void    (*fproc) (char *, ssize_t, struct msghdr *, struct timeval *);
25     void    (*fsend) (void);
26     void    (*finit) (void);
27     struct sockaddr *sasend;    /* sockaddr{} for send, from getaddrinfo */
28     struct sockaddr *sarecv;    /* sockaddr{} for receiving */
29     socklen_t salen;            /* length of sockaddr {}s */
30     int     icmpproto;          /* IPPROTO_xxx value for ICMP */
31 } *pr;

32 #ifdef IPV6

33 #include    <netinet/ip6.h>
34 #include    <netinet/icmp6.h>

35 #endif

Include IPv4 and ICMPv4 headers

1鈥?2 We include the basic IPv4 and ICMPv4 headers, define some global variables, and our function prototypes.

Define proto structure

23鈥?1 We use the proto structure to handle the difference between IPv4 and IPv6. This structure contains two function pointers, two pointers to socket address structures, the size of the socket address structures, and the protocol value for ICMP. The global pointer pr will point to one of the structures that we will initialize for either IPv4 or IPv6.

Include IPv6 and ICMPv6 headers

32鈥?5 We include two headers that define the IPv6 and ICMPv6 structures and constants (RFC 3542 [Stevens et al. 2003]).

The main function is shown in Figure 28.5.

Figure 28.5 main function.

ping/main.c

 1 #include     "ping.h"

 2 struct proto proto_v4 =
 3     { proc_v4, send_v4, NULL, NULL, NULL, 0, IPPROTO_ICMP };

 4 #ifdef  IPV6
 5 struct proto proto_v6 =
 6     { proc_v6, send_v6, NULL, NULL, 0, IPPROTO_ICMPV6 };
 7 #endif

 8 int     datalen = 56;   /* data that goes with ICMP echo request */

 9 int
10 main(int argc, char **argv)
11 {
12     int     c;
13     struct addrinfo *ai;
14     char   *h;

15     opterr = 0;                  /* don't want getopt() writing to stderr */
16     while ( (c = getopt (argc, argv, "v") ) != -1) {
17         switch (c) {
18         case 'v':
19             verbose++;
20             break;

21          case '?':
22              err_quit ("unrecognized option: %c", c);
23          }
24     }

25     if  (optind != argc - 1)
26         err_quit ("usage: ping [ -v ] <hostname>");
27     host = argv [optind];

28     pid = getpid() & Oxffff;     /* ICMP ID field is 16 bits */
29     Signal(SIGALRM, sig_alrm);

30     ai = Host_serv (host, NULL, 0, 0);

31     h = Sock_ntop_host(ai->ai_addr, ai->ai_addrlen);
32     printf ("PING %s (%s): %d data bytes\n",
33             ai->ai_canonname ? ai->ai_canonname : h, h, datalen);

34         /* initialize  according to protocol */
35     if (ai->ai_family == AF_INET) {
36          pr = &proto_v4;
37 #ifdef   IPV6
38     } else if (ai->ai_family == AF_INET6) {
39         pr = &proto_v6;
40         if (IN6_IS_ADDR_V4MAPPED (&(((struct sockaddr_in6 *)
41                             ai->ai_addr)->sin6_addr)))
42             err_quit ("cannot ping IPv4-mapped IPv6 address");
43 #endif
44     } else
45         err_quit ("unknown address family %d", ai->ai_family);

46     pr->sasend = ai->ai_addr;
47     pr->sacrecv = Calloc (1, ai->ai_addrlen);
48     pr->salen = ai->ai_addrlen);

49     readloop();

50     exit(0);
51 }

Define proto structures for IPv4 and IPv6

2鈥? We define a proto structure for IPv4 and IPv6. The socket address structure pointers are initialized to null pointers, as we do not yet know whether we will use IPv4 or IPv6.

Length of optional data

8 We set the amount of optional data that gets sent with the ICMP echo request to 56 bytes. This will yield an 84-byte IPv4 datagram (20-byte IPv4 header and 8-byte ICMP header) or a 104-byte IPv6 datagram. Any data that accompanies an echo request must be sent back in the echo reply. We will store the time at which we send an echo request in the first 8 bytes of this data area and then use this to calculate and print the RTT when the echo reply is received.

Handle command-line options

15鈥?4 The only comman-line option we support is -v, which will cause us to print most received ICMP messages. (We do not print echo replies belonging to another copy of ping that is running.) A signal handler is established for SIGALRM, and we will see that this signal is generated once per second and causes an ICMP echo request to be sent.

Process hostname argument

31鈥?8 A hostname or IP address string is a required argument and it is processed by our host_serv function. The returned addrinfo structure contains the protocol family, either AF_INET or AF_INET6. We initialize the pr global to the correct proto structure. We also make certain that an IPv6 address is not really an IPv4-mapped IPv6 address by calling IN6_IS_ADDR_V4MAPPED, because even though the returned address is an IPv6 address, IPv4 packets will be sent to the host. (We could switch and use IPv4 when this happens.) The socket address structure that has already been allocated by the getaddrinfo function is used as the one for sending, and another socket address structure of the same size is allocated for receiving.

49 The function readloop is where the processing takes place. We will show this in Figure 28.6.

Create socket

12鈥?3 A raw socket of the appropriate protocol is created. The call to setuid sets our effective user ID to our real user ID, in case the program was set-user-ID instead of being run by root. The program must have superuser privileges to create the raw socket, but now that the socket is created, we can give up the extra privileges. It is always best to give up an extra privilege when it is no longer needed, just in case the program has a latent bug that someone could exploit.

Figure 28.6 readloop function.

ping/readloop.c

 1 #include     "ping.h"

 2 void
 3 readloop(void)
 4 {
 5     int     size;
 6     char    recvbuf[BUFSIZE];
 7     char    controlbuf[BUFSIZE];
 8     struct msghdr msg;
 9     struct iovec iov;
10     ssize_t n;
11     struct timeval tval;

12     sockfd = Socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);
13     setuid(getuid());           /* don't need special permissions any more */
14     if (pr->finit)
15         (*pr->finit) ();

16     size = 60 * 1024;           /* OK if setsockopt fails */
17     setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size));

18     sig_alrm (SIGALRM);         /* send first packet */

19     iov.iov_base = recvbuf;
20     iov.iov_len = sizeof (recvbuf);
21     msg.msg_name = pr->sarecv;
22     msg.msg_iov = &iov;
23     msg.msg_iovlen = 1;
24     msg.msg_control = controlbuf;
25     for ( ; ; ) {
26         msg.msg_namelen = pr->salen;
27         msg.msg_controllen = sizeof (controlbuf);
28         n = recvmsg (sockfd, &msg, 0);
29         if (n < o) {
30             if (errno == EINTR)
31                 continue;
32             else
33                 err_sys("recvmsg error");
34         }
35         Gettimeofday (&tval, NULL);
36         (*pr->fproc) (recvbuf, n, &msg, &tval);
37    }
38 }

Perform protocol-specific initialization

14鈥?5 If the protocol specified an initialization function, we call it. We show the IPv6 initialization function in Figure 28.10.

Set socket receive buffer size

16鈥?7 We try to set the socket receive buffer size to 61,440 bytes (60 x 1024), which should be larger than the default. We do this in case the user pings either the IPv4 broadcast address or a multicast address, either of which can generate lots of replies. By making the buffer larger, there is a smaller chance that the socket receive buffer will overflow.

Send first packet

18 We call our signal handler, which we will see sends a packet and schedules a SIGALRM for one second in the future. It is not common to see a signal handler called directly, as we do here, but it is acceptable. A signal handler is just a C function, even though it is normally called asynchronously.

Set up msghdr for recvmsg

19鈥?4 We set up the unchanging fields in the msghdr and iovec structs that we will pass to recvmsg.

Infinite loop reading all ICMP messages

25鈥?7 The main loop of the program is an infinite loop that reads all packets returned on the raw ICMP socket. We call gettimeofday to record the time that the packet was received and then call the appropriate protocol function (proc_v4 or proc_v6) to process the ICMP message.

Figure 28.7 shows the tv_sub function, which subtracts two timeval structures, storing the result in the first structure.

Figure 28.7 tv_sub function: subtracts two timeval structures.

lib/tv_sub.c

 1 #include     "unp.h"

 2 void
 3 tv_sub (struct timeval *out, struct timeval *in)
 4 {
 5     if ((out->tv_usec -= in->tv_usec) < 0) {     /* out -= in */
 6         --out->tv_sec;
 7         out->tv_usec += 1000000;
 8     }
 9     out->tv_sec -= in->tv_sec;
10 }

Figure 28.8 shows the proc_v4 function, which processes all received ICMPv4 messages. You may want to refer to Figure A.1, which shows the format of the IPv4 header. Also realize that when the ICMPv4 message is received by the process on the raw socket, the kernel has already verified that the basic fields in the IPv4 header and in the ICMPv4 header are valid (pp. 214 and 311 of TCPv2).

Figure 28.8 proc_v4 function: processes ICMPv4 message.

ping/proc_v4.c

 1 #include     "ping.h"

 2 void
 3 proc_v4 (char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
 4 {
 5     int     hlenl, icmplen;
 6     double  rtt;
 7     struct ip *ip;
 8     struct icmp *icmp;
 9     struct timeval *tvsend;

10     ip = (struct ip *) ptr;      /* start of IP header */
11     hlenl = ip->ip_hl << 2;      /* length of IP header */
12     if (ip->ip_p != IPPROTO_ICMP)
13         return;                  /* not ICMP */

14     icmp = (struct icmp *) (ptr + hlenl);   /* start of ICMP header */
15     if ( (icmplen = len - hlenl) < 8)
16         return;                  /* malformed packet */

17     if (icmp->icmp_type == ICMP_ECHOREPLY) {
18         if (icmp->icmp_id != pid)
19             return;                /* not a response to our ECHO_REQUEST */
20         if (icmplen < 16)
21             return;                /* not enough data to use */

22         tvsend = (struct  timeval  *) icmp->icmp_data;
23         tv_sub (tvrecv, tvsend);
24         rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;

25         printf ("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n",
26                 icmplen, Sock_ntop_host (pr->sarecv, pr->salen),
27                 icmp->icmp_seq, ip->ip_ttl, rtt);

28     } else if  (verbose) {
29         printf (" %d bytes from %s: type = %d, code = %d\n",
30                 icmplen, Sock_ntop_host (pr->sarecv, pr->salen),
31                 icmp->icmp_type, icmp->icmp_code);
32     }
33 }

Get pointer to ICMP header

10鈥?6 The IPv4 header length field is multiplied by 4, giving the size of the IPv4 header in bytes. (Remember that an IPv4 header can contain options.) This lets us set icmp to point to the beginning of the ICMP header. We make sure that the IP protocol is ICMP and that there is enough data echoed to look at the timestamp we included in the echo request. Figure 28.9 shows the various headers, pointers, and lengths used by the code.

Figure 28.9. Headers, pointers, and lengths in processing ICMPv4 reply.

graphics/28fig09.gif

Check for ICMP echo reply

17鈥?1 If the message is an ICMP echo reply, then we must check the identifier field to see if this reply is in response to a request our process sent. If the ping program is running multiple times on this host, each process gets a copy of all received ICMP messages.

22鈥?7 We calculate the RTT by subtracting the time the message was sent (contained in the optional data portion of the ICMP reply) from the current time (pointed to by the tvrecv function argument). The RTT is converted from microseconds to milliseconds and printed, along with the sequence number field and the received TTL. The sequence number field lets the user see if packets were dropped, reordered, or duplicated, and the TTL gives an indication of the number of hops between the two hosts.

Print all received ICMP messages if verbose option specified

28鈥?2 If the user specified the -v command-line option, we print the type and code fields from all other received ICMP messages.

The processing of ICMPv6 messages is handled by the proc_v6 function, shown in Figure 28.12 (p. 751). It is similar to the proc_v4 function; however, since IPv6 raw sockets do not return the IPv6 header, it receives the hop limit as ancillary data. This was set up using the init_v6 function, shown in Figure 28.10.

The init_v6 function prepares the socket for use.

Set ICMPv6 receive filter

6鈥?4 If the -v command-line option was not specified, install a filter that blocks all ICMP message types except for the expected echo reply. This reduces the number of packets received on the socket.

Request IPV6_HOPLIMIT ancillary data

15鈥?2 The API to request reception of the hop limit with incoming packets has changed over time. We prefer the newer API: setting the IPV6_RECVHOPLIMIT socket option. However, if the constant for this option is not defined, we can try the older API: setting IPV6_HOPLIMIT as an option. We don't check the return value from setsockopt, since the program can still do useful work without receiving the hop limit.

Figure 28.10 init_v6 function: initializes ICMPv6 socket.

ping/init_v6.c

 1 void
 2 init_v6()
 3 {
 4 #ifdef IPV6
 5     int     on = 1;

 6     if (verbose == 0) {
 7         /* install a filter that only passes ICMP6_ECHO_REPLY unless verbose */
 8         struct icmp6_filter myfilt;
 9         ICMP6_FILTER_SETBLOCKALL (&myfilt);
10         ICMP6_FILTER_SETPASS (ICMP6_ECHO_REPLY, &myfilt);
11         setsockopt (sockfd, IPPROTO_IPV6, ICMP6_FILTER, &myfilt,
12                     sizeof (myfilt));
13         /* ignore error return; the filter is an optimization */
14     }

15     /* ignore error returned below; we just won't receive the hop limit */
16 #ifdef IPV6_RECVHOPLIMIT
17     /* RFC 3542 */
18     setsockopt (sockfd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on));
19 #else
20     /* RFC 2292 */
21     setsockopt (sockfd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on));
22 #endif
23 #endif
24 }

The proc_v6 function (Figure 28.12) processes incoming packets.

Get pointer to ICMPv6 header

11鈥?3 The ICMPv6 header is the data returned by the receive operation. (Recall that the IPv6 header and extension headers, if any, are never returned as normal data, but as ancillary data.) Figure 28.11 shows the various headers, pointers, and lengths used by the code.

Figure 28.11. Headers, pointers, and lengths in processing ICMPv6 reply.

graphics/28fig11.gif

Check for ICMP echo reply

14鈥?7 If the ICMP message type is an echo reply, we check the identifier field to see if the reply is for us. If so, we calculate the RTT and then print it along with the sequence number and IPv6 hop limit. We obtain the hop limit from the IPV6_HOPLIMIT ancillary data.

Figure 28.12 proc_v6 function: processes received ICMPv6 message.

ping/proc_v6.c

 1 #include     "ping.h"

 2 void
 3 proc_v6 (char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
 4 {
 5 #ifdef IPV6
 6     double rtt;
 7     struct icmp6_hdr *icmp6;
 8     struct timeval *tvsend;
 9     struct cmsghdr *cmsg;
10     int     hlim;

11     icmp6 = (struct icmp6_hdr *) ptr;
12     if (len < 8)
13         return;                 /* malformed packet */

14     if (icmp6->icmp6_type == ICMP6_ECHO_REPLY) {
15         if (icmp6->icmp6_id != pid)
16             return;             /* not a response to our ECHO_REQUEST */
17         if (len < 16)
18             return;             /* not enough data to use */

19         tvsend = (struct timeval *) (icmp6 + 1);
20         tv_sub (tvrecv, tvsend);
21         rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;

22         hlim = -1;
23         for (cmsg = CMSG_FIRSTHDR (msg); cmsg != NULL;
24              cmsg = CMSG_NXTHDR (msg, cmsg)) {
25             if (cmsg->cmsg_level == IPPROTO_IPV6
26                 && cmsg->cmsg_type == IPV6_HOPLIMIT) {
27                 hlim = * (u_int32_t *) CMSG_DATA (cmsg);
28                 break;
29             }
30         }
31         printf("%d bytes from %s: seq=%u, hlim=",
32                len, Sock_ntop_host (pr->sarecv, pr->salen), icmp6->icmp6_seq);
33         if (hlim == -1)
34             printf("???");     /* ancillary data missing */
35         else
36             printf("%d", hlim);
37         printf(", rtt=%.3f ms\n", rtt);
38     } else if (verbose) {
39         printf(" %d bytes from %s: type = %d, code = %d\n",
40                len, Sock_ntop_host (pr->sarecv, pr->salen),
41                icmp6->icmp6_type, icmp6->icmp6_code);
42     }
43 #endif /* IPV6 */
44 }

Print all received ICMP messages if verbose option specified

38鈥?1 If the user specified the -v command-line option, we print the type and code fields from all other received ICMP messages.

Our signal handler for the SIGALRM signal is the sig_alrm function, shown in Figure 28.13. We saw in Figure 28.6 that our readloop function calls this signal handler once at the beginning to send the first packet. This function just calls the protocol-dependent function to send an ICMP echo request (send_v4 or send_v6) and then schedules another SIGALRM for one second in the future.

Figure 28.13 sig_alrm function: SIGALRM signal handler.

ping/sig_alrm.c

1 #include     "ping.h"

2 void
3 sig_alrm (int signo)
4 {
5     (*pr->fsend) ();

6     alarm(1);
7     return;
8 }

The function send_v4, shown in Figure 28.14, builds an ICMPv4 echo request message and writes it to the raw socket.

Figure 28.14 send_v4 function: builds an ICMPv4 echo request message and sends it.

ping/send_v4.c

 1 #include     "ping.h"

 2 void
 3 send_v4 (void)
 4 {
 5     int     len;
 6     struct icmp *icmp;

 7     icmp = (struct icmp *) sendbuf;
 8     icmp->icmp_type = ICMP_ECHO;
 9     icmp->icmp_code = 0;
10     icmp->icmp_id = pid;
11     icmp->icmp-seq = nsent++;
12     memset (icmp->icmp_data, 0xa5, datalen); /* fill with pattern */
13     Gettimeofday ((struct timeval *) icmp->icmp_data, NULL);

14     len = 8 + datalen;           /* checksum ICMP header and data */
15     icmp->icmp_cksum = 0;
16     icmp->icmp_cksum = in_cksum ((u_short *) icmp, len);

17     Sendto (sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
18 }

Build ICMPv4 message

7鈥?3 The ICMPv4 message is built. The identifier field is set to our PID and the sequence number field is set to the global nsent, which is then incremented for the next packet. We store a pattern of 0xa5 in the data portion of the ICMP message. The current time-of-day is then stored in the beginning of the data portion.

Calculate ICMP checksum

14鈥?6 To calculate the ICMP checksum, we set the checksum field to 0 and call the function in_cksum, storing the result in the checksum field. The ICMPv4 checksum is calculated from the ICMPv4 header and any data that follows.

Send datagram

17 The ICMP message is sent on the raw socket. Since we have not set the IP_HDRINCL socket option, the kernel builds the IPv4 header and prepends it to our buffer.

The Internet checksum is the one's complement of the one's complement sum of the 16-bit values to be checksummed. If the data length is an odd number, then 1 byte of 0 is logically appended to the end of the data, just for the checksum computation. Before computing the checksum, the checksum field itself is set to 0. This algorithm is used for the IPv4, ICMPv4, IGMPv4, ICMPv6, UDP, and TCP checksums. RFC 1071 [Braden, Borman, and Partridge 1988] contains additional information and some numeric examples. Section 8.7 of TCPv2 talks about this algorithm in more detail and shows a more efficient implementation. Our in_cksum function, shown in Figure 28.15, calculates the checksum.

Figure 28.15 in_cksum function: calculate the Internet checksum.

libfree/in_cksum.c

 1 uint16_t
 2 in_cksum (uint16_t * addr, int len)
 3 {
 4     int     nleft = len;
 5     uint32_t sum = 0;
 6     uint16_t *w = addr;
 7     uint16_t answer = 0;

 8     /*
 9      * Our algorithm is simple, using a 32 bit accumulator (sum), we add
10      * sequential 16 bit words to it, and at the end, fold back all the
11      * carry bits from the top 16 bits into the lower 16 bits.
12      */
13     while (nleft > 1) {
14         sum += *w++;
15         nleft -= 2;
16     }
17         /* mop up an odd byte, if necessary */
18     if (nleft == 1) {
19         * (unsigned char *) (&answer) = * (unsigned char *) w;
20         sum += answer;
21     }

22         /* add back carry outs from top 16 bits to low 16 bits */
23     sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
24     sum += (sum >> 16);     /* add carry */
25     answer = ~sum;     /* truncate to 16 bits */
26     return (answer);
27 }

Internet checksum algorithm

1鈥?7 The first while loop calculates the sum of all the 16-bit values. If the length is odd, then the final byte is added in with the sum. The algorithm we show in Figure 28.15 is the simple algorithm. The kernel often has a specially optimized checksum algorithm due to the high volume of checksum computations performed by the kernel.

This function is taken from the public domain version of ping by Mike Muuss.

The final function for our ping program is send_v6, shown in Figure 28.16, which builds and sends an ICMPv6 echo request.

Figure 28.16 send_v6 function: builds and sends an ICMPv6 echo request message.

ping/send_v6.c

 1 #include     "ping.h"

 2 void
 3 send_v6 ()
 4 {
 5 #ifdef IPV6
 6     int     len;
 7     struct icmp6_hdr *icmp6;

 8     icmp6 = (struct icmp6_hdr *) sendbuf;
 9     icmp6->icmp6_type = ICMP6_ECHO_REQUEST;
10     icmp6->icmp6_code = 0;
11     icmp6->icmp6_id = pid;
12     icmp6->icmp6_seq = nsent++;
13     memset ((icmp6 + 1), 0xa5, datalen); /* fill with pattern */
14     Gettimeofday ((struct timeval *) (icmp6 + 1), NULL);

15     len = 8 + datalen;           /* 8-byte ICMPv6 header */

16     Sendto (sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
17         /* kernel calculates and stores checksum for us */
18 #endif  /* IPV6 */
19 }

The send_v6 function is similar to send_v4, but notice that it does not compute the ICMPv6 checksum. As we mentioned earlier in the chapter, since the ICMPv6 checksum uses the source address from the IPv6 header in its computation, this checksum is calculated by the kernel for us, after the kernel chooses the source address.

[ Team LiB ] Previous Section Next Section
Converted from CHM to HTML with chm2web Pro 2.85 (unicode)