ipsec: Support 4o6 and 6o4 for SPD tunnel mode SAs

Type: feature

the es4-encrypt and esp6-encrypt nodes need to be siblings so they both have the same edges for the DPO on which the tunnel mode SA stacks.

Signed-off-by: Neale Ranns <nranns@cisco.com>
Change-Id: I2126589135a1df6c95ee14503dfde9ff406df60a
diff --git a/src/scripts/vnet/ipsec_spd b/src/scripts/vnet/ipsec_spd
new file mode 100644
index 0000000..5acced6
--- /dev/null
+++ b/src/scripts/vnet/ipsec_spd
@@ -0,0 +1,55 @@
+
+create packet-generator interface pg0
+create packet-generator interface pg1
+
+set int ip address pg0 192.168.0.1/24
+set int ip address pg1 192.168.1.1/24
+
+pipe create
+
+ip6 table add 1
+ip table add 1
+set int ip6 table pipe0.1 1
+set int ip table pipe0.1 1
+
+set int ip address pipe0.0 2001::1/64
+set int ip address pipe0.1 2001::2/64
+set int unnum pipe0.1 use pg1
+
+set int state pg0 up
+set int state pg1 up
+set int state pipe0 up
+
+ipsec sa add 20 spi 200 crypto-key 6541686776336961656264656f6f6579 crypto-alg aes-cbc-128 tunnel-src 2001::1 tunnel-dst 2001::2
+ipsec sa add 30 spi 200 crypto-key 6541686776336961656264656f6f6579 crypto-alg aes-cbc-128 tunnel-src 2001::1 tunnel-dst 2001::2
+
+ipsec spd add 1
+ipsec spd add 2
+set interface ipsec spd pipe0.0 1
+set interface ipsec spd pipe0.1 2
+ipsec policy add spd 1 ip6 priority 100 outbound action bypass protocol 50
+ipsec policy add spd 1 priority 10 outbound action protect sa 20 local-ip-range 192.168.0.0 - 192.168.0.255 remote-ip-range 10.6.0.0 - 10.6.255.255
+
+ipsec policy add spd 2 priority 10 inbound  action protect sa 30 local-ip-range 2001::2 - 2001::2 remote-ip-range 2001::1 - 2001::1
+
+ip route add 10.6.0.0/16 via pipe0.0 
+ip route table 1 add 10.6.0.0/16 via 192.168.1.2 pg1
+set ip neighbor pg1 192.168.1.2 00:11:22:33:44:55
+
+
+trace add pg-input 100
+
+packet-generator new {
+  name ipsec1
+  limit 1
+  rate 1e4
+  node ip4-input
+  interface pg0
+  size 100-100
+  data {
+   UDP: 192.168.0.2 -> 10.6.0.1
+   UDP: 4321 -> 1234
+    length 72
+    incrementing 100
+  }
+}
diff --git a/src/vnet/ipsec/esp_encrypt.c b/src/vnet/ipsec/esp_encrypt.c
index 15f590a..e9feb8b 100644
--- a/src/vnet/ipsec/esp_encrypt.c
+++ b/src/vnet/ipsec/esp_encrypt.c
@@ -27,9 +27,11 @@
 #include <vnet/ipsec/esp.h>
 
 #define foreach_esp_encrypt_next                   \
-_(DROP, "error-drop")                              \
+_(DROP4, "ip4-drop")                               \
+_(DROP6, "ip6-drop")                               \
 _(PENDING, "pending")                              \
-_(HANDOFF, "handoff")                              \
+_(HANDOFF4, "handoff4")                            \
+_(HANDOFF6, "handoff6")                            \
 _(INTERFACE_OUTPUT, "interface-output")
 
 #define _(v, s) ESP_ENCRYPT_NEXT_##v,
@@ -235,7 +237,8 @@
 static_always_inline void
 esp_process_chained_ops (vlib_main_t * vm, vlib_node_runtime_t * node,
 			 vnet_crypto_op_t * ops, vlib_buffer_t * b[],
-			 u16 * nexts, vnet_crypto_op_chunk_t * chunks)
+			 u16 * nexts, vnet_crypto_op_chunk_t * chunks,
+			 u16 drop_next)
 {
   u32 n_fail, n_ops = vec_len (ops);
   vnet_crypto_op_t *op = ops;
@@ -253,7 +256,7 @@
 	{
 	  u32 bi = op->user_data;
 	  b[bi]->error = node->errors[ESP_ENCRYPT_ERROR_CRYPTO_ENGINE_ERROR];
-	  nexts[bi] = ESP_ENCRYPT_NEXT_DROP;
+	  nexts[bi] = drop_next;
 	  n_fail--;
 	}
       op++;
@@ -262,7 +265,8 @@
 
 static_always_inline void
 esp_process_ops (vlib_main_t * vm, vlib_node_runtime_t * node,
-		 vnet_crypto_op_t * ops, vlib_buffer_t * b[], u16 * nexts)
+		 vnet_crypto_op_t * ops, vlib_buffer_t * b[], u16 * nexts,
+		 u16 drop_next)
 {
   u32 n_fail, n_ops = vec_len (ops);
   vnet_crypto_op_t *op = ops;
@@ -280,7 +284,7 @@
 	{
 	  u32 bi = op->user_data;
 	  b[bi]->error = node->errors[ESP_ENCRYPT_ERROR_CRYPTO_ENGINE_ERROR];
-	  nexts[bi] = ESP_ENCRYPT_NEXT_DROP;
+	  nexts[bi] = drop_next;
 	  n_fail--;
 	}
       op++;
@@ -555,13 +559,14 @@
 /* when submitting a frame is failed, drop all buffers in the frame */
 static_always_inline void
 esp_async_recycle_failed_submit (vnet_crypto_async_frame_t * f,
-				 vlib_buffer_t ** b, u16 * next)
+				 vlib_buffer_t ** b, u16 * next,
+				 u16 drop_next)
 {
   u32 n_drop = f->n_elts;
   while (--n_drop)
     {
       (b - n_drop)[0]->error = ESP_ENCRYPT_ERROR_CRYPTO_ENGINE_ERROR;
-      (next - n_drop)[0] = ESP_ENCRYPT_NEXT_DROP;
+      (next - n_drop)[0] = drop_next;
     }
   vnet_crypto_async_reset_frame (f);
 }
@@ -590,6 +595,7 @@
   vnet_crypto_async_frame_t *async_frame = 0;
   int is_async = im->async_mode;
   vnet_crypto_async_op_id_t last_async_op = ~0;
+  u16 drop_next = (is_ip6 ? ESP_ENCRYPT_NEXT_DROP6 : ESP_ENCRYPT_NEXT_DROP4);
 
   vlib_get_buffers (vm, from, b, n_left);
   if (!is_async)
@@ -653,7 +659,8 @@
 		{
 		  if (vnet_crypto_async_submit_open_frame (vm, async_frame)
 		      < 0)
-		    esp_async_recycle_failed_submit (async_frame, b, next);
+		    esp_async_recycle_failed_submit (async_frame, b,
+						     next, drop_next);
 		}
 	      async_frame =
 		vnet_crypto_async_get_frame (vm, sa0->crypto_async_enc_op_id);
@@ -672,7 +679,8 @@
 
       if (PREDICT_TRUE (thread_index != sa0->encrypt_thread_index))
 	{
-	  next[0] = ESP_ENCRYPT_NEXT_HANDOFF;
+	  next[0] = (is_ip6 ?
+		     ESP_ENCRYPT_NEXT_HANDOFF6 : ESP_ENCRYPT_NEXT_HANDOFF4);
 	  goto trace;
 	}
 
@@ -681,7 +689,7 @@
       if (n_bufs == 0)
 	{
 	  b[0]->error = node->errors[ESP_ENCRYPT_ERROR_NO_BUFFERS];
-	  next[0] = ESP_ENCRYPT_NEXT_DROP;
+	  next[0] = drop_next;
 	  goto trace;
 	}
 
@@ -703,7 +711,7 @@
       if (PREDICT_FALSE (esp_seq_advance (sa0)))
 	{
 	  b[0]->error = node->errors[ESP_ENCRYPT_ERROR_SEQ_CYCLED];
-	  next[0] = ESP_ENCRYPT_NEXT_DROP;
+	  next[0] = drop_next;
 	  goto trace;
 	}
 
