summaryrefslogtreecommitdiffstats
path: root/bgpd/bgp_conditional_adv.c
diff options
context:
space:
mode:
Diffstat (limited to 'bgpd/bgp_conditional_adv.c')
-rw-r--r--bgpd/bgp_conditional_adv.c518
1 files changed, 518 insertions, 0 deletions
diff --git a/bgpd/bgp_conditional_adv.c b/bgpd/bgp_conditional_adv.c
new file mode 100644
index 0000000..6ed0dd7
--- /dev/null
+++ b/bgpd/bgp_conditional_adv.c
@@ -0,0 +1,518 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BGP Conditional advertisement
+ * Copyright (C) 2020 Samsung R&D Institute India - Bangalore.
+ * Madhurilatha Kuruganti
+ */
+
+#include <zebra.h>
+
+#include "bgpd/bgp_conditional_adv.h"
+#include "bgpd/bgp_vty.h"
+
+static route_map_result_t
+bgp_check_rmap_prefixes_in_bgp_table(struct bgp_table *table,
+ struct route_map *rmap)
+{
+ struct attr dummy_attr = {0};
+ struct bgp_dest *dest;
+ struct bgp_path_info *pi;
+ struct bgp_path_info path = {0};
+ struct bgp_path_info_extra path_extra = {0};
+ const struct prefix *dest_p;
+ route_map_result_t ret = RMAP_DENYMATCH;
+
+ for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) {
+ dest_p = bgp_dest_get_prefix(dest);
+ assert(dest_p);
+
+ for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) {
+ dummy_attr = *pi->attr;
+
+ /* Fill temp path_info */
+ prep_for_rmap_apply(&path, &path_extra, dest, pi,
+ pi->peer, &dummy_attr);
+
+ RESET_FLAG(dummy_attr.rmap_change_flags);
+
+ ret = route_map_apply(rmap, dest_p, &path);
+ bgp_attr_flush(&dummy_attr);
+
+ if (ret == RMAP_PERMITMATCH) {
+ bgp_dest_unlock_node(dest);
+ bgp_cond_adv_debug(
+ "%s: Condition map routes present in BGP table",
+ __func__);
+
+ return ret;
+ }
+ }
+ }
+
+ bgp_cond_adv_debug("%s: Condition map routes not present in BGP table",
+ __func__);
+
+ return ret;
+}
+
+static void bgp_conditional_adv_routes(struct peer *peer, afi_t afi,
+ safi_t safi, struct bgp_table *table,
+ struct route_map *rmap,
+ enum update_type update_type)
+{
+ bool addpath_capable;
+ struct bgp_dest *dest;
+ struct bgp_path_info *pi;
+ struct bgp_path_info path;
+ struct peer_af *paf;
+ const struct prefix *dest_p;
+ struct update_subgroup *subgrp;
+ struct attr advmap_attr = {0}, attr = {0};
+ struct bgp_path_info_extra path_extra = {0};
+ route_map_result_t ret;
+
+ paf = peer_af_find(peer, afi, safi);
+ if (!paf)
+ return;
+
+ subgrp = PAF_SUBGRP(paf);
+ /* Ignore if subgroup doesn't exist (implies AF is not negotiated) */
+ if (!subgrp)
+ return;
+
+ subgrp->pscount = 0;
+ SET_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING);
+
+ bgp_cond_adv_debug("%s: %s routes to/from %s for %s", __func__,
+ update_type == UPDATE_TYPE_ADVERTISE ? "Advertise"
+ : "Withdraw",
+ peer->host, get_afi_safi_str(afi, safi, false));
+
+ addpath_capable = bgp_addpath_encode_tx(peer, afi, safi);
+
+ SET_FLAG(subgrp->sflags, SUBGRP_STATUS_FORCE_UPDATES);
+ for (dest = bgp_table_top(table); dest; dest = bgp_route_next(dest)) {
+ dest_p = bgp_dest_get_prefix(dest);
+ assert(dest_p);
+
+ for (pi = bgp_dest_get_bgp_path_info(dest); pi; pi = pi->next) {
+ advmap_attr = *pi->attr;
+
+ /* Fill temp path_info */
+ prep_for_rmap_apply(&path, &path_extra, dest, pi,
+ pi->peer, &advmap_attr);
+
+ RESET_FLAG(advmap_attr.rmap_change_flags);
+
+ ret = route_map_apply(rmap, dest_p, &path);
+ if (ret != RMAP_PERMITMATCH ||
+ !bgp_check_selected(pi, peer, addpath_capable, afi,
+ safi)) {
+ bgp_attr_flush(&advmap_attr);
+ continue;
+ }
+
+ /* Skip route-map checks in
+ * subgroup_announce_check while executing from
+ * the conditional advertise scanner process.
+ * otherwise when route-map is also configured
+ * on same peer, routes in advertise-map may not
+ * be advertised as expected.
+ */
+ if (update_type == UPDATE_TYPE_ADVERTISE &&
+ subgroup_announce_check(dest, pi, subgrp, dest_p,
+ &attr, &advmap_attr)) {
+ bgp_adj_out_set_subgroup(dest, subgrp, &attr,
+ pi);
+ } else {
+ /* If default originate is enabled for
+ * the peer, do not send explicit
+ * withdraw. This will prevent deletion
+ * of default route advertised through
+ * default originate.
+ */
+ if (CHECK_FLAG(peer->af_flags[afi][safi],
+ PEER_FLAG_DEFAULT_ORIGINATE) &&
+ is_default_prefix(dest_p))
+ break;
+
+ bgp_adj_out_unset_subgroup(
+ dest, subgrp, 1,
+ bgp_addpath_id_for_peer(
+ peer, afi, safi,
+ &pi->tx_addpath));
+
+ bgp_attr_flush(&advmap_attr);
+ }
+ }
+ }
+ UNSET_FLAG(subgrp->sflags, SUBGRP_STATUS_TABLE_REPARSING);
+}
+
+/* Handler of conditional advertisement timer event.
+ * Each route in the condition-map is evaluated.
+ */
+static void bgp_conditional_adv_timer(struct event *t)
+{
+ afi_t afi;
+ safi_t safi;
+ int pfx_rcd_safi;
+ struct bgp *bgp = NULL;
+ struct peer *peer = NULL;
+ struct peer_af *paf = NULL;
+ struct bgp_table *table = NULL;
+ struct bgp_filter *filter = NULL;
+ struct listnode *node, *nnode = NULL;
+ struct update_subgroup *subgrp = NULL;
+ route_map_result_t ret;
+ bool advmap_table_changed = false;
+
+ bgp = EVENT_ARG(t);
+ assert(bgp);
+
+ event_add_timer(bm->master, bgp_conditional_adv_timer, bgp,
+ bgp->condition_check_period, &bgp->t_condition_check);
+
+ /* loop through each peer and check if we have peers with
+ * advmap_table_change attribute set, to make sure we send
+ * conditional advertisements properly below.
+ * peer->advmap_table_change is added on incoming BGP UPDATES,
+ * but here it's used for outgoing UPDATES, hence we need to
+ * check if at least one peer got advmap_table_change.
+ */
+ for (ALL_LIST_ELEMENTS_RO(bgp->peer, node, peer)) {
+ if (peer->advmap_table_change) {
+ advmap_table_changed = true;
+ break;
+ }
+ }
+
+ /* loop through each peer and advertise or withdraw routes if
+ * advertise-map is configured and prefix(es) in condition-map
+ * does exist(exist-map)/not exist(non-exist-map) in BGP table
+ * based on condition(exist-map or non-exist map)
+ */
+ for (ALL_LIST_ELEMENTS(bgp->peer, node, nnode, peer)) {
+ if (!CHECK_FLAG(peer->flags, PEER_FLAG_CONFIG_NODE))
+ continue;
+
+ if (!peer_established(peer->connection))
+ continue;
+
+ FOREACH_AFI_SAFI (afi, safi) {
+ if (!peer->afc_nego[afi][safi])
+ continue;
+
+ /* labeled-unicast routes are installed in the unicast
+ * table so in order to display the correct PfxRcd value
+ * we must look at SAFI_UNICAST
+ */
+ pfx_rcd_safi = (safi == SAFI_LABELED_UNICAST)
+ ? SAFI_UNICAST
+ : safi;
+
+ table = bgp->rib[afi][pfx_rcd_safi];
+ if (!table)
+ continue;
+
+ filter = &peer->filter[afi][safi];
+
+ if (!filter->advmap.aname || !filter->advmap.cname
+ || !filter->advmap.amap || !filter->advmap.cmap)
+ continue;
+
+ if (!peer->advmap_config_change[afi][safi] &&
+ !advmap_table_changed)
+ continue;
+
+ if (BGP_DEBUG(cond_adv, COND_ADV)) {
+ if (peer->advmap_table_change)
+ zlog_debug(
+ "%s: %s - routes changed in BGP table.",
+ __func__, peer->host);
+ if (peer->advmap_config_change[afi][safi])
+ zlog_debug(
+ "%s: %s for %s - advertise/condition map configuration is changed.",
+ __func__, peer->host,
+ get_afi_safi_str(afi, safi,
+ false));
+ }
+
+ /* cmap (route-map attached to exist-map or
+ * non-exist-map) map validation
+ */
+ ret = bgp_check_rmap_prefixes_in_bgp_table(
+ table, filter->advmap.cmap);
+
+ /* Derive conditional advertisement status from
+ * condition and return value of condition-map
+ * validation.
+ */
+ if (filter->advmap.condition == CONDITION_EXIST)
+ filter->advmap.update_type =
+ (ret == RMAP_PERMITMATCH)
+ ? UPDATE_TYPE_ADVERTISE
+ : UPDATE_TYPE_WITHDRAW;
+ else
+ filter->advmap.update_type =
+ (ret == RMAP_PERMITMATCH)
+ ? UPDATE_TYPE_WITHDRAW
+ : UPDATE_TYPE_ADVERTISE;
+
+ /*
+ * Update condadv update type so
+ * subgroup_announce_check() can properly apply
+ * outbound policy according to advertisement state
+ */
+ paf = peer_af_find(peer, afi, safi);
+ if (paf && (SUBGRP_PEER(PAF_SUBGRP(paf))
+ ->filter[afi][safi]
+ .advmap.update_type !=
+ filter->advmap.update_type)) {
+ /* Handle change to peer advmap */
+ bgp_cond_adv_debug(
+ "%s: advmap.update_type changed for peer %s, adjusting update_group.",
+ __func__, peer->host);
+
+ update_group_adjust_peer(paf);
+ }
+
+ /* Send regular update as per the existing policy.
+ * There is a change in route-map, match-rule, ACLs,
+ * or route-map filter configuration on the same peer.
+ */
+ if (peer->advmap_config_change[afi][safi]) {
+
+ bgp_cond_adv_debug(
+ "%s: Configuration is changed on peer %s for %s, send the normal update first.",
+ __func__, peer->host,
+ get_afi_safi_str(afi, safi, false));
+ if (paf) {
+ update_subgroup_split_peer(paf, NULL);
+ subgrp = paf->subgroup;
+
+ if (subgrp && subgrp->update_group)
+ subgroup_announce_table(
+ paf->subgroup, NULL);
+ }
+ peer->advmap_config_change[afi][safi] = false;
+ }
+
+ /* Send update as per the conditional advertisement */
+ bgp_conditional_adv_routes(peer, afi, safi, table,
+ filter->advmap.amap,
+ filter->advmap.update_type);
+ }
+ peer->advmap_table_change = false;
+ }
+}
+
+void bgp_conditional_adv_enable(struct peer *peer, afi_t afi, safi_t safi)
+{
+ struct bgp *bgp = peer->bgp;
+
+ assert(bgp);
+
+ /* This flag is used to monitor conditional routes status in BGP table,
+ * and advertise/withdraw routes only when there is a change in BGP
+ * table w.r.t conditional routes
+ */
+ peer->advmap_config_change[afi][safi] = true;
+
+ /* advertise-map is already configured on at least one of its
+ * neighbors (AFI/SAFI). So just increment the counter.
+ */
+ if (++bgp->condition_filter_count > 1) {
+ bgp_cond_adv_debug("%s: condition_filter_count %d", __func__,
+ bgp->condition_filter_count);
+
+ return;
+ }
+
+ /* Register for conditional routes polling timer */
+ if (!event_is_scheduled(bgp->t_condition_check))
+ event_add_timer(bm->master, bgp_conditional_adv_timer, bgp, 0,
+ &bgp->t_condition_check);
+}
+
+void bgp_conditional_adv_disable(struct peer *peer, afi_t afi, safi_t safi)
+{
+ struct bgp *bgp = peer->bgp;
+
+ assert(bgp);
+
+ /* advertise-map is not configured on any of its neighbors or
+ * it is configured on more than one neighbor(AFI/SAFI).
+ * So there's nothing to do except decrementing the counter.
+ */
+ if (--bgp->condition_filter_count != 0) {
+ bgp_cond_adv_debug("%s: condition_filter_count %d", __func__,
+ bgp->condition_filter_count);
+
+ return;
+ }
+
+ /* Last filter removed. So cancel conditional routes polling thread. */
+ EVENT_OFF(bgp->t_condition_check);
+}
+
+static void peer_advertise_map_filter_update(struct peer *peer, afi_t afi,
+ safi_t safi, const char *amap_name,
+ struct route_map *amap,
+ const char *cmap_name,
+ struct route_map *cmap,
+ bool condition, bool set)
+{
+ struct bgp_filter *filter;
+ bool filter_exists = false;
+
+ filter = &peer->filter[afi][safi];
+
+ /* advertise-map is already configured. */
+ if (filter->advmap.aname) {
+ filter_exists = true;
+ XFREE(MTYPE_BGP_FILTER_NAME, filter->advmap.aname);
+ XFREE(MTYPE_BGP_FILTER_NAME, filter->advmap.cname);
+ }
+
+ route_map_counter_decrement(filter->advmap.amap);
+
+ /* Removed advertise-map configuration */
+ if (!set) {
+ memset(&filter->advmap, 0, sizeof(filter->advmap));
+
+ /* decrement condition_filter_count delete timer if
+ * this is the last advertise-map to be removed.
+ */
+ if (filter_exists)
+ bgp_conditional_adv_disable(peer, afi, safi);
+
+ /* Process peer route updates. */
+ peer_on_policy_change(peer, afi, safi, 1);
+
+ return;
+ }
+
+ /* Update filter data with newly configured values. */
+ filter->advmap.aname = XSTRDUP(MTYPE_BGP_FILTER_NAME, amap_name);
+ filter->advmap.cname = XSTRDUP(MTYPE_BGP_FILTER_NAME, cmap_name);
+ filter->advmap.amap = amap;
+ filter->advmap.cmap = cmap;
+ filter->advmap.condition = condition;
+ route_map_counter_increment(filter->advmap.amap);
+ peer->advmap_config_change[afi][safi] = true;
+
+ /* Increment condition_filter_count and/or create timer. */
+ if (!filter_exists) {
+ filter->advmap.update_type = UPDATE_TYPE_ADVERTISE;
+ bgp_conditional_adv_enable(peer, afi, safi);
+ }
+
+ /* Process peer route updates. */
+ peer_on_policy_change(peer, afi, safi, 1);
+}
+
+/* Set advertise-map to the peer. */
+int peer_advertise_map_set(struct peer *peer, afi_t afi, safi_t safi,
+ const char *advertise_name,
+ struct route_map *advertise_map,
+ const char *condition_name,
+ struct route_map *condition_map, bool condition)
+{
+ struct peer *member;
+ struct listnode *node, *nnode;
+
+ /* Set configuration on peer. */
+ peer_advertise_map_filter_update(peer, afi, safi, advertise_name,
+ advertise_map, condition_name,
+ condition_map, condition, true);
+
+ /* Check if handling a regular peer & Skip peer-group mechanics. */
+ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) {
+ /* Set override-flag and process peer route updates. */
+ SET_FLAG(peer->filter_override[afi][safi][RMAP_OUT],
+ PEER_FT_ADVERTISE_MAP);
+ return 0;
+ }
+
+ /*
+ * Set configuration on all peer-group members, unless they are
+ * explicitly overriding peer-group configuration.
+ */
+ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) {
+ /* Skip peers with overridden configuration. */
+ if (CHECK_FLAG(member->filter_override[afi][safi][RMAP_OUT],
+ PEER_FT_ADVERTISE_MAP))
+ continue;
+
+ /* Set configuration on peer-group member. */
+ peer_advertise_map_filter_update(
+ member, afi, safi, advertise_name, advertise_map,
+ condition_name, condition_map, condition, true);
+ }
+
+ return 0;
+}
+
+/* Unset advertise-map from the peer. */
+int peer_advertise_map_unset(struct peer *peer, afi_t afi, safi_t safi,
+ const char *advertise_name,
+ struct route_map *advertise_map,
+ const char *condition_name,
+ struct route_map *condition_map, bool condition)
+{
+ struct peer *member;
+ struct listnode *node, *nnode;
+
+ /* advertise-map is not configured */
+ if (!peer->filter[afi][safi].advmap.aname)
+ return 0;
+
+ /* Unset override-flag unconditionally. */
+ UNSET_FLAG(peer->filter_override[afi][safi][RMAP_OUT],
+ PEER_FT_ADVERTISE_MAP);
+
+ /* Inherit configuration from peer-group if peer is member. */
+ if (peer_group_active(peer)) {
+ PEER_STR_ATTR_INHERIT(peer, peer->group,
+ filter[afi][safi].advmap.aname,
+ MTYPE_BGP_FILTER_NAME);
+ PEER_ATTR_INHERIT(peer, peer->group,
+ filter[afi][safi].advmap.amap);
+ } else
+ peer_advertise_map_filter_update(
+ peer, afi, safi, advertise_name, advertise_map,
+ condition_name, condition_map, condition, false);
+
+ /* Check if handling a regular peer and skip peer-group mechanics. */
+ if (!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) {
+ /* Process peer route updates. */
+ bgp_cond_adv_debug("%s: Send normal update to %s for %s",
+ __func__, peer->host,
+ get_afi_safi_str(afi, safi, false));
+
+ return 0;
+ }
+
+ /*
+ * Remove configuration on all peer-group members, unless they are
+ * explicitly overriding peer-group configuration.
+ */
+ for (ALL_LIST_ELEMENTS(peer->group->peer, node, nnode, member)) {
+ /* Skip peers with overridden configuration. */
+ if (CHECK_FLAG(member->filter_override[afi][safi][RMAP_OUT],
+ PEER_FT_ADVERTISE_MAP))
+ continue;
+ /* Remove configuration on peer-group member. */
+ peer_advertise_map_filter_update(
+ member, afi, safi, advertise_name, advertise_map,
+ condition_name, condition_map, condition, false);
+
+ /* Process peer route updates. */
+ bgp_cond_adv_debug("%s: Send normal update to %s for %s ",
+ __func__, member->host,
+ get_afi_safi_str(afi, safi, false));
+ }
+
+ return 0;
+}