Merge branch 'master' into next

Signed-off-by: David Ahern <dsahern@gmail.com>
diff --git a/lib/utils.c b/lib/utils.c
index c6f19ce..c98021d 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -1628,9 +1628,9 @@
 	double tmp = time;
 
 	if (tmp >= TIME_UNITS_PER_SEC)
-		snprintf(buf, len, "%.1fs", tmp/TIME_UNITS_PER_SEC);
+		snprintf(buf, len, "%.3gs", tmp/TIME_UNITS_PER_SEC);
 	else if (tmp >= TIME_UNITS_PER_SEC/1000)
-		snprintf(buf, len, "%.1fms", tmp/(TIME_UNITS_PER_SEC/1000));
+		snprintf(buf, len, "%.3gms", tmp/(TIME_UNITS_PER_SEC/1000));
 	else
 		snprintf(buf, len, "%uus", time);
 }
@@ -1681,11 +1681,11 @@
 	double nsec = time;
 
 	if (time >= NSEC_PER_SEC)
-		snprintf(buf, len, "%.3fs", nsec/NSEC_PER_SEC);
+		snprintf(buf, len, "%.3gs", nsec/NSEC_PER_SEC);
 	else if (time >= NSEC_PER_MSEC)
-		snprintf(buf, len, "%.3fms", nsec/NSEC_PER_MSEC);
+		snprintf(buf, len, "%.3gms", nsec/NSEC_PER_MSEC);
 	else if (time >= NSEC_PER_USEC)
-		snprintf(buf, len, "%.3fus", nsec/NSEC_PER_USEC);
+		snprintf(buf, len, "%.3gus", nsec/NSEC_PER_USEC);
 	else
 		snprintf(buf, len, "%lldns", time);
 }
diff --git a/man/man8/tc-ct.8 b/man/man8/tc-ct.8
new file mode 100644
index 0000000..45d2932
--- /dev/null
+++ b/man/man8/tc-ct.8
@@ -0,0 +1,107 @@
+.TH "ct action in tc" 8 "14 May 2020" "iproute2" "Linux"
+.SH NAME
+ct \- tc connection tracking action
+.SH SYNOPSIS
+.in +8
+.ti -8
+.BR "tc ... action ct commit [ force ] [ zone "
+.IR ZONE
+.BR "] [ mark "
+.IR MASKED_MARK
+.BR "] [ label "
+.IR MASKED_LABEL
+.BR "] [ nat "
+.IR NAT_SPEC
+.BR "]"
+
+.ti -8
+.BR "tc ... action ct [ nat ] [ zone "
+.IR ZONE
+.BR "]"
+
+.ti -8
+.BR "tc ... action ct clear"
+
+.SH DESCRIPTION
+The ct action is a tc action for sending packets and interacting with the netfilter conntrack module.
+
+It can (as shown in the synopsis, in order):
+
+Send the packet to conntrack, and commit the connection, while configuring
+a 32bit mark, 128bit label, and src/dst nat.
+
+Send the packet to conntrack, which will mark the packet with the connection's state and
+configured metadata (mark/label), and execute previous configured nat.
+
+Clear the packet's of previous connection tracking state.
+
+.SH OPTIONS
+.TP
+.BI zone " ZONE"
+Specify a conntrack zone number on which to send the packet to conntrack.
+.TP
+.BI mark " MASKED_MARK"
+Specify a masked 32bit mark to set for the connection (only valid with commit).
+.TP
+.BI label " MASKED_LABEL"
+Specify a masked 128bit label to set for the connection (only valid with commit).
+.TP
+.BI nat " NAT_SPEC"
+.BI Where " NAT_SPEC " ":= {src|dst} addr" " addr1" "[-" "addr2" "] [port " "port1" "[-" "port2" "]]"
+
+Specify src/dst and range of nat to configure for the connection (only valid with commit).
+.RS
+.TP
+src/dst - configure src or dst nat
+.TP
+.BI  "" "addr1" "/" "addr2" " - IPv4/IPv6 addresses"
+.TP
+.BI  "" "port1" "/" "port2" " - Port numbers"
+.RE
+.TP
+.BI nat
+Restore any previous configured nat.
+.TP
+.BI clear
+Remove any conntrack state and metadata (mark/label) from the packet (must only option specified).
+.TP
+.BI force
+Forces conntrack direction for a previously commited connections, so that current direction will become the original direction (only valid with commit).
+
+.SH EXAMPLES
+Example showing natted firewall in conntrack zone 2, and conntrack mark usage:
+.EX
+
+#Add ingress qdisc on eth0 and eth1 interfaces
+.nf
+$ tc qdisc add dev eth0 handle ingress
+$ tc qdisc add dev eth1 handle ingress
+
+#Setup filters on eth0, allowing opening new connections in zone 2, and doing src nat + mark for each new connection
+$ tc filter add dev eth0 ingress prio 1 chain 0 proto ip flower ip_proto tcp ct_state -trk \\
+action ct zone 2 pipe action goto chain 2
+$ tc filter add dev eth0 ingress prio 1 chain 2 proto ip flower ct_state +trk+new \\
+action ct zone 2 commit mark 0xbb nat src addr 5.5.5.7 pipe action mirred egress redirect dev eth1
+$ tc filter add dev eth0 ingress prio 1 chain 2 proto ip flower ct_zone 2 ct_mark 0xbb ct_state +trk+est \\
+action ct nat pipe action mirred egress redirect dev eth1
+
+#Setup filters on eth1, allowing only established connections of zone 2 through, and reverse nat (dst nat in this case)
+$ tc filter add dev eth1 ingress prio 1 chain 0 proto ip flower ip_proto tcp ct_state -trk \\
+action ct zone 2 pipe action goto chain 1
+$ tc filter add dev eth1 ingress prio 1 chain 1 proto ip flower ct_zone 2 ct_mark 0xbb ct_state +trk+est \\
+action ct nat pipe action mirred egress redirect dev eth0
+.fi
+
+.EE
+
+.RE
+.SH SEE ALSO
+.BR tc (8),
+.BR tc-flower (8)
+.BR tc-mirred (8)
+.SH AUTHORS
+Paul Blakey <paulb@mellanox.com>
+
+Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
+
+Yossi Kuperman <yossiku@mellanox.com>
diff --git a/man/man8/tc-flower.8 b/man/man8/tc-flower.8
index b3dfcf6..4d32ff1 100644
--- a/man/man8/tc-flower.8
+++ b/man/man8/tc-flower.8
@@ -1,5 +1,11 @@
 .TH "Flower filter in tc" 8 "22 Oct 2015" "iproute2" "Linux"
 
+	"Usage: ct clear\n"
+		"	ct commit [force] [zone ZONE] [mark MASKED_MARK] [label MASKED_LABEL] [nat NAT_SPEC] [OFFLOAD_POLICY]\n"
+		"	ct [nat] [zone ZONE] [OFFLOAD_POLICY]\n"
+		"Where: ZONE is the conntrack zone table number\n"
+		"	NAT_SPEC is {src|dst} addr addr1[-addr2] [port port1[-port2]]\n"
+		"	OFFLOAD_POLICY is [policy_pkts PACKETS] [policy_timeout TIMEOUT]\n"
 .SH NAME
 flower \- flow based traffic control filter
 .SH SYNOPSIS
diff --git a/misc/ss.c b/misc/ss.c
index 7122421..f3d0181 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -2410,14 +2410,23 @@
 	return 0;
 }
 
+/*
+ * Display bandwidth in standard units
+ * See: https://en.wikipedia.org/wiki/Data-rate_units
+ * bw is in bits per second
+ */
 static char *sprint_bw(char *buf, double bw)
 {
 	if (numeric)
 		sprintf(buf, "%.0f", bw);
-	else if (bw > 1000000.)
-		sprintf(buf, "%.1fM", bw / 1000000.);
-	else if (bw > 1000.)
-		sprintf(buf, "%.1fK", bw / 1000.);
+	else if (bw >= 1e12)
+		sprintf(buf, "%.3gT", bw / 1e12);
+	else if (bw >= 1e9)
+		sprintf(buf, "%.3gG", bw / 1e9);
+	else if (bw >= 1e6)
+		sprintf(buf, "%.3gM", bw / 1e6);
+	else if (bw >= 1e3)
+		sprintf(buf, "%.3gk", bw / 1e3);
 	else
 		sprintf(buf, "%g", bw);
 
diff --git a/tc/q_fq.c b/tc/q_fq.c
index ffae052..98d1bf4 100644
--- a/tc/q_fq.c
+++ b/tc/q_fq.c
@@ -379,7 +379,9 @@
 	if (tb[TCA_FQ_TIMER_SLACK] &&
 	    RTA_PAYLOAD(tb[TCA_FQ_TIMER_SLACK]) >= sizeof(__u32)) {
 		timer_slack = rta_getattr_u32(tb[TCA_FQ_TIMER_SLACK]);
-		fprintf(f, "timer_slack %s ", sprint_time64(timer_slack, b1));
+		print_uint(PRINT_JSON, "timer_slack", NULL, timer_slack);
+		print_string(PRINT_FP, NULL, "timer_slack %s ",
+			     sprint_time64(timer_slack, b1));
 	}
 
 	return 0;
@@ -442,7 +444,7 @@
 		print_nl();
 		print_lluint(PRINT_ANY, "pkts_too_long",
 			     "  pkts_too_long %llu", st->pkts_too_long);
-		print_lluint(PRINT_ANY, "alloc_errors", " alloc_erros %llu",
+		print_lluint(PRINT_ANY, "alloc_errors", " alloc_errors %llu",
 			     st->allocation_errors);
 	}
 
diff --git a/tc/tc_util.c b/tc/tc_util.c
index 12f865c..fd5fcb2 100644
--- a/tc/tc_util.c
+++ b/tc/tc_util.c
@@ -750,21 +750,17 @@
 {
 	int hz = get_user_hz();
 
-	if (tm->install != 0) {
-		print_uint(PRINT_JSON, "installed", NULL, tm->install);
-		print_uint(PRINT_FP, NULL, " installed %u sec",
-			   (unsigned int)(tm->install/hz));
-	}
-	if (tm->lastuse != 0) {
-		print_uint(PRINT_JSON, "last_used", NULL, tm->lastuse);
-		print_uint(PRINT_FP, NULL, " used %u sec",
-			   (unsigned int)(tm->lastuse/hz));
-	}
-	if (tm->expires != 0) {
-		print_uint(PRINT_JSON, "expires", NULL, tm->expires);
-		print_uint(PRINT_FP, NULL, " expires %u sec",
-			   (unsigned int)(tm->expires/hz));
-	}
+	if (tm->install != 0)
+		print_uint(PRINT_ANY, "installed", " installed %u sec",
+			   tm->install / hz);
+
+	if (tm->lastuse != 0)
+		print_uint(PRINT_ANY, "last_used", " used %u sec",
+			   tm->lastuse / hz);
+
+	if (tm->expires != 0)
+		print_uint(PRINT_ANY, "expires", " expires %u sec",
+			   tm->expires / hz);
 }
 
 static void print_tcstats_basic_hw(struct rtattr **tbs, char *prefix)