From: torek@horse.ee.lbl.gov (Chris Torek)
Newsgroups: comp.unix.internals
Subject: Re: A problem in bind() system call.Help!
Date: 5 Nov 1992 06:04:15 GMT
Organization: Lawrence Berkeley Laboratory, Berkeley
Reply-To: torek@horse.ee.lbl.gov (Chris Torek)
NNTP-Posting-Host: 128.3.112.15

In article <1992Nov4.192429.10342@wuecl.wustl.edu> pete@arl.wustl.edu writes:
>... I tried to use SO_REUSEADDR as well, but I believe it doesn't work in
>all cases.

Oddly enough, I just looked into something similar today.

SO_REUSEADDR is rather broken.  It does work for special cases.

The current 4BSD kernel---recent SunOS releases are similar---has this
code in in_pcbbind():

	if ((so->so_options & SO_REUSEADDR) == 0 &&
	    ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 ||
	     (so->so_options & SO_ACCEPTCONN) == 0))
		wild = INPLOOKUP_WILDCARD;
	if (in_pcblookup(..., sin->sin_addr, lport, wild))
		return (EADDRINUSE);

The `wild' flag tells in_pcblookup to include all TCP or UDP sockets,
rather than only those `listening'.  This is often desirable since a
unique <laddr,lport> pair automatically guarantees that the 4-tuple
<laddr,lport,faddr,fport> will be unique.  (TCP and UDP require the
latter.)  At the time of the bind() call, the <faddr,fport> portion are
unknown.

(This suggests---rather appropriately, actually---that the system
really should have a combined bind and connect call, in which one of
the two addresses is optional.  Then the kernel would usually have all
the information it needs to pass judgement on the four-tuple, rather
than having to second-guess two elements, or apply too-inclusive tests
to the <laddr,lport> portion, as it does now.  But never mind that.)

The tests in in_pcbbind() use a `wildcard' lookup if:

	a) you did not set SO_REUSEADDR, and:
	    b1) this is a UDP socket (not PR_CONNREQUIRED), or:
	    b2) you have not yet done a listen() call.

Point b2) is most peculiar, because the `standard' method of writing a
tcp server is:

	s = socket(...);
	if (s < 0) ERROR
	if (bind(s, ...)) ERROR
	if (listen(s, 5)) ERROR
	for (;;) {
		s2 = accept(s, ...);
		if (s2 < 0) ERROR
		...
	}

If you put a listen() call before the bind(), it does its own internal
bind(), and the system assigns you a port number.  This is only OK if
you use a `service number service' a la Sun's portmap.  Even then,
SO_ACCEPTCONN will not be set in in_pcbbind(), because listen() sets it
*after* the internal bind.  Hence, this is effectively the same as:

	if (!SO_REUSEADDR && (is_udp || 1))
		wild = INPLOOKUP_WILDCARD;

or just

	if (!SO_REUSEADDR)

Thus, all SO_REUSEADDR does is prevent setting INPLOOKUP_WILDCARD, and
we can concentrate on the logic (or lack thereof?) in in_pcblookup.  It
is long and twisty, but it boils down to one thing in the case of TCP
servers:

 *** If you specify a local address (other than 0) in your bind()
 *** call, SO_REUSEADDR will have no effect.

The code really ought to say

	if ((so->so_state & SO_REUSEADDR) == 0 && in_pcblookup(...))
		return (EADDRINUSE);

and programmers who use SO_REUSEADDR should expect to get EADDRINUSE
errors from connect() whenever they do not get a unique 4-tuple.
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 510 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov
Newsgroups: comp.unix.internals