From b031cd2102d9bc2b6ce20a880068022fac9e2d87 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Tue, 15 Dec 2020 14:53:00 +0100 Subject: conntrack: pretty-print the portid DESTROY events already include the portid. Add some /proc glue to lookup the portid. Problem is that there is no direct mapping to a name. Lookup steps are: 1. Obtain the portid inode from /proc/net/netlink. If we can't even find that, no luck. 2. assume portid == pid and search /proc/portid/fd/ for a socket with matching inode. This is modeled on iproute2 ss tool. If /proc/portid/fd/ comes up empty, entire process space (/proc/*/fd) is searched for a matching inode. As this is quite some work, cache the last portid result (including 'not found', so that 'conntrack -F' generating 10000k events will do this lookup only once. The lookup won't work in case the deleting/flushing program has already exited; in that case [USERSPACE] tag and portid are still included. Example: $ conntrack -E -o userspace [DESTROY] tcp 6 src=192... dst=192... sport=4404 dport=22 ... [USERSPACE] portid=5146 progname=conntrack Signed-off-by: Florian Westphal --- src/conntrack.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 7 deletions(-) diff --git a/src/conntrack.c b/src/conntrack.c index d05a599..cc58d3f 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -55,6 +55,7 @@ #include #include #include +#include #ifdef HAVE_ARPA_INET_H #include #endif @@ -1671,6 +1672,169 @@ exp_event_sighandler(int s) exit(0); } +static char *pid2name(pid_t pid) +{ + char procname[256], *prog; + FILE *fp; + int ret; + + ret = snprintf(procname, sizeof(procname), "/proc/%lu/stat", (unsigned long)pid); + if (ret < 0 || ret > (int)sizeof(procname)) + return NULL; + + fp = fopen(procname, "r"); + if (!fp) + return NULL; + + ret = fscanf(fp, "%*u (%m[^)]", &prog); + + fclose(fp); + + if (ret == 1) + return prog; + + return NULL; +} + +static char *portid2name(pid_t pid, uint32_t portid, unsigned long inode) +{ + const struct dirent *ent; + char procname[256]; + DIR *dir; + int ret; + + ret = snprintf(procname, sizeof(procname), "/proc/%lu/fd/", (unsigned long)pid); + if (ret < 0 || ret >= (int)sizeof(procname)) + return NULL; + + dir = opendir(procname); + if (!dir) + return NULL; + + for (;;) { + unsigned long ino; + char tmp[128]; + ssize_t rl; + + ent = readdir(dir); + if (!ent) + break; + + if (ent->d_type != DT_LNK) + continue; + + ret = snprintf(procname, sizeof(procname), "/proc/%d/fd/%s", + pid, ent->d_name); + if (ret < 0 || ret >= (int)sizeof(procname)) + continue; + + rl = readlink(procname, tmp, sizeof(tmp)); + if (rl <= 0 || rl > (ssize_t)sizeof(tmp)) + continue; + + tmp[rl] = 0; + + ret = sscanf(tmp, "socket:[%lu]", &ino); + if (ret == 1 && ino == inode) { + closedir(dir); + return pid2name(pid); + } + } + + closedir(dir); + return NULL; +} + +static char *name_by_portid(uint32_t portid, unsigned long inode) +{ + const struct dirent *ent; + char *prog; + DIR *dir; + + /* Many netlink users use their process ID to allocate the first port id. */ + prog = portid2name(portid, portid, inode); + if (prog) + return prog; + + /* no luck, search harder. */ + dir = opendir("/proc"); + if (!dir) + return NULL; + + for (;;) { + unsigned long pid; + char *end; + + ent = readdir(dir); + if (!ent) + break; + + if (ent->d_type != DT_DIR) + continue; + + pid = strtoul(ent->d_name, &end, 10); + if (pid <= 1 || *end) + continue; + + if (pid == portid) /* already tried */ + continue; + + prog = portid2name(pid, portid, inode); + if (prog) + break; + } + + closedir(dir); + return prog; +} + +static char *get_progname(uint32_t portid) +{ + FILE *fp = fopen("/proc/net/netlink", "r"); + uint32_t portid_check; + unsigned long inode; + int ret, prot; + + if (!fp) + return NULL; + + for (;;) { + char line[256]; + + if (!fgets(line, sizeof(line), fp)) + break; + + ret = sscanf(line, "%*x %d %u %*x %*d %*d %*x %*d %*u %lu\n", + &prot, &portid_check, &inode); + + if (ret == EOF) + break; + + if (ret == 3 && portid_check == portid && prot == NETLINK_NETFILTER) { + static uint32_t last_portid; + static uint32_t last_inode; + static char *last_program; + char *prog; + + fclose(fp); + + if (last_portid == portid && last_inode == inode) + return last_program; + + prog = name_by_portid(portid, inode); + + free(last_program); + last_program = prog; + last_portid = portid; + last_inode = inode; + return prog; + } + } + + fclose(fp); + return NULL; +} + static int event_cb(const struct nlmsghdr *nlh, void *data) { struct nfgenmsg *nfh = mnl_nlmsg_get_payload(nlh); @@ -1679,7 +1843,6 @@ static int event_cb(const struct nlmsghdr *nlh, void *data) enum nf_conntrack_msg_type type; unsigned int op_flags = 0; struct nf_conntrack *ct; - bool userspace = false; char buf[1024]; switch(nlh->nlmsg_type & 0xff) { @@ -1740,14 +1903,16 @@ static int event_cb(const struct nlmsghdr *nlh, void *data) nfct_snprintf_labels(buf, sizeof(buf), ct, type, op_type, op_flags, labelmap); done: - if (output_mask & _O_US) { - if (nlh->nlmsg_pid) - userspace = true; + if ((output_mask & _O_US) && nlh->nlmsg_pid) { + char *prog = get_progname(nlh->nlmsg_pid); + + if (prog) + printf("%s [USERSPACE] portid=%u progname=%s\n", buf, nlh->nlmsg_pid, prog); else - userspace = false; + printf("%s [USERSPACE] portid=%u\n", buf, nlh->nlmsg_pid); + } else { + puts(buf); } - - printf("%s%s\n", buf, userspace ? " [USERSPACE]" : ""); fflush(stdout); counter++; -- cgit v1.2.3