From af897c5e3fa76180fbe0634052bde98b4b3c34d7 Mon Sep 17 00:00:00 2001 From: Nathan Skrzypczak Date: Mon, 21 Sep 2020 19:14:08 +0200 Subject: [PATCH] cnat: Add DHCP support Type: feature Change-Id: I4bd50fd672ac35cf14ebda2b0b10ec0b9a208628 Signed-off-by: Nathan Skrzypczak --- src/plugins/cnat/cnat.api | 23 +++ src/plugins/cnat/cnat_api.c | 40 ++++- src/plugins/cnat/cnat_client.c | 6 +- src/plugins/cnat/cnat_node_snat.c | 4 +- src/plugins/cnat/cnat_snat.c | 56 ++++-- src/plugins/cnat/cnat_snat.h | 3 + src/plugins/cnat/cnat_translation.c | 343 ++++++++++++++++++++++++++++++++---- src/plugins/cnat/cnat_translation.h | 83 ++++++--- src/plugins/cnat/cnat_types.c | 76 +++++++- src/plugins/cnat/cnat_types.h | 20 ++- src/plugins/cnat/test/test_cnat.py | 171 ++++++++++++++++-- 11 files changed, 716 insertions(+), 109 deletions(-) diff --git a/src/plugins/cnat/cnat.api b/src/plugins/cnat/cnat.api index 10af9b9f8d7..a9507c90b4d 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 @@ 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 diff --git a/src/plugins/cnat/cnat_api.c b/src/plugins/cnat/cnat_api.c index 2049d440d46..f692451c14b 100644 --- a/src/plugins/cnat/cnat_api.c +++ b/src/plugins/cnat/cnat_api.c @@ -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); } diff --git a/src/plugins/cnat/cnat_client.c b/src/plugins/cnat/cnat_client.c index 314000d785e..1074fccc97f 100644 --- a/src/plugins/cnat/cnat_client.c +++ b/src/plugins/cnat/cnat_client.c @@ -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)); diff --git a/src/plugins/cnat/cnat_node_snat.c b/src/plugins/cnat/cnat_node_snat.c index c0000385ffb..d92200fec05 100644 --- a/src/plugins/cnat/cnat_node_snat.c +++ b/src/plugins/cnat/cnat_node_snat.c @@ -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); } diff --git a/src/plugins/cnat/cnat_snat.c b/src/plugins/cnat/cnat_snat.c index 21ac8257a23..cc83dce4b20 100644 --- a/src/plugins/cnat/cnat_snat.c +++ b/src/plugins/cnat/cnat_snat.c @@ -15,6 +15,7 @@ #include #include +#include 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 [][]", - .function = cnat_set_snat, + .short_help = "cnat snat with [][][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); diff --git a/src/plugins/cnat/cnat_snat.h b/src/plugins/cnat/cnat_snat.h index 326746f8915..6ebb8034dbc 100644 --- a/src/plugins/cnat/cnat_snat.h +++ b/src/plugins/cnat/cnat_snat.h @@ -18,6 +18,9 @@ #include + +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 e453b8a0fda..b128679cded 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 @@ 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); } diff --git a/src/plugins/cnat/cnat_translation.h b/src/plugins/cnat/cnat_translation.h index 748487a908a..cfcbf5bc13b 100644 --- a/src/plugins/cnat/cnat_translation.h +++ b/src/plugins/cnat/cnat_translation.h @@ -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; diff --git a/src/plugins/cnat/cnat_types.c b/src/plugins/cnat/cnat_types.c index a66ebf647ea..c15c2f61bc9 100644 --- a/src/plugins/cnat/cnat_types.c +++ b/src/plugins/cnat/cnat_types.c @@ -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); } diff --git a/src/plugins/cnat/cnat_types.h b/src/plugins/cnat/cnat_types.h index b6b6e012c53..bfddd6e2414 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 @@ 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 * diff --git a/src/plugins/cnat/test/test_cnat.py b/src/plugins/cnat/test/test_cnat.py index 3f8d33cec4e..d46d047e558 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 @@ 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) -- 2.16.6