Author: karels Date: Mon May 18 22:53:12 2020 New Revision: 361228 URL: https://svnweb.freebsd.org/changeset/base/361228
Log: Allow TCP to reuse local port with different destinations Previously, tcp_connect() would bind a local port before connecting, forcing the local port to be unique across all outgoing TCP connections for the address family. Instead, choose a local port after selecting the destination and the local address, requiring only that the tuple is unique and does not match a wildcard binding. Reviewed by: tuexen (rscheff, rrs previous version) MFC after: 1 month Sponsored by: Forcepoint LLC Differential Revision: https://reviews.freebsd.org/D24781 Modified: head/sys/netinet/in_pcb.c head/sys/netinet/in_pcb.h head/sys/netinet/tcp_usrreq.c head/sys/netinet6/in6_pcb.c head/sys/netinet6/in6_pcb.h Modified: head/sys/netinet/in_pcb.c ============================================================================== --- head/sys/netinet/in_pcb.c Mon May 18 19:48:38 2020 (r361227) +++ head/sys/netinet/in_pcb.c Mon May 18 22:53:12 2020 (r361228) @@ -610,13 +610,15 @@ in_pcbbind(struct inpcb *inp, struct sockaddr *nam, st } #endif +#if defined(INET) || defined(INET6) /* - * Select a local port (number) to use. + * Assign a local port like in_pcb_lport(), but also used with connect() + * and a foreign address and port. If fsa is non-NULL, choose a local port + * that is unused with those, otherwise one that is completely unused. */ -#if defined(INET) || defined(INET6) int -in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp, u_short *lportp, - struct ucred *cred, int lookupflags) +in_pcb_lport_dest(struct inpcb *inp, struct sockaddr *lsa, u_short *lportp, + struct sockaddr *fsa, u_short fport, struct ucred *cred, int lookupflags) { struct inpcbinfo *pcbinfo; struct inpcb *tmpinp; @@ -624,8 +626,11 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp int count, dorandom, error; u_short aux, first, last, lport; #ifdef INET - struct in_addr laddr; + struct in_addr laddr, faddr; #endif +#ifdef INET6 + struct in6_addr *laddr6, *faddr6; +#endif pcbinfo = inp->inp_pcbinfo; @@ -685,15 +690,22 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp } #ifdef INET - /* Make the compiler happy. */ - laddr.s_addr = 0; + laddr.s_addr = INADDR_ANY; if ((inp->inp_vflag & (INP_IPV4|INP_IPV6)) == INP_IPV4) { - KASSERT(laddrp != NULL, ("%s: laddrp NULL for v4 inp %p", - __func__, inp)); - laddr = *laddrp; + laddr = ((struct sockaddr_in *)lsa)->sin_addr; + if (fsa != NULL) + faddr = ((struct sockaddr_in *)fsa)->sin_addr; } #endif - tmpinp = NULL; /* Make compiler happy. */ +#ifdef INET6 + if (lsa->sa_family == AF_INET6) { + laddr6 = &((struct sockaddr_in6 *)lsa)->sin6_addr; + if (fsa != NULL) + faddr6 = &((struct sockaddr_in6 *)fsa)->sin6_addr; + } +#endif + + tmpinp = NULL; lport = *lportp; if (dorandom) @@ -709,30 +721,62 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp *lastport = first; lport = htons(*lastport); + if (fsa != NULL) { + +#ifdef INET + if (lsa->sa_family == AF_INET) { + tmpinp = in_pcblookup_hash_locked(pcbinfo, + faddr, fport, laddr, lport, lookupflags, + NULL); + } +#endif #ifdef INET6 - if ((inp->inp_vflag & INP_IPV6) != 0) - tmpinp = in6_pcblookup_local(pcbinfo, - &inp->in6p_laddr, lport, lookupflags, cred); + if (lsa->sa_family == AF_INET6) { + tmpinp = in6_pcblookup_hash_locked(pcbinfo, + faddr6, fport, laddr6, lport, lookupflags, + NULL); + } #endif + } else { +#ifdef INET6 + if ((inp->inp_vflag & INP_IPV6) != 0) + tmpinp = in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, lookupflags, cred); +#endif #if defined(INET) && defined(INET6) - else + else #endif #ifdef INET - tmpinp = in_pcblookup_local(pcbinfo, laddr, - lport, lookupflags, cred); + tmpinp = in_pcblookup_local(pcbinfo, laddr, + lport, lookupflags, cred); #endif + } } while (tmpinp != NULL); -#ifdef INET - if ((inp->inp_vflag & (INP_IPV4|INP_IPV6)) == INP_IPV4) - laddrp->s_addr = laddr.s_addr; -#endif *lportp = lport; return (0); } /* + * Select a local port (number) to use. + */ +int +in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp, u_short *lportp, + struct ucred *cred, int lookupflags) +{ + struct sockaddr_in laddr; + + if (laddrp) { + bzero(&laddr, sizeof(laddr)); + laddr.sin_family = AF_INET; + laddr.sin_addr = *laddrp; + } + return (in_pcb_lport_dest(inp, laddrp ? (struct sockaddr *) &laddr : + NULL, lportp, NULL, 0, cred, lookupflags)); +} + +/* * Return cached socket options. */ int @@ -1344,16 +1388,26 @@ in_pcbconnect_setup(struct inpcb *inp, struct sockaddr if (error) return (error); } - oinp = in_pcblookup_hash_locked(inp->inp_pcbinfo, faddr, fport, - laddr, lport, 0, NULL); - if (oinp != NULL) { - if (oinpp != NULL) - *oinpp = oinp; - return (EADDRINUSE); - } - if (lport == 0) { - error = in_pcbbind_setup(inp, NULL, &laddr.s_addr, &lport, - cred); + if (lport != 0) { + oinp = in_pcblookup_hash_locked(inp->inp_pcbinfo, faddr, + fport, laddr, lport, 0, NULL); + if (oinp != NULL) { + if (oinpp != NULL) + *oinpp = oinp; + return (EADDRINUSE); + } + } else { + struct sockaddr_in lsin, fsin; + + bzero(&lsin, sizeof(lsin)); + bzero(&fsin, sizeof(fsin)); + lsin.sin_family = AF_INET; + lsin.sin_addr = laddr; + fsin.sin_family = AF_INET; + fsin.sin_addr = faddr; + error = in_pcb_lport_dest(inp, (struct sockaddr *) &lsin, + &lport, (struct sockaddr *)& fsin, fport, cred, + INPLOOKUP_WILDCARD); if (error) return (error); } Modified: head/sys/netinet/in_pcb.h ============================================================================== --- head/sys/netinet/in_pcb.h Mon May 18 19:48:38 2020 (r361227) +++ head/sys/netinet/in_pcb.h Mon May 18 22:53:12 2020 (r361228) @@ -824,6 +824,9 @@ void in_pcbgroup_update_mbuf(struct inpcb *, struct mb void in_pcbpurgeif0(struct inpcbinfo *, struct ifnet *); int in_pcballoc(struct socket *, struct inpcbinfo *); int in_pcbbind(struct inpcb *, struct sockaddr *, struct ucred *); +int in_pcb_lport_dest(struct inpcb *inp, struct sockaddr *lsa, + u_short *lportp, struct sockaddr *fsa, u_short fport, + struct ucred *cred, int lookupflags); int in_pcb_lport(struct inpcb *, struct in_addr *, u_short *, struct ucred *, int); int in_pcbbind_setup(struct inpcb *, struct sockaddr *, in_addr_t *, Modified: head/sys/netinet/tcp_usrreq.c ============================================================================== --- head/sys/netinet/tcp_usrreq.c Mon May 18 19:48:38 2020 (r361227) +++ head/sys/netinet/tcp_usrreq.c Mon May 18 22:53:12 2020 (r361228) @@ -147,6 +147,16 @@ static int tcp_pru_options_support(struct tcpcb *tp, i #endif /* + * tcp_require_unique port requires a globally-unique source port for each + * outgoing connection. The default is to require the 4-tuple to be unique. + */ +VNET_DEFINE(int, tcp_require_unique_port) = 0; +SYSCTL_INT(_net_inet_tcp, OID_AUTO, require_unique_port, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(tcp_require_unique_port), 0, + "Require globally-unique ephemeral port for outgoing connections"); +#define V_tcp_require_unique_port VNET(tcp_require_unique_port) + +/* * TCP attaches to socket via pru_attach(), reserving space, * and an internet control block. */ @@ -1514,7 +1524,7 @@ tcp_connect(struct tcpcb *tp, struct sockaddr *nam, st INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK(&V_tcbinfo); - if (inp->inp_lport == 0) { + if (V_tcp_require_unique_port && inp->inp_lport == 0) { error = in_pcbbind(inp, (struct sockaddr *)0, td->td_ucred); if (error) goto out; @@ -1535,6 +1545,15 @@ tcp_connect(struct tcpcb *tp, struct sockaddr *nam, st error = EADDRINUSE; goto out; } + /* Handle initial bind if it hadn't been done in advance. */ + if (inp->inp_lport == 0) { + inp->inp_lport = lport; + if (in_pcbinshash(inp) != 0) { + inp->inp_lport = 0; + error = EAGAIN; + goto out; + } + } inp->inp_laddr = laddr; in_pcbrehash(inp); INP_HASH_WUNLOCK(&V_tcbinfo); @@ -1574,7 +1593,7 @@ tcp6_connect(struct tcpcb *tp, struct sockaddr *nam, s INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK(&V_tcbinfo); - if (inp->inp_lport == 0) { + if (V_tcp_require_unique_port && inp->inp_lport == 0) { error = in6_pcbbind(inp, (struct sockaddr *)0, td->td_ucred); if (error) goto out; Modified: head/sys/netinet6/in6_pcb.c ============================================================================== --- head/sys/netinet6/in6_pcb.c Mon May 18 19:48:38 2020 (r361227) +++ head/sys/netinet6/in6_pcb.c Mon May 18 22:53:12 2020 (r361228) @@ -113,9 +113,6 @@ __FBSDID("$FreeBSD$"); #include <netinet6/in6_fib.h> #include <netinet6/scope6_var.h> -static struct inpcb *in6_pcblookup_hash_locked(struct inpcbinfo *, - struct in6_addr *, u_int, struct in6_addr *, u_int, int, struct ifnet *); - int in6_pcbbind(struct inpcb *inp, struct sockaddr *nam, struct ucred *cred) @@ -417,9 +414,12 @@ in6_pcbconnect_mbuf(struct inpcb *inp, struct sockaddr { struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; - struct in6_addr addr6; + struct sockaddr_in6 laddr6; int error; + bzero(&laddr6, sizeof(laddr6)); + laddr6.sin6_family = AF_INET6; + INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK_ASSERT(pcbinfo); @@ -427,25 +427,36 @@ in6_pcbconnect_mbuf(struct inpcb *inp, struct sockaddr * Call inner routine, to assign local interface address. * in6_pcbladdr() may automatically fill in sin6_scope_id. */ - if ((error = in6_pcbladdr(inp, nam, &addr6)) != 0) + if ((error = in6_pcbladdr(inp, nam, &laddr6.sin6_addr)) != 0) return (error); if (in6_pcblookup_hash_locked(pcbinfo, &sin6->sin6_addr, sin6->sin6_port, IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) - ? &addr6 : &inp->in6p_laddr, + ? &laddr6.sin6_addr : &inp->in6p_laddr, inp->inp_lport, 0, NULL) != NULL) { return (EADDRINUSE); } if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) { if (inp->inp_lport == 0) { + /* + * rehash was required to be true in the past for + * this case; retain that convention. However, + * we now call in_pcb_lport_dest rather than + * in6_pcbbind; the former does not insert into + * the hash table, the latter does. Change rehash + * to false to do the in_pcbinshash below. + */ KASSERT(rehash == true, ("Rehashing required for unbound inps")); - error = in6_pcbbind(inp, (struct sockaddr *)0, cred); + rehash = false; + error = in_pcb_lport_dest(inp, + (struct sockaddr *) &laddr6, &inp->inp_lport, + (struct sockaddr *) sin6, sin6->sin6_port, cred, 0); if (error) return (error); } - inp->in6p_laddr = addr6; + inp->in6p_laddr = laddr6.sin6_addr; } inp->in6p_faddr = sin6->sin6_addr; inp->inp_fport = sin6->sin6_port; @@ -1122,9 +1133,9 @@ found: #endif /* PCBGROUP */ /* - * Lookup PCB in hash list. + * Lookup PCB in hash list. Used in in_pcb.c as well as here. */ -static struct inpcb * +struct inpcb * in6_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, struct in6_addr *faddr, u_int fport_arg, struct in6_addr *laddr, u_int lport_arg, int lookupflags, struct ifnet *ifp) Modified: head/sys/netinet6/in6_pcb.h ============================================================================== --- head/sys/netinet6/in6_pcb.h Mon May 18 19:48:38 2020 (r361227) +++ head/sys/netinet6/in6_pcb.h Mon May 18 22:53:12 2020 (r361228) @@ -92,6 +92,10 @@ struct inpcb * in6_pcblookup_local(struct inpcbinfo *, struct in6_addr *, u_short, int, struct ucred *); +struct inpcb * + in6_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, + struct in6_addr *faddr, u_int fport_arg, struct in6_addr *laddr, + u_int lport_arg, int lookupflags, struct ifnet *ifp); struct inpcb * in6_pcblookup(struct inpcbinfo *, struct in6_addr *, u_int, struct in6_addr *, u_int, int, _______________________________________________ svn-src-head@freebsd.org mailing list https://lists.freebsd.org/mailman/listinfo/svn-src-head To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"