summaryrefslogtreecommitdiffstats
path: root/bgpd/bgp_pbr.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /bgpd/bgp_pbr.c
parentInitial commit. (diff)
downloadfrr-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 'bgpd/bgp_pbr.c')
-rw-r--r--bgpd/bgp_pbr.c2960
1 files changed, 2960 insertions, 0 deletions
diff --git a/bgpd/bgp_pbr.c b/bgpd/bgp_pbr.c
new file mode 100644
index 0000000..b71e19a
--- /dev/null
+++ b/bgpd/bgp_pbr.c
@@ -0,0 +1,2960 @@
+/*
+ * BGP pbr
+ * Copyright (C) 6WIND
+ *
+ * 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 "prefix.h"
+#include "zclient.h"
+#include "jhash.h"
+#include "pbr.h"
+
+#include "lib/printfrr.h"
+
+#include "bgpd/bgpd.h"
+#include "bgpd/bgp_pbr.h"
+#include "bgpd/bgp_debug.h"
+#include "bgpd/bgp_flowspec_util.h"
+#include "bgpd/bgp_ecommunity.h"
+#include "bgpd/bgp_route.h"
+#include "bgpd/bgp_attr.h"
+#include "bgpd/bgp_zebra.h"
+#include "bgpd/bgp_mplsvpn.h"
+#include "bgpd/bgp_flowspec_private.h"
+#include "bgpd/bgp_errors.h"
+
+DEFINE_MTYPE_STATIC(BGPD, PBR_MATCH_ENTRY, "PBR match entry");
+DEFINE_MTYPE_STATIC(BGPD, PBR_MATCH, "PBR match");
+DEFINE_MTYPE_STATIC(BGPD, PBR_ACTION, "PBR action");
+DEFINE_MTYPE_STATIC(BGPD, PBR_RULE, "PBR rule");
+DEFINE_MTYPE_STATIC(BGPD, PBR, "BGP PBR Context");
+DEFINE_MTYPE_STATIC(BGPD, PBR_VALMASK, "BGP PBR Val Mask Value");
+
+/* chain strings too long to fit in one line */
+#define FSPEC_ACTION_EXCEED_LIMIT "flowspec actions exceeds limit"
+#define IPV6_FRAGMENT_INVALID "fragment not valid for IPv6 for this implementation"
+
+RB_GENERATE(bgp_pbr_interface_head, bgp_pbr_interface,
+ id_entry, bgp_pbr_interface_compare);
+struct bgp_pbr_interface_head ifaces_by_name_ipv4 =
+ RB_INITIALIZER(&ifaces_by_name_ipv4);
+
+static int bgp_pbr_match_counter_unique;
+static int bgp_pbr_match_entry_counter_unique;
+static int bgp_pbr_action_counter_unique;
+static int bgp_pbr_match_iptable_counter_unique;
+
+struct bgp_pbr_match_iptable_unique {
+ uint32_t unique;
+ struct bgp_pbr_match *bpm_found;
+};
+
+struct bgp_pbr_match_entry_unique {
+ uint32_t unique;
+ struct bgp_pbr_match_entry *bpme_found;
+};
+
+struct bgp_pbr_action_unique {
+ uint32_t unique;
+ struct bgp_pbr_action *bpa_found;
+};
+
+struct bgp_pbr_rule_unique {
+ uint32_t unique;
+ struct bgp_pbr_rule *bpr_found;
+};
+
+static int bgp_pbr_rule_walkcb(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_rule *bpr = (struct bgp_pbr_rule *)bucket->data;
+ struct bgp_pbr_rule_unique *bpru = (struct bgp_pbr_rule_unique *)
+ arg;
+ uint32_t unique = bpru->unique;
+
+ if (bpr->unique == unique) {
+ bpru->bpr_found = bpr;
+ return HASHWALK_ABORT;
+ }
+ return HASHWALK_CONTINUE;
+}
+
+static int bgp_pbr_action_walkcb(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_action *bpa = (struct bgp_pbr_action *)bucket->data;
+ struct bgp_pbr_action_unique *bpau = (struct bgp_pbr_action_unique *)
+ arg;
+ uint32_t unique = bpau->unique;
+
+ if (bpa->unique == unique) {
+ bpau->bpa_found = bpa;
+ return HASHWALK_ABORT;
+ }
+ return HASHWALK_CONTINUE;
+}
+
+static int bgp_pbr_match_entry_walkcb(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_match_entry *bpme =
+ (struct bgp_pbr_match_entry *)bucket->data;
+ struct bgp_pbr_match_entry_unique *bpmeu =
+ (struct bgp_pbr_match_entry_unique *)arg;
+ uint32_t unique = bpmeu->unique;
+
+ if (bpme->unique == unique) {
+ bpmeu->bpme_found = bpme;
+ return HASHWALK_ABORT;
+ }
+ return HASHWALK_CONTINUE;
+}
+
+struct bgp_pbr_match_ipsetname {
+ char *ipsetname;
+ struct bgp_pbr_match *bpm_found;
+};
+
+static int bgp_pbr_match_pername_walkcb(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data;
+ struct bgp_pbr_match_ipsetname *bpmi =
+ (struct bgp_pbr_match_ipsetname *)arg;
+ char *ipset_name = bpmi->ipsetname;
+
+ if (!strncmp(ipset_name, bpm->ipset_name,
+ ZEBRA_IPSET_NAME_SIZE)) {
+ bpmi->bpm_found = bpm;
+ return HASHWALK_ABORT;
+ }
+ return HASHWALK_CONTINUE;
+}
+
+static int bgp_pbr_match_iptable_walkcb(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data;
+ struct bgp_pbr_match_iptable_unique *bpmiu =
+ (struct bgp_pbr_match_iptable_unique *)arg;
+ uint32_t unique = bpmiu->unique;
+
+ if (bpm->unique2 == unique) {
+ bpmiu->bpm_found = bpm;
+ return HASHWALK_ABORT;
+ }
+ return HASHWALK_CONTINUE;
+}
+
+struct bgp_pbr_match_unique {
+ uint32_t unique;
+ struct bgp_pbr_match *bpm_found;
+};
+
+static int bgp_pbr_match_walkcb(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data;
+ struct bgp_pbr_match_unique *bpmu = (struct bgp_pbr_match_unique *)
+ arg;
+ uint32_t unique = bpmu->unique;
+
+ if (bpm->unique == unique) {
+ bpmu->bpm_found = bpm;
+ return HASHWALK_ABORT;
+ }
+ return HASHWALK_CONTINUE;
+}
+
+static int snprintf_bgp_pbr_match_val(char *str, int len,
+ struct bgp_pbr_match_val *mval,
+ const char *prepend)
+{
+ char *ptr = str;
+ int delta;
+
+ if (prepend) {
+ delta = snprintf(ptr, len, "%s", prepend);
+ ptr += delta;
+ len -= delta;
+ } else {
+ if (mval->unary_operator & OPERATOR_UNARY_OR) {
+ delta = snprintf(ptr, len, ", or ");
+ ptr += delta;
+ len -= delta;
+ }
+ if (mval->unary_operator & OPERATOR_UNARY_AND) {
+ delta = snprintf(ptr, len, ", and ");
+ ptr += delta;
+ len -= delta;
+ }
+ }
+ if (mval->compare_operator & OPERATOR_COMPARE_LESS_THAN) {
+ delta = snprintf(ptr, len, "<");
+ ptr += delta;
+ len -= delta;
+ }
+ if (mval->compare_operator & OPERATOR_COMPARE_GREATER_THAN) {
+ delta = snprintf(ptr, len, ">");
+ ptr += delta;
+ len -= delta;
+ }
+ if (mval->compare_operator & OPERATOR_COMPARE_EQUAL_TO) {
+ delta = snprintf(ptr, len, "=");
+ ptr += delta;
+ len -= delta;
+ }
+ if (mval->compare_operator & OPERATOR_COMPARE_EXACT_MATCH) {
+ delta = snprintf(ptr, len, "match");
+ ptr += delta;
+ len -= delta;
+ }
+ ptr += snprintf(ptr, len, " %u", mval->value);
+ return (int)(ptr - str);
+}
+
+#define INCREMENT_DISPLAY(_ptr, _cnt, _len) do { \
+ int sn_delta; \
+ \
+ if (_cnt) { \
+ sn_delta = snprintf((_ptr), (_len), "; ");\
+ (_len) -= sn_delta; \
+ (_ptr) += sn_delta; \
+ } \
+ (_cnt)++; \
+ } while (0)
+
+/* this structure can be used for port range,
+ * but also for other values range like packet length range
+ */
+struct bgp_pbr_range_port {
+ uint16_t min_port;
+ uint16_t max_port;
+};
+
+/* this structure can be used to filter with a mask
+ * for instance it supports not instructions like for
+ * tcpflags
+ */
+struct bgp_pbr_val_mask {
+ uint16_t val;
+ uint16_t mask;
+};
+
+/* this structure is used to pass instructs
+ * so that BGP can create pbr instructions to ZEBRA
+ */
+struct bgp_pbr_filter {
+ uint8_t type;
+ vrf_id_t vrf_id;
+ uint8_t family;
+ struct prefix *src;
+ struct prefix *dst;
+ uint8_t bitmask_iprule;
+ uint8_t protocol;
+ struct bgp_pbr_range_port *pkt_len;
+ struct bgp_pbr_range_port *src_port;
+ struct bgp_pbr_range_port *dst_port;
+ struct bgp_pbr_val_mask *tcp_flags;
+ struct bgp_pbr_val_mask *dscp;
+ struct bgp_pbr_val_mask *flow_label;
+ struct bgp_pbr_val_mask *pkt_len_val;
+ struct bgp_pbr_val_mask *fragment;
+};
+
+/* this structure is used to contain OR instructions
+ * so that BGP can create multiple pbr instructions
+ * to ZEBRA
+ */
+struct bgp_pbr_or_filter {
+ struct list *tcpflags;
+ struct list *dscp;
+ struct list *flowlabel;
+ struct list *pkt_len;
+ struct list *fragment;
+ struct list *icmp_type;
+ struct list *icmp_code;
+};
+
+static void bgp_pbr_policyroute_add_to_zebra_unit(struct bgp *bgp,
+ struct bgp_path_info *path,
+ struct bgp_pbr_filter *bpf,
+ struct nexthop *nh,
+ float *rate);
+
+static void bgp_pbr_dump_entry(struct bgp_pbr_filter *bpf, bool add);
+
+static bool bgp_pbr_extract_enumerate_unary_opposite(
+ uint8_t unary_operator,
+ struct bgp_pbr_val_mask *and_valmask,
+ struct list *or_valmask, uint32_t value,
+ uint8_t type_entry)
+{
+ if (unary_operator == OPERATOR_UNARY_AND && and_valmask) {
+ if (type_entry == FLOWSPEC_TCP_FLAGS) {
+ and_valmask->mask |=
+ TCP_HEADER_ALL_FLAGS &
+ ~(value);
+ } else if (type_entry == FLOWSPEC_DSCP ||
+ type_entry == FLOWSPEC_FLOW_LABEL ||
+ type_entry == FLOWSPEC_PKT_LEN ||
+ type_entry == FLOWSPEC_FRAGMENT) {
+ and_valmask->val = value;
+ and_valmask->mask = 1; /* inverse */
+ }
+ } else if (unary_operator == OPERATOR_UNARY_OR && or_valmask) {
+ and_valmask = XCALLOC(MTYPE_PBR_VALMASK,
+ sizeof(struct bgp_pbr_val_mask));
+ if (type_entry == FLOWSPEC_TCP_FLAGS) {
+ and_valmask->val = TCP_HEADER_ALL_FLAGS;
+ and_valmask->mask |=
+ TCP_HEADER_ALL_FLAGS &
+ ~(value);
+ } else if (type_entry == FLOWSPEC_DSCP ||
+ type_entry == FLOWSPEC_FLOW_LABEL ||
+ type_entry == FLOWSPEC_FRAGMENT ||
+ type_entry == FLOWSPEC_PKT_LEN) {
+ and_valmask->val = value;
+ and_valmask->mask = 1; /* inverse */
+ }
+ listnode_add(or_valmask, and_valmask);
+ } else if (type_entry == FLOWSPEC_ICMP_CODE ||
+ type_entry == FLOWSPEC_ICMP_TYPE)
+ return false;
+ return true;
+}
+
+/* TCP : FIN and SYN -> val = ALL; mask = 3
+ * TCP : not (FIN and SYN) -> val = ALL; mask = ALL & ~(FIN|RST)
+ * other variables type: dscp, pkt len, fragment, flow label
+ * - value is copied in bgp_pbr_val_mask->val value
+ * - if negate form is identifierd, bgp_pbr_val_mask->mask set to 1
+ */
+static bool bgp_pbr_extract_enumerate_unary(struct bgp_pbr_match_val list[],
+ int num, uint8_t unary_operator,
+ void *valmask, uint8_t type_entry)
+{
+ int i = 0;
+ struct bgp_pbr_val_mask *and_valmask = NULL;
+ struct list *or_valmask = NULL;
+ bool ret;
+
+ if (valmask) {
+ if (unary_operator == OPERATOR_UNARY_AND) {
+ and_valmask = (struct bgp_pbr_val_mask *)valmask;
+ memset(and_valmask, 0, sizeof(struct bgp_pbr_val_mask));
+ } else if (unary_operator == OPERATOR_UNARY_OR) {
+ or_valmask = (struct list *)valmask;
+ }
+ }
+ for (i = 0; i < num; i++) {
+ if (i != 0 && list[i].unary_operator !=
+ unary_operator)
+ return false;
+ if (!(list[i].compare_operator &
+ OPERATOR_COMPARE_EQUAL_TO) &&
+ !(list[i].compare_operator &
+ OPERATOR_COMPARE_EXACT_MATCH)) {
+ if ((list[i].compare_operator &
+ OPERATOR_COMPARE_LESS_THAN) &&
+ (list[i].compare_operator &
+ OPERATOR_COMPARE_GREATER_THAN)) {
+ ret = bgp_pbr_extract_enumerate_unary_opposite(
+ unary_operator, and_valmask,
+ or_valmask, list[i].value,
+ type_entry);
+ if (!ret)
+ return ret;
+ continue;
+ }
+ return false;
+ }
+ if (unary_operator == OPERATOR_UNARY_AND && and_valmask) {
+ if (type_entry == FLOWSPEC_TCP_FLAGS)
+ and_valmask->mask |=
+ TCP_HEADER_ALL_FLAGS & list[i].value;
+ } else if (unary_operator == OPERATOR_UNARY_OR && or_valmask) {
+ and_valmask = XCALLOC(MTYPE_PBR_VALMASK,
+ sizeof(struct bgp_pbr_val_mask));
+ if (type_entry == FLOWSPEC_TCP_FLAGS) {
+ and_valmask->val = TCP_HEADER_ALL_FLAGS;
+ and_valmask->mask |=
+ TCP_HEADER_ALL_FLAGS & list[i].value;
+ } else if (type_entry == FLOWSPEC_DSCP ||
+ type_entry == FLOWSPEC_FLOW_LABEL ||
+ type_entry == FLOWSPEC_ICMP_TYPE ||
+ type_entry == FLOWSPEC_ICMP_CODE ||
+ type_entry == FLOWSPEC_FRAGMENT ||
+ type_entry == FLOWSPEC_PKT_LEN)
+ and_valmask->val = list[i].value;
+ listnode_add(or_valmask, and_valmask);
+ }
+ }
+ if (unary_operator == OPERATOR_UNARY_AND && and_valmask
+ && type_entry == FLOWSPEC_TCP_FLAGS)
+ and_valmask->val = TCP_HEADER_ALL_FLAGS;
+ return true;
+}
+
+/* if unary operator can either be UNARY_OR/AND/OR-AND.
+ * in the latter case, combinationf of both is not handled
+ */
+static bool bgp_pbr_extract_enumerate(struct bgp_pbr_match_val list[],
+ int num, uint8_t unary_operator,
+ void *valmask, uint8_t type_entry)
+{
+ bool ret;
+ uint8_t unary_operator_val;
+ bool double_check = false;
+
+ if ((unary_operator & OPERATOR_UNARY_OR) &&
+ (unary_operator & OPERATOR_UNARY_AND)) {
+ unary_operator_val = OPERATOR_UNARY_AND;
+ double_check = true;
+ } else
+ unary_operator_val = unary_operator;
+ ret = bgp_pbr_extract_enumerate_unary(list, num, unary_operator_val,
+ valmask, type_entry);
+ if (!ret && double_check)
+ ret = bgp_pbr_extract_enumerate_unary(list, num,
+ OPERATOR_UNARY_OR,
+ valmask,
+ type_entry);
+ return ret;
+}
+
+/* returns the unary operator that is in the list
+ * return 0 if both operators are used
+ */
+static uint8_t bgp_pbr_match_val_get_operator(struct bgp_pbr_match_val list[],
+ int num)
+
+{
+ int i;
+ uint8_t unary_operator = OPERATOR_UNARY_AND;
+
+ for (i = 0; i < num; i++) {
+ if (i == 0)
+ continue;
+ if (list[i].unary_operator & OPERATOR_UNARY_OR)
+ unary_operator = OPERATOR_UNARY_OR;
+ if ((list[i].unary_operator & OPERATOR_UNARY_AND
+ && unary_operator == OPERATOR_UNARY_OR) ||
+ (list[i].unary_operator & OPERATOR_UNARY_OR
+ && unary_operator == OPERATOR_UNARY_AND))
+ return 0;
+ }
+ return unary_operator;
+}
+
+
+/* return true if extraction ok
+ */
+static bool bgp_pbr_extract(struct bgp_pbr_match_val list[],
+ int num,
+ struct bgp_pbr_range_port *range)
+{
+ int i = 0;
+ bool exact_match = false;
+
+ if (range)
+ memset(range, 0, sizeof(struct bgp_pbr_range_port));
+
+ if (num > 2)
+ return false;
+ for (i = 0; i < num; i++) {
+ if (i != 0 && (list[i].compare_operator ==
+ OPERATOR_COMPARE_EQUAL_TO))
+ return false;
+ if (i == 0 && (list[i].compare_operator ==
+ OPERATOR_COMPARE_EQUAL_TO)) {
+ if (range)
+ range->min_port = list[i].value;
+ exact_match = true;
+ }
+ if (exact_match && i > 0)
+ return false;
+ if (list[i].compare_operator ==
+ (OPERATOR_COMPARE_GREATER_THAN +
+ OPERATOR_COMPARE_EQUAL_TO)) {
+ if (range)
+ range->min_port = list[i].value;
+ } else if (list[i].compare_operator ==
+ (OPERATOR_COMPARE_LESS_THAN +
+ OPERATOR_COMPARE_EQUAL_TO)) {
+ if (range)
+ range->max_port = list[i].value;
+ } else if (list[i].compare_operator ==
+ OPERATOR_COMPARE_LESS_THAN) {
+ if (range)
+ range->max_port = list[i].value - 1;
+ } else if (list[i].compare_operator ==
+ OPERATOR_COMPARE_GREATER_THAN) {
+ if (range)
+ range->min_port = list[i].value + 1;
+ }
+ }
+ return true;
+}
+
+static int bgp_pbr_validate_policy_route(struct bgp_pbr_entry_main *api)
+{
+ bool enumerate_icmp = false;
+
+ if (api->type == BGP_PBR_UNDEFINED) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: pbr entry undefined. cancel.");
+ return 0;
+ }
+ /* because bgp pbr entry may contain unsupported
+ * combinations, a message will be displayed here if
+ * not supported.
+ * for now, only match/set supported is
+ * - combination src/dst => redirect nexthop [ + rate]
+ * - combination src/dst => redirect VRF [ + rate]
+ * - combination src/dst => drop
+ * - combination srcport + @IP
+ */
+ if (api->match_protocol_num > 1) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match protocol operations:multiple protocols ( %d). ignoring.",
+ api->match_protocol_num);
+ return 0;
+ }
+ if (api->src_prefix_offset > 0 ||
+ api->dst_prefix_offset > 0) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match prefix offset:"
+ "implementation does not support it.");
+ return 0;
+ }
+ if (api->match_protocol_num == 1 &&
+ api->protocol[0].value != PROTOCOL_UDP &&
+ api->protocol[0].value != PROTOCOL_ICMP &&
+ api->protocol[0].value != PROTOCOL_ICMPV6 &&
+ api->protocol[0].value != PROTOCOL_TCP) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match protocol operations:protocol (%d) not supported. ignoring",
+ api->match_protocol_num);
+ return 0;
+ }
+ if (!bgp_pbr_extract(api->src_port, api->match_src_port_num, NULL)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match src port operations:too complex. ignoring.");
+ return 0;
+ }
+ if (!bgp_pbr_extract(api->dst_port, api->match_dst_port_num, NULL)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match dst port operations:too complex. ignoring.");
+ return 0;
+ }
+ if (!bgp_pbr_extract_enumerate(api->tcpflags,
+ api->match_tcpflags_num,
+ OPERATOR_UNARY_AND |
+ OPERATOR_UNARY_OR, NULL,
+ FLOWSPEC_TCP_FLAGS)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match tcp flags:too complex. ignoring.");
+ return 0;
+ }
+ if (!bgp_pbr_extract(api->icmp_type, api->match_icmp_type_num, NULL)) {
+ if (!bgp_pbr_extract_enumerate(api->icmp_type,
+ api->match_icmp_type_num,
+ OPERATOR_UNARY_OR, NULL,
+ FLOWSPEC_ICMP_TYPE)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match icmp type operations:too complex. ignoring.");
+ return 0;
+ }
+ enumerate_icmp = true;
+ }
+ if (!bgp_pbr_extract(api->icmp_code, api->match_icmp_code_num, NULL)) {
+ if (!bgp_pbr_extract_enumerate(api->icmp_code,
+ api->match_icmp_code_num,
+ OPERATOR_UNARY_OR, NULL,
+ FLOWSPEC_ICMP_CODE)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match icmp code operations:too complex. ignoring.");
+ return 0;
+ } else if (api->match_icmp_type_num > 1 &&
+ !enumerate_icmp) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match icmp code is enumerate, and icmp type is not. too complex. ignoring.");
+ return 0;
+ }
+ }
+ if (!bgp_pbr_extract(api->port, api->match_port_num, NULL)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match port operations:too complex. ignoring.");
+ return 0;
+ }
+ if (api->match_packet_length_num) {
+ bool ret;
+
+ ret = bgp_pbr_extract(api->packet_length,
+ api->match_packet_length_num, NULL);
+ if (!ret)
+ ret = bgp_pbr_extract_enumerate(api->packet_length,
+ api->match_packet_length_num,
+ OPERATOR_UNARY_OR
+ | OPERATOR_UNARY_AND,
+ NULL, FLOWSPEC_PKT_LEN);
+ if (!ret) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match packet length operations:too complex. ignoring.");
+ return 0;
+ }
+ }
+ if (api->match_dscp_num) {
+ if (!bgp_pbr_extract_enumerate(api->dscp, api->match_dscp_num,
+ OPERATOR_UNARY_OR | OPERATOR_UNARY_AND,
+ NULL, FLOWSPEC_DSCP)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match DSCP operations:too complex. ignoring.");
+ return 0;
+ }
+ }
+ if (api->match_flowlabel_num) {
+ if (api->afi == AFI_IP) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match Flow Label operations:"
+ "Not for IPv4.");
+ return 0;
+ }
+ if (!bgp_pbr_extract_enumerate(api->flow_label,
+ api->match_flowlabel_num,
+ OPERATOR_UNARY_OR | OPERATOR_UNARY_AND,
+ NULL, FLOWSPEC_FLOW_LABEL)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match FlowLabel operations:"
+ "too complex. ignoring.");
+ return 0;
+ }
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match FlowLabel operations "
+ "not supported. ignoring.");
+ return 0;
+ }
+ if (api->match_fragment_num) {
+ char fail_str[64];
+ bool success;
+
+ success = bgp_pbr_extract_enumerate(api->fragment,
+ api->match_fragment_num,
+ OPERATOR_UNARY_OR
+ | OPERATOR_UNARY_AND,
+ NULL, FLOWSPEC_FRAGMENT);
+ if (success) {
+ int i;
+
+ for (i = 0; i < api->match_fragment_num; i++) {
+ if (api->fragment[i].value != 1 &&
+ api->fragment[i].value != 2 &&
+ api->fragment[i].value != 4 &&
+ api->fragment[i].value != 8) {
+ success = false;
+ snprintf(
+ fail_str, sizeof(fail_str),
+ "Value not valid (%d) for this implementation",
+ api->fragment[i].value);
+ }
+ if (api->afi == AFI_IP6 &&
+ api->fragment[i].value == 1) {
+ success = false;
+ snprintf(fail_str, sizeof(fail_str),
+ "IPv6 dont fragment match invalid (%d)",
+ api->fragment[i].value);
+ }
+ }
+ if (api->afi == AFI_IP6) {
+ success = false;
+ snprintf(fail_str, sizeof(fail_str),
+ "%s", IPV6_FRAGMENT_INVALID);
+ }
+ } else
+ snprintf(fail_str, sizeof(fail_str),
+ "too complex. ignoring");
+ if (!success) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match fragment operation (%d) %s",
+ api->match_fragment_num,
+ fail_str);
+ return 0;
+ }
+ }
+
+ /* no combinations with both src_port and dst_port
+ * or port with src_port and dst_port
+ */
+ if (api->match_src_port_num + api->match_dst_port_num +
+ api->match_port_num > 3) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match multiple port operations: too complex. ignoring.");
+ return 0;
+ }
+ if ((api->match_src_port_num || api->match_dst_port_num
+ || api->match_port_num) && (api->match_icmp_type_num
+ || api->match_icmp_code_num)) {
+ if (BGP_DEBUG(pbr, PBR))
+ zlog_debug("BGP: match multiple port/imcp operations: too complex. ignoring.");
+ return 0;
+ }
+ /* iprule only supports redirect IP */
+ if (api->type == BGP_PBR_IPRULE) {
+ int i;
+
+ for (i = 0; i < api->action_num; i++) {
+ if (api->actions[i].action == ACTION_TRAFFICRATE &&
+ api->actions[i].u.r.rate == 0) {
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_debug("BGP: iprule match actions drop not supported");
+ }
+ return 0;
+ }
+ if (api->actions[i].action == ACTION_MARKING) {
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_warn("PBR: iprule set DSCP/Flow Label %u not supported",
+ api->actions[i].u.marking_dscp);
+ }
+ }
+ if (api->actions[i].action == ACTION_REDIRECT) {
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_warn("PBR: iprule redirect VRF %u not supported",
+ api->actions[i].u.redirect_vrf);
+ }
+ }
+ }
+
+ } else if (!(api->match_bitmask & PREFIX_SRC_PRESENT) &&
+ !(api->match_bitmask & PREFIX_DST_PRESENT)) {
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_debug("BGP: match actions without src or dst address can not operate. ignoring.");
+ }
+ return 0;
+ }
+ return 1;
+}
+
+/* return -1 if build or validation failed */
+
+int bgp_pbr_build_and_validate_entry(const struct prefix *p,
+ struct bgp_path_info *path,
+ struct bgp_pbr_entry_main *api)
+{
+ int ret;
+ uint32_t i, action_count = 0;
+ struct ecommunity *ecom;
+ struct ecommunity_val *ecom_eval;
+ struct bgp_pbr_entry_action *api_action;
+ struct prefix *src = NULL, *dst = NULL;
+ int valid_prefix = 0;
+ struct bgp_pbr_entry_action *api_action_redirect_ip = NULL;
+ bool discard_action_found = false;
+ afi_t afi = family2afi(p->u.prefix_flowspec.family);
+
+ /* extract match from flowspec entries */
+ ret = bgp_flowspec_match_rules_fill((uint8_t *)p->u.prefix_flowspec.ptr,
+ p->u.prefix_flowspec.prefixlen, api, afi);
+ if (ret < 0)
+ return -1;
+ /* extract actiosn from flowspec ecom list */
+ if (path && bgp_attr_get_ecommunity(path->attr)) {
+ ecom = bgp_attr_get_ecommunity(path->attr);
+ for (i = 0; i < ecom->size; i++) {
+ ecom_eval = (struct ecommunity_val *)
+ (ecom->val + (i * ECOMMUNITY_SIZE));
+ action_count++;
+ if (action_count > ACTIONS_MAX_NUM) {
+ if (BGP_DEBUG(pbr, PBR_ERROR))
+ flog_err(
+ EC_BGP_FLOWSPEC_PACKET,
+ "%s: %s (max %u)",
+ __func__,
+ FSPEC_ACTION_EXCEED_LIMIT,
+ action_count);
+ break;
+ }
+ api_action = &api->actions[action_count - 1];
+
+ if ((ecom_eval->val[1] ==
+ (char)ECOMMUNITY_REDIRECT_VRF) &&
+ (ecom_eval->val[0] ==
+ (char)ECOMMUNITY_ENCODE_TRANS_EXP ||
+ ecom_eval->val[0] ==
+ (char)ECOMMUNITY_EXTENDED_COMMUNITY_PART_2 ||
+ ecom_eval->val[0] ==
+ (char)ECOMMUNITY_EXTENDED_COMMUNITY_PART_3)) {
+ struct ecommunity *eckey = ecommunity_new();
+ struct ecommunity_val ecom_copy;
+
+ memcpy(&ecom_copy, ecom_eval,
+ sizeof(struct ecommunity_val));
+ ecom_copy.val[0] &=
+ ~ECOMMUNITY_ENCODE_TRANS_EXP;
+ ecom_copy.val[1] = ECOMMUNITY_ROUTE_TARGET;
+ ecommunity_add_val(eckey, &ecom_copy,
+ false, false);
+
+ api_action->action = ACTION_REDIRECT;
+ api_action->u.redirect_vrf =
+ get_first_vrf_for_redirect_with_rt(
+ eckey);
+ ecommunity_free(&eckey);
+ } else if ((ecom_eval->val[0] ==
+ (char)ECOMMUNITY_ENCODE_REDIRECT_IP_NH) &&
+ (ecom_eval->val[1] ==
+ (char)ECOMMUNITY_REDIRECT_IP_NH)) {
+ /* in case the 2 ecom present,
+ * do not overwrite
+ * draft-ietf-idr-flowspec-redirect
+ */
+ if (api_action_redirect_ip &&
+ p->u.prefix_flowspec.family == AF_INET) {
+ if (api_action_redirect_ip->u
+ .zr.redirect_ip_v4.s_addr
+ != INADDR_ANY)
+ continue;
+ if (path->attr->nexthop.s_addr
+ == INADDR_ANY)
+ continue;
+ api_action_redirect_ip->u.zr
+ .redirect_ip_v4.s_addr =
+ path->attr->nexthop.s_addr;
+ api_action_redirect_ip->u.zr.duplicate
+ = ecom_eval->val[7];
+ continue;
+ } else if (api_action_redirect_ip &&
+ p->u.prefix_flowspec.family == AF_INET6) {
+ if (memcmp(&api_action_redirect_ip->u
+ .zr.redirect_ip_v6,
+ &in6addr_any,
+ sizeof(struct in6_addr)))
+ continue;
+ if (path->attr->mp_nexthop_len == 0 ||
+ path->attr->mp_nexthop_len ==
+ BGP_ATTR_NHLEN_IPV4 ||
+ path->attr->mp_nexthop_len ==
+ BGP_ATTR_NHLEN_VPNV4)
+ continue;
+ memcpy(&api_action_redirect_ip->u
+ .zr.redirect_ip_v6,
+ &path->attr->mp_nexthop_global,
+ sizeof(struct in6_addr));
+ api_action_redirect_ip->u.zr.duplicate
+ = ecom_eval->val[7];
+ continue;
+ } else if (p->u.prefix_flowspec.family ==
+ AF_INET) {
+ api_action->action = ACTION_REDIRECT_IP;
+ api_action->u.zr.redirect_ip_v4.s_addr =
+ path->attr->nexthop.s_addr;
+ api_action->u.zr.duplicate =
+ ecom_eval->val[7];
+ api_action_redirect_ip = api_action;
+ } else if (p->u.prefix_flowspec.family ==
+ AF_INET6) {
+ api_action->action = ACTION_REDIRECT_IP;
+ memcpy(&api_action->u
+ .zr.redirect_ip_v6,
+ &path->attr->mp_nexthop_global,
+ sizeof(struct in6_addr));
+ api_action->u.zr.duplicate
+ = ecom_eval->val[7];
+ api_action_redirect_ip = api_action;
+ }
+ } else if ((ecom_eval->val[0] ==
+ (char)ECOMMUNITY_ENCODE_IP) &&
+ (ecom_eval->val[1] ==
+ (char)ECOMMUNITY_FLOWSPEC_REDIRECT_IPV4)) {
+ /* in case the 2 ecom present,
+ * overwrite simpson draft
+ * update redirect ip fields
+ */
+ if (api_action_redirect_ip) {
+ memcpy(&(api_action_redirect_ip->u
+ .zr.redirect_ip_v4.s_addr),
+ (ecom_eval->val+2), 4);
+ api_action_redirect_ip->u
+ .zr.duplicate =
+ ecom_eval->val[7];
+ continue;
+ } else {
+ api_action->action = ACTION_REDIRECT_IP;
+ memcpy(&(api_action->u
+ .zr.redirect_ip_v4.s_addr),
+ (ecom_eval->val+2), 4);
+ api_action->u.zr.duplicate =
+ ecom_eval->val[7];
+ api_action_redirect_ip = api_action;
+ }
+ } else {
+ if (ecom_eval->val[0] !=
+ (char)ECOMMUNITY_ENCODE_TRANS_EXP)
+ continue;
+ ret = ecommunity_fill_pbr_action(ecom_eval,
+ api_action,
+ afi);
+ if (ret != 0)
+ continue;
+ if ((api_action->action == ACTION_TRAFFICRATE)
+ && api->actions[i].u.r.rate == 0)
+ discard_action_found = true;
+ }
+ api->action_num++;
+ }
+ }
+ if (path && path->attr && bgp_attr_get_ipv6_ecommunity(path->attr)) {
+ struct ecommunity_val_ipv6 *ipv6_ecom_eval;
+
+ ecom = bgp_attr_get_ipv6_ecommunity(path->attr);
+ for (i = 0; i < ecom->size; i++) {
+ ipv6_ecom_eval = (struct ecommunity_val_ipv6 *)
+ (ecom->val + (i * ecom->unit_size));
+ action_count++;
+ if (action_count > ACTIONS_MAX_NUM) {
+ if (BGP_DEBUG(pbr, PBR_ERROR))
+ flog_err(
+ EC_BGP_FLOWSPEC_PACKET,
+ "%s: flowspec actions exceeds limit (max %u)",
+ __func__, action_count);
+ break;
+ }
+ api_action = &api->actions[action_count - 1];
+ if ((ipv6_ecom_eval->val[1] ==
+ (char)ECOMMUNITY_FLOWSPEC_REDIRECT_IPV6) &&
+ (ipv6_ecom_eval->val[0] ==
+ (char)ECOMMUNITY_ENCODE_TRANS_EXP)) {
+ struct ecommunity *eckey = ecommunity_new();
+ struct ecommunity_val_ipv6 ecom_copy;
+
+ eckey->unit_size = IPV6_ECOMMUNITY_SIZE;
+ memcpy(&ecom_copy, ipv6_ecom_eval,
+ sizeof(struct ecommunity_val_ipv6));
+ ecom_copy.val[1] = ECOMMUNITY_ROUTE_TARGET;
+ ecommunity_add_val_ipv6(eckey, &ecom_copy,
+ false, false);
+ api_action->action = ACTION_REDIRECT;
+ api_action->u.redirect_vrf =
+ get_first_vrf_for_redirect_with_rt(
+ eckey);
+ ecommunity_free(&eckey);
+ api->action_num++;
+ }
+ }
+ }
+ /* if ECOMMUNITY_TRAFFIC_RATE = 0 as action
+ * then reduce the API action list to that action
+ */
+ if (api->action_num > 1 && discard_action_found) {
+ api->action_num = 1;
+ memset(&api->actions[0], 0,
+ sizeof(struct bgp_pbr_entry_action));
+ api->actions[0].action = ACTION_TRAFFICRATE;
+ }
+
+ /* validate if incoming matc/action is compatible
+ * with our policy routing engine
+ */
+ if (!bgp_pbr_validate_policy_route(api))
+ return -1;
+
+ /* check inconsistency in the match rule */
+ if (api->match_bitmask & PREFIX_SRC_PRESENT) {
+ src = &api->src_prefix;
+ afi = family2afi(src->family);
+ valid_prefix = 1;
+ }
+ if (api->match_bitmask & PREFIX_DST_PRESENT) {
+ dst = &api->dst_prefix;
+ if (valid_prefix && afi != family2afi(dst->family)) {
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_debug("%s: inconsistency: no match for afi src and dst (%u/%u)",
+ __func__, afi, family2afi(dst->family));
+ }
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void bgp_pbr_match_entry_free(void *arg)
+{
+ struct bgp_pbr_match_entry *bpme;
+
+ bpme = (struct bgp_pbr_match_entry *)arg;
+
+ if (bpme->installed) {
+ bgp_send_pbr_ipset_entry_match(bpme, false);
+ bpme->installed = false;
+ bpme->backpointer = NULL;
+ }
+ XFREE(MTYPE_PBR_MATCH_ENTRY, bpme);
+}
+
+static void bgp_pbr_match_free(void *arg)
+{
+ struct bgp_pbr_match *bpm;
+
+ bpm = (struct bgp_pbr_match *)arg;
+
+ hash_clean(bpm->entry_hash, bgp_pbr_match_entry_free);
+
+ if (hashcount(bpm->entry_hash) == 0) {
+ /* delete iptable entry first */
+ /* then delete ipset match */
+ if (bpm->installed) {
+ if (bpm->installed_in_iptable) {
+ bgp_send_pbr_iptable(bpm->action,
+ bpm, false);
+ bpm->installed_in_iptable = false;
+ bpm->action->refcnt--;
+ }
+ bgp_send_pbr_ipset_match(bpm, false);
+ bpm->installed = false;
+ bpm->action = NULL;
+ }
+ }
+ hash_free(bpm->entry_hash);
+
+ XFREE(MTYPE_PBR_MATCH, bpm);
+}
+
+static void *bgp_pbr_match_alloc_intern(void *arg)
+{
+ struct bgp_pbr_match *bpm, *new;
+
+ bpm = (struct bgp_pbr_match *)arg;
+
+ new = XCALLOC(MTYPE_PBR_MATCH, sizeof(*new));
+ memcpy(new, bpm, sizeof(*bpm));
+
+ return new;
+}
+
+static void bgp_pbr_rule_free(void *arg)
+{
+ struct bgp_pbr_rule *bpr;
+
+ bpr = (struct bgp_pbr_rule *)arg;
+
+ /* delete iprule */
+ if (bpr->installed) {
+ bgp_send_pbr_rule_action(bpr->action, bpr, false);
+ bpr->installed = false;
+ bpr->action->refcnt--;
+ bpr->action = NULL;
+ }
+ XFREE(MTYPE_PBR_RULE, bpr);
+}
+
+static void *bgp_pbr_rule_alloc_intern(void *arg)
+{
+ struct bgp_pbr_rule *bpr, *new;
+
+ bpr = (struct bgp_pbr_rule *)arg;
+
+ new = XCALLOC(MTYPE_PBR_RULE, sizeof(*new));
+ memcpy(new, bpr, sizeof(*bpr));
+
+ return new;
+}
+
+static void bgp_pbr_bpa_remove(struct bgp_pbr_action *bpa)
+{
+ if ((bpa->refcnt == 0) && bpa->installed && bpa->table_id != 0) {
+ bgp_send_pbr_rule_action(bpa, NULL, false);
+ bgp_zebra_announce_default(bpa->bgp, &bpa->nh, bpa->afi,
+ bpa->table_id, false);
+ bpa->installed = false;
+ }
+}
+
+static void bgp_pbr_bpa_add(struct bgp_pbr_action *bpa)
+{
+ if (!bpa->installed && !bpa->install_in_progress) {
+ bgp_send_pbr_rule_action(bpa, NULL, true);
+ bgp_zebra_announce_default(bpa->bgp, &bpa->nh, bpa->afi,
+ bpa->table_id, true);
+ }
+}
+
+static void bgp_pbr_action_free(void *arg)
+{
+ struct bgp_pbr_action *bpa = arg;
+
+ bgp_pbr_bpa_remove(bpa);
+
+ XFREE(MTYPE_PBR_ACTION, bpa);
+}
+
+static void *bgp_pbr_action_alloc_intern(void *arg)
+{
+ struct bgp_pbr_action *bpa, *new;
+
+ bpa = (struct bgp_pbr_action *)arg;
+
+ new = XCALLOC(MTYPE_PBR_ACTION, sizeof(*new));
+
+ memcpy(new, bpa, sizeof(*bpa));
+
+ return new;
+}
+
+static void *bgp_pbr_match_entry_alloc_intern(void *arg)
+{
+ struct bgp_pbr_match_entry *bpme, *new;
+
+ bpme = (struct bgp_pbr_match_entry *)arg;
+
+ new = XCALLOC(MTYPE_PBR_MATCH_ENTRY, sizeof(*new));
+
+ memcpy(new, bpme, sizeof(*bpme));
+
+ return new;
+}
+
+uint32_t bgp_pbr_match_hash_key(const void *arg)
+{
+ const struct bgp_pbr_match *pbm = arg;
+ uint32_t key;
+
+ key = jhash_1word(pbm->vrf_id, 0x4312abde);
+ key = jhash_1word(pbm->flags, key);
+ key = jhash_1word(pbm->family, key);
+ key = jhash(&pbm->pkt_len_min, 2, key);
+ key = jhash(&pbm->pkt_len_max, 2, key);
+ key = jhash(&pbm->tcp_flags, 2, key);
+ key = jhash(&pbm->tcp_mask_flags, 2, key);
+ key = jhash(&pbm->dscp_value, 1, key);
+ key = jhash(&pbm->flow_label, 2, key);
+ key = jhash(&pbm->fragment, 1, key);
+ key = jhash(&pbm->protocol, 1, key);
+ return jhash_1word(pbm->type, key);
+}
+
+bool bgp_pbr_match_hash_equal(const void *arg1, const void *arg2)
+{
+ const struct bgp_pbr_match *r1, *r2;
+
+ r1 = (const struct bgp_pbr_match *)arg1;
+ r2 = (const struct bgp_pbr_match *)arg2;
+
+ if (r1->vrf_id != r2->vrf_id)
+ return false;
+
+ if (r1->family != r2->family)
+ return false;
+
+ if (r1->type != r2->type)
+ return false;
+
+ if (r1->flags != r2->flags)
+ return false;
+
+ if (r1->action != r2->action)
+ return false;
+
+ if (r1->pkt_len_min != r2->pkt_len_min)
+ return false;
+
+ if (r1->pkt_len_max != r2->pkt_len_max)
+ return false;
+
+ if (r1->tcp_flags != r2->tcp_flags)
+ return false;
+
+ if (r1->tcp_mask_flags != r2->tcp_mask_flags)
+ return false;
+
+ if (r1->dscp_value != r2->dscp_value)
+ return false;
+
+ if (r1->flow_label != r2->flow_label)
+ return false;
+
+ if (r1->fragment != r2->fragment)
+ return false;
+
+ if (r1->protocol != r2->protocol)
+ return false;
+ return true;
+}
+
+uint32_t bgp_pbr_rule_hash_key(const void *arg)
+{
+ const struct bgp_pbr_rule *pbr = arg;
+ uint32_t key;
+
+ key = prefix_hash_key(&pbr->src);
+ key = jhash_1word(pbr->vrf_id, key);
+ key = jhash_1word(pbr->flags, key);
+ return jhash_1word(prefix_hash_key(&pbr->dst), key);
+}
+
+bool bgp_pbr_rule_hash_equal(const void *arg1, const void *arg2)
+{
+ const struct bgp_pbr_rule *r1, *r2;
+
+ r1 = (const struct bgp_pbr_rule *)arg1;
+ r2 = (const struct bgp_pbr_rule *)arg2;
+
+ if (r1->vrf_id != r2->vrf_id)
+ return false;
+
+ if (r1->flags != r2->flags)
+ return false;
+
+ if (r1->action != r2->action)
+ return false;
+
+ if ((r1->flags & MATCH_IP_SRC_SET) &&
+ !prefix_same(&r1->src, &r2->src))
+ return false;
+
+ if ((r1->flags & MATCH_IP_DST_SET) &&
+ !prefix_same(&r1->dst, &r2->dst))
+ return false;
+
+ return true;
+}
+
+uint32_t bgp_pbr_match_entry_hash_key(const void *arg)
+{
+ const struct bgp_pbr_match_entry *pbme;
+ uint32_t key;
+
+ pbme = arg;
+ key = prefix_hash_key(&pbme->src);
+ key = jhash_1word(prefix_hash_key(&pbme->dst), key);
+ key = jhash(&pbme->dst_port_min, 2, key);
+ key = jhash(&pbme->src_port_min, 2, key);
+ key = jhash(&pbme->dst_port_max, 2, key);
+ key = jhash(&pbme->src_port_max, 2, key);
+ key = jhash(&pbme->proto, 1, key);
+
+ return key;
+}
+
+bool bgp_pbr_match_entry_hash_equal(const void *arg1, const void *arg2)
+{
+ const struct bgp_pbr_match_entry *r1, *r2;
+
+ r1 = (const struct bgp_pbr_match_entry *)arg1;
+ r2 = (const struct bgp_pbr_match_entry *)arg2;
+
+ /*
+ * on updates, comparing backpointer is not necessary
+ * unique value is self calculated
+ * rate is ignored for now
+ */
+
+ if (!prefix_same(&r1->src, &r2->src))
+ return false;
+
+ if (!prefix_same(&r1->dst, &r2->dst))
+ return false;
+
+ if (r1->src_port_min != r2->src_port_min)
+ return false;
+
+ if (r1->dst_port_min != r2->dst_port_min)
+ return false;
+
+ if (r1->src_port_max != r2->src_port_max)
+ return false;
+
+ if (r1->dst_port_max != r2->dst_port_max)
+ return false;
+
+ if (r1->proto != r2->proto)
+ return false;
+
+ return true;
+}
+
+uint32_t bgp_pbr_action_hash_key(const void *arg)
+{
+ const struct bgp_pbr_action *pbra;
+ uint32_t key;
+
+ pbra = arg;
+ key = jhash_1word(pbra->table_id, 0x4312abde);
+ key = jhash_1word(pbra->fwmark, key);
+ key = jhash_1word(pbra->afi, key);
+ return key;
+}
+
+bool bgp_pbr_action_hash_equal(const void *arg1, const void *arg2)
+{
+ const struct bgp_pbr_action *r1, *r2;
+
+ r1 = (const struct bgp_pbr_action *)arg1;
+ r2 = (const struct bgp_pbr_action *)arg2;
+
+ /* unique value is self calculated
+ * table and fwmark is self calculated
+ * rate is ignored
+ */
+ if (r1->vrf_id != r2->vrf_id)
+ return false;
+
+ if (r1->afi != r2->afi)
+ return false;
+
+ return nexthop_same(&r1->nh, &r2->nh);
+}
+
+struct bgp_pbr_rule *bgp_pbr_rule_lookup(vrf_id_t vrf_id,
+ uint32_t unique)
+{
+ struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id);
+ struct bgp_pbr_rule_unique bpru;
+
+ if (!bgp || unique == 0)
+ return NULL;
+ bpru.unique = unique;
+ bpru.bpr_found = NULL;
+ hash_walk(bgp->pbr_rule_hash, bgp_pbr_rule_walkcb, &bpru);
+ return bpru.bpr_found;
+}
+
+struct bgp_pbr_action *bgp_pbr_action_rule_lookup(vrf_id_t vrf_id,
+ uint32_t unique)
+{
+ struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id);
+ struct bgp_pbr_action_unique bpau;
+
+ if (!bgp || unique == 0)
+ return NULL;
+ bpau.unique = unique;
+ bpau.bpa_found = NULL;
+ hash_walk(bgp->pbr_action_hash, bgp_pbr_action_walkcb, &bpau);
+ return bpau.bpa_found;
+}
+
+struct bgp_pbr_match *bgp_pbr_match_ipset_lookup(vrf_id_t vrf_id,
+ uint32_t unique)
+{
+ struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id);
+ struct bgp_pbr_match_unique bpmu;
+
+ if (!bgp || unique == 0)
+ return NULL;
+ bpmu.unique = unique;
+ bpmu.bpm_found = NULL;
+ hash_walk(bgp->pbr_match_hash, bgp_pbr_match_walkcb, &bpmu);
+ return bpmu.bpm_found;
+}
+
+struct bgp_pbr_match_entry *bgp_pbr_match_ipset_entry_lookup(vrf_id_t vrf_id,
+ char *ipset_name,
+ uint32_t unique)
+{
+ struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id);
+ struct bgp_pbr_match_entry_unique bpmeu;
+ struct bgp_pbr_match_ipsetname bpmi;
+
+ if (!bgp || unique == 0)
+ return NULL;
+ bpmi.ipsetname = XCALLOC(MTYPE_TMP, ZEBRA_IPSET_NAME_SIZE);
+ snprintf(bpmi.ipsetname, ZEBRA_IPSET_NAME_SIZE, "%s", ipset_name);
+ bpmi.bpm_found = NULL;
+ hash_walk(bgp->pbr_match_hash, bgp_pbr_match_pername_walkcb, &bpmi);
+ XFREE(MTYPE_TMP, bpmi.ipsetname);
+ if (!bpmi.bpm_found)
+ return NULL;
+ bpmeu.bpme_found = NULL;
+ bpmeu.unique = unique;
+ hash_walk(bpmi.bpm_found->entry_hash,
+ bgp_pbr_match_entry_walkcb, &bpmeu);
+ return bpmeu.bpme_found;
+}
+
+struct bgp_pbr_match *bgp_pbr_match_iptable_lookup(vrf_id_t vrf_id,
+ uint32_t unique)
+{
+ struct bgp *bgp = bgp_lookup_by_vrf_id(vrf_id);
+ struct bgp_pbr_match_iptable_unique bpmiu;
+
+ if (!bgp || unique == 0)
+ return NULL;
+ bpmiu.unique = unique;
+ bpmiu.bpm_found = NULL;
+ hash_walk(bgp->pbr_match_hash, bgp_pbr_match_iptable_walkcb, &bpmiu);
+ return bpmiu.bpm_found;
+}
+
+void bgp_pbr_cleanup(struct bgp *bgp)
+{
+ if (bgp->pbr_match_hash) {
+ hash_clean(bgp->pbr_match_hash, bgp_pbr_match_free);
+ hash_free(bgp->pbr_match_hash);
+ bgp->pbr_match_hash = NULL;
+ }
+ if (bgp->pbr_rule_hash) {
+ hash_clean(bgp->pbr_rule_hash, bgp_pbr_rule_free);
+ hash_free(bgp->pbr_rule_hash);
+ bgp->pbr_rule_hash = NULL;
+ }
+ if (bgp->pbr_action_hash) {
+ hash_clean(bgp->pbr_action_hash, bgp_pbr_action_free);
+ hash_free(bgp->pbr_action_hash);
+ bgp->pbr_action_hash = NULL;
+ }
+ if (bgp->bgp_pbr_cfg == NULL)
+ return;
+ bgp_pbr_reset(bgp, AFI_IP);
+ bgp_pbr_reset(bgp, AFI_IP6);
+ XFREE(MTYPE_PBR, bgp->bgp_pbr_cfg);
+}
+
+void bgp_pbr_init(struct bgp *bgp)
+{
+ bgp->pbr_match_hash =
+ hash_create_size(8, bgp_pbr_match_hash_key,
+ bgp_pbr_match_hash_equal,
+ "Match Hash");
+ bgp->pbr_action_hash =
+ hash_create_size(8, bgp_pbr_action_hash_key,
+ bgp_pbr_action_hash_equal,
+ "Match Hash Entry");
+
+ bgp->pbr_rule_hash =
+ hash_create_size(8, bgp_pbr_rule_hash_key,
+ bgp_pbr_rule_hash_equal,
+ "Match Rule");
+
+ bgp->bgp_pbr_cfg = XCALLOC(MTYPE_PBR, sizeof(struct bgp_pbr_config));
+ bgp->bgp_pbr_cfg->pbr_interface_any_ipv4 = true;
+}
+
+void bgp_pbr_print_policy_route(struct bgp_pbr_entry_main *api)
+{
+ int i = 0;
+ char return_string[512];
+ char *ptr = return_string;
+ int nb_items = 0;
+ int delta, len = sizeof(return_string);
+
+ delta = snprintf(ptr, sizeof(return_string), "MATCH : ");
+ len -= delta;
+ ptr += delta;
+ if (api->match_bitmask & PREFIX_SRC_PRESENT) {
+ struct prefix *p = &(api->src_prefix);
+
+ if (api->src_prefix_offset)
+ delta = snprintfrr(ptr, len, "@src %pFX/off%u", p,
+ api->src_prefix_offset);
+ else
+ delta = snprintfrr(ptr, len, "@src %pFX", p);
+ len -= delta;
+ ptr += delta;
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ }
+ if (api->match_bitmask & PREFIX_DST_PRESENT) {
+ struct prefix *p = &(api->dst_prefix);
+
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ if (api->dst_prefix_offset)
+ delta = snprintfrr(ptr, len, "@dst %pFX/off%u", p,
+ api->dst_prefix_offset);
+ else
+ delta = snprintfrr(ptr, len, "@dst %pFX", p);
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_protocol_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_protocol_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->protocol[i],
+ i > 0 ? NULL : "@proto ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_src_port_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_src_port_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->src_port[i],
+ i > 0 ? NULL : "@srcport ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_dst_port_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_dst_port_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->dst_port[i],
+ i > 0 ? NULL : "@dstport ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_port_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_port_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->port[i],
+ i > 0 ? NULL : "@port ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_icmp_type_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_icmp_type_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->icmp_type[i],
+ i > 0 ? NULL : "@icmptype ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_icmp_code_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_icmp_code_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->icmp_code[i],
+ i > 0 ? NULL : "@icmpcode ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_packet_length_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_packet_length_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len,
+ &api->packet_length[i],
+ i > 0 ? NULL : "@plen ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_dscp_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_dscp_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->dscp[i],
+ i > 0 ? NULL : "@dscp ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_flowlabel_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_flowlabel_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len,
+ &api->flow_label[i],
+ i > 0 ? NULL : "@flowlabel ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_tcpflags_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_tcpflags_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->tcpflags[i],
+ i > 0 ? NULL : "@tcpflags ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ if (api->match_fragment_num)
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ for (i = 0; i < api->match_fragment_num; i++) {
+ delta = snprintf_bgp_pbr_match_val(ptr, len, &api->fragment[i],
+ i > 0 ? NULL : "@fragment ");
+ len -= delta;
+ ptr += delta;
+ }
+
+ len = sizeof(return_string);
+ if (!nb_items) {
+ ptr = return_string;
+ } else {
+ len -= (ptr - return_string);
+ delta = snprintf(ptr, len, "; ");
+ len -= delta;
+ ptr += delta;
+ }
+ if (api->action_num) {
+ delta = snprintf(ptr, len, "SET : ");
+ len -= delta;
+ ptr += delta;
+ }
+ nb_items = 0;
+ for (i = 0; i < api->action_num; i++) {
+ switch (api->actions[i].action) {
+ case ACTION_TRAFFICRATE:
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ delta = snprintf(ptr, len, "@set rate %f",
+ api->actions[i].u.r.rate);
+ len -= delta;
+ ptr += delta;
+ break;
+ case ACTION_TRAFFIC_ACTION:
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ delta = snprintf(ptr, len, "@action ");
+ len -= delta;
+ ptr += delta;
+ if (api->actions[i].u.za.filter
+ & TRAFFIC_ACTION_TERMINATE) {
+ delta = snprintf(ptr, len,
+ " terminate (apply filter(s))");
+ len -= delta;
+ ptr += delta;
+ }
+ if (api->actions[i].u.za.filter
+ & TRAFFIC_ACTION_DISTRIBUTE) {
+ delta = snprintf(ptr, len, " distribute");
+ len -= delta;
+ ptr += delta;
+ }
+ if (api->actions[i].u.za.filter
+ & TRAFFIC_ACTION_SAMPLE) {
+ delta = snprintf(ptr, len, " sample");
+ len -= delta;
+ ptr += delta;
+ }
+ break;
+ case ACTION_REDIRECT_IP: {
+ char local_buff[INET6_ADDRSTRLEN];
+ void *ptr_ip;
+
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ if (api->afi == AF_INET)
+ ptr_ip = &api->actions[i].u.zr.redirect_ip_v4;
+ else
+ ptr_ip = &api->actions[i].u.zr.redirect_ip_v6;
+ if (inet_ntop(afi2family(api->afi),
+ ptr_ip, local_buff,
+ INET6_ADDRSTRLEN) != NULL) {
+ delta = snprintf(ptr, len,
+ "@redirect ip nh %s", local_buff);
+ len -= delta;
+ ptr += delta;
+ }
+ break;
+ }
+ case ACTION_REDIRECT: {
+ struct vrf *vrf;
+
+ vrf = vrf_lookup_by_id(api->actions[i].u.redirect_vrf);
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ delta = snprintf(ptr, len, "@redirect vrf %s(%u)",
+ VRF_LOGNAME(vrf),
+ api->actions[i].u.redirect_vrf);
+ len -= delta;
+ ptr += delta;
+ break;
+ }
+ case ACTION_MARKING:
+ INCREMENT_DISPLAY(ptr, nb_items, len);
+ delta = snprintf(ptr, len, "@set dscp/flowlabel %u",
+ api->actions[i].u.marking_dscp);
+ len -= delta;
+ ptr += delta;
+ break;
+ default:
+ break;
+ }
+ }
+ zlog_info("%s", return_string);
+}
+
+static void bgp_pbr_flush_iprule(struct bgp *bgp, struct bgp_pbr_action *bpa,
+ struct bgp_pbr_rule *bpr)
+{
+ /* if bpr is null, do nothing
+ */
+ if (bpr == NULL)
+ return;
+ if (bpr->installed) {
+ bgp_send_pbr_rule_action(bpa, bpr, false);
+ bpr->installed = false;
+ bpr->action->refcnt--;
+ bpr->action = NULL;
+ if (bpr->path) {
+ struct bgp_path_info *path;
+ struct bgp_path_info_extra *extra;
+
+ /* unlink path to bpme */
+ path = (struct bgp_path_info *)bpr->path;
+ extra = bgp_path_info_extra_get(path);
+ if (extra->bgp_fs_iprule)
+ listnode_delete(extra->bgp_fs_iprule, bpr);
+ bpr->path = NULL;
+ }
+ }
+ hash_release(bgp->pbr_rule_hash, bpr);
+ bgp_pbr_bpa_remove(bpa);
+}
+
+static void bgp_pbr_flush_entry(struct bgp *bgp, struct bgp_pbr_action *bpa,
+ struct bgp_pbr_match *bpm,
+ struct bgp_pbr_match_entry *bpme)
+{
+ /* if bpme is null, bpm is also null
+ */
+ if (bpme == NULL)
+ return;
+ /* ipset del entry */
+ if (bpme->installed) {
+ bgp_send_pbr_ipset_entry_match(bpme, false);
+ bpme->installed = false;
+ bpme->backpointer = NULL;
+ if (bpme->path) {
+ struct bgp_path_info *path;
+ struct bgp_path_info_extra *extra;
+
+ /* unlink path to bpme */
+ path = (struct bgp_path_info *)bpme->path;
+ extra = bgp_path_info_extra_get(path);
+ if (extra->bgp_fs_pbr)
+ listnode_delete(extra->bgp_fs_pbr, bpme);
+ bpme->path = NULL;
+ }
+ }
+ hash_release(bpm->entry_hash, bpme);
+ if (hashcount(bpm->entry_hash) == 0) {
+ /* delete iptable entry first */
+ /* then delete ipset match */
+ if (bpm->installed) {
+ if (bpm->installed_in_iptable) {
+ bgp_send_pbr_iptable(bpm->action,
+ bpm, false);
+ bpm->installed_in_iptable = false;
+ bpm->action->refcnt--;
+ }
+ bgp_send_pbr_ipset_match(bpm, false);
+ bpm->installed = false;
+ bpm->action = NULL;
+ }
+ hash_release(bgp->pbr_match_hash, bpm);
+ /* XXX release pbr_match_action if not used
+ * note that drop does not need to call send_pbr_action
+ */
+ }
+ bgp_pbr_bpa_remove(bpa);
+}
+
+struct bgp_pbr_match_entry_remain {
+ struct bgp_pbr_match_entry *bpme_to_match;
+ struct bgp_pbr_match_entry *bpme_found;
+};
+
+struct bgp_pbr_rule_remain {
+ struct bgp_pbr_rule *bpr_to_match;
+ struct bgp_pbr_rule *bpr_found;
+};
+
+static int bgp_pbr_get_same_rule(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_rule *r1 = (struct bgp_pbr_rule *)bucket->data;
+ struct bgp_pbr_rule_remain *ctxt =
+ (struct bgp_pbr_rule_remain *)arg;
+ struct bgp_pbr_rule *r2;
+
+ r2 = ctxt->bpr_to_match;
+
+ if (r1->vrf_id != r2->vrf_id)
+ return HASHWALK_CONTINUE;
+
+ if (r1->flags != r2->flags)
+ return HASHWALK_CONTINUE;
+
+ if ((r1->flags & MATCH_IP_SRC_SET) &&
+ !prefix_same(&r1->src, &r2->src))
+ return HASHWALK_CONTINUE;
+
+ if ((r1->flags & MATCH_IP_DST_SET) &&
+ !prefix_same(&r1->dst, &r2->dst))
+ return HASHWALK_CONTINUE;
+
+ /* this function is used for two cases:
+ * - remove an entry upon withdraw request
+ * (case r2->action is null)
+ * - replace an old iprule with different action
+ * (case r2->action is != null)
+ * the old one is removed after the new one
+ * this is to avoid disruption in traffic
+ */
+ if (r2->action == NULL ||
+ r1->action != r2->action) {
+ ctxt->bpr_found = r1;
+ return HASHWALK_ABORT;
+ }
+ return HASHWALK_CONTINUE;
+}
+
+static int bgp_pbr_get_remaining_entry(struct hash_bucket *bucket, void *arg)
+{
+ struct bgp_pbr_match *bpm = (struct bgp_pbr_match *)bucket->data;
+ struct bgp_pbr_match_entry_remain *bpmer =
+ (struct bgp_pbr_match_entry_remain *)arg;
+ struct bgp_pbr_match *bpm_temp;
+ struct bgp_pbr_match_entry *bpme = bpmer->bpme_to_match;
+
+ if (!bpme->backpointer ||
+ bpm == bpme->backpointer ||
+ bpme->backpointer->action == bpm->action)
+ return HASHWALK_CONTINUE;
+ /* ensure bpm other characteristics are equal */
+ bpm_temp = bpme->backpointer;
+ if (bpm_temp->vrf_id != bpm->vrf_id ||
+ bpm_temp->type != bpm->type ||
+ bpm_temp->flags != bpm->flags ||
+ bpm_temp->tcp_flags != bpm->tcp_flags ||
+ bpm_temp->tcp_mask_flags != bpm->tcp_mask_flags ||
+ bpm_temp->pkt_len_min != bpm->pkt_len_min ||
+ bpm_temp->pkt_len_max != bpm->pkt_len_max ||
+ bpm_temp->dscp_value != bpm->dscp_value ||
+ bpm_temp->flow_label != bpm->flow_label ||
+ bpm_temp->family != bpm->family ||
+ bpm_temp->fragment != bpm->fragment)
+ return HASHWALK_CONTINUE;
+
+ /* look for remaining bpme */
+ bpmer->bpme_found = hash_lookup(bpm->entry_hash, bpme);
+ if (!bpmer->bpme_found)
+ return HASHWALK_CONTINUE;
+ return HASHWALK_ABORT;
+}
+
+static void bgp_pbr_policyroute_remove_from_zebra_unit(
+ struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf)
+{
+ struct bgp_pbr_match temp;
+ struct bgp_pbr_match_entry temp2;
+ struct bgp_pbr_rule pbr_rule;
+ struct bgp_pbr_rule *bpr;
+ struct bgp_pbr_match *bpm;
+ struct bgp_pbr_match_entry *bpme;
+ struct bgp_pbr_match_entry_remain bpmer;
+ struct bgp_pbr_range_port *src_port;
+ struct bgp_pbr_range_port *dst_port;
+ struct bgp_pbr_range_port *pkt_len;
+ struct bgp_pbr_rule_remain bprr;
+
+ if (!bpf)
+ return;
+ src_port = bpf->src_port;
+ dst_port = bpf->dst_port;
+ pkt_len = bpf->pkt_len;
+
+ if (BGP_DEBUG(zebra, ZEBRA))
+ bgp_pbr_dump_entry(bpf, false);
+
+ /* as we don't know information from EC
+ * look for bpm that have the bpm
+ * with vrf_id characteristics
+ */
+ memset(&temp2, 0, sizeof(temp2));
+ memset(&temp, 0, sizeof(temp));
+
+ if (bpf->type == BGP_PBR_IPRULE) {
+ memset(&pbr_rule, 0, sizeof(pbr_rule));
+ pbr_rule.vrf_id = bpf->vrf_id;
+ if (bpf->src) {
+ prefix_copy(&pbr_rule.src, bpf->src);
+ pbr_rule.flags |= MATCH_IP_SRC_SET;
+ }
+ if (bpf->dst) {
+ prefix_copy(&pbr_rule.dst, bpf->dst);
+ pbr_rule.flags |= MATCH_IP_DST_SET;
+ }
+ bpr = &pbr_rule;
+ /* A previous entry may already exist
+ * flush previous entry if necessary
+ */
+ bprr.bpr_to_match = bpr;
+ bprr.bpr_found = NULL;
+ hash_walk(bgp->pbr_rule_hash, bgp_pbr_get_same_rule, &bprr);
+ if (bprr.bpr_found) {
+ static struct bgp_pbr_rule *local_bpr;
+ static struct bgp_pbr_action *local_bpa;
+
+ local_bpr = bprr.bpr_found;
+ local_bpa = local_bpr->action;
+ bgp_pbr_flush_iprule(bgp, local_bpa,
+ local_bpr);
+ }
+ return;
+ }
+
+ temp.family = bpf->family;
+ if (bpf->src) {
+ temp.flags |= MATCH_IP_SRC_SET;
+ prefix_copy(&temp2.src, bpf->src);
+ } else
+ temp2.src.family = bpf->family;
+ if (bpf->dst) {
+ temp.flags |= MATCH_IP_DST_SET;
+ prefix_copy(&temp2.dst, bpf->dst);
+ } else
+ temp2.dst.family = bpf->family;
+ if (src_port && (src_port->min_port || bpf->protocol == IPPROTO_ICMP)) {
+ if (bpf->protocol == IPPROTO_ICMP)
+ temp.flags |= MATCH_ICMP_SET;
+ temp.flags |= MATCH_PORT_SRC_SET;
+ temp2.src_port_min = src_port->min_port;
+ if (src_port->max_port) {
+ temp.flags |= MATCH_PORT_SRC_RANGE_SET;
+ temp2.src_port_max = src_port->max_port;
+ }
+ }
+ if (dst_port && (dst_port->min_port || bpf->protocol == IPPROTO_ICMP)) {
+ if (bpf->protocol == IPPROTO_ICMP)
+ temp.flags |= MATCH_ICMP_SET;
+ temp.flags |= MATCH_PORT_DST_SET;
+ temp2.dst_port_min = dst_port->min_port;
+ if (dst_port->max_port) {
+ temp.flags |= MATCH_PORT_DST_RANGE_SET;
+ temp2.dst_port_max = dst_port->max_port;
+ }
+ }
+ temp2.proto = bpf->protocol;
+
+ if (pkt_len) {
+ temp.pkt_len_min = pkt_len->min_port;
+ if (pkt_len->max_port)
+ temp.pkt_len_max = pkt_len->max_port;
+ } else if (bpf->pkt_len_val) {
+ if (bpf->pkt_len_val->mask)
+ temp.flags |= MATCH_PKT_LEN_INVERSE_SET;
+ temp.pkt_len_min = bpf->pkt_len_val->val;
+ }
+ if (bpf->tcp_flags) {
+ temp.tcp_flags = bpf->tcp_flags->val;
+ temp.tcp_mask_flags = bpf->tcp_flags->mask;
+ }
+ if (bpf->dscp) {
+ if (bpf->dscp->mask)
+ temp.flags |= MATCH_DSCP_INVERSE_SET;
+ else
+ temp.flags |= MATCH_DSCP_SET;
+ temp.dscp_value = bpf->dscp->val;
+ }
+ if (bpf->flow_label) {
+ if (bpf->flow_label->mask)
+ temp.flags |= MATCH_FLOW_LABEL_INVERSE_SET;
+ else
+ temp.flags |= MATCH_FLOW_LABEL_SET;
+ temp.flow_label = bpf->flow_label->val;
+ }
+
+ if (bpf->fragment) {
+ if (bpf->fragment->mask)
+ temp.flags |= MATCH_FRAGMENT_INVERSE_SET;
+ temp.fragment = bpf->fragment->val;
+ }
+
+ if (bpf->src == NULL || bpf->dst == NULL) {
+ if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET))
+ temp.type = IPSET_NET_PORT;
+ else
+ temp.type = IPSET_NET;
+ } else {
+ if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET))
+ temp.type = IPSET_NET_PORT_NET;
+ else
+ temp.type = IPSET_NET_NET;
+ }
+ if (bpf->vrf_id == VRF_UNKNOWN) /* XXX case BGP destroy */
+ temp.vrf_id = VRF_DEFAULT;
+ else
+ temp.vrf_id = bpf->vrf_id;
+ bpme = &temp2;
+ bpm = &temp;
+ bpme->backpointer = bpm;
+ /* right now, a previous entry may already exist
+ * flush previous entry if necessary
+ */
+ bpmer.bpme_to_match = bpme;
+ bpmer.bpme_found = NULL;
+ hash_walk(bgp->pbr_match_hash, bgp_pbr_get_remaining_entry, &bpmer);
+ if (bpmer.bpme_found) {
+ static struct bgp_pbr_match *local_bpm;
+ static struct bgp_pbr_action *local_bpa;
+
+ local_bpm = bpmer.bpme_found->backpointer;
+ local_bpa = local_bpm->action;
+ bgp_pbr_flush_entry(bgp, local_bpa,
+ local_bpm, bpmer.bpme_found);
+ }
+}
+
+static uint8_t bgp_pbr_next_type_entry(uint8_t type_entry)
+{
+ if (type_entry == FLOWSPEC_TCP_FLAGS)
+ return FLOWSPEC_DSCP;
+ if (type_entry == FLOWSPEC_DSCP)
+ return FLOWSPEC_FLOW_LABEL;
+ if (type_entry == FLOWSPEC_FLOW_LABEL)
+ return FLOWSPEC_PKT_LEN;
+ if (type_entry == FLOWSPEC_PKT_LEN)
+ return FLOWSPEC_FRAGMENT;
+ if (type_entry == FLOWSPEC_FRAGMENT)
+ return FLOWSPEC_ICMP_TYPE;
+ return 0;
+}
+
+static void bgp_pbr_icmp_action(struct bgp *bgp, struct bgp_path_info *path,
+ struct bgp_pbr_filter *bpf,
+ struct bgp_pbr_or_filter *bpof, bool add,
+ struct nexthop *nh, float *rate)
+{
+ struct bgp_pbr_range_port srcp, dstp;
+ struct bgp_pbr_val_mask *icmp_type, *icmp_code;
+ struct listnode *tnode, *cnode;
+
+ if (!bpf)
+ return;
+ if (bpf->protocol != IPPROTO_ICMP)
+ return;
+
+ memset(&srcp, 0, sizeof(srcp));
+ memset(&dstp, 0, sizeof(dstp));
+ bpf->src_port = &srcp;
+ bpf->dst_port = &dstp;
+ /* parse icmp type and lookup appropriate icmp code
+ * if no icmp code found, create as many entryes as
+ * there are listed icmp codes for that icmp type
+ */
+ if (!bpof->icmp_type) {
+ srcp.min_port = 0;
+ srcp.max_port = 255;
+ for (ALL_LIST_ELEMENTS_RO(bpof->icmp_code, cnode, icmp_code)) {
+ dstp.min_port = icmp_code->val;
+ if (add)
+ bgp_pbr_policyroute_add_to_zebra_unit(
+ bgp, path, bpf, nh, rate);
+ else
+ bgp_pbr_policyroute_remove_from_zebra_unit(
+ bgp, path, bpf);
+ }
+ return;
+ }
+ for (ALL_LIST_ELEMENTS_RO(bpof->icmp_type, tnode, icmp_type)) {
+ srcp.min_port = icmp_type->val;
+ srcp.max_port = 0;
+ dstp.max_port = 0;
+ /* only icmp type. create an entry only with icmp type */
+ if (!bpof->icmp_code) {
+ /* icmp type is not one of the above
+ * forge an entry only based on the icmp type
+ */
+ dstp.min_port = 0;
+ dstp.max_port = 255;
+ if (add)
+ bgp_pbr_policyroute_add_to_zebra_unit(
+ bgp, path, bpf, nh, rate);
+ else
+ bgp_pbr_policyroute_remove_from_zebra_unit(
+ bgp, path, bpf);
+ continue;
+ }
+ for (ALL_LIST_ELEMENTS_RO(bpof->icmp_code, cnode, icmp_code)) {
+ dstp.min_port = icmp_code->val;
+ if (add)
+ bgp_pbr_policyroute_add_to_zebra_unit(
+ bgp, path, bpf, nh, rate);
+ else
+ bgp_pbr_policyroute_remove_from_zebra_unit(
+ bgp, path, bpf);
+ }
+ }
+
+ bpf->src_port = NULL;
+ bpf->dst_port = NULL;
+}
+
+static void bgp_pbr_policyroute_remove_from_zebra_recursive(
+ struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf,
+ struct bgp_pbr_or_filter *bpof, uint8_t type_entry)
+{
+ struct listnode *node, *nnode;
+ struct bgp_pbr_val_mask *valmask;
+ uint8_t next_type_entry;
+ struct list *orig_list;
+ struct bgp_pbr_val_mask **target_val;
+
+ if (type_entry == 0) {
+ bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf);
+ return;
+ }
+ next_type_entry = bgp_pbr_next_type_entry(type_entry);
+ if (type_entry == FLOWSPEC_TCP_FLAGS && bpof->tcpflags) {
+ orig_list = bpof->tcpflags;
+ target_val = &bpf->tcp_flags;
+ } else if (type_entry == FLOWSPEC_DSCP && bpof->dscp) {
+ orig_list = bpof->dscp;
+ target_val = &bpf->dscp;
+ } else if (type_entry == FLOWSPEC_FLOW_LABEL && bpof->flowlabel) {
+ orig_list = bpof->flowlabel;
+ target_val = &bpf->flow_label;
+ } else if (type_entry == FLOWSPEC_PKT_LEN && bpof->pkt_len) {
+ orig_list = bpof->pkt_len;
+ target_val = &bpf->pkt_len_val;
+ } else if (type_entry == FLOWSPEC_FRAGMENT && bpof->fragment) {
+ orig_list = bpof->fragment;
+ target_val = &bpf->fragment;
+ } else if (type_entry == FLOWSPEC_ICMP_TYPE &&
+ (bpof->icmp_type || bpof->icmp_code)) {
+ /* enumerate list for icmp - must be last one */
+ bgp_pbr_icmp_action(bgp, path, bpf, bpof, false, NULL, NULL);
+ return;
+ } else {
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, next_type_entry);
+ return;
+ }
+ for (ALL_LIST_ELEMENTS(orig_list, node, nnode, valmask)) {
+ *target_val = valmask;
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, next_type_entry);
+ }
+}
+
+static void bgp_pbr_policyroute_remove_from_zebra(
+ struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf,
+ struct bgp_pbr_or_filter *bpof)
+{
+ if (!bpof) {
+ bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf);
+ return;
+ }
+ if (bpof->tcpflags)
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, FLOWSPEC_TCP_FLAGS);
+ else if (bpof->dscp)
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, FLOWSPEC_DSCP);
+ else if (bpof->flowlabel)
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, FLOWSPEC_FLOW_LABEL);
+ else if (bpof->pkt_len)
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, FLOWSPEC_PKT_LEN);
+ else if (bpof->fragment)
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, FLOWSPEC_FRAGMENT);
+ else if (bpof->icmp_type || bpof->icmp_code)
+ bgp_pbr_policyroute_remove_from_zebra_recursive(
+ bgp, path, bpf, bpof, FLOWSPEC_ICMP_TYPE);
+ else
+ bgp_pbr_policyroute_remove_from_zebra_unit(bgp, path, bpf);
+ /* flush bpof */
+ if (bpof->tcpflags)
+ list_delete_all_node(bpof->tcpflags);
+ if (bpof->dscp)
+ list_delete_all_node(bpof->dscp);
+ if (bpof->flowlabel)
+ list_delete_all_node(bpof->flowlabel);
+ if (bpof->pkt_len)
+ list_delete_all_node(bpof->pkt_len);
+ if (bpof->fragment)
+ list_delete_all_node(bpof->fragment);
+}
+
+static void bgp_pbr_dump_entry(struct bgp_pbr_filter *bpf, bool add)
+{
+ struct bgp_pbr_range_port *src_port;
+ struct bgp_pbr_range_port *dst_port;
+ struct bgp_pbr_range_port *pkt_len;
+ char bufsrc[64], bufdst[64];
+ char buffer[64];
+ int remaining_len = 0;
+ char protocol_str[16];
+
+ if (!bpf)
+ return;
+ src_port = bpf->src_port;
+ dst_port = bpf->dst_port;
+ pkt_len = bpf->pkt_len;
+
+ protocol_str[0] = '\0';
+ if (bpf->tcp_flags && bpf->tcp_flags->mask)
+ bpf->protocol = IPPROTO_TCP;
+ if (bpf->protocol)
+ snprintf(protocol_str, sizeof(protocol_str),
+ "proto %d", bpf->protocol);
+ buffer[0] = '\0';
+ if (bpf->protocol == IPPROTO_ICMP && src_port && dst_port)
+ remaining_len += snprintf(buffer, sizeof(buffer),
+ "type %d, code %d",
+ src_port->min_port,
+ dst_port->min_port);
+ else if (bpf->protocol == IPPROTO_UDP ||
+ bpf->protocol == IPPROTO_TCP) {
+
+ if (src_port && src_port->min_port)
+ remaining_len += snprintf(buffer,
+ sizeof(buffer),
+ "from [%u:%u]",
+ src_port->min_port,
+ src_port->max_port ?
+ src_port->max_port :
+ src_port->min_port);
+ if (dst_port && dst_port->min_port)
+ remaining_len += snprintf(buffer +
+ remaining_len,
+ sizeof(buffer)
+ - remaining_len,
+ "to [%u:%u]",
+ dst_port->min_port,
+ dst_port->max_port ?
+ dst_port->max_port :
+ dst_port->min_port);
+ }
+ if (pkt_len && (pkt_len->min_port || pkt_len->max_port)) {
+ remaining_len += snprintf(buffer + remaining_len,
+ sizeof(buffer)
+ - remaining_len,
+ " len [%u:%u]",
+ pkt_len->min_port,
+ pkt_len->max_port ?
+ pkt_len->max_port :
+ pkt_len->min_port);
+ } else if (bpf->pkt_len_val) {
+ remaining_len += snprintf(buffer + remaining_len,
+ sizeof(buffer)
+ - remaining_len,
+ " %s len %u",
+ bpf->pkt_len_val->mask
+ ? "!" : "",
+ bpf->pkt_len_val->val);
+ }
+ if (bpf->tcp_flags) {
+ remaining_len += snprintf(buffer + remaining_len,
+ sizeof(buffer)
+ - remaining_len,
+ "tcpflags %x/%x",
+ bpf->tcp_flags->val,
+ bpf->tcp_flags->mask);
+ }
+ if (bpf->dscp) {
+ snprintf(buffer + remaining_len,
+ sizeof(buffer)
+ - remaining_len,
+ "%s dscp %d",
+ bpf->dscp->mask
+ ? "!" : "",
+ bpf->dscp->val);
+ }
+ if (bpf->flow_label) {
+ snprintf(buffer + remaining_len,
+ sizeof(buffer)
+ - remaining_len,
+ "%s flow_label %d",
+ bpf->flow_label->mask
+ ? "!" : "",
+ bpf->flow_label->val);
+ }
+ zlog_debug("BGP: %s FS PBR from %s to %s, %s %s",
+ add ? "adding" : "removing",
+ bpf->src == NULL ? "<all>" :
+ prefix2str(bpf->src, bufsrc, sizeof(bufsrc)),
+ bpf->dst == NULL ? "<all>" :
+ prefix2str(bpf->dst, bufdst, sizeof(bufdst)),
+ protocol_str, buffer);
+
+}
+
+static void bgp_pbr_policyroute_add_to_zebra_unit(struct bgp *bgp,
+ struct bgp_path_info *path,
+ struct bgp_pbr_filter *bpf,
+ struct nexthop *nh,
+ float *rate)
+{
+ struct bgp_pbr_match temp;
+ struct bgp_pbr_match_entry temp2;
+ struct bgp_pbr_match *bpm;
+ struct bgp_pbr_match_entry *bpme = NULL;
+ struct bgp_pbr_action temp3;
+ struct bgp_pbr_action *bpa = NULL;
+ struct bgp_pbr_match_entry_remain bpmer;
+ struct bgp_pbr_rule_remain bprr;
+ struct bgp_pbr_range_port *src_port;
+ struct bgp_pbr_range_port *dst_port;
+ struct bgp_pbr_range_port *pkt_len;
+ struct bgp_pbr_rule pbr_rule;
+ struct bgp_pbr_rule *bpr;
+ bool bpr_found = false;
+ bool bpme_found = false;
+ struct vrf *vrf = NULL;
+
+ if (!bpf)
+ return;
+ src_port = bpf->src_port;
+ dst_port = bpf->dst_port;
+ pkt_len = bpf->pkt_len;
+
+ if (BGP_DEBUG(zebra, ZEBRA))
+ bgp_pbr_dump_entry(bpf, true);
+
+ /* look for bpa first */
+ memset(&temp3, 0, sizeof(temp3));
+ if (rate)
+ temp3.rate = *rate;
+ if (nh)
+ memcpy(&temp3.nh, nh, sizeof(struct nexthop));
+ temp3.vrf_id = bpf->vrf_id;
+ temp3.afi = family2afi(bpf->family);
+ bpa = hash_get(bgp->pbr_action_hash, &temp3,
+ bgp_pbr_action_alloc_intern);
+
+ if (nh)
+ vrf = vrf_lookup_by_id(nh->vrf_id);
+ if (bpa->fwmark == 0) {
+ /* drop is handled by iptable */
+ if (nh && nh->type == NEXTHOP_TYPE_BLACKHOLE) {
+ bpa->table_id = 0;
+ bpa->installed = true;
+ } else {
+ bpa->fwmark = bgp_zebra_tm_get_id();
+ /* if action is redirect-vrf, then
+ * use directly table_id of vrf
+ */
+ if (nh && vrf && !vrf_is_backend_netns()
+ && bpf->vrf_id != vrf->vrf_id)
+ bpa->table_id = vrf->data.l.table_id;
+ else
+ bpa->table_id = bpa->fwmark;
+ bpa->installed = false;
+ }
+ bpa->bgp = bgp;
+ bpa->unique = ++bgp_pbr_action_counter_unique;
+ /* 0 value is forbidden */
+ bpa->install_in_progress = false;
+ }
+ if (bpf->type == BGP_PBR_IPRULE) {
+ memset(&pbr_rule, 0, sizeof(pbr_rule));
+ pbr_rule.vrf_id = bpf->vrf_id;
+ pbr_rule.priority = 20;
+ if (bpf->src) {
+ pbr_rule.flags |= MATCH_IP_SRC_SET;
+ prefix_copy(&pbr_rule.src, bpf->src);
+ }
+ if (bpf->dst) {
+ pbr_rule.flags |= MATCH_IP_DST_SET;
+ prefix_copy(&pbr_rule.dst, bpf->dst);
+ }
+ pbr_rule.action = bpa;
+ bpr = hash_get(bgp->pbr_rule_hash, &pbr_rule,
+ bgp_pbr_rule_alloc_intern);
+ if (bpr->unique == 0) {
+ bpr->unique = ++bgp_pbr_action_counter_unique;
+ bpr->installed = false;
+ bpr->install_in_progress = false;
+ /* link bgp info to bpr */
+ bpr->path = (void *)path;
+ } else
+ bpr_found = true;
+ /* already installed */
+ if (bpr_found) {
+ struct bgp_path_info_extra *extra =
+ bgp_path_info_extra_get(path);
+
+ if (extra &&
+ listnode_lookup_nocheck(extra->bgp_fs_iprule,
+ bpr)) {
+ if (BGP_DEBUG(pbr, PBR_ERROR))
+ zlog_err("%s: entry %p/%p already installed in bgp pbr iprule",
+ __func__, path, bpr);
+ return;
+ }
+ }
+
+ bgp_pbr_bpa_add(bpa);
+
+ /* ip rule add */
+ if (!bpr->installed)
+ bgp_send_pbr_rule_action(bpa, bpr, true);
+
+ /* A previous entry may already exist
+ * flush previous entry if necessary
+ */
+ bprr.bpr_to_match = bpr;
+ bprr.bpr_found = NULL;
+ hash_walk(bgp->pbr_rule_hash, bgp_pbr_get_same_rule, &bprr);
+ if (bprr.bpr_found) {
+ static struct bgp_pbr_rule *local_bpr;
+ static struct bgp_pbr_action *local_bpa;
+
+ local_bpr = bprr.bpr_found;
+ local_bpa = local_bpr->action;
+ bgp_pbr_flush_iprule(bgp, local_bpa,
+ local_bpr);
+ }
+ return;
+ }
+ /* then look for bpm */
+ memset(&temp, 0, sizeof(temp));
+ temp.vrf_id = bpf->vrf_id;
+ temp.family = bpf->family;
+ if (bpf->src)
+ temp.flags |= MATCH_IP_SRC_SET;
+ if (bpf->dst)
+ temp.flags |= MATCH_IP_DST_SET;
+
+ if (src_port && (src_port->min_port || bpf->protocol == IPPROTO_ICMP)) {
+ if (bpf->protocol == IPPROTO_ICMP)
+ temp.flags |= MATCH_ICMP_SET;
+ temp.flags |= MATCH_PORT_SRC_SET;
+ }
+ if (dst_port && (dst_port->min_port || bpf->protocol == IPPROTO_ICMP)) {
+ if (bpf->protocol == IPPROTO_ICMP)
+ temp.flags |= MATCH_ICMP_SET;
+ temp.flags |= MATCH_PORT_DST_SET;
+ }
+ if (src_port && src_port->max_port)
+ temp.flags |= MATCH_PORT_SRC_RANGE_SET;
+ if (dst_port && dst_port->max_port)
+ temp.flags |= MATCH_PORT_DST_RANGE_SET;
+
+ if (bpf->src == NULL || bpf->dst == NULL) {
+ if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET))
+ temp.type = IPSET_NET_PORT;
+ else
+ temp.type = IPSET_NET;
+ } else {
+ if (temp.flags & (MATCH_PORT_DST_SET | MATCH_PORT_SRC_SET))
+ temp.type = IPSET_NET_PORT_NET;
+ else
+ temp.type = IPSET_NET_NET;
+ }
+ if (pkt_len) {
+ temp.pkt_len_min = pkt_len->min_port;
+ if (pkt_len->max_port)
+ temp.pkt_len_max = pkt_len->max_port;
+ } else if (bpf->pkt_len_val) {
+ if (bpf->pkt_len_val->mask)
+ temp.flags |= MATCH_PKT_LEN_INVERSE_SET;
+ temp.pkt_len_min = bpf->pkt_len_val->val;
+ }
+ if (bpf->tcp_flags) {
+ temp.tcp_flags = bpf->tcp_flags->val;
+ temp.tcp_mask_flags = bpf->tcp_flags->mask;
+ }
+ if (bpf->dscp) {
+ if (bpf->dscp->mask)
+ temp.flags |= MATCH_DSCP_INVERSE_SET;
+ else
+ temp.flags |= MATCH_DSCP_SET;
+ temp.dscp_value = bpf->dscp->val;
+ }
+ if (bpf->flow_label) {
+ if (bpf->flow_label->mask)
+ temp.flags |= MATCH_FLOW_LABEL_INVERSE_SET;
+ else
+ temp.flags |= MATCH_FLOW_LABEL_SET;
+ temp.flow_label = bpf->flow_label->val;
+ }
+ if (bpf->fragment) {
+ if (bpf->fragment->mask)
+ temp.flags |= MATCH_FRAGMENT_INVERSE_SET;
+ temp.fragment = bpf->fragment->val;
+ }
+ if (bpf->protocol) {
+ temp.protocol = bpf->protocol;
+ temp.flags |= MATCH_PROTOCOL_SET;
+ }
+ temp.action = bpa;
+ bpm = hash_get(bgp->pbr_match_hash, &temp,
+ bgp_pbr_match_alloc_intern);
+
+ /* new, then self allocate ipset_name and unique */
+ if (bpm->unique == 0) {
+ bpm->unique = ++bgp_pbr_match_counter_unique;
+ /* 0 value is forbidden */
+ snprintf(bpm->ipset_name, sizeof(bpm->ipset_name),
+ "match%p", bpm);
+ bpm->entry_hash = hash_create_size(8,
+ bgp_pbr_match_entry_hash_key,
+ bgp_pbr_match_entry_hash_equal,
+ "Match Entry Hash");
+ bpm->installed = false;
+
+ /* unique2 should be updated too */
+ bpm->unique2 = ++bgp_pbr_match_iptable_counter_unique;
+ bpm->installed_in_iptable = false;
+ bpm->install_in_progress = false;
+ bpm->install_iptable_in_progress = false;
+ }
+
+ memset(&temp2, 0, sizeof(temp2));
+ if (bpf->src)
+ prefix_copy(&temp2.src, bpf->src);
+ else
+ temp2.src.family = bpf->family;
+ if (bpf->dst)
+ prefix_copy(&temp2.dst, bpf->dst);
+ else
+ temp2.dst.family = bpf->family;
+ temp2.src_port_min = src_port ? src_port->min_port : 0;
+ temp2.dst_port_min = dst_port ? dst_port->min_port : 0;
+ temp2.src_port_max = src_port ? src_port->max_port : 0;
+ temp2.dst_port_max = dst_port ? dst_port->max_port : 0;
+ temp2.proto = bpf->protocol;
+ bpme = hash_get(bpm->entry_hash, &temp2,
+ bgp_pbr_match_entry_alloc_intern);
+ if (bpme->unique == 0) {
+ bpme->unique = ++bgp_pbr_match_entry_counter_unique;
+ /* 0 value is forbidden */
+ bpme->backpointer = bpm;
+ bpme->installed = false;
+ bpme->install_in_progress = false;
+ /* link bgp info to bpme */
+ bpme->path = (void *)path;
+ } else
+ bpme_found = true;
+
+ /* already installed */
+ if (bpme_found) {
+ struct bgp_path_info_extra *extra =
+ bgp_path_info_extra_get(path);
+
+ if (extra &&
+ listnode_lookup_nocheck(extra->bgp_fs_pbr, bpme)) {
+ if (BGP_DEBUG(pbr, PBR_ERROR))
+ zlog_err(
+ "%s: entry %p/%p already installed in bgp pbr",
+ __func__, path, bpme);
+ return;
+ }
+ }
+ /* BGP FS: append entry to zebra
+ * - policies are not routing entries and as such
+ * route replace semantics don't necessarily follow
+ * through to policy entries
+ * - because of that, not all policing information will be stored
+ * into zebra. and non selected policies will be suppressed from zebra
+ * - as consequence, in order to bring consistency
+ * a policy will be added, then ifan ecmp policy exists,
+ * it will be suppressed subsequently
+ */
+ /* ip rule add */
+ bgp_pbr_bpa_add(bpa);
+
+ /* ipset create */
+ if (!bpm->installed)
+ bgp_send_pbr_ipset_match(bpm, true);
+ /* ipset add */
+ if (!bpme->installed)
+ bgp_send_pbr_ipset_entry_match(bpme, true);
+
+ /* iptables */
+ if (!bpm->installed_in_iptable)
+ bgp_send_pbr_iptable(bpa, bpm, true);
+
+ /* A previous entry may already exist
+ * flush previous entry if necessary
+ */
+ bpmer.bpme_to_match = bpme;
+ bpmer.bpme_found = NULL;
+ hash_walk(bgp->pbr_match_hash, bgp_pbr_get_remaining_entry, &bpmer);
+ if (bpmer.bpme_found) {
+ static struct bgp_pbr_match *local_bpm;
+ static struct bgp_pbr_action *local_bpa;
+
+ local_bpm = bpmer.bpme_found->backpointer;
+ local_bpa = local_bpm->action;
+ bgp_pbr_flush_entry(bgp, local_bpa,
+ local_bpm, bpmer.bpme_found);
+ }
+
+
+}
+
+static void bgp_pbr_policyroute_add_to_zebra_recursive(
+ struct bgp *bgp, struct bgp_path_info *path, struct bgp_pbr_filter *bpf,
+ struct bgp_pbr_or_filter *bpof, struct nexthop *nh, float *rate,
+ uint8_t type_entry)
+{
+ struct listnode *node, *nnode;
+ struct bgp_pbr_val_mask *valmask;
+ uint8_t next_type_entry;
+ struct list *orig_list;
+ struct bgp_pbr_val_mask **target_val;
+
+ if (type_entry == 0) {
+ bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate);
+ return;
+ }
+ next_type_entry = bgp_pbr_next_type_entry(type_entry);
+ if (type_entry == FLOWSPEC_TCP_FLAGS && bpof->tcpflags) {
+ orig_list = bpof->tcpflags;
+ target_val = &bpf->tcp_flags;
+ } else if (type_entry == FLOWSPEC_DSCP && bpof->dscp) {
+ orig_list = bpof->dscp;
+ target_val = &bpf->dscp;
+ } else if (type_entry == FLOWSPEC_PKT_LEN && bpof->pkt_len) {
+ orig_list = bpof->pkt_len;
+ target_val = &bpf->pkt_len_val;
+ } else if (type_entry == FLOWSPEC_FRAGMENT && bpof->fragment) {
+ orig_list = bpof->fragment;
+ target_val = &bpf->fragment;
+ } else if (type_entry == FLOWSPEC_ICMP_TYPE &&
+ (bpof->icmp_type || bpof->icmp_code)) {
+ /* enumerate list for icmp - must be last one */
+ bgp_pbr_icmp_action(bgp, path, bpf, bpof, true, nh, rate);
+ return;
+ } else {
+ bgp_pbr_policyroute_add_to_zebra_recursive(
+ bgp, path, bpf, bpof, nh, rate, next_type_entry);
+ return;
+ }
+ for (ALL_LIST_ELEMENTS(orig_list, node, nnode, valmask)) {
+ *target_val = valmask;
+ bgp_pbr_policyroute_add_to_zebra_recursive(
+ bgp, path, bpf, bpof, nh, rate, next_type_entry);
+ }
+}
+
+static void bgp_pbr_policyroute_add_to_zebra(struct bgp *bgp,
+ struct bgp_path_info *path,
+ struct bgp_pbr_filter *bpf,
+ struct bgp_pbr_or_filter *bpof,
+ struct nexthop *nh, float *rate)
+{
+ if (!bpof) {
+ bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate);
+ return;
+ }
+ if (bpof->tcpflags)
+ bgp_pbr_policyroute_add_to_zebra_recursive(
+ bgp, path, bpf, bpof, nh, rate, FLOWSPEC_TCP_FLAGS);
+ else if (bpof->dscp)
+ bgp_pbr_policyroute_add_to_zebra_recursive(
+ bgp, path, bpf, bpof, nh, rate, FLOWSPEC_DSCP);
+ else if (bpof->pkt_len)
+ bgp_pbr_policyroute_add_to_zebra_recursive(
+ bgp, path, bpf, bpof, nh, rate, FLOWSPEC_PKT_LEN);
+ else if (bpof->fragment)
+ bgp_pbr_policyroute_add_to_zebra_recursive(
+ bgp, path, bpf, bpof, nh, rate, FLOWSPEC_FRAGMENT);
+ else if (bpof->icmp_type || bpof->icmp_code)
+ bgp_pbr_policyroute_add_to_zebra_recursive(
+ bgp, path, bpf, bpof, nh, rate, FLOWSPEC_ICMP_TYPE);
+ else
+ bgp_pbr_policyroute_add_to_zebra_unit(bgp, path, bpf, nh, rate);
+ /* flush bpof */
+ if (bpof->tcpflags)
+ list_delete_all_node(bpof->tcpflags);
+ if (bpof->dscp)
+ list_delete_all_node(bpof->dscp);
+ if (bpof->pkt_len)
+ list_delete_all_node(bpof->pkt_len);
+ if (bpof->fragment)
+ list_delete_all_node(bpof->fragment);
+ if (bpof->icmp_type)
+ list_delete_all_node(bpof->icmp_type);
+ if (bpof->icmp_code)
+ list_delete_all_node(bpof->icmp_code);
+}
+
+static void bgp_pbr_handle_entry(struct bgp *bgp, struct bgp_path_info *path,
+ struct bgp_pbr_entry_main *api, bool add)
+{
+ struct nexthop nh;
+ int i = 0;
+ int continue_loop = 1;
+ float rate = 0;
+ struct prefix *src = NULL, *dst = NULL;
+ uint8_t proto = 0;
+ struct bgp_pbr_range_port *srcp = NULL, *dstp = NULL;
+ struct bgp_pbr_range_port range, range_icmp_code;
+ struct bgp_pbr_range_port pkt_len;
+ struct bgp_pbr_filter bpf;
+ uint8_t kind_enum;
+ struct bgp_pbr_or_filter bpof;
+ struct bgp_pbr_val_mask bpvm;
+
+ memset(&range, 0, sizeof(range));
+ memset(&nh, 0, sizeof(nh));
+ memset(&bpf, 0, sizeof(bpf));
+ memset(&bpof, 0, sizeof(bpof));
+ if (api->match_bitmask & PREFIX_SRC_PRESENT ||
+ (api->type == BGP_PBR_IPRULE &&
+ api->match_bitmask_iprule & PREFIX_SRC_PRESENT))
+ src = &api->src_prefix;
+ if (api->match_bitmask & PREFIX_DST_PRESENT ||
+ (api->type == BGP_PBR_IPRULE &&
+ api->match_bitmask_iprule & PREFIX_DST_PRESENT))
+ dst = &api->dst_prefix;
+ if (api->type == BGP_PBR_IPRULE)
+ bpf.type = api->type;
+ memset(&nh, 0, sizeof(nh));
+ nh.vrf_id = VRF_UNKNOWN;
+ if (api->match_protocol_num) {
+ proto = (uint8_t)api->protocol[0].value;
+ if (api->afi == AF_INET6 && proto == IPPROTO_ICMPV6)
+ proto = IPPROTO_ICMP;
+ }
+ /* if match_port is selected, then either src or dst port will be parsed
+ * but not both at the same time
+ */
+ if (api->match_port_num >= 1) {
+ bgp_pbr_extract(api->port,
+ api->match_port_num,
+ &range);
+ srcp = dstp = &range;
+ } else if (api->match_src_port_num >= 1) {
+ bgp_pbr_extract(api->src_port,
+ api->match_src_port_num,
+ &range);
+ srcp = &range;
+ dstp = NULL;
+ } else if (api->match_dst_port_num >= 1) {
+ bgp_pbr_extract(api->dst_port,
+ api->match_dst_port_num,
+ &range);
+ dstp = &range;
+ srcp = NULL;
+ }
+ if (api->match_icmp_type_num >= 1) {
+ proto = IPPROTO_ICMP;
+ if (bgp_pbr_extract(api->icmp_type,
+ api->match_icmp_type_num,
+ &range))
+ srcp = &range;
+ else {
+ bpof.icmp_type = list_new();
+ bgp_pbr_extract_enumerate(api->icmp_type,
+ api->match_icmp_type_num,
+ OPERATOR_UNARY_OR,
+ bpof.icmp_type,
+ FLOWSPEC_ICMP_TYPE);
+ }
+ }
+ if (api->match_icmp_code_num >= 1) {
+ proto = IPPROTO_ICMP;
+ if (bgp_pbr_extract(api->icmp_code,
+ api->match_icmp_code_num,
+ &range_icmp_code))
+ dstp = &range_icmp_code;
+ else {
+ bpof.icmp_code = list_new();
+ bgp_pbr_extract_enumerate(api->icmp_code,
+ api->match_icmp_code_num,
+ OPERATOR_UNARY_OR,
+ bpof.icmp_code,
+ FLOWSPEC_ICMP_CODE);
+ }
+ }
+
+ if (api->match_tcpflags_num) {
+ kind_enum = bgp_pbr_match_val_get_operator(api->tcpflags,
+ api->match_tcpflags_num);
+ if (kind_enum == OPERATOR_UNARY_AND) {
+ bpf.tcp_flags = &bpvm;
+ bgp_pbr_extract_enumerate(api->tcpflags,
+ api->match_tcpflags_num,
+ OPERATOR_UNARY_AND,
+ bpf.tcp_flags,
+ FLOWSPEC_TCP_FLAGS);
+ } else if (kind_enum == OPERATOR_UNARY_OR) {
+ bpof.tcpflags = list_new();
+ bgp_pbr_extract_enumerate(api->tcpflags,
+ api->match_tcpflags_num,
+ OPERATOR_UNARY_OR,
+ bpof.tcpflags,
+ FLOWSPEC_TCP_FLAGS);
+ }
+ }
+ if (api->match_packet_length_num) {
+ bool ret;
+
+ ret = bgp_pbr_extract(api->packet_length,
+ api->match_packet_length_num,
+ &pkt_len);
+ if (ret)
+ bpf.pkt_len = &pkt_len;
+ else {
+ bpof.pkt_len = list_new();
+ bgp_pbr_extract_enumerate(api->packet_length,
+ api->match_packet_length_num,
+ OPERATOR_UNARY_OR,
+ bpof.pkt_len,
+ FLOWSPEC_PKT_LEN);
+ }
+ }
+ if (api->match_dscp_num >= 1) {
+ bpof.dscp = list_new();
+ bgp_pbr_extract_enumerate(api->dscp, api->match_dscp_num,
+ OPERATOR_UNARY_OR,
+ bpof.dscp, FLOWSPEC_DSCP);
+ }
+ if (api->match_fragment_num) {
+ bpof.fragment = list_new();
+ bgp_pbr_extract_enumerate(api->fragment,
+ api->match_fragment_num,
+ OPERATOR_UNARY_OR,
+ bpof.fragment,
+ FLOWSPEC_FRAGMENT);
+ }
+ bpf.vrf_id = api->vrf_id;
+ bpf.src = src;
+ bpf.dst = dst;
+ bpf.protocol = proto;
+ bpf.src_port = srcp;
+ bpf.dst_port = dstp;
+ bpf.family = afi2family(api->afi);
+ if (!add) {
+ bgp_pbr_policyroute_remove_from_zebra(bgp, path, &bpf, &bpof);
+ return;
+ }
+ /* no action for add = true */
+ for (i = 0; i < api->action_num; i++) {
+ switch (api->actions[i].action) {
+ case ACTION_TRAFFICRATE:
+ /* drop packet */
+ if (api->actions[i].u.r.rate == 0) {
+ nh.vrf_id = api->vrf_id;
+ nh.type = NEXTHOP_TYPE_BLACKHOLE;
+ bgp_pbr_policyroute_add_to_zebra(
+ bgp, path, &bpf, &bpof, &nh, &rate);
+ } else {
+ /* update rate. can be reentrant */
+ rate = api->actions[i].u.r.rate;
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_warn("PBR: ignoring Set action rate %f",
+ api->actions[i].u.r.rate);
+ }
+ }
+ break;
+ case ACTION_TRAFFIC_ACTION:
+ if (api->actions[i].u.za.filter
+ & TRAFFIC_ACTION_SAMPLE) {
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_warn("PBR: Sample action Ignored");
+ }
+ }
+ /* terminate action: run other filters
+ */
+ break;
+ case ACTION_REDIRECT_IP:
+ nh.vrf_id = api->vrf_id;
+ if (api->afi == AFI_IP) {
+ nh.type = NEXTHOP_TYPE_IPV4;
+ nh.gate.ipv4.s_addr =
+ api->actions[i].u.zr.
+ redirect_ip_v4.s_addr;
+ } else {
+ nh.type = NEXTHOP_TYPE_IPV6;
+ memcpy(&nh.gate.ipv6,
+ &api->actions[i].u.zr.redirect_ip_v6,
+ sizeof(struct in6_addr));
+ }
+ bgp_pbr_policyroute_add_to_zebra(bgp, path, &bpf, &bpof,
+ &nh, &rate);
+ /* XXX combination with REDIRECT_VRF
+ * + REDIRECT_NH_IP not done
+ */
+ continue_loop = 0;
+ break;
+ case ACTION_REDIRECT:
+ if (api->afi == AFI_IP)
+ nh.type = NEXTHOP_TYPE_IPV4;
+ else
+ nh.type = NEXTHOP_TYPE_IPV6;
+ nh.vrf_id = api->actions[i].u.redirect_vrf;
+ bgp_pbr_policyroute_add_to_zebra(bgp, path, &bpf, &bpof,
+ &nh, &rate);
+ continue_loop = 0;
+ break;
+ case ACTION_MARKING:
+ if (BGP_DEBUG(pbr, PBR)) {
+ bgp_pbr_print_policy_route(api);
+ zlog_warn("PBR: Set DSCP/FlowLabel %u Ignored",
+ api->actions[i].u.marking_dscp);
+ }
+ break;
+ default:
+ break;
+ }
+ if (continue_loop == 0)
+ break;
+ }
+}
+
+void bgp_pbr_update_entry(struct bgp *bgp, const struct prefix *p,
+ struct bgp_path_info *info, afi_t afi, safi_t safi,
+ bool nlri_update)
+{
+ struct bgp_pbr_entry_main api;
+
+ if (safi != SAFI_FLOWSPEC)
+ return; /* not supported */
+ /* Make Zebra API structure. */
+ memset(&api, 0, sizeof(api));
+ api.vrf_id = bgp->vrf_id;
+ api.afi = afi;
+
+ if (!bgp_zebra_tm_chunk_obtained()) {
+ if (BGP_DEBUG(pbr, PBR_ERROR))
+ flog_err(EC_BGP_TABLE_CHUNK,
+ "%s: table chunk not obtained yet", __func__);
+ return;
+ }
+
+ if (bgp_pbr_build_and_validate_entry(p, info, &api) < 0) {
+ if (BGP_DEBUG(pbr, PBR_ERROR))
+ flog_err(EC_BGP_FLOWSPEC_INSTALLATION,
+ "%s: cancel updating entry %p in bgp pbr",
+ __func__, info);
+ return;
+ }
+ bgp_pbr_handle_entry(bgp, info, &api, nlri_update);
+}
+
+int bgp_pbr_interface_compare(const struct bgp_pbr_interface *a,
+ const struct bgp_pbr_interface *b)
+{
+ return strcmp(a->name, b->name);
+}
+
+struct bgp_pbr_interface *bgp_pbr_interface_lookup(const char *name,
+ struct bgp_pbr_interface_head *head)
+{
+ struct bgp_pbr_interface pbr_if;
+
+ strlcpy(pbr_if.name, name, sizeof(pbr_if.name));
+ return (RB_FIND(bgp_pbr_interface_head,
+ head, &pbr_if));
+}
+
+/* this function resets to the default policy routing
+ * go back to default status
+ */
+void bgp_pbr_reset(struct bgp *bgp, afi_t afi)
+{
+ struct bgp_pbr_config *bgp_pbr_cfg = bgp->bgp_pbr_cfg;
+ struct bgp_pbr_interface_head *head;
+ struct bgp_pbr_interface *pbr_if;
+
+ if (!bgp_pbr_cfg)
+ return;
+ if (afi == AFI_IP)
+ head = &(bgp_pbr_cfg->ifaces_by_name_ipv4);
+ else
+ head = &(bgp_pbr_cfg->ifaces_by_name_ipv6);
+ while (!RB_EMPTY(bgp_pbr_interface_head, head)) {
+ pbr_if = RB_ROOT(bgp_pbr_interface_head, head);
+ RB_REMOVE(bgp_pbr_interface_head, head, pbr_if);
+ XFREE(MTYPE_TMP, pbr_if);
+ }
+}