From 589fe7ca61111d742ed2e6249728eb54423bab16 Mon Sep 17 00:00:00 2001 From: hedi bouattour Date: Mon, 11 Sep 2023 14:48:12 +0000 Subject: [PATCH] cnat: add flow hash config to cnat translation Type: feature this patch adds a hash config field to cnat translation to use it in load balancing instead of always using default one Change-Id: I5b79642ca8b365b5dcc06664f6c100a9d3830a29 Signed-off-by: hedi bouattour --- src/plugins/cnat/cnat.api | 6 ++- src/plugins/cnat/cnat_api.c | 6 ++- src/plugins/cnat/cnat_translation.c | 16 +++++-- src/plugins/cnat/cnat_translation.h | 8 +++- test/test_cnat.py | 92 ++++++++++++++++++++++++++++++++++++- 5 files changed, 119 insertions(+), 9 deletions(-) diff --git a/src/plugins/cnat/cnat.api b/src/plugins/cnat/cnat.api index 6026432507f..e6ad37dd6eb 100644 --- a/src/plugins/cnat/cnat.api +++ b/src/plugins/cnat/cnat.api @@ -1,6 +1,6 @@ /* Hey Emacs use -*- mode: C -*- */ /* - * Copyright (c) 2016 Cisco and/or its affiliates. + * Copyright (c) 2023 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: @@ -19,10 +19,11 @@ used to control the ABF plugin */ -option version = "0.2.0"; +option version = "0.3.0"; import "vnet/ip/ip_types.api"; import "vnet/fib/fib_types.api"; import "vnet/interface_types.api"; +import "vnet/ip/ip.api"; enum cnat_translation_flags:u8 { @@ -71,6 +72,7 @@ typedef cnat_translation u8 flags; vl_api_cnat_lb_type_t lb_type; u32 n_paths; + vl_api_ip_flow_hash_config_v2_t flow_hash_config; vl_api_cnat_endpoint_tuple_t paths[n_paths]; }; diff --git a/src/plugins/cnat/cnat_api.c b/src/plugins/cnat/cnat_api.c index a2e06a493a7..c578e303499 100644 --- a/src/plugins/cnat/cnat_api.c +++ b/src/plugins/cnat/cnat_api.c @@ -97,6 +97,7 @@ vl_api_cnat_translation_update_t_handler (vl_api_cnat_translation_update_t int rv = 0; u32 pi, n_paths; cnat_lb_type_t lb_type; + flow_hash_config_t flow_hash_config = 0; rv = ip_proto_decode (mp->translation.ip_proto, &ip_proto); @@ -123,7 +124,10 @@ vl_api_cnat_translation_update_t_handler (vl_api_cnat_translation_update_t flags |= CNAT_FLAG_EXCLUSIVE; lb_type = (cnat_lb_type_t) mp->translation.lb_type; - id = cnat_translation_update (&vip, ip_proto, paths, flags, lb_type); + flow_hash_config = (flow_hash_config_t) clib_net_to_host_u32 ( + mp->translation.flow_hash_config); + id = cnat_translation_update (&vip, ip_proto, paths, flags, lb_type, + flow_hash_config); vec_free (paths); diff --git a/src/plugins/cnat/cnat_translation.c b/src/plugins/cnat/cnat_translation.c index 4645c50c3c2..1cdb94f04a6 100644 --- a/src/plugins/cnat/cnat_translation.c +++ b/src/plugins/cnat/cnat_translation.c @@ -221,8 +221,11 @@ cnat_translation_stack (cnat_translation_t * ct) if (trk->ct_flags & CNAT_TRK_ACTIVE) vec_add1 (ct->ct_active_paths, *trk); + flow_hash_config_t fhc = IP_FLOW_HASH_DEFAULT; + if (ct->fhc != 0) + fhc = ct->fhc; lbi = load_balance_create (vec_len (ct->ct_active_paths), - fib_proto_to_dpo (fproto), IP_FLOW_HASH_DEFAULT); + fib_proto_to_dpo (fproto), fhc); ep_idx = 0; vec_foreach (trk, ct->ct_active_paths) @@ -263,7 +266,7 @@ cnat_translation_delete (u32 id) u32 cnat_translation_update (cnat_endpoint_t *vip, ip_protocol_t proto, cnat_endpoint_tuple_t *paths, u8 flags, - cnat_lb_type_t lb_type) + cnat_lb_type_t lb_type, flow_hash_config_t fhc) { cnat_endpoint_tuple_t *path; const cnat_client_t *cc; @@ -296,6 +299,7 @@ cnat_translation_update (cnat_endpoint_t *vip, ip_protocol_t proto, ct->ct_cci = cci; ct->index = ct - cnat_translation_pool; ct->lb_type = lb_type; + ct->fhc = fhc; cnat_add_translation_to_db (cci, vip, proto, ct->index); cnat_client_translation_added (cci); @@ -384,6 +388,11 @@ format_cnat_translation (u8 * s, va_list * args) format_ip_protocol, ct->ct_proto); s = format (s, "lb:%U ", format_cnat_lb_type, ct->lb_type); + if ((ct->fhc == 0) || (ct->fhc == IP_FLOW_HASH_DEFAULT)) + s = format (s, "fhc:0x%x(default)", IP_FLOW_HASH_DEFAULT); + else + s = format (s, "fhc:0x%x", ct->fhc); + vec_foreach (ck, ct->ct_paths) s = format (s, "\n%U", format_cnat_ep_trk, ck, 2); @@ -576,8 +585,9 @@ cnat_translation_cli_add_del (vlib_main_t * vm, } } + flow_hash_config_t fhc = 0; if (INDEX_INVALID == del_index) - cnat_translation_update (&vip, proto, paths, flags, lb_type); + cnat_translation_update (&vip, proto, paths, flags, lb_type, fhc); else cnat_translation_delete (del_index); diff --git a/src/plugins/cnat/cnat_translation.h b/src/plugins/cnat/cnat_translation.h index d5923f04397..9bb3455d9fe 100644 --- a/src/plugins/cnat/cnat_translation.h +++ b/src/plugins/cnat/cnat_translation.h @@ -169,6 +169,11 @@ typedef struct cnat_translation_t_ */ cnat_lb_type_t lb_type; + /** + * Type of flow hash config + */ + flow_hash_config_t fhc; + union { u32 *lb_maglev; @@ -191,7 +196,8 @@ extern u8 *format_cnat_translation (u8 * s, va_list * args); extern u32 cnat_translation_update (cnat_endpoint_t *vip, ip_protocol_t ip_proto, cnat_endpoint_tuple_t *backends, u8 flags, - cnat_lb_type_t lb_type); + cnat_lb_type_t lb_type, + flow_hash_config_t fhc); /** * Delete a translation diff --git a/test/test_cnat.py b/test/test_cnat.py index 096e9f443a9..a7f949db799 100644 --- a/test/test_cnat.py +++ b/test/test_cnat.py @@ -110,11 +110,12 @@ class Endpoint(object): class Translation(VppObject): - def __init__(self, test, iproto, vip, paths): + def __init__(self, test, iproto, vip, paths, fhc): self._test = test self.vip = vip self.iproto = iproto self.paths = paths + self.fhc = fhc self.id = None def __str__(self): @@ -140,6 +141,7 @@ class Translation(VppObject): "ip_proto": self._vl4_proto(), "n_paths": len(self.paths), "paths": self._encoded_paths(), + "flow_hash_config": self.fhc, } ) self._test.registry.register(self, self._test.logger) @@ -381,6 +383,41 @@ class TestCNatTranslation(CnatCommonTestCase): i.admin_down() super(TestCNatTranslation, self).tearDown() + def cnat_fhc_translation(self): + """CNat Translation""" + self.logger.info(self.vapi.cli("sh cnat client")) + self.logger.info(self.vapi.cli("sh cnat translation")) + + for nbr, translation in enumerate(self.mbtranslations): + vip = translation.vip + + # + # Flows to the VIP with same ips and different source ports are loadbalanced identically + # in both cases of flow hash 0x03 (src ip and dst ip) and 0x08 (dst port) + # + ctx = CnatTestContext(self, translation.iproto, vip.is_v6) + for src_pgi, sport in product(range(N_REMOTE_HOSTS), [1234, 1233]): + # from client to vip + ctx.cnat_send(self.pg0, src_pgi, sport, self.pg1, vip.ip, vip.port) + dport1 = ctx.rxs[0][ctx.L4PROTO].dport + ctx._test.assertIn( + dport1, + [translation.paths[0][DST].port, translation.paths[1][DST].port], + ) + ctx.cnat_expect(self.pg0, src_pgi, sport, self.pg1, nbr, dport1) + + ctx.cnat_send( + self.pg0, src_pgi, sport + 122, self.pg1, vip.ip, vip.port + ) + dport2 = ctx.rxs[0][ctx.L4PROTO].dport + ctx._test.assertIn( + dport2, + [translation.paths[0][DST].port, translation.paths[1][DST].port], + ) + ctx.cnat_expect(self.pg0, src_pgi, sport + 122, self.pg1, nbr, dport2) + + ctx._test.assertEqual(dport1, dport2) + def cnat_translation(self): """CNat Translation""" self.logger.info(self.vapi.cli("sh cnat client")) @@ -494,6 +531,46 @@ class TestCNatTranslation(CnatCommonTestCase): ctx.cnat_expect(self.pg0, 0, 1234, self.pg2, 0, vip.port) ctx.cnat_send_icmp_return_error().cnat_expect_icmp_error_return() + def _make_multi_backend_translations(self): + self.translations = [] + self.mbtranslations = [] + self.mbtranslations.append( + Translation( + self, + TCP, + Endpoint(ip="30.0.0.5", port=5555, is_v6=False), + [ + ( + Endpoint(is_v6=False), + Endpoint(pg=self.pg1, pgi=0, port=4001, is_v6=False), + ), + ( + Endpoint(is_v6=False), + Endpoint(pg=self.pg1, pgi=0, port=4005, is_v6=False), + ), + ], + 0x03, # hash only on dst ip and src ip + ).add_vpp_config() + ) + self.mbtranslations.append( + Translation( + self, + TCP, + Endpoint(ip="30.0.0.6", port=5555, is_v6=False), + [ + ( + Endpoint(is_v6=False), + Endpoint(pg=self.pg1, pgi=1, port=4006, is_v6=False), + ), + ( + Endpoint(is_v6=False), + Endpoint(pg=self.pg1, pgi=1, port=4007, is_v6=False), + ), + ], + 0x08, # hash only on dst port + ).add_vpp_config() + ) + def _make_translations_v4(self): self.translations = [] self.translations.append( @@ -507,6 +584,7 @@ class TestCNatTranslation(CnatCommonTestCase): Endpoint(pg=self.pg1, pgi=0, port=4001, is_v6=False), ) ], + 0x9F, ).add_vpp_config() ) self.translations.append( @@ -520,6 +598,7 @@ class TestCNatTranslation(CnatCommonTestCase): Endpoint(pg=self.pg1, pgi=1, port=4002, is_v6=False), ) ], + 0x9F, ).add_vpp_config() ) self.translations.append( @@ -533,6 +612,7 @@ class TestCNatTranslation(CnatCommonTestCase): Endpoint(pg=self.pg1, pgi=2, port=4003, is_v6=False), ) ], + 0x9F, ).add_vpp_config() ) @@ -549,6 +629,7 @@ class TestCNatTranslation(CnatCommonTestCase): Endpoint(pg=self.pg1, pgi=0, port=4001, is_v6=True), ) ], + 0x9F, ).add_vpp_config() ) self.translations.append( @@ -562,6 +643,7 @@ class TestCNatTranslation(CnatCommonTestCase): Endpoint(pg=self.pg1, pgi=1, port=4002, is_v6=True), ) ], + 0x9F, ).add_vpp_config() ) self.translations.append( @@ -575,6 +657,7 @@ class TestCNatTranslation(CnatCommonTestCase): Endpoint(pg=self.pg1, pgi=2, port=4003, is_v6=True), ) ], + 0x9F, ).add_vpp_config() ) @@ -598,6 +681,11 @@ class TestCNatTranslation(CnatCommonTestCase): self._make_translations_v4() self.cnat_translation() + def test_cnat_fhc(self): + # """ CNat Translation flow hash config """ + self._make_multi_backend_translations() + self.cnat_fhc_translation() + class TestCNatSourceNAT(CnatCommonTestCase): """CNat Source NAT""" @@ -797,7 +885,7 @@ class TestCNatDHCP(CnatCommonTestCase): (Endpoint(pg=self.pg1, is_v6=is_v6), Endpoint(pg=self.pg3, is_v6=is_v6)), ] ep = Endpoint(pg=self.pg0, is_v6=is_v6) - t = Translation(self, TCP, ep, paths).add_vpp_config() + t = Translation(self, TCP, ep, paths, 0x9F).add_vpp_config() # Add an address on every interface # and check it is reflected in the cnat config for pg in self.pg_interfaces: -- 2.16.6