| /* |
| * Copyright (c) 2020 Cisco and/or its affiliates. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <vnet/fib/fib_source.h> |
| #include <vnet/fib/fib_table.h> |
| #include <vnet/fib/fib_entry_track.h> |
| #include <vnet/dpo/load_balance.h> |
| #include <vnet/dpo/drop_dpo.h> |
| |
| #include <cnat/cnat_translation.h> |
| #include <cnat/cnat_session.h> |
| #include <cnat/cnat_client.h> |
| |
| 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; |
| |
| vlib_combined_counter_main_t cnat_translation_counters = { |
| .name = "cnat-translation", |
| .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, 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 (&trk->ct_ep[VLIB_TX].ce_ip, &pfx); |
| trk->ct_fei = fib_entry_track (CNAT_FIB_TABLE, |
| &pfx, |
| cnat_translation_fib_node_type, |
| cti, &trk->ct_sibling); |
| |
| fib_entry_contribute_forwarding (trk->ct_fei, |
| fib_forw_chain_type_from_fib_proto |
| (pfx.fp_proto), &trk->ct_dpo); |
| } |
| |
| /** |
| * 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; |
| 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; |
| |
| clib_bihash_add_del_8_8 (&cnat_translation_db, &bkey, 1); |
| } |
| |
| /** |
| * 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; |
| 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; |
| |
| clib_bihash_add_del_8_8 (&cnat_translation_db, &bkey, 0); |
| } |
| |
| static void |
| 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); |
| |
| vec_foreach (trk, ct->ct_paths) if (trk->is_active) |
| ep_idx++; |
| |
| 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); |
| } |
| |
| int |
| cnat_translation_delete (u32 id) |
| { |
| cnat_translation_t *ct; |
| cnat_ep_trk_t *trk; |
| |
| if (pool_is_free_index (cnat_translation_pool, id)) |
| return (VNET_API_ERROR_NO_SUCH_ENTRY); |
| |
| ct = pool_elt_at_index (cnat_translation_pool, id); |
| |
| dpo_reset (&ct->ct_lb); |
| |
| vec_foreach (trk, ct->ct_paths) cnat_tracker_release (trk); |
| |
| 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 (cnat_endpoint_t * vip, |
| ip_protocol_t proto, |
| cnat_endpoint_tuple_t * paths, u8 flags) |
| { |
| 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); |
| |
| ct = cnat_find_translation (cc->parent_cci, vip->ce_port, proto); |
| } |
| |
| if (NULL == ct) |
| { |
| pool_get_zero (cnat_translation_pool, ct); |
| |
| clib_memcpy (&ct->ct_vip, vip, sizeof (*vip)); |
| ct->ct_proto = proto; |
| ct->ct_cci = cci; |
| ct->index = ct - cnat_translation_pool; |
| |
| cnat_add_translation_to_db (cci, vip, proto, ct->index); |
| cnat_client_translation_added (cci); |
| |
| vlib_validate_combined_counter (&cnat_translation_counters, ct->index); |
| vlib_zero_combined_counter (&cnat_translation_counters, ct->index); |
| } |
| 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); |
| } |
| |
| 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); |
| |
| 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); |
| |
| return (ct->index); |
| } |
| |
| void |
| cnat_translation_walk (cnat_translation_walk_cb_t cb, void *ctx) |
| { |
| u32 api; |
| |
| /* *INDENT-OFF* */ |
| pool_foreach_index(api, cnat_translation_pool, |
| ({ |
| if (!cb(api, ctx)) |
| break; |
| })); |
| /* *INDENT-ON* */ |
| } |
| |
| static u8 * |
| format_cnat_ep_trk (u8 * s, va_list * args) |
| { |
| cnat_ep_trk_t *ck = va_arg (*args, cnat_ep_trk_t *); |
| u32 indent = va_arg (*args, u32); |
| |
| s = format (s, "%U->%U", format_cnat_endpoint, &ck->ct_ep[VLIB_RX], |
| format_cnat_endpoint, &ck->ct_ep[VLIB_TX]); |
| s = format (s, "\n%Ufib-entry:%d", format_white_space, indent, ck->ct_fei); |
| s = format (s, "\n%U%U", |
| format_white_space, indent, format_dpo_id, &ck->ct_dpo, 6); |
| |
| return (s); |
| } |
| |
| u8 * |
| format_cnat_translation (u8 * s, va_list * args) |
| { |
| cnat_translation_t *ct = va_arg (*args, cnat_translation_t *); |
| cnat_ep_trk_t *ck; |
| |
| s = format (s, "[%d] ", ct->index); |
| s = format (s, "%U %U", format_cnat_endpoint, &ct->ct_vip, |
| format_ip_protocol, ct->ct_proto); |
| |
| vec_foreach (ck, ct->ct_paths) |
| s = format (s, "\n%U", format_cnat_ep_trk, ck, 2); |
| |
| /* If printing a trace, the LB object might be deleted */ |
| if (!pool_is_free_index (load_balance_pool, ct->ct_lb.dpoi_index)) |
| { |
| s = format (s, "\n via:"); |
| s = format (s, "\n%U%U", |
| format_white_space, 2, format_dpo_id, &ct->ct_lb, 2); |
| } |
| |
| return (s); |
| } |
| |
| static clib_error_t * |
| cnat_translation_show (vlib_main_t * vm, |
| unformat_input_t * input, vlib_cli_command_t * cmd) |
| { |
| index_t cti; |
| cnat_translation_t *ct; |
| |
| cti = INDEX_INVALID; |
| |
| while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) |
| { |
| if (unformat (input, "%d", &cti)) |
| ; |
| else |
| return (clib_error_return (0, "unknown input '%U'", |
| format_unformat_error, input)); |
| } |
| |
| if (INDEX_INVALID == cti) |
| { |
| /* *INDENT-OFF* */ |
| pool_foreach_index(cti, cnat_translation_pool, |
| ({ |
| ct = pool_elt_at_index (cnat_translation_pool, cti); |
| vlib_cli_output(vm, "%U", format_cnat_translation, ct); |
| })); |
| /* *INDENT-ON* */ |
| } |
| else |
| { |
| vlib_cli_output (vm, "Invalid policy ID:%d", cti); |
| } |
| |
| return (NULL); |
| } |
| |
| int |
| cnat_translation_purge (void) |
| { |
| /* purge all the translations */ |
| index_t tri, *trp, *trs = NULL; |
| |
| /* *INDENT-OFF* */ |
| pool_foreach_index(tri, cnat_translation_pool, |
| ({ |
| vec_add1(trs, tri); |
| })); |
| /* *INDENT-ON* */ |
| |
| vec_foreach (trp, trs) cnat_translation_delete (*trp); |
| |
| ASSERT (0 == pool_elts (cnat_translation_pool)); |
| |
| vec_free (trs); |
| |
| return (0); |
| } |
| |
| /* *INDENT-OFF* */ |
| VLIB_CLI_COMMAND (cnat_translation_show_cmd_node, static) = { |
| .path = "show cnat translation", |
| .function = cnat_translation_show, |
| .short_help = "show cnat translation <VIP>", |
| .is_mp_safe = 1, |
| }; |
| /* *INDENT-ON* */ |
| |
| static fib_node_t * |
| cnat_translation_get_node (fib_node_index_t index) |
| { |
| cnat_translation_t *ct = cnat_translation_get (index); |
| return (&(ct->ct_node)); |
| } |
| |
| static cnat_translation_t * |
| cnat_translation_get_from_node (fib_node_t * node) |
| { |
| return ((cnat_translation_t *) (((char *) node) - |
| STRUCT_OFFSET_OF (cnat_translation_t, |
| ct_node))); |
| } |
| |
| static void |
| cnat_translation_last_lock_gone (fib_node_t * node) |
| { |
| /**/} |
| |
| /* |
| * A back walk has reached this ABF policy |
| */ |
| static fib_node_back_walk_rc_t |
| cnat_translation_back_walk_notify (fib_node_t * node, |
| fib_node_back_walk_ctx_t * ctx) |
| { |
| /* |
| * re-stack the fmask on the n-eos of the via |
| */ |
| cnat_translation_t *ct = cnat_translation_get_from_node (node); |
| |
| cnat_translation_stack (ct); |
| |
| return (FIB_NODE_BACK_WALK_CONTINUE); |
| } |
| |
| /* |
| * The translation's graph node virtual function table |
| */ |
| static const fib_node_vft_t cnat_translation_vft = { |
| .fnv_get = cnat_translation_get_node, |
| .fnv_last_lock = cnat_translation_last_lock_gone, |
| .fnv_back_walk = cnat_translation_back_walk_notify, |
| }; |
| |
| static clib_error_t * |
| cnat_translation_cli_add_del (vlib_main_t * vm, |
| unformat_input_t * input, |
| vlib_cli_command_t * cmd) |
| { |
| u32 del_index = INDEX_INVALID; |
| ip_protocol_t proto = IP_PROTOCOL_TCP; |
| cnat_endpoint_t vip; |
| u8 flags = CNAT_FLAG_EXCLUSIVE; |
| cnat_endpoint_tuple_t tmp, *paths = NULL, *path; |
| |
| while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) |
| { |
| if (unformat (input, "add")) |
| del_index = INDEX_INVALID; |
| else if (unformat (input, "del %d", &del_index)) |
| ; |
| else if (unformat (input, "proto %U", unformat_ip_protocol, &proto)) |
| ; |
| else if (unformat (input, "vip %U", unformat_cnat_ep, &vip)) |
| flags = CNAT_FLAG_EXCLUSIVE; |
| else if (unformat (input, "real %U", unformat_cnat_ep, &vip)) |
| flags = 0; |
| else if (unformat (input, "to %U", unformat_cnat_ep_tuple, &tmp)) |
| { |
| pool_get (paths, path); |
| clib_memcpy (path, &tmp, sizeof (cnat_endpoint_tuple_t)); |
| } |
| else |
| return (clib_error_return (0, "unknown input '%U'", |
| format_unformat_error, input)); |
| } |
| |
| if (INDEX_INVALID == del_index) |
| cnat_translation_update (&vip, proto, paths, flags); |
| else |
| cnat_translation_delete (del_index); |
| |
| pool_free (paths); |
| return (NULL); |
| } |
| |
| /* *INDENT-OFF* */ |
| 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|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); |
| |
| clib_bihash_init_8_8 (&cnat_translation_db, "CNat translation DB", |
| 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); |
| } |
| |
| VLIB_INIT_FUNCTION (cnat_translation_init); |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |