git: 2c436d4890c7 - main - sockstat(1): print out full connection graph for unix(4) sockets

From: Gleb Smirnoff <glebius_at_FreeBSD.org>
Date: Thu, 07 Jul 2022 05:20:37 UTC
The branch main has been updated by glebius:

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

commit 2c436d4890c7ed7f50321725dee229834114166b
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2022-07-07 05:19:08 +0000
Commit:     Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2022-07-07 05:19:08 +0000

    sockstat(1): print out full connection graph for unix(4) sockets
    
    Kernel provides us with enough information to display all possible
    connections between UNIX sockets.
    
    o Store unp_conn, xu_firstref and xu_nextref in the faddr of a UNIX sock.
    o Build tree of file descriptors, indexed by the socket pointer.
    o In displaysock() print out all possible information:
      1) if socket is bound, print name of this socket
      2) if socket has connected to a peer with a name, print peers name
      3) if socket has connected to a peer without a name, print [pid fd]
      4) if a bound socket has received connections, print list of them
         as [pid fd]
      Previously, only 1) either 2) were printed.
    
    Reviewed by:            tuexen
    Differential revision:  https://reviews.freebsd.org/D35726
---
 usr.bin/sockstat/sockstat.1 |  25 +++++++---
 usr.bin/sockstat/sockstat.c | 119 ++++++++++++++++++++++++++++++++++----------
 2 files changed, 110 insertions(+), 34 deletions(-)

diff --git a/usr.bin/sockstat/sockstat.1 b/usr.bin/sockstat/sockstat.1
index bc8fa628f725..d01b7dba08f0 100644
--- a/usr.bin/sockstat/sockstat.1
+++ b/usr.bin/sockstat/sockstat.1
@@ -27,7 +27,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd February 2, 2022
+.Dd June 6, 2022
 .Dt SOCKSTAT 1
 .Os
 .Sh NAME
@@ -151,18 +151,27 @@ sockets.
 For Internet sockets, this is the address the local end of the socket
 is bound to (see
 .Xr getsockname 2 ) .
+.Pp
 For bound
 .Ux
-sockets, it is the socket's filename.
-For other
+sockets, socket's filename is printed.
+For not bound
 .Ux
-sockets, it is a right arrow followed by the endpoint's filename, or
-.Dq Li ??
-if the endpoint could not be determined.
+sockets, the field is empty.
 .It Li FOREIGN ADDRESS
-(Internet sockets only)
-The address the foreign end of the socket is bound to (see
+For Internet sockets, this is the address the foreign end of the socket
+is bound to (see
 .Xr getpeername 2 ) .
+.Pp
+For bound
+.Ux
+sockets a left arrow followed by the peer list is printed.
+For
+.Ux
+sockets that went through
+.Xr connect 2
+system call a right arrow followed by the peer is printed.
+Peers are printed in square brackets as [PID FD].
 .It Li ID
 The inp_gencnt if
 .Fl i
diff --git a/usr.bin/sockstat/sockstat.c b/usr.bin/sockstat/sockstat.c
index 143f4f0f7aca..d813b3adfd42 100644
--- a/usr.bin/sockstat/sockstat.c
+++ b/usr.bin/sockstat/sockstat.c
@@ -114,7 +114,14 @@ static int	*ports;
 #define	CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT)))
 
 struct addr {
-	struct sockaddr_storage address;
+	union {
+		struct sockaddr_storage address;
+		struct {	/* unix(4) faddr */
+			kvaddr_t conn;
+			kvaddr_t firstref;
+			kvaddr_t nextref;
+		};
+	};
 	unsigned int encaps_port;
 	int state;
 	struct addr *next;
@@ -159,8 +166,24 @@ RB_GENERATE_STATIC(pcbs_t, sock, pcb_tree, pcb_compare);
 
 static SLIST_HEAD(, sock) nosocks = SLIST_HEAD_INITIALIZER(&nosocks);
 
-static struct xfile *xfiles;
-static int nxfiles;
+struct file {
+	RB_ENTRY(file)	file_tree;
+	kvaddr_t	xf_data;
+	pid_t	xf_pid;
+	uid_t	xf_uid;
+	int	xf_fd;
+};
+
+static RB_HEAD(files_t, file) ftree = RB_INITIALIZER(&ftree);
+static int64_t
+file_compare(const struct file *a, const struct file *b)
+{
+	return ((int64_t)(a->xf_data/2 - b->xf_data/2));
+}
+RB_GENERATE_STATIC(files_t, file, file_tree, file_compare);
+
+static struct file *files;
+static int nfiles;
 
 static cap_channel_t *capnet;
 static cap_channel_t *capnetdb;
@@ -862,8 +885,9 @@ gather_unix(int proto)
 		if (xup->xu_addr.sun_family == AF_UNIX)
 			laddr->address =
 			    *(struct sockaddr_storage *)(void *)&xup->xu_addr;
-		else if (xup->unp_conn != 0)
-			*(kvaddr_t*)&(faddr->address) = xup->unp_conn;
+		faddr->conn = xup->unp_conn;
+		faddr->firstref = xup->xu_firstref;
+		faddr->nextref = xup->xu_nextref;
 		laddr->next = NULL;
 		faddr->next = NULL;
 		sock->laddr = laddr;
@@ -878,6 +902,7 @@ out:
 static void
 getfiles(void)
 {
+	struct xfile *xfiles;
 	size_t len, olen;
 
 	olen = len = sizeof(*xfiles);
@@ -893,7 +918,20 @@ getfiles(void)
 	}
 	if (len > 0)
 		enforce_ksize(xfiles->xf_size, struct xfile);
-	nxfiles = len / sizeof(*xfiles);
+	nfiles = len / sizeof(*xfiles);
+
+	if ((files = malloc(nfiles * sizeof(struct file))) == NULL)
+		err(1, "malloc()");
+
+	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]);
+	}
+
+	free(xfiles);
 }
 
 static int
@@ -1066,10 +1104,8 @@ sctp_path_state(int state)
 static void
 displaysock(struct sock *s, int pos)
 {
-	kvaddr_t p;
 	int first, offset;
 	struct addr *laddr, *faddr;
-	struct sock *s_tmp;
 
 	while (pos < 30)
 		pos += xprintf(" ");
@@ -1106,26 +1142,57 @@ displaysock(struct sock *s, int pos)
 			if ((laddr == NULL) || (faddr == NULL))
 				errx(1, "laddr = %p or faddr = %p is NULL",
 				    (void *)laddr, (void *)faddr);
-			/* server */
-			if (laddr->address.ss_len > 0) {
-				pos += printaddr(&laddr->address);
-				break;
-			}
-			/* client */
-			p = *(kvaddr_t*)&(faddr->address);
-			if (p == 0) {
+			if (laddr->address.ss_len == 0 && faddr->conn == 0) {
 				pos += xprintf("(not connected)");
 				offset += opt_w ? 92 : 44;
 				break;
 			}
-			pos += xprintf("-> ");
-			s_tmp = RB_FIND(pcbs_t, &pcbs,
-			    &(struct sock){ .pcb = p });
-			if (s_tmp == NULL || s_tmp->laddr == NULL ||
-			    s_tmp->laddr->address.ss_len == 0)
-				pos += xprintf("??");
-			else
-				pos += printaddr(&s_tmp->laddr->address);
+			/* Local bind(2) address, if any. */
+			if (laddr->address.ss_len > 0)
+				pos += printaddr(&laddr->address);
+			/* Remote peer we connect(2) to, if any. */
+			if (faddr->conn != 0) {
+				struct sock *p;
+
+				pos += xprintf("%s-> ",
+				    laddr->address.ss_len > 0 ? " " : "");
+				p = RB_FIND(pcbs_t, &pcbs,
+				    &(struct sock){ .pcb = faddr->conn });
+				if (__predict_false(p == NULL)) {
+					/* XXGL: can this happen at all? */
+					pos += xprintf("??");
+				}  else if (p->laddr->address.ss_len == 0) {
+					struct file *f;
+
+					f = RB_FIND(files_t, &ftree,
+					    &(struct file){ .xf_data =
+					    p->socket });
+					pos += xprintf("[%lu %d]",
+					    (u_long)f->xf_pid, f->xf_fd);
+				} else
+					pos += printaddr(&p->laddr->address);
+			}
+			/* Remote peer(s) connect(2)ed to us, if any. */
+			if (faddr->firstref != 0) {
+				struct sock *p;
+				struct file *f;
+				kvaddr_t ref = faddr->firstref;
+				bool fref = true;
+
+				pos += xprintf(" <- ");
+
+				while ((p = RB_FIND(pcbs_t, &pcbs,
+				    &(struct sock){ .pcb = ref })) != 0) {
+					f = RB_FIND(files_t, &ftree,
+					    &(struct file){ .xf_data =
+					    p->socket });
+					pos += xprintf("%s[%lu %d]",
+					    fref ? "" : ",",
+					    (u_long)f->xf_pid, f->xf_fd);
+					ref = p->faddr->nextref;
+					fref = false;
+				}
+			}
 			offset += opt_w ? 92 : 44;
 			break;
 		default:
@@ -1228,7 +1295,7 @@ static void
 display(void)
 {
 	struct passwd *pwd;
-	struct xfile *xf;
+	struct file *xf;
 	struct sock *s;
 	int n, pos;
 
@@ -1253,7 +1320,7 @@ display(void)
 		printf("\n");
 	}
 	cap_setpassent(cappwd, 1);
-	for (xf = xfiles, n = 0; n < nxfiles; ++n, ++xf) {
+	for (xf = files, n = 0; n < nfiles; ++n, ++xf) {
 		if (xf->xf_data == 0)
 			continue;
 		if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid))