diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
commit | cfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch) | |
tree | d0baf160cbee3195249d095f85e52d20c21acf02 /misc-utils/lsfd-sock-xinfo.c | |
parent | Initial commit. (diff) | |
download | util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.tar.xz util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.zip |
Adding upstream version 2.39.3.upstream/2.39.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'misc-utils/lsfd-sock-xinfo.c')
-rw-r--r-- | misc-utils/lsfd-sock-xinfo.c | 2025 |
1 files changed, 2025 insertions, 0 deletions
diff --git a/misc-utils/lsfd-sock-xinfo.c b/misc-utils/lsfd-sock-xinfo.c new file mode 100644 index 0000000..61b8607 --- /dev/null +++ b/misc-utils/lsfd-sock-xinfo.c @@ -0,0 +1,2025 @@ +/* + * lsfd-sock-xinfo.c - read various information from files under /proc/net/ + * + * Copyright (C) 2022 Red Hat, Inc. All rights reserved. + * Written by Masatake YAMATO <yamato@redhat.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it would be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <arpa/inet.h> /* inet_ntop */ +#include <netinet/in.h> /* in6_addr */ +#include <fcntl.h> /* open(2) */ +#include <ifaddrs.h> /* getifaddrs */ +#include <inttypes.h> /* SCNu16 */ +#include <net/if.h> /* if_nametoindex */ +#include <linux/if_ether.h> /* ETH_P_* */ +#include <linux/net.h> /* SS_* */ +#include <linux/netlink.h> /* NETLINK_* */ +#include <linux/un.h> /* UNIX_PATH_MAX */ +#include <sched.h> /* for setns(2) */ +#include <search.h> +#include <stdint.h> +#include <string.h> +#include <sys/socket.h> /* SOCK_* */ + +#include "xalloc.h" +#include "nls.h" +#include "libsmartcols.h" +#include "sysfs.h" +#include "bitops.h" + +#include "lsfd.h" +#include "lsfd-sock.h" + +static void load_xinfo_from_proc_icmp(ino_t netns_inode); +static void load_xinfo_from_proc_icmp6(ino_t netns_inode); +static void load_xinfo_from_proc_unix(ino_t netns_inode); +static void load_xinfo_from_proc_raw(ino_t netns_inode); +static void load_xinfo_from_proc_tcp(ino_t netns_inode); +static void load_xinfo_from_proc_udp(ino_t netns_inode); +static void load_xinfo_from_proc_udplite(ino_t netns_inode); +static void load_xinfo_from_proc_tcp6(ino_t netns_inode); +static void load_xinfo_from_proc_udp6(ino_t netns_inode); +static void load_xinfo_from_proc_udplite6(ino_t netns_inode); +static void load_xinfo_from_proc_raw6(ino_t netns_inode); +static void load_xinfo_from_proc_netlink(ino_t netns_inode); +static void load_xinfo_from_proc_packet(ino_t netns_inode); + +static int self_netns_fd = -1; +static struct stat self_netns_sb; + +static void *xinfo_tree; /* for tsearch/tfind */ +static void *netns_tree; + +struct iface { + unsigned int index; + char name[IF_NAMESIZE]; +}; + +static const char *get_iface_name(ino_t netns, unsigned int iface_index); + +struct netns { + ino_t inode; + struct iface *ifaces; +}; + +static int netns_compare(const void *a, const void *b) +{ + const struct netns *netns_a = a; + const struct netns *netns_b = b; + + return netns_a->inode - netns_b->inode; +} + +static void netns_free(void *netns) +{ + struct netns *nsobj = netns; + + free(nsobj->ifaces); + free(netns); +} + +/* + * iface index -> iface name mappings + */ +static void load_ifaces_from_getifaddrs(struct netns *nsobj) +{ + struct ifaddrs *ifa_list; + struct ifaddrs *ifa; + size_t i, count = 0; + + if (getifaddrs(&ifa_list) < 0) + return; + + for (ifa = ifa_list; ifa != NULL; ifa = ifa->ifa_next) + count++; + + nsobj->ifaces = xcalloc(count + 1, sizeof(*nsobj->ifaces)); + + for (ifa = ifa_list, i = 0; ifa != NULL; ifa = ifa->ifa_next, i++) { + unsigned int if_index = if_nametoindex(ifa->ifa_name); + + nsobj->ifaces[i].index = if_index; + strncpy(nsobj->ifaces[i].name, ifa->ifa_name, IF_NAMESIZE - 1); + /* The slot for the last byte is already filled by calloc. */ + } + /* nsobj->ifaces[count] is the sentinel value. */ + + freeifaddrs(ifa_list); + + return; +} + +static const char *get_iface_name(ino_t netns, unsigned int iface_index) +{ + struct netns key = { .inode = netns }; + struct netns **nsobj = tfind(&key, &netns_tree, netns_compare); + if (!nsobj) + return NULL; + + for (size_t i = 0; (*nsobj)->ifaces[i].index; i++) { + if ((*nsobj)->ifaces[i].index == iface_index) + return (*nsobj)->ifaces[i].name; + } + + return NULL; +} + +static bool is_sock_xinfo_loaded(ino_t netns) +{ + struct netns key = { .inode = netns }; + return tfind(&key, &netns_tree, netns_compare)? true: false; +} + +static struct netns *mark_sock_xinfo_loaded(ino_t ino) +{ + struct netns *netns = xcalloc(1, sizeof(*netns)); + ino_t **tmp; + + netns->inode = ino; + tmp = tsearch(netns, &netns_tree, netns_compare); + if (tmp == NULL) + errx(EXIT_FAILURE, _("failed to allocate memory")); + return *(struct netns **)tmp; +} + +static void load_sock_xinfo_no_nsswitch(struct netns *nsobj) +{ + ino_t netns = nsobj? nsobj->inode: 0; + + load_xinfo_from_proc_unix(netns); + load_xinfo_from_proc_tcp(netns); + load_xinfo_from_proc_udp(netns); + load_xinfo_from_proc_udplite(netns); + load_xinfo_from_proc_raw(netns); + load_xinfo_from_proc_tcp6(netns); + load_xinfo_from_proc_udp6(netns); + load_xinfo_from_proc_udplite6(netns); + load_xinfo_from_proc_raw6(netns); + load_xinfo_from_proc_icmp(netns); + load_xinfo_from_proc_icmp6(netns); + load_xinfo_from_proc_netlink(netns); + load_xinfo_from_proc_packet(netns); + + if (nsobj) + load_ifaces_from_getifaddrs(nsobj); +} + +static void load_sock_xinfo_with_fd(int fd, struct netns *nsobj) +{ + if (setns(fd, CLONE_NEWNET) == 0) { + load_sock_xinfo_no_nsswitch(nsobj); + setns(self_netns_fd, CLONE_NEWNET); + } +} + +void load_sock_xinfo(struct path_cxt *pc, const char *name, ino_t netns) +{ + if (self_netns_fd == -1) + return; + + if (!is_sock_xinfo_loaded(netns)) { + int fd; + struct netns *nsobj = mark_sock_xinfo_loaded(netns); + fd = ul_path_open(pc, O_RDONLY, name); + if (fd < 0) + return; + + load_sock_xinfo_with_fd(fd, nsobj); + close(fd); + } +} + +void initialize_sock_xinfos(void) +{ + struct path_cxt *pc; + DIR *dir; + struct dirent *d; + + self_netns_fd = open("/proc/self/ns/net", O_RDONLY); + + if (self_netns_fd < 0) + load_sock_xinfo_no_nsswitch(NULL); + else { + if (fstat(self_netns_fd, &self_netns_sb) == 0) { + struct netns *nsobj = mark_sock_xinfo_loaded(self_netns_sb.st_ino); + load_sock_xinfo_no_nsswitch(nsobj); + } + } + + /* Load /proc/net/{unix,...} of the network namespace + * specified with netns files under /var/run/netns/. + * + * `ip netns' command pins a network namespace on + * /var/run/netns. + */ + pc = ul_new_path("/var/run/netns"); + if (!pc) + err(EXIT_FAILURE, _("failed to alloc path context for /var/run/netns")); + dir = ul_path_opendir(pc, NULL); + if (dir == NULL) { + ul_unref_path(pc); + return; + } + while ((d = readdir(dir))) { + struct stat sb; + int fd; + struct netns *nsobj; + if (ul_path_stat(pc, &sb, 0, d->d_name) < 0) + continue; + if (is_sock_xinfo_loaded(sb.st_ino)) + continue; + nsobj = mark_sock_xinfo_loaded(sb.st_ino); + fd = ul_path_open(pc, O_RDONLY, d->d_name); + if (fd < 0) + continue; + load_sock_xinfo_with_fd(fd, nsobj); + close(fd); + } + closedir(dir); + ul_unref_path(pc); +} + +static void free_sock_xinfo(void *node) +{ + struct sock_xinfo *xinfo = node; + if (xinfo->class->free) + xinfo->class->free(xinfo); + free(node); +} + +void finalize_sock_xinfos(void) +{ + if (self_netns_fd != -1) + close(self_netns_fd); + tdestroy(netns_tree, netns_free); + tdestroy(xinfo_tree, free_sock_xinfo); +} + +static int xinfo_compare(const void *a, const void *b) +{ + return ((struct sock_xinfo *)a)->inode - ((struct sock_xinfo *)b)->inode; +} + +static void add_sock_info(struct sock_xinfo *xinfo) +{ + struct sock_xinfo **tmp = tsearch(xinfo, &xinfo_tree, xinfo_compare); + + if (tmp == NULL) + errx(EXIT_FAILURE, _("failed to allocate memory")); +} + +struct sock_xinfo *get_sock_xinfo(ino_t netns_inode) +{ + struct sock_xinfo key = { .inode = netns_inode }; + struct sock_xinfo **xinfo = tfind(&key, &xinfo_tree, xinfo_compare); + + if (xinfo) + return *xinfo; + return NULL; +} + +bool is_nsfs_dev(dev_t dev) +{ + return dev == self_netns_sb.st_dev; +} + +static const char *sock_decode_type(uint16_t type) +{ + switch (type) { + case SOCK_STREAM: + return "stream"; + case SOCK_DGRAM: + return "dgram"; + case SOCK_RAW: + return "raw"; + case SOCK_RDM: + return "rdm"; + case SOCK_SEQPACKET: + return "seqpacket"; + case SOCK_DCCP: + return "dccp"; + case SOCK_PACKET: + return "packet"; + default: + return "unknown"; + } +} + +/* + * Protocol specific code + */ + +/* + * UNIX + */ +struct unix_xinfo { + struct sock_xinfo sock; + int acceptcon; /* flags */ + uint16_t type; + uint8_t st; + char path[ + UNIX_PATH_MAX + + 1 /* for @ */ + + 1 /* \0? */ + ]; +}; + +static const char *unix_decode_state(uint8_t st) +{ + switch (st) { + case SS_FREE: + return "free"; + case SS_UNCONNECTED: + return "unconnected"; + case SS_CONNECTING: + return "connecting"; + case SS_CONNECTED: + return "connected"; + case SS_DISCONNECTING: + return "disconnecting"; + default: + return "unknown"; + } +} + +static char *unix_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock) +{ + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + const char *state = unix_decode_state(ux->st); + char *str = NULL; + + if (sock->protoname && (strcmp(sock->protoname, "UNIX-STREAM") == 0)) + xasprintf(&str, "state=%s%s%s", + (ux->acceptcon)? "listen": state, + *(ux->path)? " path=": "", + *(ux->path)? ux->path: ""); + else + xasprintf(&str, "state=%s%s%s type=%s", + (ux->acceptcon)? "listen": state, + *(ux->path)? " path=": "", + *(ux->path)? ux->path: "", + sock_decode_type(ux->type)); + return str; +} + +static char *unix_get_type(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + const char *str; + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + + str = sock_decode_type(ux->type); + return xstrdup(str); +} + +static char *unix_get_state(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + const char *str; + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + + if (ux->acceptcon) + return xstrdup("listen"); + + str = unix_decode_state(ux->st); + return xstrdup(str); +} + +static bool unix_get_listening(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + + return ux->acceptcon; +} + +static bool unix_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct unix_xinfo *ux = (struct unix_xinfo *)sock_xinfo; + + switch (column_id) { + case COL_UNIX_PATH: + if (*ux->path) { + *str = xstrdup(ux->path); + return true; + } + break; + } + + return false; +} + +static const struct sock_xinfo_class unix_xinfo_class = { + .get_name = unix_get_name, + .get_type = unix_get_type, + .get_state = unix_get_state, + .get_listening = unix_get_listening, + .fill_column = unix_fill_column, + .free = NULL, +}; + +/* UNIX_LINE_LEN need at least 54 + 21 + UNIX_PATH_MAX + 1. + * + * An actual number must be used in this definition + * since UNIX_LINE_LEN is specified as an argument for + * stringify_value(). + */ +#define UNIX_LINE_LEN 256 +static void load_xinfo_from_proc_unix(ino_t netns_inode) +{ + char line[UNIX_LINE_LEN]; + FILE *unix_fp; + + unix_fp = fopen("/proc/net/unix", "r"); + if (!unix_fp) + return; + + if (fgets(line, sizeof(line), unix_fp) == NULL) + goto out; + if (!(line[0] == 'N' && line[1] == 'u' && line[2] == 'm')) + /* Unexpected line */ + goto out; + + while (fgets(line, sizeof(line), unix_fp)) { + uint64_t flags; + uint32_t type; + unsigned int st; + unsigned long inode; + struct unix_xinfo *ux; + char path[UNIX_LINE_LEN + 1] = { 0 }; + + + if (sscanf(line, "%*x: %*x %*x %" SCNx64 " %x %x %lu %" + stringify_value(UNIX_LINE_LEN) "[^\n]", + &flags, &type, &st, &inode, path) < 4) + continue; + + if (inode == 0) + continue; + + ux = xcalloc(1, sizeof(*ux)); + ux->sock.class = &unix_xinfo_class; + ux->sock.inode = (ino_t)inode; + ux->sock.netns_inode = netns_inode; + + ux->acceptcon = !!flags; + ux->type = type; + ux->st = st; + xstrncpy(ux->path, path, sizeof(ux->path)); + + add_sock_info(&ux->sock); + } + + out: + fclose(unix_fp); +} + +/* + * AF_INET + */ +struct inet_xinfo { + struct sock_xinfo sock; + struct in_addr local_addr; + struct in_addr remote_addr; +}; + +static uint32_t kernel32_to_cpu(enum sysfs_byteorder byteorder, uint32_t v) +{ + if (byteorder == SYSFS_BYTEORDER_LITTLE) + return le32_to_cpu(v); + else + return be32_to_cpu(v); +} + +/* + * AF_INET6 + */ +struct inet6_xinfo { + struct sock_xinfo sock; + struct in6_addr local_addr; + struct in6_addr remote_addr; +}; + +/* + * L4 abstract-layer for protocols stacked on IP and IP6. + */ +enum l4_state { + /* + * Taken from linux/include/net/tcp_states.h. + * (GPL-2.0-or-later) + * + * UDP and RAW sockets also uses the contents in Linux. + */ + TCP_ESTABLISHED = 1, + TCP_SYN_SENT, + TCP_SYN_RECV, + TCP_FIN_WAIT1, + TCP_FIN_WAIT2, + TCP_TIME_WAIT, + TCP_CLOSE, + TCP_CLOSE_WAIT, + TCP_LAST_ACK, + TCP_LISTEN, + TCP_CLOSING, + TCP_NEW_SYN_RECV, + + TCP_MAX_STATES /* Leave at the end! */ +}; + +static const char *l4_decode_state(enum l4_state st) +{ + const char * table [] = { + [TCP_ESTABLISHED] = "established", + [TCP_SYN_SENT] = "syn-sent", + [TCP_SYN_RECV] = "syn-recv", + [TCP_FIN_WAIT1] = "fin-wait1", + [TCP_FIN_WAIT2] = "fin-wait2", + [TCP_TIME_WAIT] = "time-wait", + [TCP_CLOSE] = "close", + [TCP_CLOSE_WAIT] = "close-wait", + [TCP_LAST_ACK] = "last-ack", + [TCP_LISTEN] = "listen", + [TCP_CLOSING] = "closing", + [TCP_NEW_SYN_RECV] = "new-syn-recv", + }; + + if (st < TCP_MAX_STATES) + return table[st]; + return "unknown"; +} + +struct l4_xinfo { + union { + struct inet_xinfo inet; + struct inet6_xinfo inet6; + }; + enum l4_state st; +}; + +enum l4_side { L4_LOCAL, L4_REMOTE }; +enum l3_decorator { L3_DECO_START, L3_DECO_END }; + +struct l4_xinfo_class { + struct sock_xinfo_class sock; + struct sock_xinfo *(*scan_line)(const struct sock_xinfo_class *, + char *, + ino_t, + enum sysfs_byteorder); + void * (*get_addr)(struct l4_xinfo *, enum l4_side); + bool (*is_any_addr)(void *); + int family; + const char *l3_decorator[2]; +}; + +#define l3_fill_column_handler(L3, SOCK_XINFO, COLUMN_ID, STR) __extension__ \ + ({ \ + struct l4_xinfo_class *class = (struct l4_xinfo_class *)SOCK_XINFO->class; \ + struct l4_xinfo *l4 = (struct l4_xinfo *)SOCK_XINFO; \ + void *n = NULL; \ + char s[BUFSIZ]; \ + bool r = false; \ + \ + switch (COLUMN_ID) { \ + case COL_##L3##_LADDR: \ + n = class->get_addr(l4, L4_LOCAL); \ + break; \ + case COL_##L3##_RADDR: \ + n = class->get_addr(l4, L4_REMOTE); \ + break; \ + default: \ + break; \ + } \ + \ + if (n && inet_ntop(class->family, n, s, sizeof(s))) { \ + *STR = xstrdup(s); \ + r = true; \ + } \ + r; \ + }) + +/* + * TCP + */ +struct tcp_xinfo { + struct l4_xinfo l4; + uint16_t local_port; + uint16_t remote_port; +}; + +static char *tcp_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + char *str = NULL; + struct tcp_xinfo *tcp = ((struct tcp_xinfo *)sock_xinfo); + struct l4_xinfo *l4 = &tcp->l4; + const char *st_str = l4_decode_state(l4->st); + struct l4_xinfo_class *class = (struct l4_xinfo_class *)sock_xinfo->class; + void *laddr = class->get_addr(l4, L4_LOCAL); + void *raddr = class->get_addr(l4, L4_REMOTE); + char local_s[BUFSIZ]; + char remote_s[BUFSIZ]; + const char *start = class->l3_decorator[L3_DECO_START]; + const char *end = class->l3_decorator[L3_DECO_END]; + + if (!inet_ntop(class->family, laddr, local_s, sizeof(local_s))) + xasprintf(&str, "state=%s", st_str); + else if (l4->st == TCP_LISTEN + || !inet_ntop(class->family, raddr, remote_s, sizeof(remote_s))) + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port); + else + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16" raddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port, + start, remote_s, end, tcp->remote_port); + return str; +} + +static char *tcp_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("stream"); +} + +static char *tcp_get_state(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup(l4_decode_state(((struct l4_xinfo *)sock_xinfo)->st)); +} + +static bool tcp_get_listening(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return ((struct l4_xinfo *)sock_xinfo)->st == TCP_LISTEN; +} + +#define l4_fill_column_handler(L4, SOCK_XINFO, COLUMN_ID, STR) __extension__ \ + ({ \ + struct l4_xinfo_class *class = (struct l4_xinfo_class *)SOCK_XINFO->class; \ + struct tcp_xinfo *tcp = (struct tcp_xinfo *)SOCK_XINFO; \ + struct l4_xinfo *l4 = &tcp->l4; \ + void *n = NULL; \ + bool has_laddr = false; \ + unsigned short p; \ + bool has_lport = false; \ + char s[BUFSIZ]; \ + bool r = true; \ + \ + switch (COLUMN_ID) { \ + case COL_##L4##_LADDR: \ + n = class->get_addr(l4, L4_LOCAL); \ + has_laddr = true; \ + p = tcp->local_port; \ + /* FALL THROUGH */ \ + case COL_##L4##_RADDR: \ + if (!has_laddr) { \ + n = class->get_addr(l4, L4_REMOTE); \ + p = tcp->remote_port; \ + } \ + if (n && inet_ntop(class->family, n, s, sizeof(s))) \ + xasprintf(STR, "%s%s%s:%"PRIu16, \ + class->l3_decorator[L3_DECO_START], \ + s, \ + class->l3_decorator[L3_DECO_END], \ + p); \ + break; \ + case COL_##L4##_LPORT: \ + p = tcp->local_port; \ + has_lport = true; \ + /* FALL THROUGH */ \ + case COL_##L4##_RPORT: \ + if (!has_lport) \ + p = tcp->remote_port; \ + xasprintf(STR, "%"PRIu16, p); \ + break; \ + default: \ + r = false; \ + break; \ + } \ + r; \ + }) + +static struct sock_xinfo *tcp_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + unsigned long local_addr; + unsigned long local_port; + unsigned long remote_addr; + unsigned long remote_port; + unsigned long st; + unsigned long long inode; + struct tcp_xinfo *tcp; + struct inet_xinfo *inet; + struct sock_xinfo *sock; + + if (sscanf(line, "%*d: %lx:%lx %lx:%lx %lx %*x:%*x %*x:%*x %*x %*u %*u %lld", + &local_addr, &local_port, &remote_addr, &remote_port, + &st, &inode) != 6) + return NULL; + + if (inode == 0) + return NULL; + + tcp = xcalloc(1, sizeof(*tcp)); + inet = &tcp->l4.inet; + sock = &inet->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + inet->local_addr.s_addr = kernel32_to_cpu(byteorder, local_addr); + tcp->local_port = local_port; + inet->remote_addr.s_addr = kernel32_to_cpu(byteorder, remote_addr); + tcp->remote_port = remote_port; + tcp->l4.st = st; + + return sock; +} + +static void *tcp_xinfo_get_addr(struct l4_xinfo *l4, enum l4_side side) +{ + return (side == L4_LOCAL) + ? &l4->inet.local_addr + : &l4->inet.remote_addr; +} + +static bool tcp_xinfo_is_any_addr(void *addr) +{ + return ((struct in_addr *)addr)->s_addr == INADDR_ANY; +} + +static bool tcp_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET, sock_xinfo, column_id, str) + || l4_fill_column_handler(TCP, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class tcp_xinfo_class = { + .sock = { + .get_name = tcp_get_name, + .get_type = tcp_get_type, + .get_state = tcp_get_state, + .get_listening = tcp_get_listening, + .fill_column = tcp_fill_column, + .free = NULL, + }, + .scan_line = tcp_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static bool L4_verify_initial_line(const char *line) +{ + /* At least we expect two white spaces. */ + if (strncmp(line, " ", 2) != 0) + return false; + line += 2; + + /* Skip white spaces. */ + line = skip_space(line); + + return strncmp(line, "sl", 2) == 0; +} + +#define TCP_LINE_LEN 256 +static void load_xinfo_from_proc_inet_L4(ino_t netns_inode, const char *proc_file, + const struct l4_xinfo_class *class) +{ + char line[TCP_LINE_LEN]; + FILE *tcp_fp; + + tcp_fp = fopen(proc_file, "r"); + if (!tcp_fp) + return; + + if (fgets(line, sizeof(line), tcp_fp) == NULL) + goto out; + if (!L4_verify_initial_line(line)) + /* Unexpected line */ + goto out; + + enum sysfs_byteorder byteorder = sysfs_get_byteorder(NULL); + + while (fgets(line, sizeof(line), tcp_fp)) { + struct sock_xinfo *sock = class->scan_line(&class->sock, line, netns_inode, byteorder); + if (sock) + add_sock_info(sock); + } + + out: + fclose(tcp_fp); +} + +static void load_xinfo_from_proc_tcp(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/tcp", + &tcp_xinfo_class); +} + +/* + * UDP + */ +static char *udp_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + char *str = NULL; + struct tcp_xinfo *tcp = ((struct tcp_xinfo *)sock_xinfo); + struct l4_xinfo *l4 = &tcp->l4; + unsigned int st = l4->st; + const char *st_str = l4_decode_state(st); + struct l4_xinfo_class *class = (struct l4_xinfo_class *)sock_xinfo->class; + void *laddr = class->get_addr(l4, L4_LOCAL); + void *raddr = class->get_addr(l4, L4_REMOTE); + char local_s[BUFSIZ]; + char remote_s[BUFSIZ]; + const char *start = class->l3_decorator[L3_DECO_START]; + const char *end = class->l3_decorator[L3_DECO_END]; + + if (!inet_ntop(class->family, laddr, local_s, sizeof(local_s))) + xasprintf(&str, "state=%s", st_str); + else if ((class->is_any_addr(raddr) && tcp->remote_port == 0) + || !inet_ntop(class->family, raddr, remote_s, sizeof(remote_s))) + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port); + else + xasprintf(&str, "state=%s laddr=%s%s%s:%"PRIu16" raddr=%s%s%s:%"PRIu16, + st_str, + start, local_s, end, tcp->local_port, + start, remote_s, end, tcp->remote_port); + return str; +} + +static char *udp_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("dgram"); +} + +static bool udp_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDP, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udp_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udp_fill_column, + .free = NULL, + }, + .scan_line = tcp_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_udp(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udp", + &udp_xinfo_class); +} + +/* + * UDP-Lite + */ +static bool udplite_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDPLITE, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udplite_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udplite_fill_column, + .free = NULL, + }, + .scan_line = tcp_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_udplite(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udplite", + &udplite_xinfo_class); +} + +/* + * RAW + */ +struct raw_xinfo { + struct l4_xinfo l4; + uint16_t protocol; +}; + +static char *raw_get_name_common(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + const char *port_label) +{ + char *str = NULL; + struct l4_xinfo_class *class = (struct l4_xinfo_class *)sock_xinfo->class; + struct raw_xinfo *raw = ((struct raw_xinfo *)sock_xinfo); + struct l4_xinfo *l4 = &raw->l4; + const char *st_str = l4_decode_state(l4->st); + void *laddr = class->get_addr(l4, L4_LOCAL); + void *raddr = class->get_addr(l4, L4_REMOTE); + char local_s[BUFSIZ]; + char remote_s[BUFSIZ]; + + if (!inet_ntop(class->family, laddr, local_s, sizeof(local_s))) + xasprintf(&str, "state=%s", st_str); + else if (class->is_any_addr(raddr) + || !inet_ntop(class->family, raddr, remote_s, sizeof(remote_s))) + xasprintf(&str, "state=%s %s=%"PRIu16" laddr=%s", + st_str, + port_label, + raw->protocol, local_s); + else + xasprintf(&str, "state=%s %s=%"PRIu16" laddr=%s raddr=%s", + st_str, + port_label, + raw->protocol, local_s, remote_s); + return str; +} + +static char *raw_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return raw_get_name_common(sock_xinfo, sock, "protocol"); +} + +static char *raw_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("raw"); +} + +static bool raw_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + if (l3_fill_column_handler(INET, sock_xinfo, column_id, str)) + return true; + + if (column_id == COL_RAW_PROTOCOL) { + xasprintf(str, "%"PRIu16, + ((struct raw_xinfo *)sock_xinfo)->protocol); + return true; + } + + return false; +} + +static struct sock_xinfo *raw_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + unsigned long local_addr; + unsigned long protocol; + unsigned long remote_addr; + unsigned long st; + unsigned long long inode; + struct raw_xinfo *raw; + struct inet_xinfo *inet; + struct sock_xinfo *sock; + + if (sscanf(line, "%*d: %lx:%lx %lx:%*x %lx %*x:%*x %*x:%*x %*x %*u %*u %lld", + &local_addr, &protocol, &remote_addr, + &st, &inode) != 5) + return NULL; + + if (inode == 0) + return NULL; + + raw = xcalloc(1, sizeof(*raw)); + inet = &raw->l4.inet; + sock = &inet->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + inet->local_addr.s_addr = kernel32_to_cpu(byteorder, local_addr); + inet->remote_addr.s_addr = kernel32_to_cpu(byteorder, remote_addr); + raw->protocol = protocol; + raw->l4.st = st; + + return sock; +} + +static const struct l4_xinfo_class raw_xinfo_class = { + .sock = { + .get_name = raw_get_name, + .get_type = raw_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = raw_fill_column, + .free = NULL, + }, + .scan_line = raw_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_raw(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/raw", + &raw_xinfo_class); +} + +/* + * PING + */ +static char *ping_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + return raw_get_name_common(sock_xinfo, sock, "id"); +} + +static char *ping_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("dgram"); +} + +static bool ping_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + if (l3_fill_column_handler(INET, sock_xinfo, column_id, str)) + return true; + + if (column_id == COL_PING_ID) { + xasprintf(str, "%"PRIu16, + ((struct raw_xinfo *)sock_xinfo)->protocol); + return true; + } + + return false; +} + +static const struct l4_xinfo_class ping_xinfo_class = { + .sock = { + .get_name = ping_get_name, + .get_type = ping_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = ping_fill_column, + .free = NULL, + }, + .scan_line = raw_xinfo_scan_line, + .get_addr = tcp_xinfo_get_addr, + .is_any_addr = tcp_xinfo_is_any_addr, + .family = AF_INET, + .l3_decorator = {"", ""}, +}; + +static void load_xinfo_from_proc_icmp(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/icmp", + &ping_xinfo_class); +} + +/* + * TCP6 + */ +static struct sock_xinfo *tcp6_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + uint32_t local_addr[4]; + unsigned int local_port; + uint32_t remote_addr[4]; + unsigned int remote_port; + unsigned int st; + unsigned long inode; + struct tcp_xinfo *tcp; + struct inet6_xinfo *inet6; + struct sock_xinfo *sock; + + if (sscanf(line, + "%*d: " + "%08x%08x%08x%08x:%04x " + "%08x%08x%08x%08x:%04x " + "%x %*x:%*x %*x:%*x %*x %*u %*d %lu ", + local_addr+0, local_addr+1, local_addr+2, local_addr+3, &local_port, + remote_addr+0, remote_addr+1, remote_addr+2, remote_addr+3, &remote_port, + &st, &inode) != 12) + return NULL; + + if (inode == 0) + return NULL; + + tcp = xmalloc(sizeof(*tcp)); + inet6 = &tcp->l4.inet6; + sock = &inet6->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + tcp->local_port = local_port; + for (int i = 0; i < 4; i++) { + inet6->local_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, local_addr[i]); + inet6->remote_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, remote_addr[i]); + } + tcp->remote_port = remote_port; + tcp->l4.st = st; + + return sock; +} + +static bool tcp6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET6, sock_xinfo, column_id, str) + || l4_fill_column_handler(TCP, sock_xinfo, column_id, str); +} + +static void *tcp6_xinfo_get_addr(struct l4_xinfo * l4, enum l4_side side) +{ + return (side == L4_LOCAL) + ? &l4->inet6.local_addr + : &l4->inet6.remote_addr; +} + +static bool tcp6_xinfo_is_any_addr(void *addr) +{ + return IN6_ARE_ADDR_EQUAL(addr, &(struct in6_addr)IN6ADDR_ANY_INIT); +} + +static const struct l4_xinfo_class tcp6_xinfo_class = { + .sock = { + .get_name = tcp_get_name, + .get_type = tcp_get_type, + .get_state = tcp_get_state, + .get_listening = tcp_get_listening, + .fill_column = tcp6_fill_column, + .free = NULL, + }, + .scan_line = tcp6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_tcp6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/tcp6", + &tcp6_xinfo_class); +} + +/* + * UDP6 + */ +static bool udp6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET6, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDP, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udp6_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udp6_fill_column, + .free = NULL, + }, + .scan_line = tcp6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_udp6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udp6", + &udp6_xinfo_class); +} + +/* + * UDPLITEv6 + */ +static bool udplite6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + return l3_fill_column_handler(INET6, sock_xinfo, column_id, str) + || l4_fill_column_handler(UDPLITE, sock_xinfo, column_id, str); +} + +static const struct l4_xinfo_class udplite6_xinfo_class = { + .sock = { + .get_name = udp_get_name, + .get_type = udp_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = udplite6_fill_column, + .free = NULL, + }, + .scan_line = tcp6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_udplite6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/udplite6", + &udplite6_xinfo_class); +} + +/* + * RAW6 + */ +static struct sock_xinfo *raw6_xinfo_scan_line(const struct sock_xinfo_class *class, + char * line, + ino_t netns_inode, + enum sysfs_byteorder byteorder) +{ + uint32_t local_addr[4]; + unsigned int protocol; + uint32_t remote_addr[4]; + unsigned int st; + unsigned long inode; + struct raw_xinfo *raw; + struct inet6_xinfo *inet6; + struct sock_xinfo *sock; + + if (sscanf(line, + "%*d: " + "%08x%08x%08x%08x:%04x " + "%08x%08x%08x%08x:0000 " + "%x %*x:%*x %*x:%*x %*x %*u %*d %lu ", + local_addr+0, local_addr+1, local_addr+2, local_addr+3, &protocol, + remote_addr+0, remote_addr+1, remote_addr+2, remote_addr+3, + &st, &inode) != 11) + return NULL; + + if (inode == 0) + return NULL; + + raw = xmalloc(sizeof(*raw)); + inet6 = &raw->l4.inet6; + sock = &inet6->sock; + sock->class = class; + sock->inode = (ino_t)inode; + sock->netns_inode = netns_inode; + for (int i = 0; i < 4; i++) { + inet6->local_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, local_addr[i]); + inet6->remote_addr.s6_addr32[i] = kernel32_to_cpu(byteorder, remote_addr[i]); + } + raw->protocol = protocol; + raw->l4.st = st; + + return sock; +} + +static bool raw6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct raw_xinfo *raw; + + if (l3_fill_column_handler(INET6, sock_xinfo, column_id, str)) + return true; + + raw = (struct raw_xinfo *)sock_xinfo; + if (column_id == COL_RAW_PROTOCOL) { + xasprintf(str, "%"PRIu16, raw->protocol); + return true; + } + + return false; +} + +static const struct l4_xinfo_class raw6_xinfo_class = { + .sock = { + .get_name = raw_get_name, + .get_type = raw_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = raw6_fill_column, + .free = NULL, + }, + .scan_line = raw6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_raw6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/raw6", + &raw6_xinfo_class); +} + +/* + * PINGv6 + */ +static bool ping6_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + if (l3_fill_column_handler(INET6, sock_xinfo, column_id, str)) + return true; + + if (column_id == COL_PING_ID) { + xasprintf(str, "%"PRIu16, + ((struct raw_xinfo *)sock_xinfo)->protocol); + return true; + } + + return false; +} + +static const struct l4_xinfo_class ping6_xinfo_class = { + .sock = { + .get_name = ping_get_name, + .get_type = ping_get_type, + .get_state = tcp_get_state, + .get_listening = NULL, + .fill_column = ping6_fill_column, + .free = NULL, + }, + .scan_line = raw6_xinfo_scan_line, + .get_addr = tcp6_xinfo_get_addr, + .is_any_addr = tcp6_xinfo_is_any_addr, + .family = AF_INET6, + .l3_decorator = {"[", "]"}, +}; + +static void load_xinfo_from_proc_icmp6(ino_t netns_inode) +{ + load_xinfo_from_proc_inet_L4(netns_inode, + "/proc/net/icmp6", + &ping6_xinfo_class); +} + +/* + * NETLINK + */ +struct netlink_xinfo { + struct sock_xinfo sock; + uint16_t protocol; + uint32_t lportid; /* netlink_diag may provide rportid. */ + uint32_t groups; +}; + +static const char *netlink_decode_protocol(uint16_t protocol) +{ + switch (protocol) { + case NETLINK_ROUTE: + return "route"; + case NETLINK_UNUSED: + return "unused"; + case NETLINK_USERSOCK: + return "usersock"; + case NETLINK_FIREWALL: + return "firewall"; + case NETLINK_SOCK_DIAG: + return "sock_diag"; + case NETLINK_NFLOG: + return "nflog"; + case NETLINK_XFRM: + return "xfrm"; + case NETLINK_SELINUX: + return "selinux"; + case NETLINK_ISCSI: + return "iscsi"; + case NETLINK_AUDIT: + return "audit"; + case NETLINK_FIB_LOOKUP: + return "fib_lookup"; + case NETLINK_CONNECTOR: + return "connector"; + case NETLINK_NETFILTER: + return "netfilter"; + case NETLINK_IP6_FW: + return "ip6_fw"; + case NETLINK_DNRTMSG: + return "dnrtmsg"; + case NETLINK_KOBJECT_UEVENT: + return "kobject_uevent"; + case NETLINK_GENERIC: + return "generic"; + case NETLINK_SCSITRANSPORT: + return "scsitransport"; + case NETLINK_ECRYPTFS: + return "ecryptfs"; + case NETLINK_RDMA: + return "rdma"; + case NETLINK_CRYPTO: + return "crypto"; +#ifdef NETLINK_SMC + case NETLINK_SMC: + return "smc"; +#endif + default: + return "unknown"; + } +} + +static char *netlink_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + struct netlink_xinfo *nl = (struct netlink_xinfo *)sock_xinfo; + char *str = NULL; + const char *protocol = netlink_decode_protocol(nl->protocol); + + if (nl->groups) + xasprintf(&str, "protocol=%s lport=%"PRIu16 " groups=%"PRIu32, + protocol, + nl->lportid, nl->groups); + else + xasprintf(&str, "protocol=%s lport=%"PRIu16, + protocol, + nl->lportid); + return str; +} + +static char *netlink_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + return xstrdup("raw"); +} + +static bool netlink_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct netlink_xinfo *nl = (struct netlink_xinfo *)sock_xinfo; + + switch (column_id) { + case COL_NETLINK_GROUPS: + xasprintf(str, "%"PRIu32, nl->groups); + return true; + case COL_NETLINK_LPORT: + xasprintf(str, "%"PRIu32, nl->lportid); + return true; + case COL_NETLINK_PROTOCOL: + *str = xstrdup(netlink_decode_protocol(nl->protocol)); + return true; + } + + return false; +} + +static const struct sock_xinfo_class netlink_xinfo_class = { + .get_name = netlink_get_name, + .get_type = netlink_get_type, + .get_state = NULL, + .get_listening = NULL, + .fill_column = netlink_fill_column, + .free = NULL, +}; + +static void load_xinfo_from_proc_netlink(ino_t netns_inode) +{ + char line[BUFSIZ]; + FILE *netlink_fp; + + netlink_fp = fopen("/proc/net/netlink", "r"); + if (!netlink_fp) + return; + + if (fgets(line, sizeof(line), netlink_fp) == NULL) + goto out; + if (!(line[0] == 's' && line[1] == 'k')) + /* Unexpected line */ + goto out; + + while (fgets(line, sizeof(line), netlink_fp)) { + uint16_t protocol; + uint32_t lportid; + uint32_t groups; + unsigned long inode; + struct netlink_xinfo *nl; + + if (sscanf(line, "%*x %" SCNu16 " %" SCNu32 " %" SCNx32 " %*d %*d %*d %*d %*u %lu", + &protocol, &lportid, &groups, &inode) < 4) + continue; + + if (inode == 0) + continue; + + nl = xcalloc(1, sizeof(*nl)); + nl->sock.class = &netlink_xinfo_class; + nl->sock.inode = (ino_t)inode; + nl->sock.netns_inode = netns_inode; + + nl->protocol = protocol; + nl->lportid = lportid; + nl->groups = groups; + + add_sock_info(&nl->sock); + } + + out: + fclose(netlink_fp); +} + +/* + * PACKET + */ +struct packet_xinfo { + struct sock_xinfo sock; + uint16_t type; + uint16_t protocol; + unsigned int iface; +}; + +static const char *packet_decode_protocol(uint16_t proto) +{ + switch (proto) { + case 0: + return NULL; + case ETH_P_802_3: + return "802_3"; + case ETH_P_AX25: + return "ax25"; + case ETH_P_ALL: + return "all"; + case ETH_P_802_2: + return "802_2"; + case ETH_P_SNAP: + return "snap"; + case ETH_P_DDCMP: + return "ddcmp"; + case ETH_P_WAN_PPP: + return "wan_ppp"; + case ETH_P_PPP_MP: + return "ppp_mp"; + case ETH_P_LOCALTALK: + return "localtalk"; + case ETH_P_CAN: + return "can"; + case ETH_P_CANFD: + return "canfd"; +#ifdef ETH_P_CANXL + case ETH_P_CANXL: + return "canxl"; +#endif + case ETH_P_PPPTALK: + return "ppptalk"; + case ETH_P_TR_802_2: + return "tr_802_2"; + case ETH_P_MOBITEX: + return "mobitex"; + case ETH_P_CONTROL: + return "control"; + case ETH_P_IRDA: + return "irda"; + case ETH_P_ECONET: + return "econet"; + case ETH_P_HDLC: + return "hdlc"; + case ETH_P_ARCNET: + return "arcnet"; + case ETH_P_DSA: + return "dsa"; + case ETH_P_TRAILER: + return "trailer"; + case ETH_P_PHONET: + return "phonet"; + case ETH_P_IEEE802154: + return "ieee802154"; + case ETH_P_CAIF: + return "caif"; +#ifdef ETH_P_XDSA + case ETH_P_XDSA: + return "xdsa"; +#endif +#ifdef ETH_P_MAP + case ETH_P_MAP: + return "map"; +#endif +#ifdef ETH_P_MCTP + case ETH_P_MCTP: + return "mctp"; +#endif + case ETH_P_LOOP: + return "loop"; + case ETH_P_PUP: + return "pup"; + case ETH_P_PUPAT: + return "pupat"; +#ifdef ETH_P_TSN + case ETH_P_TSN: + return "tsn"; +#endif +#ifdef ETH_P_ERSPAN2 + case ETH_P_ERSPAN2: + return "erspan2"; +#endif + case ETH_P_IP: + return "ip"; + case ETH_P_X25: + return "x25"; + case ETH_P_ARP: + return "arp"; + case ETH_P_BPQ: + return "bpq"; + case ETH_P_IEEEPUP: + return "ieeepup"; + case ETH_P_IEEEPUPAT: + return "ieeepupat"; + case ETH_P_BATMAN: + return "batman"; + case ETH_P_DEC: + return "dec"; + case ETH_P_DNA_DL: + return "dna_dl"; + case ETH_P_DNA_RC: + return "dna_rc"; + case ETH_P_DNA_RT: + return "dna_rt"; + case ETH_P_LAT: + return "lat"; + case ETH_P_DIAG: + return "diag"; + case ETH_P_CUST: + return "cust"; + case ETH_P_SCA: + return "sca"; + case ETH_P_TEB: + return "teb"; + case ETH_P_RARP: + return "rarp"; + case ETH_P_ATALK: + return "atalk"; + case ETH_P_AARP: + return "aarp"; + case ETH_P_8021Q: + return "8021q"; +#ifdef ETH_P_ERSPAN + case ETH_P_ERSPAN: + return "erspan"; +#endif + case ETH_P_IPX: + return "ipx"; + case ETH_P_IPV6: + return "ipv6"; + case ETH_P_PAUSE: + return "pause"; + case ETH_P_SLOW: + return "slow"; + case ETH_P_WCCP: + return "wccp"; + case ETH_P_MPLS_UC: + return "mpls_uc"; + case ETH_P_MPLS_MC: + return "mpls_mc"; + case ETH_P_ATMMPOA: + return "atmmpoa"; +#ifdef ETH_P_PPP_DISC + case ETH_P_PPP_DISC: + return "ppp_disc"; +#endif +#ifdef ETH_P_PPP_SES + case ETH_P_PPP_SES: + return "ppp_ses"; +#endif + case ETH_P_LINK_CTL: + return "link_ctl"; + case ETH_P_ATMFATE: + return "atmfate"; + case ETH_P_PAE: + return "pae"; +#ifdef ETH_P_PROFINET + case ETH_P_PROFINET: + return "profinet"; +#endif +#ifdef ETH_P_REALTEK + case ETH_P_REALTEK: + return "realtek"; +#endif + case ETH_P_AOE: + return "aoe"; +#ifdef ETH_P_ETHERCAT + case ETH_P_ETHERCAT: + return "ethercat"; +#endif + case ETH_P_8021AD: + return "8021ad"; + case ETH_P_802_EX1: + return "802_ex1"; +#ifdef ETH_P_PREAUTH + case ETH_P_PREAUTH: + return "preauth"; +#endif + case ETH_P_TIPC: + return "tipc"; +#ifdef ETH_P_LLDP + case ETH_P_LLDP: + return "lldp"; +#endif +#ifdef ETH_P_MRP + case ETH_P_MRP: + return "mrp"; +#endif +#ifdef ETH_P_MACSEC + case ETH_P_MACSEC: + return "macsec"; +#endif + case ETH_P_8021AH: + return "8021ah"; +#ifdef ETH_P_MVRP + case ETH_P_MVRP: + return "mvrp"; +#endif + case ETH_P_1588: + return "1588"; +#ifdef ETH_P_NCSI + case ETH_P_NCSI: + return "ncsi"; +#endif +#ifdef ETH_P_PRP + case ETH_P_PRP: + return "prp"; +#endif +#ifdef ETH_P_CFM + case ETH_P_CFM: + return "cfm"; +#endif + case ETH_P_FCOE: + return "fcoe"; +#ifdef ETH_P_IBOE + case ETH_P_IBOE: + return "iboe"; +#endif + case ETH_P_TDLS: + return "tdls"; + case ETH_P_FIP: + return "fip"; +#ifdef ETH_P_80221 + case ETH_P_80221: + return "80221"; +#endif +#ifdef ETH_P_HSR + case ETH_P_HSR: + return "hsr"; +#endif +#ifdef ETH_P_NSH + case ETH_P_NSH: + return "nsh"; +#endif +#ifdef ETH_P_LOOPBACK + case ETH_P_LOOPBACK: + return "loopback"; +#endif + case ETH_P_QINQ1: + return "qinq1"; + case ETH_P_QINQ2: + return "qinq2"; + case ETH_P_QINQ3: + return "qinq3"; + case ETH_P_EDSA: + return "edsa"; +#ifdef ETH_P_DSA_8021Q + case ETH_P_DSA_8021Q: + return "dsa_8021q"; +#endif +#ifdef ETH_P_DSA_A5PSW + case ETH_P_DSA_A5PSW: + return "dsa_a5psw"; +#endif +#ifdef ETH_P_IFE + case ETH_P_IFE: + return "ife"; +#endif + case ETH_P_AF_IUCV: + return "af_iucv"; +#ifdef ETH_P_802_3_MIN + case ETH_P_802_3_MIN: + return "802_3_min"; +#endif + default: + return "unknown"; + } +} + +static char *packet_get_name(struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__))) +{ + struct packet_xinfo *pkt = (struct packet_xinfo *)sock_xinfo; + char *str = NULL; + const char *type = sock_decode_type(pkt->type); + const char *proto = packet_decode_protocol(pkt->protocol); + const char *iface = get_iface_name(sock_xinfo->netns_inode, + pkt->iface); + + if (iface && proto) + xasprintf(&str, "type=%s protocol=%s iface=%s", + type, proto, iface); + else if (proto) + xasprintf(&str, "type=%s protocol=%s", + type, proto); + else if (iface) + xasprintf(&str, "type=%s iface=%s", + type, iface); + else + xasprintf(&str, "type=%s", type); + + return str; +} + +static char *packet_get_type(struct sock_xinfo *sock_xinfo __attribute__((__unused__)), + struct sock *sock __attribute__((__unused__))) +{ + const char *str; + struct packet_xinfo *pkt = (struct packet_xinfo *)sock_xinfo; + + str = sock_decode_type(pkt->type); + return xstrdup(str); +} + +static bool packet_fill_column(struct proc *proc __attribute__((__unused__)), + struct sock_xinfo *sock_xinfo, + struct sock *sock __attribute__((__unused__)), + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct packet_xinfo *pkt = (struct packet_xinfo *)sock_xinfo; + + switch (column_id) { + case COL_PACKET_IFACE: { + const char *iface; + iface = get_iface_name(sock_xinfo->netns_inode, + pkt->iface); + if (iface) { + *str = xstrdup(iface); + return true; + } + break; + } + case COL_PACKET_PROTOCOL: { + const char *proto; + proto = packet_decode_protocol(pkt->protocol); + if (proto) { + *str = xstrdup(proto); + return true; + } + break; + } + default: + break; + } + return false; +} + +static const struct sock_xinfo_class packet_xinfo_class = { + .get_name = packet_get_name, + .get_type = packet_get_type, + .get_state = NULL, + .get_listening = NULL, + .fill_column = packet_fill_column, + .free = NULL, +}; + +static void load_xinfo_from_proc_packet(ino_t netns_inode) +{ + char line[BUFSIZ]; + FILE *packet_fp; + + packet_fp = fopen("/proc/net/packet", "r"); + if (!packet_fp) + return; + + if (fgets(line, sizeof(line), packet_fp) == NULL) + goto out; + if (!(line[0] == 's' && line[1] == 'k')) + /* Unexpected line */ + goto out; + + while (fgets(line, sizeof(line), packet_fp)) { + uint16_t type; + uint16_t protocol; + unsigned int iface; + unsigned long inode; + struct packet_xinfo *pkt; + + if (sscanf(line, "%*x %*d %" SCNu16 " %" SCNu16 " %u %*d %*d %*d %lu", + &type, &protocol, &iface, &inode) < 4) + continue; + + pkt = xcalloc(1, sizeof(*pkt)); + pkt->sock.class = &packet_xinfo_class; + pkt->sock.inode = (ino_t)inode; + pkt->sock.netns_inode = netns_inode; + + pkt->type = type; + pkt->protocol = protocol; + pkt->iface = iface; + + add_sock_info(&pkt->sock); + } + + out: + fclose(packet_fp); +} |