mirror_frr/bgpd/bgp_bfd.c
Donald Sharp 3e5a31b24e bgpd: Convert struct peer_connection to dynamically allocated
As part of the conversion to a `struct peer_connection` it will
be desirable to have 2 pointers one for when we open a connection
and one for when we receive a connection.  Start this actual
conversion over to this in `struct peer`.  If this sounds confusing
take a look at the bgp state machine for connections and how
it resolves the processing of this router opening -vs- this
router receiving an open.  At some point in time the state
machine decides that we are keeping one of the two connections.

Future commits will allow us to untangle the peer/doppelganger
duality with this abstraction.

Signed-off-by: Donald Sharp <sharpd@nvidia.com>
2023-08-18 09:29:04 -04:00

628 lines
16 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"
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;
}
peer->last_reset = PEER_DOWN_BFD_DOWN;
/* rfc9384 */
if (BGP_IS_VALID_STATE_FOR_NOTIF(peer->connection->status))
bgp_notify_send(peer, BGP_NOTIFY_CEASE,
BGP_NOTIFY_CEASE_BFD_DOWN);
BGP_EVENT_ADD(peer, BGP_Stop);
}
if (bss->state == BSS_UP && bss->previous_state != BSS_UP
&& !peer_established(peer)) {
if (!BGP_PEER_START_SUPPRESSED(peer)) {
bgp_fsm_nht_update(peer, true);
BGP_EVENT_ADD(peer, 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 = p->bfd_config->session;
const union sockunion *source;
bool changed = false;
int family;
union {
struct in_addr v4;
struct in6_addr v6;
} src, dst;
/* 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) && p->update_source)
source = p->update_source;
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->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->su.sin.sin_addr);
bfd_sess_set_ipv4_addrs(
session, source ? &source->sin.sin_addr : NULL,
&p->su.sin.sin_addr);
changed = true;
}
} else {
if ((source && memcmp(&source->sin6, &src.v6, sizeof(src.v6)))
|| memcmp(&p->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->su.sin6.sin6_addr);
bfd_sess_set_ipv6_addrs(session,
source ? &source->sin6.sin6_addr
: NULL,
&p->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->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->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->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 (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 */
}