// SPDX-License-Identifier: GPL-2.0-or-later /* * PBR - vty code * Copyright (C) 2018 Cumulus Networks, Inc. * Donald Sharp * Portions: * Copyright (c) 2021 The MITRE Corporation. * Copyright (c) 2023 LabN Consulting, L.L.C. */ #include #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" #include "pbrd/pbr_vty_clippy.c" /* clang-format off */ 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") { /* clang-format on */ /* 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; } /* clang-format off */ 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") { /* clang-format on */ pbr_nht_set_tableid_range(PBR_NHT_DEFAULT_LOW_TABLEID, PBR_NHT_DEFAULT_HIGH_TABLEID); return CMD_SUCCESS; } /* clang-format off */ 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") { /* clang-format on */ 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; } /* clang-format off */ 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") { /* clang-format on */ 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); pbr_map_sequence_delete(pbrms); } return CMD_SUCCESS; } /*********************************************************************** * pbrms/rule Match L3 Fields ***********************************************************************/ /* * Address Family Matters * * Linux Kernel constraints * ------------------------ * The underlying linux kernel dataplane requires that rules be * installed into an IPv4-specific or an IPv6-specific database. * * Not only do we need to designate an address-family for rule * installation, but we ALSO must have the same address-family * available to be able to delete the rule from the correct kernel * database. * * Determining the address-family * ------------------------------ * In the current code, we do our best to infer the correct family * from any configured IP-address match or set clauses in a rule. * Absent any of those fields, the NHT code also tries to glean the * address family from resolved nexthops or nexthop-groups. All of * those opportunistic address-family determinations are stored in * the "family" field of struct pbr_map_sequence. * * This "family" field value is needed particularly when deleting * a rule piece-by-piece because at the end, the match/set fields * will be empty. Maybe it would be possible to handle this issue * as an internal zebra matter in the future. * * We also attempt to maintain address-family consistency among the * various configured fields in a rule. So far, these fields are * src/dst IP-address match/set values. * * It is probably possible to perform the same address-family check in * the CLI for single nexthops (set nexthop A.B.C.D|X:X::X:X) but the * address-family is not immediately available for nexthop-groups. * In both the single-nexthop and nexthop-group, the NHT resolution code * sets the "family" field of struct pbr_map_sequence asynchronously. * * There isn't currently any flagging of rules that have a consistent * set of src/dst IP-address match/set values but an asynchronously-resolved * nexthop-group that has a different address-family. * * The match/set IP-address handlers below blindly set "family"; it's * probably possible to wrongly set "family" to, e.g., IPv4 this way after * a v6 NHG has been resolved and break rule removal. It's not clear * how to best address this potential issue. */ static bool pbr_family_consistent(struct pbr_map_sequence *pbrms, uint8_t family, uint32_t skip_filter_bm, uint32_t skip_action_bm, const char **msg) { uint32_t filter_bm = pbrms->filter_bm & ~skip_filter_bm; uint32_t action_bm = pbrms->action_bm & ~skip_action_bm; if (CHECK_FLAG(filter_bm, PBR_FILTER_SRC_IP) && (family != pbrms->src->family)) { if (msg) *msg = "match src-ip"; return false; } if (CHECK_FLAG(filter_bm, PBR_FILTER_DST_IP) && (family != pbrms->dst->family)) { if (msg) *msg = "match dst-ip"; return false; } if (CHECK_FLAG(action_bm, PBR_ACTION_SRC_IP) && (family != sockunion_family(&pbrms->action_src))) { if (msg) *msg = "set src-ip"; return false; } if (CHECK_FLAG(filter_bm, PBR_ACTION_DST_IP) && (family != sockunion_family(&pbrms->action_dst))) { if (msg) *msg = "set dst-ip"; return false; } return true; } /* clang-format off */ DEFPY (pbr_map_match_src, pbr_map_match_src_cmd, "[no] match src-ip ![$prefix]", NO_STR "Match the rest of the command\n" "Choose the src ipv4 or ipv6 prefix to use\n" "v4 Prefix\n" "v6 Prefix\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); const char *fmsg = NULL; if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) return CMD_SUCCESS; prefix_free(&pbrms->src); UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP); goto check; } assert(prefix); if (!pbr_family_consistent(pbrms, prefix->family, PBR_FILTER_SRC_IP, 0, &fmsg)) { vty_out(vty, "Address family mismatch (%s)\n", fmsg); return CMD_WARNING_CONFIG_FAILED; } pbrms->family = prefix->family; if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) { if (prefix_same(pbrms->src, prefix)) return CMD_SUCCESS; } else pbrms->src = prefix_new(); prefix_copy(pbrms->src, prefix); SET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_match_dst, pbr_map_match_dst_cmd, "[no] match dst-ip ![$prefix]", NO_STR "Match the rest of the command\n" "Choose the dst ipv4 or ipv6 prefix to use\n" "v4 Prefix\n" "v6 Prefix\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); const char *fmsg = NULL; if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) return CMD_SUCCESS; prefix_free(&pbrms->dst); UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP); goto check; } assert(prefix); if (!pbr_family_consistent(pbrms, prefix->family, PBR_FILTER_DST_IP, 0, &fmsg)) { vty_out(vty, "Address family mismatch (%s)\n", fmsg); return CMD_WARNING_CONFIG_FAILED; } pbrms->family = prefix->family; if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) { if (prefix_same(pbrms->dst, prefix)) return CMD_SUCCESS; } else pbrms->dst = prefix_new(); prefix_copy(pbrms->dst, prefix); SET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_match_ip_proto, pbr_map_match_ip_proto_cmd, "[no] match ip-protocol ![PROTO$ip_proto]", NO_STR "Match the rest of the command\n" "Choose an ip-protocol\n" "Protocol name\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); struct protoent *p = NULL; if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL); goto check; } if (ip_proto) p = getprotobyname(ip_proto); if (!ip_proto || !p) { vty_out(vty, "Unable to convert %s to proto id\n", (ip_proto ? ip_proto : "(null)")); return CMD_WARNING_CONFIG_FAILED; } pbrms->ip_proto = p->p_proto; SET_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT); goto check; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT) && (pbrms->src_prt == port)) { return CMD_SUCCESS; } pbrms->src_prt = port; SET_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT); goto check; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT) && (pbrms->dst_prt == port)) { return CMD_SUCCESS; } pbrms->dst_prt = port; SET_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP); pbrms->dsfield &= ~PBR_DSFIELD_DSCP; goto check; } unsigned long ul_dscp; char *pend = NULL; uint8_t shifted_dscp; assert(dscp); ul_dscp = strtoul(dscp, &pend, 0); if (pend && *pend) ul_dscp = pbr_map_decode_dscp_enum(dscp); if (ul_dscp > (PBR_DSFIELD_DSCP >> 2)) { vty_out(vty, "Invalid dscp value: %s%s\n", dscp, ((pend && *pend) ? "" : " (numeric value must be in range 0-63)")); return CMD_WARNING_CONFIG_FAILED; } shifted_dscp = (ul_dscp << 2) & PBR_DSFIELD_DSCP; if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP) && ((pbrms->dsfield & PBR_DSFIELD_DSCP) == shifted_dscp)) { return CMD_SUCCESS; } /* Set the DSCP bits of the DSField */ pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_DSCP) | shifted_dscp; SET_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_ECN); pbrms->dsfield &= ~PBR_DSFIELD_ECN; goto check; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN) && ((pbrms->dsfield & PBR_DSFIELD_ECN) == ecn)) { return CMD_SUCCESS; } /* Set the ECN bits of the DSField */ pbrms->dsfield = (pbrms->dsfield & ~PBR_DSFIELD_ECN) | ecn; SET_FLAG(pbrms->filter_bm, PBR_FILTER_ECN); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /*********************************************************************** * pbrms/rule Match L2 fields ***********************************************************************/ /* clang-format off */ DEFPY (pbr_map_match_pcp, pbr_map_match_pcp_cmd, "[no] match pcp ![(0-7)$pcp]", NO_STR "Match spec follows\n" "Match based on 802.1p Priority Code Point (PCP) value\n" "PCP value to match\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_PCP); goto check; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP) && (pbrms->match_pcp == pcp)) { return CMD_SUCCESS; } pbrms->match_pcp = pcp; SET_FLAG(pbrms->filter_bm, PBR_FILTER_PCP); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_match_vlan_id, pbr_map_match_vlan_id_cmd, "[no] match vlan ![(1-4094)$vlan_id]", NO_STR "Match spec follows\n" "Match based on VLAN ID\n" "VLAN ID to match\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID); goto check; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID) && (pbrms->match_vlan_id == vlan_id)) { return CMD_SUCCESS; } /* * Maintaining previous behavior: setting a vlan_id match * automatically clears any vlan_flags matching. */ UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS); SET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID); pbrms->match_vlan_id = vlan_id; check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_match_vlan_tag, pbr_map_match_vlan_tag_cmd, "[no] match vlan ![$tag_type]", NO_STR "Match the rest of the command\n" "Match based on VLAN tagging\n" "Match all tagged frames\n" "Match all untagged frames\n" "Match untagged frames, or tagged frames with id zero\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); uint16_t vlan_flags; if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS); goto check; } assert(tag_type); if (strmatch(tag_type, "tagged")) vlan_flags = PBR_VLAN_FLAGS_TAGGED; else if (strmatch(tag_type, "untagged")) vlan_flags = PBR_VLAN_FLAGS_UNTAGGED; else if (strmatch(tag_type, "untagged-or-zero")) vlan_flags = PBR_VLAN_FLAGS_UNTAGGED_0; else { vty_out(vty, "unknown vlan flag\n"); return CMD_WARNING_CONFIG_FAILED; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS) && (pbrms->match_vlan_flags == vlan_flags)) { return CMD_SUCCESS; } /* * Maintaining previous behavior: setting a vlan_flags match * automatically clears any vlan_id matching. */ UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID); SET_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS); pbrms->match_vlan_flags = vlan_flags; check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /*********************************************************************** * pbrms/rule Match meta ***********************************************************************/ /* clang-format off */ 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") { /* clang-format on */ 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 (!CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) return CMD_SUCCESS; UNSET_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK); goto check; } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK) && (pbrms->mark == (uint32_t)mark)) { return CMD_SUCCESS; } pbrms->mark = (uint32_t)mark; SET_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /*********************************************************************** * pbrms/rule Action Set L3 Fields ***********************************************************************/ /* clang-format off */ DEFPY (pbr_map_action_src, pbr_map_action_src_cmd, "[no] set src-ip ![$su]", NO_STR "Set command\n" "Set the src ipv4 or ipv6 prefix\n" "v4 Prefix\n" "v6 Prefix\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); const char *fmsg = NULL; if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP); goto check; } assert(su); if (!pbr_family_consistent(pbrms, sockunion_family(su), PBR_ACTION_SRC_IP, 0, &fmsg)) { vty_out(vty, "Address family mismatch (%s)\n", fmsg); return CMD_WARNING_CONFIG_FAILED; } pbrms->family = sockunion_family(su); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP) && (sockunion_same(&pbrms->action_src, su))) { return CMD_SUCCESS; } pbrms->action_src = *su; SET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_action_dst, pbr_map_action_dst_cmd, "[no] set dst-ip ![$su]", NO_STR "Set command\n" "Set the dst ipv4 or ipv6 prefix\n" "v4 Prefix\n" "v6 Prefix\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); const char *fmsg = NULL; if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP); goto check; } assert(su); if (!pbr_family_consistent(pbrms, sockunion_family(su), PBR_ACTION_DST_IP, 0, &fmsg)) { vty_out(vty, "Address family mismatch (%s)\n", fmsg); return CMD_WARNING_CONFIG_FAILED; } pbrms->family = sockunion_family(su); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP) && (sockunion_same(&pbrms->action_dst, su))) { return CMD_SUCCESS; } pbrms->action_dst = *su; SET_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_action_src_port, pbr_map_action_src_port_cmd, "[no] set src-port ![(1-65535)$port]", NO_STR "Set command\n" "Set Source Port\n" "The Source Port\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT); goto check; } if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT) && (pbrms->action_src_port == port)) return CMD_SUCCESS; pbrms->action_src_port = port; SET_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_action_dst_port, pbr_map_action_dst_port_cmd, "[no] set dst-port ![(1-65535)$port]", NO_STR "Set command\n" "Set Destination Port\n" "The Destination Port\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT); goto check; } if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT) && (pbrms->action_dst_port == port)) return CMD_SUCCESS; SET_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT); pbrms->action_dst_port = port; check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_action_dscp, pbr_map_action_dscp_cmd, "[no] set dscp ![DSCP$dscp]", NO_STR "Set command\n" "Set IP DSCP field\n" "DSCP numeric value (0-63) or standard codepoint name\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DSCP); goto check; } unsigned long ul_dscp; char *pend = NULL; uint8_t shifted_dscp; assert(dscp); ul_dscp = strtoul(dscp, &pend, 0); if (pend && *pend) ul_dscp = pbr_map_decode_dscp_enum(dscp); if (ul_dscp > (PBR_DSFIELD_DSCP >> 2)) { vty_out(vty, "Invalid dscp value: %s%s\n", dscp, ((pend && *pend) ? "" : " (numeric value must be in range 0-63)")); return CMD_WARNING_CONFIG_FAILED; } shifted_dscp = (ul_dscp << 2) & PBR_DSFIELD_DSCP; if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP) && (pbrms->action_dscp == shifted_dscp)) { return CMD_SUCCESS; } SET_FLAG(pbrms->action_bm, PBR_ACTION_DSCP); pbrms->action_dscp = shifted_dscp; check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ DEFPY (pbr_map_action_ecn, pbr_map_action_ecn_cmd, "[no] set ecn ![(0-3)$ecn]", NO_STR "Set command\n" "Set IP ECN field\n" "Explicit Congestion Notification value\n") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_ECN); goto check; } if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN) && (pbrms->action_ecn == ecn)) { return CMD_SUCCESS; } SET_FLAG(pbrms->action_bm, PBR_ACTION_ECN); pbrms->action_ecn = ecn; check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /*********************************************************************** * pbrms/rule Action Set Meta ***********************************************************************/ /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID); goto check; } if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID) && (pbrms->action_queue_id == (uint32_t)queue_id)) { return CMD_SUCCESS; } pbrms->action_queue_id = (uint32_t)queue_id; SET_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /*********************************************************************** * pbrms/rule Action Set L2 Fields ***********************************************************************/ /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_PCP); goto check; } if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP) && pbrms->action_pcp == pcp) { return CMD_SUCCESS; } pbrms->action_pcp = pcp; SET_FLAG(pbrms->action_bm, PBR_ACTION_PCP); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID); goto check; } if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID) && (pbrms->action_vlan_id == vlan_id)) { return CMD_SUCCESS; } /* * Setting a vlan_id action automatically clears any strip-inner action */ pbrms->action_vlan_id = vlan_id; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY); SET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /* clang-format off */ 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") { /* clang-format on */ struct pbr_map_sequence *pbrms = VTY_GET_CONTEXT(pbr_map_sequence); if (!pbrms) return CMD_WARNING_CONFIG_FAILED; if (no) { if (!CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) return CMD_SUCCESS; UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY); goto check; } if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) return CMD_SUCCESS; /* * Setting a strip-inner action automatically clears any vlan_id action */ UNSET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID); SET_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY); check: pbr_map_check(pbrms, true); return CMD_SUCCESS; } /*********************************************************************** * pbrms/rule Action Forwarding ***********************************************************************/ 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; pbrms->forwarding_type = PBR_FT_UNSPEC; /* clear advisory flag indicating nexthop == blackhole */ UNSET_FLAG(pbrms->action_bm, PBR_ACTION_DROP); } 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; } /* clang-format off */ DEFPY (pbr_map_nexthop, pbr_map_nexthop_cmd, "set nexthop\ <\ $addr [INTERFACE$intf]\ |INTERFACE$intf\ |blackhole$bh\ >\ [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" "Blackhole route\n" "If the nexthop is in a different vrf tell us\n" "The nexthop-vrf Name\n") { /* clang-format on */ 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 if (bh) { nhop.type = NEXTHOP_TYPE_BLACKHOLE; /* advisory flag for non-linux dataplanes */ SET_FLAG(pbrms->action_bm, PBR_ACTION_DROP); } 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; } /* clang-format off */ DEFPY (no_pbr_map_nexthop, no_pbr_map_nexthop_cmd, "no set nexthop\ [<\ $addr [INTERFACE$intf]\ |INTERFACE$intf\ |blackhole$bh\ >\ [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" "Blackhole route\n" "If the nexthop is in a different vrf tell us\n" "The nexthop-vrf Name\n") { /* clang-format on */ 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 ", "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 ((pbrms->forwarding_type == PBR_FT_SETVRF) && vrf_name && strncmp(pbrms->vrf_name, vrf_name, sizeof(pbrms->vrf_name)) == 0) return CMD_SUCCESS; else if (!vrf_name && (pbrms->forwarding_type == PBR_FT_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; pbrms->forwarding_type = PBR_FT_SETVRF; strlcpy(pbrms->vrf_name, vrf_name, sizeof(pbrms->vrf_name)); } else { pbrms->forwarding_type = PBR_FT_VRF_UNCHANGED; 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 []", 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; } /*********************************************************************** * Policy ***********************************************************************/ /* clang-format off */ 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") { /* clang-format on */ 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"); /* match clauses first */ if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) { struct protoent *p; p = getprotobynumber(pbrms->ip_proto); vty_out(vty, " IP Protocol Match: %s\n", p->p_name); } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) vty_out(vty, " SRC IP Match: %pFX\n", pbrms->src); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) vty_out(vty, " DST IP Match: %pFX\n", pbrms->dst); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) vty_out(vty, " SRC Port Match: %u\n", pbrms->src_prt); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) vty_out(vty, " DST Port Match: %u\n", pbrms->dst_prt); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) vty_out(vty, " DSCP Match: %u\n", (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) vty_out(vty, " ECN Match: %u\n", pbrms->dsfield & PBR_DSFIELD_ECN); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) vty_out(vty, " MARK Match: %u\n", pbrms->mark); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) vty_out(vty, " PCP Match: %d\n", pbrms->match_pcp); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) vty_out(vty, " Match VLAN ID: %u\n", pbrms->match_vlan_id); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) { if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_TAGGED) vty_out(vty, " Match VLAN tagged frames\n"); if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED) vty_out(vty, " Match VLAN untagged frames\n"); if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED_0) vty_out(vty, " Match VLAN untagged or ID 0\n"); } /* set actions */ if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) vty_out(vty, " Set SRC IP: %pSU\n", &pbrms->action_src); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) vty_out(vty, " Set DST IP: %pSU\n", &pbrms->action_dst); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) vty_out(vty, " Set Src port: %u\n", pbrms->action_src_port); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) vty_out(vty, " Set Dst port: %u\n", pbrms->action_dst_port); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) vty_out(vty, " Set DSCP: %u\n", (pbrms->action_dscp) >> 2); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) vty_out(vty, " Set ECN: %u\n", pbrms->action_ecn); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) vty_out(vty, " Set VLAN ID %u\n", pbrms->action_vlan_id); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) vty_out(vty, " Strip VLAN ID\n"); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) vty_out(vty, " Set PCP %u\n", pbrms->action_pcp); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) vty_out(vty, " Set Queue ID: %u\n", pbrms->action_queue_id); switch (pbrms->forwarding_type) { case PBR_FT_UNSPEC: vty_out(vty, " Nexthop-Group: Unknown Installed: no\n"); break; case PBR_FT_VRF_UNCHANGED: vty_out(vty, " VRF Unchanged (use interface vrf)\n"); break; case PBR_FT_SETVRF: assert(pbrms->vrf_name); vty_out(vty, " VRF Lookup: %s\n", pbrms->vrf_name); break; case PBR_FT_NEXTHOP_GROUP: assert(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)); break; case PBR_FT_NEXTHOP_SINGLE: assert(pbrms->internal_nhg_name); 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)); break; } } 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", pbrms->installed); json_object_string_add(jpbrm, "installedReason", pbrms->reason ? rbuf : "Valid"); switch (pbrms->forwarding_type) { case PBR_FT_UNSPEC: break; case PBR_FT_VRF_UNCHANGED: break; case PBR_FT_SETVRF: assert(pbrms->vrf_name); json_object_string_add(jpbrm, "vrfName", pbrms->vrf_name); break; case PBR_FT_NEXTHOP_GROUP: case PBR_FT_NEXTHOP_SINGLE: assert(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); break; } /* * Match clauses */ /* IP Header */ if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) json_object_int_add(jpbrm, "matchIpProtocol", pbrms->ip_proto); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) json_object_string_addf(jpbrm, "matchSrc", "%pFX", pbrms->src); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) json_object_string_addf(jpbrm, "matchDst", "%pFX", pbrms->dst); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) json_object_int_add(jpbrm, "matchSrcPort", pbrms->src_prt); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) json_object_int_add(jpbrm, "matchDstPort", pbrms->dst_prt); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) json_object_int_add(jpbrm, "matchDscp", (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) json_object_int_add(jpbrm, "matchEcn", pbrms->dsfield & PBR_DSFIELD_ECN); /* L2 headers */ if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) json_object_int_add(jpbrm, "matchPcp", pbrms->match_pcp); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) json_object_int_add(jpbrm, "matchVlanId", pbrms->match_vlan_id); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) { const char *p = "?"; if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_TAGGED) p = "tagged"; if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED) p = "untagged"; if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED_0) p = "untagged-or-0"; json_object_string_addf(jpbrm, "matchVlanFlags", "%s", p); } /* meta */ if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) json_object_int_add(jpbrm, "matchMark", pbrms->mark); /* * action clauses */ /* IP header fields */ if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) json_object_string_addf(jpbrm, "actionSetSrcIpAddr", "%pSU", &pbrms->action_src); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) json_object_string_addf(jpbrm, "actionSetDstIpAddr", "%pSU", &pbrms->action_dst); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) json_object_int_add(jpbrm, "actionSetSrcPort", pbrms->action_src_port); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) json_object_int_add(jpbrm, "actionSetDstPort", pbrms->action_dst_port); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) json_object_int_add(jpbrm, "actionSetDscp", pbrms->action_dscp >> 2); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) json_object_int_add(jpbrm, "actionSetEcn", pbrms->action_ecn); /* L2 header fields */ if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) json_object_boolean_true_add(jpbrm, "actionVlanStripInnerAny"); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) json_object_int_add(jpbrm, "actionSetVlanId", pbrms->action_vlan_id); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) json_object_int_add(jpbrm, "actionSetPcp", pbrms->action_pcp); /* meta */ if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) json_object_int_add(jpbrm, "actionSetQueueId", pbrms->action_queue_id); 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); cmd_show_lib_debugs(vty); 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 (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_IP_PROTOCOL)) { struct protoent *p; p = getprotobynumber(pbrms->ip_proto); vty_out(vty, " match ip-protocol %s\n", p->p_name); } if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_IP)) vty_out(vty, " match src-ip %pFX\n", pbrms->src); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_IP)) vty_out(vty, " match dst-ip %pFX\n", pbrms->dst); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_SRC_PORT)) vty_out(vty, " match src-port %u\n", pbrms->src_prt); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DST_PORT)) vty_out(vty, " match dst-port %u\n", pbrms->dst_prt); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_DSCP)) vty_out(vty, " match dscp %u\n", (pbrms->dsfield & PBR_DSFIELD_DSCP) >> 2); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_ECN)) vty_out(vty, " match ecn %u\n", pbrms->dsfield & PBR_DSFIELD_ECN); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_PCP)) vty_out(vty, " match pcp %d\n", pbrms->match_pcp); /* L2 headers */ if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_ID)) vty_out(vty, " match vlan %u\n", pbrms->match_vlan_id); if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_VLAN_FLAGS)) { if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_TAGGED) vty_out(vty, " match vlan tagged\n"); if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED) vty_out(vty, " match vlan untagged\n"); if (pbrms->match_vlan_flags == PBR_VLAN_FLAGS_UNTAGGED_0) vty_out(vty, " match vlan untagged-or-zero\n"); } /* meta */ if (CHECK_FLAG(pbrms->filter_bm, PBR_FILTER_FWMARK)) vty_out(vty, " match mark %u\n", pbrms->mark); /* * action clauses */ if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_IP)) vty_out(vty, " set src-ip %pSU\n", &pbrms->action_src); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_IP)) vty_out(vty, " set dst-ip %pSU\n", &pbrms->action_dst); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_SRC_PORT)) vty_out(vty, " set src-port %d\n", pbrms->action_src_port); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DST_PORT)) vty_out(vty, " set dst-port %d\n", pbrms->action_dst_port); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_DSCP)) vty_out(vty, " set dscp %u\n", (pbrms->action_dscp) >> 2); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_ECN)) vty_out(vty, " set ecn %u\n", pbrms->action_ecn); /* L2 header fields */ if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_STRIP_INNER_ANY)) vty_out(vty, " strip vlan any\n"); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_VLAN_ID)) vty_out(vty, " set vlan %u\n", pbrms->action_vlan_id); if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_PCP)) vty_out(vty, " set pcp %d\n", pbrms->action_pcp); /* meta */ if (CHECK_FLAG(pbrms->action_bm, PBR_ACTION_QUEUE_ID)) vty_out(vty, " set queue-id %d\n", pbrms->action_queue_id); switch (pbrms->forwarding_type) { case PBR_FT_UNSPEC: break; case PBR_FT_VRF_UNCHANGED: vty_out(vty, " set vrf unchanged\n"); break; case PBR_FT_SETVRF: assert(pbrms->vrf_name); vty_out(vty, " set vrf %s\n", pbrms->vrf_name); break; case PBR_FT_NEXTHOP_GROUP: assert(pbrms->nhgrp_name); vty_out(vty, " set nexthop-group %s\n", pbrms->nhgrp_name); break; case PBR_FT_NEXTHOP_SINGLE: assert(pbrms->nhg); vty_out(vty, " set "); pbrms_nexthop_group_write_individual_nexthop(vty, pbrms); break; } 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_vlan_id_cmd); install_element(PBRMAP_NODE, &pbr_map_match_vlan_tag_cmd); install_element(PBRMAP_NODE, &pbr_map_match_pcp_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_action_src_cmd); install_element(PBRMAP_NODE, &pbr_map_action_dst_cmd); install_element(PBRMAP_NODE, &pbr_map_action_dscp_cmd); install_element(PBRMAP_NODE, &pbr_map_action_ecn_cmd); install_element(PBRMAP_NODE, &pbr_map_action_src_port_cmd); install_element(PBRMAP_NODE, &pbr_map_action_dst_port_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); }