[ 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–11 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–8 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–3 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–16 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–21 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–23 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–33 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–38 If a datagram is returned by the server, we print it to standard output.

Handle ICMP error

39–48 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–17 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–10 The client array is initialized by setting the connected socket member to –1.

Create sockets

12–28 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–34 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–42 The raw ICMPv4 socket is tested, and then the raw ICMPv6 socket.

Check connected Unix domain sockets

43–49 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–25 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–18 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–20 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–25 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–33 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–45 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 –1, 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–31 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–42 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–55 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–70 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