diff options
author | Neale Ranns <nranns@cisco.com> | 2019-09-30 10:53:31 +0000 |
---|---|---|
committer | Ole Trøan <otroan@employees.org> | 2019-12-17 10:56:20 +0000 |
commit | cbe25aab3be72154f2c706c39eeba6a77f34450f (patch) | |
tree | 131fb53b5ec973be045ffb9e2eb797af01d112a0 /src/vnet/ip-neighbor/ip_neighbor.c | |
parent | 96453fd2417ebd1d69354a7fb692976129cea80e (diff) |
ip: Protocol Independent IP Neighbors
Type: feature
- ip-neighbour: generic neighbour handling; APIs, DBs, event handling,
aging
- arp: ARP protocol implementation
- ip6-nd; IPv6 neighbor discovery implementation; separate ND,
MLD, RA
- ip6-link; manage link-local addresses
- l2-arp-term; events separated from IP neighbours, since they are not
the same.
vnet retains just enough education to perform ND/ARP packet
construction.
arp and ip6-nd to be moved to plugins soon.
Change-Id: I88dedd0006b299344f4c7024a0aa5baa6b9a8bbe
Signed-off-by: Neale Ranns <nranns@cisco.com>
Diffstat (limited to 'src/vnet/ip-neighbor/ip_neighbor.c')
-rw-r--r-- | src/vnet/ip-neighbor/ip_neighbor.c | 1664 |
1 files changed, 1664 insertions, 0 deletions
diff --git a/src/vnet/ip-neighbor/ip_neighbor.c b/src/vnet/ip-neighbor/ip_neighbor.c new file mode 100644 index 00000000000..11684eb9de5 --- /dev/null +++ b/src/vnet/ip-neighbor/ip_neighbor.c @@ -0,0 +1,1664 @@ +/* + * src/vnet/ip/ip_neighboor.c: ip neighbor generic handling + * + * Copyright (c) 2018 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 <vppinfra/llist.h> + +#include <vnet/ip-neighbor/ip_neighbor.h> +#include <vnet/ip-neighbor/ip4_neighbor.h> +#include <vnet/ip-neighbor/ip6_neighbor.h> +#include <vnet/ip-neighbor/ip_neighbor_watch.h> + +#include <vnet/ip/ip6_ll_table.h> +#include <vnet/fib/fib_table.h> +#include <vnet/adj/adj_mcast.h> + +/** Pool for All IP neighbors */ +static ip_neighbor_t *ip_neighbor_pool; + +/** protocol specific lists of time sorted neighbors */ +index_t ip_neighbor_list_head[IP46_N_TYPES]; + +typedef struct ip_neighbor_elt_t_ +{ + clib_llist_anchor_t ipne_anchor; + index_t ipne_index; +} ip_neighbor_elt_t; + +/** Pool of linked list elemeents */ +ip_neighbor_elt_t *ip_neighbor_elt_pool; + +typedef struct ip_neighbor_db_t_ +{ + /** per interface hash */ + uword **ipndb_hash; + /** per-protocol limit - max number of neighbors*/ + u32 ipndb_limit; + /** max age of a neighbor before it's forcibly evicted */ + u32 ipndb_age; + /** when the limit is reached and new neighbors are created, should + * we recycle an old one */ + bool ipndb_recycle; + /** per-protocol number of elements */ + u32 ipndb_n_elts; + /** per-protocol number of elements per-fib-index*/ + u32 *ipndb_n_elts_per_fib; +} ip_neighbor_db_t; + +static vlib_log_class_t ipn_logger; + +/* DBs of neighbours one per AF */ +/* *INDENT-OFF* */ +static ip_neighbor_db_t ip_neighbor_db[IP46_N_TYPES] = { + [IP46_TYPE_IP4] = { + .ipndb_limit = 50000, + /* Default to not aging and not recycling */ + .ipndb_age = 0, + .ipndb_recycle = false, + }, + [IP46_TYPE_IP6] = { + .ipndb_limit = 50000, + /* Default to not aging and not recycling */ + .ipndb_age = 0, + .ipndb_recycle = false, + } +}; +/* *INDENT-ON* */ + +#define IP_NEIGHBOR_DBG(...) \ + vlib_log_debug (ipn_logger, __VA_ARGS__); + +#define IP_NEIGHBOR_INFO(...) \ + vlib_log_notice (ipn_logger, __VA_ARGS__); + +ip_neighbor_t * +ip_neighbor_get (index_t ipni) +{ + if (pool_is_free_index (ip_neighbor_pool, ipni)) + return (NULL); + + return (pool_elt_at_index (ip_neighbor_pool, ipni)); +} + +static index_t +ip_neighbor_get_index (const ip_neighbor_t * ipn) +{ + return (ipn - ip_neighbor_pool); +} + +static bool +ip_neighbor_is_dynamic (const ip_neighbor_t * ipn) +{ + return (ipn->ipn_flags & IP_NEIGHBOR_FLAG_DYNAMIC); +} + +const ip46_address_t * +ip_neighbor_get_ip (const ip_neighbor_t * ipn) +{ + return (&ipn->ipn_key->ipnk_ip); +} + +const mac_address_t * +ip_neighbor_get_mac (const ip_neighbor_t * ipn) +{ + return (&ipn->ipn_mac); +} + +const u32 +ip_neighbor_get_sw_if_index (const ip_neighbor_t * ipn) +{ + return (ipn->ipn_key->ipnk_sw_if_index); +} + +static void +ip_neighbor_list_remove (ip_neighbor_t * ipn) +{ + /* new neighbours, are added to the head of the list, since the + * list is time sorted, newest first */ + ip_neighbor_elt_t *elt; + + if (~0 != ipn->ipn_elt) + { + elt = pool_elt_at_index (ip_neighbor_elt_pool, ipn->ipn_elt); + + clib_llist_remove (ip_neighbor_elt_pool, ipne_anchor, elt); + } +} + +static void +ip_neighbor_refresh (ip_neighbor_t * ipn) +{ + /* new neighbours, are added to the head of the list, since the + * list is time sorted, newest first */ + ip_neighbor_elt_t *elt, *head; + + ipn->ipn_time_last_updated = vlib_time_now (vlib_get_main ()); + ipn->ipn_n_probes = 0; + + if (ip_neighbor_is_dynamic (ipn)) + { + if (~0 == ipn->ipn_elt) + /* first time insertion */ + pool_get_zero (ip_neighbor_elt_pool, elt); + else + { + /* already inserted - extract first */ + elt = pool_elt_at_index (ip_neighbor_elt_pool, ipn->ipn_elt); + + clib_llist_remove (ip_neighbor_elt_pool, ipne_anchor, elt); + } + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[ipn-> + ipn_key->ipnk_type]); + + elt->ipne_index = ip_neighbor_get_index (ipn); + clib_llist_add (ip_neighbor_elt_pool, ipne_anchor, elt, head); + ipn->ipn_elt = elt - ip_neighbor_elt_pool; + } +} + +static void +ip_neighbor_db_add (const ip_neighbor_t * ipn) +{ + vec_validate (ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash, + ipn->ipn_key->ipnk_sw_if_index); + + if (!ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash + [ipn->ipn_key->ipnk_sw_if_index]) + ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash[ipn-> + ipn_key->ipnk_sw_if_index] + = hash_create_mem (0, sizeof (ip_neighbor_key_t), sizeof (index_t)); + + hash_set_mem (ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_hash + [ipn->ipn_key->ipnk_sw_if_index], ipn->ipn_key, + ip_neighbor_get_index (ipn)); + + ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_n_elts++; +} + +static void +ip_neighbor_db_remove (const ip_neighbor_key_t * key) +{ + vec_validate (ip_neighbor_db[key->ipnk_type].ipndb_hash, + key->ipnk_sw_if_index); + + hash_unset_mem (ip_neighbor_db[key->ipnk_type].ipndb_hash + [key->ipnk_sw_if_index], key); + + ip_neighbor_db[key->ipnk_type].ipndb_n_elts--; +} + +static ip_neighbor_t * +ip_neighbor_db_find (const ip_neighbor_key_t * key) +{ + uword *p; + + if (key->ipnk_sw_if_index >= + vec_len (ip_neighbor_db[key->ipnk_type].ipndb_hash)) + return NULL; + + p = + hash_get_mem (ip_neighbor_db[key->ipnk_type].ipndb_hash + [key->ipnk_sw_if_index], key); + + if (p) + return ip_neighbor_get (p[0]); + + return (NULL); +} + +static u8 +ip46_type_pfx_len (ip46_type_t type) +{ + return (type == IP46_TYPE_IP4 ? 32 : 128); +} + +static void +ip_neighbor_adj_fib_add (ip_neighbor_t * ipn, u32 fib_index) +{ + if (ipn->ipn_key->ipnk_type == IP46_TYPE_IP6 && + ip6_address_is_link_local_unicast (&ipn->ipn_key->ipnk_ip.ip6)) + { + ip6_ll_prefix_t pfx = { + .ilp_addr = ipn->ipn_key->ipnk_ip.ip6, + .ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index, + }; + ipn->ipn_fib_entry_index = + ip6_ll_table_entry_update (&pfx, FIB_ROUTE_PATH_FLAG_NONE); + } + else + { + fib_protocol_t fproto; + + fproto = fib_proto_from_ip46 (ipn->ipn_key->ipnk_type); + + fib_prefix_t pfx = { + .fp_len = ip46_type_pfx_len (ipn->ipn_key->ipnk_type), + .fp_proto = fproto, + .fp_addr = ipn->ipn_key->ipnk_ip, + }; + + ipn->ipn_fib_entry_index = + fib_table_entry_path_add (fib_index, &pfx, FIB_SOURCE_ADJ, + FIB_ENTRY_FLAG_ATTACHED, + fib_proto_to_dpo (fproto), + &pfx.fp_addr, + ipn->ipn_key->ipnk_sw_if_index, + ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE); + + vec_validate (ip_neighbor_db + [ipn->ipn_key->ipnk_type].ipndb_n_elts_per_fib, + fib_index); + + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]++; + + if (1 == + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]) + fib_table_lock (fib_index, fproto, FIB_SOURCE_ADJ); + } +} + +static void +ip_neighbor_adj_fib_remove (ip_neighbor_t * ipn, u32 fib_index) +{ + if (FIB_NODE_INDEX_INVALID != ipn->ipn_fib_entry_index) + { + if (ipn->ipn_key->ipnk_type == IP46_TYPE_IP6 && + ip6_address_is_link_local_unicast (&ipn->ipn_key->ipnk_ip.ip6)) + { + ip6_ll_prefix_t pfx = { + .ilp_addr = ipn->ipn_key->ipnk_ip.ip6, + .ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index, + }; + ip6_ll_table_entry_delete (&pfx); + } + else + { + fib_protocol_t fproto; + + fproto = fib_proto_from_ip46 (ipn->ipn_key->ipnk_type); + + fib_prefix_t pfx = { + .fp_len = ip46_type_pfx_len (ipn->ipn_key->ipnk_type), + .fp_proto = fproto, + .fp_addr = ipn->ipn_key->ipnk_ip, + }; + + fib_table_entry_path_remove (fib_index, + &pfx, + FIB_SOURCE_ADJ, + fib_proto_to_dpo (fproto), + &pfx.fp_addr, + ipn->ipn_key->ipnk_sw_if_index, + ~0, 1, FIB_ROUTE_PATH_FLAG_NONE); + + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]--; + + if (0 == + ip_neighbor_db[ipn->ipn_key-> + ipnk_type].ipndb_n_elts_per_fib[fib_index]) + fib_table_unlock (fib_index, fproto, FIB_SOURCE_ADJ); + } + } +} + +static void +ip_neighbor_mk_complete (adj_index_t ai, ip_neighbor_t * ipn) +{ + adj_nbr_update_rewrite (ai, ADJ_NBR_REWRITE_FLAG_COMPLETE, + ethernet_build_rewrite (vnet_get_main (), + ipn-> + ipn_key->ipnk_sw_if_index, + adj_get_link_type (ai), + ipn->ipn_mac.bytes)); +} + +static void +ip_neighbor_mk_incomplete (adj_index_t ai) +{ + ip_adjacency_t *adj = adj_get (ai); + + adj_nbr_update_rewrite (ai, + ADJ_NBR_REWRITE_FLAG_INCOMPLETE, + ethernet_build_rewrite (vnet_get_main (), + adj-> + rewrite_header.sw_if_index, + adj_get_link_type (ai), + VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST)); +} + +static adj_walk_rc_t +ip_neighbor_mk_complete_walk (adj_index_t ai, void *ctx) +{ + ip_neighbor_t *ipn = ctx; + + ip_neighbor_mk_complete (ai, ipn); + + return (ADJ_WALK_RC_CONTINUE); +} + +static adj_walk_rc_t +ip_neighbor_mk_incomplete_walk (adj_index_t ai, void *ctx) +{ + ip_neighbor_mk_incomplete (ai); + + return (ADJ_WALK_RC_CONTINUE); +} + +static void +ip_neighbor_free (ip_neighbor_t * ipn) +{ + IP_NEIGHBOR_DBG ("free: %U", format_ip_neighbor, + ip_neighbor_get_index (ipn)); + + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_incomplete_walk, ipn); + ip_neighbor_adj_fib_remove + (ipn, + fib_table_get_index_for_sw_if_index + (fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + ipn->ipn_key->ipnk_sw_if_index)); + + ip_neighbor_list_remove (ipn); + ip_neighbor_db_remove (ipn->ipn_key); + clib_mem_free (ipn->ipn_key); + + pool_put (ip_neighbor_pool, ipn); +} + +static bool +ip_neighbor_force_reuse (ip46_type_t type) +{ + if (!ip_neighbor_db[type].ipndb_recycle) + return false; + + /* pluck the oldest entry, which is the one from the end of the list */ + ip_neighbor_elt_t *elt, *head; + + head = + pool_elt_at_index (ip_neighbor_elt_pool, ip_neighbor_list_head[type]); + + if (clib_llist_is_empty (ip_neighbor_elt_pool, ipne_anchor, head)) + return (false); + + elt = clib_llist_prev (ip_neighbor_elt_pool, ipne_anchor, head); + ip_neighbor_free (ip_neighbor_get (elt->ipne_index)); + + return (true); +} + +static ip_neighbor_t * +ip_neighbor_alloc (const ip_neighbor_key_t * key, + const mac_address_t * mac, ip_neighbor_flags_t flags) +{ + ip_neighbor_t *ipn; + + if (ip_neighbor_db[key->ipnk_type].ipndb_limit && + (ip_neighbor_db[key->ipnk_type].ipndb_n_elts >= + ip_neighbor_db[key->ipnk_type].ipndb_limit)) + { + if (!ip_neighbor_force_reuse (key->ipnk_type)) + return (NULL); + } + + pool_get_zero (ip_neighbor_pool, ipn); + + ipn->ipn_key = clib_mem_alloc (sizeof (*ipn->ipn_key)); + clib_memcpy (ipn->ipn_key, key, sizeof (*ipn->ipn_key)); + + ipn->ipn_fib_entry_index = FIB_NODE_INDEX_INVALID; + ipn->ipn_flags = flags; + ipn->ipn_elt = ~0; + + mac_address_copy (&ipn->ipn_mac, mac); + + ip_neighbor_db_add (ipn); + + /* create the adj-fib. the entry in the FIB table for the peer's interface */ + if (!(ipn->ipn_flags & IP_NEIGHBOR_FLAG_NO_FIB_ENTRY)) + ip_neighbor_adj_fib_add + (ipn, fib_table_get_index_for_sw_if_index + (fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + ipn->ipn_key->ipnk_sw_if_index)); + + return (ipn); +} + +int +ip_neighbor_add (const ip46_address_t * ip, + ip46_type_t type, + const mac_address_t * mac, + u32 sw_if_index, + ip_neighbor_flags_t flags, u32 * stats_index) +{ + fib_protocol_t fproto; + ip_neighbor_t *ipn; + + /* main thread only */ + ASSERT (0 == vlib_get_thread_index ()); + + fproto = fib_proto_from_ip46 (type); + + const ip_neighbor_key_t key = { + .ipnk_ip = *ip, + .ipnk_sw_if_index = sw_if_index, + .ipnk_type = type, + }; + + ipn = ip_neighbor_db_find (&key); + + if (ipn) + { + IP_NEIGHBOR_DBG ("update: %U, %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_address, ip, type, + format_ip_neighbor_flags, flags, format_mac_address_t, + mac); + + /* Refuse to over-write static neighbor entry. */ + if (!(flags & IP_NEIGHBOR_FLAG_STATIC) && + (ipn->ipn_flags & IP_NEIGHBOR_FLAG_STATIC)) + { + /* if MAC address match, still check to send event */ + if (0 == mac_address_cmp (&ipn->ipn_mac, mac)) + goto check_customers; + return -2; + } + + /* + * prevent a DoS attack from the data-plane that + * spams us with no-op updates to the MAC address + */ + if (0 == mac_address_cmp (&ipn->ipn_mac, mac)) + { + ip_neighbor_refresh (ipn); + goto check_customers; + } + + mac_address_copy (&ipn->ipn_mac, mac); + + /* A dynamic entry can become static, but not vice-versa. + * i.e. since if it was programmed by the CP then it must + * be removed by the CP */ + if ((flags & IP_NEIGHBOR_FLAG_STATIC) && + !(ipn->ipn_flags & IP_NEIGHBOR_FLAG_STATIC)) + { + ip_neighbor_list_remove (ipn); + ipn->ipn_flags |= IP_NEIGHBOR_FLAG_STATIC; + ipn->ipn_flags &= ~IP_NEIGHBOR_FLAG_DYNAMIC; + } + } + else + { + IP_NEIGHBOR_INFO ("add: %U, %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_address, ip, type, + format_ip_neighbor_flags, flags, format_mac_address_t, + mac); + + ipn = ip_neighbor_alloc (&key, mac, flags); + + if (NULL == ipn) + return VNET_API_ERROR_LIMIT_EXCEEDED; + } + + /* Update time stamp and flags. */ + ip_neighbor_refresh (ipn); + + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fproto, &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, ipn); + +check_customers: + /* Customer(s) requesting event for this address? */ + ip_neighbor_publish (ip_neighbor_get_index (ipn)); + + if (stats_index) + *stats_index = adj_nbr_find (fproto, + fib_proto_to_link (fproto), + &ipn->ipn_key->ipnk_ip, + ipn->ipn_key->ipnk_sw_if_index); + return 0; +} + +int +ip_neighbor_del (const ip46_address_t * ip, ip46_type_t type, u32 sw_if_index) +{ + ip_neighbor_t *ipn; + + /* main thread only */ + ASSERT (0 == vlib_get_thread_index ()); + + IP_NEIGHBOR_INFO ("delete: %U, %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_address, ip, type); + + const ip_neighbor_key_t key = { + .ipnk_ip = *ip, + .ipnk_sw_if_index = sw_if_index, + .ipnk_type = type, + }; + + ipn = ip_neighbor_db_find (&key); + + if (NULL == ipn) + return (VNET_API_ERROR_NO_SUCH_ENTRY); + + ip_neighbor_free (ipn); + + return (0); +} + +void +ip_neighbor_update (vnet_main_t * vnm, adj_index_t ai) +{ + ip_neighbor_t *ipn; + ip_adjacency_t *adj; + + adj = adj_get (ai); + + ip_neighbor_key_t key = { + .ipnk_ip = adj->sub_type.nbr.next_hop, + .ipnk_type = fib_proto_to_ip46 (adj->ia_nh_proto), + .ipnk_sw_if_index = adj->rewrite_header.sw_if_index, + }; + ipn = ip_neighbor_db_find (&key); + + switch (adj->lookup_next_index) + { + case IP_LOOKUP_NEXT_ARP: + if (NULL != ipn) + { + adj_nbr_walk_nh (adj->rewrite_header.sw_if_index, + adj->ia_nh_proto, + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, ipn); + } + else + { + /* + * no matching ARP entry. + * construct the rewrite required to for an ARP packet, and stick + * that in the adj's pipe to smoke. + */ + adj_nbr_update_rewrite + (ai, + ADJ_NBR_REWRITE_FLAG_INCOMPLETE, + ethernet_build_rewrite + (vnm, + adj->rewrite_header.sw_if_index, + VNET_LINK_ARP, + VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST)); + + /* + * since the FIB has added this adj for a route, it makes sense it + * may want to forward traffic sometime soon. Let's send a + * speculative ARP. just one. If we were to do periodically that + * wouldn't be bad either, but that's more code than i'm prepared to + * write at this time for relatively little reward. + */ + ip_neighbor_probe (adj); + } + break; + case IP_LOOKUP_NEXT_GLEAN: + case IP_LOOKUP_NEXT_BCAST: + case IP_LOOKUP_NEXT_MCAST: + case IP_LOOKUP_NEXT_DROP: + case IP_LOOKUP_NEXT_PUNT: + case IP_LOOKUP_NEXT_LOCAL: + case IP_LOOKUP_NEXT_REWRITE: + case IP_LOOKUP_NEXT_MCAST_MIDCHAIN: + case IP_LOOKUP_NEXT_MIDCHAIN: + case IP_LOOKUP_NEXT_ICMP_ERROR: + case IP_LOOKUP_N_NEXT: + ASSERT (0); + break; + } +} + +void +ip_neighbor_learn (const ip_neighbor_learn_t * l) +{ + ip_neighbor_add (&l->ip, l->type, &l->mac, l->sw_if_index, + IP_NEIGHBOR_FLAG_DYNAMIC, NULL); +} + +static clib_error_t * +ip_neighbor_cmd (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + ip46_address_t ip = ip46_address_initializer; + mac_address_t mac = ZERO_MAC_ADDRESS; + vnet_main_t *vnm = vnet_get_main (); + ip_neighbor_flags_t flags; + u32 sw_if_index = ~0; + int is_add = 1; + int count = 1; + + flags = IP_NEIGHBOR_FLAG_DYNAMIC; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + /* set ip arp TenGigE1/1/0/1 1.2.3.4 aa:bb:... or aabb.ccdd... */ + if (unformat (input, "%U %U %U", + unformat_vnet_sw_interface, vnm, &sw_if_index, + unformat_ip46_address, &ip, IP46_TYPE_ANY, + unformat_mac_address_t, &mac)) + ; + else if (unformat (input, "delete") || unformat (input, "del")) + is_add = 0; + else if (unformat (input, "static")) + { + flags |= IP_NEIGHBOR_FLAG_STATIC; + flags &= ~IP_NEIGHBOR_FLAG_DYNAMIC; + } + else if (unformat (input, "no-fib-entry")) + flags |= IP_NEIGHBOR_FLAG_NO_FIB_ENTRY; + else if (unformat (input, "count %d", &count)) + ; + else + break; + } + + if (sw_if_index == ~0 || + ip46_address_is_zero (&ip) || mac_address_is_zero (&mac)) + return clib_error_return (0, + "specify interface, IP address and MAC: `%U'", + format_unformat_error, input); + + while (count) + { + if (is_add) + ip_neighbor_add (&ip, ip46_address_get_type (&ip), &mac, sw_if_index, + flags, NULL); + else + ip_neighbor_del (&ip, ip46_address_get_type (&ip), sw_if_index); + + ip46_address_increment (ip46_address_get_type (&ip), &ip); + mac_address_increment (&mac); + + --count; + } + + return NULL; +} + +/* *INDENT-OFF* */ +/*? + * Add or delete IPv4 ARP cache entries. + * + * @note 'set ip neighbor' options (e.g. delete, static, 'fib-id <id>', + * 'count <number>', 'interface ip4_addr mac_addr') can be added in + * any order and combination. + * + * @cliexpar + * @parblock + * Add or delete IPv4 ARP cache entries as follows. MAC Address can be in + * either aa:bb:cc:dd:ee:ff format or aabb.ccdd.eeff format. + * @cliexcmd{set ip neighbor GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @cliexcmd{set ip neighbor delete GigabitEthernet2/0/0 6.0.0.3 de:ad:be:ef:ba:be} + * + * To add or delete an IPv4 ARP cache entry to or from a specific fib + * table: + * @cliexcmd{set ip neighbor fib-id 1 GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @cliexcmd{set ip neighbor fib-id 1 delete GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * + * Add or delete IPv4 static ARP cache entries as follows: + * @cliexcmd{set ip neighbor static GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @cliexcmd{set ip neighbor static delete GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * + * For testing / debugging purposes, the 'set ip neighbor' command can add or + * delete multiple entries. Supply the 'count N' parameter: + * @cliexcmd{set ip neighbor count 10 GigabitEthernet2/0/0 6.0.0.3 dead.beef.babe} + * @endparblock + ?*/ +VLIB_CLI_COMMAND (ip_neighbor_command, static) = { + .path = "set ip neighbor", + .short_help = + "set ip neighbor [del] <intfc> <ip-address> <mac-address> [static] [no-fib-entry] [count <count>] [fib-id <fib-id>] [proxy <lo-addr> - <hi-addr>]", + .function = ip_neighbor_cmd, +}; +VLIB_CLI_COMMAND (ip_neighbor_command2, static) = { + .path = "ip neighbor", + .short_help = + "ip neighbor [del] <intfc> <ip-address> <mac-address> [static] [no-fib-entry] [count <count>] [fib-id <fib-id>] [proxy <lo-addr> - <hi-addr>]", + .function = ip_neighbor_cmd, +}; +/* *INDENT-ON* */ + +static int +ip_neighbor_sort (void *a1, void *a2) +{ + index_t *ipni1 = a1, *ipni2 = a2; + ip_neighbor_t *ipn1, *ipn2; + int cmp; + + ipn1 = ip_neighbor_get (*ipni1); + ipn2 = ip_neighbor_get (*ipni2); + + cmp = vnet_sw_interface_compare (vnet_get_main (), + ipn1->ipn_key->ipnk_sw_if_index, + ipn2->ipn_key->ipnk_sw_if_index); + if (!cmp) + cmp = ip46_address_cmp (&ipn1->ipn_key->ipnk_ip, &ipn2->ipn_key->ipnk_ip); + return cmp; +} + +static index_t * +ip_neighbor_entries (u32 sw_if_index, ip46_type_t type) +{ + index_t *ipnis = NULL; + ip_neighbor_t *ipn; + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (sw_if_index != ~0 && + ipn->ipn_key->ipnk_sw_if_index != sw_if_index && + (IP46_TYPE_ANY == type || + (ipn->ipn_key->ipnk_type == type))) + continue; + vec_add1 (ipnis, ip_neighbor_get_index(ipn)); + })); + + /* *INDENT-ON* */ + + if (ipnis) + vec_sort_with_function (ipnis, ip_neighbor_sort); + return ipnis; +} + +static clib_error_t * +ip_neighbor_show_sorted_i (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd, ip46_type_t type) +{ + ip_neighbor_elt_t *elt, *head; + + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[type]); + + + vlib_cli_output (vm, "%=12s%=40s%=6s%=20s%=24s", "Time", "IP", + "Flags", "Ethernet", "Interface"); + + /* *INDENT-OFF*/ + /* the list is time sorted, newest first, so start from the back + * and work forwards. Stop when we get to one that is alive */ + clib_llist_foreach_reverse(ip_neighbor_elt_pool, + ipne_anchor, head, elt, + ({ + vlib_cli_output (vm, "%U", format_ip_neighbor, elt->ipne_index); + })); + /* *INDENT-ON*/ + + return (NULL); +} + +static clib_error_t * +ip_neighbor_show_i (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd, ip46_type_t type) +{ + index_t *ipni, *ipnis = NULL; + u32 sw_if_index; + + /* Filter entries by interface if given. */ + sw_if_index = ~0; + (void) unformat_user (input, unformat_vnet_sw_interface, vnet_get_main (), + &sw_if_index); + + ipnis = ip_neighbor_entries (sw_if_index, type); + + if (ipnis) + vlib_cli_output (vm, "%=12s%=40s%=6s%=20s%=24s", "Time", "IP", + "Flags", "Ethernet", "Interface"); + + vec_foreach (ipni, ipnis) + { + vlib_cli_output (vm, "%U", format_ip_neighbor, *ipni); + } + vec_free (ipnis); + + return (NULL); +} + +static clib_error_t * +ip_neighbor_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_i (vm, input, cmd, IP46_TYPE_ANY)); +} + +static clib_error_t * +ip6_neighbor_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_i (vm, input, cmd, IP46_TYPE_IP6)); +} + +static clib_error_t * +ip4_neighbor_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_i (vm, input, cmd, IP46_TYPE_IP4)); +} + +static clib_error_t * +ip6_neighbor_show_sorted (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_sorted_i (vm, input, cmd, IP46_TYPE_IP6)); +} + +static clib_error_t * +ip4_neighbor_show_sorted (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + return (ip_neighbor_show_sorted_i (vm, input, cmd, IP46_TYPE_IP4)); +} + +/*? + * Display all the IP neighbor entries. + * + * @cliexpar + * Example of how to display the IPv4 ARP table: + * @cliexstart{show ip neighbor} + * Time FIB IP4 Flags Ethernet Interface + * 346.3028 0 6.1.1.3 de:ad:be:ef:ba:be GigabitEthernet2/0/0 + * 3077.4271 0 6.1.1.4 S de:ad:be:ef:ff:ff GigabitEthernet2/0/0 + * 2998.6409 1 6.2.2.3 de:ad:be:ef:00:01 GigabitEthernet2/0/0 + * Proxy arps enabled for: + * Fib_index 0 6.0.0.1 - 6.0.0.11 + * @cliexend + ?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_ip_neighbors_cmd_node, static) = { + .path = "show ip neighbors", + .function = ip_neighbor_show, + .short_help = "show ip neighbors [interface]", +}; +VLIB_CLI_COMMAND (show_ip4_neighbors_cmd_node, static) = { + .path = "show ip4 neighbors", + .function = ip4_neighbor_show, + .short_help = "show ip4 neighbors [interface]", +}; +VLIB_CLI_COMMAND (show_ip6_neighbors_cmd_node, static) = { + .path = "show ip6 neighbors", + .function = ip6_neighbor_show, + .short_help = "show ip6 neighbors [interface]", +}; +VLIB_CLI_COMMAND (show_ip_neighbor_cmd_node, static) = { + .path = "show ip neighbor", + .function = ip_neighbor_show, + .short_help = "show ip neighbor [interface]", +}; +VLIB_CLI_COMMAND (show_ip4_neighbor_cmd_node, static) = { + .path = "show ip4 neighbor", + .function = ip4_neighbor_show, + .short_help = "show ip4 neighbor [interface]", +}; +VLIB_CLI_COMMAND (show_ip6_neighbor_cmd_node, static) = { + .path = "show ip6 neighbor", + .function = ip6_neighbor_show, + .short_help = "show ip6 neighbor [interface]", +}; +VLIB_CLI_COMMAND (show_ip4_neighbor_sorted_cmd_node, static) = { + .path = "show ip4 neighbor-sorted", + .function = ip4_neighbor_show_sorted, + .short_help = "show ip4 neighbor-sorted", +}; +VLIB_CLI_COMMAND (show_ip6_neighbor_sorted_cmd_node, static) = { + .path = "show ip6 neighbor-sorted", + .function = ip6_neighbor_show_sorted, + .short_help = "show ip6 neighbor-sorted", +}; +/* *INDENT-ON* */ + +static ip_neighbor_vft_t ip_nbr_vfts[IP46_N_TYPES]; + +void +ip_neighbor_register (ip46_type_t type, const ip_neighbor_vft_t * vft) +{ + ip_nbr_vfts[type] = *vft; +} + +void +ip_neighbor_probe_dst (const ip_adjacency_t * adj, const ip46_address_t * dst) +{ + if (!vnet_sw_interface_is_admin_up (vnet_get_main (), + adj->rewrite_header.sw_if_index)) + return; + + switch (adj->ia_nh_proto) + { + case FIB_PROTOCOL_IP6: + ip6_neighbor_probe_dst (adj, &dst->ip6); + break; + case FIB_PROTOCOL_IP4: + ip4_neighbor_probe_dst (adj, &dst->ip4); + break; + case FIB_PROTOCOL_MPLS: + ASSERT (0); + break; + } +} + +void +ip_neighbor_probe (const ip_adjacency_t * adj) +{ + ip_neighbor_probe_dst (adj, &adj->sub_type.nbr.next_hop); +} + +void +ip_neighbor_advertise (vlib_main_t * vm, + ip46_type_t type, + const ip46_address_t * addr, u32 sw_if_index) +{ + vnet_main_t *vnm = vnet_get_main (); + + if (type == IP46_TYPE_IP4 || type == IP46_TYPE_BOTH) + ip4_neighbor_advertise (vm, vnm, sw_if_index, &addr->ip4); + if (type == IP46_TYPE_IP6 || type == IP46_TYPE_BOTH) + ip6_neighbor_advertise (vm, vnm, sw_if_index, &addr->ip6); +} + +void +ip_neighbor_walk (ip46_type_t type, + u32 sw_if_index, ip_neighbor_walk_cb_t cb, void *ctx) +{ + ip_neighbor_key_t *key; + index_t ipni; + + if (~0 == sw_if_index) + { + uword **hash; + + vec_foreach (hash, ip_neighbor_db[type].ipndb_hash) + { + /* *INDENT-OFF* */ + hash_foreach (key, ipni, *hash, + ({ + cb (ipni, ctx); + })); + /* *INDENT-ON* */ + } + } + else + { + uword *hash; + + if (vec_len (ip_neighbor_db[type].ipndb_hash) <= sw_if_index) + return; + hash = ip_neighbor_db[type].ipndb_hash[sw_if_index]; + + /* *INDENT-OFF* */ + hash_foreach (key, ipni, hash, + ({ + cb (ipni, ctx); + })); + /* *INDENT-ON* */ + } +} + +int +ip4_neighbor_proxy_add (u32 fib_index, + const ip4_address_t * start, + const ip4_address_t * end) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_add) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_add + (fib_index, start, end)); + } + + return (-1); +} + +int +ip4_neighbor_proxy_delete (u32 fib_index, + const ip4_address_t * start, + const ip4_address_t * end) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_del) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_del + (fib_index, start, end)); + } + return -1; +} + +int +ip4_neighbor_proxy_enable (u32 sw_if_index) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_enable) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_enable (sw_if_index)); + } + return -1; +} + +int +ip4_neighbor_proxy_disable (u32 sw_if_index) +{ + if (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_disable) + { + return (ip_nbr_vfts[IP46_TYPE_IP4].inv_proxy4_disable (sw_if_index)); + } + return -1; +} + +int +ip6_neighbor_proxy_add (u32 sw_if_index, const ip6_address_t * addr) +{ + if (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_add) + { + return (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_add (sw_if_index, addr)); + } + return -1; +} + +int +ip6_neighbor_proxy_del (u32 sw_if_index, const ip6_address_t * addr) +{ + if (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_del) + { + return (ip_nbr_vfts[IP46_TYPE_IP6].inv_proxy6_del (sw_if_index, addr)); + } + return -1; +} + +static void +ip_neighbor_ethernet_change_mac (ethernet_main_t * em, + u32 sw_if_index, uword opaque) +{ + ip_neighbor_t *ipn; + adj_index_t ai; + + IP_NEIGHBOR_DBG ("mac-change: %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index); + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (ipn->ipn_key->ipnk_sw_if_index == sw_if_index) + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fib_proto_from_ip46(ipn->ipn_key->ipnk_type), + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, + ipn); + })); + /* *INDENT-ON* */ + + ai = adj_glean_get (FIB_PROTOCOL_IP4, sw_if_index); + + if (ADJ_INDEX_INVALID != ai) + adj_glean_update_rewrite (ai); +} + +void +ip_neighbor_populate (ip46_type_t type, u32 sw_if_index) +{ + index_t *ipnis = NULL, *ipni; + ip_neighbor_t *ipn; + + IP_NEIGHBOR_DBG ("populate: %U %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_type, type); + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (ipn->ipn_key->ipnk_type == type && + ipn->ipn_key->ipnk_sw_if_index == sw_if_index) + vec_add1 (ipnis, ipn - ip_neighbor_pool); + })); + /* *INDENT-ON* */ + + vec_foreach (ipni, ipnis) + { + ipn = ip_neighbor_get (*ipni); + + adj_nbr_walk_nh (ipn->ipn_key->ipnk_sw_if_index, + fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + &ipn->ipn_key->ipnk_ip, + ip_neighbor_mk_complete_walk, ipn); + } + vec_free (ipnis); +} + +void +ip_neighbor_flush (ip46_type_t type, u32 sw_if_index) +{ + index_t *ipnis = NULL, *ipni; + ip_neighbor_t *ipn; + + IP_NEIGHBOR_DBG ("flush: %U %U", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip46_type, type); + + /* *INDENT-OFF* */ + pool_foreach (ipn, ip_neighbor_pool, + ({ + if (ipn->ipn_key->ipnk_type == type && + ipn->ipn_key->ipnk_sw_if_index == sw_if_index && + ip_neighbor_is_dynamic (ipn)) + vec_add1 (ipnis, ipn - ip_neighbor_pool); + })); + /* *INDENT-ON* */ + + vec_foreach (ipni, ipnis) ip_neighbor_free (ip_neighbor_get (*ipni)); + vec_free (ipnis); +} + +/* + * Remove any arp entries associated with the specified interface + */ +static clib_error_t * +ip_neighbor_interface_admin_change (vnet_main_t * vnm, + u32 sw_if_index, u32 flags) +{ + ip46_type_t type; + + IP_NEIGHBOR_DBG ("interface-admin: %U %s", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, + (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP ? "up" : "down")); + + if (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) + { + FOREACH_IP46_TYPE (type) ip_neighbor_populate (type, sw_if_index); + } + else + { + /* admin down, flush all neighbours */ + FOREACH_IP46_TYPE (type) ip_neighbor_flush (type, sw_if_index); + } + + return (NULL); +} + +VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (ip_neighbor_interface_admin_change); + +/* + * Remove any arp entries associated with the specified interface + */ +static clib_error_t * +ip_neighbor_delete_sw_interface (vnet_main_t * vnm, + u32 sw_if_index, u32 is_add) +{ + IP_NEIGHBOR_DBG ("interface-change: %U %s", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, (is_add ? "add" : "del")); + + if (!is_add && sw_if_index != ~0) + { + ip46_type_t type; + + FOREACH_IP46_TYPE (type) ip_neighbor_flush (type, sw_if_index); + } + + return (NULL); +} + +VNET_SW_INTERFACE_ADD_DEL_FUNCTION (ip_neighbor_delete_sw_interface); + +typedef struct ip_neighbor_walk_covered_ctx_t_ +{ + ip46_type_t type; + ip46_address_t addr; + u32 length; + index_t *ipnis; +} ip_neighbor_walk_covered_ctx_t; + +static walk_rc_t +ip_neighbor_walk_covered (index_t ipni, void *arg) +{ + ip_neighbor_walk_covered_ctx_t *ctx = arg; + ip_neighbor_t *ipn; + + ipn = ip_neighbor_get (ipni); + + ASSERT (ipn->ipn_key->ipnk_type == ctx->type); + + if (IP46_TYPE_IP4 == ctx->type) + { + if (ip4_destination_matches_route (&ip4_main, + &ipn->ipn_key->ipnk_ip.ip4, + &ctx->addr.ip4, + ctx->length) && + ip_neighbor_is_dynamic (ipn)) + { + vec_add1 (ctx->ipnis, ip_neighbor_get_index (ipn)); + } + } + return (WALK_CONTINUE); +} + + +/* + * callback when an interface address is added or deleted + */ +static void +ip_neighbor_add_del_interface_address_v4 (ip4_main_t * im, + uword opaque, + u32 sw_if_index, + ip4_address_t * address, + u32 address_length, + u32 if_address_index, u32 is_del) +{ + /* + * Flush the ARP cache of all entries covered by the address + * that is being removed. + */ + IP_NEIGHBOR_DBG ("addr-%d: %U, %U/%d", + (is_del ? "del" : "add"), + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip4_address, address, address_length); + + if (is_del) + { + ip_neighbor_walk_covered_ctx_t ctx = { + .addr.ip4 = *address, + .type = IP46_TYPE_IP4, + .length = address_length, + }; + index_t *ipni; + + ip_neighbor_walk (IP46_TYPE_IP4, sw_if_index, + ip_neighbor_walk_covered, &ctx); + + vec_foreach (ipni, ctx.ipnis) + ip_neighbor_free (ip_neighbor_get (*ipni)); + + vec_free (ctx.ipnis); + } +} + +/* + * callback when an interface address is added or deleted + */ +static void +ip_neighbor_add_del_interface_address_v6 (ip6_main_t * im, + uword opaque, + u32 sw_if_index, + ip6_address_t * address, + u32 address_length, + u32 if_address_index, u32 is_del) +{ + /* + * Flush the ARP cache of all entries covered by the address + * that is being removed. + */ + IP_NEIGHBOR_DBG ("addr-change: %U, %U/%d %s", + format_vnet_sw_if_index_name, vnet_get_main (), + sw_if_index, format_ip6_address, address, address_length, + (is_del ? "del" : "add")); + + if (is_del) + { + ip_neighbor_walk_covered_ctx_t ctx = { + .addr.ip6 = *address, + .type = IP46_TYPE_IP6, + .length = address_length, + }; + index_t *ipni; + + ip_neighbor_walk (IP46_TYPE_IP6, sw_if_index, + ip_neighbor_walk_covered, &ctx); + + vec_foreach (ipni, ctx.ipnis) + ip_neighbor_free (ip_neighbor_get (*ipni)); + + vec_free (ctx.ipnis); + } +} + +typedef struct ip_neighbor_table_bind_ctx_t_ +{ + u32 new_fib_index; + u32 old_fib_index; +} ip_neighbor_table_bind_ctx_t; + +static walk_rc_t +ip_neighbor_walk_table_bind (index_t ipni, void *arg) +{ + ip_neighbor_table_bind_ctx_t *ctx = arg; + ip_neighbor_t *ipn; + + ipn = ip_neighbor_get (ipni); + ip_neighbor_adj_fib_remove (ipn, ctx->old_fib_index); + ip_neighbor_adj_fib_add (ipn, ctx->new_fib_index); + + return (WALK_CONTINUE); +} + +static void +ip_neighbor_table_bind_v4 (ip4_main_t * im, + uword opaque, + u32 sw_if_index, + u32 new_fib_index, u32 old_fib_index) +{ + ip_neighbor_table_bind_ctx_t ctx = { + .old_fib_index = old_fib_index, + .new_fib_index = new_fib_index, + }; + + ip_neighbor_walk (IP46_TYPE_IP4, sw_if_index, + ip_neighbor_walk_table_bind, &ctx); +} + +static void +ip_neighbor_table_bind_v6 (ip6_main_t * im, + uword opaque, + u32 sw_if_index, + u32 new_fib_index, u32 old_fib_index) +{ + ip_neighbor_table_bind_ctx_t ctx = { + .old_fib_index = old_fib_index, + .new_fib_index = new_fib_index, + }; + + ip_neighbor_walk (IP46_TYPE_IP6, sw_if_index, + ip_neighbor_walk_table_bind, &ctx); +} + +typedef enum ip_neighbor_age_state_t_ +{ + IP_NEIGHBOR_AGE_ALIVE, + IP_NEIGHBOR_AGE_PROBE, + IP_NEIGHBOR_AGE_DEAD, +} ip_neighbor_age_state_t; + +#define IP_NEIGHBOR_PROCESS_SLEEP_LONG (0) + +static ip_neighbor_age_state_t +ip_neighbour_age_out (index_t ipni, f64 now, f64 * wait) +{ + ip_neighbor_t *ipn; + f64 ttl; + + ipn = ip_neighbor_get (ipni); + ttl = now - ipn->ipn_time_last_updated; + *wait = IP_NEIGHBOR_PROCESS_SLEEP_LONG; + + if (ttl > ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_age) + { + IP_NEIGHBOR_DBG ("aged: %U @%f - %f > %d", + format_ip_neighbor, ipni, now, + ipn->ipn_time_last_updated, + ip_neighbor_db[ipn->ipn_key->ipnk_type].ipndb_age); + if (ipn->ipn_n_probes > 2) + { + /* 3 strikes and yea-re out */ + IP_NEIGHBOR_DBG ("dead: %U", format_ip_neighbor, ipni); + return (IP_NEIGHBOR_AGE_DEAD); + } + else + { + adj_index_t ai; + + ai = adj_glean_get (fib_proto_from_ip46 (ipn->ipn_key->ipnk_type), + ip_neighbor_get_sw_if_index (ipn)); + + if (ADJ_INDEX_INVALID != ai) + ip_neighbor_probe_dst (adj_get (ai), ip_neighbor_get_ip (ipn)); + + ipn->ipn_n_probes++; + *wait = 1; + } + } + else + { + *wait = ttl; + return (IP_NEIGHBOR_AGE_ALIVE); + } + + return (IP_NEIGHBOR_AGE_PROBE); +} + +typedef enum ip_neighbor_process_event_t_ +{ + IP_NEIGHBOR_AGE_PROCESS_WAKEUP, +} ip_neighbor_process_event_t; + +static uword +ip_neighbor_age_loop (vlib_main_t * vm, + vlib_node_runtime_t * rt, + vlib_frame_t * f, ip46_type_t type) +{ + uword event_type, *event_data = NULL; + f64 timeout; + + /* Set the timeout to an effectively infinite value when the process starts */ + timeout = IP_NEIGHBOR_PROCESS_SLEEP_LONG; + + while (1) + { + f64 now; + + if (!timeout) + vlib_process_wait_for_event (vm); + else + vlib_process_wait_for_event_or_clock (vm, timeout); + + event_type = vlib_process_get_events (vm, &event_data); + vec_reset_length (event_data); + + now = vlib_time_now (vm); + + switch (event_type) + { + case ~0: + { + /* timer expired */ + ip_neighbor_elt_t *elt, *head; + f64 wait; + + timeout = 1e5; + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[type]); + + /* *INDENT-OFF*/ + /* the list is time sorted, newest first, so start from the back + * and work forwards. Stop when we get to one that is alive */ + restart: + clib_llist_foreach_reverse(ip_neighbor_elt_pool, + ipne_anchor, head, elt, + ({ + ip_neighbor_age_state_t res; + + res = ip_neighbour_age_out(elt->ipne_index, now, &wait); + + if (IP_NEIGHBOR_AGE_ALIVE == res) { + /* the oldest neighbor has not yet expired, go back to sleep */ + break; + } + else if (IP_NEIGHBOR_AGE_DEAD == res) { + /* the oldest neighbor is dead, pop it, then restart the walk + * again from the back */ + ip_neighbor_free (ip_neighbor_get(elt->ipne_index)); + goto restart; + } + + timeout = clib_min (wait, timeout); + })); + /* *INDENT-ON* */ + break; + } + case IP_NEIGHBOR_AGE_PROCESS_WAKEUP: + { + + if (!ip_neighbor_db[type].ipndb_age) + { + /* aging has been disabled */ + timeout = 0; + break; + } + ip_neighbor_elt_t *elt, *head; + + head = pool_elt_at_index (ip_neighbor_elt_pool, + ip_neighbor_list_head[type]); + elt = clib_llist_prev (ip_neighbor_elt_pool, ipne_anchor, head); + + /* poke the oldset neighbour for aging, which returns how long we sleep for */ + if (IP_NEIGHBOR_AGE_PROBE == + ip_neighbour_age_out (elt->ipne_index, now, &timeout)) + /* we probed for the oldest entry, sleep for a short time to get to the next */ + timeout = 0.01; + break; + } + } + } + return 0; +} + +static uword +ip4_neighbor_age_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + return (ip_neighbor_age_loop (vm, rt, f, IP46_TYPE_IP4)); +} + +static uword +ip6_neighbor_age_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + return (ip_neighbor_age_loop (vm, rt, f, IP46_TYPE_IP6)); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (ip4_neighbor_age_process_node,static) = { + .function = ip4_neighbor_age_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "ip4-neighbor-age-process", +}; +VLIB_REGISTER_NODE (ip6_neighbor_age_process_node,static) = { + .function = ip6_neighbor_age_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "ip6-neighbor-age-process", +}; +/* *INDENT-ON* */ + +int +ip_neighbor_config (ip46_type_t type, u32 limit, u32 age, bool recycle) +{ + ip_neighbor_db[type].ipndb_limit = limit; + ip_neighbor_db[type].ipndb_recycle = recycle; + ip_neighbor_db[type].ipndb_age = age; + + vlib_process_signal_event (vlib_get_main (), + (IP46_TYPE_IP4 == type ? + ip4_neighbor_age_process_node.index : + ip6_neighbor_age_process_node.index), + IP_NEIGHBOR_AGE_PROCESS_WAKEUP, 0); + + return (0); +} + +static clib_error_t * +ip_neighbor_config_show (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + ip46_type_t type; + + /* *INDENT-OFF* */ + FOREACH_IP46_TYPE(type) { + vlib_cli_output (vm, "%U:", format_ip46_type, type); + vlib_cli_output (vm, " limit:%d, age:%d, recycle:%d", + ip_neighbor_db[type].ipndb_limit, + ip_neighbor_db[type].ipndb_age, + ip_neighbor_db[type].ipndb_recycle); + } + + /* *INDENT-ON* */ + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_ip_neighbor_cfg_cmd_node, static) = { + .path = "show ip neighbor-config", + .function = ip_neighbor_config_show, + .short_help = "show ip neighbor-config", +}; +/* *INDENT-ON* */ + +static clib_error_t * +ip_neighbor_init (vlib_main_t * vm) +{ + { + ip4_add_del_interface_address_callback_t cb = { + .function = ip_neighbor_add_del_interface_address_v4, + }; + vec_add1 (ip4_main.add_del_interface_address_callbacks, cb); + } + { + ip6_add_del_interface_address_callback_t cb = { + .function = ip_neighbor_add_del_interface_address_v6, + }; + vec_add1 (ip6_main.add_del_interface_address_callbacks, cb); + } + { + ip4_table_bind_callback_t cb = { + .function = ip_neighbor_table_bind_v4, + }; + vec_add1 (ip4_main.table_bind_callbacks, cb); + } + { + ip6_table_bind_callback_t cb = { + .function = ip_neighbor_table_bind_v6, + }; + vec_add1 (ip6_main.table_bind_callbacks, cb); + } + { + ethernet_address_change_ctx_t ctx = { + .function = ip_neighbor_ethernet_change_mac, + .function_opaque = 0, + }; + vec_add1 (ethernet_main.address_change_callbacks, ctx); + } + + ipn_logger = vlib_log_register_class ("ip", "neighbor"); + + ip46_type_t type; + + FOREACH_IP46_TYPE (type) + ip_neighbor_list_head[type] = + clib_llist_make_head (ip_neighbor_elt_pool, ipne_anchor); + + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_INIT_FUNCTION (ip_neighbor_init) = +{ + .runs_after = VLIB_INITS("ip_main_init"), +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ |