/*
 * lsfd-sock-xinfo.c - read various information from files under /proc/net/ and NETLINK_SOCK_DIAG
 *
 * 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 <netdb.h>		/* getprotobyname */
#include <net/if.h>		/* if_nametoindex */
#include <linux/if_ether.h>	/* ETH_P_* */
#include <linux/net.h>		/* SS_* */
#include <linux/netlink.h>	/* NETLINK_*, NLMSG_* */
#include <linux/rtnetlink.h>	/* RTA_*, struct rtattr,  */
#include <linux/sock_diag.h>	/* SOCK_DIAG_BY_FAMILY */
#include <linux/un.h>		/* UNIX_PATH_MAX */
#include <linux/unix_diag.h>	/* UNIX_DIAG_*, UDIAG_SHOW_*,
				   struct unix_diag_req */
#include <linux/vm_sockets.h>	/* VMADDR_CID* */
#if HAVE_LINUX_VM_SOCKETS_DIAG_H
# include <linux/vm_sockets_diag.h> /* vsock_diag_req/vsock_diag_msg */
#endif
#include <sched.h>		/* for setns(2) */
#include <search.h>		/* tfind, tsearch */
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>		/* SOCK_* */

#include "sysfs.h"
#include "bitops.h"

#include "lsfd.h"
#include "pidfd-utils.h"
#include "sock.h"

static void load_xinfo_from_proc_icmp(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_icmp6(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_unix(ino_t netns_inode);
static void load_xinfo_from_proc_raw(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_tcp(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_udp(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_udplite(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_tcp6(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_udp6(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_udplite6(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_raw6(ino_t netns_inode, enum sysfs_byteorder byteorder);
static void load_xinfo_from_proc_netlink(ino_t netns_inode);
static void load_xinfo_from_proc_packet(ino_t netns_inode);

static void load_xinfo_from_diag_unix(int diag, ino_t netns_inode);
static void load_xinfo_from_diag_vsock(int diag, ino_t netns_inode);

static void fill_peers_of_unix_oneway_ipcs(void);

static int self_netns_fd = -1;
static struct stat self_netns_sb;

static void *xinfo_tree;	/* for tsearch/tfind */
static void *netns_tree;

static struct list_head unix_ipcs;
static void *unix_oneway_ipc_tree;	/* for tsearch/tfind */

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;
	int diagsd;
	enum sysfs_byteorder byteorder = sysfs_get_byteorder(NULL);

	load_xinfo_from_proc_unix(netns);
	load_xinfo_from_proc_tcp(netns, byteorder);
	load_xinfo_from_proc_udp(netns, byteorder);
	load_xinfo_from_proc_udplite(netns, byteorder);
	load_xinfo_from_proc_raw(netns, byteorder);
	load_xinfo_from_proc_tcp6(netns, byteorder);
	load_xinfo_from_proc_udp6(netns, byteorder);
	load_xinfo_from_proc_udplite6(netns, byteorder);
	load_xinfo_from_proc_raw6(netns, byteorder);
	load_xinfo_from_proc_icmp(netns, byteorder);
	load_xinfo_from_proc_icmp6(netns, byteorder);
	load_xinfo_from_proc_netlink(netns);
	load_xinfo_from_proc_packet(netns);

	diagsd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_SOCK_DIAG);
	DBG(ENDPOINTS, ul_debug("made a diagnose socket [fd=%d; %s]", diagsd,
				(diagsd >= 0)? "successful": strerror(errno)));
	if (diagsd >= 0) {
		load_xinfo_from_diag_unix(diagsd, netns);
		load_xinfo_from_diag_vsock(diagsd, netns);
		close(diagsd);
		DBG(ENDPOINTS, ul_debug("close the diagnose socket"));
	}

	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 load_fdsk_xinfo(ino_t netns_ino, int netns_fd)
{
	if (!is_sock_xinfo_loaded(netns_ino)) {
		struct netns *nsobj = mark_sock_xinfo_loaded(netns_ino);
		load_sock_xinfo_with_fd(netns_fd, nsobj);
	}
}

void initialize_sock_xinfos(void)
{
	struct path_cxt *pc;
	DIR *dir;
	struct dirent *d;

	INIT_LIST_HEAD(&unix_ipcs);

	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) {
			unsigned long m;
			struct netns *nsobj = mark_sock_xinfo_loaded(self_netns_sb.st_ino);
			load_sock_xinfo_no_nsswitch(nsobj);

			m = minor(self_netns_sb.st_dev);
			add_nodev(m, "nsfs");
		}
	}

	/* 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)
		goto out;
	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);
 out:
	ul_unref_path(pc);

	fill_peers_of_unix_oneway_ipcs();
}

static void free_sock_xinfo(void *node)
{
	struct sock_xinfo *xinfo = node;
	if (xinfo->class->free)
		xinfo->class->free(xinfo);
	free(node);
}

static void do_nothing(void *node __attribute__((__unused__)))
{
}

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);
	tdestroy(unix_oneway_ipc_tree, do_nothing);
}

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 inode)
{
	struct sock_xinfo key = { .inode = 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";
	}
}

static void send_diag_request(int diagsd, void *req, size_t req_size,
			      bool (*cb)(ino_t, size_t, void *),
			      ino_t netns)
{
	int r;
	struct sockaddr_nl nladdr = {
		.nl_family = AF_NETLINK,
	};

	struct nlmsghdr nlh = {
		.nlmsg_len = sizeof(nlh) + req_size,
		.nlmsg_type = SOCK_DIAG_BY_FAMILY,
		.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
	};

	struct iovec iovecs[] = {
		{ &nlh, sizeof(nlh) },
		{ req, req_size },
	};

	const struct msghdr mhd = {
		.msg_namelen = sizeof(nladdr),
		.msg_name = &nladdr,
		.msg_iovlen = ARRAY_SIZE(iovecs),
		.msg_iov = iovecs,
	};

	__attribute__((aligned(sizeof(void *)))) uint8_t buf[8192];

	r = sendmsg(diagsd, &mhd, 0);
	DBG(ENDPOINTS, ul_debug("sendmsg [rc=%d; %s]",
				r, (r >= 0)? "successful": strerror(errno)));
	if (r < 0)
		return;

	for (;;) {
		const struct nlmsghdr *h;
		r = recvfrom(diagsd, buf, sizeof(buf), 0, NULL, NULL);
		DBG(ENDPOINTS, ul_debug("recvfrom [rc=%d; %s]",
					r, (r >= 0)? "successful": strerror(errno)));
		if (r < 0)
			return;

		h = (void *) buf;
		DBG(ENDPOINTS, ul_debug("   OK: %d", NLMSG_OK(h, (size_t)r)));
		if (!NLMSG_OK(h, (size_t)r))
			return;

		for (; NLMSG_OK(h, (size_t)r); h = NLMSG_NEXT(h, r)) {
			if (h->nlmsg_type == NLMSG_DONE) {
				DBG(ENDPOINTS, ul_debug("      DONE"));
				return;
			}
			if (h->nlmsg_type == NLMSG_ERROR)  {
				struct nlmsgerr *e = (struct nlmsgerr *)NLMSG_DATA(h);
				DBG(ENDPOINTS, ul_debug("      ERROR: %s",
							strerror(- e->error)));
				return;
			}

			if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY) {
				DBG(ENDPOINTS, ul_debug("      FAMILY"));
				if (!cb(netns, h->nlmsg_len, NLMSG_DATA(h)))
					return;
			}
			DBG(ENDPOINTS, ul_debug("   NEXT"));
		}
		DBG(ENDPOINTS, ul_debug("   OK: 0"));
	}
}

/*
 * Protocol specific code
 */

/*
 * UNIX
 */
struct unix_ipc {
	struct ipc ipc;
	ino_t inode;
	ino_t ipeer;
	struct list_head unix_ipcs;
};

struct unix_xinfo {
	struct sock_xinfo sock;
	int acceptcon;	/* flags */
	uint16_t type;
	uint8_t  st;
#define is_shutdown_mask_set(mask) ((mask) & (1 << 2))
#define set_shutdown_mask(mask)    ((mask) |= (1 << 2))
	uint8_t  shutdown_mask:3;
	struct unix_ipc *unix_ipc;
	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 unsigned int unix_get_hash(struct file *file)
{
	return (unsigned int)(file->stat.st_ino % UINT_MAX);
}

static bool unix_is_suitable_ipc(struct ipc *ipc, struct file *file)
{
	return ((struct unix_ipc *)ipc)->inode == file->stat.st_ino;
}

/* For looking up an ipc struct for a sock inode, we need a sock strcuct
 * for the inode. See the signature o get_ipc().
 *
 * However, there is a case that we have no sock strcuct for the inode;
 * in the context we know only the sock inode.
 * For the case, unix_make_dummy_sock() provides the way to make a
 * dummy sock struct for the inode.
 */
static void unix_make_dummy_sock(struct sock *original, ino_t ino, struct sock *dummy)
{
	*dummy = *original;
	dummy->file.stat.st_ino = ino;
}

static struct ipc_class unix_ipc_class = {
	.size = sizeof(struct unix_ipc),
	.get_hash = unix_get_hash,
	.is_suitable_ipc = unix_is_suitable_ipc,
	.free = NULL,
};

static struct ipc_class *unix_get_ipc_class(struct sock_xinfo *sock_xinfo __attribute__((__unused__)),
					    struct sock *sock __attribute__((__unused__)))
{
	return &unix_ipc_class;
}

static bool unix_shutdown_chars(struct unix_xinfo *ux, char rw[2])
{
	uint8_t mask = ux->shutdown_mask;

	if (is_shutdown_mask_set(mask)) {
		rw[0] = ((mask & (1 << 0))? '-': 'r');
		rw[1] = ((mask & (1 << 1))? '-': 'w');
		return true;
	}

	return false;
}

static inline char *unix_xstrendpoint(struct sock *sock)
{
	char *str = NULL;
	char shutdown_chars[3] = { 0 };

	if (!unix_shutdown_chars(((struct unix_xinfo *)sock->xinfo), shutdown_chars)) {
		shutdown_chars[0] = '?';
		shutdown_chars[1] = '?';
	}
	xasprintf(&str, "%d,%s,%d%c%c",
		  sock->file.proc->pid, sock->file.proc->command, sock->file.association,
		  shutdown_chars[0], shutdown_chars[1]);

	return str;
}

static struct ipc *unix_get_peer_ipc(struct unix_xinfo *ux,
				     struct sock *sock)
{
	struct unix_ipc *unix_ipc;
	struct sock dummy_peer_sock;

	unix_ipc = ux->unix_ipc;
	if (!unix_ipc)
		return NULL;

	unix_make_dummy_sock(sock, unix_ipc->ipeer, &dummy_peer_sock);
	return get_ipc(&dummy_peer_sock.file);
}

static void unix_fill_column_append_endpoints(struct ipc *peer_ipc, char **str)
{
	struct list_head *e;

	list_for_each_backwardly(e, &peer_ipc->endpoints) {
		struct sock *peer_sock = list_entry(e, struct sock, endpoint.endpoints);
		char *estr;

		if (*str)
			xstrputc(str, '\n');
		estr = unix_xstrendpoint(peer_sock);
		xstrappend(str, estr);
		free(estr);
	}
}

static bool unix_fill_column(struct proc *proc __attribute__((__unused__)),
			     struct sock_xinfo *sock_xinfo,
			     struct sock *sock,
			     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;
	struct ipc *peer_ipc;
	char shutdown_chars[3] = { 0 };

	switch (column_id) {
	case COL_UNIX_PATH:
		if (*ux->path) {
			*str = xstrdup(ux->path);
			return true;
		}
		break;
	case COL_UNIX_IPEER:
		if (ux->unix_ipc) {
			xasprintf(str, "%llu", (unsigned long long)ux->unix_ipc->ipeer);
			return true;
		}
		break;
	case COL_ENDPOINTS:
		if (ux->unix_ipc == NULL)
			break;

		peer_ipc = unix_get_peer_ipc(ux, sock);
		if (!peer_ipc && ux->unix_ipc->ipeer != 0)
			break;

		if (peer_ipc)
			unix_fill_column_append_endpoints(peer_ipc, str);

		if (ux->unix_ipc->ipeer == 0) {
			struct list_head *e;
			list_for_each(e, &ux->unix_ipc->unix_ipcs) {
				struct unix_ipc *peer_unix_ipc = list_entry(e, struct unix_ipc, unix_ipcs);
				peer_ipc = &peer_unix_ipc->ipc;
				unix_fill_column_append_endpoints(peer_ipc, str);
			}
		}

		if (*str)
			return true;
		break;
	case COL_SOCK_SHUTDOWN:
		if (unix_shutdown_chars(ux, shutdown_chars)) {
			*str = xstrdup(shutdown_chars);
			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,
	.get_ipc_class = unix_get_ipc_class,
	.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");
	DBG(ENDPOINTS, ul_debug("open /proc/net/unix [fp=%p; %s]", unix_fp,
				unix_fp? "successful": strerror(errno)));
	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 };
		int r;

		DBG(ENDPOINTS, ul_debug("   line: %s", line));

		r = sscanf(line, "%*x: %*x %*x %" SCNx64 " %x %x %lu %"
			   stringify_value(UNIX_LINE_LEN) "[^\n]",
			   &flags, &type, &st, &inode, path);
		DBG(ENDPOINTS, ul_debug("   scanf: %d", r));
		if (r < 4)
			continue;

		DBG(ENDPOINTS, ul_debug("   inode: %lu", inode));
		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));

		DBG(ENDPOINTS, ul_debug("   path: %s", ux->path));
		add_sock_info(&ux->sock);
	}

 out:
	DBG(ENDPOINTS, ul_debug("close /proc/net/unix"));
	fclose(unix_fp);
}

/* The path name extracted from /proc/net/unix is unreliable; the line oriented interface cannot
 * represent a file name including newlines. With unix_refill_name(), we patch the path
 * member of unix_xinfos with information received via netlink diag interface. */
static void unix_refill_name(struct sock_xinfo *xinfo, const char *name, size_t len)
{
	struct unix_xinfo *ux = (struct unix_xinfo *)xinfo;
	size_t min_len;

	if (len == 0)
		return;

	min_len = min(sizeof(ux->path) - 1, len);
	memcpy(ux->path, name, min_len);
	if (ux->path[0] == '\0') {
		ux->path[0] = '@';
	}
	ux->path[min_len] = '\0';
}

static int unix_oneway_ipc_compare(const void *a, const void *b)
{
	return ((struct unix_ipc *)a)->inode - ((struct unix_ipc *)b)->inode;
}

static void add_unix_oneway_ipc(struct unix_ipc *unix_oneway_ipc)
{
	struct unix_ipc **tmp = tsearch(unix_oneway_ipc,
					&unix_oneway_ipc_tree,
					unix_oneway_ipc_compare);

	if (tmp == NULL)
		errx(EXIT_FAILURE, _("failed to allocate memory"));
}

static struct unix_ipc *get_unix_oneway_ipc(ino_t inode)
{
	struct unix_ipc key = { .inode = inode };
	struct unix_ipc  **unix_oneway_ipc = tfind(&key,
						   &unix_oneway_ipc_tree,
						   unix_oneway_ipc_compare);

	if (unix_oneway_ipc)
		return *unix_oneway_ipc;
	return NULL;
}

static bool handle_diag_unix(ino_t netns __attribute__((__unused__)),
			     size_t nlmsg_len, void *nlmsg_data)
{
	const struct unix_diag_msg *diag = nlmsg_data;
	size_t rta_len;
	ino_t inode;
	struct sock_xinfo *xinfo;
	struct unix_xinfo *unix_xinfo;
	bool peer_added = false;
	bool maybe_oneway = false;

	if (diag->udiag_family != AF_UNIX)
		return false;
	DBG(ENDPOINTS, ul_debug("         UNIX"));
	DBG(ENDPOINTS, ul_debug("         LEN: %zu (>= %zu)", nlmsg_len,
				(size_t)(NLMSG_LENGTH(sizeof(*diag)))));

	if (nlmsg_len < NLMSG_LENGTH(sizeof(*diag)))
		return false;

	inode = (ino_t)diag->udiag_ino;
	xinfo = get_sock_xinfo(inode);

	DBG(ENDPOINTS, ul_debug("         inode: %llu", (unsigned long long)inode));
	DBG(ENDPOINTS, ul_debug("         xinfo: %p", xinfo));

	if (xinfo == NULL)
		/* The socket is found in the diag response
		   but not in the proc fs. */
		return true;

	DBG(ENDPOINTS, ul_debug("         xinfo->class == &unix_xinfo_class: %d",
				xinfo->class == &unix_xinfo_class));
	if (xinfo->class != &unix_xinfo_class)
		return true;
	unix_xinfo = (struct unix_xinfo *)xinfo;

	rta_len = nlmsg_len - NLMSG_LENGTH(sizeof(*diag));
	DBG(ENDPOINTS, ul_debug("         rta_len: %zu", rta_len));
	for (struct rtattr *attr = (struct rtattr *)(diag + 1);
	     RTA_OK(attr, rta_len);
	     attr = RTA_NEXT(attr, rta_len)) {
		size_t len = RTA_PAYLOAD(attr);

		DBG(ENDPOINTS, ul_debug("            len = %2zu, type: %d",
					rta_len, attr->rta_type));
		switch (attr->rta_type) {
		case UNIX_DIAG_NAME:
			unix_refill_name(xinfo, RTA_DATA(attr), len);
			maybe_oneway = true;
			break;

		case UNIX_DIAG_SHUTDOWN:
			if (len < 1)
				break;

			unix_xinfo->shutdown_mask = *(uint8_t *)RTA_DATA(attr);
			set_shutdown_mask(unix_xinfo->shutdown_mask);
			break;

		case UNIX_DIAG_PEER:
			if (len < 4)
				break;

			unix_xinfo->unix_ipc = (struct unix_ipc *)new_ipc(&unix_ipc_class);
			unix_xinfo->unix_ipc->inode = inode;
			unix_xinfo->unix_ipc->ipeer = (ino_t)(*(uint32_t *)RTA_DATA(attr));

			INIT_LIST_HEAD(&unix_xinfo->unix_ipc->unix_ipcs);
			list_add(&unix_xinfo->unix_ipc->unix_ipcs, &unix_ipcs);

			add_ipc(&unix_xinfo->unix_ipc->ipc, inode % UINT_MAX);
			peer_added = true;
			break;
		}
	}

	if (!peer_added && maybe_oneway) {
		assert(unix_xinfo->unix_ipc == NULL);
		unix_xinfo->unix_ipc = (struct unix_ipc *)new_ipc(&unix_ipc_class);
		unix_xinfo->unix_ipc->inode = inode;
		unix_xinfo->unix_ipc->ipeer = 0;

		INIT_LIST_HEAD(&unix_xinfo->unix_ipc->unix_ipcs);
		add_unix_oneway_ipc(unix_xinfo->unix_ipc);

		add_ipc(&unix_xinfo->unix_ipc->ipc, inode % UINT_MAX);
	};
	return true;
}

static void load_xinfo_from_diag_unix(int diagsd, ino_t netns)
{
	struct unix_diag_req udr = {
		.sdiag_family = AF_UNIX,
		.udiag_states = -1, /* set the all bits. */
		.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UNIX_DIAG_SHUTDOWN,
	};

	send_diag_request(diagsd, &udr, sizeof(udr), handle_diag_unix, netns);
}

static void fill_peers_of_unix_oneway_ipcs(void)
{
	struct list_head *e, *enext;

	list_for_each_safe(e, enext, &unix_ipcs) {
		struct unix_ipc *unix_ipc = list_entry(e, struct unix_ipc, unix_ipcs);
		struct unix_ipc *unix_oneway_ipc;

		list_del_init(e);
		if (unix_ipc->ipeer == 0)
			continue;

		unix_oneway_ipc = get_unix_oneway_ipc(unix_ipc->ipeer);
		if (unix_oneway_ipc == NULL)
			continue;
		list_add(e, &unix_oneway_ipc->unix_ipcs);
	};
}

/*
 * 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 * const 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;				\
			FALLTHROUGH;					\
		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;				\
			FALLTHROUGH;					\
		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,
					 enum sysfs_byteorder byteorder)
{
	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;

	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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/tcp",
				     &tcp_xinfo_class,
				     byteorder);
}

/*
 * 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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/udp",
				     &udp_xinfo_class,
				     byteorder);
}

/*
 * 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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/udplite",
				     &udplite_xinfo_class,
				     byteorder);
}

/*
 * RAW
 */
struct raw_xinfo {
	struct l4_xinfo l4;
	uint16_t protocol;
};

static const char *raw_decode_protocol(uint16_t proto, bool decoded_as_ipv6)
{
	struct protoent *pent;

	if (proto == 0) {
		if (decoded_as_ipv6)
			return "hopopts"; /* IPPROTO_HOPOPTS */
		else
			return "ip"; /* IPPROTO_IP */
	}

	switch (proto) {
	case IPPROTO_ICMP:
		return "icmp";
	case IPPROTO_IGMP:
		return "igmp";
	case IPPROTO_IPIP:
		return "ipip";
	case IPPROTO_TCP:
		return "tcp";
	case IPPROTO_EGP:
		return "egp";
	case IPPROTO_PUP:
		return "pup";
	case IPPROTO_UDP:
		return "udp";
	case IPPROTO_IDP:
		return "idp";
	case IPPROTO_TP:
		return "tp";
	case IPPROTO_DCCP:
		return "dccp";
	case IPPROTO_IPV6:
		return "ipv6";
	case IPPROTO_RSVP:
		return "rsvp";
	case IPPROTO_GRE:
		return "gre";
	case IPPROTO_ESP:
		return "esp";
	case IPPROTO_AH:
		return "ah";
	case IPPROTO_MTP:
		return "mtp";
	case IPPROTO_BEETPH:
		return "beetph";
	case IPPROTO_ENCAP:
		return "encap";
	case IPPROTO_PIM:
		return "pim";
	case IPPROTO_COMP:
		return "comp";
#ifdef IPPROTO_L2TP
	case IPPROTO_L2TP:
		return "l2tp";
#endif
	case IPPROTO_SCTP:
		return "sctp";
	case IPPROTO_UDPLITE:
		return "udplite";
	case IPPROTO_MPLS:
		return "mpls";
#ifdef IPPROTO_ETHERNET
	case IPPROTO_ETHERNET:
		return "ethernet";
#endif
	case IPPROTO_RAW:
		return "raw";
#ifdef IPPROTO_MPTCP
	case IPPROTO_MPTCP:
		return "mptcp";
#endif
	case IPPROTO_ROUTING:
		return "routing";
	case IPPROTO_FRAGMENT:
		return "fragment";
	case IPPROTO_ICMPV6:
		return "icmpv6";
	case IPPROTO_NONE:
		return "none";
	case IPPROTO_DSTOPTS:
		return "dstopts";
	case IPPROTO_MH:
		return "mh";
	}

	pent = getprotobynumber(proto);
	if (pent && pent->p_name)
		return pent->p_name;

	return NULL;
}

static char *raw_get_name_common(struct sock_xinfo *sock_xinfo,
				 struct sock *sock  __attribute__((__unused__)),
				 const char *port_label, bool decode_as_protocol)
{
	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 {
		const char *protocol_str = NULL;
		char protocol_buf[ sizeof ("unknown(")
				   + sizeof (stringify_value(UINT16_MA))
				   + sizeof (")") ];

		if (decode_as_protocol) {
			protocol_str = raw_decode_protocol(raw->protocol,
							   class->family == AF_INET6);
			if (protocol_str == NULL) {
				snprintf(protocol_buf, sizeof(protocol_buf),
					 "unknown(%"PRIu16")", raw->protocol);
				protocol_str = protocol_buf;
			}
		} else {
			snprintf(protocol_buf, sizeof(protocol_buf), "%"PRIu16, raw->protocol);
			protocol_str = protocol_buf;
		}

		if (class->is_any_addr(raddr)
		    || !inet_ntop(class->family, raddr, remote_s, sizeof(remote_s)))
			xasprintf(&str, "state=%s %s=%s laddr=%s",
				  st_str,
				  port_label,
				  protocol_str, local_s);
		else
			xasprintf(&str, "state=%s %s=%s laddr=%s raddr=%s",
				  st_str,
				  port_label,
				  protocol_str, 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", true);
}

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_common(struct raw_xinfo *raw,
				   int column_id,
				   char **str,
				   bool decoded_as_ipv6)
{
	const char *pname;

	switch (column_id) {
	case COL_RAW_PROTOCOL:
		pname = raw_decode_protocol(raw->protocol, decoded_as_ipv6);
		if (pname) {
			*str = xstrdup(pname);
			return true;
		}
		FALLTHROUGH;
	case COL_RAW_PROTOCOL_RAW:
		xasprintf(str, "%"PRIu16, raw->protocol);
		return true;
	}

	return false;
}

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;

	return raw_fill_column_common((struct raw_xinfo *)sock_xinfo,
				      column_id, str, 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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/raw",
				     &raw_xinfo_class,
				     byteorder);
}

/*
 * 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", false);
}

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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/icmp",
				     &ping_xinfo_class,
				     byteorder);
}

/*
 * 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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/tcp6",
				     &tcp6_xinfo_class,
				     byteorder);
}

/*
 * 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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/udp6",
				     &udp6_xinfo_class,
				     byteorder);
}

/*
 * 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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/udplite6",
				     &udplite6_xinfo_class,
				     byteorder);
}

/*
 * 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)
{
	if (l3_fill_column_handler(INET6, sock_xinfo, column_id, str))
		return true;

	return raw_fill_column_common((struct raw_xinfo *)sock_xinfo,
				      column_id, str, true);
}

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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/raw6",
				     &raw6_xinfo_class,
				     byteorder);
}

/*
 * 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, enum sysfs_byteorder byteorder)
{
	load_xinfo_from_proc_inet_L4(netns_inode,
				     "/proc/net/icmp6",
				     &ping6_xinfo_class,
				     byteorder);
}

/*
 * 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=%"PRIu32 " groups=%"PRIu32,
			  protocol,
			  nl->lportid, nl->groups);
	else
		xasprintf(&str, "protocol=%s lport=%"PRIu32,
			  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 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
	}

	return NULL;
}

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_str = packet_decode_protocol(pkt->protocol);
	char proto_buf[ sizeof("unknown(") + sizeof (stringify_value(UINT16_MAX)) + sizeof (")") ];
	const char *iface = get_iface_name(sock_xinfo->netns_inode,
					   pkt->iface);

	if (proto_str == NULL) {
		snprintf(proto_buf, sizeof(proto_buf), "unknown(%"PRIu16")", pkt->protocol);
		proto_str = proto_buf;
	}

	if (iface)
		xasprintf(&str, "type=%s protocol=%s iface=%s",
			  type, proto_str, iface);
	else
		xasprintf(&str, "type=%s protocol=%s",
			  type, proto_str);

	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);
		else
			xstrfappend(str, "unknown(%"PRIu16")", pkt->protocol);
		return true;
	}
	case COL_PACKET_PROTOCOL_RAW:
		xasprintf(str, "%"PRIu16, pkt->protocol);
		return true;
	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 " %" SCNx16 " %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);
}

/*
 * VSOCK
 */
#if HAVE_LINUX_VM_SOCKETS_DIAG_H
struct vsock_addr {
	uint32_t cid;
	uint32_t port;
};

struct vsock_xinfo {
	struct sock_xinfo sock;
	uint8_t type;
	uint8_t  st;
	uint8_t  shutdown_mask:3;
	struct vsock_addr local;
	struct vsock_addr remote;
};

static const char *vsock_decode_cid(uint32_t cid)
{
	switch (cid) {
	case VMADDR_CID_ANY:
		return "*";
	case VMADDR_CID_HYPERVISOR:
		return "hypervisor";
#if HAVE_DECL_VMADDR_CID_LOCAL
	case VMADDR_CID_LOCAL:
		return "local";
#endif	/* HAVE_DECL_VMADDR_CID_LOCAL */
	case VMADDR_CID_HOST:
		return "host";
	default:
		return NULL;
	}
}

static const char *vsock_decode_port(uint32_t port)
{
	if (port == VMADDR_PORT_ANY)
		return "*";
	return NULL;
}

static char* vsock_get_addr(struct vsock_addr *addr)
{
	const char *tmp_cid = vsock_decode_cid(addr->cid);
	const char *tmp_port = vsock_decode_port(addr->port);
	char cidstr[BUFSIZ];
	char portstr[BUFSIZ];
	char *str = NULL;

	if (tmp_cid)
		snprintf(cidstr, sizeof(cidstr), "%s", tmp_cid);
	else
		snprintf(cidstr, sizeof(cidstr), "%"PRIu32, addr->cid);

	if (tmp_port)
		snprintf(portstr, sizeof(portstr), "%s", tmp_port);
	else
		snprintf(portstr, sizeof(portstr), "%"PRIu32, addr->port);

	xasprintf(&str, "%s:%s", cidstr, portstr);
	return str;
}

static char *vsock_get_name(struct sock_xinfo *sock_xinfo,
			    struct sock *sock __attribute__((__unused__)))
{
	struct vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;
	char *str = NULL;
	const char *st_str = l4_decode_state(vs->st);
	const char *type_str = sock_decode_type(vs->type);
	char *laddr = vsock_get_addr(&vs->local);

	if (vs->st == TCP_LISTEN)
		xasprintf(&str, "state=%s type=%s laddr=%s",
			  st_str, type_str, laddr);
	else {
		char *raddr = vsock_get_addr(&vs->remote);

		xasprintf(&str, "state=%s type=%s laddr=%s raddr=%s",
			  st_str, type_str, laddr, raddr);
		free(raddr);
	}
	free(laddr);

	return str;
}

static char *vsock_get_type(struct sock_xinfo *sock_xinfo,
			   struct sock *sock __attribute__((__unused__)))
{
	const char *str;
	struct vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;

	str = sock_decode_type(vs->type);
	return xstrdup(str);
}

static char *vsock_get_state(struct sock_xinfo *sock_xinfo,
			     struct sock *sock __attribute__((__unused__)))
{
	const char *str;
	struct vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;

	str = l4_decode_state(vs->st);
	return xstrdup(str);
}

static bool vsock_get_listening(struct sock_xinfo *sock_xinfo,
				struct sock *sock __attribute__((__unused__)))
{
	return ((struct vsock_xinfo *)sock_xinfo)->st == TCP_LISTEN;
}

static bool vsock_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 vsock_xinfo *vs = (struct vsock_xinfo *)sock_xinfo;

	switch (column_id) {
	case COL_VSOCK_LCID:
		xasprintf(str, "%"PRIu32, vs->local.cid);
		return true;
	case COL_VSOCK_RCID:
		xasprintf(str, "%"PRIu32, vs->remote.cid);
		return true;
	case COL_VSOCK_LPORT:
		xasprintf(str, "%"PRIu32, vs->local.port);
		return true;
	case COL_VSOCK_RPORT:
		xasprintf(str, "%"PRIu32, vs->remote.port);
		return true;
	case COL_VSOCK_LADDR:
		*str = vsock_get_addr(&vs->local);
		return true;
	case COL_VSOCK_RADDR:
		*str = vsock_get_addr(&vs->remote);
		return true;
	}
	return false;
}

static const struct sock_xinfo_class vsock_xinfo_class = {
	.get_name = vsock_get_name,
	.get_type = vsock_get_type,
	.get_state = vsock_get_state,
	.get_listening = vsock_get_listening,
	.fill_column = vsock_fill_column,
	.free = NULL,
};

static bool handle_diag_vsock(ino_t netns __attribute__((__unused__)),
			     size_t nlmsg_len, void *nlmsg_data)
{
	const struct vsock_diag_msg *diag = nlmsg_data;
	ino_t inode;
	struct sock_xinfo *xinfo;
	struct vsock_xinfo *vx;

	if (diag->vdiag_family != AF_VSOCK)
		return false;
	DBG(ENDPOINTS, ul_debug("         VSOCK"));
	DBG(ENDPOINTS, ul_debug("         LEN: %zu (>= %zu)", nlmsg_len,
				(size_t)(NLMSG_LENGTH(sizeof(*diag)))));

	if (nlmsg_len < NLMSG_LENGTH(sizeof(*diag)))
		return false;

	inode = (ino_t)diag->vdiag_ino;
	DBG(ENDPOINTS, ul_debug("         inode: %llu", (unsigned long long)inode));

	xinfo = get_sock_xinfo(inode);
	if (xinfo != NULL)
		/* It seems that the same socket reported twice. */
		return true;

	vx = xcalloc(1, sizeof(*vx));
	xinfo = &vx->sock;
	DBG(ENDPOINTS, ul_debug("         xinfo: %p", xinfo));

	xinfo->class = &vsock_xinfo_class;
	xinfo->inode = (ino_t)inode;
	xinfo->netns_inode = (ino_t)netns;

	vx->type = diag->vdiag_type;
	vx->st = diag->vdiag_state;
	vx->shutdown_mask = diag->vdiag_shutdown;
	vx->local.cid = diag->vdiag_src_cid;
	vx->local.port = diag->vdiag_src_port;
	vx->remote.cid = diag->vdiag_dst_cid;
	vx->remote.port = diag->vdiag_dst_port;

	add_sock_info(xinfo);
	return true;
}

static void load_xinfo_from_diag_vsock(int diagsd, ino_t netns)
{
	struct vsock_diag_req vdr;

	memset(&vdr, 0, sizeof(vdr));
	vdr.sdiag_family = AF_VSOCK;
	vdr.vdiag_states =  ~(uint32_t)0;

	send_diag_request(diagsd, &vdr, sizeof(vdr), handle_diag_vsock, netns);
}
#else
static void load_xinfo_from_diag_vsock(int diagsd __attribute__((__unused__)),
				       ino_t netns __attribute__((__unused__)))
{
}
#endif	/* HAVE_LINUX_VM_SOCKETS_DIAG_H */
