ss: Add support for SCTP protocol

This makes use of the sctp_diag interface recently added to the kernel.

Joint work with Xin Long who provided the PoC implementation which I
merely polished up a bit.

Signed-off-by: Phil Sutter <phil@nwl.cc>
This commit is contained in:
Phil Sutter 2016-11-09 12:12:24 +01:00 committed by Stephen Hemminger
parent 5dec02d7b4
commit f89d46ad63
2 changed files with 207 additions and 8 deletions

View File

@ -122,6 +122,9 @@ Display RAW sockets.
.B \-x, \-\-unix
Display Unix domain sockets (alias for -f unix).
.TP
.B \-S, \-\-sctp
Display SCTP sockets.
.TP
.B \-f FAMILY, \-\-family=FAMILY
Display sockets of type FAMILY.
Currently the following families are supported: unix, inet, inet6, link, netlink.

212
misc/ss.c
View File

@ -43,6 +43,7 @@
#include <linux/filter.h>
#include <linux/packet_diag.h>
#include <linux/netlink_diag.h>
#include <linux/sctp.h>
#define MAGIC_SEQ 123456
@ -102,6 +103,7 @@ int show_header = 1;
/* If show_users & show_proc_ctx only do user_ent_hash_build() once */
int user_ent_hash_build_init;
int follow_events;
int sctp_ino;
int netid_width;
int state_width;
@ -111,6 +113,7 @@ int serv_width;
int screen_width;
static const char *TCP_PROTO = "tcp";
static const char *SCTP_PROTO = "sctp";
static const char *UDP_PROTO = "udp";
static const char *RAW_PROTO = "raw";
static const char *dg_proto;
@ -126,13 +129,14 @@ enum {
PACKET_DG_DB,
PACKET_R_DB,
NETLINK_DB,
SCTP_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))
#define INET_DBM ((1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB)|(1<<RAW_DB)|(1<<SCTP_DB))
enum {
SS_UNKNOWN,
@ -150,6 +154,17 @@ enum {
SS_MAX
};
enum {
SCTP_STATE_CLOSED = 0,
SCTP_STATE_COOKIE_WAIT = 1,
SCTP_STATE_COOKIE_ECHOED = 2,
SCTP_STATE_ESTABLISHED = 3,
SCTP_STATE_SHUTDOWN_PENDING = 4,
SCTP_STATE_SHUTDOWN_SENT = 5,
SCTP_STATE_SHUTDOWN_RECEIVED = 6,
SCTP_STATE_SHUTDOWN_ACK_SENT = 7,
};
#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)))
@ -204,6 +219,10 @@ static const struct filter default_dbs[MAX_DB] = {
.states = (1 << SS_CLOSE),
.families = (1 << AF_NETLINK),
},
[SCTP_DB] = {
.states = SS_CONN,
.families = (1 << AF_INET) | (1 << AF_INET6),
},
};
static const struct filter default_afs[AF_MAX] = {
@ -264,6 +283,7 @@ static void filter_default_dbs(struct filter *f)
filter_db_set(f, PACKET_R_DB);
filter_db_set(f, PACKET_DG_DB);
filter_db_set(f, NETLINK_DB);
filter_db_set(f, SCTP_DB);
}
static void filter_states_set(struct filter *f, int states)
@ -705,6 +725,17 @@ static const char *sstate_name[] = {
[SS_CLOSING] = "CLOSING",
};
static const char *sctp_sstate_name[] = {
[SCTP_STATE_CLOSED] = "CLOSED",
[SCTP_STATE_COOKIE_WAIT] = "COOKIE_WAIT",
[SCTP_STATE_COOKIE_ECHOED] = "COOKIE_ECHOED",
[SCTP_STATE_ESTABLISHED] = "ESTAB",
[SCTP_STATE_SHUTDOWN_PENDING] = "SHUTDOWN_PENDING",
[SCTP_STATE_SHUTDOWN_SENT] = "SHUTDOWN_SENT",
[SCTP_STATE_SHUTDOWN_RECEIVED] = "SHUTDOWN_RECEIVED",
[SCTP_STATE_SHUTDOWN_ACK_SENT] = "ACK_SENT",
};
static const char *sstate_namel[] = {
"UNKNOWN",
[SS_ESTABLISHED] = "established",
@ -793,12 +824,30 @@ struct tcpstat {
struct tcp_bbr_info *bbr_info;
};
/* SCTP assocs share the same inode number with their parent endpoint. So if we
* have seen the inode number before, it must be an assoc instead of the next
* endpoint. */
static bool is_sctp_assoc(struct sockstat *s, const char *sock_name)
{
if (strcmp(sock_name, "sctp"))
return false;
if (!sctp_ino || sctp_ino != s->ino)
return false;
return true;
}
static void sock_state_print(struct sockstat *s, const char *sock_name)
{
if (netid_width)
printf("%-*s ", netid_width, sock_name);
if (state_width)
printf("%-*s ", state_width, sstate_name[s->state]);
printf("%-*s ", netid_width,
is_sctp_assoc(s, sock_name) ? "" : sock_name);
if (state_width) {
if (is_sctp_assoc(s, sock_name))
printf("`- %-*s ", state_width - 3,
sctp_sstate_name[s->state]);
else
printf("%-*s ", state_width, sstate_name[s->state]);
}
printf("%-6d %-6d ", s->rq, s->wq);
}
@ -908,6 +957,8 @@ static void init_service_resolver(void)
c->proto = TCP_PROTO;
else if (strcmp(proto, UDP_PROTO) == 0)
c->proto = UDP_PROTO;
else if (strcmp(proto, SCTP_PROTO) == 0)
c->proto = SCTP_PROTO;
else
c->proto = NULL;
c->next = rlist;
@ -1679,6 +1730,8 @@ static char *proto_name(int protocol)
return "udp";
case IPPROTO_TCP:
return "tcp";
case IPPROTO_SCTP:
return "sctp";
case IPPROTO_DCCP:
return "dccp";
}
@ -1771,6 +1824,56 @@ static char *sprint_bw(char *buf, double bw)
return buf;
}
static void sctp_stats_print(struct sctp_info *s)
{
if (s->sctpi_tag)
printf(" tag:%x", s->sctpi_tag);
if (s->sctpi_state)
printf(" state:%s", sctp_sstate_name[s->sctpi_state]);
if (s->sctpi_rwnd)
printf(" rwnd:%d", s->sctpi_rwnd);
if (s->sctpi_unackdata)
printf(" unackdata:%d", s->sctpi_unackdata);
if (s->sctpi_penddata)
printf(" penddata:%d", s->sctpi_penddata);
if (s->sctpi_instrms)
printf(" instrms:%d", s->sctpi_instrms);
if (s->sctpi_outstrms)
printf(" outstrms:%d", s->sctpi_outstrms);
if (s->sctpi_inqueue)
printf(" inqueue:%d", s->sctpi_inqueue);
if (s->sctpi_outqueue)
printf(" outqueue:%d", s->sctpi_outqueue);
if (s->sctpi_overall_error)
printf(" overerr:%d", s->sctpi_overall_error);
if (s->sctpi_max_burst)
printf(" maxburst:%d", s->sctpi_max_burst);
if (s->sctpi_maxseg)
printf(" maxseg:%d", s->sctpi_maxseg);
if (s->sctpi_peer_rwnd)
printf(" prwnd:%d", s->sctpi_peer_rwnd);
if (s->sctpi_peer_tag)
printf(" ptag:%x", s->sctpi_peer_tag);
if (s->sctpi_peer_capable)
printf(" pcapable:%d", s->sctpi_peer_capable);
if (s->sctpi_peer_sack)
printf(" psack:%d", s->sctpi_peer_sack);
if (s->sctpi_s_autoclose)
printf(" autoclose:%d", s->sctpi_s_autoclose);
if (s->sctpi_s_adaptation_ind)
printf(" adapind:%d", s->sctpi_s_adaptation_ind);
if (s->sctpi_s_pd_point)
printf(" pdpoint:%d", s->sctpi_s_pd_point);
if (s->sctpi_s_nodelay)
printf(" nodealy:%d", s->sctpi_s_nodelay);
if (s->sctpi_s_disable_fragments)
printf(" nofrag:%d", s->sctpi_s_disable_fragments);
if (s->sctpi_s_v4mapped)
printf(" v4mapped:%d", s->sctpi_s_v4mapped);
if (s->sctpi_s_frag_interleave)
printf(" fraginl:%d", s->sctpi_s_frag_interleave);
}
static void tcp_stats_print(struct tcpstat *s)
{
char b1[64];
@ -1902,6 +2005,13 @@ static void tcp_timer_print(struct tcpstat *s)
}
}
static void sctp_timer_print(struct tcpstat *s)
{
if (s->timer)
printf(" timer:(T3_RTX,%s,%d)",
print_ms_timer(s->timeout), s->retrans);
}
static int tcp_show_line(char *line, const struct filter *f, int family)
{
int rto = 0, ato = 0;
@ -2168,6 +2278,64 @@ static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
}
}
static const char *format_host_sa(struct sockaddr_storage *sa)
{
union {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
} *saddr = (void *)sa;
switch (sa->ss_family) {
case AF_INET:
return format_host(AF_INET, 4, &saddr->sin.sin_addr);
case AF_INET6:
return format_host(AF_INET6, 16, &saddr->sin6.sin6_addr);
default:
return "";
}
}
static void sctp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
struct rtattr *tb[])
{
struct sockaddr_storage *sa;
int len;
print_skmeminfo(tb, INET_DIAG_SKMEMINFO);
if (tb[INET_DIAG_LOCALS]) {
len = RTA_PAYLOAD(tb[INET_DIAG_LOCALS]);
sa = RTA_DATA(tb[INET_DIAG_LOCALS]);
printf("locals:%s", format_host_sa(sa));
for (sa++, len -= sizeof(*sa); len > 0; sa++, len -= sizeof(*sa))
printf(",%s", format_host_sa(sa));
}
if (tb[INET_DIAG_PEERS]) {
len = RTA_PAYLOAD(tb[INET_DIAG_PEERS]);
sa = RTA_DATA(tb[INET_DIAG_PEERS]);
printf(" peers:%s", format_host_sa(sa));
for (sa++, len -= sizeof(*sa); len > 0; sa++, len -= sizeof(*sa))
printf(",%s", format_host_sa(sa));
}
if (tb[INET_DIAG_INFO]) {
struct sctp_info *info;
len = RTA_PAYLOAD(tb[INET_DIAG_INFO]);
/* workaround for older kernels with less fields */
if (len < sizeof(*info)) {
info = alloca(sizeof(*info));
memcpy(info, RTA_DATA(tb[INET_DIAG_INFO]), len);
memset((char *)info + len, 0, sizeof(*info) - len);
} else
info = RTA_DATA(tb[INET_DIAG_INFO]);
sctp_stats_print(info);
}
}
static void parse_diag_msg(struct nlmsghdr *nlh, struct sockstat *s)
{
struct rtattr *tb[INET_DIAG_MAX+1];
@ -2221,7 +2389,10 @@ static int inet_show_sock(struct nlmsghdr *nlh,
t.timer = r->idiag_timer;
t.timeout = r->idiag_expires;
t.retrans = r->idiag_retrans;
tcp_timer_print(&t);
if (protocol == IPPROTO_SCTP)
sctp_timer_print(&t);
else
tcp_timer_print(&t);
}
if (show_details) {
@ -2242,8 +2413,12 @@ static int inet_show_sock(struct nlmsghdr *nlh,
if (show_mem || show_tcpinfo) {
printf("\n\t");
tcp_show_info(nlh, r, tb);
if (protocol == IPPROTO_SCTP)
sctp_show_info(nlh, r, tb);
else
tcp_show_info(nlh, r, tb);
}
sctp_ino = s->ino;
printf("\n");
return 0;
@ -2627,6 +2802,17 @@ outerr:
} while (0);
}
static int sctp_show(struct filter *f)
{
if (!filter_af_get(f, AF_INET) && !filter_af_get(f, AF_INET6))
return 0;
if (!getenv("PROC_NET_SCTP") && !getenv("PROC_ROOT")
&& inet_show_netlink(f, NULL, IPPROTO_SCTP) == 0)
return 0;
return 0;
}
static int dgram_show_line(char *line, const struct filter *f, int family)
{
@ -3740,6 +3926,7 @@ static void _usage(FILE *dest)
" -6, --ipv6 display only IP version 6 sockets\n"
" -0, --packet display PACKET sockets\n"
" -t, --tcp display only TCP sockets\n"
" -S, --sctp display only SCTP sockets\n"
" -u, --udp display only UDP sockets\n"
" -d, --dccp display only DCCP sockets\n"
" -w, --raw display only RAW sockets\n"
@ -3822,6 +4009,7 @@ static const struct option long_opts[] = {
{ "events", 0, 0, 'E' },
{ "dccp", 0, 0, 'd' },
{ "tcp", 0, 0, 't' },
{ "sctp", 0, 0, 'S' },
{ "udp", 0, 0, 'u' },
{ "raw", 0, 0, 'w' },
{ "unix", 0, 0, 'x' },
@ -3857,7 +4045,7 @@ int main(int argc, char *argv[])
int ch;
int state_filter = 0;
while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spbEf:miA:D:F:vVzZN:KH",
while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spbEf:miA:D:F:vVzZN:KHS",
long_opts, NULL)) != EOF) {
switch (ch) {
case 'n':
@ -3896,6 +4084,9 @@ int main(int argc, char *argv[])
case 't':
filter_db_set(&current_filter, TCP_DB);
break;
case 'S':
filter_db_set(&current_filter, SCTP_DB);
break;
case 'u':
filter_db_set(&current_filter, UDP_DB);
break;
@ -3960,6 +4151,7 @@ int main(int argc, char *argv[])
filter_db_set(&current_filter, UDP_DB);
filter_db_set(&current_filter, DCCP_DB);
filter_db_set(&current_filter, TCP_DB);
filter_db_set(&current_filter, SCTP_DB);
filter_db_set(&current_filter, RAW_DB);
} else if (strcmp(p, "udp") == 0) {
filter_db_set(&current_filter, UDP_DB);
@ -3967,6 +4159,8 @@ int main(int argc, char *argv[])
filter_db_set(&current_filter, DCCP_DB);
} else if (strcmp(p, "tcp") == 0) {
filter_db_set(&current_filter, TCP_DB);
} else if (strcmp(p, "sctp") == 0) {
filter_db_set(&current_filter, SCTP_DB);
} else if (strcmp(p, "raw") == 0) {
filter_db_set(&current_filter, RAW_DB);
} else if (strcmp(p, "unix") == 0) {
@ -4091,7 +4285,7 @@ int main(int argc, char *argv[])
filter_merge_defaults(&current_filter);
if (resolve_services && resolve_hosts &&
(current_filter.dbs&(UNIX_DBM|(1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB))))
(current_filter.dbs&(UNIX_DBM|(1<<TCP_DB)|(1<<UDP_DB)|(1<<DCCP_DB)|(1<<SCTP_DB))))
init_service_resolver();
@ -4207,6 +4401,8 @@ int main(int argc, char *argv[])
tcp_show(&current_filter, IPPROTO_TCP);
if (current_filter.dbs & (1<<DCCP_DB))
tcp_show(&current_filter, IPPROTO_DCCP);
if (current_filter.dbs & (1<<SCTP_DB))
sctp_show(&current_filter);
if (show_users || show_proc_ctx || show_sock_ctx)
user_ent_destroy();