cnat: add flow hash config to cnat translation 07/39507/13
authorhedi bouattour <hedibouattour2010@gmail.com>
Mon, 11 Sep 2023 14:48:12 +0000 (14:48 +0000)
committerDave Wallace <dwallacelf@gmail.com>
Fri, 6 Oct 2023 18:18:00 +0000 (18:18 +0000)
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 <hedibouattour2010@gmail.com>
src/plugins/cnat/cnat.api
src/plugins/cnat/cnat_api.c
src/plugins/cnat/cnat_translation.c
src/plugins/cnat/cnat_translation.h
test/test_cnat.py

index 6026432..e6ad37d 100644 (file)
@@ -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:
     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];
 };
 
index a2e06a4..c578e30 100644 (file)
@@ -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);
 
index 4645c50..1cdb94f 100644 (file)
@@ -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);
 
index d5923f0..9bb3455 100644 (file)
@@ -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
index 096e9f4..a7f949d 100644 (file)
@@ -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: