Index: share/man/man4/ip.4 =================================================================== RCS file: /dump/FreeBSD-CVS/src/share/man/man4/ip.4,v retrieving revision 1.25 diff -u -r1.25 ip.4 --- share/man/man4/ip.4 9 Apr 2002 18:03:18 -0000 1.25 +++ share/man/man4/ip.4 14 Oct 2002 13:10:20 -0000 @@ -136,6 +136,34 @@ cmsg_type = IP_RECVDSTADDR .Ed .Pp +The source address to be used for outgoing +.Tn UDP +datagrams on a socket that is not bound to a specific +.Tn IP +address can be specified as ancillary data with a type code of +.Dv IP_SENDSRCADDR . +The msg_control field in the msghdr structure should point to a buffer +that contains a cmsghdr structure followed by the +.Tn IP +address. +The cmsghdr fields should have the following values: +.Bd -literal +cmsg_len = sizeof(struct in_addr) +cmsg_level = IPPROTO_IP +cmsg_type = IP_SENDSRCADDR +.Ed +.Pp +For convenience, +.Dv IP_SENDSRCADDR +is defined to have the same value as +.Dv IP_RECVDSTADDR , +so the +.Dv IP_RECVDSTADDR +control message from +.Xr recvmsg 2 +can be used directly as a control message for +.Xr sendmsg 2 . +.Pp .Dv IP_PORTRANGE may be used to set the port range used for selecting a local port number on a socket with an unspecified (zero) port number. Index: sys/netinet/in.h =================================================================== RCS file: /dump/FreeBSD-CVS/src/sys/netinet/in.h,v retrieving revision 1.71 diff -u -r1.71 in.h --- sys/netinet/in.h 4 Sep 2002 09:55:50 -0000 1.71 +++ sys/netinet/in.h 22 Sep 2002 15:03:41 -0000 @@ -366,6 +366,7 @@ #define IP_RECVOPTS 5 /* bool; receive all IP opts w/dgram */ #define IP_RECVRETOPTS 6 /* bool; receive IP opts for response */ #define IP_RECVDSTADDR 7 /* bool; receive IP dst addr w/dgram */ +#define IP_SENDSRCADDR IP_RECVDSTADDR /* cmsg_type to set src addr */ #define IP_RETOPTS 8 /* ip_opts; set/get IP options */ #define IP_MULTICAST_IF 9 /* u_char; set/get IP multicast i/f */ #define IP_MULTICAST_TTL 10 /* u_char; set/get IP multicast ttl */ Index: sys/netinet/in_pcb.c =================================================================== RCS file: /dump/FreeBSD-CVS/src/sys/netinet/in_pcb.c,v retrieving revision 1.110 diff -u -r1.110 in_pcb.c --- sys/netinet/in_pcb.c 21 Aug 2002 11:57:04 -0000 1.110 +++ sys/netinet/in_pcb.c 14 Oct 2002 12:41:26 -0000 @@ -179,17 +179,56 @@ struct sockaddr *nam; struct thread *td; { - register struct socket *so = inp->inp_socket; + int anonport, error; + + if (inp->inp_lport != 0 || inp->inp_laddr.s_addr != INADDR_ANY) + return (EINVAL); + anonport = inp->inp_lport == 0 && (nam == NULL || + ((struct sockaddr_in *)nam)->sin_port == 0); + error = in_pcbbind_setup(inp, nam, &inp->inp_laddr.s_addr, + &inp->inp_lport, td); + if (error) + return (error); + if (in_pcbinshash(inp) != 0) { + inp->inp_laddr.s_addr = INADDR_ANY; + inp->inp_lport = 0; + return (EAGAIN); + } + if (anonport) + inp->inp_flags |= INP_ANONPORT; + return (0); +} + +/* + * Set up a bind operation on a PCB, performing port allocation + * as required, but do not actually modify the PCB. Callers can + * either complete the bind by setting inp_laddr/inp_lport and + * calling in_pcbinshash(), or they can just use the resulting + * port and address to authorise the sending of a once-off packet. + * + * On error, the values of *laddrp and *lportp are not changed. + */ +int +in_pcbbind_setup(inp, nam, laddrp, lportp, td) + struct inpcb *inp; + struct sockaddr *nam; + in_addr_t *laddrp; + u_short *lportp; + struct thread *td; +{ + struct socket *so = inp->inp_socket; unsigned short *lastport; struct sockaddr_in *sin; struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; + struct in_addr laddr; u_short lport = 0; int wild = 0, reuseport = (so->so_options & SO_REUSEPORT); int error, prison = 0; if (TAILQ_EMPTY(&in_ifaddrhead)) /* XXX broken! */ return (EADDRNOTAVAIL); - if (inp->inp_lport || inp->inp_laddr.s_addr != INADDR_ANY) + laddr.s_addr = *laddrp; + if (nam != NULL && laddr.s_addr != INADDR_ANY) return (EINVAL); if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0) wild = 1; @@ -208,7 +247,13 @@ if (sin->sin_addr.s_addr != INADDR_ANY) if (prison_ip(td->td_ucred, 0, &sin->sin_addr.s_addr)) return(EINVAL); - lport = sin->sin_port; + if (sin->sin_port != *lportp) { + /* Don't allow the port to change. */ + if (*lportp != 0) + return (EINVAL); + lport = sin->sin_port; + } + /* NB: lport is left as 0 if the port isn't being changed. */ if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) { /* * Treat SO_REUSEADDR as SO_REUSEPORT for multicast; @@ -225,6 +270,7 @@ if (ifa_ifwithaddr((struct sockaddr *)sin) == 0) return (EADDRNOTAVAIL); } + laddr = sin->sin_addr; if (lport) { struct inpcb *t; /* GROSS */ @@ -274,28 +320,25 @@ return (EADDRINUSE); } } - inp->inp_laddr = sin->sin_addr; } + if (*lportp != 0) + lport = *lportp; if (lport == 0) { ushort first, last; int count; - if (inp->inp_laddr.s_addr != INADDR_ANY) - if (prison_ip(td->td_ucred, 0, &inp->inp_laddr.s_addr )) { - inp->inp_laddr.s_addr = INADDR_ANY; + if (laddr.s_addr != INADDR_ANY) + if (prison_ip(td->td_ucred, 0, &laddr.s_addr)) return (EINVAL); - } - inp->inp_flags |= INP_ANONPORT; if (inp->inp_flags & INP_HIGHPORT) { first = ipport_hifirstauto; /* sysctl */ last = ipport_hilastauto; lastport = &pcbinfo->lasthi; } else if (inp->inp_flags & INP_LOWPORT) { - if (td && (error = suser_cred(td->td_ucred, PRISON_ROOT))) { - inp->inp_laddr.s_addr = INADDR_ANY; + if (td && (error = suser_cred(td->td_ucred, + PRISON_ROOT))) return error; - } first = ipport_lowfirstauto; /* 1023 */ last = ipport_lowlastauto; /* 600 */ lastport = &pcbinfo->lastlow; @@ -318,16 +361,14 @@ count = first - last; do { - if (count-- < 0) { /* completely used? */ - inp->inp_laddr.s_addr = INADDR_ANY; + if (count-- < 0) /* completely used? */ return (EADDRNOTAVAIL); - } --*lastport; if (*lastport > first || *lastport < last) *lastport = first; lport = htons(*lastport); - } while (in_pcblookup_local(pcbinfo, - inp->inp_laddr, lport, wild)); + } while (in_pcblookup_local(pcbinfo, laddr, lport, + wild)); } else { /* * counting up @@ -335,63 +376,125 @@ count = last - first; do { - if (count-- < 0) { /* completely used? */ - /* - * Undo any address bind that may have - * occurred above. - */ - inp->inp_laddr.s_addr = INADDR_ANY; + if (count-- < 0) /* completely used? */ return (EADDRNOTAVAIL); - } ++*lastport; if (*lastport < first || *lastport > last) *lastport = first; lport = htons(*lastport); - } while (in_pcblookup_local(pcbinfo, - inp->inp_laddr, lport, wild)); + } while (in_pcblookup_local(pcbinfo, laddr, lport, + wild)); } } - inp->inp_lport = lport; - if (prison_ip(td->td_ucred, 0, &inp->inp_laddr.s_addr)) { - inp->inp_laddr.s_addr = INADDR_ANY; - inp->inp_lport = 0; + if (prison_ip(td->td_ucred, 0, &laddr.s_addr)) return (EINVAL); + *laddrp = laddr.s_addr; + *lportp = lport; + return (0); +} + +/* + * Connect from a socket to a specified address. + * Both address and port must be specified in argument sin. + * If don't have a local address for this socket yet, + * then pick one. + */ +int +in_pcbconnect(inp, nam, td) + register struct inpcb *inp; + struct sockaddr *nam; + struct thread *td; +{ + u_short lport, fport; + in_addr_t laddr, faddr; + int error; + + lport = inp->inp_lport; + laddr = inp->inp_laddr.s_addr; + error = in_pcbconnect_setup(inp, nam, &laddr, &lport, &faddr, &fport, + NULL, td); + if (error) + return (error); + + /* Do the initial binding of the local address if required. */ + if (inp->inp_laddr.s_addr == INADDR_ANY && inp->inp_lport == 0) { + inp->inp_lport = lport; + inp->inp_laddr.s_addr = laddr; + if (in_pcbinshash(inp) != 0) { + inp->inp_laddr.s_addr = INADDR_ANY; + inp->inp_lport = 0; + return (EAGAIN); + } } - if (in_pcbinshash(inp) != 0) { - inp->inp_laddr.s_addr = INADDR_ANY; - inp->inp_lport = 0; - return (EAGAIN); - } + + /* Commit the remaining changes. */ + inp->inp_lport = lport; + inp->inp_laddr.s_addr = laddr; + inp->inp_faddr.s_addr = faddr; + inp->inp_fport = fport; + in_pcbrehash(inp); return (0); } /* - * Transform old in_pcbconnect() into an inner subroutine for new - * in_pcbconnect(): Do some validity-checking on the remote - * address (in mbuf 'nam') and then determine local host address - * (i.e., which interface) to use to access that remote host. + * Set up for a connect from a socket to the specified address. + * On entry, *laddrp and *lportp should contain the current local + * address and port for the PCB; these are updated to the values + * that should be placed in inp_laddr and inp_lport to complete + * the connect. + * + * On success, *fportp and *lportp will be set to the remote address + * and port. These are not updated in the error case. * - * This preserves definition of in_pcbconnect(), while supporting a - * slightly different version for T/TCP. (This is more than - * a bit of a kludge, but cleaning up the internal interfaces would - * have forced minor changes in every protocol). + * If the operation fails because the connection already exists, + * *oinpp will be set to the PCB of that connection so that the + * caller can decide to override it. In all other cases, *oinpp + * is set to NULL. */ - int -in_pcbladdr(inp, nam, plocal_sin) +in_pcbconnect_setup(inp, nam, laddrp, lportp, faddrp, fportp, oinpp, td) register struct inpcb *inp; struct sockaddr *nam; - struct sockaddr_in **plocal_sin; + in_addr_t *laddrp; + u_short *lportp; + in_addr_t *faddrp; + u_short *fportp; + struct inpcb **oinpp; + struct thread *td; { + struct sockaddr_in *sin = (struct sockaddr_in *)nam; struct in_ifaddr *ia; - register struct sockaddr_in *sin = (struct sockaddr_in *)nam; + struct sockaddr_in sa; + struct ucred *cred; + struct inpcb *oinp; + struct in_addr laddr, faddr; + u_short lport, fport; + int error; + if (oinpp != NULL) + *oinpp = NULL; if (nam->sa_len != sizeof (*sin)) return (EINVAL); if (sin->sin_family != AF_INET) return (EAFNOSUPPORT); if (sin->sin_port == 0) return (EADDRNOTAVAIL); + laddr.s_addr = *laddrp; + lport = *lportp; + faddr = sin->sin_addr; + fport = sin->sin_port; + cred = inp->inp_socket->so_cred; + if (laddr.s_addr == INADDR_ANY && jailed(cred)) { + bzero(&sa, sizeof(sa)); + sa.sin_addr.s_addr = htonl(prison_getip(cred)); + sa.sin_len = sizeof(sa); + sa.sin_family = AF_INET; + error = in_pcbbind_setup(inp, (struct sockaddr *)&sa, + &laddr.s_addr, &lport, td); + if (error) + return (error); + } + if (!TAILQ_EMPTY(&in_ifaddrhead)) { /* * If the destination address is INADDR_ANY, @@ -400,13 +503,15 @@ * and the primary interface supports broadcast, * choose the broadcast address for that interface. */ - if (sin->sin_addr.s_addr == INADDR_ANY) - sin->sin_addr = IA_SIN(TAILQ_FIRST(&in_ifaddrhead))->sin_addr; - else if (sin->sin_addr.s_addr == (u_long)INADDR_BROADCAST && - (TAILQ_FIRST(&in_ifaddrhead)->ia_ifp->if_flags & IFF_BROADCAST)) - sin->sin_addr = satosin(&TAILQ_FIRST(&in_ifaddrhead)->ia_broadaddr)->sin_addr; + if (faddr.s_addr == INADDR_ANY) + faddr = IA_SIN(TAILQ_FIRST(&in_ifaddrhead))->sin_addr; + else if (faddr.s_addr == (u_long)INADDR_BROADCAST && + (TAILQ_FIRST(&in_ifaddrhead)->ia_ifp->if_flags & + IFF_BROADCAST)) + faddr = satosin(&TAILQ_FIRST( + &in_ifaddrhead)->ia_broadaddr)->sin_addr; } - if (inp->inp_laddr.s_addr == INADDR_ANY) { + if (laddr.s_addr == INADDR_ANY) { register struct route *ro; ia = (struct in_ifaddr *)0; @@ -419,8 +524,7 @@ ro = &inp->inp_route; if (ro->ro_rt && (ro->ro_dst.sa_family != AF_INET || - satosin(&ro->ro_dst)->sin_addr.s_addr != - sin->sin_addr.s_addr || + satosin(&ro->ro_dst)->sin_addr.s_addr != faddr.s_addr || inp->inp_socket->so_options & SO_DONTROUTE)) { RTFREE(ro->ro_rt); ro->ro_rt = (struct rtentry *)0; @@ -432,8 +536,7 @@ bzero(&ro->ro_dst, sizeof(struct sockaddr_in)); ro->ro_dst.sa_family = AF_INET; ro->ro_dst.sa_len = sizeof(struct sockaddr_in); - ((struct sockaddr_in *) &ro->ro_dst)->sin_addr = - sin->sin_addr; + ((struct sockaddr_in *)&ro->ro_dst)->sin_addr = faddr; rtalloc(ro); } /* @@ -445,13 +548,14 @@ if (ro->ro_rt && !(ro->ro_rt->rt_ifp->if_flags & IFF_LOOPBACK)) ia = ifatoia(ro->ro_rt->rt_ifa); if (ia == 0) { - u_short fport = sin->sin_port; + bzero(&sa, sizeof(sa)); + sa.sin_addr = faddr; + sa.sin_len = sizeof(sa); + sa.sin_family = AF_INET; - sin->sin_port = 0; - ia = ifatoia(ifa_ifwithdstaddr(sintosa(sin))); + ia = ifatoia(ifa_ifwithdstaddr(sintosa(&sa))); if (ia == 0) - ia = ifatoia(ifa_ifwithnet(sintosa(sin))); - sin->sin_port = fport; + ia = ifatoia(ifa_ifwithnet(sintosa(&sa))); if (ia == 0) ia = TAILQ_FIRST(&in_ifaddrhead); if (ia == 0) @@ -462,7 +566,7 @@ * interface has been set as a multicast option, use the * address of that interface as our source address. */ - if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr)) && + if (IN_MULTICAST(ntohl(faddr.s_addr)) && inp->inp_moptions != NULL) { struct ip_moptions *imo; struct ifnet *ifp; @@ -477,67 +581,25 @@ return (EADDRNOTAVAIL); } } - /* - * Don't do pcblookup call here; return interface in plocal_sin - * and exit to caller, that will do the lookup. - */ - *plocal_sin = &ia->ia_addr; - + laddr = ia->ia_addr.sin_addr; } - return(0); -} - -/* - * Outer subroutine: - * Connect from a socket to a specified address. - * Both address and port must be specified in argument sin. - * If don't have a local address for this socket yet, - * then pick one. - */ -int -in_pcbconnect(inp, nam, td) - register struct inpcb *inp; - struct sockaddr *nam; - struct thread *td; -{ - struct sockaddr_in *ifaddr; - struct sockaddr_in *sin = (struct sockaddr_in *)nam; - struct sockaddr_in sa; - struct ucred *cred; - int error; - cred = inp->inp_socket->so_cred; - if (inp->inp_laddr.s_addr == INADDR_ANY && jailed(cred)) { - bzero(&sa, sizeof (sa)); - sa.sin_addr.s_addr = htonl(prison_getip(cred)); - sa.sin_len=sizeof (sa); - sa.sin_family = AF_INET; - error = in_pcbbind(inp, (struct sockaddr *)&sa, td); - if (error) - return (error); - } - /* - * Call inner routine, to assign local interface address. - */ - if ((error = in_pcbladdr(inp, nam, &ifaddr)) != 0) - return(error); - - if (in_pcblookup_hash(inp->inp_pcbinfo, sin->sin_addr, sin->sin_port, - inp->inp_laddr.s_addr ? inp->inp_laddr : ifaddr->sin_addr, - inp->inp_lport, 0, NULL) != NULL) { + oinp = in_pcblookup_hash(inp->inp_pcbinfo, faddr, fport, laddr, lport, + 0, NULL); + if (oinp != NULL) { + if (oinpp != NULL) + *oinpp = oinp; return (EADDRINUSE); } - if (inp->inp_laddr.s_addr == INADDR_ANY) { - if (inp->inp_lport == 0) { - error = in_pcbbind(inp, (struct sockaddr *)0, td); - if (error) - return (error); - } - inp->inp_laddr = ifaddr->sin_addr; + if (laddr.s_addr == INADDR_ANY || lport == 0) { + error = in_pcbbind_setup(inp, NULL, &laddr.s_addr, &lport, td); + if (error) + return (error); } - inp->inp_faddr = sin->sin_addr; - inp->inp_fport = sin->sin_port; - in_pcbrehash(inp); + *laddrp = laddr.s_addr; + *lportp = lport; + *faddrp = faddr.s_addr; + *fportp = fport; return (0); } Index: sys/netinet/in_pcb.h =================================================================== RCS file: /dump/FreeBSD-CVS/src/sys/netinet/in_pcb.h,v retrieving revision 1.53 diff -u -r1.53 in_pcb.h --- sys/netinet/in_pcb.h 5 Sep 2002 19:48:52 -0000 1.53 +++ sys/netinet/in_pcb.h 22 Sep 2002 15:03:41 -0000 @@ -328,12 +328,15 @@ in_rtchange(struct inpcb *, int); int in_pcballoc(struct socket *, struct inpcbinfo *, struct thread *); int in_pcbbind(struct inpcb *, struct sockaddr *, struct thread *); +int in_pcbbind_setup(struct inpcb *, struct sockaddr *, in_addr_t *, + u_short *, struct thread *); int in_pcbconnect(struct inpcb *, struct sockaddr *, struct thread *); +int in_pcbconnect_setup(struct inpcb *, struct sockaddr *, in_addr_t *, + u_short *, in_addr_t *, u_short *, struct inpcb **, + struct thread *); void in_pcbdetach(struct inpcb *); void in_pcbdisconnect(struct inpcb *); int in_pcbinshash(struct inpcb *); -int in_pcbladdr(struct inpcb *, struct sockaddr *, - struct sockaddr_in **); struct inpcb * in_pcblookup_local(struct inpcbinfo *, struct in_addr, u_int, int); Index: sys/netinet/tcp_usrreq.c =================================================================== RCS file: /dump/FreeBSD-CVS/src/sys/netinet/tcp_usrreq.c,v retrieving revision 1.82 diff -u -r1.82 tcp_usrreq.c --- sys/netinet/tcp_usrreq.c 22 Aug 2002 21:24:01 -0000 1.82 +++ sys/netinet/tcp_usrreq.c 31 Aug 2002 00:44:58 -0000 @@ -832,10 +832,10 @@ /* * Common subroutine to open a TCP connection to remote host specified * by struct sockaddr_in in mbuf *nam. Call in_pcbbind to assign a local - * port number if needed. Call in_pcbladdr to do the routing and to choose - * a local host address (interface). If there is an existing incarnation - * of the same connection in TIME-WAIT state and if the remote host was - * sending CC options and if the connection duration was < MSL, then + * port number if needed. Call in_pcbconnect_setup to do the routing and + * to choose a local host address (interface). If there is an existing + * incarnation of the same connection in TIME-WAIT state and if the remote + * host was sending CC options and if the connection duration was < MSL, then * truncate the previous TIME-WAIT state and proceed. * Initialize connection parameters and enter SYN-SENT state. */ @@ -849,9 +849,10 @@ struct socket *so = inp->inp_socket; struct tcpcb *otp; struct sockaddr_in *sin = (struct sockaddr_in *)nam; - struct sockaddr_in *ifaddr; struct rmxp_tao *taop; struct rmxp_tao tao_noncached; + struct in_addr laddr; + u_short lport; int error; if (inp->inp_lport == 0) { @@ -860,19 +861,12 @@ return error; } - /* - * Cannot simply call in_pcbconnect, because there might be an - * earlier incarnation of this same connection still in - * TIME_WAIT state, creating an ADDRINUSE error. - */ - error = in_pcbladdr(inp, nam, &ifaddr); - if (error) + laddr = inp->inp_laddr; + lport = inp->inp_lport; + error = in_pcbconnect_setup(inp, nam, &laddr.s_addr, &lport, + &inp->inp_faddr.s_addr, &inp->inp_fport, &oinp, td); + if (error && oinp == NULL) return error; - oinp = in_pcblookup_hash(inp->inp_pcbinfo, - sin->sin_addr, sin->sin_port, - inp->inp_laddr.s_addr != INADDR_ANY ? inp->inp_laddr - : ifaddr->sin_addr, - inp->inp_lport, 0, NULL); if (oinp) { if (oinp != inp && (otp = intotcpcb(oinp)) != NULL && otp->t_state == TCPS_TIME_WAIT && @@ -882,8 +876,7 @@ else return EADDRINUSE; } - if (inp->inp_laddr.s_addr == INADDR_ANY) - inp->inp_laddr = ifaddr->sin_addr; + inp->inp_laddr = laddr; inp->inp_faddr = sin->sin_addr; inp->inp_fport = sin->sin_port; in_pcbrehash(inp); Index: sys/netinet/udp_usrreq.c =================================================================== RCS file: /dump/FreeBSD-CVS/src/sys/netinet/udp_usrreq.c,v retrieving revision 1.122 diff -u -r1.122 udp_usrreq.c --- sys/netinet/udp_usrreq.c 15 Aug 2002 22:04:31 -0000 1.122 +++ sys/netinet/udp_usrreq.c 18 Aug 2002 21:07:22 -0000 @@ -34,6 +34,7 @@ * $FreeBSD: src/sys/netinet/udp_usrreq.c,v 1.122 2002/08/15 22:04:31 rwatson Exp $ */ +#include "opt_ipfw.h" #include "opt_ipsec.h" #include "opt_inet6.h" #include "opt_mac.h" @@ -727,7 +728,7 @@ static int udp_output(inp, m, addr, control, td) - register struct inpcb *inp; + struct inpcb *inp; struct mbuf *m; struct sockaddr *addr; struct mbuf *control; @@ -735,42 +736,110 @@ { register struct udpiphdr *ui; register int len = m->m_pkthdr.len; - struct in_addr laddr; - struct sockaddr_in *sin; - int s = 0, error = 0; + struct in_addr faddr, laddr; + struct cmsghdr *cm = NULL; + struct sockaddr_in *sin, src; + int error = 0; + u_short fport, lport; #ifdef MAC mac_create_mbuf_from_socket(inp->inp_socket, m); #endif - if (control) - m_freem(control); /* XXX */ - if (len + sizeof(struct udpiphdr) > IP_MAXPACKET) { error = EMSGSIZE; + if (control) + m_freem(control); + goto release; + } + + src.sin_addr.s_addr = INADDR_ANY; + if (control != NULL) { + /* + * XXX: Currently, we assume all the optional information + * is stored in a single mbuf. + */ + if (control->m_next) { + error = EINVAL; + m_freem(control); + goto release; + } + for (; control->m_len > 0; + control->m_data += CMSG_ALIGN(cm->cmsg_len), + control->m_len -= CMSG_ALIGN(cm->cmsg_len)) { + cm = mtod(control, struct cmsghdr *); + if (control->m_len < sizeof(*cm) || cm->cmsg_len == 0 || + cm->cmsg_len > control->m_len) { + error = EINVAL; + break; + } + if (cm->cmsg_level != IPPROTO_IP) + continue; + + switch (cm->cmsg_type) { + case IP_SENDSRCADDR: + if (cm->cmsg_len != + CMSG_LEN(sizeof(struct in_addr))) { + error = EINVAL; + break; + } + bzero(&src, sizeof(src)); + src.sin_family = AF_INET; + src.sin_len = sizeof(src); + src.sin_port = inp->inp_lport; + src.sin_addr = *(struct in_addr *)CMSG_DATA(cm); + break; + default: + error = ENOPROTOOPT; + break; + } + if (error) + break; + } + m_freem(control); + } + if (error) goto release; + laddr = inp->inp_laddr; + lport = inp->inp_lport; + if (src.sin_addr.s_addr != INADDR_ANY) { + if (lport == 0) { + error = EINVAL; + goto release; + } + error = in_pcbbind_setup(inp, (struct sockaddr *)&src, + &laddr.s_addr, &lport, td); + if (error) + goto release; } if (addr) { sin = (struct sockaddr_in *)addr; if (td && jailed(td->td_ucred)) prison_remote_ip(td->td_ucred, 0, &sin->sin_addr.s_addr); - laddr = inp->inp_laddr; if (inp->inp_faddr.s_addr != INADDR_ANY) { error = EISCONN; goto release; } - /* - * Must block input while temporarily connected. - */ - s = splnet(); - error = in_pcbconnect(inp, addr, td); - if (error) { - splx(s); + error = in_pcbconnect_setup(inp, addr, &laddr.s_addr, &lport, + &faddr.s_addr, &fport, NULL, td); + if (error) goto release; + + /* Commit the local port if newly assigned. */ + if (inp->inp_laddr.s_addr == INADDR_ANY && + inp->inp_lport == 0) { + inp->inp_lport = lport; + if (in_pcbinshash(inp) != 0) { + inp->inp_lport = 0; + error = EAGAIN; + goto release; + } } } else { - if (inp->inp_faddr.s_addr == INADDR_ANY) { + faddr = inp->inp_faddr; + fport = inp->inp_fport; + if (faddr.s_addr == INADDR_ANY) { error = ENOTCONN; goto release; } @@ -782,8 +851,6 @@ M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT); if (m == 0) { error = ENOBUFS; - if (addr) - splx(s); goto release; } @@ -794,10 +861,10 @@ ui = mtod(m, struct udpiphdr *); bzero(ui->ui_x1, sizeof(ui->ui_x1)); /* XXX still needed? */ ui->ui_pr = IPPROTO_UDP; - ui->ui_src = inp->inp_laddr; - ui->ui_dst = inp->inp_faddr; - ui->ui_sport = inp->inp_lport; - ui->ui_dport = inp->inp_fport; + ui->ui_src = laddr; + ui->ui_dst = faddr; + ui->ui_sport = lport; + ui->ui_dport = fport; ui->ui_ulen = htons((u_short)len + sizeof(struct udphdr)); /* @@ -825,12 +892,6 @@ error = ip_output(m, inp->inp_options, &inp->inp_route, (inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST)), inp->inp_moptions); - - if (addr) { - in_pcbdisconnect(inp); - inp->inp_laddr = laddr; /* XXX rehash? */ - splx(s); - } return (error); release: