cnat: Add DHCP support 81/28981/8
authorNathan Skrzypczak <nathan.skrzypczak@gmail.com>
Mon, 21 Sep 2020 17:14:08 +0000 (19:14 +0200)
committerDave Barach <openvpp@barachs.net>
Fri, 25 Sep 2020 19:55:39 +0000 (19:55 +0000)
Type: feature

Change-Id: I4bd50fd672ac35cf14ebda2b0b10ec0b9a208628
Signed-off-by: Nathan Skrzypczak <nathan.skrzypczak@gmail.com>
src/plugins/cnat/cnat.api
src/plugins/cnat/cnat_api.c
src/plugins/cnat/cnat_client.c
src/plugins/cnat/cnat_node_snat.c
src/plugins/cnat/cnat_snat.c
src/plugins/cnat/cnat_snat.h
src/plugins/cnat/cnat_translation.c
src/plugins/cnat/cnat_translation.h
src/plugins/cnat/cnat_types.c
src/plugins/cnat/cnat_types.h
src/plugins/cnat/test/test_cnat.py

index 10af9b9..a9507c9 100644 (file)
 option version = "0.1.0";
 import "vnet/ip/ip_types.api";
 import "vnet/fib/fib_types.api";
+import "vnet/interface_types.api";
 
 enum cnat_translation_flags:u8
 {
   CNAT_TRANSLATION_ALLOC_PORT = 1,
 };
 
+/* An enpoint is either
+ * An IP & a port
+ * An interface, an address familiy and a port */
 typedef cnat_endpoint
 {
   vl_api_address_t addr;
+  vl_api_interface_index_t sw_if_index;
+  vl_api_address_family_t if_af;
   u16 port;
 };
 
@@ -117,6 +123,23 @@ autoreply define cnat_set_snat_addresses
   u32 context;
   vl_api_ip4_address_t snat_ip4;
   vl_api_ip6_address_t snat_ip6;
+  vl_api_interface_index_t sw_if_index;
+};
+
+define cnat_get_snat_addresses
+{
+  u32 client_index;
+  u32 context;
+};
+
+define cnat_get_snat_addresses_reply
+{
+  u32 context;
+  i32 retval;
+  u32 id;
+  vl_api_ip4_address_t snat_ip4;
+  vl_api_ip6_address_t snat_ip6;
+  vl_api_interface_index_t sw_if_index;
 };
 
 autoreply define cnat_add_del_snat_prefix
index 2049d44..f692451 100644 (file)
@@ -47,8 +47,13 @@ static void
 cnat_endpoint_decode (const vl_api_cnat_endpoint_t * in,
                      cnat_endpoint_t * out)
 {
-  ip_address_decode2 (&in->addr, &out->ce_ip);
   out->ce_port = clib_net_to_host_u16 (in->port);
+  out->ce_sw_if_index = clib_net_to_host_u32 (in->sw_if_index);
+  out->ce_flags = 0;
+  if (out->ce_sw_if_index == INDEX_INVALID)
+    ip_address_decode2 (&in->addr, &out->ce_ip);
+  else
+    ip_address_family_decode (in->if_af, &out->ce_ip.version);
 }
 
 static void
@@ -63,8 +68,13 @@ static void
 cnat_endpoint_encode (const cnat_endpoint_t * in,
                      vl_api_cnat_endpoint_t * out)
 {
-  ip_address_encode2 (&in->ce_ip, &out->addr);
   out->port = clib_net_to_host_u16 (in->ce_port);
+  out->sw_if_index = clib_net_to_host_u32 (in->ce_sw_if_index);
+  out->if_af = ip_address_family_encode (in->ce_ip.version);
+  if (in->ce_flags & CNAT_EP_FLAG_RESOLVED)
+    ip_address_encode2 (&in->ce_ip, &out->addr);
+  else
+    clib_memset ((void *) &in->ce_ip, 0, sizeof (in->ce_ip));
 }
 
 static void
@@ -258,17 +268,37 @@ vl_api_cnat_session_purge_t_handler (vl_api_cnat_session_purge_t * mp)
   REPLY_MACRO (VL_API_CNAT_SESSION_PURGE_REPLY);
 }
 
+static void
+vl_api_cnat_get_snat_addresses_t_handler (vl_api_cnat_get_snat_addresses_t
+                                         * mp)
+{
+  vl_api_cnat_get_snat_addresses_reply_t *rmp;
+  int rv = 0;
+
+  /* *INDENT-OFF* */
+  REPLY_MACRO2 (VL_API_CNAT_GET_SNAT_ADDRESSES_REPLY,
+  ({
+    ip6_address_encode (&ip_addr_v6(&cnat_main.snat_ip6.ce_ip), rmp->snat_ip6);
+    ip4_address_encode (&ip_addr_v4(&cnat_main.snat_ip4.ce_ip), rmp->snat_ip4);
+    rmp->sw_if_index = clib_host_to_net_u32 (cnat_main.snat_ip6.ce_sw_if_index);
+  }));
+  /* *INDENT-ON* */
+}
+
 static void
 vl_api_cnat_set_snat_addresses_t_handler (vl_api_cnat_set_snat_addresses_t
                                          * mp)
 {
   vl_api_cnat_set_snat_addresses_reply_t *rmp;
+  u32 sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);
+  ip4_address_t ip4;
+  ip6_address_t ip6;
   int rv = 0;
 
-  cnat_lazy_init ();
+  ip4_address_decode (mp->snat_ip4, &ip4);
+  ip6_address_decode (mp->snat_ip6, &ip6);
 
-  ip4_address_decode (mp->snat_ip4, &cnat_main.snat_ip4);
-  ip6_address_decode (mp->snat_ip6, &cnat_main.snat_ip6);
+  cnat_set_snat (&ip4, &ip6, sw_if_index);
 
   REPLY_MACRO (VL_API_CNAT_SET_SNAT_ADDRESSES_REPLY);
 }
index 314000d..1074fcc 100644 (file)
@@ -48,7 +48,6 @@ cnat_client_destroy (cnat_client_t * cc)
     {
       ASSERT (fib_entry_is_sourced (cc->cc_fei, cnat_fib_source));
       fib_table_entry_delete_index (cc->cc_fei, cnat_fib_source);
-      ASSERT (!fib_entry_is_sourced (cc->cc_fei, cnat_fib_source));
     }
   cnat_client_db_remove (cc);
   dpo_reset (&cc->cc_parent);
