ipsec: new api for sa ips and ports updates 59/37059/11
authorArthur de Kerhor <arthurdekerhor@gmail.com>
Wed, 31 Aug 2022 17:13:03 +0000 (19:13 +0200)
committerBeno�t Ganne <bganne@cisco.com>
Fri, 16 Dec 2022 10:13:24 +0000 (10:13 +0000)
Useful to update the tunnel paramaters and udp ports (NAT-T) of an SA
without having to rekey. Could be done by deleting and re-adding the
SA but it would not preserve the anti-replay window if there is one.
Use case: a nat update/reboot between the 2 endpoints of the tunnel.

Type: feature
Change-Id: Icf5c0aac218603e8aa9a008ed6f614e4a6db59a0
Signed-off-by: Arthur de Kerhor <arthurdekerhor@gmail.com>
src/vnet/ipsec/ipsec.api
src/vnet/ipsec/ipsec_api.c
src/vnet/ipsec/ipsec_sa.c
src/vnet/ipsec/ipsec_sa.h
src/vnet/ipsec/ipsec_test.c
test/template_ipsec.py
test/test_ipsec_tun_if_esp.py
test/vpp_ipsec.py

index 56ad646..6cbad6e 100644 (file)
@@ -201,6 +201,28 @@ autoreply define ipsec_sad_entry_del
   u32 id;
 };
 
+/** \brief An API to update the tunnel parameters and the ports associated with an SA
+
+    Used in the NAT-T case when the NAT data changes
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sa_id - the id of the SA to update
+    @param is_tun - update the tunnel if non-zero, else update only the ports
+    @param tunnel - sender context, to match reply w/ request
+    @param udp_src_port - new src port for NAT-T. Used if different from 0xffff
+    @param udp_dst_port - new dst port for NAT-T. Used if different from 0xffff
+ */
+autoreply define ipsec_sad_entry_update
+{
+  u32 client_index;
+  u32 context;
+  u32 sad_id;
+  bool is_tun;
+  vl_api_tunnel_t tunnel;
+  u16 udp_src_port [default=0xffff];
+  u16 udp_dst_port [default=0xffff];
+};
+
 define ipsec_sad_entry_add_del_reply
 {
   option deprecated;
index 378f493..3994150 100644 (file)
@@ -567,6 +567,31 @@ vl_api_ipsec_sad_entry_add_t_handler (vl_api_ipsec_sad_entry_add_t *mp)
                { rmp->stat_index = htonl (sa_index); });
 }
 
+static void
+vl_api_ipsec_sad_entry_update_t_handler (vl_api_ipsec_sad_entry_update_t *mp)
+{
+  vl_api_ipsec_sad_entry_update_reply_t *rmp;
+  u32 id;
+  tunnel_t tun = { 0 };
+  int rv;
+
+  id = ntohl (mp->sad_id);
+
+  if (mp->is_tun)
+    {
+      rv = tunnel_decode (&mp->tunnel, &tun);
+
+      if (rv)
+       goto out;
+    }
+
+  rv = ipsec_sa_update (id, htons (mp->udp_src_port), htons (mp->udp_dst_port),
+                       &tun, mp->is_tun);
+
+out:
+  REPLY_MACRO (VL_API_IPSEC_SAD_ENTRY_UPDATE_REPLY);
+}
+
 static void
 send_ipsec_spds_details (ipsec_spd_t * spd, vl_api_registration_t * reg,
                         u32 context)
index a330abc..295323b 100644 (file)
@@ -171,6 +171,137 @@ ipsec_sa_set_async_op_ids (ipsec_sa_t * sa)
   /* *INDENT-ON* */
 }
 
+int
+ipsec_sa_update (u32 id, u16 src_port, u16 dst_port, const tunnel_t *tun,
+                bool is_tun)
+{
+  ipsec_main_t *im = &ipsec_main;
+  ipsec_sa_t *sa;
+  u32 sa_index;
+  uword *p;
+  int rv;
+
+  p = hash_get (im->sa_index_by_sa_id, id);
+  if (!p)
+    return VNET_API_ERROR_NO_SUCH_ENTRY;
+
+  sa = ipsec_sa_get (p[0]);
+  sa_index = sa - ipsec_sa_pool;
+
+  if (is_tun && ipsec_sa_is_set_IS_TUNNEL (sa) &&
+      (ip_address_cmp (&tun->t_src, &sa->tunnel.t_src) != 0 ||
+       ip_address_cmp (&tun->t_dst, &sa->tunnel.t_dst) != 0))
+    {
+      /* if the source IP is updated for an inbound SA under a tunnel protect,
+       we need to update the tun_protect DB with the new src IP */
+      if (ipsec_sa_is_set_IS_INBOUND (sa) &&
+         ip_address_cmp (&tun->t_src, &sa->tunnel.t_src) != 0 &&
+         !ip46_address_is_zero (&tun->t_src.ip))
+       {
+         if (ip46_address_is_ip4 (&sa->tunnel.t_src.ip))
+           {
+             ipsec4_tunnel_kv_t old_key, new_key;
+             clib_bihash_kv_8_16_t res,
+               *bkey = (clib_bihash_kv_8_16_t *) &old_key;
+
+             ipsec4_tunnel_mk_key (&old_key, &sa->tunnel.t_src.ip.ip4,
+                                   clib_host_to_net_u32 (sa->spi));
+             ipsec4_tunnel_mk_key (&new_key, &tun->t_src.ip.ip4,
+                                   clib_host_to_net_u32 (sa->spi));
+
+             if (!clib_bihash_search_8_16 (&im->tun4_protect_by_key, bkey,
+                                           &res))
+               {
+                 clib_bihash_add_del_8_16 (&im->tun4_protect_by_key, &res, 0);
+                 res.key = new_key.key;
+                 clib_bihash_add_del_8_16 (&im->tun4_protect_by_key, &res, 1);
+               }
+           }
+         else
+           {
+             ipsec6_tunnel_kv_t old_key = {
+          .key = {
+            .remote_ip =  sa->tunnel.t_src.ip.ip6,
+            .spi = clib_host_to_net_u32 (sa->spi),
+          },
+        }, new_key = {
+          .key = {
+            .remote_ip = tun->t_src.ip.ip6,
+            .spi = clib_host_to_net_u32 (sa->spi),
+          }};
+             clib_bihash_kv_24_16_t res,
+               *bkey = (clib_bihash_kv_24_16_t *) &old_key;
+
+             if (!clib_bihash_search_24_16 (&im->tun6_protect_by_key, bkey,
+                                            &res))
+               {
+                 clib_bihash_add_del_24_16 (&im->tun6_protect_by_key, &res,
+                                            0);
+                 clib_memcpy (&res.key, &new_key.key, 3);
+                 clib_bihash_add_del_24_16 (&im->tun6_protect_by_key, &res,
+                                            1);
+               }
+           }
+       }
+      tunnel_unresolve (&sa->tunnel);
+      tunnel_copy (tun, &sa->tunnel);
+      if (!ipsec_sa_is_set_IS_INBOUND (sa))
+       {
+         dpo_reset (&sa->dpo);
+
+         sa->tunnel_flags = sa->tunnel.t_encap_decap_flags;
+
+         rv = tunnel_resolve (&sa->tunnel, FIB_NODE_TYPE_IPSEC_SA, sa_index);
+
+         if (rv)
+           {
+             hash_unset (im->sa_index_by_sa_id, sa->id);
+             pool_put (ipsec_sa_pool, sa);
+             return rv;
+           }
+         ipsec_sa_stack (sa);
+         /* generate header templates */
+         if (ipsec_sa_is_set_IS_TUNNEL_V6 (sa))
+           {
+             tunnel_build_v6_hdr (&sa->tunnel,
+                                  (ipsec_sa_is_set_UDP_ENCAP (sa) ?
+                                           IP_PROTOCOL_UDP :
+                                           IP_PROTOCOL_IPSEC_ESP),
+                                  &sa->ip6_hdr);
+           }
+         else
+           {
+             tunnel_build_v4_hdr (&sa->tunnel,
+                                  (ipsec_sa_is_set_UDP_ENCAP (sa) ?
+                                           IP_PROTOCOL_UDP :
+                                           IP_PROTOCOL_IPSEC_ESP),
+                                  &sa->ip4_hdr);
+           }
+       }
+    }
+
+  if (ipsec_sa_is_set_UDP_ENCAP (sa))
+    {
+      if (dst_port != IPSEC_UDP_PORT_NONE &&
+         dst_port != clib_net_to_host_u16 (sa->udp_hdr.dst_port))
+       {
+         if (ipsec_sa_is_set_IS_INBOUND (sa))
+           {
+             ipsec_unregister_udp_port (
+               clib_net_to_host_u16 (sa->udp_hdr.dst_port),
+               !ipsec_sa_is_set_IS_TUNNEL_V6 (sa));
+             ipsec_register_udp_port (dst_port,
+                                      !ipsec_sa_is_set_IS_TUNNEL_V6 (sa));
+           }
+         sa->udp_hdr.dst_port = clib_host_to_net_u16 (dst_port);
+       }
+      if (src_port != IPSEC_UDP_PORT_NONE &&
+         src_port != clib_net_to_host_u16 (sa->udp_hdr.src_port))
+       sa->udp_hdr.src_port = clib_host_to_net_u16 (src_port);
+    }
+  return (0);
+}
+
 int
 ipsec_sa_add_and_lock (u32 id, u32 spi, ipsec_protocol_t proto,
                       ipsec_crypto_alg_t crypto_alg, const ipsec_key_t *ck,
index 057e8cd..df079b1 100644 (file)
@@ -270,6 +270,8 @@ extern vlib_simple_counter_main_t ipsec_sa_lost_counters;
 
 extern void ipsec_mk_key (ipsec_key_t * key, const u8 * data, u8 len);
 
+extern int ipsec_sa_update (u32 id, u16 src_port, u16 dst_port,
+                           const tunnel_t *tun, bool is_tun);
 extern int
 ipsec_sa_add_and_lock (u32 id, u32 spi, ipsec_protocol_t proto,
                       ipsec_crypto_alg_t crypto_alg, const ipsec_key_t *ck,
index f143619..16d8642 100644 (file)
@@ -306,6 +306,12 @@ api_ipsec_sad_entry_add_del_v3 (vat_main_t *vat)
   return -1;
 }
 
+static int
+api_ipsec_sad_entry_update (vat_main_t *vat)
+{
+  return -1;
+}
+
 static int
 api_ipsec_tunnel_protect_update (vat_main_t *vat)
 {
index 9d9ea3a..d00216c 100644 (file)
@@ -1291,7 +1291,7 @@ class IpsecTun4(object):
         decrypt_pkts = []
         for rx in rxs:
             if p.nat_header:
-                self.assertEqual(rx[UDP].dport, 4500)
+                self.assertEqual(rx[UDP].dport, p.nat_header.dport)
             self.assert_packet_checksums_valid(rx)
             self.assertEqual(len(rx) - len(Ether()), rx[IP].len)
             try:
index 61a66d4..fe05f98 100644 (file)
@@ -300,7 +300,7 @@ class TemplateIpsec4TunIfEspUdp(TemplateIpsec4TunProtect, TemplateIpsec):
                 # which strips them
                 self.assertTrue(rx.haslayer(UDP))
                 self.assert_equal(rx[UDP].sport, p.nat_header.sport)
-                self.assert_equal(rx[UDP].dport, 4500)
+                self.assert_equal(rx[UDP].dport, p.nat_header.dport)
 
                 pkt = sa.decrypt(rx[IP])
                 if not pkt.haslayer(IP):
@@ -344,7 +344,8 @@ class TemplateIpsec4TunIfEspUdp(TemplateIpsec4TunProtect, TemplateIpsec):
             p.crypt_algo_vpp_id,
             p.crypt_key,
             self.vpp_esp_protocol,
-            flags=p.flags,
+            flags=p.flags
+            | VppEnum.vl_api_ipsec_sad_flags_t.IPSEC_API_SAD_FLAG_IS_INBOUND,
             udp_src=p.nat_header.sport,
             udp_dst=p.nat_header.dport,
         )
@@ -429,6 +430,24 @@ class TestIpsec4TunIfEspUdpGCM(TemplateIpsec4TunIfEspUdp, IpsecTun4Tests):
         p.salt = 0
 
 
+class TestIpsec4TunIfEspUdpUpdate(TemplateIpsec4TunIfEspUdp, IpsecTun4Tests):
+    """Ipsec ESP UDP update tests"""
+
+    tun4_input_node = "ipsec4-tun-input"
+
+    def setUp(self):
+        super(TestIpsec4TunIfEspUdpUpdate, self).setUp()
+        p = self.ipv4_params
+        p.nat_header = UDP(sport=6565, dport=7676)
+        config_tun_params(p, self.encryption_type, p.tun_if)
+        p.tun_sa_in.update_vpp_config(
+            udp_src=p.nat_header.dport, udp_dst=p.nat_header.sport
+        )
+        p.tun_sa_out.update_vpp_config(
+            udp_src=p.nat_header.sport, udp_dst=p.nat_header.dport
+        )
+
+
 class TestIpsec4TunIfEsp2(TemplateIpsec4TunIfEsp, IpsecTcpTests):
     """Ipsec ESP - TCP tests"""
 
@@ -583,7 +602,7 @@ class TemplateIpsec6TunIfEspUdp(TemplateIpsec6TunProtect, TemplateIpsec):
                 # which strips them
                 self.assertTrue(rx.haslayer(UDP))
                 self.assert_equal(rx[UDP].sport, p.nat_header.sport)
-                self.assert_equal(rx[UDP].dport, 4500)
+                self.assert_equal(rx[UDP].dport, p.nat_header.dport)
 
                 pkt = sa.decrypt(rx[IP])
                 if not pkt.haslayer(IP):
@@ -629,7 +648,8 @@ class TemplateIpsec6TunIfEspUdp(TemplateIpsec6TunProtect, TemplateIpsec):
             p.crypt_algo_vpp_id,
             p.crypt_key,
             self.vpp_esp_protocol,
-            flags=p.flags,
+            flags=p.flags
+            | VppEnum.vl_api_ipsec_sad_flags_t.IPSEC_API_SAD_FLAG_IS_INBOUND,
             udp_src=p.nat_header.sport,
             udp_dst=p.nat_header.dport,
         )
@@ -2957,7 +2977,8 @@ class TemplateIpsecItf4(object):
             self.vpp_esp_protocol,
             dst,
             src,
-            flags=p.flags,
+            flags=p.flags
+            | VppEnum.vl_api_ipsec_sad_flags_t.IPSEC_API_SAD_FLAG_IS_INBOUND,
         )
         p.tun_sa_in.add_vpp_config()
 
@@ -3063,6 +3084,20 @@ class TestIpsecItf4(TemplateIpsec, TemplateIpsecItf4, IpsecTun4):
 
         self.tun4_encrypt_node_name = "esp4-encrypt-tun"
 
+        # update the SA tunnel
+        config_tun_params(
+            p, self.encryption_type, None, self.pg2.local_ip4, self.pg2.remote_ip4
+        )
+        p.tun_sa_in.update_vpp_config(
+            is_tun=True, tun_src=self.pg2.remote_ip4, tun_dst=self.pg2.local_ip4
+        )
+        p.tun_sa_out.update_vpp_config(
+            is_tun=True, tun_src=self.pg2.local_ip4, tun_dst=self.pg2.remote_ip4
+        )
+        self.verify_tun_44(p, count=n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), 5 * n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), 4 * n_pkts)
+
         self.vapi.cli("clear interfaces")
 
         # rekey - create new SAs and update the tunnel protection
index eb0209f..f50d491 100644 (file)
@@ -295,6 +295,26 @@ class VppIpsecSA(VppObject):
         self.test.registry.register(self, self.test.logger)
         return self
 
+    def update_vpp_config(
+        self, udp_src=None, udp_dst=None, is_tun=False, tun_src=None, tun_dst=None
+    ):
+        if is_tun:
+            if tun_src:
+                self.tun_src = ip_address(text_type(tun_src))
+            if tun_dst:
+                self.tun_dst = ip_address(text_type(tun_dst))
+        if udp_src:
+            self.udp_src = udp_src
+        if udp_dst:
+            self.udp_dst = udp_dst
+        self.test.vapi.ipsec_sad_entry_update(
+            sad_id=self.id,
+            is_tun=is_tun,
+            tunnel=self.tunnel_encode(),
+            udp_src_port=udp_src,
+            udp_dst_port=udp_dst,
+        )
+
     def remove_vpp_config(self):
         self.test.vapi.ipsec_sad_entry_del(id=self.id)