/*
* ss.c "sockstat", socket statistics
*
* 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.
*
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <fnmatch.h>
#include <getopt.h>
#include <stdbool.h>
#include "utils.h"
#include "rt_names.h"
#include "ll_map.h"
#include "libnetlink.h"
#include "namespace.h"
#include "SNAPSHOT.h"
#include <linux/tcp.h>
#include <linux/sock_diag.h>
#include <linux/inet_diag.h>
#include <linux/unix_diag.h>
#include <linux/netdevice.h> /* for MAX_ADDR_LEN */
#include <linux/filter.h>
#include <linux/packet_diag.h>
#include <linux/netlink_diag.h>
#define MAGIC_SEQ 123456
#define DIAG_REQUEST(_req, _r) \
struct { \
struct nlmsghdr nlh; \
_r; \
} _req = { \
.nlh = { \
.nlmsg_type = SOCK_DIAG_BY_FAMILY, \
.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST,\
.nlmsg_seq = MAGIC_SEQ, \
.nlmsg_len = sizeof(_req), \
}, \
}
#if HAVE_SELINUX
#include <selinux/selinux.h>
#else
/* Stubs for SELinux functions */
static int is_selinux_enabled(void)
{
return -1;
}
static int getpidcon(pid_t pid, char **context)
{
*context = NULL;
return -1;
}
static int getfilecon(char *path, char **context)
{
*context = NULL;
return -1;
}
static int security_get_initial_context(char *name, char **context)
{
*context = NULL;
return -1;
}
#endif
int resolve_hosts = 0;
int resolve_services = 1;
int preferred_family = AF_UNSPEC;
int show_options = 0;
int show_details = 0;
int show_users = 0;
int show_mem = 0;
int show_tcpinfo = 0;
int show_bpf = 0;
int show_proc_ctx = 0;
int show_sock_ctx = 0;
/* If show_users & show_proc_ctx only do user_ent_hash_build() once */
int user_ent_hash_build_init = 0;
int follow_events = 0;
int netid_width;
int state_width;
int addrp_width;
int addr_width;
int serv_width;
int screen_width;
static const char *TCP_PROTO = "tcp";
static const char *UDP_PROTO = "udp";
static const char *RAW_PROTO = "raw";
static const char *dg_proto = NULL;
enum
{
TCP_DB,
DCCP_DB,
UDP_DB,
RAW_DB,
UNIX_DG_DB,
UNIX_ST_DB,
UNIX_SQ_DB,
PACKET_DG_DB,
PACKET_R_DB,
NETLINK_DB,
MAX_DB
};
#define PACKET_DBM ((1<<PACKET_DG_DB)|(1<<PACKET_R_DB))
#define UNIX_DBM ((1<<UNIX_DG_DB)|(1<<UNIX_ST_DB)|(1<<UNIX_SQ_DB))
#define ALL_DB ((1<<MAX_DB)-1)
#define INET_DBM ((1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB)|(1<<RAW_DB))
enum {
SS_UNKNOWN,
SS_ESTABLISHED,
SS_SYN_SENT,
SS_SYN_RECV,
SS_FIN_WAIT1,
SS_FIN_WAIT2,
SS_TIME_WAIT,
SS_CLOSE,
SS_CLOSE_WAIT,
SS_LAST_ACK,
SS_LISTEN,
SS_CLOSING,
SS_MAX
};
#define SS_ALL ((1 << SS_MAX) - 1)
#define SS_CONN (SS_ALL & ~((1<<SS_LISTEN)|(1<<SS_CLOSE)|(1<<SS_TIME_WAIT)|(1<<SS_SYN_RECV)))
#include "ssfilter.h"
struct filter
{
int dbs;
int states;
int families;
struct ssfilter *f;
};
static const struct filter default_dbs[MAX_DB] = {
[TCP_DB] = {
.states = SS_CONN,
.families = (1 << AF_INET) | (1 << AF_INET6),
},
[DCCP_DB] = {
.states = SS_CONN,
.families = (1 << AF_INET) | (1 << AF_INET6),
},
[UDP_DB] = {
.states = (1 << SS_ESTABLISHED),
.families = (1 << AF_INET) | (1 << AF_INET6),
},
[RAW_DB] = {
.states = (1 << SS_ESTABLISHED),
.families = (1 << AF_INET) | (1 << AF_INET6),
},
[UNIX_DG_DB] = {
.states = (1 << SS_CLOSE),
.families = (1 << AF_UNIX),
},
[UNIX_ST_DB] = {
.states = SS_CONN,
.families = (1 << AF_UNIX),
},
[UNIX_SQ_DB] = {
.states = SS_CONN,
.families = (1 << AF_UNIX),
},
[PACKET_DG_DB] = {
.states = (1 << SS_CLOSE),
.families = (1 << AF_PACKET),
},
[PACKET_R_DB] = {
.states = (1 << SS_CLOSE),
.families = (1 << AF_PACKET),
},
[NETLINK_DB] = {
.states = (1 << SS_CLOSE),
.families = (1 << AF_NETLINK),
},
};
static const struct filter default_afs[AF_MAX] = {
[AF_INET] = {
.dbs = INET_DBM,
.states = SS_CONN,
},
[AF_INET6] = {
.dbs = INET_DBM,
.states = SS_CONN,
},
[AF_UNIX] = {
.dbs = UNIX_DBM,
.states = SS_CONN,
},
[AF_PACKET] = {
.dbs = PACKET_DBM,
.states = (1 << SS_CLOSE),
},
[AF_NETLINK] = {
.dbs = (1 << NETLINK_DB),
.states = (1 << SS_CLOSE),
},
};
static int do_default = 1;
static struct filter current_filter;
static void filter_db_set(struct filter *f, int db)
{
f->states |= default_dbs[db].states;
f->dbs |= 1 << db;
do_default = 0;
}
static void filter_af_set(struct filter *f, int af)
{
f->states |= default_afs[af].states;
f->families |= 1 << af;
do_default = 0;
preferred_family = af;
}
static int filter_af_get(struct filter *f, int af)
{
return f->families & (1 << af);
}
static void filter_default_dbs(struct filter *f)
{
filter_db_set(f, UDP_DB);
filter_db_set(f, DCCP_DB);
filter_db_set(f, TCP_DB);
filter_db_set(f, RAW_DB);
filter_db_set(f, UNIX_ST_DB);
filter_db_set(f, UNIX_DG_DB);
filter_db_set(f, UNIX_SQ_DB);
filter_db_set(f, PACKET_R_DB);
filter_db_set(f, PACKET_DG_DB);
filter_db_set(f, NETLINK_DB);
}
static void filter_states_set(struct filter *f, int states)
{
if (states)
f->states = (f->states | states) & states;
}
static void filter_merge_defaults(struct filter *f)
{
int db;
int af;
for (db = 0; db < MAX_DB; db++) {
if (!(f->dbs & (1 << db)))
continue;
if (!(default_dbs[db].families & f->families))
f->families |= default_dbs[db].families;
}
for (af = 0; af < AF_MAX; af++) {
if (!(f->families & (1 << af)))
continue;
if (!(default_afs[af].dbs & f->dbs))
f->dbs |= default_afs[af].dbs;
}
}
static FILE *generic_proc_open(const char *env, const char *name)
{
const char *p = getenv(env);
char store[128];
if (!p) {
p = getenv("PROC_ROOT") ? : "/proc";
snprintf(store, sizeof(store)-1, "%s/%s", p, name);
p = store;
}
return fopen(p, "r");
}
static FILE *net_tcp_open(void)
{
return generic_proc_open("PROC_NET_TCP", "net/tcp");
}
static FILE *net_tcp6_open(void)
{
return generic_proc_open("PROC_NET_TCP6", "net/tcp6");
}
static FILE *net_udp_open(void)
{
return generic_proc_open("PROC_NET_UDP", "net/udp");
}
static FILE *net_udp6_open(void)
{
return generic_proc_open("PROC_NET_UDP6", "net/udp6");
}
static FILE *net_raw_open(void)
{
return generic_proc_open("PROC_NET_RAW", "net/raw");
}
static FILE *net_raw6_open(void)
{
return generic_proc_open("PROC_NET_RAW6", "net/raw6");
}
static FILE *net_unix_open(void)
{
return generic_proc_open("PROC_NET_UNIX", "net/unix");
}
static FILE *net_packet_open(void)
{
return generic_proc_open("PROC_NET_PACKET", "net/packet");
}
static FILE *net_netlink_open(void)
{
return generic_proc_open("PROC_NET_NETLINK", "net/netlink");
}
static FILE *slabinfo_open(void)
{
return generic_proc_open("PROC_SLABINFO", "slabinfo");
}
static FILE *net_sockstat_open(void)
{
return generic_proc_open("PROC_NET_SOCKSTAT", "net/sockstat");
}
static FILE *net_sockstat6_open(void)
{
return generic_proc_open("PROC_NET_SOCKSTAT6", "net/sockstat6");
}
static FILE *net_snmp_open(void)
{
return generic_proc_open("PROC_NET_SNMP", "net/snmp");
}
static FILE *ephemeral_ports_open(void)
{
return generic_proc_open("PROC_IP_LOCAL_PORT_RANGE", "sys/net/ipv4/ip_local_port_range");
}
struct user_ent {
struct user_ent *next;
unsigned int ino;
int pid;
int fd;
char *process;
char *process_ctx;
char *socket_ctx;
};
#define USER_ENT_HASH_SIZE 256
struct user_ent *user_ent_hash[USER_ENT_HASH_SIZE];
static int user_ent_hashfn(unsigned int ino)
{
int val = (ino >> 24) ^ (ino >> 16) ^ (ino >> 8) ^ ino;
return val & (US