diff --git a/man/man8/ss.8 b/man/man8/ss.8 index c80853f9..3b2559ff 100644 --- a/man/man8/ss.8 +++ b/man/man8/ss.8 @@ -286,6 +286,15 @@ Class id set by net_cls cgroup. If class is zero this shows priority set by SO_PRIORITY. .RE .TP +.B \-\-cgroup +Show cgroup information. Below fields may appear: +.RS +.P +.TP +.B cgroup +Cgroup v2 pathname. This pathname is relative to the mount point of the hierarchy. +.RE +.TP .B \-K, \-\-kill Attempts to forcibly close sockets. This option displays sockets that are successfully closed and silently skips sockets that the kernel does not support diff --git a/misc/ss.c b/misc/ss.c index ee840149..2a71317d 100644 --- a/misc/ss.c +++ b/misc/ss.c @@ -36,6 +36,7 @@ #include "namespace.h" #include "SNAPSHOT.h" #include "rt_names.h" +#include "cg_map.h" #include #include @@ -123,6 +124,7 @@ static int follow_events; static int sctp_ino; static int show_tipcinfo; static int show_tos; +static int show_cgroup; int oneline; enum col_id { @@ -798,6 +800,7 @@ struct sockstat { char *name; char *peer_name; __u32 mark; + __u64 cgroup_id; }; struct dctcpstat { @@ -1418,6 +1421,9 @@ static void sock_details_print(struct sockstat *s) if (s->mark) out(" fwmark:0x%x", s->mark); + + if (s->cgroup_id) + out(" cgroup:%s", cg_id_to_path(s->cgroup_id)); } static void sock_addr_print(const char *addr, char *delim, const char *port, @@ -1644,6 +1650,7 @@ struct aafilter { unsigned int iface; __u32 mark; __u32 mask; + __u64 cgroup_id; struct aafilter *next; }; @@ -1771,6 +1778,12 @@ static int run_ssfilter(struct ssfilter *f, struct sockstat *s) struct aafilter *a = (void *)f->pred; return (s->mark & a->mask) == a->mark; + } + case SSF_CGROUPCOND: + { + struct aafilter *a = (void *)f->pred; + + return s->cgroup_id == a->cgroup_id; } /* Yup. It is recursion. Sorry. */ case SSF_AND: @@ -1962,6 +1975,23 @@ static int ssfilter_bytecompile(struct ssfilter *f, char **bytecode) { a->mark, a->mask}, }; + return inslen; + } + case SSF_CGROUPCOND: + { + struct aafilter *a = (void *)f->pred; + struct instr { + struct inet_diag_bc_op op; + __u64 cgroup_id; + } __attribute__((packed)); + int inslen = sizeof(struct instr); + + if (!(*bytecode = malloc(inslen))) abort(); + ((struct instr *)*bytecode)[0] = (struct instr) { + { INET_DIAG_BC_CGROUP_COND, inslen, inslen + 4 }, + a->cgroup_id, + }; + return inslen; } default: @@ -2301,6 +2331,22 @@ void *parse_markmask(const char *markmask) return res; } +void *parse_cgroupcond(const char *path) +{ + struct aafilter *res; + __u64 id; + + id = get_cgroup2_id(path); + if (!id) + return NULL; + + res = malloc(sizeof(*res)); + if (res) + res->cgroup_id = id; + + return res; +} + static void proc_ctx_print(struct sockstat *s) { char *buf; @@ -3157,6 +3203,9 @@ static void parse_diag_msg(struct nlmsghdr *nlh, struct sockstat *s) s->mark = 0; if (tb[INET_DIAG_MARK]) s->mark = rta_getattr_u32(tb[INET_DIAG_MARK]); + s->cgroup_id = 0; + if (tb[INET_DIAG_CGROUP_ID]) + s->cgroup_id = rta_getattr_u64(tb[INET_DIAG_CGROUP_ID]); if (tb[INET_DIAG_PROTOCOL]) s->raw_prot = rta_getattr_u8(tb[INET_DIAG_PROTOCOL]); else @@ -3224,6 +3273,11 @@ static int inet_show_sock(struct nlmsghdr *nlh, out(" class_id:%#x", rta_getattr_u32(tb[INET_DIAG_CLASS_ID])); } + if (show_cgroup) { + if (tb[INET_DIAG_CGROUP_ID]) + out(" cgroup:%s", cg_id_to_path(rta_getattr_u64(tb[INET_DIAG_CGROUP_ID]))); + } + if (show_mem || (show_tcpinfo && s->type != IPPROTO_UDP)) { if (!oneline) out("\n\t"); @@ -5049,6 +5103,7 @@ static void _usage(FILE *dest) " --tipcinfo show internal tipc socket information\n" " -s, --summary show socket usage summary\n" " --tos show tos and priority information\n" +" --cgroup show cgroup information\n" " -b, --bpf show bpf filter socket information\n" " -E, --events continually display sockets as they are destroyed\n" " -Z, --context display process SELinux security contexts\n" @@ -5159,6 +5214,8 @@ static int scan_state(const char *state) /* Values of 'x' are already used so a non-character is used */ #define OPT_XDPSOCK 260 +#define OPT_CGROUP 261 + static const struct option long_opts[] = { { "numeric", 0, 0, 'n' }, { "resolve", 0, 0, 'r' }, @@ -5195,6 +5252,7 @@ static const struct option long_opts[] = { { "net", 1, 0, 'N' }, { "tipcinfo", 0, 0, OPT_TIPCINFO}, { "tos", 0, 0, OPT_TOS }, + { "cgroup", 0, 0, OPT_CGROUP }, { "kill", 0, 0, 'K' }, { "no-header", 0, 0, 'H' }, { "xdp", 0, 0, OPT_XDPSOCK}, @@ -5382,6 +5440,9 @@ int main(int argc, char *argv[]) case OPT_TOS: show_tos = 1; break; + case OPT_CGROUP: + show_cgroup = 1; + break; case 'K': current_filter.kill = 1; break; diff --git a/misc/ssfilter.h b/misc/ssfilter.h index f5b0bc8a..d85c084e 100644 --- a/misc/ssfilter.h +++ b/misc/ssfilter.h @@ -11,6 +11,7 @@ #define SSF_S_AUTO 9 #define SSF_DEVCOND 10 #define SSF_MARKMASK 11 +#define SSF_CGROUPCOND 12 #include @@ -25,3 +26,4 @@ int ssfilter_parse(struct ssfilter **f, int argc, char **argv, FILE *fp); void *parse_hostcond(char *addr, bool is_port); void *parse_devcond(char *name); void *parse_markmask(const char *markmask); +void *parse_cgroupcond(const char *path); diff --git a/misc/ssfilter.y b/misc/ssfilter.y index a901ae75..b4175795 100644 --- a/misc/ssfilter.y +++ b/misc/ssfilter.y @@ -36,7 +36,7 @@ static void yyerror(char *s) %} -%token HOSTCOND DCOND SCOND DPORT SPORT LEQ GEQ NEQ AUTOBOUND DEVCOND DEVNAME MARKMASK FWMARK +%token HOSTCOND DCOND SCOND DPORT SPORT LEQ GEQ NEQ AUTOBOUND DEVCOND DEVNAME MARKMASK FWMARK CGROUPCOND CGROUPPATH %left '|' %left '&' %nonassoc '!' @@ -156,6 +156,14 @@ expr: '(' exprlist ')' { $$ = alloc_node(SSF_NOT, alloc_node(SSF_MARKMASK, $3)); } + | CGROUPPATH eq CGROUPCOND + { + $$ = alloc_node(SSF_CGROUPCOND, $3); + } + | CGROUPPATH NEQ CGROUPCOND + { + $$ = alloc_node(SSF_NOT, alloc_node(SSF_CGROUPCOND, $3)); + } | AUTOBOUND { $$ = alloc_node(SSF_S_AUTO, NULL); @@ -276,6 +284,10 @@ int yylex(void) tok_type = FWMARK; return FWMARK; } + if (strcmp(curtok, "cgroup") == 0) { + tok_type = CGROUPPATH; + return CGROUPPATH; + } if (strcmp(curtok, ">=") == 0 || strcmp(curtok, "ge") == 0 || strcmp(curtok, "geq") == 0) @@ -318,6 +330,14 @@ int yylex(void) } return MARKMASK; } + if (tok_type == CGROUPPATH) { + yylval = (void*)parse_cgroupcond(curtok); + if (yylval == NULL) { + fprintf(stderr, "Cannot parse cgroup %s.\n", curtok); + exit(1); + } + return CGROUPCOND; + } yylval = (void*)parse_hostcond(curtok, tok_type == SPORT || tok_type == DPORT); if (yylval == NULL) { fprintf(stderr, "Cannot parse dst/src address.\n");