git: 2deeed061b14 - main - sockstat(1): Add "-F" parameter

From: Juraj Lutter <otis_at_FreeBSD.org>
Date: Sun, 02 Nov 2025 15:18:06 UTC
The branch main has been updated by otis:

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

commit 2deeed061b1473a4230211c6562a533b76ce846c
Author:     Juraj Lutter <otis@FreeBSD.org>
AuthorDate: 2025-10-29 20:48:21 +0000
Commit:     Juraj Lutter <otis@FreeBSD.org>
CommitDate: 2025-11-02 15:17:47 +0000

    sockstat(1): Add "-F" parameter
    
    Add "-F" parameter that, when specified, instructs sockstat(1) to
    only display sockets for username/UID specified.
    
    For consistency with "-j", the last occurence of "-F" is used.
    
    Reviewed by:            asomers
    Approved by:            asomers
    Differential Revision:  https://reviews.freebsd.org/D53458
---
 usr.bin/sockstat/main.c     | 170 +++++++++++++++++++++++++++++++++-----------
 usr.bin/sockstat/sockstat.1 |   7 +-
 2 files changed, 136 insertions(+), 41 deletions(-)

diff --git a/usr.bin/sockstat/main.c b/usr.bin/sockstat/main.c
index 07663e54534d..1f174d827e1a 100644
--- a/usr.bin/sockstat/main.c
+++ b/usr.bin/sockstat/main.c
@@ -88,6 +88,7 @@ static bool	 opt_A;		/* Show kernel address of pcb */
 static bool	 opt_b;		/* Show BBLog state */
 static bool	 opt_C;		/* Show congestion control */
 static bool	 opt_c;		/* Show connected sockets */
+static bool	 opt_F;		/* Show sockets for selected user only */
 static bool	 opt_f;		/* Show FIB numbers */
 static bool	 opt_I;		/* Show spliced socket addresses */
 static bool	 opt_i;		/* Show inp_gencnt */
@@ -115,6 +116,12 @@ static size_t	   default_numprotos = nitems(default_protos);
 static int	*protos;	/* protocols to use */
 static size_t	 numprotos;	/* allocated size of protos[] */
 
+/*
+ * Show sockets for user username or UID specified
+ */
+static char	*filter_user_optarg = NULL;	/* saved optarg for username/UID resolving */
+static uid_t	filter_user_uid;		/* UID to show sockets for */
+
 struct addr {
 	union {
 		struct sockaddr_storage address;
@@ -217,6 +224,18 @@ _enforce_ksize(size_t received_size, size_t expected_size, const char *struct_na
 }
 #define enforce_ksize(_sz, _struct)	(_enforce_ksize(_sz, sizeof(_struct), #_struct))
 
+static inline bool
+filtered_uid(uid_t i_uid)
+{
+	return ((i_uid) == filter_user_uid);
+}
+
+static inline bool
+need_nosocks(void)
+{
+	return !(opt_F || (opt_j >= 0));
+}
+
 static int
 get_proto_type(const char *proto)
 {
@@ -758,7 +777,8 @@ gather_inet(int proto)
 		if (sock->socket != 0)
 			RB_INSERT(socks_t, &socks, sock);
 		else
-			SLIST_INSERT_HEAD(&nosocks, sock, socket_list);
+			if (need_nosocks())
+				SLIST_INSERT_HEAD(&nosocks, sock, socket_list);
 	}
 out:
 	free(buf);
@@ -862,6 +882,8 @@ getfiles(void)
 	struct xfile *xfiles;
 	size_t len, olen;
 
+	int filenum = 0;
+
 	olen = len = sizeof(*xfiles);
 	if ((xfiles = malloc(len)) == NULL)
 		xo_err(1, "malloc()");
@@ -880,14 +902,23 @@ getfiles(void)
 	if ((files = malloc(nfiles * sizeof(struct file))) == NULL)
 		xo_err(1, "malloc()");
 
+	/* Fill files structure, optionally for specified user */
 	for (int i = 0; i < nfiles; i++) {
-		files[i].xf_data = xfiles[i].xf_data;
-		files[i].xf_pid = xfiles[i].xf_pid;
-		files[i].xf_uid = xfiles[i].xf_uid;
-		files[i].xf_fd = xfiles[i].xf_fd;
-		RB_INSERT(files_t, &ftree, &files[i]);
+		if (opt_F && !filtered_uid(xfiles[i].xf_uid))
+				continue;
+		files[filenum].xf_data = xfiles[i].xf_data;
+		files[filenum].xf_pid = xfiles[i].xf_pid;
+		files[filenum].xf_uid = xfiles[i].xf_uid;
+		files[filenum].xf_fd = xfiles[i].xf_fd;
+		RB_INSERT(files_t, &ftree, &files[filenum]);
+		filenum++;
 	}
 
+	/* Adjust global nfiles to match the number of files we
+	 * actually filled into files[] array
+	 */
+	nfiles = filenum;
+
 	free(xfiles);
 }
 
@@ -1584,6 +1615,24 @@ display_sock(struct sock *s, struct col_widths *cw, char *buf, size_t bufsize)
 static void
 display(void)
 {
+	static const char *__HDR_USER="USER",
+			  *__HDR_COMMAND="COMMAND",
+			  *__HDR_PID="PID",
+			  *__HDR_FD="FD",
+			  *__HDR_PROTO="PROTO",
+			  *__HDR_LOCAL_ADDRESS="LOCAL ADDRESS",
+			  *__HDR_FOREIGN_ADDRESS="FOREIGN ADDRESS",
+			  *__HDR_PCB_KVA="PCB KVA",
+			  *__HDR_FIB="FIB",
+			  *__HDR_SPLICE_ADDRESS="SPLICE ADDRESS",
+			  *__HDR_ID="ID",
+			  *__HDR_ENCAPS="ENCAPS",
+			  *__HDR_PATH_STATE="PATH STATE",
+			  *__HDR_CONN_STATE="CONN STATE",
+			  *__HDR_BBLOG_STATE="BBLOG STATE",
+			  *__HDR_STACK="STACK",
+			  *__HDR_CC="CC";
+
 	struct passwd *pwd;
 	struct file *xf;
 	struct sock *s;
@@ -1598,23 +1647,23 @@ display(void)
 
 	if (!is_xo_style_encoding) {
 		cw = (struct col_widths) {
-			.user = strlen("USER"),
+			.user = strlen(__HDR_USER),
 			.command = 10,
-			.pid = strlen("PID"),
-			.fd = strlen("FD"),
-			.proto = strlen("PROTO"),
-			.local_addr = opt_w ? strlen("LOCAL ADDRESS") : 21,
-			.foreign_addr = opt_w ? strlen("FOREIGN ADDRESS") : 21,
+			.pid = strlen(__HDR_PID),
+			.fd = strlen(__HDR_FD),
+			.proto = strlen(__HDR_PROTO),
+			.local_addr = opt_w ? strlen(__HDR_LOCAL_ADDRESS) : 21,
+			.foreign_addr = opt_w ? strlen(__HDR_FOREIGN_ADDRESS) : 21,
 			.pcb_kva = 18,
-			.fib = strlen("FIB"),
-			.splice_address = strlen("SPLICE ADDRESS"),
-			.inp_gencnt = strlen("ID"),
-			.encaps = strlen("ENCAPS"),
-			.path_state = strlen("PATH STATE"),
-			.conn_state = strlen("CONN STATE"),
-			.bblog_state = strlen("BBLOG STATE"),
-			.stack = strlen("STACK"),
-			.cc = strlen("CC"),
+			.fib = strlen(__HDR_FIB),
+			.splice_address = strlen(__HDR_SPLICE_ADDRESS),
+			.inp_gencnt = strlen(__HDR_ID),
+			.encaps = strlen(__HDR_ENCAPS),
+			.path_state = strlen(__HDR_PATH_STATE),
+			.conn_state = strlen(__HDR_CONN_STATE),
+			.bblog_state = strlen(__HDR_BBLOG_STATE),
+			.stack = strlen(__HDR_STACK),
+			.cc = strlen(__HDR_CC),
 		};
 		calculate_column_widths(&cw);
 	} else
@@ -1625,34 +1674,34 @@ display(void)
 	xo_open_list("socket");
 	if (!opt_q) {
 		xo_emit("{T:/%-*s} {T:/%-*s} {T:/%*s} {T:/%*s} {T:/%-*s} "
-			"{T:/%-*s} {T:/%-*s}", cw.user, "USER", cw.command,
-			"COMMAND", cw.pid, "PID", cw.fd, "FD", cw.proto,
-			"PROTO", cw.local_addr, "LOCAL ADDRESS",
-			cw.foreign_addr, "FOREIGN ADDRESS");
+			"{T:/%-*s} {T:/%-*s}", cw.user, __HDR_USER, cw.command,
+			__HDR_COMMAND, cw.pid, __HDR_PID, cw.fd, __HDR_FD, cw.proto,
+			__HDR_PROTO, cw.local_addr, __HDR_LOCAL_ADDRESS,
+			cw.foreign_addr, __HDR_FOREIGN_ADDRESS);
 		if (opt_A)
-			xo_emit(" {T:/%-*s}", cw.pcb_kva, "PCB KVA");
+			xo_emit(" {T:/%-*s}", cw.pcb_kva, __HDR_PCB_KVA);
 		if (opt_f)
 			/* RT_MAXFIBS is 65535. */
-			xo_emit(" {T:/%*s}", cw.fib, "FIB");
+			xo_emit(" {T:/%*s}", cw.fib, __HDR_FIB);
 		if (opt_I)
 			xo_emit(" {T:/%-*s}", cw.splice_address,
-			    "SPLICE ADDRESS");
+			    __HDR_SPLICE_ADDRESS);
 		if (opt_i)
-			xo_emit(" {T:/%*s}", cw.inp_gencnt, "ID");
+			xo_emit(" {T:/%*s}", cw.inp_gencnt, __HDR_ID);
 		if (opt_U)
-			xo_emit(" {T:/%*s}", cw.encaps, "ENCAPS");
+			xo_emit(" {T:/%*s}", cw.encaps, __HDR_ENCAPS);
 		if (opt_s) {
 			if (show_path_state)
 				xo_emit(" {T:/%-*s}", cw.path_state,
-				    "PATH STATE");
-			xo_emit(" {T:/%-*s}", cw.conn_state, "CONN STATE");
+				    __HDR_PATH_STATE);
+			xo_emit(" {T:/%-*s}", cw.conn_state, __HDR_CONN_STATE);
 		}
 		if (opt_b)
