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.
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.
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.
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.
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 }
|