git: a35bdd4489b9 - main - tcp: add sysctl interface for setting socket options

From: Michael Tuexen <tuexen_at_FreeBSD.org>
Date: Wed, 09 Feb 2022 12:22:36 UTC
The branch main has been updated by tuexen:

URL: https://cgit.FreeBSD.org/src/commit/?id=a35bdd4489b97f14cb46ebd0f5354685e4488af3

commit a35bdd4489b97f14cb46ebd0f5354685e4488af3
Author:     Michael Tuexen <tuexen@FreeBSD.org>
AuthorDate: 2022-02-09 11:24:41 +0000
Commit:     Michael Tuexen <tuexen@FreeBSD.org>
CommitDate: 2022-02-09 11:24:41 +0000

    tcp: add sysctl interface for setting socket options
    
    This interface allows to set a socket option on a TCP endpoint,
    which is specified by its inp_gencnt. This interface will be
    used in an upcoming command line tool tcpsso.
    
    Reviewed by:            glebius, rrs
    Sponsored by:           Netflix, Inc.
    Differential Revision:  https://reviews.freebsd.org/D34138
---
 sys/netinet/in_pcb.c   | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++
 sys/netinet/in_pcb.h   | 12 ++++++++
 sys/netinet/tcp_subr.c | 12 ++++++++
 3 files changed, 100 insertions(+)

diff --git a/sys/netinet/in_pcb.c b/sys/netinet/in_pcb.c
index 97471ae7800c..147e0b88214d 100644
--- a/sys/netinet/in_pcb.c
+++ b/sys/netinet/in_pcb.c
@@ -63,6 +63,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/socket.h>
 #include <sys/socketvar.h>
 #include <sys/sockio.h>
+#include <sys/sysctl.h>
 #include <sys/priv.h>
 #include <sys/proc.h>
 #include <sys/refcount.h>
@@ -2799,6 +2800,81 @@ in_pcbtoxinpcb(const struct inpcb *inp, struct xinpcb *xi)
 	xi->inp_ip_minttl = inp->inp_ip_minttl;
 }
 
+int
+sysctl_setsockopt(SYSCTL_HANDLER_ARGS, struct inpcbinfo *pcbinfo,
+    int (*ctloutput_set)(struct inpcb *, struct sockopt *))
+{
+	struct sockopt sopt;
+	struct inpcb_iterator inpi = INP_ALL_ITERATOR(pcbinfo,
+	    INPLOOKUP_WLOCKPCB);
+	struct inpcb *inp;
+	struct sockopt_parameters *params;
+	struct socket *so;
+	int error;
+	char buf[1024];
+
+	if (req->oldptr != NULL || req->oldlen != 0)
+		return (EINVAL);
+	if (req->newptr == NULL)
+		return (EPERM);
+	if (req->newlen > sizeof(buf))
+		return (ENOMEM);
+	error = SYSCTL_IN(req, buf, req->newlen);
+	if (error != 0)
+		return (error);
+	if (req->newlen < sizeof(struct sockopt_parameters))
+		return (EINVAL);
+	params = (struct sockopt_parameters *)buf;
+	sopt.sopt_level = params->sop_level;
+	sopt.sopt_name = params->sop_optname;
+	sopt.sopt_dir = SOPT_SET;
+	sopt.sopt_val = params->sop_optval;
+	sopt.sopt_valsize = req->newlen - sizeof(struct sockopt_parameters);
+	sopt.sopt_td = NULL;
+	if (params->sop_inc.inc_flags & INC_ISIPV6) {
+		if (IN6_IS_SCOPE_LINKLOCAL(&params->sop_inc.inc6_laddr))
+			params->sop_inc.inc6_laddr.s6_addr16[1] =
+			    htons(params->sop_inc.inc6_zoneid & 0xffff);
+		if (IN6_IS_SCOPE_LINKLOCAL(&params->sop_inc.inc6_faddr))
+			params->sop_inc.inc6_faddr.s6_addr16[1] =
+			    htons(params->sop_inc.inc6_zoneid & 0xffff);
+	}
+	if (params->sop_inc.inc_lport != htons(0)) {
+		if (params->sop_inc.inc_fport == htons(0))
+			inpi.hash = INP_PCBHASH_WILD(params->sop_inc.inc_lport,
+			    pcbinfo->ipi_hashmask);
+		else
+			if (params->sop_inc.inc_flags & INC_ISIPV6)
+				inpi.hash = INP6_PCBHASH(
+				    &params->sop_inc.inc6_faddr,
+				    params->sop_inc.inc_lport,
+				    params->sop_inc.inc_fport,
+				    pcbinfo->ipi_hashmask);
+			else
+				inpi.hash = INP_PCBHASH(
+				    &params->sop_inc.inc_faddr,
+				    params->sop_inc.inc_lport,
+				    params->sop_inc.inc_fport,
+				    pcbinfo->ipi_hashmask);
+	}
+	while ((inp = inp_next(&inpi)) != NULL)
+		if (inp->inp_gencnt == params->sop_id) {
+			if (inp->inp_flags & (INP_TIMEWAIT | INP_DROPPED)) {
+				INP_WUNLOCK(inp);
+				return (ECONNRESET);
+			}
+			so = inp->inp_socket;
+			KASSERT(so != NULL, ("inp_socket == NULL"));
+			soref(so);
+			error = (*ctloutput_set)(inp, &sopt);
+			sorele(so);
+			break;
+		}
+	if (inp == NULL)
+		error = ESRCH;
+	return (error);
+}
+
 #ifdef DDB
 static void
 db_print_indent(int indent)
