From 4117b24acb4241d7f2ef38248bc254f6a4a7b422 Mon Sep 17 00:00:00 2001 From: Arthur de Kerhor Date: Wed, 31 Aug 2022 19:13:03 +0200 Subject: ipsec: new api for sa ips and ports updates 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 --- src/vnet/ipsec/ipsec.api | 22 +++++++ src/vnet/ipsec/ipsec_api.c | 25 ++++++++ src/vnet/ipsec/ipsec_sa.c | 131 ++++++++++++++++++++++++++++++++++++++++++ src/vnet/ipsec/ipsec_sa.h | 2 + src/vnet/ipsec/ipsec_test.c | 6 ++ test/template_ipsec.py | 2 +- test/test_ipsec_tun_if_esp.py | 45 +++++++++++++-- test/vpp_ipsec.py | 20 +++++++ 8 files changed, 247 insertions(+), 6 deletions(-) diff --git a/src/vnet/ipsec/ipsec.api b/src/vnet/ipsec/ipsec.api index 56ad646d001..6cbad6e74fa 100644 --- a/src/vnet/ipsec/ipsec.api +++ b/src/vnet/ipsec/ipsec.api @@ -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; diff --git a/src/vnet/ipsec/ipsec_api.c b/src/vnet/ipsec/ipsec_api.c index 378f493ec14..3994150d895 100644 --- a/src/vnet/ipsec/ipsec_api.c +++ b/src/vnet/ipsec/ipsec_api.c @@ -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) diff --git a/src/vnet/ipsec/ipsec_sa.c b/src/vnet/ipsec/ipsec_sa.c index a330abcb244..295323b8f7e 100644 --- a/src/vnet/ipsec/ipsec_sa.c +++ b/src/vnet/ipsec/ipsec_sa.c @@ -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, diff --git a/src/vnet/ipsec/ipsec_sa.h b/src/vnet/ipsec/ipsec_sa.h index 057e8cd9bff..df079b13872 100644 --- a/src/vnet/ipsec/ipsec_sa.h +++ b/src/vnet/ipsec/ipsec_sa.h @@ -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, diff --git a/src/vnet/ipsec/ipsec_test.c b/src/vnet/ipsec/ipsec_test.c index f1436193636..16d86427ea7 100644 --- a/src/vnet/ipsec/ipsec_test.c +++ b/src/vnet/ipsec/ipsec_test.c @@ -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) { diff --git a/test/template_ipsec.py b/test/template_ipsec.py index 9d9ea3a86d3..d00216c7308 100644 --- a/test/template_ipsec.py +++ b/test/template_ipsec.py @@ -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: diff --git a/test/test_ipsec_tun_if_esp.py b/test/test_ipsec_tun_if_esp.py index 61a66d40a4e..fe05f98e6e6 100644 --- a/test/test_ipsec_tun_if_esp.py +++ b/test/test_ipsec_tun_if_esp.py @@ -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 diff --git a/test/vpp_ipsec.py b/test/vpp_ipsec.py index eb0209fc57a..f50d491c396 100644 --- a/test/vpp_ipsec.py +++ b/test/vpp_ipsec.py @@ -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) -- cgit 1.2.3-korg