-			xo_emit(" {T:/%-*s}", cw.bblog_state, "BBLOG STATE");
+			xo_emit(" {T:/%-*s}", cw.bblog_state, __HDR_BBLOG_STATE);
 		if (opt_S)
-			xo_emit(" {T:/%-*s}", cw.stack, "STACK");
+			xo_emit(" {T:/%-*s}", cw.stack, __HDR_STACK);
 		if (opt_C)
-			xo_emit(" {T:/%-*s}", cw.cc, "CC");
+			xo_emit(" {T:/%-*s}", cw.cc, __HDR_CC);
 		xo_emit("\n");
 	}
 	cap_setpassent(cappwd, 1);
@@ -1684,7 +1733,7 @@ display(void)
 			xo_close_instance("socket");
 		}
 	}
-	if (opt_j >= 0)
+	if (!need_nosocks())
 		goto out;
 	SLIST_FOREACH(s, &nosocks, socket_list) {
 		if (!check_ports(s))
@@ -1775,11 +1824,44 @@ jail_getvnet(int jid)
 	return (vnet);
 }
 
+/*
+ * Parse username and/or UID
+ */
+static bool
+parse_filter_user(void)
+{
+	struct passwd *pwd;
+	char *ep;
+	uid_t uid;
+	bool rv = false;
+
+	uid = (uid_t)strtol(filter_user_optarg, &ep, 10);
+
+	/* Open and/or rewind capsicumized password file */
+	cap_setpassent(cappwd, 1);
+
+	if (*ep == '\0') {
+		/* We have an UID specified, check if it's valid */
+		if ((pwd = cap_getpwuid(cappwd, uid)) == NULL) 
+			goto out;
+		filter_user_uid = uid;
+	} else {
+		/* Check if we have a valid username */
+		if ((pwd = cap_getpwnam(cappwd, filter_user_optarg)) == NULL) 
+			goto out;
+		filter_user_uid = pwd->pw_uid;
+	}
+
+	rv = true;
+out:
+	return (rv);
+}
+
 static void
 usage(void)
 {
 	xo_error(
-"usage: sockstat [--libxo ...] [-46AbCcfIiLlnqSsUuvw] [-j jid] [-p ports]\n"
+"usage: sockstat [--libxo ...] [-46AbCcfIiLlnqSsUuvw] [-F uid/username] [-j jid] [-p ports]\n"
 "                [-P protocols]\n");
 	exit(1);
 }
