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
This commit is contained in:
Gleb Smirnoff 2022-07-06 22:19:08 -07:00
parent c5bdcd1f10
commit 2c436d4890
2 changed files with 110 additions and 34 deletions

View File

@ -27,7 +27,7 @@
.\" .\"
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd February 2, 2022 .Dd June 6, 2022
.Dt SOCKSTAT 1 .Dt SOCKSTAT 1
.Os .Os
.Sh NAME .Sh NAME
@ -151,18 +151,27 @@ sockets.
For Internet sockets, this is the address the local end of the socket For Internet sockets, this is the address the local end of the socket
is bound to (see is bound to (see
.Xr getsockname 2 ) . .Xr getsockname 2 ) .
.Pp
For bound For bound
.Ux .Ux
sockets, it is the socket's filename. sockets, socket's filename is printed.
For other For not bound
.Ux .Ux
sockets, it is a right arrow followed by the endpoint's filename, or sockets, the field is empty.
.Dq Li ??
if the endpoint could not be determined.
.It Li FOREIGN ADDRESS .It Li FOREIGN ADDRESS
(Internet sockets only) For Internet sockets, this is the address the foreign end of the socket
The address the foreign end of the socket is bound to (see is bound to (see
.Xr getpeername 2 ) . .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 .It Li ID
The inp_gencnt if The inp_gencnt if
.Fl i .Fl i

View File

@ -114,7 +114,14 @@ static int *ports;
#define CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT))) #define CHK_PORT(p) (ports[p / INT_BIT] & (1 << (p % INT_BIT)))
struct addr { struct addr {
union {
struct sockaddr_storage address; struct sockaddr_storage address;
struct { /* unix(4) faddr */
kvaddr_t conn;
kvaddr_t firstref;
kvaddr_t nextref;
};
};
unsigned int encaps_port; unsigned int encaps_port;
int state; int state;
struct addr *next; 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 SLIST_HEAD(, sock) nosocks = SLIST_HEAD_INITIALIZER(&nosocks);
static struct xfile *xfiles; struct file {
static int nxfiles; 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 *capnet;
static cap_channel_t *capnetdb; static cap_channel_t *capnetdb;
@ -862,8 +885,9 @@ gather_unix(int proto)
if (xup->xu_addr.sun_family == AF_UNIX) if (xup->xu_addr.sun_family == AF_UNIX)
laddr->address = laddr->address =
*(struct sockaddr_storage *)(void *)&xup->xu_addr; *(struct sockaddr_storage *)(void *)&xup->xu_addr;
else if (xup->unp_conn != 0) faddr->conn = xup->unp_conn;
*(kvaddr_t*)&(faddr->address) = xup->unp_conn; faddr->firstref = xup->xu_firstref;
faddr->nextref = xup->xu_nextref;
laddr->next = NULL; laddr->next = NULL;
faddr->next = NULL; faddr->next = NULL;
sock->laddr = laddr; sock->laddr = laddr;
@ -878,6 +902,7 @@ gather_unix(int proto)
static void static void
getfiles(void) getfiles(void)
{ {
struct xfile *xfiles;
size_t len, olen; size_t len, olen;
olen = len = sizeof(*xfiles); olen = len = sizeof(*xfiles);
@ -893,7 +918,20 @@ getfiles(void)
} }
if (len > 0) if (len > 0)
enforce_ksize(xfiles->xf_size, struct xfile); 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 static int
@ -1066,10 +1104,8 @@ sctp_path_state(int state)
static void static void
displaysock(struct sock *s, int pos) displaysock(struct sock *s, int pos)
{ {
kvaddr_t p;
int first, offset; int first, offset;
struct addr *laddr, *faddr; struct addr *laddr, *faddr;
struct sock *s_tmp;
while (pos < 30) while (pos < 30)
pos += xprintf(" "); pos += xprintf(" ");
@ -1106,26 +1142,57 @@ displaysock(struct sock *s, int pos)
if ((laddr == NULL) || (faddr == NULL)) if ((laddr == NULL) || (faddr == NULL))
errx(1, "laddr = %p or faddr = %p is NULL", errx(1, "laddr = %p or faddr = %p is NULL",
(void *)laddr, (void *)faddr); (void *)laddr, (void *)faddr);
/* server */ if (laddr->address.ss_len == 0 && faddr->conn == 0) {
if (laddr->address.ss_len > 0) {
pos += printaddr(&laddr->address);
break;
}
/* client */
p = *(kvaddr_t*)&(faddr->address);
if (p == 0) {
pos += xprintf("(not connected)"); pos += xprintf("(not connected)");
offset += opt_w ? 92 : 44; offset += opt_w ? 92 : 44;
break; break;
} }
pos += xprintf("-> "); /* Local bind(2) address, if any. */
s_tmp = RB_FIND(pcbs_t, &pcbs, if (laddr->address.ss_len > 0)
&(struct sock){ .pcb = p }); pos += printaddr(&laddr->address);
if (s_tmp == NULL || s_tmp->laddr == NULL || /* Remote peer we connect(2) to, if any. */
s_tmp->laddr->address.ss_len == 0) 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("??"); pos += xprintf("??");
else } else if (p->laddr->address.ss_len == 0) {
pos += printaddr(&s_tmp->laddr->address); 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; offset += opt_w ? 92 : 44;
break; break;
default: default:
@ -1228,7 +1295,7 @@ static void
display(void) display(void)
{ {
struct passwd *pwd; struct passwd *pwd;
struct xfile *xf; struct file *xf;
struct sock *s; struct sock *s;
int n, pos; int n, pos;
@ -1253,7 +1320,7 @@ display(void)
printf("\n"); printf("\n");
} }
cap_setpassent(cappwd, 1); 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) if (xf->xf_data == 0)
continue; continue;
if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid)) if (opt_j >= 0 && opt_j != getprocjid(xf->xf_pid))