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
This commit is contained in:
Juraj Lutter 2025-10-29 21:48:21 +01:00
parent a38c2f99f8
commit 2deeed061b
2 changed files with 136 additions and 41 deletions

View file

@ -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)

View file

@ -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