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 | |
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 '')
-rw-r--r-- | pbrd/.gitignore | 1 | ||||
-rw-r--r-- | pbrd/Makefile | 10 | ||||
-rw-r--r-- | pbrd/pbr_debug.c | 80 | ||||
-rw-r--r-- | pbrd/pbr_debug.h | 73 | ||||
-rw-r--r-- | pbrd/pbr_main.c | 186 | ||||
-rw-r--r-- | pbrd/pbr_map.c | 924 | ||||
-rw-r--r-- | pbrd/pbr_map.h | 238 | ||||
-rw-r--r-- | pbrd/pbr_memory.c | 27 | ||||
-rw-r--r-- | pbrd/pbr_memory.h | 24 | ||||
-rw-r--r-- | pbrd/pbr_nht.c | 1488 | ||||
-rw-r--r-- | pbrd/pbr_nht.h | 157 | ||||
-rw-r--r-- | pbrd/pbr_vrf.c | 139 | ||||
-rw-r--r-- | pbrd/pbr_vrf.h | 43 | ||||
-rw-r--r-- | pbrd/pbr_vty.c | 1432 | ||||
-rw-r--r-- | pbrd/pbr_vty.h | 24 | ||||
-rw-r--r-- | pbrd/pbr_zebra.c | 616 | ||||
-rw-r--r-- | pbrd/pbr_zebra.h | 52 | ||||
-rw-r--r-- | pbrd/subdir.am | 43 |
18 files changed, 5557 insertions, 0 deletions
diff --git a/pbrd/.gitignore b/pbrd/.gitignore new file mode 100644 index 0000000..86622ea --- /dev/null +++ b/pbrd/.gitignore @@ -0,0 +1 @@ +pbrd diff --git a/pbrd/Makefile b/pbrd/Makefile new file mode 100644 index 0000000..e8999c3 --- /dev/null +++ b/pbrd/Makefile @@ -0,0 +1,10 @@ +all: ALWAYS + @$(MAKE) -s -C .. pbrd/pbrd +%: ALWAYS + @$(MAKE) -s -C .. pbrd/$@ + +Makefile: + #nothing +ALWAYS: +.PHONY: ALWAYS makefiles +.SUFFIXES: diff --git a/pbrd/pbr_debug.c b/pbrd/pbr_debug.c new file mode 100644 index 0000000..82f045c --- /dev/null +++ b/pbrd/pbr_debug.c @@ -0,0 +1,80 @@ +/* + * PBR - debugging + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + * + * This program 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 of the License, or (at your option) + * any later version. + * + * This program 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 "debug.h" +#include "command.h" +#include "vector.h" + +#ifndef VTYSH_EXTRACT_PL +#include "pbrd/pbr_debug_clippy.c" +#endif +#include "pbrd/pbr_debug.h" + +struct debug pbr_dbg_map = {0, "PBR map"}; +struct debug pbr_dbg_zebra = {0, "PBR Zebra communications"}; +struct debug pbr_dbg_nht = {0, "PBR nexthop tracking"}; +struct debug pbr_dbg_event = {0, "PBR events"}; + +struct debug *pbr_debugs[] = {&pbr_dbg_map, &pbr_dbg_zebra, &pbr_dbg_nht, + &pbr_dbg_event}; + +const char *pbr_debugs_conflines[] = { + "debug pbr map", + "debug pbr zebra", + "debug pbr nht", + "debug pbr events", +}; + +void pbr_debug_set_all(uint32_t flags, bool set) +{ + for (unsigned int i = 0; i < array_size(pbr_debugs); i++) { + DEBUG_FLAGS_SET(pbr_debugs[i], flags, set); + + /* if all modes have been turned off, don't preserve options */ + if (!DEBUG_MODE_CHECK(pbr_debugs[i], DEBUG_MODE_ALL)) + DEBUG_CLEAR(pbr_debugs[i]); + } +} + +int pbr_debug_config_write_helper(struct vty *vty, bool config) +{ + uint32_t mode = DEBUG_MODE_ALL; + + if (config) + mode = DEBUG_MODE_CONF; + + for (unsigned int i = 0; i < array_size(pbr_debugs); i++) + if (DEBUG_MODE_CHECK(pbr_debugs[i], mode)) + vty_out(vty, "%s\n", pbr_debugs_conflines[i]); + return 0; +} + +int pbr_debug_config_write(struct vty *vty) +{ + return pbr_debug_config_write_helper(vty, true); +} + +struct debug_callbacks pbr_dbg_cbs = {.debug_set_all = pbr_debug_set_all}; + +void pbr_debug_init(void) +{ + debug_init(&pbr_dbg_cbs); +} diff --git a/pbrd/pbr_debug.h b/pbrd/pbr_debug.h new file mode 100644 index 0000000..e72fb88 --- /dev/null +++ b/pbrd/pbr_debug.h @@ -0,0 +1,73 @@ +/* + * PBR - debugging + * Copyright (C) 2018 Cumulus Networks, Inc. + * Quentin Young + * + * This program 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 of the License, or (at your option) + * any later version. + * + * This program 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 + */ +#ifndef __PBR_DEBUG_H__ +#define __PBR_DEBUG_H__ + +#include <zebra.h> + +#include "debug.h" + +/* PBR debugging records */ +extern struct debug pbr_dbg_map; +extern struct debug pbr_dbg_zebra; +extern struct debug pbr_dbg_nht; +extern struct debug pbr_dbg_event; + +/* + * Initialize PBR debugging. + * + * Installs VTY commands and registers callbacks. + */ +void pbr_debug_init(void); + +/* + * Set or unset flags on all debugs for pbrd. + * + * flags + * The flags to set + * + * set + * Whether to set or unset the specified flags + */ +void pbr_debug_set_all(uint32_t flags, bool set); + +/* + * Config write helper. + * + * vty + * Vty to write to + * + * config + * Whether we are writing to show run or saving config file + * + * Returns: + * 0 for convenience + */ +int pbr_debug_config_write_helper(struct vty *vty, bool config); + +/* + * Print PBR debugging configuration. + * + * vty + * VTY to print debugging configuration to. + */ +int pbr_debug_config_write(struct vty *vty); + +#endif /* __PBR_DEBUG_H__ */ diff --git a/pbrd/pbr_main.c b/pbrd/pbr_main.c new file mode 100644 index 0000000..59aa367 --- /dev/null +++ b/pbrd/pbr_main.c @@ -0,0 +1,186 @@ +/* + * PBR - main 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 <lib/version.h> +#include "getopt.h" +#include "thread.h" +#include "prefix.h" +#include "linklist.h" +#include "if.h" +#include "vector.h" +#include "vty.h" +#include "command.h" +#include "filter.h" +#include "plist.h" +#include "stream.h" +#include "log.h" +#include "memory.h" +#include "privs.h" +#include "sigevent.h" +#include "zclient.h" +#include "keychain.h" +#include "distribute.h" +#include "libfrr.h" +#include "routemap.h" +#include "nexthop.h" +#include "nexthop_group.h" + +#include "pbr_nht.h" +#include "pbr_map.h" +#include "pbr_zebra.h" +#include "pbr_vty.h" +#include "pbr_debug.h" +#include "pbr_vrf.h" + +zebra_capabilities_t _caps_p[] = { + ZCAP_NET_RAW, ZCAP_BIND, ZCAP_NET_ADMIN, +}; + +struct zebra_privs_t pbr_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0}; + +struct option longopts[] = { { 0 } }; + +/* Master of threads. */ +struct thread_master *master; + +/* SIGHUP handler. */ +static void sighup(void) +{ + zlog_info("SIGHUP received"); +} + +/* SIGINT / SIGTERM handler. */ +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + + pbr_vrf_terminate(); + + frr_fini(); + + exit(0); +} + +/* SIGUSR1 handler. */ +static void sigusr1(void) +{ + zlog_rotate(); +} + +struct frr_signal_t pbr_signals[] = { + { + .signal = SIGHUP, + .handler = &sighup, + }, + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +#define PBR_VTY_PORT 2615 + +static const struct frr_yang_module_info *const pbrd_yang_modules[] = { + &frr_filter_info, + &frr_interface_info, + &frr_vrf_info, +}; + +FRR_DAEMON_INFO(pbrd, PBR, .vty_port = PBR_VTY_PORT, + + .proghelp = "Implementation of PBR.", + + .signals = pbr_signals, + .n_signals = array_size(pbr_signals), + + .privs = &pbr_privs, + + .yang_modules = pbrd_yang_modules, + .n_yang_modules = array_size(pbrd_yang_modules), +); + +int main(int argc, char **argv, char **envp) +{ + frr_preinit(&pbrd_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + pbr_debug_init(); + + nexthop_group_init(pbr_nhgroup_add_cb, + pbr_nhgroup_add_nexthop_cb, + pbr_nhgroup_del_nexthop_cb, + pbr_nhgroup_delete_cb); + + /* + * So we safely ignore these commands since + * we are getting them at this point in time + */ + access_list_init(); + pbr_nht_init(); + pbr_map_init(); + if_zapi_callbacks(pbr_ifp_create, pbr_ifp_up, + pbr_ifp_down, pbr_ifp_destroy); + pbr_zebra_init(); + pbr_vrf_init(); + pbr_vty_init(); + + frr_config_fork(); + frr_run(master); + + /* Not reached. */ + return 0; +} diff --git a/pbrd/pbr_map.c b/pbrd/pbr_map.c new file mode 100644 index 0000000..5daac55 --- /dev/null +++ b/pbrd/pbr_map.c @@ -0,0 +1,924 @@ +/* + * PBR-map 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 "thread.h" +#include "linklist.h" +#include "prefix.h" +#include "table.h" +#include "vrf.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "memory.h" +#include "log.h" +#include "vty.h" + +#include "pbr_nht.h" +#include "pbr_map.h" +#include "pbr_zebra.h" +#include "pbr_memory.h" +#include "pbr_debug.h" +#include "pbr_vrf.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP, "PBR Map"); +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_SEQNO, "PBR Map Sequence"); +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_INTERFACE, "PBR Map Interface"); + +static uint32_t pbr_map_sequence_unique; + +static bool pbr_map_check_valid_internal(struct pbr_map *pbrm); +static inline int pbr_map_compare(const struct pbr_map *pbrmap1, + const struct pbr_map *pbrmap2); + +RB_GENERATE(pbr_map_entry_head, pbr_map, pbr_map_entry, pbr_map_compare) + +struct pbr_map_entry_head pbr_maps = RB_INITIALIZER(&pbr_maps); + +DEFINE_QOBJ_TYPE(pbr_map_sequence); + +static inline int pbr_map_compare(const struct pbr_map *pbrmap1, + const struct pbr_map *pbrmap2) +{ + return strcmp(pbrmap1->name, pbrmap2->name); +} + +static int pbr_map_sequence_compare(const struct pbr_map_sequence *pbrms1, + const struct pbr_map_sequence *pbrms2) +{ + if (pbrms1->seqno == pbrms2->seqno) + return 0; + + if (pbrms1->seqno < pbrms2->seqno) + return -1; + + return 1; +} + +static void pbr_map_sequence_delete(struct pbr_map_sequence *pbrms) +{ + XFREE(MTYPE_TMP, pbrms->internal_nhg_name); + + QOBJ_UNREG(pbrms); + XFREE(MTYPE_PBR_MAP_SEQNO, pbrms); +} + +static int pbr_map_interface_compare(const struct pbr_map_interface *pmi1, + const struct pbr_map_interface *pmi2) +{ + return strcmp(pmi1->ifp->name, pmi2->ifp->name); +} + +static void pbr_map_interface_list_delete(struct pbr_map_interface *pmi) +{ + struct pbr_map_interface *pmi_int; + struct listnode *node, *nnode; + struct pbr_map *pbrm; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS(pbrm->incoming, node, nnode, pmi_int)) { + if (pmi == pmi_int) { + pbr_map_policy_delete(pbrm, pmi); + return; + } + } + } +} + +static bool pbrms_is_installed(const struct pbr_map_sequence *pbrms, + const struct pbr_map_interface *pmi) +{ + uint64_t is_installed = (uint64_t)1 << pmi->install_bit; + + is_installed &= pbrms->installed; + + if (is_installed) + return true; + + return false; +} + +/* If any sequence is installed on the interface, assume installed */ +static bool +pbr_map_interface_is_installed(const struct pbr_map *pbrm, + const struct pbr_map_interface *check_pmi) +{ + + struct pbr_map_sequence *pbrms; + struct pbr_map_interface *pmi; + struct listnode *node, *inode; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + if (pmi == check_pmi && pbrms_is_installed(pbrms, pmi)) + return true; + + return false; +} + +static bool pbr_map_interface_is_valid(const struct pbr_map_interface *pmi) +{ + /* Don't install rules without a real ifindex on the incoming interface. + * + * This can happen when we have config for an interface that does not + * exist or when an interface is changing vrfs. + */ + if (pmi->ifp && pmi->ifp->ifindex != IFINDEX_INTERNAL) + return true; + + return false; +} + +static void pbr_map_pbrms_update_common(struct pbr_map_sequence *pbrms, + bool install, bool changed) +{ + struct pbr_map *pbrm; + struct listnode *node; + struct pbr_map_interface *pmi; + + pbrm = pbrms->parent; + + if (pbrms->nhs_installed && pbrm->incoming->count) { + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { + if (!pmi->ifp) + continue; + + if (install && !pbr_map_interface_is_valid(pmi)) + continue; + + pbr_send_pbr_map(pbrms, pmi, install, changed); + } + } +} + +static void pbr_map_pbrms_install(struct pbr_map_sequence *pbrms, bool changed) +{ + pbr_map_pbrms_update_common(pbrms, true, changed); +} + +static void pbr_map_pbrms_uninstall(struct pbr_map_sequence *pbrms) +{ + pbr_map_pbrms_update_common(pbrms, false, false); +} + +static const char *const pbr_map_reason_str[] = { + "Invalid NH-group", "Invalid NH", "No Nexthops", + "Both NH and NH-Group", "Invalid Src or Dst", "Invalid VRF", + "Both VLAN Set and Strip", "Deleting Sequence", +}; + +void pbr_map_reason_string(unsigned int reason, char *buf, int size) +{ + unsigned int bit; + int len = 0; + + if (!buf) + return; + + for (bit = 0; bit < array_size(pbr_map_reason_str); bit++) { + if ((reason & (1 << bit)) && (len < size)) { + len += snprintf((buf + len), (size - len), "%s%s", + (len > 0) ? ", " : "", + pbr_map_reason_str[bit]); + } + } +} + +void pbr_map_final_interface_deletion(struct pbr_map *pbrm, + struct pbr_map_interface *pmi) +{ + if (pmi->delete && !pbr_map_interface_is_installed(pbrm, pmi)) { + listnode_delete(pbrm->incoming, pmi); + pmi->pbrm = NULL; + + bf_release_index(pbrm->ifi_bitfield, pmi->install_bit); + XFREE(MTYPE_PBR_MAP_INTERFACE, pmi); + } +} + +void pbr_map_interface_delete(struct pbr_map *pbrm, struct interface *ifp_del) +{ + + struct listnode *node; + struct pbr_map_interface *pmi; + + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { + if (ifp_del == pmi->ifp) + break; + } + + if (pmi) + pbr_map_policy_delete(pbrm, pmi); +} + +void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp_add) +{ + struct listnode *node; + struct pbr_map_interface *pmi; + + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, node, pmi)) { + if (ifp_add == pmi->ifp) + return; + } + + pmi = XCALLOC(MTYPE_PBR_MAP_INTERFACE, sizeof(*pmi)); + pmi->ifp = ifp_add; + pmi->pbrm = pbrm; + listnode_add_sort(pbrm->incoming, pmi); + + bf_assign_index(pbrm->ifi_bitfield, pmi->install_bit); + pbr_map_check_valid(pbrm->name); + if (pbrm->valid) + pbr_map_install(pbrm); +} + +static int +pbr_map_policy_interface_update_common(const struct interface *ifp, + struct pbr_interface **pbr_ifp, + struct pbr_map **pbrm) +{ + if (!ifp->info) { + DEBUGD(&pbr_dbg_map, "%s: %s has no pbr_interface info", + __func__, ifp->name); + return -1; + } + + *pbr_ifp = ifp->info; + + *pbrm = pbrm_find((*pbr_ifp)->mapname); + + if (!*pbrm) { + DEBUGD(&pbr_dbg_map, "%s: applied PBR-MAP(%s) does not exist?", + __func__, (*pbr_ifp)->mapname); + return -1; + } + + return 0; +} + +void pbr_map_policy_interface_update(const struct interface *ifp, bool state_up) +{ + struct pbr_interface *pbr_ifp; + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node, *inode; + struct pbr_map_interface *pmi; + + if (pbr_map_policy_interface_update_common(ifp, &pbr_ifp, &pbrm)) + return; + + DEBUGD(&pbr_dbg_map, "%s: %s %s rules on interface %s", __func__, + pbr_ifp->mapname, (state_up ? "installing" : "removing"), + ifp->name); + + /* + * Walk the list and install/remove maps on the interface. + */ + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + if (pmi->ifp == ifp && pbr_map_interface_is_valid(pmi)) + pbr_send_pbr_map(pbrms, pmi, state_up, true); +} + +static void pbrms_vrf_update(struct pbr_map_sequence *pbrms, + const struct pbr_vrf *pbr_vrf) +{ + const char *vrf_name = pbr_vrf_name(pbr_vrf); + + if (pbrms->vrf_lookup + && (strncmp(vrf_name, pbrms->vrf_name, sizeof(pbrms->vrf_name)) + == 0)) { + DEBUGD(&pbr_dbg_map, " Seq %u uses vrf %s (%u), updating map", + pbrms->seqno, vrf_name, pbr_vrf_id(pbr_vrf)); + + pbr_map_check(pbrms, false); + } +} + +/* Vrf enabled/disabled */ +void pbr_map_vrf_update(const struct pbr_vrf *pbr_vrf) +{ + struct pbr_map *pbrm; + struct pbr_map_sequence *pbrms; + struct listnode *node; + + if (!pbr_vrf) + return; + + bool enabled = pbr_vrf_is_enabled(pbr_vrf); + + DEBUGD(&pbr_dbg_map, "%s: %s (%u) %s, updating pbr maps", __func__, + pbr_vrf_name(pbr_vrf), pbr_vrf_id(pbr_vrf), + enabled ? "enabled" : "disabled"); + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + DEBUGD(&pbr_dbg_map, "%s: Looking at %s", __func__, pbrm->name); + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + pbrms_vrf_update(pbrms, pbr_vrf); + } +} + +void pbr_map_write_interfaces(struct vty *vty, struct interface *ifp) +{ + struct pbr_interface *pbr_ifp = ifp->info; + + if (pbr_ifp + && strncmp(pbr_ifp->mapname, "", sizeof(pbr_ifp->mapname)) != 0) + vty_out(vty, " pbr-policy %s\n", pbr_ifp->mapname); +} + +struct pbr_map *pbrm_find(const char *name) +{ + struct pbr_map pbrm; + + strlcpy(pbrm.name, name, sizeof(pbrm.name)); + + return RB_FIND(pbr_map_entry_head, &pbr_maps, &pbrm); +} + +extern void pbr_map_delete(struct pbr_map_sequence *pbrms) +{ + struct pbr_map *pbrm; + struct listnode *inode; + struct pbr_map_interface *pmi; + + pbrm = pbrms->parent; + + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + pbr_send_pbr_map(pbrms, pmi, false, false); + + if (pbrms->nhg) + pbr_nht_delete_individual_nexthop(pbrms); + + listnode_delete(pbrm->seqnumbers, pbrms); + + if (pbrm->seqnumbers->count == 0) { + RB_REMOVE(pbr_map_entry_head, &pbr_maps, pbrm); + + bf_free(pbrm->ifi_bitfield); + XFREE(MTYPE_PBR_MAP, pbrm); + } +} + +static void pbr_map_delete_common(struct pbr_map_sequence *pbrms) +{ + struct pbr_map *pbrm = pbrms->parent; + + pbr_map_pbrms_uninstall(pbrms); + + pbrm->valid = false; + pbrms->nhs_installed = false; + pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; + XFREE(MTYPE_TMP, pbrms->nhgrp_name); +} + +void pbr_map_delete_nexthops(struct pbr_map_sequence *pbrms) +{ + pbr_map_delete_common(pbrms); +} + +void pbr_map_delete_vrf(struct pbr_map_sequence *pbrms) +{ + pbr_map_delete_common(pbrms); +} + +struct pbr_map_sequence *pbrms_lookup_unique(uint32_t unique, char *ifname, + struct pbr_map_interface **ppmi) +{ + struct pbr_map_sequence *pbrms; + struct listnode *snode, *inode; + struct pbr_map_interface *pmi; + struct pbr_map *pbrm; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) { + if (strcmp(pmi->ifp->name, ifname) != 0) + continue; + + if (ppmi) + *ppmi = pmi; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, snode, + pbrms)) { + DEBUGD(&pbr_dbg_map, "%s: Comparing %u to %u", + __func__, pbrms->unique, unique); + if (pbrms->unique == unique) + return pbrms; + } + } + } + + return NULL; +} + +static void pbr_map_add_interfaces(struct pbr_map *pbrm) +{ + struct interface *ifp; + struct pbr_interface *pbr_ifp; + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) { + if (ifp->info) { + pbr_ifp = ifp->info; + if (strcmp(pbrm->name, pbr_ifp->mapname) == 0) + pbr_map_add_interface(pbrm, ifp); + } + } + } +} + +/* Decodes a standardized DSCP into its representative value */ +uint8_t pbr_map_decode_dscp_enum(const char *name) +{ + /* Standard Differentiated Services Field Codepoints */ + if (!strcmp(name, "cs0")) + return 0; + if (!strcmp(name, "cs1")) + return 8; + if (!strcmp(name, "cs2")) + return 16; + if (!strcmp(name, "cs3")) + return 24; + if (!strcmp(name, "cs4")) + return 32; + if (!strcmp(name, "cs5")) + return 40; + if (!strcmp(name, "cs6")) + return 48; + if (!strcmp(name, "cs7")) + return 56; + if (!strcmp(name, "af11")) + return 10; + if (!strcmp(name, "af12")) + return 12; + if (!strcmp(name, "af13")) + return 14; + if (!strcmp(name, "af21")) + return 18; + if (!strcmp(name, "af22")) + return 20; + if (!strcmp(name, "af23")) + return 22; + if (!strcmp(name, "af31")) + return 26; + if (!strcmp(name, "af32")) + return 28; + if (!strcmp(name, "af33")) + return 30; + if (!strcmp(name, "af41")) + return 34; + if (!strcmp(name, "af42")) + return 36; + if (!strcmp(name, "af43")) + return 38; + if (!strcmp(name, "ef")) + return 46; + if (!strcmp(name, "voice-admit")) + return 44; + + /* No match? Error out */ + return -1; +} + +struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno) +{ + struct pbr_map *pbrm; + struct pbr_map_sequence *pbrms; + struct listnode *node; + + pbrm = pbrm_find(name); + if (!pbrm) { + pbrm = XCALLOC(MTYPE_PBR_MAP, sizeof(*pbrm)); + snprintf(pbrm->name, sizeof(pbrm->name), "%s", name); + + pbrm->seqnumbers = list_new(); + pbrm->seqnumbers->cmp = + (int (*)(void *, void *))pbr_map_sequence_compare; + pbrm->seqnumbers->del = + (void (*)(void *))pbr_map_sequence_delete; + + pbrm->incoming = list_new(); + pbrm->incoming->cmp = + (int (*)(void *, void *))pbr_map_interface_compare; + pbrm->incoming->del = + (void (*)(void *))pbr_map_interface_list_delete; + + RB_INSERT(pbr_map_entry_head, &pbr_maps, pbrm); + + bf_init(pbrm->ifi_bitfield, 64); + pbr_map_add_interfaces(pbrm); + } + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + if (pbrms->seqno == seqno) + break; + + } + + if (!pbrms) { + pbrms = XCALLOC(MTYPE_PBR_MAP_SEQNO, sizeof(*pbrms)); + pbrms->unique = pbr_map_sequence_unique++; + pbrms->seqno = seqno; + pbrms->ruleno = pbr_nht_get_next_rule(seqno); + pbrms->parent = pbrm; + + pbrms->action_vlan_id = 0; + pbrms->action_vlan_flags = 0; + pbrms->action_pcp = 0; + + pbrms->action_queue_id = PBR_MAP_UNDEFINED_QUEUE_ID; + + pbrms->reason = + PBR_MAP_INVALID_EMPTY | + PBR_MAP_INVALID_NO_NEXTHOPS; + pbrms->vrf_name[0] = '\0'; + + QOBJ_REG(pbrms, pbr_map_sequence); + listnode_add_sort(pbrm->seqnumbers, pbrms); + } + + return pbrms; +} + +static void +pbr_map_sequence_check_nexthops_valid(struct pbr_map_sequence *pbrms) +{ + /* Check if any are present first */ + if (!pbrms->vrf_unchanged && !pbrms->vrf_lookup && !pbrms->nhg + && !pbrms->nhgrp_name) { + pbrms->reason |= PBR_MAP_INVALID_NO_NEXTHOPS; + return; + } + + /* + * Check validness of vrf. + */ + + /* This one can be considered always valid */ + if (pbrms->vrf_unchanged) + pbrms->nhs_installed = true; + + if (pbrms->vrf_lookup) { + struct pbr_vrf *pbr_vrf = + pbr_vrf_lookup_by_name(pbrms->vrf_name); + + if (pbr_vrf && pbr_vrf_is_valid(pbr_vrf)) + pbrms->nhs_installed = true; + else + pbrms->reason |= PBR_MAP_INVALID_VRF; + } + + /* + * Check validness of the nexthop or nexthop-group + */ + + /* Only nexthop or nexthop group allowed */ + if (pbrms->nhg && pbrms->nhgrp_name) + pbrms->reason |= PBR_MAP_INVALID_BOTH_NHANDGRP; + + if (pbrms->nhg && + !pbr_nht_nexthop_group_valid(pbrms->internal_nhg_name)) + pbrms->reason |= PBR_MAP_INVALID_NEXTHOP; + + if (pbrms->nhgrp_name) { + if (!pbr_nht_nexthop_group_valid(pbrms->nhgrp_name)) + pbrms->reason |= PBR_MAP_INVALID_NEXTHOP_GROUP; + else + pbrms->nhs_installed = true; + } +} + +static void pbr_map_sequence_check_not_empty(struct pbr_map_sequence *pbrms) +{ + if (!pbrms->src && !pbrms->dst && !pbrms->mark && !pbrms->dsfield + && !pbrms->action_vlan_id && !pbrms->action_vlan_flags + && !pbrms->action_pcp + && pbrms->action_queue_id == PBR_MAP_UNDEFINED_QUEUE_ID) + pbrms->reason |= PBR_MAP_INVALID_EMPTY; +} + +static void pbr_map_sequence_check_vlan_actions(struct pbr_map_sequence *pbrms) +{ + /* The set vlan tag action does the following: + * 1. If the frame is untagged, it tags the frame with the + * configured VLAN ID. + * 2. If the frame is tagged, if replaces the tag. + * + * The strip vlan action removes any inner tag, so it is invalid to + * specify both a set and strip action. + */ + if ((pbrms->action_vlan_id != 0) && (pbrms->action_vlan_flags != 0)) + pbrms->reason |= PBR_MAP_INVALID_SET_STRIP_VLAN; +} + + +/* + * Checks to see if we think that the pbmrs is valid. If we think + * the config is valid return true. + */ +static void pbr_map_sequence_check_valid(struct pbr_map_sequence *pbrms) +{ + pbr_map_sequence_check_nexthops_valid(pbrms); + pbr_map_sequence_check_vlan_actions(pbrms); + pbr_map_sequence_check_not_empty(pbrms); +} + +static bool pbr_map_check_valid_internal(struct pbr_map *pbrm) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + + pbrm->valid = true; + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + pbrms->reason = 0; + pbr_map_sequence_check_valid(pbrms); + /* + * A pbr_map_sequence that is invalid causes + * the whole shebang to be invalid + */ + if (pbrms->reason != 0) + pbrm->valid = false; + } + + return pbrm->valid; +} + +/* + * For a given PBR-MAP check to see if we think it is a + * valid config or not. If so note that it is and return + * that we are valid. + */ +bool pbr_map_check_valid(const char *name) +{ + struct pbr_map *pbrm; + + pbrm = pbrm_find(name); + if (!pbrm) { + DEBUGD(&pbr_dbg_map, + "%s: Specified PBR-MAP(%s) does not exist?", __func__, + name); + return false; + } + + pbr_map_check_valid_internal(pbrm); + return pbrm->valid; +} + +void pbr_map_schedule_policy_from_nhg(const char *nh_group, bool installed) +{ + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + DEBUGD(&pbr_dbg_map, "%s: Looking at %s", __func__, pbrm->name); + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + DEBUGD(&pbr_dbg_map, " NH Grp name: %s", + pbrms->nhgrp_name ? + pbrms->nhgrp_name : pbrms->internal_nhg_name); + + if (pbrms->nhgrp_name + && (strcmp(nh_group, pbrms->nhgrp_name) == 0)) { + pbrms->nhs_installed = installed; + + pbr_map_check(pbrms, false); + } + + if (pbrms->nhg + && (strcmp(nh_group, pbrms->internal_nhg_name) + == 0)) { + pbrms->nhs_installed = installed; + + pbr_map_check(pbrms, false); + } + } + } +} + +void pbr_map_policy_install(const char *name) +{ + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node, *inode; + struct pbr_map_interface *pmi; + + DEBUGD(&pbr_dbg_map, "%s: for %s", __func__, name); + pbrm = pbrm_find(name); + if (!pbrm) + return; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + DEBUGD(&pbr_dbg_map, + "%s: Looking at what to install %s(%u) %d %d", __func__, + name, pbrms->seqno, pbrm->valid, pbrms->nhs_installed); + + if (pbrm->valid && pbrms->nhs_installed + && pbrm->incoming->count) { + DEBUGD(&pbr_dbg_map, " Installing %s %u", pbrm->name, + pbrms->seqno); + for (ALL_LIST_ELEMENTS_RO(pbrm->incoming, inode, pmi)) + if (pbr_map_interface_is_valid(pmi)) + pbr_send_pbr_map(pbrms, pmi, true, + false); + } + } +} + +void pbr_map_policy_delete(struct pbr_map *pbrm, struct pbr_map_interface *pmi) +{ + struct listnode *node; + struct pbr_map_sequence *pbrms; + bool sent = false; + + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + if (pbr_send_pbr_map(pbrms, pmi, false, true)) + sent = true; /* rule removal sent to zebra */ + + pmi->delete = true; + + /* + * If we actually sent something for deletion, wait on zapi callback + * before clearing data. + */ + if (sent) + return; + + pbr_map_final_interface_deletion(pbrm, pmi); +} + +/* + * For a nexthop group specified, see if any of the pbr-maps + * are using it and if so, check to see that we are still + * valid for usage. If we are valid then schedule the installation/deletion + * of the pbr-policy. + */ +void pbr_map_check_nh_group_change(const char *nh_group) +{ + struct pbr_map_sequence *pbrms; + struct pbr_map *pbrm; + struct listnode *node, *inode; + struct pbr_map_interface *pmi; + bool found_name; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + found_name = false; + if (pbrms->nhgrp_name) + found_name = + !strcmp(nh_group, pbrms->nhgrp_name); + else if (pbrms->nhg) + found_name = !strcmp(nh_group, + pbrms->internal_nhg_name); + + if (found_name) { + bool original = pbrm->valid; + + /* Set data we were waiting on */ + if (pbrms->nhgrp_name) + pbr_nht_set_seq_nhg_data( + pbrms, + nhgc_find(pbrms->nhgrp_name)); + + pbr_map_check_valid_internal(pbrm); + + if (pbrm->valid && (original != pbrm->valid)) + pbr_map_install(pbrm); + + if (pbrm->valid == false) + for (ALL_LIST_ELEMENTS_RO( + pbrm->incoming, inode, + pmi)) + pbr_send_pbr_map(pbrms, pmi, + false, false); + } + } + } +} + +void pbr_map_check_vrf_nh_group_change(const char *nh_group, + struct pbr_vrf *pbr_vrf, + uint32_t old_vrf_id) +{ + struct pbr_map *pbrm; + struct pbr_map_sequence *pbrms; + struct listnode *node; + + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + if (pbrms->nhgrp_name) + continue; + + if (pbrms->nhg == NULL) + continue; + + if (strcmp(nh_group, pbrms->internal_nhg_name)) + continue; + + if (pbrms->nhg->nexthop == NULL) + continue; + + if (pbrms->nhg->nexthop->vrf_id != old_vrf_id) + continue; + + pbrms->nhg->nexthop->vrf_id = pbr_vrf_id(pbr_vrf); + } + } +} + +void pbr_map_check_interface_nh_group_change(const char *nh_group, + struct interface *ifp, + ifindex_t oldifindex) +{ + struct pbr_map *pbrm; + struct pbr_map_sequence *pbrms; + struct listnode *node; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) { + if (pbrms->nhgrp_name) + continue; + + if (pbrms->nhg == NULL) + continue; + + if (strcmp(nh_group, pbrms->internal_nhg_name)) + continue; + + if (pbrms->nhg->nexthop == NULL) + continue; + + if (pbrms->nhg->nexthop->ifindex != oldifindex) + continue; + + pbrms->nhg->nexthop->ifindex = ifp->ifindex; + } + } +} + +void pbr_map_check(struct pbr_map_sequence *pbrms, bool changed) +{ + struct pbr_map *pbrm; + bool install; + + pbrm = pbrms->parent; + DEBUGD(&pbr_dbg_map, "%s: for %s(%u)", __func__, pbrm->name, + pbrms->seqno); + if (pbr_map_check_valid(pbrm->name)) + DEBUGD(&pbr_dbg_map, "We are totally valid %s", + pbrm->name); + + if (pbrms->reason == PBR_MAP_VALID_SEQUENCE_NUMBER) { + install = true; + DEBUGD(&pbr_dbg_map, "%s: Installing %s(%u) reason: %" PRIu64, + __func__, pbrm->name, pbrms->seqno, pbrms->reason); + DEBUGD(&pbr_dbg_map, + " Sending PBR_MAP_POLICY_INSTALL event"); + } else { + install = false; + DEBUGD(&pbr_dbg_map, "%s: Removing %s(%u) reason: %" PRIu64, + __func__, pbrm->name, pbrms->seqno, pbrms->reason); + } + + if (install) + pbr_map_pbrms_install(pbrms, changed); + else + pbr_map_pbrms_uninstall(pbrms); +} + +void pbr_map_install(struct pbr_map *pbrm) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + + if (!pbrm->incoming->count) + return; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + pbr_map_pbrms_install(pbrms, false); +} + +void pbr_map_init(void) +{ + RB_INIT(pbr_map_entry_head, &pbr_maps); + + pbr_map_sequence_unique = 1; +} diff --git a/pbrd/pbr_map.h b/pbrd/pbr_map.h new file mode 100644 index 0000000..3527523 --- /dev/null +++ b/pbrd/pbr_map.h @@ -0,0 +1,238 @@ +/* + * PBR-map Header + * 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 + */ +#ifndef __PBR_MAP_H__ +#define __PBR_MAP_H__ + +#include <bitfield.h> + +#include "pbr_vrf.h" + +struct pbr_map { + /* + * RB Tree of the pbr_maps + */ + RB_ENTRY(pbr_map) pbr_map_entry; + + /* + * The name of the PBR_MAP + */ +#define PBR_MAP_NAMELEN 100 + char name[PBR_MAP_NAMELEN]; + + struct list *seqnumbers; + + /* + * The list of incoming interfaces that + * we will apply this policy map onto + */ + struct list *incoming; + + bitfield_t ifi_bitfield; + /* + * If valid is true we think the pbr_map is valid, + * If false, look in individual pbrms to see + * what we think is the invalid reason + */ + bool valid; +}; + +RB_HEAD(pbr_map_entry_head, pbr_map); +RB_PROTOTYPE(pbr_map_entry_head, pbr_map, pbr_map_entry, pbr_map_compare) + +struct pbr_map_interface { + uint32_t install_bit; + + struct interface *ifp; + + struct pbr_map *pbrm; + + bool delete; +}; + +struct pbr_map_sequence { + struct pbr_map *parent; + + /* + * The Unique identifier of this specific pbrms + */ + uint32_t unique; + + /* + * The sequence of where we are for display + */ + uint32_t seqno; + + /* + * The rule number to install into + */ + uint32_t ruleno; + + /* + * src and dst ports + */ + uint16_t src_prt; + uint16_t dst_prt; + + /* + * The ip protocol we want to match on + */ + uint8_t ip_proto; + + /* + * Our policy Catchers + */ + struct prefix *src; + struct prefix *dst; + uint8_t dsfield; + uint32_t mark; + + /* + * Actions + */ + uint8_t action_pcp; + uint8_t action_vlan_id; +#define PBR_MAP_STRIP_INNER_ANY (1 << 0) + uint8_t action_vlan_flags; + +#define PBR_MAP_UNDEFINED_QUEUE_ID 0 + uint32_t action_queue_id; + + /* + * Family of the src/dst. Needed when deleting since we clear them + */ + unsigned char family; + + /* + * Use interface's vrf. + */ + bool vrf_unchanged; + + /* + * The vrf to lookup in was directly configured. + */ + bool vrf_lookup; + + /* + * VRF to lookup. + */ + char vrf_name[VRF_NAMSIZ + 1]; + + /* + * The nexthop group we auto create + * for when the user specifies a individual + * nexthop + */ + struct nexthop_group *nhg; + char *internal_nhg_name; + + /* + * The name of the nexthop group + * configured in the pbr-map + */ + char *nhgrp_name; + + /* + * Do we think are nexthops are installed + */ + bool nhs_installed; + + /* + * Are we installed + */ + uint64_t installed; + + /* + * A reason of 0 means we think the pbr_map_sequence is good to go + * We can accumuluate multiple failure states + */ +#define PBR_MAP_VALID_SEQUENCE_NUMBER 0 +#define PBR_MAP_INVALID_NEXTHOP_GROUP (1 << 0) +#define PBR_MAP_INVALID_NEXTHOP (1 << 1) +#define PBR_MAP_INVALID_NO_NEXTHOPS (1 << 2) +#define PBR_MAP_INVALID_BOTH_NHANDGRP (1 << 3) +#define PBR_MAP_INVALID_EMPTY (1 << 4) +#define PBR_MAP_INVALID_VRF (1 << 5) +#define PBR_MAP_INVALID_SET_STRIP_VLAN (1 << 6) + uint64_t reason; + + QOBJ_FIELDS; +}; + +DECLARE_QOBJ_TYPE(pbr_map_sequence); + +extern struct pbr_map_entry_head pbr_maps; + +extern struct pbr_map_sequence *pbrms_get(const char *name, uint32_t seqno); +extern struct pbr_map_sequence * +pbrms_lookup_unique(uint32_t unique, char *ifname, + struct pbr_map_interface **ppmi); + +extern struct pbr_map *pbrm_find(const char *name); +extern void pbr_map_delete(struct pbr_map_sequence *pbrms); +extern void pbr_map_delete_nexthops(struct pbr_map_sequence *pbrms); +extern void pbr_map_delete_vrf(struct pbr_map_sequence *pbrms); +extern void pbr_map_add_interface(struct pbr_map *pbrm, struct interface *ifp); +extern void pbr_map_interface_delete(struct pbr_map *pbrm, + struct interface *ifp); + +extern uint8_t pbr_map_decode_dscp_enum(const char *name); + +/* Update maps installed on interface */ +extern void pbr_map_policy_interface_update(const struct interface *ifp, + bool state_up); + +extern void pbr_map_final_interface_deletion(struct pbr_map *pbrm, + struct pbr_map_interface *pmi); + +extern void pbr_map_vrf_update(const struct pbr_vrf *pbr_vrf); + +extern void pbr_map_write_interfaces(struct vty *vty, struct interface *ifp); +extern void pbr_map_init(void); + +extern bool pbr_map_check_valid(const char *name); + +/** + * Re-check the pbr map for validity. + * + * Install if valid, remove if not. + * + * If changed is set, the config on the on the map has changed somewhere + * and the rules need to be replaced if valid. + */ +extern void pbr_map_check(struct pbr_map_sequence *pbrms, bool changed); +extern void pbr_map_check_nh_group_change(const char *nh_group); +extern void pbr_map_reason_string(unsigned int reason, char *buf, int size); + +extern void pbr_map_schedule_policy_from_nhg(const char *nh_group, + bool installed); + +extern void pbr_map_install(struct pbr_map *pbrm); + +extern void pbr_map_policy_install(const char *name); +extern void pbr_map_policy_delete(struct pbr_map *pbrm, + struct pbr_map_interface *pmi); + +extern void pbr_map_check_vrf_nh_group_change(const char *nh_group, + struct pbr_vrf *pbr_vrf, + uint32_t old_vrf_id); +extern void pbr_map_check_interface_nh_group_change(const char *nh_group, + struct interface *ifp, + ifindex_t oldifindex); +#endif diff --git a/pbrd/pbr_memory.c b/pbrd/pbr_memory.c new file mode 100644 index 0000000..5531d41 --- /dev/null +++ b/pbrd/pbr_memory.c @@ -0,0 +1,27 @@ +/* + * PBR memory 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 <memory.h> + +#include "pbrd/pbr_memory.h" + + +DEFINE_MGROUP(PBRD, "pbrd"); diff --git a/pbrd/pbr_memory.h b/pbrd/pbr_memory.h new file mode 100644 index 0000000..eb13d5d --- /dev/null +++ b/pbrd/pbr_memory.h @@ -0,0 +1,24 @@ +/* + * pbr memory 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 + */ +#ifndef __PBR_MEMORY_H__ + +DECLARE_MGROUP(PBRD); + +#endif 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; +} diff --git a/pbrd/pbr_nht.h b/pbrd/pbr_nht.h new file mode 100644 index 0000000..ecc92cc --- /dev/null +++ b/pbrd/pbr_nht.h @@ -0,0 +1,157 @@ +/* + * PBR-nht Header + * 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 + */ +#ifndef __PBR_NHT_H__ +#define __PBR_NHT_H__ + +#include <lib/zclient.h> +#include <lib/nexthop_group.h> + +#include "pbr_map.h" +#include "json.h" + +#define PBR_NHC_NAMELEN PBR_MAP_NAMELEN + 10 + +extern struct hash *pbr_nhg_hash; + +struct pbr_nexthop_group_cache { + char name[PBR_NHC_NAMELEN]; + + uint32_t table_id; + + struct hash *nhh; + + /* + * If all nexthops are considered valid + */ + bool valid; + + bool installed; +}; + +struct pbr_nexthop_cache { + struct pbr_nexthop_group_cache *parent; + + char vrf_name[VRF_NAMSIZ + 1]; + char intf_name[INTERFACE_NAMSIZ + 1]; + + struct nexthop nexthop; + + bool looked_at; + bool valid; + bool nhr_matched; +}; + +extern void pbr_nht_write_table_range(struct vty *vty); +#define PBR_NHT_DEFAULT_LOW_TABLEID 10000 +#define PBR_NHT_DEFAULT_HIGH_TABLEID 11000 +extern void pbr_nht_set_tableid_range(uint32_t low, uint32_t high); + +/* + * Find and reserve the next available table for installation; + * Sequential calls to this function will reserve sequential table numbers + * until the configured range is exhausted; calls made after exhaustion always + * return 0 + */ +extern uint32_t +pbr_nht_reserve_next_table_id(struct pbr_nexthop_group_cache *nhgc); +/* + * Get the next tableid to use for installation to kernel + */ +extern uint32_t pbr_nht_find_next_unallocated_table_id(void); +/* + * Calculate where the next table representing a nhg will go in kernel + */ +extern void pbr_nht_update_next_unallocated_table_id(void); +/* + * Indicate if there are free spots to install a table to kernel within the + * configured PBR table range + */ +extern bool pbr_nht_has_unallocated_table(void); +/* + * Get the next rule number to use for installation + */ +extern void pbr_nht_write_rule_range(struct vty *vty); + +#define PBR_NHT_DEFAULT_LOW_RULE 300 +#define PBR_NHT_DEFAULT_HIGH_RULE 1300 +extern void pbr_nht_set_rule_range(uint32_t low, uint32_t high); + +extern uint32_t pbr_nht_get_next_rule(uint32_t seqno); + +extern void pbr_nhgroup_add_cb(const char *name); +extern void pbr_nhgroup_add_nexthop_cb(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); +extern void pbr_nhgroup_del_nexthop_cb(const struct nexthop_group_cmd *nhg, + const struct nexthop *nhop); +extern void pbr_nhgroup_delete_cb(const char *name); + +extern bool pbr_nht_nexthop_valid(struct nexthop_group *nhg); +extern bool pbr_nht_nexthop_group_valid(const char *name); + +extern struct pbr_nexthop_group_cache *pbr_nht_add_group(const char *name); +extern void pbr_nht_change_group(const char *name); +extern void pbr_nht_delete_group(const char *name); + +extern void pbr_nht_set_seq_nhg_data(struct pbr_map_sequence *pbrms, + const struct nexthop_group_cmd *nhgc); +extern void pbr_nht_set_seq_nhg(struct pbr_map_sequence *pbrms, + const char *name); + +extern void pbr_nht_add_individual_nexthop(struct pbr_map_sequence *pbrms, + const struct nexthop *nhop); +extern void pbr_nht_delete_individual_nexthop(struct pbr_map_sequence *pbrms); +/* + * Given the tableid of the installed default + * route, find the nexthop-group associated with + * it, then find all pbr-maps that use it and + * install/delete them as well. + */ +extern void pbr_nht_route_installed_for_table(uint32_t table_id); +extern void pbr_nht_route_removed_for_table(uint32_t table_id); + +/* + * Given the nexthop group name, lookup the associated + * tableid with it + */ +extern uint32_t pbr_nht_get_table(const char *name); + +extern bool pbr_nht_get_installed(const char *name); + +extern char *pbr_nht_nexthop_make_name(char *name, size_t l, uint32_t seqno, + char *buffer); + +extern void pbr_nht_show_nexthop_group(struct vty *vty, const char *name); +extern void pbr_nht_json_nexthop_group(json_object *j, const char *name); + +/* + * When we get a callback from zebra about a nexthop changing + */ +extern void pbr_nht_nexthop_update(struct zapi_route *nhr); + +/* + * When we get a callback from zebra about an interface status update. + */ +extern void pbr_nht_nexthop_interface_update(struct interface *ifp); + +extern void pbr_nht_init(void); + +extern void pbr_nht_vrf_update(struct pbr_vrf *pbr_vrf); +extern void pbr_nht_interface_update(struct interface *ifp); +#endif diff --git a/pbrd/pbr_vrf.c b/pbrd/pbr_vrf.c new file mode 100644 index 0000000..277269a --- /dev/null +++ b/pbrd/pbr_vrf.c @@ -0,0 +1,139 @@ +/* + * PBR - vrf code + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + * + * 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 "vrf.h" + +#include "pbr_vrf.h" +#include "pbr_memory.h" +#include "pbr_map.h" +#include "pbr_debug.h" +#include "pbr_nht.h" +#include "pbr_zebra.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_MAP_VRF, "PBR Map VRF"); + +static struct pbr_vrf *pbr_vrf_alloc(void) +{ + struct pbr_vrf *pbr_vrf; + + pbr_vrf = XCALLOC(MTYPE_PBR_MAP_VRF, sizeof(struct pbr_vrf)); + + return pbr_vrf; +} + +static void pbr_vrf_free(struct pbr_vrf *pbr_vrf) +{ + XFREE(MTYPE_PBR_MAP_VRF, pbr_vrf); +} + +static int pbr_vrf_new(struct vrf *vrf) +{ + struct pbr_vrf *pbr_vrf; + + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + pbr_vrf = pbr_vrf_alloc(); + vrf->info = pbr_vrf; + pbr_vrf->vrf = vrf; + + return 0; +} + +static int pbr_vrf_enable(struct vrf *vrf) +{ + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + pbr_nht_vrf_update(vrf->info); + pbr_map_vrf_update(vrf->info); + + return 0; +} + +static int pbr_vrf_disable(struct vrf *vrf) +{ + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + pbr_map_vrf_update(vrf->info); + + return 0; +} + +static int pbr_vrf_delete(struct vrf *vrf) +{ + DEBUGD(&pbr_dbg_event, "%s: %u (%s)", __func__, vrf->vrf_id, vrf->name); + + /* + * Make sure vrf is always marked disabled first so we handle + * pbr rules using it. + */ + assert(!vrf_is_enabled(vrf)); + + pbr_vrf_free(vrf->info); + vrf->info = NULL; + + return 0; +} + +struct pbr_vrf *pbr_vrf_lookup_by_name(const char *name) +{ + struct vrf *vrf; + + if (!name) + name = VRF_DEFAULT_NAME; + + vrf = vrf_lookup_by_name(name); + if (vrf) + return ((struct pbr_vrf *)vrf->info); + + return NULL; +} + +bool pbr_vrf_is_enabled(const struct pbr_vrf *pbr_vrf) +{ + return vrf_is_enabled(pbr_vrf->vrf) ? true : false; +} + +bool pbr_vrf_is_valid(const struct pbr_vrf *pbr_vrf) +{ + if (vrf_is_backend_netns()) + return false; + + if (!pbr_vrf->vrf) + return false; + + return pbr_vrf_is_enabled(pbr_vrf); +} + +void pbr_vrf_init(void) +{ + vrf_init(pbr_vrf_new, pbr_vrf_enable, pbr_vrf_disable, pbr_vrf_delete); +} + +void pbr_vrf_terminate(void) +{ + struct vrf *vrf; + struct interface *ifp; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) + pbr_if_del(ifp); + } +} diff --git a/pbrd/pbr_vrf.h b/pbrd/pbr_vrf.h new file mode 100644 index 0000000..e37bcd4 --- /dev/null +++ b/pbrd/pbr_vrf.h @@ -0,0 +1,43 @@ +/* + * VRF library for PBR + * Copyright (C) 2019 Cumulus Networks, Inc. + * Stephen Worley + * + * 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 + */ +#ifndef __PBR_VRF_H__ +#define __PBR_VRF_H__ + +struct pbr_vrf { + struct vrf *vrf; +}; + +static inline const char *pbr_vrf_name(const struct pbr_vrf *pbr_vrf) +{ + return pbr_vrf->vrf->name; +} + +static inline vrf_id_t pbr_vrf_id(const struct pbr_vrf *pbr_vrf) +{ + return pbr_vrf->vrf->vrf_id; +} + +extern struct pbr_vrf *pbr_vrf_lookup_by_name(const char *name); +extern bool pbr_vrf_is_valid(const struct pbr_vrf *pbr_vrf); +extern bool pbr_vrf_is_enabled(const struct pbr_vrf *pbr_vrf); + +extern void pbr_vrf_init(void); +extern void pbr_vrf_terminate(void); +#endif diff --git a/pbrd/pbr_vty.c b/pbrd/pbr_vty.c new file mode 100644 index 0000000..60e215e --- /dev/null +++ b/pbrd/pbr_vty.c @@ -0,0 +1,1432 @@ +/* + * PBR - vty 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 "vty.h" +#include "command.h" +#include "prefix.h" +#include "vrf.h" +#include "nexthop.h" +#include "nexthop_group.h" +#include "nexthop_group_private.h" +#include "log.h" +#include "json.h" +#include "debug.h" +#include "pbr.h" + +#include "pbrd/pbr_nht.h" +#include "pbrd/pbr_map.h" +#include "pbrd/pbr_zebra.h" +#include "pbrd/pbr_vty.h" +#include "pbrd/pbr_debug.h" +#ifndef VTYSH_EXTRACT_PL +#include "pbrd/pbr_vty_clippy.c" +#endif + +DEFUN_NOSH(pbr_map, pbr_map_cmd, "pbr-map PBRMAP seq (1-700)", + "Create pbr-map or enter pbr-map command mode\n" + "The name of the PBR MAP\n" + "Sequence to insert in existing pbr-map entry\n" + "Sequence number\n") +{ + const char *pbrm_name = argv[1]->arg; + uint32_t seqno = atoi(argv[3]->arg); + struct pbr_map_sequence *pbrms; + + pbrms = pbrms_get(pbrm_name, seqno); + VTY_PUSH_CONTEXT(PBRMAP_NODE, pbrms); + + return CMD_SUCCESS; +} + +DEFUN_NOSH(no_pbr_map, no_pbr_map_cmd, "no pbr-map PBRMAP [seq (1-700)]", + NO_STR + "Delete pbr-map\n" + "The name of the PBR MAP\n" + "Sequence to delete from existing pbr-map entry\n" + "Sequence number\n") +{ + const char *pbrm_name = argv[2]->arg; + uint32_t seqno = 0; + struct pbr_map *pbrm = pbrm_find(pbrm_name); + struct pbr_map_sequence *pbrms; + struct listnode *node, *next_node; + + if (argc > 3) + seqno = atoi(argv[4]->arg); + + if (!pbrm) { + vty_out(vty, "pbr-map %s not found\n", pbrm_name); + return CMD_SUCCESS; + } + + for (ALL_LIST_ELEMENTS(pbrm->seqnumbers, node, next_node, pbrms)) { + if (seqno && pbrms->seqno != seqno) + continue; + + pbr_map_delete(pbrms); + } + + return CMD_SUCCESS; +} + +DEFPY(pbr_set_table_range, + pbr_set_table_range_cmd, + "pbr table range (10000-4294966272)$lb (10000-4294966272)$ub", + PBR_STR + "Set table ID range\n" + "Set table ID range\n" + "Lower bound for table ID range\n" + "Upper bound for table ID range\n") +{ + /* upper bound is 2^32 - 2^10 */ + int ret = CMD_WARNING; + const int minrange = 1000; + + /* validate given bounds */ + if (lb > ub) + vty_out(vty, "%% Lower bound must be less than upper bound\n"); + else if (ub - lb < minrange) + vty_out(vty, "%% Range breadth must be at least %d\n", minrange); + else { + ret = CMD_SUCCESS; + pbr_nht_set_tableid_range((uint32_t) lb, (uint32_t) ub); + } + + return ret; +} + +DEFPY(no_pbr_set_table_range, no_pbr_set_table_range_cmd, + "no pbr table range [(10000-4294966272)$lb (10000-4294966272)$ub]", + NO_STR + PBR_STR + "Set table ID range\n" + "Set table ID range\n" + "Lower bound for table ID range\n" + "Upper bound for table ID range\n") +{ + pbr_nht_set_tableid_range(PBR_NHT_DEFAULT_LOW_TABLEID, + PBR_NHT_DEFAULT_HIGH_TABLEID); + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_src, pbr_map_match_src_cmd, + "[no] match src-ip <A.B.C.D/M|X:X::X:X/M>$prefix", + NO_STR + "Match the rest of the command\n" + "Choose the src ip or ipv6 prefix to use\n" + "v4 Prefix\n" + "v6 Prefix\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (pbrms->dst && prefix->family != pbrms->dst->family) { + vty_out(vty, "Cannot mismatch families within match src/dst\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + pbrms->family = prefix->family; + + if (!no) { + if (pbrms->src) { + if (prefix_same(pbrms->src, prefix)) + return CMD_SUCCESS; + } else + pbrms->src = prefix_new(); + + prefix_copy(pbrms->src, prefix); + } else + prefix_free(&pbrms->src); + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_dst, pbr_map_match_dst_cmd, + "[no] match dst-ip <A.B.C.D/M|X:X::X:X/M>$prefix", + NO_STR + "Match the rest of the command\n" + "Choose the dst ip or ipv6 prefix to use\n" + "v4 Prefix\n" + "v6 Prefix\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (pbrms->src && prefix->family != pbrms->src->family) { + vty_out(vty, "Cannot mismatch families within match src/dst\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + pbrms->family = prefix->family; + + if (!no) { + if (pbrms->dst) { + if (prefix_same(pbrms->dst, prefix)) + return CMD_SUCCESS; + } else + pbrms->dst = prefix_new(); + + prefix_copy(pbrms->dst, prefix); + } else + prefix_free(&pbrms->dst); + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_ip_proto, pbr_map_match_ip_proto_cmd, + "[no] match ip-protocol [tcp|udp]$ip_proto", + NO_STR + "Match the rest of the command\n" + "Choose an ip-protocol\n" + "Match on tcp flows\n" + "Match on udp flows\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + struct protoent *p; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) { + p = getprotobyname(ip_proto); + if (!p) { + vty_out(vty, "Unable to convert %s to proto id\n", + ip_proto); + return CMD_WARNING; + } + + pbrms->ip_proto = p->p_proto; + } else + pbrms->ip_proto = 0; + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_src_port, pbr_map_match_src_port_cmd, + "[no] match src-port (1-65535)$port", + NO_STR + "Match the rest of the command\n" + "Choose the source port to use\n" + "The Source Port\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) { + if (pbrms->src_prt == port) + return CMD_SUCCESS; + else + pbrms->src_prt = port; + } else + pbrms->src_prt = 0; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_dst_port, pbr_map_match_dst_port_cmd, + "[no] match dst-port (1-65535)$port", + NO_STR + "Match the rest of the command\n" + "Choose the destination port to use\n" + "The Destination Port\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) { + if (pbrms->dst_prt == port) + return CMD_SUCCESS; + else + pbrms->dst_prt = port; + } else + pbrms->dst_prt = 0; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_dscp, pbr_map_match_dscp_cmd, + "[no] match dscp DSCP$dscp", + NO_STR + "Match the rest of the command\n" + "Match based on IP DSCP field\n" + "DSCP value (below 64) or standard codepoint name\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + char dscpname[100]; + uint8_t rawDscp; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + /* Discriminate dscp enums (cs0, cs1 etc.) and numbers */ + bool isANumber = true; + for (int i = 0; i < (int)strlen(dscp); i++) { + /* Letters are not numbers */ + if (!isdigit(dscp[i])) + isANumber = false; + + /* Lowercase the dscp enum (if needed) */ + if (isupper(dscp[i])) + dscpname[i] = tolower(dscp[i]); + else + dscpname[i] = dscp[i]; + } + dscpname[strlen(dscp)] = '\0'; + + if (isANumber) { + /* dscp passed is a regular number */ + long dscpAsNum = strtol(dscp, NULL, 0); + + if (dscpAsNum > PBR_DSFIELD_DSCP >> 2) { + /* Refuse to install on overflow */ + vty_out(vty, "dscp (%s) must be less than 64\n", dscp); + return CMD_WARNING_CONFIG_FAILED; + } + rawDscp = dscpAsNum; + } else { + /* check dscp if it is an enum like cs0 */ + rawDscp = pbr_map_decode_dscp_enum(dscpname); + if (rawDscp > PBR_DSFIELD_DSCP) { + vty_out(vty, "Invalid dscp value: %s\n", dscpname); + return CMD_WARNING_CONFIG_FAILED; + } + } + + if (!no) { + if (((pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2) == rawDscp) + return CMD_SUCCESS; + + /* Set the DSCP bits of the DSField */ + pbrms->dsfield = + (pbrms->dsfield & ~PBR_DSFIELD_DSCP) | (rawDscp << 2); + } else { + pbrms->dsfield &= ~PBR_DSFIELD_DSCP; + } + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_ecn, pbr_map_match_ecn_cmd, + "[no] match ecn (0-3)$ecn", + NO_STR + "Match the rest of the command\n" + "Match based on IP ECN field\n" + "Explicit Congestion Notification\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) { + if ((pbrms->dsfield & PBR_DSFIELD_ECN) == ecn) + return CMD_SUCCESS; + + /* Set the ECN bits of the DSField */ + pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_ECN) | ecn; + } else { + pbrms->dsfield &= ~PBR_DSFIELD_ECN; + } + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_match_mark, pbr_map_match_mark_cmd, + "[no] match mark (1-4294967295)$mark", + NO_STR + "Match the rest of the command\n" + "Choose the mark value to use\n" + "mark\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + +#ifndef GNU_LINUX + vty_out(vty, "pbr marks are not supported on this platform\n"); + return CMD_WARNING_CONFIG_FAILED; +#endif + + if (!no) { + if (pbrms->mark) + if (pbrms->mark == (uint32_t)mark) + return CMD_SUCCESS; + + pbrms->mark = (uint32_t)mark; + } else + pbrms->mark = 0; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +static void pbrms_clear_set_vrf_config(struct pbr_map_sequence *pbrms) +{ + if (pbrms->vrf_lookup || pbrms->vrf_unchanged) { + pbr_map_delete_vrf(pbrms); + pbrms->vrf_name[0] = '\0'; + pbrms->vrf_lookup = false; + pbrms->vrf_unchanged = false; + } +} + +static void pbrms_clear_set_nhg_config(struct pbr_map_sequence *pbrms) +{ + if (pbrms->nhgrp_name) + pbr_map_delete_nexthops(pbrms); +} + +static void pbrms_clear_set_nexthop_config(struct pbr_map_sequence *pbrms) +{ + if (pbrms->nhg) + pbr_nht_delete_individual_nexthop(pbrms); +} + +static void pbrms_clear_set_config(struct pbr_map_sequence *pbrms) +{ + pbrms_clear_set_vrf_config(pbrms); + pbrms_clear_set_nhg_config(pbrms); + pbrms_clear_set_nexthop_config(pbrms); + + pbrms->nhs_installed = false; +} + + +DEFPY(pbr_map_action_queue_id, pbr_map_action_queue_id_cmd, + "[no] set queue-id <(1-65535)$queue_id>", + NO_STR + "Set the rest of the command\n" + "Set based on egress port queue id\n" + "A valid value in range 1..65535 \n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) + pbrms->action_queue_id = queue_id; + else if ((uint32_t)queue_id == pbrms->action_queue_id) + pbrms->action_queue_id = PBR_MAP_UNDEFINED_QUEUE_ID; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_action_pcp, pbr_map_action_pcp_cmd, "[no] set pcp <(0-7)$pcp>", + NO_STR + "Set the rest of the command\n" + "Set based on 802.1p Priority Code Point (PCP) value\n" + "A valid value in range 0..7\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) + pbrms->action_pcp = pcp; + else if (pcp == pbrms->action_pcp) + pbrms->action_pcp = 0; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_action_vlan_id, pbr_map_action_vlan_id_cmd, + "[no] set vlan <(1-4094)$vlan_id>", + NO_STR + "Set the rest of the command\n" + "Set action for VLAN tagging\n" + "A valid value in range 1..4094\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) + pbrms->action_vlan_id = vlan_id; + else if (pbrms->action_vlan_id == vlan_id) + pbrms->action_vlan_id = 0; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_action_strip_vlan, pbr_map_action_strip_vlan_cmd, + "[no] strip vlan", + NO_STR + "Strip the vlan tags from frame\n" + "Strip any inner vlan tag \n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (!no) + pbrms->action_vlan_flags = PBR_MAP_STRIP_INNER_ANY; + else + pbrms->action_vlan_flags = 0; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + + +DEFPY(pbr_map_nexthop_group, pbr_map_nexthop_group_cmd, + "set nexthop-group NHGNAME$name", + "Set for the PBR-MAP\n" + "nexthop-group to use\n" + "The name of the nexthop-group\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + struct nexthop_group_cmd *nhgc; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + nhgc = nhgc_find(name); + if (!nhgc) { + vty_out(vty, "Specified nexthop-group %s does not exist\n", + name); + vty_out(vty, + "PBR-MAP will not be applied until it is created\n"); + } + + if (pbrms->nhgrp_name && strcmp(name, pbrms->nhgrp_name) == 0) + return CMD_SUCCESS; + + /* This is new/replacement config */ + pbrms_clear_set_config(pbrms); + + pbr_nht_set_seq_nhg(pbrms, name); + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(no_pbr_map_nexthop_group, no_pbr_map_nexthop_group_cmd, + "no set nexthop-group [NHGNAME$name]", + NO_STR + "Set for the PBR-MAP\n" + "nexthop-group to use\n" + "The name of the nexthop-group\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + pbrms_clear_set_config(pbrms); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_nexthop, pbr_map_nexthop_cmd, + "set nexthop\ + <\ + <A.B.C.D|X:X::X:X>$addr [INTERFACE$intf]\ + |INTERFACE$intf\ + >\ + [nexthop-vrf NAME$vrf_name]", + "Set for the PBR-MAP\n" + "Specify one of the nexthops in this map\n" + "v4 Address\n" + "v6 Address\n" + "Interface to use\n" + "Interface to use\n" + "If the nexthop is in a different vrf tell us\n" + "The nexthop-vrf Name\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + struct vrf *vrf; + struct nexthop nhop; + struct nexthop *nh = NULL; + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + if (vrf_name) + vrf = vrf_lookup_by_name(vrf_name); + else + vrf = vrf_lookup_by_id(VRF_DEFAULT); + + if (!vrf) { + vty_out(vty, "Specified VRF: %s is non-existent\n", vrf_name); + return CMD_WARNING_CONFIG_FAILED; + } + + memset(&nhop, 0, sizeof(nhop)); + nhop.vrf_id = vrf->vrf_id; + + if (intf) { + struct interface *ifp = NULL; + struct interface *ifptmp; + struct vrf *vrftmp; + int count = 0; + + if (vrf_is_backend_netns() && vrf_name) { + ifp = if_lookup_by_name_vrf(intf, vrf); + } else { + RB_FOREACH (vrftmp, vrf_name_head, &vrfs_by_name) { + ifptmp = if_lookup_by_name_vrf(intf, vrftmp); + if (ifptmp) { + ifp = ifptmp; + count++; + if (!vrf_is_backend_netns()) + break; + } + } + } + + if (!ifp) { + vty_out(vty, "Specified Intf %s does not exist\n", + intf); + return CMD_WARNING_CONFIG_FAILED; + } + if (count > 1) { + vty_out(vty, + "Specified Intf %s exists in multiple VRFs\n", + intf); + vty_out(vty, "You must specify the nexthop-vrf\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (ifp->vrf->vrf_id != vrf->vrf_id) + vty_out(vty, + "Specified Intf %s is not in vrf %s but is in vrf %s, using actual vrf\n", + ifp->name, vrf->name, ifp->vrf->name); + nhop.ifindex = ifp->ifindex; + nhop.vrf_id = ifp->vrf->vrf_id; + } + + if (addr) { + if (addr->sa.sa_family == AF_INET) { + nhop.gate.ipv4.s_addr = addr->sin.sin_addr.s_addr; + if (intf) + nhop.type = NEXTHOP_TYPE_IPV4_IFINDEX; + else + nhop.type = NEXTHOP_TYPE_IPV4; + } else { + nhop.gate.ipv6 = addr->sin6.sin6_addr; + if (intf) + nhop.type = NEXTHOP_TYPE_IPV6_IFINDEX; + else { + if (IN6_IS_ADDR_LINKLOCAL(&nhop.gate.ipv6)) { + vty_out(vty, + "Specified a v6 LL with no interface, rejecting\n"); + return CMD_WARNING_CONFIG_FAILED; + } + nhop.type = NEXTHOP_TYPE_IPV6; + } + } + } else + nhop.type = NEXTHOP_TYPE_IFINDEX; + + if (pbrms->nhg) + nh = nexthop_exists(pbrms->nhg, &nhop); + + if (nh) /* Same config re-entered */ + goto done; + + /* This is new/replacement config */ + pbrms_clear_set_config(pbrms); + + pbr_nht_add_individual_nexthop(pbrms, &nhop); + + pbr_map_check(pbrms, true); + +done: + 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); + } + + return CMD_SUCCESS; +} + +DEFPY(no_pbr_map_nexthop, no_pbr_map_nexthop_cmd, + "no set nexthop\ + [<\ + <A.B.C.D|X:X::X:X>$addr [INTERFACE$intf]\ + |INTERFACE$intf\ + >\ + [nexthop-vrf NAME$vrf_name]]", + NO_STR + "Set for the PBR-MAP\n" + "Specify one of the nexthops in this map\n" + "v4 Address\n" + "v6 Address\n" + "Interface to use\n" + "Interface to use\n" + "If the nexthop is in a different vrf tell us\n" + "The nexthop-vrf Name\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + pbrms_clear_set_config(pbrms); + + return CMD_SUCCESS; +} + +DEFPY(pbr_map_vrf, pbr_map_vrf_cmd, + "set vrf <NAME$vrf_name|unchanged>", + "Set for the PBR-MAP\n" + "Specify the VRF for this map\n" + "The VRF Name\n" + "Use the interface's VRF for lookup\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + /* + * If an equivalent set vrf * exists, just return success. + */ + if (vrf_name && pbrms->vrf_lookup + && strncmp(pbrms->vrf_name, vrf_name, sizeof(pbrms->vrf_name)) == 0) + return CMD_SUCCESS; + else if (!vrf_name && pbrms->vrf_unchanged) /* Unchanged already set */ + return CMD_SUCCESS; + + if (vrf_name && !pbr_vrf_lookup_by_name(vrf_name)) { + vty_out(vty, "Specified: %s is non-existent\n", vrf_name); + return CMD_WARNING_CONFIG_FAILED; + } + + /* This is new/replacement config */ + pbrms_clear_set_config(pbrms); + + if (vrf_name) { + pbrms->vrf_lookup = true; + strlcpy(pbrms->vrf_name, vrf_name, sizeof(pbrms->vrf_name)); + } else + pbrms->vrf_unchanged = true; + + pbr_map_check(pbrms, true); + + return CMD_SUCCESS; +} + +DEFPY(no_pbr_map_vrf, no_pbr_map_vrf_cmd, + "no set vrf [<NAME$vrf_name|unchanged>]", + NO_STR + "Set for the PBR-MAP\n" + "Specify the VRF for this map\n" + "The VRF Name\n" + "Use the interface's VRF for lookup\n") +{ + struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); + + if (!pbrms) + return CMD_WARNING_CONFIG_FAILED; + + pbrms_clear_set_config(pbrms); + + return CMD_SUCCESS; +} + +DEFPY (pbr_policy, + pbr_policy_cmd, + "[no] pbr-policy PBRMAP$mapname", + NO_STR + "Policy to use\n" + "Name of the pbr-map to apply\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct pbr_map *pbrm, *old_pbrm; + struct pbr_interface *pbr_ifp = ifp->info; + + old_pbrm = NULL; + pbrm = pbrm_find(mapname); + + if (!pbr_ifp) { + /* we don't want one and we don't have one, so... */ + if (no) + return CMD_SUCCESS; + + /* Some one could have fat fingered the interface name */ + pbr_ifp = pbr_if_new(ifp); + } + + if (no) { + if (strcmp(pbr_ifp->mapname, mapname) == 0) { + pbr_ifp->mapname[0] = '\0'; + if (pbrm) + pbr_map_interface_delete(pbrm, ifp); + } + } else { + if (strcmp(pbr_ifp->mapname, "") != 0) { + old_pbrm = pbrm_find(pbr_ifp->mapname); + + /* + * So if we have an old pbrm we should only + * delete it if we are actually deleting and + * moving to a new pbrm + */ + if (old_pbrm && old_pbrm != pbrm) + pbr_map_interface_delete(old_pbrm, ifp); + } + snprintf(pbr_ifp->mapname, sizeof(pbr_ifp->mapname), + "%s", mapname); + + /* + * So only reinstall if the old_pbrm and this pbrm are + * different. + */ + if (pbrm && pbrm != old_pbrm) + pbr_map_add_interface(pbrm, ifp); + } + + return CMD_SUCCESS; +} + +DEFPY (show_pbr, + show_pbr_cmd, + "show pbr", + SHOW_STR + PBR_STR) +{ + pbr_nht_write_table_range(vty); + pbr_nht_write_rule_range(vty); + + return CMD_SUCCESS; +} + +static void +pbrms_nexthop_group_write_individual_nexthop( + struct vty *vty, const struct pbr_map_sequence *pbrms) +{ + struct pbr_nexthop_group_cache find; + struct pbr_nexthop_group_cache *pnhgc; + struct pbr_nexthop_cache lookup; + struct pbr_nexthop_cache *pnhc; + + memset(&find, 0, sizeof(find)); + strlcpy(find.name, pbrms->internal_nhg_name, sizeof(find.name)); + + pnhgc = hash_lookup(pbr_nhg_hash, &find); + assert(pnhgc); + + lookup.nexthop = *pbrms->nhg->nexthop; + pnhc = hash_lookup(pnhgc->nhh, &lookup); + + nexthop_group_write_nexthop_simple( + vty, pbrms->nhg->nexthop, + pnhc->nexthop.ifindex != 0 ? pnhc->intf_name : NULL); + if (pnhc->nexthop.vrf_id != VRF_DEFAULT) + vty_out(vty, " nexthop-vrf %s", pnhc->vrf_name); + + vty_out(vty, "\n"); +} + +static void vty_show_pbrms(struct vty *vty, + const struct pbr_map_sequence *pbrms, bool detail) +{ + char rbuf[64]; + + if (pbrms->reason) + pbr_map_reason_string(pbrms->reason, rbuf, sizeof(rbuf)); + + vty_out(vty, " Seq: %u rule: %u\n", pbrms->seqno, pbrms->ruleno); + + if (detail) + vty_out(vty, " Installed: %" PRIu64 "(%u) Reason: %s\n", + pbrms->installed, pbrms->unique, + pbrms->reason ? rbuf : "Valid"); + else + vty_out(vty, " Installed: %s Reason: %s\n", + pbrms->installed ? "yes" : "no", + pbrms->reason ? rbuf : "Valid"); + + if (pbrms->ip_proto) { + struct protoent *p; + + p = getprotobynumber(pbrms->ip_proto); + vty_out(vty, " IP Protocol Match: %s\n", p->p_name); + } + + if (pbrms->src) + vty_out(vty, " SRC IP Match: %pFX\n", pbrms->src); + if (pbrms->dst) + vty_out(vty, " DST IP Match: %pFX\n", pbrms->dst); + if (pbrms->src_prt) + vty_out(vty, " SRC Port Match: %u\n", pbrms->src_prt); + if (pbrms->dst_prt) + vty_out(vty, " DST Port Match: %u\n", pbrms->dst_prt); + if (pbrms->dsfield & PBR_DSFIELD_DSCP) + vty_out(vty, " DSCP Match: %u\n", + (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + if (pbrms->dsfield & PBR_DSFIELD_ECN) + vty_out(vty, " ECN Match: %u\n", + pbrms->dsfield & PBR_DSFIELD_ECN); + if (pbrms->mark) + vty_out(vty, " MARK Match: %u\n", pbrms->mark); + + if (pbrms->action_queue_id != PBR_MAP_UNDEFINED_QUEUE_ID) + vty_out(vty, " Set Queue ID %u\n", + pbrms->action_queue_id); + + if (pbrms->action_vlan_id != 0) + vty_out(vty, " Set VLAN ID %u\n", pbrms->action_vlan_id); + if (pbrms->action_vlan_flags == PBR_MAP_STRIP_INNER_ANY) + vty_out(vty, " Strip VLAN ID\n"); + if (pbrms->action_pcp) + vty_out(vty, " Set PCP %u\n", pbrms->action_pcp); + + + if (pbrms->nhgrp_name) { + vty_out(vty, " Nexthop-Group: %s\n", pbrms->nhgrp_name); + + if (detail) + vty_out(vty, + " Installed: %u(%d) Tableid: %u\n", + pbrms->nhs_installed, + pbr_nht_get_installed(pbrms->nhgrp_name), + pbr_nht_get_table(pbrms->nhgrp_name)); + else + vty_out(vty, " Installed: %s Tableid: %u\n", + pbr_nht_get_installed(pbrms->nhgrp_name) ? "yes" + : "no", + pbr_nht_get_table(pbrms->nhgrp_name)); + + } else if (pbrms->nhg) { + vty_out(vty, " "); + pbrms_nexthop_group_write_individual_nexthop(vty, pbrms); + if (detail) + vty_out(vty, + " Installed: %u(%d) Tableid: %u\n", + pbrms->nhs_installed, + pbr_nht_get_installed(pbrms->internal_nhg_name), + pbr_nht_get_table(pbrms->internal_nhg_name)); + else + vty_out(vty, " Installed: %s Tableid: %u\n", + pbr_nht_get_installed(pbrms->internal_nhg_name) + ? "yes" + : "no", + pbr_nht_get_table(pbrms->internal_nhg_name)); + + } else if (pbrms->vrf_unchanged) { + vty_out(vty, " VRF Unchanged (use interface vrf)\n"); + } else if (pbrms->vrf_lookup) { + vty_out(vty, " VRF Lookup: %s\n", pbrms->vrf_name); + } else { + vty_out(vty, " Nexthop-Group: Unknown Installed: no\n"); + } +} + +static void vty_json_pbrms(json_object *j, struct vty *vty, + const struct pbr_map_sequence *pbrms) +{ + json_object *jpbrm, *nexthop_group; + char *nhg_name = pbrms->nhgrp_name ? pbrms->nhgrp_name + : pbrms->internal_nhg_name; + char rbuf[64]; + + jpbrm = json_object_new_object(); + + json_object_int_add(jpbrm, "id", pbrms->unique); + + if (pbrms->reason) + pbr_map_reason_string(pbrms->reason, rbuf, sizeof(rbuf)); + + json_object_int_add(jpbrm, "sequenceNumber", pbrms->seqno); + json_object_int_add(jpbrm, "ruleNumber", pbrms->ruleno); + json_object_boolean_add(jpbrm, "vrfUnchanged", pbrms->vrf_unchanged); + json_object_boolean_add(jpbrm, "installed", + pbr_nht_get_installed(nhg_name)); + json_object_string_add(jpbrm, "installedReason", + pbrms->reason ? rbuf : "Valid"); + + if (nhg_name) { + nexthop_group = json_object_new_object(); + + json_object_int_add(nexthop_group, "tableId", + pbr_nht_get_table(nhg_name)); + json_object_string_add(nexthop_group, "name", nhg_name); + json_object_boolean_add(nexthop_group, "installed", + pbr_nht_get_installed(nhg_name)); + json_object_int_add(nexthop_group, "installedInternally", + pbrms->nhs_installed); + + json_object_object_add(jpbrm, "nexthopGroup", nexthop_group); + } + + if (pbrms->vrf_lookup) + json_object_string_add(jpbrm, "vrfName", pbrms->vrf_name); + + if (pbrms->src) + json_object_string_addf(jpbrm, "matchSrc", "%pFX", pbrms->src); + if (pbrms->dst) + json_object_string_addf(jpbrm, "matchDst", "%pFX", pbrms->dst); + if (pbrms->mark) + json_object_int_add(jpbrm, "matchMark", pbrms->mark); + if (pbrms->dsfield & PBR_DSFIELD_DSCP) + json_object_int_add(jpbrm, "matchDscp", + (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + if (pbrms->dsfield & PBR_DSFIELD_ECN) + json_object_int_add(jpbrm, "matchEcn", + pbrms->dsfield & PBR_DSFIELD_ECN); + + json_object_array_add(j, jpbrm); +} + +static void vty_show_pbr_map(struct vty *vty, const struct pbr_map *pbrm, + bool detail) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + + vty_out(vty, " pbr-map %s valid: %s\n", pbrm->name, + pbrm->valid ? "yes" : "no"); + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + vty_show_pbrms(vty, pbrms, detail); +} + +static void vty_json_pbr_map(json_object *j, struct vty *vty, + const struct pbr_map *pbrm) +{ + struct pbr_map_sequence *pbrms; + struct listnode *node; + json_object *jpbrms; + + json_object_string_add(j, "name", pbrm->name); + json_object_boolean_add(j, "valid", pbrm->valid); + + jpbrms = json_object_new_array(); + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + vty_json_pbrms(jpbrms, vty, pbrms); + + json_object_object_add(j, "policies", jpbrms); +} + +DEFPY (show_pbr_map, + show_pbr_map_cmd, + "show pbr map [NAME$name] [detail$detail|json$json]", + SHOW_STR + PBR_STR + "PBR Map\n" + "PBR Map Name\n" + "Detailed information\n" + JSON_STR) +{ + struct pbr_map *pbrm; + json_object *j = NULL; + + if (json) + j = json_object_new_array(); + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) { + json_object *this_map = NULL; + if (name && strcmp(name, pbrm->name) != 0) + continue; + + if (j) + this_map = json_object_new_object(); + + if (this_map) { + vty_json_pbr_map(this_map, vty, pbrm); + + json_object_array_add(j, this_map); + continue; + } + + vty_show_pbr_map(vty, pbrm, detail); + } + + if (j) + vty_json(vty, j); + + return CMD_SUCCESS; +} + +DEFPY(show_pbr_nexthop_group, + show_pbr_nexthop_group_cmd, + "show pbr nexthop-groups [WORD$word] [json$json]", + SHOW_STR + PBR_STR + "Nexthop Groups\n" + "Optional Name of the nexthop group\n" + JSON_STR) +{ + json_object *j = NULL; + + if (json) + j = json_object_new_array(); + + if (j) { + pbr_nht_json_nexthop_group(j, word); + + vty_json(vty, j); + } else + pbr_nht_show_nexthop_group(vty, word); + + + return CMD_SUCCESS; +} + +DEFPY (show_pbr_interface, + show_pbr_interface_cmd, + "show pbr interface [NAME$name] [json$json]", + SHOW_STR + PBR_STR + "PBR Interface\n" + "PBR Interface Name\n" + JSON_STR) +{ + struct interface *ifp; + struct vrf *vrf; + struct pbr_interface *pbr_ifp; + json_object *j = NULL; + + if (json) + j = json_object_new_array(); + + RB_FOREACH(vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES(vrf, ifp) { + struct pbr_map *pbrm; + json_object *this_iface = NULL; + + if (j) + this_iface = json_object_new_object(); + + if (!ifp->info) { + json_object_free(this_iface); + continue; + } + + if (name && strcmp(ifp->name, name) != 0) { + json_object_free(this_iface); + continue; + } + + pbr_ifp = ifp->info; + + if (strcmp(pbr_ifp->mapname, "") == 0) { + json_object_free(this_iface); + continue; + } + + pbrm = pbrm_find(pbr_ifp->mapname); + + if (this_iface) { + json_object_string_add(this_iface, "name", + ifp->name); + json_object_int_add(this_iface, "index", + ifp->ifindex); + json_object_string_add(this_iface, "policy", + pbr_ifp->mapname); + json_object_boolean_add(this_iface, "valid", + pbrm); + + json_object_array_add(j, this_iface); + continue; + } + + vty_out(vty, " %s(%d) with pbr-policy %s", ifp->name, + ifp->ifindex, pbr_ifp->mapname); + if (!pbrm) + vty_out(vty, " (map doesn't exist)"); + vty_out(vty, "\n"); + } + } + + if (j) + vty_json(vty, j); + + return CMD_SUCCESS; +} + +/* PBR debugging CLI ------------------------------------------------------- */ + +static struct cmd_node debug_node = { + .name = "debug", + .node = DEBUG_NODE, + .prompt = "", + .config_write = pbr_debug_config_write, +}; + +DEFPY(debug_pbr, + debug_pbr_cmd, + "[no] debug pbr [{map$map|zebra$zebra|nht$nht|events$events}]", + NO_STR + DEBUG_STR + PBR_STR + "Policy maps\n" + "PBRD <-> Zebra communications\n" + "Nexthop tracking\n" + "Events\n") +{ + uint32_t mode = DEBUG_NODE2MODE(vty->node); + + if (map) + DEBUG_MODE_SET(&pbr_dbg_map, mode, !no); + if (zebra) + DEBUG_MODE_SET(&pbr_dbg_zebra, mode, !no); + if (nht) + DEBUG_MODE_SET(&pbr_dbg_nht, mode, !no); + if (events) + DEBUG_MODE_SET(&pbr_dbg_event, mode, !no); + + /* no specific debug --> act on all of them */ + if (strmatch(argv[argc - 1]->text, "pbr")) + pbr_debug_set_all(mode, !no); + + return CMD_SUCCESS; +} + +DEFUN_NOSH(show_debugging_pbr, + show_debugging_pbr_cmd, + "show debugging [pbr]", + SHOW_STR + DEBUG_STR + PBR_STR) +{ + vty_out(vty, "PBR debugging status:\n"); + + pbr_debug_config_write_helper(vty, false); + + return CMD_SUCCESS; +} + +/* ------------------------------------------------------------------------- */ + + +static int pbr_interface_config_write(struct vty *vty) +{ + struct interface *ifp; + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + FOR_ALL_INTERFACES (vrf, ifp) { + if_vty_config_start(vty, ifp); + + if (ifp->desc) + vty_out(vty, " description %s\n", ifp->desc); + + pbr_map_write_interfaces(vty, ifp); + + if_vty_config_end(vty); + } + } + + return 1; +} + +static int pbr_vty_map_config_write(struct vty *vty); +/* PBR map node structure. */ +static struct cmd_node pbr_map_node = { + .name = "pbr-map", + .node = PBRMAP_NODE, + .parent_node = CONFIG_NODE, + .prompt = "%s(config-pbr-map)# ", + .config_write = pbr_vty_map_config_write, +}; + +static int pbr_vty_map_config_write_sequence(struct vty *vty, + struct pbr_map *pbrm, + struct pbr_map_sequence *pbrms) +{ + vty_out(vty, "pbr-map %s seq %u\n", pbrm->name, pbrms->seqno); + + if (pbrms->src) + vty_out(vty, " match src-ip %pFX\n", pbrms->src); + + if (pbrms->dst) + vty_out(vty, " match dst-ip %pFX\n", pbrms->dst); + + if (pbrms->src_prt) + vty_out(vty, " match src-port %u\n", pbrms->src_prt); + if (pbrms->dst_prt) + vty_out(vty, " match dst-port %u\n", pbrms->dst_prt); + + if (pbrms->ip_proto) { + struct protoent *p; + + p = getprotobynumber(pbrms->ip_proto); + vty_out(vty, " match ip-protocol %s\n", p->p_name); + } + + if (pbrms->dsfield & PBR_DSFIELD_DSCP) + vty_out(vty, " match dscp %u\n", + (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); + + if (pbrms->dsfield & PBR_DSFIELD_ECN) + vty_out(vty, " match ecn %u\n", + pbrms->dsfield & PBR_DSFIELD_ECN); + + if (pbrms->mark) + vty_out(vty, " match mark %u\n", pbrms->mark); + + + if (pbrms->action_queue_id != PBR_MAP_UNDEFINED_QUEUE_ID) + vty_out(vty, " set queue-id %d\n", pbrms->action_queue_id); + + if (pbrms->action_pcp) + vty_out(vty, " set pcp %d\n", pbrms->action_pcp); + + if (pbrms->action_vlan_id) + vty_out(vty, " set vlan %u\n", pbrms->action_vlan_id); + + if (pbrms->action_vlan_flags == PBR_MAP_STRIP_INNER_ANY) + vty_out(vty, " strip vlan any\n"); + + if (pbrms->vrf_unchanged) + vty_out(vty, " set vrf unchanged\n"); + + if (pbrms->vrf_lookup) + vty_out(vty, " set vrf %s\n", pbrms->vrf_name); + + if (pbrms->nhgrp_name) + vty_out(vty, " set nexthop-group %s\n", pbrms->nhgrp_name); + + if (pbrms->nhg) { + vty_out(vty, " set "); + pbrms_nexthop_group_write_individual_nexthop(vty, pbrms); + } + + vty_out(vty, "exit\n"); + vty_out(vty, "!\n"); + return 1; +} + +static int pbr_vty_map_config_write(struct vty *vty) +{ + struct pbr_map *pbrm; + + pbr_nht_write_table_range(vty); + pbr_nht_write_rule_range(vty); + + RB_FOREACH(pbrm, pbr_map_entry_head, &pbr_maps) { + struct pbr_map_sequence *pbrms; + struct listnode *node; + + for (ALL_LIST_ELEMENTS_RO(pbrm->seqnumbers, node, pbrms)) + pbr_vty_map_config_write_sequence(vty, pbrm, pbrms); + } + + return 1; +} + +static void pbr_map_completer(vector comps, struct cmd_token *token) +{ + struct pbr_map *pbrm; + + RB_FOREACH (pbrm, pbr_map_entry_head, &pbr_maps) + vector_set(comps, XSTRDUP(MTYPE_COMPLETION, pbrm->name)); +} + +static const struct cmd_variable_handler pbr_map_name[] = { + { + .tokenname = "PBRMAP", .completions = pbr_map_completer, + }, + { + .completions = NULL + } +}; + +extern struct zebra_privs_t pbr_privs; + +void pbr_vty_init(void) +{ + cmd_variable_handler_register(pbr_map_name); + + vrf_cmd_init(NULL); + + if_cmd_init(pbr_interface_config_write); + + install_node(&pbr_map_node); + + /* debug */ + install_node(&debug_node); + install_element(ENABLE_NODE, &debug_pbr_cmd); + install_element(CONFIG_NODE, &debug_pbr_cmd); + install_element(ENABLE_NODE, &show_debugging_pbr_cmd); + + install_default(PBRMAP_NODE); + + install_element(CONFIG_NODE, &pbr_map_cmd); + install_element(CONFIG_NODE, &no_pbr_map_cmd); + install_element(CONFIG_NODE, &pbr_set_table_range_cmd); + install_element(CONFIG_NODE, &no_pbr_set_table_range_cmd); + install_element(INTERFACE_NODE, &pbr_policy_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_ip_proto_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_src_port_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_dst_port_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_src_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_dst_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_dscp_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_ecn_cmd); + install_element(PBRMAP_NODE, &pbr_map_match_mark_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_queue_id_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_strip_vlan_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_vlan_id_cmd); + install_element(PBRMAP_NODE, &pbr_map_action_pcp_cmd); + install_element(PBRMAP_NODE, &pbr_map_nexthop_group_cmd); + install_element(PBRMAP_NODE, &no_pbr_map_nexthop_group_cmd); + install_element(PBRMAP_NODE, &pbr_map_nexthop_cmd); + install_element(PBRMAP_NODE, &no_pbr_map_nexthop_cmd); + install_element(PBRMAP_NODE, &pbr_map_vrf_cmd); + install_element(PBRMAP_NODE, &no_pbr_map_vrf_cmd); + install_element(VIEW_NODE, &show_pbr_cmd); + install_element(VIEW_NODE, &show_pbr_map_cmd); + install_element(VIEW_NODE, &show_pbr_interface_cmd); + install_element(VIEW_NODE, &show_pbr_nexthop_group_cmd); +} diff --git a/pbrd/pbr_vty.h b/pbrd/pbr_vty.h new file mode 100644 index 0000000..6e345fd --- /dev/null +++ b/pbrd/pbr_vty.h @@ -0,0 +1,24 @@ +/* + * VTY library for PBR + * 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 + */ +#ifndef __PBR_VTY_H__ +#define __PBR_VTY_H__ + +extern void pbr_vty_init(void); +#endif diff --git a/pbrd/pbr_zebra.c b/pbrd/pbr_zebra.c new file mode 100644 index 0000000..4506dc1 --- /dev/null +++ b/pbrd/pbr_zebra.c @@ -0,0 +1,616 @@ +/* + * Zebra connect 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 "thread.h" +#include "command.h" +#include "network.h" +#include "prefix.h" +#include "routemap.h" +#include "table.h" +#include "stream.h" +#include "memory.h" +#include "zclient.h" +#include "filter.h" +#include "plist.h" +#include "log.h" +#include "nexthop.h" +#include "nexthop_group.h" + +#include "pbr_nht.h" +#include "pbr_map.h" +#include "pbr_memory.h" +#include "pbr_zebra.h" +#include "pbr_debug.h" +#include "pbr_vrf.h" + +DEFINE_MTYPE_STATIC(PBRD, PBR_INTERFACE, "PBR Interface"); + +/* Zebra structure to hold current status. */ +struct zclient *zclient; + +struct pbr_interface *pbr_if_new(struct interface *ifp) +{ + struct pbr_interface *pbr_ifp; + + assert(ifp); + assert(!ifp->info); + + pbr_ifp = XCALLOC(MTYPE_PBR_INTERFACE, sizeof(*pbr_ifp)); + + ifp->info = pbr_ifp; + return pbr_ifp; +} + +void pbr_if_del(struct interface *ifp) +{ + XFREE(MTYPE_PBR_INTERFACE, ifp->info); +} + +/* Interface addition message from zebra. */ +int pbr_ifp_create(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s", __func__, ifp->name); + + if (!ifp->info) + pbr_if_new(ifp); + + pbr_nht_interface_update(ifp); + /* Update nexthops tracked from a `set nexthop` command */ + pbr_nht_nexthop_interface_update(ifp); + + pbr_map_policy_interface_update(ifp, true); + + return 0; +} + +int pbr_ifp_destroy(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s", __func__, ifp->name); + + pbr_map_policy_interface_update(ifp, false); + + return 0; +} + +static int interface_address_add(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + char buf[PREFIX_STRLEN]; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + DEBUGD(&pbr_dbg_zebra, "%s: %s added %s", __func__, + c ? c->ifp->name : "Unknown", + c ? prefix2str(c->address, buf, sizeof(buf)) : "Unknown"); + + return 0; +} + +static int interface_address_delete(ZAPI_CALLBACK_ARGS) +{ + struct connected *c; + + c = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id); + + if (!c) + return 0; + + DEBUGD(&pbr_dbg_zebra, "%s: %s deleted %pFX", __func__, c->ifp->name, + c->address); + + connected_free(&c); + return 0; +} + +int pbr_ifp_up(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s is up", __func__, ifp->name); + + pbr_nht_nexthop_interface_update(ifp); + + return 0; +} + +int pbr_ifp_down(struct interface *ifp) +{ + DEBUGD(&pbr_dbg_zebra, "%s: %s is down", __func__, ifp->name); + + pbr_nht_nexthop_interface_update(ifp); + + return 0; +} + +static int interface_vrf_update(ZAPI_CALLBACK_ARGS) +{ + struct interface *ifp; + vrf_id_t new_vrf_id; + + ifp = zebra_interface_vrf_update_read(zclient->ibuf, vrf_id, + &new_vrf_id); + + if (!ifp) { + DEBUGD(&pbr_dbg_zebra, "%s: VRF change interface not found", + __func__); + + return 0; + } + + DEBUGD(&pbr_dbg_zebra, "%s: %s VRF change %u -> %u", __func__, + ifp->name, vrf_id, new_vrf_id); + + if_update_to_new_vrf(ifp, new_vrf_id); + + return 0; +} + +static int route_notify_owner(ZAPI_CALLBACK_ARGS) +{ + struct prefix p; + enum zapi_route_notify_owner note; + uint32_t table_id; + + if (!zapi_route_notify_decode(zclient->ibuf, &p, &table_id, ¬e, + NULL, NULL)) + return -1; + + switch (note) { + case ZAPI_ROUTE_FAIL_INSTALL: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route install failure for table: %u", + __func__, &p, table_id); + break; + case ZAPI_ROUTE_BETTER_ADMIN_WON: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route better admin distance won for table: %u", + __func__, &p, table_id); + break; + case ZAPI_ROUTE_INSTALLED: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route installed succeeded for table: %u", + __func__, &p, table_id); + pbr_nht_route_installed_for_table(table_id); + break; + case ZAPI_ROUTE_REMOVED: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route Removed succeeded for table: %u", + __func__, &p, table_id); + pbr_nht_route_removed_for_table(table_id); + break; + case ZAPI_ROUTE_REMOVE_FAIL: + DEBUGD(&pbr_dbg_zebra, + "%s: [%pFX] Route remove fail for table: %u", __func__, + &p, table_id); + break; + } + + return 0; +} + +static int rule_notify_owner(ZAPI_CALLBACK_ARGS) +{ + uint32_t seqno, priority, unique; + enum zapi_rule_notify_owner note; + struct pbr_map_sequence *pbrms; + struct pbr_map_interface *pmi; + char ifname[INTERFACE_NAMSIZ + 1]; + uint64_t installed; + + if (!zapi_rule_notify_decode(zclient->ibuf, &seqno, &priority, &unique, + ifname, ¬e)) + return -1; + + pmi = NULL; + pbrms = pbrms_lookup_unique(unique, ifname, &pmi); + if (!pbrms) { + DEBUGD(&pbr_dbg_zebra, + "%s: Failure to lookup pbrms based upon %u", __func__, + unique); + return 0; + } + + installed = 1 << pmi->install_bit; + + switch (note) { + case ZAPI_RULE_FAIL_INSTALL: + pbrms->installed &= ~installed; + break; + case ZAPI_RULE_INSTALLED: + pbrms->installed |= installed; + break; + case ZAPI_RULE_FAIL_REMOVE: + /* Don't change state on rule removal failure */ + break; + case ZAPI_RULE_REMOVED: + pbrms->installed &= ~installed; + break; + } + + DEBUGD(&pbr_dbg_zebra, "%s: Received %s: %" PRIu64, __func__, + zapi_rule_notify_owner2str(note), pbrms->installed); + + pbr_map_final_interface_deletion(pbrms->parent, pmi); + + return 0; +} + +static void zebra_connected(struct zclient *zclient) +{ + DEBUGD(&pbr_dbg_zebra, "%s: Registering for fun and profit", __func__); + zclient_send_reg_requests(zclient, VRF_DEFAULT); +} + +static void route_add_helper(struct zapi_route *api, struct nexthop_group nhg, + uint8_t install_afi) +{ + struct zapi_nexthop *api_nh; + struct nexthop *nhop; + int i; + + api->prefix.family = install_afi; + + DEBUGD(&pbr_dbg_zebra, " Encoding %pFX", &api->prefix); + + i = 0; + for (ALL_NEXTHOPS(nhg, nhop)) { + api_nh = &api->nexthops[i]; + api_nh->vrf_id = nhop->vrf_id; + api_nh->type = nhop->type; + api_nh->weight = nhop->weight; + switch (nhop->type) { + case NEXTHOP_TYPE_IPV4: + api_nh->gate.ipv4 = nhop->gate.ipv4; + break; + case NEXTHOP_TYPE_IPV4_IFINDEX: + api_nh->gate.ipv4 = nhop->gate.ipv4; + api_nh->ifindex = nhop->ifindex; + break; + case NEXTHOP_TYPE_IFINDEX: + api_nh->ifindex = nhop->ifindex; + break; + case NEXTHOP_TYPE_IPV6: + memcpy(&api_nh->gate.ipv6, &nhop->gate.ipv6, + IPV6_MAX_BYTELEN); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + api_nh->ifindex = nhop->ifindex; + memcpy(&api_nh->gate.ipv6, &nhop->gate.ipv6, + IPV6_MAX_BYTELEN); + break; + case NEXTHOP_TYPE_BLACKHOLE: + api_nh->bh_type = nhop->bh_type; + break; + } + i++; + } + api->nexthop_num = i; + + zclient_route_send(ZEBRA_ROUTE_ADD, zclient, api); +} + +/* + * This function assumes a default route is being + * installed into the appropriate tableid + */ +void route_add(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg, + afi_t install_afi) +{ + struct zapi_route api; + + DEBUGD(&pbr_dbg_zebra, "%s for Table: %d", __func__, pnhgc->table_id); + + memset(&api, 0, sizeof(api)); + + api.vrf_id = VRF_DEFAULT; + api.type = ZEBRA_ROUTE_PBR; + api.safi = SAFI_UNICAST; + /* + * Sending a default route + */ + api.tableid = pnhgc->table_id; + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); + switch (install_afi) { + case AFI_MAX: + route_add_helper(&api, nhg, AF_INET); + route_add_helper(&api, nhg, AF_INET6); + break; + case AFI_IP: + route_add_helper(&api, nhg, AF_INET); + break; + case AFI_IP6: + route_add_helper(&api, nhg, AF_INET6); + break; + case AFI_L2VPN: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to install unsupported route type: L2VPN", + __func__); + break; + case AFI_UNSPEC: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to install unspecified route type", __func__); + break; + } +} + +/* + * This function assumes a default route is being + * removed from the appropriate tableid + */ +void route_delete(struct pbr_nexthop_group_cache *pnhgc, afi_t afi) +{ + struct zapi_route api; + + DEBUGD(&pbr_dbg_zebra, "%s for Table: %d", __func__, pnhgc->table_id); + + memset(&api, 0, sizeof(api)); + api.vrf_id = VRF_DEFAULT; + api.type = ZEBRA_ROUTE_PBR; + api.safi = SAFI_UNICAST; + + api.tableid = pnhgc->table_id; + SET_FLAG(api.message, ZAPI_MESSAGE_TABLEID); + + switch (afi) { + case AFI_IP: + api.prefix.family = AF_INET; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + break; + case AFI_IP6: + api.prefix.family = AF_INET6; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + break; + case AFI_MAX: + api.prefix.family = AF_INET; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + api.prefix.family = AF_INET6; + zclient_route_send(ZEBRA_ROUTE_DELETE, zclient, &api); + break; + case AFI_L2VPN: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to delete unsupported route type: L2VPN", + __func__); + break; + case AFI_UNSPEC: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to delete unspecified route type", __func__); + break; + } +} + +static int pbr_zebra_nexthop_update(ZAPI_CALLBACK_ARGS) +{ + struct zapi_route nhr; + struct prefix matched; + uint32_t i; + + if (!zapi_nexthop_update_decode(zclient->ibuf, &matched, &nhr)) { + zlog_err("Failure to decode Nexthop update message"); + return 0; + } + + if (DEBUG_MODE_CHECK(&pbr_dbg_zebra, DEBUG_MODE_ALL)) { + + DEBUGD(&pbr_dbg_zebra, + "%s: Received Nexthop update: %pFX against %pFX", + __func__, &matched, &nhr.prefix); + + DEBUGD(&pbr_dbg_zebra, "%s: (Nexthops(%u)", __func__, + nhr.nexthop_num); + + for (i = 0; i < nhr.nexthop_num; i++) { + DEBUGD(&pbr_dbg_zebra, + "%s: Type: %d: vrf: %d, ifindex: %d gate: %pI4", + __func__, nhr.nexthops[i].type, + nhr.nexthops[i].vrf_id, nhr.nexthops[i].ifindex, + &nhr.nexthops[i].gate.ipv4); + } + } + + nhr.prefix = matched; + pbr_nht_nexthop_update(&nhr); + return 1; +} + +extern struct zebra_privs_t pbr_privs; + +static zclient_handler *const pbr_handlers[] = { + [ZEBRA_INTERFACE_ADDRESS_ADD] = interface_address_add, + [ZEBRA_INTERFACE_ADDRESS_DELETE] = interface_address_delete, + [ZEBRA_INTERFACE_VRF_UPDATE] = interface_vrf_update, + [ZEBRA_ROUTE_NOTIFY_OWNER] = route_notify_owner, + [ZEBRA_RULE_NOTIFY_OWNER] = rule_notify_owner, + [ZEBRA_NEXTHOP_UPDATE] = pbr_zebra_nexthop_update, +}; + +void pbr_zebra_init(void) +{ + struct zclient_options opt = { .receive_notify = true }; + + zclient = zclient_new(master, &opt, pbr_handlers, + array_size(pbr_handlers)); + + zclient_init(zclient, ZEBRA_ROUTE_PBR, 0, &pbr_privs); + zclient->zebra_connected = zebra_connected; +} + +void pbr_send_rnh(struct nexthop *nhop, bool reg) +{ + uint32_t command; + struct prefix p; + + command = (reg) ? + ZEBRA_NEXTHOP_REGISTER : ZEBRA_NEXTHOP_UNREGISTER; + + memset(&p, 0, sizeof(p)); + switch (nhop->type) { + case NEXTHOP_TYPE_IFINDEX: + case NEXTHOP_TYPE_BLACKHOLE: + return; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + p.family = AF_INET; + p.u.prefix4.s_addr = nhop->gate.ipv4.s_addr; + p.prefixlen = IPV4_MAX_BITLEN; + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + p.family = AF_INET6; + memcpy(&p.u.prefix6, &nhop->gate.ipv6, IPV6_MAX_BYTELEN); + p.prefixlen = IPV6_MAX_BITLEN; + if (IN6_IS_ADDR_LINKLOCAL(&nhop->gate.ipv6)) + /* + * Don't bother tracking link locals, just track their + * interface state. + */ + return; + break; + } + + if (zclient_send_rnh(zclient, command, &p, SAFI_UNICAST, false, false, + nhop->vrf_id) + == ZCLIENT_SEND_FAILURE) { + zlog_warn("%s: Failure to send nexthop to zebra", __func__); + } +} + +static void pbr_encode_pbr_map_sequence_prefix(struct stream *s, + struct prefix *p, + unsigned char family) +{ + struct prefix any; + + if (!p) { + memset(&any, 0, sizeof(any)); + any.family = family; + p = &any; + } + + stream_putc(s, p->family); + stream_putc(s, p->prefixlen); + stream_put(s, &p->u.prefix, prefix_blen(p)); +} + +static void +pbr_encode_pbr_map_sequence_vrf(struct stream *s, + const struct pbr_map_sequence *pbrms, + const struct interface *ifp) +{ + struct pbr_vrf *pbr_vrf; + + if (pbrms->vrf_unchanged) + pbr_vrf = ifp->vrf->info; + else + pbr_vrf = pbr_vrf_lookup_by_name(pbrms->vrf_name); + + if (!pbr_vrf) { + DEBUGD(&pbr_dbg_zebra, "%s: VRF not found", __func__); + return; + } + + stream_putl(s, pbr_vrf->vrf->data.l.table_id); +} + +static void pbr_encode_pbr_map_sequence(struct stream *s, + struct pbr_map_sequence *pbrms, + struct interface *ifp) +{ + unsigned char family; + + family = AF_INET; + if (pbrms->family) + family = pbrms->family; + + stream_putl(s, pbrms->seqno); + stream_putl(s, pbrms->ruleno); + stream_putl(s, pbrms->unique); + stream_putc(s, pbrms->ip_proto); /* The ip_proto */ + pbr_encode_pbr_map_sequence_prefix(s, pbrms->src, family); + stream_putw(s, pbrms->src_prt); + pbr_encode_pbr_map_sequence_prefix(s, pbrms->dst, family); + stream_putw(s, pbrms->dst_prt); + stream_putc(s, pbrms->dsfield); + stream_putl(s, pbrms->mark); + + stream_putl(s, pbrms->action_queue_id); + + stream_putw(s, pbrms->action_vlan_id); + stream_putw(s, pbrms->action_vlan_flags); + stream_putw(s, pbrms->action_pcp); + + if (pbrms->vrf_unchanged || pbrms->vrf_lookup) + pbr_encode_pbr_map_sequence_vrf(s, pbrms, ifp); + else if (pbrms->nhgrp_name) + stream_putl(s, pbr_nht_get_table(pbrms->nhgrp_name)); + else if (pbrms->nhg) + stream_putl(s, pbr_nht_get_table(pbrms->internal_nhg_name)); + stream_put(s, ifp->name, INTERFACE_NAMSIZ); +} + +bool pbr_send_pbr_map(struct pbr_map_sequence *pbrms, + struct pbr_map_interface *pmi, bool install, bool changed) +{ + struct pbr_map *pbrm = pbrms->parent; + struct stream *s; + uint64_t is_installed = (uint64_t)1 << pmi->install_bit; + + is_installed &= pbrms->installed; + + DEBUGD(&pbr_dbg_zebra, "%s: for %s %d(%" PRIu64 ")", __func__, + pbrm->name, install, is_installed); + + /* + * If we are installed and asked to do so again and the config + * has not changed, just return. + * + * If we are not installed and asked + * to delete just return. + */ + if (install && is_installed && !changed) + return false; + + if (!install && !is_installed) + return false; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, + install ? ZEBRA_RULE_ADD : ZEBRA_RULE_DELETE, + VRF_DEFAULT); + + /* + * We are sending one item at a time at the moment + */ + stream_putl(s, 1); + + DEBUGD(&pbr_dbg_zebra, "%s: %s %s seq %u %d %s %u", __func__, + install ? "Installing" : "Deleting", pbrm->name, pbrms->seqno, + install, pmi->ifp->name, pmi->delete); + + pbr_encode_pbr_map_sequence(s, pbrms, pmi->ifp); + + stream_putw_at(s, 0, stream_get_endp(s)); + + zclient_send_message(zclient); + + return true; +} diff --git a/pbrd/pbr_zebra.h b/pbrd/pbr_zebra.h new file mode 100644 index 0000000..d0f9ff9 --- /dev/null +++ b/pbrd/pbr_zebra.h @@ -0,0 +1,52 @@ +/* + * Zebra connect library for PBR + * 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 + */ +#ifndef __PBR_ZEBRA_H__ +#define __PBR_ZEBRA_H__ + +struct pbr_interface { + char mapname[100]; +}; + +extern struct thread_master *master; + +extern void pbr_zebra_init(void); + +extern void route_add(struct pbr_nexthop_group_cache *pnhgc, + struct nexthop_group nhg, afi_t install_afi); +extern void route_delete(struct pbr_nexthop_group_cache *pnhgc, + afi_t install_afi); + +extern void pbr_send_rnh(struct nexthop *nhop, bool reg); + +extern bool pbr_send_pbr_map(struct pbr_map_sequence *pbrms, + struct pbr_map_interface *pmi, bool install, + bool changed); + +extern struct pbr_interface *pbr_if_new(struct interface *ifp); + +extern int pbr_ifp_create(struct interface *ifp); +extern int pbr_ifp_up(struct interface *ifp); +extern int pbr_ifp_down(struct interface *ifp); +extern int pbr_ifp_destroy(struct interface *ifp); + +/* Free the ifp->info pointer */ +extern void pbr_if_del(struct interface *ifp); + +#endif diff --git a/pbrd/subdir.am b/pbrd/subdir.am new file mode 100644 index 0000000..bbe3f2a --- /dev/null +++ b/pbrd/subdir.am @@ -0,0 +1,43 @@ +# +# pbrd +# + +if PBRD +noinst_LIBRARIES += pbrd/libpbr.a +sbin_PROGRAMS += pbrd/pbrd +vtysh_scan += \ + pbrd/pbr_vty.c \ + pbrd/pbr_debug.c \ + # end +vtysh_daemons += pbrd +man8 += $(MANBUILD)/frr-pbrd.8 +endif + +pbrd_libpbr_a_SOURCES = \ + pbrd/pbr_zebra.c \ + pbrd/pbr_vty.c \ + pbrd/pbr_map.c \ + pbrd/pbr_memory.c \ + pbrd/pbr_nht.c \ + pbrd/pbr_debug.c \ + pbrd/pbr_vrf.c \ + # end + +noinst_HEADERS += \ + pbrd/pbr_map.h \ + pbrd/pbr_memory.h \ + pbrd/pbr_nht.h \ + pbrd/pbr_vty.h \ + pbrd/pbr_zebra.h \ + pbrd/pbr_debug.h \ + pbrd/pbr_vrf.h \ + # end + +clippy_scan += \ + pbrd/pbr_debug.c \ + pbrd/pbr_vty.c \ + # end + +pbrd_pbrd_SOURCES = pbrd/pbr_main.c +pbrd_pbrd_LDADD = pbrd/libpbr.a lib/libfrr.la $(LIBCAP) + |