@@ -721,7 +729,7 @@
 	  if (!next_hdr_ptr)
 	    {
 	      b[0]->error = node->errors[ESP_ENCRYPT_ERROR_NO_BUFFERS];
-	      next[0] = ESP_ENCRYPT_NEXT_DROP;
+	      next[0] = drop_next;
 	      goto trace;
 	    }
 	  b[0]->flags &= ~VLIB_BUFFER_TOTAL_LENGTH_VALID;
@@ -884,7 +892,8 @@
 				       icv_sz, from[b - bufs], next, hdr_len,
 				       async_next, lb))
 	    {
-	      esp_async_recycle_failed_submit (async_frame, b, next);
+	      esp_async_recycle_failed_submit (async_frame, b, next,
+					       drop_next);
 	      goto trace;
 	    }
 	}
@@ -924,18 +933,18 @@
 				   current_sa_bytes);
   if (!is_async)
     {
-      esp_process_ops (vm, node, ptd->crypto_ops, bufs, nexts);
+      esp_process_ops (vm, node, ptd->crypto_ops, bufs, nexts, drop_next);
       esp_process_chained_ops (vm, node, ptd->chained_crypto_ops, bufs, nexts,
-			       ptd->chunks);
+			       ptd->chunks, drop_next);
 
-      esp_process_ops (vm, node, ptd->integ_ops, bufs, nexts);
+      esp_process_ops (vm, node, ptd->integ_ops, bufs, nexts, drop_next);
       esp_process_chained_ops (vm, node, ptd->chained_integ_ops, bufs, nexts,
-			       ptd->chunks);
+			       ptd->chunks, drop_next);
     }
   else if (async_frame && async_frame->n_elts)
     {
       if (vnet_crypto_async_submit_open_frame (vm, async_frame) < 0)
-	esp_async_recycle_failed_submit (async_frame, b, next);
+	esp_async_recycle_failed_submit (async_frame, b, next, drop_next);
     }
 
   vlib_node_increment_counter (vm, node->node_index,
@@ -1051,8 +1060,10 @@
 
   .n_next_nodes = ESP_ENCRYPT_N_NEXT,
   .next_nodes = {
-    [ESP_ENCRYPT_NEXT_DROP] = "ip4-drop",
-    [ESP_ENCRYPT_NEXT_HANDOFF] = "esp4-encrypt-handoff",
+    [ESP_ENCRYPT_NEXT_DROP4] = "ip4-drop",
+    [ESP_ENCRYPT_NEXT_DROP6] = "ip6-drop",
+    [ESP_ENCRYPT_NEXT_HANDOFF4] = "esp4-encrypt-handoff",
+    [ESP_ENCRYPT_NEXT_HANDOFF6] = "esp6-encrypt-handoff",
     [ESP_ENCRYPT_NEXT_INTERFACE_OUTPUT] = "interface-output",
     [ESP_ENCRYPT_NEXT_PENDING] = "esp-encrypt-pending",
   },
@@ -1093,17 +1104,10 @@
   .vector_size = sizeof (u32),
   .format_trace = format_esp_encrypt_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
+  .sibling_of = "esp4-encrypt",
 
   .n_errors = ARRAY_LEN(esp_encrypt_error_strings),
   .error_strings = esp_encrypt_error_strings,
-
-  .n_next_nodes = ESP_ENCRYPT_N_NEXT,
-  .next_nodes = {
-    [ESP_ENCRYPT_NEXT_DROP] = "ip6-drop",
-    [ESP_ENCRYPT_NEXT_HANDOFF] = "esp6-encrypt-handoff",
-    [ESP_ENCRYPT_NEXT_INTERFACE_OUTPUT] = "interface-output",
-    [ESP_ENCRYPT_NEXT_PENDING] = "esp-encrypt-pending",
-  },
 };
 /* *INDENT-ON* */
 
@@ -1120,7 +1124,7 @@
   .vector_size = sizeof (u32),
   .format_trace = format_esp_post_encrypt_trace,
   .type = VLIB_NODE_TYPE_INTERNAL,
-  .sibling_of = "esp6-encrypt",
+  .sibling_of = "esp4-encrypt",
 
   .n_errors = ARRAY_LEN(esp_encrypt_error_strings),
   .error_strings = esp_encrypt_error_strings,
