naccyde / rpms / iproute

Forked from rpms/iproute 9 months ago
Clone
Blob Blame History Raw
From 48d997511c1537ec8e2d6227251f8be1ede064d4 Mon Sep 17 00:00:00 2001
From: Phil Sutter <psutter@redhat.com>
Date: Wed, 1 Feb 2017 12:10:00 +0100
Subject: [PATCH] ss: Add support for SCTP protocol

Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1063934
Upstream Status: iproute2.git commit f89d46ad63f6f
Conflicts:
* missing commit 6885e3bf8efa3
  ("ss: Include -E option for socket destroy events")
* missing commit acd1e437befbb ("misc: fix style issues")
* missing commit 82d73ea03a149 ("ss: Refactor inet_show_sock")
* missing other commandline flags/features
* fixed for missing commit a418e451643e7
  ("make format_host non-reentrant by default")

commit f89d46ad63f6f606f777da964205bc53b2197cfa
Author: Phil Sutter <phil@nwl.cc>
Date:   Wed Nov 9 12:12:24 2016 +0100

    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>
---
 man/man8/ss.8 |   3 +
 misc/ss.c     | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 210 insertions(+), 8 deletions(-)

diff --git a/man/man8/ss.8 b/man/man8/ss.8
index f4d5264..b02a68c 100644
--- a/man/man8/ss.8
+++ b/man/man8/ss.8
@@ -114,6 +114,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.
diff --git a/misc/ss.c b/misc/ss.c
index 45fb4b0..d438428 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -42,6 +42,7 @@
 #include <linux/filter.h>
 #include <linux/packet_diag.h>
 #include <linux/netlink_diag.h>
+#include <linux/sctp.h>
 
 #define MAGIC_SEQ 123456
 
@@ -99,6 +100,7 @@ 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 sctp_ino;
 
 int netid_width;
 int state_width;
@@ -108,6 +110,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 = NULL;
@@ -124,13 +127,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,
@@ -148,6 +152,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)))
 
@@ -202,6 +217,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] = {
@@ -262,6 +281,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)
@@ -709,6 +729,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",
@@ -792,12 +823,30 @@ struct tcpstat
 	struct dctcpstat    *dctcp;
 };
 
+/* 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);
 }
@@ -895,6 +944,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;
@@ -1566,6 +1617,8 @@ static char *proto_name(int protocol)
 		return "udp";
 	case IPPROTO_TCP:
 		return "tcp";
+	case IPPROTO_SCTP:
+		return "sctp";
 	case IPPROTO_DCCP:
 		return "dccp";
 	}
@@ -1658,6 +1711,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];
@@ -1762,6 +1865,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;
@@ -2007,6 +2117,67 @@ static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
 	}
 }
 
+static const char *format_host_sa(struct sockaddr_storage *sa)
+{
+	char buf[1024];
+	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,
+				   buf, sizeof(buf));
+	case AF_INET6:
+		return format_host(AF_INET6, 16, &saddr->sin6.sin6_addr,
+				   buf, sizeof(buf));
+	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 int inet_show_sock(struct nlmsghdr *nlh, struct filter *f, int protocol)
 {
 	struct rtattr * tb[INET_DIAG_MAX+1];
@@ -2047,7 +2218,10 @@ static int inet_show_sock(struct nlmsghdr *nlh, struct filter *f, int protocol)
 		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) {
@@ -2066,8 +2240,12 @@ static int inet_show_sock(struct nlmsghdr *nlh, struct filter *f, int protocol)
 
 	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;
@@ -2392,6 +2570,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)
 {
@@ -3343,6 +3532,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"
@@ -3419,6 +3609,7 @@ static const struct option long_opts[] = {
 	{ "bpf", 0, 0, 'b' },
 	{ "dccp", 0, 0, 'd' },
 	{ "tcp", 0, 0, 't' },
+	{ "sctp", 0, 0, 'S' },
 	{ "udp", 0, 0, 'u' },
 	{ "raw", 0, 0, 'w' },
 	{ "unix", 0, 0, 'x' },
@@ -3452,7 +3643,7 @@ int main(int argc, char *argv[])
 	int ch;
 	int state_filter = 0;
 
-	while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spbf:miA:D:F:vVzZN:",
+	while ((ch = getopt_long(argc, argv, "dhaletuwxnro460spbf:miA:D:F:vVzZN:S",
 				 long_opts, NULL)) != EOF) {
 		switch(ch) {
 		case 'n':
@@ -3488,6 +3679,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;
@@ -3551,6 +3745,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);
@@ -3558,6 +3753,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) {
@@ -3682,7 +3879,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();
 
 
@@ -3790,6 +3987,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();
-- 
1.8.3.1