Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions bfdd/bfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,27 @@ static void _bfd_session_update(struct bfd_session *bs,
*/
if (bpc->bpc_has_profile)
bfd_profile_apply(bpc->bpc_profile, bs);

/*
* Propagate any SBFD/SRv6 tail fields the caller flagged on this
* re-register. Each field is gated by its `BFD_REGEXT_FLAG_*` bit
* so absent fields don't clobber the session's current state. The
* `bfd_mode` and `bfd_name` (key) of an existing session are
* deliberately NOT mutated here: changing either mid-flight would
* require socket re-open / key re-hash, which `bs_registrate`
* already handles on the initial-create path; callers wanting that
* must deregister + re-register instead.
*/
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
memcpy(&bs->out_sip6, &bpc->srv6_source_ipv6, sizeof(bs->out_sip6));
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SEG_LIST) {
bs->segnum = bpc->seg_num;
if (bpc->seg_num > 0)
memcpy(bs->seg_list, bpc->seg_list,
sizeof(struct in6_addr) * bpc->seg_num);
}
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
bs->discrs.remote_discr = bpc->remote_discr;
}

int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc)
Expand Down Expand Up @@ -1230,8 +1251,12 @@ struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc)
return NULL;
}

/* Get BFD session storage with its defaults. */
bfd = bfd_session_new(BFD_MODE_TYPE_BFD);
/*
* honour the BFD session mode supplied via the ZAPI
* register payload. Defaulting to classical BFD when the caller
* left the field at zero preserves pre-existing behaviour.
*/
bfd = bfd_session_new(bpc->bfd_mode ? bpc->bfd_mode : BFD_MODE_TYPE_BFD);

/*
* Store interface/VRF name in case we need to delay session
Expand Down Expand Up @@ -1284,6 +1309,34 @@ struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc)

bfd->key.mhop = bpc->bpc_mhop;

/*
* propagate the rest of the SBFD/SRv6 tail onto the new
* session *before* `bs_registrate` runs. `bs_registrate` invokes
* `bfd_session_enable`, which depends on `bs->bfd_mode`,
* `bs->segnum`, `bs->out_sip6`, `bs->seg_list[]` and on the key
* hash containing `bfd_name` — so a post-`bs_registrate` copy
* would race past the SBFD socket-open and hash-insertion paths.
*
* Gating is on the wire-format flag bits in `bpc->bfd_regext_flags`,
* so a caller explicitly setting `remote_discr == 0` (legitimate for
* `sbfd_init` before the responder is learned) is honoured rather
* than skipped.
*/
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_BFD_NAME) {
strlcpy(bfd->bfd_name, bpc->bfd_name, sizeof(bfd->bfd_name));
strlcpy(bfd->key.bfdname, bpc->bfd_name, sizeof(bfd->key.bfdname));
}
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
memcpy(&bfd->out_sip6, &bpc->srv6_source_ipv6, sizeof(bfd->out_sip6));
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SEG_LIST) {
bfd->segnum = bpc->seg_num;
if (bpc->seg_num > 0)
memcpy(bfd->seg_list, bpc->seg_list,
sizeof(struct in6_addr) * bpc->seg_num);
}
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
bfd->discrs.remote_discr = bpc->remote_discr;

if (bs_registrate(bfd) == NULL)
return NULL;

