-#include <vnet/ip/adj_alloc.h>
-
-static void
-ip_multipath_del_adjacency (ip_lookup_main_t * lm, u32 del_adj_index);
-
-always_inline void
-ip_poison_adjacencies (ip_adjacency_t * adj, uword n_adj)
-{
- if (CLIB_DEBUG > 0)
- {
- u32 save_handle = adj->heap_handle;;
- u32 save_n_adj = adj->n_adj;
-
- memset (adj, 0xfe, n_adj * sizeof (adj[0]));
-
- adj->heap_handle = save_handle;
- adj->n_adj = save_n_adj;
- }
-}
-
-static void
-ip_share_adjacency(ip_lookup_main_t * lm, u32 adj_index)
-{
- ip_adjacency_t * adj = ip_get_adjacency(lm, adj_index);
- uword * p;
- u32 old_ai;
- uword signature = vnet_ip_adjacency_signature (adj);
-
- p = hash_get (lm->adj_index_by_signature, signature);
- /* Hash collision? */
- if (p)
- {
- /* Save the adj index, p[0] will be toast after the unset! */
- old_ai = p[0];
- hash_unset (lm->adj_index_by_signature, signature);
- hash_set (lm->adj_index_by_signature, signature, adj_index);
- adj->next_adj_with_signature = old_ai;
- }
- else
- {
- adj->next_adj_with_signature = 0;
- hash_set (lm->adj_index_by_signature, signature, adj_index);
- }
-}
-
-static void
-ip_unshare_adjacency(ip_lookup_main_t * lm, u32 adj_index)
-{
- ip_adjacency_t * adj = ip_get_adjacency(lm, adj_index);
- uword signature;
- uword * p;
- u32 this_ai;
- ip_adjacency_t * this_adj, * prev_adj = 0;
-
- signature = vnet_ip_adjacency_signature (adj);
- p = hash_get (lm->adj_index_by_signature, signature);
- if (p == 0)
- return;
-
- this_ai = p[0];
- /* At the top of the signature chain (likely)? */
- if (this_ai == adj_index)
- {
- if (adj->next_adj_with_signature == 0)
- {
- hash_unset (lm->adj_index_by_signature, signature);
- return;
- }
- else
- {
- this_adj = ip_get_adjacency (lm, adj->next_adj_with_signature);
- hash_unset (lm->adj_index_by_signature, signature);
- hash_set (lm->adj_index_by_signature, signature,
- this_adj->heap_handle);
- }
- }
- else /* walk signature chain */
- {
- this_adj = ip_get_adjacency (lm, this_ai);
- while (this_adj != adj)
- {
- prev_adj = this_adj;
- this_adj = ip_get_adjacency
- (lm, this_adj->next_adj_with_signature);
- /*
- * This can happen when creating the first multipath adj of a set
- * We end up looking at the miss adjacency (handle==0).
- */
- if (this_adj->heap_handle == 0)
- return;
- }
- prev_adj->next_adj_with_signature = this_adj->next_adj_with_signature;
- }
-}
-
-int ip_register_adjacency(vlib_main_t *vm,
- u8 is_ip4,
- ip_adj_register_t *reg)
-{
- ip_lookup_main_t *lm = (is_ip4)?&ip4_main.lookup_main:&ip6_main.lookup_main;
- vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) ((is_ip4)?"ip4-lookup":"ip6-lookup"));
- vlib_node_t *next_node = vlib_get_node_by_name(vm, (u8 *) reg->node_name);
- *reg->next_index = vlib_node_add_next (vm, node->index, next_node->index);
- vec_validate(lm->registered_adjacencies, *reg->next_index);
- lm->registered_adjacencies[*reg->next_index] = *reg;
- return 0;
-}
-
-int ip_init_registered_adjacencies(u8 is_ip4)
-{
- vlib_main_t *vm = vlib_get_main();
- ip_lookup_main_t *lm = (is_ip4)?&ip4_main.lookup_main:&ip6_main.lookup_main;
- ip_adj_register_t *reg = lm->registered_adjacencies;
- lm->registered_adjacencies = 0; //Init vector
- int rv;
- while (reg) {
- if((rv = ip_register_adjacency(vm, is_ip4, reg)))
- return rv;
- reg = reg->next;
- }
- return 0;
-}
-
-/* Create new block of given number of contiguous adjacencies. */
-ip_adjacency_t *
-ip_add_adjacency (ip_lookup_main_t * lm,
- ip_adjacency_t * copy_adj,
- u32 n_adj,
- u32 * adj_index_return)
-{
- ip_adjacency_t * adj;
- u32 ai, i, handle;
-
- /* See if we know enough to attempt to share an existing adjacency */
- if (copy_adj && n_adj == 1)
- {
- uword signature;
- uword * p;
-
- switch (copy_adj->lookup_next_index)
- {
- case IP_LOOKUP_NEXT_DROP:
- if (lm->drop_adj_index)
- {
- adj = ip_get_adjacency (lm, lm->drop_adj_index);
- *adj_index_return = lm->drop_adj_index;
- return (adj);
- }
- break;
-
- case IP_LOOKUP_NEXT_LOCAL:
- if (lm->local_adj_index)
- {
- adj = ip_get_adjacency (lm, lm->local_adj_index);
- *adj_index_return = lm->local_adj_index;
- return (adj);
- }
- default:
- break;
- }
-
- signature = vnet_ip_adjacency_signature (copy_adj);
- p = hash_get (lm->adj_index_by_signature, signature);
- if (p)
- {
- adj = vec_elt_at_index (lm->adjacency_heap, p[0]);
- while (1)
- {
- if (vnet_ip_adjacency_share_compare (adj, copy_adj))
- {
- adj->share_count++;
- *adj_index_return = p[0];
- return adj;
- }
- if (adj->next_adj_with_signature == 0)
- break;
- adj = vec_elt_at_index (lm->adjacency_heap,
- adj->next_adj_with_signature);
- }
- }
- }
-
- lm->adjacency_heap = aa_alloc (lm->adjacency_heap, &adj, n_adj);
- handle = ai = adj->heap_handle;
-
- ip_poison_adjacencies (adj, n_adj);
-
- /* Validate adjacency counters. */
- vlib_validate_combined_counter (&lm->adjacency_counters, ai + n_adj - 1);
-
- for (i = 0; i < n_adj; i++)
- {
- /* Make sure certain fields are always initialized. */
- adj[i].rewrite_header.sw_if_index = ~0;
- adj[i].explicit_fib_index = ~0;
- adj[i].mcast_group_index = ~0;
- adj[i].classify.table_index = ~0;
- adj[i].saved_lookup_next_index = 0;
- adj[i].special_adjacency_format_function_index = 0;
-
- if (copy_adj)
- adj[i] = copy_adj[i];
-
- adj[i].heap_handle = handle;
- adj[i].n_adj = n_adj;
- adj[i].share_count = 0;
- adj[i].next_adj_with_signature = 0;
-
- /* Zero possibly stale counters for re-used adjacencies. */
- vlib_zero_combined_counter (&lm->adjacency_counters, ai + i);
- }
-
- /* Set up to share the adj later */
- if (copy_adj && n_adj == 1)
- ip_share_adjacency(lm, ai);
-
- *adj_index_return = ai;
- return adj;
-}
-
-void
-ip_update_adjacency (ip_lookup_main_t * lm,
- u32 adj_index,
- ip_adjacency_t * copy_adj)
-{
- ip_adjacency_t * adj = ip_get_adjacency(lm, adj_index);
-
- ip_call_add_del_adjacency_callbacks (lm, adj_index, /* is_del */ 1);
- ip_unshare_adjacency(lm, adj_index);
-
- /* temporary redirect to drop while updating rewrite data */
- adj->lookup_next_index = IP_LOOKUP_NEXT_ARP;
- CLIB_MEMORY_BARRIER();
-
- clib_memcpy (&adj->rewrite_header, ©_adj->rewrite_header,
- VLIB_BUFFER_PRE_DATA_SIZE);
- adj->lookup_next_index = copy_adj->lookup_next_index;
- ip_share_adjacency(lm, adj_index);
- ip_call_add_del_adjacency_callbacks (lm, adj_index, /* is_del */ 0);
-}
-
-static void ip_del_adjacency2 (ip_lookup_main_t * lm, u32 adj_index, u32 delete_multipath_adjacency)
-{
- ip_adjacency_t * adj;
-
- ip_call_add_del_adjacency_callbacks (lm, adj_index, /* is_del */ 1);
-
- adj = ip_get_adjacency (lm, adj_index);
-
- /* Special-case miss, local, drop adjs */
- if (adj_index < 3)
- return;
-
- if (adj->n_adj == 1)
- {
- if (adj->share_count > 0)
- {
- adj->share_count --;
- return;
- }
-
- ip_unshare_adjacency(lm, adj_index);
- }
-
- if (delete_multipath_adjacency)
- ip_multipath_del_adjacency (lm, adj_index);
-
- ip_poison_adjacencies (adj, adj->n_adj);
-
- aa_free (lm->adjacency_heap, adj);
-}
-
-void ip_del_adjacency (ip_lookup_main_t * lm, u32 adj_index)
-{ ip_del_adjacency2 (lm, adj_index, /* delete_multipath_adjacency */ 1); }
-
-static int
-next_hop_sort_by_weight (ip_multipath_next_hop_t * n1,
- ip_multipath_next_hop_t * n2)
-{
- int cmp = (int) n1->weight - (int) n2->weight;
- return (cmp == 0
- ? (int) n1->next_hop_adj_index - (int) n2->next_hop_adj_index
- : (cmp > 0 ? +1 : -1));
-}
-
-/* Given next hop vector is over-written with normalized one with sorted weights and
- with weights corresponding to the number of adjacencies for each next hop.
- Returns number of adjacencies in block. */
-static u32 ip_multipath_normalize_next_hops (ip_lookup_main_t * lm,
- ip_multipath_next_hop_t * raw_next_hops,
- ip_multipath_next_hop_t ** normalized_next_hops)
-{
- ip_multipath_next_hop_t * nhs;
- uword n_nhs, n_adj, n_adj_left, i;
- f64 sum_weight, norm, error;
-
- n_nhs = vec_len (raw_next_hops);
- ASSERT (n_nhs > 0);
- if (n_nhs == 0)
- return 0;
-
- /* Allocate enough space for 2 copies; we'll use second copy to save original weights. */
- nhs = *normalized_next_hops;
- vec_validate (nhs, 2*n_nhs - 1);
-
- /* Fast path: 1 next hop in block. */
- n_adj = n_nhs;
- if (n_nhs == 1)
- {
- nhs[0] = raw_next_hops[0];
- nhs[0].weight = 1;
- _vec_len (nhs) = 1;
- goto done;
- }
-
- else if (n_nhs == 2)
- {
- int cmp = next_hop_sort_by_weight (&raw_next_hops[0], &raw_next_hops[1]) < 0;
-
- /* Fast sort. */
- nhs[0] = raw_next_hops[cmp];
- nhs[1] = raw_next_hops[cmp ^ 1];
-
- /* Fast path: equal cost multipath with 2 next hops. */
- if (nhs[0].weight == nhs[1].weight)
- {
- nhs[0].weight = nhs[1].weight = 1;
- _vec_len (nhs) = 2;
- goto done;
- }
- }
- else
- {
- clib_memcpy (nhs, raw_next_hops, n_nhs * sizeof (raw_next_hops[0]));
- qsort (nhs, n_nhs, sizeof (nhs[0]), (void *) next_hop_sort_by_weight);
- }
-
- /* Find total weight to normalize weights. */
- sum_weight = 0;
- for (i = 0; i < n_nhs; i++)
- sum_weight += nhs[i].weight;
-
- /* In the unlikely case that all weights are given as 0, set them all to 1. */
- if (sum_weight == 0)
- {
- for (i = 0; i < n_nhs; i++)
- nhs[i].weight = 1;
- sum_weight = n_nhs;
- }
-
- /* Save copies of all next hop weights to avoid being overwritten in loop below. */
- for (i = 0; i < n_nhs; i++)
- nhs[n_nhs + i].weight = nhs[i].weight;
-
- /* Try larger and larger power of 2 sized adjacency blocks until we
- find one where traffic flows to within 1% of specified weights. */
- for (n_adj = max_pow2 (n_nhs); ; n_adj *= 2)
- {
- error = 0;
-
- norm = n_adj / sum_weight;
- n_adj_left = n_adj;
- for (i = 0; i < n_nhs; i++)
- {
- f64 nf = nhs[n_nhs + i].weight * norm; /* use saved weights */
- word n = flt_round_nearest (nf);
-
- n = n > n_adj_left ? n_adj_left : n;
- n_adj_left -= n;
- error += fabs (nf - n);
- nhs[i].weight = n;
- }
-
- nhs[0].weight += n_adj_left;
-
- /* Less than 5% average error per adjacency with this size adjacency block? */
- if (error <= lm->multipath_next_hop_error_tolerance*n_adj)
- {
- /* Truncate any next hops with zero weight. */
- _vec_len (nhs) = i;
- break;
- }
- }
-
- done:
- /* Save vector for next call. */
- *normalized_next_hops = nhs;
- return n_adj;
-}
-
-always_inline uword
-ip_next_hop_hash_key_from_handle (uword handle)
-{ return 1 + 2*handle; }
-
-always_inline uword
-ip_next_hop_hash_key_is_heap_handle (uword k)
-{ return k & 1; }
-
-always_inline uword
-ip_next_hop_hash_key_get_heap_handle (uword k)
-{
- ASSERT (ip_next_hop_hash_key_is_heap_handle (k));
- return k / 2;
-}
-
-static u32
-ip_multipath_adjacency_get (ip_lookup_main_t * lm,
- ip_multipath_next_hop_t * raw_next_hops,
- uword create_if_non_existent)
-{
- uword * p;
- u32 i, j, n_adj, adj_index, adj_heap_handle;
- ip_adjacency_t * adj, * copy_adj;
- ip_multipath_next_hop_t * nh, * nhs;
- ip_multipath_adjacency_t * madj;
-
- n_adj = ip_multipath_normalize_next_hops (lm, raw_next_hops, &lm->next_hop_hash_lookup_key_normalized);
- nhs = lm->next_hop_hash_lookup_key_normalized;
-
- /* Basic sanity. */
- ASSERT (n_adj >= vec_len (raw_next_hops));
-
- /* Use normalized next hops to see if we've seen a block equivalent to this one before. */
- p = hash_get_mem (lm->multipath_adjacency_by_next_hops, nhs);
- if (p)
- return p[0];
-
- if (! create_if_non_existent)
- return 0;
-
- adj = ip_add_adjacency (lm, /* copy_adj */ 0, n_adj, &adj_index);
- adj_heap_handle = adj[0].heap_handle;
-
- /* Fill in adjacencies in block based on corresponding next hop adjacencies. */
- i = 0;
- vec_foreach (nh, nhs)
- {
- copy_adj = ip_get_adjacency (lm, nh->next_hop_adj_index);
- for (j = 0; j < nh->weight; j++)
- {
- adj[i] = copy_adj[0];
- adj[i].heap_handle = adj_heap_handle;
- adj[i].n_adj = n_adj;
- i++;
- }
- }
-
- /* All adjacencies should have been initialized. */
- ASSERT (i == n_adj);
-
- vec_validate (lm->multipath_adjacencies, adj_heap_handle);
- madj = vec_elt_at_index (lm->multipath_adjacencies, adj_heap_handle);
-
- madj->adj_index = adj_index;
- madj->n_adj_in_block = n_adj;
- madj->reference_count = 0; /* caller will set to one. */
-
- madj->normalized_next_hops.count = vec_len (nhs);
- madj->normalized_next_hops.heap_offset
- = heap_alloc (lm->next_hop_heap, vec_len (nhs),
- madj->normalized_next_hops.heap_handle);
- clib_memcpy (lm->next_hop_heap + madj->normalized_next_hops.heap_offset,
- nhs, vec_bytes (nhs));
-
- hash_set (lm->multipath_adjacency_by_next_hops,
- ip_next_hop_hash_key_from_handle (madj->normalized_next_hops.heap_handle),
- madj - lm->multipath_adjacencies);
-
- madj->unnormalized_next_hops.count = vec_len (raw_next_hops);
- madj->unnormalized_next_hops.heap_offset
- = heap_alloc (lm->next_hop_heap, vec_len (raw_next_hops),
- madj->unnormalized_next_hops.heap_handle);
- clib_memcpy (lm->next_hop_heap + madj->unnormalized_next_hops.heap_offset,
- raw_next_hops, vec_bytes (raw_next_hops));
-
- ip_call_add_del_adjacency_callbacks (lm, adj_index, /* is_del */ 0);
-
- return adj_heap_handle;
-}
-
-/* Returns 0 for next hop not found. */
-u32
-ip_multipath_adjacency_add_del_next_hop (ip_lookup_main_t * lm,
- u32 is_del,
- u32 old_mp_adj_index,
- u32 next_hop_adj_index,
- u32 next_hop_weight,
- u32 * new_mp_adj_index)
-{
- ip_multipath_adjacency_t * mp_old, * mp_new;
- ip_multipath_next_hop_t * nh, * nhs, * hash_nhs;
- u32 n_nhs, i_nh;
-
- mp_new = mp_old = 0;
- n_nhs = 0;
- i_nh = 0;
- nhs = 0;
-
- /* If old adj is not multipath, we need to "convert" it by calling this
- * function recursively */
- if (old_mp_adj_index != ~0 && !ip_adjacency_is_multipath(lm, old_mp_adj_index))
- {
- ip_multipath_adjacency_add_del_next_hop(lm, /* is_del */ 0,
- /* old_mp_adj_index */ ~0,
- /* nh_adj_index */ old_mp_adj_index,
- /* weight * */ 1,
- &old_mp_adj_index);
- }
-
- /* If old multipath adjacency is valid, find requested next hop. */
- if (old_mp_adj_index < vec_len (lm->multipath_adjacencies)
- && lm->multipath_adjacencies[old_mp_adj_index].normalized_next_hops.count > 0)
- {
- mp_old = vec_elt_at_index (lm->multipath_adjacencies, old_mp_adj_index);
-
- nhs = vec_elt_at_index (lm->next_hop_heap, mp_old->unnormalized_next_hops.heap_offset);
- n_nhs = mp_old->unnormalized_next_hops.count;
-
- /* Linear search: ok since n_next_hops is small. */
- for (i_nh = 0; i_nh < n_nhs; i_nh++)
- if (nhs[i_nh].next_hop_adj_index == next_hop_adj_index)
- break;
-
- /* Given next hop not found. */
- if (i_nh >= n_nhs && is_del)
- return 0;
- }
-
- hash_nhs = lm->next_hop_hash_lookup_key;
- if (hash_nhs)
- _vec_len (hash_nhs) = 0;
-
- if (is_del)
- {
- if (n_nhs > 1)
- {
- /* Prepare lookup key for multipath with target next hop deleted. */
- if (i_nh > 0)
- vec_add (hash_nhs, nhs + 0, i_nh);
- if (i_nh + 1 < n_nhs)
- vec_add (hash_nhs, nhs + i_nh + 1, n_nhs - (i_nh + 1));
- }
- }
- else /* it's an add. */
- {
- /* If next hop is already there with the same weight, we have nothing to do. */
- if (i_nh < n_nhs && nhs[i_nh].weight == next_hop_weight)
- {
- new_mp_adj_index[0] = ~0;
- goto done;
- }
-
- /* Copy old next hops to lookup key vector. */
- if (n_nhs > 0)
- vec_add (hash_nhs, nhs, n_nhs);
-
- if (i_nh < n_nhs)
- {
- /* Change weight of existing next hop. */
- nh = vec_elt_at_index (hash_nhs, i_nh);
- }
- else
- {
- /* Add a new next hop. */
- vec_add2 (hash_nhs, nh, 1);
- nh->next_hop_adj_index = next_hop_adj_index;
- }
-
- /* Set weight for added or old next hop. */
- nh->weight = next_hop_weight;
- }
-
- if (vec_len (hash_nhs) > 0)
- {
- u32 tmp = ip_multipath_adjacency_get (lm, hash_nhs,
- /* create_if_non_existent */ 1);
- if (tmp != ~0)
- mp_new = vec_elt_at_index (lm->multipath_adjacencies, tmp);
-
- /* Fetch again since pool may have moved. */
- if (mp_old)
- mp_old = vec_elt_at_index (lm->multipath_adjacencies, old_mp_adj_index);
- }
-
- new_mp_adj_index[0] = mp_new ? mp_new - lm->multipath_adjacencies : ~0;
-
- if (mp_new != mp_old)
- {
- if (mp_old)
- {
- ASSERT (mp_old->reference_count > 0);
- mp_old->reference_count -= 1;
- }
- if (mp_new)
- mp_new->reference_count += 1;
- }
-
- if (mp_old && mp_old->reference_count == 0)
- ip_multipath_adjacency_free (lm, mp_old);
-
- done:
- /* Save key vector next call. */
- lm->next_hop_hash_lookup_key = hash_nhs;
-
- return 1;
-}
-
-static void
-ip_multipath_del_adjacency (ip_lookup_main_t * lm, u32 del_adj_index)
-{
- ip_adjacency_t * adj = ip_get_adjacency (lm, del_adj_index);
- ip_multipath_adjacency_t * madj, * new_madj;
- ip_multipath_next_hop_t * nhs, * hash_nhs;
- u32 i, n_nhs, madj_index, new_madj_index;
-
- if (adj->heap_handle >= vec_len (lm->multipath_adjacencies))
- return;
-
- vec_validate (lm->adjacency_remap_table, vec_len (lm->adjacency_heap) - 1);
-
- for (madj_index = 0; madj_index < vec_len (lm->multipath_adjacencies); madj_index++)
- {
- madj = vec_elt_at_index (lm->multipath_adjacencies, madj_index);
- if (madj->n_adj_in_block == 0)
- continue;
-
- nhs = heap_elt_at_index (lm->next_hop_heap, madj->unnormalized_next_hops.heap_offset);
- n_nhs = madj->unnormalized_next_hops.count;
- for (i = 0; i < n_nhs; i++)
- if (nhs[i].next_hop_adj_index == del_adj_index)
- break;
-
- /* del_adj_index not found in unnormalized_next_hops? We're done. */
- if (i >= n_nhs)
- continue;
-
- new_madj = 0;
- if (n_nhs > 1)
- {
- hash_nhs = lm->next_hop_hash_lookup_key;
- if (hash_nhs)
- _vec_len (hash_nhs) = 0;
- if (i > 0)
- vec_add (hash_nhs, nhs + 0, i);
- if (i + 1 < n_nhs)
- vec_add (hash_nhs, nhs + i + 1, n_nhs - (i + 1));
-
- new_madj_index = ip_multipath_adjacency_get (lm, hash_nhs, /* create_if_non_existent */ 1);
-
- lm->next_hop_hash_lookup_key = hash_nhs;
-
- if (new_madj_index == madj_index)
- continue;
-
- new_madj = vec_elt_at_index (lm->multipath_adjacencies, new_madj_index);
- }
-
- lm->adjacency_remap_table[madj->adj_index] = new_madj ? 1 + new_madj->adj_index : ~0;
- lm->n_adjacency_remaps += 1;
- ip_multipath_adjacency_free (lm, madj);
- }
-}
-
-void
-ip_multipath_adjacency_free (ip_lookup_main_t * lm,
- ip_multipath_adjacency_t * a)
-{
- hash_unset (lm->multipath_adjacency_by_next_hops,
- ip_next_hop_hash_key_from_handle (a->normalized_next_hops.heap_handle));
- heap_dealloc (lm->next_hop_heap, a->normalized_next_hops.heap_handle);
- heap_dealloc (lm->next_hop_heap, a->unnormalized_next_hops.heap_handle);
-
- ip_del_adjacency2 (lm, a->adj_index, a->reference_count == 0);
- memset (a, 0, sizeof (a[0]));
-}
-
-always_inline ip_multipath_next_hop_t *
-ip_next_hop_hash_key_get_next_hops (ip_lookup_main_t * lm, uword k,
- uword * n_next_hops)
-{
- ip_multipath_next_hop_t * nhs;
- uword n_nhs;
- if (ip_next_hop_hash_key_is_heap_handle (k))
- {
- uword handle = ip_next_hop_hash_key_get_heap_handle (k);
- nhs = heap_elt_with_handle (lm->next_hop_heap, handle);
- n_nhs = heap_len (lm->next_hop_heap, handle);
- }
- else
- {
- nhs = uword_to_pointer (k, ip_multipath_next_hop_t *);
- n_nhs = vec_len (nhs);
- }
- *n_next_hops = n_nhs;
- return nhs;
-}
-
-static uword
-ip_next_hop_hash_key_sum (hash_t * h, uword key0)
-{
- ip_lookup_main_t * lm = uword_to_pointer (h->user, ip_lookup_main_t *);
- ip_multipath_next_hop_t * k0;
- uword n0;
-
- k0 = ip_next_hop_hash_key_get_next_hops (lm, key0, &n0);
- return hash_memory (k0, n0 * sizeof (k0[0]), /* seed */ n0);
-}
-
-static uword
-ip_next_hop_hash_key_equal (hash_t * h, uword key0, uword key1)
-{
- ip_lookup_main_t * lm = uword_to_pointer (h->user, ip_lookup_main_t *);
- ip_multipath_next_hop_t * k0, * k1;
- uword n0, n1;
-
- k0 = ip_next_hop_hash_key_get_next_hops (lm, key0, &n0);
- k1 = ip_next_hop_hash_key_get_next_hops (lm, key1, &n1);
-
- return n0 == n1 && ! memcmp (k0, k1, n0 * sizeof (k0[0]));
-}