--- //depot/vendor/freebsd/src/sys/netinet/udp_usrreq.c	2008/07/07 12:30:15
+++ //depot/user/rwatson/netisr/src/sys/netinet/udp_usrreq.c	2008/07/08 07:51:14
@@ -845,9 +845,28 @@
 		return (error);
 	}
 
-	if (src.sin_family == AF_INET || addr != NULL) {
+	/*
+	 * We may enter binding/connecting routines and manipulate global
+	 * address lists if a source address is requested explicitly, or if
+	 * an explicit destination is requested and a connect has not yet
+	 * been performed.  In these cases, acquire the global lock.
+	 *
+	 * XXXRW: In some of these cases, if may be sufficient to acquire a
+	 * read lock?
+	 *
+	 * XXXRW: This isn't right, as we are looking at the inp values
+	 * before acquiring the pcbinfo lock.  In the future, we'll want to
+	 * detect this race and handle it...
+	 */
+	if (addr != NULL && (inp->inp_laddr.s_addr == INADDR_ANY &&
+	    inp->inp_lport == 0)) {
 		INP_INFO_WLOCK(&udbinfo);
 		INP_WLOCK(inp);
+		unlock_udbinfo = 2;
+	} else if ((addr != NULL && (inp->inp_laddr.s_addr == INADDR_ANY ||
+	    inp->inp_lport == 0)) || src.sin_family == AF_INET) {
+		INP_INFO_RLOCK(&udbinfo);
+		INP_RLOCK(inp);
 		unlock_udbinfo = 1;
 	} else {
 		unlock_udbinfo = 0;
@@ -879,39 +898,77 @@
 			goto release;
 	}
 
+	/*
+	 * If a UDP socket has been connected, then a local address/port will
+	 * have been selected and bound.
+	 *
+	 * If a UDP socket has not been connected to, then an explicit
+	 * destination address must be used, in which case local address/port
+	 * may not have been selected and bound.
+	 */
 	if (addr) {
-		INP_INFO_LOCK_ASSERT(&udbinfo);
 		INP_LOCK_ASSERT(inp);
+		if (inp->inp_faddr.s_addr != INADDR_ANY) {
+			error = EISCONN;
+			goto release;
+		}
+
+		/*
+		 * Jail may rewrite the destination address, so let it do
+		 * that before we use it.
+		 */
 		sin = (struct sockaddr_in *)addr;
 		if (jailed(td->td_ucred))
 			prison_remote_ip(td->td_ucred, 0,
 			    &sin->sin_addr.s_addr);
-		if (inp->inp_faddr.s_addr != INADDR_ANY) {
-			error = EISCONN;
-			goto release;
-		}
-		error = in_pcbconnect_setup(inp, addr, &laddr.s_addr, &lport,
-		    &faddr.s_addr, &fport, NULL, td->td_ucred);
-		if (error)
-			goto release;
 
-		/* Commit the local port if newly assigned. */
-		if (inp->inp_laddr.s_addr == INADDR_ANY &&
+		/*
+		 * If local address or port hasn't yet been selected, do that
+		 * now.  Once a port is selected, we commit the binding back
+		 * to the socket; we also commit the binding of the address
+		 * if in jail.
+		 */
+		if (inp->inp_laddr.s_addr == INADDR_ANY ||
 		    inp->inp_lport == 0) {
-			INP_INFO_WLOCK_ASSERT(&udbinfo);
-			INP_WLOCK_ASSERT(inp);
+			INP_INFO_LOCK_ASSERT(&udbinfo);
+			error = in_pcbconnect_setup(inp, addr, &laddr.s_addr,
+			    &lport, &faddr.s_addr, &fport, NULL,
+			    td->td_ucred);
+			if (error)
+				goto release;
+
 			/*
-			 * Remember addr if jailed, to prevent rebinding.
+			 * XXXRW: Why not commit the port if the address is
+			 * !INADDR_ANY?
 			 */
-			if (jailed(td->td_ucred))
-				inp->inp_laddr = laddr;
-			inp->inp_lport = lport;
-			if (in_pcbinshash(inp) != 0) {
-				inp->inp_lport = 0;
-				error = EAGAIN;
-				goto release;
+			/* Commit the local port if newly assigned. */
+			if (inp->inp_laddr.s_addr == INADDR_ANY &&
+			    inp->inp_lport == 0) {
+				INP_INFO_WLOCK_ASSERT(&udbinfo);
+				INP_WLOCK_ASSERT(inp);
+				/*
+				 * Remember addr if jailed, to prevent
+				 * rebinding.
+				 */
+				if (jailed(td->td_ucred))
+					inp->inp_laddr = laddr;
+				inp->inp_lport = lport;
+				if (in_pcbinshash(inp) != 0) {
+					inp->inp_lport = 0;
+					error = EAGAIN;
+					goto release;
+				}
+				inp->inp_flags |= INP_ANONPORT;
 			}
-			inp->inp_flags |= INP_ANONPORT;
+		} else {
+			/*
+			 * XXXRW: Is it OK that we haven't checked for a
+			 * colliding binding for these foreign address and
+			 * port combined with the existing binding for local
+			 * address and port?
+			 */
+			faddr = sin->sin_addr;
+			fport = sin->sin_port;
 		}
 	} else {
 		INP_LOCK_ASSERT(inp);
@@ -985,20 +1042,25 @@
 	((struct ip *)ui)->ip_tos = inp->inp_ip_tos;	/* XXX */
 	udpstat.udps_opackets++;
 
-	if (unlock_udbinfo)
+	if (unlock_udbinfo == 2)
 		INP_INFO_WUNLOCK(&udbinfo);
+	else if (unlock_udbinfo == 1)
+		INP_INFO_RUNLOCK(&udbinfo);
 	error = ip_output(m, inp->inp_options, NULL, ipflags,
 	    inp->inp_moptions, inp);
-	if (unlock_udbinfo)
+	if (unlock_udbinfo == 2)
 		INP_WUNLOCK(inp);
 	else
 		INP_RUNLOCK(inp);
 	return (error);
 
 release:
-	if (unlock_udbinfo) {
+	if (unlock_udbinfo == 2) {
+		INP_WUNLOCK(inp);
 		INP_INFO_WUNLOCK(&udbinfo);
-		INP_WUNLOCK(inp);
+	} else if (unlock_udbinfo == 1) {
+		INP_RUNLOCK(inp);
+		INP_INFO_RUNLOCK(&udbinfo);
 	} else
 		INP_RUNLOCK(inp);
 	m_freem(m);