Expand Down
15 changes: 15 additions & 0 deletions bfdd/bfd.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ struct bfd_peer_cfg {
char bfd_name[BFD_NAME_SIZE + 1];
uint8_t bfd_name_len;

/*
* SBFD over SRv6 fields carried in the optional tail of
* the ZEBRA_BFD_DEST_REGISTER / DEST_UPDATE / DEST_DEREGISTER
* payload. `bfd_regext_flags` mirrors the wire-format
* `BFD_REGEXT_FLAG_*` word so propagation and update paths can
* distinguish "field absent" from "field set to zero" (notably
* `remote_discr == 0` for `sbfd_init`).
*/
uint16_t bfd_regext_flags;
uint8_t bfd_mode;
uint32_t remote_discr;
struct in6_addr srv6_source_ipv6;
uint8_t seg_num;
struct in6_addr seg_list[SRV6_MAX_SEGS];

struct {
/* Keychain name for authentication */
char key_chain_name[MAXKEYCHAINNAMELEN + 1];
Expand Down
111 changes: 111 additions & 0 deletions bfdd/ptm_adapter.c
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,23 @@ static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id,
* - c: profile name length.
* - X bytes: profile name.
*
* Optional tail (modeled on route ZAPI's ZAPI_MESSAGE_* pattern):
* - w: bfd_regext_flags (always, when tail present)
* if FLAG_BFD_MODE: c bfd_mode (`bfd_mode_type` in bfdd/bfd.h)
* if FLAG_REMOTE_DISCR: l remote_discr
* if FLAG_SRV6_SOURCE: 16 srv6_source_ipv6
* if FLAG_SEG_LIST: c seg_num (0..SRV6_MAX_SEGS)
* 16*seg_num bytes: seg_list[]
* if FLAG_BFD_NAME: c bfd_name length
* X bytes: bfd_name
*
* The encoder emits the tail when at least one BFD_REGEXT_FLAG_* bit
* is set on `bfd_session_arg`, including on deregister frames so a
* named session (whose key includes `bfd_name`) can be located for
* deletion. The decoder detects the no-tail case via STREAM_READABLE
* and leaves all optional fields zero-initialised, so classical-BFD
* senders are accepted unchanged.
*
* q(64), l(32), w(16), c(8)
*/

Expand Down Expand Up @@ -461,6 +478,84 @@ static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id,
bpc->bpc_profile[ifnamelen] = 0;
}

/*
* Optional-field tail. Each BFD_REGEXT_FLAG_* bit in the leading
* flags word (see `lib/bfd.h`) explicitly indicates that the
* corresponding field follows. Classical-BFD senders write no tail
* at all and are detected via STREAM_READABLE; in that case every
* optional field stays at its memset-zero default.
*
* The "no tail" check is the only positional probing left; past the
* flags word, every field's presence is signalled explicitly, so a
* future upstream extension that appends bytes here (or that lands
* with no flags word at all) is rejected cleanly via the bounds
* check on the trailing wire bytes rather than silently misparsing
* as an SBFD field.
*
* Forward compatibility: bytes left after the last flagged field
* this decoder knows about are silently ignored. A newer sender
* may append fields by claiming new BFD_REGEXT_FLAG_* bits; older
* decoders parse what they recognise and stop.
*/
if (STREAM_READABLE(msg) > 0) {
uint16_t flags;
uint8_t i;

STREAM_GETW(msg, flags);
bpc->bfd_regext_flags = flags;

if (flags & BFD_REGEXT_FLAG_BFD_MODE) {
STREAM_GETC(msg, bpc->bfd_mode);
if (bpc->bfd_mode > BFD_MODE_TYPE_SBFD_INIT) {
zlog_err("ptm-read: unknown bfd_mode %u (max %u)",
bpc->bfd_mode, BFD_MODE_TYPE_SBFD_INIT);
goto stream_failure;
}
}
if (flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
STREAM_GETL(msg, bpc->remote_discr);
if (flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
STREAM_GET(&bpc->srv6_source_ipv6, msg,
sizeof(struct in6_addr));
if (flags & BFD_REGEXT_FLAG_SEG_LIST) {
STREAM_GETC(msg, bpc->seg_num);
if (bpc->seg_num > SRV6_MAX_SEGS) {
zlog_err("ptm-read: seg_num %u exceeds SRV6_MAX_SEGS %u",
bpc->seg_num, SRV6_MAX_SEGS);
goto stream_failure;
}
for (i = 0; i < bpc->seg_num; i++)
STREAM_GET(&bpc->seg_list[i], msg,
sizeof(struct in6_addr));
}
if (flags & BFD_REGEXT_FLAG_BFD_NAME) {
/*
* bfd_name_len is uint8_t (max 255) and the destination
* buffer has BFD_NAME_SIZE + 1 == 256 bytes, so any wire
* value fits with room for the NUL terminator.
*/
STREAM_GETC(msg, bpc->bfd_name_len);
if (bpc->bfd_name_len) {
STREAM_GET(bpc->bfd_name, msg, bpc->bfd_name_len);
bpc->bfd_name[bpc->bfd_name_len] = '\0';
}
}

/*
* Cross-flag validation: SBFD_INIT identifies the remote end
* by a discriminator (RFC 7881 §3); discriminator 0 is reserved,
* so a session whose mode is SBFD_INIT must arrive with an
* explicit `remote_discr` (FLAG_REMOTE_DISCR set). Reject the
* frame rather than create a session that can never come up.
*/
if ((flags & BFD_REGEXT_FLAG_BFD_MODE) &&
bpc->bfd_mode == BFD_MODE_TYPE_SBFD_INIT &&
!(flags & BFD_REGEXT_FLAG_REMOTE_DISCR)) {
zlog_err("ptm-read: SBFD_INIT mode without FLAG_REMOTE_DISCR; rejecting");
goto stream_failure;
}
}

/* Sanity check: peer and local address must match IP types. */
if (bpc->bpc_local.sa_sin.sin_family != AF_UNSPEC
&& (bpc->bpc_local.sa_sin.sin_family
Expand Down Expand Up @@ -513,6 +608,22 @@ static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id)
bfd_profile_apply(bpc.bpc_profile, bs);
}

/*
* the SBFD/SRv6 fields decoded in `_ptm_msg_read` are
* propagated onto the freshly-created session inside
* `ptm_bfd_sess_new` — *before* `bs_registrate` runs. That ordering
* matters because `bfd_session_enable` (called from `bs_registrate`)
* dispatches on `bs->bfd_mode` to choose between the SRH and the
* classical UDP socket; setting `bs->bfd_mode` here would be too
* late.
*
* Re-registers of an *existing* session reach the `bs != NULL`
* branch above and never enter `ptm_bfd_sess_new`, so the SBFD
* fields on the live session are intentionally immutable from the
* ZAPI surface. A pathd-side reroute that wants to change the
* segment list must issue a DEREGISTER followed by a REGISTER.
*/

/* Create client peer notification register. */
pcn_new(pc, bs);

Expand Down
84 changes: 84 additions & 0 deletions lib/bfd.c
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,90 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
stream_putc(s, args->profilelen);
if (args->profilelen)
stream_put(s, args->profile, args->profilelen);

/*
* Optional-field tail. Modeled on the ZAPI_MESSAGE_* pattern used by
* the route ZAPI messages: each BFD_REGEXT_FLAG_* bit in
* `args->bfd_regext_flags` indicates that the corresponding field
* follows. The tail is emitted iff at least one bit is set —
* including on `ZEBRA_BFD_DEST_DEREGISTER`, because `bfd_name`
* participates in `gen_bfd_key` and a named session cannot be
* located for deletion without it. Classical-BFD callers leave
* `bfd_regext_flags` zero and produce byte-identical wire frames to
* the pre-extension layout.
*
* Tail format:
* w bfd_regext_flags (always, when tail is emitted)
* if FLAG_BFD_MODE: c bfd_mode
* if FLAG_REMOTE_DISCR: l remote_discr
* if FLAG_SRV6_SOURCE: 16 srv6_source_ipv6
* if FLAG_SEG_LIST: c seg_num + 16*seg_num bytes seg_list
* if FLAG_BFD_NAME: c bfd_name length + X bytes bfd_name
*
* q(64), l(32), w(16), c(8)
*/
if (args->bfd_regext_flags != 0) {
uint16_t flags = args->bfd_regext_flags;

/*
* Reject cross-flag combinations the wire format admits but
* the session semantics don't: `remote_discr` is only
* meaningful when `bfd_mode` is set to an SBFD mode, and
* `FLAG_BFD_MODE` should carry a non-classical mode value.
* Catch caller bugs at the boundary rather than silently
* shipping garbage on the wire.
*/
if ((flags & BFD_REGEXT_FLAG_REMOTE_DISCR) &&
!(flags & BFD_REGEXT_FLAG_BFD_MODE)) {
zlog_err("%s: FLAG_REMOTE_DISCR set without FLAG_BFD_MODE; rejecting",
__func__);
return -1;
}
if ((flags & BFD_REGEXT_FLAG_BFD_MODE) && args->bfd_mode == 0) {
zlog_err("%s: FLAG_BFD_MODE set with classical mode (0); rejecting",
__func__);
return -1;
}

/*
* Reject out-of-range `seg_num` rather than silently
* truncating: an SBFD/SRv6 session whose SID list is short
* by one entry would route through the wrong path and the
* BFD session would still report Up — the worst possible
* silent failure mode for fast-switchover infrastructure.
*/
if ((flags & BFD_REGEXT_FLAG_SEG_LIST) &&
args->seg_num > SRV6_MAX_SEGS) {
zlog_err("%s: seg_num %u exceeds SRV6_MAX_SEGS %u; rejecting registration",
__func__, args->seg_num, SRV6_MAX_SEGS);
return -1;
}

stream_putw(s, flags);

if (flags & BFD_REGEXT_FLAG_BFD_MODE)
stream_putc(s, args->bfd_mode);
if (flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
stream_putl(s, args->remote_discr);
if (flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
stream_put(s, &args->srv6_source_ipv6,
sizeof(struct in6_addr));
if (flags & BFD_REGEXT_FLAG_SEG_LIST) {
stream_putc(s, args->seg_num);
for (uint8_t i = 0; i < args->seg_num; i++)
stream_put(s, &args->seg_list[i],
sizeof(struct in6_addr));
}
if (flags & BFD_REGEXT_FLAG_BFD_NAME) {
size_t bfd_name_len = strnlen(args->bfd_name,
sizeof(args->bfd_name));
if (bfd_name_len > UINT8_MAX)
bfd_name_len = UINT8_MAX;
stream_putc(s, (uint8_t)bfd_name_len);
if (bfd_name_len)
stream_put(s, args->bfd_name, bfd_name_len);
}
}
#else /* PTM BFD */
/* Encode timers if this is a registration message. */
if (args->command != ZEBRA_BFD_DEST_DEREGISTER) {
Expand Down
47 changes: 47 additions & 0 deletions lib/bfd.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef _ZEBRA_BFD_H
#define _ZEBRA_BFD_H

#include "lib/srv6.h"
#include "lib/zclient.h"

#ifdef __cplusplus
Expand Down Expand Up @@ -355,6 +356,32 @@ void bfd_sess_show(struct vty *vty, struct json_object *json,
*/
void bfd_protocol_integration_init(struct zclient *zc, struct event_loop *tm);

/*
* Optional-field flags for the ZEBRA_BFD_DEST_REGISTER / DEST_UPDATE tail.
*
* Each bit indicates that the corresponding field is present in the wire
* payload and carries a meaningful value (including zero). Modeled on the
* ZAPI_MESSAGE_* pattern used by the route ZAPI messages: presence is
* explicit, so "field absent" and "field set to zero" remain
* distinguishable, and new optional fields can be added by claiming a new
* bit without reshuffling positional probing on either side.
*
* Encoder: if any bit in `bfd_regext_flags` is set, emit the 16-bit
* flags word followed by each flagged field in the order below. The
* tail is emitted on deregister too — `bfd_name` participates in
* `gen_bfd_key`, so a named session cannot be located for deletion
* without it. Classical-BFD callers leave `bfd_regext_flags` zero and
* produce byte-identical wire frames to the pre-extension layout.
*
* Decoder: detect the no-tail case via STREAM_READABLE; on a non-empty
* tail, read the flags word and conditionally read each flagged field.
*/
#define BFD_REGEXT_FLAG_BFD_MODE 0x0001
#define BFD_REGEXT_FLAG_REMOTE_DISCR 0x0002
#define BFD_REGEXT_FLAG_SRV6_SOURCE 0x0004
#define BFD_REGEXT_FLAG_SEG_LIST 0x0008
#define BFD_REGEXT_FLAG_BFD_NAME 0x0010

/**
* BFD session registration arguments.
*/
Expand Down Expand Up @@ -414,6 +441,26 @@ struct bfd_session_arg {
uint32_t detection_multiplier;
/* bfd session name*/
char bfd_name[BFD_NAME_SIZE + 1];

/*
* Optional-tail fields for the ZEBRA_BFD_DEST_REGISTER /
* DEST_UPDATE payload. Each field is emitted on the wire iff the
* corresponding BFD_REGEXT_FLAG_* bit is set in `bfd_regext_flags`
* (above). Classical-BFD callers leave `bfd_regext_flags` zero and
* none of these fields are touched on the wire.
*/
/** Bitmask of optional-tail fields present (BFD_REGEXT_FLAG_*). */
uint16_t bfd_regext_flags;
/** BFD session mode (`bfd_mode_type` in bfdd/bfd.h). */
uint8_t bfd_mode;
/** A-priori remote discriminator (sbfd_init only). */
uint32_t remote_discr;
/** SRv6 outer-IPv6 source. */
struct in6_addr srv6_source_ipv6;
/** Number of valid SIDs in `seg_list[]` (0 .. `SRV6_MAX_SEGS`). */
uint8_t seg_num;
/** SRv6 SID list, transmission order. */
struct in6_addr seg_list[SRV6_MAX_SEGS];
};

/**
Expand Down
Empty file.
7 changes: 7 additions & 0 deletions tests/topotests/bfd_zapi_sbfd_topo1/r1/frr.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
hostname r1
!
interface r1-eth0
ipv6 address 2001:db8:1::1/64
!
bfd
!
Loading
Loading