Skip to content

Commit cca8ea1

Browse files
bfdd, lib: extend ZEBRA_BFD_DEST_REGISTER with SBFD/SRv6 fields
Append an optional flag-gated tail to the ZEBRA_BFD_DEST_REGISTER / DEST_UPDATE / DEST_DEREGISTER ZAPI payload so external BFD clients (e.g. pathd) can register S-BFD sessions over ZAPI without introducing a new opcode. Modeled on the ZAPI_MESSAGE_* pattern in lib/zclient.h: a leading uint16 flags word in the optional tail indicates which optional fields follow, so "field absent" and "field set to zero" remain distinguishable (remote_discr == 0 on sbfd_init is a legitimate value) and new optional fields can be added by claiming a new bit without reshuffling positional probing. The wire-format extension is backward compatible: * lib/bfd.c::zclient_bfd_command emits the flags word + flagged fields iff `bfd_regext_flags != 0`. Includes deregister frames so a named session (whose key includes `bfd_name`) can be located for deletion; classical-BFD callers leave `bfd_regext_flags` zero and produce byte-identical wire frames. Rejects nonsensical flag combinations (FLAG_REMOTE_DISCR without FLAG_BFD_MODE, FLAG_BFD_MODE with mode 0) at the encoder boundary. * bfdd/ptm_adapter.c::_ptm_msg_read detects the no-tail case via STREAM_READABLE; on a non-empty tail, reads the flags word into `bpc->bfd_regext_flags` and conditionally reads each flagged field. Bounds-checks `seg_num` and `bfd_name_len` on tail-present frames. * ptm_bfd_sess_new propagates the new fields onto the freshly- allocated bfd_session before bs_registrate (because bs_registrate -> bfd_session_enable dispatches on bs->bfd_mode and bs_peer_find keys on bfd_name). Gating is on `bpc->bfd_regext_flags` bits so an explicit `remote_discr == 0` is honoured. * _bfd_session_update propagates `out_sip6`, `seg_list[]`, and `remote_discr` on re-register, also gated by flag bits. `bfd_mode` and `bfd_name` are deliberately not mutated on update (changing either requires socket re-open / key re-hash, which only the initial-create path performs). Only the S-BFD echo path is exercised end-to-end. The wire layout reserves room for SBFD_INIT but the init-session path is not driven by this change.
1 parent 182263b commit cca8ea1

5 files changed

Lines changed: 312 additions & 2 deletions

File tree

bfdd/bfd.c

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,27 @@ static void _bfd_session_update(struct bfd_session *bs,
11751175
*/
11761176
if (bpc->bpc_has_profile)
11771177
bfd_profile_apply(bpc->bpc_profile, bs);
1178+
1179+
/*
1180+
* Propagate any SBFD/SRv6 tail fields the caller flagged on this
1181+
* re-register. Each field is gated by its `BFD_REGEXT_FLAG_*` bit
1182+
* so absent fields don't clobber the session's current state. The
1183+
* `bfd_mode` and `bfd_name` (key) of an existing session are
1184+
* deliberately NOT mutated here: changing either mid-flight would
1185+
* require socket re-open / key re-hash, which `bs_registrate`
1186+
* already handles on the initial-create path; callers wanting that
1187+
* must deregister + re-register instead.
1188+
*/
1189+
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
1190+
memcpy(&bs->out_sip6, &bpc->srv6_source_ipv6, sizeof(bs->out_sip6));
1191+
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SEG_LIST) {
1192+
bs->segnum = bpc->seg_num;
1193+
if (bpc->seg_num > 0)
1194+
memcpy(bs->seg_list, bpc->seg_list,
1195+
sizeof(struct in6_addr) * bpc->seg_num);
1196+
}
1197+
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
1198+
bs->discrs.remote_discr = bpc->remote_discr;
11781199
}
11791200

11801201
int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc)
@@ -1230,8 +1251,12 @@ struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc)
12301251
return NULL;
12311252
}
12321253

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

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

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

