frr/bgpd/bgp_conditional_adv.c

517 lines
15 KiB
C

// 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);
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 thread *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 = THREAD_ARG(t);
assert(bgp);
thread_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))
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 (!thread_is_scheduled(bgp->t_condition_check))
thread_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. */
THREAD_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;
}