[comp.bugs.4bsd] UDP reports ICMP "port unreachable" errors to too many sockets

guy@auspex.auspex.com (Guy Harris) (06/01/89)

Index: sys 4.3BSD-tahoe

Description:
	When a UDP datagram is received for a port on which nobody's
	listening, the 4.3BSD and 4.3-tahoe UDP code will send an ICMP
	"port unreachable" packet in reply.

	When such an ICMP packet is received, the 4.3BSD and 4.3-tahoe
	code will try to notify everybody potentially interested in this
	message using "in_pcbnotify".  Unfortunately, "in_pcbnotify"
	takes only a *host* address, not a *port* address, as an
	argument; this means that it cannot choose to notify only those
	sockets actually "connected" to the unreachable port.

	This means that if you send to a UDP port on which nobody's
	listening, and the remote side sends an ICMP "port unreachable"
	message, any UDP socket that was "connect"ed to the remote *host*
	will get an ECONNREFUSED error - even if it was talking to some
	completely unrelated service.

	This is probably not a good idea.  I haven't actually been
	bitten by this personally - I came across it while trying to see
	whether I would get any notification when trying to talk to an
	unreachable UDP port - but Stuart Friedberg of U of Wisconsin
	(stuart@cs.wisc.edu) apparently *was* bitten by it (he mentioned
	this in a mailing to the TCP-IP mailing list).

	Oh, yes, as I noted, I came across it while trying to see
	whether I would get any notification - if this is really
	considered an "official" feature of the implementation, it would
	be nice to document it as such, so that people without source
	(or the ambition to plow through the source) could find it out
	by reading, say, UDP(4P).
Repeat-By:
	Compile and run the following program:

#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <netdb.h>

#include <netinet/in.h>

static char message[] = "Hi, mom!";

int
main(argc, argv)
	int argc;
	char **argv;
{
	register struct hostent *hp;
	u_short badport;
	u_short goodport;
	register int badsock;	/* connect to bogus port */
	register int goodsock;	/* connect to valid port */
	struct sockaddr_in addr;
	char buf[1024];

	if (argc != 4) {
		(void) fprintf(stderr, "Usage: test host badport goodport\n");
		exit(1);
	}

	if ((hp = gethostbyname(argv[1])) == NULL) {
		(void) fprintf(stderr, "%s: No such host\n", argv[1]);
		exit(2);
	}
	if (hp->h_addrtype != AF_INET) {
		(void) fprintf(stderr, "%s: No Internet address\n",
		    argv[1]);
		exit(2);
	}
	badport = atoi(argv[2]);
	goodport = atoi(argv[3]);

	if ((badsock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("Can't create socket");
		exit(2);
	}

	if ((goodsock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
		perror("Can't create socket");
		exit(2);
	}

	addr.sin_family = AF_INET;
	bcopy(hp->h_addr, (char *)&addr.sin_addr, sizeof addr.sin_addr);
	addr.sin_port = badport;
	if (connect(badsock, (struct sockaddr *)&addr, sizeof addr) < 0) {
		perror("Can't connect to bad port");
		exit(2);
	}
	addr.sin_port = goodport;
	if (connect(goodsock, (struct sockaddr *)&addr, sizeof addr) < 0) {
		perror("Can't connect to good port");
		exit(2);
	}

	if (send(badsock, message, sizeof message, 0) < 0) {
		perror("Can't send to bad port");
		exit(2);
	}

	if (recv(goodsock, buf, sizeof buf, 0) < 0)
		perror("Error on receive from good port");

	exit(0);
}

	with a first argument that's the hostname of a valid host, a
	second argument that's an INvalid port number on that host, and
	a third argument that's some port number - I think a good one or
	a bad one will do; I succeeded by giving it a good one.

	The program will report something like

		Error on receive from good port: Connection refused

	if the bug is present; it should just hang if it's not present.

	Changing the last "recv" call to:

		if (recv(badsock, buf, sizeof buf, 0) < 0)
			perror("Error on receive from bad port");

	should get a report like

		Error on receive from bad port: Connection refused

	even if the bug *is* fixed.

Fix:
	Change "in_pcbnotify" to take a port number as well as a host
	address as an argument, and if the port number is not 0 have it
	skip sockets not connected to the specified port.  Have those
	notifications that are to go to all sockets connected to the
	specified host pass 0, and have other notifications pass the
	proper port number.

	Then *document* the resulting behavior in UDP(4P) (and TCP(4P),
	if ICMP messages cause errors to be reported that aren't
	described there or in the section 2 manuals).