--- //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);