@@ -110,6 +109,9 @@ void
 cnat_client_translation_added (index_t cci)
 {
   cnat_client_t *cc;
+  if (INDEX_INVALID == cci)
+    return;
+
   cc = cnat_client_get (cci);
   ASSERT (!(cc->flags & CNAT_FLAG_EXPIRES));
   cc->tr_refcnt++;
@@ -119,6 +121,8 @@ void
 cnat_client_translation_deleted (index_t cci)
 {
   cnat_client_t *cc;
+  if (INDEX_INVALID == cci)
+    return;
 
   cc = cnat_client_get (cci);
   ASSERT (!(cc->flags & CNAT_FLAG_EXPIRES));
index c000038..d92200f 100644 (file)
@@ -121,14 +121,14 @@ cnat_snat_inline (vlib_main_t * vm,
       if (AF_IP4 == ctx->af)
        {
          ip46_address_set_ip4 (&session->value.cs_ip[VLIB_RX],
-                               &cm->snat_ip4);
+                               &ip_addr_v4 (&cm->snat_ip4.ce_ip));
          ip46_address_set_ip4 (&session->value.cs_ip[VLIB_TX],
                                &ip4->dst_address);
        }
       else
        {
          ip46_address_set_ip6 (&session->value.cs_ip[VLIB_RX],
-                               &cm->snat_ip6);
+                               &ip_addr_v6 (&cm->snat_ip6.ce_ip));
          ip46_address_set_ip6 (&session->value.cs_ip[VLIB_TX],
                                &ip6->dst_address);
        }
index 21ac825..cc83dce 100644 (file)
@@ -15,6 +15,7 @@
 
 #include <vnet/ip/ip.h>
 #include <cnat/cnat_snat.h>
+#include <cnat/cnat_translation.h>
 
 static void
 cnat_compute_prefix_lengths_in_search_order (cnat_snat_pfx_table_t *
@@ -157,13 +158,36 @@ format_cnat_snat_prefix (u8 * s, va_list * args)
   return (s);
 }
 
+void
+cnat_set_snat (ip4_address_t * ip4, ip6_address_t * ip6, u32 sw_if_index)
+{
+  cnat_lazy_init ();
+
+  cnat_translation_unwatch_addr (INDEX_INVALID, CNAT_RESOLV_ADDR_SNAT);
+
+  ip_address_set (&cnat_main.snat_ip4.ce_ip, ip4, AF_IP4);
+  ip_address_set (&cnat_main.snat_ip6.ce_ip, ip6, AF_IP6);
+  cnat_main.snat_ip4.ce_sw_if_index = sw_if_index;
+  cnat_main.snat_ip6.ce_sw_if_index = sw_if_index;
+
+  cnat_resolve_ep (&cnat_main.snat_ip4);
+  cnat_resolve_ep (&cnat_main.snat_ip6);
+  cnat_translation_watch_addr (INDEX_INVALID, 0, &cnat_main.snat_ip4,
+                              CNAT_RESOLV_ADDR_SNAT);
+  cnat_translation_watch_addr (INDEX_INVALID, 0, &cnat_main.snat_ip6,
+                              CNAT_RESOLV_ADDR_SNAT);
+}
+
 static clib_error_t *
-cnat_set_snat (vlib_main_t * vm,
-              unformat_input_t * input, vlib_cli_command_t * cmd)
+cnat_set_snat_cli (vlib_main_t * vm,
+                  unformat_input_t * input, vlib_cli_command_t * cmd)
 {
   unformat_input_t _line_input, *line_input = &_line_input;
+  vnet_main_t *vnm = vnet_get_main ();
+  ip4_address_t ip4 = { {0} };
+  ip6_address_t ip6 = { {0} };
   clib_error_t *e = 0;
-  ip_address_t addr;
+  u32 sw_if_index = INDEX_INVALID;
 
   cnat_lazy_init ();
 
@@ -173,15 +197,13 @@ cnat_set_snat (vlib_main_t * vm,
 
   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
     {
-      if (unformat (line_input, "%U", unformat_ip_address, &addr))
-       {
-         if (ip_addr_version (&addr) == AF_IP4)
-           clib_memcpy (&cnat_main.snat_ip4, &ip_addr_v4 (&addr),
-                        sizeof (ip4_address_t));
-         else
-           clib_memcpy (&cnat_main.snat_ip6, &ip_addr_v6 (&addr),
-                        sizeof (ip6_address_t));
-       }
+      if (unformat_user (line_input, unformat_ip4_address, &ip4))
+       ;
+      else if (unformat_user (line_input, unformat_ip6_address, &ip6))
+       ;
+      else if (unformat_user (line_input, unformat_vnet_sw_interface,
+                             vnm, &sw_if_index))
+       ;
       else
        {
          e = clib_error_return (0, "unknown input '%U'",
@@ -190,6 +212,8 @@ cnat_set_snat (vlib_main_t * vm,
        }
     }
 
+  cnat_set_snat (&ip4, &ip6, sw_if_index);
+
 done:
   unformat_free (line_input);
 
@@ -200,8 +224,8 @@ done:
 VLIB_CLI_COMMAND (cnat_set_snat_command, static) =
 {
   .path = "cnat snat with",
-  .short_help = "cnat snat with [<ip4-address>][<ip6-address>]",
-  .function = cnat_set_snat,
+  .short_help = "cnat snat with [<ip4-address>][<ip6-address>][sw_if_index]",
+  .function = cnat_set_snat_cli,
 };
 /* *INDENT-ON* */
 
@@ -252,8 +276,8 @@ cnat_show_snat (vlib_main_t * vm,
 {
   cnat_snat_pfx_table_t *table = &cnat_main.snat_pfx_table;
   vlib_cli_output (vm, "Source NAT\nip4: %U\nip6: %U\n",
-                  format_ip4_address, &cnat_main.snat_ip4,
-                  format_ip6_address, &cnat_main.snat_ip6);
+                  format_cnat_endpoint, &cnat_main.snat_ip4,
+                  format_cnat_endpoint, &cnat_main.snat_ip6);
   vlib_cli_output (vm, "Prefixes:\n%U\n",
                   format_bihash_24_8, &table->ip_hash, 1);
   return (NULL);
index 326746f..6ebb803 100644 (file)
@@ -18,6 +18,9 @@
 
 #include <cnat/cnat_types.h>
 
+
+extern void cnat_set_snat (ip4_address_t * ip4, ip6_address_t * ip6,
+                          u32 sw_if_index);
 extern int cnat_add_snat_prefix (ip_prefix_t * pfx);
 extern int cnat_del_snat_prefix (ip_prefix_t * pfx);
 
index e453b8a..b128679 100644 (file)
 
 cnat_translation_t *cnat_translation_pool;
 clib_bihash_8_8_t cnat_translation_db;
+addr_resolution_t *tr_resolutions;
+
+typedef void (*cnat_if_addr_add_cb_t) (addr_resolution_t * ar,
+                                      ip_address_t * address, u8 is_del);
+cnat_if_addr_add_cb_t *cnat_if_addr_add_cbs;
 
 static fib_node_type_t cnat_translation_fib_node_type;
 
@@ -33,25 +38,68 @@ vlib_combined_counter_main_t cnat_translation_counters = {
   .stat_segment_name = "/net/cnat-translation",
 };
 
+void
+cnat_translation_watch_addr (index_t cti, u64 opaque, cnat_endpoint_t * ep,
+                            cnat_addr_resol_type_t type)
+{
+  addr_resolution_t *ar;
+
+  if (INDEX_INVALID == ep->ce_sw_if_index)
+    return;
+
+  pool_get (tr_resolutions, ar);
+  ar->af = ep->ce_ip.version;
+  ar->sw_if_index = ep->ce_sw_if_index;
+  ar->type = type;
+  ar->opaque = opaque;
+  ar->cti = cti;
+}
+
+static void
+cnat_resolve_ep_tuple (cnat_endpoint_tuple_t * path)
+{
+  cnat_resolve_ep (&path->src_ep);
+  cnat_resolve_ep (&path->dst_ep);
+}
+
+void
+cnat_translation_unwatch_addr (u32 cti, cnat_addr_resol_type_t type)
+{
+  /* Delete tr resolution entries matching translation index */
+  addr_resolution_t *ar;
+  index_t *indexes = 0, *ari;
+  /* *INDENT-OFF* */
+  pool_foreach (ar, tr_resolutions, ({
+    if ((cti == INDEX_INVALID || ar->cti == cti) &&
+      (ar->type == type || CNAT_RESOLV_ADDR_ANY == type))
+      vec_add1(indexes, ar - tr_resolutions);
+  }));
+  /* *INDENT-ON* */
+  vec_foreach (ari, indexes) pool_put_index (tr_resolutions, *ari);
+
+  vec_free (indexes);
+}
+
 static void
 cnat_tracker_release (cnat_ep_trk_t * trk)
 {
+  /* We only track fully resolved endpoints */
+  if (!trk->is_active)
+    return;
   fib_entry_untrack (trk->ct_fei, trk->ct_sibling);
 }
 
 static void
-cnat_tracker_track (index_t cti,
-                   const cnat_endpoint_tuple_t * path, cnat_ep_trk_t * trk)
+cnat_tracker_track (index_t cti, cnat_ep_trk_t * trk)
 {
   fib_prefix_t pfx;
+  /* We only track fully resolved endpoints */
+  trk->is_active = trk->ct_ep[VLIB_TX].ce_flags & CNAT_EP_FLAG_RESOLVED
+    && trk->ct_ep[VLIB_RX].ce_flags & CNAT_EP_FLAG_RESOLVED;
+  if (!trk->is_active)
+    return;
 
-  ip_address_to_fib_prefix (&path->dst_ep.ce_ip, &pfx);
-
-  clib_memcpy (&trk->ct_ep[VLIB_TX], &path->dst_ep,
-              sizeof (trk->ct_ep[VLIB_TX]));
-  clib_memcpy (&trk->ct_ep[VLIB_RX], &path->src_ep,
-              sizeof (trk->ct_ep[VLIB_RX]));
-
+  ip_address_to_fib_prefix (&trk->ct_ep[VLIB_TX].ce_ip, &pfx);
   trk->ct_fei = fib_entry_track (CNAT_FIB_TABLE,
                                 &pfx,
                                 cnat_translation_fib_node_type,
@@ -62,15 +110,32 @@ cnat_tracker_track (index_t cti,
                                   (pfx.fp_proto), &trk->ct_dpo);
 }
 
-void
-cnat_add_translation_to_db (index_t cci, u16 port, ip_protocol_t proto,
-                           index_t cti)
+/**
+ * Add a translation to the bihash
+ *
+ * @param cci the ID of the parent client (invalid if vip not resolved)
+ * @param vip the translation endpoint
+ * @param proto the translation proto
+ * @param cti the translation index to be used as value
+ */
+static void
+cnat_add_translation_to_db (index_t cci, cnat_endpoint_t * vip,
+                           ip_protocol_t proto, index_t cti)
 {
   clib_bihash_kv_8_8_t bkey;
   u64 key;
-
-  key = (proto << 16) | port;
-  key = key << 32 | (u32) cci;
+  if (INDEX_INVALID == cci)
+    {
+      key = proto << 8 | 0x80 | vip->ce_ip.version;
+      key = key << 16 | vip->ce_port;
+      key = key << 32 | (u32) vip->ce_sw_if_index;
+    }
+  else
+    {
+      key = proto << 8;
+      key = key << 16 | vip->ce_port;
+      key = key << 32 | (u32) cci;
+    }
 
   bkey.key = key;
   bkey.value = cti;
@@ -78,14 +143,31 @@ cnat_add_translation_to_db (index_t cci, u16 port, ip_protocol_t proto,
   clib_bihash_add_del_8_8 (&cnat_translation_db, &bkey, 1);
 }
 
-void
-cnat_remove_translation_from_db (index_t cci, u16 port, ip_protocol_t proto)
+/**
+ * Remove a translation from the bihash
+ *
+ * @param cci the ID of the parent client
+ * @param vip the translation endpoint
+ * @param proto the translation proto
+ */
+static void
+cnat_remove_translation_from_db (index_t cci, cnat_endpoint_t * vip,
+                                ip_protocol_t proto)
 {
   clib_bihash_kv_8_8_t bkey;
   u64 key;
-
-  key = (proto << 16) | port;
-  key = key << 32 | (u32) cci;
+  if (INDEX_INVALID == cci)
+    {
+      key = proto << 8 | 0x80 | vip->ce_ip.version;
+      key = key << 16 | vip->ce_port;
+      key = key << 32 | (u32) vip->ce_sw_if_index;
+    }
+  else
+    {
+      key = proto << 8;
+      key = key << 16 | vip->ce_port;
+      key = key << 32 | (u32) cci;
+    }
 
   bkey.key = key;
 
@@ -98,16 +180,21 @@ cnat_translation_stack (cnat_translation_t * ct)
   fib_protocol_t fproto;
   cnat_ep_trk_t *trk;
   dpo_proto_t dproto;
+  u8 ep_idx = 0;
   index_t lbi;
 
   fproto = ip_address_family_to_fib_proto (ct->ct_vip.ce_ip.version);
   dproto = fib_proto_to_dpo (fproto);
 
-  lbi = load_balance_create (vec_len (ct->ct_paths),
-                            fib_proto_to_dpo (fproto), IP_FLOW_HASH_DEFAULT);
+  vec_foreach (trk, ct->ct_paths) if (trk->is_active)
+    ep_idx++;
 
-  vec_foreach (trk, ct->ct_paths)
-    load_balance_set_bucket (lbi, trk - ct->ct_paths, &trk->ct_dpo);
+  lbi = load_balance_create (ep_idx, fib_proto_to_dpo (fproto),
+                            IP_FLOW_HASH_DEFAULT);
+
+  ep_idx = 0;
+  vec_foreach (trk, ct->ct_paths) if (trk->is_active)
+    load_balance_set_bucket (lbi, ep_idx++, &trk->ct_dpo);
 
   dpo_set (&ct->ct_lb, DPO_LOAD_BALANCE, dproto, lbi);
   dpo_stack (cnat_client_dpo, dproto, &ct->ct_lb, &ct->ct_lb);
@@ -128,32 +215,40 @@ cnat_translation_delete (u32 id)
 
   vec_foreach (trk, ct->ct_paths) cnat_tracker_release (trk);
 
-  cnat_remove_translation_from_db (ct->ct_cci, ct->ct_vip.ce_port,
-                                  ct->ct_proto);
+  cnat_remove_translation_from_db (ct->ct_cci, &ct->ct_vip, ct->ct_proto);
   cnat_client_translation_deleted (ct->ct_cci);
+  cnat_translation_unwatch_addr (id, CNAT_RESOLV_ADDR_ANY);
   pool_put (cnat_translation_pool, ct);
 
   return (0);
 }
 
 u32
-cnat_translation_update (const cnat_endpoint_t * vip,
+cnat_translation_update (cnat_endpoint_t * vip,
                         ip_protocol_t proto,
-                        const cnat_endpoint_tuple_t * paths, u8 flags)
+                        cnat_endpoint_tuple_t * paths, u8 flags)
 {
-  const cnat_endpoint_tuple_t *path;
+  cnat_endpoint_tuple_t *path;
   const cnat_client_t *cc;
   cnat_translation_t *ct;
   cnat_ep_trk_t *trk;
   index_t cci;
 
   cnat_lazy_init ();
+  if (cnat_resolve_ep (vip))
+    {
+      /* vip only contains a sw_if_index for now */
+      ct = cnat_find_translation (vip->ce_sw_if_index, vip->ce_port, proto);
+      cci = INDEX_INVALID;
+    }
+  else
+    {
+      /* do we know of this ep's vip */
+      cci = cnat_client_add (&vip->ce_ip, flags);
+      cc = cnat_client_get (cci);
 
-  /* do we know of this ep's vip */
-  cci = cnat_client_add (&vip->ce_ip, flags);
-  cc = cnat_client_get (cci);
-
-  ct = cnat_find_translation (cc->parent_cci, vip->ce_port, proto);
+      ct = cnat_find_translation (cc->parent_cci, vip->ce_port, proto);
+    }
 
   if (NULL == ct)
     {
@@ -164,8 +259,7 @@ cnat_translation_update (const cnat_endpoint_t * vip,
       ct->ct_cci = cci;
       ct->index = ct - cnat_translation_pool;
 
-      cnat_add_translation_to_db (cci, ct->ct_vip.ce_port, ct->ct_proto,
-                                 ct->index);
+      cnat_add_translation_to_db (cci, vip, proto, ct->index);
       cnat_client_translation_added (cci);
 
       vlib_validate_combined_counter (&cnat_translation_counters, ct->index);
@@ -173,6 +267,10 @@ cnat_translation_update (const cnat_endpoint_t * vip,
     }
   ct->flags = flags;
 
+  cnat_translation_unwatch_addr (ct->index, CNAT_RESOLV_ADDR_ANY);
+  cnat_translation_watch_addr (ct->index, 0, vip,
+                              CNAT_RESOLV_ADDR_TRANSLATION);
+
   vec_foreach (trk, ct->ct_paths)
   {
     cnat_tracker_release (trk);
@@ -180,11 +278,26 @@ cnat_translation_update (const cnat_endpoint_t * vip,
 
   vec_reset_length (ct->ct_paths);
 
+  u64 path_idx = 0;
   vec_foreach (path, paths)
   {
+    cnat_resolve_ep_tuple (path);
+    cnat_translation_watch_addr (ct->index,
+                                path_idx << 32 | VLIB_RX, &path->src_ep,
+                                CNAT_RESOLV_ADDR_BACKEND);
+    cnat_translation_watch_addr (ct->index,
+                                path_idx << 32 | VLIB_TX, &path->dst_ep,
+                                CNAT_RESOLV_ADDR_BACKEND);
+    path_idx++;
+
     vec_add2 (ct->ct_paths, trk, 1);
 
-    cnat_tracker_track (ct->index, path, trk);
+    clib_memcpy (&trk->ct_ep[VLIB_TX], &path->dst_ep,
+                sizeof (trk->ct_ep[VLIB_TX]));
+    clib_memcpy (&trk->ct_ep[VLIB_RX], &path->src_ep,
+                sizeof (trk->ct_ep[VLIB_RX]));
+
+    cnat_tracker_track (ct->index, trk);
   }
 
   cnat_translation_stack (ct);
@@ -404,14 +517,154 @@ cnat_translation_cli_add_del (vlib_main_t * vm,
 VLIB_CLI_COMMAND (cnat_translation_cli_add_del_command, static) =
 {
   .path = "cnat translation",
-  .short_help = "cnat translation [add|del] proto [TCP|UDP] [vip|real] [ip] [port] [to [ip] [port]->[ip] [port]]",
+  .short_help = "cnat translation [add|del] proto [TCP|UDP] [vip|real] [ip|sw_if_index [v6]] [port] [to [ip|sw_if_index [v6]] [port]->[ip|sw_if_index [v6]] [port]]",
   .function = cnat_translation_cli_add_del,
 };
 /* *INDENT-ON* */
 
+static void
+cnat_if_addr_add_del_translation_cb (addr_resolution_t * ar,
+                                    ip_address_t * address, u8 is_del)
+{
+  cnat_translation_t *ct;
+  ct = cnat_translation_get (ar->cti);
+  if (!is_del && ct->ct_vip.ce_flags & CNAT_EP_FLAG_RESOLVED)
+    return;
+
+  cnat_remove_translation_from_db (ct->ct_cci, &ct->ct_vip, ct->ct_proto);
+
+  if (is_del)
+    {
+      ct->ct_vip.ce_flags &= ~CNAT_EP_FLAG_RESOLVED;
+      ct->ct_cci = INDEX_INVALID;
+      cnat_client_translation_deleted (ct->ct_cci);
+      /* Are there remaining addresses ? */
+      if (0 == cnat_resolve_addr (ar->sw_if_index, ar->af, address))
+       is_del = 0;
+    }
+
+  if (!is_del)
+    {
+      ct->ct_cci = cnat_client_add (address, ct->flags);
+      cnat_client_translation_added (ct->ct_cci);
+      ip_address_copy (&ct->ct_vip.ce_ip, address);
+      ct->ct_vip.ce_flags |= CNAT_EP_FLAG_RESOLVED;
+    }
+
+  cnat_add_translation_to_db (ct->ct_cci, &ct->ct_vip, ct->ct_proto,
+                             ct->index);
+}
+
+static void
+cnat_if_addr_add_del_backend_cb (addr_resolution_t * ar,
+                                ip_address_t * address, u8 is_del)
+{
+  cnat_translation_t *ct;
+  cnat_ep_trk_t *trk;
+  cnat_endpoint_t *ep;
+
+  u8 direction = ar->opaque & 0xf;
+  u32 path_idx = ar->opaque >> 32;
+
+  ct = cnat_translation_get (ar->cti);
+
+  trk = &ct->ct_paths[path_idx];
+  ep = &trk->ct_ep[direction];
+
+  if (!is_del && ep->ce_flags & CNAT_EP_FLAG_RESOLVED)
+    return;
+
+  ASSERT (ep->ce_sw_if_index == ar->sw_if_index);
+
+  if (is_del)
+    {
+      ep->ce_flags &= ~CNAT_EP_FLAG_RESOLVED;
+      /* Are there remaining addresses ? */
+      if (0 == cnat_resolve_addr (ar->sw_if_index, ar->af, address))
+       is_del = 0;
+    }
+
+  if (!is_del)
+    {
+      ip_address_copy (&ep->ce_ip, address);
+      ep->ce_flags |= CNAT_EP_FLAG_RESOLVED;
+    }
+  cnat_tracker_track (ar->cti, trk);
+
+  cnat_translation_stack (ct);
+}
+
+static void
+cnat_if_addr_add_del_snat_cb (addr_resolution_t * ar, ip_address_t * address,
+                             u8 is_del)
+{
+  cnat_endpoint_t *ep;
+  ep = AF_IP4 == ar->af ? &cnat_main.snat_ip4 : &cnat_main.snat_ip6;
+
+  if (!is_del && ep->ce_flags & CNAT_EP_FLAG_RESOLVED)
+    return;
+
+  if (is_del)
+    {
+      ep->ce_flags &= ~CNAT_EP_FLAG_RESOLVED;
+      /* Are there remaining addresses ? */
+      if (0 == cnat_resolve_addr (ar->sw_if_index, ar->af, address))
+       is_del = 0;
+    }
+
+  if (!is_del)
+    {
+      ip_address_copy (&ep->ce_ip, address);
+      ep->ce_flags |= CNAT_EP_FLAG_RESOLVED;
+    }
+
+}
+
+static void
+cnat_if_addr_add_del_callback (u32 sw_if_index, ip_address_t * address,
+                              u8 is_del)
+{
+  addr_resolution_t *ar;
+  /* *INDENT-OFF* */
+  pool_foreach (ar, tr_resolutions, ({
+    if (ar->sw_if_index != sw_if_index)
+      continue;
+    if (ar->af != ip_addr_version (address))
+      continue;
+    cnat_if_addr_add_cbs[ar->type] (ar, address, is_del);
+  }));
+  /* *INDENT-ON* */
+}
+
+static void
+cnat_ip6_if_addr_add_del_callback (struct ip6_main_t *im,
+                                  uword opaque, u32 sw_if_index,
+                                  ip6_address_t * address,
+                                  u32 address_length, u32 if_address_index,
+                                  u32 is_del)
+{
+  ip_address_t addr;
+  ip_address_set (&addr, address, AF_IP6);
+  cnat_if_addr_add_del_callback (sw_if_index, &addr, is_del);
+}
+
+static void
+cnat_ip4_if_addr_add_del_callback (struct ip4_main_t *im,
+                                  uword opaque, u32 sw_if_index,
+                                  ip4_address_t * address,
+                                  u32 address_length, u32 if_address_index,
+                                  u32 is_del)
+{
+  ip_address_t addr;
+  ip_address_set (&addr, address, AF_IP4);
+  cnat_if_addr_add_del_callback (sw_if_index, &addr, is_del);
+}
+
 static clib_error_t *
 cnat_translation_init (vlib_main_t * vm)
 {
+  ip4_main_t *i4m = &ip4_main;
+  ip6_main_t *i6m = &ip6_main;
   cnat_main_t *cm = &cnat_main;
   cnat_translation_fib_node_type =
     fib_node_register_new_type (&cnat_translation_vft);
@@ -420,6 +673,20 @@ cnat_translation_init (vlib_main_t * vm)
                        cm->translation_hash_buckets,
                        cm->translation_hash_memory);
 
+  ip4_add_del_interface_address_callback_t cb4;
+  cb4.function = cnat_ip4_if_addr_add_del_callback;
+  vec_add1 (i4m->add_del_interface_address_callbacks, cb4);
+
+  ip6_add_del_interface_address_callback_t cb6;
+  cb6.function = cnat_ip6_if_addr_add_del_callback;
+  vec_add1 (i6m->add_del_interface_address_callbacks, cb6);
+
+  vec_validate (cnat_if_addr_add_cbs, CNAT_ADDR_N_RESOLUTIONS);
+  cnat_if_addr_add_cbs[CNAT_RESOLV_ADDR_BACKEND] =
+    cnat_if_addr_add_del_backend_cb;
+  cnat_if_addr_add_cbs[CNAT_RESOLV_ADDR_SNAT] = cnat_if_addr_add_del_snat_cb;
+  cnat_if_addr_add_cbs[CNAT_RESOLV_ADDR_TRANSLATION] =
+    cnat_if_addr_add_del_translation_cb;
   return (NULL);
 }
 
index 748487a..cfcbf5b 100644 (file)
@@ -49,6 +49,11 @@ typedef struct cnat_ep_trk_t_
    * The forwarding contributed by the entry
    */
   dpo_id_t ct_dpo;
+
+  /**
+   * Allows to disable if not resolved yet
+   */
+  u8 is_active;
 } cnat_ep_trk_t;
 
 typedef enum cnat_translation_flag_t_
@@ -56,6 +61,43 @@ typedef enum cnat_translation_flag_t_
   CNAT_TRANSLATION_FLAG_ALLOCATE_PORT = (1 << 0),
 } cnat_translation_flag_t;
 
+typedef enum
+{
+  CNAT_RESOLV_ADDR_ANY,
+  CNAT_RESOLV_ADDR_BACKEND,
+  CNAT_RESOLV_ADDR_SNAT,
+  CNAT_RESOLV_ADDR_TRANSLATION,
+  CNAT_ADDR_N_RESOLUTIONS,
+} cnat_addr_resol_type_t;
+
+/**
+ * Entry used to account for a translation's backend
+ * waiting for address resolution
+ */
+typedef struct addr_resolution_t_
+{
+  /**
+   * The interface index to resolve
+   */
+  u32 sw_if_index;
+  /**
+   * ip4 or ip6 resolution
+   */
+  ip_address_family_t af;
+  /**
+   * The cnat_addr_resolution_t
+   */
+  cnat_addr_resol_type_t type;
+  /**
+   * Translation index
+   */
+  index_t cti;
+  /**
+   * Callback data
+   */
+  u64 opaque;
+} addr_resolution_t;
+
 /**
  * A Translation represents the translation of a VEP to one of a set
  * of real server addresses
@@ -89,6 +131,7 @@ typedef struct cnat_translation_t_
 
   /**
    * The client object this translation belongs on
+   * INDEX_INVALID if vip is unresolved
    */
   index_t ct_cci;
 
@@ -116,32 +159,11 @@ extern u8 *format_cnat_translation (u8 * s, va_list * args);
  *
  * @return the ID of the translation. used to delete and gather stats
  */
-extern u32 cnat_translation_update (const cnat_endpoint_t * vip,
+extern u32 cnat_translation_update (cnat_endpoint_t * vip,
                                    ip_protocol_t ip_proto,
-                                   const cnat_endpoint_tuple_t *
+                                   cnat_endpoint_tuple_t *
                                    backends, u8 flags);
 
-/**
- * Add a translation to the bihash
- *
- * @param cci the ID of the parent client
- * @param port the translation port
- * @param proto the translation proto
- * @param cti the translation index to be used as value
- */
-extern void cnat_add_translation_to_db (index_t cci, u16 port,
-                                       ip_protocol_t proto, index_t cti);
-
-/**
- * Remove a translation from the bihash
- *
- * @param cci the ID of the parent client
- * @param port the translation port
- * @param proto the translation proto
- */
-extern void cnat_remove_translation_from_db (index_t cci, u16 port,
-                                            ip_protocol_t proto);
-
 /**
  * Delete a translation
  *
@@ -164,6 +186,19 @@ extern void cnat_translation_walk (cnat_translation_walk_cb_t cb, void *ctx);
  */
 extern int cnat_translation_purge (void);
 
+/**
+ * Add an address resolution request
+ */
+extern void cnat_translation_watch_addr (index_t cti, u64 opaque,
+                                        cnat_endpoint_t * ep,
+                                        cnat_addr_resol_type_t type);
+
+/**
+ * Cleanup matching addr resolution requests
+ */
+extern void cnat_translation_unwatch_addr (u32 cti,
+                                          cnat_addr_resol_type_t type);
+
 /*
  * Data plane functions
  */
@@ -182,7 +217,7 @@ cnat_find_translation (index_t cti, u16 port, ip_protocol_t proto)
   u64 key;
   int rv;
 
-  key = (proto << 16) | port;
+  key = (proto << 24) | port;
   key = key << 32 | (u32) cti;
 
   bkey.key = key;
index a66ebf6..c15c2f6 100644 (file)
@@ -26,17 +26,75 @@ char *cnat_error_strings[] = {
 #undef cnat_error
 };
 
+u8
+cnat_resolve_addr (u32 sw_if_index, ip_address_family_t af,
+                  ip_address_t * addr)
+{
+  /* Tries to resolve IP from sw_if_index
+   * returns 1 if we need to schedule DHCP */
+  if (INDEX_INVALID == sw_if_index)
+    return 0;
+  if (af == AF_IP6)
+    {
+      ip6_address_t *ip6 = 0;
+      ip6 = ip6_interface_first_address (&ip6_main, sw_if_index);
+      if (ip6)
+       {
+         ip_address_set (addr, ip6, AF_IP6);
+         return 0;
+       }
+      else
+       return 1;
+    }
+  else
+    {
+      ip4_address_t *ip4 = 0;
+      ip4 = ip4_interface_first_address (&ip4_main, sw_if_index, 0);
+      if (ip4)
+       {
+         ip_address_set (addr, ip4, AF_IP4);
+         return 0;
+       }
+      else
+       return 1;
+    }
+}
+
+u8
+cnat_resolve_ep (cnat_endpoint_t * ep)
+{
+  int rv;
+  rv = cnat_resolve_addr (ep->ce_sw_if_index, ep->ce_ip.version, &ep->ce_ip);
+  if (0 == rv)
+    ep->ce_flags |= CNAT_EP_FLAG_RESOLVED;
+  return rv;
+}
+
 uword
 unformat_cnat_ep (unformat_input_t * input, va_list * args)
 {
   cnat_endpoint_t *a = va_arg (*args, cnat_endpoint_t *);
+  vnet_main_t *vnm = vnet_get_main ();
   int port = 0;
 
   clib_memset (a, 0, sizeof (*a));
+  a->ce_sw_if_index = INDEX_INVALID;
   if (unformat (input, "%U %d", unformat_ip_address, &a->ce_ip, &port))
     ;
   else if (unformat_user (input, unformat_ip_address, &a->ce_ip))
     ;
+  else if (unformat (input, "%U v6 %d", unformat_vnet_sw_interface,
+                    vnm, &a->ce_sw_if_index, &port))
+    a->ce_ip.version = AF_IP6;
+  else if (unformat (input, "%U v6", unformat_vnet_sw_interface,
+                    vnm, &a->ce_sw_if_index))
+    a->ce_ip.version = AF_IP6;
+  else if (unformat (input, "%U %d", unformat_vnet_sw_interface,
+                    vnm, &a->ce_sw_if_index, &port))
+    a->ce_ip.version = AF_IP4;
+  else if (unformat_user (input, unformat_vnet_sw_interface,
+                         vnm, &a->ce_sw_if_index))
+    a->ce_ip.version = AF_IP4;
   else if (unformat (input, "%d", &port))
     ;
   else
@@ -65,9 +123,21 @@ u8 *
 format_cnat_endpoint (u8 * s, va_list * args)
 {
   cnat_endpoint_t *cep = va_arg (*args, cnat_endpoint_t *);
-
-  s = format (s, "%U;%d", format_ip_address, &cep->ce_ip, cep->ce_port);
-
+  vnet_main_t *vnm = vnet_get_main ();
+  if (INDEX_INVALID == cep->ce_sw_if_index)
+    s = format (s, "%U;%d", format_ip_address, &cep->ce_ip, cep->ce_port);
+  else
+    {
+      if (cep->ce_flags & CNAT_EP_FLAG_RESOLVED)
+       s = format (s, "%U (%U);%d", format_vnet_sw_if_index_name, vnm,
+                   cep->ce_sw_if_index, format_ip_address, &cep->ce_ip,
+                   cep->ce_port);
+      else
+       s =
+         format (s, "%U (%U);%d", format_vnet_sw_if_index_name, vnm,
+                 cep->ce_sw_if_index, format_ip_address_family,
+                 cep->ce_ip.version, cep->ce_port);
+    }
   return (s);
 }
 
index b6b6e01..bfddd6e 100644 (file)
 
 #define MIN_SRC_PORT ((u16) 0xC000)
 
+typedef enum
+{
+  /* Endpoint addr has been resolved */
+  CNAT_EP_FLAG_RESOLVED = 1,
+} cnat_ep_flag_t;
+
 typedef struct cnat_endpoint_t_
 {
   ip_address_t ce_ip;
+  u32 ce_sw_if_index;
   u16 ce_port;
+  u8 ce_flags;
 } cnat_endpoint_t;
 
 typedef struct cnat_endpoint_tuple_t_
@@ -118,10 +126,10 @@ typedef struct cnat_main_
   clib_rwlock_t ts_lock;
 
   /* Ip4 Address to use for source NATing */
-  ip4_address_t snat_ip4;
+  cnat_endpoint_t snat_ip4;
 
   /* Ip6 Address to use for source NATing */
-  ip6_address_t snat_ip6;
+  cnat_endpoint_t snat_ip6;
 
   /* Longest prefix Match table for source NATing */
   cnat_snat_pfx_table_t snat_pfx_table;
@@ -192,6 +200,14 @@ extern void cnat_lazy_init ();
  */
 extern void cnat_enable_disable_scanner (cnat_scanner_cmd_t event_type);
 
+/**
+ * Resolve endpoint address
+ */
+extern u8 cnat_resolve_ep (cnat_endpoint_t * ep);
+extern u8 cnat_resolve_addr (u32 sw_if_index, ip_address_family_t af,
+                            ip_address_t * addr);
+
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
index 3f8d33c..d46d047 100644 (file)
@@ -3,7 +3,8 @@
 import unittest
 
 from framework import VppTestCase, VppTestRunner
-from vpp_ip import DpoProto
+from vpp_ip import DpoProto, INVALID_INDEX
+from itertools import product
 
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether
@@ -23,25 +24,34 @@ from vpp_papi import VppEnum
 N_PKTS = 15
 
 
-def find_cnat_translation(test, id):
-    ts = test.vapi.cnat_translation_dump()
-    for t in ts:
-        if id == t.translation.id:
-            return True
-    return False
-
-
 class Ep(object):
     """ CNat endpoint """
 
-    def __init__(self, ip, port, l4p=TCP):
+    def __init__(self, ip=None, port=0, l4p=TCP,
+                 sw_if_index=INVALID_INDEX, is_v6=False):
         self.ip = ip
+        if ip is None:
+            self.ip = "::" if is_v6 else "0.0.0.0"
         self.port = port
         self.l4p = l4p
+        self.sw_if_index = sw_if_index
+        if is_v6:
+            self.if_af = VppEnum.vl_api_address_family_t.ADDRESS_IP6
+        else:
+            self.if_af = VppEnum.vl_api_address_family_t.ADDRESS_IP4
 
     def encode(self):
         return {'addr': self.ip,
-                'port': self.port}
+                'port': self.port,
+                'sw_if_index': self.sw_if_index,
+                'if_af': self.if_af}
+
+    @classmethod
+    def from_pg(cls, pg, is_v6=False):
+        if pg is None:
+            return cls(is_v6=is_v6)
+        else:
+            return cls(sw_if_index=pg.sw_if_index, is_v6=is_v6)
 
     @property
     def isV6(self):
@@ -77,6 +87,9 @@ class VppCNatTranslation(VppObject):
         for path in self.paths:
             self.encoded_paths.append(path.encode())
 
+    def __str__(self):
+        return ("%s %s %s" % (self.vip, self.iproto, self.paths))
+
     @property
     def vl4_proto(self):
         ip_proto = VppEnum.vl_api_ip_proto_t
@@ -85,9 +98,6 @@ class VppCNatTranslation(VppObject):
             TCP: ip_proto.IP_API_PROTO_TCP,
         }[self.iproto]
 
-    def delete(self):
-        r = self._test.vapi.cnat_translation_del(id=self.id)
-
     def add_vpp_config(self):
         r = self._test.vapi.cnat_translation_update(
             {'vip': self.vip.encode(),
@@ -111,10 +121,13 @@ class VppCNatTranslation(VppObject):
         self._test.registry.register(self, self._test.logger)
 
     def remove_vpp_config(self):
-        self._test.vapi.cnat_translation_del(self.id)
+        self._test.vapi.cnat_translation_del(id=self.id)
 
     def query_vpp_config(self):
-        return find_cnat_translation(self._test, self.id)
+        for t in self._test.vapi.cnat_translation_dump():
+            if self.id == t.translation.id:
+                return t.translation
+        return None
 
     def object_id(self):
         return ("cnat-translation-%s" % (self.vip))
@@ -191,6 +204,7 @@ class TestCNatTranslation(VppTestCase):
                 rxs = self.send_and_expect(self.pg0,
                                            p1 * N_PKTS,
                                            self.pg1)
+                self.logger.info(self.vapi.cli("show trace max 1"))
 
                 for rx in rxs:
                     self.assert_packet_checksums_valid(rx)
@@ -352,7 +366,7 @@ class TestCNatTranslation(VppTestCase):
             n_tries += 1
             sessions = self.vapi.cnat_session_dump()
             self.sleep(2)
-            print(self.vapi.cli("show cnat session verbose"))
+            self.logger.info(self.vapi.cli("show cnat session verbose"))
 
         self.assertTrue(n_tries < 100)
         self.vapi.cli("test cnat scanner off")
@@ -374,7 +388,7 @@ class TestCNatTranslation(VppTestCase):
                                          self.pg2)
 
         for tr in trs:
-            tr.delete()
+            tr.remove_vpp_config()
 
         self.assertTrue(self.vapi.cnat_session_dump())
         self.vapi.cnat_session_purge()
@@ -762,10 +776,13 @@ class TestCNatSourceNAT(VppTestCase):
                 l4p(sport=sports[nbr], dport=dports[nbr]) /
                 Raw())
 
+            self.vapi.cli("trace add pg-input 1")
             rxs = self.send_and_expect(
                 self.pg0,
                 p1 * N_PKTS,
                 self.pg1)
+            self.logger.info(self.vapi.cli("show trace max 1"))
+
             for rx in rxs:
                 self.assert_packet_checksums_valid(rx)
                 self.assertEqual(rx[IP46].dst, remote_addr)
@@ -825,5 +842,123 @@ class TestCNatSourceNAT(VppTestCase):
             self.vapi.cnat_session_purge()
 
 
+class TestCNatDHCP(VppTestCase):
+    """ CNat Translation """
+    extra_vpp_punt_config = ["cnat", "{",
+                             "session-db-buckets", "64",
+                             "session-cleanup-timeout", "0.1",
+                             "session-max-age", "1",
+                             "tcp-max-age", "1",
+                             "scanner", "off", "}"]
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestCNatDHCP, cls).setUpClass()
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestCNatDHCP, cls).tearDownClass()
+
+    def tearDown(self):
+        for i in self.pg_interfaces:
+            i.admin_down()
+        super(TestCNatDHCP, self).tearDown()
+
+    def create_translation(self, vip_pg, *args, is_v6=False):
+        vip = Ep(sw_if_index=vip_pg.sw_if_index, is_v6=is_v6)
+        paths = []
+        for (src_pg, dst_pg) in args:
+            paths.append(EpTuple(
+                Ep.from_pg(src_pg, is_v6=is_v6),
+                Ep.from_pg(dst_pg, is_v6=is_v6)
+            ))
+        t1 = VppCNatTranslation(self, TCP, vip, paths)
+        t1.add_vpp_config()
+        return t1
+
+    def make_addr(self, sw_if_index, i, is_v6):
+        if is_v6:
+            return "fd01:%x::%u" % (sw_if_index, i + 1)
+        else:
+            return "172.16.%u.%u" % (sw_if_index, i)
+
+    def make_prefix(self, sw_if_index, i, is_v6):
+        if is_v6:
+            return "%s/128" % self.make_addr(sw_if_index, i, is_v6)
+        else:
+            return "%s/32" % self.make_addr(sw_if_index, i, is_v6)
+
+    def check_resolved(self, tr, vip_pg, *args, i=0, is_v6=False):
+        qt1 = tr.query_vpp_config()
+        self.assertEqual(str(qt1.vip.addr), self.make_addr(
+            vip_pg.sw_if_index, i, is_v6))
+        for (src_pg, dst_pg), path in zip(args, qt1.paths):
+            if src_pg:
+                self.assertEqual(str(path.src_ep.addr), self.make_addr(
+                    src_pg.sw_if_index, i, is_v6))
+            if dst_pg:
+                self.assertEqual(str(path.dst_ep.addr), self.make_addr(
+                    dst_pg.sw_if_index, i, is_v6))
+
+    def config_ips(self, rng, is_add=1, is_v6=False):
+        for pg, i in product(self.pg_interfaces, rng):
+            self.vapi.sw_interface_add_del_address(
+                sw_if_index=pg.sw_if_index,
+                prefix=self.make_prefix(pg.sw_if_index, i, is_v6),
+                is_add=is_add)
+
+    def test_dhcp_v4(self):
+        self.create_pg_interfaces(range(5))
+        for i in self.pg_interfaces:
+            i.admin_up()
+        pglist = (self.pg0, (self.pg1, self.pg2), (self.pg1, self.pg4))
+        t1 = self.create_translation(*pglist)
+        self.config_ips([0])
+        self.check_resolved(t1, *pglist)
+        self.config_ips([1])
+        self.config_ips([0], is_add=0)
+        self.check_resolved(t1, *pglist, i=1)
+        self.config_ips([1], is_add=0)
+        t1.remove_vpp_config()
+
+    def test_dhcp_v6(self):
+        self.create_pg_interfaces(range(5))
+        for i in self.pg_interfaces:
+            i.admin_up()
+        pglist = (self.pg0, (self.pg1, self.pg2), (self.pg1, self.pg4))
+        t1 = self.create_translation(*pglist, is_v6=True)
+        self.config_ips([0], is_v6=True)
+        self.check_resolved(t1, *pglist, is_v6=True)
+        self.config_ips([1], is_v6=True)
+        self.config_ips([0], is_add=0, is_v6=True)
+        self.check_resolved(t1, *pglist, i=1, is_v6=True)
+        self.config_ips([1], is_add=0, is_v6=True)
+        t1.remove_vpp_config()
+
+    def test_dhcp_snat(self):
+        self.create_pg_interfaces(range(1))
+        for i in self.pg_interfaces:
+            i.admin_up()
+        self.vapi.cnat_set_snat_addresses(sw_if_index=self.pg0.sw_if_index)
+        self.config_ips([0], is_v6=False)
+        self.config_ips([0], is_v6=True)
+        r = self.vapi.cnat_get_snat_addresses()
+        self.assertEqual(str(r.snat_ip4), self.make_addr(
+            self.pg0.sw_if_index, 0, False))
+        self.assertEqual(str(r.snat_ip6), self.make_addr(
+            self.pg0.sw_if_index, 0, True))
+        self.config_ips([1], is_v6=False)
+        self.config_ips([1], is_v6=True)
+        self.config_ips([0], is_add=0, is_v6=False)
+        self.config_ips([0], is_add=0, is_v6=True)
+        r = self.vapi.cnat_get_snat_addresses()
+        self.assertEqual(str(r.snat_ip4), self.make_addr(
+            self.pg0.sw_if_index, 1, False))
+        self.assertEqual(str(r.snat_ip6), self.make_addr(
+            self.pg0.sw_if_index, 1, True))
+        self.config_ips([1], is_add=0, is_v6=False)
+        self.config_ips([1], is_add=0, is_v6=True)
+
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)