diff options
Diffstat (limited to 'src/vnet')
-rw-r--r-- | src/vnet/adj/adj.c | 51 | ||||
-rw-r--r-- | src/vnet/adj/adj.h | 15 | ||||
-rw-r--r-- | src/vnet/adj/adj_bfd.c | 184 | ||||
-rw-r--r-- | src/vnet/adj/adj_delegate.c | 144 | ||||
-rw-r--r-- | src/vnet/adj/adj_delegate.h | 104 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_main.c | 23 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_main.h | 44 | ||||
-rw-r--r-- | src/vnet/bfd/bfd_udp.h | 8 | ||||
-rw-r--r-- | src/vnet/fib/fib_attached_export.c | 75 | ||||
-rw-r--r-- | src/vnet/fib/fib_attached_export.h | 4 | ||||
-rw-r--r-- | src/vnet/fib/fib_bfd.c | 197 | ||||
-rw-r--r-- | src/vnet/fib/fib_entry.c | 50 | ||||
-rw-r--r-- | src/vnet/fib/fib_entry.h | 1 | ||||
-rw-r--r-- | src/vnet/fib/fib_entry_cover.c | 45 | ||||
-rw-r--r-- | src/vnet/fib/fib_entry_cover.h | 5 | ||||
-rw-r--r-- | src/vnet/fib/fib_entry_delegate.c | 100 | ||||
-rw-r--r-- | src/vnet/fib/fib_entry_delegate.h | 33 | ||||
-rw-r--r-- | src/vnet/fib/fib_path.c | 52 | ||||
-rw-r--r-- | src/vnet/fib/fib_test.c | 522 | ||||
-rw-r--r-- | src/vnet/fib/ip4_fib.c | 16 | ||||
-rw-r--r-- | src/vnet/fib/ip6_fib.c | 21 |
21 files changed, 1550 insertions, 144 deletions
diff --git a/src/vnet/adj/adj.c b/src/vnet/adj/adj.c index 7cf9e9d0..90182006 100644 --- a/src/vnet/adj/adj.c +++ b/src/vnet/adj/adj.c @@ -18,6 +18,7 @@ #include <vnet/adj/adj_glean.h> #include <vnet/adj/adj_midchain.h> #include <vnet/adj/adj_mcast.h> +#include <vnet/adj/adj_delegate.h> #include <vnet/fib/fib_node_list.h> /* Adjacency packet/byte counters indexed by adjacency index. */ @@ -57,13 +58,14 @@ adj_alloc (fib_protocol_t proto) vlib_validate_combined_counter(&adjacency_counters, adj_get_index(adj)); - adj->rewrite_header.sw_if_index = ~0; - adj->lookup_next_index = 0; - fib_node_init(&adj->ia_node, FIB_NODE_TYPE_ADJ); + adj->ia_nh_proto = proto; adj->ia_flags = 0; + adj->rewrite_header.sw_if_index = ~0; + adj->lookup_next_index = 0; + adj->ia_delegates = NULL; ip4_main.lookup_main.adjacency_heap = adj_pool; ip6_main.lookup_main.adjacency_heap = adj_pool; @@ -122,11 +124,19 @@ format_ip_adjacency (u8 * s, va_list * args) if (fiaf & FORMAT_IP_ADJACENCY_DETAIL) { + adj_delegate_type_t adt; + adj_delegate_t *aed; vlib_counter_t counts; vlib_get_combined_counter(&adjacency_counters, adj_index, &counts); s = format (s, "\n counts:[%Ld:%Ld]", counts.packets, counts.bytes); s = format (s, "\n locks:%d", adj->ia_node.fn_locks); + s = format(s, "\n delegates:\n "); + FOR_EACH_ADJ_DELEGATE(adj, adt, aed, + { + s = format(s, " %U\n", format_adj_deletegate, aed); + }); + s = format(s, "\n children:\n "); s = fib_node_children_format(adj->ia_node.fn_children, s); } @@ -173,7 +183,11 @@ adj_last_lock_gone (ip_adjacency_t *adj) adj_mcast_remove(adj->ia_nh_proto, adj->rewrite_header.sw_if_index); break; - default: + case IP_LOOKUP_NEXT_DROP: + case IP_LOOKUP_NEXT_PUNT: + case IP_LOOKUP_NEXT_LOCAL: + case IP_LOOKUP_NEXT_ICMP_ERROR: + case IP_LOOKUP_N_NEXT: /* * type not stored in any DB from which we need to remove it */ @@ -183,6 +197,8 @@ adj_last_lock_gone (ip_adjacency_t *adj) vlib_worker_thread_barrier_release(vm); fib_node_deinit(&adj->ia_node); + ASSERT(0 == vec_len(adj->ia_delegates)); + vec_free(adj->ia_delegates); pool_put(adj_pool, adj); } @@ -352,6 +368,33 @@ adj_get_sw_if_index (adj_index_t ai) } /** + * @brief Return true if the adjacency is 'UP', i.e. can be used for forwarding + * 0 is down, !0 is up. + */ +int +adj_is_up (adj_index_t ai) +{ + const adj_delegate_t *aed; + + aed = adj_delegate_get(adj_get(ai), ADJ_DELEGATE_BFD); + + if (NULL == aed) + { + /* + * no BFD tracking - resolved + */ + return (!0); + } + else + { + /* + * defer to the state of the BFD tracking + */ + return (ADJ_BFD_STATE_UP == aed->ad_bfd_state); + } +} + +/** * @brief Return the rewrite string of the adjacency */ const u8* diff --git a/src/vnet/adj/adj.h b/src/vnet/adj/adj.h index af7730f7..32997c91 100644 --- a/src/vnet/adj/adj.h +++ b/src/vnet/adj/adj.h @@ -255,6 +255,15 @@ typedef struct ip_adjacency_t_ /* Rewrite in second/third cache lines */ vnet_declare_rewrite (VLIB_BUFFER_PRE_DATA_SIZE); + + /** + * more control plane members that do not fit on the first cacheline + */ + /** + * A sorted vector of delegates + */ + struct adj_delegate_t_ *ia_delegates; + } ip_adjacency_t; STATIC_ASSERT ((STRUCT_OFFSET_OF (ip_adjacency_t, cacheline0) == 0), @@ -308,6 +317,12 @@ extern vnet_link_t adj_get_link_type (adj_index_t ai); extern u32 adj_get_sw_if_index (adj_index_t ai); /** + * @brief Return true if the adjacency is 'UP', i.e. can be used for forwarding. + * 0 is down, !0 is up. + */ +extern int adj_is_up (adj_index_t ai); + +/** * @brief Return the link type of the adjacency */ extern const u8* adj_get_rewrite (adj_index_t ai); diff --git a/src/vnet/adj/adj_bfd.c b/src/vnet/adj/adj_bfd.c new file mode 100644 index 00000000..3d294c46 --- /dev/null +++ b/src/vnet/adj/adj_bfd.c @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016 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/bfd/bfd_main.h> + +#include <vnet/adj/adj_delegate.h> +#include <vnet/adj/adj_nbr.h> +#include <vnet/fib/fib_walk.h> + +static adj_bfd_state_t +adj_bfd_bfd_state_to_fib (bfd_state_e bstate) +{ + switch (bstate) + { + case BFD_STATE_up: + return (ADJ_BFD_STATE_UP); + case BFD_STATE_down: + case BFD_STATE_admin_down: + case BFD_STATE_init: + return (ADJ_BFD_STATE_DOWN); + } + return (ADJ_BFD_STATE_DOWN); +} + +static void +adj_bfd_update_walk (adj_index_t ai) +{ + /* + * initiate a backwalk of dependent children + * to notify of the state change of this adj. + */ + fib_node_back_walk_ctx_t ctx = { + .fnbw_reason = FIB_NODE_BW_REASON_FLAG_ADJ_UPDATE, + }; + fib_walk_sync(FIB_NODE_TYPE_ADJ, ai, &ctx); +} + +/** + * @brief Callback function registered with BFD module to receive notifications + * of the CRUD of BFD sessions + * would be static but for the fact it's called from the unit-tests + */ +void +adj_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session) +{ + const bfd_udp_key_t *key; + fib_protocol_t fproto; + adj_delegate_t *aed; + adj_index_t ai; + + if (BFD_HOP_TYPE_SINGLE != session->hop_type) + { + /* + * multi-hop BFD sessions attach directly to the FIB entry + * single-hop adj to the associate adjacency. + */ + return; + } + + key = &session->udp.key; + + fproto = (ip46_address_is_ip4 (&key->peer_addr) ? + FIB_PROTOCOL_IP4: + FIB_PROTOCOL_IP6); + + /* + * find the adj that corresponds to the BFD session. + */ + ai = adj_nbr_add_or_lock(fproto, + fib_proto_to_link(fproto), + &key->peer_addr, + key->sw_if_index); + + switch (event) + { + case BFD_LISTEN_EVENT_CREATE: + /* + * The creation of a new session + */ + if ((ADJ_INDEX_INVALID != ai) && + (aed = adj_delegate_get(adj_get(ai), + ADJ_DELEGATE_BFD))) + { + /* + * already got state for this adj + */ + } + else + { + /* + * lock the adj. add the delegate. + * Lockinging the adj prevents it being removed and thus maintains + * the BFD derived states + */ + adj_lock(ai); + + aed = adj_delegate_find_or_add(adj_get(ai), ADJ_DELEGATE_BFD); + + /* + * pretend the session is up and skip the walk. + * If we set it down then we get traffic loss on new children. + * if we walk then we lose traffic for existing children. Wait + * for the first BFD UP/DOWN before we let the session's state + * influence forwarding. + */ + aed->ad_bfd_state = ADJ_BFD_STATE_UP; + aed->ad_bfd_index = session->bs_idx; + } + break; + + case BFD_LISTEN_EVENT_UPDATE: + /* + * state change up/dowm and + */ + aed = adj_delegate_get(adj_get(ai), ADJ_DELEGATE_BFD); + + if (NULL != aed) + { + aed->ad_bfd_state = adj_bfd_bfd_state_to_fib(session->local_state); + adj_bfd_update_walk(ai); + } + /* + * else + * not an adj with BFD state + */ + break; + + case BFD_LISTEN_EVENT_DELETE: + /* + * session has been removed. + */ + + if (adj_delegate_get(adj_get(ai), ADJ_DELEGATE_BFD)) + { + /* + * has an associated BFD tracking delegate + * remove the BFD tracking deletgate, update children, then + * unlock the adj + */ + adj_delegate_remove(adj_get(ai), ADJ_DELEGATE_BFD); + + adj_bfd_update_walk(ai); + adj_unlock(ai); + } + /* + * else + * no BFD associated state + */ + break; + } + + /* + * unlock match of the add-or-lock at the start + */ + adj_unlock(ai); +} + +static clib_error_t * +adj_bfd_main_init (vlib_main_t * vm) +{ + clib_error_t * error = NULL; + + if ((error = vlib_call_init_function (vm, bfd_main_init))) + return (error); + + bfd_register_listener(adj_bfd_notify); + + return (error); +} + +VLIB_INIT_FUNCTION (adj_bfd_main_init); diff --git a/src/vnet/adj/adj_delegate.c b/src/vnet/adj/adj_delegate.c new file mode 100644 index 00000000..701b36e2 --- /dev/null +++ b/src/vnet/adj/adj_delegate.c @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2016 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/adj/adj_delegate.h> +#include <vnet/adj/adj.h> +#include <vnet/adj/adj_internal.h> + +static adj_delegate_t * +adj_delegate_find_i (const ip_adjacency_t *adj, + adj_delegate_type_t type, + u32 *index) +{ + adj_delegate_t *delegate; + int ii; + + ii = 0; + vec_foreach(delegate, adj->ia_delegates) + { + if (delegate->ad_type == type) + { + if (NULL != index) + *index = ii; + + return (delegate); + } + else + { + ii++; + } + } + + return (NULL); +} + +adj_delegate_t * +adj_delegate_get (const ip_adjacency_t *adj, + adj_delegate_type_t type) +{ + return (adj_delegate_find_i(adj, type, NULL)); +} + +void +adj_delegate_remove (ip_adjacency_t *adj, + adj_delegate_type_t type) +{ + adj_delegate_t *aed; + u32 index = ~0; + + aed = adj_delegate_find_i(adj, type, &index); + + ASSERT(NULL != aed); + + vec_del1(adj->ia_delegates, index); +} + +static int +adj_delegate_cmp_for_sort (void * v1, + void * v2) +{ + adj_delegate_t *delegate1 = v1, *delegate2 = v2; + + return (delegate1->ad_type - delegate2->ad_type); +} + +static void +adj_delegate_init (ip_adjacency_t *adj, + adj_delegate_type_t type) + +{ + adj_delegate_t delegate = { + .ad_adj_index = adj_get_index(adj), + .ad_type = type, + }; + + vec_add1(adj->ia_delegates, delegate); + vec_sort_with_function(adj->ia_delegates, + adj_delegate_cmp_for_sort); +} + +adj_delegate_t * +adj_delegate_find_or_add (ip_adjacency_t *adj, + adj_delegate_type_t adt) +{ + adj_delegate_t *delegate; + + delegate = adj_delegate_get(adj, adt); + + if (NULL == delegate) + { + adj_delegate_init(adj, adt); + } + + return (adj_delegate_get(adj, adt)); +} + +/** + * typedef for printing a delegate + */ +typedef u8 * (*adj_delegate_format_t)(const adj_delegate_t *aed, + u8 *s); + +/** + * Print a delegate that represents BFD tracking + */ +static u8 * +adj_delegate_fmt_bfd (const adj_delegate_t *aed, + u8 *s) +{ + s = format(s, "BFD:[state:%d index:%d]", + aed->ad_bfd_state, + aed->ad_bfd_index); + + return (s); +} + +/** + * A delegate type to formatter map + */ +static adj_delegate_format_t aed_formatters[] = +{ + [ADJ_DELEGATE_BFD] = adj_delegate_fmt_bfd, +}; + +u8 * +format_adj_deletegate (u8 * s, va_list * args) +{ + adj_delegate_t *aed; + + aed = va_arg (*args, adj_delegate_t *); + + return (aed_formatters[aed->ad_type](aed, s)); +} diff --git a/src/vnet/adj/adj_delegate.h b/src/vnet/adj/adj_delegate.h new file mode 100644 index 00000000..17651203 --- /dev/null +++ b/src/vnet/adj/adj_delegate.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016 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. + */ + +#ifndef __ADJ_DELEGATE_T__ +#define __ADJ_DELEGATE_T__ + +#include <vnet/adj/adj.h> + +/** + * Delegate types + */ +typedef enum adj_delegate_type_t_ { + /** + * BFD session state + */ + ADJ_DELEGATE_BFD, +} adj_delegate_type_t; + +#define FOR_EACH_ADJ_DELEGATE(_adj, _adt, _aed, _body) \ +{ \ + for (_adt = ADJ_DELEGATE_BFD; \ + _adt <= ADJ_DELEGATE_BFD; \ + _adt++) \ + { \ + _aed = adj_delegate_get(_adj, _adt); \ + if (NULL != _aed) { \ + _body; \ + } \ + } \ +} + +/** + * Distillation of the BFD session states into a go/no-go for using + * the associated tracked adjacency + */ +typedef enum adj_bfd_state_t_ +{ + ADJ_BFD_STATE_DOWN, + ADJ_BFD_STATE_UP, +} adj_bfd_state_t; + +/** + * A Delagate is a means to implement the Delagation design pattern; + * the extension of an object's functionality through the composition of, + * and delgation to, other objects. + * These 'other' objects are delegates. Delagates are thus attached to + * ADJ objects to extend their functionality. + */ +typedef struct adj_delegate_t_ +{ + /** + * The ADJ entry object to which the delagate is attached + */ + adj_index_t ad_adj_index; + + /** + * The delagate type + */ + adj_delegate_type_t ad_type; + + /** + * A union of data for the different delegate types + */ + union + { + /** + * BFD delegate daa + */ + struct { + /** + * BFD session state + */ + adj_bfd_state_t ad_bfd_state; + /** + * BFD session index + */ + u32 ad_bfd_index; + }; + }; +} adj_delegate_t; + +extern void adj_delegate_remove(ip_adjacency_t *adj, + adj_delegate_type_t type); + +extern adj_delegate_t *adj_delegate_find_or_add(ip_adjacency_t *adj, + adj_delegate_type_t fdt); +extern adj_delegate_t *adj_delegate_get(const ip_adjacency_t *adj, + adj_delegate_type_t type); + +extern u8 *format_adj_deletegate(u8 * s, va_list * args); + +#endif diff --git a/src/vnet/bfd/bfd_main.c b/src/vnet/bfd/bfd_main.c index 2b70a20c..66b31ce5 100644 --- a/src/vnet/bfd/bfd_main.c +++ b/src/vnet/bfd/bfd_main.c @@ -101,6 +101,7 @@ bfd_set_defaults (bfd_main_t * bm, bfd_session_t * bs) bs->local_diag = BFD_DIAG_CODE_no_diag; bs->remote_state = BFD_STATE_down; bs->remote_discr = 0; + bs->hop_type = BFD_HOP_TYPE_SINGLE; bs->config_desired_min_tx_usec = BFD_DEFAULT_DESIRED_MIN_TX_USEC; bs->config_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; bs->effective_desired_min_tx_clocks = bm->default_desired_min_tx_clocks; @@ -387,6 +388,17 @@ bfd_set_remote_required_min_echo_rx (bfd_main_t * bm, bfd_session_t * bs, } } +static void +bfd_notify_listeners (bfd_main_t * bm, + bfd_listen_event_e event, const bfd_session_t * bs) +{ + bfd_notify_fn_t *fn; + vec_foreach (fn, bm->listeners) + { + (*fn) (event, bs); + } +} + void bfd_session_start (bfd_main_t * bm, bfd_session_t * bs) { @@ -396,6 +408,7 @@ bfd_session_start (bfd_main_t * bm, bfd_session_t * bs) bfd_recalc_tx_interval (bm, bs); vlib_process_signal_event (bm->vlib_main, bm->bfd_process_node_index, BFD_EVENT_NEW_SESSION, bs->bs_idx); + bfd_notify_listeners (bm, BFD_LISTEN_EVENT_CREATE, bs); } void @@ -533,6 +546,7 @@ bfd_on_state_change (bfd_main_t * bm, bfd_session_t * bs, u64 now, bfd_set_timer (bm, bs, now, handling_wakeup); break; } + bfd_notify_listeners (bm, BFD_LISTEN_EVENT_UPDATE, bs); } static void @@ -1121,6 +1135,14 @@ bfd_hw_interface_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags) VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (bfd_hw_interface_up_down); +void +bfd_register_listener (bfd_notify_fn_t fn) +{ + bfd_main_t *bm = &bfd_main; + + vec_add1 (bm->listeners, fn); +} + /* * setup function */ @@ -1180,6 +1202,7 @@ bfd_get_session (bfd_main_t * bm, bfd_transport_e t) void bfd_put_session (bfd_main_t * bm, bfd_session_t * bs) { + bfd_notify_listeners (bm, BFD_LISTEN_EVENT_DELETE, bs); if (bs->auth.curr_key) { --bs->auth.curr_key->use_count; diff --git a/src/vnet/bfd/bfd_main.h b/src/vnet/bfd/bfd_main.h index d722a552..93adac3d 100644 --- a/src/vnet/bfd/bfd_main.h +++ b/src/vnet/bfd/bfd_main.h @@ -66,6 +66,20 @@ typedef enum #undef F } bfd_poll_state_e; +/** + * hop types + */ +#define foreach_bfd_hop(F) \ + F (SINGLE, "single") \ + F (MULTI, "multi") \ + +typedef enum +{ +#define F(sym, str) BFD_HOP_TYPE_##sym, + foreach_bfd_hop (F) +#undef F +} bfd_hop_type_e; + typedef struct bfd_session_s { /** index in bfd_main.sessions pool */ @@ -77,6 +91,9 @@ typedef struct bfd_session_s /** remote session state */ bfd_state_e remote_state; + /** BFD hop type */ + bfd_hop_type_e hop_type; + /** local diagnostics */ bfd_diag_code_e local_diag; @@ -220,6 +237,26 @@ typedef struct bfd_session_s }; } bfd_session_t; +/** + * listener events + */ +#define foreach_bfd_listen_event(F) \ + F (CREATE, "sesion-created") \ + F (UPDATE, "session-updated") \ + F (DELETE, "session-deleted") + +typedef enum +{ +#define F(sym, str) BFD_LISTEN_EVENT_##sym, + foreach_bfd_listen_event (F) +#undef F +} bfd_listen_event_e; + +/** + * session nitification call back function type + */ +typedef void (*bfd_notify_fn_t) (bfd_listen_event_e, const bfd_session_t *); + typedef struct { /** pool of bfd sessions context data */ @@ -259,6 +296,8 @@ typedef struct /** hashmap - index in pool auth_keys by conf_key_id */ u32 *auth_key_by_conf_key_id; + /** A vector of callback notification functions */ + bfd_notify_fn_t *listeners; } bfd_main_t; extern bfd_main_t bfd_main; @@ -345,6 +384,11 @@ const char *bfd_poll_state_string (bfd_poll_state_e state); */ #define BFD_REQUIRED_MIN_RX_USEC_WHILE_ECHO USEC_PER_SECOND +/** + * Register a callback function to receive session notifications. + */ +void bfd_register_listener (bfd_notify_fn_t fn); + #endif /* __included_bfd_main_h__ */ /* diff --git a/src/vnet/bfd/bfd_udp.h b/src/vnet/bfd/bfd_udp.h index a4adbadf..87868104 100644 --- a/src/vnet/bfd/bfd_udp.h +++ b/src/vnet/bfd/bfd_udp.h @@ -27,8 +27,12 @@ /* *INDENT-OFF* */ /** identifier of BFD session based on UDP transport only */ typedef CLIB_PACKED (struct { - /** interface to which the session is tied */ - u32 sw_if_index; + union { + /** interface to which the session is tied - single-hop */ + u32 sw_if_index; + /** the FIB index the peer is in - multi-hop*/ + u32 fib_index; + }; /** local address */ ip46_address_t local_addr; /** peer address */ diff --git a/src/vnet/fib/fib_attached_export.c b/src/vnet/fib/fib_attached_export.c index 715e63e7..cc8ebc86 100644 --- a/src/vnet/fib/fib_attached_export.c +++ b/src/vnet/fib/fib_attached_export.c @@ -514,67 +514,52 @@ fib_attached_export_cover_update (fib_entry_t *fib_entry) } u8* -fib_ae_import_format (fib_entry_t *fib_entry, +fib_ae_import_format (fib_node_index_t impi, u8* s) { - fib_entry_delegate_t *fed; - - fed = fib_entry_delegate_get(fib_entry, - FIB_ENTRY_DELEGATE_ATTACHED_IMPORT); - - if (NULL != fed) - { - fib_node_index_t *index; - fib_ae_import_t *import; - - import = pool_elt_at_index(fib_ae_import_pool, fed->fd_index); + fib_node_index_t *index; + fib_ae_import_t *import; - s = format(s, "\n Attached-Import:%d:[", (import - fib_ae_import_pool)); - s = format(s, "export-prefix:%U ", format_fib_prefix, &import->faei_prefix); - s = format(s, "export-entry:%d ", import->faei_export_entry); - s = format(s, "export-sibling:%d ", import->faei_export_sibling); - s = format(s, "exporter:%d ", import->faei_exporter); - s = format(s, "export-fib:%d ", import->faei_export_fib); + import = pool_elt_at_index(fib_ae_import_pool, impi); - s = format(s, "import-entry:%d ", import->faei_import_entry); - s = format(s, "import-fib:%d ", import->faei_import_fib); + s = format(s, "\n Attached-Import:%d:[", (import - fib_ae_import_pool)); + s = format(s, "export-prefix:%U ", format_fib_prefix, &import->faei_prefix); + s = format(s, "export-entry:%d ", import->faei_export_entry); + s = format(s, "export-sibling:%d ", import->faei_export_sibling); + s = format(s, "exporter:%d ", import->faei_exporter); + s = format(s, "export-fib:%d ", import->faei_export_fib); + + s = format(s, "import-entry:%d ", import->faei_import_entry); + s = format(s, "import-fib:%d ", import->faei_import_fib); - s = format(s, "importeds:["); - vec_foreach(index, import->faei_importeds) - { - s = format(s, "%d, ", *index); - } - s = format(s, "]]"); + s = format(s, "importeds:["); + vec_foreach(index, import->faei_importeds) + { + s = format(s, "%d, ", *index); } + s = format(s, "]]"); return (s); } u8* -fib_ae_export_format (fib_entry_t *fib_entry, +fib_ae_export_format (fib_node_index_t expi, u8* s) { - fib_entry_delegate_t *fed; - - fed = fib_entry_delegate_get(fib_entry, - FIB_ENTRY_DELEGATE_ATTACHED_EXPORT); - - if (NULL != fed) - { - fib_node_index_t *index; - fib_ae_export_t *export; + fib_node_index_t *index; + fib_ae_export_t *export; - export = pool_elt_at_index(fib_ae_export_pool, fed->fd_list); + export = pool_elt_at_index(fib_ae_export_pool, expi); - s = format(s, "\n Attached-Export:%d:[", (export - fib_ae_export_pool)); - s = format(s, "export-entry:%d ", export->faee_ei); + s = format(s, "\n Attached-Export:%d:[", (export - fib_ae_export_pool)); + s = format(s, "export-entry:%d ", export->faee_ei); - s = format(s, "importers:["); - vec_foreach(index, export->faee_importers) - { - s = format(s, "%d, ", *index); - } - s = format(s, "]]"); + s = format(s, "importers:["); + vec_foreach(index, export->faee_importers) + { + s = format(s, "%d, ", *index); } + s = format(s, "]]"); + return (s); } diff --git a/src/vnet/fib/fib_attached_export.h b/src/vnet/fib/fib_attached_export.h index fa28a6e1..d4c2b57c 100644 --- a/src/vnet/fib/fib_attached_export.h +++ b/src/vnet/fib/fib_attached_export.h @@ -51,7 +51,7 @@ extern void fib_attached_export_covered_removed(fib_entry_t *cover, extern void fib_attached_export_cover_change(fib_entry_t *fib_entry); extern void fib_attached_export_cover_update(fib_entry_t *fib_entry); -extern u8* fib_ae_import_format(fib_entry_t *fib_entry, u8*s); -extern u8* fib_ae_export_format(fib_entry_t *fib_entry, u8*s); +extern u8* fib_ae_import_format(fib_node_index_t impi, u8*s); +extern u8* fib_ae_export_format(fib_node_index_t expi, u8*s); #endif diff --git a/src/vnet/fib/fib_bfd.c b/src/vnet/fib/fib_bfd.c new file mode 100644 index 00000000..e5affb8d --- /dev/null +++ b/src/vnet/fib/fib_bfd.c @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016 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/bfd/bfd_main.h> + +#include <vnet/fib/fib_entry_delegate.h> +#include <vnet/fib/fib_entry.h> +#include <vnet/fib/fib_table.h> +#include <vnet/fib/fib_walk.h> + +static fib_bfd_state_t +fib_bfd_bfd_state_to_fib (bfd_state_e bstate) +{ + switch (bstate) + { + case BFD_STATE_up: + return (FIB_BFD_STATE_UP); + case BFD_STATE_down: + case BFD_STATE_admin_down: + case BFD_STATE_init: + return (FIB_BFD_STATE_DOWN); + } + return (FIB_BFD_STATE_DOWN); +} + +static void +fib_bfd_update_walk (fib_node_index_t fei) +{ + /* + * initiate a backwalk of dependent children + * to notify of the state change of this entry. + */ + fib_node_back_walk_ctx_t ctx = { + .fnbw_reason = FIB_NODE_BW_REASON_FLAG_EVALUATE, + }; + fib_walk_sync(FIB_NODE_TYPE_ENTRY, fei, &ctx); +} + +/** + * @brief Callback function registered with BFD module to receive notifications + * of the CRUD of BFD sessions + * would be static but for the fact it's called from the unit-tests + */ +void +fib_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session) +{ + fib_entry_delegate_t *fed; + const bfd_udp_key_t *key; + fib_node_index_t fei; + + if (BFD_HOP_TYPE_MULTI != session->hop_type) + { + /* + * multi-hop BFD sessions attach directly to the FIB entry + * single-hop adj to the associate adjacency. + */ + return; + } + + key = &session->udp.key; + + fib_prefix_t pfx = { + .fp_addr = key->peer_addr, + .fp_proto = (ip46_address_is_ip4 (&key->peer_addr) ? + FIB_PROTOCOL_IP4: + FIB_PROTOCOL_IP6), + .fp_len = (ip46_address_is_ip4 (&key->peer_addr) ? + 32: + 128), + }; + + /* + * get the FIB entry + */ + fei = fib_table_lookup_exact_match(key->fib_index, &pfx); + + switch (event) + { + case BFD_LISTEN_EVENT_CREATE: + /* + * The creation of a new session + */ + if ((FIB_NODE_INDEX_INVALID != fei) && + (fed = fib_entry_delegate_get(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD))) + { + /* + * already got state for this entry + */ + } + else + { + /* + * source and lock the entry. add the delegate + */ + fei = fib_table_entry_special_add(key->fib_index, + &pfx, + FIB_SOURCE_RR, + FIB_ENTRY_FLAG_NONE, + ADJ_INDEX_INVALID); + fib_entry_lock(fei); + + fed = fib_entry_delegate_find_or_add(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD); + + /* + * pretend the session is up and skip the walk. + * If we set it down then we get traffic loss on new children. + * if we walk then we lose traffic for existing children. Wait + * for the first BFD UP/DOWN before we let the session's state + * influence forwarding. + */ + fed->fd_bfd_state = FIB_BFD_STATE_UP; + } + break; + + case BFD_LISTEN_EVENT_UPDATE: + /* + * state change up/dowm and + */ + ASSERT(FIB_NODE_INDEX_INVALID != fei); + + fed = fib_entry_delegate_get(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD); + + if (NULL != fed) + { + fed->fd_bfd_state = fib_bfd_bfd_state_to_fib(session->local_state); + fib_bfd_update_walk(fei); + } + /* + * else + * no BFD state + */ + break; + + case BFD_LISTEN_EVENT_DELETE: + /* + * session has been removed. + */ + if (FIB_NODE_INDEX_INVALID == fei) + { + /* + * no FIB entry + */ + } + else if (fib_entry_delegate_get(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD)) + { + /* + * has an associated BFD tracking delegate + * usource the entry and remove the BFD tracking deletgate + */ + fib_entry_delegate_remove(fib_entry_get(fei), + FIB_ENTRY_DELEGATE_BFD); + fib_bfd_update_walk(fei); + + fib_table_entry_special_remove(key->fib_index, + &pfx, + FIB_SOURCE_RR); + fib_entry_unlock(fei); + } + /* + * else + * no BFD associated state + */ + break; + } +} + +static clib_error_t * +fib_bfd_main_init (vlib_main_t * vm) +{ + clib_error_t * error = NULL; + + if ((error = vlib_call_init_function (vm, bfd_main_init))) + return (error); + + bfd_register_listener(fib_bfd_notify); + + return (error); +} + +VLIB_INIT_FUNCTION (fib_bfd_main_init); diff --git a/src/vnet/fib/fib_entry.c b/src/vnet/fib/fib_entry.c index 6ac5461d..dac1fce9 100644 --- a/src/vnet/fib/fib_entry.c +++ b/src/vnet/fib/fib_entry.c @@ -99,7 +99,6 @@ format_fib_entry (u8 * s, va_list * args) fib_entry_src_t *src; fib_node_index_t fei; fib_source_t source; - u32 n_covered; int level; fei = va_arg (*args, fib_node_index_t); @@ -143,14 +142,6 @@ format_fib_entry (u8 * s, va_list * args) } })); - n_covered = fib_entry_cover_get_size(fib_entry); - if (n_covered > 0) { - s = format(s, "\n tracking %d covered: ", n_covered); - s = fib_entry_cover_list_format(fib_entry, s); - } - s = fib_ae_import_format(fib_entry, s); - s = fib_ae_export_format(fib_entry, s); - s = format (s, "\n forwarding: "); } else @@ -179,20 +170,17 @@ format_fib_entry (u8 * s, va_list * args) fib_entry_delegate_type_t fdt; fib_entry_delegate_t *fed; - FOR_EACH_DELEGATE_CHAIN(fib_entry, fdt, fed, + s = format (s, " Delegates:\n"); + FOR_EACH_DELEGATE(fib_entry, fdt, fed, { - s = format(s, " %U-chain\n %U", - format_fib_forw_chain_type, - fib_entry_delegate_type_to_chain_type(fdt), - format_dpo_id, &fed->fd_dpo, 2); - s = format(s, "\n"); + s = format(s, " %U\n", format_fib_entry_deletegate, fed); }); } } if (level >= FIB_ENTRY_FORMAT_DETAIL2) { - s = format(s, "\nchildren:"); + s = format(s, " Children:"); s = fib_node_children_format(fib_entry->fe_node.fn_children, s); } @@ -1339,6 +1327,36 @@ fib_entry_get_best_source (fib_node_index_t entry_index) return (fib_entry_src_get_source(bsrc)); } +/** + * Return !0 is the entry is reoslved, i.e. will return a valid forwarding + * chain + */ +int +fib_entry_is_resolved (fib_node_index_t fib_entry_index) +{ + fib_entry_delegate_t *fed; + fib_entry_t *fib_entry; + + fib_entry = fib_entry_get(fib_entry_index); + + fed = fib_entry_delegate_get(fib_entry, FIB_ENTRY_DELEGATE_BFD); + + if (NULL == fed) + { + /* + * no BFD tracking - resolved + */ + return (!0); + } + else + { + /* + * defer to the state of the BFD tracking + */ + return (FIB_BFD_STATE_UP == fed->fd_bfd_state); + } +} + static int fib_ip4_address_compare (const ip4_address_t * a1, const ip4_address_t * a2) diff --git a/src/vnet/fib/fib_entry.h b/src/vnet/fib/fib_entry.h index 12fa9eb4..a3f75e60 100644 --- a/src/vnet/fib/fib_entry.h +++ b/src/vnet/fib/fib_entry.h @@ -525,6 +525,7 @@ extern int fib_entry_is_sourced(fib_node_index_t fib_entry_index, fib_source_t source); extern fib_node_index_t fib_entry_get_path_list(fib_node_index_t fib_entry_index); +extern int fib_entry_is_resolved(fib_node_index_t fib_entry_index); extern void fib_entry_module_init(void); diff --git a/src/vnet/fib/fib_entry_cover.c b/src/vnet/fib/fib_entry_cover.c index 147c5daa..814df578 100644 --- a/src/vnet/fib/fib_entry_cover.c +++ b/src/vnet/fib/fib_entry_cover.c @@ -106,51 +106,6 @@ fib_entry_cover_walk (fib_entry_t *cover, &ctx); } -u32 -fib_entry_cover_get_size (fib_entry_t *cover) -{ - fib_entry_delegate_t *fed; - - fed = fib_entry_delegate_get(cover, FIB_ENTRY_DELEGATE_COVERED); - - if (NULL == fed) - return (0); - - return (fib_node_list_get_size(fed->fd_list)); -} - -typedef struct fib_entry_cover_list_format_ctx_t_ { - u8 *s; -} fib_entry_cover_list_format_ctx_t; - -static int -fib_entry_covered_list_format_one (fib_entry_t *cover, - fib_node_index_t covered, - void *args) -{ - fib_entry_cover_list_format_ctx_t * ctx = args; - - ctx->s = format(ctx->s, "%d, ", covered); - - /* continue */ - return (1); -} - -u8* -fib_entry_cover_list_format (fib_entry_t *fib_entry, - u8 *s) -{ - fib_entry_cover_list_format_ctx_t ctx = { - .s = s, - }; - - fib_entry_cover_walk(fib_entry, - fib_entry_covered_list_format_one, - &ctx); - - return (ctx.s); -} - static int fib_entry_cover_change_one (fib_entry_t *cover, fib_node_index_t covered, diff --git a/src/vnet/fib/fib_entry_cover.h b/src/vnet/fib/fib_entry_cover.h index fbbbc211..500d5b33 100644 --- a/src/vnet/fib/fib_entry_cover.h +++ b/src/vnet/fib/fib_entry_cover.h @@ -39,9 +39,4 @@ extern void fib_entry_cover_change_notify(fib_node_index_t cover_index, fib_node_index_t covered_index); extern void fib_entry_cover_update_notify(fib_entry_t *cover); -extern u32 fib_entry_cover_get_size(fib_entry_t *cover); - -extern u8* fib_entry_cover_list_format(fib_entry_t *fib_entry, - u8 *s); - #endif diff --git a/src/vnet/fib/fib_entry_delegate.c b/src/vnet/fib/fib_entry_delegate.c index 70840b16..41af14f2 100644 --- a/src/vnet/fib/fib_entry_delegate.c +++ b/src/vnet/fib/fib_entry_delegate.c @@ -15,6 +15,7 @@ #include <vnet/fib/fib_entry_delegate.h> #include <vnet/fib/fib_entry.h> +#include <vnet/fib/fib_attached_export.h> static fib_entry_delegate_t * fib_entry_delegate_find_i (const fib_entry_t *fib_entry, @@ -149,8 +150,107 @@ fib_entry_delegate_type_to_chain_type (fib_entry_delegate_type_t fdt) case FIB_ENTRY_DELEGATE_COVERED: case FIB_ENTRY_DELEGATE_ATTACHED_IMPORT: case FIB_ENTRY_DELEGATE_ATTACHED_EXPORT: + case FIB_ENTRY_DELEGATE_BFD: break; } ASSERT(0); return (FIB_FORW_CHAIN_TYPE_UNICAST_IP4); } + +/** + * typedef for printing a delegate + */ +typedef u8 * (*fib_entry_delegate_format_t)(const fib_entry_delegate_t *fed, + u8 *s); + +/** + * Print a delegate that represents a forwarding chain + */ +static u8 * +fib_entry_delegate_fmt_fwd_chain (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "%U-chain\n %U", + format_fib_forw_chain_type, + fib_entry_delegate_type_to_chain_type(fed->fd_type), + format_dpo_id, &fed->fd_dpo, 2); + + return (s); +} + +/** + * Print a delegate that represents cover tracking + */ +static u8 * +fib_entry_delegate_fmt_covered (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "covered:["); + s = fib_node_children_format(fed->fd_list, s); + s = format(s, "]"); + + return (s); +} + +/** + * Print a delegate that represents attached-import tracking + */ +static u8 * +fib_entry_delegate_fmt_import (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "import:%U", fib_ae_import_format, fed->fd_index); + + return (s); +} + +/** + * Print a delegate that represents attached-export tracking + */ +static u8 * +fib_entry_delegate_fmt_export (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "export:%U", fib_ae_export_format, fed->fd_index); + + return (s); +} + +/** + * Print a delegate that represents BFD tracking + */ +static u8 * +fib_entry_delegate_fmt_bfd (const fib_entry_delegate_t *fed, + u8 *s) +{ + s = format(s, "BFD:%d", fed->fd_bfd_state); + + return (s); +} + +/** + * A delegate type to formatter map + */ +static fib_entry_delegate_format_t fed_formatters[] = +{ + [FIB_ENTRY_DELEGATE_CHAIN_UNICAST_IP4] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_UNICAST_IP6] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_MPLS_EOS] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_MPLS_NON_EOS] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_ETHERNET] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_CHAIN_NSH] = fib_entry_delegate_fmt_fwd_chain, + [FIB_ENTRY_DELEGATE_COVERED] = fib_entry_delegate_fmt_covered, + [FIB_ENTRY_DELEGATE_ATTACHED_IMPORT] = fib_entry_delegate_fmt_import, + [FIB_ENTRY_DELEGATE_ATTACHED_EXPORT] = fib_entry_delegate_fmt_export, + [FIB_ENTRY_DELEGATE_BFD] = fib_entry_delegate_fmt_bfd, +}; + +u8 * +format_fib_entry_deletegate (u8 * s, va_list * args) +{ + fib_entry_delegate_t *fed; + + fed = va_arg (*args, fib_entry_delegate_t *); + + return (fed_formatters[fed->fd_type](fed, s)); +} diff --git a/src/vnet/fib/fib_entry_delegate.h b/src/vnet/fib/fib_entry_delegate.h index d9183c5f..333d357c 100644 --- a/src/vnet/fib/fib_entry_delegate.h +++ b/src/vnet/fib/fib_entry_delegate.h @@ -43,6 +43,10 @@ typedef enum fib_entry_delegate_type_t_ { */ FIB_ENTRY_DELEGATE_COVERED, /** + * BFD session state + */ + FIB_ENTRY_DELEGATE_BFD, + /** * Attached import/export functionality */ FIB_ENTRY_DELEGATE_ATTACHED_IMPORT, @@ -61,6 +65,28 @@ typedef enum fib_entry_delegate_type_t_ { } \ } \ } +#define FOR_EACH_DELEGATE(_entry, _fdt, _fed, _body) \ +{ \ + for (_fdt = FIB_ENTRY_DELEGATE_CHAIN_UNICAST_IP4; \ + _fdt <= FIB_ENTRY_DELEGATE_ATTACHED_EXPORT; \ + _fdt++) \ + { \ + _fed = fib_entry_delegate_get(_entry, _fdt); \ + if (NULL != _fed) { \ + _body; \ + } \ + } \ +} + +/** + * Distillation of the BFD session states into a go/no-go for using + * the associated tracked FIB entry + */ +typedef enum fib_bfd_state_t_ +{ + FIB_BFD_STATE_UP, + FIB_BFD_STATE_DOWN, +} fib_bfd_state_t; /** * A Delagate is a means to implmenet the Delagation design pattern; the extension of an @@ -103,6 +129,11 @@ typedef struct fib_entry_delegate_t_ * For the cover tracking. The node list; */ fib_node_list_t fd_list; + + /** + * BFD state + */ + fib_bfd_state_t fd_bfd_state; }; } fib_entry_delegate_t; @@ -122,4 +153,6 @@ extern fib_forward_chain_type_t fib_entry_delegate_type_to_chain_type( extern fib_entry_delegate_type_t fib_entry_chain_type_to_delegate_type( fib_forward_chain_type_t type); +extern u8 *format_fib_entry_deletegate(u8 * s, va_list * args); + #endif diff --git a/src/vnet/fib/fib_path.c b/src/vnet/fib/fib_path.c index 928a9d43..6b202a97 100644 --- a/src/vnet/fib/fib_path.c +++ b/src/vnet/fib/fib_path.c @@ -558,12 +558,6 @@ fib_path_attached_next_hop_set (fib_path_t *path) * resolve directly via the adjacnecy discribed by the * interface and next-hop */ - if (!vnet_sw_interface_is_admin_up(vnet_get_main(), - path->attached_next_hop.fp_interface)) - { - path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; - } - dpo_set(&path->fp_dpo, DPO_ADJACENCY, fib_proto_to_dpo(path->fp_nh_proto), @@ -578,6 +572,13 @@ fib_path_attached_next_hop_set (fib_path_t *path) path->fp_sibling = adj_child_add(path->fp_dpo.dpoi_index, FIB_NODE_TYPE_PATH, fib_path_get_index(path)); + + if (!vnet_sw_interface_is_admin_up(vnet_get_main(), + path->attached_next_hop.fp_interface) || + !adj_is_up(path->fp_dpo.dpoi_index)) + { + path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; + } } /* @@ -653,6 +654,19 @@ fib_path_recursive_adj_update (fib_path_t *path, load_balance_map_path_state_change(fib_path_get_index(path)); } } + /* + * check for over-riding factors on the FIB entry itself + */ + if (!fib_entry_is_resolved(path->fp_via_fib)) + { + path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; + dpo_copy(&via_dpo, drop_dpo_get(fib_proto_to_dpo(path->fp_nh_proto))); + + /* + * PIC edge trigger. let the load-balance maps know + */ + load_balance_map_path_state_change(fib_path_get_index(path)); + } /* * update the path's contributed DPO @@ -855,15 +869,16 @@ FIXME comment vnet_get_main(), path->attached_next_hop.fp_interface); - if (if_is_up) - { - path->fp_oper_flags |= FIB_PATH_OPER_FLAG_RESOLVED; - } - ai = fib_path_attached_next_hop_get_adj( path, fib_proto_to_link(path->fp_nh_proto)); + path->fp_oper_flags &= ~FIB_PATH_OPER_FLAG_RESOLVED; + if (if_is_up && adj_is_up(ai)) + { + path->fp_oper_flags |= FIB_PATH_OPER_FLAG_RESOLVED; + } + dpo_set(&path->fp_dpo, DPO_ADJACENCY, fib_proto_to_dpo(path->fp_nh_proto), ai); @@ -1684,11 +1699,11 @@ fib_path_contribute_urpf (fib_node_index_t path_index, { fib_path_t *path; - if (!fib_path_is_resolved(path_index)) - return; - path = fib_path_get(path_index); + /* + * resolved and unresolved paths contribute to the RPF list. + */ switch (path->fp_type) { case FIB_PATH_TYPE_ATTACHED_NEXT_HOP: @@ -1700,7 +1715,14 @@ fib_path_contribute_urpf (fib_node_index_t path_index, break; case FIB_PATH_TYPE_RECURSIVE: - fib_entry_contribute_urpf(path->fp_via_fib, urpf); + if (FIB_NODE_INDEX_INVALID != path->fp_via_fib) + { + /* + * there's unresolved due to constraints, and there's unresolved + * due to ain't go no via. can't do nowt w'out via. + */ + fib_entry_contribute_urpf(path->fp_via_fib, urpf); + } break; case FIB_PATH_TYPE_EXCLUSIVE: diff --git a/src/vnet/fib/fib_test.c b/src/vnet/fib/fib_test.c index 92141ddf..3c9b8a38 100644 --- a/src/vnet/fib/fib_test.c +++ b/src/vnet/fib/fib_test.c @@ -24,6 +24,7 @@ #include <vnet/dpo/drop_dpo.h> #include <vnet/dpo/receive_dpo.h> #include <vnet/dpo/ip_null_dpo.h> +#include <vnet/bfd/bfd_main.h> #include <vnet/mpls/mpls.h> @@ -33,6 +34,11 @@ #include <vnet/fib/fib_node_list.h> #include <vnet/fib/fib_urpf_list.h> +/* + * Add debugs for passing tests + */ +static int fib_test_do_debug; + #define FIB_TEST_I(_cond, _comment, _args...) \ ({ \ int _evald = (_cond); \ @@ -40,6 +46,9 @@ fformat(stderr, "FAIL:%d: " _comment "\n", \ __LINE__, ##_args); \ } else { \ + if (fib_test_do_debug) \ + fformat(stderr, "PASS:%d: " _comment "\n", \ + __LINE__, ##_args); \ } \ _evald; \ }) @@ -6735,6 +6744,509 @@ fib_test_walk (void) return (0); } +/* + * declaration of the otherwise static callback functions + */ +void fib_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session); +void adj_bfd_notify (bfd_listen_event_e event, + const bfd_session_t *session); + +/** + * Test BFD session interaction with FIB + */ +static int +fib_test_bfd (void) +{ + fib_node_index_t fei; + test_main_t *tm; + int n_feis; + + /* via 10.10.10.1 */ + ip46_address_t nh_10_10_10_1 = { + .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a01), + }; + /* via 10.10.10.2 */ + ip46_address_t nh_10_10_10_2 = { + .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a02), + }; + /* via 10.10.10.10 */ + ip46_address_t nh_10_10_10_10 = { + .ip4.as_u32 = clib_host_to_net_u32(0x0a0a0a0a), + }; + n_feis = fib_entry_pool_size(); + + tm = &test_main; + + /* + * add interface routes. we'll assume this works. it's tested elsewhere + */ + fib_prefix_t pfx_10_10_10_10_s_24 = { + .fp_len = 24, + .fp_proto = FIB_PROTOCOL_IP4, + .fp_addr = nh_10_10_10_10, + }; + + fib_table_entry_update_one_path(0, &pfx_10_10_10_10_s_24, + FIB_SOURCE_INTERFACE, + (FIB_ENTRY_FLAG_CONNECTED | + FIB_ENTRY_FLAG_ATTACHED), + FIB_PROTOCOL_IP4, + NULL, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, // weight + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + fib_prefix_t pfx_10_10_10_10_s_32 = { + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + .fp_addr = nh_10_10_10_10, + }; + fib_table_entry_update_one_path(0, &pfx_10_10_10_10_s_32, + FIB_SOURCE_INTERFACE, + (FIB_ENTRY_FLAG_CONNECTED | + FIB_ENTRY_FLAG_LOCAL), + FIB_PROTOCOL_IP4, + NULL, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, // weight + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + /* + * A BFD session via a neighbour we do not yet know + */ + bfd_session_t bfd_10_10_10_1 = { + .udp = { + .key = { + .fib_index = 0, + .peer_addr = nh_10_10_10_1, + }, + }, + .hop_type = BFD_HOP_TYPE_MULTI, + .local_state = BFD_STATE_init, + }; + + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + /* + * A new entry will be created that forwards via the adj + */ + adj_index_t ai_10_10_10_1 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index); + fib_prefix_t pfx_10_10_10_1_s_32 = { + .fp_addr = nh_10_10_10_1, + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + }; + fib_test_lb_bucket_t adj_o_10_10_10_1 = { + .type = FT_LB_ADJ, + .adj = { + .adj = ai_10_10_10_1, + }, + }; + + fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32); + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &adj_o_10_10_10_1), + "BFD sourced %U via %U", + format_fib_prefix, &pfx_10_10_10_1_s_32, + format_ip_adjacency, ai_10_10_10_1, FORMAT_IP_ADJACENCY_NONE); + + /* + * Delete the BFD session. Expect the fib_entry to be removed + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32); + FIB_TEST(FIB_NODE_INDEX_INVALID == fei, + "BFD sourced %U removed", + format_fib_prefix, &pfx_10_10_10_1_s_32); + + /* + * Add the BFD source back + */ + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + /* + * source the entry via the ADJ fib + */ + fei = fib_table_entry_update_one_path(0, + &pfx_10_10_10_1_s_32, + FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + FIB_PROTOCOL_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + /* + * Delete the BFD session. Expect the fib_entry to remain + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + fei = fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32); + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &adj_o_10_10_10_1), + "BFD sourced %U remains via %U", + format_fib_prefix, &pfx_10_10_10_1_s_32, + format_ip_adjacency, ai_10_10_10_1, FORMAT_IP_ADJACENCY_NONE); + + /* + * Add the BFD source back + */ + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + /* + * Create another ADJ FIB + */ + fib_prefix_t pfx_10_10_10_2_s_32 = { + .fp_addr = nh_10_10_10_2, + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + }; + fib_table_entry_update_one_path(0, + &pfx_10_10_10_2_s_32, + FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + /* + * A BFD session for the new ADJ FIB + */ + bfd_session_t bfd_10_10_10_2 = { + .udp = { + .key = { + .fib_index = 0, + .peer_addr = nh_10_10_10_2, + }, + }, + .hop_type = BFD_HOP_TYPE_MULTI, + .local_state = BFD_STATE_init, + }; + + fib_bfd_notify (BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_2); + + /* + * remove the adj-fib source whilst the session is present + * then add it back + */ + fib_table_entry_delete(0, &pfx_10_10_10_2_s_32, FIB_SOURCE_ADJ); + fib_table_entry_update_one_path(0, + &pfx_10_10_10_2_s_32, + FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + /* + * Before adding a recursive via the BFD tracked ADJ-FIBs, + * bring one of the sessions UP, leave the other down + */ + bfd_10_10_10_1.local_state = BFD_STATE_up; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + bfd_10_10_10_2.local_state = BFD_STATE_down; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2); + + /* + * A recursive prefix via both of the ADJ FIBs + */ + fib_prefix_t pfx_200_0_0_0_s_24 = { + .fp_proto = FIB_PROTOCOL_IP4, + .fp_len = 32, + .fp_addr = { + .ip4.as_u32 = clib_host_to_net_u32(0xc8000000), + }, + }; + const dpo_id_t *dpo_10_10_10_1, *dpo_10_10_10_2; + + dpo_10_10_10_1 = + fib_entry_contribute_ip_forwarding( + fib_table_lookup_exact_match(0, &pfx_10_10_10_1_s_32)); + dpo_10_10_10_2 = + fib_entry_contribute_ip_forwarding( + fib_table_lookup_exact_match(0, &pfx_10_10_10_2_s_32)); + + fib_test_lb_bucket_t lb_o_10_10_10_1 = { + .type = FT_LB_O_LB, + .lb = { + .lb = dpo_10_10_10_1->dpoi_index, + }, + }; + fib_test_lb_bucket_t lb_o_10_10_10_2 = { + .type = FT_LB_O_LB, + .lb = { + .lb = dpo_10_10_10_2->dpoi_index, + }, + }; + + /* + * A prefix via the adj-fib that is BFD down => DROP + */ + fei = fib_table_entry_path_add(0, + &pfx_200_0_0_0_s_24, + FIB_SOURCE_API, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + ~0, // recursive + 0, // default fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + FIB_TEST(load_balance_is_drop(fib_entry_contribute_ip_forwarding(fei)), + "%U resolves via drop", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * add a path via the UP BFD adj-fib. + * we expect that the DOWN BFD ADJ FIB is not used. + */ + fei = fib_table_entry_path_add(0, + &pfx_200_0_0_0_s_24, + FIB_SOURCE_API, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_1, + ~0, // recursive + 0, // default fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &lb_o_10_10_10_1), + "Recursive %U only UP BFD adj-fibs", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Send a BFD state change to UP - both sessions are now up + * the recursive prefix should LB over both + */ + bfd_10_10_10_2.local_state = BFD_STATE_up; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2); + + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &lb_o_10_10_10_1, + &lb_o_10_10_10_2), + "Recursive %U via both UP BFD adj-fibs", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Send a BFD state change to DOWN + * the recursive prefix should exclude the down + */ + bfd_10_10_10_2.local_state = BFD_STATE_down; + fib_bfd_notify (BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_2); + + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &lb_o_10_10_10_1), + "Recursive %U via only UP", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Delete the BFD session while it is in the DOWN state. + * FIB should consider the entry's state as back up + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_2); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &lb_o_10_10_10_1, + &lb_o_10_10_10_2), + "Recursive %U via both UP BFD adj-fibs post down session delete", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * Delete the BFD other session while it is in the UP state. + */ + fib_bfd_notify (BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &lb_o_10_10_10_1, + &lb_o_10_10_10_2), + "Recursive %U via both UP BFD adj-fibs post up session delete", + format_fib_prefix, &pfx_200_0_0_0_s_24); + + /* + * cleaup + */ + fib_table_entry_delete(0, &pfx_200_0_0_0_s_24, FIB_SOURCE_API); + fib_table_entry_delete(0, &pfx_10_10_10_1_s_32, FIB_SOURCE_ADJ); + fib_table_entry_delete(0, &pfx_10_10_10_2_s_32, FIB_SOURCE_ADJ); + + fib_table_entry_delete(0, &pfx_10_10_10_10_s_32, FIB_SOURCE_INTERFACE); + fib_table_entry_delete(0, &pfx_10_10_10_10_s_24, FIB_SOURCE_INTERFACE); + + adj_unlock(ai_10_10_10_1); + /* + * test no-one left behind + */ + FIB_TEST((n_feis == fib_entry_pool_size()), "Entries gone"); + FIB_TEST(0 == adj_nbr_db_size(), "All adjacencies removed"); + + /* + * Single-hop BFD tests + */ + bfd_10_10_10_1.hop_type = BFD_HOP_TYPE_SINGLE; + bfd_10_10_10_1.udp.key.sw_if_index = tm->hw[0]->sw_if_index; + + adj_bfd_notify(BFD_LISTEN_EVENT_CREATE, &bfd_10_10_10_1); + + ai_10_10_10_1 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index); + /* + * whilst the BFD session is not signalled, the adj is up + */ + FIB_TEST(adj_is_up(ai_10_10_10_1), "Adj state up on uninit session"); + + /* + * bring the BFD session up + */ + bfd_10_10_10_1.local_state = BFD_STATE_up; + adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + FIB_TEST(adj_is_up(ai_10_10_10_1), "Adj state up on UP session"); + + /* + * bring the BFD session down + */ + bfd_10_10_10_1.local_state = BFD_STATE_down; + adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + FIB_TEST(!adj_is_up(ai_10_10_10_1), "Adj state down on DOWN session"); + + + /* + * add an attached next hop FIB entry via the down adj + */ + fib_prefix_t pfx_5_5_5_5_s_32 = { + .fp_addr = { + .ip4 = { + .as_u32 = clib_host_to_net_u32(0x05050505), + }, + }, + .fp_len = 32, + .fp_proto = FIB_PROTOCOL_IP4, + }; + + fei = fib_table_entry_path_add(0, + &pfx_5_5_5_5_s_32, + FIB_SOURCE_CLI, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_1, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + FIB_TEST(load_balance_is_drop(fib_entry_contribute_ip_forwarding(fei)), + "%U resolves via drop", + format_fib_prefix, &pfx_5_5_5_5_s_32); + + /* + * Add a path via an ADJ that is up + */ + adj_index_t ai_10_10_10_2 = adj_nbr_add_or_lock(FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index); + + fib_test_lb_bucket_t adj_o_10_10_10_2 = { + .type = FT_LB_ADJ, + .adj = { + .adj = ai_10_10_10_2, + }, + }; + adj_o_10_10_10_1.adj.adj = ai_10_10_10_1; + + fei = fib_table_entry_path_add(0, + &pfx_5_5_5_5_s_32, + FIB_SOURCE_CLI, + FIB_ENTRY_FLAG_NONE, + FIB_PROTOCOL_IP4, + &nh_10_10_10_2, + tm->hw[0]->sw_if_index, + ~0, // invalid fib index + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 1, + &adj_o_10_10_10_2), + "BFD sourced %U via %U", + format_fib_prefix, &pfx_5_5_5_5_s_32, + format_ip_adjacency, ai_10_10_10_2, FORMAT_IP_ADJACENCY_NONE); + + /* + * Bring up the down session - should now LB + */ + bfd_10_10_10_1.local_state = BFD_STATE_up; + adj_bfd_notify(BFD_LISTEN_EVENT_UPDATE, &bfd_10_10_10_1); + FIB_TEST(fib_test_validate_entry(fei, + FIB_FORW_CHAIN_TYPE_UNICAST_IP4, + 2, + &adj_o_10_10_10_1, + &adj_o_10_10_10_2), + "BFD sourced %U via noth adjs", + format_fib_prefix, &pfx_5_5_5_5_s_32); + + /* + * remove the BFD session state from the adj + */ + adj_bfd_notify(BFD_LISTEN_EVENT_DELETE, &bfd_10_10_10_1); + + /* + * clean-up + */ + fib_table_entry_delete(0, &pfx_5_5_5_5_s_32, FIB_SOURCE_CLI); + adj_unlock(ai_10_10_10_1); + adj_unlock(ai_10_10_10_2); + + /* + * test no-one left behind + */ + FIB_TEST((n_feis == fib_entry_pool_size()), "Entries gone"); + FIB_TEST(0 == adj_nbr_db_size(), "All adjacencies removed"); + return (0); +} + static int lfib_test (void) { @@ -7119,6 +7631,11 @@ fib_test (vlib_main_t * vm, res = 0; fib_test_mk_intf(4); + if (unformat (input, "debug")) + { + fib_test_do_debug = 1; + } + if (unformat (input, "ip")) { res += fib_test_v4(); @@ -7140,6 +7657,10 @@ fib_test (vlib_main_t * vm, { res += fib_test_walk(); } + else if (unformat (input, "bfd")) + { + res += fib_test_bfd(); + } else { /* @@ -7151,6 +7672,7 @@ fib_test (vlib_main_t * vm, res += fib_test_v4(); res += fib_test_v6(); res += fib_test_ae(); + res += fib_test_bfd(); res += fib_test_label(); res += lfib_test(); } diff --git a/src/vnet/fib/ip4_fib.c b/src/vnet/fib/ip4_fib.c index 98d4e52f..b03186e8 100644 --- a/src/vnet/fib/ip4_fib.c +++ b/src/vnet/fib/ip4_fib.c @@ -477,12 +477,15 @@ static void ip4_fib_table_show_one (ip4_fib_t *fib, vlib_main_t * vm, ip4_address_t *address, - u32 mask_len) + u32 mask_len, + int detail) { vlib_cli_output(vm, "%U", format_fib_entry, ip4_fib_table_lookup(fib, address, mask_len), - FIB_ENTRY_FORMAT_DETAIL); + (detail ? + FIB_ENTRY_FORMAT_DETAIL2 : + FIB_ENTRY_FORMAT_DETAIL)); } static clib_error_t * @@ -496,6 +499,7 @@ ip4_show_fib (vlib_main_t * vm, ip4_address_t matching_address; u32 matching_mask = 32; int i, table_id = -1, fib_index = ~0; + int detail = 0; verbose = 1; matching = 0; @@ -506,6 +510,9 @@ ip4_show_fib (vlib_main_t * vm, || unformat (input, "sum")) verbose = 0; + else if (unformat (input, "detail") || unformat (input, "det")) + detail = 1; + else if (unformat (input, "mtrie")) mtrie = 1; @@ -563,7 +570,8 @@ ip4_show_fib (vlib_main_t * vm, } else { - ip4_fib_table_show_one(fib, vm, &matching_address, matching_mask); + ip4_fib_table_show_one(fib, vm, &matching_address, + matching_mask, detail); } })); @@ -717,7 +725,7 @@ ip4_show_fib (vlib_main_t * vm, /* *INDENT-OFF* */ VLIB_CLI_COMMAND (ip4_show_fib_command, static) = { .path = "show ip fib", - .short_help = "show ip fib [summary] [table <table-id>] [index <fib-id>] [<ip4-addr>[/<mask>]] [mtrie]", + .short_help = "show ip fib [summary] [table <table-id>] [index <fib-id>] [<ip4-addr>[/<mask>]] [mtrie] [detail]", .function = ip4_show_fib, }; /* *INDENT-ON* */ diff --git a/src/vnet/fib/ip6_fib.c b/src/vnet/fib/ip6_fib.c index 0ee029d3..00297140 100644 --- a/src/vnet/fib/ip6_fib.c +++ b/src/vnet/fib/ip6_fib.c @@ -560,12 +560,15 @@ static void ip6_fib_table_show_one (ip6_fib_t *fib, vlib_main_t * vm, ip6_address_t *address, - u32 mask_len) + u32 mask_len, + int detail) { vlib_cli_output(vm, "%U", format_fib_entry, ip6_fib_table_lookup(fib->index, address, mask_len), - FIB_ENTRY_FORMAT_DETAIL); + (detail ? + FIB_ENTRY_FORMAT_DETAIL2: + FIB_ENTRY_FORMAT_DETAIL)); } typedef struct { @@ -573,8 +576,9 @@ typedef struct { u64 count_by_prefix_length[129]; } count_routes_in_fib_at_prefix_length_arg_t; -static void count_routes_in_fib_at_prefix_length -(BVT(clib_bihash_kv) * kvp, void *arg) +static void +count_routes_in_fib_at_prefix_length (BVT(clib_bihash_kv) * kvp, + void *arg) { count_routes_in_fib_at_prefix_length_arg_t * ap = arg; int mask_width; @@ -600,6 +604,7 @@ ip6_show_fib (vlib_main_t * vm, ip6_address_t matching_address; u32 mask_len = 128; int table_id = -1, fib_index = ~0; + int detail = 0; verbose = 1; matching = 0; @@ -610,6 +615,10 @@ ip6_show_fib (vlib_main_t * vm, unformat (input, "summary") || unformat (input, "sum")) verbose = 0; + + else if (unformat (input, "detail") || + unformat (input, "det")) + detail = 1; else if (unformat (input, "%U/%d", unformat_ip6_address, &matching_address, &mask_len)) @@ -667,7 +676,7 @@ ip6_show_fib (vlib_main_t * vm, } else { - ip6_fib_table_show_one(fib, vm, &matching_address, mask_len); + ip6_fib_table_show_one(fib, vm, &matching_address, mask_len, detail); } })); @@ -771,7 +780,7 @@ ip6_show_fib (vlib_main_t * vm, /* *INDENT-OFF* */ VLIB_CLI_COMMAND (ip6_show_fib_command, static) = { .path = "show ip6 fib", - .short_help = "show ip6 fib [summary] [table <table-id>] [index <fib-id>] [<ip6-addr>[/<width>]]", + .short_help = "show ip6 fib [summary] [table <table-id>] [index <fib-id>] [<ip6-addr>[/<width>]] [detail]", .function = ip6_show_fib, }; /* *INDENT-ON* */ |