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 .B \-x, \-\-unix
Display Unix domain sockets (alias for -f unix). Display Unix domain sockets (alias for -f unix).
.TP .TP
.B \-S, \-\-sctp
Display SCTP sockets.
.TP
.B \-f FAMILY, \-\-family=FAMILY .B \-f FAMILY, \-\-family=FAMILY
Display sockets of type FAMILY. Display sockets of type FAMILY.
Currently the following families are supported: unix, inet, inet6, link, netlink. 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/filter.h>
#include <linux/packet_diag.h> #include <linux/packet_diag.h>
#include <linux/netlink_diag.h> #include <linux/netlink_diag.h>
#include <linux/sctp.h>
#define MAGIC_SEQ 123456 #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 */ /* If show_users & show_proc_ctx only do user_ent_hash_build() once */
int user_ent_hash_build_init; int user_ent_hash_build_init;
int follow_events; int follow_events;
int sctp_ino;
int netid_width; int netid_width;
int state_width; int state_width;
@ -111,6 +113,7 @@ int serv_width;
int screen_width; int screen_width;
static const char *TCP_PROTO = "tcp"; static const char *TCP_PROTO = "tcp";
static const char *SCTP_PROTO = "sctp";
static const char *UDP_PROTO = "udp"; static const char *UDP_PROTO = "udp";
static const char *RAW_PROTO = "raw"; static const char *RAW_PROTO = "raw";
static const char *dg_proto; static const char *dg_proto;
@ -126,13 +129,14 @@ enum {
PACKET_DG_DB, PACKET_DG_DB,
PACKET_R_DB, PACKET_R_DB,
NETLINK_DB, NETLINK_DB,
SCTP_DB,
MAX_DB MAX_DB
}; };
#define PACKET_DBM ((1<<PACKET_DG_DB)|(1<<PACKET_R_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 UNIX_DBM ((1<<UNIX_DG_DB)|(1<<UNIX_ST_DB)|(1<<UNIX_SQ_DB))
#define ALL_DB ((1<<MAX_DB)-1) #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 { enum {
SS_UNKNOWN, SS_UNKNOWN,
@ -150,6 +154,17 @@ enum {
SS_MAX 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_ALL ((1 << SS_MAX) - 1)
#define SS_CONN (SS_ALL & ~((1<<SS_LISTEN)|(1<<SS_CLOSE)|(1<<SS_TIME_WAIT)|(1<<SS_SYN_RECV))) #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), .states = (1 << SS_CLOSE),
.families = (1 << AF_NETLINK), .families = (1 << AF_NETLINK),
}, },
[SCTP_DB] = {
.states = SS_CONN,
.families = (1 << AF_INET) | (1 << AF_INET6),
},
}; };
static const struct filter default_afs[AF_MAX] = { 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_R_DB);
filter_db_set(f, PACKET_DG_DB); filter_db_set(f, PACKET_DG_DB);
filter_db_set(f, NETLINK_DB); filter_db_set(f, NETLINK_DB);
filter_db_set(f, SCTP_DB);
} }
static void filter_states_set(struct filter *f, int states) static void filter_states_set(struct filter *f, int states)
@ -705,6 +725,17 @@ static const char *sstate_name[] = {
[SS_CLOSING] = "CLOSING", [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[] = { static const char *sstate_namel[] = {
"UNKNOWN", "UNKNOWN",
[SS_ESTABLISHED] = "established", [SS_ESTABLISHED] = "established",
@ -793,12 +824,30 @@ struct tcpstat {
struct tcp_bbr_info *bbr_info; 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) static void sock_state_print(struct sockstat *s, const char *sock_name)
{ {
if (netid_width) if (netid_width)
printf("%-*s ", netid_width, sock_name); printf("%-*s ", netid_width,
if (state_width) is_sctp_assoc(s, sock_name) ? "" : sock_name);
printf("%-*s ", state_width, sstate_name[s->state]); 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); printf("%-6d %-6d ", s->rq, s->wq);
} }
@ -908,6 +957,8 @@ static void init_service_resolver(void)
c->proto = TCP_PROTO; c->proto = TCP_PROTO;
else if (strcmp(proto, UDP_PROTO) == 0) else if (strcmp(proto, UDP_PROTO) == 0)
c->proto = UDP_PROTO; c->proto = UDP_PROTO;
else if (strcmp(proto, SCTP_PROTO) == 0)
c->proto = SCTP_PROTO;
else else
c->proto = NULL; c->proto = NULL;
c->next = rlist; c->next = rlist;
@ -1679,6 +1730,8 @@ static char *proto_name(int protocol)
return "udp"; return "udp";
case IPPROTO_TCP: case IPPROTO_TCP:
return "tcp"; return "tcp";
case IPPROTO_SCTP:
return "sctp";
case IPPROTO_DCCP: case IPPROTO_DCCP:
return "dccp"; return "dccp";
} }
@ -1771,6 +1824,56 @@ static char *sprint_bw(char *buf, double bw)
return buf; 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) static void tcp_stats_print(struct tcpstat *s)
{ {
char b1[64]; 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) static int tcp_show_line(char *line, const struct filter *f, int family)
{ {
int rto = 0, ato = 0; 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) static void parse_diag_msg(struct nlmsghdr *nlh, struct sockstat *s)
{ {
struct rtattr *tb[INET_DIAG_MAX+1]; 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.timer = r->idiag_timer;
t.timeout = r->idiag_expires; t.timeout = r->idiag_expires;
t.retrans = r->idiag_retrans; 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) { if (show_details) {
@ -2242,8 +2413,12 @@ static int inet_show_sock(struct nlmsghdr *nlh,
if (show_mem || show_tcpinfo) { if (show_mem || show_tcpinfo) {
printf("\n\t"); 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"); printf("\n");
return 0; return 0;
@ -2627,6 +2802,17 @@ outerr:
} while (0); } 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) 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" " -6, --ipv6 display only IP version 6 sockets\n"
" -0, --packet display PACKET sockets\n" " -0, --packet display PACKET sockets\n"
" -t, --tcp display only TCP sockets\n" " -t, --tcp display only TCP sockets\n"
" -S, --sctp display only SCTP sockets\n"
" -u, --udp display only UDP sockets\n" " -u, --udp display only UDP sockets\n"
" -d, --dccp display only DCCP sockets\n" " -d, --dccp display only DCCP sockets\n"
" -w, --raw display only RAW sockets\n" " -w, --raw display only RAW sockets\n"
@ -3822,6 +4009,7 @@ static const struct option long_opts[] = {
{ "events", 0, 0, 'E' }, { "events", 0, 0, 'E' },
{ "dccp", 0, 0, 'd' }, { "dccp", 0, 0, 'd' },
{ "tcp", 0, 0, 't' }, { "tcp", 0, 0, 't' },
{ "sctp", 0, 0, 'S' },
{ "udp", 0, 0, 'u' }, { "udp", 0, 0, 'u' },
{ "raw", 0, 0, 'w' }, { "raw", 0, 0, 'w' },
{ "unix", 0, 0, 'x' }, { "unix", 0, 0, 'x' },
@ -3857,7 +4045,7 @@ int main(int argc, char *argv[])
int ch; int ch;
int state_filter = 0; 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) { long_opts, NULL)) != EOF) {
switch (ch) { switch (ch) {
case 'n': case 'n':
@ -3896,6 +4084,9 @@ int main(int argc, char *argv[])
case 't': case 't':
filter_db_set(&current_filter, TCP_DB); filter_db_set(&current_filter, TCP_DB);
break; break;
case 'S':
filter_db_set(&current_filter, SCTP_DB);
break;
case 'u': case 'u':
filter_db_set(&current_filter, UDP_DB); filter_db_set(&current_filter, UDP_DB);
break; break;
@ -3960,6 +4151,7 @@ int main(int argc, char *argv[])
filter_db_set(&current_filter, UDP_DB); filter_db_set(&current_filter, UDP_DB);
filter_db_set(&current_filter, DCCP_DB); filter_db_set(&current_filter, DCCP_DB);
filter_db_set(&current_filter, TCP_DB); filter_db_set(&current_filter, TCP_DB);
filter_db_set(&current_filter, SCTP_DB);
filter_db_set(&current_filter, RAW_DB); filter_db_set(&current_filter, RAW_DB);
} else if (strcmp(p, "udp") == 0) { } else if (strcmp(p, "udp") == 0) {
filter_db_set(&current_filter, UDP_DB); filter_db_set(&current_filter, UDP_DB);
@ -3967,6 +4159,8 @@ int main(int argc, char *argv[])
filter_db_set(&current_filter, DCCP_DB); filter_db_set(&current_filter, DCCP_DB);
} else if (strcmp(p, "tcp") == 0) { } else if (strcmp(p, "tcp") == 0) {
filter_db_set(&current_filter, TCP_DB); 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) { } else if (strcmp(p, "raw") == 0) {
filter_db_set(&current_filter, RAW_DB); filter_db_set(&current_filter, RAW_DB);
} else if (strcmp(p, "unix") == 0) { } else if (strcmp(p, "unix") == 0) {
@ -4091,7 +4285,7 @@ int main(int argc, char *argv[])
filter_merge_defaults(&current_filter); filter_merge_defaults(&current_filter);
if (resolve_services && resolve_hosts && 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(); init_service_resolver();
@ -4207,6 +4401,8 @@ int main(int argc, char *argv[])
tcp_show(&current_filter, IPPROTO_TCP); tcp_show(&current_filter, IPPROTO_TCP);
if (current_filter.dbs & (1<<DCCP_DB)) if (current_filter.dbs & (1<<DCCP_DB))
tcp_show(&current_filter, IPPROTO_DCCP); 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) if (show_users || show_proc_ctx || show_sock_ctx)
user_ent_destroy(); user_ent_destroy();