From 2c436d4890c7ed7f50321725dee229834114166b Mon Sep 17 00:00:00 2001 From: Gleb Smirnoff Date: Wed, 6 Jul 2022 22:19:08 -0700 Subject: [PATCH] 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 @@ gather_unix(int proto) 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))