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
This commit is contained in:
parent
6863ab0b8b
commit
2510235150
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=361228
@ -610,13 +610,15 @@ in_pcbbind(struct inpcb *inp, struct sockaddr *nam, struct ucred *cred)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
|
||||||
* Select a local port (number) to use.
|
|
||||||
*/
|
|
||||||
#if defined(INET) || defined(INET6)
|
#if defined(INET) || defined(INET6)
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
int
|
int
|
||||||
in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp, u_short *lportp,
|
in_pcb_lport_dest(struct inpcb *inp, struct sockaddr *lsa, u_short *lportp,
|
||||||
struct ucred *cred, int lookupflags)
|
struct sockaddr *fsa, u_short fport, struct ucred *cred, int lookupflags)
|
||||||
{
|
{
|
||||||
struct inpcbinfo *pcbinfo;
|
struct inpcbinfo *pcbinfo;
|
||||||
struct inpcb *tmpinp;
|
struct inpcb *tmpinp;
|
||||||
@ -624,7 +626,10 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp, u_short *lportp,
|
|||||||
int count, dorandom, error;
|
int count, dorandom, error;
|
||||||
u_short aux, first, last, lport;
|
u_short aux, first, last, lport;
|
||||||
#ifdef INET
|
#ifdef INET
|
||||||
struct in_addr laddr;
|
struct in_addr laddr, faddr;
|
||||||
|
#endif
|
||||||
|
#ifdef INET6
|
||||||
|
struct in6_addr *laddr6, *faddr6;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
pcbinfo = inp->inp_pcbinfo;
|
pcbinfo = inp->inp_pcbinfo;
|
||||||
@ -685,15 +690,22 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp, u_short *lportp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef INET
|
#ifdef INET
|
||||||
/* Make the compiler happy. */
|
laddr.s_addr = INADDR_ANY;
|
||||||
laddr.s_addr = 0;
|
|
||||||
if ((inp->inp_vflag & (INP_IPV4|INP_IPV6)) == INP_IPV4) {
|
if ((inp->inp_vflag & (INP_IPV4|INP_IPV6)) == INP_IPV4) {
|
||||||
KASSERT(laddrp != NULL, ("%s: laddrp NULL for v4 inp %p",
|
laddr = ((struct sockaddr_in *)lsa)->sin_addr;
|
||||||
__func__, inp));
|
if (fsa != NULL)
|
||||||
laddr = *laddrp;
|
faddr = ((struct sockaddr_in *)fsa)->sin_addr;
|
||||||
}
|
}
|
||||||
#endif
|
#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;
|
lport = *lportp;
|
||||||
|
|
||||||
if (dorandom)
|
if (dorandom)
|
||||||
@ -709,29 +721,61 @@ in_pcb_lport(struct inpcb *inp, struct in_addr *laddrp, u_short *lportp,
|
|||||||
*lastport = first;
|
*lastport = first;
|
||||||
lport = htons(*lastport);
|
lport = htons(*lastport);
|
||||||
|
|
||||||
#ifdef INET6
|
if (fsa != NULL) {
|
||||||
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
|
|
||||||
#endif
|
|
||||||
#ifdef INET
|
|
||||||
tmpinp = in_pcblookup_local(pcbinfo, laddr,
|
|
||||||
lport, lookupflags, cred);
|
|
||||||
#endif
|
|
||||||
} while (tmpinp != NULL);
|
|
||||||
|
|
||||||
#ifdef INET
|
#ifdef INET
|
||||||
if ((inp->inp_vflag & (INP_IPV4|INP_IPV6)) == INP_IPV4)
|
if (lsa->sa_family == AF_INET) {
|
||||||
laddrp->s_addr = laddr.s_addr;
|
tmpinp = in_pcblookup_hash_locked(pcbinfo,
|
||||||
|
faddr, fport, laddr, lport, lookupflags,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef INET6
|
||||||
|
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
|
||||||
|
#endif
|
||||||
|
#ifdef INET
|
||||||
|
tmpinp = in_pcblookup_local(pcbinfo, laddr,
|
||||||
|
lport, lookupflags, cred);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} while (tmpinp != NULL);
|
||||||
|
|
||||||
*lportp = lport;
|
*lportp = lport;
|
||||||
|
|
||||||
return (0);
|
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.
|
* Return cached socket options.
|
||||||
*/
|
*/
|
||||||
@ -1344,16 +1388,26 @@ in_pcbconnect_setup(struct inpcb *inp, struct sockaddr *nam,
|
|||||||
if (error)
|
if (error)
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
oinp = in_pcblookup_hash_locked(inp->inp_pcbinfo, faddr, fport,
|
if (lport != 0) {
|
||||||
laddr, lport, 0, NULL);
|
oinp = in_pcblookup_hash_locked(inp->inp_pcbinfo, faddr,
|
||||||
if (oinp != NULL) {
|
fport, laddr, lport, 0, NULL);
|
||||||
if (oinpp != NULL)
|
if (oinp != NULL) {
|
||||||
*oinpp = oinp;
|
if (oinpp != NULL)
|
||||||
return (EADDRINUSE);
|
*oinpp = oinp;
|
||||||
}
|
return (EADDRINUSE);
|
||||||
if (lport == 0) {
|
}
|
||||||
error = in_pcbbind_setup(inp, NULL, &laddr.s_addr, &lport,
|
} else {
|
||||||
cred);
|
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)
|
if (error)
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
|
@ -824,6 +824,9 @@ void in_pcbgroup_update_mbuf(struct inpcb *, struct mbuf *);
|
|||||||
void in_pcbpurgeif0(struct inpcbinfo *, struct ifnet *);
|
void in_pcbpurgeif0(struct inpcbinfo *, struct ifnet *);
|
||||||
int in_pcballoc(struct socket *, struct inpcbinfo *);
|
int in_pcballoc(struct socket *, struct inpcbinfo *);
|
||||||
int in_pcbbind(struct inpcb *, struct sockaddr *, struct ucred *);
|
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 *,
|
int in_pcb_lport(struct inpcb *, struct in_addr *, u_short *,
|
||||||
struct ucred *, int);
|
struct ucred *, int);
|
||||||
int in_pcbbind_setup(struct inpcb *, struct sockaddr *, in_addr_t *,
|
int in_pcbbind_setup(struct inpcb *, struct sockaddr *, in_addr_t *,
|
||||||
|
@ -146,6 +146,16 @@ static int tcp_pru_options_support(struct tcpcb *tp, int flags);
|
|||||||
#define TCPDEBUG2(req)
|
#define TCPDEBUG2(req)
|
||||||
#endif
|
#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,
|
* TCP attaches to socket via pru_attach(), reserving space,
|
||||||
* and an internet control block.
|
* and an internet control block.
|
||||||
@ -1514,7 +1524,7 @@ tcp_connect(struct tcpcb *tp, struct sockaddr *nam, struct thread *td)
|
|||||||
INP_WLOCK_ASSERT(inp);
|
INP_WLOCK_ASSERT(inp);
|
||||||
INP_HASH_WLOCK(&V_tcbinfo);
|
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);
|
error = in_pcbbind(inp, (struct sockaddr *)0, td->td_ucred);
|
||||||
if (error)
|
if (error)
|
||||||
goto out;
|
goto out;
|
||||||
@ -1535,6 +1545,15 @@ tcp_connect(struct tcpcb *tp, struct sockaddr *nam, struct thread *td)
|
|||||||
error = EADDRINUSE;
|
error = EADDRINUSE;
|
||||||
goto out;
|
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;
|
inp->inp_laddr = laddr;
|
||||||
in_pcbrehash(inp);
|
in_pcbrehash(inp);
|
||||||
INP_HASH_WUNLOCK(&V_tcbinfo);
|
INP_HASH_WUNLOCK(&V_tcbinfo);
|
||||||
@ -1574,7 +1593,7 @@ tcp6_connect(struct tcpcb *tp, struct sockaddr *nam, struct thread *td)
|
|||||||
INP_WLOCK_ASSERT(inp);
|
INP_WLOCK_ASSERT(inp);
|
||||||
INP_HASH_WLOCK(&V_tcbinfo);
|
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);
|
error = in6_pcbbind(inp, (struct sockaddr *)0, td->td_ucred);
|
||||||
if (error)
|
if (error)
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -113,9 +113,6 @@ __FBSDID("$FreeBSD$");
|
|||||||
#include <netinet6/in6_fib.h>
|
#include <netinet6/in6_fib.h>
|
||||||
#include <netinet6/scope6_var.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
|
int
|
||||||
in6_pcbbind(struct inpcb *inp, struct sockaddr *nam,
|
in6_pcbbind(struct inpcb *inp, struct sockaddr *nam,
|
||||||
struct ucred *cred)
|
struct ucred *cred)
|
||||||
@ -417,9 +414,12 @@ in6_pcbconnect_mbuf(struct inpcb *inp, struct sockaddr *nam,
|
|||||||
{
|
{
|
||||||
struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
|
struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
|
||||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam;
|
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam;
|
||||||
struct in6_addr addr6;
|
struct sockaddr_in6 laddr6;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
|
bzero(&laddr6, sizeof(laddr6));
|
||||||
|
laddr6.sin6_family = AF_INET6;
|
||||||
|
|
||||||
INP_WLOCK_ASSERT(inp);
|
INP_WLOCK_ASSERT(inp);
|
||||||
INP_HASH_WLOCK_ASSERT(pcbinfo);
|
INP_HASH_WLOCK_ASSERT(pcbinfo);
|
||||||
|
|
||||||
@ -427,25 +427,36 @@ in6_pcbconnect_mbuf(struct inpcb *inp, struct sockaddr *nam,
|
|||||||
* Call inner routine, to assign local interface address.
|
* Call inner routine, to assign local interface address.
|
||||||
* in6_pcbladdr() may automatically fill in sin6_scope_id.
|
* 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);
|
return (error);
|
||||||
|
|
||||||
if (in6_pcblookup_hash_locked(pcbinfo, &sin6->sin6_addr,
|
if (in6_pcblookup_hash_locked(pcbinfo, &sin6->sin6_addr,
|
||||||
sin6->sin6_port,
|
sin6->sin6_port,
|
||||||
IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)
|
IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)
|
||||||
? &addr6 : &inp->in6p_laddr,
|
? &laddr6.sin6_addr : &inp->in6p_laddr,
|
||||||
inp->inp_lport, 0, NULL) != NULL) {
|
inp->inp_lport, 0, NULL) != NULL) {
|
||||||
return (EADDRINUSE);
|
return (EADDRINUSE);
|
||||||
}
|
}
|
||||||
if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) {
|
if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) {
|
||||||
if (inp->inp_lport == 0) {
|
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,
|
KASSERT(rehash == true,
|
||||||
("Rehashing required for unbound inps"));
|
("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)
|
if (error)
|
||||||
return (error);
|
return (error);
|
||||||
}
|
}
|
||||||
inp->in6p_laddr = addr6;
|
inp->in6p_laddr = laddr6.sin6_addr;
|
||||||
}
|
}
|
||||||
inp->in6p_faddr = sin6->sin6_addr;
|
inp->in6p_faddr = sin6->sin6_addr;
|
||||||
inp->inp_fport = sin6->sin6_port;
|
inp->inp_fport = sin6->sin6_port;
|
||||||
@ -1122,9 +1133,9 @@ in6_pcblookup_group(struct inpcbinfo *pcbinfo, struct inpcbgroup *pcbgroup,
|
|||||||
#endif /* PCBGROUP */
|
#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,
|
in6_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, struct in6_addr *faddr,
|
||||||
u_int fport_arg, struct in6_addr *laddr, u_int lport_arg,
|
u_int fport_arg, struct in6_addr *laddr, u_int lport_arg,
|
||||||
int lookupflags, struct ifnet *ifp)
|
int lookupflags, struct ifnet *ifp)
|
||||||
|
@ -92,6 +92,10 @@ struct inpcb *
|
|||||||
in6_pcblookup_local(struct inpcbinfo *,
|
in6_pcblookup_local(struct inpcbinfo *,
|
||||||
struct in6_addr *, u_short, int,
|
struct in6_addr *, u_short, int,
|
||||||
struct ucred *);
|
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 *
|
struct inpcb *
|
||||||
in6_pcblookup(struct inpcbinfo *, struct in6_addr *,
|
in6_pcblookup(struct inpcbinfo *, struct in6_addr *,
|
||||||
u_int, struct in6_addr *, u_int, int,
|
u_int, struct in6_addr *, u_int, int,
|
||||||
|
Loading…
Reference in New Issue
Block a user