From 6347ad8c810f62078d875ba4bd37c47d1e77d1b7 Mon Sep 17 00:00:00 2001
From: Michael Tuexen <tuexen@fh-muenster.de>
Date: Thu, 11 Jan 2018 00:08:23 +0100
Subject: [PATCH] Update TCP Fast Open support to RFC 7413.

Use the IANA assigned option number for the TCP Fast Open option.
Use EXP-FO instead of TO for using the deprecate experimental option.
---
 gtests/net/packetdrill/lexer.l                |  1 +
 gtests/net/packetdrill/parser.y               | 58 +++++++++++++++++--
 gtests/net/packetdrill/tcp.h                  |  1 +
 gtests/net/packetdrill/tcp_options.h          | 21 ++++++-
 gtests/net/packetdrill/tcp_options_iterator.c |  1 +
 .../net/packetdrill/tcp_options_to_string.c   | 42 ++++++++++++--
 6 files changed, 110 insertions(+), 14 deletions(-)

diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index f0876095..ea3c647d 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -320,6 +320,7 @@ nop				return NOP;
 sack				return SACK;
 sackOK				return SACKOK;
 TS				return TIMESTAMP;
+EXP-FO				return EXP_FAST_OPEN;
 FO				return FAST_OPEN;
 val				return VAL;
 win				return WIN;
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index 80d421e4..db2b43f4 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -399,7 +399,7 @@ static int parse_hex_string(const char *hex, u8 *buf, int buf_len,
 }
 
 static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
-						    char **error)
+						   char **error)
 {
 	int cookie_string_len = strlen(cookie_string);
 	if (cookie_string_len & 1) {
@@ -415,10 +415,9 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 			 cookie_bytes, MAX_TCP_FAST_OPEN_COOKIE_BYTES);
 		return NULL;
 	}
-	u8 option_bytes = TCPOLEN_EXP_FASTOPEN_BASE + cookie_bytes;
+	u8 option_bytes = TCPOLEN_FASTOPEN_BASE + cookie_bytes;
 	struct tcp_option *option;
-	option = tcp_option_new(TCPOPT_EXP, option_bytes);
-	option->data.fast_open.magic = htons(TCPOPT_FASTOPEN_MAGIC);
+	option = tcp_option_new(TCPOPT_FASTOPEN, option_bytes);
 	int parsed_bytes = 0;
 	/* Parse cookie. This should be an ASCII hex string
 	 * representing an even number of bytes (4-16 bytes). But we
@@ -437,6 +436,45 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 	return option;
 }
 
+static struct tcp_option *new_tcp_exp_fast_open_option(const char *cookie_string,
+						       char **error)
+{
+	int cookie_string_len = strlen(cookie_string);
+	if (cookie_string_len & 1) {
+		asprintf(error,
+			 "TCP fast open cookie has an odd number of digits");
+		return NULL;
+	}
+	int cookie_bytes = cookie_string_len / 2;  /* 2 hex chars per byte */
+	if (cookie_bytes > MAX_TCP_EXP_FAST_OPEN_COOKIE_BYTES) {
+		asprintf(error, "TCP fast open cookie too long");
+		asprintf(error, "TCP fast open cookie of %d bytes "
+			 "exceeds maximum cookie length of %d bytes",
+			 cookie_bytes, MAX_TCP_EXP_FAST_OPEN_COOKIE_BYTES);
+		return NULL;
+	}
+	u8 option_bytes = TCPOLEN_EXP_FASTOPEN_BASE + cookie_bytes;
+	struct tcp_option *option;
+	option = tcp_option_new(TCPOPT_EXP, option_bytes);
+	option->data.exp_fast_open.magic = htons(TCPOPT_FASTOPEN_MAGIC);
+	int parsed_bytes = 0;
+	/* Parse cookie. This should be an ASCII hex string
+	 * representing an even number of bytes (4-16 bytes). But we
+	 * do not enforce this, since we want to allow test cases that
+	 * supply invalid cookies.
+	 */
+	if (parse_hex_string(cookie_string, option->data.exp_fast_open.cookie,
+			     sizeof(option->data.exp_fast_open.cookie),
+			     &parsed_bytes)) {
+		free(option);
+		asprintf(error,
+			 "TCP fast open cookie is not a valid hex string");
+		return NULL;
+	}
+	assert(parsed_bytes == cookie_bytes);
+	return option;
+}
+
 %}
 
 %locations
@@ -525,7 +563,7 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token <reserved> SF_HDTR_HEADERS SF_HDTR_TRAILERS
 %token <reserved> FD EVENTS REVENTS ONOFF LINGER
 %token <reserved> ACK ECR EOL MSS NOP SACK NR_SACK SACKOK TIMESTAMP VAL WIN WSCALE PRO
-%token <reserved> FAST_OPEN
+%token <reserved> EXP_FAST_OPEN FAST_OPEN
 %token <reserved> IOV_BASE IOV_LEN
 %token <reserved> ECT0 ECT1 CE ECT01 NO_ECN
 %token <reserved> IPV4 IPV6 ICMP SCTP UDP UDPLITE GRE MTU
@@ -2752,6 +2790,16 @@ tcp_option
 		free(error);
 	}
 }
+| EXP_FAST_OPEN opt_tcp_fast_open_cookie  {
+	char *error = NULL;
+	$$ = new_tcp_exp_fast_open_option($2, &error);
+	free($2);
+	if ($$ == NULL) {
+		assert(error != NULL);
+		semantic_error(error);
+		free(error);
+	}
+}
 ;
 
 abs_integer
diff --git a/gtests/net/packetdrill/tcp.h b/gtests/net/packetdrill/tcp.h
index c07bdb8f..fbc8edbd 100644
--- a/gtests/net/packetdrill/tcp.h
+++ b/gtests/net/packetdrill/tcp.h
@@ -85,6 +85,7 @@
 #define TCPOPT_SACK		5
 #define TCPOPT_TIMESTAMP	8
 #define TCPOLEN_TIMESTAMP	10
+#define TCPOPT_FASTOPEN		34
 #define TCPOPT_EXP		254	/* Experimental */
 
 /* A portable TCP header definition (Linux and *BSD use different names). */
diff --git a/gtests/net/packetdrill/tcp_options.h b/gtests/net/packetdrill/tcp_options.h
index 3fb8231c..34ae671c 100644
--- a/gtests/net/packetdrill/tcp_options.h
+++ b/gtests/net/packetdrill/tcp_options.h
@@ -35,10 +35,10 @@
  * option value for sharing TCP experimental options.
  *
  * For a description of experimental options, see:
- *   http://tools.ietf.org/html/draft-ietf-tcpm-experimental-options-00
+ *   https://tools.ietf.org/html/rfc6994
  *
  * For a description of TFO, see:
- *   http://tools.ietf.org/html/draft-cheng-tcpm-fastopen-02
+ *   https://tools.ietf.org/html/rfc7413
  */
 #define TCPOPT_FASTOPEN_MAGIC	0xF989
 
@@ -46,9 +46,16 @@
 #define TCPOLEN_EXP_FASTOPEN_BASE 4	/* smallest legal TFO option size */
 
 /* The TFO option base prefix leaves this amount of space: */
-#define MAX_TCP_FAST_OPEN_COOKIE_BYTES				\
+#define MAX_TCP_EXP_FAST_OPEN_COOKIE_BYTES				\
 	(MAX_TCP_OPTION_BYTES - TCPOLEN_EXP_FASTOPEN_BASE)
 
+/* TFO option must have: 1-byte kind, 1-byte length */
+#define TCPOLEN_FASTOPEN_BASE 2	/* smallest legal TFO option size */
+
+/* The TFO option base prefix leaves this amount of space: */
+#define MAX_TCP_FAST_OPEN_COOKIE_BYTES				\
+	(MAX_TCP_OPTION_BYTES - TCPOLEN_FASTOPEN_BASE)
+
 /* Represents a list of TCP options in their wire format. */
 struct tcp_options {
 	u8 data[MAX_TCP_OPTION_BYTES];	/* The options data, in wire format */
@@ -85,6 +92,14 @@ struct tcp_option {
 		} sack;
 		struct {
 			u16 magic;	/* must be TCPOPT_FASTOPEN_MAGIC */
+			/* The fast open chookie should be 4-16 bytes
+			 * of cookie, multiple of 2 bytes, but we
+			 * allow for larger sizes, so we can test what
+			 * stacks do with illegal options.
+			 */
+			u8 cookie[MAX_TCP_EXP_FAST_OPEN_COOKIE_BYTES];
+		} exp_fast_open;
+		struct {
 			/* The fast open chookie should be 4-16 bytes
 			 * of cookie, multiple of 2 bytes, but we
 			 * allow for larger sizes, so we can test what
diff --git a/gtests/net/packetdrill/tcp_options_iterator.c b/gtests/net/packetdrill/tcp_options_iterator.c
index 90257eb0..1ec8dd81 100644
--- a/gtests/net/packetdrill/tcp_options_iterator.c
+++ b/gtests/net/packetdrill/tcp_options_iterator.c
@@ -62,6 +62,7 @@ static int get_expected_tcp_option_length(u8 kind, u8 *expected_length,
 		break;
 
 	case TCPOPT_SACK:
+	case TCPOPT_FASTOPEN:
 	case TCPOPT_EXP:
 		*expected_length = 0;	/* variable-length option */
 		break;
diff --git a/gtests/net/packetdrill/tcp_options_to_string.c b/gtests/net/packetdrill/tcp_options_to_string.c
index d022ec2c..d23ecd30 100644
--- a/gtests/net/packetdrill/tcp_options_to_string.c
+++ b/gtests/net/packetdrill/tcp_options_to_string.c
@@ -26,23 +26,45 @@
 
 #include "tcp_options_iterator.h"
 
+static int tcp_fast_open_option_to_string(FILE *s, struct tcp_option *option)
+{
+	if (option->length < TCPOLEN_FASTOPEN_BASE) {
+		return STATUS_ERR;
+	}
+
+	fputs("FO", s);
+	int cookie_bytes = option->length - TCPOLEN_FASTOPEN_BASE;
+	assert(cookie_bytes >= 0);
+	assert(cookie_bytes <= MAX_TCP_FAST_OPEN_COOKIE_BYTES);
+	if (cookie_bytes > 0) {
+		fputs(" ", s);
+	}
+	int i;
+	for (i = 0; i < cookie_bytes; ++i)
+		fprintf(s, "%02x", option->data.fast_open.cookie[i]);
+	return STATUS_OK;
+}
+
 /* See if the given experimental option is a TFO option, and if so
  * then print the TFO option and return STATUS_OK. Otherwise, return
  * STATUS_ERR.
  */
-static int tcp_fast_open_option_to_string(FILE *s, struct tcp_option *option)
+static int tcp_exp_fast_open_option_to_string(FILE *s, struct tcp_option *option)
 {
 	if ((option->length < TCPOLEN_EXP_FASTOPEN_BASE) ||
-	    (ntohs(option->data.fast_open.magic) != TCPOPT_FASTOPEN_MAGIC))
+	    (ntohs(option->data.exp_fast_open.magic) != TCPOPT_FASTOPEN_MAGIC))
 		return STATUS_ERR;
 
-	fprintf(s, "FO ");
+	fputs("EXP-FO", s);
 	int cookie_bytes = option->length - TCPOLEN_EXP_FASTOPEN_BASE;
 	assert(cookie_bytes >= 0);
-	assert(cookie_bytes <= MAX_TCP_FAST_OPEN_COOKIE_BYTES);
+	assert(cookie_bytes <= MAX_TCP_EXP_FAST_OPEN_COOKIE_BYTES);
+	if (cookie_bytes > 0) {
+		fputs(" ", s);
+	}
 	int i;
 	for (i = 0; i < cookie_bytes; ++i)
-		fprintf(s, "%02x", option->data.fast_open.cookie[i]);
+		fprintf(s, "%02x", option->data.exp_fast_open.cookie[i]);
 	return STATUS_OK;
 }
 
@@ -106,8 +128,16 @@ int tcp_options_to_string(struct packet *packet,
 				ntohl(option->data.time_stamp.ecr));
 			break;
 
-		case TCPOPT_EXP:
+		case TCPOPT_FASTOPEN:
 			if (tcp_fast_open_option_to_string(s, option)) {
+				asprintf(error, "invalid length: %u",
+					 option->length);
+				goto out;
+			}
+			break;
+
+		case TCPOPT_EXP:
+			if (tcp_exp_fast_open_option_to_string(s, option)) {
 				asprintf(error,
 					 "unknown experimental option");
 				goto out;
-- 
GitLab