mirror_frr/bgpd/bgp_bfd.c
Donald Sharp c1c6298dd3 bgpd: Allow bfd to work if peer known but interface address not yet
If bgp is coming up and bgp has not received the interface address yet
but bgp has knowledge about a bfd peering, allow it to set the peering
data appropriately.

Signed-off-by: Donald Sharp <sharpd@nvidia.com>
2024-11-20 16:13:42 -05:00

662 lines
18 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/**
* bgp_bfd.c: BGP BFD handling routines
*
* @copyright Copyright (C) 2015 Cumulus Networks, Inc.
*/
#include <zebra.h>
#include "command.h"
#include "linklist.h"
#include "memory.h"
#include "prefix.h"
#include "frrevent.h"
#include "buffer.h"
#include "stream.h"
#include "vrf.h"
#include "zclient.h"
#include "bfd.h"
#include "lib/json.h"
#include "filter.h"
#include "bgpd/bgpd.h"
#include "bgp_fsm.h"
#include "bgpd/bgp_bfd.h"
#include "bgpd/bgp_debug.h"
#include "bgpd/bgp_vty.h"
#include "bgpd/bgp_packet.h"
#include "bgpd/bgp_network.h"
DEFINE_MTYPE_STATIC(BGPD, BFD_CONFIG, "BFD configuration data");
extern struct zclient *zclient;
static void bfd_session_status_update(struct bfd_session_params *bsp,
const struct bfd_session_status *bss,
void *arg)
{
struct peer *peer = arg;
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: neighbor %s vrf %s(%u) bfd state %s -> %s",
__func__, peer->conf_if ? peer->conf_if : peer->host,
bfd_sess_vrf(bsp), bfd_sess_vrf_id(bsp),
bfd_get_status_str(bss->previous_state),
bfd_get_status_str(bss->state));
if (bss->state == BSS_DOWN && bss->previous_state == BSS_UP) {
if (CHECK_FLAG(peer->sflags, PEER_STATUS_NSF_MODE)
&& bfd_sess_cbit(bsp) && !bss->remote_cbit) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug(
"%s BFD DOWN message ignored in the process of graceful restart when C bit is cleared",
peer->host);
return;
}
/* Once the BFD session is UP, and later BGP session is UP,
* BFD notices that peer->su_local changed, and BFD session goes down.
* We should trigger BGP session reset if BFD session is UP
* only when BGP session is UP already.
* Otherwise, we end up resetting BGP session when BFD session is UP,
* when the source address is changed, e.g. 0.0.0.0 -> 10.0.0.1.
*/
if (bss->last_event > peer->uptime) {
peer->last_reset = PEER_DOWN_BFD_DOWN;
/* rfc9384 */
if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status))
bgp_notify_send(peer->connection, BGP_NOTIFY_CEASE,
BGP_NOTIFY_CEASE_BFD_DOWN);
BGP_EVENT_ADD(peer->connection, BGP_Stop);
}
}
if (bss->state == BSS_UP && bss->previous_state != BSS_UP &&
!peer_established(peer->connection)) {
if (!BGP_PEER_START_SUPPRESSED(peer)) {
bgp_fsm_nht_update(peer->connection, peer, true);
BGP_EVENT_ADD(peer->connection, BGP_Start);
}
}
}
void bgp_peer_config_apply(struct peer *p, struct peer_group *pg)
{
struct listnode *n;
struct peer *pn;
struct peer *gconfig;
/* When called on a group, apply to all peers. */
if (CHECK_FLAG(p->sflags, PEER_STATUS_GROUP)) {
for (ALL_LIST_ELEMENTS_RO(p->group->peer, n, pn))
bgp_peer_config_apply(pn, pg);
return;
}
/* No group, just use current configuration. */
if (pg == NULL || pg->conf->bfd_config == NULL) {
bfd_sess_set_timers(p->bfd_config->session,
p->bfd_config->detection_multiplier,
p->bfd_config->min_rx,
p->bfd_config->min_tx);
bfd_sess_set_cbit(p->bfd_config->session, p->bfd_config->cbit);
bfd_sess_set_profile(p->bfd_config->session,
p->bfd_config->profile);
bfd_sess_install(p->bfd_config->session);
return;
}
/*
* Check if the group configuration was overwritten or apply group
* configuration.
*/
gconfig = pg->conf;
/*
* If using default control plane independent configuration,
* then prefer group's (e.g. it means it wasn't manually configured).
*/
if (!p->bfd_config->cbit)
bfd_sess_set_cbit(p->bfd_config->session,
gconfig->bfd_config->cbit);
else
bfd_sess_set_cbit(p->bfd_config->session, p->bfd_config->cbit);
/* If no profile was specified in peer, then use the group profile. */
if (p->bfd_config->profile[0] == 0)
bfd_sess_set_profile(p->bfd_config->session,
gconfig->bfd_config->profile);
else
bfd_sess_set_profile(p->bfd_config->session,
p->bfd_config->profile);
/* If no specific timers were configured, then use the group timers. */
if (p->bfd_config->detection_multiplier == BFD_DEF_DETECT_MULT
|| p->bfd_config->min_rx == BFD_DEF_MIN_RX
|| p->bfd_config->min_tx == BFD_DEF_MIN_TX)
bfd_sess_set_timers(p->bfd_config->session,
gconfig->bfd_config->detection_multiplier,
gconfig->bfd_config->min_rx,
gconfig->bfd_config->min_tx);
else
bfd_sess_set_timers(p->bfd_config->session,
p->bfd_config->detection_multiplier,
p->bfd_config->min_rx,
p->bfd_config->min_tx);
bfd_sess_install(p->bfd_config->session);
}
void bgp_peer_bfd_update_source(struct peer *p)
{
struct bfd_session_params *session;
const union sockunion *source = NULL;
bool changed = false;
int family;
union {
struct in_addr v4;
struct in6_addr v6;
} src, dst;
struct interface *ifp;
union sockunion addr;
if (!p->bfd_config)
return;
session = p->bfd_config->session;
/* Nothing to do for groups. */
if (CHECK_FLAG(p->sflags, PEER_STATUS_GROUP))
return;
/* Figure out the correct source to use. */
if (CHECK_FLAG(p->flags, PEER_FLAG_UPDATE_SOURCE)) {
if (p->update_source) {
source = p->update_source;
} else if (p->update_if) {
ifp = if_lookup_by_name(p->update_if, p->bgp->vrf_id);
if (ifp) {
sockunion_init(&addr);
if (bgp_update_address(ifp, &p->connection->su, &addr)) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: can't find the source address for interface %s",
__func__, p->update_if);
}
source = &addr;
}
}
} else {
source = p->su_local;
}
/* Update peer's source/destination addresses. */
bfd_sess_addresses(session, &family, &src.v6, &dst.v6);
if (family == AF_INET) {
if ((source && source->sin.sin_addr.s_addr != src.v4.s_addr) ||
p->connection->su.sin.sin_addr.s_addr != dst.v4.s_addr) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: address [%pI4->%pI4] to [%pI4->%pI4]",
__func__, &src.v4, &dst.v4,
source ? &source->sin.sin_addr
: &src.v4,
&p->connection->su.sin.sin_addr);
bfd_sess_set_ipv4_addrs(session,
source ? &source->sin.sin_addr
: NULL,
&p->connection->su.sin.sin_addr);
changed = true;
}
} else {
if ((source && memcmp(&source->sin6, &src.v6, sizeof(src.v6))) ||
memcmp(&p->connection->su.sin6, &dst.v6, sizeof(dst.v6))) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: address [%pI6->%pI6] to [%pI6->%pI6]",
__func__, &src.v6, &dst.v6,
source ? &source->sin6.sin6_addr
: &src.v6,
&p->connection->su.sin6.sin6_addr);
bfd_sess_set_ipv6_addrs(session,
source ? &source->sin6.sin6_addr
: NULL,
&p->connection->su.sin6.sin6_addr);
changed = true;
}
}
/* Update interface. */
if (p->nexthop.ifp && bfd_sess_interface(session) == NULL) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: interface none to %s", __func__,
p->nexthop.ifp->name);
bfd_sess_set_interface(session, p->nexthop.ifp->name);
changed = true;
}
/*
* Update TTL.
*
* Two cases:
* - We detected that the peer is a hop away from us (remove multi hop).
* (this happens when `p->shared_network` is set to `true`)
* - eBGP multi hop / TTL security changed.
*/
if (!PEER_IS_MULTIHOP(p) && bfd_sess_hop_count(session) > 1) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: TTL %d to 1", __func__,
bfd_sess_hop_count(session));
bfd_sess_set_hop_count(session, 1);
changed = true;
}
if (PEER_IS_MULTIHOP(p) && p->ttl != bfd_sess_hop_count(session)) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug("%s: TTL %d to %d", __func__,
bfd_sess_hop_count(session), p->ttl);
bfd_sess_set_hop_count(session, p->ttl);
changed = true;
}
/* Update VRF. */
if (bfd_sess_vrf_id(session) != p->bgp->vrf_id) {
if (BGP_DEBUG(bfd, BFD_LIB))
zlog_debug(
"%s: VRF %s(%d) to %s(%d)", __func__,
bfd_sess_vrf(session), bfd_sess_vrf_id(session),
vrf_id_to_name(p->bgp->vrf_id), p->bgp->vrf_id);
bfd_sess_set_vrf(session, p->bgp->vrf_id);
changed = true;
}
if (changed)
bfd_sess_install(session);
}
/**
* Reset BFD configuration data structure to its defaults settings.
*/
static void bgp_peer_bfd_reset(struct peer *p)
{
/* Set defaults. */
p->bfd_config->detection_multiplier = BFD_DEF_DETECT_MULT;
p->bfd_config->min_rx = BFD_DEF_MIN_RX;
p->bfd_config->min_tx = BFD_DEF_MIN_TX;
p->bfd_config->cbit = false;
p->bfd_config->profile[0] = 0;
}
void bgp_peer_configure_bfd(struct peer *p, bool manual)
{
/* Groups should not call this. */
assert(!CHECK_FLAG(p->sflags, PEER_STATUS_GROUP));
/* Already configured, skip it. */
if (p->bfd_config) {
/* If manually active update flag. */
if (!p->bfd_config->manual)
p->bfd_config->manual = manual;
return;
}
/* Allocate memory for configuration overrides. */
p->bfd_config = XCALLOC(MTYPE_BFD_CONFIG, sizeof(*p->bfd_config));
p->bfd_config->manual = manual;
/* Create new session and assign callback. */
p->bfd_config->session = bfd_sess_new(bfd_session_status_update, p);
bgp_peer_bfd_reset(p);
/* Configure session with basic BGP peer data. */
if (p->connection->su.sa.sa_family == AF_INET)
bfd_sess_set_ipv4_addrs(p->bfd_config->session,
p->su_local ? &p->su_local->sin.sin_addr
: NULL,
&p->connection->su.sin.sin_addr);
else
bfd_sess_set_ipv6_addrs(p->bfd_config->session,
p->su_local
? &p->su_local->sin6.sin6_addr
: NULL,
&p->connection->su.sin6.sin6_addr);
bfd_sess_set_vrf(p->bfd_config->session, p->bgp->vrf_id);
bfd_sess_set_hop_count(p->bfd_config->session,
PEER_IS_MULTIHOP(p) ? p->ttl : 1);
if (p->nexthop.ifp)
bfd_sess_set_interface(p->bfd_config->session,
p->nexthop.ifp->name);
}
static void bgp_peer_remove_bfd(struct peer *p)
{
/* Groups should not call this. */
assert(!CHECK_FLAG(p->sflags, PEER_STATUS_GROUP));
/*
* Peer configuration was removed, however we must check if there
* is still a group configuration to keep this running.
*/
if (p->group && p->group->conf->bfd_config) {
p->bfd_config->manual = false;
bgp_peer_bfd_reset(p);
bgp_peer_config_apply(p, p->group);
return;
}
if (p->bfd_config)
bfd_sess_free(&p->bfd_config->session);
XFREE(MTYPE_BFD_CONFIG, p->bfd_config);
}
static void bgp_group_configure_bfd(struct peer *p)
{
struct listnode *n;
struct peer *pn;
/* Peers should not call this. */
assert(CHECK_FLAG(p->sflags, PEER_STATUS_GROUP));
/* Already allocated: do nothing. */
if (p->bfd_config)
return;
p->bfd_config = XCALLOC(MTYPE_BFD_CONFIG, sizeof(*p->bfd_config));
/* Set defaults. */
p->bfd_config->detection_multiplier = BFD_DEF_DETECT_MULT;
p->bfd_config->min_rx = BFD_DEF_MIN_RX;
p->bfd_config->min_tx = BFD_DEF_MIN_TX;
for (ALL_LIST_ELEMENTS_RO(p->group->peer, n, pn))
bgp_peer_configure_bfd(pn, false);
}
static void bgp_group_remove_bfd(struct peer *p)
{
struct listnode *n;
struct peer *pn;
/* Peers should not call this. */
assert(CHECK_FLAG(p->sflags, PEER_STATUS_GROUP));
/* Already freed: do nothing. */
if (p->bfd_config == NULL)
return;
/* Free configuration and point to `NULL`. */
XFREE(MTYPE_BFD_CONFIG, p->bfd_config);
/* Now that it is `NULL` recalculate configuration for all peers. */
for (ALL_LIST_ELEMENTS_RO(p->group->peer, n, pn)) {
if (pn->bfd_config->manual)
bgp_peer_config_apply(pn, NULL);
else
bgp_peer_remove_bfd(pn);
}
}
void bgp_peer_remove_bfd_config(struct peer *p)
{
if (CHECK_FLAG(p->sflags, PEER_STATUS_GROUP))
bgp_group_remove_bfd(p);
else
bgp_peer_remove_bfd(p);
}
/*
* bgp_bfd_peer_config_write - Write the peer BFD configuration.
*/
void bgp_bfd_peer_config_write(struct vty *vty, const struct peer *peer,
const char *addr)
{
/*
* Always show group BFD configuration, but peer only when explicitly
* configured.
*/
if ((!CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)
&& peer->bfd_config->manual)
|| CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP)) {
#if HAVE_BFDD > 0
vty_out(vty, " neighbor %s bfd\n", addr);
#else
vty_out(vty, " neighbor %s bfd %d %d %d\n", addr,
peer->bfd_config->detection_multiplier,
peer->bfd_config->min_rx, peer->bfd_config->min_tx);
#endif /* HAVE_BFDD */
}
if (peer->bfd_config->profile[0])
vty_out(vty, " neighbor %s bfd profile %s\n", addr,
peer->bfd_config->profile);
if (peer->bfd_config->cbit)
vty_out(vty, " neighbor %s bfd check-control-plane-failure\n",
addr);
}
/*
* bgp_bfd_show_info - Show the peer BFD information.
*/
void bgp_bfd_show_info(struct vty *vty, const struct peer *peer,
json_object *json_neigh)
{
bfd_sess_show(vty, json_neigh, peer->bfd_config->session);
}
DEFUN (neighbor_bfd,
neighbor_bfd_cmd,
"neighbor <A.B.C.D|X:X::X:X|WORD> bfd",
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"Enables BFD support\n")
{
int idx_peer = 1;
struct peer *peer;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (!peer)
return CMD_WARNING_CONFIG_FAILED;
if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
bgp_group_configure_bfd(peer);
else
bgp_peer_configure_bfd(peer, true);
bgp_peer_config_apply(peer, peer->group);
return CMD_SUCCESS;
}
#if HAVE_BFDD > 0
DEFUN_HIDDEN(
#else
DEFUN(
#endif /* HAVE_BFDD */
neighbor_bfd_param,
neighbor_bfd_param_cmd,
"neighbor <A.B.C.D|X:X::X:X|WORD> bfd (2-255) (50-60000) (50-60000)",
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"Enables BFD support\n"
"Detect Multiplier\n"
"Required min receive interval\n"
"Desired min transmit interval\n")
{
int idx_peer = 1;
int idx_number_1 = 3;
int idx_number_2 = 4;
int idx_number_3 = 5;
long detection_multiplier, min_rx, min_tx;
struct peer *peer;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (!peer)
return CMD_WARNING_CONFIG_FAILED;
detection_multiplier = strtol(argv[idx_number_1]->arg, NULL, 10);
min_rx = strtol(argv[idx_number_2]->arg, NULL, 10);
min_tx = strtol(argv[idx_number_3]->arg, NULL, 10);
if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
bgp_group_configure_bfd(peer);
else
bgp_peer_configure_bfd(peer, true);
peer->bfd_config->detection_multiplier = detection_multiplier;
peer->bfd_config->min_rx = min_rx;
peer->bfd_config->min_tx = min_tx;
bgp_peer_config_apply(peer, peer->group);
return CMD_SUCCESS;
}
DEFUN (neighbor_bfd_check_controlplane_failure,
neighbor_bfd_check_controlplane_failure_cmd,
"[no] neighbor <A.B.C.D|X:X::X:X|WORD> bfd check-control-plane-failure",
NO_STR
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"BFD support\n"
"Link dataplane status with BGP controlplane\n")
{
const char *no = strmatch(argv[0]->text, "no") ? "no" : NULL;
int idx_peer = 0;
struct peer *peer;
if (no)
idx_peer = 2;
else
idx_peer = 1;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (!peer)
return CMD_WARNING_CONFIG_FAILED;
if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
bgp_group_configure_bfd(peer);
else
bgp_peer_configure_bfd(peer, true);
peer->bfd_config->cbit = no == NULL;
bgp_peer_config_apply(peer, peer->group);
return CMD_SUCCESS;
}
DEFUN (no_neighbor_bfd,
no_neighbor_bfd_cmd,
#if HAVE_BFDD > 0
"no neighbor <A.B.C.D|X:X::X:X|WORD> bfd",
#else
"no neighbor <A.B.C.D|X:X::X:X|WORD> bfd [(2-255) (50-60000) (50-60000)]",
#endif /* HAVE_BFDD */
NO_STR
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"Disables BFD support\n"
#if HAVE_BFDD == 0
"Detect Multiplier\n"
"Required min receive interval\n"
"Desired min transmit interval\n"
#endif /* !HAVE_BFDD */
)
{
int idx_peer = 2;
struct peer *peer;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (!peer)
return CMD_WARNING_CONFIG_FAILED;
if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
bgp_group_remove_bfd(peer);
else
bgp_peer_remove_bfd(peer);
return CMD_SUCCESS;
}
#if HAVE_BFDD > 0
DEFUN(neighbor_bfd_profile, neighbor_bfd_profile_cmd,
"neighbor <A.B.C.D|X:X::X:X|WORD> bfd profile BFDPROF",
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"BFD integration\n"
BFD_PROFILE_STR
BFD_PROFILE_NAME_STR)
{
int idx_peer = 1, idx_prof = 4;
struct peer *peer;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (!peer)
return CMD_WARNING_CONFIG_FAILED;
if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
bgp_group_configure_bfd(peer);
else
bgp_peer_configure_bfd(peer, true);
strlcpy(peer->bfd_config->profile, argv[idx_prof]->arg,
sizeof(peer->bfd_config->profile));
bgp_peer_config_apply(peer, peer->group);
return CMD_SUCCESS;
}
DEFUN(no_neighbor_bfd_profile, no_neighbor_bfd_profile_cmd,
"no neighbor <A.B.C.D|X:X::X:X|WORD> bfd profile [BFDPROF]",
NO_STR
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"BFD integration\n"
BFD_PROFILE_STR
BFD_PROFILE_NAME_STR)
{
int idx_peer = 2;
struct peer *peer;
peer = peer_and_group_lookup_vty(vty, argv[idx_peer]->arg);
if (!peer)
return CMD_WARNING_CONFIG_FAILED;
if (!peer->bfd_config)
return CMD_SUCCESS;
if (CHECK_FLAG(peer->sflags, PEER_STATUS_GROUP))
bgp_group_configure_bfd(peer);
else
bgp_peer_configure_bfd(peer, true);
peer->bfd_config->profile[0] = 0;
bgp_peer_config_apply(peer, peer->group);
return CMD_SUCCESS;
}
#endif /* HAVE_BFDD */
void bgp_bfd_init(struct event_loop *tm)
{
/* Initialize BFD client functions */
bfd_protocol_integration_init(zclient, tm);
/* "neighbor bfd" commands. */
install_element(BGP_NODE, &neighbor_bfd_cmd);
install_element(BGP_NODE, &neighbor_bfd_param_cmd);
install_element(BGP_NODE, &neighbor_bfd_check_controlplane_failure_cmd);
install_element(BGP_NODE, &no_neighbor_bfd_cmd);
#if HAVE_BFDD > 0
install_element(BGP_NODE, &neighbor_bfd_profile_cmd);
install_element(BGP_NODE, &no_neighbor_bfd_profile_cmd);
#endif /* HAVE_BFDD */
}