[tcp] Report TCP statistics via the "ipstat" command

Gather some basic statistics on TCP connections to allow out-of-order
packets and duplicate packets to be observed even in non-debug builds.

Report these statistics via the existing "ipstat" command, rather than
introducing a separate "tcpstat" command, on the basis that we do not
need the additional overhead of a separate command.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/include/ipxe/tcp.h b/src/include/ipxe/tcp.h
index 1f65a3d..a1e89f7 100644
--- a/src/include/ipxe/tcp.h
+++ b/src/include/ipxe/tcp.h
@@ -439,6 +439,23 @@
  */
 #define TCP_FINISH_TIMEOUT ( 1 * TICKS_PER_SEC )
 
+/** TCP statistics */
+struct tcp_statistics {
+	/** Number of packets received */
+	unsigned long in_segs;
+	/** Total number of packets discarded due to lack of memory */
+	unsigned long in_discards;
+	/** Total number of packets received out of order */
+	unsigned long in_out_of_order;
+
+	/** Number of octets received (including duplicate data) */
+	unsigned long in_octets;
+	/** Number of octets processed and passed to upper layer */
+	unsigned long in_octets_good;
+};
+
 extern struct tcpip_protocol tcp_protocol __tcpip_protocol;
 
+extern struct tcp_statistics tcp_stats;
+
 #endif /* _IPXE_TCP_H */
diff --git a/src/net/tcp.c b/src/net/tcp.c
index d47196b..2e52cf4 100644
--- a/src/net/tcp.c
+++ b/src/net/tcp.c
@@ -167,6 +167,9 @@
  */
 static LIST_HEAD ( tcp_conns );
 
+/** TCP statistics */
+struct tcp_statistics tcp_stats;
+
 /** Transmit profiler */
 static struct profiler tcp_tx_profiler __profiler = { .name = "tcp.tx" };
 
@@ -1216,6 +1219,9 @@
 	/* Acknowledge new data */
 	tcp_rx_seq ( tcp, len );
 
+	/* Update statistics */
+	tcp_stats.in_octets_good += len;
+
 	/* Deliver data to application */
 	profile_start ( &tcp_xfer_profiler );
 	if ( ( rc = xfer_deliver_iob ( &tcp->xfer, iobuf ) ) != 0 ) {
@@ -1299,6 +1305,7 @@
 	size_t len;
 	uint32_t seq_len;
 	uint32_t nxt;
+	uint32_t gap;
 
 	/* Calculate remaining flags and sequence length.  Note that
 	 * SYN, if present, has already been processed by this point.
@@ -1330,12 +1337,18 @@
 	tcpqhdr->flags = flags;
 
 	/* Add to RX queue */
+	gap = tcp->rcv_ack;
 	list_for_each_entry ( queued, &tcp->rx_queue, list ) {
 		tcpqhdr = queued->data;
 		if ( tcp_cmp ( seq, tcpqhdr->seq ) < 0 )
 			break;
+		gap = tcpqhdr->nxt;
 	}
 	list_add_tail ( &iobuf->list, &queued->list );
+
+	/* Update statistics */
+	if ( seq != gap )
+		tcp_stats.in_out_of_order++;
 }
 
 /**
@@ -1459,6 +1472,10 @@
 	seq_len = ( len + ( ( flags & TCP_SYN ) ? 1 : 0 ) +
 		    ( ( flags & TCP_FIN ) ? 1 : 0 ) );
 
+	/* Update statistics */
+	tcp_stats.in_segs++;
+	tcp_stats.in_octets += len;
+
 	/* Dump header */
 	DBGC2 ( tcp, "TCP %p RX %d<-%d           %08x %08x..%08x %4zd",
 		tcp, ntohs ( tcphdr->dest ), ntohs ( tcphdr->src ),
@@ -1569,6 +1586,9 @@
 			list_del ( &iobuf->list );
 			free_iob ( iobuf );
 
+			/* Update statistics */
+			tcp_stats.in_discards++;
+
 			/* Report discard */
 			discarded++;
 			break;
diff --git a/src/usr/ipstat.c b/src/usr/ipstat.c
index 0f09cc2..b9c5e02 100644
--- a/src/usr/ipstat.c
+++ b/src/usr/ipstat.c
@@ -24,23 +24,25 @@
 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <stdio.h>
+#include <ipxe/tcp.h>
 #include <ipxe/ipstat.h>
 #include <usr/ipstat.h>
 
 /** @file
  *
- * IP statistics
+ * TCP/IP statistics
  *
  */
 
 /**
- * Print IP statistics
+ * Print TCP/IP statistics
  *
  */
 void ipstat ( void ) {
 	struct ip_statistics_family *family;
 	struct ip_statistics *stats;
 
+	/* Print per-family statistics */
 	for_each_table_entry ( family, IP_STATISTICS_FAMILIES ) {
 		stats = family->stats;
 		printf ( "IP version %d:\n", family->version );
@@ -63,4 +65,12 @@
 			 stats->out_mcast_pkts, stats->out_bcast_pkts,
 			 stats->out_octets );
 	}
+
+	/* Print TCP statistics */
+	printf ( "TCP:\n" );
+	printf ( "  InSegs:%ld InOctets:%ld InOctetsGood:%ld\n",
+		 tcp_stats.in_segs, tcp_stats.in_octets,
+		 tcp_stats.in_octets_good );
+	printf ( "  InDiscards:%ld InOutOfOrder:%ld\n",
+		 tcp_stats.in_discards, tcp_stats.in_out_of_order );
 }