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.7 An ICMP Message Daemon

Receiving asynchronous ICMP errors on a UDP socket has been, and continues to be, a problem. ICMP errors are received by the kernel, but are rarely delivered to the application that needs to know about them. In the sockets API, we have seen that it requires connecting the UDP socket to one IP address to receive these errors (Section 8.11). The reason for this limitation is that the only error returned from recvfrom is an integer errno code, and if the application sends datagrams to multiple destinations and then calls recvfrom, this function cannot tell the application which datagram encountered an error.

In this section, we will provide a solution that does not require any kernel changes. We will provide an ICMP message daemon, icmpd, that creates a raw ICMPv4 socket and a raw ICMPv6 socket and receives all ICMP messages the kernel passes to these two raw sockets. It also creates a Unix domain stream socket, binds it to the pathname /tmp/icmpd, and listens for incoming client connects to this pathname. We will show this in Figure 28.26.

Figure 28.26. icmpd daemon: initial sockets created.

graphics/28fig26.gif

A UDP application (which is a client to the daemon) first creates its UDP socket, the socket for which it wants to receive asynchronous errors. The application must bind an ephemeral port to this socket, for reasons we will discuss later. It then creates a Unix domain socket and connects to this daemon's well-known pathname. We will show this in Figure 28.27.

Figure 28.27. Application creates its UDP socket and a Unix domain connection to the daemon.

graphics/28fig27.gif

The application next "passes" its UDP socket to the daemon across the Unix domain connection using descriptor passing, as we described in Section 15.7. This gives the daemon a copy of the socket so that it can call getsockname and obtain the port number bound to the socket. We will show this passing of the socket in Figure 28.28.

Figure 28.28. Passing UDP socket to daemon across Unix domain connection.

graphics/28fig28.gif

After the daemon obtains the port number bound to the UDP socket, it closes its copy of the socket, taking us back to the arrangement shown in Figure 28.27.

If the host supports credential passing (Section 15.8), the application could also send its credentials to the daemon. The daemon could then check whether this user should be allowed access to this facility.

From this point on, any ICMP errors the daemon receives in response to UDP datagrams sent from the port bound to the application's UDP socket cause the daemon to send a message (which we will describe shortly) across the Unix domain socket to the application. The application must therefore use select or poll, awaiting data on either the UDP socket or the Unix domain socket.

We now look at the source code for an application using this daemon, and then the daemon itself. We start with Figure 28.29, our header that is included by both the application and the daemon.

Figure 28.29 unpicmpd.h header.

icmpd/unpicmpd.h

 1 #ifndef__unpicmp_h
 2 #define__unpicmp_h

 3 #include   "unp.h"

 4 #define ICMPD_PATH     "/tmp/icmpd"     /* server's well-known pathname */

 5 struct icmpd_err {
 6     int      icmpd_errno;      /* EHOSTUNREACH, EMSGSIZE, ECONNREFUSED */
 7     char     icmpd_type;       /* actual ICMPv[46] type */
 8     char     icmpd_code;       /* actual ICMPv[46] code */
 9     socklen_t icmpd_len;       /* length of sockaddr{} that follows */
10     struct sockaddr_storage icmpd_dest;  /* sockaddr_storage handles any size */
11 };
12 #endif  /* __unpicmp_h */

4鈥?1 We define the server's well-known pathname and the icmpd_err structure that is passed from the server to the application whenever an ICMP message is received that should be passed to this application.

6鈥? A problem is that the ICMPv4 message types differ numerically (and sometimes conceptually) from the ICMPv6 message types (Figures A.15 and A.16). The actual ICMP type and code values are returned, but we also map these into an errno value (icmpd_errno), similar to the final columns in Figures A.15 and A.16. The application can deal with this value instead of the protocol-dependent ICMPv4 or ICMPv6 values. Figure 28.30 shows the ICMP messages that are handled, plus their mapping into an errno value.

Figure 28.30. icmpd_errno mapping from ICMPv4 and ICMPv6 errors.

graphics/28fig30.gif

The daemon returns five types of ICMP errors.

  • "port unreachable," indicating that no socket is bound to the destination port at the destination IP address.

  • "packet too big," which is used with path MTU discovery. Currently, there is no API defined to allow a UDP application to perform path MTU discovery. What often happens on kernels that support path MTU discovery for UDP is that the receipt of this ICMP error causes the kernel to record the new path MTU value in the kernel's routing table, but the UDP application that sent the datagram that got discarded is not notified. Instead, the application must time out and retransmit the datagram, in which case, the kernel will find the new (and smaller) MTU in its routing table, and the kernel will then fragment the datagram. Passing this error back to the application lets the application retransmit sooner, and perhaps lets the application reduce the size of the datagrams it sends.

  • The "time exceeded" error is normally seen with a code of 0, indicating that either the IPv4 TTL or IPv6 hop limit reached 0. This often indicates a routing loop, which might be a transient error.

  • ICMPv4 "source quenches," while deprecated by RFC 1812 [Baker 1995], may be sent by routers (or by misconfigured hosts acting as routers). They indicate that a packet has been discarded, and we therefore treat them like a "destination unreachable" message. Note that IPv6 does not have a "source quench" error.

  • All other destination unreachable messages indicate that a packet has been discarded.

10 The icmpd_dest member is a socket address structure containing the destination IP address and port of the datagram that generated the ICMP error. This member will be either a sockaddr_in structure for IPv4 or a sockaddr_in6 structure for IPv6. If the application is sending datagrams to multiple destinations, it probably has one socket address structure per destination. By returning this information in a socket address structure, the application can compare it against its own structures to find the one that caused the error. It is a sockaddr_storage to allow storage of any sockaddr type the system supports.

UDP Echo Client That Uses Our icmpd Daemon

We now modify our UDP echo client, the dg_cli function, to use our icmpd daemon. Figure 28.31 shows the first half of the function.

2鈥? The function arguments are the same as all previous versions of this function.

bind wildcard address and ephemeral port

12 We call our sock_bind_wild function to bind the wildcard IP address and an ephemeral port to the UDP socket. We do this so that the copy of this socket that we pass to the daemon has bound a port, as the daemon needs to know this port.

The daemon could also do this bind if a local port has not already been bound to the socket that it receives, but this does not work in all environments. Certain SVR4 implementations, such as Solaris 2.5, in which sockets are not part of the kernel, have a bug when one process binds a port to a shared socket; the other process with a copy of that socket gets strange errors when it tries to use the socket. The easiest solution is to require the application to bind the local port before passing the socket to the daemon.

Establish Unix domain connection to daemon

13鈥?6 We create an AF_LOCAL socket and connect to the daemon's well-known pathname.

Figure 28.31 First half of dg_cli application.