@@ -1147,8 +1151,10 @@
 
   .n_next_nodes = ESP_ENCRYPT_N_NEXT,
   .next_nodes = {
-    [ESP_ENCRYPT_NEXT_DROP] = "ip4-drop",
-    [ESP_ENCRYPT_NEXT_HANDOFF] = "esp4-encrypt-tun-handoff",
+    [ESP_ENCRYPT_NEXT_DROP4] = "ip4-drop",
+    [ESP_ENCRYPT_NEXT_DROP6] = "ip6-drop",
+    [ESP_ENCRYPT_NEXT_HANDOFF4] = "esp4-encrypt-tun-handoff",
+    [ESP_ENCRYPT_NEXT_HANDOFF6] = "error-drop",
     [ESP_ENCRYPT_NEXT_INTERFACE_OUTPUT] = "adj-midchain-tx",
     [ESP_ENCRYPT_NEXT_PENDING] = "esp-encrypt-pending",
   },
@@ -1194,8 +1200,10 @@
 
   .n_next_nodes = ESP_ENCRYPT_N_NEXT,
   .next_nodes = {
-    [ESP_ENCRYPT_NEXT_DROP] = "ip6-drop",
-    [ESP_ENCRYPT_NEXT_HANDOFF] = "esp6-encrypt-tun-handoff",
+    [ESP_ENCRYPT_NEXT_DROP4] = "ip4-drop",
+    [ESP_ENCRYPT_NEXT_DROP6] = "ip6-drop",
+    [ESP_ENCRYPT_NEXT_HANDOFF4] = "error-drop",
+    [ESP_ENCRYPT_NEXT_HANDOFF6] = "esp6-encrypt-tun-handoff",
     [ESP_ENCRYPT_NEXT_PENDING] = "esp-encrypt-pending",
     [ESP_ENCRYPT_NEXT_INTERFACE_OUTPUT] = "adj-midchain-tx",
   },
diff --git a/src/vnet/ipsec/ipsec_cli.c b/src/vnet/ipsec/ipsec_cli.c
index 695e5f0..4d452d5 100644
--- a/src/vnet/ipsec/ipsec_cli.c
+++ b/src/vnet/ipsec/ipsec_cli.c
@@ -234,13 +234,13 @@
   unformat_input_t _line_input, *line_input = &_line_input;
   ipsec_policy_t p;
   int rv, is_add = 0;
-  u32 tmp, tmp2, stat_index;
+  u32 tmp, tmp2, stat_index, local_range_set, remote_range_set;
   clib_error_t *error = NULL;
   u32 is_outbound;
 
   clib_memset (&p, 0, sizeof (p));
   p.lport.stop = p.rport.stop = ~0;
-  is_outbound = 0;
+  remote_range_set = local_range_set = is_outbound = 0;
 
   if (!unformat_user (input, unformat_line_input, line_input))
     return 0;
@@ -251,6 +251,8 @@
 	is_add = 1;
       else if (unformat (line_input, "del"))
 	is_add = 0;
+      else if (unformat (line_input, "ip6"))
+	p.is_ipv6 = 1;
       else if (unformat (line_input, "spd %u", &p.id))
 	;
       else if (unformat (line_input, "inbound"))
@@ -277,22 +279,24 @@
       else if (unformat (line_input, "local-ip-range %U - %U",
 			 unformat_ip4_address, &p.laddr.start.ip4,
 			 unformat_ip4_address, &p.laddr.stop.ip4))
-	;
+	local_range_set = 1;
       else if (unformat (line_input, "remote-ip-range %U - %U",
 			 unformat_ip4_address, &p.raddr.start.ip4,
 			 unformat_ip4_address, &p.raddr.stop.ip4))
-	;
+	remote_range_set = 1;
       else if (unformat (line_input, "local-ip-range %U - %U",
 			 unformat_ip6_address, &p.laddr.start.ip6,
 			 unformat_ip6_address, &p.laddr.stop.ip6))
 	{
 	  p.is_ipv6 = 1;
+	  local_range_set = 1;
 	}
       else if (unformat (line_input, "remote-ip-range %U - %U",
 			 unformat_ip6_address, &p.raddr.start.ip6,
 			 unformat_ip6_address, &p.raddr.stop.ip6))
 	{
 	  p.is_ipv6 = 1;
+	  remote_range_set = 1;
 	}
       else if (unformat (line_input, "local-port-range %u - %u", &tmp, &tmp2))
 	{
@@ -313,6 +317,21 @@
 	}
     }
 
