diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
commit | 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch) | |
tree | c05dc0f8e6aa3accc84e3e5cffc933ed94941383 /pbrd/pbr_nht.c | |
parent | Initial commit. (diff) | |
download | frr-upstream.tar.xz frr-upstream.zip |
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pbrd/pbr_nht.c')
-rw-r--r-- | pbrd/pbr_nht.c | 1488 |
1 files changed, 1488 insertions, 0 deletions
diff --git a/pbrd/pbr_nht.c b/pbrd/pbr_nht.c new file mode 100644 index 0000000..cbff483 --- /dev/null +++ b/pbrd/pbr_nht.c @@ -0,0 +1,1488 @@ +/* + * PBR-nht Code + * Copyright (C) 2018 Cumulus Networks, Inc. + * Donald Sharp + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <zebra.h> + +#include <log.h> +#include <nexthop.h> +#include "nexthop_group.h" +#include "nexthop_group_private.h" +#include <hash.h> +#include <jhash.h> +#include <vty.h> +#include <zclient.h> +#include <debug.h> + +#include "pbrd/pbr_nht.h" +#include "pbrd/pbr_map.h" +#include "pbrd/pbr_zebra.h" +#include "pbrd/pbr_memory.h" +#include "pbrd/pbr_debug.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_NHG, "PBR Nexthop Groups"); + +struct hash *pbr_nhg_hash; +static struct hash *pbr_nhrc_hash; +static struct hash *pbr_nhg_allocated_id_hash; + +static uint32_t pbr_nhg_low_table; +static uint32_t pbr_nhg_high_table; +static uint32_t pbr_next_unallocated_table_id; +static uint32_t pbr_nhg_low_rule; +static uint32_t pbr_nhg_high_rule; + +static void pbr_nht_install_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg); +static void +pbr_nht_uninstall_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg, + enum nexthop_types_t nh_type); + +/* + * Nexthop refcount. + */ +struct nhrc { + struct nexthop nexthop; + unsigned int refcount; +}; + +/* Hash functions for pbr_nhrc_hash ---------------------------------------- */ + +static void *pbr_nhrc_hash_alloc(void *p) +{ + struct nhrc *nhrc = XCALLOC(MTYPE_PBR_NHG, sizeof(struct nhrc)); + nhrc->nexthop = *(struct nexthop *)p; + nhrc->nexthop.next = NULL; + nhrc->nexthop.prev = NULL; + return nhrc; +} + +static bool pbr_nhrc_hash_equal(const void *arg1, const void *arg2) +{ + const struct nexthop *nh1, *nh2; + + nh1 = arg1; + nh2 = arg2; + + return nexthop_same(nh1, nh2); +} + +/* ------------------------------------------------------------------------- */ + +static void *pbr_nh_alloc(void *p) +{ + struct pbr_nexthop_cache *new; + struct pbr_nexthop_cache *pnhc = (struct pbr_nexthop_cache *)p; + struct nhrc *nhrc; + + new = XCALLOC(MTYPE_PBR_NHG, sizeof(*new)); + nhrc = hash_get(pbr_nhrc_hash, &pnhc->nexthop, pbr_nhrc_hash_alloc); + new->nexthop = nhrc->nexthop; + + /* Decremented again in pbr_nh_delete */ + ++nhrc->refcount; + + DEBUGD(&pbr_dbg_nht, "%s: Sending nexthop to Zebra", __func__); + + pbr_send_rnh(&new->nexthop, true); + + new->valid = false; + return new; +} + +static void pbr_nh_delete(struct pbr_nexthop_cache **pnhc) +{ + struct nhrc *nhrc; + + nhrc = hash_lookup(pbr_nhrc_hash, &((*pnhc)->nexthop)); + + if (nhrc) + --nhrc->refcount; + if (!nhrc || nhrc->refcount == 0) { + DEBUGD(&pbr_dbg_nht, "%s: Removing nexthop from Zebra", + __func__); + pbr_send_rnh(&((*pnhc)->nexthop), false); + } + if (nhrc && nhrc->refcount == 0) { + hash_release(pbr_nhrc_hash, nhrc); + XFREE(MTYPE_PBR_NHG, nhrc); + } + + XFREE(MTYPE_PBR_NHG, *pnhc); +} + +static void pbr_nh_delete_iterate(struct hash_bucket *b, void *p) +{ + pbr_nh_delete((struct pbr_nexthop_cache **)&b->data); +} + +static uint32_t pbr_nh_hash_key(const void *arg) +{ + uint32_t key; + const struct pbr_nexthop_cache *pbrnc = arg; + + key = nexthop_hash(&pbrnc->nexthop); + + return key; +} + +static bool pbr_nh_hash_equal(const void *arg1, const void *arg2) +{ + const struct pbr_nexthop_cache *pbrnc1 = + (const struct pbr_nexthop_cache *)arg1; + const struct pbr_nexthop_cache *pbrnc2 = + (const struct pbr_nexthop_cache *)arg2; + + if (pbrnc1->nexthop.vrf_id != pbrnc2->nexthop.vrf_id) + return false; + + if (pbrnc1->nexthop.ifindex != pbrnc2->nexthop.ifindex) + return false; + + if (pbrnc1->nexthop.type != pbrnc2->nexthop.type) + return false; + + switch (pbrnc1->nexthop.type) { + case NEXTHOP_TYPE_IFINDEX: + return pbrnc1->nexthop.ifindex == pbrnc2->nexthop.ifindex; + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + return pbrnc1->nexthop.gate.ipv4.s_addr + == pbrnc2->nexthop.gate.ipv4.s_addr; + case NEXTHOP_TYPE_IPV6_IFINDEX: + case NEXTHOP_TYPE_IPV6: + return !memcmp(&pbrnc1->nexthop.gate.ipv6, + &pbrnc2->nexthop.gate.ipv6, 16); + case NEXTHOP_TYPE_BLACKHOLE: + return pbrnc1->nexthop.bh_type == pbrnc2->nexthop.bh_type; + } + + /* + * We should not get here + */ + return false; +} + +static void pbr_nhgc_delete(struct pbr_nexthop_group_cache *p) +{ + hash_iterate(p->nhh, pbr_nh_delete_iterate, NULL); + hash_free(p->nhh); + XFREE(MTYPE_PBR_NHG, p); +} + +static void *pbr_nhgc_alloc(void *p) +{ + struct pbr_nexthop_group_cache *new; + struct pbr_nexthop_group_cache *pnhgc = + (struct pbr_nexthop_group_cache *)p; + + new = XCALLOC(MTYPE_PBR_NHG, sizeof(*new)); + + strlcpy(new->name, pnhgc->name, sizeof(pnhgc->name)); + pbr_nht_reserve_next_table_id(new); + + DEBUGD(&pbr_dbg_nht, "%s: NHT: %s assigned Table ID: %u", __func__, + new->name, new->table_id); + + new->nhh = hash_create_size(8, pbr_nh_hash_key, pbr_nh_hash_equal, + "PBR NH Cache Hash"); + return new; +} + + +void pbr_nhgroup_add_cb(const char *name) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct nexthop_group_cmd *nhgc; + + nhgc = nhgc_find(name); + + if (!nhgc) { + DEBUGD(&pbr_dbg_nht, "%s: Could not find nhgc with name: %s", + __func__, name); + return; + } + + pnhgc = pbr_nht_add_group(name); + + if (!pnhgc) + return; + + DEBUGD(&pbr_dbg_nht, "%s: Added nexthop-group %s", __func__, name); + + pbr_map_check_nh_group_change(name); +} + +void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ + char debugstr[256]; + struct pbr_nexthop_group_cache pnhgc_find = {}; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_cache pnhc_find = {}; + struct pbr_nexthop_cache *pnhc; + + /* find pnhgc by name */ + strlcpy(pnhgc_find.name, nhgc->name, sizeof(pnhgc_find.name)); + pnhgc = hash_lookup(pbr_nhg_hash, &pnhgc_find); + + if (!pnhgc) { + /* Check if configured table range is exhausted */ + if (!pbr_nht_has_unallocated_table()) { + zlog_warn( + "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", + __func__, nhgc->name); + return; + } + + /* No nhgc but range not exhausted? Then alloc it */ + pnhgc = hash_get(pbr_nhg_hash, &pnhgc_find, pbr_nhgc_alloc); + } + + /* create & insert new pnhc into pnhgc->nhh */ + pnhc_find.nexthop = *nhop; + pnhc = hash_get(pnhgc->nhh, &pnhc_find, pbr_nh_alloc); + + /* set parent pnhgc */ + pnhc->parent = pnhgc; + + if (DEBUG_MODE_CHECK(&pbr_dbg_nht, DEBUG_MODE_ALL)) { + nexthop2str(nhop, debugstr, sizeof(debugstr)); + DEBUGD(&pbr_dbg_nht, "%s: Added %s to nexthop-group %s", + __func__, debugstr, nhgc->name); + } + + pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); + pbr_map_check_nh_group_change(nhgc->name); + + if (nhop->type == NEXTHOP_TYPE_IFINDEX + || (nhop->type == NEXTHOP_TYPE_IPV6_IFINDEX + && IN6_IS_ADDR_LINKLOCAL(&nhop->gate.ipv6))) { + struct interface *ifp; + + ifp = if_lookup_by_index(nhop->ifindex, nhop->vrf_id); + if (ifp) + pbr_nht_nexthop_interface_update(ifp); + } +} + +void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhgc, + const struct nexthop *nhop) +{ + char debugstr[256]; + struct pbr_nexthop_group_cache pnhgc_find = {}; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_cache pnhc_find = {}; + struct pbr_nexthop_cache *pnhc; + enum nexthop_types_t nh_type = nhop->type; + + /* find pnhgc by name */ + strlcpy(pnhgc_find.name, nhgc->name, sizeof(pnhgc_find.name)); + pnhgc = hash_lookup(pbr_nhg_hash, &pnhgc_find); + + /* + * Ignore deletions of nhg we did not / could not allocate nhgc for + * Occurs when PBR table range is full but new nhg keep coming in + */ + if (!pnhgc) + return; + + /* delete pnhc from pnhgc->nhh */ + pnhc_find.nexthop = *nhop; + pnhc = hash_release(pnhgc->nhh, &pnhc_find); + + /* delete pnhc */ + pbr_nh_delete(&pnhc); + + if (DEBUG_MODE_CHECK(&pbr_dbg_nht, DEBUG_MODE_ALL)) { + nexthop2str(nhop, debugstr, sizeof(debugstr)); + DEBUGD(&pbr_dbg_nht, "%s: Removed %s from nexthop-group %s", + __func__, debugstr, nhgc->name); + } + + if (pnhgc->nhh->count) + pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); + else + pbr_nht_uninstall_nexthop_group(pnhgc, nhgc->nhg, nh_type); + + pbr_map_check_nh_group_change(nhgc->name); +} + +void pbr_nhgroup_delete_cb(const char *name) +{ + DEBUGD(&pbr_dbg_nht, "%s: Removed nexthop-group %s", __func__, name); + + /* delete group from all pbrms's */ + pbr_nht_delete_group(name); + + pbr_map_check_nh_group_change(name); +} + +static void +pbr_nht_find_nhg_from_table_update(struct pbr_nexthop_group_cache *pnhgc, + uint32_t table_id, bool installed) +{ + if (pnhgc->table_id == table_id) { + DEBUGD(&pbr_dbg_nht, "%s: %s: Table ID (%u) matches %s", + __func__, (installed ? "install" : "remove"), table_id, + pnhgc->name); + + pnhgc->installed = installed; + pnhgc->valid = installed; + pbr_map_schedule_policy_from_nhg(pnhgc->name, pnhgc->installed); + } +} + +static void pbr_nht_find_nhg_from_table_install(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = + (struct pbr_nexthop_group_cache *)b->data; + uint32_t table_id = *(uint32_t *)data; + + pbr_nht_find_nhg_from_table_update(pnhgc, table_id, true); +} + +void pbr_nht_route_installed_for_table(uint32_t table_id) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_find_nhg_from_table_install, + &table_id); +} + +static void pbr_nht_find_nhg_from_table_remove(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = + (struct pbr_nexthop_group_cache *)b->data; + uint32_t table_id = *(uint32_t *)data; + + pbr_nht_find_nhg_from_table_update(pnhgc, table_id, false); +} + +void pbr_nht_route_removed_for_table(uint32_t table_id) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_find_nhg_from_table_remove, + &table_id); +} + +/* + * Loop through all nexthops in a nexthop group to check that they are all the + * same. If they are not all the same, log this peculiarity. + * + * nhg + * The nexthop group to check + * + * Returns: + * - AFI of last nexthop in the group + * - AFI_MAX on error + */ +static afi_t pbr_nht_which_afi(struct nexthop_group nhg, + enum nexthop_types_t nh_type) +{ + struct nexthop *nexthop; + afi_t install_afi = AFI_MAX; + bool v6, v4, bh; + + if (nh_type) { + switch (nh_type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + return AFI_IP; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + return AFI_IP6; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + return AFI_MAX; + } + } + + v6 = v4 = bh = false; + + for (ALL_NEXTHOPS(nhg, nexthop)) { + nh_type = nexthop->type; + + switch (nh_type) { + case NEXTHOP_TYPE_IFINDEX: + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + v6 = true; + install_afi = AFI_IP; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + v4 = true; + install_afi = AFI_IP6; + break; + case NEXTHOP_TYPE_BLACKHOLE: + bh = true; + break; + } + } + + /* Interface and/or blackhole nexthops only. */ + if (!v4 && !v6) + install_afi = AFI_MAX; + + if (!bh && v6 && v4) + DEBUGD(&pbr_dbg_nht, + "%s: Saw both V6 and V4 nexthops...using %s", __func__, + afi2str(install_afi)); + if (bh && (v6 || v4)) + DEBUGD(&pbr_dbg_nht, + "%s: Saw blackhole nexthop(s) with %s%s%s nexthop(s), using AFI_MAX.", + __func__, v4 ? "v4" : "", (v4 && v6) ? " and " : "", + v6 ? "v6" : ""); + + return install_afi; +} + +static void pbr_nht_install_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg) +{ + afi_t install_afi; + enum nexthop_types_t nh_type = 0; + + install_afi = pbr_nht_which_afi(nhg, nh_type); + + route_add(pnhgc, nhg, install_afi); +} + +static void +pbr_nht_uninstall_nexthop_group(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg, + enum nexthop_types_t nh_type) +{ + afi_t install_afi; + + install_afi = pbr_nht_which_afi(nhg, nh_type); + + pnhgc->installed = false; + pnhgc->valid = false; + route_delete(pnhgc, install_afi); +} + +void pbr_nht_change_group(const char *name) +{ + struct nexthop_group_cmd *nhgc; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache find; + struct nexthop *nhop; + + nhgc = nhgc_find(name); + if (!nhgc) + return; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", name); + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + if (!pnhgc) { + DEBUGD(&pbr_dbg_nht, + "%s: Could not find nexthop-group cache w/ name '%s'", + __func__, name); + return; + } + + for (ALL_NEXTHOPS(nhgc->nhg, nhop)) { + struct pbr_nexthop_cache lookup; + struct pbr_nexthop_cache *pnhc; + + lookup.nexthop = *nhop; + pnhc = hash_lookup(pnhgc->nhh, &lookup); + if (!pnhc) { + pnhc = hash_get(pnhgc->nhh, &lookup, pbr_nh_alloc); + pnhc->parent = pnhgc; + } + } + pbr_nht_install_nexthop_group(pnhgc, nhgc->nhg); +} + +char *pbr_nht_nexthop_make_name(char *name, size_t l, + uint32_t seqno, char *buffer) +{ + snprintf(buffer, l, "%s%u", name, seqno); + return buffer; +} + +/* Set data derived from nhg in pbrms */ +void pbr_nht_set_seq_nhg_data(struct pbr_map_sequence *pbrms, + const struct nexthop_group_cmd *nhgc) +{ + const struct nexthop_group *nhg; + + if (!nhgc) + return; + + nhg = &nhgc->nhg; + if (!nhg->nexthop) + return; + + switch (nhg->nexthop->type) { + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + pbrms->family = AF_INET6; + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + pbrms->family = AF_INET; + default: + break; + } +} + +/* Configure a routemap sequence to use a given nexthop group */ +void pbr_nht_set_seq_nhg(struct pbr_map_sequence *pbrms, const char *name) +{ + struct nexthop_group_cmd *nhgc; + + if (!name) + return; + + pbrms->nhgrp_name = XSTRDUP(MTYPE_TMP, name); + + nhgc = nhgc_find(name); + if (!nhgc) + return; + + pbr_nht_set_seq_nhg_data(pbrms, nhgc); +} + +void pbr_nht_add_individual_nexthop(struct pbr_map_sequence *pbrms, + const struct nexthop *nhop) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_cache *pnhc; + struct pbr_nexthop_cache lookup; + struct nexthop *nh; + char buf[PBR_NHC_NAMELEN]; + + pbrms->nhg = nexthop_group_new(); + pbrms->internal_nhg_name = XSTRDUP( + MTYPE_TMP, + pbr_nht_nexthop_make_name(pbrms->parent->name, PBR_NHC_NAMELEN, + pbrms->seqno, buf)); + + nh = nexthop_new(); + memcpy(nh, nhop, sizeof(*nh)); + + nexthop_group_add_sorted(pbrms->nhg, nh); + + memset(&find, 0, sizeof(find)); + pbr_nht_nexthop_make_name(pbrms->parent->name, PBR_NHC_NAMELEN, + pbrms->seqno, find.name); + + if (!pbr_nht_has_unallocated_table()) { + zlog_warn( + "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", + __func__, find.name); + return; + } + + if (!pbrms->internal_nhg_name) + pbrms->internal_nhg_name = XSTRDUP(MTYPE_TMP, find.name); + + pnhgc = hash_get(pbr_nhg_hash, &find, pbr_nhgc_alloc); + + lookup.nexthop = *pbrms->nhg->nexthop; + pnhc = hash_get(pnhgc->nhh, &lookup, pbr_nh_alloc); + pnhc->parent = pnhgc; + if (nhop->vrf_id != VRF_DEFAULT) { + struct vrf *vrf = vrf_lookup_by_id(nhop->vrf_id); + + if (vrf) + strlcpy(pnhc->vrf_name, vrf->name, + sizeof(pnhc->vrf_name)); + } + + if (nhop->ifindex != 0) { + struct interface *ifp = + if_lookup_by_index(nhop->ifindex, nhop->vrf_id); + + if (ifp) + strlcpy(pnhc->intf_name, ifp->name, + sizeof(pnhc->intf_name)); + } + pbr_nht_install_nexthop_group(pnhgc, *pbrms->nhg); +} + +static void pbr_nht_release_individual_nexthop(struct pbr_map_sequence *pbrms) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_cache *pnhc; + struct pbr_nexthop_cache lup; + struct nexthop *nh; + enum nexthop_types_t nh_type = 0; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", pbrms->internal_nhg_name); + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + nh = pbrms->nhg->nexthop; + nh_type = nh->type; + lup.nexthop = *nh; + pnhc = hash_lookup(pnhgc->nhh, &lup); + pnhc->parent = NULL; + hash_release(pnhgc->nhh, pnhc); + pbr_nh_delete(&pnhc); + pbr_nht_uninstall_nexthop_group(pnhgc, *pbrms->nhg, nh_type); + + hash_release(pbr_nhg_hash, pnhgc); + pbr_nhgc_delete(pnhgc); + + nexthop_group_delete(&pbrms->nhg); + XFREE(MTYPE_TMP, pbrms->internal_nhg_name); +} + +void pbr_nht_delete_individual_nexthop(struct pbr_map_sequence *pbrms) +{ + pbr_map_delete_nexthops(pbrms); + + pbr_nht_release_individual_nexthop(pbrms); +} + +struct pbr_nexthop_group_cache *pbr_nht_add_group(const char *name) +{ + struct nexthop *nhop; + struct nexthop_group_cmd *nhgc; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache lookup; + + if (!pbr_nht_has_unallocated_table()) { + zlog_warn( + "%s: Exhausted all table identifiers; cannot create nexthop-group cache for nexthop-group '%s'", + __func__, name); + return NULL; + } + + nhgc = nhgc_find(name); + + if (!nhgc) { + DEBUGD(&pbr_dbg_nht, "%s: Could not find nhgc with name: %s", + __func__, name); + return NULL; + } + + snprintf(lookup.name, sizeof(lookup.name), "%s", name); + pnhgc = hash_get(pbr_nhg_hash, &lookup, pbr_nhgc_alloc); + DEBUGD(&pbr_dbg_nht, "%s: Retrieved NHGC @ %p", __func__, pnhgc); + + for (ALL_NEXTHOPS(nhgc->nhg, nhop)) { + struct pbr_nexthop_cache lookupc; + struct pbr_nexthop_cache *pnhc; + + lookupc.nexthop = *nhop; + pnhc = hash_lookup(pnhgc->nhh, &lookupc); + if (!pnhc) { + pnhc = hash_get(pnhgc->nhh, &lookupc, pbr_nh_alloc); + pnhc->parent = pnhgc; + } + } + + return pnhgc; +} + +void pbr_nht_delete_group(const char *name) +{ + struct pbr_map_sequence *pbrms; + struct listnode *snode; + struct pbr_map *pbrm; + struct pbr_nexthop_group_cache pnhgc_find; + struct pbr_nexthop_group_cache *pnhgc; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, snode, pbrms)) { + if (pbrms->nhgrp_name + && strmatch(pbrms->nhgrp_name, name)) { + pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; + pbrms->nhg = NULL; + pbrms->internal_nhg_name = NULL; + pbrm->valid = false; + } + } + } + + strlcpy(pnhgc_find.name, name, sizeof(pnhgc_find.name)); + pnhgc = hash_release(pbr_nhg_hash, &pnhgc_find); + + /* + * Ignore deletions of nh we did not / could not allocate nhgc for + * Occurs when PBR table range is full but new nhg keep coming in + */ + if (!pnhgc) + return; + + /* Remove and recalculate the next table id */ + hash_release(pbr_nhg_allocated_id_hash, pnhgc); + pbr_nht_update_next_unallocated_table_id(); + + pbr_nhgc_delete(pnhgc); +} + +bool pbr_nht_nexthop_valid(struct nexthop_group *nhg) +{ + DEBUGD(&pbr_dbg_nht, "%s: %p", __func__, nhg); + return true; +} + +bool pbr_nht_nexthop_group_valid(const char *name) +{ + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_group_cache lookup; + + DEBUGD(&pbr_dbg_nht, "%s: %s", __func__, name); + + snprintf(lookup.name, sizeof(lookup.name), "%s", name); + pnhgc = hash_get(pbr_nhg_hash, &lookup, NULL); + if (!pnhgc) + return false; + DEBUGD(&pbr_dbg_nht, "%s: %d %d", __func__, pnhgc->valid, + pnhgc->installed); + if (pnhgc->valid && pnhgc->installed) + return true; + + return false; +} + +struct pbr_nht_individual { + struct zapi_route *nhr; + struct interface *ifp; + struct pbr_vrf *pbr_vrf; + struct pbr_nexthop_cache *pnhc; + vrf_id_t old_vrf_id; + + bool valid; + + bool nhr_matched; +}; + +static bool +pbr_nht_individual_nexthop_gw_update(struct pbr_nexthop_cache *pnhc, + struct pbr_nht_individual *pnhi) +{ + bool is_valid = pnhc->valid; + + /* + * If we have an interface down event, let's note that + * it is happening and find all the nexthops that depend + * on that interface. As that if we have an interface + * flapping fast enough it means that zebra might turn + * those nexthop tracking events into a no-update + * So let's search and do the right thing on the + * interface event. + */ + if (!pnhi->nhr) { + switch (pnhc->nexthop.type) { + case NEXTHOP_TYPE_BLACKHOLE: + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + goto done; + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (pnhc->nexthop.ifindex == pnhi->ifp->ifindex) + is_valid = if_is_up(pnhi->ifp); + goto done; + } + + goto done; + } + + switch (pnhi->nhr->prefix.family) { + case AF_INET: + if (pnhc->nexthop.gate.ipv4.s_addr + != pnhi->nhr->prefix.u.prefix4.s_addr) + goto done; /* Unrelated change */ + break; + case AF_INET6: + if (memcmp(&pnhc->nexthop.gate.ipv6, + &pnhi->nhr->prefix.u.prefix6, 16) + != 0) + goto done; /* Unrelated change */ + break; + } + + pnhi->nhr_matched = true; + if (!pnhi->nhr->nexthop_num) { + is_valid = false; + goto done; + } + + if (pnhc->nexthop.type == NEXTHOP_TYPE_IPV4_IFINDEX + || pnhc->nexthop.type == NEXTHOP_TYPE_IPV6_IFINDEX) { + + /* GATEWAY_IFINDEX type shouldn't resolve to group */ + if (pnhi->nhr->nexthop_num > 1) { + is_valid = false; + goto done; + } + + /* If whatever we resolved to wasn't on the interface we + * specified. (i.e. not a connected route), its invalid. + */ + if (pnhi->nhr->nexthops[0].ifindex != pnhc->nexthop.ifindex) { + is_valid = false; + goto done; + } + } + + is_valid = true; + +done: + pnhc->valid = is_valid; + + return pnhc->valid; +} + +static bool +pbr_nht_individual_nexthop_interface_update(struct pbr_nexthop_cache *pnhc, + struct pbr_nht_individual *pnhi) +{ + bool is_valid = pnhc->valid; + + if (!pnhi->ifp) /* It doesn't care about non-interface updates */ + goto done; + + if (pnhc->nexthop.ifindex + != pnhi->ifp->ifindex) /* Un-related interface */ + goto done; + + pnhi->nhr_matched = true; + is_valid = !!if_is_up(pnhi->ifp); + +done: + pnhc->valid = is_valid; + + return pnhc->valid; +} + +/* Given this update either from interface or nexthop tracking, re-validate this + * nexthop. + * + * If the update is un-related, the subroutines shoud just return their cached + * valid state. + */ +static void pbr_nht_individual_nexthop_update(struct pbr_nexthop_cache *pnhc, + struct pbr_nht_individual *pnhi) +{ + assert(pnhi->nhr || pnhi->ifp); /* Either nexthop or interface update */ + + switch (pnhc->nexthop.type) { + case NEXTHOP_TYPE_IFINDEX: + pbr_nht_individual_nexthop_interface_update(pnhc, pnhi); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + if (IN6_IS_ADDR_LINKLOCAL(&pnhc->nexthop.gate.ipv6)) { + pbr_nht_individual_nexthop_interface_update(pnhc, pnhi); + break; + } + /* Intentional fall thru */ + case NEXTHOP_TYPE_IPV4_IFINDEX: + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV6: + pbr_nht_individual_nexthop_gw_update(pnhc, pnhi); + break; + case NEXTHOP_TYPE_BLACKHOLE: + pnhc->valid = true; + break; + } +} + +static void pbr_nht_individual_nexthop_update_lookup(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + bool old_valid; + + old_valid = pnhc->valid; + + pbr_nht_individual_nexthop_update(pnhc, pnhi); + + DEBUGD(&pbr_dbg_nht, " Found %pFX: old: %d new: %d", + &pnhi->nhr->prefix, old_valid, pnhc->valid); + + if (pnhc->valid) + pnhi->valid = true; +} + +static void pbr_nexthop_group_cache_iterate_to_group(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct nexthop_group *nhg = data; + struct nexthop *nh = NULL; + + copy_nexthops(&nh, &pnhc->nexthop, NULL); + + _nexthop_add(&nhg->nexthop, nh); +} + +static void +pbr_nexthop_group_cache_to_nexthop_group(struct nexthop_group *nhg, + struct pbr_nexthop_group_cache *pnhgc) +{ + hash_iterate(pnhgc->nhh, pbr_nexthop_group_cache_iterate_to_group, nhg); +} + +static void pbr_nht_nexthop_update_lookup(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_individual pnhi = {}; + struct nexthop_group nhg = {}; + bool old_valid; + + old_valid = pnhgc->valid; + + pnhi.nhr = (struct zapi_route *)data; + pnhi.valid = false; + pnhi.nhr_matched = false; + hash_iterate(pnhgc->nhh, pbr_nht_individual_nexthop_update_lookup, + &pnhi); + + if (!pnhi.nhr_matched) + return; + + /* + * If any of the specified nexthops are valid we are valid + */ + pnhgc->valid = !!pnhi.valid; + + pbr_nexthop_group_cache_to_nexthop_group(&nhg, pnhgc); + + if (pnhgc->valid) + pbr_nht_install_nexthop_group(pnhgc, nhg); + else + pbr_nht_uninstall_nexthop_group(pnhgc, nhg, 0); + + /* Don't need copied nexthops anymore */ + nexthops_free(nhg.nexthop); + + if (old_valid != pnhgc->valid) + pbr_map_check_nh_group_change(pnhgc->name); +} + +void pbr_nht_nexthop_update(struct zapi_route *nhr) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_update_lookup, nhr); +} + +struct nhrc_vrf_info { + struct pbr_vrf *pbr_vrf; + uint32_t old_vrf_id; + struct nhrc *nhrc; +}; + +static int pbr_nht_nhrc_vrf_change(struct hash_bucket *b, void *data) +{ + struct nhrc *nhrc = b->data; + struct nhrc_vrf_info *nhrcvi = data; + + if (nhrc->nexthop.vrf_id == nhrcvi->old_vrf_id) { + nhrcvi->nhrc = nhrc; + return HASHWALK_ABORT; + } + + return HASHWALK_CONTINUE; +} + +static int pbr_nht_individual_nexthop_vrf_handle(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + + if (pnhc->looked_at == true) + return HASHWALK_CONTINUE; + + if (pnhc->nexthop.vrf_id == VRF_DEFAULT) + return HASHWALK_CONTINUE; + + if (strncmp(pnhc->vrf_name, pbr_vrf_name(pnhi->pbr_vrf), + sizeof(pnhc->vrf_name)) + == 0) { + pnhi->pnhc = pnhc; + + if (pnhc->nexthop.vrf_id != pbr_vrf_id(pnhi->pbr_vrf)) { + struct nhrc_vrf_info nhrcvi; + + memset(&nhrcvi, 0, sizeof(nhrcvi)); + nhrcvi.pbr_vrf = pnhi->pbr_vrf; + nhrcvi.old_vrf_id = pnhc->nexthop.vrf_id; + + pnhi->nhr_matched = true; + pnhi->old_vrf_id = pnhc->nexthop.vrf_id; + + do { + nhrcvi.nhrc = NULL; + hash_walk(pbr_nhrc_hash, + pbr_nht_nhrc_vrf_change, &nhrcvi); + if (nhrcvi.nhrc) { + hash_release(pbr_nhrc_hash, + nhrcvi.nhrc); + nhrcvi.nhrc->nexthop.vrf_id = + pbr_vrf_id(pnhi->pbr_vrf); + (void)hash_get(pbr_nhrc_hash, + nhrcvi.nhrc, + hash_alloc_intern); + pbr_send_rnh(&nhrcvi.nhrc->nexthop, true); + } + } while (nhrcvi.nhrc); + } + + pnhc->looked_at = true; + return HASHWALK_ABORT; + } + + return HASHWALK_CONTINUE; +} + +static void pbr_nht_clear_looked_at(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + + pnhc->looked_at = false; +} + +static void pbr_nht_nexthop_vrf_handle(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_vrf *pbr_vrf = data; + struct pbr_nht_individual pnhi = {}; + + hash_iterate(pnhgc->nhh, pbr_nht_clear_looked_at, NULL); + memset(&pnhi, 0, sizeof(pnhi)); + pnhi.pbr_vrf = pbr_vrf; + do { + struct pbr_nexthop_cache *pnhc; + + pnhi.pnhc = NULL; + hash_walk(pnhgc->nhh, pbr_nht_individual_nexthop_vrf_handle, + &pnhi); + + if (!pnhi.pnhc) + continue; + + pnhc = pnhi.pnhc; + pnhc->nexthop.vrf_id = pnhi.old_vrf_id; + pnhi.pnhc = hash_release(pnhgc->nhh, pnhi.pnhc); + if (pnhi.pnhc) { + pnhi.pnhc->nexthop.vrf_id = pbr_vrf_id(pbr_vrf); + + (void)hash_get(pnhgc->nhh, pnhi.pnhc, + hash_alloc_intern); + } else + pnhc->nexthop.vrf_id = pbr_vrf_id(pbr_vrf); + + pbr_map_check_vrf_nh_group_change(pnhgc->name, pbr_vrf, + pnhi.old_vrf_id); + } while (pnhi.pnhc); +} + +void pbr_nht_vrf_update(struct pbr_vrf *pbr_vrf) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_vrf_handle, pbr_vrf); +} + +static void pbr_nht_individual_nexthop_interface_handle(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + + if (pnhc->nexthop.ifindex == 0) + return; + + if ((strncmp(pnhc->intf_name, pnhi->ifp->name, sizeof(pnhc->intf_name)) + == 0) + && pnhc->nexthop.ifindex != pnhi->ifp->ifindex) + pnhi->pnhc = pnhc; +} + +static void pbr_nht_nexthop_interface_handle(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct interface *ifp = data; + struct pbr_nht_individual pnhi = {}; + struct nhrc *nhrc; + uint32_t old_ifindex; + + do { + memset(&pnhi, 0, sizeof(pnhi)); + pnhi.ifp = ifp; + hash_iterate(pnhgc->nhh, + pbr_nht_individual_nexthop_interface_handle, + &pnhi); + + if (!pnhi.pnhc) + continue; + + pnhi.pnhc = hash_release(pnhgc->nhh, pnhi.pnhc); + old_ifindex = pnhi.pnhc->nexthop.ifindex; + + nhrc = hash_lookup(pbr_nhrc_hash, &pnhi.pnhc->nexthop); + if (nhrc) { + hash_release(pbr_nhrc_hash, nhrc); + nhrc->nexthop.ifindex = ifp->ifindex; + (void)hash_get(pbr_nhrc_hash, nhrc, hash_alloc_intern); + } + pnhi.pnhc->nexthop.ifindex = ifp->ifindex; + + (void)hash_get(pnhgc->nhh, pnhi.pnhc, hash_alloc_intern); + + pbr_map_check_interface_nh_group_change(pnhgc->name, ifp, + old_ifindex); + } while (pnhi.pnhc); +} + +void pbr_nht_interface_update(struct interface *ifp) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_interface_handle, ifp); +} + +static void +pbr_nht_individual_nexthop_interface_update_lookup(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct pbr_nht_individual *pnhi = data; + bool old_valid; + + old_valid = pnhc->valid; + + pbr_nht_individual_nexthop_update(pnhc, pnhi); + + DEBUGD(&pbr_dbg_nht, " Found %s: old: %d new: %d", pnhi->ifp->name, + old_valid, pnhc->valid); + + if (pnhc->valid) + pnhi->valid = true; +} + +static void pbr_nht_nexthop_interface_update_lookup(struct hash_bucket *b, + void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_individual pnhi = {}; + struct nexthop_group nhg = {}; + bool old_valid; + + old_valid = pnhgc->valid; + + pnhi.ifp = data; + pnhi.valid = false; + hash_iterate(pnhgc->nhh, + pbr_nht_individual_nexthop_interface_update_lookup, &pnhi); + + /* + * If any of the specified nexthops are valid we are valid + */ + pnhgc->valid = pnhi.valid; + + pbr_nexthop_group_cache_to_nexthop_group(&nhg, pnhgc); + + if (pnhgc->valid) + pbr_nht_install_nexthop_group(pnhgc, nhg); + else + pbr_nht_uninstall_nexthop_group(pnhgc, nhg, 0); + + nexthops_free(nhg.nexthop); + + if (old_valid != pnhgc->valid) + pbr_map_check_nh_group_change(pnhgc->name); +} + +void pbr_nht_nexthop_interface_update(struct interface *ifp) +{ + hash_iterate(pbr_nhg_hash, pbr_nht_nexthop_interface_update_lookup, + ifp); +} + +static bool pbr_nhg_allocated_id_hash_equal(const void *arg1, const void *arg2) +{ + const struct pbr_nexthop_group_cache *left, *right; + + left = (const struct pbr_nexthop_group_cache *)arg1; + right = (const struct pbr_nexthop_group_cache *)arg2; + + return left->table_id == right->table_id; +} + +static uint32_t pbr_nhg_allocated_id_hash_key(const void *arg) +{ + const struct pbr_nexthop_group_cache *nhgc = arg; + + /* table_id makes elements in this hash unique */ + return nhgc->table_id; +} + +static uint32_t pbr_nhg_hash_key(const void *arg) +{ + const struct pbr_nexthop_group_cache *nhgc = arg; + + return jhash(&nhgc->name, strlen(nhgc->name), 0x52c34a96); +} + +static bool pbr_nhg_hash_equal(const void *arg1, const void *arg2) +{ + const struct pbr_nexthop_group_cache *nhgc1 = + (const struct pbr_nexthop_group_cache *)arg1; + const struct pbr_nexthop_group_cache *nhgc2 = + (const struct pbr_nexthop_group_cache *)arg2; + + return !strcmp(nhgc1->name, nhgc2->name); +} + +uint32_t pbr_nht_find_next_unallocated_table_id(void) +{ + struct pbr_nexthop_group_cache iter; + + /* + * Find the smallest unallocated table id + * This can be non-trivial considering nhg removals / shifting upper & + * lower bounds, so start at the lowest in the range and continue until + * an unallocated space is found + */ + for (iter.table_id = pbr_nhg_low_table; + iter.table_id < pbr_nhg_high_table; ++iter.table_id) + if (!hash_lookup(pbr_nhg_allocated_id_hash, &iter)) + return iter.table_id; + + /* Configured range is full, cannot install anywhere */ + return 0; +} + +bool pbr_nht_has_unallocated_table(void) +{ + return !!pbr_next_unallocated_table_id; +} + +void pbr_nht_update_next_unallocated_table_id(void) +{ + pbr_next_unallocated_table_id = + pbr_nht_find_next_unallocated_table_id(); +} + +uint32_t pbr_nht_reserve_next_table_id(struct pbr_nexthop_group_cache *nhgc) +{ + /* Nothing to reserve if all tables in range already used */ + if (!pbr_next_unallocated_table_id) + return 0; + + /* Reserve this table id */ + nhgc->table_id = pbr_next_unallocated_table_id; + + /* Mark table id as allocated in id-indexed hash */ + (void)hash_get(pbr_nhg_allocated_id_hash, nhgc, hash_alloc_intern); + + /* Pre-compute the next unallocated table id */ + pbr_nht_update_next_unallocated_table_id(); + + /* Present caller with reserved table id */ + return nhgc->table_id; +} + +void pbr_nht_set_tableid_range(uint32_t low, uint32_t high) +{ + pbr_nhg_low_table = low; + pbr_nhg_high_table = high; + + /* Re-compute next unallocated id within new range */ + pbr_nht_update_next_unallocated_table_id(); +} + +void pbr_nht_write_table_range(struct vty *vty) +{ + if (pbr_nhg_low_table != PBR_NHT_DEFAULT_LOW_TABLEID + || pbr_nhg_high_table != PBR_NHT_DEFAULT_HIGH_TABLEID) { + vty_out(vty, "pbr table range %u %u\n", pbr_nhg_low_table, + pbr_nhg_high_table); + } +} + +uint32_t pbr_nht_get_next_rule(uint32_t seqno) +{ + return seqno + pbr_nhg_low_rule - 1; +} +void pbr_nht_set_rule_range(uint32_t low, uint32_t high) +{ + pbr_nhg_low_rule = low; + pbr_nhg_high_rule = high; +} + +void pbr_nht_write_rule_range(struct vty *vty) +{ + if (pbr_nhg_low_rule != PBR_NHT_DEFAULT_LOW_RULE + || pbr_nhg_high_rule != PBR_NHT_DEFAULT_HIGH_RULE) { + vty_out(vty, "pbr rule range %u %u\n", pbr_nhg_low_rule, + pbr_nhg_high_rule); + } +} + +uint32_t pbr_nht_get_table(const char *name) +{ + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_group_cache *pnhgc; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", name); + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + if (!pnhgc) { + DEBUGD(&pbr_dbg_nht, + "%s: Could not find nexthop-group cache w/ name '%s'", + __func__, name); + return 5000; + } + + return pnhgc->table_id; +} + +bool pbr_nht_get_installed(const char *name) +{ + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_group_cache *pnhgc; + + memset(&find, 0, sizeof(find)); + snprintf(find.name, sizeof(find.name), "%s", name); + + pnhgc = hash_lookup(pbr_nhg_hash, &find); + + if (!pnhgc) + return false; + + return pnhgc->installed; +} + +static void pbr_nht_show_nhg_nexthops(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + struct vty *vty = data; + + vty_out(vty, "\tValid: %d ", pnhc->valid); + nexthop_group_write_nexthop(vty, &pnhc->nexthop); +} + +static void pbr_nht_json_nhg_nexthops(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_cache *pnhc = b->data; + json_object *all_hops = data; + json_object *this_hop; + + this_hop = json_object_new_object(); + nexthop_group_json_nexthop(this_hop, &pnhc->nexthop); + json_object_boolean_add(this_hop, "valid", pnhc->valid); + + json_object_array_add(all_hops, this_hop); +} + +struct pbr_nht_show { + struct vty *vty; + json_object *json; + const char *name; +}; + +static void pbr_nht_show_nhg(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_show *pns = data; + struct vty *vty; + + if (pns->name && strcmp(pns->name, pnhgc->name) != 0) + return; + + vty = pns->vty; + vty_out(vty, "Nexthop-Group: %s Table: %u Valid: %d Installed: %d\n", + pnhgc->name, pnhgc->table_id, pnhgc->valid, pnhgc->installed); + + hash_iterate(pnhgc->nhh, pbr_nht_show_nhg_nexthops, vty); +} + +static void pbr_nht_json_nhg(struct hash_bucket *b, void *data) +{ + struct pbr_nexthop_group_cache *pnhgc = b->data; + struct pbr_nht_show *pns = data; + json_object *j, *this_group, *group_hops; + + if (pns->name && strcmp(pns->name, pnhgc->name) != 0) + return; + + j = pns->json; + this_group = json_object_new_object(); + + if (!j || !this_group) + return; + + json_object_int_add(this_group, "id", pnhgc->table_id); + json_object_string_add(this_group, "name", pnhgc->name); + json_object_boolean_add(this_group, "valid", pnhgc->valid); + json_object_boolean_add(this_group, "installed", pnhgc->installed); + + group_hops = json_object_new_array(); + + if (group_hops) { + hash_iterate(pnhgc->nhh, pbr_nht_json_nhg_nexthops, group_hops); + json_object_object_add(this_group, "nexthops", group_hops); + } + + json_object_array_add(j, this_group); +} + +void pbr_nht_show_nexthop_group(struct vty *vty, const char *name) +{ + struct pbr_nht_show pns; + + pns.vty = vty; + pns.name = name; + + hash_iterate(pbr_nhg_hash, pbr_nht_show_nhg, &pns); +} + +void pbr_nht_json_nexthop_group(json_object *j, const char *name) +{ + struct pbr_nht_show pns; + + pns.name = name; + pns.json = j; + + hash_iterate(pbr_nhg_hash, pbr_nht_json_nhg, &pns); +} + +void pbr_nht_init(void) +{ + pbr_nhg_hash = hash_create_size( + 16, pbr_nhg_hash_key, pbr_nhg_hash_equal, "PBR NHG Cache Hash"); + pbr_nhrc_hash = + hash_create_size(16, (unsigned int (*)(const void *))nexthop_hash, + pbr_nhrc_hash_equal, "PBR NH Hash"); + pbr_nhg_allocated_id_hash = hash_create_size( + 16, pbr_nhg_allocated_id_hash_key, + pbr_nhg_allocated_id_hash_equal, "PBR Allocated Table Hash"); + + pbr_nhg_low_table = PBR_NHT_DEFAULT_LOW_TABLEID; + pbr_nhg_high_table = PBR_NHT_DEFAULT_HIGH_TABLEID; + pbr_nhg_low_rule = PBR_NHT_DEFAULT_LOW_RULE; + pbr_nhg_high_rule = PBR_NHT_DEFAULT_HIGH_RULE; + + /* First unallocated table is lowest in range on init */ + pbr_next_unallocated_table_id = PBR_NHT_DEFAULT_LOW_TABLEID; +} |