icmpd/dgcli01.c

 1 #include     "unpicmpd.h"

 2 void
 3 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
 4 {
 5     int     icmpfd, maxfdpl;
 6     char    sendline[MAXLINE], recvline[MAXLINE + 1];
 7     fd_set  rset;
 8     ssize_t n;
 9     struct timeval tv;
10     struct icmpd_err icmpd_err;
11     struct sockaddr_un sun;

12     Sock_bind_wild(sockfd, pservaddr->sa_family);

13     icmpfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
14     sun.sun_family = AF_LOCAL;
15     strcpy(sun.sun_path, ICMPD_PATH);
16     Connect(icmpfd, (SA *) &sun, sizeof(sun));
17     Write_fd(icmpfd, "1", 1, sockfd);
18     n = Read(icmpfd, recvline, 1);
19     if (n != 1 | | recvline[0] != '1')
20         err_quit("error creating icmp socket, n = %d, char = %c",
21                   n, recvline[0]);

22     FD_ZERO(&rset);
23     maxfdpl = max(sockfd, icmpfd) + 1;
Send UDP socket to daemon, await daemon's reply

17鈥?1 We call our write_fd function from Figure 15.13 to send a copy of our UDP socket to the daemon. We also send a single byte of data, the character "1", because some implementations do not like passing a descriptor without any data. The daemon sends back a single byte of data, consisting of the character "1" to indicate success. Any other reply indicates an error.

22鈥?3 We initialize a descriptor set and calculate the first argument for select (the maximum of the two descriptors, plus one).

The last half of our client is shown in Figure 28.32. This is the loop that reads a line from standard input, sends the line to the server, reads back the server's reply, and writes the reply to standard output.

Figure 28.32 Last half of dg_cli application.

icmpd/dgcli01.c

24     while (Fgets(sendline, MAXLINE, fp) ! = NULL) {
25         Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

26          tv.tv_sec = 5;
27          tv.tv_usec = 0;
28          FD_SET(sockfd, &rset);
29          FD_SET(icmpfd, &rset);
30          if ( (n = Select(maxfdpl, &rset,  NULL, NULL, &tv)) == 0) {
31              fprintf(stderr,   socket timeout\n);
32              continue;
33          }

34          if (FD_ISSET(sockfd,  &rset)) {
35              n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
36              recvline[n] = 0;     /* null terminate */
37              Fputs(recvline, stdout);
38          }

39          if (FD_ISSET(icmpfd, &rset)) {
40              if ( (n = Read(icmpfd, &icmpd_err, sizeof(icmpd_err))) == 0)
41                   err_quit ("ICMP daemon terminated");
42              else if (n ! = sizeof(icmpd_err))
43                   err_quit("n = %d, expected %d", n, sizeof(icmpd_err));
44              printf("ICMP error: dest = %s, %s, type = %d, code = %d\n",
45                     Sock_ntop(&icmpd_err.icmpd_dest, icmpd_err.icmpd_len),
46                     strerror(icmpd_err.icmpd_errno),
47                     icmpd_err.icmpd_type, icmpd_err.icmpd_code);
48          }
49     }
50 }
Call select

26鈥?3 Since we are calling select, we can easily place a timeout on our wait for the echo server's reply. We set this to five seconds, enable both descriptors for readability, and call select. If a timeout occurs, we print a message and go back to the top of the loop.

Print server's reply

34鈥?8 If a datagram is returned by the server, we print it to standard output.

Handle ICMP error

39鈥?8 If our Unix domain connection to the icmpd daemon is readable, we try to read an icmpd_err structure. If this succeeds, we print the relevant information the daemon returns.

strerror is an example of a simple, almost trivial, function that should be more portable than it is. First, ANSI C says nothing about an error return from the function. The Solaris man page says that the function returns a null pointer if the argument is out of range. But this means code like


printf("%s", strerror(arg));

is incorrect because strerror can return a null pointer. But the FreeBSD implementation, along with all the source code implementations the authors could find, handle an invalid argument by returning a pointer to a string such as "Unknown error." This makes sense and means the code above is fine. But POSIX changes this and says that because no return value is reserved to indicate an error, if the argument is out of range, the function sets errno to EINVAL. (POSIX does not say anything about the returned pointer in the case of an error.) This means that completely conforming code must set errno to 0, call strerror, test whether errno equals EINVAL, and print some other message in case of an error.

UDP Echo Client Examples

We now show some examples of this client before looking at the daemon source code. We first send datagrams to an IP address that is not connected to the Internet.


freebsd % udpcli01 192.0.2.5 echo
hi there
socket timeout
and hello
socket timeout

We assume icmpd is running and expect ICMP "host unreachable" errors to be returned by some router, but none are received. Instead, our application times out. We show this to reiterate that a timeout is still required and the generation of ICMP messages such as "host unreachable" may not occur.

Our next example sends a datagram to the standard echo server on a host that is not running the server. We receive an ICMPv4 "port unreachable" as expected.


freebsd % udpcli01 aix-4 echo
hello, world
ICMP error: dest = 192.168.42.2:7, Connection refused, type = 3, code = 3

We try again with IPv6 and receive an ICMPv6 "port unreachable" as expected.


freebsd % udpcli01 aix-6 echo
hello, world
ICMP error: dest = [3ffe:b80:1f8d:2:204:acff:fe17:bf38] :7,
                                   Connection refused, type = 1, code = 4