@@ -1789,8 +1871,8 @@ main(int argc, char *argv[])
 {
 	cap_channel_t *capcas;
 	cap_net_limit_t *limit;
-	const char *pwdcmds[] = { "setpassent", "getpwuid" };
-	const char *pwdfields[] = { "pw_name" };
+	const char *pwdcmds[] = { "setpassent", "getpwuid", "getpwnam" };
+	const char *pwdfields[] = { "pw_name", "pw_uid" };
 	int protos_defined = -1;
 	int o, i, err;
 
@@ -1803,7 +1885,7 @@ main(int argc, char *argv[])
 			is_xo_style_encoding = true;
 	}
 	opt_j = -1;
-	while ((o = getopt(argc, argv, "46AbCcfIij:Llnp:P:qSsUuvw")) != -1)
+	while ((o = getopt(argc, argv, "46AbCcF:fIij:Llnp:P:qSsUuvw")) != -1)
 		switch (o) {
 		case '4':
 			opt_4 = true;
@@ -1823,6 +1905,11 @@ main(int argc, char *argv[])
 		case 'c':
 			opt_c = true;
 			break;
+		case 'F':
+			/* Save optarg for later use when we enter capabilities mode */
+			filter_user_optarg = optarg;
+			opt_F = true;
+			break;
 		case 'f':
 			opt_f = true;
 			break;
@@ -1934,6 +2021,9 @@ main(int argc, char *argv[])
 	if (cap_pwd_limit_fields(cappwd, pwdfields, nitems(pwdfields)) < 0)
 		xo_err(1, "Unable to apply pwd commands limits");
 
+	if (opt_F && !parse_filter_user())
+		xo_errx(1, "Invalid username or UID specified");
+
 	if ((!opt_4 && !opt_6) && protos_defined != -1)
 		opt_4 = opt_6 = true;
 	if (!opt_4 && !opt_6 && !opt_u)
diff --git a/usr.bin/sockstat/sockstat.1 b/usr.bin/sockstat/sockstat.1
index 1498fb1d88f7..b0fae81ee566 100644
--- a/usr.bin/sockstat/sockstat.1
+++ b/usr.bin/sockstat/sockstat.1
@@ -25,7 +25,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd October 14, 2025
+.Dd October 29, 2025
 .Dt SOCKSTAT 1
 .Os
 .Sh NAME
@@ -35,6 +35,7 @@
 .Nm
 .Op Fl -libxo
 .Op Fl 46AbCcfIiLlnqSsUuvw
+.Op Fl F Ar user
 .Op Fl j Ar jail
 .Op Fl p Ar ports
 .Op Fl P Ar protocols
@@ -73,6 +74,10 @@ Display the congestion control module, if applicable.
 This is currently only implemented for TCP.
 .It Fl c
 Show connected sockets.
+.It Fl F Ar user
+Show sockets for specified
+.Ar user
+(user name or UID) only.
 .It Fl f
 Show the FIB number of each socket.
 .It Fl I