+  if (!remote_range_set)
+    {
+      if (p.is_ipv6)
+	clib_memset (&p.raddr.stop.ip6, 0xff, 16);
+      else
+	clib_memset (&p.raddr.stop.ip4, 0xff, 4);
+    }
+  if (!local_range_set)
+    {
+      if (p.is_ipv6)
+	clib_memset (&p.laddr.stop.ip6, 0xff, 16);
+      else
+	clib_memset (&p.laddr.stop.ip4, 0xff, 4);
+    }
+
   rv = ipsec_policy_mk_type (is_outbound, p.is_ipv6, p.policy, &p.type);
 
   if (rv)
diff --git a/test/test_ipsec_esp.py b/test/test_ipsec_esp.py
index 6be49ef..036fbf3 100644
--- a/test/test_ipsec_esp.py
+++ b/test/test_ipsec_esp.py
@@ -291,7 +291,81 @@
 class TestIpsecEsp1(TemplateIpsecEsp, IpsecTra46Tests,
                     IpsecTun46Tests, IpsecTra6ExtTests):
     """ Ipsec ESP - TUN & TRA tests """
-    pass
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestIpsecEsp1, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestIpsecEsp1, cls).tearDownClass()
+
+    def setUp(self):
+        super(TestIpsecEsp1, self).setUp()
+
+    def tearDown(self):
+        super(TestIpsecEsp1, self).tearDown()
+
+    def test_tun_46(self):
+        """ ipsec 4o6 tunnel """
+        # add an SPD entry to direct 2.2.2.2 to the v6 tunnel SA
+        p6 = self.ipv6_params
+        p4 = self.ipv4_params
+
+        p6.remote_tun_if_host4 = "2.2.2.2"
+        e = VppEnum.vl_api_ipsec_spd_action_t
+
+        VppIpsecSpdEntry(self,
+                         self.tun_spd,
+                         p6.scapy_tun_sa_id,
+                         self.pg1.remote_addr[p4.addr_type],
+                         self.pg1.remote_addr[p4.addr_type],
+                         p6.remote_tun_if_host4,
+                         p6.remote_tun_if_host4,
+                         0,
+                         priority=10,
+                         policy=e.IPSEC_API_SPD_ACTION_PROTECT,
+                         is_outbound=1).add_vpp_config()
+        VppIpRoute(self,  p6.remote_tun_if_host4, p4.addr_len,
+                   [VppRoutePath(self.tun_if.remote_addr[p4.addr_type],
+                                 0xffffffff)]).add_vpp_config()
+
+        old_name = self.tun6_encrypt_node_name
+        self.tun6_encrypt_node_name = "esp4-encrypt"
+
+        self.verify_tun_46(p6, count=63)
+        self.tun6_encrypt_node_name = old_name
+
+    def test_tun_64(self):
+        """ ipsec 6o4 tunnel """
+        # add an SPD entry to direct 4444::4 to the v4 tunnel SA
+        p6 = self.ipv6_params
+        p4 = self.ipv4_params
+
+        p4.remote_tun_if_host6 = "4444::4"
+        e = VppEnum.vl_api_ipsec_spd_action_t
+
+        VppIpsecSpdEntry(self,
+                         self.tun_spd,
+                         p4.scapy_tun_sa_id,
+                         self.pg1.remote_addr[p6.addr_type],
+                         self.pg1.remote_addr[p6.addr_type],
+                         p4.remote_tun_if_host6,
+                         p4.remote_tun_if_host6,
+                         0,
+                         priority=10,
+                         policy=e.IPSEC_API_SPD_ACTION_PROTECT,
+                         is_outbound=1).add_vpp_config()
+        d = DpoProto.DPO_PROTO_IP6
+        VppIpRoute(self,  p4.remote_tun_if_host6, p6.addr_len,
+                   [VppRoutePath(self.tun_if.remote_addr[p6.addr_type],
+                                 0xffffffff,
+                                 proto=d)]).add_vpp_config()
+
+        old_name = self.tun4_encrypt_node_name
+        self.tun4_encrypt_node_name = "esp6-encrypt"
+        self.verify_tun_64(p4, count=63)
+        self.tun4_encrypt_node_name = old_name
 
 
 class TestIpsecEsp2(TemplateIpsecEsp, IpsecTcpTests):