diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
commit | e2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch) | |
tree | f0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /bfdd | |
parent | Initial commit. (diff) | |
download | frr-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/.gitignore | 2 | ||||
-rw-r--r-- | bfdd/Makefile | 10 | ||||
-rw-r--r-- | bfdd/bfd.c | 2069 | ||||
-rw-r--r-- | bfdd/bfd.h | 844 | ||||
-rw-r--r-- | bfdd/bfd_packet.c | 1759 | ||||
-rw-r--r-- | bfdd/bfdctl.h | 157 | ||||
-rw-r--r-- | bfdd/bfdd.c | 395 | ||||
-rw-r--r-- | bfdd/bfdd_cli.c | 718 | ||||
-rw-r--r-- | bfdd/bfdd_nb.c | 490 | ||||
-rw-r--r-- | bfdd/bfdd_nb.h | 213 | ||||
-rw-r--r-- | bfdd/bfdd_nb_config.c | 847 | ||||
-rw-r--r-- | bfdd/bfdd_nb_state.c | 360 | ||||
-rw-r--r-- | bfdd/bfdd_vty.c | 1051 | ||||
-rw-r--r-- | bfdd/bfddp_packet.h | 369 | ||||
-rw-r--r-- | bfdd/config.c | 592 | ||||
-rw-r--r-- | bfdd/control.c | 841 | ||||
-rw-r--r-- | bfdd/dplane.c | 1176 | ||||
-rw-r--r-- | bfdd/event.c | 114 | ||||
-rw-r--r-- | bfdd/ptm_adapter.c | 980 | ||||
-rw-r--r-- | bfdd/subdir.am | 50 |
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 |