From 8eb75b5d5e518432ec98154d6235b8a1b0621f61 Mon Sep 17 00:00:00 2001
From: Neal Cardwell <ncardwell@google.com>
Date: Mon, 4 Nov 2013 15:31:11 -0500
Subject: [PATCH] net-test: packetdrill encap support: IPv6 support

Add support for IPv6 in encapsulation layers, including: lexical
scanning of IPv6 addresses, parsing IPv6 encap in scripts, appending
and filling in IPv6 encapsulation.


Change-Id: I1349cd4056c168a4114a92324eb2a321f7db7697
---
 gtests/net/packetdrill/ip_packet.c | 48 ++++++++++++++++++++++++++++++
 gtests/net/packetdrill/ip_packet.h | 17 ++++++++++-
 gtests/net/packetdrill/lexer.l     | 30 +++++++++++++++++--
 gtests/net/packetdrill/packet.c    |  2 +-
 gtests/net/packetdrill/parser.y    | 17 +++++++++--
 5 files changed, 107 insertions(+), 7 deletions(-)

diff --git a/gtests/net/packetdrill/ip_packet.c b/gtests/net/packetdrill/ip_packet.c
index 7c0aad16..147728c2 100644
--- a/gtests/net/packetdrill/ip_packet.c
+++ b/gtests/net/packetdrill/ip_packet.c
@@ -153,6 +153,37 @@ int ipv4_header_append(struct packet *packet,
 	return STATUS_OK;
 }
 
+int ipv6_header_append(struct packet *packet,
+		       const char *ip_src,
+		       const char *ip_dst,
+		       char **error)
+{
+	struct header *header = NULL;
+	const int ipv6_bytes = sizeof(struct ipv6);
+	struct ipv6 *ipv6 = NULL;
+
+	header = packet_append_header(packet, HEADER_IPV6, ipv6_bytes);
+	if (header == NULL) {
+		asprintf(error, "too many headers");
+		return STATUS_ERR;
+	}
+
+	ipv6 = header->h.ipv6;
+	set_ip_header(ipv6, AF_INET6, sizeof(struct ipv6), ECN_NONE, 0);
+
+	if (inet_pton(AF_INET6, ip_src, &ipv6->src_ip) != 1) {
+		asprintf(error, "bad IPv6 src address: '%s'\n", ip_src);
+		return STATUS_ERR;
+	}
+
+	if (inet_pton(AF_INET6, ip_dst, &ipv6->dst_ip) != 1) {
+		asprintf(error, "bad IPv6 dst address: '%s'\n", ip_dst);
+		return STATUS_ERR;
+	}
+
+	return STATUS_OK;
+}
+
 int ipv4_header_finish(struct packet *packet,
 		       struct header *header, struct header *next_inner)
 {
@@ -170,3 +201,20 @@ int ipv4_header_finish(struct packet *packet,
 
 	return STATUS_OK;
 }
+
+int ipv6_header_finish(struct packet *packet,
+		       struct header *header, struct header *next_inner)
+{
+	struct ipv6 *ipv6 = header->h.ipv6;
+	int ip_bytes = sizeof(struct ipv6) + next_inner->total_bytes;
+
+	assert(next_inner->total_bytes <= 0xffff);
+	ipv6->payload_len = htons(next_inner->total_bytes);
+	ipv6->next_header = header_type_info(next_inner->type)->ip_proto;
+
+	/* IPv6 has no header checksum. */
+
+	header->total_bytes = ip_bytes;
+
+	return STATUS_OK;
+}
diff --git a/gtests/net/packetdrill/ip_packet.h b/gtests/net/packetdrill/ip_packet.h
index ae39e578..7531a292 100644
--- a/gtests/net/packetdrill/ip_packet.h
+++ b/gtests/net/packetdrill/ip_packet.h
@@ -50,10 +50,25 @@ extern int ipv4_header_append(struct packet *packet,
 			      const char *ip_dst,
 			      char **error);
 
-/* Finalize the IPV4 header by filling in all necessary fields that
+/* Append an IPv6 header to the end of the given packet and fill in
+ * src/dst.  On success, return STATUS_OK; on error return STATUS_ERR
+ * and fill in a malloc-allocated error message in *error.
+ */
+extern int ipv6_header_append(struct packet *packet,
+			      const char *ip_src,
+			      const char *ip_dst,
+			      char **error);
+
+/* Finalize the IPv4 header by filling in all necessary fields that
  * were not filled in at parse time.
  */
 extern int ipv4_header_finish(struct packet *packet,
 			      struct header *header, struct header *next_inner);
 
+/* Finalize the IPv6 header by filling in all necessary fields that
+ * were not filled in at parse time.
+ */
+extern int ipv6_header_finish(struct packet *packet,
+			      struct header *header, struct header *next_inner);
+
 #endif /* __IP_PACKET_H__ */
diff --git a/gtests/net/packetdrill/lexer.l b/gtests/net/packetdrill/lexer.l
index 759f9bd0..398b434a 100644
--- a/gtests/net/packetdrill/lexer.l
+++ b/gtests/net/packetdrill/lexer.l
@@ -131,11 +131,33 @@ c_comment	\/\*(([^*])|(\*[^\/]))*\*\/
  */
 code		\%\{(([^}])|(\}[^\%]))*\}\%
 
-/* A regular experssion for an IP address
- * TODO(ncardwell): IPv6
- */
+/* IPv4: a regular experssion for an IPv4 address */
 ipv4_addr		[0-9]+[.][0-9]+[.][0-9]+[.][0-9]+
 
