svn commit: r362446 - in stable/12/sys: netinet netinet6
Mike Karels
karels at FreeBSD.org
Sat Jun 20 20:10:44 UTC 2020
Author: karels
Date: Sat Jun 20 20:10:42 2020
New Revision: 362446
URL: https://svnweb.freebsd.org/changeset/base/362446
Log:
Allow TCP to reuse local port with different destinations
MFC r361228, r361231:
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.
Note that in_pcb_lport and in_pcb_lport_dest can be called with a NULL
local address for IPv6 sockets; handle it.
Sponsored by: Forcepoint LLC
Modified:
stable/12/sys/netinet/in_pcb.c
stable/12/sys/netinet/in_pcb.h
stable/12/sys/netinet/tcp_usrreq.c
stable/12/sys/netinet6/in6_pcb.c
stable/12/sys/netinet6/in6_pcb.h
Directory Properties:
stable/12/ (props changed)
Modified: stable/12/sys/netinet/in_pcb.c
==============================================================================
--- stable/12/sys/netinet/in_pcb.c Sat Jun 20 20:06:52 2020 (r362445)
+++ stable/12/sys/netinet/in_pcb.c Sat Jun 20 20:10:42 2020 (r362446)
@@ -591,13 +591,16 @@ 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.
+ * lsa can be NULL for IPv6.
*/
-#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;
@@ -605,8 +608,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;
@@ -666,15 +672,25 @@ 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;
+ if (lsa != NULL)
+ 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
+ laddr6 = NULL;
+ if ((inp->inp_vflag & INP_IPV6) != 0) {
+ if (lsa != NULL)
+ laddr6 = &((struct sockaddr_in6 *)lsa)->sin6_addr;
+ if (fsa != NULL)
+ faddr6 = &((struct sockaddr_in6 *)fsa)->sin6_addr;
+ }
+#endif
+
+ tmpinp = NULL;
lport = *lportp;
if (dorandom)
@@ -690,30 +706,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
@@ -1327,16 +1375,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: stable/12/sys/netinet/in_pcb.h
==============================================================================
--- stable/12/sys/netinet/in_pcb.h Sat Jun 20 20:06:52 2020 (r362445)
+++ stable/12/sys/netinet/in_pcb.h Sat Jun 20 20:10:42 2020 (r362446)
@@ -837,6 +837,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: stable/12/sys/netinet/tcp_usrreq.c
==============================================================================
--- stable/12/sys/netinet/tcp_usrreq.c Sat Jun 20 20:06:52 2020 (r362445)
+++ stable/12/sys/netinet/tcp_usrreq.c Sat Jun 20 20:10:42 2020 (r362446)
@@ -134,6 +134,16 @@ static void tcp_fill_info(struct tcpcb *, struct tcp_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.
*/
@@ -1456,7 +1466,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;
@@ -1477,6 +1487,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);
@@ -1516,7 +1535,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: stable/12/sys/netinet6/in6_pcb.c
==============================================================================
--- stable/12/sys/netinet6/in6_pcb.c Sat Jun 20 20:06:52 2020 (r362445)
+++ stable/12/sys/netinet6/in6_pcb.c Sat Jun 20 20:10:42 2020 (r362446)
@@ -111,9 +111,6 @@ __FBSDID("$FreeBSD$");
#include <netinet6/in6_pcb.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)
@@ -416,9 +413,13 @@ 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;
+ bool rehash = true;
+ bzero(&laddr6, sizeof(laddr6));
+ laddr6.sin6_family = AF_INET6;
+
INP_WLOCK_ASSERT(inp);
INP_HASH_WLOCK_ASSERT(pcbinfo);
@@ -426,23 +427,26 @@ 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) {
- 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;
@@ -452,7 +456,11 @@ in6_pcbconnect_mbuf(struct inpcb *inp, struct sockaddr
inp->inp_flow |=
(htonl(ip6_randomflowlabel()) & IPV6_FLOWLABEL_MASK);
- in_pcbrehash_mbuf(inp, m);
+ if (rehash) {
+ in_pcbrehash_mbuf(inp, m);
+ } else {
+ in_pcbinshash(inp);
+ }
return (0);
}
@@ -1115,9 +1123,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: stable/12/sys/netinet6/in6_pcb.h
==============================================================================
--- stable/12/sys/netinet6/in6_pcb.h Sat Jun 20 20:06:52 2020 (r362445)
+++ stable/12/sys/netinet6/in6_pcb.h Sat Jun 20 20:10:42 2020 (r362446)
@@ -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,
More information about the svn-src-stable-12
mailing list