summaryrefslogtreecommitdiffstats
path: root/bfdd
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-09 13:16:35 +0000
commite2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch)
treef0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /bfdd
parentInitial commit. (diff)
downloadfrr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz
frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'bfdd')
-rw-r--r--bfdd/.gitignore2
-rw-r--r--bfdd/Makefile10
-rw-r--r--bfdd/bfd.c2069
-rw-r--r--bfdd/bfd.h844
-rw-r--r--bfdd/bfd_packet.c1759
-rw-r--r--bfdd/bfdctl.h157
-rw-r--r--bfdd/bfdd.c395
-rw-r--r--bfdd/bfdd_cli.c718
-rw-r--r--bfdd/bfdd_nb.c490
-rw-r--r--bfdd/bfdd_nb.h213
-rw-r--r--bfdd/bfdd_nb_config.c847
-rw-r--r--bfdd/bfdd_nb_state.c360
-rw-r--r--bfdd/bfdd_vty.c1051
-rw-r--r--bfdd/bfddp_packet.h369
-rw-r--r--bfdd/config.c592
-rw-r--r--bfdd/control.c841
-rw-r--r--bfdd/dplane.c1176
-rw-r--r--bfdd/event.c114
-rw-r--r--bfdd/ptm_adapter.c980
-rw-r--r--bfdd/subdir.am50
20 files changed, 13037 insertions, 0 deletions
diff --git a/bfdd/.gitignore b/bfdd/.gitignore
new file mode 100644
index 0000000..2b02091
--- /dev/null
+++ b/bfdd/.gitignore
@@ -0,0 +1,2 @@
+# ignore binary files
+bfdd
diff --git a/bfdd/Makefile b/bfdd/Makefile
new file mode 100644
index 0000000..dfe7823
--- /dev/null
+++ b/bfdd/Makefile
@@ -0,0 +1,10 @@
+all: ALWAYS
+ @$(MAKE) -s -C .. bfdd/bfdd
+%: ALWAYS
+ @$(MAKE) -s -C .. bfdd/$@
+
+Makefile:
+ #nothing
+ALWAYS:
+.PHONY: ALWAYS makefiles
+.SUFFIXES:
diff --git a/bfdd/bfd.c b/bfdd/bfd.c
new file mode 100644
index 0000000..3096f47
--- /dev/null
+++ b/bfdd/bfd.c
@@ -0,0 +1,2069 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2013 Cumulus Networks, LLC. All rights reserved.
+ * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved.
+ *
+ * bfd.c: implements the BFD protocol.
+ *
+ * Authors
+ * -------
+ * Shrijeet Mukherjee [shm@cumulusnetworks.com]
+ * Kanna Rajagopal [kanna@cumulusnetworks.com]
+ * Radhika Mahankali [Radhika@cumulusnetworks.com]
+ */
+
+#include <zebra.h>
+
+#include "lib/jhash.h"
+#include "lib/network.h"
+
+#include "bfd.h"
+
+DEFINE_MTYPE_STATIC(BFDD, BFDD_CONFIG, "long-lived configuration memory");
+DEFINE_MTYPE_STATIC(BFDD, BFDD_PROFILE, "long-lived profile memory");
+DEFINE_MTYPE_STATIC(BFDD, BFDD_SESSION_OBSERVER, "Session observer");
+DEFINE_MTYPE_STATIC(BFDD, BFDD_VRF, "BFD VRF");
+
+/*
+ * Prototypes
+ */
+static uint32_t ptm_bfd_gen_ID(void);
+static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd);
+static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa,
+ uint32_t ldisc);
+static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc);
+static const char *get_diag_str(int diag);
+
+static void bs_admin_down_handler(struct bfd_session *bs, int nstate);
+static void bs_down_handler(struct bfd_session *bs, int nstate);
+static void bs_init_handler(struct bfd_session *bs, int nstate);
+static void bs_up_handler(struct bfd_session *bs, int nstate);
+
+/**
+ * Remove BFD profile from all BFD sessions so we don't leave dangling
+ * pointers.
+ */
+static void bfd_profile_detach(struct bfd_profile *bp);
+
+/* Zeroed array with the size of an IPv6 address. */
+struct in6_addr zero_addr;
+
+/** BFD profiles list. */
+struct bfdproflist bplist;
+
+/*
+ * Functions
+ */
+struct bfd_profile *bfd_profile_lookup(const char *name)
+{
+ struct bfd_profile *bp;
+
+ TAILQ_FOREACH (bp, &bplist, entry) {
+ if (strcmp(name, bp->name))
+ continue;
+
+ return bp;
+ }
+
+ return NULL;
+}
+
+static void bfd_profile_set_default(struct bfd_profile *bp)
+{
+ bp->admin_shutdown = false;
+ bp->detection_multiplier = BFD_DEFDETECTMULT;
+ bp->echo_mode = false;
+ bp->passive = false;
+ bp->minimum_ttl = BFD_DEF_MHOP_TTL;
+ bp->min_echo_rx = BFD_DEF_REQ_MIN_ECHO_RX;
+ bp->min_echo_tx = BFD_DEF_DES_MIN_ECHO_TX;
+ bp->min_rx = BFD_DEFREQUIREDMINRX;
+ bp->min_tx = BFD_DEFDESIREDMINTX;
+}
+
+struct bfd_profile *bfd_profile_new(const char *name)
+{
+ struct bfd_profile *bp;
+
+ /* Search for duplicates. */
+ if (bfd_profile_lookup(name) != NULL)
+ return NULL;
+
+ /* Allocate, name it and put into list. */
+ bp = XCALLOC(MTYPE_BFDD_PROFILE, sizeof(*bp));
+ strlcpy(bp->name, name, sizeof(bp->name));
+ TAILQ_INSERT_TAIL(&bplist, bp, entry);
+
+ /* Set default values. */
+ bfd_profile_set_default(bp);
+
+ return bp;
+}
+
+void bfd_profile_free(struct bfd_profile *bp)
+{
+ /* Detach from any session. */
+ if (bglobal.bg_shutdown == false)
+ bfd_profile_detach(bp);
+
+ /* Remove from global list. */
+ TAILQ_REMOVE(&bplist, bp, entry);
+
+ XFREE(MTYPE_BFDD_PROFILE, bp);
+}
+
+void bfd_profile_apply(const char *profname, struct bfd_session *bs)
+{
+ struct bfd_profile *bp;
+
+ /* Remove previous profile if any. */
+ if (bs->profile_name) {
+ /* We are changing profiles. */
+ if (strcmp(bs->profile_name, profname)) {
+ XFREE(MTYPE_BFDD_PROFILE, bs->profile_name);
+ bs->profile_name =
+ XSTRDUP(MTYPE_BFDD_PROFILE, profname);
+ }
+ } else /* Save the current profile name (in case it doesn't exist). */
+ bs->profile_name = XSTRDUP(MTYPE_BFDD_PROFILE, profname);
+
+ /* Look up new profile to apply. */
+ bp = bfd_profile_lookup(profname);
+
+ /* Point to profile if it exists. */
+ bs->profile = bp;
+
+ /* Apply configuration. */
+ bfd_session_apply(bs);
+}
+
+void bfd_session_apply(struct bfd_session *bs)
+{
+ struct bfd_profile *bp;
+ uint32_t min_tx = bs->timers.desired_min_tx;
+ uint32_t min_rx = bs->timers.required_min_rx;
+
+ /* Pick the source of configuration. */
+ bp = bs->profile ? bs->profile : &bs->peer_profile;
+
+ /* Set multiplier if not the default. */
+ if (bs->peer_profile.detection_multiplier == BFD_DEFDETECTMULT)
+ bs->detect_mult = bp->detection_multiplier;
+ else
+ bs->detect_mult = bs->peer_profile.detection_multiplier;
+
+ /* Set timers if not the default. */
+ if (bs->peer_profile.min_tx == BFD_DEFDESIREDMINTX)
+ bs->timers.desired_min_tx = bp->min_tx;
+ else
+ bs->timers.desired_min_tx = bs->peer_profile.min_tx;
+
+ if (bs->peer_profile.min_rx == BFD_DEFREQUIREDMINRX)
+ bs->timers.required_min_rx = bp->min_rx;
+ else
+ bs->timers.required_min_rx = bs->peer_profile.min_rx;
+
+ /* We can only apply echo options on single hop sessions. */
+ if (!CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ /* Configure echo timers if they were default. */
+ if (bs->peer_profile.min_echo_rx == BFD_DEF_REQ_MIN_ECHO_RX)
+ bs->timers.required_min_echo_rx = bp->min_echo_rx;
+ else
+ bs->timers.required_min_echo_rx =
+ bs->peer_profile.min_echo_rx;
+
+ if (bs->peer_profile.min_echo_tx == BFD_DEF_DES_MIN_ECHO_TX)
+ bs->timers.desired_min_echo_tx = bp->min_echo_tx;
+ else
+ bs->timers.desired_min_echo_tx =
+ bs->peer_profile.min_echo_tx;
+
+ /* Toggle echo if default value. */
+ if (bs->peer_profile.echo_mode == false)
+ bfd_set_echo(bs, bp->echo_mode);
+ else
+ bfd_set_echo(bs, bs->peer_profile.echo_mode);
+ } else {
+ /* Configure the TTL packet filter. */
+ if (bs->peer_profile.minimum_ttl == BFD_DEF_MHOP_TTL)
+ bs->mh_ttl = bp->minimum_ttl;
+ else
+ bs->mh_ttl = bs->peer_profile.minimum_ttl;
+ }
+
+ /* Toggle 'passive-mode' if default value. */
+ if (bs->peer_profile.passive == false)
+ bfd_set_passive_mode(bs, bp->passive);
+ else
+ bfd_set_passive_mode(bs, bs->peer_profile.passive);
+
+ /* Toggle 'no shutdown' if default value. */
+ if (bs->peer_profile.admin_shutdown == false)
+ bfd_set_shutdown(bs, bp->admin_shutdown);
+ else
+ bfd_set_shutdown(bs, bs->peer_profile.admin_shutdown);
+
+ /* If session interval changed negotiate new timers. */
+ if (bs->ses_state == PTM_BFD_UP
+ && (bs->timers.desired_min_tx != min_tx
+ || bs->timers.required_min_rx != min_rx))
+ bfd_set_polling(bs);
+
+ /* Send updated information to data plane. */
+ bfd_dplane_update_session(bs);
+}
+
+void bfd_profile_remove(struct bfd_session *bs)
+{
+ /* Remove any previous set profile name. */
+ XFREE(MTYPE_BFDD_PROFILE, bs->profile_name);
+ bs->profile = NULL;
+
+ bfd_session_apply(bs);
+}
+
+void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer,
+ struct sockaddr_any *local, bool mhop, const char *ifname,
+ const char *vrfname)
+{
+ memset(key, 0, sizeof(*key));
+
+ switch (peer->sa_sin.sin_family) {
+ case AF_INET:
+ key->family = AF_INET;
+ memcpy(&key->peer, &peer->sa_sin.sin_addr,
+ sizeof(peer->sa_sin.sin_addr));
+ memcpy(&key->local, &local->sa_sin.sin_addr,
+ sizeof(local->sa_sin.sin_addr));
+ break;
+ case AF_INET6:
+ key->family = AF_INET6;
+ memcpy(&key->peer, &peer->sa_sin6.sin6_addr,
+ sizeof(peer->sa_sin6.sin6_addr));
+ memcpy(&key->local, &local->sa_sin6.sin6_addr,
+ sizeof(local->sa_sin6.sin6_addr));
+ break;
+ }
+
+ key->mhop = mhop;
+ if (ifname && ifname[0])
+ strlcpy(key->ifname, ifname, sizeof(key->ifname));
+ if (vrfname && vrfname[0])
+ strlcpy(key->vrfname, vrfname, sizeof(key->vrfname));
+ else
+ strlcpy(key->vrfname, VRF_DEFAULT_NAME, sizeof(key->vrfname));
+}
+
+struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc)
+{
+ struct bfd_session *bs;
+ struct peer_label *pl;
+ struct bfd_key key;
+
+ /* Try to find label first. */
+ if (bpc->bpc_has_label) {
+ pl = pl_find(bpc->bpc_label);
+ if (pl != NULL) {
+ bs = pl->pl_bs;
+ return bs;
+ }
+ }
+
+ /* Otherwise fallback to peer/local hash lookup. */
+ gen_bfd_key(&key, &bpc->bpc_peer, &bpc->bpc_local, bpc->bpc_mhop,
+ bpc->bpc_localif, bpc->bpc_vrfname);
+
+ return bfd_key_lookup(key);
+}
+
+/*
+ * Starts a disabled BFD session.
+ *
+ * A session is disabled when the specified interface/VRF doesn't exist
+ * yet. It might happen on FRR boot or with virtual interfaces.
+ */
+int bfd_session_enable(struct bfd_session *bs)
+{
+ struct interface *ifp = NULL;
+ struct vrf *vrf = NULL;
+ int psock;
+
+ /* We are using data plane, we don't need software. */
+ if (bs->bdc)
+ return 0;
+
+ /*
+ * If the interface or VRF doesn't exist, then we must register
+ * the session but delay its start.
+ */
+ if (bs->key.vrfname[0]) {
+ vrf = vrf_lookup_by_name(bs->key.vrfname);
+ if (vrf == NULL) {
+ zlog_err(
+ "session-enable: specified VRF %s doesn't exists.",
+ bs->key.vrfname);
+ return 0;
+ }
+ } else {
+ vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ }
+
+ assert(vrf);
+
+ if (bs->key.ifname[0]) {
+ ifp = if_lookup_by_name(bs->key.ifname, vrf->vrf_id);
+ if (ifp == NULL) {
+ zlog_err(
+ "session-enable: specified interface %s (VRF %s) doesn't exist.",
+ bs->key.ifname, vrf->name);
+ return 0;
+ }
+ }
+
+ /* Assign interface/VRF pointers. */
+ bs->vrf = vrf;
+
+ /* Assign interface pointer (if any). */
+ bs->ifp = ifp;
+
+ /* Attempt to use data plane. */
+ if (bglobal.bg_use_dplane && bfd_dplane_add_session(bs) == 0) {
+ control_notify_config(BCM_NOTIFY_CONFIG_ADD, bs);
+ return 0;
+ }
+
+ /* Sanity check: don't leak open sockets. */
+ if (bs->sock != -1) {
+ if (bglobal.debug_peer_event)
+ zlog_debug("%s: previous socket open", __func__);
+
+ close(bs->sock);
+ bs->sock = -1;
+ }
+
+ /*
+ * Get socket for transmitting control packets. Note that if we
+ * could use the destination port (3784) for the source
+ * port we wouldn't need a socket per session.
+ */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6) == 0) {
+ psock = bp_peer_socket(bs);
+ if (psock == -1)
+ return 0;
+ } else {
+ psock = bp_peer_socketv6(bs);
+ if (psock == -1)
+ return 0;
+ }
+
+ /*
+ * We've got a valid socket, lets start the timers and the
+ * protocol.
+ */
+ bs->sock = psock;
+
+ /* Only start timers if we are using active mode. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE) == 0) {
+ bfd_recvtimer_update(bs);
+ ptm_bfd_start_xmt_timer(bs, false);
+ }
+
+ /* initialize RTT */
+ bfd_rtt_init(bs);
+
+ return 0;
+}
+
+/*
+ * Disabled a running BFD session.
+ *
+ * A session is disabled when the specified interface/VRF gets removed
+ * (e.g. virtual interfaces).
+ */
+void bfd_session_disable(struct bfd_session *bs)
+{
+ /* We are using data plane, we don't need software. */
+ if (bs->bdc)
+ return;
+
+ /* Free up socket resources. */
+ if (bs->sock != -1) {
+ close(bs->sock);
+ bs->sock = -1;
+ }
+
+ /* Disable all timers. */
+ bfd_recvtimer_delete(bs);
+ bfd_xmttimer_delete(bs);
+ ptm_bfd_echo_stop(bs);
+
+ /* Set session down so it doesn't report UP and disabled. */
+ ptm_bfd_sess_dn(bs, BD_PATH_DOWN);
+}
+
+static uint32_t ptm_bfd_gen_ID(void)
+{
+ uint32_t session_id;
+
+ /*
+ * RFC 5880, Section 6.8.1. recommends that we should generate
+ * random session identification numbers.
+ */
+ do {
+ session_id = ((frr_weak_random() << 16) & 0xFFFF0000)
+ | (frr_weak_random() & 0x0000FFFF);
+ } while (session_id == 0 || bfd_id_lookup(session_id) != NULL);
+
+ return session_id;
+}
+
+void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo)
+{
+ uint64_t jitter, xmt_TO;
+ int maxpercent;
+
+ xmt_TO = is_echo ? bfd->echo_xmt_TO : bfd->xmt_TO;
+
+ /*
+ * From section 6.5.2: trasmit interval should be randomly jittered
+ * between
+ * 75% and 100% of nominal value, unless detect_mult is 1, then should
+ * be
+ * between 75% and 90%.
+ */
+ maxpercent = (bfd->detect_mult == 1) ? 16 : 26;
+ jitter = (xmt_TO * (75 + (frr_weak_random() % maxpercent))) / 100;
+ /* XXX remove that division above */
+
+ if (is_echo)
+ bfd_echo_xmttimer_update(bfd, jitter);
+ else
+ bfd_xmttimer_update(bfd, jitter);
+}
+
+static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd)
+{
+ /* Send the scheduled echo packet */
+ /* if ipv4 use the new echo implementation that causes
+ * the packet to be looped in forwarding plane of peer
+ */
+ if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6) == 0)
+#ifdef BFD_LINUX
+ ptm_bfd_echo_fp_snd(bfd);
+#else
+ ptm_bfd_echo_snd(bfd);
+#endif
+ else
+ ptm_bfd_echo_snd(bfd);
+
+ /* Restart the timer for next time */
+ ptm_bfd_start_xmt_timer(bfd, true);
+}
+
+void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit)
+{
+ /* Send the scheduled control packet */
+ ptm_bfd_snd(bfd, fbit);
+
+ /* Restart the timer for next time */
+ ptm_bfd_start_xmt_timer(bfd, false);
+}
+
+void ptm_bfd_echo_stop(struct bfd_session *bfd)
+{
+ bfd->echo_xmt_TO = 0;
+ bfd->echo_detect_TO = 0;
+ UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE);
+
+ bfd_echo_xmttimer_delete(bfd);
+ bfd_echo_recvtimer_delete(bfd);
+}
+
+void ptm_bfd_echo_start(struct bfd_session *bfd)
+{
+ bfd->echo_detect_TO = (bfd->remote_detect_mult * bfd->echo_xmt_TO);
+ if (bfd->echo_detect_TO > 0) {
+ bfd_echo_recvtimer_update(bfd);
+ ptm_bfd_echo_xmt_TO(bfd);
+ }
+}
+
+void ptm_bfd_sess_up(struct bfd_session *bfd)
+{
+ int old_state = bfd->ses_state;
+
+ bfd->local_diag = 0;
+ bfd->ses_state = PTM_BFD_UP;
+ monotime(&bfd->uptime);
+
+ /* Connection is up, lets negotiate timers. */
+ bfd_set_polling(bfd);
+
+ /* Start sending control packets with poll bit immediately. */
+ ptm_bfd_snd(bfd, 0);
+
+ control_notify(bfd, bfd->ses_state);
+
+ if (old_state != bfd->ses_state) {
+ bfd->stats.session_up++;
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: [%s] %s -> %s",
+ bs_to_string(bfd), state_list[old_state].str,
+ state_list[bfd->ses_state].str);
+ }
+}
+
+void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag)
+{
+ int old_state = bfd->ses_state;
+
+ bfd->local_diag = diag;
+ bfd->discrs.remote_discr = 0;
+ bfd->ses_state = PTM_BFD_DOWN;
+ bfd->polling = 0;
+ bfd->demand_mode = 0;
+ monotime(&bfd->downtime);
+
+ /*
+ * Only attempt to send if we have a valid socket:
+ * this function might be called by session disablers and in
+ * this case we won't have a valid socket (i.e. interface was
+ * removed or VRF doesn't exist anymore).
+ */
+ if (bfd->sock != -1)
+ ptm_bfd_snd(bfd, 0);
+
+ /* Slow down the control packets, the connection is down. */
+ bs_set_slow_timers(bfd);
+
+ /* only signal clients when going from up->down state */
+ if (old_state == PTM_BFD_UP)
+ control_notify(bfd, PTM_BFD_DOWN);
+
+ /* Stop echo packet transmission if they are active */
+ if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
+ ptm_bfd_echo_stop(bfd);
+
+ /* Stop attempting to transmit or expect control packets if passive. */
+ if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_PASSIVE)) {
+ bfd_recvtimer_delete(bfd);
+ bfd_xmttimer_delete(bfd);
+ }
+
+ if (old_state != bfd->ses_state) {
+ bfd->stats.session_down++;
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: [%s] %s -> %s reason:%s",
+ bs_to_string(bfd), state_list[old_state].str,
+ state_list[bfd->ses_state].str,
+ get_diag_str(bfd->local_diag));
+ }
+
+ /* clear peer's mac address */
+ UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET);
+ memset(bfd->peer_hw_addr, 0, sizeof(bfd->peer_hw_addr));
+ /* reset local address ,it might has been be changed after bfd is up*/
+ memset(&bfd->local_address, 0, sizeof(bfd->local_address));
+
+ /* reset RTT */
+ bfd_rtt_init(bfd);
+}
+
+static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa,
+ uint32_t ldisc)
+{
+ struct bfd_session *bs;
+
+ bs = bfd_id_lookup(ldisc);
+ if (bs == NULL)
+ return NULL;
+
+ switch (bs->key.family) {
+ case AF_INET:
+ if (memcmp(&sa->sa_sin.sin_addr, &bs->key.peer,
+ sizeof(sa->sa_sin.sin_addr)))
+ return NULL;
+ break;
+ case AF_INET6:
+ if (memcmp(&sa->sa_sin6.sin6_addr, &bs->key.peer,
+ sizeof(sa->sa_sin6.sin6_addr)))
+ return NULL;
+ break;
+ }
+
+ return bs;
+}
+
+struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp,
+ struct sockaddr_any *peer,
+ struct sockaddr_any *local,
+ struct interface *ifp,
+ vrf_id_t vrfid,
+ bool is_mhop)
+{
+ struct vrf *vrf;
+ struct bfd_key key;
+
+ /* Find our session using the ID signaled by the remote end. */
+ if (cp->discrs.remote_discr)
+ return bfd_find_disc(peer, ntohl(cp->discrs.remote_discr));
+
+ /* Search for session without using discriminator. */
+ vrf = vrf_lookup_by_id(vrfid);
+
+ gen_bfd_key(&key, peer, local, is_mhop, ifp ? ifp->name : NULL,
+ vrf ? vrf->name : VRF_DEFAULT_NAME);
+
+ /* XXX maybe remoteDiscr should be checked for remoteHeard cases. */
+ return bfd_key_lookup(key);
+}
+
+void bfd_xmt_cb(struct event *t)
+{
+ struct bfd_session *bs = EVENT_ARG(t);
+
+ ptm_bfd_xmt_TO(bs, 0);
+}
+
+void bfd_echo_xmt_cb(struct event *t)
+{
+ struct bfd_session *bs = EVENT_ARG(t);
+
+ if (bs->echo_xmt_TO > 0)
+ ptm_bfd_echo_xmt_TO(bs);
+}
+
+/* Was ptm_bfd_detect_TO() */
+void bfd_recvtimer_cb(struct event *t)
+{
+ struct bfd_session *bs = EVENT_ARG(t);
+
+ switch (bs->ses_state) {
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ ptm_bfd_sess_dn(bs, BD_CONTROL_EXPIRED);
+ break;
+ }
+}
+
+/* Was ptm_bfd_echo_detect_TO() */
+void bfd_echo_recvtimer_cb(struct event *t)
+{
+ struct bfd_session *bs = EVENT_ARG(t);
+
+ switch (bs->ses_state) {
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ ptm_bfd_sess_dn(bs, BD_ECHO_FAILED);
+ break;
+ }
+}
+
+struct bfd_session *bfd_session_new(void)
+{
+ struct bfd_session *bs;
+
+ bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs));
+
+ /* Set peer session defaults. */
+ bfd_profile_set_default(&bs->peer_profile);
+
+ bs->timers.desired_min_tx = BFD_DEFDESIREDMINTX;
+ bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX;
+ bs->timers.required_min_echo_rx = BFD_DEF_REQ_MIN_ECHO_RX;
+ bs->timers.desired_min_echo_tx = BFD_DEF_DES_MIN_ECHO_TX;
+ bs->detect_mult = BFD_DEFDETECTMULT;
+ bs->mh_ttl = BFD_DEF_MHOP_TTL;
+ bs->ses_state = PTM_BFD_DOWN;
+
+ /* Initiate connection with slow timers. */
+ bs_set_slow_timers(bs);
+
+ /* Initiate remote settings as well. */
+ bs->remote_timers = bs->cur_timers;
+ bs->remote_detect_mult = BFD_DEFDETECTMULT;
+
+ bs->sock = -1;
+ monotime(&bs->uptime);
+ bs->downtime = bs->uptime;
+
+ return bs;
+}
+
+int bfd_session_update_label(struct bfd_session *bs, const char *nlabel)
+{
+ /* New label treatment:
+ * - Check if the label is taken;
+ * - Try to allocate the memory for it and register;
+ */
+ if (bs->pl == NULL) {
+ if (pl_find(nlabel) != NULL) {
+ /* Someone is already using it. */
+ return -1;
+ }
+
+ pl_new(nlabel, bs);
+
+ return 0;
+ }
+
+ /*
+ * Test label change consistency:
+ * - Do nothing if it's the same label;
+ * - Check if the future label is already taken;
+ * - Change label;
+ */
+ if (strcmp(nlabel, bs->pl->pl_label) == 0)
+ return -1;
+ if (pl_find(nlabel) != NULL)
+ return -1;
+
+ strlcpy(bs->pl->pl_label, nlabel, sizeof(bs->pl->pl_label));
+ return 0;
+}
+
+static void _bfd_session_update(struct bfd_session *bs,
+ struct bfd_peer_cfg *bpc)
+{
+ if (bpc->bpc_has_txinterval) {
+ bs->timers.desired_min_tx = bpc->bpc_txinterval * 1000;
+ bs->peer_profile.min_tx = bs->timers.desired_min_tx;
+ }
+
+ if (bpc->bpc_has_recvinterval) {
+ bs->timers.required_min_rx = bpc->bpc_recvinterval * 1000;
+ bs->peer_profile.min_rx = bs->timers.required_min_rx;
+ }
+
+ if (bpc->bpc_has_detectmultiplier) {
+ bs->detect_mult = bpc->bpc_detectmultiplier;
+ bs->peer_profile.detection_multiplier = bs->detect_mult;
+ }
+
+ if (bpc->bpc_has_echorecvinterval) {
+ bs->timers.required_min_echo_rx = bpc->bpc_echorecvinterval * 1000;
+ bs->peer_profile.min_echo_rx = bs->timers.required_min_echo_rx;
+ }
+
+ if (bpc->bpc_has_echotxinterval) {
+ bs->timers.desired_min_echo_tx = bpc->bpc_echotxinterval * 1000;
+ bs->peer_profile.min_echo_tx = bs->timers.desired_min_echo_tx;
+ }
+
+ if (bpc->bpc_has_label)
+ bfd_session_update_label(bs, bpc->bpc_label);
+
+ if (bpc->bpc_cbit)
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_CBIT);
+ else
+ UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CBIT);
+
+ if (bpc->bpc_has_minimum_ttl) {
+ bs->mh_ttl = bpc->bpc_minimum_ttl;
+ bs->peer_profile.minimum_ttl = bpc->bpc_minimum_ttl;
+ }
+
+ bs->peer_profile.echo_mode = bpc->bpc_echo;
+ bfd_set_echo(bs, bpc->bpc_echo);
+
+ /*
+ * Shutdown needs to be the last in order to avoid timers enable when
+ * the session is disabled.
+ */
+ bs->peer_profile.admin_shutdown = bpc->bpc_shutdown;
+ bfd_set_passive_mode(bs, bpc->bpc_passive);
+ bfd_set_shutdown(bs, bpc->bpc_shutdown);
+
+ /*
+ * Apply profile last: it also calls `bfd_set_shutdown`.
+ *
+ * There is no problem calling `shutdown` twice if the value doesn't
+ * change or if it is overridden by peer specific configuration.
+ */
+ if (bpc->bpc_has_profile)
+ bfd_profile_apply(bpc->bpc_profile, bs);
+}
+
+static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc)
+{
+ /* User didn't want to update, return failure. */
+ if (bpc->bpc_createonly)
+ return -1;
+
+ _bfd_session_update(bs, bpc);
+
+ control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs);
+
+ return 0;
+}
+
+void bfd_session_free(struct bfd_session *bs)
+{
+ struct bfd_session_observer *bso;
+
+ bfd_session_disable(bs);
+
+ /* Remove session from data plane if any. */
+ bfd_dplane_delete_session(bs);
+
+ bfd_key_delete(bs->key);
+ bfd_id_delete(bs->discrs.my_discr);
+
+ /* Remove observer if any. */
+ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) {
+ if (bso->bso_bs != bs)
+ continue;
+
+ break;
+ }
+ if (bso != NULL)
+ bs_observer_del(bso);
+
+ pl_free(bs->pl);
+
+ XFREE(MTYPE_BFDD_PROFILE, bs->profile_name);
+ XFREE(MTYPE_BFDD_CONFIG, bs);
+}
+
+struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc)
+{
+ struct bfd_session *bfd, *l_bfd;
+
+ /* check to see if this needs a new session */
+ l_bfd = bs_peer_find(bpc);
+ if (l_bfd) {
+ /* Requesting a duplicated peer means update configuration. */
+ if (bfd_session_update(l_bfd, bpc) == 0)
+ return l_bfd;
+ else
+ return NULL;
+ }
+
+ /* Get BFD session storage with its defaults. */
+ bfd = bfd_session_new();
+
+ /*
+ * Store interface/VRF name in case we need to delay session
+ * start. See `bfd_session_enable` for more information.
+ */
+ if (bpc->bpc_has_localif)
+ strlcpy(bfd->key.ifname, bpc->bpc_localif,
+ sizeof(bfd->key.ifname));
+
+ if (bpc->bpc_has_vrfname)
+ strlcpy(bfd->key.vrfname, bpc->bpc_vrfname,
+ sizeof(bfd->key.vrfname));
+ else
+ strlcpy(bfd->key.vrfname, VRF_DEFAULT_NAME,
+ sizeof(bfd->key.vrfname));
+
+ /* Copy remaining data. */
+ if (bpc->bpc_ipv4 == false)
+ SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6);
+
+ bfd->key.family = (bpc->bpc_ipv4) ? AF_INET : AF_INET6;
+ switch (bfd->key.family) {
+ case AF_INET:
+ memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin.sin_addr,
+ sizeof(bpc->bpc_peer.sa_sin.sin_addr));
+ memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin.sin_addr,
+ sizeof(bpc->bpc_local.sa_sin.sin_addr));
+ break;
+
+ case AF_INET6:
+ memcpy(&bfd->key.peer, &bpc->bpc_peer.sa_sin6.sin6_addr,
+ sizeof(bpc->bpc_peer.sa_sin6.sin6_addr));
+ memcpy(&bfd->key.local, &bpc->bpc_local.sa_sin6.sin6_addr,
+ sizeof(bpc->bpc_local.sa_sin6.sin6_addr));
+ break;
+
+ default:
+ assert(1);
+ break;
+ }
+
+ if (bpc->bpc_mhop)
+ SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH);
+
+ bfd->key.mhop = bpc->bpc_mhop;
+
+ if (bs_registrate(bfd) == NULL)
+ return NULL;
+
+ /* Apply other configurations. */
+ _bfd_session_update(bfd, bpc);
+
+ return bfd;
+}
+
+struct bfd_session *bs_registrate(struct bfd_session *bfd)
+{
+ /* Registrate session into data structures. */
+ bfd_key_insert(bfd);
+ bfd->discrs.my_discr = ptm_bfd_gen_ID();
+ bfd_id_insert(bfd);
+
+ /* Try to enable session and schedule for packet receive/send. */
+ if (bfd_session_enable(bfd) == -1) {
+ /* Unrecoverable failure, remove the session/peer. */
+ bfd_session_free(bfd);
+ return NULL;
+ }
+
+ /* Add observer if we have moving parts. */
+ if (bfd->key.ifname[0] || bfd->key.vrfname[0] || bfd->sock == -1)
+ bs_observer_add(bfd);
+
+ if (bglobal.debug_peer_event)
+ zlog_debug("session-new: %s", bs_to_string(bfd));
+
+ control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd);
+
+ return bfd;
+}
+
+int ptm_bfd_sess_del(struct bfd_peer_cfg *bpc)
+{
+ struct bfd_session *bs;
+
+ /* Find session and call free(). */
+ bs = bs_peer_find(bpc);
+ if (bs == NULL)
+ return -1;
+
+ /* This pointer is being referenced, don't let it be deleted. */
+ if (bs->refcount > 0) {
+ zlog_err("session-delete: refcount failure: %" PRIu64" references",
+ bs->refcount);
+ return -1;
+ }
+
+ if (bglobal.debug_peer_event)
+ zlog_debug("%s: %s", __func__, bs_to_string(bs));
+
+ control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs);
+
+ bfd_session_free(bs);
+
+ return 0;
+}
+
+void bfd_set_polling(struct bfd_session *bs)
+{
+ /*
+ * Start polling procedure: the only timers that require polling
+ * to change value without losing connection are:
+ *
+ * - Desired minimum transmission interval;
+ * - Required minimum receive interval;
+ *
+ * RFC 5880, Section 6.8.3.
+ */
+ bs->polling = 1;
+}
+
+/*
+ * bs_<state>_handler() functions implement the BFD state machine
+ * transition mechanism. `<state>` is the current session state and
+ * the parameter `nstate` is the peer new state.
+ */
+static void bs_admin_down_handler(struct bfd_session *bs
+ __attribute__((__unused__)),
+ int nstate __attribute__((__unused__)))
+{
+ /*
+ * We are administratively down, there is no state machine
+ * handling.
+ */
+}
+
+static void bs_down_handler(struct bfd_session *bs, int nstate)
+{
+ switch (nstate) {
+ case PTM_BFD_ADM_DOWN:
+ /*
+ * Remote peer doesn't want to talk, so lets keep the
+ * connection down.
+ */
+ case PTM_BFD_UP:
+ /* Peer can't be up yet, wait it go to 'init' or 'down'. */
+ break;
+
+ case PTM_BFD_DOWN:
+ /*
+ * Remote peer agreed that the path is down, lets try to
+ * bring it up.
+ */
+ bs->ses_state = PTM_BFD_INIT;
+
+ /*
+ * RFC 5880, Section 6.1.
+ * A system taking the Passive role MUST NOT begin
+ * sending BFD packets for a particular session until
+ * it has received a BFD packet for that session, and thus
+ * has learned the remote system's discriminator value.
+ *
+ * Now we can start transmission timer in passive mode.
+ */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE))
+ ptm_bfd_xmt_TO(bs, 0);
+
+ break;
+
+ case PTM_BFD_INIT:
+ /*
+ * Remote peer told us his path is up, lets turn
+ * activate the session.
+ */
+ ptm_bfd_sess_up(bs);
+ break;
+
+ default:
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: unhandled neighbor state: %d",
+ nstate);
+ break;
+ }
+}
+
+static void bs_init_handler(struct bfd_session *bs, int nstate)
+{
+ switch (nstate) {
+ case PTM_BFD_ADM_DOWN:
+ /*
+ * Remote peer doesn't want to talk, so lets make the
+ * connection down.
+ */
+ ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN);
+ break;
+
+ case PTM_BFD_DOWN:
+ /* Remote peer hasn't moved to first stage yet. */
+ break;
+
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ /* We agreed on the settings and the path is up. */
+ ptm_bfd_sess_up(bs);
+ break;
+
+ default:
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: unhandled neighbor state: %d",
+ nstate);
+ break;
+ }
+}
+
+static void bs_up_handler(struct bfd_session *bs, int nstate)
+{
+ switch (nstate) {
+ case PTM_BFD_ADM_DOWN:
+ case PTM_BFD_DOWN:
+ /* Peer lost or asked to shutdown connection. */
+ ptm_bfd_sess_dn(bs, BD_NEIGHBOR_DOWN);
+ break;
+
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ /* Path is up and working. */
+ break;
+
+ default:
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: unhandled neighbor state: %d",
+ nstate);
+ break;
+ }
+}
+
+void bs_state_handler(struct bfd_session *bs, int nstate)
+{
+ switch (bs->ses_state) {
+ case PTM_BFD_ADM_DOWN:
+ bs_admin_down_handler(bs, nstate);
+ break;
+ case PTM_BFD_DOWN:
+ bs_down_handler(bs, nstate);
+ break;
+ case PTM_BFD_INIT:
+ bs_init_handler(bs, nstate);
+ break;
+ case PTM_BFD_UP:
+ bs_up_handler(bs, nstate);
+ break;
+
+ default:
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: [%s] is in invalid state: %d",
+ bs_to_string(bs), nstate);
+ break;
+ }
+}
+
+/*
+ * Handles echo timer manipulation after updating timer.
+ */
+void bs_echo_timer_handler(struct bfd_session *bs)
+{
+ uint32_t old_timer;
+
+ /*
+ * Before doing any echo handling, check if it is possible to
+ * use it.
+ *
+ * - Check for `echo-mode` configuration.
+ * - Check that we are not using multi hop (RFC 5883,
+ * Section 3).
+ * - Check that we are already at the up state.
+ */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO) == 0
+ || CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)
+ || bs->ses_state != PTM_BFD_UP)
+ return;
+
+ /* Remote peer asked to stop echo. */
+ if (bs->remote_timers.required_min_echo == 0) {
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
+ ptm_bfd_echo_stop(bs);
+
+ return;
+ }
+
+ /*
+ * Calculate the echo transmission timer: we must not send
+ * echo packets faster than the minimum required time
+ * announced by the remote system.
+ *
+ * RFC 5880, Section 6.8.9.
+ */
+ old_timer = bs->echo_xmt_TO;
+ if (bs->remote_timers.required_min_echo > bs->timers.desired_min_echo_tx)
+ bs->echo_xmt_TO = bs->remote_timers.required_min_echo;
+ else
+ bs->echo_xmt_TO = bs->timers.desired_min_echo_tx;
+
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO_ACTIVE) == 0
+ || old_timer != bs->echo_xmt_TO)
+ ptm_bfd_echo_start(bs);
+}
+
+/*
+ * RFC 5880 Section 6.5.
+ *
+ * When a BFD control packet with the final bit is received, we must
+ * update the session parameters.
+ */
+void bs_final_handler(struct bfd_session *bs)
+{
+ /* Start using our new timers. */
+ bs->cur_timers.desired_min_tx = bs->timers.desired_min_tx;
+ bs->cur_timers.required_min_rx = bs->timers.required_min_rx;
+
+ /*
+ * TODO: demand mode. See RFC 5880 Section 6.1.
+ *
+ * When using demand mode we must disable the detection timer
+ * for lost control packets.
+ */
+ if (bs->demand_mode) {
+ /* Notify watchers about changed timers. */
+ control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs);
+ return;
+ }
+
+ /*
+ * Calculate transmission time based on new timers.
+ *
+ * Transmission calculation:
+ * Unless specified by exceptions at the end of Section 6.8.7, the
+ * transmission time will be determined by the system with the
+ * slowest rate.
+ *
+ * RFC 5880, Section 6.8.7.
+ */
+ if (bs->timers.desired_min_tx > bs->remote_timers.required_min_rx)
+ bs->xmt_TO = bs->timers.desired_min_tx;
+ else
+ bs->xmt_TO = bs->remote_timers.required_min_rx;
+
+ /* Apply new transmission timer immediately. */
+ ptm_bfd_start_xmt_timer(bs, false);
+
+ /* Notify watchers about changed timers. */
+ control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs);
+}
+
+void bs_set_slow_timers(struct bfd_session *bs)
+{
+ /*
+ * BFD connection must use slow timers before going up or after
+ * losing connectivity to avoid wasting bandwidth.
+ *
+ * RFC 5880, Section 6.8.3.
+ */
+ bs->cur_timers.desired_min_tx = BFD_DEF_SLOWTX;
+ bs->cur_timers.required_min_rx = BFD_DEF_SLOWTX;
+ bs->cur_timers.required_min_echo = 0;
+
+ /* Set the appropriated timeouts for slow connection. */
+ bs->detect_TO = (BFD_DEFDETECTMULT * BFD_DEF_SLOWTX);
+ bs->xmt_TO = BFD_DEF_SLOWTX;
+}
+
+void bfd_set_echo(struct bfd_session *bs, bool echo)
+{
+ if (echo) {
+ /* Check if echo mode is already active. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ return;
+
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
+
+ /* Activate/update echo receive timeout timer. */
+ if (bs->bdc == NULL)
+ bs_echo_timer_handler(bs);
+ } else {
+ /* Check if echo mode is already disabled. */
+ if (!CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ return;
+
+ UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
+
+ /* Deactivate timeout timer. */
+ if (bs->bdc == NULL)
+ ptm_bfd_echo_stop(bs);
+ }
+}
+
+void bfd_set_shutdown(struct bfd_session *bs, bool shutdown)
+{
+ bool is_shutdown;
+
+ /*
+ * Special case: we are batching changes and the previous state was
+ * not shutdown. Instead of potentially disconnect a running peer,
+ * we'll get the current status to validate we were really down.
+ */
+ if (bs->ses_state == PTM_BFD_UP)
+ is_shutdown = false;
+ else
+ is_shutdown = CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+
+ if (shutdown) {
+ /* Already shutdown. */
+ if (is_shutdown)
+ return;
+
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+
+ /* Handle data plane shutdown case. */
+ if (bs->bdc) {
+ bs->ses_state = PTM_BFD_ADM_DOWN;
+ bfd_dplane_update_session(bs);
+ control_notify(bs, bs->ses_state);
+ return;
+ }
+
+ /* Disable all events. */
+ bfd_recvtimer_delete(bs);
+ bfd_echo_recvtimer_delete(bs);
+ bfd_xmttimer_delete(bs);
+ bfd_echo_xmttimer_delete(bs);
+
+ /* Change and notify state change. */
+ bs->ses_state = PTM_BFD_ADM_DOWN;
+ control_notify(bs, bs->ses_state);
+
+ /* Don't try to send packets with a disabled session. */
+ if (bs->sock != -1)
+ ptm_bfd_snd(bs, 0);
+ } else {
+ /* Already working. */
+ if (!is_shutdown)
+ return;
+
+ UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+
+ /* Handle data plane shutdown case. */
+ if (bs->bdc) {
+ bs->ses_state = PTM_BFD_DOWN;
+ bfd_dplane_update_session(bs);
+ control_notify(bs, bs->ses_state);
+ return;
+ }
+
+ /* Change and notify state change. */
+ bs->ses_state = PTM_BFD_DOWN;
+ control_notify(bs, bs->ses_state);
+
+ /* Enable timers if non passive, otherwise stop them. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE)) {
+ bfd_recvtimer_delete(bs);
+ bfd_xmttimer_delete(bs);
+ } else {
+ bfd_recvtimer_update(bs);
+ bfd_xmttimer_update(bs, bs->xmt_TO);
+ }
+ }
+}
+
+void bfd_set_passive_mode(struct bfd_session *bs, bool passive)
+{
+ if (passive) {
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE);
+
+ /* Session is already up and running, nothing to do now. */
+ if (bs->ses_state != PTM_BFD_DOWN)
+ return;
+
+ /* Lets disable the timers since we are now passive. */
+ bfd_recvtimer_delete(bs);
+ bfd_xmttimer_delete(bs);
+ } else {
+ UNSET_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE);
+
+ /* Session is already up and running, nothing to do now. */
+ if (bs->ses_state != PTM_BFD_DOWN)
+ return;
+
+ /* Session is down, let it attempt to start the connection. */
+ bfd_xmttimer_update(bs, bs->xmt_TO);
+ bfd_recvtimer_update(bs);
+ }
+}
+
+/*
+ * Helper functions.
+ */
+static const char *get_diag_str(int diag)
+{
+ for (int i = 0; diag_list[i].str; i++) {
+ if (diag_list[i].type == diag)
+ return diag_list[i].str;
+ }
+ return "N/A";
+}
+
+const char *satostr(const struct sockaddr_any *sa)
+{
+#define INETSTR_BUFCOUNT 8
+ static char buf[INETSTR_BUFCOUNT][INET6_ADDRSTRLEN];
+ static int bufidx;
+ const struct sockaddr_in *sin = &sa->sa_sin;
+ const struct sockaddr_in6 *sin6 = &sa->sa_sin6;
+
+ bufidx += (bufidx + 1) % INETSTR_BUFCOUNT;
+ buf[bufidx][0] = 0;
+
+ switch (sin->sin_family) {
+ case AF_INET:
+ inet_ntop(AF_INET, &sin->sin_addr, buf[bufidx],
+ sizeof(buf[bufidx]));
+ break;
+ case AF_INET6:
+ inet_ntop(AF_INET6, &sin6->sin6_addr, buf[bufidx],
+ sizeof(buf[bufidx]));
+ break;
+
+ default:
+ strlcpy(buf[bufidx], "unknown", sizeof(buf[bufidx]));
+ break;
+ }
+
+ return buf[bufidx];
+}
+
+const char *diag2str(uint8_t diag)
+{
+ switch (diag) {
+ case 0:
+ return "ok";
+ case 1:
+ return "control detection time expired";
+ case 2:
+ return "echo function failed";
+ case 3:
+ return "neighbor signaled session down";
+ case 4:
+ return "forwarding plane reset";
+ case 5:
+ return "path down";
+ case 6:
+ return "concatenated path down";
+ case 7:
+ return "administratively down";
+ case 8:
+ return "reverse concatenated path down";
+ default:
+ return "unknown";
+ }
+}
+
+int strtosa(const char *addr, struct sockaddr_any *sa)
+{
+ memset(sa, 0, sizeof(*sa));
+
+ if (inet_pton(AF_INET, addr, &sa->sa_sin.sin_addr) == 1) {
+ sa->sa_sin.sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sa->sa_sin.sin_len = sizeof(sa->sa_sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ return 0;
+ }
+
+ if (inet_pton(AF_INET6, addr, &sa->sa_sin6.sin6_addr) == 1) {
+ sa->sa_sin6.sin6_family = AF_INET6;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ return 0;
+ }
+
+ return -1;
+}
+
+void integer2timestr(uint64_t time, char *buf, size_t buflen)
+{
+ uint64_t year, month, day, hour, minute, second;
+ int rv;
+
+#define MINUTES (60)
+#define HOURS (60 * MINUTES)
+#define DAYS (24 * HOURS)
+#define MONTHS (30 * DAYS)
+#define YEARS (12 * MONTHS)
+ if (time >= YEARS) {
+ year = time / YEARS;
+ time -= year * YEARS;
+
+ rv = snprintfrr(buf, buflen, "%" PRIu64 " year(s), ", year);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= MONTHS) {
+ month = time / MONTHS;
+ time -= month * MONTHS;
+
+ rv = snprintfrr(buf, buflen, "%" PRIu64 " month(s), ", month);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= DAYS) {
+ day = time / DAYS;
+ time -= day * DAYS;
+
+ rv = snprintfrr(buf, buflen, "%" PRIu64 " day(s), ", day);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= HOURS) {
+ hour = time / HOURS;
+ time -= hour * HOURS;
+
+ rv = snprintfrr(buf, buflen, "%" PRIu64 " hour(s), ", hour);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= MINUTES) {
+ minute = time / MINUTES;
+ time -= minute * MINUTES;
+
+ rv = snprintfrr(buf, buflen, "%" PRIu64 " minute(s), ", minute);
+ buf += rv;
+ buflen -= rv;
+ }
+ second = time % MINUTES;
+ snprintfrr(buf, buflen, "%" PRIu64 " second(s)", second);
+}
+
+const char *bs_to_string(const struct bfd_session *bs)
+{
+ static char buf[256];
+ char addr_buf[INET6_ADDRSTRLEN];
+ int pos;
+ bool is_mhop = CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH);
+
+ pos = snprintf(buf, sizeof(buf), "mhop:%s", is_mhop ? "yes" : "no");
+ pos += snprintf(buf + pos, sizeof(buf) - pos, " peer:%s",
+ inet_ntop(bs->key.family, &bs->key.peer, addr_buf,
+ sizeof(addr_buf)));
+ pos += snprintf(buf + pos, sizeof(buf) - pos, " local:%s",
+ inet_ntop(bs->key.family, &bs->key.local, addr_buf,
+ sizeof(addr_buf)));
+ if (bs->key.vrfname[0])
+ pos += snprintf(buf + pos, sizeof(buf) - pos, " vrf:%s",
+ bs->key.vrfname);
+ if (bs->key.ifname[0])
+ pos += snprintf(buf + pos, sizeof(buf) - pos, " ifname:%s",
+ bs->key.ifname);
+
+ (void)pos;
+
+ return buf;
+}
+
+int bs_observer_add(struct bfd_session *bs)
+{
+ struct bfd_session_observer *bso;
+
+ bso = XCALLOC(MTYPE_BFDD_SESSION_OBSERVER, sizeof(*bso));
+ bso->bso_bs = bs;
+ bso->bso_addr.family = bs->key.family;
+ memcpy(&bso->bso_addr.u.prefix, &bs->key.local,
+ sizeof(bs->key.local));
+
+ TAILQ_INSERT_TAIL(&bglobal.bg_obslist, bso, bso_entry);
+
+ return 0;
+}
+
+void bs_observer_del(struct bfd_session_observer *bso)
+{
+ TAILQ_REMOVE(&bglobal.bg_obslist, bso, bso_entry);
+ XFREE(MTYPE_BFDD_SESSION_OBSERVER, bso);
+}
+
+void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc)
+{
+ memset(bpc, 0, sizeof(*bpc));
+
+ bpc->bpc_ipv4 = (bs->key.family == AF_INET);
+ bpc->bpc_mhop = bs->key.mhop;
+
+ switch (bs->key.family) {
+ case AF_INET:
+ bpc->bpc_peer.sa_sin.sin_family = AF_INET;
+ memcpy(&bpc->bpc_peer.sa_sin.sin_addr, &bs->key.peer,
+ sizeof(bpc->bpc_peer.sa_sin.sin_addr));
+
+ if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local))) {
+ bpc->bpc_local.sa_sin.sin_family = AF_INET6;
+ memcpy(&bpc->bpc_local.sa_sin.sin_addr, &bs->key.local,
+ sizeof(bpc->bpc_local.sa_sin.sin_addr));
+ }
+ break;
+
+ case AF_INET6:
+ bpc->bpc_peer.sa_sin.sin_family = AF_INET6;
+ memcpy(&bpc->bpc_peer.sa_sin6.sin6_addr, &bs->key.peer,
+ sizeof(bpc->bpc_peer.sa_sin6.sin6_addr));
+
+ bpc->bpc_local.sa_sin6.sin6_family = AF_INET6;
+ memcpy(&bpc->bpc_local.sa_sin6.sin6_addr, &bs->key.local,
+ sizeof(bpc->bpc_local.sa_sin6.sin6_addr));
+ break;
+ }
+
+ if (bs->key.ifname[0]) {
+ bpc->bpc_has_localif = true;
+ strlcpy(bpc->bpc_localif, bs->key.ifname,
+ sizeof(bpc->bpc_localif));
+ }
+
+ if (bs->key.vrfname[0]) {
+ bpc->bpc_has_vrfname = true;
+ strlcpy(bpc->bpc_vrfname, bs->key.vrfname,
+ sizeof(bpc->bpc_vrfname));
+ }
+}
+
+
+/*
+ * BFD hash data structures to find sessions.
+ */
+static struct hash *bfd_id_hash;
+static struct hash *bfd_key_hash;
+
+static unsigned int bfd_id_hash_do(const void *p);
+static unsigned int bfd_key_hash_do(const void *p);
+
+static void _bfd_free(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)));
+
+/* BFD hash for our discriminator. */
+static unsigned int bfd_id_hash_do(const void *p)
+{
+ const struct bfd_session *bs = p;
+
+ return jhash_1word(bs->discrs.my_discr, 0);
+}
+
+static bool bfd_id_hash_cmp(const void *n1, const void *n2)
+{
+ const struct bfd_session *bs1 = n1, *bs2 = n2;
+
+ return bs1->discrs.my_discr == bs2->discrs.my_discr;
+}
+
+/* BFD hash for single hop. */
+static unsigned int bfd_key_hash_do(const void *p)
+{
+ const struct bfd_session *bs = p;
+ struct bfd_key key = bs->key;
+
+ /*
+ * Local address and interface name are optional and
+ * can be filled any time after session creation.
+ * Hash key should not depend on these fields.
+ */
+ memset(&key.local, 0, sizeof(key.local));
+ memset(key.ifname, 0, sizeof(key.ifname));
+
+ return jhash(&key, sizeof(key), 0);
+}
+
+static bool bfd_key_hash_cmp(const void *n1, const void *n2)
+{
+ const struct bfd_session *bs1 = n1, *bs2 = n2;
+
+ if (bs1->key.family != bs2->key.family)
+ return false;
+ if (bs1->key.mhop != bs2->key.mhop)
+ return false;
+ if (memcmp(&bs1->key.peer, &bs2->key.peer, sizeof(bs1->key.peer)))
+ return false;
+ if (memcmp(bs1->key.vrfname, bs2->key.vrfname,
+ sizeof(bs1->key.vrfname)))
+ return false;
+
+ /*
+ * Local address is optional and can be empty.
+ * If both addresses are not empty and different,
+ * then the keys are different.
+ */
+ if (memcmp(&bs1->key.local, &zero_addr, sizeof(bs1->key.local))
+ && memcmp(&bs2->key.local, &zero_addr, sizeof(bs2->key.local))
+ && memcmp(&bs1->key.local, &bs2->key.local, sizeof(bs1->key.local)))
+ return false;
+
+ /*
+ * Interface name is optional and can be empty.
+ * If both names are not empty and different,
+ * then the keys are different.
+ */
+ if (bs1->key.ifname[0] && bs2->key.ifname[0]
+ && memcmp(bs1->key.ifname, bs2->key.ifname,
+ sizeof(bs1->key.ifname)))
+ return false;
+
+ return true;
+}
+
+
+/*
+ * Hash public interface / exported functions.
+ */
+
+/* Lookup functions. */
+struct bfd_session *bfd_id_lookup(uint32_t id)
+{
+ struct bfd_session bs;
+
+ bs.discrs.my_discr = id;
+
+ return hash_lookup(bfd_id_hash, &bs);
+}
+
+struct bfd_session *bfd_key_lookup(struct bfd_key key)
+{
+ struct bfd_session bs;
+
+ bs.key = key;
+
+ return hash_lookup(bfd_key_hash, &bs);
+}
+
+/*
+ * Delete functions.
+ *
+ * Delete functions searches and remove the item from the hash and
+ * returns a pointer to the removed item data. If the item was not found
+ * then it returns NULL.
+ *
+ * The data stored inside the hash is not free()ed, so you must do it
+ * manually after getting the pointer back.
+ */
+struct bfd_session *bfd_id_delete(uint32_t id)
+{
+ struct bfd_session bs;
+
+ bs.discrs.my_discr = id;
+
+ return hash_release(bfd_id_hash, &bs);
+}
+
+struct bfd_session *bfd_key_delete(struct bfd_key key)
+{
+ struct bfd_session bs;
+
+ bs.key = key;
+
+ return hash_release(bfd_key_hash, &bs);
+}
+
+/* Iteration functions. */
+void bfd_id_iterate(hash_iter_func hif, void *arg)
+{
+ hash_iterate(bfd_id_hash, hif, arg);
+}
+
+void bfd_key_iterate(hash_iter_func hif, void *arg)
+{
+ hash_iterate(bfd_key_hash, hif, arg);
+}
+
+/*
+ * Insert functions.
+ *
+ * Inserts session into hash and returns `true` on success, otherwise
+ * `false`.
+ */
+bool bfd_id_insert(struct bfd_session *bs)
+{
+ return (hash_get(bfd_id_hash, bs, hash_alloc_intern) == bs);
+}
+
+bool bfd_key_insert(struct bfd_session *bs)
+{
+ return (hash_get(bfd_key_hash, bs, hash_alloc_intern) == bs);
+}
+
+void bfd_initialize(void)
+{
+ bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp,
+ "BFD session discriminator hash");
+ bfd_key_hash = hash_create(bfd_key_hash_do, bfd_key_hash_cmp,
+ "BFD session hash");
+ TAILQ_INIT(&bplist);
+}
+
+static void _bfd_free(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct bfd_session *bs = hb->data;
+
+ bfd_session_free(bs);
+}
+
+void bfd_shutdown(void)
+{
+ struct bfd_profile *bp;
+
+ /*
+ * Close and free all BFD sessions.
+ *
+ * _bfd_free() will call bfd_session_free() which will take care
+ * of removing the session from all hashes, so we just run an
+ * assert() here to make sure it really happened.
+ */
+ bfd_id_iterate(_bfd_free, NULL);
+ assert(bfd_key_hash->count == 0);
+
+ /* Now free the hashes themselves. */
+ hash_free(bfd_id_hash);
+ hash_free(bfd_key_hash);
+
+ /* Free all profile allocations. */
+ while ((bp = TAILQ_FIRST(&bplist)) != NULL)
+ bfd_profile_free(bp);
+}
+
+struct bfd_session_iterator {
+ int bsi_stop;
+ bool bsi_mhop;
+ const struct bfd_session *bsi_bs;
+};
+
+static int _bfd_session_next(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_session_iterator *bsi = arg;
+ struct bfd_session *bs = hb->data;
+
+ /* Previous entry signaled stop. */
+ if (bsi->bsi_stop == 1) {
+ /* Match the single/multi hop sessions. */
+ if (bs->key.mhop != bsi->bsi_mhop)
+ return HASHWALK_CONTINUE;
+
+ bsi->bsi_bs = bs;
+ return HASHWALK_ABORT;
+ }
+
+ /* We found the current item, stop in the next one. */
+ if (bsi->bsi_bs == hb->data) {
+ bsi->bsi_stop = 1;
+ /* Set entry to NULL to signal end of list. */
+ bsi->bsi_bs = NULL;
+ } else if (bsi->bsi_bs == NULL && bsi->bsi_mhop == bs->key.mhop) {
+ /* We want the first list item. */
+ bsi->bsi_stop = 1;
+ bsi->bsi_bs = hb->data;
+ return HASHWALK_ABORT;
+ }
+
+ return HASHWALK_CONTINUE;
+}
+
+/*
+ * bfd_session_next: uses the current session to find the next.
+ *
+ * `bs` might point to NULL to get the first item of the data structure.
+ */
+const struct bfd_session *bfd_session_next(const struct bfd_session *bs,
+ bool mhop)
+{
+ struct bfd_session_iterator bsi;
+
+ bsi.bsi_stop = 0;
+ bsi.bsi_bs = bs;
+ bsi.bsi_mhop = mhop;
+ hash_walk(bfd_key_hash, _bfd_session_next, &bsi);
+ if (bsi.bsi_stop == 0)
+ return NULL;
+
+ return bsi.bsi_bs;
+}
+
+static void _bfd_session_remove_manual(struct hash_bucket *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct bfd_session *bs = hb->data;
+
+ /* Delete only manually configured sessions. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) == 0)
+ return;
+
+ bs->refcount--;
+ UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG);
+
+ /* Don't delete sessions still in use. */
+ if (bs->refcount != 0)
+ return;
+
+ bfd_session_free(bs);
+}
+
+/*
+ * bfd_sessions_remove_manual: remove all manually configured sessions.
+ *
+ * NOTE: this function doesn't remove automatically created sessions.
+ */
+void bfd_sessions_remove_manual(void)
+{
+ hash_iterate(bfd_key_hash, _bfd_session_remove_manual, NULL);
+}
+
+void bfd_profiles_remove(void)
+{
+ struct bfd_profile *bp;
+
+ while ((bp = TAILQ_FIRST(&bplist)) != NULL)
+ bfd_profile_free(bp);
+}
+
+/*
+ * Profile related hash functions.
+ */
+static void _bfd_profile_update(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_profile *bp = arg;
+ struct bfd_session *bs = hb->data;
+
+ /* This session is not using the profile. */
+ if (bs->profile_name == NULL || strcmp(bs->profile_name, bp->name) != 0)
+ return;
+
+ bfd_profile_apply(bp->name, bs);
+}
+
+void bfd_profile_update(struct bfd_profile *bp)
+{
+ hash_iterate(bfd_key_hash, _bfd_profile_update, bp);
+}
+
+static void _bfd_profile_detach(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_profile *bp = arg;
+ struct bfd_session *bs = hb->data;
+
+ /* This session is not using the profile. */
+ if (bs->profile_name == NULL || strcmp(bs->profile_name, bp->name) != 0)
+ return;
+
+ bfd_profile_remove(bs);
+}
+
+static void bfd_profile_detach(struct bfd_profile *bp)
+{
+ hash_iterate(bfd_key_hash, _bfd_profile_detach, bp);
+}
+
+/*
+ * VRF related functions.
+ */
+static int bfd_vrf_new(struct vrf *vrf)
+{
+ if (bglobal.debug_zebra)
+ zlog_debug("VRF Created: %s(%u)", vrf->name, vrf->vrf_id);
+
+ return 0;
+}
+
+static int bfd_vrf_delete(struct vrf *vrf)
+{
+ if (bglobal.debug_zebra)
+ zlog_debug("VRF Deletion: %s(%u)", vrf->name, vrf->vrf_id);
+
+ return 0;
+}
+
+static int bfd_vrf_enable(struct vrf *vrf)
+{
+ struct bfd_vrf_global *bvrf;
+
+ /* a different name */
+ if (!vrf->info) {
+ bvrf = XCALLOC(MTYPE_BFDD_VRF, sizeof(struct bfd_vrf_global));
+ bvrf->vrf = vrf;
+ vrf->info = (void *)bvrf;
+
+ /* Disable sockets if using data plane. */
+ if (bglobal.bg_use_dplane) {
+ bvrf->bg_shop = -1;
+ bvrf->bg_mhop = -1;
+ bvrf->bg_shop6 = -1;
+ bvrf->bg_mhop6 = -1;
+ bvrf->bg_echo = -1;
+ bvrf->bg_echov6 = -1;
+ }
+ } else
+ bvrf = vrf->info;
+
+ if (bglobal.debug_zebra)
+ zlog_debug("VRF enable add %s id %u", vrf->name, vrf->vrf_id);
+
+ if (!bvrf->bg_shop)
+ bvrf->bg_shop = bp_udp_shop(vrf);
+ if (!bvrf->bg_mhop)
+ bvrf->bg_mhop = bp_udp_mhop(vrf);
+ if (!bvrf->bg_shop6)
+ bvrf->bg_shop6 = bp_udp6_shop(vrf);
+ if (!bvrf->bg_mhop6)
+ bvrf->bg_mhop6 = bp_udp6_mhop(vrf);
+ if (!bvrf->bg_echo)
+ bvrf->bg_echo = bp_echo_socket(vrf);
+ if (!bvrf->bg_echov6)
+ bvrf->bg_echov6 = bp_echov6_socket(vrf);
+
+ if (!bvrf->bg_ev[0] && bvrf->bg_shop != -1)
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop,
+ &bvrf->bg_ev[0]);
+ if (!bvrf->bg_ev[1] && bvrf->bg_mhop != -1)
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop,
+ &bvrf->bg_ev[1]);
+ if (!bvrf->bg_ev[2] && bvrf->bg_shop6 != -1)
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop6,
+ &bvrf->bg_ev[2]);
+ if (!bvrf->bg_ev[3] && bvrf->bg_mhop6 != -1)
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop6,
+ &bvrf->bg_ev[3]);
+ if (!bvrf->bg_ev[4] && bvrf->bg_echo != -1)
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echo,
+ &bvrf->bg_ev[4]);
+ if (!bvrf->bg_ev[5] && bvrf->bg_echov6 != -1)
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echov6,
+ &bvrf->bg_ev[5]);
+
+ if (vrf->vrf_id != VRF_DEFAULT) {
+ bfdd_zclient_register(vrf->vrf_id);
+ bfdd_sessions_enable_vrf(vrf);
+ }
+ return 0;
+}
+
+static int bfd_vrf_disable(struct vrf *vrf)
+{
+ struct bfd_vrf_global *bvrf;
+
+ if (!vrf->info)
+ return 0;
+ bvrf = vrf->info;
+
+ if (vrf->vrf_id != VRF_DEFAULT) {
+ bfdd_sessions_disable_vrf(vrf);
+ bfdd_zclient_unregister(vrf->vrf_id);
+ }
+
+ if (bglobal.debug_zebra)
+ zlog_debug("VRF disable %s id %d", vrf->name, vrf->vrf_id);
+
+ /* Disable read/write poll triggering. */
+ EVENT_OFF(bvrf->bg_ev[0]);
+ EVENT_OFF(bvrf->bg_ev[1]);
+ EVENT_OFF(bvrf->bg_ev[2]);
+ EVENT_OFF(bvrf->bg_ev[3]);
+ EVENT_OFF(bvrf->bg_ev[4]);
+ EVENT_OFF(bvrf->bg_ev[5]);
+
+ /* Close all descriptors. */
+ socket_close(&bvrf->bg_echo);
+ socket_close(&bvrf->bg_shop);
+ socket_close(&bvrf->bg_mhop);
+ if (bvrf->bg_shop6 != -1)
+ socket_close(&bvrf->bg_shop6);
+ if (bvrf->bg_mhop6 != -1)
+ socket_close(&bvrf->bg_mhop6);
+ socket_close(&bvrf->bg_echo);
+ if (bvrf->bg_echov6 != -1)
+ socket_close(&bvrf->bg_echov6);
+
+ /* free context */
+ XFREE(MTYPE_BFDD_VRF, bvrf);
+ vrf->info = NULL;
+
+ return 0;
+}
+
+void bfd_vrf_init(void)
+{
+ vrf_init(bfd_vrf_new, bfd_vrf_enable, bfd_vrf_disable, bfd_vrf_delete);
+}
+
+void bfd_vrf_terminate(void)
+{
+ vrf_terminate();
+}
+
+struct bfd_vrf_global *bfd_vrf_look_by_session(struct bfd_session *bfd)
+{
+ struct vrf *vrf;
+
+ if (!vrf_is_backend_netns()) {
+ vrf = vrf_lookup_by_id(VRF_DEFAULT);
+ if (vrf)
+ return (struct bfd_vrf_global *)vrf->info;
+ return NULL;
+ }
+ if (!bfd)
+ return NULL;
+ if (!bfd->vrf)
+ return NULL;
+ return bfd->vrf->info;
+}
+
+unsigned long bfd_get_session_count(void)
+{
+ return bfd_key_hash->count;
+}
+
+void bfd_rtt_init(struct bfd_session *bfd)
+{
+ uint8_t i;
+
+ /* initialize RTT */
+ bfd->rtt_valid = 0;
+ bfd->rtt_index = 0;
+ for (i = 0; i < BFD_RTT_SAMPLE; i++)
+ bfd->rtt[i] = 0;
+}
diff --git a/bfdd/bfd.h b/bfdd/bfd.h
new file mode 100644
index 0000000..6c5a1e9
--- /dev/null
+++ b/bfdd/bfd.h
@@ -0,0 +1,844 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved.
+ *
+ * bfd.h: implements the BFD protocol.
+ */
+
+#ifndef _BFD_H_
+#define _BFD_H_
+
+#include <netinet/in.h>
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include "lib/hash.h"
+#include "lib/libfrr.h"
+#include "lib/qobj.h"
+#include "lib/queue.h"
+#include "lib/vrf.h"
+
+#include "bfdctl.h"
+
+#ifdef BFD_DEBUG
+#define BFDD_JSON_CONV_OPTIONS (JSON_C_TO_STRING_PRETTY)
+#else
+#define BFDD_JSON_CONV_OPTIONS (0)
+#endif
+
+DECLARE_MGROUP(BFDD);
+DECLARE_MTYPE(BFDD_CONTROL);
+DECLARE_MTYPE(BFDD_NOTIFICATION);
+
+/* bfd Authentication Type. */
+#define BFD_AUTH_NULL 0
+#define BFD_AUTH_SIMPLE 1
+#define BFD_AUTH_CRYPTOGRAPHIC 2
+
+struct bfd_timers {
+ uint32_t desired_min_tx;
+ uint32_t required_min_rx;
+ uint32_t required_min_echo;
+};
+
+struct bfd_discrs {
+ uint32_t my_discr;
+ uint32_t remote_discr;
+};
+
+/*
+ * Format of control packet. From section 4)
+ */
+struct bfd_pkt {
+ union {
+ uint32_t byteFields;
+ struct {
+ uint8_t diag;
+ uint8_t flags;
+ uint8_t detect_mult;
+ uint8_t len;
+ };
+ };
+ struct bfd_discrs discrs;
+ struct bfd_timers timers;
+};
+
+/*
+ * Format of authentification.
+ */
+struct bfd_auth {
+ uint8_t type;
+ uint8_t length;
+};
+
+
+/*
+ * Format of Echo packet.
+ */
+struct bfd_echo_pkt {
+ union {
+ uint32_t byteFields;
+ struct {
+ uint8_t ver;
+ uint8_t len;
+ uint16_t reserved;
+ };
+ };
+ uint32_t my_discr;
+ uint64_t time_sent_sec;
+ uint64_t time_sent_usec;
+};
+
+
+/* Macros for manipulating control packets */
+#define BFD_VERMASK 0x07
+#define BFD_DIAGMASK 0x1F
+#define BFD_GETVER(diag) ((diag >> 5) & BFD_VERMASK)
+#define BFD_SETVER(diag, val) ((diag) |= (val & BFD_VERMASK) << 5)
+#define BFD_VERSION 1
+#define BFD_PBIT 0x20
+#define BFD_FBIT 0x10
+#define BFD_CBIT 0x08
+#define BFD_ABIT 0x04
+#define BFD_DEMANDBIT 0x02
+#define BFD_SETDEMANDBIT(flags, val) \
+ { \
+ if ((val)) \
+ flags |= BFD_DEMANDBIT; \
+ }
+#define BFD_SETPBIT(flags, val) \
+ { \
+ if ((val)) \
+ flags |= BFD_PBIT; \
+ }
+#define BFD_GETPBIT(flags) (flags & BFD_PBIT)
+#define BFD_SETFBIT(flags, val) \
+ { \
+ if ((val)) \
+ flags |= BFD_FBIT; \
+ }
+#define BFD_GETFBIT(flags) (flags & BFD_FBIT)
+#define BFD_SETSTATE(flags, val) \
+ { \
+ if ((val)) \
+ flags |= (val & 0x3) << 6; \
+ }
+#define BFD_GETSTATE(flags) ((flags >> 6) & 0x3)
+#define BFD_SETCBIT(flags, val) \
+ { \
+ if ((val)) \
+ flags |= val; \
+ }
+#define BFD_GETCBIT(flags) (flags & BFD_FBIT)
+#define BFD_ECHO_VERSION 1
+#define BFD_ECHO_PKT_LEN sizeof(struct bfd_echo_pkt)
+
+enum bfd_diagnosticis {
+ BD_OK = 0,
+ /* Control Detection Time Expired. */
+ BD_CONTROL_EXPIRED = 1,
+ /* Echo Function Failed. */
+ BD_ECHO_FAILED = 2,
+ /* Neighbor Signaled Session Down. */
+ BD_NEIGHBOR_DOWN = 3,
+ /* Forwarding Plane Reset. */
+ BD_FORWARDING_RESET = 4,
+ /* Path Down. */
+ BD_PATH_DOWN = 5,
+ /* Concatenated Path Down. */
+ BD_CONCATPATH_DOWN = 6,
+ /* Administratively Down. */
+ BD_ADMIN_DOWN = 7,
+ /* Reverse Concatenated Path Down. */
+ BD_REVCONCATPATH_DOWN = 8,
+ /* 9..31: reserved. */
+};
+
+/* BFD session flags */
+enum bfd_session_flags {
+ BFD_SESS_FLAG_NONE = 0,
+ BFD_SESS_FLAG_ECHO = 1 << 0, /* BFD Echo functionality */
+ BFD_SESS_FLAG_ECHO_ACTIVE = 1 << 1, /* BFD Echo Packets are being sent
+ * actively
+ */
+ BFD_SESS_FLAG_MH = 1 << 2, /* BFD Multi-hop session */
+ BFD_SESS_FLAG_IPV6 = 1 << 4, /* BFD IPv6 session */
+ BFD_SESS_FLAG_SEND_EVT_ACTIVE = 1 << 5, /* send event timer active */
+ BFD_SESS_FLAG_SEND_EVT_IGNORE = 1 << 6, /* ignore send event when timer
+ * expires
+ */
+ BFD_SESS_FLAG_SHUTDOWN = 1 << 7, /* disable BGP peer function */
+ BFD_SESS_FLAG_CONFIG = 1 << 8, /* Session configured with bfd NB API */
+ BFD_SESS_FLAG_CBIT = 1 << 9, /* CBIT is set */
+ BFD_SESS_FLAG_PASSIVE = 1 << 10, /* Passive mode */
+ BFD_SESS_FLAG_MAC_SET = 1 << 11, /* MAC of peer known */
+};
+
+/*
+ * BFD session hash key.
+ *
+ * This structure must not have any padding bytes because their value is
+ * unspecified after the struct assignment. Even when all fields of two keys
+ * are the same, if the padding bytes are different, then the calculated hash
+ * value is different, and the hash lookup will fail.
+ *
+ * Currently, the structure fields are correctly aligned, and the "packed"
+ * attribute is added as a precaution. "family" and "mhop" fields are two-bytes
+ * to eliminate unaligned memory access to "peer" and "local".
+ */
+struct bfd_key {
+ uint16_t family;
+ uint16_t mhop;
+ struct in6_addr peer;
+ struct in6_addr local;
+ char ifname[INTERFACE_NAMSIZ];
+ char vrfname[VRF_NAMSIZ];
+} __attribute__((packed));
+
+struct bfd_session_stats {
+ uint64_t rx_ctrl_pkt;
+ uint64_t tx_ctrl_pkt;
+ uint64_t rx_echo_pkt;
+ uint64_t tx_echo_pkt;
+ uint64_t session_up;
+ uint64_t session_down;
+ uint64_t znotification;
+};
+
+/**
+ * BFD session profile to override default configurations.
+ */
+struct bfd_profile {
+ /** Profile name. */
+ char name[64];
+
+ /** Session detection multiplier. */
+ uint8_t detection_multiplier;
+ /** Desired transmission interval (in microseconds). */
+ uint32_t min_tx;
+ /** Minimum required receive interval (in microseconds). */
+ uint32_t min_rx;
+ /** Administrative state. */
+ bool admin_shutdown;
+ /** Passive mode. */
+ bool passive;
+ /** Minimum expected TTL value. */
+ uint8_t minimum_ttl;
+
+ /** Echo mode (only applies to single hop). */
+ bool echo_mode;
+ /** Desired echo transmission interval (in microseconds). */
+ uint32_t min_echo_tx;
+ /** Minimum required echo receive interval (in microseconds). */
+ uint32_t min_echo_rx;
+
+ /** Profile list entry. */
+ TAILQ_ENTRY(bfd_profile) entry;
+};
+
+/** Profile list type. */
+TAILQ_HEAD(bfdproflist, bfd_profile);
+
+/* bfd_session shortcut label forwarding. */
+struct peer_label;
+
+struct bfd_config_timers {
+ uint32_t desired_min_tx;
+ uint32_t required_min_rx;
+ uint32_t desired_min_echo_tx;
+ uint32_t required_min_echo_rx;
+};
+
+#define BFD_RTT_SAMPLE 8
+
+/*
+ * Session state information
+ */
+struct bfd_session {
+
+ /* protocol state per RFC 5880*/
+ uint8_t ses_state;
+ struct bfd_discrs discrs;
+ uint8_t local_diag;
+ uint8_t demand_mode;
+ uint8_t detect_mult;
+ uint8_t remote_detect_mult;
+ uint8_t mh_ttl;
+ uint8_t remote_cbit;
+
+ /** BFD profile name. */
+ char *profile_name;
+ /** BFD pre configured profile. */
+ struct bfd_profile *profile;
+ /** BFD peer configuration (without profile). */
+ struct bfd_profile peer_profile;
+
+ /* Timers */
+ struct bfd_config_timers timers;
+ struct bfd_timers cur_timers;
+ uint64_t detect_TO;
+ struct event *echo_recvtimer_ev;
+ struct event *recvtimer_ev;
+ uint64_t xmt_TO;
+ uint64_t echo_xmt_TO;
+ struct event *xmttimer_ev;
+ struct event *echo_xmttimer_ev;
+ uint64_t echo_detect_TO;
+
+ /* software object state */
+ uint8_t polling;
+
+ /* This and the localDiscr are the keys to state info */
+ struct bfd_key key;
+ struct peer_label *pl;
+
+ struct bfd_dplane_ctx *bdc;
+ struct sockaddr_any local_address;
+ uint8_t peer_hw_addr[ETH_ALEN];
+ struct interface *ifp;
+ struct vrf *vrf;
+
+ int sock;
+
+ /* BFD session flags */
+ enum bfd_session_flags flags;
+
+ struct bfd_session_stats stats;
+
+ struct timeval uptime; /* last up time */
+ struct timeval downtime; /* last down time */
+
+ /* Remote peer data (for debugging mostly) */
+ uint8_t remote_diag;
+ struct bfd_timers remote_timers;
+
+ uint64_t refcount; /* number of pointers referencing this. */
+
+ uint8_t rtt_valid; /* number of valid samples */
+ uint8_t rtt_index; /* last index added */
+ uint64_t rtt[BFD_RTT_SAMPLE]; /* RRT in usec for echo to be looped */
+};
+
+struct peer_label {
+ TAILQ_ENTRY(peer_label) pl_entry;
+
+ struct bfd_session *pl_bs;
+ char pl_label[MAXNAMELEN];
+};
+TAILQ_HEAD(pllist, peer_label);
+
+struct bfd_diag_str_list {
+ const char *str;
+ int type;
+};
+
+struct bfd_state_str_list {
+ const char *str;
+ int type;
+};
+
+struct bfd_session_observer {
+ struct bfd_session *bso_bs;
+ char bso_entryname[MAXNAMELEN];
+ struct prefix bso_addr;
+
+ TAILQ_ENTRY(bfd_session_observer) bso_entry;
+};
+TAILQ_HEAD(obslist, bfd_session_observer);
+
+
+/* States defined per 4.1 */
+#define PTM_BFD_ADM_DOWN 0
+#define PTM_BFD_DOWN 1
+#define PTM_BFD_INIT 2
+#define PTM_BFD_UP 3
+
+
+/* Various constants */
+/* Retrieved from ptm_timer.h from Cumulus PTM sources. */
+#define BFD_DEF_DEMAND 0
+#define BFD_DEFDETECTMULT 3
+#define BFD_DEFDESIREDMINTX (300 * 1000) /* microseconds. */
+#define BFD_DEFREQUIREDMINRX (300 * 1000) /* microseconds. */
+#define BFD_DEF_DES_MIN_ECHO_TX (50 * 1000) /* microseconds. */
+#define BFD_DEF_REQ_MIN_ECHO_RX (50 * 1000) /* microseconds. */
+#define BFD_DEF_SLOWTX (1000 * 1000) /* microseconds. */
+/** Minimum multi hop TTL. */
+#define BFD_DEF_MHOP_TTL 254
+#define BFD_PKT_LEN 24 /* Length of control packet */
+#define BFD_TTL_VAL 255
+#define BFD_RCV_TTL_VAL 1
+#define BFD_TOS_VAL 0xC0
+#define BFD_PKT_INFO_VAL 1
+#define BFD_IPV6_PKT_INFO_VAL 1
+#define BFD_IPV6_ONLY_VAL 1
+#define BFD_SRCPORTINIT 49152
+#define BFD_SRCPORTMAX 65535
+#define BFD_DEFDESTPORT 3784
+#define BFD_DEF_ECHO_PORT 3785
+#define BFD_DEF_MHOP_DEST_PORT 4784
+
+/*
+ * control.c
+ *
+ * Daemon control code to speak with local consumers.
+ */
+
+/* See 'bfdctrl.h' for client protocol definitions. */
+
+struct bfd_control_buffer {
+ size_t bcb_left;
+ size_t bcb_pos;
+ union {
+ struct bfd_control_msg *bcb_bcm;
+ uint8_t *bcb_buf;
+ };
+};
+
+struct bfd_control_queue {
+ TAILQ_ENTRY(bfd_control_queue) bcq_entry;
+
+ struct bfd_control_buffer bcq_bcb;
+};
+TAILQ_HEAD(bcqueue, bfd_control_queue);
+
+struct bfd_notify_peer {
+ TAILQ_ENTRY(bfd_notify_peer) bnp_entry;
+
+ struct bfd_session *bnp_bs;
+};
+TAILQ_HEAD(bnplist, bfd_notify_peer);
+
+struct bfd_control_socket {
+ TAILQ_ENTRY(bfd_control_socket) bcs_entry;
+
+ int bcs_sd;
+ struct event *bcs_ev;
+ struct event *bcs_outev;
+ struct bcqueue bcs_bcqueue;
+
+ /* Notification data */
+ uint64_t bcs_notify;
+ struct bnplist bcs_bnplist;
+
+ enum bc_msg_version bcs_version;
+ enum bc_msg_type bcs_type;
+
+ /* Message buffering */
+ struct bfd_control_buffer bcs_bin;
+ struct bfd_control_buffer *bcs_bout;
+};
+TAILQ_HEAD(bcslist, bfd_control_socket);
+
+int control_init(const char *path);
+void control_shutdown(void);
+int control_notify(struct bfd_session *bs, uint8_t notify_state);
+int control_notify_config(const char *op, struct bfd_session *bs);
+void control_accept(struct event *t);
+
+
+/*
+ * bfdd.c
+ *
+ * Daemon specific code.
+ */
+struct bfd_vrf_global {
+ int bg_shop;
+ int bg_mhop;
+ int bg_shop6;
+ int bg_mhop6;
+ int bg_echo;
+ int bg_echov6;
+ struct vrf *vrf;
+
+ struct event *bg_ev[6];
+};
+
+/* Forward declaration of data plane context struct. */
+struct bfd_dplane_ctx;
+TAILQ_HEAD(dplane_queue, bfd_dplane_ctx);
+
+struct bfd_global {
+ int bg_csock;
+ struct event *bg_csockev;
+ struct bcslist bg_bcslist;
+
+ struct pllist bg_pllist;
+
+ struct obslist bg_obslist;
+
+ struct zebra_privs_t bfdd_privs;
+
+ /**
+ * Daemon is exit()ing? Use this to avoid actions that expect a
+ * running system or to avoid unnecessary operations when quitting.
+ */
+ bool bg_shutdown;
+
+ /* Distributed BFD items. */
+ bool bg_use_dplane;
+ int bg_dplane_sock;
+ struct event *bg_dplane_sockev;
+ struct dplane_queue bg_dplaneq;
+
+ /* Debug options. */
+ /* Show distributed BFD debug messages. */
+ bool debug_dplane;
+ /* Show all peer state changes events. */
+ bool debug_peer_event;
+ /*
+ * Show zebra message exchanges:
+ * - Interface add/delete.
+ * - Local address add/delete.
+ * - VRF add/delete.
+ */
+ bool debug_zebra;
+ /*
+ * Show network level debug information:
+ * - Echo packets without session.
+ * - Unavailable peer sessions.
+ * - Network system call failures.
+ */
+ bool debug_network;
+};
+
+extern struct bfd_global bglobal;
+extern const struct bfd_diag_str_list diag_list[];
+extern const struct bfd_state_str_list state_list[];
+
+void socket_close(int *s);
+
+
+/*
+ * config.c
+ *
+ * Contains the code related with loading/reloading configuration.
+ */
+int parse_config(const char *fname);
+int config_request_add(const char *jsonstr);
+int config_request_del(const char *jsonstr);
+char *config_response(const char *status, const char *error);
+char *config_notify(struct bfd_session *bs);
+char *config_notify_config(const char *op, struct bfd_session *bs);
+
+typedef int (*bpc_handle)(struct bfd_peer_cfg *, void *arg);
+int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr,
+ bpc_handle bh);
+
+struct peer_label *pl_new(const char *label, struct bfd_session *bs);
+struct peer_label *pl_find(const char *label);
+void pl_free(struct peer_label *pl);
+
+
+/*
+ * logging - alias to zebra log
+ */
+#define zlog_fatal(msg, ...) \
+ do { \
+ zlog_err(msg, ##__VA_ARGS__); \
+ assert(!msg); \
+ abort(); \
+ } while (0)
+
+
+/*
+ * bfd_packet.c
+ *
+ * Contains the code related with receiving/seding, packing/unpacking BFD data.
+ */
+int bp_set_ttlv6(int sd, uint8_t value);
+int bp_set_ttl(int sd, uint8_t value);
+int bp_set_tosv6(int sd, uint8_t value);
+int bp_set_tos(int sd, uint8_t value);
+int bp_bind_dev(int sd, const char *dev);
+
+int bp_udp_shop(const struct vrf *vrf);
+int bp_udp_mhop(const struct vrf *vrf);
+int bp_udp6_shop(const struct vrf *vrf);
+int bp_udp6_mhop(const struct vrf *vrf);
+int bp_peer_socket(const struct bfd_session *bs);
+int bp_peer_socketv6(const struct bfd_session *bs);
+int bp_echo_socket(const struct vrf *vrf);
+int bp_echov6_socket(const struct vrf *vrf);
+
+void ptm_bfd_snd(struct bfd_session *bfd, int fbit);
+void ptm_bfd_echo_snd(struct bfd_session *bfd);
+void ptm_bfd_echo_fp_snd(struct bfd_session *bfd);
+
+void bfd_recv_cb(struct event *t);
+
+
+/*
+ * event.c
+ *
+ * Contains the code related with event loop.
+ */
+typedef void (*bfd_ev_cb)(struct event *t);
+
+void bfd_recvtimer_update(struct bfd_session *bs);
+void bfd_echo_recvtimer_update(struct bfd_session *bs);
+void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter);
+void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter);
+
+void bfd_xmttimer_delete(struct bfd_session *bs);
+void bfd_echo_xmttimer_delete(struct bfd_session *bs);
+void bfd_recvtimer_delete(struct bfd_session *bs);
+void bfd_echo_recvtimer_delete(struct bfd_session *bs);
+
+void bfd_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd);
+void bfd_echo_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd);
+void bfd_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb);
+void bfd_echo_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb);
+
+
+/*
+ * bfd.c
+ *
+ * BFD protocol specific code.
+ */
+int bfd_session_enable(struct bfd_session *bs);
+void bfd_session_disable(struct bfd_session *bs);
+struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc);
+int ptm_bfd_sess_del(struct bfd_peer_cfg *bpc);
+void ptm_bfd_sess_dn(struct bfd_session *bfd, uint8_t diag);
+void ptm_bfd_sess_up(struct bfd_session *bfd);
+void ptm_bfd_echo_stop(struct bfd_session *bfd);
+void ptm_bfd_echo_start(struct bfd_session *bfd);
+void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit);
+void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo);
+struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp,
+ struct sockaddr_any *peer,
+ struct sockaddr_any *local,
+ struct interface *ifp,
+ vrf_id_t vrfid,
+ bool is_mhop);
+
+struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc);
+int bfd_session_update_label(struct bfd_session *bs, const char *nlabel);
+void bfd_set_polling(struct bfd_session *bs);
+void bs_state_handler(struct bfd_session *bs, int nstate);
+void bs_echo_timer_handler(struct bfd_session *bs);
+void bs_final_handler(struct bfd_session *bs);
+void bs_set_slow_timers(struct bfd_session *bs);
+const char *satostr(const struct sockaddr_any *sa);
+const char *diag2str(uint8_t diag);
+int strtosa(const char *addr, struct sockaddr_any *sa);
+void integer2timestr(uint64_t time, char *buf, size_t buflen);
+const char *bs_to_string(const struct bfd_session *bs);
+
+int bs_observer_add(struct bfd_session *bs);
+void bs_observer_del(struct bfd_session_observer *bso);
+
+void bs_to_bpc(struct bfd_session *bs, struct bfd_peer_cfg *bpc);
+
+void gen_bfd_key(struct bfd_key *key, struct sockaddr_any *peer,
+ struct sockaddr_any *local, bool mhop, const char *ifname,
+ const char *vrfname);
+struct bfd_session *bfd_session_new(void);
+struct bfd_session *bs_registrate(struct bfd_session *bs);
+void bfd_session_free(struct bfd_session *bs);
+const struct bfd_session *bfd_session_next(const struct bfd_session *bs,
+ bool mhop);
+void bfd_sessions_remove_manual(void);
+void bfd_profiles_remove(void);
+void bfd_rtt_init(struct bfd_session *bfd);
+
+/**
+ * Set the BFD session echo state.
+ *
+ * \param bs the BFD session.
+ * \param echo the echo operational state.
+ */
+void bfd_set_echo(struct bfd_session *bs, bool echo);
+
+/**
+ * Set the BFD session functional state.
+ *
+ * \param bs the BFD session.
+ * \param shutdown the operational value.
+ */
+void bfd_set_shutdown(struct bfd_session *bs, bool shutdown);
+
+/**
+ * Set the BFD session passive mode.
+ *
+ * \param bs the BFD session.
+ * \param passive the passive mode.
+ */
+void bfd_set_passive_mode(struct bfd_session *bs, bool passive);
+
+/**
+ * Picks the BFD session configuration from the appropriated source:
+ * if using the default peer configuration prefer profile (if it exists),
+ * otherwise use session.
+ *
+ * \param bs the BFD session.
+ */
+void bfd_session_apply(struct bfd_session *bs);
+
+/* BFD hash data structures interface */
+void bfd_initialize(void);
+void bfd_shutdown(void);
+void bfd_vrf_init(void);
+void bfd_vrf_terminate(void);
+struct bfd_vrf_global *bfd_vrf_look_by_session(struct bfd_session *bfd);
+struct bfd_session *bfd_id_lookup(uint32_t id);
+struct bfd_session *bfd_key_lookup(struct bfd_key key);
+
+struct bfd_session *bfd_id_delete(uint32_t id);
+struct bfd_session *bfd_key_delete(struct bfd_key key);
+
+bool bfd_id_insert(struct bfd_session *bs);
+bool bfd_key_insert(struct bfd_session *bs);
+
+typedef void (*hash_iter_func)(struct hash_bucket *hb, void *arg);
+void bfd_id_iterate(hash_iter_func hif, void *arg);
+void bfd_key_iterate(hash_iter_func hif, void *arg);
+
+unsigned long bfd_get_session_count(void);
+
+/* Export callback functions for `event.c`. */
+extern struct event_loop *master;
+
+void bfd_recvtimer_cb(struct event *t);
+void bfd_echo_recvtimer_cb(struct event *t);
+void bfd_xmt_cb(struct event *t);
+void bfd_echo_xmt_cb(struct event *t);
+
+extern struct in6_addr zero_addr;
+
+/**
+ * Creates a new profile entry and insert into the global list.
+ *
+ * \param name the BFD profile name.
+ *
+ * \returns `NULL` if it already exists otherwise the new entry.
+ */
+struct bfd_profile *bfd_profile_new(const char *name);
+
+/**
+ * Search for configured BFD profiles (profile name is case insensitive).
+ *
+ * \param name the BFD profile name.
+ *
+ * \returns `NULL` if it doesn't exist otherwise the entry.
+ */
+struct bfd_profile *bfd_profile_lookup(const char *name);
+
+/**
+ * Removes profile from list and free memory.
+ *
+ * \param bp the BFD profile.
+ */
+void bfd_profile_free(struct bfd_profile *bp);
+
+/**
+ * Apply a profile configuration to an existing BFD session. The non default
+ * values will not be overridden.
+ *
+ * NOTE: if the profile doesn't exist yet, then the profile will be applied
+ * once it begins to exist.
+ *
+ * \param profile_name the BFD profile name.
+ * \param bs the BFD session.
+ */
+void bfd_profile_apply(const char *profname, struct bfd_session *bs);
+
+/**
+ * Remove any applied profile from session and revert the session
+ * configuration.
+ *
+ * \param bs the BFD session.
+ */
+void bfd_profile_remove(struct bfd_session *bs);
+
+/**
+ * Apply new profile values to sessions using it.
+ *
+ * \param[in] bp the BFD profile that got updated.
+ */
+void bfd_profile_update(struct bfd_profile *bp);
+
+/*
+ * bfdd_vty.c
+ *
+ * BFD daemon vty shell commands.
+ */
+void bfdd_vty_init(void);
+
+
+/*
+ * bfdd_cli.c
+ *
+ * BFD daemon CLI implementation.
+ */
+void bfdd_cli_init(void);
+
+
+/*
+ * ptm_adapter.c
+ */
+void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv);
+void bfdd_zclient_stop(void);
+void bfdd_zclient_unregister(vrf_id_t vrf_id);
+void bfdd_zclient_register(vrf_id_t vrf_id);
+void bfdd_sessions_enable_vrf(struct vrf *vrf);
+void bfdd_sessions_disable_vrf(struct vrf *vrf);
+
+int ptm_bfd_notify(struct bfd_session *bs, uint8_t notify_state);
+
+/*
+ * dplane.c
+ */
+
+/**
+ * Initialize BFD data plane infrastructure for distributed BFD implementation.
+ *
+ * \param sa socket address.
+ * \param salen socket address structure length.
+ * \param client `true` means connecting socket, `false` listening socket.
+ */
+void bfd_dplane_init(const struct sockaddr *sa, socklen_t salen, bool client);
+
+/**
+ * Attempts to delegate the BFD session liveness detection to hardware.
+ *
+ * \param bs the BFD session data structure.
+ *
+ * \returns
+ * `0` on success and BFD daemon should do nothing or `-1` on failure
+ * and we should fallback to software implementation.
+ */
+int bfd_dplane_add_session(struct bfd_session *bs);
+
+/**
+ * Send new session settings to data plane.
+ *
+ * \param bs the BFD session to update.
+ */
+int bfd_dplane_update_session(const struct bfd_session *bs);
+
+/**
+ * Deletes session from data plane.
+ *
+ * \param bs the BFD session to delete.
+ *
+ * \returns `0` on success otherwise `-1`.
+ */
+int bfd_dplane_delete_session(struct bfd_session *bs);
+
+/**
+ * Asks the data plane for updated counters and update the session data
+ * structure.
+ *
+ * \param bs the BFD session that needs updating.
+ *
+ * \returns `0` on success otherwise `-1` on failure.
+ */
+int bfd_dplane_update_session_counters(struct bfd_session *bs);
+
+void bfd_dplane_show_counters(struct vty *vty);
+
+#endif /* _BFD_H_ */
diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c
new file mode 100644
index 0000000..5d8bf47
--- /dev/null
+++ b/bfdd/bfd_packet.c
@@ -0,0 +1,1759 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2017 Cumulus Networks, Inc. All rights reserved.
+ *
+ * bfd_packet.c: implements the BFD protocol packet handling.
+ *
+ * Authors
+ * -------
+ * Shrijeet Mukherjee [shm@cumulusnetworks.com]
+ * Kanna Rajagopal [kanna@cumulusnetworks.com]
+ * Radhika Mahankali [Radhika@cumulusnetworks.com]
+ */
+
+#include <zebra.h>
+
+#ifdef BFD_LINUX
+#include <linux/if_packet.h>
+#endif /* BFD_LINUX */
+
+#include <netinet/if_ether.h>
+#include <netinet/udp.h>
+
+#include "lib/sockopt.h"
+#include "lib/checksum.h"
+#include "lib/network.h"
+
+#include "bfd.h"
+
+/*
+ * Prototypes
+ */
+static int ptm_bfd_process_echo_pkt(struct bfd_vrf_global *bvrf, int s);
+int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data,
+ size_t datalen);
+
+static void bfd_sd_reschedule(struct bfd_vrf_global *bvrf, int sd);
+ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl,
+ ifindex_t *ifindex, struct sockaddr_any *local,
+ struct sockaddr_any *peer);
+ssize_t bfd_recv_ipv6(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl,
+ ifindex_t *ifindex, struct sockaddr_any *local,
+ struct sockaddr_any *peer);
+int bp_udp_send(int sd, uint8_t ttl, uint8_t *data, size_t datalen,
+ struct sockaddr *to, socklen_t tolen);
+int bp_bfd_echo_in(struct bfd_vrf_global *bvrf, int sd, uint8_t *ttl,
+ uint32_t *my_discr, uint64_t *my_rtt);
+#ifdef BFD_LINUX
+ssize_t bfd_recv_ipv4_fp(int sd, uint8_t *msgbuf, size_t msgbuflen,
+ uint8_t *ttl, ifindex_t *ifindex,
+ struct sockaddr_any *local, struct sockaddr_any *peer);
+void bfd_peer_mac_set(int sd, struct bfd_session *bfd,
+ struct sockaddr_any *peer, struct interface *ifp);
+int bp_udp_send_fp(int sd, uint8_t *data, size_t datalen,
+ struct bfd_session *bfd);
+ssize_t bfd_recv_fp_echo(int sd, uint8_t *msgbuf, size_t msgbuflen,
+ uint8_t *ttl, ifindex_t *ifindex,
+ struct sockaddr_any *local, struct sockaddr_any *peer);
+#endif
+
+/* socket related prototypes */
+static void bp_set_ipopts(int sd);
+static void bp_bind_ip(int sd, uint16_t port);
+static void bp_set_ipv6opts(int sd);
+static void bp_bind_ipv6(int sd, uint16_t port);
+
+
+/*
+ * Functions
+ */
+int _ptm_bfd_send(struct bfd_session *bs, uint16_t *port, const void *data,
+ size_t datalen)
+{
+ struct sockaddr *sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ socklen_t slen;
+ ssize_t rv;
+ int sd = -1;
+
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) {
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ memcpy(&sin6.sin6_addr, &bs->key.peer, sizeof(sin6.sin6_addr));
+ if (bs->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr))
+ sin6.sin6_scope_id = bs->ifp->ifindex;
+
+ sin6.sin6_port =
+ (port) ? *port
+ : (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH))
+ ? htons(BFD_DEF_MHOP_DEST_PORT)
+ : htons(BFD_DEFDESTPORT);
+
+ sd = bs->sock;
+ sa = (struct sockaddr *)&sin6;
+ slen = sizeof(sin6);
+ } else {
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ memcpy(&sin.sin_addr, &bs->key.peer, sizeof(sin.sin_addr));
+ sin.sin_port =
+ (port) ? *port
+ : (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH))
+ ? htons(BFD_DEF_MHOP_DEST_PORT)
+ : htons(BFD_DEFDESTPORT);
+
+ sd = bs->sock;
+ sa = (struct sockaddr *)&sin;
+ slen = sizeof(sin);
+ }
+
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sa->sa_len = slen;
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ rv = sendto(sd, data, datalen, 0, sa, slen);
+ if (rv <= 0) {
+ if (bglobal.debug_network)
+ zlog_debug("packet-send: send failure: %s",
+ strerror(errno));
+ return -1;
+ }
+ if (rv < (ssize_t)datalen) {
+ if (bglobal.debug_network)
+ zlog_debug("packet-send: send partial: %s",
+ strerror(errno));
+ }
+
+ return 0;
+}
+
+#ifdef BFD_LINUX
+/*
+ * Compute the UDP checksum.
+ *
+ * Checksum is not set in the packet, just computed.
+ *
+ * pkt
+ * Packet, fully filled out except for checksum field.
+ *
+ * pktsize
+ * sizeof(*pkt)
+ *
+ * ip
+ * IP address that pkt will be transmitted from and to.
+ *
+ * Returns:
+ * Checksum in network byte order.
+ */
+static uint16_t bfd_pkt_checksum(struct udphdr *pkt, size_t pktsize,
+ struct in6_addr *ip, sa_family_t family)
+{
+ uint16_t chksum;
+
+ pkt->check = 0;
+
+ if (family == AF_INET6) {
+ struct ipv6_ph ph = {};
+
+ memcpy(&ph.src, ip, sizeof(ph.src));
+ memcpy(&ph.dst, ip, sizeof(ph.dst));
+ ph.ulpl = htons(pktsize);
+ ph.next_hdr = IPPROTO_UDP;
+ chksum = in_cksum_with_ph6(&ph, pkt, pktsize);
+ } else {
+ struct ipv4_ph ph = {};
+
+ memcpy(&ph.src, ip, sizeof(ph.src));
+ memcpy(&ph.dst, ip, sizeof(ph.dst));
+ ph.proto = IPPROTO_UDP;
+ ph.len = htons(pktsize);
+ chksum = in_cksum_with_ph4(&ph, pkt, pktsize);
+ }
+
+ return chksum;
+}
+
+/*
+ * This routine creates the entire ECHO packet so that it will be looped
+ * in the forwarding plane of the peer router instead of going up the
+ * stack in BFD to be looped. If we haven't learned the peers MAC yet
+ * no echo is sent.
+ *
+ * echo packet with src/dst IP equal to local IP
+ * dest MAC as peer's MAC
+ *
+ * currently support ipv4
+ */
+void ptm_bfd_echo_fp_snd(struct bfd_session *bfd)
+{
+ int sd;
+ struct bfd_vrf_global *bvrf = bfd_vrf_look_by_session(bfd);
+ int total_len = 0;
+ struct ethhdr *eth;
+ struct udphdr *uh;
+ struct iphdr *iph;
+ struct bfd_echo_pkt *beph;
+ static char sendbuff[100];
+ struct timeval time_sent;
+
+ if (!bvrf)
+ return;
+ if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET))
+ return;
+ if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
+ SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE);
+
+ memset(sendbuff, 0, sizeof(sendbuff));
+
+ /* add eth hdr */
+ eth = (struct ethhdr *)(sendbuff);
+ memcpy(eth->h_source, bfd->ifp->hw_addr, sizeof(eth->h_source));
+ memcpy(eth->h_dest, bfd->peer_hw_addr, sizeof(eth->h_dest));
+
+ total_len += sizeof(struct ethhdr);
+
+ sd = bvrf->bg_echo;
+ eth->h_proto = htons(ETH_P_IP);
+
+ /* add ip hdr */
+ iph = (struct iphdr *)(sendbuff + sizeof(struct ethhdr));
+
+ iph->ihl = sizeof(struct ip) >> 2;
+ iph->version = IPVERSION;
+ iph->tos = IPTOS_PREC_INTERNETCONTROL;
+ iph->id = (uint16_t)frr_weak_random();
+ iph->ttl = BFD_TTL_VAL;
+ iph->protocol = IPPROTO_UDP;
+ memcpy(&iph->saddr, &bfd->local_address.sa_sin.sin_addr,
+ sizeof(bfd->local_address.sa_sin.sin_addr));
+ memcpy(&iph->daddr, &bfd->local_address.sa_sin.sin_addr,
+ sizeof(bfd->local_address.sa_sin.sin_addr));
+ total_len += sizeof(struct iphdr);
+
+ /* add udp hdr */
+ uh = (struct udphdr *)(sendbuff + sizeof(struct iphdr) +
+ sizeof(struct ethhdr));
+ uh->source = htons(BFD_DEF_ECHO_PORT);
+ uh->dest = htons(BFD_DEF_ECHO_PORT);
+
+ total_len += sizeof(struct udphdr);
+
+ /* add bfd echo */
+ beph = (struct bfd_echo_pkt *)(sendbuff + sizeof(struct udphdr) +
+ sizeof(struct iphdr) +
+ sizeof(struct ethhdr));
+
+ beph->ver = BFD_ECHO_VERSION;
+ beph->len = BFD_ECHO_PKT_LEN;
+ beph->my_discr = htonl(bfd->discrs.my_discr);
+
+ /* RTT calculation: add starting time in packet */
+ monotime(&time_sent);
+ beph->time_sent_sec = htobe64(time_sent.tv_sec);
+ beph->time_sent_usec = htobe64(time_sent.tv_usec);
+
+ total_len += sizeof(struct bfd_echo_pkt);
+ uh->len =
+ htons(total_len - sizeof(struct iphdr) - sizeof(struct ethhdr));
+ uh->check = bfd_pkt_checksum(
+ uh, (total_len - sizeof(struct iphdr) - sizeof(struct ethhdr)),
+ (struct in6_addr *)&iph->saddr, AF_INET);
+
+ iph->tot_len = htons(total_len - sizeof(struct ethhdr));
+ iph->check = in_cksum((const void *)iph, sizeof(struct iphdr));
+
+ if (bp_udp_send_fp(sd, (uint8_t *)&sendbuff, total_len, bfd) == -1)
+ return;
+
+ bfd->stats.tx_echo_pkt++;
+}
+#endif
+
+void ptm_bfd_echo_snd(struct bfd_session *bfd)
+{
+ struct sockaddr *sa;
+ socklen_t salen;
+ int sd;
+ struct bfd_echo_pkt bep;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct bfd_vrf_global *bvrf = bfd_vrf_look_by_session(bfd);
+
+ if (!bvrf)
+ return;
+ if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
+ SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE);
+
+ memset(&bep, 0, sizeof(bep));
+ bep.ver = BFD_ECHO_VERSION;
+ bep.len = BFD_ECHO_PKT_LEN;
+ bep.my_discr = htonl(bfd->discrs.my_discr);
+
+ if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6)) {
+ if (bvrf->bg_echov6 == -1)
+ return;
+ sd = bvrf->bg_echov6;
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ memcpy(&sin6.sin6_addr, &bfd->key.peer, sizeof(sin6.sin6_addr));
+ if (bfd->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr))
+ sin6.sin6_scope_id = bfd->ifp->ifindex;
+
+ sin6.sin6_port = htons(BFD_DEF_ECHO_PORT);
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin6.sin6_len = sizeof(sin6);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+
+ sa = (struct sockaddr *)&sin6;
+ salen = sizeof(sin6);
+ } else {
+ sd = bvrf->bg_echo;
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ memcpy(&sin.sin_addr, &bfd->key.peer, sizeof(sin.sin_addr));
+ sin.sin_port = htons(BFD_DEF_ECHO_PORT);
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin.sin_len = sizeof(sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+
+ sa = (struct sockaddr *)&sin;
+ salen = sizeof(sin);
+ }
+ if (bp_udp_send(sd, BFD_TTL_VAL, (uint8_t *)&bep, sizeof(bep), sa,
+ salen)
+ == -1)
+ return;
+
+ bfd->stats.tx_echo_pkt++;
+}
+
+static int ptm_bfd_process_echo_pkt(struct bfd_vrf_global *bvrf, int s)
+{
+ struct bfd_session *bfd;
+ uint32_t my_discr = 0;
+ uint64_t my_rtt = 0;
+ uint8_t ttl = 0;
+
+ /* Receive and parse echo packet. */
+ if (bp_bfd_echo_in(bvrf, s, &ttl, &my_discr, &my_rtt) == -1)
+ return 0;
+
+ /* Your discriminator not zero - use it to find session */
+ bfd = bfd_id_lookup(my_discr);
+ if (bfd == NULL) {
+ if (bglobal.debug_network)
+ zlog_debug("echo-packet: no matching session (id:%u)",
+ my_discr);
+ return -1;
+ }
+
+ if (!CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) {
+ if (bglobal.debug_network)
+ zlog_debug("echo-packet: echo disabled [%s] (id:%u)",
+ bs_to_string(bfd), my_discr);
+ return -1;
+ }
+
+ /* RTT Calculation: add current RTT to samples */
+ if (my_rtt != 0) {
+ bfd->rtt[bfd->rtt_index] = my_rtt;
+ bfd->rtt_index++;
+ if (bfd->rtt_index >= BFD_RTT_SAMPLE)
+ bfd->rtt_index = 0;
+ if (bfd->rtt_valid < BFD_RTT_SAMPLE)
+ bfd->rtt_valid++;
+ }
+
+ bfd->stats.rx_echo_pkt++;
+
+ /* Compute detect time */
+ bfd->echo_detect_TO = bfd->remote_detect_mult * bfd->echo_xmt_TO;
+
+ /* Update echo receive timeout. */
+ if (bfd->echo_detect_TO > 0)
+ bfd_echo_recvtimer_update(bfd);
+
+ return 0;
+}
+
+void ptm_bfd_snd(struct bfd_session *bfd, int fbit)
+{
+ struct bfd_pkt cp = {};
+
+ /* Set fields according to section 6.5.7 */
+ cp.diag = bfd->local_diag;
+ BFD_SETVER(cp.diag, BFD_VERSION);
+ cp.flags = 0;
+ BFD_SETSTATE(cp.flags, bfd->ses_state);
+
+ if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_CBIT))
+ BFD_SETCBIT(cp.flags, BFD_CBIT);
+
+ BFD_SETDEMANDBIT(cp.flags, BFD_DEF_DEMAND);
+
+ /*
+ * Polling and Final can't be set at the same time.
+ *
+ * RFC 5880, Section 6.5.
+ */
+ BFD_SETFBIT(cp.flags, fbit);
+ if (fbit == 0)
+ BFD_SETPBIT(cp.flags, bfd->polling);
+
+ cp.detect_mult = bfd->detect_mult;
+ cp.len = BFD_PKT_LEN;
+ cp.discrs.my_discr = htonl(bfd->discrs.my_discr);
+ cp.discrs.remote_discr = htonl(bfd->discrs.remote_discr);
+ if (bfd->polling) {
+ cp.timers.desired_min_tx =
+ htonl(bfd->timers.desired_min_tx);
+ cp.timers.required_min_rx =
+ htonl(bfd->timers.required_min_rx);
+ } else {
+ /*
+ * We can only announce current setting on poll, this
+ * avoids timing mismatch with our peer and give it
+ * the oportunity to learn. See `bs_final_handler` for
+ * more information.
+ */
+ cp.timers.desired_min_tx =
+ htonl(bfd->cur_timers.desired_min_tx);
+ cp.timers.required_min_rx =
+ htonl(bfd->cur_timers.required_min_rx);
+ }
+ cp.timers.required_min_echo = htonl(bfd->timers.required_min_echo_rx);
+
+ if (_ptm_bfd_send(bfd, NULL, &cp, BFD_PKT_LEN) != 0)
+ return;
+
+ bfd->stats.tx_ctrl_pkt++;
+}
+
+#ifdef BFD_LINUX
+/*
+ * receive the ipv4 echo packet that was loopback in the peers forwarding plane
+ */
+ssize_t bfd_recv_ipv4_fp(int sd, uint8_t *msgbuf, size_t msgbuflen,
+ uint8_t *ttl, ifindex_t *ifindex,
+ struct sockaddr_any *local, struct sockaddr_any *peer)
+{
+ ssize_t mlen;
+ struct sockaddr_ll msgaddr;
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ uint16_t recv_checksum;
+ uint16_t checksum;
+ struct iphdr *ip;
+ struct udphdr *uh;
+
+ /* Prepare the recvmsg params. */
+ iov[0].iov_base = msgbuf;
+ iov[0].iov_len = msgbuflen;
+
+ memset(&msghdr, 0, sizeof(msghdr));
+ msghdr.msg_name = &msgaddr;
+ msghdr.msg_namelen = sizeof(msgaddr);
+ msghdr.msg_iov = iov;
+ msghdr.msg_iovlen = 1;
+
+ mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT);
+ if (mlen == -1) {
+ if (errno != EAGAIN || errno != EWOULDBLOCK || errno != EINTR)
+ zlog_err("%s: recv failed: %s", __func__,
+ strerror(errno));
+
+ return -1;
+ }
+
+ ip = (struct iphdr *)(msgbuf + sizeof(struct ethhdr));
+
+ /* verify ip checksum */
+ recv_checksum = ip->check;
+ ip->check = 0;
+ checksum = in_cksum((const void *)ip, sizeof(struct iphdr));
+ if (recv_checksum != checksum) {
+ if (bglobal.debug_network)
+ zlog_debug(
+ "%s: invalid iphdr checksum expected 0x%x rcvd 0x%x",
+ __func__, checksum, recv_checksum);
+ return -1;
+ }
+
+ *ttl = ip->ttl;
+ if (*ttl != 254) {
+ if (bglobal.debug_network)
+ zlog_debug("%s: invalid TTL: %u", __func__, *ttl);
+ return -1;
+ }
+
+ local->sa_sin.sin_family = AF_INET;
+ memcpy(&local->sa_sin.sin_addr, &ip->saddr, sizeof(ip->saddr));
+ peer->sa_sin.sin_family = AF_INET;
+ memcpy(&peer->sa_sin.sin_addr, &ip->daddr, sizeof(ip->daddr));
+
+ *ifindex = msgaddr.sll_ifindex;
+
+ /* verify udp checksum */
+ uh = (struct udphdr *)(msgbuf + sizeof(struct iphdr) +
+ sizeof(struct ethhdr));
+ recv_checksum = uh->check;
+ uh->check = 0;
+ checksum = bfd_pkt_checksum(uh, ntohs(uh->len),
+ (struct in6_addr *)&ip->saddr, AF_INET);
+ if (recv_checksum != checksum) {
+ if (bglobal.debug_network)
+ zlog_debug(
+ "%s: invalid udphdr checksum expected 0x%x rcvd 0x%x",
+ __func__, checksum, recv_checksum);
+ return -1;
+ }
+ return mlen;
+}
+#endif
+
+ssize_t bfd_recv_ipv4(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl,
+ ifindex_t *ifindex, struct sockaddr_any *local,
+ struct sockaddr_any *peer)
+{
+ struct cmsghdr *cm;
+ ssize_t mlen;
+ struct sockaddr_in msgaddr;
+ struct msghdr msghdr;
+ struct iovec iov[1];
+ uint8_t cmsgbuf[255];
+
+ /* Prepare the recvmsg params. */
+ iov[0].iov_base = msgbuf;
+ iov[0].iov_len = msgbuflen;
+
+ memset(&msghdr, 0, sizeof(msghdr));
+ msghdr.msg_name = &msgaddr;
+ msghdr.msg_namelen = sizeof(msgaddr);
+ msghdr.msg_iov = iov;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_control = cmsgbuf;
+ msghdr.msg_controllen = sizeof(cmsgbuf);
+
+ mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT);
+ if (mlen == -1) {
+ if (errno != EAGAIN)
+ zlog_err("ipv4-recv: recv failed: %s", strerror(errno));
+
+ return -1;
+ }
+
+ /* Get source address */
+ peer->sa_sin = *((struct sockaddr_in *)(msghdr.msg_name));
+
+ /* Get and check TTL */
+ for (cm = CMSG_FIRSTHDR(&msghdr); cm != NULL;
+ cm = CMSG_NXTHDR(&msghdr, cm)) {
+ if (cm->cmsg_level != IPPROTO_IP)
+ continue;
+
+ switch (cm->cmsg_type) {
+#ifdef BFD_LINUX
+ case IP_TTL: {
+ uint32_t ttlval;
+
+ memcpy(&ttlval, CMSG_DATA(cm), sizeof(ttlval));
+ if (ttlval > 255) {
+ if (bglobal.debug_network)
+ zlog_debug("%s: invalid TTL: %u",
+ __func__, ttlval);
+ return -1;
+ }
+ *ttl = ttlval;
+ break;
+ }
+
+ case IP_PKTINFO: {
+ struct in_pktinfo *pi =
+ (struct in_pktinfo *)CMSG_DATA(cm);
+
+ if (pi == NULL)
+ break;
+
+ local->sa_sin.sin_family = AF_INET;
+ local->sa_sin.sin_addr = pi->ipi_addr;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ local->sa_sin.sin_len = sizeof(local->sa_sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+
+ *ifindex = pi->ipi_ifindex;
+ break;
+ }
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ case IP_RECVTTL: {
+ memcpy(ttl, CMSG_DATA(cm), sizeof(*ttl));
+ break;
+ }
+
+ case IP_RECVDSTADDR: {
+ struct in_addr ia;
+
+ memcpy(&ia, CMSG_DATA(cm), sizeof(ia));
+ local->sa_sin.sin_family = AF_INET;
+ local->sa_sin.sin_addr = ia;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ local->sa_sin.sin_len = sizeof(local->sa_sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ break;
+ }
+#endif /* BFD_BSD */
+
+ default:
+ /*
+ * On *BSDs we expect to land here when skipping
+ * the IP_RECVIF header. It will be handled by
+ * getsockopt_ifindex() below.
+ */
+ /* NOTHING */
+ break;
+ }
+ }
+
+ /* OS agnostic way of getting interface name. */
+ if (*ifindex == IFINDEX_INTERNAL)
+ *ifindex = getsockopt_ifindex(AF_INET, &msghdr);
+
+ return mlen;
+}
+
+ssize_t bfd_recv_ipv6(int sd, uint8_t *msgbuf, size_t msgbuflen, uint8_t *ttl,
+ ifindex_t *ifindex, struct sockaddr_any *local,
+ struct sockaddr_any *peer)
+{
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi6 = NULL;
+ ssize_t mlen;
+ uint32_t ttlval;
+ struct sockaddr_in6 msgaddr6;
+ struct msghdr msghdr6;
+ struct iovec iov[1];
+ uint8_t cmsgbuf6[255];
+
+ /* Prepare the recvmsg params. */
+ iov[0].iov_base = msgbuf;
+ iov[0].iov_len = msgbuflen;
+
+ memset(&msghdr6, 0, sizeof(msghdr6));
+ msghdr6.msg_name = &msgaddr6;
+ msghdr6.msg_namelen = sizeof(msgaddr6);
+ msghdr6.msg_iov = iov;
+ msghdr6.msg_iovlen = 1;
+ msghdr6.msg_control = cmsgbuf6;
+ msghdr6.msg_controllen = sizeof(cmsgbuf6);
+
+ mlen = recvmsg(sd, &msghdr6, MSG_DONTWAIT);
+ if (mlen == -1) {
+ if (errno != EAGAIN)
+ zlog_err("ipv6-recv: recv failed: %s", strerror(errno));
+
+ return -1;
+ }
+
+ /* Get source address */
+ peer->sa_sin6 = *((struct sockaddr_in6 *)(msghdr6.msg_name));
+
+ /* Get and check TTL */
+ for (cm = CMSG_FIRSTHDR(&msghdr6); cm != NULL;
+ cm = CMSG_NXTHDR(&msghdr6, cm)) {
+ if (cm->cmsg_level != IPPROTO_IPV6)
+ continue;
+
+ if (cm->cmsg_type == IPV6_HOPLIMIT) {
+ memcpy(&ttlval, CMSG_DATA(cm), sizeof(ttlval));
+ if (ttlval > 255) {
+ if (bglobal.debug_network)
+ zlog_debug("%s: invalid TTL: %u",
+ __func__, ttlval);
+ return -1;
+ }
+
+ *ttl = ttlval;
+ } else if (cm->cmsg_type == IPV6_PKTINFO) {
+ pi6 = (struct in6_pktinfo *)CMSG_DATA(cm);
+ if (pi6) {
+ local->sa_sin6.sin6_family = AF_INET6;
+ local->sa_sin6.sin6_addr = pi6->ipi6_addr;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ local->sa_sin6.sin6_len = sizeof(local->sa_sin6);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+
+ *ifindex = pi6->ipi6_ifindex;
+
+ /* Set scope ID for link local addresses. */
+ if (IN6_IS_ADDR_LINKLOCAL(
+ &peer->sa_sin6.sin6_addr))
+ peer->sa_sin6.sin6_scope_id = *ifindex;
+ if (IN6_IS_ADDR_LINKLOCAL(
+ &local->sa_sin6.sin6_addr))
+ local->sa_sin6.sin6_scope_id = *ifindex;
+ }
+ }
+ }
+
+ return mlen;
+}
+
+static void bfd_sd_reschedule(struct bfd_vrf_global *bvrf, int sd)
+{
+ if (sd == bvrf->bg_shop) {
+ EVENT_OFF(bvrf->bg_ev[0]);
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop,
+ &bvrf->bg_ev[0]);
+ } else if (sd == bvrf->bg_mhop) {
+ EVENT_OFF(bvrf->bg_ev[1]);
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop,
+ &bvrf->bg_ev[1]);
+ } else if (sd == bvrf->bg_shop6) {
+ EVENT_OFF(bvrf->bg_ev[2]);
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_shop6,
+ &bvrf->bg_ev[2]);
+ } else if (sd == bvrf->bg_mhop6) {
+ EVENT_OFF(bvrf->bg_ev[3]);
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_mhop6,
+ &bvrf->bg_ev[3]);
+ } else if (sd == bvrf->bg_echo) {
+ EVENT_OFF(bvrf->bg_ev[4]);
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echo,
+ &bvrf->bg_ev[4]);
+ } else if (sd == bvrf->bg_echov6) {
+ EVENT_OFF(bvrf->bg_ev[5]);
+ event_add_read(master, bfd_recv_cb, bvrf, bvrf->bg_echov6,
+ &bvrf->bg_ev[5]);
+ }
+}
+
+PRINTFRR(6, 7)
+static void cp_debug(bool mhop, struct sockaddr_any *peer,
+ struct sockaddr_any *local, ifindex_t ifindex,
+ vrf_id_t vrfid, const char *fmt, ...)
+{
+ char buf[512], peerstr[128], localstr[128], portstr[64], vrfstr[64];
+ va_list vl;
+
+ /* Don't to any processing if debug is disabled. */
+ if (bglobal.debug_network == false)
+ return;
+
+ if (peer->sa_sin.sin_family)
+ snprintf(peerstr, sizeof(peerstr), " peer:%s", satostr(peer));
+ else
+ peerstr[0] = 0;
+
+ if (local->sa_sin.sin_family)
+ snprintf(localstr, sizeof(localstr), " local:%s",
+ satostr(local));
+ else
+ localstr[0] = 0;
+
+ if (ifindex != IFINDEX_INTERNAL)
+ snprintf(portstr, sizeof(portstr), " port:%u", ifindex);
+ else
+ portstr[0] = 0;
+
+ if (vrfid != VRF_DEFAULT)
+ snprintf(vrfstr, sizeof(vrfstr), " vrf:%u", vrfid);
+ else
+ vrfstr[0] = 0;
+
+ va_start(vl, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, vl);
+ va_end(vl);
+
+ zlog_debug("control-packet: %s [mhop:%s%s%s%s%s]", buf,
+ mhop ? "yes" : "no", peerstr, localstr, portstr, vrfstr);
+}
+
+static bool bfd_check_auth(const struct bfd_session *bfd,
+ const struct bfd_pkt *cp)
+{
+ if (CHECK_FLAG(cp->flags, BFD_ABIT)) {
+ /* RFC5880 4.1: Authentication Section is present. */
+ struct bfd_auth *auth = (struct bfd_auth *)(cp + 1);
+ uint16_t pkt_auth_type = ntohs(auth->type);
+
+ if (cp->len < BFD_PKT_LEN + sizeof(struct bfd_auth))
+ return false;
+
+ if (cp->len < BFD_PKT_LEN + auth->length)
+ return false;
+
+ switch (pkt_auth_type) {
+ case BFD_AUTH_NULL:
+ return false;
+ case BFD_AUTH_SIMPLE:
+ /* RFC5880 6.7: To be finshed. */
+ return false;
+ case BFD_AUTH_CRYPTOGRAPHIC:
+ /* RFC5880 6.7: To be finshed. */
+ return false;
+ default:
+ /* RFC5880 6.7: To be finshed. */
+ return false;
+ }
+ }
+ return true;
+}
+
+void bfd_recv_cb(struct event *t)
+{
+ int sd = EVENT_FD(t);
+ struct bfd_session *bfd;
+ struct bfd_pkt *cp;
+ bool is_mhop;
+ ssize_t mlen = 0;
+ uint8_t ttl = 0;
+ vrf_id_t vrfid;
+ ifindex_t ifindex = IFINDEX_INTERNAL;
+ struct sockaddr_any local, peer;
+ uint8_t msgbuf[1516];
+ struct interface *ifp = NULL;
+ struct bfd_vrf_global *bvrf = EVENT_ARG(t);
+
+ /* Schedule next read. */
+ bfd_sd_reschedule(bvrf, sd);
+
+ /* Handle echo packets. */
+ if (sd == bvrf->bg_echo || sd == bvrf->bg_echov6) {
+ ptm_bfd_process_echo_pkt(bvrf, sd);
+ return;
+ }
+
+ /* Sanitize input/output. */
+ memset(&local, 0, sizeof(local));
+ memset(&peer, 0, sizeof(peer));
+
+ /* Handle control packets. */
+ is_mhop = false;
+ if (sd == bvrf->bg_shop || sd == bvrf->bg_mhop) {
+ is_mhop = sd == bvrf->bg_mhop;
+ mlen = bfd_recv_ipv4(sd, msgbuf, sizeof(msgbuf), &ttl, &ifindex,
+ &local, &peer);
+ } else if (sd == bvrf->bg_shop6 || sd == bvrf->bg_mhop6) {
+ is_mhop = sd == bvrf->bg_mhop6;
+ mlen = bfd_recv_ipv6(sd, msgbuf, sizeof(msgbuf), &ttl, &ifindex,
+ &local, &peer);
+ }
+
+ /*
+ * With netns backend, we have a separate socket in each VRF. It means
+ * that bvrf here is correct and we believe the bvrf->vrf->vrf_id.
+ * With VRF-lite backend, we have a single socket in the default VRF.
+ * It means that we can't believe the bvrf->vrf->vrf_id. But in
+ * VRF-lite, the ifindex is globally unique, so we can retrieve the
+ * correct vrf_id from the interface.
+ */
+ vrfid = bvrf->vrf->vrf_id;
+ if (ifindex) {
+ ifp = if_lookup_by_index(ifindex, vrfid);
+ if (ifp)
+ vrfid = ifp->vrf->vrf_id;
+ }
+
+ /* Implement RFC 5880 6.8.6 */
+ if (mlen < BFD_PKT_LEN) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "too small (%zd bytes)", mlen);
+ return;
+ }
+
+ /* Validate single hop packet TTL. */
+ if ((!is_mhop) && (ttl != BFD_TTL_VAL)) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "invalid TTL: %d expected %d", ttl, BFD_TTL_VAL);
+ return;
+ }
+
+ /*
+ * Parse the control header for inconsistencies:
+ * - Invalid version;
+ * - Bad multiplier configuration;
+ * - Short packets;
+ * - Invalid discriminator;
+ */
+ cp = (struct bfd_pkt *)(msgbuf);
+ if (BFD_GETVER(cp->diag) != BFD_VERSION) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "bad version %d", BFD_GETVER(cp->diag));
+ return;
+ }
+
+ if (cp->detect_mult == 0) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "detect multiplier set to zero");
+ return;
+ }
+
+ if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid, "too small");
+ return;
+ }
+
+ if (cp->discrs.my_discr == 0) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "'my discriminator' is zero");
+ return;
+ }
+
+ /* Find the session that this packet belongs. */
+ bfd = ptm_bfd_sess_find(cp, &peer, &local, ifp, vrfid, is_mhop);
+ if (bfd == NULL) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "no session found");
+ return;
+ }
+ /*
+ * We may have a situation where received packet is on wrong vrf
+ */
+ if (bfd && bfd->vrf && bfd->vrf->vrf_id != vrfid) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "wrong vrfid.");
+ return;
+ }
+
+ /* Ensure that existing good sessions are not overridden. */
+ if (!cp->discrs.remote_discr && bfd->ses_state != PTM_BFD_DOWN &&
+ bfd->ses_state != PTM_BFD_ADM_DOWN) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "'remote discriminator' is zero, not overridden");
+ return;
+ }
+
+ /*
+ * Multi hop: validate packet TTL.
+ * Single hop: set local address that received the packet.
+ * set peers mac address for echo packets
+ */
+ if (is_mhop) {
+ if (ttl < bfd->mh_ttl) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "exceeded max hop count (expected %d, got %d)",
+ bfd->mh_ttl, ttl);
+ return;
+ }
+ } else {
+
+ if (bfd->local_address.sa_sin.sin_family == AF_UNSPEC)
+ bfd->local_address = local;
+#ifdef BFD_LINUX
+ if (ifp)
+ bfd_peer_mac_set(sd, bfd, &peer, ifp);
+#endif
+ }
+
+ bfd->stats.rx_ctrl_pkt++;
+
+ /*
+ * If no interface was detected, save the interface where the
+ * packet came in.
+ */
+ if (!is_mhop && bfd->ifp == NULL)
+ bfd->ifp = ifp;
+
+ /* Log remote discriminator changes. */
+ if ((bfd->discrs.remote_discr != 0)
+ && (bfd->discrs.remote_discr != ntohl(cp->discrs.my_discr)))
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "remote discriminator mismatch (expected %u, got %u)",
+ bfd->discrs.remote_discr, ntohl(cp->discrs.my_discr));
+
+ bfd->discrs.remote_discr = ntohl(cp->discrs.my_discr);
+
+ /* Check authentication. */
+ if (!bfd_check_auth(bfd, cp)) {
+ cp_debug(is_mhop, &peer, &local, ifindex, vrfid,
+ "Authentication failed");
+ return;
+ }
+
+ /* Save remote diagnostics before state switch. */
+ bfd->remote_diag = cp->diag & BFD_DIAGMASK;
+
+ /* Update remote timers settings. */
+ bfd->remote_timers.desired_min_tx = ntohl(cp->timers.desired_min_tx);
+ bfd->remote_timers.required_min_rx = ntohl(cp->timers.required_min_rx);
+ bfd->remote_timers.required_min_echo =
+ ntohl(cp->timers.required_min_echo);
+ bfd->remote_detect_mult = cp->detect_mult;
+
+ if (BFD_GETCBIT(cp->flags))
+ bfd->remote_cbit = 1;
+ else
+ bfd->remote_cbit = 0;
+
+ /* State switch from section 6.2. */
+ bs_state_handler(bfd, BFD_GETSTATE(cp->flags));
+
+ /* RFC 5880, Section 6.5: handle POLL/FINAL negotiation sequence. */
+ if (bfd->polling && BFD_GETFBIT(cp->flags)) {
+ /* Disable polling. */
+ bfd->polling = 0;
+
+ /* Handle poll finalization. */
+ bs_final_handler(bfd);
+ }
+
+ /*
+ * Detection timeout calculation:
+ * The minimum detection timeout is the remote detection
+ * multipler (number of packets to be missed) times the agreed
+ * transmission interval.
+ *
+ * RFC 5880, Section 6.8.4.
+ */
+ if (bfd->cur_timers.required_min_rx > bfd->remote_timers.desired_min_tx)
+ bfd->detect_TO = bfd->remote_detect_mult
+ * bfd->cur_timers.required_min_rx;
+ else
+ bfd->detect_TO = bfd->remote_detect_mult
+ * bfd->remote_timers.desired_min_tx;
+
+ /* Apply new receive timer immediately. */
+ bfd_recvtimer_update(bfd);
+
+ /* Handle echo timers changes. */
+ bs_echo_timer_handler(bfd);
+
+ /*
+ * We've received a packet with the POLL bit set, we must send
+ * a control packet back with the FINAL bit set.
+ *
+ * RFC 5880, Section 6.5.
+ */
+ if (BFD_GETPBIT(cp->flags)) {
+ /* We are finalizing a poll negotiation. */
+ bs_final_handler(bfd);
+
+ /* Send the control packet with the final bit immediately. */
+ ptm_bfd_snd(bfd, 1);
+ }
+}
+
+/*
+ * bp_bfd_echo_in: proccesses an BFD echo packet. On TTL == BFD_TTL_VAL
+ * the packet is looped back or returns the my discriminator ID along
+ * with the TTL.
+ *
+ * Returns -1 on error or loopback or 0 on success.
+ */
+int bp_bfd_echo_in(struct bfd_vrf_global *bvrf, int sd, uint8_t *ttl,
+ uint32_t *my_discr, uint64_t *my_rtt)
+{
+ struct bfd_echo_pkt *bep;
+ ssize_t rlen;
+ struct sockaddr_any local, peer;
+ ifindex_t ifindex = IFINDEX_INTERNAL;
+ vrf_id_t vrfid = VRF_DEFAULT;
+ uint8_t msgbuf[1516];
+ size_t bfd_offset = 0;
+
+ if (sd == bvrf->bg_echo) {
+#ifdef BFD_LINUX
+ rlen = bfd_recv_ipv4_fp(sd, msgbuf, sizeof(msgbuf), ttl,
+ &ifindex, &local, &peer);
+
+ /* silently drop echo packet that is looped in fastpath but
+ * still comes up to BFD
+ */
+ if (rlen == -1)
+ return -1;
+ bfd_offset = sizeof(struct udphdr) + sizeof(struct iphdr) +
+ sizeof(struct ethhdr);
+#else
+ rlen = bfd_recv_ipv4(sd, msgbuf, sizeof(msgbuf), ttl, &ifindex,
+ &local, &peer);
+ bfd_offset = 0;
+#endif
+ } else {
+ rlen = bfd_recv_ipv6(sd, msgbuf, sizeof(msgbuf), ttl, &ifindex,
+ &local, &peer);
+ bfd_offset = 0;
+ }
+
+ /* Short packet, better not risk reading it. */
+ if (rlen < (ssize_t)sizeof(*bep)) {
+ cp_debug(false, &peer, &local, ifindex, vrfid,
+ "small echo packet");
+ return -1;
+ }
+
+ /* Test for loopback for ipv6, ipv4 is looped in forwarding plane */
+ if ((*ttl == BFD_TTL_VAL) && (sd == bvrf->bg_echov6)) {
+ bp_udp_send(sd, *ttl - 1, msgbuf, rlen,
+ (struct sockaddr *)&peer,
+ (sd == bvrf->bg_echo) ? sizeof(peer.sa_sin)
+ : sizeof(peer.sa_sin6));
+ return -1;
+ }
+
+ /* Read my discriminator from BFD Echo packet. */
+ bep = (struct bfd_echo_pkt *)(msgbuf + bfd_offset);
+ *my_discr = ntohl(bep->my_discr);
+ if (*my_discr == 0) {
+ cp_debug(false, &peer, &local, ifindex, vrfid,
+ "invalid echo packet discriminator (zero)");
+ return -1;
+ }
+
+#ifdef BFD_LINUX
+ /* RTT Calculation: determine RTT time of IPv4 echo pkt */
+ if (sd == bvrf->bg_echo) {
+ struct timeval time_sent = {0, 0};
+
+ time_sent.tv_sec = be64toh(bep->time_sent_sec);
+ time_sent.tv_usec = be64toh(bep->time_sent_usec);
+ *my_rtt = monotime_since(&time_sent, NULL);
+ }
+#endif
+
+ return 0;
+}
+
+#ifdef BFD_LINUX
+/*
+ * send a bfd packet with src/dst same IP so that the peer will receive
+ * the packet and forward it back to sender in the forwarding plane
+ */
+int bp_udp_send_fp(int sd, uint8_t *data, size_t datalen,
+ struct bfd_session *bfd)
+{
+ ssize_t wlen;
+ struct msghdr msg = {0};
+ struct iovec iov[1];
+ uint8_t msgctl[255];
+ struct sockaddr_ll sadr_ll = {0};
+
+ sadr_ll.sll_ifindex = bfd->ifp->ifindex;
+ sadr_ll.sll_halen = ETH_ALEN;
+ memcpy(sadr_ll.sll_addr, bfd->peer_hw_addr, sizeof(bfd->peer_hw_addr));
+ sadr_ll.sll_protocol = htons(ETH_P_IP);
+
+ /* Prepare message data. */
+ iov[0].iov_base = data;
+ iov[0].iov_len = datalen;
+
+ memset(msgctl, 0, sizeof(msgctl));
+ msg.msg_name = &sadr_ll;
+ msg.msg_namelen = sizeof(sadr_ll);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ /* Send echo to peer */
+ wlen = sendmsg(sd, &msg, 0);
+
+ if (wlen <= 0) {
+ if (bglobal.debug_network)
+ zlog_debug("%s: loopback failure: (%d) %s", __func__,
+ errno, strerror(errno));
+ return -1;
+ } else if (wlen < (ssize_t)datalen) {
+ if (bglobal.debug_network)
+ zlog_debug("%s: partial send: %zd expected %zu",
+ __func__, wlen, datalen);
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+int bp_udp_send(int sd, uint8_t ttl, uint8_t *data, size_t datalen,
+ struct sockaddr *to, socklen_t tolen)
+{
+ struct cmsghdr *cmsg;
+ ssize_t wlen;
+ int ttlval = ttl;
+ bool is_ipv6 = to->sa_family == AF_INET6;
+ struct msghdr msg;
+ struct iovec iov[1];
+ uint8_t msgctl[255];
+
+ /* Prepare message data. */
+ iov[0].iov_base = data;
+ iov[0].iov_len = datalen;
+
+ memset(&msg, 0, sizeof(msg));
+ memset(msgctl, 0, sizeof(msgctl));
+ msg.msg_name = to;
+ msg.msg_namelen = tolen;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ /* Prepare the packet TTL information. */
+ if (ttl > 0) {
+ /* Use ancillary data. */
+ msg.msg_control = msgctl;
+ msg.msg_controllen = CMSG_LEN(sizeof(ttlval));
+
+ /* Configure the ancillary data. */
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(ttlval));
+ if (is_ipv6) {
+ cmsg->cmsg_level = IPPROTO_IPV6;
+ cmsg->cmsg_type = IPV6_HOPLIMIT;
+ } else {
+#ifdef BFD_LINUX
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_TTL;
+#else
+ /* FreeBSD does not support TTL in ancillary data. */
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+
+ bp_set_ttl(sd, ttl);
+#endif /* BFD_BSD */
+ }
+ memcpy(CMSG_DATA(cmsg), &ttlval, sizeof(ttlval));
+ }
+
+ /* Send echo back. */
+ wlen = sendmsg(sd, &msg, 0);
+ if (wlen <= 0) {
+ if (bglobal.debug_network)
+ zlog_debug("%s: loopback failure: (%d) %s", __func__,
+ errno, strerror(errno));
+ return -1;
+ } else if (wlen < (ssize_t)datalen) {
+ if (bglobal.debug_network)
+ zlog_debug("%s: partial send: %zd expected %zu",
+ __func__, wlen, datalen);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Sockets creation.
+ */
+
+
+/*
+ * IPv4 sockets
+ */
+int bp_set_ttl(int sd, uint8_t value)
+{
+ int ttl = value;
+
+ if (setsockopt(sd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) {
+ zlog_warn("%s: setsockopt(IP_TTL, %d): %s", __func__, value,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int bp_set_tos(int sd, uint8_t value)
+{
+ int tos = value;
+
+ if (setsockopt(sd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1) {
+ zlog_warn("%s: setsockopt(IP_TOS, %d): %s", __func__, value,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static bool bp_set_reuse_addr(int sd)
+{
+ int one = 1;
+
+ if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
+ zlog_warn("%s: setsockopt(SO_REUSEADDR, %d): %s", __func__, one,
+ strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+static bool bp_set_reuse_port(int sd)
+{
+ int one = 1;
+
+ if (setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) == -1) {
+ zlog_warn("%s: setsockopt(SO_REUSEPORT, %d): %s", __func__, one,
+ strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+
+static void bp_set_ipopts(int sd)
+{
+ int rcvttl = BFD_RCV_TTL_VAL;
+
+ if (!bp_set_reuse_addr(sd))
+ zlog_fatal("set-reuse-addr: failed");
+
+ if (!bp_set_reuse_port(sd))
+ zlog_fatal("set-reuse-port: failed");
+
+ if (bp_set_ttl(sd, BFD_TTL_VAL) != 0)
+ zlog_fatal("set-ipopts: TTL configuration failed");
+
+ if (setsockopt(sd, IPPROTO_IP, IP_RECVTTL, &rcvttl, sizeof(rcvttl))
+ == -1)
+ zlog_fatal("set-ipopts: setsockopt(IP_RECVTTL, %d): %s", rcvttl,
+ strerror(errno));
+
+#ifdef BFD_LINUX
+ int pktinfo = BFD_PKT_INFO_VAL;
+
+ /* Figure out address and interface to do the peer matching. */
+ if (setsockopt(sd, IPPROTO_IP, IP_PKTINFO, &pktinfo, sizeof(pktinfo))
+ == -1)
+ zlog_fatal("set-ipopts: setsockopt(IP_PKTINFO, %d): %s",
+ pktinfo, strerror(errno));
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ int yes = 1;
+
+ /* Find out our address for peer matching. */
+ if (setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) == -1)
+ zlog_fatal("set-ipopts: setsockopt(IP_RECVDSTADDR, %d): %s",
+ yes, strerror(errno));
+
+ /* Find out interface where the packet came in. */
+ if (setsockopt_ifindex(AF_INET, sd, yes) == -1)
+ zlog_fatal("set-ipopts: setsockopt_ipv4_ifindex(%d): %s", yes,
+ strerror(errno));
+#endif /* BFD_BSD */
+}
+
+static void bp_bind_ip(int sd, uint16_t port)
+{
+ struct sockaddr_in sin;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ sin.sin_port = htons(port);
+ if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
+ zlog_fatal("bind-ip: bind: %s", strerror(errno));
+}
+
+int bp_udp_shop(const struct vrf *vrf)
+{
+ int sd;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id,
+ vrf->name);
+ }
+ if (sd == -1)
+ zlog_fatal("udp-shop: socket: %s", strerror(errno));
+
+ bp_set_ipopts(sd);
+ bp_bind_ip(sd, BFD_DEFDESTPORT);
+ return sd;
+}
+
+int bp_udp_mhop(const struct vrf *vrf)
+{
+ int sd;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id,
+ vrf->name);
+ }
+ if (sd == -1)
+ zlog_fatal("udp-mhop: socket: %s", strerror(errno));
+
+ bp_set_ipopts(sd);
+ bp_bind_ip(sd, BFD_DEF_MHOP_DEST_PORT);
+
+ return sd;
+}
+
+int bp_peer_socket(const struct bfd_session *bs)
+{
+ int sd, pcount;
+ struct sockaddr_in sin;
+ static int srcPort = BFD_SRCPORTINIT;
+ const char *device_to_bind = NULL;
+
+ if (bs->key.ifname[0])
+ device_to_bind = (const char *)bs->key.ifname;
+ else if ((!vrf_is_backend_netns() && bs->vrf->vrf_id != VRF_DEFAULT)
+ || ((CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)
+ && bs->key.vrfname[0])))
+ device_to_bind = (const char *)bs->key.vrfname;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ sd = vrf_socket(AF_INET, SOCK_DGRAM, PF_UNSPEC,
+ bs->vrf->vrf_id, device_to_bind);
+ }
+ if (sd == -1) {
+ zlog_err("ipv4-new: failed to create socket: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ /* Set TTL to 255 for all transmitted packets */
+ if (bp_set_ttl(sd, BFD_TTL_VAL) != 0) {
+ close(sd);
+ return -1;
+ }
+
+ /* Set TOS to CS6 for all transmitted packets */
+ if (bp_set_tos(sd, BFD_TOS_VAL) != 0) {
+ close(sd);
+ return -1;
+ }
+
+ /* Find an available source port in the proper range */
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin.sin_len = sizeof(sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ memcpy(&sin.sin_addr, &bs->key.local, sizeof(sin.sin_addr));
+
+ pcount = 0;
+ do {
+ if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) {
+ /* Searched all ports, none available */
+ zlog_err("ipv4-new: failed to bind port: %s",
+ strerror(errno));
+ close(sd);
+ return -1;
+ }
+ if (srcPort >= BFD_SRCPORTMAX)
+ srcPort = BFD_SRCPORTINIT;
+ sin.sin_port = htons(srcPort++);
+ } while (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0);
+
+ return sd;
+}
+
+
+/*
+ * IPv6 sockets
+ */
+
+int bp_peer_socketv6(const struct bfd_session *bs)
+{
+ int sd, pcount;
+ struct sockaddr_in6 sin6;
+ static int srcPort = BFD_SRCPORTINIT;
+ const char *device_to_bind = NULL;
+
+ if (bs->key.ifname[0])
+ device_to_bind = (const char *)bs->key.ifname;
+ else if ((!vrf_is_backend_netns() && bs->vrf->vrf_id != VRF_DEFAULT)
+ || ((CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)
+ && bs->key.vrfname[0])))
+ device_to_bind = (const char *)bs->key.vrfname;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC,
+ bs->vrf->vrf_id, device_to_bind);
+ }
+ if (sd == -1) {
+ zlog_err("ipv6-new: failed to create socket: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ /* Set TTL to 255 for all transmitted packets */
+ if (bp_set_ttlv6(sd, BFD_TTL_VAL) != 0) {
+ close(sd);
+ return -1;
+ }
+
+ /* Set TOS to CS6 for all transmitted packets */
+ if (bp_set_tosv6(sd, BFD_TOS_VAL) != 0) {
+ close(sd);
+ return -1;
+ }
+
+ /* Find an available source port in the proper range */
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin6.sin6_len = sizeof(sin6);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ memcpy(&sin6.sin6_addr, &bs->key.local, sizeof(sin6.sin6_addr));
+ if (bs->ifp && IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr))
+ sin6.sin6_scope_id = bs->ifp->ifindex;
+
+ pcount = 0;
+ do {
+ if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) {
+ /* Searched all ports, none available */
+ zlog_err("ipv6-new: failed to bind port: %s",
+ strerror(errno));
+ close(sd);
+ return -1;
+ }
+ if (srcPort >= BFD_SRCPORTMAX)
+ srcPort = BFD_SRCPORTINIT;
+ sin6.sin6_port = htons(srcPort++);
+ } while (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0);
+
+ return sd;
+}
+
+int bp_set_ttlv6(int sd, uint8_t value)
+{
+ int ttl = value;
+
+ if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl))
+ == -1) {
+ zlog_warn("set-ttlv6: setsockopt(IPV6_UNICAST_HOPS, %d): %s",
+ value, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int bp_set_tosv6(int sd, uint8_t value)
+{
+ int tos = value;
+
+ if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos))
+ == -1) {
+ zlog_warn("set-tosv6: setsockopt(IPV6_TCLASS, %d): %s", value,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void bp_set_ipv6opts(int sd)
+{
+ int ipv6_pktinfo = BFD_IPV6_PKT_INFO_VAL;
+ int ipv6_only = BFD_IPV6_ONLY_VAL;
+
+ if (!bp_set_reuse_addr(sd))
+ zlog_fatal("set-reuse-addr: failed");
+
+ if (!bp_set_reuse_port(sd))
+ zlog_fatal("set-reuse-port: failed");
+
+ if (bp_set_ttlv6(sd, BFD_TTL_VAL) == -1)
+ zlog_fatal(
+ "set-ipv6opts: setsockopt(IPV6_UNICAST_HOPS, %d): %s",
+ BFD_TTL_VAL, strerror(errno));
+
+ if (setsockopt_ipv6_hoplimit(sd, BFD_RCV_TTL_VAL) == -1)
+ zlog_fatal("set-ipv6opts: setsockopt(IPV6_HOPLIMIT, %d): %s",
+ BFD_RCV_TTL_VAL, strerror(errno));
+
+ if (setsockopt_ipv6_pktinfo(sd, ipv6_pktinfo) == -1)
+ zlog_fatal("set-ipv6opts: setsockopt(IPV6_PKTINFO, %d): %s",
+ ipv6_pktinfo, strerror(errno));
+
+ if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only,
+ sizeof(ipv6_only))
+ == -1)
+ zlog_fatal("set-ipv6opts: setsockopt(IPV6_V6ONLY, %d): %s",
+ ipv6_only, strerror(errno));
+}
+
+static void bp_bind_ipv6(int sd, uint16_t port)
+{
+ struct sockaddr_in6 sin6;
+
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr = in6addr_any;
+ sin6.sin6_port = htons(port);
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin6.sin6_len = sizeof(sin6);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ if (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1)
+ zlog_fatal("bind-ipv6: bind: %s", strerror(errno));
+}
+
+int bp_udp6_shop(const struct vrf *vrf)
+{
+ int sd;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id,
+ vrf->name);
+ }
+ if (sd == -1) {
+ if (errno != EAFNOSUPPORT)
+ zlog_fatal("udp6-shop: socket: %s", strerror(errno));
+ else
+ zlog_warn("udp6-shop: V6 is not supported, continuing");
+
+ return -1;
+ }
+
+ bp_set_ipv6opts(sd);
+ bp_bind_ipv6(sd, BFD_DEFDESTPORT);
+
+ return sd;
+}
+
+int bp_udp6_mhop(const struct vrf *vrf)
+{
+ int sd;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ sd = vrf_socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC, vrf->vrf_id,
+ vrf->name);
+ }
+ if (sd == -1) {
+ if (errno != EAFNOSUPPORT)
+ zlog_fatal("udp6-mhop: socket: %s", strerror(errno));
+ else
+ zlog_warn("udp6-mhop: V6 is not supported, continuing");
+
+ return -1;
+ }
+
+ bp_set_ipv6opts(sd);
+ bp_bind_ipv6(sd, BFD_DEF_MHOP_DEST_PORT);
+
+ return sd;
+}
+
+#ifdef BFD_LINUX
+/* tcpdump -dd udp dst port 3785 */
+struct sock_filter my_filterudp[] = {
+ {0x28, 0, 0, 0x0000000c}, {0x15, 0, 8, 0x00000800},
+ {0x30, 0, 0, 0x00000017}, {0x15, 0, 6, 0x00000011},
+ {0x28, 0, 0, 0x00000014}, {0x45, 4, 0, 0x00001fff},
+ {0xb1, 0, 0, 0x0000000e}, {0x48, 0, 0, 0x00000010},
+ {0x15, 0, 1, 0x00000ec9}, {0x6, 0, 0, 0x00040000},
+ {0x6, 0, 0, 0x00000000},
+};
+
+#define MY_FILTER_LENGTH 11
+
+int bp_echo_socket(const struct vrf *vrf)
+{
+ int s;
+
+ frr_with_privs (&bglobal.bfdd_privs) {
+ s = vrf_socket(AF_PACKET, SOCK_RAW, ETH_P_IP, vrf->vrf_id,
+ vrf->name);
+ }
+
+ if (s == -1)
+ zlog_fatal("echo-socket: socket: %s", strerror(errno));
+
+ struct sock_fprog pf;
+ struct sockaddr_ll sll = {0};
+
+ /* adjust filter for socket to only receive ECHO packets */
+ pf.filter = my_filterudp;
+ pf.len = MY_FILTER_LENGTH;
+ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) ==
+ -1) {
+ zlog_warn("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__,
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ memset(&sll, 0, sizeof(sll));
+ sll.sll_family = AF_PACKET;
+ sll.sll_protocol = htons(ETH_P_IP);
+ sll.sll_ifindex = 0;
+ if (bind(s, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
+ zlog_warn("Failed to bind echo socket: %s",
+ safe_strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+#else
+int bp_echo_socket(const struct vrf *vrf)
+{
+ int s;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ s = vrf_socket(AF_INET, SOCK_DGRAM, 0, vrf->vrf_id, vrf->name);
+ }
+ if (s == -1)
+ zlog_fatal("echo-socket: socket: %s", strerror(errno));
+
+ bp_set_ipopts(s);
+ bp_bind_ip(s, BFD_DEF_ECHO_PORT);
+
+ return s;
+}
+#endif
+
+int bp_echov6_socket(const struct vrf *vrf)
+{
+ int s;
+
+ frr_with_privs(&bglobal.bfdd_privs) {
+ s = vrf_socket(AF_INET6, SOCK_DGRAM, 0, vrf->vrf_id, vrf->name);
+ }
+ if (s == -1) {
+ if (errno != EAFNOSUPPORT)
+ zlog_fatal("echov6-socket: socket: %s",
+ strerror(errno));
+ else
+ zlog_warn("echov6-socket: V6 is not supported, continuing");
+
+ return -1;
+ }
+
+ bp_set_ipv6opts(s);
+ bp_bind_ipv6(s, BFD_DEF_ECHO_PORT);
+
+ return s;
+}
+
+#ifdef BFD_LINUX
+/* get peer's mac address to be used with Echo packets when they are looped in
+ * peers forwarding plane
+ */
+void bfd_peer_mac_set(int sd, struct bfd_session *bfd,
+ struct sockaddr_any *peer, struct interface *ifp)
+{
+ struct arpreq arpreq_;
+
+ if (CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET))
+ return;
+ if (ifp->flags & IFF_NOARP)
+ return;
+
+ if (peer->sa_sin.sin_family == AF_INET) {
+ /* IPV4 */
+ struct sockaddr_in *addr =
+ (struct sockaddr_in *)&arpreq_.arp_pa;
+
+ memset(&arpreq_, 0, sizeof(struct arpreq));
+ addr->sin_family = AF_INET;
+ memcpy(&addr->sin_addr.s_addr, &peer->sa_sin.sin_addr,
+ sizeof(addr->sin_addr));
+ strlcpy(arpreq_.arp_dev, ifp->name, sizeof(arpreq_.arp_dev));
+
+ if (ioctl(sd, SIOCGARP, &arpreq_) < 0) {
+ if (bglobal.debug_network)
+ zlog_debug(
+ "BFD: getting peer's mac on %s failed error %s",
+ ifp->name, strerror(errno));
+ UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET);
+ memset(bfd->peer_hw_addr, 0, sizeof(bfd->peer_hw_addr));
+
+ } else {
+ memcpy(bfd->peer_hw_addr, arpreq_.arp_ha.sa_data,
+ sizeof(bfd->peer_hw_addr));
+ SET_FLAG(bfd->flags, BFD_SESS_FLAG_MAC_SET);
+ }
+ }
+}
+#endif
diff --git a/bfdd/bfdctl.h b/bfdd/bfdctl.h
new file mode 100644
index 0000000..f1f8185
--- /dev/null
+++ b/bfdd/bfdctl.h
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * bfdctl.h: all BFDd control socket protocol definitions.
+ *
+ * Authors
+ * -------
+ * Rafael Zalamena <rzalamena@opensourcerouting.org>
+ */
+
+#ifndef _BFDCTRL_H_
+#define _BFDCTRL_H_
+
+#include <netinet/in.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/*
+ * Auxiliary definitions
+ */
+struct sockaddr_any {
+ union {
+ struct sockaddr_in sa_sin;
+ struct sockaddr_in6 sa_sin6;
+ };
+};
+
+#ifndef MAXNAMELEN
+#define MAXNAMELEN 32
+#endif
+
+#define BPC_DEF_DETECTMULTIPLIER 3
+#define BPC_DEF_RECEIVEINTERVAL 300 /* milliseconds */
+#define BPC_DEF_TRANSMITINTERVAL 300 /* milliseconds */
+#define BPC_DEF_ECHORECEIVEINTERVAL 50 /* milliseconds */
+#define BPC_DEF_ECHOTRANSMITINTERVAL 50 /* milliseconds */
+
+/* Peer status */
+enum bfd_peer_status {
+ BPS_SHUTDOWN = 0, /* == PTM_BFD_ADM_DOWN, "adm-down" */
+ BPS_DOWN = 1, /* == PTM_BFD_DOWN, "down" */
+ BPS_INIT = 2, /* == PTM_BFD_INIT, "init" */
+ BPS_UP = 3, /* == PTM_BFD_UP, "up" */
+};
+
+struct bfd_peer_cfg {
+ bool bpc_mhop;
+ bool bpc_ipv4;
+ struct sockaddr_any bpc_peer;
+ struct sockaddr_any bpc_local;
+
+ bool bpc_has_label;
+ char bpc_label[MAXNAMELEN];
+
+ bool bpc_has_localif;
+ char bpc_localif[MAXNAMELEN + 1];
+
+ bool bpc_has_vrfname;
+ char bpc_vrfname[MAXNAMELEN + 1];
+
+ bool bpc_has_detectmultiplier;
+ uint8_t bpc_detectmultiplier;
+
+ bool bpc_has_recvinterval;
+ uint64_t bpc_recvinterval;
+
+ bool bpc_has_txinterval;
+ uint64_t bpc_txinterval;
+
+ bool bpc_has_echorecvinterval;
+ uint64_t bpc_echorecvinterval;
+
+ bool bpc_has_echotxinterval;
+ uint64_t bpc_echotxinterval;
+
+ bool bpc_has_minimum_ttl;
+ uint8_t bpc_minimum_ttl;
+
+ bool bpc_echo;
+ bool bpc_createonly;
+ bool bpc_shutdown;
+
+ bool bpc_cbit;
+ bool bpc_passive;
+
+ bool bpc_has_profile;
+ char bpc_profile[64];
+
+ /* Status information */
+ enum bfd_peer_status bpc_bps;
+ uint32_t bpc_id;
+ uint32_t bpc_remoteid;
+ uint8_t bpc_diag;
+ uint8_t bpc_remotediag;
+ uint8_t bpc_remote_detectmultiplier;
+ uint64_t bpc_remote_recvinterval;
+ uint64_t bpc_remote_txinterval;
+ uint64_t bpc_remote_echointerval;
+ uint64_t bpc_lastevent;
+};
+
+
+/*
+ * Protocol definitions
+ */
+enum bc_msg_version {
+ BMV_VERSION_1 = 1,
+};
+
+enum bc_msg_type {
+ BMT_RESPONSE = 1,
+ BMT_REQUEST_ADD = 2,
+ BMT_REQUEST_DEL = 3,
+ BMT_NOTIFY = 4,
+ BMT_NOTIFY_ADD = 5,
+ BMT_NOTIFY_DEL = 6,
+};
+
+/* Notify flags to use with bcm_notify. */
+#define BCM_NOTIFY_ALL ((uint64_t)-1)
+#define BCM_NOTIFY_PEER_STATE (1ULL << 0)
+#define BCM_NOTIFY_CONFIG (1ULL << 1)
+#define BCM_NOTIFY_NONE 0
+
+/* Response 'status' definitions. */
+#define BCM_RESPONSE_OK "ok"
+#define BCM_RESPONSE_ERROR "error"
+
+/* Notify operation. */
+#define BCM_NOTIFY_PEER_STATUS "status"
+#define BCM_NOTIFY_CONFIG_ADD "add"
+#define BCM_NOTIFY_CONFIG_DELETE "delete"
+#define BCM_NOTIFY_CONFIG_UPDATE "update"
+
+/* Notification special ID. */
+#define BCM_NOTIFY_ID 0
+
+struct bfd_control_msg {
+ /* Total length without the header. */
+ uint32_t bcm_length;
+ /*
+ * Message request/response id.
+ * All requests will have a correspondent response with the
+ * same id.
+ */
+ uint16_t bcm_id;
+ /* Message type. */
+ uint8_t bcm_type;
+ /* Message version. */
+ uint8_t bcm_ver;
+ /* Message payload. */
+ uint8_t bcm_data[0];
+};
+
+#endif
diff --git a/bfdd/bfdd.c b/bfdd/bfdd.c
new file mode 100644
index 0000000..95066b9
--- /dev/null
+++ b/bfdd/bfdd.c
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD daemon code
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ */
+
+#include <zebra.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <err.h>
+
+#include "filter.h"
+#include "if.h"
+#include "vrf.h"
+
+#include "bfd.h"
+#include "bfdd_nb.h"
+#include "bfddp_packet.h"
+#include "lib/version.h"
+#include "lib/command.h"
+
+
+/*
+ * FRR related code.
+ */
+DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon");
+DEFINE_MTYPE(BFDD, BFDD_CONTROL, "control socket memory");
+DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "control notification data");
+
+/* Master of threads. */
+struct event_loop *master;
+
+/* BFDd privileges */
+static zebra_capabilities_t _caps_p[] = {ZCAP_BIND, ZCAP_SYS_ADMIN, ZCAP_NET_RAW};
+
+/* BFD daemon information. */
+static struct frr_daemon_info bfdd_di;
+
+void socket_close(int *s)
+{
+ if (*s <= 0)
+ return;
+
+ if (close(*s) != 0)
+ zlog_err("%s: close(%d): (%d) %s", __func__, *s, errno,
+ strerror(errno));
+
+ *s = -1;
+}
+
+static void sigusr1_handler(void)
+{
+ zlog_rotate();
+}
+
+static void sigterm_handler(void)
+{
+ bglobal.bg_shutdown = true;
+
+ /* Signalize shutdown. */
+ frr_early_fini();
+
+ /* Stop receiving message from zebra. */
+ bfdd_zclient_stop();
+
+ /* Shutdown controller to avoid receiving anymore commands. */
+ control_shutdown();
+
+ /* Shutdown and free all protocol related memory. */
+ bfd_shutdown();
+
+ bfd_vrf_terminate();
+
+ /* Terminate and free() FRR related memory. */
+ frr_fini();
+
+ exit(0);
+}
+
+static void sighup_handler(void)
+{
+ zlog_info("SIGHUP received");
+
+ /* Reload config file. */
+ vty_read_config(NULL, bfdd_di.config_file, config_default);
+}
+
+static struct frr_signal_t bfd_signals[] = {
+ {
+ .signal = SIGUSR1,
+ .handler = &sigusr1_handler,
+ },
+ {
+ .signal = SIGTERM,
+ .handler = &sigterm_handler,
+ },
+ {
+ .signal = SIGINT,
+ .handler = &sigterm_handler,
+ },
+ {
+ .signal = SIGHUP,
+ .handler = &sighup_handler,
+ },
+};
+
+static const struct frr_yang_module_info *const bfdd_yang_modules[] = {
+ &frr_filter_info,
+ &frr_interface_info,
+ &frr_bfdd_info,
+ &frr_vrf_info,
+};
+
+FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617,
+ .proghelp = "Implementation of the BFD protocol.",
+ .signals = bfd_signals, .n_signals = array_size(bfd_signals),
+ .privs = &bglobal.bfdd_privs,
+ .yang_modules = bfdd_yang_modules,
+ .n_yang_modules = array_size(bfdd_yang_modules),
+);
+
+#define OPTION_CTLSOCK 1001
+#define OPTION_DPLANEADDR 2000
+static const struct option longopts[] = {
+ {"bfdctl", required_argument, NULL, OPTION_CTLSOCK},
+ {"dplaneaddr", required_argument, NULL, OPTION_DPLANEADDR},
+ {0}
+};
+
+
+/*
+ * BFD daemon related code.
+ */
+struct bfd_global bglobal;
+
+const struct bfd_diag_str_list diag_list[] = {
+ {.str = "control-expired", .type = BD_CONTROL_EXPIRED},
+ {.str = "echo-failed", .type = BD_ECHO_FAILED},
+ {.str = "neighbor-down", .type = BD_NEIGHBOR_DOWN},
+ {.str = "forwarding-reset", .type = BD_FORWARDING_RESET},
+ {.str = "path-down", .type = BD_PATH_DOWN},
+ {.str = "concatenated-path-down", .type = BD_CONCATPATH_DOWN},
+ {.str = "administratively-down", .type = BD_ADMIN_DOWN},
+ {.str = "reverse-concat-path-down", .type = BD_REVCONCATPATH_DOWN},
+ {.str = NULL},
+};
+
+const struct bfd_state_str_list state_list[] = {
+ {.str = "admin-down", .type = PTM_BFD_ADM_DOWN},
+ {.str = "down", .type = PTM_BFD_DOWN},
+ {.str = "init", .type = PTM_BFD_INIT},
+ {.str = "up", .type = PTM_BFD_UP},
+ {.str = NULL},
+};
+
+static uint16_t
+parse_port(const char *str)
+{
+ char *nulbyte;
+ long rv;
+
+ errno = 0;
+ rv = strtol(str, &nulbyte, 10);
+ /* No conversion performed. */
+ if (rv == 0 && errno == EINVAL) {
+ fprintf(stderr, "invalid BFD data plane address port: %s\n",
+ str);
+ exit(0);
+ }
+ /* Invalid number range. */
+ if ((rv <= 0 || rv >= 65535) || errno == ERANGE) {
+ fprintf(stderr, "invalid BFD data plane port range: %s\n",
+ str);
+ exit(0);
+ }
+ /* There was garbage at the end of the string. */
+ if (*nulbyte != 0) {
+ fprintf(stderr, "invalid BFD data plane port: %s\n",
+ str);
+ exit(0);
+ }
+
+ return (uint16_t)rv;
+}
+
+static void
+distributed_bfd_init(const char *arg)
+{
+ char *sptr, *saux;
+ bool is_client = false;
+ size_t slen;
+ socklen_t salen;
+ char addr[64];
+ char type[64];
+ union {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_un sun;
+ } sa;
+
+ /* Basic parsing: find ':' to figure out type part and address part. */
+ sptr = strchr(arg, ':');
+ if (sptr == NULL) {
+ fprintf(stderr, "invalid BFD data plane socket: %s\n", arg);
+ exit(1);
+ }
+
+ /* Calculate type string length. */
+ slen = (size_t)(sptr - arg);
+
+ /* Copy the address part. */
+ sptr++;
+ strlcpy(addr, sptr, sizeof(addr));
+
+ /* Copy type part. */
+ strlcpy(type, arg, slen + 1);
+
+ /* Reset address data. */
+ memset(&sa, 0, sizeof(sa));
+
+ /* Fill the address information. */
+ if (strcmp(type, "unix") == 0 || strcmp(type, "unixc") == 0) {
+ if (strcmp(type, "unixc") == 0)
+ is_client = true;
+
+ salen = sizeof(sa.sun);
+ sa.sun.sun_family = AF_UNIX;
+ strlcpy(sa.sun.sun_path, addr, sizeof(sa.sun.sun_path));
+ } else if (strcmp(type, "ipv4") == 0 || strcmp(type, "ipv4c") == 0) {
+ if (strcmp(type, "ipv4c") == 0)
+ is_client = true;
+
+ salen = sizeof(sa.sin);
+ sa.sin.sin_family = AF_INET;
+
+ /* Parse port if any. */
+ sptr = strchr(addr, ':');
+ if (sptr == NULL) {
+ sa.sin.sin_port = htons(BFD_DATA_PLANE_DEFAULT_PORT);
+ } else {
+ *sptr = 0;
+ sa.sin.sin_port = htons(parse_port(sptr + 1));
+ }
+
+ if (inet_pton(AF_INET, addr, &sa.sin.sin_addr) != 1)
+ errx(1, "%s: inet_pton: invalid address %s", __func__,
+ addr);
+ } else if (strcmp(type, "ipv6") == 0 || strcmp(type, "ipv6c") == 0) {
+ if (strcmp(type, "ipv6c") == 0)
+ is_client = true;
+
+ salen = sizeof(sa.sin6);
+ sa.sin6.sin6_family = AF_INET6;
+
+ /* Check for IPv6 enclosures '[]' */
+ sptr = &addr[0];
+ if (*sptr != '[')
+ errx(1, "%s: invalid IPv6 address format: %s", __func__,
+ addr);
+
+ saux = strrchr(addr, ']');
+ if (saux == NULL)
+ errx(1, "%s: invalid IPv6 address format: %s", __func__,
+ addr);
+
+ /* Consume the '[]:' part. */
+ slen = saux - sptr;
+ memmove(addr, addr + 1, slen);
+ addr[slen - 1] = 0;
+
+ /* Parse port if any. */
+ saux++;
+ sptr = strrchr(saux, ':');
+ if (sptr == NULL) {
+ sa.sin6.sin6_port = htons(BFD_DATA_PLANE_DEFAULT_PORT);
+ } else {
+ *sptr = 0;
+ sa.sin6.sin6_port = htons(parse_port(sptr + 1));
+ }
+
+ if (inet_pton(AF_INET6, addr, &sa.sin6.sin6_addr) != 1)
+ errx(1, "%s: inet_pton: invalid address %s", __func__,
+ addr);
+ } else {
+ fprintf(stderr, "invalid BFD data plane socket type: %s\n",
+ type);
+ exit(1);
+ }
+
+ /* Initialize BFD data plane listening socket. */
+ bfd_dplane_init((struct sockaddr *)&sa, salen, is_client);
+}
+
+static void bg_init(void)
+{
+ struct zebra_privs_t bfdd_privs = {
+#if defined(FRR_USER) && defined(FRR_GROUP)
+ .user = FRR_USER,
+ .group = FRR_GROUP,
+#endif
+#if defined(VTY_GROUP)
+ .vty_group = VTY_GROUP,
+#endif
+ .caps_p = _caps_p,
+ .cap_num_p = array_size(_caps_p),
+ .cap_num_i = 0,
+ };
+
+ TAILQ_INIT(&bglobal.bg_bcslist);
+ TAILQ_INIT(&bglobal.bg_obslist);
+
+ memcpy(&bglobal.bfdd_privs, &bfdd_privs,
+ sizeof(bfdd_privs));
+}
+
+int main(int argc, char *argv[])
+{
+ char ctl_path[512], dplane_addr[512];
+ bool ctlsockused = false;
+ int opt;
+
+ bglobal.bg_use_dplane = false;
+
+ /* Initialize system sockets. */
+ bg_init();
+
+ frr_preinit(&bfdd_di, argc, argv);
+ frr_opt_add("", longopts,
+ " --bfdctl Specify bfdd control socket\n"
+ " --dplaneaddr Specify BFD data plane address\n");
+
+ snprintf(ctl_path, sizeof(ctl_path), BFDD_CONTROL_SOCKET,
+ "", "");
+ while (true) {
+ opt = frr_getopt(argc, argv, NULL);
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case OPTION_CTLSOCK:
+ strlcpy(ctl_path, optarg, sizeof(ctl_path));
+ ctlsockused = true;
+ break;
+ case OPTION_DPLANEADDR:
+ strlcpy(dplane_addr, optarg, sizeof(dplane_addr));
+ bglobal.bg_use_dplane = true;
+ break;
+
+ default:
+ frr_help_exit(1);
+ }
+ }
+
+ if (bfdd_di.pathspace && !ctlsockused)
+ snprintf(ctl_path, sizeof(ctl_path), BFDD_CONTROL_SOCKET,
+ "/", bfdd_di.pathspace);
+
+ /* Initialize FRR infrastructure. */
+ master = frr_init();
+
+ /* Initialize control socket. */
+ control_init(ctl_path);
+
+ /* Initialize BFD data structures. */
+ bfd_initialize();
+
+ bfd_vrf_init();
+
+ access_list_init();
+
+ /* Initialize zebra connection. */
+ bfdd_zclient_init(&bglobal.bfdd_privs);
+
+ event_add_read(master, control_accept, NULL, bglobal.bg_csock,
+ &bglobal.bg_csockev);
+
+ /* Install commands. */
+ bfdd_vty_init();
+
+ /* read configuration file and daemonize */
+ frr_config_fork();
+
+ /* Initialize BFD data plane listening socket. */
+ if (bglobal.bg_use_dplane)
+ distributed_bfd_init(dplane_addr);
+
+ frr_run(master);
+ /* NOTREACHED */
+
+ return 0;
+}
diff --git a/bfdd/bfdd_cli.c b/bfdd/bfdd_cli.c
new file mode 100644
index 0000000..44439c6
--- /dev/null
+++ b/bfdd/bfdd_cli.c
@@ -0,0 +1,718 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD daemon CLI implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ */
+
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/log.h"
+#include "lib/northbound_cli.h"
+
+#include "bfdd/bfdd_cli_clippy.c"
+
+#include "bfd.h"
+#include "bfdd_nb.h"
+
+/*
+ * Definitions.
+ */
+#define PEER_STR "Configure peer\n"
+#define INTERFACE_NAME_STR "Configure interface name to use\n"
+#define PEER_IPV4_STR "IPv4 peer address\n"
+#define PEER_IPV6_STR "IPv6 peer address\n"
+#define MHOP_STR "Configure multihop\n"
+#define LOCAL_STR "Configure local address\n"
+#define LOCAL_IPV4_STR "IPv4 local address\n"
+#define LOCAL_IPV6_STR "IPv6 local address\n"
+#define LOCAL_INTF_STR "Configure local interface name to use\n"
+#define VRF_STR "Configure VRF\n"
+#define VRF_NAME_STR "Configure VRF name\n"
+
+/*
+ * Prototypes.
+ */
+static bool
+bfd_cli_is_single_hop(struct vty *vty)
+{
+ return strstr(VTY_CURR_XPATH, "/single-hop") != NULL;
+}
+
+static bool
+bfd_cli_is_profile(struct vty *vty)
+{
+ return strstr(VTY_CURR_XPATH, "/bfd/profile") != NULL;
+}
+
+/*
+ * Functions.
+ */
+DEFPY_YANG_NOSH(
+ bfd_enter, bfd_enter_cmd,
+ "bfd",
+ "Configure BFD peers\n")
+{
+ int ret;
+
+ nb_cli_enqueue_change(vty, "/frr-bfdd:bfdd/bfd", NB_OP_CREATE, NULL);
+ ret = nb_cli_apply_changes(vty, NULL);
+ if (ret == CMD_SUCCESS)
+ VTY_PUSH_XPATH(BFD_NODE, "/frr-bfdd:bfdd/bfd");
+
+ return ret;
+}
+
+DEFUN_YANG(
+ bfd_config_reset, bfd_config_reset_cmd,
+ "no bfd",
+ NO_STR
+ "Configure BFD peers\n")
+{
+ nb_cli_enqueue_change(vty, "/frr-bfdd:bfdd/bfd", NB_OP_DESTROY, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_header(struct vty *vty,
+ const struct lyd_node *dnode
+ __attribute__((__unused__)),
+ bool show_defaults __attribute__((__unused__)))
+{
+ vty_out(vty, "!\nbfd\n");
+}
+
+void bfd_cli_show_header_end(struct vty *vty, const struct lyd_node *dnode
+ __attribute__((__unused__)))
+{
+ vty_out(vty, "exit\n");
+ vty_out(vty, "!\n");
+}
+
+DEFPY_YANG_NOSH(
+ bfd_peer_enter, bfd_peer_enter_cmd,
+ "peer <A.B.C.D|X:X::X:X> [{multihop$multihop|local-address <A.B.C.D|X:X::X:X>|interface IFNAME$ifname|vrf NAME}]",
+ PEER_STR
+ PEER_IPV4_STR
+ PEER_IPV6_STR
+ MHOP_STR
+ LOCAL_STR
+ LOCAL_IPV4_STR
+ LOCAL_IPV6_STR
+ INTERFACE_STR
+ LOCAL_INTF_STR
+ VRF_STR
+ VRF_NAME_STR)
+{
+ int ret, slen;
+ char source_str[INET6_ADDRSTRLEN + 32];
+ char xpath[XPATH_MAXLEN], xpath_srcaddr[XPATH_MAXLEN + 32];
+
+ if (multihop) {
+ if (!local_address_str) {
+ vty_out(vty,
+ "%% local-address is required when using multihop\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ if (ifname) {
+ vty_out(vty,
+ "%% interface is prohibited when using multihop\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ snprintf(source_str, sizeof(source_str), "[source-addr='%s']",
+ local_address_str);
+ } else
+ source_str[0] = 0;
+
+ slen = snprintf(xpath, sizeof(xpath),
+ "/frr-bfdd:bfdd/bfd/sessions/%s%s[dest-addr='%s']",
+ multihop ? "multi-hop" : "single-hop", source_str,
+ peer_str);
+ if (ifname)
+ slen += snprintf(xpath + slen, sizeof(xpath) - slen,
+ "[interface='%s']", ifname);
+ else if (!multihop)
+ slen += snprintf(xpath + slen, sizeof(xpath) - slen,
+ "[interface='*']");
+ if (vrf)
+ snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", vrf);
+ else
+ snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']",
+ VRF_DEFAULT_NAME);
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+ if (multihop == NULL && local_address_str != NULL) {
+ snprintf(xpath_srcaddr, sizeof(xpath_srcaddr),
+ "%s/source-addr", xpath);
+ nb_cli_enqueue_change(vty, xpath_srcaddr, NB_OP_MODIFY,
+ local_address_str);
+ }
+
+ /* Apply settings immediately. */
+ ret = nb_cli_apply_changes(vty, NULL);
+ if (ret == CMD_SUCCESS)
+ VTY_PUSH_XPATH(BFD_PEER_NODE, xpath);
+
+ return ret;
+}
+
+DEFPY_YANG(
+ bfd_no_peer, bfd_no_peer_cmd,
+ "no peer <A.B.C.D|X:X::X:X> [{multihop$multihop|local-address <A.B.C.D|X:X::X:X>|interface IFNAME$ifname|vrf NAME}]",
+ NO_STR
+ PEER_STR
+ PEER_IPV4_STR
+ PEER_IPV6_STR
+ MHOP_STR
+ LOCAL_STR
+ LOCAL_IPV4_STR
+ LOCAL_IPV6_STR
+ INTERFACE_STR
+ LOCAL_INTF_STR
+ VRF_STR
+ VRF_NAME_STR)
+{
+ int slen;
+ char xpath[XPATH_MAXLEN];
+ char source_str[INET6_ADDRSTRLEN + 32];
+
+ if (multihop) {
+ if (!local_address_str) {
+ vty_out(vty,
+ "%% local-address is required when using multihop\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ if (ifname) {
+ vty_out(vty,
+ "%% interface is prohibited when using multihop\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ snprintf(source_str, sizeof(source_str), "[source-addr='%s']",
+ local_address_str);
+ } else
+ source_str[0] = 0;
+
+ slen = snprintf(xpath, sizeof(xpath),
+ "/frr-bfdd:bfdd/bfd/sessions/%s%s[dest-addr='%s']",
+ multihop ? "multi-hop" : "single-hop", source_str,
+ peer_str);
+ if (ifname)
+ slen += snprintf(xpath + slen, sizeof(xpath) - slen,
+ "[interface='%s']", ifname);
+ else if (!multihop)
+ slen += snprintf(xpath + slen, sizeof(xpath) - slen,
+ "[interface='*']");
+ if (vrf)
+ snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']", vrf);
+ else
+ snprintf(xpath + slen, sizeof(xpath) - slen, "[vrf='%s']",
+ VRF_DEFAULT_NAME);
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ /* Apply settings immediatly. */
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+static void _bfd_cli_show_peer(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults __attribute__((__unused__)),
+ bool mhop)
+{
+ const char *vrf = yang_dnode_get_string(dnode, "./vrf");
+
+ vty_out(vty, " peer %s",
+ yang_dnode_get_string(dnode, "./dest-addr"));
+
+ if (mhop)
+ vty_out(vty, " multihop");
+
+ if (yang_dnode_exists(dnode, "./source-addr"))
+ vty_out(vty, " local-address %s",
+ yang_dnode_get_string(dnode, "./source-addr"));
+
+ if (strcmp(vrf, VRF_DEFAULT_NAME))
+ vty_out(vty, " vrf %s", vrf);
+
+ if (!mhop) {
+ const char *ifname =
+ yang_dnode_get_string(dnode, "./interface");
+ if (strcmp(ifname, "*"))
+ vty_out(vty, " interface %s", ifname);
+ }
+
+ vty_out(vty, "\n");
+}
+
+void bfd_cli_show_single_hop_peer(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ _bfd_cli_show_peer(vty, dnode, show_defaults, false);
+}
+
+void bfd_cli_show_multi_hop_peer(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ _bfd_cli_show_peer(vty, dnode, show_defaults, true);
+}
+
+void bfd_cli_show_peer_end(struct vty *vty, const struct lyd_node *dnode
+ __attribute__((__unused__)))
+{
+ vty_out(vty, " exit\n");
+ vty_out(vty, " !\n");
+}
+
+DEFPY_YANG(
+ bfd_peer_shutdown, bfd_peer_shutdown_cmd,
+ "[no] shutdown",
+ NO_STR
+ "Disable BFD peer\n")
+{
+ nb_cli_enqueue_change(vty, "./administrative-down", NB_OP_MODIFY,
+ no ? "false" : "true");
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " %sshutdown\n",
+ yang_dnode_get_bool(dnode, NULL) ? "" : "no ");
+}
+
+DEFPY_YANG(
+ bfd_peer_passive, bfd_peer_passive_cmd,
+ "[no] passive-mode",
+ NO_STR
+ "Don't attempt to start sessions\n")
+{
+ nb_cli_enqueue_change(vty, "./passive-mode", NB_OP_MODIFY,
+ no ? "false" : "true");
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_passive(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " %spassive-mode\n",
+ yang_dnode_get_bool(dnode, NULL) ? "" : "no ");
+}
+
+DEFPY_YANG(
+ bfd_peer_minimum_ttl, bfd_peer_minimum_ttl_cmd,
+ "[no] minimum-ttl (1-254)$ttl",
+ NO_STR
+ "Expect packets with at least this TTL\n"
+ "Minimum TTL expected\n")
+{
+ if (bfd_cli_is_single_hop(vty)) {
+ vty_out(vty, "%% Minimum TTL is only available for multi hop sessions.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (no)
+ nb_cli_enqueue_change(vty, "./minimum-ttl", NB_OP_DESTROY,
+ NULL);
+ else
+ nb_cli_enqueue_change(vty, "./minimum-ttl", NB_OP_MODIFY,
+ ttl_str);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ no_bfd_peer_minimum_ttl, no_bfd_peer_minimum_ttl_cmd,
+ "no minimum-ttl",
+ NO_STR
+ "Expect packets with at least this TTL\n")
+{
+ nb_cli_enqueue_change(vty, "./minimum-ttl", NB_OP_DESTROY, NULL);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_minimum_ttl(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " minimum-ttl %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+DEFPY_YANG(
+ bfd_peer_mult, bfd_peer_mult_cmd,
+ "detect-multiplier (2-255)$multiplier",
+ "Configure peer detection multiplier\n"
+ "Configure peer detection multiplier value\n")
+{
+ nb_cli_enqueue_change(vty, "./detection-multiplier", NB_OP_MODIFY,
+ multiplier_str);
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_mult(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " detect-multiplier %s\n",
+ yang_dnode_get_string(dnode, NULL));
+}
+
+DEFPY_YANG(
+ bfd_peer_rx, bfd_peer_rx_cmd,
+ "receive-interval (10-60000)$interval",
+ "Configure peer receive interval\n"
+ "Configure peer receive interval value in milliseconds\n")
+{
+ char value[32];
+
+ snprintf(value, sizeof(value), "%ld", interval * 1000);
+ nb_cli_enqueue_change(vty, "./required-receive-interval", NB_OP_MODIFY,
+ value);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_rx(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ uint32_t value = yang_dnode_get_uint32(dnode, NULL);
+
+ vty_out(vty, " receive-interval %u\n", value / 1000);
+}
+
+DEFPY_YANG(
+ bfd_peer_tx, bfd_peer_tx_cmd,
+ "transmit-interval (10-60000)$interval",
+ "Configure peer transmit interval\n"
+ "Configure peer transmit interval value in milliseconds\n")
+{
+ char value[32];
+
+ snprintf(value, sizeof(value), "%ld", interval * 1000);
+ nb_cli_enqueue_change(vty, "./desired-transmission-interval",
+ NB_OP_MODIFY, value);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_tx(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ uint32_t value = yang_dnode_get_uint32(dnode, NULL);
+
+ vty_out(vty, " transmit-interval %u\n", value / 1000);
+}
+
+DEFPY_YANG(
+ bfd_peer_echo, bfd_peer_echo_cmd,
+ "[no] echo-mode",
+ NO_STR
+ "Configure echo mode\n")
+{
+ if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) {
+ vty_out(vty,
+ "%% Echo mode is only available for single hop sessions.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (!no && !bglobal.bg_use_dplane) {
+#ifdef BFD_LINUX
+ vty_out(vty,
+ "%% Echo mode works correctly for IPv4, but only works when the peer is also FRR for IPv6.\n");
+#else
+ vty_out(vty,
+ "%% Current implementation of echo mode works only when the peer is also FRR.\n");
+#endif /* BFD_LINUX */
+ }
+
+ nb_cli_enqueue_change(vty, "./echo-mode", NB_OP_MODIFY,
+ no ? "false" : "true");
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_echo(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " %secho-mode\n",
+ yang_dnode_get_bool(dnode, NULL) ? "" : "no ");
+}
+
+DEFPY_YANG(
+ bfd_peer_echo_interval, bfd_peer_echo_interval_cmd,
+ "echo-interval (10-60000)$interval",
+ "Configure peer echo intervals\n"
+ "Configure peer echo rx/tx intervals value in milliseconds\n")
+{
+ char value[32];
+
+ if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) {
+ vty_out(vty, "%% Echo mode is only available for single hop sessions.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ snprintf(value, sizeof(value), "%ld", interval * 1000);
+ nb_cli_enqueue_change(vty, "./desired-echo-transmission-interval",
+ NB_OP_MODIFY, value);
+ nb_cli_enqueue_change(vty, "./required-echo-receive-interval",
+ NB_OP_MODIFY, value);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+DEFPY_YANG(
+ bfd_peer_echo_transmit_interval, bfd_peer_echo_transmit_interval_cmd,
+ "echo transmit-interval (10-60000)$interval",
+ "Configure peer echo intervals\n"
+ "Configure desired transmit interval\n"
+ "Configure interval value in milliseconds\n")
+{
+ char value[32];
+
+ if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) {
+ vty_out(vty, "%% Echo mode is only available for single hop sessions.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ snprintf(value, sizeof(value), "%ld", interval * 1000);
+ nb_cli_enqueue_change(vty, "./desired-echo-transmission-interval",
+ NB_OP_MODIFY, value);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_desired_echo_transmission_interval(
+ struct vty *vty, const struct lyd_node *dnode, bool show_defaults)
+{
+ uint32_t value = yang_dnode_get_uint32(dnode, NULL);
+
+ vty_out(vty, " echo transmit-interval %u\n", value / 1000);
+}
+
+DEFPY_YANG(
+ bfd_peer_echo_receive_interval, bfd_peer_echo_receive_interval_cmd,
+ "echo receive-interval <disabled$disabled|(10-60000)$interval>",
+ "Configure peer echo intervals\n"
+ "Configure required receive interval\n"
+ "Disable echo packets receive\n"
+ "Configure interval value in milliseconds\n")
+{
+ char value[32];
+
+ if (!bfd_cli_is_profile(vty) && !bfd_cli_is_single_hop(vty)) {
+ vty_out(vty, "%% Echo mode is only available for single hop sessions.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (disabled)
+ snprintf(value, sizeof(value), "0");
+ else
+ snprintf(value, sizeof(value), "%ld", interval * 1000);
+
+ nb_cli_enqueue_change(vty, "./required-echo-receive-interval",
+ NB_OP_MODIFY, value);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_required_echo_receive_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ uint32_t value = yang_dnode_get_uint32(dnode, NULL);
+
+ if (value)
+ vty_out(vty, " echo receive-interval %u\n", value / 1000);
+ else
+ vty_out(vty, " echo receive-interval disabled\n");
+}
+
+/*
+ * Profile commands.
+ */
+DEFPY_YANG_NOSH(bfd_profile, bfd_profile_cmd,
+ "profile BFDPROF$name",
+ BFD_PROFILE_STR
+ BFD_PROFILE_NAME_STR)
+{
+ char xpath[XPATH_MAXLEN];
+ int rv;
+
+ snprintf(xpath, sizeof(xpath), "/frr-bfdd:bfdd/bfd/profile[name='%s']",
+ name);
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL);
+
+ /* Apply settings immediately. */
+ rv = nb_cli_apply_changes(vty, NULL);
+ if (rv == CMD_SUCCESS)
+ VTY_PUSH_XPATH(BFD_PROFILE_NODE, xpath);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY_YANG(no_bfd_profile, no_bfd_profile_cmd,
+ "no profile BFDPROF$name",
+ NO_STR
+ BFD_PROFILE_STR
+ BFD_PROFILE_NAME_STR)
+{
+ char xpath[XPATH_MAXLEN];
+
+ snprintf(xpath, sizeof(xpath), "/frr-bfdd:bfdd/bfd/profile[name='%s']",
+ name);
+
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+
+ /* Apply settings immediately. */
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_show_profile(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " profile %s\n", yang_dnode_get_string(dnode, "./name"));
+}
+
+ALIAS_YANG(bfd_peer_mult, bfd_profile_mult_cmd,
+ "detect-multiplier (2-255)$multiplier",
+ "Configure peer detection multiplier\n"
+ "Configure peer detection multiplier value\n")
+
+ALIAS_YANG(bfd_peer_tx, bfd_profile_tx_cmd,
+ "transmit-interval (10-60000)$interval",
+ "Configure peer transmit interval\n"
+ "Configure peer transmit interval value in milliseconds\n")
+
+ALIAS_YANG(bfd_peer_rx, bfd_profile_rx_cmd,
+ "receive-interval (10-60000)$interval",
+ "Configure peer receive interval\n"
+ "Configure peer receive interval value in milliseconds\n")
+
+ALIAS_YANG(bfd_peer_shutdown, bfd_profile_shutdown_cmd,
+ "[no] shutdown",
+ NO_STR
+ "Disable BFD peer\n")
+
+ALIAS_YANG(bfd_peer_passive, bfd_profile_passive_cmd,
+ "[no] passive-mode",
+ NO_STR
+ "Don't attempt to start sessions\n")
+
+ALIAS_YANG(bfd_peer_minimum_ttl, bfd_profile_minimum_ttl_cmd,
+ "[no] minimum-ttl (1-254)$ttl",
+ NO_STR
+ "Expect packets with at least this TTL\n"
+ "Minimum TTL expected\n")
+
+ALIAS_YANG(no_bfd_peer_minimum_ttl, no_bfd_profile_minimum_ttl_cmd,
+ "no minimum-ttl",
+ NO_STR
+ "Expect packets with at least this TTL\n")
+
+ALIAS_YANG(bfd_peer_echo, bfd_profile_echo_cmd,
+ "[no] echo-mode",
+ NO_STR
+ "Configure echo mode\n")
+
+ALIAS_YANG(bfd_peer_echo_interval, bfd_profile_echo_interval_cmd,
+ "echo-interval (10-60000)$interval",
+ "Configure peer echo interval\n"
+ "Configure peer echo interval value in milliseconds\n")
+
+ALIAS_YANG(
+ bfd_peer_echo_transmit_interval, bfd_profile_echo_transmit_interval_cmd,
+ "echo transmit-interval (10-60000)$interval",
+ "Configure peer echo intervals\n"
+ "Configure desired transmit interval\n"
+ "Configure interval value in milliseconds\n")
+
+ALIAS_YANG(
+ bfd_peer_echo_receive_interval, bfd_profile_echo_receive_interval_cmd,
+ "echo receive-interval <disabled$disabled|(10-60000)$interval>",
+ "Configure peer echo intervals\n"
+ "Configure required receive interval\n"
+ "Disable echo packets receive\n"
+ "Configure interval value in milliseconds\n")
+
+DEFPY_YANG(bfd_peer_profile, bfd_peer_profile_cmd,
+ "[no] profile BFDPROF$pname",
+ NO_STR
+ "Use BFD profile settings\n"
+ BFD_PROFILE_NAME_STR)
+{
+ if (no)
+ nb_cli_enqueue_change(vty, "./profile", NB_OP_DESTROY, NULL);
+ else
+ nb_cli_enqueue_change(vty, "./profile", NB_OP_MODIFY, pname);
+
+ return nb_cli_apply_changes(vty, NULL);
+}
+
+void bfd_cli_peer_profile_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults)
+{
+ vty_out(vty, " profile %s\n", yang_dnode_get_string(dnode, NULL));
+}
+
+struct cmd_node bfd_profile_node = {
+ .name = "bfd profile",
+ .node = BFD_PROFILE_NODE,
+ .parent_node = BFD_NODE,
+ .prompt = "%s(config-bfd-profile)# ",
+};
+
+static void bfd_profile_var(vector comps, struct cmd_token *token)
+{
+ extern struct bfdproflist bplist;
+ struct bfd_profile *bp;
+
+ TAILQ_FOREACH (bp, &bplist, entry) {
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION, bp->name));
+ }
+}
+
+static const struct cmd_variable_handler bfd_vars[] = {
+ {.tokenname = "BFDPROF", .completions = bfd_profile_var},
+ {.completions = NULL}
+};
+
+void
+bfdd_cli_init(void)
+{
+ install_element(CONFIG_NODE, &bfd_enter_cmd);
+ install_element(CONFIG_NODE, &bfd_config_reset_cmd);
+
+ install_element(BFD_NODE, &bfd_peer_enter_cmd);
+ install_element(BFD_NODE, &bfd_no_peer_cmd);
+
+ install_element(BFD_PEER_NODE, &bfd_peer_shutdown_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_mult_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_rx_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_tx_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_echo_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_echo_interval_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_echo_transmit_interval_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_echo_receive_interval_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_profile_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_passive_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_minimum_ttl_cmd);
+ install_element(BFD_PEER_NODE, &no_bfd_peer_minimum_ttl_cmd);
+
+ /* Profile commands. */
+ cmd_variable_handler_register(bfd_vars);
+
+ install_node(&bfd_profile_node);
+ install_default(BFD_PROFILE_NODE);
+
+ install_element(BFD_NODE, &bfd_profile_cmd);
+ install_element(BFD_NODE, &no_bfd_profile_cmd);
+
+ install_element(BFD_PROFILE_NODE, &bfd_profile_mult_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_tx_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_rx_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_shutdown_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_echo_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_echo_interval_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_echo_transmit_interval_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_echo_receive_interval_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_passive_cmd);
+ install_element(BFD_PROFILE_NODE, &bfd_profile_minimum_ttl_cmd);
+ install_element(BFD_PROFILE_NODE, &no_bfd_profile_minimum_ttl_cmd);
+}
diff --git a/bfdd/bfdd_nb.c b/bfdd/bfdd_nb.c
new file mode 100644
index 0000000..114fbc2
--- /dev/null
+++ b/bfdd/bfdd_nb.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD daemon northbound implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ */
+
+#include <zebra.h>
+
+#include "lib/log.h"
+#include "lib/northbound.h"
+
+#include "bfdd_nb.h"
+
+/* clang-format off */
+const struct frr_yang_module_info frr_bfdd_info = {
+ .name = "frr-bfdd",
+ .nodes = {
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd",
+ .cbs = {
+ .create = bfdd_bfd_create,
+ .destroy = bfdd_bfd_destroy,
+ .cli_show = bfd_cli_show_header,
+ .cli_show_end = bfd_cli_show_header_end,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile",
+ .cbs = {
+ .create = bfdd_bfd_profile_create,
+ .destroy = bfdd_bfd_profile_destroy,
+ .cli_show = bfd_cli_show_profile,
+ .cli_show_end = bfd_cli_show_peer_end,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/detection-multiplier",
+ .cbs = {
+ .modify = bfdd_bfd_profile_detection_multiplier_modify,
+ .cli_show = bfd_cli_show_mult,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/desired-transmission-interval",
+ .cbs = {
+ .modify = bfdd_bfd_profile_desired_transmission_interval_modify,
+ .cli_show = bfd_cli_show_tx,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/required-receive-interval",
+ .cbs = {
+ .modify = bfdd_bfd_profile_required_receive_interval_modify,
+ .cli_show = bfd_cli_show_rx,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/administrative-down",
+ .cbs = {
+ .modify = bfdd_bfd_profile_administrative_down_modify,
+ .cli_show = bfd_cli_show_shutdown,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/passive-mode",
+ .cbs = {
+ .modify = bfdd_bfd_profile_passive_mode_modify,
+ .cli_show = bfd_cli_show_passive,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/minimum-ttl",
+ .cbs = {
+ .modify = bfdd_bfd_profile_minimum_ttl_modify,
+ .cli_show = bfd_cli_show_minimum_ttl,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/echo-mode",
+ .cbs = {
+ .modify = bfdd_bfd_profile_echo_mode_modify,
+ .cli_show = bfd_cli_show_echo,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/desired-echo-transmission-interval",
+ .cbs = {
+ .modify = bfdd_bfd_profile_desired_echo_transmission_interval_modify,
+ .cli_show = bfd_cli_show_desired_echo_transmission_interval,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/profile/required-echo-receive-interval",
+ .cbs = {
+ .modify = bfdd_bfd_profile_required_echo_receive_interval_modify,
+ .cli_show = bfd_cli_show_required_echo_receive_interval,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop",
+ .cbs = {
+ .create = bfdd_bfd_sessions_single_hop_create,
+ .destroy = bfdd_bfd_sessions_single_hop_destroy,
+ .get_next = bfdd_bfd_sessions_single_hop_get_next,
+ .get_keys = bfdd_bfd_sessions_single_hop_get_keys,
+ .lookup_entry = bfdd_bfd_sessions_single_hop_lookup_entry,
+ .cli_show = bfd_cli_show_single_hop_peer,
+ .cli_show_end = bfd_cli_show_peer_end,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/source-addr",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_source_addr_modify,
+ .destroy = bfdd_bfd_sessions_single_hop_source_addr_destroy,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/profile",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_profile_modify,
+ .destroy = bfdd_bfd_sessions_single_hop_profile_destroy,
+ .cli_show = bfd_cli_peer_profile_show,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/detection-multiplier",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_detection_multiplier_modify,
+ .cli_show = bfd_cli_show_mult,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/desired-transmission-interval",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify,
+ .cli_show = bfd_cli_show_tx,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/required-receive-interval",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_required_receive_interval_modify,
+ .cli_show = bfd_cli_show_rx,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/administrative-down",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_administrative_down_modify,
+ .cli_show = bfd_cli_show_shutdown,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/passive-mode",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_passive_mode_modify,
+ .cli_show = bfd_cli_show_passive,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/echo-mode",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_echo_mode_modify,
+ .cli_show = bfd_cli_show_echo,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/desired-echo-transmission-interval",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify,
+ .cli_show = bfd_cli_show_desired_echo_transmission_interval,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/required-echo-receive-interval",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_required_echo_receive_interval_modify,
+ .cli_show = bfd_cli_show_required_echo_receive_interval,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-discriminator",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-state",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_state_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-diagnostic",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-multiplier",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-discriminator",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-state",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-diagnostic",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-multiplier",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-transmission-interval",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-receive-interval",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/detection-mode",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-down-time",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-up-time",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-down-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-up-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-input-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-output-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-echo-transmission-interval",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-input-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-output-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop",
+ .cbs = {
+ .create = bfdd_bfd_sessions_multi_hop_create,
+ .destroy = bfdd_bfd_sessions_multi_hop_destroy,
+ .get_next = bfdd_bfd_sessions_multi_hop_get_next,
+ .get_keys = bfdd_bfd_sessions_multi_hop_get_keys,
+ .lookup_entry = bfdd_bfd_sessions_multi_hop_lookup_entry,
+ .cli_show = bfd_cli_show_multi_hop_peer,
+ .cli_show_end = bfd_cli_show_peer_end,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/profile",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_profile_modify,
+ .destroy = bfdd_bfd_sessions_single_hop_profile_destroy,
+ .cli_show = bfd_cli_peer_profile_show,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/detection-multiplier",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_detection_multiplier_modify,
+ .cli_show = bfd_cli_show_mult,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/desired-transmission-interval",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify,
+ .cli_show = bfd_cli_show_tx,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/required-receive-interval",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_required_receive_interval_modify,
+ .cli_show = bfd_cli_show_rx,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/administrative-down",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_administrative_down_modify,
+ .cli_show = bfd_cli_show_shutdown,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/passive-mode",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_single_hop_passive_mode_modify,
+ .cli_show = bfd_cli_show_passive,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/minimum-ttl",
+ .cbs = {
+ .modify = bfdd_bfd_sessions_multi_hop_minimum_ttl_modify,
+ .cli_show = bfd_cli_show_minimum_ttl,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-discriminator",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-state",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_state_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-diagnostic",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/local-multiplier",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-discriminator",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-state",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-diagnostic",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/remote-multiplier",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-transmission-interval",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-receive-interval",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/detection-mode",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/last-down-time",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/last-up-time",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/session-down-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/session-up-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/control-packet-input-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/control-packet-output-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/negotiated-echo-transmission-interval",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/echo-packet-input-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem,
+ }
+ },
+ {
+ .xpath = "/frr-bfdd:bfdd/bfd/sessions/multi-hop/stats/echo-packet-output-count",
+ .cbs = {
+ .get_elem = bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/bfdd/bfdd_nb.h b/bfdd/bfdd_nb.h
new file mode 100644
index 0000000..b5b00b5
--- /dev/null
+++ b/bfdd/bfdd_nb.h
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD daemon northbound implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ */
+
+#ifndef _FRR_BFDD_NB_H_
+#define _FRR_BFDD_NB_H_
+
+extern const struct frr_yang_module_info frr_bfdd_info;
+
+/* Mandatory callbacks. */
+int bfdd_bfd_create(struct nb_cb_create_args *args);
+int bfdd_bfd_destroy(struct nb_cb_destroy_args *args);
+int bfdd_bfd_profile_create(struct nb_cb_create_args *args);
+int bfdd_bfd_profile_destroy(struct nb_cb_destroy_args *args);
+int bfdd_bfd_profile_detection_multiplier_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_desired_transmission_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_required_receive_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_administrative_down_modify(struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_passive_mode_modify(struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_minimum_ttl_modify(struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_echo_mode_modify(struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_desired_echo_transmission_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_profile_required_echo_receive_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_create(struct nb_cb_create_args *args);
+int bfdd_bfd_sessions_single_hop_destroy(struct nb_cb_destroy_args *args);
+const void *
+bfdd_bfd_sessions_single_hop_get_next(struct nb_cb_get_next_args *args);
+int bfdd_bfd_sessions_single_hop_get_keys(struct nb_cb_get_keys_args *args);
+const void *
+bfdd_bfd_sessions_single_hop_lookup_entry(struct nb_cb_lookup_entry_args *args);
+int bfdd_bfd_sessions_single_hop_source_addr_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_source_addr_destroy(
+ struct nb_cb_destroy_args *args);
+int bfdd_bfd_sessions_single_hop_profile_modify(struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_profile_destroy(
+ struct nb_cb_destroy_args *args);
+int bfdd_bfd_sessions_single_hop_detection_multiplier_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_required_receive_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_administrative_down_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_passive_mode_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_echo_mode_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_single_hop_required_echo_receive_interval_modify(
+ struct nb_cb_modify_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_state_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+int bfdd_bfd_sessions_multi_hop_create(struct nb_cb_create_args *args);
+int bfdd_bfd_sessions_multi_hop_destroy(struct nb_cb_destroy_args *args);
+const void *
+bfdd_bfd_sessions_multi_hop_get_next(struct nb_cb_get_next_args *args);
+int bfdd_bfd_sessions_multi_hop_get_keys(struct nb_cb_get_keys_args *args);
+const void *
+bfdd_bfd_sessions_multi_hop_lookup_entry(struct nb_cb_lookup_entry_args *args);
+int bfdd_bfd_sessions_multi_hop_detection_multiplier_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_multi_hop_desired_transmission_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_multi_hop_required_receive_interval_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_multi_hop_administrative_down_modify(
+ struct nb_cb_modify_args *args);
+int bfdd_bfd_sessions_multi_hop_minimum_ttl_modify(
+ struct nb_cb_modify_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_local_discriminator_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_local_state_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_local_diagnostic_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_local_multiplier_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_remote_discriminator_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_remote_state_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_remote_diagnostic_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_remote_multiplier_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_negotiated_transmission_interval_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_negotiated_receive_interval_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_detection_mode_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_last_down_time_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_last_up_time_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_session_down_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *bfdd_bfd_sessions_multi_hop_stats_session_up_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_control_packet_input_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_control_packet_output_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_negotiated_echo_transmission_interval_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_echo_packet_input_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+struct yang_data *
+bfdd_bfd_sessions_multi_hop_stats_echo_packet_output_count_get_elem(
+ struct nb_cb_get_elem_args *args);
+
+/* Optional 'cli_show' callbacks. */
+void bfd_cli_show_header(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_header_end(struct vty *vty, const struct lyd_node *dnode);
+void bfd_cli_show_single_hop_peer(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_multi_hop_peer(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_peer_end(struct vty *vty, const struct lyd_node *dnode);
+void bfd_cli_show_mult(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_tx(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_rx(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_shutdown(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_echo(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_desired_echo_transmission_interval(
+ struct vty *vty, const struct lyd_node *dnode, bool show_defaults);
+void bfd_cli_show_required_echo_receive_interval(struct vty *vty,
+ const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_profile(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_peer_profile_show(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_passive(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+void bfd_cli_show_minimum_ttl(struct vty *vty, const struct lyd_node *dnode,
+ bool show_defaults);
+
+#endif /* _FRR_BFDD_NB_H_ */
diff --git a/bfdd/bfdd_nb_config.c b/bfdd/bfdd_nb_config.c
new file mode 100644
index 0000000..8cf2f0a
--- /dev/null
+++ b/bfdd/bfdd_nb_config.c
@@ -0,0 +1,847 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD daemon northbound implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ */
+
+#include <zebra.h>
+
+#include "lib/log.h"
+#include "lib/northbound.h"
+
+#include "bfd.h"
+#include "bfdd_nb.h"
+
+/*
+ * Helpers.
+ */
+static void bfd_session_get_key(bool mhop, const struct lyd_node *dnode,
+ struct bfd_key *bk)
+{
+ const char *ifname = NULL, *vrfname = NULL;
+ struct sockaddr_any psa, lsa;
+
+ /* Required destination parameter. */
+ strtosa(yang_dnode_get_string(dnode, "./dest-addr"), &psa);
+
+ /* Get optional source address. */
+ memset(&lsa, 0, sizeof(lsa));
+ if (yang_dnode_exists(dnode, "./source-addr"))
+ strtosa(yang_dnode_get_string(dnode, "./source-addr"), &lsa);
+
+ vrfname = yang_dnode_get_string(dnode, "./vrf");
+
+ if (!mhop) {
+ ifname = yang_dnode_get_string(dnode, "./interface");
+ if (strcmp(ifname, "*") == 0)
+ ifname = NULL;
+ }
+
+ /* Generate the corresponding key. */
+ gen_bfd_key(bk, &psa, &lsa, mhop, ifname, vrfname);
+}
+
+struct session_iter {
+ int count;
+ bool wildcard;
+};
+
+static int session_iter_cb(const struct lyd_node *dnode, void *arg)
+{
+ struct session_iter *iter = arg;
+ const char *ifname;
+
+ ifname = yang_dnode_get_string(dnode, "./interface");
+
+ if (strmatch(ifname, "*"))
+ iter->wildcard = true;
+
+ iter->count++;
+
+ return YANG_ITER_CONTINUE;
+}
+
+static int bfd_session_create(struct nb_cb_create_args *args, bool mhop)
+{
+ const struct lyd_node *sess_dnode;
+ struct session_iter iter;
+ struct bfd_session *bs;
+ const char *dest;
+ const char *ifname;
+ const char *vrfname;
+ struct bfd_key bk;
+ struct prefix p;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ yang_dnode_get_prefix(&p, args->dnode, "./dest-addr");
+
+ if (mhop) {
+ /*
+ * Do not allow IPv6 link-local address for multihop.
+ */
+ if (p.family == AF_INET6
+ && IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "Cannot use link-local address for multihop sessions");
+ return NB_ERR_VALIDATION;
+ }
+ return NB_OK;
+ }
+
+ /*
+ * When `dest-addr` is IPv6 and link-local we must
+ * require interface name, otherwise we can't figure
+ * which interface to use to send the packets.
+ */
+ ifname = yang_dnode_get_string(args->dnode, "./interface");
+
+ if (p.family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&p.u.prefix6)
+ && strcmp(ifname, "*") == 0) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "When using link-local you must specify an interface");
+ return NB_ERR_VALIDATION;
+ }
+
+ iter.count = 0;
+ iter.wildcard = false;
+
+ sess_dnode = yang_dnode_get_parent(args->dnode, "sessions");
+
+ dest = yang_dnode_get_string(args->dnode, "./dest-addr");
+ vrfname = yang_dnode_get_string(args->dnode, "./vrf");
+
+ yang_dnode_iterate(session_iter_cb, &iter, sess_dnode,
+ "./single-hop[dest-addr='%s'][vrf='%s']",
+ dest, vrfname);
+
+ if (iter.wildcard && iter.count > 1) {
+ snprintf(
+ args->errmsg, args->errmsg_len,
+ "It is not allowed to configure the same peer with and without ifname");
+ return NB_ERR_VALIDATION;
+ }
+ break;
+
+ case NB_EV_PREPARE:
+ bfd_session_get_key(mhop, args->dnode, &bk);
+ bs = bfd_key_lookup(bk);
+
+ /* This session was already configured by another daemon. */
+ if (bs != NULL) {
+ /* Now it is configured also by CLI. */
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG);
+ bs->refcount++;
+
+ args->resource->ptr = bs;
+ break;
+ }
+
+ bs = bfd_session_new();
+
+ /* Fill the session key. */
+ bfd_session_get_key(mhop, args->dnode, &bs->key);
+
+ /* Set configuration flags. */
+ bs->refcount = 1;
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG);
+ if (mhop)
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_MH);
+ if (bs->key.family == AF_INET6)
+ SET_FLAG(bs->flags, BFD_SESS_FLAG_IPV6);
+
+ args->resource->ptr = bs;
+ break;
+
+ case NB_EV_APPLY:
+ bs = args->resource->ptr;
+
+ /* Only attempt to registrate if freshly allocated. */
+ if (bs->discrs.my_discr == 0 && bs_registrate(bs) == NULL)
+ return NB_ERR_RESOURCE;
+
+ nb_running_set_entry(args->dnode, bs);
+ break;
+
+ case NB_EV_ABORT:
+ bs = args->resource->ptr;
+ if (bs->refcount <= 1)
+ bfd_session_free(bs);
+ break;
+ }
+
+ return NB_OK;
+}
+
+static int bfd_session_destroy(enum nb_event event,
+ const struct lyd_node *dnode, bool mhop)
+{
+ struct bfd_session *bs;
+ struct bfd_key bk;
+
+ switch (event) {
+ case NB_EV_VALIDATE:
+ bfd_session_get_key(mhop, dnode, &bk);
+ if (bfd_key_lookup(bk) == NULL)
+ return NB_ERR_INCONSISTENCY;
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bs = nb_running_unset_entry(dnode);
+ /* CLI is not using this session anymore. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) == 0)
+ break;
+
+ UNSET_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG);
+ bs->refcount--;
+ /* There are still daemons using it. */
+ if (bs->refcount > 0)
+ break;
+
+ bfd_session_free(bs);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd
+ */
+int bfdd_bfd_create(struct nb_cb_create_args *args)
+{
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ /*
+ * Set any non-NULL value to be able to call
+ * nb_running_unset_entry in bfdd_bfd_destroy.
+ */
+ nb_running_set_entry(args->dnode, (void *)0x1);
+
+ return NB_OK;
+}
+
+int bfdd_bfd_destroy(struct nb_cb_destroy_args *args)
+{
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ /* NOTHING */
+ return NB_OK;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ return NB_OK;
+
+ case NB_EV_APPLY:
+ /*
+ * We need to call this to unset pointers from
+ * the child nodes - sessions and profiles.
+ */
+ nb_running_unset_entry(args->dnode);
+
+ bfd_sessions_remove_manual();
+ bfd_profiles_remove();
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ return NB_OK;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile
+ */
+int bfdd_bfd_profile_create(struct nb_cb_create_args *args)
+{
+ struct bfd_profile *bp;
+ const char *name;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ name = yang_dnode_get_string(args->dnode, "./name");
+ bp = bfd_profile_new(name);
+ nb_running_set_entry(args->dnode, bp);
+
+ return NB_OK;
+}
+
+int bfdd_bfd_profile_destroy(struct nb_cb_destroy_args *args)
+{
+ struct bfd_profile *bp;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ bp = nb_running_unset_entry(args->dnode);
+ bfd_profile_free(bp);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/detection-multiplier
+ */
+int bfdd_bfd_profile_detection_multiplier_modify(struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->detection_multiplier = yang_dnode_get_uint8(args->dnode, NULL);
+ bfd_profile_update(bp);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/desired-transmission-interval
+ */
+int bfdd_bfd_profile_desired_transmission_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->min_tx = yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_profile_update(bp);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/required-receive-interval
+ */
+int bfdd_bfd_profile_required_receive_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->min_rx = yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_profile_update(bp);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/administrative-down
+ */
+int bfdd_bfd_profile_administrative_down_modify(struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->admin_shutdown = yang_dnode_get_bool(args->dnode, NULL);
+ bfd_profile_update(bp);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/passive-mode
+ */
+int bfdd_bfd_profile_passive_mode_modify(struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->passive = yang_dnode_get_bool(args->dnode, NULL);
+ bfd_profile_update(bp);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/minimum-ttl
+ */
+int bfdd_bfd_profile_minimum_ttl_modify(struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->minimum_ttl = yang_dnode_get_uint8(args->dnode, NULL);
+ bfd_profile_update(bp);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/echo-mode
+ */
+int bfdd_bfd_profile_echo_mode_modify(struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+ bool echo;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ echo = yang_dnode_get_bool(args->dnode, NULL);
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ if (bp->echo_mode == echo)
+ return NB_OK;
+
+ bp->echo_mode = echo;
+ bfd_profile_update(bp);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/desired-echo-transmission-interval
+ */
+int bfdd_bfd_profile_desired_echo_transmission_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->min_echo_tx = yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_profile_update(bp);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/profile/required-echo-receive-interval
+ */
+int bfdd_bfd_profile_required_echo_receive_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_profile *bp;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bp = nb_running_get_entry(args->dnode, NULL, true);
+ bp->min_echo_rx = yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_profile_update(bp);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop
+ */
+int bfdd_bfd_sessions_single_hop_create(struct nb_cb_create_args *args)
+{
+ return bfd_session_create(args, false);
+}
+
+int bfdd_bfd_sessions_single_hop_destroy(struct nb_cb_destroy_args *args)
+{
+ return bfd_session_destroy(args->event, args->dnode, false);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/source-addr
+ */
+int bfdd_bfd_sessions_single_hop_source_addr_modify(
+ struct nb_cb_modify_args *args)
+{
+ return NB_OK;
+}
+
+int bfdd_bfd_sessions_single_hop_source_addr_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/profile
+ */
+int bfdd_bfd_sessions_single_hop_profile_modify(struct nb_cb_modify_args *args)
+{
+ struct bfd_session *bs;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bfd_profile_apply(yang_dnode_get_string(args->dnode, NULL), bs);
+
+ return NB_OK;
+}
+
+int bfdd_bfd_sessions_single_hop_profile_destroy(
+ struct nb_cb_destroy_args *args)
+{
+ struct bfd_session *bs;
+
+ if (args->event != NB_EV_APPLY)
+ return NB_OK;
+
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bfd_profile_remove(bs);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/detection-multiplier
+ */
+int bfdd_bfd_sessions_single_hop_detection_multiplier_modify(
+ struct nb_cb_modify_args *args)
+{
+ uint8_t detection_multiplier = yang_dnode_get_uint8(args->dnode, NULL);
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.detection_multiplier = detection_multiplier;
+ bfd_session_apply(bs);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/desired-transmission-interval
+ */
+int bfdd_bfd_sessions_single_hop_desired_transmission_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.min_tx =
+ yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_session_apply(bs);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/required-receive-interval
+ */
+int bfdd_bfd_sessions_single_hop_required_receive_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.min_rx =
+ yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_session_apply(bs);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/administrative-down
+ */
+int bfdd_bfd_sessions_single_hop_administrative_down_modify(
+ struct nb_cb_modify_args *args)
+{
+ bool shutdown = yang_dnode_get_bool(args->dnode, NULL);
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ return NB_OK;
+
+ case NB_EV_APPLY:
+ break;
+
+ case NB_EV_ABORT:
+ return NB_OK;
+ }
+
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.admin_shutdown = shutdown;
+ bfd_session_apply(bs);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/passive-mode
+ */
+int bfdd_bfd_sessions_single_hop_passive_mode_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_session *bs;
+ bool passive;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ return NB_OK;
+
+ case NB_EV_APPLY:
+ break;
+
+ case NB_EV_ABORT:
+ return NB_OK;
+ }
+
+ passive = yang_dnode_get_bool(args->dnode, NULL);
+
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.passive = passive;
+ bfd_session_apply(bs);
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/echo-mode
+ */
+int bfdd_bfd_sessions_single_hop_echo_mode_modify(
+ struct nb_cb_modify_args *args)
+{
+ bool echo = yang_dnode_get_bool(args->dnode, NULL);
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ return NB_OK;
+
+ case NB_EV_APPLY:
+ break;
+
+ case NB_EV_ABORT:
+ return NB_OK;
+ }
+
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.echo_mode = echo;
+ bfd_session_apply(bs);
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-bfdd:bfdd/bfd/sessions/single-hop/desired-echo-transmission-interval
+ */
+int bfdd_bfd_sessions_single_hop_desired_echo_transmission_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.min_echo_tx =
+ yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_session_apply(bs);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath:
+ * /frr-bfdd:bfdd/bfd/sessions/single-hop/required-echo-receive-interval
+ */
+int bfdd_bfd_sessions_single_hop_required_echo_receive_interval_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ break;
+
+ case NB_EV_PREPARE:
+ /* NOTHING */
+ break;
+
+ case NB_EV_APPLY:
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.min_echo_rx =
+ yang_dnode_get_uint32(args->dnode, NULL);
+ bfd_session_apply(bs);
+ break;
+
+ case NB_EV_ABORT:
+ /* NOTHING */
+ break;
+ }
+
+ return NB_OK;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/multi-hop
+ */
+int bfdd_bfd_sessions_multi_hop_create(struct nb_cb_create_args *args)
+{
+ return bfd_session_create(args, true);
+}
+
+int bfdd_bfd_sessions_multi_hop_destroy(struct nb_cb_destroy_args *args)
+{
+ return bfd_session_destroy(args->event, args->dnode, true);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/multi-hop/minimum-ttl
+ */
+int bfdd_bfd_sessions_multi_hop_minimum_ttl_modify(
+ struct nb_cb_modify_args *args)
+{
+ struct bfd_session *bs;
+
+ switch (args->event) {
+ case NB_EV_VALIDATE:
+ case NB_EV_PREPARE:
+ return NB_OK;
+
+ case NB_EV_APPLY:
+ break;
+
+ case NB_EV_ABORT:
+ return NB_OK;
+ }
+
+ bs = nb_running_get_entry(args->dnode, NULL, true);
+ bs->peer_profile.minimum_ttl = yang_dnode_get_uint8(args->dnode, NULL);
+ bfd_session_apply(bs);
+
+ return NB_OK;
+}
diff --git a/bfdd/bfdd_nb_state.c b/bfdd/bfdd_nb_state.c
new file mode 100644
index 0000000..12acda8
--- /dev/null
+++ b/bfdd/bfdd_nb_state.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD daemon northbound implementation.
+ *
+ * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ */
+
+#include <zebra.h>
+
+#include "lib/log.h"
+#include "lib/northbound.h"
+
+#include "bfd.h"
+#include "bfdd_nb.h"
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop
+ */
+const void *
+bfdd_bfd_sessions_single_hop_get_next(struct nb_cb_get_next_args *args)
+{
+ return bfd_session_next(args->list_entry, false);
+}
+
+int bfdd_bfd_sessions_single_hop_get_keys(struct nb_cb_get_keys_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+ char dstbuf[INET6_ADDRSTRLEN];
+
+ inet_ntop(bs->key.family, &bs->key.peer, dstbuf, sizeof(dstbuf));
+
+ args->keys->num = 3;
+ strlcpy(args->keys->key[0], dstbuf, sizeof(args->keys->key[0]));
+ strlcpy(args->keys->key[1], bs->key.ifname, sizeof(args->keys->key[1]));
+ strlcpy(args->keys->key[2], bs->key.vrfname,
+ sizeof(args->keys->key[2]));
+
+ return NB_OK;
+}
+
+const void *
+bfdd_bfd_sessions_single_hop_lookup_entry(struct nb_cb_lookup_entry_args *args)
+{
+ const char *dest_addr = args->keys->key[0];
+ const char *ifname = args->keys->key[1];
+ const char *vrf = args->keys->key[2];
+ struct sockaddr_any psa, lsa;
+ struct bfd_key bk;
+
+ strtosa(dest_addr, &psa);
+ memset(&lsa, 0, sizeof(lsa));
+ gen_bfd_key(&bk, &psa, &lsa, false, ifname, vrf);
+
+ return bfd_key_lookup(bk);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-discriminator
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_local_discriminator_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath, bs->discrs.my_discr);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-state
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_state_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_enum(args->xpath, bs->ses_state);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-diagnostic
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_diagnostic_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_enum(args->xpath, bs->local_diag);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/local-multiplier
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_local_multiplier_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_int8(args->xpath, bs->detect_mult);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-discriminator
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_remote_discriminator_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ if (bs->discrs.remote_discr == 0)
+ return NULL;
+
+ return yang_data_new_uint32(args->xpath, bs->discrs.remote_discr);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-state
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_state_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_enum(args->xpath, bs->ses_state);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-diagnostic
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_diagnostic_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_enum(args->xpath, bs->remote_diag);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/remote-multiplier
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_remote_multiplier_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_int8(args->xpath, bs->remote_detect_mult);
+}
+
+/*
+ * XPath:
+ * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-transmission-interval
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_negotiated_transmission_interval_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath,
+ bs->remote_timers.desired_min_tx);
+}
+
+/*
+ * XPath:
+ * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-receive-interval
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_negotiated_receive_interval_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath,
+ bs->remote_timers.required_min_rx);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/detection-mode
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_detection_mode_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+ int detection_mode;
+
+ /*
+ * Detection mode:
+ * 1. Async with echo
+ * 2. Async without echo
+ * 3. Demand with echo
+ * 4. Demand without echo
+ *
+ * TODO: support demand mode.
+ */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ detection_mode = 1;
+ else
+ detection_mode = 2;
+
+ return yang_data_new_enum(args->xpath, detection_mode);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-down-time
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_down_time_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ /*
+ * TODO: implement me.
+ *
+ * No yang support for time elements yet.
+ */
+ return NULL;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/last-up-time
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_last_up_time_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ /*
+ * TODO: implement me.
+ *
+ * No yang support for time elements yet.
+ */
+ return NULL;
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-down-count
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_session_down_count_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint64(args->xpath, bs->stats.session_down);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/session-up-count
+ */
+struct yang_data *bfdd_bfd_sessions_single_hop_stats_session_up_count_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint64(args->xpath, bs->stats.session_up);
+}
+
+/*
+ * XPath:
+ * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-input-count
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_control_packet_input_count_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint64(args->xpath, bs->stats.rx_ctrl_pkt);
+}
+
+/*
+ * XPath:
+ * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/control-packet-output-count
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_control_packet_output_count_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint64(args->xpath, bs->stats.tx_ctrl_pkt);
+}
+
+/*
+ * XPath:
+ * /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/negotiated-echo-transmission-interval
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_negotiated_echo_transmission_interval_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint32(args->xpath,
+ bs->remote_timers.required_min_echo);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-input-count
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_echo_packet_input_count_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint64(args->xpath, bs->stats.rx_echo_pkt);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/single-hop/stats/echo-packet-output-count
+ */
+struct yang_data *
+bfdd_bfd_sessions_single_hop_stats_echo_packet_output_count_get_elem(
+ struct nb_cb_get_elem_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+
+ return yang_data_new_uint64(args->xpath, bs->stats.tx_echo_pkt);
+}
+
+/*
+ * XPath: /frr-bfdd:bfdd/bfd/sessions/multi-hop
+ */
+const void *
+bfdd_bfd_sessions_multi_hop_get_next(struct nb_cb_get_next_args *args)
+{
+ return bfd_session_next(args->list_entry, true);
+}
+
+int bfdd_bfd_sessions_multi_hop_get_keys(struct nb_cb_get_keys_args *args)
+{
+ const struct bfd_session *bs = args->list_entry;
+ char dstbuf[INET6_ADDRSTRLEN], srcbuf[INET6_ADDRSTRLEN];
+
+ inet_ntop(bs->key.family, &bs->key.peer, dstbuf, sizeof(dstbuf));
+ inet_ntop(bs->key.family, &bs->key.local, srcbuf, sizeof(srcbuf));
+
+ args->keys->num = 4;
+ strlcpy(args->keys->key[0], srcbuf, sizeof(args->keys->key[0]));
+ strlcpy(args->keys->key[1], dstbuf, sizeof(args->keys->key[1]));
+ strlcpy(args->keys->key[2], bs->key.vrfname,
+ sizeof(args->keys->key[2]));
+
+ return NB_OK;
+}
+
+const void *
+bfdd_bfd_sessions_multi_hop_lookup_entry(struct nb_cb_lookup_entry_args *args)
+{
+ const char *source_addr = args->keys->key[0];
+ const char *dest_addr = args->keys->key[1];
+ const char *vrf = args->keys->key[2];
+ struct sockaddr_any psa, lsa;
+ struct bfd_key bk;
+
+ strtosa(dest_addr, &psa);
+ strtosa(source_addr, &lsa);
+ gen_bfd_key(&bk, &psa, &lsa, true, NULL, vrf);
+
+ return bfd_key_lookup(bk);
+}
diff --git a/bfdd/bfdd_vty.c b/bfdd/bfdd_vty.c
new file mode 100644
index 0000000..496d501
--- /dev/null
+++ b/bfdd/bfdd_vty.c
@@ -0,0 +1,1051 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD daemon code
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ */
+
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/json.h"
+#include "lib/log.h"
+#include "lib/northbound_cli.h"
+#include "lib/vty.h"
+
+#include "bfd.h"
+
+#include "bfdd/bfdd_vty_clippy.c"
+
+/*
+ * Commands help string definitions.
+ */
+#define PEER_IPV4_STR "IPv4 peer address\n"
+#define PEER_IPV6_STR "IPv6 peer address\n"
+#define MHOP_STR "Configure multihop\n"
+#define LOCAL_STR "Configure local address\n"
+#define LOCAL_IPV4_STR "IPv4 local address\n"
+#define LOCAL_IPV6_STR "IPv6 local address\n"
+#define LOCAL_INTF_STR "Configure local interface name to use\n"
+
+/*
+ * Prototypes
+ */
+static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop,
+ const struct sockaddr_any *peer,
+ const struct sockaddr_any *local,
+ const char *ifname, const char *vrfname,
+ char *ebuf, size_t ebuflen);
+
+static void _display_peer_header(struct vty *vty, struct bfd_session *bs);
+static struct json_object *__display_peer_json(struct bfd_session *bs);
+static struct json_object *_peer_json_header(struct bfd_session *bs);
+static void _display_peer_json(struct vty *vty, struct bfd_session *bs);
+static void _display_peer(struct vty *vty, struct bfd_session *bs);
+static void _display_all_peers(struct vty *vty, char *vrfname, bool use_json);
+static void _display_peer_iter(struct hash_bucket *hb, void *arg);
+static void _display_peer_json_iter(struct hash_bucket *hb, void *arg);
+static void _display_peer_counter(struct vty *vty, struct bfd_session *bs);
+static struct json_object *__display_peer_counters_json(struct bfd_session *bs);
+static void _display_peer_counters_json(struct vty *vty, struct bfd_session *bs);
+static void _display_peer_counter_iter(struct hash_bucket *hb, void *arg);
+static void _display_peer_counter_json_iter(struct hash_bucket *hb, void *arg);
+static void _display_peers_counter(struct vty *vty, char *vrfname, bool use_json);
+static void _display_rtt(uint32_t *min, uint32_t *avg, uint32_t *max,
+ struct bfd_session *bs);
+
+static struct bfd_session *
+_find_peer_or_error(struct vty *vty, int argc, struct cmd_token **argv,
+ const char *label, const char *peer_str,
+ const char *local_str, const char *ifname,
+ const char *vrfname);
+
+
+/*
+ * Show commands helper functions
+ */
+static void _display_peer_header(struct vty *vty, struct bfd_session *bs)
+{
+ char addr_buf[INET6_ADDRSTRLEN];
+
+ vty_out(vty, "\tpeer %s",
+ inet_ntop(bs->key.family, &bs->key.peer, addr_buf,
+ sizeof(addr_buf)));
+
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH))
+ vty_out(vty, " multihop");
+
+ if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local)))
+ vty_out(vty, " local-address %s",
+ inet_ntop(bs->key.family, &bs->key.local, addr_buf,
+ sizeof(addr_buf)));
+
+ if (bs->key.vrfname[0])
+ vty_out(vty, " vrf %s", bs->key.vrfname);
+ if (bs->key.ifname[0])
+ vty_out(vty, " interface %s", bs->key.ifname);
+ vty_out(vty, "\n");
+
+ if (bs->pl)
+ vty_out(vty, "\t\tlabel: %s\n", bs->pl->pl_label);
+}
+
+static void _display_peer(struct vty *vty, struct bfd_session *bs)
+{
+ char buf[256];
+ time_t now;
+ uint32_t min = 0;
+ uint32_t avg = 0;
+ uint32_t max = 0;
+
+ _display_peer_header(vty, bs);
+
+ vty_out(vty, "\t\tID: %u\n", bs->discrs.my_discr);
+ vty_out(vty, "\t\tRemote ID: %u\n", bs->discrs.remote_discr);
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE))
+ vty_out(vty, "\t\tPassive mode\n");
+ else
+ vty_out(vty, "\t\tActive mode\n");
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH))
+ vty_out(vty, "\t\tMinimum TTL: %d\n", bs->mh_ttl);
+
+ vty_out(vty, "\t\tStatus: ");
+ switch (bs->ses_state) {
+ case PTM_BFD_ADM_DOWN:
+ vty_out(vty, "shutdown\n");
+ break;
+ case PTM_BFD_DOWN:
+ vty_out(vty, "down\n");
+
+ now = monotime(NULL);
+ integer2timestr(now - bs->downtime.tv_sec, buf, sizeof(buf));
+ vty_out(vty, "\t\tDowntime: %s\n", buf);
+ break;
+ case PTM_BFD_INIT:
+ vty_out(vty, "init\n");
+ break;
+ case PTM_BFD_UP:
+ vty_out(vty, "up\n");
+
+ now = monotime(NULL);
+ integer2timestr(now - bs->uptime.tv_sec, buf, sizeof(buf));
+ vty_out(vty, "\t\tUptime: %s\n", buf);
+ break;
+
+ default:
+ vty_out(vty, "unknown\n");
+ break;
+ }
+
+ vty_out(vty, "\t\tDiagnostics: %s\n", diag2str(bs->local_diag));
+ vty_out(vty, "\t\tRemote diagnostics: %s\n", diag2str(bs->remote_diag));
+ vty_out(vty, "\t\tPeer Type: %s\n",
+ CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG) ? "configured" : "dynamic");
+ _display_rtt(&min, &avg, &max, bs);
+ vty_out(vty, "\t\tRTT min/avg/max: %u/%u/%u usec\n", min, avg, max);
+
+ vty_out(vty, "\t\tLocal timers:\n");
+ vty_out(vty, "\t\t\tDetect-multiplier: %u\n",
+ bs->detect_mult);
+ vty_out(vty, "\t\t\tReceive interval: %ums\n",
+ bs->timers.required_min_rx / 1000);
+ vty_out(vty, "\t\t\tTransmission interval: %ums\n",
+ bs->timers.desired_min_tx / 1000);
+ if (bs->timers.required_min_echo_rx != 0)
+ vty_out(vty, "\t\t\tEcho receive interval: %ums\n",
+ bs->timers.required_min_echo_rx / 1000);
+ else
+ vty_out(vty, "\t\t\tEcho receive interval: disabled\n");
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ vty_out(vty, "\t\t\tEcho transmission interval: %ums\n",
+ bs->timers.desired_min_echo_tx / 1000);
+ else
+ vty_out(vty, "\t\t\tEcho transmission interval: disabled\n");
+
+ vty_out(vty, "\t\tRemote timers:\n");
+ vty_out(vty, "\t\t\tDetect-multiplier: %u\n",
+ bs->remote_detect_mult);
+ vty_out(vty, "\t\t\tReceive interval: %ums\n",
+ bs->remote_timers.required_min_rx / 1000);
+ vty_out(vty, "\t\t\tTransmission interval: %ums\n",
+ bs->remote_timers.desired_min_tx / 1000);
+ if (bs->remote_timers.required_min_echo != 0)
+ vty_out(vty, "\t\t\tEcho receive interval: %ums\n",
+ bs->remote_timers.required_min_echo / 1000);
+ else
+ vty_out(vty, "\t\t\tEcho receive interval: disabled\n");
+
+ vty_out(vty, "\n");
+}
+
+static struct json_object *_peer_json_header(struct bfd_session *bs)
+{
+ struct json_object *jo = json_object_new_object();
+ char addr_buf[INET6_ADDRSTRLEN];
+
+ if (bs->key.mhop)
+ json_object_boolean_true_add(jo, "multihop");
+ else
+ json_object_boolean_false_add(jo, "multihop");
+
+ json_object_string_add(jo, "peer",
+ inet_ntop(bs->key.family, &bs->key.peer,
+ addr_buf, sizeof(addr_buf)));
+ if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local)))
+ json_object_string_add(jo, "local",
+ inet_ntop(bs->key.family, &bs->key.local,
+ addr_buf, sizeof(addr_buf)));
+
+ if (bs->key.vrfname[0])
+ json_object_string_add(jo, "vrf", bs->key.vrfname);
+ if (bs->key.ifname[0])
+ json_object_string_add(jo, "interface", bs->key.ifname);
+
+ if (bs->pl)
+ json_object_string_add(jo, "label", bs->pl->pl_label);
+
+ return jo;
+}
+
+static struct json_object *__display_peer_json(struct bfd_session *bs)
+{
+ struct json_object *jo = _peer_json_header(bs);
+ uint32_t min = 0;
+ uint32_t avg = 0;
+ uint32_t max = 0;
+
+ if (bs->key.ifname[0])
+ json_object_string_add(jo, "interface", bs->key.ifname);
+ json_object_int_add(jo, "id", bs->discrs.my_discr);
+ json_object_int_add(jo, "remote-id", bs->discrs.remote_discr);
+ json_object_boolean_add(jo, "passive-mode",
+ CHECK_FLAG(bs->flags, BFD_SESS_FLAG_PASSIVE));
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH))
+ json_object_int_add(jo, "minimum-ttl", bs->mh_ttl);
+
+ switch (bs->ses_state) {
+ case PTM_BFD_ADM_DOWN:
+ json_object_string_add(jo, "status", "shutdown");
+ break;
+ case PTM_BFD_DOWN:
+ json_object_string_add(jo, "status", "down");
+ json_object_int_add(jo, "downtime",
+ monotime(NULL) - bs->downtime.tv_sec);
+ break;
+ case PTM_BFD_INIT:
+ json_object_string_add(jo, "status", "init");
+ break;
+ case PTM_BFD_UP:
+ json_object_string_add(jo, "status", "up");
+ json_object_int_add(jo, "uptime",
+ monotime(NULL) - bs->uptime.tv_sec);
+ break;
+
+ default:
+ json_object_string_add(jo, "status", "unknown");
+ break;
+ }
+
+ json_object_string_add(jo, "diagnostic", diag2str(bs->local_diag));
+ json_object_string_add(jo, "remote-diagnostic",
+ diag2str(bs->remote_diag));
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG))
+ json_object_string_add(jo, "type", "configured");
+ else
+ json_object_string_add(jo, "type", "dynamic");
+
+ json_object_int_add(jo, "receive-interval",
+ bs->timers.required_min_rx / 1000);
+ json_object_int_add(jo, "transmit-interval",
+ bs->timers.desired_min_tx / 1000);
+ json_object_int_add(jo, "echo-receive-interval",
+ bs->timers.required_min_echo_rx / 1000);
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ json_object_int_add(jo, "echo-transmit-interval",
+ bs->timers.desired_min_echo_tx / 1000);
+ else
+ json_object_int_add(jo, "echo-transmit-interval", 0);
+
+ json_object_int_add(jo, "detect-multiplier", bs->detect_mult);
+
+ json_object_int_add(jo, "remote-receive-interval",
+ bs->remote_timers.required_min_rx / 1000);
+ json_object_int_add(jo, "remote-transmit-interval",
+ bs->remote_timers.desired_min_tx / 1000);
+ json_object_int_add(jo, "remote-echo-receive-interval",
+ bs->remote_timers.required_min_echo / 1000);
+ json_object_int_add(jo, "remote-detect-multiplier",
+ bs->remote_detect_mult);
+
+ _display_rtt(&min, &avg, &max, bs);
+ json_object_int_add(jo, "rtt-min", min);
+ json_object_int_add(jo, "rtt-avg", avg);
+ json_object_int_add(jo, "rtt-max", max);
+
+ return jo;
+}
+
+static void _display_peer_json(struct vty *vty, struct bfd_session *bs)
+{
+ struct json_object *jo = __display_peer_json(bs);
+
+ vty_json(vty, jo);
+}
+
+struct bfd_vrf_tuple {
+ const char *vrfname;
+ struct vty *vty;
+ struct json_object *jo;
+};
+
+static void _display_peer_iter(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_vrf_tuple *bvt = (struct bfd_vrf_tuple *)arg;
+ struct vty *vty;
+ struct bfd_session *bs = hb->data;
+
+ if (!bvt)
+ return;
+ vty = bvt->vty;
+
+ if (bvt->vrfname) {
+ if (!bs->key.vrfname[0] ||
+ !strmatch(bs->key.vrfname, bvt->vrfname))
+ return;
+ }
+ _display_peer(vty, bs);
+}
+
+static void _display_peer_json_iter(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_vrf_tuple *bvt = (struct bfd_vrf_tuple *)arg;
+ struct json_object *jo, *jon = NULL;
+ struct bfd_session *bs = hb->data;
+
+ if (!bvt)
+ return;
+ jo = bvt->jo;
+
+ if (bvt->vrfname) {
+ if (!bs->key.vrfname[0] ||
+ !strmatch(bs->key.vrfname, bvt->vrfname))
+ return;
+ }
+
+ jon = __display_peer_json(bs);
+ if (jon == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+
+ json_object_array_add(jo, jon);
+}
+
+static void _display_all_peers(struct vty *vty, char *vrfname, bool use_json)
+{
+ struct json_object *jo;
+ struct bfd_vrf_tuple bvt = {0};
+
+ bvt.vrfname = vrfname;
+
+ if (!use_json) {
+ bvt.vty = vty;
+ vty_out(vty, "BFD Peers:\n");
+ bfd_id_iterate(_display_peer_iter, &bvt);
+ return;
+ }
+
+ jo = json_object_new_array();
+ bvt.jo = jo;
+ bfd_id_iterate(_display_peer_json_iter, &bvt);
+
+ vty_json(vty, jo);
+}
+
+static void _display_peer_counter(struct vty *vty, struct bfd_session *bs)
+{
+ _display_peer_header(vty, bs);
+
+ /* Ask data plane for updated counters. */
+ if (bfd_dplane_update_session_counters(bs) == -1)
+ zlog_debug("%s: failed to update BFD session counters (%s)",
+ __func__, bs_to_string(bs));
+
+ vty_out(vty, "\t\tControl packet input: %" PRIu64 " packets\n",
+ bs->stats.rx_ctrl_pkt);
+ vty_out(vty, "\t\tControl packet output: %" PRIu64 " packets\n",
+ bs->stats.tx_ctrl_pkt);
+ vty_out(vty, "\t\tEcho packet input: %" PRIu64 " packets\n",
+ bs->stats.rx_echo_pkt);
+ vty_out(vty, "\t\tEcho packet output: %" PRIu64 " packets\n",
+ bs->stats.tx_echo_pkt);
+ vty_out(vty, "\t\tSession up events: %" PRIu64 "\n",
+ bs->stats.session_up);
+ vty_out(vty, "\t\tSession down events: %" PRIu64 "\n",
+ bs->stats.session_down);
+ vty_out(vty, "\t\tZebra notifications: %" PRIu64 "\n",
+ bs->stats.znotification);
+ vty_out(vty, "\n");
+}
+
+static struct json_object *__display_peer_counters_json(struct bfd_session *bs)
+{
+ struct json_object *jo = _peer_json_header(bs);
+
+ /* Ask data plane for updated counters. */
+ if (bfd_dplane_update_session_counters(bs) == -1)
+ zlog_debug("%s: failed to update BFD session counters (%s)",
+ __func__, bs_to_string(bs));
+
+ json_object_int_add(jo, "control-packet-input", bs->stats.rx_ctrl_pkt);
+ json_object_int_add(jo, "control-packet-output", bs->stats.tx_ctrl_pkt);
+ json_object_int_add(jo, "echo-packet-input", bs->stats.rx_echo_pkt);
+ json_object_int_add(jo, "echo-packet-output", bs->stats.tx_echo_pkt);
+ json_object_int_add(jo, "session-up", bs->stats.session_up);
+ json_object_int_add(jo, "session-down", bs->stats.session_down);
+ json_object_int_add(jo, "zebra-notifications", bs->stats.znotification);
+
+ return jo;
+}
+
+static void _display_peer_counters_json(struct vty *vty, struct bfd_session *bs)
+{
+ struct json_object *jo = __display_peer_counters_json(bs);
+
+ vty_json(vty, jo);
+}
+
+static void _display_peer_counter_iter(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_vrf_tuple *bvt = arg;
+ struct vty *vty;
+ struct bfd_session *bs = hb->data;
+
+ if (!bvt)
+ return;
+ vty = bvt->vty;
+
+ if (bvt->vrfname) {
+ if (!bs->key.vrfname[0] ||
+ !strmatch(bs->key.vrfname, bvt->vrfname))
+ return;
+ }
+
+ _display_peer_counter(vty, bs);
+}
+
+static void _display_peer_counter_json_iter(struct hash_bucket *hb, void *arg)
+{
+ struct json_object *jo, *jon = NULL;
+ struct bfd_session *bs = hb->data;
+ struct bfd_vrf_tuple *bvt = arg;
+
+ if (!bvt)
+ return;
+ jo = bvt->jo;
+
+ if (bvt->vrfname) {
+ if (!bs->key.vrfname[0] ||
+ !strmatch(bs->key.vrfname, bvt->vrfname))
+ return;
+ }
+
+ jon = __display_peer_counters_json(bs);
+ if (jon == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+
+ json_object_array_add(jo, jon);
+}
+
+static void _display_peers_counter(struct vty *vty, char *vrfname, bool use_json)
+{
+ struct json_object *jo;
+ struct bfd_vrf_tuple bvt = {0};
+
+ bvt.vrfname = vrfname;
+ if (!use_json) {
+ bvt.vty = vty;
+ vty_out(vty, "BFD Peers:\n");
+ bfd_id_iterate(_display_peer_counter_iter, &bvt);
+ return;
+ }
+
+ jo = json_object_new_array();
+ bvt.jo = jo;
+ bfd_id_iterate(_display_peer_counter_json_iter, &bvt);
+
+ vty_json(vty, jo);
+}
+
+static void _clear_peer_counter(struct bfd_session *bs)
+{
+ /* Clear only pkt stats, intention is not to loose system
+ events counters */
+ bs->stats.rx_ctrl_pkt = 0;
+ bs->stats.tx_ctrl_pkt = 0;
+ bs->stats.rx_echo_pkt = 0;
+ bs->stats.tx_echo_pkt = 0;
+}
+
+static void _display_peer_brief(struct vty *vty, struct bfd_session *bs)
+{
+ char addr_buf[INET6_ADDRSTRLEN];
+
+ vty_out(vty, "%-10u", bs->discrs.my_discr);
+ inet_ntop(bs->key.family, &bs->key.local, addr_buf, sizeof(addr_buf));
+ vty_out(vty, " %-40s", addr_buf);
+ inet_ntop(bs->key.family, &bs->key.peer, addr_buf, sizeof(addr_buf));
+ vty_out(vty, " %-40s", addr_buf);
+ vty_out(vty, "%-15s\n", state_list[bs->ses_state].str);
+}
+
+static void _display_peer_brief_iter(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_vrf_tuple *bvt = arg;
+ struct vty *vty;
+ struct bfd_session *bs = hb->data;
+
+ if (!bvt)
+ return;
+ vty = bvt->vty;
+
+ if (bvt->vrfname) {
+ if (!bs->key.vrfname[0] ||
+ !strmatch(bs->key.vrfname, bvt->vrfname))
+ return;
+ }
+
+ _display_peer_brief(vty, bs);
+}
+
+static void _display_peers_brief(struct vty *vty, const char *vrfname, bool use_json)
+{
+ struct json_object *jo;
+ struct bfd_vrf_tuple bvt = {0};
+
+ bvt.vrfname = vrfname;
+
+ if (!use_json) {
+ bvt.vty = vty;
+
+ vty_out(vty, "Session count: %lu\n", bfd_get_session_count());
+ vty_out(vty, "%-10s", "SessionId");
+ vty_out(vty, " %-40s", "LocalAddress");
+ vty_out(vty, " %-40s", "PeerAddress");
+ vty_out(vty, "%-15s\n", "Status");
+
+ vty_out(vty, "%-10s", "=========");
+ vty_out(vty, " %-40s", "============");
+ vty_out(vty, " %-40s", "===========");
+ vty_out(vty, "%-15s\n", "======");
+
+ bfd_id_iterate(_display_peer_brief_iter, &bvt);
+ return;
+ }
+
+ jo = json_object_new_array();
+ bvt.jo = jo;
+
+ bfd_id_iterate(_display_peer_json_iter, &bvt);
+
+ vty_json(vty, jo);
+}
+
+static struct bfd_session *
+_find_peer_or_error(struct vty *vty, int argc, struct cmd_token **argv,
+ const char *label, const char *peer_str,
+ const char *local_str, const char *ifname,
+ const char *vrfname)
+{
+ int idx;
+ bool mhop;
+ struct bfd_session *bs = NULL;
+ struct peer_label *pl;
+ struct bfd_peer_cfg bpc;
+ struct sockaddr_any psa, lsa, *lsap;
+ char errormsg[128];
+
+ /* Look up the BFD peer. */
+ if (label) {
+ pl = pl_find(label);
+ if (pl)
+ bs = pl->pl_bs;
+ } else if (peer_str) {
+ strtosa(peer_str, &psa);
+ if (local_str) {
+ strtosa(local_str, &lsa);
+ lsap = &lsa;
+ } else
+ lsap = NULL;
+
+ idx = 0;
+ mhop = argv_find(argv, argc, "multihop", &idx);
+
+ if (bfd_configure_peer(&bpc, mhop, &psa, lsap, ifname, vrfname,
+ errormsg, sizeof(errormsg))
+ != 0) {
+ vty_out(vty, "%% Invalid peer configuration: %s\n",
+ errormsg);
+ return NULL;
+ }
+
+ bs = bs_peer_find(&bpc);
+ } else {
+ vty_out(vty, "%% Invalid arguments\n");
+ return NULL;
+ }
+
+ /* Find peer data. */
+ if (bs == NULL) {
+ vty_out(vty, "%% Unable to find 'peer %s",
+ label ? label : peer_str);
+ if (ifname)
+ vty_out(vty, " interface %s", ifname);
+ if (local_str)
+ vty_out(vty, " local-address %s", local_str);
+ if (vrfname)
+ vty_out(vty, " vrf %s", vrfname);
+ vty_out(vty, "'\n");
+
+ return NULL;
+ }
+
+ return bs;
+}
+
+void _display_rtt(uint32_t *min, uint32_t *avg, uint32_t *max,
+ struct bfd_session *bs)
+{
+#ifdef BFD_LINUX
+ uint8_t i;
+ uint32_t average = 0;
+
+ if (bs->rtt_valid == 0)
+ return;
+
+ *max = bs->rtt[0];
+ *min = 1000;
+ *avg = 0;
+
+ for (i = 0; i < bs->rtt_valid; i++) {
+ if (bs->rtt[i] < *min)
+ *min = bs->rtt[i];
+ if (bs->rtt[i] > *max)
+ *max = bs->rtt[i];
+ average += bs->rtt[i];
+ }
+ *avg = average / bs->rtt_valid;
+
+#endif
+}
+
+/*
+ * Show commands.
+ */
+DEFPY(bfd_show_peers, bfd_show_peers_cmd, "show bfd [vrf NAME] peers [json]",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ VRF_CMD_HELP_STR
+ "BFD peers status\n" JSON_STR)
+{
+ char *vrf_name = NULL;
+ int idx_vrf = 0;
+
+ if (argv_find(argv, argc, "vrf", &idx_vrf))
+ vrf_name = argv[idx_vrf + 1]->arg;
+
+ _display_all_peers(vty, vrf_name, use_json(argc, argv));
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_show_peer, bfd_show_peer_cmd,
+ "show bfd [vrf NAME$vrf_name] peer <WORD$label|<A.B.C.D|X:X::X:X>$peer [{multihop|local-address <A.B.C.D|X:X::X:X>$local|interface IFNAME$ifname}]> [json]",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ VRF_CMD_HELP_STR
+ "BFD peers status\n"
+ "Peer label\n" PEER_IPV4_STR PEER_IPV6_STR MHOP_STR LOCAL_STR
+ LOCAL_IPV4_STR LOCAL_IPV6_STR INTERFACE_STR LOCAL_INTF_STR JSON_STR)
+{
+ struct bfd_session *bs;
+
+ /* Look up the BFD peer. */
+ bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str,
+ ifname, vrf_name);
+ if (bs == NULL)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ if (use_json(argc, argv)) {
+ _display_peer_json(vty, bs);
+ } else {
+ vty_out(vty, "BFD Peer:\n");
+ _display_peer(vty, bs);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_show_peer_counters, bfd_show_peer_counters_cmd,
+ "show bfd [vrf NAME$vrf_name] peer <WORD$label|<A.B.C.D|X:X::X:X>$peer [{multihop|local-address <A.B.C.D|X:X::X:X>$local|interface IFNAME$ifname}]> counters [json]",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ VRF_CMD_HELP_STR
+ "BFD peers status\n"
+ "Peer label\n"
+ PEER_IPV4_STR
+ PEER_IPV6_STR
+ MHOP_STR
+ LOCAL_STR
+ LOCAL_IPV4_STR
+ LOCAL_IPV6_STR
+ INTERFACE_STR
+ LOCAL_INTF_STR
+ "Show BFD peer counters information\n"
+ JSON_STR)
+{
+ struct bfd_session *bs;
+
+ /* Look up the BFD peer. */
+ bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str,
+ ifname, vrf_name);
+ if (bs == NULL)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ if (use_json(argc, argv))
+ _display_peer_counters_json(vty, bs);
+ else
+ _display_peer_counter(vty, bs);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_show_peers_counters, bfd_show_peers_counters_cmd,
+ "show bfd [vrf NAME] peers counters [json]",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ VRF_CMD_HELP_STR
+ "BFD peers status\n"
+ "Show BFD peer counters information\n"
+ JSON_STR)
+{
+ char *vrf_name = NULL;
+ int idx_vrf = 0;
+
+ if (argv_find(argv, argc, "vrf", &idx_vrf))
+ vrf_name = argv[idx_vrf + 1]->arg;
+
+ _display_peers_counter(vty, vrf_name, use_json(argc, argv));
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_clear_peer_counters, bfd_clear_peer_counters_cmd,
+ "clear bfd [vrf <NAME$vrfname>] peer <WORD$label|<A.B.C.D|X:X::X:X>$peer [{multihop|local-address <A.B.C.D|X:X::X:X>$local|interface IFNAME$ifname}]> counters",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ VRF_CMD_HELP_STR
+ "BFD peers status\n"
+ "Peer label\n"
+ PEER_IPV4_STR
+ PEER_IPV6_STR
+ MHOP_STR
+ LOCAL_STR
+ LOCAL_IPV4_STR
+ LOCAL_IPV6_STR
+ INTERFACE_STR
+ LOCAL_INTF_STR
+ "clear BFD peer counters information\n")
+{
+ struct bfd_session *bs;
+
+ /* Look up the BFD peer. */
+ bs = _find_peer_or_error(vty, argc, argv, label, peer_str, local_str,
+ ifname, vrfname);
+ if (bs == NULL)
+ return CMD_WARNING_CONFIG_FAILED;
+
+ _clear_peer_counter(bs);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_show_peers_brief, bfd_show_peers_brief_cmd,
+ "show bfd [vrf <NAME$vrfname>] peers brief [json]",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ VRF_CMD_HELP_STR
+ "BFD peers status\n"
+ "Show BFD peer information in tabular form\n"
+ JSON_STR)
+{
+ char *vrf_name = NULL;
+ int idx_vrf = 0;
+
+ if (argv_find(argv, argc, "vrf", &idx_vrf))
+ vrf_name = argv[idx_vrf + 1]->arg;
+
+ _display_peers_brief(vty, vrf_name, use_json(argc, argv));
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(show_bfd_distributed, show_bfd_distributed_cmd,
+ "show bfd distributed",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ "Show BFD data plane (distributed BFD) statistics\n")
+{
+ bfd_dplane_show_counters(vty);
+ return CMD_SUCCESS;
+}
+
+DEFPY(
+ bfd_debug_distributed, bfd_debug_distributed_cmd,
+ "[no] debug bfd distributed",
+ NO_STR
+ DEBUG_STR
+ "Bidirection Forwarding Detection\n"
+ "BFD data plane (distributed BFD) debugging\n")
+{
+ bglobal.debug_dplane = !no;
+ return CMD_SUCCESS;
+}
+
+DEFPY(
+ bfd_debug_peer, bfd_debug_peer_cmd,
+ "[no] debug bfd peer",
+ NO_STR
+ DEBUG_STR
+ "Bidirection Forwarding Detection\n"
+ "Peer events debugging\n")
+{
+ bglobal.debug_peer_event = !no;
+ return CMD_SUCCESS;
+}
+
+DEFPY(
+ bfd_debug_zebra, bfd_debug_zebra_cmd,
+ "[no] debug bfd zebra",
+ NO_STR
+ DEBUG_STR
+ "Bidirection Forwarding Detection\n"
+ "Zebra events debugging\n")
+{
+ bglobal.debug_zebra = !no;
+ return CMD_SUCCESS;
+}
+
+DEFPY(
+ bfd_debug_network, bfd_debug_network_cmd,
+ "[no] debug bfd network",
+ NO_STR
+ DEBUG_STR
+ "Bidirection Forwarding Detection\n"
+ "Network layer debugging\n")
+{
+ bglobal.debug_network = !no;
+ return CMD_SUCCESS;
+}
+
+/*
+ * Function definitions.
+ */
+
+/*
+ * Configuration rules:
+ *
+ * Single hop:
+ * peer + (interface name)
+ *
+ * Multi hop:
+ * peer + local + (optional vrf)
+ *
+ * Anything else is misconfiguration.
+ */
+static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop,
+ const struct sockaddr_any *peer,
+ const struct sockaddr_any *local,
+ const char *ifname, const char *vrfname,
+ char *ebuf, size_t ebuflen)
+{
+ memset(bpc, 0, sizeof(*bpc));
+
+ /* Defaults */
+ bpc->bpc_shutdown = false;
+ bpc->bpc_detectmultiplier = BPC_DEF_DETECTMULTIPLIER;
+ bpc->bpc_recvinterval = BPC_DEF_RECEIVEINTERVAL;
+ bpc->bpc_txinterval = BPC_DEF_TRANSMITINTERVAL;
+ bpc->bpc_echorecvinterval = BPC_DEF_ECHORECEIVEINTERVAL;
+ bpc->bpc_echotxinterval = BPC_DEF_ECHOTRANSMITINTERVAL;
+ bpc->bpc_lastevent = monotime(NULL);
+
+ /* Safety check: when no error buf is provided len must be zero. */
+ if (ebuf == NULL)
+ ebuflen = 0;
+
+ /* Peer is always mandatory. */
+ if (peer == NULL) {
+ snprintf(ebuf, ebuflen, "peer must not be empty");
+ return -1;
+ }
+
+ /* Validate address families. */
+ if (peer->sa_sin.sin_family == AF_INET) {
+ if (local && local->sa_sin.sin_family != AF_INET) {
+ snprintf(ebuf, ebuflen,
+ "local is IPv6, but peer is IPv4");
+ return -1;
+ }
+
+ bpc->bpc_ipv4 = true;
+ } else if (peer->sa_sin.sin_family == AF_INET6) {
+ if (local && local->sa_sin.sin_family != AF_INET6) {
+ snprintf(ebuf, ebuflen,
+ "local is IPv4, but peer is IPv6");
+ return -1;
+ }
+
+ bpc->bpc_ipv4 = false;
+ } else {
+ snprintf(ebuf, ebuflen, "invalid peer address family");
+ return -1;
+ }
+
+ /* Copy local and/or peer addresses. */
+ if (local)
+ bpc->bpc_local = *local;
+
+ bpc->bpc_peer = *peer;
+ bpc->bpc_mhop = mhop;
+
+ /* Handle interface specification configuration. */
+ if (ifname) {
+ bpc->bpc_has_localif = true;
+ if (strlcpy(bpc->bpc_localif, ifname, sizeof(bpc->bpc_localif))
+ > sizeof(bpc->bpc_localif)) {
+ snprintf(ebuf, ebuflen, "interface name too long");
+ return -1;
+ }
+ }
+
+ /* Handle VRF configuration. */
+ if (vrfname) {
+ bpc->bpc_has_vrfname = true;
+ if (strlcpy(bpc->bpc_vrfname, vrfname, sizeof(bpc->bpc_vrfname))
+ > sizeof(bpc->bpc_vrfname)) {
+ snprintf(ebuf, ebuflen, "vrf name too long");
+ return -1;
+ }
+ } else {
+ bpc->bpc_has_vrfname = true;
+ strlcpy(bpc->bpc_vrfname, VRF_DEFAULT_NAME, sizeof(bpc->bpc_vrfname));
+ }
+
+ return 0;
+}
+
+DEFUN_NOSH(show_debugging_bfd,
+ show_debugging_bfd_cmd,
+ "show debugging [bfd]",
+ SHOW_STR
+ DEBUG_STR
+ "BFD daemon\n")
+{
+ vty_out(vty, "BFD debugging status:\n");
+ if (bglobal.debug_dplane)
+ vty_out(vty, " Distributed BFD debugging is on.\n");
+ if (bglobal.debug_peer_event)
+ vty_out(vty, " Peer events debugging is on.\n");
+ if (bglobal.debug_zebra)
+ vty_out(vty, " Zebra events debugging is on.\n");
+ if (bglobal.debug_network)
+ vty_out(vty, " Network layer debugging is on.\n");
+
+ cmd_show_lib_debugs(vty);
+
+ return CMD_SUCCESS;
+}
+
+static int bfdd_write_config(struct vty *vty);
+struct cmd_node bfd_node = {
+ .name = "bfd",
+ .node = BFD_NODE,
+ .parent_node = CONFIG_NODE,
+ .prompt = "%s(config-bfd)# ",
+ .config_write = bfdd_write_config,
+};
+
+struct cmd_node bfd_peer_node = {
+ .name = "bfd peer",
+ .node = BFD_PEER_NODE,
+ .parent_node = BFD_NODE,
+ .prompt = "%s(config-bfd-peer)# ",
+};
+
+static int bfdd_write_config(struct vty *vty)
+{
+ struct lyd_node *dnode;
+ int written = 0;
+
+ if (bglobal.debug_dplane) {
+ vty_out(vty, "debug bfd distributed\n");
+ written = 1;
+ }
+
+ if (bglobal.debug_peer_event) {
+ vty_out(vty, "debug bfd peer\n");
+ written = 1;
+ }
+
+ if (bglobal.debug_zebra) {
+ vty_out(vty, "debug bfd zebra\n");
+ written = 1;
+ }
+
+ if (bglobal.debug_network) {
+ vty_out(vty, "debug bfd network\n");
+ written = 1;
+ }
+
+ dnode = yang_dnode_get(running_config->dnode, "/frr-bfdd:bfdd");
+ if (dnode) {
+ nb_cli_show_dnode_cmds(vty, dnode, false);
+ written = 1;
+ }
+
+ return written;
+}
+
+void bfdd_vty_init(void)
+{
+ install_element(ENABLE_NODE, &bfd_show_peers_counters_cmd);
+ install_element(ENABLE_NODE, &bfd_show_peer_counters_cmd);
+ install_element(ENABLE_NODE, &bfd_clear_peer_counters_cmd);
+ install_element(ENABLE_NODE, &bfd_show_peers_cmd);
+ install_element(ENABLE_NODE, &bfd_show_peer_cmd);
+ install_element(ENABLE_NODE, &bfd_show_peers_brief_cmd);
+ install_element(ENABLE_NODE, &show_bfd_distributed_cmd);
+ install_element(ENABLE_NODE, &show_debugging_bfd_cmd);
+
+ install_element(ENABLE_NODE, &bfd_debug_distributed_cmd);
+ install_element(ENABLE_NODE, &bfd_debug_peer_cmd);
+ install_element(ENABLE_NODE, &bfd_debug_zebra_cmd);
+ install_element(ENABLE_NODE, &bfd_debug_network_cmd);
+
+ install_element(CONFIG_NODE, &bfd_debug_distributed_cmd);
+ install_element(CONFIG_NODE, &bfd_debug_peer_cmd);
+ install_element(CONFIG_NODE, &bfd_debug_zebra_cmd);
+ install_element(CONFIG_NODE, &bfd_debug_network_cmd);
+
+ /* Install BFD node and commands. */
+ install_node(&bfd_node);
+ install_default(BFD_NODE);
+
+ /* Install BFD peer node. */
+ install_node(&bfd_peer_node);
+ install_default(BFD_PEER_NODE);
+
+ bfdd_cli_init();
+}
diff --git a/bfdd/bfddp_packet.h b/bfdd/bfddp_packet.h
new file mode 100644
index 0000000..afcbdd7
--- /dev/null
+++ b/bfdd/bfddp_packet.h
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: MIT
+/*
+ * BFD Data Plane protocol messages header.
+ *
+ * Copyright (C) 2020 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael F. Zalamena
+ */
+
+/**
+ * \file bfddp_packet.h
+ */
+#ifndef BFD_DP_PACKET_H
+#define BFD_DP_PACKET_H
+
+#include <netinet/in.h>
+
+#include <stdint.h>
+
+/*
+ * Protocol definitions.
+ */
+
+/**
+ * BFD protocol version as defined in RFC5880 Section 4.1 Generic BFD Control
+ * Packet Format.
+ */
+#define BFD_PROTOCOL_VERSION 1
+
+/** Default data plane port. */
+#define BFD_DATA_PLANE_DEFAULT_PORT 50700
+
+/** BFD single hop UDP port, as defined in RFC 5881 Section 4. Encapsulation. */
+#define BFD_SINGLE_HOP_PORT 3784
+
+/** BFD multi hop UDP port, as defined in RFC 5883 Section 5. Encapsulation. */
+#define BFD_MULTI_HOP_PORT 4784
+
+/** Default slow start multiplier. */
+#define SLOWSTART_DMULT 3
+/** Default slow start transmission speed. */
+#define SLOWSTART_TX 1000000u
+/** Default slow start receive speed. */
+#define SLOWSTART_RX 1000000u
+/** Default slow start echo receive speed. */
+#define SLOWSTART_ERX 0u
+
+/*
+ * BFD single hop source UDP ports. As defined in RFC 5881 Section 4.
+ * Encapsulation.
+ */
+#define BFD_SOURCE_PORT_BEGIN 49152
+#define BFD_SOURCE_PORT_END 65535
+
+/** BFD data plane protocol version. */
+#define BFD_DP_VERSION 1
+
+/** BFD data plane message types. */
+enum bfddp_message_type {
+ /** Ask for BFD daemon or data plane for echo packet. */
+ ECHO_REQUEST = 0,
+ /** Answer a ECHO_REQUEST packet. */
+ ECHO_REPLY = 1,
+ /** Add or update BFD peer session. */
+ DP_ADD_SESSION = 2,
+ /** Delete BFD peer session. */
+ DP_DELETE_SESSION = 3,
+ /** Tell BFD daemon state changed: timer expired or session down. */
+ BFD_STATE_CHANGE = 4,
+
+ /** Ask for BFD session counters. */
+ DP_REQUEST_SESSION_COUNTERS = 5,
+ /** Tell BFD daemon about counters values. */
+ BFD_SESSION_COUNTERS = 6,
+};
+
+/**
+ * `ECHO_REQUEST`/`ECHO_REPLY` data payload.
+ *
+ * Data plane might use whatever precision it wants for `dp_time`
+ * field, however if you want to be able to tell the delay between
+ * data plane packet send and BFD daemon packet processing you should
+ * use `gettimeofday()` and have the data plane clock synchronized with
+ * BFD daemon (not a problem if data plane runs in the same system).
+ *
+ * Normally data plane will only check the time stamp it sent to determine
+ * the whole packet trip time.
+ */
+struct bfddp_echo {
+ /** Filled by data plane. */
+ uint64_t dp_time;
+ /** Filled by BFD daemon. */
+ uint64_t bfdd_time;
+};
+
+
+/** BFD session flags. */
+enum bfddp_session_flag {
+ /** Set when using multi hop. */
+ SESSION_MULTIHOP = (1 << 0),
+ /** Set when using demand mode. */
+ SESSION_DEMAND = (1 << 1),
+ /** Set when using cbit (Control Plane Independent). */
+ SESSION_CBIT = (1 << 2),
+ /** Set when using echo mode. */
+ SESSION_ECHO = (1 << 3),
+ /** Set when using IPv6. */
+ SESSION_IPV6 = (1 << 4),
+ /** Set when using passive mode. */
+ SESSION_PASSIVE = (1 << 5),
+ /** Set when session is administrative down. */
+ SESSION_SHUTDOWN = (1 << 6),
+};
+
+/**
+ * `DP_ADD_SESSION`/`DP_DELETE_SESSION` data payload.
+ *
+ * `lid` is unique in BFD daemon so it might be used as key for data
+ * structures lookup.
+ */
+struct bfddp_session {
+ /** Important session flags. \see bfddp_session_flag. */
+ uint32_t flags;
+ /**
+ * Session source address.
+ *
+ * Check `flags` field for `SESSION_IPV6` before using as IPv6.
+ */
+ struct in6_addr src;
+ /**
+ * Session destination address.
+ *
+ * Check `flags` field for `SESSION_IPV6` before using as IPv6.
+ */
+ struct in6_addr dst;
+
+ /** Local discriminator. */
+ uint32_t lid;
+ /**
+ * Minimum desired transmission interval (in microseconds) without
+ * jitter.
+ */
+ uint32_t min_tx;
+ /**
+ * Required minimum receive interval rate (in microseconds) without
+ * jitter.
+ */
+ uint32_t min_rx;
+ /**
+ * Minimum desired echo transmission interval (in microseconds)
+ * without jitter.
+ */
+ uint32_t min_echo_tx;
+ /**
+ * Required minimum echo receive interval rate (in microseconds)
+ * without jitter.
+ */
+ uint32_t min_echo_rx;
+ /** Amount of milliseconds to wait before starting the session */
+ uint32_t hold_time;
+
+ /** Minimum TTL. */
+ uint8_t ttl;
+ /** Detection multiplier. */
+ uint8_t detect_mult;
+ /** Reserved / zeroed. */
+ uint16_t zero;
+
+ /** Interface index (set to `0` when unavailable). */
+ uint32_t ifindex;
+ /** Interface name (empty when unavailable). */
+ char ifname[64];
+
+ /* TODO: missing authentication. */
+};
+
+/** BFD packet state values as defined in RFC 5880, Section 4.1. */
+enum bfd_state_value {
+ /** Session is administratively down. */
+ STATE_ADMINDOWN = 0,
+ /** Session is down or went down. */
+ STATE_DOWN = 1,
+ /** Session is initializing. */
+ STATE_INIT = 2,
+ /** Session is up. */
+ STATE_UP = 3,
+};
+
+/** BFD diagnostic field values as defined in RFC 5880, Section 4.1. */
+enum bfd_diagnostic_value {
+ /** Nothing was diagnosed. */
+ DIAG_NOTHING = 0,
+ /** Control detection time expired. */
+ DIAG_CONTROL_EXPIRED = 1,
+ /** Echo function failed. */
+ DIAG_ECHO_FAILED = 2,
+ /** Neighbor signaled down. */
+ DIAG_DOWN = 3,
+ /** Forwarding plane reset. */
+ DIAG_FP_RESET = 4,
+ /** Path down. */
+ DIAG_PATH_DOWN = 5,
+ /** Concatenated path down. */
+ DIAG_CONCAT_PATH_DOWN = 6,
+ /** Administratively down. */
+ DIAG_ADMIN_DOWN = 7,
+ /** Reverse concatenated path down. */
+ DIAG_REV_CONCAT_PATH_DOWN = 8,
+};
+
+/** BFD remote state flags. */
+enum bfd_remote_flags {
+ /** Control Plane Independent bit. */
+ RBIT_CPI = (1 << 0),
+ /** Demand mode bit. */
+ RBIT_DEMAND = (1 << 1),
+ /** Multipoint bit. */
+ RBIT_MP = (1 << 2),
+};
+
+/**
+ * `BFD_STATE_CHANGE` data payload.
+ */
+struct bfddp_state_change {
+ /** Local discriminator. */
+ uint32_t lid;
+ /** Remote discriminator. */
+ uint32_t rid;
+ /** Remote configurations/bits set. \see bfd_remote_flags. */
+ uint32_t remote_flags;
+ /** Remote minimum desired transmission interval. */
+ uint32_t desired_tx;
+ /** Remote minimum receive interval. */
+ uint32_t required_rx;
+ /** Remote minimum echo receive interval. */
+ uint32_t required_echo_rx;
+ /** Remote state. \see bfd_state_values.*/
+ uint8_t state;
+ /** Remote diagnostics (if any) */
+ uint8_t diagnostics;
+ /** Remote detection multiplier. */
+ uint8_t detection_multiplier;
+};
+
+/**
+ * BFD control packet state bits definition.
+ */
+enum bfddp_control_state_bits {
+ /** Used to request connection establishment signal. */
+ STATE_POLL_BIT = (1 << 5),
+ /** Finalizes the connection establishment signal. */
+ STATE_FINAL_BIT = (1 << 4),
+ /** Signalizes that forward plane doesn't depend on control plane. */
+ STATE_CPI_BIT = (1 << 3),
+ /** Signalizes the use of authentication. */
+ STATE_AUTH_BIT = (1 << 2),
+ /** Signalizes that peer is using demand mode. */
+ STATE_DEMAND_BIT = (1 << 1),
+ /** Used in RFC 8562 implementation. */
+ STATE_MULTI_BIT = (1 << 0),
+};
+
+/**
+ * BFD control packet.
+ *
+ * As defined in 'RFC 5880 Section 4.1 Generic BFD Control Packet Format'.
+ */
+struct bfddp_control_packet {
+ /** (3 bits version << 5) | (5 bits diag). */
+ uint8_t version_diag;
+ /**
+ * (2 bits state << 6) | (6 bits flags)
+ *
+ * \see bfd_state_value, bfddp_control_state_bits.
+ */
+ uint8_t state_bits;
+ /** Detection multiplier. */
+ uint8_t detection_multiplier;
+ /** Packet length in bytes. */
+ uint8_t length;
+ /** Our discriminator. */
+ uint32_t local_id;
+ /** Remote system discriminator. */
+ uint32_t remote_id;
+ /** Desired minimum send interval in microseconds. */
+ uint32_t desired_tx;
+ /** Desired minimum receive interval in microseconds. */
+ uint32_t required_rx;
+ /** Desired minimum echo receive interval in microseconds. */
+ uint32_t required_echo_rx;
+};
+
+/**
+ * The protocol wire message header structure.
+ */
+struct bfddp_message_header {
+ /** Protocol version format. \see BFD_DP_VERSION. */
+ uint8_t version;
+ /** Reserved / zero field. */
+ uint8_t zero;
+ /** Message contents type. \see bfddp_message_type. */
+ uint16_t type;
+ /**
+ * Message identification (to pair request/response).
+ *
+ * The ID `0` is reserved for asynchronous messages (e.g. unrequested
+ * messages).
+ */
+ uint16_t id;
+ /** Message length. */
+ uint16_t length;
+};
+
+/**
+ * Data plane session counters request.
+ *
+ * Message type: `DP_REQUEST_SESSION_COUNTERS`.
+ */
+struct bfddp_request_counters {
+ /** Session local discriminator. */
+ uint32_t lid;
+};
+
+/**
+ * BFD session counters reply.
+ *
+ * Message type: `BFD_SESSION_COUNTERS`.
+ */
+struct bfddp_session_counters {
+ /** Session local discriminator. */
+ uint32_t lid;
+
+ /** Control packet bytes input. */
+ uint64_t control_input_bytes;
+ /** Control packets input. */
+ uint64_t control_input_packets;
+ /** Control packet bytes output. */
+ uint64_t control_output_bytes;
+ /** Control packets output. */
+ uint64_t control_output_packets;
+
+ /** Echo packet bytes input. */
+ uint64_t echo_input_bytes;
+ /** Echo packets input. */
+ uint64_t echo_input_packets;
+ /** Echo packet bytes output. */
+ uint64_t echo_output_bytes;
+ /** Echo packets output. */
+ uint64_t echo_output_packets;
+};
+
+/**
+ * The protocol wire messages structure.
+ */
+struct bfddp_message {
+ /** Message header. \see bfddp_message_header. */
+ struct bfddp_message_header header;
+
+ /** Message payload. \see bfddp_message_type. */
+ union {
+ struct bfddp_echo echo;
+ struct bfddp_session session;
+ struct bfddp_state_change state;
+ struct bfddp_control_packet control;
+ struct bfddp_request_counters counters_req;
+ struct bfddp_session_counters session_counters;
+ } data;
+};
+
+#endif /* BFD_DP_PACKET_H */
diff --git a/bfdd/config.c b/bfdd/config.c
new file mode 100644
index 0000000..22d7d7d
--- /dev/null
+++ b/bfdd/config.c
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * config.c: implements the BFD daemon configuration handling.
+ *
+ * Authors
+ * -------
+ * Rafael Zalamena <rzalamena@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include <string.h>
+
+#include "lib/json.h"
+
+#include "bfd.h"
+
+DEFINE_MTYPE_STATIC(BFDD, BFDD_LABEL, "long-lived label memory");
+
+/*
+ * Definitions
+ */
+enum peer_list_type {
+ PLT_IPV4,
+ PLT_IPV6,
+ PLT_LABEL,
+};
+
+
+/*
+ * Prototypes
+ */
+static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg);
+static int parse_list(struct json_object *jo, enum peer_list_type plt,
+ bpc_handle h, void *arg);
+static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc);
+static int parse_peer_label_config(struct json_object *jo,
+ struct bfd_peer_cfg *bpc);
+
+static int config_add(struct bfd_peer_cfg *bpc, void *arg);
+static int config_del(struct bfd_peer_cfg *bpc, void *arg);
+
+static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs);
+
+
+/*
+ * Implementation
+ */
+static int config_add(struct bfd_peer_cfg *bpc,
+ void *arg __attribute__((unused)))
+{
+ return ptm_bfd_sess_new(bpc) == NULL;
+}
+
+static int config_del(struct bfd_peer_cfg *bpc,
+ void *arg __attribute__((unused)))
+{
+ return ptm_bfd_sess_del(bpc) != 0;
+}
+
+static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg)
+{
+ const char *key, *sval;
+ struct json_object *jo_val;
+ struct json_object_iterator joi, join;
+ int error = 0;
+
+ JSON_FOREACH (jo, joi, join) {
+ key = json_object_iter_peek_name(&joi);
+ jo_val = json_object_iter_peek_value(&joi);
+
+ if (strcmp(key, "ipv4") == 0) {
+ error += parse_list(jo_val, PLT_IPV4, h, arg);
+ } else if (strcmp(key, "ipv6") == 0) {
+ error += parse_list(jo_val, PLT_IPV6, h, arg);
+ } else if (strcmp(key, "label") == 0) {
+ error += parse_list(jo_val, PLT_LABEL, h, arg);
+ } else {
+ sval = json_object_get_string(jo_val);
+ zlog_warn("%s:%d invalid configuration: %s", __func__,
+ __LINE__, sval);
+ error++;
+ }
+ }
+
+ /*
+ * Our callers never call free() on json_object and only expect
+ * the return value, so lets free() it here.
+ */
+ json_object_put(jo);
+
+ return error;
+}
+
+int parse_config(const char *fname)
+{
+ struct json_object *jo;
+
+ jo = json_object_from_file(fname);
+ if (jo == NULL)
+ return -1;
+
+ return parse_config_json(jo, config_add, NULL);
+}
+
+static int parse_list(struct json_object *jo, enum peer_list_type plt,
+ bpc_handle h, void *arg)
+{
+ struct json_object *jo_val;
+ struct bfd_peer_cfg bpc;
+ int allen, idx;
+ int error = 0, result;
+
+ allen = json_object_array_length(jo);
+ for (idx = 0; idx < allen; idx++) {
+ jo_val = json_object_array_get_idx(jo, idx);
+
+ /* Set defaults. */
+ memset(&bpc, 0, sizeof(bpc));
+ bpc.bpc_detectmultiplier = BFD_DEFDETECTMULT;
+ bpc.bpc_recvinterval = BFD_DEFREQUIREDMINRX;
+ bpc.bpc_txinterval = BFD_DEFDESIREDMINTX;
+ bpc.bpc_echorecvinterval = BFD_DEF_REQ_MIN_ECHO_RX;
+ bpc.bpc_echotxinterval = BFD_DEF_DES_MIN_ECHO_TX;
+
+ switch (plt) {
+ case PLT_IPV4:
+ zlog_debug("ipv4 peers %d:", allen);
+ bpc.bpc_ipv4 = true;
+ break;
+ case PLT_IPV6:
+ zlog_debug("ipv6 peers %d:", allen);
+ bpc.bpc_ipv4 = false;
+ break;
+ case PLT_LABEL:
+ zlog_debug("label peers %d:", allen);
+ if (parse_peer_label_config(jo_val, &bpc) != 0) {
+ error++;
+ continue;
+ }
+ break;
+
+ default:
+ error++;
+ zlog_err("%s:%d: unsupported peer type", __func__,
+ __LINE__);
+ break;
+ }
+
+ result = parse_peer_config(jo_val, &bpc);
+ error += result;
+ if (result == 0)
+ error += (h(&bpc, arg) != 0);
+ }
+
+ return error;
+}
+
+static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc)
+{
+ const char *key, *sval;
+ struct json_object *jo_val;
+ struct json_object_iterator joi, join;
+ int family_type = (bpc->bpc_ipv4) ? AF_INET : AF_INET6;
+ int error = 0;
+
+ zlog_debug(" peer: %s", bpc->bpc_ipv4 ? "ipv4" : "ipv6");
+
+ JSON_FOREACH (jo, joi, join) {
+ key = json_object_iter_peek_name(&joi);
+ jo_val = json_object_iter_peek_value(&joi);
+
+ if (strcmp(key, "multihop") == 0) {
+ bpc->bpc_mhop = json_object_get_boolean(jo_val);
+ zlog_debug(" multihop: %s",
+ bpc->bpc_mhop ? "true" : "false");
+ } else if (strcmp(key, "peer-address") == 0) {
+ sval = json_object_get_string(jo_val);
+ if (strtosa(sval, &bpc->bpc_peer) != 0
+ || bpc->bpc_peer.sa_sin.sin_family != family_type) {
+ zlog_debug(
+ "%s:%d failed to parse peer-address '%s'",
+ __func__, __LINE__, sval);
+ error++;
+ }
+ zlog_debug(" peer-address: %s", sval);
+ } else if (strcmp(key, "local-address") == 0) {
+ sval = json_object_get_string(jo_val);
+ if (strtosa(sval, &bpc->bpc_local) != 0
+ || bpc->bpc_local.sa_sin.sin_family
+ != family_type) {
+ zlog_debug(
+ "%s:%d failed to parse local-address '%s'",
+ __func__, __LINE__, sval);
+ error++;
+ }
+ zlog_debug(" local-address: %s", sval);
+ } else if (strcmp(key, "local-interface") == 0) {
+ bpc->bpc_has_localif = true;
+ sval = json_object_get_string(jo_val);
+ if (strlcpy(bpc->bpc_localif, sval,
+ sizeof(bpc->bpc_localif))
+ > sizeof(bpc->bpc_localif)) {
+ zlog_debug(
+ " local-interface: %s (truncated)",
+ sval);
+ error++;
+ } else {
+ zlog_debug(" local-interface: %s", sval);
+ }
+ } else if (strcmp(key, "vrf-name") == 0) {
+ bpc->bpc_has_vrfname = true;
+ sval = json_object_get_string(jo_val);
+ if (strlcpy(bpc->bpc_vrfname, sval,
+ sizeof(bpc->bpc_vrfname))
+ > sizeof(bpc->bpc_vrfname)) {
+ zlog_debug(" vrf-name: %s (truncated)",
+ sval);
+ error++;
+ } else {
+ zlog_debug(" vrf-name: %s", sval);
+ }
+ } else if (strcmp(key, "detect-multiplier") == 0) {
+ bpc->bpc_detectmultiplier =
+ json_object_get_int64(jo_val);
+ bpc->bpc_has_detectmultiplier = true;
+ zlog_debug(" detect-multiplier: %u",
+ bpc->bpc_detectmultiplier);
+ } else if (strcmp(key, "receive-interval") == 0) {
+ bpc->bpc_recvinterval = json_object_get_int64(jo_val);
+ bpc->bpc_has_recvinterval = true;
+ zlog_debug(" receive-interval: %" PRIu64,
+ bpc->bpc_recvinterval);
+ } else if (strcmp(key, "transmit-interval") == 0) {
+ bpc->bpc_txinterval = json_object_get_int64(jo_val);
+ bpc->bpc_has_txinterval = true;
+ zlog_debug(" transmit-interval: %" PRIu64,
+ bpc->bpc_txinterval);
+ } else if (strcmp(key, "echo-receive-interval") == 0) {
+ bpc->bpc_echorecvinterval = json_object_get_int64(jo_val);
+ bpc->bpc_has_echorecvinterval = true;
+ zlog_debug(" echo-receive-interval: %" PRIu64,
+ bpc->bpc_echorecvinterval);
+ } else if (strcmp(key, "echo-transmit-interval") == 0) {
+ bpc->bpc_echotxinterval = json_object_get_int64(jo_val);
+ bpc->bpc_has_echotxinterval = true;
+ zlog_debug(" echo-transmit-interval: %" PRIu64,
+ bpc->bpc_echotxinterval);
+ } else if (strcmp(key, "create-only") == 0) {
+ bpc->bpc_createonly = json_object_get_boolean(jo_val);
+ zlog_debug(" create-only: %s",
+ bpc->bpc_createonly ? "true" : "false");
+ } else if (strcmp(key, "shutdown") == 0) {
+ bpc->bpc_shutdown = json_object_get_boolean(jo_val);
+ zlog_debug(" shutdown: %s",
+ bpc->bpc_shutdown ? "true" : "false");
+ } else if (strcmp(key, "echo-mode") == 0) {
+ bpc->bpc_echo = json_object_get_boolean(jo_val);
+ zlog_debug(" echo-mode: %s",
+ bpc->bpc_echo ? "true" : "false");
+ } else if (strcmp(key, "label") == 0) {
+ bpc->bpc_has_label = true;
+ sval = json_object_get_string(jo_val);
+ if (strlcpy(bpc->bpc_label, sval,
+ sizeof(bpc->bpc_label))
+ > sizeof(bpc->bpc_label)) {
+ zlog_debug(" label: %s (truncated)",
+ sval);
+ error++;
+ } else {
+ zlog_debug(" label: %s", sval);
+ }
+ } else {
+ sval = json_object_get_string(jo_val);
+ zlog_warn("%s:%d invalid configuration: '%s: %s'",
+ __func__, __LINE__, key, sval);
+ error++;
+ }
+ }
+
+ if (bpc->bpc_peer.sa_sin.sin_family == 0) {
+ zlog_debug("%s:%d no peer address provided", __func__,
+ __LINE__);
+ error++;
+ }
+
+ return error;
+}
+
+static int parse_peer_label_config(struct json_object *jo,
+ struct bfd_peer_cfg *bpc)
+{
+ struct peer_label *pl;
+ struct json_object *label;
+ const char *sval;
+
+ /* Get label and translate it to BFD daemon key. */
+ if (!json_object_object_get_ex(jo, "label", &label))
+ return 1;
+
+ sval = json_object_get_string(label);
+
+ pl = pl_find(sval);
+ if (pl == NULL)
+ return 1;
+
+ zlog_debug(" peer-label: %s", sval);
+
+ /* Translate the label into BFD address keys. */
+ bs_to_bpc(pl->pl_bs, bpc);
+
+ return 0;
+}
+
+
+/*
+ * Control socket JSON parsing.
+ */
+int config_request_add(const char *jsonstr)
+{
+ struct json_object *jo;
+
+ jo = json_tokener_parse(jsonstr);
+ if (jo == NULL)
+ return -1;
+
+ return parse_config_json(jo, config_add, NULL);
+}
+
+int config_request_del(const char *jsonstr)
+{
+ struct json_object *jo;
+
+ jo = json_tokener_parse(jsonstr);
+ if (jo == NULL)
+ return -1;
+
+ return parse_config_json(jo, config_del, NULL);
+}
+
+char *config_response(const char *status, const char *error)
+{
+ struct json_object *resp, *jo;
+ char *jsonstr;
+
+ resp = json_object_new_object();
+ if (resp == NULL)
+ return NULL;
+
+ /* Add 'status' response key. */
+ jo = json_object_new_string(status);
+ if (jo == NULL) {
+ json_object_put(resp);
+ return NULL;
+ }
+
+ json_object_object_add(resp, "status", jo);
+
+ /* Add 'error' response key. */
+ if (error != NULL) {
+ jo = json_object_new_string(error);
+ if (jo == NULL) {
+ json_object_put(resp);
+ return NULL;
+ }
+
+ json_object_object_add(resp, "error", jo);
+ }
+
+ /* Generate JSON response. */
+ jsonstr = XSTRDUP(
+ MTYPE_BFDD_NOTIFICATION,
+ json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS));
+ json_object_put(resp);
+
+ return jsonstr;
+}
+
+char *config_notify(struct bfd_session *bs)
+{
+ struct json_object *resp;
+ char *jsonstr;
+ time_t now;
+
+ resp = json_object_new_object();
+ if (resp == NULL)
+ return NULL;
+
+ json_object_string_add(resp, "op", BCM_NOTIFY_PEER_STATUS);
+
+ json_object_add_peer(resp, bs);
+
+ /* Add status information */
+ json_object_int_add(resp, "id", bs->discrs.my_discr);
+ json_object_int_add(resp, "remote-id", bs->discrs.my_discr);
+
+ switch (bs->ses_state) {
+ case PTM_BFD_UP:
+ json_object_string_add(resp, "state", "up");
+
+ now = monotime(NULL);
+ json_object_int_add(resp, "uptime", now - bs->uptime.tv_sec);
+ break;
+ case PTM_BFD_ADM_DOWN:
+ json_object_string_add(resp, "state", "adm-down");
+ break;
+ case PTM_BFD_DOWN:
+ json_object_string_add(resp, "state", "down");
+
+ now = monotime(NULL);
+ json_object_int_add(resp, "downtime",
+ now - bs->downtime.tv_sec);
+ break;
+ case PTM_BFD_INIT:
+ json_object_string_add(resp, "state", "init");
+ break;
+
+ default:
+ json_object_string_add(resp, "state", "unknown");
+ break;
+ }
+
+ json_object_int_add(resp, "diagnostics", bs->local_diag);
+ json_object_int_add(resp, "remote-diagnostics", bs->remote_diag);
+
+ /* Generate JSON response. */
+ jsonstr = XSTRDUP(
+ MTYPE_BFDD_NOTIFICATION,
+ json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS));
+ json_object_put(resp);
+
+ return jsonstr;
+}
+
+char *config_notify_config(const char *op, struct bfd_session *bs)
+{
+ struct json_object *resp;
+ char *jsonstr;
+
+ resp = json_object_new_object();
+ if (resp == NULL)
+ return NULL;
+
+ json_object_string_add(resp, "op", op);
+
+ json_object_add_peer(resp, bs);
+
+ /* On peer deletion we don't need to add any additional information. */
+ if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0)
+ goto skip_config;
+
+ json_object_int_add(resp, "detect-multiplier", bs->detect_mult);
+ json_object_int_add(resp, "receive-interval",
+ bs->timers.required_min_rx / 1000);
+ json_object_int_add(resp, "transmit-interval",
+ bs->timers.desired_min_tx / 1000);
+ json_object_int_add(resp, "echo-receive-interval",
+ bs->timers.required_min_echo_rx / 1000);
+ json_object_int_add(resp, "echo-transmit-interval",
+ bs->timers.desired_min_echo_tx / 1000);
+
+ json_object_int_add(resp, "remote-detect-multiplier",
+ bs->remote_detect_mult);
+ json_object_int_add(resp, "remote-receive-interval",
+ bs->remote_timers.required_min_rx / 1000);
+ json_object_int_add(resp, "remote-transmit-interval",
+ bs->remote_timers.desired_min_tx / 1000);
+ json_object_int_add(resp, "remote-echo-receive-interval",
+ bs->remote_timers.required_min_echo / 1000);
+
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ json_object_boolean_true_add(resp, "echo-mode");
+ else
+ json_object_boolean_false_add(resp, "echo-mode");
+
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ json_object_boolean_true_add(resp, "shutdown");
+ else
+ json_object_boolean_false_add(resp, "shutdown");
+
+skip_config:
+ /* Generate JSON response. */
+ jsonstr = XSTRDUP(
+ MTYPE_BFDD_NOTIFICATION,
+ json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS));
+ json_object_put(resp);
+
+ return jsonstr;
+}
+
+int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr,
+ bpc_handle bh)
+{
+ struct json_object *jo;
+
+ jo = json_tokener_parse(jsonstr);
+ if (jo == NULL)
+ return -1;
+
+ return parse_config_json(jo, bh, bcs);
+}
+
+static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs)
+{
+ char addr_buf[INET6_ADDRSTRLEN];
+
+ /* Add peer 'key' information. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6))
+ json_object_boolean_true_add(jo, "ipv6");
+ else
+ json_object_boolean_false_add(jo, "ipv6");
+
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ json_object_boolean_true_add(jo, "multihop");
+ json_object_string_add(jo, "peer-address",
+ inet_ntop(bs->key.family, &bs->key.peer,
+ addr_buf, sizeof(addr_buf)));
+ json_object_string_add(jo, "local-address",
+ inet_ntop(bs->key.family, &bs->key.local,
+ addr_buf, sizeof(addr_buf)));
+ if (bs->key.vrfname[0])
+ json_object_string_add(jo, "vrf-name", bs->key.vrfname);
+ } else {
+ json_object_boolean_false_add(jo, "multihop");
+ json_object_string_add(jo, "peer-address",
+ inet_ntop(bs->key.family, &bs->key.peer,
+ addr_buf, sizeof(addr_buf)));
+ if (memcmp(&bs->key.local, &zero_addr, sizeof(bs->key.local)))
+ json_object_string_add(
+ jo, "local-address",
+ inet_ntop(bs->key.family, &bs->key.local,
+ addr_buf, sizeof(addr_buf)));
+ if (bs->key.ifname[0])
+ json_object_string_add(jo, "local-interface",
+ bs->key.ifname);
+ }
+
+ if (bs->pl)
+ json_object_string_add(jo, "label", bs->pl->pl_label);
+
+ return 0;
+}
+
+
+/*
+ * Label handling
+ */
+struct peer_label *pl_find(const char *label)
+{
+ struct peer_label *pl;
+
+ TAILQ_FOREACH (pl, &bglobal.bg_pllist, pl_entry) {
+ if (strcmp(pl->pl_label, label) != 0)
+ continue;
+
+ return pl;
+ }
+
+ return NULL;
+}
+
+struct peer_label *pl_new(const char *label, struct bfd_session *bs)
+{
+ struct peer_label *pl;
+
+ pl = XCALLOC(MTYPE_BFDD_LABEL, sizeof(*pl));
+
+ if (strlcpy(pl->pl_label, label, sizeof(pl->pl_label))
+ > sizeof(pl->pl_label))
+ zlog_warn("%s:%d: label was truncated", __func__, __LINE__);
+
+ pl->pl_bs = bs;
+ bs->pl = pl;
+
+ TAILQ_INSERT_HEAD(&bglobal.bg_pllist, pl, pl_entry);
+
+ return pl;
+}
+
+void pl_free(struct peer_label *pl)
+{
+ if (pl == NULL)
+ return;
+
+ /* Remove the pointer back. */
+ pl->pl_bs->pl = NULL;
+
+ TAILQ_REMOVE(&bglobal.bg_pllist, pl, pl_entry);
+ XFREE(MTYPE_BFDD_LABEL, pl);
+}
diff --git a/bfdd/control.c b/bfdd/control.c
new file mode 100644
index 0000000..f435358
--- /dev/null
+++ b/bfdd/control.c
@@ -0,0 +1,841 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * control.c: implements the BFD daemon control socket. It will be used
+ * to talk with clients daemon/scripts/consumers.
+ *
+ * Authors
+ * -------
+ * Rafael Zalamena <rzalamena@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include <sys/un.h>
+
+#include "bfd.h"
+
+/*
+ * Prototypes
+ */
+static int sock_set_nonblock(int fd);
+struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs);
+static void control_queue_free(struct bfd_control_socket *bcs,
+ struct bfd_control_queue *bcq);
+static int control_queue_dequeue(struct bfd_control_socket *bcs);
+static int control_queue_enqueue(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static int control_queue_enqueue_first(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs,
+ struct bfd_session *bs);
+static void control_notifypeer_free(struct bfd_control_socket *bcs,
+ struct bfd_notify_peer *bnp);
+struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs,
+ struct bfd_session *bs);
+
+
+struct bfd_control_socket *control_new(int sd);
+static void control_free(struct bfd_control_socket *bcs);
+static void control_reset_buf(struct bfd_control_buffer *bcb);
+static void control_read(struct event *t);
+static void control_write(struct event *t);
+
+static void control_handle_request_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void control_handle_request_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg);
+static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg);
+static void control_handle_notify_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void control_handle_notify_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void _control_handle_notify(struct hash_bucket *hb, void *arg);
+static void control_handle_notify(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm);
+static void control_response(struct bfd_control_socket *bcs, uint16_t id,
+ const char *status, const char *error);
+
+static void _control_notify_config(struct bfd_control_socket *bcs,
+ const char *op, struct bfd_session *bs);
+static void _control_notify(struct bfd_control_socket *bcs,
+ struct bfd_session *bs);
+
+
+/*
+ * Functions
+ */
+static int sock_set_nonblock(int fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1) {
+ zlog_warn("%s: fcntl F_GETFL: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ flags |= O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) == -1) {
+ zlog_warn("%s: fcntl F_SETFL: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int control_init(const char *path)
+{
+ int sd;
+ mode_t umval;
+ struct sockaddr_un sun_ = {
+ .sun_family = AF_UNIX,
+ .sun_path = BFDD_CONTROL_SOCKET,
+ };
+
+ if (path)
+ strlcpy(sun_.sun_path, path, sizeof(sun_.sun_path));
+
+ /* Remove previously created sockets. */
+ unlink(sun_.sun_path);
+
+ sd = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC);
+ if (sd == -1) {
+ zlog_err("%s: socket: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ umval = umask(0);
+ if (bind(sd, (struct sockaddr *)&sun_, sizeof(sun_)) == -1) {
+ zlog_err("%s: bind: %s", __func__, strerror(errno));
+ close(sd);
+ return -1;
+ }
+ umask(umval);
+
+ if (listen(sd, SOMAXCONN) == -1) {
+ zlog_err("%s: listen: %s", __func__, strerror(errno));
+ close(sd);
+ return -1;
+ }
+
+ sock_set_nonblock(sd);
+
+ bglobal.bg_csock = sd;
+
+ return 0;
+}
+
+void control_shutdown(void)
+{
+ struct bfd_control_socket *bcs;
+
+ event_cancel(&bglobal.bg_csockev);
+
+ socket_close(&bglobal.bg_csock);
+
+ while (!TAILQ_EMPTY(&bglobal.bg_bcslist)) {
+ bcs = TAILQ_FIRST(&bglobal.bg_bcslist);
+ control_free(bcs);
+ }
+}
+
+void control_accept(struct event *t)
+{
+ int csock, sd = EVENT_FD(t);
+
+ csock = accept(sd, NULL, 0);
+ if (csock == -1) {
+ zlog_warn("%s: accept: %s", __func__, strerror(errno));
+ return;
+ }
+
+ control_new(csock);
+
+ event_add_read(master, control_accept, NULL, sd, &bglobal.bg_csockev);
+}
+
+
+/*
+ * Client handling
+ */
+struct bfd_control_socket *control_new(int sd)
+{
+ struct bfd_control_socket *bcs;
+
+ bcs = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bcs));
+
+ /* Disable notifications by default. */
+ bcs->bcs_notify = 0;
+
+ bcs->bcs_sd = sd;
+ event_add_read(master, control_read, bcs, sd, &bcs->bcs_ev);
+
+ TAILQ_INIT(&bcs->bcs_bcqueue);
+ TAILQ_INIT(&bcs->bcs_bnplist);
+ TAILQ_INSERT_TAIL(&bglobal.bg_bcslist, bcs, bcs_entry);
+
+ return bcs;
+}
+
+static void control_free(struct bfd_control_socket *bcs)
+{
+ struct bfd_control_queue *bcq;
+ struct bfd_notify_peer *bnp;
+
+ event_cancel(&(bcs->bcs_ev));
+ event_cancel(&(bcs->bcs_outev));
+
+ close(bcs->bcs_sd);
+
+ TAILQ_REMOVE(&bglobal.bg_bcslist, bcs, bcs_entry);
+
+ /* Empty output queue. */
+ while (!TAILQ_EMPTY(&bcs->bcs_bcqueue)) {
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ control_queue_free(bcs, bcq);
+ }
+
+ /* Empty notification list. */
+ while (!TAILQ_EMPTY(&bcs->bcs_bnplist)) {
+ bnp = TAILQ_FIRST(&bcs->bcs_bnplist);
+ control_notifypeer_free(bcs, bnp);
+ }
+
+ control_reset_buf(&bcs->bcs_bin);
+ XFREE(MTYPE_BFDD_CONTROL, bcs);
+}
+
+struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs,
+ struct bfd_session *bs)
+{
+ struct bfd_notify_peer *bnp;
+
+ bnp = control_notifypeer_find(bcs, bs);
+ if (bnp)
+ return bnp;
+
+ bnp = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bnp));
+
+ TAILQ_INSERT_TAIL(&bcs->bcs_bnplist, bnp, bnp_entry);
+ bnp->bnp_bs = bs;
+ bs->refcount++;
+
+ return bnp;
+}
+
+static void control_notifypeer_free(struct bfd_control_socket *bcs,
+ struct bfd_notify_peer *bnp)
+{
+ TAILQ_REMOVE(&bcs->bcs_bnplist, bnp, bnp_entry);
+ bnp->bnp_bs->refcount--;
+ XFREE(MTYPE_BFDD_CONTROL, bnp);
+}
+
+struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs,
+ struct bfd_session *bs)
+{
+ struct bfd_notify_peer *bnp;
+
+ TAILQ_FOREACH (bnp, &bcs->bcs_bnplist, bnp_entry) {
+ if (bnp->bnp_bs == bs)
+ return bnp;
+ }
+
+ return NULL;
+}
+
+struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs)
+{
+ struct bfd_control_queue *bcq;
+
+ bcq = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*bcq));
+
+ control_reset_buf(&bcq->bcq_bcb);
+ TAILQ_INSERT_TAIL(&bcs->bcs_bcqueue, bcq, bcq_entry);
+
+ return bcq;
+}
+
+static void control_queue_free(struct bfd_control_socket *bcs,
+ struct bfd_control_queue *bcq)
+{
+ control_reset_buf(&bcq->bcq_bcb);
+ TAILQ_REMOVE(&bcs->bcs_bcqueue, bcq, bcq_entry);
+ XFREE(MTYPE_BFDD_NOTIFICATION, bcq);
+}
+
+static int control_queue_dequeue(struct bfd_control_socket *bcs)
+{
+ struct bfd_control_queue *bcq;
+
+ /* List is empty, nothing to do. */
+ if (TAILQ_EMPTY(&bcs->bcs_bcqueue))
+ goto empty_list;
+
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ control_queue_free(bcs, bcq);
+
+ /* Get the next buffer to send. */
+ if (TAILQ_EMPTY(&bcs->bcs_bcqueue))
+ goto empty_list;
+
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ bcs->bcs_bout = &bcq->bcq_bcb;
+
+ bcs->bcs_outev = NULL;
+ event_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+
+ return 1;
+
+empty_list:
+ event_cancel(&(bcs->bcs_outev));
+ bcs->bcs_bout = NULL;
+ return 0;
+}
+
+static int control_queue_enqueue(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ struct bfd_control_queue *bcq;
+ struct bfd_control_buffer *bcb;
+
+ bcq = control_queue_new(bcs);
+
+ bcb = &bcq->bcq_bcb;
+ bcb->bcb_left = sizeof(struct bfd_control_msg) + ntohl(bcm->bcm_length);
+ bcb->bcb_pos = 0;
+ bcb->bcb_bcm = bcm;
+
+ /* If this is the first item, then dequeue and start using it. */
+ if (bcs->bcs_bout == NULL) {
+ bcs->bcs_bout = bcb;
+
+ /* New messages, active write events. */
+ event_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+ }
+
+ return 0;
+}
+
+static int control_queue_enqueue_first(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ struct bfd_control_queue *bcq, *bcqn;
+ struct bfd_control_buffer *bcb;
+
+ /* Enqueue it somewhere. */
+ if (control_queue_enqueue(bcs, bcm) == -1)
+ return -1;
+
+ /*
+ * The item is either the first or the last. So we must first
+ * check the best case where the item is already the first.
+ */
+ bcq = TAILQ_FIRST(&bcs->bcs_bcqueue);
+ bcb = &bcq->bcq_bcb;
+ if (bcm == bcb->bcb_bcm)
+ return 0;
+
+ /*
+ * The item was not the first, so it is the last. We'll try to
+ * assign it to the head of the queue, however if there is a
+ * transfer in progress, then we have to make the item as the
+ * next one.
+ *
+ * Interrupting the transfer of in progress message will cause
+ * the client to lose track of the message position/data.
+ */
+ bcqn = TAILQ_LAST(&bcs->bcs_bcqueue, bcqueue);
+ TAILQ_REMOVE(&bcs->bcs_bcqueue, bcqn, bcq_entry);
+ if (bcb->bcb_pos != 0) {
+ /*
+ * First position is already being sent, insert into
+ * second position.
+ */
+ TAILQ_INSERT_AFTER(&bcs->bcs_bcqueue, bcq, bcqn, bcq_entry);
+ } else {
+ /*
+ * Old message didn't start being sent, we still have
+ * time to put this one in the head of the queue.
+ */
+ TAILQ_INSERT_HEAD(&bcs->bcs_bcqueue, bcqn, bcq_entry);
+ bcb = &bcqn->bcq_bcb;
+ bcs->bcs_bout = bcb;
+ }
+
+ return 0;
+}
+
+static void control_reset_buf(struct bfd_control_buffer *bcb)
+{
+ /* Get ride of old data. */
+ XFREE(MTYPE_BFDD_NOTIFICATION, bcb->bcb_buf);
+ bcb->bcb_pos = 0;
+ bcb->bcb_left = 0;
+}
+
+static void control_read(struct event *t)
+{
+ struct bfd_control_socket *bcs = EVENT_ARG(t);
+ struct bfd_control_buffer *bcb = &bcs->bcs_bin;
+ int sd = bcs->bcs_sd;
+ struct bfd_control_msg bcm;
+ ssize_t bread;
+ size_t plen;
+
+ /*
+ * Check if we have already downloaded message content, if so then skip
+ * to
+ * download the rest of it and process.
+ *
+ * Otherwise download a new message header and allocate the necessary
+ * memory.
+ */
+ if (bcb->bcb_buf != NULL)
+ goto skip_header;
+
+ bread = read(sd, &bcm, sizeof(bcm));
+ if (bread == 0) {
+ control_free(bcs);
+ return;
+ }
+ if (bread < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ goto schedule_next_read;
+
+ zlog_warn("%s: read: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return;
+ }
+
+ /* Validate header fields. */
+ plen = ntohl(bcm.bcm_length);
+ if (plen < 2) {
+ zlog_debug("%s: client closed due small message length: %d",
+ __func__, bcm.bcm_length);
+ control_free(bcs);
+ return;
+ }
+
+#define FRR_BFD_MAXLEN 10 * 1024
+
+ if (plen > FRR_BFD_MAXLEN) {
+ zlog_debug("%s: client closed, invalid message length: %d",
+ __func__, bcm.bcm_length);
+ control_free(bcs);
+ return;
+ }
+
+ if (bcm.bcm_ver != BMV_VERSION_1) {
+ zlog_debug("%s: client closed due bad version: %d", __func__,
+ bcm.bcm_ver);
+ control_free(bcs);
+ return;
+ }
+
+ /* Prepare the buffer to load the message. */
+ bcs->bcs_version = bcm.bcm_ver;
+ bcs->bcs_type = bcm.bcm_type;
+
+ bcb->bcb_pos = sizeof(bcm);
+ bcb->bcb_left = plen;
+ bcb->bcb_buf = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(bcm) + bcb->bcb_left + 1);
+ if (bcb->bcb_buf == NULL) {
+ zlog_warn("%s: not enough memory for message size: %zu",
+ __func__, bcb->bcb_left);
+ control_free(bcs);
+ return;
+ }
+
+ memcpy(bcb->bcb_buf, &bcm, sizeof(bcm));
+
+ /* Terminate data string with NULL for later processing. */
+ bcb->bcb_buf[sizeof(bcm) + bcb->bcb_left] = 0;
+
+skip_header:
+ /* Download the remaining data of the message and process it. */
+ bread = read(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left);
+ if (bread == 0) {
+ control_free(bcs);
+ return;
+ }
+ if (bread < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ goto schedule_next_read;
+
+ zlog_warn("%s: read: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return;
+ }
+
+ bcb->bcb_pos += bread;
+ bcb->bcb_left -= bread;
+ /* We need more data, return to wait more. */
+ if (bcb->bcb_left > 0)
+ goto schedule_next_read;
+
+ switch (bcb->bcb_bcm->bcm_type) {
+ case BMT_REQUEST_ADD:
+ control_handle_request_add(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_REQUEST_DEL:
+ control_handle_request_del(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_NOTIFY:
+ control_handle_notify(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_NOTIFY_ADD:
+ control_handle_notify_add(bcs, bcb->bcb_bcm);
+ break;
+ case BMT_NOTIFY_DEL:
+ control_handle_notify_del(bcs, bcb->bcb_bcm);
+ break;
+
+ default:
+ zlog_debug("%s: unhandled message type: %d", __func__,
+ bcb->bcb_bcm->bcm_type);
+ control_response(bcs, bcb->bcb_bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "invalid message type");
+ break;
+ }
+
+ bcs->bcs_version = 0;
+ bcs->bcs_type = 0;
+ control_reset_buf(bcb);
+
+schedule_next_read:
+ bcs->bcs_ev = NULL;
+ event_add_read(master, control_read, bcs, sd, &bcs->bcs_ev);
+}
+
+static void control_write(struct event *t)
+{
+ struct bfd_control_socket *bcs = EVENT_ARG(t);
+ struct bfd_control_buffer *bcb = bcs->bcs_bout;
+ int sd = bcs->bcs_sd;
+ ssize_t bwrite;
+
+ bwrite = write(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left);
+ if (bwrite == 0) {
+ control_free(bcs);
+ return;
+ }
+ if (bwrite < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ bcs->bcs_outev = NULL;
+ event_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+ return;
+ }
+
+ zlog_warn("%s: write: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return;
+ }
+
+ bcb->bcb_pos += bwrite;
+ bcb->bcb_left -= bwrite;
+ if (bcb->bcb_left > 0) {
+ bcs->bcs_outev = NULL;
+ event_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+ return;
+ }
+
+ control_queue_dequeue(bcs);
+}
+
+
+/*
+ * Message processing
+ */
+static void control_handle_request_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_request_add(json) == 0)
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ else
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "request add failed");
+}
+
+static void control_handle_request_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_request_del(json) == 0)
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ else
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "request del failed");
+}
+
+static struct bfd_session *_notify_find_peer(struct bfd_peer_cfg *bpc)
+{
+ struct peer_label *pl;
+
+ if (bpc->bpc_has_label) {
+ pl = pl_find(bpc->bpc_label);
+ if (pl)
+ return pl->pl_bs;
+ }
+
+ return bs_peer_find(bpc);
+}
+
+static void _control_handle_notify(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_control_socket *bcs = arg;
+ struct bfd_session *bs = hb->data;
+
+ /* Notify peer configuration. */
+ if (bcs->bcs_notify & BCM_NOTIFY_CONFIG)
+ _control_notify_config(bcs, BCM_NOTIFY_CONFIG_ADD, bs);
+
+ /* Notify peer status. */
+ if (bcs->bcs_notify & BCM_NOTIFY_PEER_STATE)
+ _control_notify(bcs, bs);
+}
+
+static void control_handle_notify(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ memcpy(&bcs->bcs_notify, bcm->bcm_data, sizeof(bcs->bcs_notify));
+
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+
+ /*
+ * If peer asked for notification configuration, send everything that
+ * was configured until the moment to sync up.
+ */
+ if (bcs->bcs_notify & (BCM_NOTIFY_CONFIG | BCM_NOTIFY_PEER_STATE))
+ bfd_id_iterate(_control_handle_notify, bcs);
+}
+
+static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg)
+{
+ struct bfd_control_socket *bcs = arg;
+ struct bfd_session *bs = _notify_find_peer(bpc);
+
+ if (bs == NULL)
+ return -1;
+
+ control_notifypeer_new(bcs, bs);
+
+ /* Notify peer status. */
+ _control_notify(bcs, bs);
+
+ return 0;
+}
+
+static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg)
+{
+ struct bfd_control_socket *bcs = arg;
+ struct bfd_session *bs = _notify_find_peer(bpc);
+ struct bfd_notify_peer *bnp;
+
+ if (bs == NULL)
+ return -1;
+
+ bnp = control_notifypeer_find(bcs, bs);
+ if (bnp)
+ control_notifypeer_free(bcs, bnp);
+
+ return 0;
+}
+
+static void control_handle_notify_add(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_notify_request(bcs, json, notify_add_cb) == 0) {
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ return;
+ }
+
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "failed to parse notify data");
+}
+
+static void control_handle_notify_del(struct bfd_control_socket *bcs,
+ struct bfd_control_msg *bcm)
+{
+ const char *json = (const char *)bcm->bcm_data;
+
+ if (config_notify_request(bcs, json, notify_del_cb) == 0) {
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL);
+ return;
+ }
+
+ control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR,
+ "failed to parse notify data");
+}
+
+
+/*
+ * Internal functions used by the BFD daemon.
+ */
+static void control_response(struct bfd_control_socket *bcs, uint16_t id,
+ const char *status, const char *error)
+{
+ struct bfd_control_msg *bcm;
+ char *jsonstr;
+ size_t jsonstrlen;
+
+ /* Generate JSON response. */
+ jsonstr = config_response(status, error);
+ if (jsonstr == NULL) {
+ zlog_warn("%s: config_response: failed to get JSON str",
+ __func__);
+ return;
+ }
+
+ /* Allocate data and answer. */
+ jsonstrlen = strlen(jsonstr);
+ bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(struct bfd_control_msg) + jsonstrlen);
+
+ bcm->bcm_length = htonl(jsonstrlen);
+ bcm->bcm_ver = BMV_VERSION_1;
+ bcm->bcm_type = BMT_RESPONSE;
+ bcm->bcm_id = id;
+ memcpy(bcm->bcm_data, jsonstr, jsonstrlen);
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+
+ control_queue_enqueue_first(bcs, bcm);
+}
+
+static void _control_notify(struct bfd_control_socket *bcs,
+ struct bfd_session *bs)
+{
+ struct bfd_control_msg *bcm;
+ char *jsonstr;
+ size_t jsonstrlen;
+
+ /* Generate JSON response. */
+ jsonstr = config_notify(bs);
+ if (jsonstr == NULL) {
+ zlog_warn("%s: config_notify: failed to get JSON str",
+ __func__);
+ return;
+ }
+
+ /* Allocate data and answer. */
+ jsonstrlen = strlen(jsonstr);
+ bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(struct bfd_control_msg) + jsonstrlen);
+
+ bcm->bcm_length = htonl(jsonstrlen);
+ bcm->bcm_ver = BMV_VERSION_1;
+ bcm->bcm_type = BMT_NOTIFY;
+ bcm->bcm_id = htons(BCM_NOTIFY_ID);
+ memcpy(bcm->bcm_data, jsonstr, jsonstrlen);
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+
+ control_queue_enqueue(bcs, bcm);
+}
+
+int control_notify(struct bfd_session *bs, uint8_t notify_state)
+{
+ struct bfd_control_socket *bcs;
+ struct bfd_notify_peer *bnp;
+
+ /* Notify zebra listeners as well. */
+ ptm_bfd_notify(bs, notify_state);
+
+ /*
+ * PERFORMANCE: reuse the bfd_control_msg allocated data for
+ * all control sockets to avoid wasting memory.
+ */
+ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) {
+ /*
+ * Test for all notifications first, then search for
+ * specific peers.
+ */
+ if ((bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) == 0) {
+ bnp = control_notifypeer_find(bcs, bs);
+ /*
+ * If the notification is not configured here,
+ * don't send it.
+ */
+ if (bnp == NULL)
+ continue;
+ }
+
+ _control_notify(bcs, bs);
+ }
+
+ return 0;
+}
+
+static void _control_notify_config(struct bfd_control_socket *bcs,
+ const char *op, struct bfd_session *bs)
+{
+ struct bfd_control_msg *bcm;
+ char *jsonstr;
+ size_t jsonstrlen;
+
+ /* Generate JSON response. */
+ jsonstr = config_notify_config(op, bs);
+ if (jsonstr == NULL) {
+ zlog_warn("%s: config_notify_config: failed to get JSON str",
+ __func__);
+ return;
+ }
+
+ /* Allocate data and answer. */
+ jsonstrlen = strlen(jsonstr);
+ bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION,
+ sizeof(struct bfd_control_msg) + jsonstrlen);
+
+ bcm->bcm_length = htonl(jsonstrlen);
+ bcm->bcm_ver = BMV_VERSION_1;
+ bcm->bcm_type = BMT_NOTIFY;
+ bcm->bcm_id = htons(BCM_NOTIFY_ID);
+ memcpy(bcm->bcm_data, jsonstr, jsonstrlen);
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+
+ control_queue_enqueue(bcs, bcm);
+}
+
+int control_notify_config(const char *op, struct bfd_session *bs)
+{
+ struct bfd_control_socket *bcs;
+ struct bfd_notify_peer *bnp;
+
+ /* Remove the control sockets notification for this peer. */
+ if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0 && bs->refcount > 0) {
+ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) {
+ bnp = control_notifypeer_find(bcs, bs);
+ if (bnp)
+ control_notifypeer_free(bcs, bnp);
+ }
+ }
+
+ /*
+ * PERFORMANCE: reuse the bfd_control_msg allocated data for
+ * all control sockets to avoid wasting memory.
+ */
+ TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) {
+ /*
+ * Test for all notifications first, then search for
+ * specific peers.
+ */
+ if ((bcs->bcs_notify & BCM_NOTIFY_CONFIG) == 0)
+ continue;
+
+ _control_notify_config(bcs, op, bs);
+ }
+
+ return 0;
+}
diff --git a/bfdd/dplane.c b/bfdd/dplane.c
new file mode 100644
index 0000000..d853981
--- /dev/null
+++ b/bfdd/dplane.c
@@ -0,0 +1,1176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD data plane implementation (distributed BFD).
+ *
+ * Copyright (C) 2020 Network Device Education Foundation, Inc. ("NetDEF")
+ * Rafael Zalamena
+ */
+
+#include <zebra.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifdef __FreeBSD__
+#include <sys/endian.h>
+#else
+#include <endian.h>
+#endif /* __FreeBSD__ */
+
+#include <errno.h>
+#include <time.h>
+
+#include "lib/hook.h"
+#include "lib/network.h"
+#include "lib/printfrr.h"
+#include "lib/stream.h"
+#include "lib/frrevent.h"
+
+#include "bfd.h"
+#include "bfddp_packet.h"
+
+#include "lib/openbsd-queue.h"
+
+DEFINE_MTYPE_STATIC(BFDD, BFDD_DPLANE_CTX,
+ "Data plane client allocated memory");
+
+/** Data plane client socket buffer size. */
+#define BFD_DPLANE_CLIENT_BUF_SIZE 8192
+
+struct bfd_dplane_ctx {
+ /** Client file descriptor. */
+ int sock;
+ /** Is this a connected or accepted? */
+ bool client;
+ /** Is the socket still connecting? */
+ bool connecting;
+ /** Client/server address. */
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_un sun;
+ } addr;
+ /** Address length. */
+ socklen_t addrlen;
+ /** Data plane current last used ID. */
+ uint16_t last_id;
+
+ /** Input buffer data. */
+ struct stream *inbuf;
+ /** Output buffer data. */
+ struct stream *outbuf;
+ /** Input event data. */
+ struct event *inbufev;
+ /** Output event data. */
+ struct event *outbufev;
+ /** Connection event. */
+ struct event *connectev;
+
+ /** Amount of bytes read. */
+ uint64_t in_bytes;
+ /** Amount of bytes read peak. */
+ uint64_t in_bytes_peak;
+ /** Amount of bytes written. */
+ uint64_t out_bytes;
+ /** Amount of bytes written peak. */
+ uint64_t out_bytes_peak;
+ /** Amount of output buffer full events (`bfd_dplane_enqueue` failed).
+ */
+ uint64_t out_fullev;
+
+ /** Amount of messages read (full messages). */
+ uint64_t in_msgs;
+ /** Amount of messages enqueued (maybe written). */
+ uint64_t out_msgs;
+
+ TAILQ_ENTRY(bfd_dplane_ctx) entry;
+};
+
+/**
+ * Callback type for `bfd_dplane_expect`. \see bfd_dplane_expect.
+ */
+typedef void (*bfd_dplane_expect_cb)(struct bfddp_message *msg, void *arg);
+
+static void bfd_dplane_client_connect(struct event *t);
+static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc);
+static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc);
+static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc,
+ struct bfd_session *bs);
+
+/*
+ * BFD data plane helper functions.
+ */
+static const char *bfd_dplane_messagetype2str(enum bfddp_message_type bmt)
+{
+ switch (bmt) {
+ case ECHO_REQUEST:
+ return "ECHO_REQUEST";
+ case ECHO_REPLY:
+ return "ECHO_REPLY";
+ case DP_ADD_SESSION:
+ return "DP_ADD_SESSION";
+ case DP_DELETE_SESSION:
+ return "DP_DELETE_SESSION";
+ case BFD_STATE_CHANGE:
+ return "BFD_STATE_CHANGE";
+ case DP_REQUEST_SESSION_COUNTERS:
+ return "DP_REQUEST_SESSION_COUNTERS";
+ case BFD_SESSION_COUNTERS:
+ return "BFD_SESSION_COUNTERS";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void bfd_dplane_debug_message(const struct bfddp_message *msg)
+{
+ enum bfddp_message_type bmt;
+ char buf[256], addrs[256];
+ uint32_t flags;
+ int rv;
+
+ if (!bglobal.debug_dplane)
+ return;
+
+ bmt = ntohs(msg->header.type);
+ zlog_debug("dplane-packet: [version=%d length=%d type=%s (%d)]",
+ msg->header.version, ntohs(msg->header.length),
+ bfd_dplane_messagetype2str(bmt), bmt);
+
+ switch (bmt) {
+ case ECHO_REPLY:
+ case ECHO_REQUEST:
+ zlog_debug(" [dp_time=%" PRIu64 " bfdd_time=%" PRIu64 "]",
+ be64toh(msg->data.echo.dp_time),
+ be64toh(msg->data.echo.bfdd_time));
+ break;
+
+ case DP_ADD_SESSION:
+ case DP_DELETE_SESSION:
+ flags = ntohl(msg->data.session.flags);
+ if (flags & SESSION_IPV6)
+ snprintfrr(addrs, sizeof(addrs), "src=%pI6 dst=%pI6",
+ &msg->data.session.src,
+ &msg->data.session.dst);
+ else
+ snprintfrr(addrs, sizeof(addrs), "src=%pI4 dst=%pI4",
+ (struct in_addr *)&msg->data.session.src,
+ (struct in_addr *)&msg->data.session.dst);
+
+ buf[0] = 0;
+ if (flags & SESSION_CBIT)
+ strlcat(buf, "cpi ", sizeof(buf));
+ if (flags & SESSION_ECHO)
+ strlcat(buf, "echo ", sizeof(buf));
+ if (flags & SESSION_IPV6)
+ strlcat(buf, "ipv6 ", sizeof(buf));
+ if (flags & SESSION_DEMAND)
+ strlcat(buf, "demand ", sizeof(buf));
+ if (flags & SESSION_PASSIVE)
+ strlcat(buf, "passive ", sizeof(buf));
+ if (flags & SESSION_MULTIHOP)
+ strlcat(buf, "multihop ", sizeof(buf));
+ if (flags & SESSION_SHUTDOWN)
+ strlcat(buf, "shutdown ", sizeof(buf));
+
+ /* Remove the last space to make things prettier. */
+ rv = (int)strlen(buf);
+ if (rv > 0)
+ buf[rv - 1] = 0;
+
+ zlog_debug(
+ " [flags=0x%08x{%s} %s ttl=%d detect_mult=%d "
+ "ifindex=%d ifname=%s]",
+ flags, buf, addrs, msg->data.session.ttl,
+ msg->data.session.detect_mult,
+ ntohl(msg->data.session.ifindex),
+ msg->data.session.ifname);
+ break;
+
+ case BFD_STATE_CHANGE:
+ buf[0] = 0;
+ flags = ntohl(msg->data.state.remote_flags);
+ if (flags & RBIT_CPI)
+ strlcat(buf, "cbit ", sizeof(buf));
+ if (flags & RBIT_DEMAND)
+ strlcat(buf, "demand ", sizeof(buf));
+ if (flags & RBIT_MP)
+ strlcat(buf, "mp ", sizeof(buf));
+
+ /* Remove the last space to make things prettier. */
+ rv = (int)strlen(buf);
+ if (rv > 0)
+ buf[rv - 1] = 0;
+
+ zlog_debug(
+ " [lid=%u rid=%u flags=0x%02x{%s} state=%s "
+ "diagnostics=%s mult=%d tx=%u rx=%u erx=%u]",
+ ntohl(msg->data.state.lid), ntohl(msg->data.state.rid),
+ flags, buf, state_list[msg->data.state.state].str,
+ diag2str(msg->data.state.diagnostics),
+ msg->data.state.detection_multiplier,
+ ntohl(msg->data.state.desired_tx),
+ ntohl(msg->data.state.required_rx),
+ ntohl(msg->data.state.required_echo_rx));
+ break;
+
+ case DP_REQUEST_SESSION_COUNTERS:
+ zlog_debug(" [lid=%u]", ntohl(msg->data.counters_req.lid));
+ break;
+
+ case BFD_SESSION_COUNTERS:
+ zlog_debug(
+ " [lid=%u "
+ "control{in %" PRIu64 " bytes (%" PRIu64
+ " packets), "
+ "out %" PRIu64 " bytes (%" PRIu64
+ " packets)} "
+ "echo{in %" PRIu64 " bytes (%" PRIu64
+ " packets), "
+ "out %" PRIu64 " bytes (%" PRIu64 " packets)}]",
+ ntohl(msg->data.session_counters.lid),
+ be64toh(msg->data.session_counters.control_input_bytes),
+ be64toh(msg->data.session_counters
+ .control_input_packets),
+ be64toh(msg->data.session_counters
+ .control_output_bytes),
+ be64toh(msg->data.session_counters
+ .control_output_packets),
+ be64toh(msg->data.session_counters.echo_input_bytes),
+ be64toh(msg->data.session_counters.echo_input_packets),
+ be64toh(msg->data.session_counters.echo_output_bytes),
+ be64toh(msg->data.session_counters
+ .echo_output_packets));
+ break;
+ }
+}
+
+/**
+ * Gets the next unused non zero identification.
+ *
+ * \param bdc the data plane context.
+ *
+ * \returns next usable id.
+ */
+static uint16_t bfd_dplane_next_id(struct bfd_dplane_ctx *bdc)
+{
+ bdc->last_id++;
+
+ /* Don't use reserved id `0`. */
+ if (bdc->last_id == 0)
+ bdc->last_id = 1;
+
+ return bdc->last_id;
+}
+
+static ssize_t bfd_dplane_flush(struct bfd_dplane_ctx *bdc)
+{
+ ssize_t total = 0;
+ int rv;
+
+ while (STREAM_READABLE(bdc->outbuf)) {
+ /* Flush buffer contents to socket. */
+ rv = stream_flush(bdc->outbuf, bdc->sock);
+ if (rv == -1) {
+ /* Interruption: try again. */
+ if (errno == EAGAIN || errno == EWOULDBLOCK
+ || errno == EINTR)
+ continue;
+
+ zlog_warn("%s: socket failed: %s", __func__,
+ strerror(errno));
+ bfd_dplane_ctx_free(bdc);
+ return 0;
+ }
+ if (rv == 0) {
+ if (bglobal.debug_dplane)
+ zlog_info("%s: connection closed", __func__);
+
+ bfd_dplane_ctx_free(bdc);
+ return 0;
+ }
+
+ /* Account total written. */
+ total += rv;
+
+ /* Account output bytes. */
+ bdc->out_bytes += (uint64_t)rv;
+
+ /* Forward pointer. */
+ stream_forward_getp(bdc->outbuf, (size_t)rv);
+ }
+
+ /* Make more space for new data. */
+ stream_pulldown(bdc->outbuf);
+
+ /* Disable write ready events. */
+ EVENT_OFF(bdc->outbufev);
+
+ return total;
+}
+
+static void bfd_dplane_write(struct event *t)
+{
+ struct bfd_dplane_ctx *bdc = EVENT_ARG(t);
+
+ /* Handle connection stage. */
+ if (bdc->connecting && bfd_dplane_client_connecting(bdc))
+ return;
+
+ bfd_dplane_flush(bdc);
+}
+
+static void
+bfd_dplane_session_state_change(struct bfd_dplane_ctx *bdc,
+ const struct bfddp_state_change *state)
+{
+ struct bfd_session *bs;
+ uint32_t flags;
+ int old_state;
+
+ /* Look up session. */
+ bs = bfd_id_lookup(ntohl(state->lid));
+ if (bs == NULL) {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: failed to find session to update",
+ __func__);
+ return;
+ }
+
+ flags = ntohl(state->remote_flags);
+ old_state = bs->ses_state;
+
+ /* Update session state. */
+ bs->ses_state = state->state;
+ bs->remote_diag = state->diagnostics;
+ bs->discrs.remote_discr = ntohl(state->rid);
+ bs->remote_cbit = !!(flags & RBIT_CPI);
+ bs->remote_detect_mult = state->detection_multiplier;
+ bs->remote_timers.desired_min_tx = ntohl(state->desired_tx);
+ bs->remote_timers.required_min_rx = ntohl(state->required_rx);
+ bs->remote_timers.required_min_echo = ntohl(state->required_echo_rx);
+
+ /* Notify and update counters. */
+ control_notify(bs, bs->ses_state);
+
+ /* No state change. */
+ if (old_state == bs->ses_state)
+ return;
+
+ switch (bs->ses_state) {
+ case PTM_BFD_ADM_DOWN:
+ case PTM_BFD_DOWN:
+ /* Both states mean down. */
+ if (old_state == PTM_BFD_ADM_DOWN || old_state == PTM_BFD_DOWN)
+ break;
+
+ monotime(&bs->downtime);
+ bs->stats.session_down++;
+ break;
+ case PTM_BFD_UP:
+ monotime(&bs->uptime);
+ bs->stats.session_up++;
+ break;
+ case PTM_BFD_INIT:
+ /* NOTHING */
+ break;
+
+ default:
+ zlog_warn("%s: unhandled new state %d", __func__,
+ bs->ses_state);
+ break;
+ }
+
+ if (bglobal.debug_peer_event)
+ zlog_debug("state-change: [data plane: %s] %s -> %s",
+ bs_to_string(bs), state_list[old_state].str,
+ state_list[bs->ses_state].str);
+}
+
+/**
+ * Enqueue message in output buffer.
+ *
+ * \param[in,out] bdc data plane client context.
+ * \param[in] buf the message to buffer.
+ * \param[in] buflen the amount of bytes to buffer.
+ *
+ * \returns `-1` on failure (buffer full) or `0` on success.
+ */
+static int bfd_dplane_enqueue(struct bfd_dplane_ctx *bdc, const void *buf,
+ size_t buflen)
+{
+ size_t rlen;
+
+ /* Handle not connected yet client. */
+ if (bdc->client && bdc->sock == -1)
+ return -1;
+
+ /* Not enough space. */
+ if (buflen > STREAM_WRITEABLE(bdc->outbuf)) {
+ bdc->out_fullev++;
+ return -1;
+ }
+
+ /* Show debug message if active. */
+ bfd_dplane_debug_message((struct bfddp_message *)buf);
+
+ /* Buffer the message. */
+ stream_write(bdc->outbuf, buf, buflen);
+
+ /* Account message as sent. */
+ bdc->out_msgs++;
+ /* Register peak buffered bytes. */
+ rlen = STREAM_READABLE(bdc->outbuf);
+ if (bdc->out_bytes_peak < rlen)
+ bdc->out_bytes_peak = rlen;
+
+ /* Schedule if it is not yet. */
+ if (bdc->outbufev == NULL)
+ event_add_write(master, bfd_dplane_write, bdc, bdc->sock,
+ &bdc->outbufev);
+
+ return 0;
+}
+
+static void bfd_dplane_echo_request_handle(struct bfd_dplane_ctx *bdc,
+ const struct bfddp_message *bm)
+{
+ struct bfddp_message msg = {};
+ uint16_t msglen = sizeof(msg.header) + sizeof(msg.data.echo);
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+
+ /* Prepare header. */
+ msg.header.version = BFD_DP_VERSION;
+ msg.header.type = htons(ECHO_REPLY);
+ msg.header.length = htons(msglen);
+
+ /* Prepare payload. */
+ msg.data.echo.dp_time = bm->data.echo.dp_time;
+ msg.data.echo.bfdd_time =
+ htobe64((uint64_t)((tv.tv_sec * 1000000) + tv.tv_usec));
+
+ /* Enqueue for output. */
+ bfd_dplane_enqueue(bdc, &msg, msglen);
+}
+
+static void bfd_dplane_handle_message(struct bfddp_message *msg, void *arg)
+{
+ enum bfddp_message_type bmt;
+ struct bfd_dplane_ctx *bdc = arg;
+
+ /* Call the appropriated handler. */
+ bmt = ntohs(msg->header.type);
+ switch (bmt) {
+ case ECHO_REQUEST:
+ bfd_dplane_echo_request_handle(bdc, msg);
+ break;
+ case BFD_STATE_CHANGE:
+ bfd_dplane_session_state_change(bdc, &msg->data.state);
+ break;
+ case ECHO_REPLY:
+ /* NOTHING: we don't do anything with this information. */
+ break;
+ case DP_ADD_SESSION:
+ case DP_DELETE_SESSION:
+ case DP_REQUEST_SESSION_COUNTERS:
+ /* NOTHING: we are not supposed to receive this. */
+ break;
+ case BFD_SESSION_COUNTERS:
+ /*
+ * NOTHING: caller of DP_REQUEST_SESSION_COUNTERS should
+ * handle this with `bfd_dplane_expect`.
+ */
+ break;
+
+ default:
+ zlog_debug("%s: unhandled message type %d", __func__, bmt);
+ break;
+ }
+}
+
+/**
+ * Reads the socket immediately to receive data plane answer to query.
+ *
+ * \param bdc the data plane context.
+ * \param id the message ID waiting response.
+ * \param cb the callback to call when ready.
+ * \param arg the callback argument.
+ *
+ * \return
+ * `-2` on unavailability (try again), `-1` on failure or `0` on success.
+ */
+static int bfd_dplane_expect(struct bfd_dplane_ctx *bdc, uint16_t id,
+ bfd_dplane_expect_cb cb, void *arg)
+{
+ struct bfddp_message_header *bh;
+ size_t rlen = 0, reads = 0;
+ ssize_t rv;
+
+ /*
+ * Don't attempt to read if buffer is full, otherwise we'll get a
+ * bogus 'connection closed' signal (rv == 0).
+ */
+ if (bdc->inbuf->endp == bdc->inbuf->size)
+ goto skip_read;
+
+read_again:
+ /* Attempt to read message from client. */
+ rv = stream_read_try(bdc->inbuf, bdc->sock,
+ STREAM_WRITEABLE(bdc->inbuf));
+ if (rv == 0) {
+ if (bglobal.debug_dplane)
+ zlog_info("%s: socket closed", __func__);
+
+ bfd_dplane_ctx_free(bdc);
+ return -1;
+ }
+ if (rv == -1) {
+ zlog_warn("%s: socket failed: %s", __func__, strerror(errno));
+ bfd_dplane_ctx_free(bdc);
+ return -1;
+ }
+
+ /* We got interrupted, reschedule read. */
+ if (rv == -2)
+ return -2;
+
+ /* Account read bytes. */
+ bdc->in_bytes += (uint64_t)rv;
+ /* Register peak buffered bytes. */
+ rlen = STREAM_READABLE(bdc->inbuf);
+ if (bdc->in_bytes_peak < rlen)
+ bdc->in_bytes_peak = rlen;
+
+skip_read:
+ while (rlen > 0) {
+ bh = (struct bfddp_message_header *)stream_pnt(bdc->inbuf);
+ /* Not enough data read. */
+ if (ntohs(bh->length) > rlen)
+ goto read_again;
+
+ /* Account full message read. */
+ bdc->in_msgs++;
+
+ /* Account this message as whole read for buffer reorganize. */
+ reads++;
+
+ /* Check for bad version. */
+ if (bh->version != BFD_DP_VERSION) {
+ zlog_err("%s: bad data plane client version: %d",
+ __func__, bh->version);
+ return -1;
+ }
+
+ /* Show debug message if active. */
+ bfd_dplane_debug_message((struct bfddp_message *)bh);
+
+ /*
+ * Handle incoming message with callback if the ID matches,
+ * otherwise fallback to default handler.
+ */
+ if (id && ntohs(bh->id) == id)
+ cb((struct bfddp_message *)bh, arg);
+ else
+ bfd_dplane_handle_message((struct bfddp_message *)bh,
+ bdc);
+
+ /* Advance current read pointer. */
+ stream_forward_getp(bdc->inbuf, ntohs(bh->length));
+
+ /* Reduce the buffer available bytes. */
+ rlen -= ntohs(bh->length);
+
+ /* Reorganize buffer to handle more bytes read. */
+ if (reads >= 3) {
+ stream_pulldown(bdc->inbuf);
+ reads = 0;
+ }
+
+ /* We found the message, return to caller. */
+ if (id && ntohs(bh->id) == id)
+ break;
+ }
+
+ return 0;
+}
+
+static void bfd_dplane_read(struct event *t)
+{
+ struct bfd_dplane_ctx *bdc = EVENT_ARG(t);
+ int rv;
+
+ rv = bfd_dplane_expect(bdc, 0, bfd_dplane_handle_message, NULL);
+ if (rv == -1)
+ return;
+
+ stream_pulldown(bdc->inbuf);
+ event_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev);
+}
+
+static void _bfd_session_register_dplane(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_session *bs = hb->data;
+ struct bfd_dplane_ctx *bdc = arg;
+
+ if (bs->bdc != NULL)
+ return;
+
+ /* Disable software session. */
+ bfd_session_disable(bs);
+
+ /* Move session to data plane. */
+ _bfd_dplane_add_session(bdc, bs);
+}
+
+static struct bfd_dplane_ctx *bfd_dplane_ctx_new(int sock)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ bdc = XCALLOC(MTYPE_BFDD_DPLANE_CTX, sizeof(*bdc));
+
+ bdc->sock = sock;
+ bdc->inbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE);
+ bdc->outbuf = stream_new(BFD_DPLANE_CLIENT_BUF_SIZE);
+
+ /* If not socket ready, skip read and session registration. */
+ if (sock == -1)
+ return bdc;
+
+ event_add_read(master, bfd_dplane_read, bdc, sock, &bdc->inbufev);
+
+ /* Register all unattached sessions. */
+ bfd_key_iterate(_bfd_session_register_dplane, bdc);
+
+ return bdc;
+}
+
+static void _bfd_session_unregister_dplane(struct hash_bucket *hb, void *arg)
+{
+ struct bfd_session *bs = hb->data;
+ struct bfd_dplane_ctx *bdc = arg;
+
+ if (bs->bdc != bdc)
+ return;
+
+ bs->bdc = NULL;
+
+ /* Fallback to software. */
+ bfd_session_enable(bs);
+}
+
+static void bfd_dplane_ctx_free(struct bfd_dplane_ctx *bdc)
+{
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: terminating data plane client %d", __func__,
+ bdc->sock);
+
+ /* Client mode has special treatment. */
+ if (bdc->client) {
+ /* Disable connection event if any. */
+ EVENT_OFF(bdc->connectev);
+
+ /* Normal treatment on shutdown. */
+ if (bglobal.bg_shutdown)
+ goto free_resources;
+
+ /* Attempt reconnection. */
+ socket_close(&bdc->sock);
+ EVENT_OFF(bdc->inbufev);
+ EVENT_OFF(bdc->outbufev);
+ event_add_timer(master, bfd_dplane_client_connect, bdc, 3,
+ &bdc->connectev);
+ return;
+ }
+
+free_resources:
+ /* Remove from the list of attached data planes. */
+ TAILQ_REMOVE(&bglobal.bg_dplaneq, bdc, entry);
+
+ /* Detach all associated sessions. */
+ if (bglobal.bg_shutdown == false)
+ bfd_key_iterate(_bfd_session_unregister_dplane, bdc);
+
+ /* Free resources. */
+ socket_close(&bdc->sock);
+ stream_free(bdc->inbuf);
+ stream_free(bdc->outbuf);
+ EVENT_OFF(bdc->inbufev);
+ EVENT_OFF(bdc->outbufev);
+ XFREE(MTYPE_BFDD_DPLANE_CTX, bdc);
+}
+
+static void _bfd_dplane_session_fill(const struct bfd_session *bs,
+ struct bfddp_message *msg)
+{
+ uint16_t msglen = sizeof(msg->header) + sizeof(msg->data.session);
+
+ /* Message header. */
+ msg->header.version = BFD_DP_VERSION;
+ msg->header.length = ntohs(msglen);
+ msg->header.type = ntohs(DP_ADD_SESSION);
+
+ /* Message payload. */
+ msg->data.session.dst = bs->key.peer;
+ msg->data.session.src = bs->key.local;
+ msg->data.session.detect_mult = bs->detect_mult;
+
+ if (bs->ifp) {
+ msg->data.session.ifindex = htonl(bs->ifp->ifindex);
+ strlcpy(msg->data.session.ifname, bs->ifp->name,
+ sizeof(msg->data.session.ifname));
+ }
+ if (bs->flags & BFD_SESS_FLAG_MH) {
+ msg->data.session.flags |= SESSION_MULTIHOP;
+ msg->data.session.ttl = bs->mh_ttl;
+ } else
+ msg->data.session.ttl = BFD_TTL_VAL;
+
+ if (bs->flags & BFD_SESS_FLAG_IPV6)
+ msg->data.session.flags |= SESSION_IPV6;
+ if (bs->flags & BFD_SESS_FLAG_ECHO)
+ msg->data.session.flags |= SESSION_ECHO;
+ if (bs->flags & BFD_SESS_FLAG_CBIT)
+ msg->data.session.flags |= SESSION_CBIT;
+ if (bs->flags & BFD_SESS_FLAG_PASSIVE)
+ msg->data.session.flags |= SESSION_PASSIVE;
+ if (bs->flags & BFD_SESS_FLAG_SHUTDOWN)
+ msg->data.session.flags |= SESSION_SHUTDOWN;
+
+ msg->data.session.flags = htonl(msg->data.session.flags);
+ msg->data.session.lid = htonl(bs->discrs.my_discr);
+ msg->data.session.min_tx = htonl(bs->timers.desired_min_tx);
+ msg->data.session.min_rx = htonl(bs->timers.required_min_rx);
+ msg->data.session.min_echo_tx = htonl(bs->timers.desired_min_echo_tx);
+ msg->data.session.min_echo_rx = htonl(bs->timers.required_min_echo_rx);
+}
+
+static int _bfd_dplane_add_session(struct bfd_dplane_ctx *bdc,
+ struct bfd_session *bs)
+{
+ int rv;
+
+ /* Associate session. */
+ bs->bdc = bdc;
+
+ /* Reset previous state. */
+ bs->remote_diag = 0;
+ bs->local_diag = 0;
+ bs->ses_state = PTM_BFD_DOWN;
+
+ /* Enqueue message to data plane client. */
+ rv = bfd_dplane_update_session(bs);
+ if (rv != 0)
+ bs->bdc = NULL;
+
+ return rv;
+}
+
+static void _bfd_dplane_update_session_counters(struct bfddp_message *msg,
+ void *arg)
+{
+ struct bfd_session *bs = arg;
+
+ bs->stats.rx_ctrl_pkt =
+ be64toh(msg->data.session_counters.control_input_packets);
+ bs->stats.tx_ctrl_pkt =
+ be64toh(msg->data.session_counters.control_output_packets);
+ bs->stats.rx_echo_pkt =
+ be64toh(msg->data.session_counters.echo_input_packets);
+ bs->stats.tx_echo_pkt =
+ be64toh(msg->data.session_counters.echo_output_bytes);
+}
+
+/**
+ * Send message to data plane requesting the session counters.
+ *
+ * \param bs the BFD session.
+ *
+ * \returns `0` on failure or the request id.
+ */
+static uint16_t bfd_dplane_request_counters(const struct bfd_session *bs)
+{
+ struct bfddp_message msg = {};
+ size_t msglen = sizeof(msg.header) + sizeof(msg.data.counters_req);
+
+ /* Fill header information. */
+ msg.header.version = BFD_DP_VERSION;
+ msg.header.length = htons(msglen);
+ msg.header.type = htons(DP_REQUEST_SESSION_COUNTERS);
+ msg.header.id = htons(bfd_dplane_next_id(bs->bdc));
+
+ /* Session to get counters. */
+ msg.data.counters_req.lid = htonl(bs->discrs.my_discr);
+
+ /* If enqueue failed, let caller know. */
+ if (bfd_dplane_enqueue(bs->bdc, &msg, msglen) == -1)
+ return 0;
+
+ /* Flush socket. */
+ bfd_dplane_flush(bs->bdc);
+
+ return ntohs(msg.header.id);
+}
+
+/*
+ * Data plane listening socket.
+ */
+static void bfd_dplane_accept(struct event *t)
+{
+ struct bfd_global *bg = EVENT_ARG(t);
+ struct bfd_dplane_ctx *bdc;
+ int sock;
+
+ /* Accept new connection. */
+ sock = accept(bg->bg_dplane_sock, NULL, 0);
+ if (sock == -1) {
+ zlog_warn("%s: accept failed: %s", __func__, strerror(errno));
+ goto reschedule_and_return;
+ }
+
+ /* Create and handle new connection. */
+ bdc = bfd_dplane_ctx_new(sock);
+ TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry);
+
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: new data plane client connected", __func__);
+
+reschedule_and_return:
+ event_add_read(master, bfd_dplane_accept, bg, bg->bg_dplane_sock,
+ &bglobal.bg_dplane_sockev);
+}
+
+/*
+ * Data plane connecting socket.
+ */
+static void _bfd_dplane_client_bootstrap(struct bfd_dplane_ctx *bdc)
+{
+ bdc->connecting = false;
+
+ /* Clean up buffers. */
+ stream_reset(bdc->inbuf);
+ stream_reset(bdc->outbuf);
+
+ /* Ask for read notifications. */
+ event_add_read(master, bfd_dplane_read, bdc, bdc->sock, &bdc->inbufev);
+
+ /* Remove all sessions then register again to send them all. */
+ bfd_key_iterate(_bfd_session_unregister_dplane, bdc);
+ bfd_key_iterate(_bfd_session_register_dplane, bdc);
+}
+
+static bool bfd_dplane_client_connecting(struct bfd_dplane_ctx *bdc)
+{
+ int rv;
+ socklen_t rvlen = sizeof(rv);
+
+ /* Make sure `errno` is reset, then test `getsockopt` success. */
+ errno = 0;
+ if (getsockopt(bdc->sock, SOL_SOCKET, SO_ERROR, &rv, &rvlen) == -1)
+ rv = -1;
+
+ /* Connection successful. */
+ if (rv == 0) {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: connected to server: %d", __func__,
+ bdc->sock);
+
+ _bfd_dplane_client_bootstrap(bdc);
+ return false;
+ }
+
+ switch (rv) {
+ case EINTR:
+ case EAGAIN:
+ case EALREADY:
+ case EINPROGRESS:
+ /* non error, wait more. */
+ return true;
+
+ default:
+ zlog_warn("%s: connection failed: %s", __func__,
+ strerror(errno));
+ bfd_dplane_ctx_free(bdc);
+ return true;
+ }
+}
+
+static void bfd_dplane_client_connect(struct event *t)
+{
+ struct bfd_dplane_ctx *bdc = EVENT_ARG(t);
+ int rv, sock;
+ socklen_t rvlen = sizeof(rv);
+
+ /* Allocate new socket. */
+ sock = socket(bdc->addr.sa.sa_family, SOCK_STREAM, 0);
+ if (sock == -1) {
+ zlog_warn("%s: failed to initialize socket: %s", __func__,
+ strerror(errno));
+ goto reschedule_connect;
+ }
+
+ /* Set non blocking socket. */
+ set_nonblocking(sock);
+
+ /* Set 'no delay' (disables nagle algorithm) for IPv4/IPv6. */
+ rv = 1;
+ if (bdc->addr.sa.sa_family != AF_UNIX
+ && setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &rv, rvlen) == -1)
+ zlog_warn("%s: TCP_NODELAY: %s", __func__, strerror(errno));
+
+ /* Attempt to connect. */
+ rv = connect(sock, &bdc->addr.sa, bdc->addrlen);
+ if (rv == -1 && (errno != EINPROGRESS && errno != EAGAIN)) {
+ zlog_warn("%s: data plane connection failed: %s", __func__,
+ strerror(errno));
+ goto reschedule_connect;
+ }
+
+ bdc->sock = sock;
+ if (rv == -1) {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: server connection in progress: %d",
+ __func__, sock);
+
+ /* If we are not connected yet, ask for write notifications. */
+ bdc->connecting = true;
+ event_add_write(master, bfd_dplane_write, bdc, bdc->sock,
+ &bdc->outbufev);
+ } else {
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: server connection: %d", __func__, sock);
+
+ /* Otherwise just start accepting data. */
+ _bfd_dplane_client_bootstrap(bdc);
+ }
+
+reschedule_connect:
+ EVENT_OFF(bdc->inbufev);
+ EVENT_OFF(bdc->outbufev);
+ socket_close(&sock);
+ event_add_timer(master, bfd_dplane_client_connect, bdc, 3,
+ &bdc->connectev);
+}
+
+static void bfd_dplane_client_init(const struct sockaddr *sa, socklen_t salen)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ /* Allocate context and copy address for reconnection. */
+ bdc = bfd_dplane_ctx_new(-1);
+ if (salen <= sizeof(bdc->addr)) {
+ memcpy(&bdc->addr, sa, salen);
+ bdc->addrlen = sizeof(bdc->addr);
+ } else {
+ memcpy(&bdc->addr, sa, sizeof(bdc->addr));
+ bdc->addrlen = sizeof(bdc->addr);
+ zlog_warn("%s: server address truncated (from %d to %d)",
+ __func__, salen, bdc->addrlen);
+ }
+
+ bdc->client = true;
+
+ event_add_timer(master, bfd_dplane_client_connect, bdc, 0,
+ &bdc->connectev);
+
+ /* Insert into data plane lists. */
+ TAILQ_INSERT_TAIL(&bglobal.bg_dplaneq, bdc, entry);
+}
+
+/**
+ * Termination phase of the distributed BFD infrastructure: free all allocated
+ * resources.
+ */
+static int bfd_dplane_finish_late(void)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ if (bglobal.debug_dplane)
+ zlog_debug("%s: terminating distributed BFD", __func__);
+
+ /* Free all data plane client contexts. */
+ while ((bdc = TAILQ_FIRST(&bglobal.bg_dplaneq)) != NULL)
+ bfd_dplane_ctx_free(bdc);
+
+ /* Cancel accept thread and close socket. */
+ EVENT_OFF(bglobal.bg_dplane_sockev);
+ close(bglobal.bg_dplane_sock);
+
+ return 0;
+}
+
+/*
+ * Data plane exported functions.
+ */
+void bfd_dplane_init(const struct sockaddr *sa, socklen_t salen, bool client)
+{
+ int sock;
+
+ zlog_info("initializing distributed BFD");
+
+ /* Initialize queue header. */
+ TAILQ_INIT(&bglobal.bg_dplaneq);
+
+ /* Initialize listening socket. */
+ bglobal.bg_dplane_sock = -1;
+
+ /* Observe shutdown events. */
+ hook_register(frr_fini, bfd_dplane_finish_late);
+
+ /* Handle client mode. */
+ if (client) {
+ bfd_dplane_client_init(sa, salen);
+ return;
+ }
+
+ /*
+ * Data plane socket creation:
+ * - Set REUSEADDR option for taking over previously open socket.
+ * - Bind to address requested (maybe IPv4, IPv6, UNIX etc...).
+ * - Listen on that address for new connections.
+ * - Ask to be waken up when a new connection comes.
+ */
+ sock = socket(sa->sa_family, SOCK_STREAM, 0);
+ if (sock == -1) {
+ zlog_warn("%s: failed to initialize socket: %s", __func__,
+ strerror(errno));
+ return;
+ }
+
+ if (sockopt_reuseaddr(sock) == -1) {
+ zlog_warn("%s: failed to set reuseaddr: %s", __func__,
+ strerror(errno));
+ close(sock);
+ return;
+ }
+
+ /* Handle UNIX socket: delete previous socket if any. */
+ if (sa->sa_family == AF_UNIX)
+ unlink(((struct sockaddr_un *)sa)->sun_path);
+
+ if (bind(sock, sa, salen) == -1) {
+ zlog_warn("%s: failed to bind socket: %s", __func__,
+ strerror(errno));
+ close(sock);
+ return;
+ }
+
+ if (listen(sock, SOMAXCONN) == -1) {
+ zlog_warn("%s: failed to put socket on listen: %s", __func__,
+ strerror(errno));
+ close(sock);
+ return;
+ }
+
+ bglobal.bg_dplane_sock = sock;
+ event_add_read(master, bfd_dplane_accept, &bglobal, sock,
+ &bglobal.bg_dplane_sockev);
+}
+
+int bfd_dplane_add_session(struct bfd_session *bs)
+{
+ struct bfd_dplane_ctx *bdc;
+
+ /* Select a data plane client to install session. */
+ TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) {
+ if (_bfd_dplane_add_session(bdc, bs) == 0)
+ return 0;
+ }
+
+ return -1;
+}
+
+int bfd_dplane_update_session(const struct bfd_session *bs)
+{
+ struct bfddp_message msg = {};
+
+ if (bs->bdc == NULL)
+ return 0;
+
+ _bfd_dplane_session_fill(bs, &msg);
+
+ /* Enqueue message to data plane client. */
+ return bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length));
+}
+
+int bfd_dplane_delete_session(struct bfd_session *bs)
+{
+ struct bfddp_message msg = {};
+ int rv;
+
+ /* Not using data plane, just return success. */
+ if (bs->bdc == NULL)
+ return 0;
+
+ /* Fill most of the common fields. */
+ _bfd_dplane_session_fill(bs, &msg);
+
+ /* Change the message type. */
+ msg.header.type = ntohs(DP_DELETE_SESSION);
+
+ /* Enqueue message to data plane client. */
+ rv = bfd_dplane_enqueue(bs->bdc, &msg, ntohs(msg.header.length));
+
+ /* Remove association. */
+ bs->bdc = NULL;
+
+ return rv;
+}
+
+/*
+ * Data plane CLI.
+ */
+void bfd_dplane_show_counters(struct vty *vty)
+{
+ struct bfd_dplane_ctx *bdc;
+
+#define SHOW_COUNTER(label, counter, formatter) \
+ vty_out(vty, "%28s: %" formatter "\n", (label), (counter))
+
+ vty_out(vty, "%28s\n%28s\n", "Data plane", "==========");
+ TAILQ_FOREACH (bdc, &bglobal.bg_dplaneq, entry) {
+ SHOW_COUNTER("File descriptor", bdc->sock, "d");
+ SHOW_COUNTER("Input bytes", bdc->in_bytes, PRIu64);
+ SHOW_COUNTER("Input bytes peak", bdc->in_bytes_peak, PRIu64);
+ SHOW_COUNTER("Input messages", bdc->in_msgs, PRIu64);
+ SHOW_COUNTER("Input current usage", STREAM_READABLE(bdc->inbuf),
+ "zu");
+ SHOW_COUNTER("Output bytes", bdc->out_bytes, PRIu64);
+ SHOW_COUNTER("Output bytes peak", bdc->out_bytes_peak, PRIu64);
+ SHOW_COUNTER("Output messages", bdc->out_msgs, PRIu64);
+ SHOW_COUNTER("Output full events", bdc->out_fullev, PRIu64);
+ SHOW_COUNTER("Output current usage",
+ STREAM_READABLE(bdc->inbuf), "zu");
+ vty_out(vty, "\n");
+ }
+#undef SHOW_COUNTER
+}
+
+int bfd_dplane_update_session_counters(struct bfd_session *bs)
+{
+ uint16_t id;
+ int rv;
+
+ /* If session is not using data plane, then just return success. */
+ if (bs->bdc == NULL)
+ return 0;
+
+ /* Make the request. */
+ id = bfd_dplane_request_counters(bs);
+ if (id == 0) {
+ zlog_debug("%s: counters request failed", __func__);
+ return -1;
+ }
+
+ /* Handle interruptions. */
+ do {
+ rv = bfd_dplane_expect(bs->bdc, id,
+ _bfd_dplane_update_session_counters, bs);
+ } while (rv == -2);
+
+ return rv;
+}
diff --git a/bfdd/event.c b/bfdd/event.c
new file mode 100644
index 0000000..e797e71
--- /dev/null
+++ b/bfdd/event.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * event.c: implements the BFD loop event handlers.
+ *
+ * Authors
+ * -------
+ * Rafael Zalamena <rzalamena@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include "bfd.h"
+
+void tv_normalize(struct timeval *tv);
+
+void tv_normalize(struct timeval *tv)
+{
+ /* Remove seconds part from microseconds. */
+ tv->tv_sec = tv->tv_usec / 1000000;
+ tv->tv_usec = tv->tv_usec % 1000000;
+}
+
+void bfd_recvtimer_update(struct bfd_session *bs)
+{
+ struct timeval tv = {.tv_sec = 0, .tv_usec = bs->detect_TO};
+
+ /* Remove previous schedule if any. */
+ bfd_recvtimer_delete(bs);
+
+ /* Don't add event if peer is deactivated. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) ||
+ bs->sock == -1)
+ return;
+
+ tv_normalize(&tv);
+
+ event_add_timer_tv(master, bfd_recvtimer_cb, bs, &tv,
+ &bs->recvtimer_ev);
+}
+
+void bfd_echo_recvtimer_update(struct bfd_session *bs)
+{
+ struct timeval tv = {.tv_sec = 0, .tv_usec = bs->echo_detect_TO};
+
+ /* Remove previous schedule if any. */
+ bfd_echo_recvtimer_delete(bs);
+
+ /* Don't add event if peer is deactivated. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) ||
+ bs->sock == -1)
+ return;
+
+ tv_normalize(&tv);
+
+ event_add_timer_tv(master, bfd_echo_recvtimer_cb, bs, &tv,
+ &bs->echo_recvtimer_ev);
+}
+
+void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter)
+{
+ struct timeval tv = {.tv_sec = 0, .tv_usec = jitter};
+
+ /* Remove previous schedule if any. */
+ bfd_xmttimer_delete(bs);
+
+ /* Don't add event if peer is deactivated. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) ||
+ bs->sock == -1)
+ return;
+
+ tv_normalize(&tv);
+
+ event_add_timer_tv(master, bfd_xmt_cb, bs, &tv, &bs->xmttimer_ev);
+}
+
+void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter)
+{
+ struct timeval tv = {.tv_sec = 0, .tv_usec = jitter};
+
+ /* Remove previous schedule if any. */
+ bfd_echo_xmttimer_delete(bs);
+
+ /* Don't add event if peer is deactivated. */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) ||
+ bs->sock == -1)
+ return;
+
+ tv_normalize(&tv);
+
+ event_add_timer_tv(master, bfd_echo_xmt_cb, bs, &tv,
+ &bs->echo_xmttimer_ev);
+}
+
+void bfd_recvtimer_delete(struct bfd_session *bs)
+{
+ EVENT_OFF(bs->recvtimer_ev);
+}
+
+void bfd_echo_recvtimer_delete(struct bfd_session *bs)
+{
+ EVENT_OFF(bs->echo_recvtimer_ev);
+}
+
+void bfd_xmttimer_delete(struct bfd_session *bs)
+{
+ EVENT_OFF(bs->xmttimer_ev);
+}
+
+void bfd_echo_xmttimer_delete(struct bfd_session *bs)
+{
+ EVENT_OFF(bs->echo_xmttimer_ev);
+}
diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c
new file mode 100644
index 0000000..9e7ed11
--- /dev/null
+++ b/bfdd/ptm_adapter.c
@@ -0,0 +1,980 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * BFD PTM adapter code
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ */
+
+#include <zebra.h>
+
+#include "lib/libfrr.h"
+#include "lib/queue.h"
+#include "lib/stream.h"
+#include "lib/zclient.h"
+#include "lib/printfrr.h"
+
+#include "lib/bfd.h"
+
+#include "bfd.h"
+
+/*
+ * Data structures
+ */
+struct ptm_client_notification {
+ struct bfd_session *pcn_bs;
+ struct ptm_client *pcn_pc;
+
+ TAILQ_ENTRY(ptm_client_notification) pcn_entry;
+};
+TAILQ_HEAD(pcnqueue, ptm_client_notification);
+
+struct ptm_client {
+ uint32_t pc_pid;
+ struct pcnqueue pc_pcnqueue;
+
+ TAILQ_ENTRY(ptm_client) pc_entry;
+};
+TAILQ_HEAD(pcqueue, ptm_client);
+
+static struct pcqueue pcqueue;
+static struct zclient *zclient;
+
+
+/*
+ * Prototypes
+ */
+static int _ptm_msg_address(struct stream *msg, int family, const void *addr);
+
+static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa);
+static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id,
+ struct bfd_peer_cfg *bpc, struct ptm_client **pc);
+
+static struct ptm_client *pc_lookup(uint32_t pid);
+static struct ptm_client *pc_new(uint32_t pid);
+static void pc_free(struct ptm_client *pc);
+static void pc_free_all(void);
+static struct ptm_client_notification *pcn_new(struct ptm_client *pc,
+ struct bfd_session *bs);
+static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc,
+ struct bfd_session *bs);
+static void pcn_free(struct ptm_client_notification *pcn);
+
+
+static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id);
+static void bfdd_dest_deregister(struct stream *msg, vrf_id_t vrf_id);
+static void bfdd_client_register(struct stream *msg);
+static void bfdd_client_deregister(struct stream *msg);
+
+/*
+ * Functions
+ */
+PRINTFRR(2, 3)
+static void debug_printbpc(const struct bfd_peer_cfg *bpc, const char *fmt, ...)
+{
+ char timers[3][128] = {};
+ char minttl_str[32] = {};
+ char addr[3][128] = {};
+ char profile[128] = {};
+ char cbit_str[32];
+ char msgbuf[512];
+ va_list vl;
+
+ /* Avoid debug calculations if it's disabled. */
+ if (bglobal.debug_zebra == false)
+ return;
+
+ snprintf(addr[0], sizeof(addr[0]), "peer:%s", satostr(&bpc->bpc_peer));
+ if (bpc->bpc_local.sa_sin.sin_family)
+ snprintf(addr[1], sizeof(addr[1]), " local:%s",
+ satostr(&bpc->bpc_local));
+
+ if (bpc->bpc_has_localif)
+ snprintf(addr[2], sizeof(addr[2]), " ifname:%s",
+ bpc->bpc_localif);
+
+ if (bpc->bpc_has_vrfname)
+ snprintf(addr[2], sizeof(addr[2]), " vrf:%s", bpc->bpc_vrfname);
+
+ if (bpc->bpc_has_recvinterval)
+ snprintfrr(timers[0], sizeof(timers[0]), " rx:%" PRIu64,
+ bpc->bpc_recvinterval);
+
+ if (bpc->bpc_has_txinterval)
+ snprintfrr(timers[1], sizeof(timers[1]), " tx:%" PRIu64,
+ bpc->bpc_recvinterval);
+
+ if (bpc->bpc_has_detectmultiplier)
+ snprintf(timers[2], sizeof(timers[2]), " detect-multiplier:%d",
+ bpc->bpc_detectmultiplier);
+
+ snprintf(cbit_str, sizeof(cbit_str), " cbit:0x%02x", bpc->bpc_cbit);
+
+ if (bpc->bpc_has_minimum_ttl)
+ snprintf(minttl_str, sizeof(minttl_str), " minimum-ttl:%d",
+ bpc->bpc_minimum_ttl);
+
+ if (bpc->bpc_has_profile)
+ snprintf(profile, sizeof(profile), " profile:%s",
+ bpc->bpc_profile);
+
+ va_start(vl, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, vl);
+ va_end(vl);
+
+ zlog_debug("%s [mhop:%s %s%s%s%s%s%s%s%s%s]", msgbuf,
+ bpc->bpc_mhop ? "yes" : "no", addr[0], addr[1], addr[2],
+ timers[0], timers[1], timers[2], cbit_str, minttl_str,
+ profile);
+}
+
+static void _ptm_bfd_session_del(struct bfd_session *bs, uint8_t diag)
+{
+ if (bglobal.debug_peer_event)
+ zlog_debug("session-delete: %s", bs_to_string(bs));
+
+ /* Change state and notify peer. */
+ bs->ses_state = PTM_BFD_DOWN;
+ bs->local_diag = diag;
+ ptm_bfd_snd(bs, 0);
+
+ /* Session reached refcount == 0, lets delete it. */
+ if (bs->refcount == 0) {
+ /*
+ * Sanity check: if there is a refcount bug, we can't delete
+ * the session a user configured manually. Lets leave a
+ * message here so we can catch the bug if it exists.
+ */
+ if (CHECK_FLAG(bs->flags, BFD_SESS_FLAG_CONFIG)) {
+ zlog_err(
+ "ptm-del-session: [%s] session refcount is zero but it was configured by CLI",
+ bs_to_string(bs));
+ } else {
+ control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs);
+ bfd_session_free(bs);
+ }
+ }
+}
+
+static int _ptm_msg_address(struct stream *msg, int family, const void *addr)
+{
+ stream_putc(msg, family);
+
+ switch (family) {
+ case AF_INET:
+ stream_put(msg, addr, sizeof(struct in_addr));
+ stream_putc(msg, 32);
+ break;
+
+ case AF_INET6:
+ stream_put(msg, addr, sizeof(struct in6_addr));
+ stream_putc(msg, 128);
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+
+ return 0;
+}
+
+int ptm_bfd_notify(struct bfd_session *bs, uint8_t notify_state)
+{
+ struct stream *msg;
+
+ bs->stats.znotification++;
+
+ /*
+ * Message format:
+ * - header: command, vrf
+ * - l: interface index
+ * - c: family
+ * - AF_INET:
+ * - 4 bytes: ipv4
+ * - AF_INET6:
+ * - 16 bytes: ipv6
+ * - c: prefix length
+ * - l: bfd status
+ * - c: family
+ * - AF_INET:
+ * - 4 bytes: ipv4
+ * - AF_INET6:
+ * - 16 bytes: ipv6
+ * - c: prefix length
+ * - c: cbit
+ *
+ * Commands: ZEBRA_BFD_DEST_REPLAY
+ *
+ * q(64), l(32), w(16), c(8)
+ */
+ msg = zclient->obuf;
+ stream_reset(msg);
+
+ /* TODO: VRF handling */
+ if (bs->vrf)
+ zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, bs->vrf->vrf_id);
+ else
+ zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT);
+
+ /* This header will be handled by `zebra_ptm.c`. */
+ stream_putl(msg, ZEBRA_INTERFACE_BFD_DEST_UPDATE);
+
+ /* NOTE: Interface is a shortcut to avoid comparing source address. */
+ if (!CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH) && bs->ifp != NULL)
+ stream_putl(msg, bs->ifp->ifindex);
+ else
+ stream_putl(msg, IFINDEX_INTERNAL);
+
+ /* BFD destination prefix information. */
+ _ptm_msg_address(msg, bs->key.family, &bs->key.peer);
+
+ /* BFD status */
+ switch (notify_state) {
+ case PTM_BFD_UP:
+ stream_putl(msg, BFD_STATUS_UP);
+ break;
+
+ case PTM_BFD_ADM_DOWN:
+ stream_putl(msg, BFD_STATUS_ADMIN_DOWN);
+ break;
+
+ case PTM_BFD_DOWN:
+ case PTM_BFD_INIT:
+ stream_putl(msg, BFD_STATUS_DOWN);
+ break;
+
+ default:
+ stream_putl(msg, BFD_STATUS_UNKNOWN);
+ break;
+ }
+
+ /* BFD source prefix information. */
+ _ptm_msg_address(msg, bs->key.family, &bs->key.local);
+
+ stream_putc(msg, bs->remote_cbit);
+
+ /* Write packet size. */
+ stream_putw_at(msg, 0, stream_get_endp(msg));
+
+ return zclient_send_message(zclient);
+}
+
+static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa)
+{
+ uint16_t family;
+
+ STREAM_GETW(msg, family);
+
+ switch (family) {
+ case AF_INET:
+ sa->sa_sin.sin_family = family;
+ STREAM_GET(&sa->sa_sin.sin_addr, msg,
+ sizeof(sa->sa_sin.sin_addr));
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sa->sa_sin.sin_len = sizeof(sa->sa_sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ return;
+
+ case AF_INET6:
+ sa->sa_sin6.sin6_family = family;
+ STREAM_GET(&sa->sa_sin6.sin6_addr, msg,
+ sizeof(sa->sa_sin6.sin6_addr));
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ return;
+
+ default:
+ zlog_warn("ptm-read-address: invalid family: %d", family);
+ break;
+ }
+
+stream_failure:
+ memset(sa, 0, sizeof(*sa));
+}
+
+static int _ptm_msg_read(struct stream *msg, int command, vrf_id_t vrf_id,
+ struct bfd_peer_cfg *bpc, struct ptm_client **pc)
+{
+ uint32_t pid;
+ size_t ifnamelen;
+
+ /*
+ * Register/Deregister/Update Message format:
+ *
+ * Old format (being used by PTM BFD).
+ * - header: Command, VRF
+ * - l: pid
+ * - w: family
+ * - AF_INET:
+ * - l: destination ipv4
+ * - AF_INET6:
+ * - 16 bytes: destination IPv6
+ * - command != ZEBRA_BFD_DEST_DEREGISTER
+ * - l: min_rx
+ * - l: min_tx
+ * - c: detect multiplier
+ * - c: is_multihop?
+ * - multihop:
+ * - w: family
+ * - AF_INET:
+ * - l: source IPv4 address
+ * - AF_INET6:
+ * - 16 bytes: source IPv6 address
+ * - c: ttl
+ * - no multihop
+ * - AF_INET6:
+ * - w: family
+ * - 16 bytes: source IPv6 address
+ * - c: ifname length
+ * - X bytes: interface name
+ *
+ * New format:
+ * - header: Command, VRF
+ * - l: pid
+ * - w: family
+ * - AF_INET:
+ * - l: destination IPv4 address
+ * - AF_INET6:
+ * - 16 bytes: destination IPv6 address
+ * - l: min_rx
+ * - l: min_tx
+ * - c: detect multiplier
+ * - c: is_multihop?
+ * - w: family
+ * - AF_INET:
+ * - l: source IPv4 address
+ * - AF_INET6:
+ * - 16 bytes: source IPv6 address
+ * - c: ttl
+ * - c: ifname length
+ * - X bytes: interface name
+ * - c: bfd_cbit
+ * - c: profile name length.
+ * - X bytes: profile name.
+ *
+ * q(64), l(32), w(16), c(8)
+ */
+
+ /* Initialize parameters return values. */
+ memset(bpc, 0, sizeof(*bpc));
+ *pc = NULL;
+
+ /* Find or allocate process context data. */
+ STREAM_GETL(msg, pid);
+
+ *pc = pc_new(pid);
+
+ /* Register/update peer information. */
+ _ptm_msg_read_address(msg, &bpc->bpc_peer);
+
+ /* Determine IP type from peer destination. */
+ bpc->bpc_ipv4 = (bpc->bpc_peer.sa_sin.sin_family == AF_INET);
+
+ /* Get peer configuration. */
+ STREAM_GETL(msg, bpc->bpc_recvinterval);
+ bpc->bpc_has_recvinterval =
+ (bpc->bpc_recvinterval != BPC_DEF_RECEIVEINTERVAL);
+
+ STREAM_GETL(msg, bpc->bpc_txinterval);
+ bpc->bpc_has_txinterval =
+ (bpc->bpc_txinterval != BPC_DEF_TRANSMITINTERVAL);
+
+ STREAM_GETC(msg, bpc->bpc_detectmultiplier);
+ bpc->bpc_has_detectmultiplier =
+ (bpc->bpc_detectmultiplier != BPC_DEF_DETECTMULTIPLIER);
+
+ /* Read (single|multi)hop and its options. */
+ STREAM_GETC(msg, bpc->bpc_mhop);
+
+ /* Read multihop source address and TTL. */
+ _ptm_msg_read_address(msg, &bpc->bpc_local);
+
+ /* Read the minimum TTL (0 means unset or invalid). */
+ STREAM_GETC(msg, bpc->bpc_minimum_ttl);
+ if (bpc->bpc_minimum_ttl == 0) {
+ bpc->bpc_minimum_ttl = BFD_DEF_MHOP_TTL;
+ bpc->bpc_has_minimum_ttl = false;
+ } else {
+ bpc->bpc_minimum_ttl = (BFD_TTL_VAL + 1) - bpc->bpc_minimum_ttl;
+ bpc->bpc_has_minimum_ttl = true;
+ }
+
+ /*
+ * Read interface name and make sure it fits our data
+ * structure, otherwise fail.
+ */
+ STREAM_GETC(msg, ifnamelen);
+ if (ifnamelen >= sizeof(bpc->bpc_localif)) {
+ zlog_err("ptm-read: interface name is too big");
+ return -1;
+ }
+
+ bpc->bpc_has_localif = ifnamelen > 0;
+ if (bpc->bpc_has_localif) {
+ STREAM_GET(bpc->bpc_localif, msg, ifnamelen);
+ bpc->bpc_localif[ifnamelen] = 0;
+ }
+
+ if (vrf_id != VRF_DEFAULT) {
+ struct vrf *vrf;
+
+ vrf = vrf_lookup_by_id(vrf_id);
+ if (vrf) {
+ bpc->bpc_has_vrfname = true;
+ strlcpy(bpc->bpc_vrfname, vrf->name, sizeof(bpc->bpc_vrfname));
+ } else {
+ zlog_err("ptm-read: vrf id %u could not be identified",
+ vrf_id);
+ return -1;
+ }
+ } else {
+ bpc->bpc_has_vrfname = true;
+ strlcpy(bpc->bpc_vrfname, VRF_DEFAULT_NAME, sizeof(bpc->bpc_vrfname));
+ }
+
+ /* Read control plane independant configuration. */
+ STREAM_GETC(msg, bpc->bpc_cbit);
+
+ /* Handle profile names. */
+ STREAM_GETC(msg, ifnamelen);
+ bpc->bpc_has_profile = ifnamelen > 0;
+ if (bpc->bpc_has_profile) {
+ STREAM_GET(bpc->bpc_profile, msg, ifnamelen);
+ bpc->bpc_profile[ifnamelen] = 0;
+ }
+
+ /* 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
+ != bpc->bpc_peer.sa_sin.sin_family)) {
+ zlog_warn("ptm-read: peer family doesn't match local type");
+ return -1;
+ }
+
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+static void bfdd_dest_register(struct stream *msg, vrf_id_t vrf_id)
+{
+ struct ptm_client *pc;
+ struct bfd_session *bs;
+ struct bfd_peer_cfg bpc;
+
+ /* Read the client context and peer data. */
+ if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_REGISTER, vrf_id, &bpc, &pc) == -1)
+ return;
+
+ debug_printbpc(&bpc, "ptm-add-dest: register peer");
+
+ /* Find or start new BFD session. */
+ bs = bs_peer_find(&bpc);
+ if (bs == NULL) {
+ bs = ptm_bfd_sess_new(&bpc);
+ if (bs == NULL) {
+ if (bglobal.debug_zebra)
+ zlog_debug(
+ "ptm-add-dest: failed to create BFD session");
+ return;
+ }
+ } else {
+ /*
+ * BFD session was already created, we are just updating the
+ * current peer.
+ *
+ * `ptm-bfd` (or `HAVE_BFDD == 0`) is the only implementation
+ * that allow users to set peer specific timers via protocol.
+ * BFD daemon (this code) on the other hand only supports
+ * changing peer configuration manually (through `peer` node)
+ * or via profiles.
+ */
+ if (bpc.bpc_has_profile)
+ bfd_profile_apply(bpc.bpc_profile, bs);
+ }
+
+ /* Create client peer notification register. */
+ pcn_new(pc, bs);
+
+ ptm_bfd_notify(bs, bs->ses_state);
+}
+
+static void bfdd_dest_deregister(struct stream *msg, vrf_id_t vrf_id)
+{
+ struct ptm_client *pc;
+ struct ptm_client_notification *pcn;
+ struct bfd_session *bs;
+ struct bfd_peer_cfg bpc;
+
+ /* Read the client context and peer data. */
+ if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_DEREGISTER, vrf_id, &bpc, &pc) == -1)
+ return;
+
+ debug_printbpc(&bpc, "ptm-del-dest: deregister peer");
+
+ /* Find or start new BFD session. */
+ bs = bs_peer_find(&bpc);
+ if (bs == NULL) {
+ if (bglobal.debug_zebra)
+ zlog_debug("ptm-del-dest: failed to find BFD session");
+ return;
+ }
+
+ /* Unregister client peer notification. */
+ pcn = pcn_lookup(pc, bs);
+ if (pcn != NULL) {
+ pcn_free(pcn);
+ return;
+ }
+
+ if (bglobal.debug_zebra)
+ zlog_debug("ptm-del-dest: failed to find BFD session");
+
+ /*
+ * XXX: We either got a double deregistration or the daemon who
+ * created this is no longer around. Lets try to delete it anyway
+ * and the worst case is the refcount will detain us.
+ */
+ _ptm_bfd_session_del(bs, BD_NEIGHBOR_DOWN);
+}
+
+/*
+ * header: command, VRF
+ * l: pid
+ */
+static void bfdd_client_register(struct stream *msg)
+{
+ uint32_t pid;
+
+ /* Find or allocate process context data. */
+ STREAM_GETL(msg, pid);
+
+ pc_new(pid);
+
+ return;
+
+stream_failure:
+ zlog_err("ptm-add-client: failed to register client");
+}
+
+/*
+ * header: command, VRF
+ * l: pid
+ */
+static void bfdd_client_deregister(struct stream *msg)
+{
+ struct ptm_client *pc;
+ uint32_t pid;
+
+ /* Find or allocate process context data. */
+ STREAM_GETL(msg, pid);
+
+ pc = pc_lookup(pid);
+ if (pc == NULL) {
+ if (bglobal.debug_zebra)
+ zlog_debug("ptm-del-client: failed to find client: %u",
+ pid);
+ return;
+ }
+
+ if (bglobal.debug_zebra)
+ zlog_debug("ptm-del-client: client pid %u", pid);
+
+ pc_free(pc);
+
+ return;
+
+stream_failure:
+ zlog_err("ptm-del-client: failed to deregister client");
+}
+
+static int bfdd_replay(ZAPI_CALLBACK_ARGS)
+{
+ struct stream *msg = zclient->ibuf;
+ uint32_t rcmd;
+
+ STREAM_GETL(msg, rcmd);
+
+ switch (rcmd) {
+ case ZEBRA_BFD_DEST_REGISTER:
+ case ZEBRA_BFD_DEST_UPDATE:
+ bfdd_dest_register(msg, vrf_id);
+ break;
+ case ZEBRA_BFD_DEST_DEREGISTER:
+ bfdd_dest_deregister(msg, vrf_id);
+ break;
+ case ZEBRA_BFD_CLIENT_REGISTER:
+ bfdd_client_register(msg);
+ break;
+ case ZEBRA_BFD_CLIENT_DEREGISTER:
+ bfdd_client_deregister(msg);
+ break;
+
+ default:
+ if (bglobal.debug_zebra)
+ zlog_debug("ptm-replay: invalid message type %u", rcmd);
+ return -1;
+ }
+
+ return 0;
+
+stream_failure:
+ zlog_err("ptm-replay: failed to find command");
+ return -1;
+}
+
+static void bfdd_zebra_connected(struct zclient *zc)
+{
+ struct stream *msg = zc->obuf;
+
+ /* Clean-up and free ptm clients data memory. */
+ pc_free_all();
+
+ /*
+ * The replay is an empty message just to trigger client daemons
+ * configuration replay.
+ */
+ stream_reset(msg);
+ zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT);
+ stream_putl(msg, ZEBRA_BFD_DEST_REPLAY);
+ stream_putw_at(msg, 0, stream_get_endp(msg));
+
+ /* Ask for interfaces information. */
+ zclient_create_header(msg, ZEBRA_INTERFACE_ADD, VRF_DEFAULT);
+
+ /* Send requests. */
+ zclient_send_message(zclient);
+}
+
+static void bfdd_sessions_enable_interface(struct interface *ifp)
+{
+ struct bfd_session_observer *bso;
+ struct bfd_session *bs;
+ struct vrf *vrf;
+
+ vrf = ifp->vrf;
+
+ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) {
+ bs = bso->bso_bs;
+ /* check vrf name */
+ if (bs->key.vrfname[0] &&
+ strcmp(vrf->name, bs->key.vrfname))
+ continue;
+
+ /* If Interface matches vrfname, then bypass iface check */
+ if (vrf_is_backend_netns() || strcmp(ifp->name, vrf->name)) {
+ /* Interface name mismatch. */
+ if (bs->key.ifname[0] &&
+ strcmp(ifp->name, bs->key.ifname))
+ continue;
+ }
+
+ /* Skip enabled sessions. */
+ if (bs->sock != -1)
+ continue;
+
+ /* Try to enable it. */
+ bfd_session_enable(bs);
+ }
+}
+
+static void bfdd_sessions_disable_interface(struct interface *ifp)
+{
+ struct bfd_session_observer *bso;
+ struct bfd_session *bs;
+
+ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) {
+ bs = bso->bso_bs;
+
+ if (bs->ifp != ifp)
+ continue;
+
+ /* Skip disabled sessions. */
+ if (bs->sock == -1) {
+ bs->ifp = NULL;
+ continue;
+ }
+
+ bfd_session_disable(bs);
+ bs->ifp = NULL;
+ }
+}
+
+void bfdd_sessions_enable_vrf(struct vrf *vrf)
+{
+ struct bfd_session_observer *bso;
+ struct bfd_session *bs;
+
+ /* it may affect configs without interfaces */
+ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) {
+ bs = bso->bso_bs;
+ if (bs->vrf)
+ continue;
+ if (bs->key.vrfname[0] &&
+ strcmp(vrf->name, bs->key.vrfname))
+ continue;
+ /* need to update the vrf information on
+ * bs so that callbacks are handled
+ */
+ bs->vrf = vrf;
+ /* Skip enabled sessions. */
+ if (bs->sock != -1)
+ continue;
+ /* Try to enable it. */
+ bfd_session_enable(bs);
+ }
+}
+
+void bfdd_sessions_disable_vrf(struct vrf *vrf)
+{
+ struct bfd_session_observer *bso;
+ struct bfd_session *bs;
+
+ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) {
+ bs = bso->bso_bs;
+ if (bs->key.vrfname[0] &&
+ strcmp(vrf->name, bs->key.vrfname))
+ continue;
+ /* Skip disabled sessions. */
+ if (bs->sock == -1)
+ continue;
+
+ bfd_session_disable(bs);
+ bs->vrf = NULL;
+ }
+}
+
+static int bfd_ifp_destroy(struct interface *ifp)
+{
+ if (bglobal.debug_zebra)
+ zlog_debug("zclient: delete interface %s (VRF %s(%u))",
+ ifp->name, ifp->vrf->name, ifp->vrf->vrf_id);
+
+ bfdd_sessions_disable_interface(ifp);
+
+ return 0;
+}
+
+static void bfdd_sessions_enable_address(struct connected *ifc)
+{
+ struct bfd_session_observer *bso;
+ struct bfd_session *bs;
+ struct prefix prefix;
+
+ TAILQ_FOREACH(bso, &bglobal.bg_obslist, bso_entry) {
+ /* Skip enabled sessions. */
+ bs = bso->bso_bs;
+ if (bs->sock != -1)
+ continue;
+
+ /* Check address. */
+ prefix = bso->bso_addr;
+ prefix.prefixlen = ifc->address->prefixlen;
+ if (prefix_cmp(&prefix, ifc->address))
+ continue;
+
+ /* Try to enable it. */
+ bfd_session_enable(bs);
+ }
+}
+
+static int bfdd_interface_address_update(ZAPI_CALLBACK_ARGS)
+{
+ struct connected *ifc;
+
+ ifc = zebra_interface_address_read(cmd, zclient->ibuf, vrf_id);
+ if (ifc == NULL)
+ return 0;
+
+ if (bglobal.debug_zebra)
+ zlog_debug("zclient: %s local address %pFX (VRF %u)",
+ cmd == ZEBRA_INTERFACE_ADDRESS_ADD ? "add"
+ : "delete",
+ ifc->address, vrf_id);
+
+ if (cmd == ZEBRA_INTERFACE_ADDRESS_ADD)
+ bfdd_sessions_enable_address(ifc);
+ else
+ connected_free(&ifc);
+
+ return 0;
+}
+
+static int bfd_ifp_create(struct interface *ifp)
+{
+ if (bglobal.debug_zebra)
+ zlog_debug("zclient: add interface %s (VRF %s(%u))", ifp->name,
+ ifp->vrf->name, ifp->vrf->vrf_id);
+ bfdd_sessions_enable_interface(ifp);
+
+ return 0;
+}
+
+static zclient_handler *const bfd_handlers[] = {
+ /*
+ * We'll receive all messages through replay, however it will
+ * contain a special field with the real command inside so we
+ * avoid having to create too many handlers.
+ */
+ [ZEBRA_BFD_DEST_REPLAY] = bfdd_replay,
+
+ /* Learn about new addresses being registered. */
+ [ZEBRA_INTERFACE_ADDRESS_ADD] = bfdd_interface_address_update,
+ [ZEBRA_INTERFACE_ADDRESS_DELETE] = bfdd_interface_address_update,
+};
+
+void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv)
+{
+ if_zapi_callbacks(bfd_ifp_create, NULL, NULL, bfd_ifp_destroy);
+ zclient = zclient_new(master, &zclient_options_default, bfd_handlers,
+ array_size(bfd_handlers));
+ assert(zclient != NULL);
+ zclient_init(zclient, ZEBRA_ROUTE_BFD, 0, bfdd_priv);
+
+ /* Send replay request on zebra connect. */
+ zclient->zebra_connected = bfdd_zebra_connected;
+}
+
+void bfdd_zclient_register(vrf_id_t vrf_id)
+{
+ if (!zclient || zclient->sock < 0)
+ return;
+ zclient_send_reg_requests(zclient, vrf_id);
+}
+
+void bfdd_zclient_unregister(vrf_id_t vrf_id)
+{
+ if (!zclient || zclient->sock < 0)
+ return;
+ zclient_send_dereg_requests(zclient, vrf_id);
+}
+
+void bfdd_zclient_stop(void)
+{
+ zclient_stop(zclient);
+
+ /* Clean-up and free ptm clients data memory. */
+ pc_free_all();
+}
+
+
+/*
+ * Client handling.
+ */
+static struct ptm_client *pc_lookup(uint32_t pid)
+{
+ struct ptm_client *pc;
+
+ TAILQ_FOREACH (pc, &pcqueue, pc_entry) {
+ if (pc->pc_pid != pid)
+ continue;
+
+ break;
+ }
+
+ return pc;
+}
+
+static struct ptm_client *pc_new(uint32_t pid)
+{
+ struct ptm_client *pc;
+
+ /* Look up first, if not found create the client. */
+ pc = pc_lookup(pid);
+ if (pc != NULL)
+ return pc;
+
+ /* Allocate the client data and save it. */
+ pc = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*pc));
+
+ pc->pc_pid = pid;
+ TAILQ_INSERT_HEAD(&pcqueue, pc, pc_entry);
+ return pc;
+}
+
+static void pc_free(struct ptm_client *pc)
+{
+ struct ptm_client_notification *pcn;
+
+ TAILQ_REMOVE(&pcqueue, pc, pc_entry);
+
+ while (!TAILQ_EMPTY(&pc->pc_pcnqueue)) {
+ pcn = TAILQ_FIRST(&pc->pc_pcnqueue);
+ pcn_free(pcn);
+ }
+
+ XFREE(MTYPE_BFDD_CONTROL, pc);
+}
+
+static void pc_free_all(void)
+{
+ struct ptm_client *pc;
+
+ while (!TAILQ_EMPTY(&pcqueue)) {
+ pc = TAILQ_FIRST(&pcqueue);
+ pc_free(pc);
+ }
+}
+
+static struct ptm_client_notification *pcn_new(struct ptm_client *pc,
+ struct bfd_session *bs)
+{
+ struct ptm_client_notification *pcn;
+
+ /* Try to find an existing pcn fist. */
+ pcn = pcn_lookup(pc, bs);
+ if (pcn != NULL)
+ return pcn;
+
+ /* Save the client notification data. */
+ pcn = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*pcn));
+
+ TAILQ_INSERT_HEAD(&pc->pc_pcnqueue, pcn, pcn_entry);
+ pcn->pcn_pc = pc;
+ pcn->pcn_bs = bs;
+ bs->refcount++;
+
+ return pcn;
+}
+
+static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc,
+ struct bfd_session *bs)
+{
+ struct ptm_client_notification *pcn;
+
+ TAILQ_FOREACH (pcn, &pc->pc_pcnqueue, pcn_entry) {
+ if (pcn->pcn_bs != bs)
+ continue;
+
+ break;
+ }
+
+ return pcn;
+}
+
+static void pcn_free(struct ptm_client_notification *pcn)
+{
+ struct ptm_client *pc;
+ struct bfd_session *bs;
+
+ /* Handle session de-registration. */
+ bs = pcn->pcn_bs;
+ pcn->pcn_bs = NULL;
+ bs->refcount--;
+
+ /* Log modification to users. */
+ if (bglobal.debug_zebra)
+ zlog_debug("ptm-del-session: [%s] refcount=%" PRIu64,
+ bs_to_string(bs), bs->refcount);
+
+ /* Set session down. */
+ _ptm_bfd_session_del(bs, BD_NEIGHBOR_DOWN);
+
+ /* Handle ptm_client deregistration. */
+ pc = pcn->pcn_pc;
+ pcn->pcn_pc = NULL;
+ TAILQ_REMOVE(&pc->pc_pcnqueue, pcn, pcn_entry);
+
+ XFREE(MTYPE_BFDD_NOTIFICATION, pcn);
+}
diff --git a/bfdd/subdir.am b/bfdd/subdir.am
new file mode 100644
index 0000000..b86a189
--- /dev/null
+++ b/bfdd/subdir.am
@@ -0,0 +1,50 @@
+#
+# bfdd
+#
+
+if BFDD
+noinst_LIBRARIES += bfdd/libbfd.a
+sbin_PROGRAMS += bfdd/bfdd
+vtysh_daemons += bfdd
+man8 += $(MANBUILD)/frr-bfdd.8
+endif
+
+bfdd_libbfd_a_SOURCES = \
+ bfdd/bfd.c \
+ bfdd/bfdd_nb.c \
+ bfdd/bfdd_nb_config.c \
+ bfdd/bfdd_nb_state.c \
+ bfdd/bfdd_vty.c \
+ bfdd/bfdd_cli.c \
+ bfdd/bfd_packet.c \
+ bfdd/config.c \
+ bfdd/control.c \
+ bfdd/dplane.c \
+ bfdd/event.c \
+ bfdd/ptm_adapter.c \
+ # end
+
+# Install headers so it can be used by external data plane
+# implementations.
+bfdd_headersdir = $(pkgincludedir)/bfdd
+bfdd_headers_HEADERS = \
+ bfdd/bfddp_packet.h \
+ # end
+
+clippy_scan += \
+ bfdd/bfdd_cli.c \
+ bfdd/bfdd_vty.c \
+ # end
+
+noinst_HEADERS += \
+ bfdd/bfdctl.h \
+ bfdd/bfdd_nb.h \
+ bfdd/bfd.h \
+ # end
+
+nodist_bfdd_bfdd_SOURCES = \
+ yang/frr-bfdd.yang.c \
+ # end
+
+bfdd_bfdd_SOURCES = bfdd/bfdd.c
+bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la