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:
parent
5dec02d7b4
commit
f89d46ad63
|
|
@ -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
212
misc/ss.c
|
|
@ -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(¤t_filter, TCP_DB);
|
||||
break;
|
||||
case 'S':
|
||||
filter_db_set(¤t_filter, SCTP_DB);
|
||||
break;
|
||||
case 'u':
|
||||
filter_db_set(¤t_filter, UDP_DB);
|
||||
break;
|
||||
|
|
@ -3960,6 +4151,7 @@ int main(int argc, char *argv[])
|
|||
filter_db_set(¤t_filter, UDP_DB);
|
||||
filter_db_set(¤t_filter, DCCP_DB);
|
||||
filter_db_set(¤t_filter, TCP_DB);
|
||||
filter_db_set(¤t_filter, SCTP_DB);
|
||||
filter_db_set(¤t_filter, RAW_DB);
|
||||
} else if (strcmp(p, "udp") == 0) {
|
||||
filter_db_set(¤t_filter, UDP_DB);
|
||||
|
|
@ -3967,6 +4159,8 @@ int main(int argc, char *argv[])
|
|||
filter_db_set(¤t_filter, DCCP_DB);
|
||||
} else if (strcmp(p, "tcp") == 0) {
|
||||
filter_db_set(¤t_filter, TCP_DB);
|
||||
} else if (strcmp(p, "sctp") == 0) {
|
||||
filter_db_set(¤t_filter, SCTP_DB);
|
||||
} else if (strcmp(p, "raw") == 0) {
|
||||
filter_db_set(¤t_filter, RAW_DB);
|
||||
} else if (strcmp(p, "unix") == 0) {
|
||||
|
|
@ -4091,7 +4285,7 @@ int main(int argc, char *argv[])
|
|||
filter_merge_defaults(¤t_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(¤t_filter, IPPROTO_TCP);
|
||||
if (current_filter.dbs & (1<<DCCP_DB))
|
||||
tcp_show(¤t_filter, IPPROTO_DCCP);
|
||||
if (current_filter.dbs & (1<<SCTP_DB))
|
||||
sctp_show(¤t_filter);
|
||||
|
||||
if (show_users || show_proc_ctx || show_sock_ctx)
|
||||
user_ent_destroy();
|
||||
|
|
|
|||
Loading…
Reference in New Issue