ipsec: allow receiving encrypted IP packets with TFC padding

Type: feature
Change-Id: I7b29c71d3d053af9a53931aa333484bf43a424ca
Signed-off-by: Arthur de Kerhor <arthurdekerhor@gmail.com>
Signed-off-by: Benoît Ganne <bganne@cisco.com>
diff --git a/src/vnet/ipsec/esp_decrypt.c b/src/vnet/ipsec/esp_decrypt.c
index 7b74e6e..fba4549 100644
--- a/src/vnet/ipsec/esp_decrypt.c
+++ b/src/vnet/ipsec/esp_decrypt.c
@@ -161,6 +161,9 @@
 {
   vlib_buffer_t *before_last = b;
 
+  if (b != last)
+    b->total_length_not_including_first_buffer -= tail;
+
   if (last->current_length > tail)
     {
       last->current_length -= tail;
@@ -178,6 +181,37 @@
   before_last->flags &= ~VLIB_BUFFER_NEXT_PRESENT;
 }
 
+always_inline void
+esp_remove_tail_and_tfc_padding (vlib_main_t *vm, vlib_node_runtime_t *node,
+				 const esp_decrypt_packet_data_t *pd,
+				 vlib_buffer_t *b, vlib_buffer_t *last,
+				 u16 *next, u16 tail, int is_ip6)
+{
+  const u16 total_buffer_length = vlib_buffer_length_in_chain (vm, b);
+  u16 ip_packet_length;
+  if (is_ip6)
+    {
+      const ip6_header_t *ip6 = vlib_buffer_get_current (b);
+      ip_packet_length =
+	clib_net_to_host_u16 (ip6->payload_length) + sizeof (ip6_header_t);
+    }
+  else
+    {
+      const ip4_header_t *ip4 = vlib_buffer_get_current (b);
+      ip_packet_length = clib_net_to_host_u16 (ip4->length);
+    }
+  /* In case of TFC padding, the size of the buffer data needs to be adjusted
+   * to the ip packet length */
+  if (PREDICT_FALSE (total_buffer_length < ip_packet_length + tail))
+    {
+      esp_decrypt_set_next_index (b, node, vm->thread_index,
+				  ESP_DECRYPT_ERROR_NO_TAIL_SPACE, 0, next,
+				  ESP_DECRYPT_NEXT_DROP, pd->sa_index);
+      return;
+    }
+  esp_remove_tail (vm, b, last, total_buffer_length - ip_packet_length);
+}
+
 /* ICV is splitted in last two buffers so move it to the last buffer and
    return pointer to it */
 static_always_inline u8 *
@@ -203,9 +237,12 @@
   before_last->current_length -= first_sz;
   if (before_last == first)
     pd->current_length -= first_sz;
+  else
+    first->total_length_not_including_first_buffer -= first_sz;
   clib_memset (vlib_buffer_get_tail (before_last), 0, first_sz);
   if (dif)
     dif[0] = first_sz;
+  first->total_length_not_including_first_buffer -= last_sz;
   pd2->lb = before_last;
   pd2->icv_removed = 1;
   pd2->free_buffer_index = before_last->next_buffer;
@@ -857,8 +894,7 @@
   u16 tail = sizeof (esp_footer_t) + pad_length + icv_sz;
   u16 tail_orig = sizeof (esp_footer_t) + pad_length + pd->icv_sz;
   b->flags &=
-    ~(VLIB_BUFFER_TOTAL_LENGTH_VALID | VNET_BUFFER_F_L4_CHECKSUM_COMPUTED |
-      VNET_BUFFER_F_L4_CHECKSUM_CORRECT);
+    ~(VNET_BUFFER_F_L4_CHECKSUM_COMPUTED | VNET_BUFFER_F_L4_CHECKSUM_CORRECT);
 
   if ((pd->flags & tun_flags) == 0 && !is_tun)	/* transport mode */
     {
@@ -908,14 +944,16 @@
 	  next[0] = ESP_DECRYPT_NEXT_IP4_INPUT;
 	  b->current_data = pd->current_data + adv;
 	  b->current_length = pd->current_length - adv;
-	  esp_remove_tail (vm, b, lb, tail);
+	  esp_remove_tail_and_tfc_padding (vm, node, pd, b, lb, next, tail,
+					   false);
 	}
       else if (next_header == IP_PROTOCOL_IPV6)
 	{
 	  next[0] = ESP_DECRYPT_NEXT_IP6_INPUT;
 	  b->current_data = pd->current_data + adv;
 	  b->current_length = pd->current_length - adv;
-	  esp_remove_tail (vm, b, lb, tail);
+	  esp_remove_tail_and_tfc_padding (vm, node, pd, b, lb, next, tail,
+					   true);
 	}
       else if (next_header == IP_PROTOCOL_MPLS_IN_IP)
 	{
diff --git a/test/test_ipsec_tun_if_esp.py b/test/test_ipsec_tun_if_esp.py
index e1579eb..5131fbe 100644
--- a/test/test_ipsec_tun_if_esp.py
+++ b/test/test_ipsec_tun_if_esp.py
@@ -5,8 +5,8 @@
 from scapy.layers.ipsec import SecurityAssociation, ESP
 from scapy.layers.l2 import Ether, GRE, Dot1Q
 from scapy.packet import Raw, bind_layers
-from scapy.layers.inet import IP, UDP
-from scapy.layers.inet6 import IPv6
+from scapy.layers.inet import IP, UDP, ICMP
+from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest
 from scapy.contrib.mpls import MPLS
 from asfframework import VppTestRunner, tag_fixme_vpp_workers
 from template_ipsec import (
@@ -367,6 +367,29 @@
         super(TemplateIpsec4TunIfEspUdp, self).tearDown()
 
 
+class TemplateIpsec4TunTfc:
+    """IPsec IPv4 tunnel with TFC"""
+
+    def gen_encrypt_pkts(self, p, sa, sw_intf, src, dst, count=1, payload_size=54):
+        pkt = (
+            IP(src=src, dst=dst, len=28 + payload_size)
+            / ICMP()
+            / Raw(b"X" * payload_size)
+            / Padding(b"Y" * 100)
+        )
+        return [
+            Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac) / sa.encrypt(pkt)
+            for i in range(count)
+        ]
+
+    def verify_decrypted(self, p, rxs):
+        for rx in rxs:
+            self.assert_equal(rx[IP].src, p.remote_tun_if_host)
+            self.assert_equal(rx[IP].dst, self.pg1.remote_ip4)
+            self.assert_equal(rx[IP].len, len(rx[IP]))
+            self.assert_packet_checksums_valid(rx)
+
+
 class TestIpsec4TunIfEsp1(TemplateIpsec4TunIfEsp, IpsecTun4Tests):
     """Ipsec ESP - TUN tests"""
 
@@ -671,6 +694,28 @@
         super(TemplateIpsec6TunIfEspUdp, self).tearDown()
 
 
+class TemplateIpsec6TunTfc:
+    """IPsec IPv6 tunnel with TFC"""
+
+    def gen_encrypt_pkts6(self, p, sa, sw_intf, src, dst, count=1, payload_size=54):
+        return [
+            Ether(src=sw_intf.remote_mac, dst=sw_intf.local_mac)
+            / sa.encrypt(
+                IPv6(src=src, dst=dst, hlim=p.inner_hop_limit, fl=p.inner_flow_label)
+                / ICMPv6EchoRequest(id=0, seq=1, data="X" * payload_size)
+                / Padding(b"Y" * 100)
+            )
+            for i in range(count)
+        ]
+
+    def verify_decrypted6(self, p, rxs):
+        for rx in rxs:
+            self.assert_equal(rx[IPv6].src, p.remote_tun_if_host)
+            self.assert_equal(rx[IPv6].dst, self.pg1.remote_ip6)
+            self.assert_equal(rx[IPv6].plen, len(rx[IPv6].payload))
+            self.assert_packet_checksums_valid(rx)
+
+
 class TestIpsec6TunIfEspUdp(TemplateIpsec6TunIfEspUdp, IpsecTun6Tests):
     """Ipsec ESP 6 UDP tests"""
 
@@ -2470,8 +2515,13 @@
 
 
 @tag_fixme_vpp_workers
+class TestIpsec4TunProtectTfc(TemplateIpsec4TunTfc, TestIpsec4TunProtect):
+    """IPsec IPv4 Tunnel protect with TFC - transport mode"""
+
+
+@tag_fixme_vpp_workers
 class TestIpsec4TunProtectUdp(TemplateIpsec, TemplateIpsec4TunProtect, IpsecTun4):
-    """IPsec IPv4 Tunnel protect - transport mode"""
+    """IPsec IPv4 UDP Tunnel protect - transport mode"""
 
     def setUp(self):
         super(TestIpsec4TunProtectUdp, self).setUp()
@@ -2514,6 +2564,11 @@
 
 
 @tag_fixme_vpp_workers
+class TestIpsec4TunProtectUdpTfc(TemplateIpsec4TunTfc, TestIpsec4TunProtectUdp):
+    """IPsec IPv4 UDP Tunnel protect with TFC - transport mode"""
+
+
+@tag_fixme_vpp_workers
 class TestIpsec4TunProtectTun(TemplateIpsec, TemplateIpsec4TunProtect, IpsecTun4):
     """IPsec IPv4 Tunnel protect - tunnel mode"""
 
@@ -2788,6 +2843,11 @@
 
 
 @tag_fixme_vpp_workers
+class TestIpsec6TunProtectTfc(TemplateIpsec6TunTfc, TestIpsec6TunProtect):
+    """IPsec IPv6 Tunnel protect with TFC - transport mode"""
+
+
+@tag_fixme_vpp_workers
 class TestIpsec6TunProtectTun(TemplateIpsec, TemplateIpsec6TunProtect, IpsecTun6):
     """IPsec IPv6 Tunnel protect - tunnel mode"""
 
@@ -3203,6 +3263,11 @@
         self.unconfig_network(p)
 
 
+@tag_fixme_vpp_workers
+class TestIpsecItf4Tfc(TemplateIpsec4TunTfc, TestIpsecItf4):
+    """IPsec Interface IPv4 with TFC"""
+
+
 class TestIpsecItf4MPLS(TemplateIpsec, TemplateIpsecItf4, IpsecTun4):
     """IPsec Interface MPLSoIPv4"""
 
@@ -3515,6 +3580,11 @@
         self.unconfig_network(p)
 
 
+@tag_fixme_vpp_workers
+class TestIpsecItf6Tfc(TemplateIpsec6TunTfc, TestIpsecItf6):
+    """IPsec Interface IPv6 with TFC"""
+
+
 class TestIpsecMIfEsp4(TemplateIpsec, IpsecTun4):
     """Ipsec P2MP ESP v4 tests"""