Re: Select and UDP problems

From: Perry The Cynic (perry@cynic.org)
Date: Fri Jan 21 2000 - 18:53:59 EST


[Not a kernel problem. But in case you were wondering...]

When you send a UDP packet to a host/port, then either
(1) The host isn't reachable (and no router on the way knows this):
        Your packet will fall into a black hole, and your select() will
        time out; OR
(2) A server is listening on that port and eats your packet; OR
(3) The host is reachable but no server listens on that port.

In case (3), the UDP layer on the server will (almost immediately) return
an ICMP PORT UNREACHABLE error message to the sending host (yours), and
your network stack translates this into an ECONNREFUSED error. Since you
selected for errors, your select() fires and returns the error to you.

Why does the send not return an error code? Because *the send succeeds*.
UDP has no notion of connections or guaranteed delivery; the packet was
sent successfully out your network port, so everything is fine.

On the other hand, the kernel recognizes that the incoming ICMP error
message is clearly associated with your (sending) network port, since
the ICMP contains (the start of) your sent packet, so it generates
an error flag on the port, and when you select() asking for error
conditions (your errSet), you get told about it almost immediately.

Note that it's possible (though unlikely) for the ICMP UNREACHABLE to
arrive more than five seconds after you send the packet, or to be eaten by
a line glitch. Or for your packet to be eaten by a grue. Such are the
vagaries of UDP (best effort, no guarantees) packets.

In case you're interested, the code for this is in net/udp.c in the
kernel, in the udp_err function.

To demonstrate the difference between cases (2) and (3), try to send your
packet to a nonexisting host down your default route (try 1.2.3.4 :-). You
can also use a packet sniffer (like tcpdump) to watch for ICMP packets.

As to HP-UX and Solaris, they may be less inclined to tie incoming ICMP
errors to UDP ports. I vaguely remember this being tunable to a degree in
Solaris, but it's been years since I've touched a Sun box...

Cheers
  -- perry

On Wed, 19 Jan 2000, Kevin Pearson wrote:

> Hello,
>
> I have a question reguarding select on a UDP socket. The following
> program creates a UDP socket, sends a character, does a select for five
> seconds waiting for a response, and recieves a response, if there was one.
>
> This works fine when there is a service that responds to the request, but
> if there isn't, select still returns immediatly and with readSet
> set. Then recvfrom return -1 with an errno of 111 (ECONNREFUSED).
>
> On HP-UX 10.20 and Solaris 5.7, when there is no response, select waits
> for 5 seconds and times out.
>
> I'm using Linux 2.2.12 SMP, glibc-2.1.2.
>
> I've tried searching the linux-kernel archives, but could find nothing.
>
> Am I doing something wrong here or is this a defect in select?
> Please reply to kevin_pearson@hp.com as I'm not subscribed to the list.
>
> Thanks in advance,
> Kevin Pearson
> kevin_pearson@hp.com
>
> #include <stdio.h>
> #include <stdlib.h>
> #include <sys/time.h>
> #include <sys/types.h>
> #include <sys/socket.h>
> #include <netinet/in.h>
> #include <arpa/inet.h>
> #include <netdb.h>
> #include <unistd.h>
> #include <errno.h>
>
> int main(int argc, char **argv)
> {
> int fd = -1;
> int dest_port = -1;
> int ret = -1;
> struct sockaddr_in myaddr;
> int addr_size = sizeof(myaddr);
> struct hostent *hostname = NULL;
> fd_set readSet;
> fd_set errSet;
> struct timeval timeout;
> char buf[512];
>
> if (argc < 3)
> {
> printf("\nUsage: test hostname port\n\n");
> exit(-1);
> }
>
> hostname = gethostbyname(argv[1]);
> dest_port = atoi(argv[2]);
>
> if (hostname == NULL)
> {
> printf("Could not resolv hostname\n");
> exit(-1);
> }
>
> fd = socket(AF_INET, SOCK_DGRAM, 0);
> if (fd < 0)
> {
> printf("Could not open socket\n");
> }
>
> addr_size = sizeof(myaddr);
> memset(&myaddr, 0, addr_size);
>
> myaddr.sin_family = AF_INET;
> myaddr.sin_port = htons(dest_port);
> memcpy(&myaddr.sin_addr, hostname->h_addr_list[0], hostname->h_length);
>
> ret = sendto(fd, "*", 1, 0, &myaddr, sizeof(myaddr));
> if (ret < 0)
> printf("send returned ret=%d, errno=%d\n", ret, errno);
>
> FD_ZERO(&readSet);
> FD_SET(fd, &readSet);
>
> FD_ZERO(&errSet);
> FD_SET(fd, &errSet);
>
> timeout.tv_sec = 5;
> timeout.tv_usec = 0;
>
> ret = select(fd + 1, &readSet, NULL, &errSet, &timeout);
> if (ret < 0)
> printf("select returned ret=%d, errno=%d\n", ret, errno);
>
> if ( FD_ISSET(fd, &readSet) )
> printf("readSet fired\n");
> if ( FD_ISSET(fd, &errSet) )
> printf("errSet fired\n");
>
> if ( (ret > 0) && (FD_ISSET(fd, &readSet)) && (!FD_ISSET(fd, &errSet)) )
> {
> ret = recvfrom(fd, buf, sizeof(buf), 0, &myaddr, &addr_size);
> if (ret < 0)
> printf("recvfrom returned ret=%d, errno=%d\n", ret, errno);
> else
> printf("buff is %s\n", buf);
> }
>
> close(fd);
> return 0;
> }
>
>
> -
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.rutgers.edu
> Please read the FAQ at http://www.tux.org/lkml/
>

---------------------------------------------------------------------------
Perry The Cynic perry@cynic.org
To a blind optimist, an optimistic realist must seem like an Accursed Cynic.
---------------------------------------------------------------------------

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Sun Jan 23 2000 - 21:00:27 EST