We have wrapped the long line for readability.

icmpd Daemon

We start the description of our icmpd daemon with the icmpd.h header, shown in Figure 28.33.

client array

2鈥?7 Since the daemon can handle any number of clients, we use an array of client structures to keep the information about each client. This is similar to the data structures we used in Section 6.8. In addition to the descriptor for the Unix domain connection to the client, we also store the address family of the client's UDP socket AF_INET or AF_INET6) and the port number bound to this socket. We also declare the function prototypes and the globals shared by these functions.

Figure 28.33 icmpd.h header for icmpd daemon.

icmpd/icmpd.h

 1 #include     "unpicmpd.h"

 2 struct client {
 3     int     connfd;             /* Unix domain stream socket to client */
 4     int     family;             /* AF_INET or AF_INET6 */
 5     int     lport;              /* local port bound to client's UDP socket */
 6     /* network byte ordered */
 7 } client [FD_SETSIZE];

 8                          /* globals */
 9 int    fd4, fd6, listenfd, maxi, maxfd, nready;
10 fd_set rset, allset;
11 struct sockaddr_un cliaddr;

12                 /* function prototypes */
13 int     readable_conn (int);
14 int     readable_listen (void);
15 int     readable_v4 (void);
16 int     readable_v6 (void);
Figure 28.34 First half of main function: creates sockets.

icmpd/icmpd.c

 1 #include     "icmpd.h"

 2 int
 3 main(int argc, char **argv)
 4 {
 5     int     i, sockfd;
 6     struct sockaddr_un sun;

 7     if (argc != 1)
 8         err_quit ("usage: icmpd");

 9     maxi = -1;                   /* index into client [] array */
10     for (i = 0; i < FD_SETSIZE; i++)
11         client [i] .connfd = -1;   /* -1 indicates available entry */
12     FD_ZERO (&allset);

13     fd4 = Socket (AF_INET, SOCK_RAW, IPPROTO_ICMP);
14     FD_SET (fd4, &allset);
15     maxfd = fd4;

16 #ifdef IPV6
17     fd6 = Socket (AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
18     FD_SET (fd6, &allset);
19     maxfd = max (maxfd, fd6);
20 #endif

21     listenfd = Socket (AF_UNIX, SOCK_STREAM, 0);
22     sun.sun_family = AF_LOCAL;
23     strcpy (sun.sun_path, ICMPD_PATH);
24     unlink (ICMPD_PATH);
25     Bind (listenfd, (SA *) &sun, sizeof (sun));
26     Listen (listenfd, LISTENQ);
27     FD_SET (listenfd, &allset);
28     maxfd = max (maxfd, listenfd);

Figure 28.34 shows the first half of the main function.

Initialize client array

9鈥?0 The client array is initialized by setting the connected socket member to 鈥?.

Create sockets

12鈥?8 Three sockets are created: a raw ICMPv4 socket, a raw ICMPv6 socket, and a Unix domain stream socket. We unlink any previously existing instance of the Unix domain socket, bind its well-known pathname to the socket, and call listen. This is the socket to which clients connect. The maximum descriptor is also calculated for select and a socket address structure is allocated for calls to accept.

Figure 28.35 shows the second half of the main function, which is an infinite loop that calls select, waiting for any of the daemon's descriptors to be readable.

Figure 28.35 Second half of main function: handles readable descriptor.

icmpd/icmpd.c

29      for ( ; ; ) {
30          rset = allset;
31          nready = Select (maxfd + 1, &rset, NULL, NULL, NULL);

32          if (FD_ISSET (listenfd, &rset))
33              if (readable_listen () <= 0)
34                  continue;

35          if (FD_ISSET (fd4, &rset))
36              if (readable_v4 () <= 0)
37                  continue;

38 #ifdef IPV6
39        if (FD_ISSET (fd6, &rset))
40              if (readable_v6 () <= 0)
41                  continue;
42 #endif

43          for (i = 0; i <= maxi; i++) { /* check all clients for data */
44              if ( (sockfd = client [i] .connfd) < 0)
45                  continue;
46          if (FD_ISSET (sockfd, &rset))
47              if (readable_conn (i) <= 0)
48                  break;     /* no more readable descriptors */
49          }
50     }
51     exit (0);
52 }
Check listening Unix domain socket

32鈥?4 The listening Unix domain socket is tested first and if ready, readable_listen is called. The variable nready, the number of descriptors that select returns as readable, is a global variable. Each of our readable_XXX function decrements this variable and returns its new value as the return value of the function. When this value reaches 0, all the readable descriptors have been processed and select is called again.

Check raw ICMP sockets

35鈥?2 The raw ICMPv4 socket is tested, and then the raw ICMPv6 socket.

Check connected Unix domain sockets

43鈥?9 We next check whether any of the connected Unix domain sockets are readable. Readability on any of these sockets means that the client has sent a descriptor, or that the client has terminated.

Figure 28.36 shows the readable_listen function, called when the daemon's listening socket is readable. This indicates a new client connection.

Figure 28.36 Handle new client connections.

icmpd/readable_listen.c

 1 #include   "icmpd.h"

 2 int
 3 readable_listen (void)
 4 {
 5     int     i, connfd;
 6     socklen_t clilen;

 7     clilen = sizeof (cliaddr);
 8     connfd = Accept (listenfd, (SA *) &cliaddr, &clilen);

 9         /* find first available client [] structure */
10     for (i = 0; i < FD_SETSIZE; i++)
11         if (client [i] .connfd < 0) {
12             client [i] .connfd = connfd; /* save descriptor */
13             break;
14          }
15     if (i == FD_SETSIZE) {
16         close (connfd);            /* can't handle new client, */
17         return (--nready);         /* rudely close the new connection */
18     }
19     printf ("new connection, i = %d, connfd = %d\n", i, connfd);

20     FD_SET (connfd, &allset);  /* add new descriptor to set */
21     if (connfd > maxfd)
22         maxfd = connfd;            /* for select () */
23     if (i > maxi)
24         maxi = i;                  /* max index in client [] array */

25     return (--nready);
26 }

7鈥?5 The connection is accepted and the first available entry in the client array is used. The code in this function was copied from the beginning of Figure 6.22. If an entry couldn't be found in the client array, we simply closed the new client connection and remained to serve our current clients.

When a connected socket is readable, our readable_conn function is called (Figure 28.37). Its argument is the index of this client in the client array.

Read client data and possibly a descriptor

13鈥?8 We call our read_fd function from Figure 15.11 to read the data and possibly a descriptor. If the return value is 0, the client has closed its end of the connection, possibly by terminating.

Figure 28.37 Read data and possible descriptor from client.

icmpd/readable_conn.c

 1 #include     "icmpd.h"

 2 int
 3 readable_conn(int i)
 4 {
 5     int     unixfd, recvfd;
 6     char    c;
 7     ssize_t n;
 8     socklen_t len;
 9     struct sockaddr_storage ss;

10     unixfd = client [i] .connfd;
11     recvfd = -1;
12     if ( (n = Read_fd (unixfd, &c, 1, &recvfd)) == 0) {
13         err_msg ("client %d terminated, recvfd = %d", i, recvfd);
14         goto clientdone;         /* client probably terminated */
15     }

16         /* data from client; should be descriptor */
17     if (recvfd < 0) {
18         err_msg ("read_fd did not return descriptor");
19         goto clienterr;
20     }

One design decision was whether to use a Unix domain stream socket between the application and the daemon, or a Unix domain datagram socket. The application's UDP socket can be passed over either type of Unix domain socket. The reason why we used a stream socket was to detect when a client terminated. When a client terminates, all its descriptors are automatically closed, including its Unix domain connection to the daemon, which tells the daemon to remove this client from the client array. Had we used a datagram socket, we would not know when the client terminated.

16鈥?0 If the client has not closed the connection, then we expect a descriptor.

The second half of our readable_conn function is shown in Figure 28.38.

Get port number bound to UDP socket

21鈥?5 getsockname is called so the daemon can obtain the port number bound to the socket. Since we do not know what size buffer to allocate for the socket address structure, we use a sockaddr_storage structure, which is large enough and appropriately aligned to store any socket address structure the system supports.

26鈥?3 The address family of the socket is stored in the client structure, along with the port number. If the port number is 0, we call our sock_bind_wild function to bind the wildcard address and an ephemeral port to the socket, but as we mentioned earlier, this does not work on some SVR4 implementations.

Figure 28.38 Get port number that client has bound to its UDP socket.

icmpd/readable_conn.c

21     len = sizeof (ss);
22     if (getsockname (recvfd, (SA *) &ss, &len) < 0) {
23          err_ret ("getsockname error");
24          goto clienterr;
25     }

26     client[i].family = ss.ss_family;
27     if ((client[i].lport = sock_get_port ((SA *) &ss, len)) == 0) {
28         client[i].lport = sock_bind_wild (recvfd, client[i].family);
29          if (client[i].lport <= 0) {
30             err_ret ("error binding ephemeral port");
31             goto clienterr;
32          }
33     }
34     Write (unixfd, "1", 1);        /* tell client all OK */
35     Close (recvfd);                /* all done with client's UDP socket */
36     return  (--nready);

37   clienterr:
38     Write (unixfd, "0", 1);        /* tell client error occurred */
39     clientdone:
40       Close (unixfd);
41       if (recvfd >= 0)
42           Close (recvfd);
43       FD_CLR (unixfd, &allset);
44       client[i].connfd = -1;
45       return (--nready);
46 }
Indicate success to client

34 One byte consisting of the character "1" is sent back to the client.

close client's UDP socket

35 We are finished with the client's UDP socket and close it. This descriptor was passed to us by the client and is therefore a copy; hence, the UDP socket is still open in the client.

Handle errors and termination of client

37鈥?5 If an error occurs, a byte of "0" is written back to the client. When the client terminates, our end of the Unix domain connection is closed, and the descriptor is removed from the set of descriptors for select. The connfd member of the client structure is set to 鈥?, indicating it is available.

Our readable_v4 function is called when the raw ICMPv4 socket is readable. We show the first half in Figure 28.39. This code is similar to the ICMPv4 code shown earlier in Figures 28.8 and 28.20

Figure 28.39 Handle received ICMPv4 datagram, first half.

icmpd/readable_v4.c

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

 6 int
 7 readable_v4 (void)
 8 {
 9     int      i, hlen1, hlen2, icmplen, sport;
10     char     buf[MAXLINE];
11     char     srcstr [INET_ADDRSTRLEN], dststr[INET_ADDRSTRLEN];
12     ssize_t  n;
13     socklen _ t len;
14     struct  ip *ip, *hip;
15     struct  icmp *icmp;
16     struct  udphdr *udp;
17     struct  sockaddr_in from, dest;
18     struct  icmpd_err icmpd_err;

19     len =  sizeof (from);
20     n =  Recvfrom(fd4, buf, MAXLINE, 0, (SA *) &from, &len);

21     printf("%d bytes ICMPv4 from %s:", n, Sock_ntop_host ((SA *) &from, len));

22     ip = (struct ip *) buf;     /* start of IP header */
23     hlen1 = ip->ip_hl << 2;     /* length of IP header */

24     icmp = (struct icmp *) (buf + hlen1);     /* start of ICMP header */
25     if ( (icmplen = n - hlen1) < 8)
26          err_quit("icmplen (%d) < 8", icmplen);

27     printf(" type = %d, code = %d\n", icmp->icmp_type, icmp->icmp_code);

This function prints some information about every received ICMPv4 message. This was done for debugging when developing this daemon and could be output based on a command-line argument.

Figure 28.40 shows the last half of our readable_v4 function.

Figure 28.40 Handle received ICMPv4 datagram, second half.

icmpd/readable_v4.c

28     if (icmp->icmp_type == ICMP_UNREACH ||
29         icmp->icmp_type == ICMP_TIMXCEED ||
30         icmp->icmp_type == ICMP_SOURCEQUENCH) {
31         if (icmplen < 8 + 20 + 8)
32             err_quit("icmplen (%d) < 8 + 20 + 8", icmplen);

33         hip = (struct ip *) (buf + hlen1 + 8);
34         hlen2 = hip->ip_hl << 2;
35         printf("\tsrcip = %s, dstip = %s, proto = %d\n",
36                Inet_ntop(AF_INET, &hip->ip_src, srcstr, sizeof(srcstr)),
37                Inet_ntop(AF_INET, &hip->ip_dst, dststr, sizeof(dststr)),
38                hip->ip_p);
39         if (hip->ip_p == IPPROTO_UDP) {
40             udp = (struct udphdr *) (buf + hlen1 + 8 + hlen2);
41             sport = udp->uh_sport;

42                 /* find client's Unix domain socket, send headers */
43             for (i = 0; i <= maxi; i++) {
44                 if (client[i].connfd >= 0 &&
45                     client[i].family == AF_INET &&
46                     client[i].lport == sport) {

47                     bzero(&dest, sizeof(dest));
48                     dest.sin_family = AF_INET;
49 #ifdef  HAVE_SOCKADDR_SA_LEN
50                     dest.sin_len = sizeof(dest);
51 #endif
52                     memcpy(&dest.sin_addr, &hip->ip_dst,
53                              sizeof(struct in_addr));
54                     dest.sin_port = udp->uh_dport;

55                     icmpd_err.icmpd_type = icmp->icmp_type;
56                     icmpd_err.icmpd_code = icmp->icmp_code;
57                     icmpd_err.icmpd_len = sizeof(struct sockaddr_in);
58                     memcpy(&icmpd_err.icmpd_dest, &dest, sizeof(dest));

59                         /* convert type & code to reasonable errno value */
60                    icmpd_err.icmpd_errno = EHOSTUNREACH;     /* default */
61                    if (icmp->icmp_type == ICMP_UNREACH) {
62                        if (icmp->icmp_code == ICMP_UNREACH_PORT)
63                            icmpd_err.icmpd_errno = ECONNREFUSED;
64                        else if (icmp->icmp_code == ICMP_UNREACH_NEEDFRAG)
65                            icmpd_err.icmpd_errno = EMSGSIZE;
66                    }
67                    Write(client[i].connfd, &icmpd_err, sizeof(icmpd_err));
68                }
69             }
70         }
71     }
72     return (--nready);
73 }
Check message type, notify application

29鈥?1 The only ICMPv4 messages that we pass to the application are "destination unreachable," "time exceeded," and "source quench" (Figure 28.30).

Check for UDP error, find client

34鈥?2 hip points to the IP header that is returned following the ICMP header. This is the IP header of the datagram that elicited the ICMP error. We verify that this IP datagram is a UDP datagram and then fetch the source UDP port number from the UDP header following the IP header.

43鈥?5 A search is made of all the client structures for a matching address family and port. If a match is found, an IPv4 socket address structure is built containing the destination IP address and port from the UDP datagram that caused the error.

Build icmpd_err structure

56鈥?0 An icmpd_err structure is built that is sent to the client across the Unix domain connection to this client. The ICMPv4 message type and code are first mapped into an errno value, as described with Figure 28.30.

Figure 28.41 Handle received ICMPv6 datagram, first half.

icmpd/readable_v6.c

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

 6 #ifdef     IPV6
 7 #include     <netinet/ip6.h>
 8 #include     <netinet/icmp6.h>
 9 #endif

10 int
11 readable_v6 (void)
12 {
13 #ifdef       IPV6
14     int      i, hlen2, icmp6len, sport;
15     char     buf [MAXLINE];
16     char     srcstr [INET6_ADDRSTRLEN], dststr [INET6_ADDRSTRLEN];
17     ssize_t n;
18     socklen_t len;
19     struct ip6_hdr *ip6, *hip6;
20     struct icmp6_hdr *icmp6;
21     struct udphdr *udp;
22     struct sockaddr_in6 from, dest;
23     struct icmpd_err icmp_err;

24     len = sizeof (from);
25     n = Recvfrom (fd6, buf, MAXLINE, 0, (SA *) &from, &len);

26     printf ("%d bytes ICMPv6 from %s:", n, Sock_ntop_host ((SA *) &from, len));

27     icmp6 = (struct icmp6_hdr *) buf;     /* start of ICMPv6 header */
28     if ( (icmp6len = n) < 8)
29         err_quit ("icmp6len (%d) < 8", icmp6len);

30     printf ("type = %d, code = %d\n", icmp6->icmp6_type, icmp6->icmp6_code);

ICMPv6 errors are handled by our readable_v6 function, the first half of which is shown in Figure 28.41. The ICMPv6 handling is similar to the code in Figures 28.12 and 28.24.

The second half of our readable_v6 function is shown in Figure 28.42 (p. 785). This code is similar to Figure 28.40: It checks the type of ICMP error, checks that the datagram that caused the error was a UDP datagram, and then builds the icmpd_err structure, which is sent to the client.

Figure 28.42 Handle received ICMPv6 datagram, second half.

icmpd/readable_v6.c

31     if (icmp6->icmp6_type == ICMP6_DST_UNREACH ||
32         icmp6->icmp6_type == ICMP6_PACKET_TOO_BIG ||
33         icmp6->icmp6_type == ICMP6_TIME_EXCEEDED) {
34         if (icmp6len < 8 + 8)
35             err_quit (" icmp6len (%d) < 8 + 8", icmp6len);

36         hip6 = (struct ip_hdr *) (buf + 8);
37         hlen2 = sizeof (struct ip6_hdr);
38         printf ("\tsrcip = %s, dstip = %s, next hdr = %d\n",
39                 Inet_ntop (AF_INET6, &hip6->ip6_src,  srcstr, sizeof (srcstr)),
40                 Inet_ntop (AF_INET6, &hip6->ip6_dst, dststr, sizeof (dststr)),
41                 hip6->ip6_nxt);
42         if (hip6->ip6_nxt == IPPROTO_UDP) {
43             udp = (struct udphdr *) (buf + 8 + hlen2);
44             sport = udp->uh_sport;

45                 /* find client's Unix domain socket, send headers */
46             for (i = 0; i <= maxi; i++) {
47                 if (client [i].connfd >= 0 &&
48                     client [i].family == AF_INET6 &&
49                     client [i].lport == sport) {

50                     bzero (&dest, sizeof (dest));
51                     dest.sin6_family = AF_INET6;
52 #ifdef HAVE_SOCKADDR_SA_LEN
53                     dest.sin6_len = sizeof (dest);
54 #endif
55                     memcpy (&dest.sin6_addr, &hip6->ip6_dst,
56                             sizeof (struct in6_addr));
57                     dest.sin6_port = udp->uh_dport;

58                     icmpd_err.icmp_type = icmp6->icmp6_type;
59                     icmpd_err.icmpd_code = icmp6->icmp6_code;
60                     icmpd_err.icmpd_len = sizeof (struct sockaddr_in6);
61                     memcpy (&icmpd_err.icmpd_dest, &dest, sizeof (dest));

62                         /* convert type & code to reasonable errno value */
63                     icmpd_err.icmpd_errno = EHOSTUNREACH; /* default */
64                     if (icmp6->icmp6_type == ICMP6_DST_UNREACH &&
65                         icmp6->icmp6_code == ICMP6_DST_UNREACH_NOPORT)
66                         icmpd_err.icmpd_errno = ECONNREFUSED;
67                     if (icmp6->icmp6_type == ICMP6_PACKET_TOO_BIG)
68                         icmpd_err.icmpd_errno = EMSGSIZE;
69                     Write (client [i].connfd, &icmpd_err, sizeof (icmpd_err));
70                }
71            }
72        }
73    }
74    return (--nready);
75 #endif
76 }
[ Team LiB ] Previous Section Next Section
Converted from CHM to HTML with chm2web Pro 2.85 (unicode)