/* * 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 #include #include /* * The 'DB' of all glean adjs. * There is one glean per-{interface, protocol, connected prefix} */ static uword **adj_gleans[FIB_PROTOCOL_IP_MAX]; static inline u32 adj_get_glean_node (fib_protocol_t proto) { switch (proto) { case FIB_PROTOCOL_IP4: return (ip4_glean_node.index); case FIB_PROTOCOL_IP6: return (ip6_glean_node.index); case FIB_PROTOCOL_MPLS: break; } ASSERT(0); return (~0); } static adj_index_t adj_glean_db_lookup (fib_protocol_t proto, u32 sw_if_index, const ip46_address_t *nh_addr) { uword *p; if ((proto >= FIB_PROTOCOL_IP_MAX) || vec_len(adj_gleans[proto]) <= sw_if_index) return (ADJ_INDEX_INVALID); p = hash_get_mem (adj_gleans[proto][sw_if_index], nh_addr); if (p) return (p[0]); return (ADJ_INDEX_INVALID); } static void adj_glean_db_insert (fib_protocol_t proto, u32 sw_if_index, const ip46_address_t *nh_addr, adj_index_t ai) { vlib_main_t *vm = vlib_get_main(); vlib_worker_thread_barrier_sync(vm); ASSERT(proto < FIB_PROTOCOL_IP_MAX); vec_validate(adj_gleans[proto], sw_if_index); if (NULL == adj_gleans[proto][sw_if_index]) { adj_gleans[proto][sw_if_index] = hash_create_mem (0, sizeof(ip46_address_t), sizeof(adj_index_t)); } hash_set_mem_alloc (&adj_gleans[proto][sw_if_index], nh_addr, ai); vlib_worker_thread_barrier_release(vm); } static void adj_glean_db_remove (fib_protocol_t proto, u32 sw_if_index, const ip46_address_t *nh_addr) { vlib_main_t *vm = vlib_get_main(); vlib_worker_thread_barrier_sync(vm); ASSERT(ADJ_INDEX_INVALID != adj_glean_db_lookup(proto, sw_if_index, nh_addr)); hash_unset_mem_free (&adj_gleans[proto][sw_if_index], nh_addr); if (0 == hash_elts(adj_gleans[proto][sw_if_index])) { hash_free(adj_gleans[proto][sw_if_index]); adj_gleans[proto][sw_if_index] = NULL; } vlib_worker_thread_barrier_release(vm); } /* * adj_glean_add_or_lock * * The next_hop address here is used for source address selection in the DP. * The glean adj is added to an interface's connected prefix, the next-hop * passed here is the local prefix on the same interface. */ adj_index_t adj_glean_add_or_lock (fib_protocol_t proto, vnet_link_t linkt, u32 sw_if_index, const fib_prefix_t *conn) { ip_adjacency_t * adj; adj_index_t ai; ai = adj_glean_db_lookup(proto, sw_if_index, &conn->fp_addr); if (ADJ_INDEX_INVALID == ai) { adj = adj_alloc(proto); adj->lookup_next_index = IP_LOOKUP_NEXT_GLEAN; adj->ia_nh_proto = proto; adj->ia_link = linkt; adj->ia_node_index = adj_get_glean_node(proto); ai = adj_get_index(adj); adj_lock(ai); ASSERT(conn); fib_prefix_normalize(conn, &adj->sub_type.glean.rx_pfx); adj->rewrite_header.sw_if_index = sw_if_index; adj->rewrite_header.data_bytes = 0; adj->rewrite_header.max_l3_packet_bytes = vnet_sw_interface_get_mtu(vnet_get_main(), sw_if_index, vnet_link_to_mtu(linkt)); vnet_update_adjacency_for_sw_interface(vnet_get_main(), sw_if_index, ai); adj_glean_db_insert(proto, sw_if_index, &adj->sub_type.glean.rx_pfx.fp_addr, ai); } else { adj = adj_get(ai); adj_lock(ai); } adj_delegate_adj_created(adj); return (ai); } /** * adj_glean_update_rewrite */ void adj_glean_update_rewrite (adj_index_t adj_index) { ip_adjacency_t *adj; ASSERT(ADJ_INDEX_INVALID != adj_index); adj = adj_get(adj_index); vnet_rewrite_for_sw_interface(vnet_get_main(), adj_fib_proto_2_nd(adj->ia_nh_proto), adj->rewrite_header.sw_if_index, adj->ia_node_index, VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST, &adj->rewrite_header, sizeof (adj->rewrite_data)); } static adj_walk_rc_t adj_glean_update_rewrite_walk (adj_index_t ai, void *data) { adj_glean_update_rewrite(ai); return (ADJ_WALK_RC_CONTINUE); } static void adj_glean_walk_proto (fib_protocol_t proto, u32 sw_if_index, adj_walk_cb_t cb, void *data) { adj_index_t ai, *aip, *ais = NULL; ip46_address_t *conn; ASSERT(proto < FIB_PROTOCOL_IP_MAX); if (vec_len(adj_gleans[proto]) <= sw_if_index || NULL == adj_gleans[proto][sw_if_index]) return; /* * Walk first to collect the indices * then walk the collection. This is safe * to modifications of the hash table */ hash_foreach_mem(conn, ai, adj_gleans[proto][sw_if_index], ({ vec_add1(ais, ai); })); vec_foreach(aip, ais) { if (ADJ_WALK_RC_STOP == cb(*aip, data)) break; } vec_free(ais); } void adj_glean_walk (u32 sw_if_index, adj_walk_cb_t cb, void *data) { fib_protocol_t proto; FOR_EACH_FIB_IP_PROTOCOL(proto) { adj_glean_walk_proto (proto, sw_if_index, cb, data); } } adj_index_t adj_glean_get (fib_protocol_t proto, u32 sw_if_index, const ip46_address_t *nh) { if (NULL != nh) { return adj_glean_db_lookup(proto, sw_if_index, nh); } else { ip46_address_t *conn; adj_index_t ai; ASSERT(proto < FIB_PROTOCOL_IP_MAX); if (vec_len(adj_gleans[proto]) <= sw_if_index || NULL == adj_gleans[proto][sw_if_index]) return (ADJ_INDEX_INVALID); hash_foreach_mem(conn, ai, adj_gleans[proto][sw_if_index], ({ return (ai); })); } return (ADJ_INDEX_INVALID); } const ip46_address_t * adj_glean_get_src (fib_protocol_t proto, u32 sw_if_index, const ip46_address_t *nh) { const ip46_address_t *conn, *source; const ip_adjacency_t *adj; adj_index_t ai; ASSERT(proto < FIB_PROTOCOL_IP_MAX); if (vec_len(adj_gleans[proto]) <= sw_if_index || NULL == adj_gleans[proto][sw_if_index]) return (NULL); fib_prefix_t pfx = { .fp_len = fib_prefix_get_host_length(proto), .fp_proto = proto, }; if (nh) pfx.fp_addr = *nh; /* * An interface can have more than one glean address. Where * possible we want to return a source address from the same * subnet as the destination. If this is not possible then any address * will do. */ source = NULL; hash_foreach_mem(conn, ai, adj_gleans[proto][sw_if_index], ({ adj = adj_get(ai); if (adj->sub_type.glean.rx_pfx.fp_len > 0) { source = &adj->sub_type.glean.rx_pfx.fp_addr; /* if no destination is specified use the just glean */ if (NULL == nh) return (source); /* check the clean covers the desintation */ if (fib_prefix_is_cover(&adj->sub_type.glean.rx_pfx, &pfx)) return (source); } })); return (source); } void adj_glean_remove (ip_adjacency_t *adj) { fib_prefix_t norm; fib_prefix_normalize(&adj->sub_type.glean.rx_pfx, &norm); adj_glean_db_remove(adj->ia_nh_proto, adj->rewrite_header.sw_if_index, &norm.fp_addr); } static adj_walk_rc_t adj_glean_start_backwalk (adj_index_t ai, void *data) { fib_node_back_walk_ctx_t bw_ctx = *(fib_node_back_walk_ctx_t*) data; fib_walk_sync(FIB_NODE_TYPE_ADJ, ai, &bw_ctx); return (ADJ_WALK_RC_CONTINUE); } static clib_error_t * adj_glean_interface_state_change (vnet_main_t * vnm, u32 sw_if_index, u32 flags) { /* * for each glean on the interface trigger a walk back to the children */ fib_node_back_walk_ctx_t bw_ctx = { .fnbw_reason = (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP ? FIB_NODE_BW_REASON_FLAG_INTERFACE_UP : FIB_NODE_BW_REASON_FLAG_INTERFACE_DOWN), }; adj_glean_walk (sw_if_index, adj_glean_start_backwalk, &bw_ctx); return (NULL); } VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION(adj_glean_interface_state_change); /** * @brief Invoked on each SW interface of a HW interface when the * HW interface state changes */ static walk_rc_t adj_nbr_hw_sw_interface_state_change (vnet_main_t * vnm, u32 sw_if_index, void *arg) { adj_glean_interface_state_change(vnm, sw_if_index, (uword) arg); return (WALK_CONTINUE); } /** * @brief Registered callback for HW interface state changes */ static clib_error_t * adj_glean_hw_interface_state_change (vnet_main_t * vnm, u32 hw_if_index, u32 flags) { /* * walk SW interfaces on the HW */ uword sw_flags; sw_flags = ((flags & VNET_HW_INTERFACE_FLAG_LINK_UP) ? VNET_SW_INTERFACE_FLAG_ADMIN_UP : 0); vnet_hw_interface_walk_sw(vnm, hw_if_index, adj_nbr_hw_sw_interface_state_change, (void*) sw_flags); return (NULL); } VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION( adj_glean_hw_interface_state_change); static clib_error_t * adj_glean_interface_delete (vnet_main_t * vnm, u32 sw_if_index, u32 is_add) { if (is_add) { /* * not interested in interface additions. we will not back walk * to resolve paths through newly added interfaces. Why? The control * plane should have the brains to add interfaces first, then routes. * So the case where there are paths with a interface that matches * one just created is the case where the path resolved through an * interface that was deleted, and still has not been removed. The * new interface added, is NO GUARANTEE that the interface being * added now, even though it may have the same sw_if_index, is the * same interface that the path needs. So tough! * If the control plane wants these routes to resolve it needs to * remove and add them again. */ return (NULL); } /* * for each glean on the interface trigger a walk back to the children */ fib_node_back_walk_ctx_t bw_ctx = { .fnbw_reason = FIB_NODE_BW_REASON_FLAG_INTERFACE_DELETE, }; adj_glean_walk (sw_if_index, adj_glean_start_backwalk, &bw_ctx); return (NULL); } VNET_SW_INTERFACE_ADD_DEL_FUNCTION(adj_glean_interface_delete); /** * Callback function invoked when an interface's MAC Address changes */ static void adj_glean_ethernet_change_mac (ethernet_main_t * em, u32 sw_if_index, uword opaque) { adj_glean_walk (sw_if_index, adj_glean_update_rewrite_walk, NULL); } static void adj_glean_table_bind (fib_protocol_t fproto, u32 sw_if_index, u32 itf_fib_index) { /* * for each glean on the interface trigger a walk back to the children */ fib_node_back_walk_ctx_t bw_ctx = { .fnbw_reason = FIB_NODE_BW_REASON_FLAG_INTERFACE_BIND, .interface_bind = { .fnbw_to_fib_index = itf_fib_index, }, }; adj_glean_walk_proto (fproto, sw_if_index, adj_glean_start_backwalk, &bw_ctx); } /** * Callback function invoked when an interface's IPv6 Table * binding changes */ static void adj_glean_ip6_table_bind (ip6_main_t * im, uword opaque, u32 sw_if_index, u32 new_fib_index, u32 old_fib_index) { adj_glean_table_bind (FIB_PROTOCOL_IP6, sw_if_index, new_fib_index); } /** * Callback function invoked when an interface's IPv4 Table * binding changes */ static void adj_glean_ip4_table_bind (ip4_main_t * im, uword opaque, u32 sw_if_index, u32 new_fib_index, u32 old_fib_index) { adj_glean_table_bind (FIB_PROTOCOL_IP4, sw_if_index, new_fib_index); } u8* format_adj_glean (u8* s, va_list *ap) { index_t index = va_arg(*ap, index_t); CLIB_UNUSED(u32 indent) = va_arg(*ap, u32); ip_adjacency_t * adj = adj_get(index); s = format(s, "%U-glean: [src:%U] %U", format_fib_protocol, adj->ia_nh_proto, format_fib_prefix, &adj->sub_type.glean.rx_pfx, format_vnet_rewrite, &adj->rewrite_header, sizeof (adj->rewrite_data), 0); return (s); } u32 adj_glean_db_size (void) { fib_protocol_t proto; u32 sw_if_index = 0; u64 count = 0; FOR_EACH_FIB_IP_PROTOCOL(proto) { vec_foreach_index(sw_if_index, adj_gleans[proto]) { if (NULL != adj_gleans[proto][sw_if_index]) { count += hash_elts(adj_gleans[proto][sw_if_index]); } } } return (count); } static void adj_dpo_lock (dpo_id_t *dpo) { adj_lock(dpo->dpoi_index); } static void adj_dpo_unlock (dpo_id_t *dpo) { adj_unlock(dpo->dpoi_index); } const static dpo_vft_t adj_glean_dpo_vft = { .dv_lock = adj_dpo_lock, .dv_unlock = adj_dpo_unlock, .dv_format = format_adj_glean, .dv_get_urpf = adj_dpo_get_urpf, .dv_get_mtu = adj_dpo_get_mtu, }; /** * @brief The per-protocol VLIB graph nodes that are assigned to a glean * object. * * this means that these graph nodes are ones from which a glean is the * parent object in the DPO-graph. */ const static char* const glean_ip4_nodes[] = { "ip4-glean", NULL, }; const static char* const glean_ip6_nodes[] = { "ip6-glean", NULL, }; const static char* const * const glean_nodes[DPO_PROTO_NUM] = { [DPO_PROTO_IP4] = glean_ip4_nodes, [DPO_PROTO_IP6] = glean_ip6_nodes, [DPO_PROTO_MPLS] = NULL, }; void adj_glean_module_init (void) { dpo_register(DPO_ADJACENCY_GLEAN, &adj_glean_dpo_vft, glean_nodes); ethernet_address_change_ctx_t ctx = { .function = adj_glean_ethernet_change_mac, .function_opaque = 0, }; vec_add1 (ethernet_main.address_change_callbacks, ctx); ip6_table_bind_callback_t cbt6 = { .function = adj_glean_ip6_table_bind, }; vec_add1 (ip6_main.table_bind_callbacks, cbt6); ip4_table_bind_callback_t cbt4 = { .function = adj_glean_ip4_table_bind, }; vec_add1 (ip4_main.table_bind_callbacks, cbt4); }