diff --git a/sys/netinet/in_pcb.h b/sys/netinet/in_pcb.h
index 49b891e33b15..c6a8448b8791 100644
--- a/sys/netinet/in_pcb.h
+++ b/sys/netinet/in_pcb.h
@@ -52,6 +52,7 @@
 #include <sys/proc.h>
 #include <sys/rwlock.h>
 #include <sys/smr.h>
+#include <sys/sysctl.h>
 #include <net/vnet.h>
 #include <vm/uma.h>
 #endif
@@ -368,7 +369,18 @@ struct xinpgen {
 	so_gen_t	xig_sogen;	/* socket generation count this time */
 	uint64_t	_xig_spare64[4];
 } __aligned(8);
+
+struct sockopt_parameters {
+	struct in_conninfo sop_inc;
+	uint64_t sop_id;
+	int sop_level;
+	int sop_optname;
+	char sop_optval[];
+};
+
 #ifdef	_KERNEL
+int	sysctl_setsockopt(SYSCTL_HANDLER_ARGS, struct inpcbinfo *pcbinfo,
+	    int (*ctloutput_set)(struct inpcb *, struct sockopt *));
 void	in_pcbtoxinpcb(const struct inpcb *, struct xinpcb *);
 #endif
 #endif /* _SYS_SOCKETVAR_H_ */
diff --git a/sys/netinet/tcp_subr.c b/sys/netinet/tcp_subr.c
index 1cd53db3bf99..298ae38d5b27 100644
--- a/sys/netinet/tcp_subr.c
+++ b/sys/netinet/tcp_subr.c
@@ -3834,6 +3834,18 @@ SYSCTL_PROC(_net_inet_tcp, TCPCTL_DROP, drop,
     CTLFLAG_NEEDGIANT, NULL, 0, sysctl_drop, "",
     "Drop TCP connection");
 
+static int
+tcp_sysctl_setsockopt(SYSCTL_HANDLER_ARGS)
+{
+	return (sysctl_setsockopt(oidp, arg1, arg2, req, &V_tcbinfo,
+	    &tcp_ctloutput_set));
+}
+
+SYSCTL_PROC(_net_inet_tcp, OID_AUTO, setsockopt,
+    CTLFLAG_VNET | CTLTYPE_STRUCT | CTLFLAG_WR | CTLFLAG_SKIP |
+    CTLFLAG_MPSAFE, NULL, 0, tcp_sysctl_setsockopt, "",
+    "Set socket option for TCP endpoint");
+
 #ifdef KERN_TLS
 static int
 sysctl_switch_tls(SYSCTL_HANDLER_ARGS)