1312+
/*
1313+
* propagate the rest of the SBFD/SRv6 tail onto the new
1314+
* session *before* `bs_registrate` runs. `bs_registrate` invokes
1315+
* `bfd_session_enable`, which depends on `bs->bfd_mode`,
1316+
* `bs->segnum`, `bs->out_sip6`, `bs->seg_list[]` and on the key
1317+
* hash containing `bfd_name` — so a post-`bs_registrate` copy
1318+
* would race past the SBFD socket-open and hash-insertion paths.
1319+
*
1320+
* Gating is on the wire-format flag bits in `bpc->bfd_regext_flags`,
1321+
* so a caller explicitly setting `remote_discr == 0` (legitimate for
1322+
* `sbfd_init` before the responder is learned) is honoured rather
1323+
* than skipped.
1324+
*/
1325+
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_BFD_NAME) {
1326+
strlcpy(bfd->bfd_name, bpc->bfd_name, sizeof(bfd->bfd_name));
1327+
strlcpy(bfd->key.bfdname, bpc->bfd_name, sizeof(bfd->key.bfdname));
1328+
}
1329+
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
1330+
memcpy(&bfd->out_sip6, &bpc->srv6_source_ipv6, sizeof(bfd->out_sip6));
1331+
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_SEG_LIST) {
1332+
bfd->segnum = bpc->seg_num;
1333+
if (bpc->seg_num > 0)
1334+
memcpy(bfd->seg_list, bpc->seg_list,
1335+
sizeof(struct in6_addr) * bpc->seg_num);
1336+
}
1337+
if (bpc->bfd_regext_flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
1338+
bfd->discrs.remote_discr = bpc->remote_discr;
1339+
12871340
if (bs_registrate(bfd) == NULL)
12881341
return NULL;
12891342

bfdd/bfd.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,21 @@ struct bfd_peer_cfg {
102102
char bfd_name[BFD_NAME_SIZE + 1];
103103
uint8_t bfd_name_len;
104104

105+
/*
106+
* SBFD over SRv6 fields carried in the optional tail of
107+
* the ZEBRA_BFD_DEST_REGISTER / DEST_UPDATE / DEST_DEREGISTER
108+
* payload. `bfd_regext_flags` mirrors the wire-format
109+
* `BFD_REGEXT_FLAG_*` word so propagation and update paths can
110+
* distinguish "field absent" from "field set to zero" (notably
111+
* `remote_discr == 0` for `sbfd_init`).
112+
*/
113+
uint16_t bfd_regext_flags;
114+
uint8_t bfd_mode;
115+
uint32_t remote_discr;
116+
struct in6_addr srv6_source_ipv6;
117+
uint8_t seg_num;
118+
struct in6_addr seg_list[SRV6_MAX_SEGS];
119+
105120
struct {
106121
/* Keychain name for authentication */
107122
char key_chain_name[MAXKEYCHAINNAMELEN + 1];

bfdd/ptm_adapter.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,23 @@ static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id,
368368
* - c: profile name length.
369369
* - X bytes: profile name.
370370
*
371+
* Optional tail (modeled on route ZAPI's ZAPI_MESSAGE_* pattern):
372+
* - w: bfd_regext_flags (always, when tail present)
373+
* if FLAG_BFD_MODE: c bfd_mode (`bfd_mode_type` in bfdd/bfd.h)
374+
* if FLAG_REMOTE_DISCR: l remote_discr
375+
* if FLAG_SRV6_SOURCE: 16 srv6_source_ipv6
376+
* if FLAG_SEG_LIST: c seg_num (0..SRV6_MAX_SEGS)
377+
* 16*seg_num bytes: seg_list[]
378+
* if FLAG_BFD_NAME: c bfd_name length
379+
* X bytes: bfd_name
380+
*
381+
* The encoder emits the tail when at least one BFD_REGEXT_FLAG_* bit
382+
* is set on `bfd_session_arg`, including on deregister frames so a
383+
* named session (whose key includes `bfd_name`) can be located for
384+
* deletion. The decoder detects the no-tail case via STREAM_READABLE
385+
* and leaves all optional fields zero-initialised, so classical-BFD
386+
* senders are accepted unchanged.
387+
*
371388
* q(64), l(32), w(16), c(8)
372389
*/
373390

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

481+
/*
482+
* Optional-field tail. Each BFD_REGEXT_FLAG_* bit in the leading
483+
* flags word (see `lib/bfd.h`) explicitly indicates that the
484+
* corresponding field follows. Classical-BFD senders write no tail
485+
* at all and are detected via STREAM_READABLE; in that case every
486+
* optional field stays at its memset-zero default.
487+
*
488+
* The "no tail" check is the only positional probing left; past the
489+
* flags word, every field's presence is signalled explicitly, so a
490+
* future upstream extension that appends bytes here (or that lands
491+
* with no flags word at all) is rejected cleanly via the bounds
492+
* check on the trailing wire bytes rather than silently misparsing
493+
* as an SBFD field.
494+
*
495+
* Forward compatibility: bytes left after the last flagged field
496+
* this decoder knows about are silently ignored. A newer sender
497+
* may append fields by claiming new BFD_REGEXT_FLAG_* bits; older
498+
* decoders parse what they recognise and stop.
499+
*/
500+
if (STREAM_READABLE(msg) > 0) {
501+
uint16_t flags;
502+
uint8_t i;
503+
504+
STREAM_GETW(msg, flags);
505+
bpc->bfd_regext_flags = flags;
506+
507+
if (flags & BFD_REGEXT_FLAG_BFD_MODE) {
508+
STREAM_GETC(msg, bpc->bfd_mode);
509+
if (bpc->bfd_mode > BFD_MODE_TYPE_SBFD_INIT) {
510+
zlog_err("ptm-read: unknown bfd_mode %u (max %u)",
511+
bpc->bfd_mode, BFD_MODE_TYPE_SBFD_INIT);
512+
goto stream_failure;
513+
}
514+
}
515+
if (flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
516+
STREAM_GETL(msg, bpc->remote_discr);
517+
if (flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
518+
STREAM_GET(&bpc->srv6_source_ipv6, msg,
519+
sizeof(struct in6_addr));
520+
if (flags & BFD_REGEXT_FLAG_SEG_LIST) {
521+
STREAM_GETC(msg, bpc->seg_num);
522+
if (bpc->seg_num > SRV6_MAX_SEGS) {
523+
zlog_err("ptm-read: seg_num %u exceeds SRV6_MAX_SEGS %u",
524+
bpc->seg_num, SRV6_MAX_SEGS);
525+
goto stream_failure;
526+
}
527+
for (i = 0; i < bpc->seg_num; i++)
528+
STREAM_GET(&bpc->seg_list[i], msg,
529+
sizeof(struct in6_addr));
530+
}
531+
if (flags & BFD_REGEXT_FLAG_BFD_NAME) {
532+
/*
533+
* bfd_name_len is uint8_t (max 255) and the destination
534+
* buffer has BFD_NAME_SIZE + 1 == 256 bytes, so any wire
535+
* value fits with room for the NUL terminator.
536+
*/
537+
STREAM_GETC(msg, bpc->bfd_name_len);
538+
if (bpc->bfd_name_len) {
539+
STREAM_GET(bpc->bfd_name, msg, bpc->bfd_name_len);
540+
bpc->bfd_name[bpc->bfd_name_len] = '\0';
541+
}
542+
}
543+
544+
/*
545+
* Cross-flag validation: SBFD_INIT identifies the remote end
546+
* by a discriminator (RFC 7881 §3); discriminator 0 is reserved,
547+
* so a session whose mode is SBFD_INIT must arrive with an
548+
* explicit `remote_discr` (FLAG_REMOTE_DISCR set). Reject the
549+
* frame rather than create a session that can never come up.
550+
*/
551+
if ((flags & BFD_REGEXT_FLAG_BFD_MODE) &&
552+
bpc->bfd_mode == BFD_MODE_TYPE_SBFD_INIT &&
553+
!(flags & BFD_REGEXT_FLAG_REMOTE_DISCR)) {
554+
zlog_err("ptm-read: SBFD_INIT mode without FLAG_REMOTE_DISCR; rejecting");
555+
goto stream_failure;
556+
}
557+
}
558+
464559
/* Sanity check: peer and local address must match IP types. */
465560
if (bpc->bpc_local.sa_sin.sin_family != AF_UNSPEC
466561
&& (bpc->bpc_local.sa_sin.sin_family
@@ -513,6 +608,22 @@ static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id)
513608
bfd_profile_apply(bpc.bpc_profile, bs);
514609
}
515610

611+
/*
612+
* the SBFD/SRv6 fields decoded in `_ptm_msg_read` are
613+
* propagated onto the freshly-created session inside
614+
* `ptm_bfd_sess_new` — *before* `bs_registrate` runs. That ordering
615+
* matters because `bfd_session_enable` (called from `bs_registrate`)
616+
* dispatches on `bs->bfd_mode` to choose between the SRH and the
617+
* classical UDP socket; setting `bs->bfd_mode` here would be too
618+
* late.
619+
*
620+
* Re-registers of an *existing* session reach the `bs != NULL`
621+
* branch above and never enter `ptm_bfd_sess_new`, so the SBFD
622+
* fields on the live session are intentionally immutable from the
623+
* ZAPI surface. A pathd-side reroute that wants to change the
624+
* segment list must issue a DEREGISTER followed by a REGISTER.
625+
*/
626+
516627
/* Create client peer notification register. */
517628
pcn_new(pc, bs);
518629

lib/bfd.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,90 @@ int zclient_bfd_command(struct zclient *zc, struct bfd_session_arg *args)
348348
stream_putc(s, args->profilelen);
349349
if (args->profilelen)
350350
stream_put(s, args->profile, args->profilelen);
351+
352+
/*
353+
* Optional-field tail. Modeled on the ZAPI_MESSAGE_* pattern used by
354+
* the route ZAPI messages: each BFD_REGEXT_FLAG_* bit in
355+
* `args->bfd_regext_flags` indicates that the corresponding field
356+
* follows. The tail is emitted iff at least one bit is set —
357+
* including on `ZEBRA_BFD_DEST_DEREGISTER`, because `bfd_name`
358+
* participates in `gen_bfd_key` and a named session cannot be
359+
* located for deletion without it. Classical-BFD callers leave
360+
* `bfd_regext_flags` zero and produce byte-identical wire frames to
361+
* the pre-extension layout.
362+
*
363+
* Tail format:
364+
* w bfd_regext_flags (always, when tail is emitted)
365+
* if FLAG_BFD_MODE: c bfd_mode
366+
* if FLAG_REMOTE_DISCR: l remote_discr
367+
* if FLAG_SRV6_SOURCE: 16 srv6_source_ipv6
368+
* if FLAG_SEG_LIST: c seg_num + 16*seg_num bytes seg_list
369+
* if FLAG_BFD_NAME: c bfd_name length + X bytes bfd_name
370+
*
371+
* q(64), l(32), w(16), c(8)
372+
*/
373+
if (args->bfd_regext_flags != 0) {
374+
uint16_t flags = args->bfd_regext_flags;
375+
376+
/*
377+
* Reject cross-flag combinations the wire format admits but
378+
* the session semantics don't: `remote_discr` is only
379+
* meaningful when `bfd_mode` is set to an SBFD mode, and
380+
* `FLAG_BFD_MODE` should carry a non-classical mode value.
381+
* Catch caller bugs at the boundary rather than silently
382+
* shipping garbage on the wire.
383+
*/
384+
if ((flags & BFD_REGEXT_FLAG_REMOTE_DISCR) &&
385+
!(flags & BFD_REGEXT_FLAG_BFD_MODE)) {
386+
zlog_err("%s: FLAG_REMOTE_DISCR set without FLAG_BFD_MODE; rejecting",
387+
__func__);
388+
return -1;
389+
}
390+
if ((flags & BFD_REGEXT_FLAG_BFD_MODE) && args->bfd_mode == 0) {
391+
zlog_err("%s: FLAG_BFD_MODE set with classical mode (0); rejecting",
392+
__func__);
393+
return -1;
394+
}
395+
396+
/*
397+
* Reject out-of-range `seg_num` rather than silently
398+
* truncating: an SBFD/SRv6 session whose SID list is short
399+
* by one entry would route through the wrong path and the
400+
* BFD session would still report Up — the worst possible
401+
* silent failure mode for fast-switchover infrastructure.
402+
*/
403+
if ((flags & BFD_REGEXT_FLAG_SEG_LIST) &&
404+
args->seg_num > SRV6_MAX_SEGS) {
405+
zlog_err("%s: seg_num %u exceeds SRV6_MAX_SEGS %u; rejecting registration",
406+
__func__, args->seg_num, SRV6_MAX_SEGS);
407+
return -1;
408+
}
409+
410+
stream_putw(s, flags);
411+
412+
if (flags & BFD_REGEXT_FLAG_BFD_MODE)
413+
stream_putc(s, args->bfd_mode);
414+
if (flags & BFD_REGEXT_FLAG_REMOTE_DISCR)
415+
stream_putl(s, args->remote_discr);
416+
if (flags & BFD_REGEXT_FLAG_SRV6_SOURCE)
417+
stream_put(s, &args->srv6_source_ipv6,
418+
sizeof(struct in6_addr));
419+
if (flags & BFD_REGEXT_FLAG_SEG_LIST) {
420+
stream_putc(s, args->seg_num);
421+
for (uint8_t i = 0; i < args->seg_num; i++)
422+
stream_put(s, &args->seg_list[i],
423+
sizeof(struct in6_addr));
424+
}
425+
if (flags & BFD_REGEXT_FLAG_BFD_NAME) {
426+
size_t bfd_name_len = strnlen(args->bfd_name,
427+
sizeof(args->bfd_name));
428+
if (bfd_name_len > UINT8_MAX)
429+
bfd_name_len = UINT8_MAX;
430+
stream_putc(s, (uint8_t)bfd_name_len);
431+
if (bfd_name_len)
432+
stream_put(s, args->bfd_name, bfd_name_len);
433+
}
434+
}
351435
#else /* PTM BFD */
352436
/* Encode timers if this is a registration message. */
353437
if (args->command != ZEBRA_BFD_DEST_DEREGISTER) {

lib/bfd.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#ifndef _ZEBRA_BFD_H
99
#define _ZEBRA_BFD_H
1010

11+
#include "lib/srv6.h"
1112
#include "lib/zclient.h"
1213

1314
#ifdef __cplusplus
@@ -355,6 +356,32 @@ void bfd_sess_show(struct vty *vty, struct json_object *json,
355356
*/
356357
void bfd_protocol_integration_init(struct zclient *zc, struct event_loop *tm);
357358

359+
/*
360+
* Optional-field flags for the ZEBRA_BFD_DEST_REGISTER / DEST_UPDATE tail.
361+
*
362+
* Each bit indicates that the corresponding field is present in the wire
363+
* payload and carries a meaningful value (including zero). Modeled on the
364+
* ZAPI_MESSAGE_* pattern used by the route ZAPI messages: presence is
365+
* explicit, so "field absent" and "field set to zero" remain
366+
* distinguishable, and new optional fields can be added by claiming a new
367+
* bit without reshuffling positional probing on either side.
368+
*
369+
* Encoder: if any bit in `bfd_regext_flags` is set, emit the 16-bit
370+
* flags word followed by each flagged field in the order below. The
371+
* tail is emitted on deregister too — `bfd_name` participates in
372+
* `gen_bfd_key`, so a named session cannot be located for deletion
373+
* without it. Classical-BFD callers leave `bfd_regext_flags` zero and
374+
* produce byte-identical wire frames to the pre-extension layout.
375+
*
376+
* Decoder: detect the no-tail case via STREAM_READABLE; on a non-empty
377+
* tail, read the flags word and conditionally read each flagged field.
378+
*/
379+
#define BFD_REGEXT_FLAG_BFD_MODE 0x0001
380+
#define BFD_REGEXT_FLAG_REMOTE_DISCR 0x0002
381+
#define BFD_REGEXT_FLAG_SRV6_SOURCE 0x0004
382+
#define BFD_REGEXT_FLAG_SEG_LIST 0x0008
383+
#define BFD_REGEXT_FLAG_BFD_NAME 0x0010
384+
358385
/**
359386
* BFD session registration arguments.
360387
*/
@@ -414,6 +441,26 @@ struct bfd_session_arg {
414441
uint32_t detection_multiplier;
415442
/* bfd session name*/
416443
char bfd_name[BFD_NAME_SIZE + 1];
444+
445+
/*
446+
* Optional-tail fields for the ZEBRA_BFD_DEST_REGISTER /
447+
* DEST_UPDATE payload. Each field is emitted on the wire iff the
448+
* corresponding BFD_REGEXT_FLAG_* bit is set in `bfd_regext_flags`
449+
* (above). Classical-BFD callers leave `bfd_regext_flags` zero and
450+
* none of these fields are touched on the wire.
451+
*/
452+
/** Bitmask of optional-tail fields present (BFD_REGEXT_FLAG_*). */
453+
uint16_t bfd_regext_flags;
454+
/** BFD session mode (`bfd_mode_type` in bfdd/bfd.h). */
455+
uint8_t bfd_mode;
456+
/** A-priori remote discriminator (sbfd_init only). */
457+
uint32_t remote_discr;
458+
/** SRv6 outer-IPv6 source. */
459+
struct in6_addr srv6_source_ipv6;
460+
/** Number of valid SIDs in `seg_list[]` (0 .. `SRV6_MAX_SEGS`). */
461+
uint8_t seg_num;
462+
/** SRv6 SID list, transmission order. */
463+
struct in6_addr seg_list[SRV6_MAX_SEGS];
417464
};
418465

419466
/**

0 commit comments

Comments
 (0)