cnat: Add DHCP support
Type: feature
Change-Id: I4bd50fd672ac35cf14ebda2b0b10ec0b9a208628
Signed-off-by: Nathan Skrzypczak <nathan.skrzypczak@gmail.com>
diff --git a/src/plugins/cnat/cnat.api b/src/plugins/cnat/cnat.api
index 10af9b9..a9507c9 100644
--- a/src/plugins/cnat/cnat.api
+++ b/src/plugins/cnat/cnat.api
@@ -22,15 +22,21 @@
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 @@
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
diff --git a/src/plugins/cnat/cnat_api.c b/src/plugins/cnat/cnat_api.c
index 2049d44..f692451 100644
--- a/src/plugins/cnat/cnat_api.c
+++ b/src/plugins/cnat/cnat_api.c
@@ -47,8 +47,13 @@
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 @@
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
@@ -259,16 +269,36 @@
}
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);
}
diff --git a/src/plugins/cnat/cnat_client.c b/src/plugins/cnat/cnat_client.c
index 314000d..1074fcc 100644
--- a/src/plugins/cnat/cnat_client.c
+++ b/src/plugins/cnat/cnat_client.c
@@ -48,7 +48,6 @@
{
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 @@
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 @@
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));
diff --git a/src/plugins/cnat/cnat_node_snat.c b/src/plugins/cnat/cnat_node_snat.c
index c000038..d92200f 100644
--- a/src/plugins/cnat/cnat_node_snat.c
+++ b/src/plugins/cnat/cnat_node_snat.c
@@ -121,14 +121,14 @@
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);
}
diff --git a/src/plugins/cnat/cnat_snat.c b/src/plugins/cnat/cnat_snat.c
index 21ac825..cc83dce 100644
--- a/src/plugins/cnat/cnat_snat.c
+++ b/src/plugins/cnat/cnat_snat.c
@@ -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 @@
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 @@
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 (&ip4, &ip6, sw_if_index);
+
done:
unformat_free (line_input);
@@ -200,8 +224,8 @@
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_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);
diff --git a/src/plugins/cnat/cnat_snat.h b/src/plugins/cnat/cnat_snat.h
index 326746f..6ebb803 100644
--- a/src/plugins/cnat/cnat_snat.h
+++ b/src/plugins/cnat/cnat_snat.h
@@ -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);
diff --git a/src/plugins/cnat/cnat_translation.c b/src/plugins/cnat/cnat_translation.c
index e453b8a..b128679 100644
--- a/src/plugins/cnat/cnat_translation.c
+++ b/src/plugins/cnat/cnat_translation.c
@@ -25,6 +25,11 @@
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 @@
.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 @@
(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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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);
}
diff --git a/src/plugins/cnat/cnat_translation.h b/src/plugins/cnat/cnat_translation.h
index 748487a..cfcbf5b 100644
--- a/src/plugins/cnat/cnat_translation.h
+++ b/src/plugins/cnat/cnat_translation.h
@@ -49,6 +49,11 @@
* 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 @@
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 @@
/**
* The client object this translation belongs on
+ * INDEX_INVALID if vip is unresolved
*/
index_t ct_cci;
@@ -116,33 +159,12 @@
*
* @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
*
* @param id the ID as returned from the create
@@ -164,6 +186,19 @@
*/
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 @@
u64 key;
int rv;
- key = (proto << 16) | port;
+ key = (proto << 24) | port;
key = key << 32 | (u32) cti;
bkey.key = key;
diff --git a/src/plugins/cnat/cnat_types.c b/src/plugins/cnat/cnat_types.c
index a66ebf6..c15c2f6 100644
--- a/src/plugins/cnat/cnat_types.c
+++ b/src/plugins/cnat/cnat_types.c
@@ -26,17 +26,75 @@
#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 @@
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);
}
diff --git a/src/plugins/cnat/cnat_types.h b/src/plugins/cnat/cnat_types.h
index b6b6e01..bfddd6e 100644
--- a/src/plugins/cnat/cnat_types.h
+++ b/src/plugins/cnat/cnat_types.h
@@ -49,10 +49,18 @@
#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 @@
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_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
*
diff --git a/src/plugins/cnat/test/test_cnat.py b/src/plugins/cnat/test/test_cnat.py
index 3f8d33c..d46d047 100644
--- a/src/plugins/cnat/test/test_cnat.py
+++ b/src/plugins/cnat/test/test_cnat.py
@@ -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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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)