+/* IPv6: a regular experssion for an IPv6 address. The complexity is
+ * unfortunate, but we can't use a super-simple approach because TCP
+ * sequence number ranges like 1:1001 can look like IPv6 addresses if
+ * we use a naive approach.
+ */
+seg	[0-9a-fA-F]{1,4}
+v0	[:][:]
+v1	({seg}[:]){7,7}{seg}
+v2	({seg}[:]){1,7}[:]
+v3	({seg}[:]){1,6}[:]{seg}
+v4	({seg}[:]){1,5}([:]{seg}){1,2}
+v5	({seg}[:]){1,4}([:]{seg}){1,3}
+v6	({seg}[:]){1,3}([:]{seg}){1,4}
+v7	({seg}[:]){1,2}([:]{seg}){1,5}
+v8	{seg}[:](([:]{seg}){1,6})
+v9	[:]([:]{seg}){1,7}
+/* IPv4-mapped IPv6 address: */
+v10	[:][:]ffff[:]{ipv4_addr}
+/* IPv4-translated IPv6 address: */
+v11	[:][:]ffff[:](0){1,4}[:]{ipv4_addr}
+/* IPv4-embedded IPv6 addresses: */
+v12	({seg}[:]){1,4}[:]{ipv4_addr}
+ipv6_addr ({v0}|{v1}|{v2}|{v3}|{v4}|{v5}|{v6}|{v7}|{v8}|{v9}|{v10}|{v11}|{v12})
+
 %%
 sa_family		return SA_FAMILY;
 sin_port		return SIN_PORT;
@@ -150,6 +172,7 @@ onoff			return ONOFF;
 linger			return LINGER;
 htons			return _HTONS_;
 ipv4			return IPV4;
+ipv6			return IPV6;
 icmp			return ICMP;
 udp			return UDP;
 gre			return GRE;
@@ -186,4 +209,5 @@ ce			return CE;
 {c_comment}		/* ignore C-style comment */;
 {code}			yylval.string = code(yytext);   return CODE;
 {ipv4_addr}		yylval.string = strdup(yytext); return IPV4_ADDR;
+{ipv6_addr}		yylval.string = strdup(yytext); return IPV6_ADDR;
 %%
diff --git a/gtests/net/packetdrill/packet.c b/gtests/net/packetdrill/packet.c
index 8f814650..310738a5 100644
--- a/gtests/net/packetdrill/packet.c
+++ b/gtests/net/packetdrill/packet.c
@@ -37,7 +37,7 @@
 struct header_type_info header_types[HEADER_NUM_TYPES] = {
 	{ "NONE",   0,			0,		NULL },
 	{ "IPV4",   IPPROTO_IPIP,	ETHERTYPE_IP,	ipv4_header_finish },
-	{ "IPV6",   IPPROTO_IPV6,	ETHERTYPE_IPV6, NULL },
+	{ "IPV6",   IPPROTO_IPV6,	ETHERTYPE_IPV6, ipv6_header_finish },
 	{ "GRE",    IPPROTO_GRE,	0,		gre_header_finish },
 	{ "TCP",    IPPROTO_TCP,	0,		NULL },
 	{ "UDP",    IPPROTO_UDP,	0,		NULL },
diff --git a/gtests/net/packetdrill/parser.y b/gtests/net/packetdrill/parser.y
index 636e2e01..7c43792c 100644
--- a/gtests/net/packetdrill/parser.y
+++ b/gtests/net/packetdrill/parser.y
@@ -473,11 +473,11 @@ static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
 %token <reserved> ACK ECR EOL MSS NOP SACK SACKOK TIMESTAMP VAL WIN WSCALE PRO
 %token <reserved> FAST_OPEN
 %token <reserved> ECT0 ECT1 CE ECT01 NO_ECN
-%token <reserved> IPV4 ICMP UDP GRE MTU
+%token <reserved> IPV4 IPV6 ICMP UDP GRE MTU
 %token <reserved> OPTION
 %token <floating> FLOAT
 %token <integer> INTEGER HEX_INTEGER
-%token <string> WORD STRING BACK_QUOTED CODE IPV4_ADDR
+%token <string> WORD STRING BACK_QUOTED CODE IPV4_ADDR IPV6_ADDR
 %type <direction> direction
 %type <ip_ecn> opt_ip_info
 %type <ip_ecn> ip_ecn
@@ -550,7 +550,9 @@ option_value
 | WORD		{ $$ = $1; }
 | STRING	{ $$ = $1; }
 | IPV4_ADDR	{ $$ = $1; }
+| IPV6_ADDR	{ $$ = $1; }
 | IPV4		{ $$ = strdup("ipv4"); }
+| IPV6		{ $$ = strdup("ipv6"); }
 ;
 
 opt_init_command
@@ -751,6 +753,17 @@ packet_prefix
 	free(ip_dst);
 	$$ = packet;
 }
+| packet_prefix IPV6 IPV6_ADDR '>' IPV6_ADDR ':' {
+	char *error = NULL;
+	struct packet *packet = $1;
+	char *ip_src = $3;
+	char *ip_dst = $5;
+	if (ipv6_header_append(packet, ip_src, ip_dst, &error))
+		semantic_error(error);
+	free(ip_src);
+	free(ip_dst);
+	$$ = packet;
+}
 | packet_prefix GRE ':' {
 	char *error = NULL;
 	struct packet *packet = $1;
-- 
GitLab