summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format1
-rw-r--r--Makefile.am1
-rw-r--r--bfdd/.gitignore3
-rw-r--r--bfdd/bfd.c1294
-rw-r--r--bfdd/bfd.h631
-rw-r--r--bfdd/bfd_packet.c1533
-rw-r--r--bfdd/bfdctl.h160
-rw-r--r--bfdd/bfdd.c236
-rw-r--r--bfdd/bfdd.conf.sample5
-rw-r--r--bfdd/bfdd_vty.c870
-rw-r--r--bfdd/bsd.c290
-rw-r--r--bfdd/config.c606
-rw-r--r--bfdd/control.c895
-rw-r--r--bfdd/event.c155
-rw-r--r--bfdd/linux.c221
-rw-r--r--bfdd/log.c125
-rw-r--r--bfdd/ptm_adapter.c718
-rw-r--r--bfdd/subdir.am33
-rw-r--r--bgpd/bgp_bfd.c20
-rwxr-xr-xconfigure.ac88
-rwxr-xr-xdebianpkg/backports/ubuntu12.04/debian/rules1
-rwxr-xr-xdebianpkg/backports/ubuntu14.04/debian/rules8
-rwxr-xr-xdebianpkg/rules8
-rw-r--r--doc/Makefile.am9
-rw-r--r--doc/manpages/bfd-options.rst10
-rw-r--r--doc/manpages/bfdd.rst40
-rw-r--r--doc/manpages/common-options.rst1
-rw-r--r--doc/manpages/conf.py1
-rw-r--r--doc/user/basic.rst61
-rw-r--r--doc/user/bfd.rst302
-rw-r--r--doc/user/index.rst1
-rw-r--r--doc/user/installation.rst4
-rw-r--r--doc/user/overview.rst8
-rw-r--r--doc/user/setup.rst3
-rw-r--r--lib/command.c11
-rw-r--r--lib/command.h2
-rw-r--r--lib/json.h14
-rw-r--r--lib/memory.c28
-rw-r--r--lib/memory.h8
-rw-r--r--lib/memory_vty.c20
-rw-r--r--lib/route_types.txt2
-rw-r--r--lib/vty.c4
-rw-r--r--lib/zclient.h1
-rw-r--r--ospf6d/ospf6_bfd.c11
-rw-r--r--ospfd/ospf_bfd.c20
-rw-r--r--pimd/pim_bfd.c2
-rw-r--r--pimd/pim_cmd.c11
-rw-r--r--redhat/daemons3
-rwxr-xr-xredhat/frr.init6
-rw-r--r--redhat/frr.logrotate7
-rw-r--r--redhat/frr.spec.in31
-rwxr-xr-xtools/checkpatch.pl8
-rw-r--r--tools/etc/frr/daemons1
-rw-r--r--tools/etc/frr/daemons.conf1
-rwxr-xr-xtools/frr2
-rw-r--r--vtysh/Makefile.am4
-rw-r--r--vtysh/vtysh.c73
-rw-r--r--vtysh/vtysh.h35
-rw-r--r--vtysh/vtysh_config.c2
-rw-r--r--zebra/zapi_msg.c3
-rw-r--r--zebra/zebra_ptm.c438
-rw-r--r--zebra/zebra_ptm.h3
62 files changed, 9040 insertions, 53 deletions
diff --git a/.clang-format b/.clang-format
index cc5a95baf6..3c6a2784ce 100644
--- a/.clang-format
+++ b/.clang-format
@@ -43,6 +43,7 @@ ForEachMacros:
- SPLAY_FOREACH
- FOR_ALL_INTERFACES
- FOR_ALL_INTERFACES_ADDRESSES
+ - JSON_FOREACH
# zebra
- RE_DEST_FOREACH_ROUTE
- RE_DEST_FOREACH_ROUTE_SAFE
diff --git a/Makefile.am b/Makefile.am
index 8c96f39f39..3e268f703d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -56,6 +56,7 @@ include sharpd/subdir.am
include pimd/subdir.am
include pbrd/subdir.am
include staticd/subdir.am
+include bfdd/subdir.am
SUBDIRS = . @LIBRFP@ @RFPTEST@ \
@BGPD@ \
diff --git a/bfdd/.gitignore b/bfdd/.gitignore
new file mode 100644
index 0000000000..e554d1b33f
--- /dev/null
+++ b/bfdd/.gitignore
@@ -0,0 +1,3 @@
+# ignore binary files
+*.a
+bfdd
diff --git a/bfdd/bfd.c b/bfdd/bfd.c
new file mode 100644
index 0000000000..28b6beadcb
--- /dev/null
+++ b/bfdd/bfd.c
@@ -0,0 +1,1294 @@
+/*********************************************************************
+ * Copyright 2013 Cumulus Networks, LLC. All rights reserved.
+ * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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 "bfd.h"
+
+DEFINE_QOBJ_TYPE(bfd_session);
+
+/*
+ * Prototypes
+ */
+static uint32_t ptm_bfd_gen_ID(void);
+static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd);
+static void bfd_session_free(struct bfd_session *bs);
+static struct bfd_session *bfd_session_new(int sd);
+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);
+
+
+/*
+ * Functions
+ */
+struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc)
+{
+ struct bfd_session *bs;
+ struct peer_label *pl;
+ struct bfd_mhop_key mhop;
+ struct bfd_shop_key shop;
+
+ /* 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. */
+ if (bpc->bpc_mhop) {
+ memset(&mhop, 0, sizeof(mhop));
+ mhop.peer = bpc->bpc_peer;
+ mhop.local = bpc->bpc_local;
+ if (bpc->bpc_has_vrfname)
+ strlcpy(mhop.vrf_name, bpc->bpc_vrfname,
+ sizeof(mhop.vrf_name));
+
+ bs = bfd_mhop_lookup(mhop);
+ } else {
+ memset(&shop, 0, sizeof(shop));
+ shop.peer = bpc->bpc_peer;
+ if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif)
+ strlcpy(shop.port_name, bpc->bpc_localif,
+ sizeof(shop.port_name));
+
+ bs = bfd_shop_lookup(shop);
+ }
+
+ return bs;
+}
+
+static uint32_t ptm_bfd_gen_ID(void)
+{
+ static uint32_t sessionID = 1;
+
+ return (sessionID++);
+}
+
+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 + (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 */
+ 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, int polling)
+{
+ bfd->echo_xmt_TO = 0;
+ bfd->echo_detect_TO = 0;
+ BFD_UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE);
+
+ bfd_echo_xmttimer_delete(bfd);
+ bfd_echo_recvtimer_delete(bfd);
+
+ if (polling) {
+ bfd->polling = polling;
+ bfd->new_timers.desired_min_tx = bfd->up_min_tx;
+ bfd->new_timers.required_min_rx = bfd->timers.required_min_rx;
+ ptm_bfd_snd(bfd, 0);
+ }
+}
+
+void ptm_bfd_echo_start(struct bfd_session *bfd)
+{
+ bfd->echo_detect_TO = (bfd->remote_detect_mult * bfd->echo_xmt_TO);
+ ptm_bfd_echo_xmt_TO(bfd);
+
+ bfd->polling = 1;
+ bfd->new_timers.desired_min_tx = bfd->up_min_tx;
+ bfd->new_timers.required_min_rx = bfd->timers.required_min_rx;
+ ptm_bfd_snd(bfd, 0);
+}
+
+void ptm_bfd_ses_up(struct bfd_session *bfd)
+{
+ int old_state = bfd->ses_state;
+
+ bfd->local_diag = 0;
+ bfd->ses_state = PTM_BFD_UP;
+ bfd->polling = 1;
+ monotime(&bfd->uptime);
+
+ /* If the peer is capable to receiving Echo pkts */
+ if (bfd->echo_xmt_TO && !BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MH)) {
+ ptm_bfd_echo_start(bfd);
+ } else {
+ bfd->new_timers.desired_min_tx = bfd->up_min_tx;
+ bfd->new_timers.required_min_rx = bfd->timers.required_min_rx;
+ ptm_bfd_snd(bfd, 0);
+ }
+
+ control_notify(bfd);
+
+ if (old_state != bfd->ses_state)
+ log_info("state-change: [%s] %s -> %s", bs_to_string(bfd),
+ state_list[old_state].str,
+ state_list[bfd->ses_state].str);
+}
+
+void ptm_bfd_ses_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);
+
+ ptm_bfd_snd(bfd, 0);
+
+ /* only signal clients when going from up->down state */
+ if (old_state == PTM_BFD_UP)
+ control_notify(bfd);
+
+ /* Stop echo packet transmission if they are active */
+ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE))
+ ptm_bfd_echo_stop(bfd, 0);
+
+ if (old_state != bfd->ses_state)
+ log_info("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));
+}
+
+static int ptm_bfd_get_vrf_name(char *port_name, char *vrf_name)
+{
+ struct bfd_iface *iface;
+ struct bfd_vrf *vrf;
+
+ if ((port_name == NULL) || (vrf_name == NULL))
+ return -1;
+
+ iface = bfd_iface_lookup(port_name);
+ if (iface) {
+ vrf = bfd_vrf_lookup(iface->vrf_id);
+ if (vrf) {
+ strlcpy(vrf_name, vrf->name, sizeof(vrf->name));
+ return 0;
+ }
+ }
+ return -1;
+}
+
+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;
+
+ /* Remove unused fields. */
+ switch (sa->sa_sin.sin_family) {
+ case AF_INET:
+ sa->sa_sin.sin_port = 0;
+ if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin)) == 0)
+ return bs;
+ break;
+ case AF_INET6:
+ sa->sa_sin6.sin6_port = 0;
+ if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin6)) == 0)
+ return bs;
+ break;
+ }
+
+ return NULL;
+}
+
+struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, char *port_name,
+ struct sockaddr_any *peer,
+ struct sockaddr_any *local,
+ char *vrf_name, bool is_mhop)
+{
+ struct bfd_session *l_bfd = NULL;
+ struct bfd_mhop_key mhop;
+ struct bfd_shop_key shop;
+ char vrf_buf[MAXNAMELEN];
+
+ /* 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. */
+ if (is_mhop) {
+ memset(&mhop, 0, sizeof(mhop));
+ mhop.peer = *peer;
+ mhop.local = *local;
+ if (vrf_name && vrf_name[0]) {
+ strlcpy(mhop.vrf_name, vrf_name, sizeof(mhop.vrf_name));
+ } else if (port_name && port_name[0]) {
+ memset(vrf_buf, 0, sizeof(vrf_buf));
+ if (ptm_bfd_get_vrf_name(port_name, vrf_buf) != -1)
+ strlcpy(mhop.vrf_name, vrf_buf,
+ sizeof(mhop.vrf_name));
+ }
+
+ l_bfd = bfd_mhop_lookup(mhop);
+ } else {
+ memset(&shop, 0, sizeof(shop));
+ shop.peer = *peer;
+ if (port_name && port_name[0])
+ strlcpy(shop.port_name, port_name,
+ sizeof(shop.port_name));
+
+ l_bfd = bfd_shop_lookup(shop);
+ }
+
+ /* XXX maybe remoteDiscr should be checked for remoteHeard cases. */
+ return l_bfd;
+}
+
+#if 0 /* TODO VxLAN Support */
+static void
+_update_vxlan_sess_parms(struct bfd_session *bfd, bfd_sess_parms *sess_parms)
+{
+ struct bfd_session_vxlan_info *vxlan_info = &bfd->vxlan_info;
+ bfd_parms_list *parms = &sess_parms->parms;
+
+ vxlan_info->vnid = parms->vnid;
+ vxlan_info->check_tnl_key = parms->check_tnl_key;
+ vxlan_info->forwarding_if_rx = parms->forwarding_if_rx;
+ vxlan_info->cpath_down = parms->cpath_down;
+ vxlan_info->decay_min_rx = parms->decay_min_rx;
+
+ inet_aton(parms->local_dst_ip, &vxlan_info->local_dst_ip);
+ inet_aton(parms->remote_dst_ip, &vxlan_info->peer_dst_ip);
+
+ memcpy(vxlan_info->local_dst_mac, parms->local_dst_mac, ETH_ALEN);
+ memcpy(vxlan_info->peer_dst_mac, parms->remote_dst_mac, ETH_ALEN);
+
+ /* The interface may change for Vxlan BFD sessions, so update
+ * the local mac and ifindex
+ */
+ bfd->ifindex = sess_parms->ifindex;
+ memcpy(bfd->local_mac, sess_parms->local_mac, sizeof(bfd->local_mac));
+}
+#endif /* VxLAN support */
+
+int bfd_xmt_cb(struct thread *t)
+{
+ struct bfd_session *bs = THREAD_ARG(t);
+
+ ptm_bfd_xmt_TO(bs, 0);
+
+ return 0;
+}
+
+int bfd_echo_xmt_cb(struct thread *t)
+{
+ struct bfd_session *bs = THREAD_ARG(t);
+
+ ptm_bfd_echo_xmt_TO(bs);
+
+ return 0;
+}
+
+/* Was ptm_bfd_detect_TO() */
+int bfd_recvtimer_cb(struct thread *t)
+{
+ struct bfd_session *bs = THREAD_ARG(t);
+
+ switch (bs->ses_state) {
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME);
+ bfd_recvtimer_update(bs);
+ break;
+
+ default:
+ /* Second detect time expiration, zero remote discr (section
+ * 6.5.1)
+ */
+ bs->discrs.remote_discr = 0;
+ break;
+ }
+
+ return 0;
+}
+
+/* Was ptm_bfd_echo_detect_TO() */
+int bfd_echo_recvtimer_cb(struct thread *t)
+{
+ struct bfd_session *bs = THREAD_ARG(t);
+
+ switch (bs->ses_state) {
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME);
+ break;
+ }
+
+ return 0;
+}
+
+static struct bfd_session *bfd_session_new(int sd)
+{
+ struct bfd_session *bs;
+
+ bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs));
+ if (bs == NULL)
+ return NULL;
+
+ QOBJ_REG(bs, bfd_session);
+
+ bs->up_min_tx = BFD_DEFDESIREDMINTX;
+ bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX;
+ bs->timers.required_min_echo = BFD_DEF_REQ_MIN_ECHO;
+ bs->detect_mult = BFD_DEFDETECTMULT;
+ bs->mh_ttl = BFD_DEF_MHOP_TTL;
+
+ bs->sock = sd;
+ 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;
+ }
+
+ if (pl_new(nlabel, bs) == NULL)
+ return -1;
+
+ 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_echo) {
+ /* Check if echo mode is already active. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ goto skip_echo;
+
+ BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
+ ptm_bfd_echo_start(bs);
+
+ /* Activate/update echo receive timeout timer. */
+ bfd_echo_recvtimer_update(bs);
+ } else {
+ /* Check if echo mode is already disabled. */
+ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ goto skip_echo;
+
+ BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
+ ptm_bfd_echo_stop(bs, 0);
+ }
+
+skip_echo:
+ if (bpc->bpc_has_txinterval)
+ bs->up_min_tx = bpc->bpc_txinterval * 1000;
+
+ if (bpc->bpc_has_recvinterval)
+ bs->timers.required_min_rx = bpc->bpc_recvinterval * 1000;
+
+ if (bpc->bpc_has_detectmultiplier)
+ bs->detect_mult = bpc->bpc_detectmultiplier;
+
+ if (bpc->bpc_has_echointerval)
+ bs->timers.required_min_echo = bpc->bpc_echointerval * 1000;
+
+ if (bpc->bpc_has_label)
+ bfd_session_update_label(bs, bpc->bpc_label);
+
+ if (bpc->bpc_shutdown) {
+ /* Check if already shutdown. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return;
+
+ BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+
+ /* 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);
+
+ ptm_bfd_snd(bs, 0);
+ } else {
+ /* Check if already working. */
+ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return;
+
+ BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+
+ /* Change and notify state change. */
+ bs->ses_state = PTM_BFD_DOWN;
+ control_notify(bs);
+
+ /* Enable all timers. */
+ bfd_recvtimer_update(bs);
+ bfd_xmttimer_update(bs, bs->xmt_TO);
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) {
+ bfd_echo_recvtimer_update(bs);
+ bfd_echo_xmttimer_update(bs, bs->echo_xmt_TO);
+ }
+ }
+}
+
+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);
+
+ /* TODO add VxLAN support. */
+
+ control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs);
+
+ return 0;
+}
+
+static void bfd_session_free(struct bfd_session *bs)
+{
+ if (bs->sock != -1)
+ close(bs->sock);
+
+ bfd_recvtimer_delete(bs);
+ bfd_echo_recvtimer_delete(bs);
+ bfd_xmttimer_delete(bs);
+ bfd_echo_xmttimer_delete(bs);
+
+ bfd_id_delete(bs->discrs.my_discr);
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH))
+ bfd_mhop_delete(bs->mhop);
+ else
+ bfd_shop_delete(bs->shop);
+
+ pl_free(bs->pl);
+
+ QOBJ_UNREG(bs);
+ XFREE(MTYPE_BFDD_CONFIG, bs);
+}
+
+struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc)
+{
+ struct bfd_session *bfd, *l_bfd;
+ int psock;
+
+ /* 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 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 (bpc->bpc_ipv4) {
+ psock = bp_peer_socket(bpc);
+ if (psock == -1)
+ return NULL;
+ } else {
+ psock = bp_peer_socketv6(bpc);
+ if (psock == -1)
+ return NULL;
+ }
+
+ /* Get memory */
+ bfd = bfd_session_new(psock);
+ if (bfd == NULL) {
+ log_error("session-new: allocation failed");
+ return NULL;
+ }
+
+ if (bpc->bpc_has_localif && !bpc->bpc_mhop) {
+ bfd->ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif);
+ ptm_bfd_fetch_local_mac(bpc->bpc_localif, bfd->local_mac);
+ }
+
+ if (bpc->bpc_has_vxlan)
+ BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN);
+
+ if (bpc->bpc_ipv4 == false) {
+ BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6);
+
+ /* Set the IPv6 scope id for link-local addresses. */
+ if (IN6_IS_ADDR_LINKLOCAL(&bpc->bpc_local.sa_sin6.sin6_addr))
+ bpc->bpc_local.sa_sin6.sin6_scope_id = bfd->ifindex;
+ if (IN6_IS_ADDR_LINKLOCAL(&bpc->bpc_peer.sa_sin6.sin6_addr))
+ bpc->bpc_peer.sa_sin6.sin6_scope_id = bfd->ifindex;
+ }
+
+ /* Initialize the session */
+ bfd->ses_state = PTM_BFD_DOWN;
+ bfd->discrs.my_discr = ptm_bfd_gen_ID();
+ bfd->discrs.remote_discr = 0;
+ bfd->local_ip = bpc->bpc_local;
+ bfd->local_address = bpc->bpc_local;
+ bfd->timers.desired_min_tx = bfd->up_min_tx;
+ bfd->detect_TO = (bfd->detect_mult * BFD_DEF_SLOWTX);
+
+ /* Use detect_TO first for slow detection, then use recvtimer_update. */
+ bfd_recvtimer_update(bfd);
+
+ bfd_id_insert(bfd);
+
+ if (bpc->bpc_mhop) {
+ BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH);
+ bfd->mhop.peer = bpc->bpc_peer;
+ bfd->mhop.local = bpc->bpc_local;
+ if (bpc->bpc_has_vrfname)
+ strlcpy(bfd->mhop.vrf_name, bpc->bpc_vrfname,
+ sizeof(bfd->mhop.vrf_name));
+
+ bfd_mhop_insert(bfd);
+ } else {
+ bfd->shop.peer = bpc->bpc_peer;
+ if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif)
+ strlcpy(bfd->shop.port_name, bpc->bpc_localif,
+ sizeof(bfd->shop.port_name));
+
+ bfd_shop_insert(bfd);
+ }
+
+ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) {
+ static uint8_t bfd_def_vxlan_dmac[] = {0x00, 0x23, 0x20,
+ 0x00, 0x00, 0x01};
+ memcpy(bfd->peer_mac, bfd_def_vxlan_dmac,
+ sizeof(bfd_def_vxlan_dmac));
+ }
+#if 0 /* TODO */
+ else if (event->rmac) {
+ if (sscanf(event->rmac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &bfd->peer_mac[0], &bfd->peer_mac[1], &bfd->peer_mac[2],
+ &bfd->peer_mac[3], &bfd->peer_mac[4], &bfd->peer_mac[5])
+ != 6)
+ DLOG("%s: Assigning remote mac = %s", __func__,
+ event->rmac);
+ }
+#endif
+
+ /*
+ * XXX: session update triggers echo start, so we must have our
+ * discriminator ID set first.
+ */
+ _bfd_session_update(bfd, bpc);
+
+ /* Start transmitting with slow interval until peer responds */
+ bfd->xmt_TO = BFD_DEF_SLOWTX;
+
+ ptm_bfd_xmt_TO(bfd, 0);
+
+ log_info("session-new: %s", bs_to_string(bfd));
+
+ control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd);
+
+ return bfd;
+}
+
+int ptm_bfd_ses_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) {
+ log_error("session-delete: refcount failure: %" PRIu64
+ " references",
+ bs->refcount);
+ return -1;
+ }
+
+ log_info("session-delete: %s", 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)
+{
+ bs->new_timers.desired_min_tx = bs->up_min_tx;
+ bs->new_timers.required_min_rx = bs->timers.required_min_rx;
+ bs->new_timers.required_min_echo = bs->timers.required_min_echo;
+ bs->polling = 1;
+}
+
+
+/*
+ * 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(struct sockaddr_any *sa)
+{
+#define INETSTR_BUFCOUNT 8
+ static char buf[INETSTR_BUFCOUNT][INET6_ADDRSTRLEN];
+ static int bufidx;
+ struct sockaddr_in *sin = &sa->sa_sin;
+ 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)
+{
+ unsigned int year, month, day, hour, minute, second;
+ int rv;
+
+#define MINUTES (60)
+#define HOURS (24 * MINUTES)
+#define DAYS (30 * HOURS)
+#define MONTHS (12 * DAYS)
+#define YEARS (MONTHS)
+ if (time >= YEARS) {
+ year = time / YEARS;
+ time -= year * YEARS;
+
+ rv = snprintf(buf, buflen, "%u year(s), ", year);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= MONTHS) {
+ month = time / MONTHS;
+ time -= month * MONTHS;
+
+ rv = snprintf(buf, buflen, "%u month(s), ", month);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= DAYS) {
+ day = time / DAYS;
+ time -= day * DAYS;
+
+ rv = snprintf(buf, buflen, "%u day(s), ", day);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= HOURS) {
+ hour = time / HOURS;
+ time -= hour * HOURS;
+
+ rv = snprintf(buf, buflen, "%u hour(s), ", hour);
+ buf += rv;
+ buflen -= rv;
+ }
+ if (time >= MINUTES) {
+ minute = time / MINUTES;
+ time -= minute * MINUTES;
+
+ rv = snprintf(buf, buflen, "%u minute(s), ", minute);
+ buf += rv;
+ buflen -= rv;
+ }
+ second = time % MINUTES;
+ snprintf(buf, buflen, "%u second(s)", second);
+}
+
+const char *bs_to_string(struct bfd_session *bs)
+{
+ static char buf[256];
+ int pos;
+ bool is_mhop = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH);
+
+ pos = snprintf(buf, sizeof(buf), "mhop:%s", is_mhop ? "yes" : "no");
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ pos += snprintf(buf + pos, sizeof(buf) - pos,
+ " peer:%s local:%s", satostr(&bs->mhop.peer),
+ satostr(&bs->mhop.local));
+
+ if (bs->mhop.vrf_name[0])
+ snprintf(buf + pos, sizeof(buf) - pos, " vrf:%s",
+ bs->mhop.vrf_name);
+ } else {
+ pos += snprintf(buf + pos, sizeof(buf) - pos, " peer:%s",
+ satostr(&bs->shop.peer));
+
+ if (bs->local_address.sa_sin.sin_family)
+ pos += snprintf(buf + pos, sizeof(buf) - pos,
+ " local:%s",
+ satostr(&bs->local_address));
+
+ if (bs->shop.port_name[0])
+ snprintf(buf + pos, sizeof(buf) - pos, " interface:%s",
+ bs->shop.port_name);
+ }
+
+ return buf;
+}
+
+
+/*
+ * BFD hash data structures to find sessions.
+ */
+static struct hash *bfd_id_hash;
+static struct hash *bfd_shop_hash;
+static struct hash *bfd_mhop_hash;
+static struct hash *bfd_vrf_hash;
+static struct hash *bfd_iface_hash;
+
+static unsigned int bfd_id_hash_do(void *p);
+static int bfd_id_hash_cmp(const void *n1, const void *n2);
+static unsigned int bfd_shop_hash_do(void *p);
+static int bfd_shop_hash_cmp(const void *n1, const void *n2);
+static unsigned int bfd_mhop_hash_do(void *p);
+static int bfd_mhop_hash_cmp(const void *n1, const void *n2);
+static unsigned int bfd_vrf_hash_do(void *p);
+static int bfd_vrf_hash_cmp(const void *n1, const void *n2);
+static unsigned int bfd_iface_hash_do(void *p);
+static int bfd_iface_hash_cmp(const void *n1, const void *n2);
+
+static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop);
+static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop);
+static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop);
+static int _iface_key(struct bfd_iface *iface, const char *ifname);
+
+static void _bfd_free(struct hash_backet *hb,
+ void *arg __attribute__((__unused__)));
+static void _vrf_free(void *arg);
+static void _iface_free(void *arg);
+
+/* BFD hash for our discriminator. */
+static unsigned int bfd_id_hash_do(void *p)
+{
+ struct bfd_session *bs = p;
+
+ return jhash_1word(bs->discrs.my_discr, 0);
+}
+
+static int 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_shop_hash_do(void *p)
+{
+ struct bfd_session *bs = p;
+
+ return jhash(&bs->shop, sizeof(bs->shop), 0);
+}
+
+static int bfd_shop_hash_cmp(const void *n1, const void *n2)
+{
+ const struct bfd_session *bs1 = n1, *bs2 = n2;
+
+ return memcmp(&bs1->shop, &bs2->shop, sizeof(bs1->shop)) == 0;
+}
+
+/* BFD hash for multi hop. */
+static unsigned int bfd_mhop_hash_do(void *p)
+{
+ struct bfd_session *bs = p;
+
+ return jhash(&bs->mhop, sizeof(bs->mhop), 0);
+}
+
+static int bfd_mhop_hash_cmp(const void *n1, const void *n2)
+{
+ const struct bfd_session *bs1 = n1, *bs2 = n2;
+
+ return memcmp(&bs1->mhop, &bs2->mhop, sizeof(bs1->mhop)) == 0;
+}
+
+/* BFD hash for VRFs. */
+static unsigned int bfd_vrf_hash_do(void *p)
+{
+ struct bfd_vrf *vrf = p;
+
+ return jhash_1word(vrf->vrf_id, 0);
+}
+
+static int bfd_vrf_hash_cmp(const void *n1, const void *n2)
+{
+ const struct bfd_vrf *v1 = n1, *v2 = n2;
+
+ return v1->vrf_id == v2->vrf_id;
+}
+
+/* BFD hash for interfaces. */
+static unsigned int bfd_iface_hash_do(void *p)
+{
+ struct bfd_iface *iface = p;
+
+ return string_hash_make(iface->ifname);
+}
+
+static int bfd_iface_hash_cmp(const void *n1, const void *n2)
+{
+ const struct bfd_iface *i1 = n1, *i2 = n2;
+
+ return strcmp(i1->ifname, i2->ifname) == 0;
+}
+
+/* Helper functions */
+static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop)
+{
+ bs->shop = *shop;
+
+ /* Remove unused fields. */
+ switch (bs->shop.peer.sa_sin.sin_family) {
+ case AF_INET:
+ bs->shop.peer.sa_sin.sin_port = 0;
+ break;
+ case AF_INET6:
+ bs->shop.peer.sa_sin6.sin6_port = 0;
+ break;
+ }
+}
+
+static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop)
+{
+ _shop_key(bs, shop);
+ memset(bs->shop.port_name, 0, sizeof(bs->shop.port_name));
+}
+
+static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop)
+{
+ bs->mhop = *mhop;
+
+ /* Remove unused fields. */
+ switch (bs->mhop.peer.sa_sin.sin_family) {
+ case AF_INET:
+ bs->mhop.peer.sa_sin.sin_port = 0;
+ bs->mhop.local.sa_sin.sin_port = 0;
+ break;
+ case AF_INET6:
+ bs->mhop.peer.sa_sin6.sin6_port = 0;
+ bs->mhop.local.sa_sin6.sin6_port = 0;
+ break;
+ }
+}
+
+static int _iface_key(struct bfd_iface *iface, const char *ifname)
+{
+ size_t slen = sizeof(iface->ifname);
+
+ memset(iface->ifname, 0, slen);
+ if (strlcpy(iface->ifname, ifname, slen) >= slen)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * 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_shop_lookup(struct bfd_shop_key shop)
+{
+ struct bfd_session bs, *bsp;
+
+ _shop_key(&bs, &shop);
+
+ bsp = hash_lookup(bfd_shop_hash, &bs);
+ if (bsp == NULL && bs.shop.port_name[0] != 0) {
+ /*
+ * Since the local interface spec is optional, try
+ * searching the key without it as well.
+ */
+ _shop_key2(&bs, &shop);
+ bsp = hash_lookup(bfd_shop_hash, &bs);
+ }
+
+ return bsp;
+}
+
+struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop)
+{
+ struct bfd_session bs;
+
+ _mhop_key(&bs, &mhop);
+
+ return hash_lookup(bfd_shop_hash, &bs);
+}
+
+struct bfd_vrf *bfd_vrf_lookup(int vrf_id)
+{
+ struct bfd_vrf vrf;
+
+ vrf.vrf_id = vrf_id;
+
+ return hash_lookup(bfd_vrf_hash, &vrf);
+}
+
+struct bfd_iface *bfd_iface_lookup(const char *ifname)
+{
+ struct bfd_iface iface;
+
+ if (_iface_key(&iface, ifname) != 0)
+ return NULL;
+
+ return hash_lookup(bfd_iface_hash, &iface);
+}
+
+/*
+ * 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_shop_delete(struct bfd_shop_key shop)
+{
+ struct bfd_session bs, *bsp;
+
+ _shop_key(&bs, &shop);
+ bsp = hash_release(bfd_shop_hash, &bs);
+ if (bsp == NULL && shop.port_name[0] != 0) {
+ /*
+ * Since the local interface spec is optional, try
+ * searching the key without it as well.
+ */
+ _shop_key2(&bs, &shop);
+ bsp = hash_release(bfd_shop_hash, &bs);
+ }
+
+ return bsp;
+}
+
+struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop)
+{
+ struct bfd_session bs;
+
+ _mhop_key(&bs, &mhop);
+
+ return hash_release(bfd_mhop_hash, &bs);
+}
+
+struct bfd_vrf *bfd_vrf_delete(int vrf_id)
+{
+ struct bfd_vrf vrf;
+
+ vrf.vrf_id = vrf_id;
+
+ return hash_release(bfd_vrf_hash, &vrf);
+}
+
+struct bfd_iface *bfd_iface_delete(const char *ifname)
+{
+ struct bfd_iface iface;
+
+ if (_iface_key(&iface, ifname) != 0)
+ return NULL;
+
+ return hash_release(bfd_iface_hash, &iface);
+}
+
+/* Iteration functions. */
+void bfd_id_iterate(hash_iter_func hif, void *arg)
+{
+ hash_iterate(bfd_id_hash, hif, arg);
+}
+
+void bfd_shop_iterate(hash_iter_func hif, void *arg)
+{
+ hash_iterate(bfd_shop_hash, hif, arg);
+}
+
+void bfd_mhop_iterate(hash_iter_func hif, void *arg)
+{
+ hash_iterate(bfd_mhop_hash, hif, arg);
+}
+
+void bfd_vrf_iterate(hash_iter_func hif, void *arg)
+{
+ hash_iterate(bfd_vrf_hash, hif, arg);
+}
+
+void bfd_iface_iterate(hash_iter_func hif, void *arg)
+{
+ hash_iterate(bfd_iface_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_shop_insert(struct bfd_session *bs)
+{
+ return (hash_get(bfd_shop_hash, bs, hash_alloc_intern) == bs);
+}
+
+bool bfd_mhop_insert(struct bfd_session *bs)
+{
+ return (hash_get(bfd_mhop_hash, bs, hash_alloc_intern) == bs);
+}
+
+bool bfd_vrf_insert(struct bfd_vrf *vrf)
+{
+ return (hash_get(bfd_vrf_hash, vrf, hash_alloc_intern) == vrf);
+}
+
+bool bfd_iface_insert(struct bfd_iface *iface)
+{
+ return (hash_get(bfd_iface_hash, iface, hash_alloc_intern) == iface);
+}
+
+void bfd_initialize(void)
+{
+ bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp,
+ "BFD discriminator hash");
+ bfd_shop_hash = hash_create(bfd_shop_hash_do, bfd_shop_hash_cmp,
+ "BFD single hop hash");
+ bfd_mhop_hash = hash_create(bfd_mhop_hash_do, bfd_mhop_hash_cmp,
+ "BFD multihop hop hash");
+ bfd_vrf_hash =
+ hash_create(bfd_vrf_hash_do, bfd_vrf_hash_cmp, "BFD VRF hash");
+ bfd_iface_hash = hash_create(bfd_iface_hash_do, bfd_iface_hash_cmp,
+ "BFD interface hash");
+}
+
+static void _bfd_free(struct hash_backet *hb,
+ void *arg __attribute__((__unused__)))
+{
+ struct bfd_session *bs = hb->data;
+
+ bfd_session_free(bs);
+}
+
+static void _vrf_free(void *arg)
+{
+ struct bfd_vrf *vrf = arg;
+
+ XFREE(MTYPE_BFDD_CONFIG, vrf);
+}
+
+static void _iface_free(void *arg)
+{
+ struct bfd_iface *iface = arg;
+
+ XFREE(MTYPE_BFDD_CONFIG, iface);
+}
+
+void bfd_shutdown(void)
+{
+ /*
+ * 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_shop_hash->count == 0);
+ assert(bfd_mhop_hash->count == 0);
+
+ /* Clean the VRF and interface hashes. */
+ hash_clean(bfd_vrf_hash, _vrf_free);
+ hash_clean(bfd_iface_hash, _iface_free);
+
+ /* Now free the hashes themselves. */
+ hash_free(bfd_id_hash);
+ hash_free(bfd_shop_hash);
+ hash_free(bfd_mhop_hash);
+ hash_free(bfd_vrf_hash);
+ hash_free(bfd_iface_hash);
+}
diff --git a/bfdd/bfd.h b/bfdd/bfd.h
new file mode 100644
index 0000000000..40aecaa67b
--- /dev/null
+++ b/bfdd/bfd.h
@@ -0,0 +1,631 @@
+/*********************************************************************
+ * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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 "bfdctl.h"
+
+#define ETHERNET_ADDRESS_LENGTH 6
+
+#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_TMP);
+DECLARE_MTYPE(BFDD_CONFIG);
+DECLARE_MTYPE(BFDD_LABEL);
+DECLARE_MTYPE(BFDD_CONTROL);
+DECLARE_MTYPE(BFDD_NOTIFICATION);
+
+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 Echo packet.
+ */
+struct bfd_echo_pkt {
+ union {
+ uint32_t byteFields;
+ struct {
+ uint8_t ver;
+ uint8_t len;
+ uint16_t reserved;
+ };
+ };
+ uint32_t my_discr;
+ uint8_t pad[16];
+};
+
+
+/* Macros for manipulating control packets */
+#define BFD_VERMASK 0x03
+#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_DIAGNEIGHDOWN 3
+#define BFD_DIAGDETECTTIME 1
+#define BFD_DIAGADMINDOWN 7
+#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_ECHO_VERSION 1
+#define BFD_ECHO_PKT_LEN sizeof(struct bfd_echo_pkt)
+#define BFD_CTRL_PKT_LEN sizeof(struct bfd_pkt)
+#define IP_HDR_LEN 20
+#define UDP_HDR_LEN 8
+#define ETH_HDR_LEN 14
+#define VXLAN_HDR_LEN 8
+#define HEADERS_MIN_LEN (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN)
+#define BFD_ECHO_PKT_TOT_LEN \
+ ((int)(ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN + BFD_ECHO_PKT_LEN))
+#define BFD_VXLAN_PKT_TOT_LEN \
+ ((int)(VXLAN_HDR_LEN + ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN \
+ + BFD_CTRL_PKT_LEN))
+#define BFD_RX_BUF_LEN 160
+
+/* 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_VXLAN = 1 << 3, /* BFD Multi-hop session which is
+ * used to monitor vxlan tunnel
+ */
+ 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 */
+};
+
+#define BFD_SET_FLAG(field, flag) (field |= flag)
+#define BFD_UNSET_FLAG(field, flag) (field &= ~flag)
+#define BFD_CHECK_FLAG(field, flag) (field & flag)
+
+/* BFD session hash keys */
+struct bfd_shop_key {
+ struct sockaddr_any peer;
+ char port_name[MAXNAMELEN + 1];
+};
+
+struct bfd_mhop_key {
+ struct sockaddr_any peer;
+ struct sockaddr_any local;
+ char vrf_name[MAXNAMELEN + 1];
+};
+
+struct bfd_session_stats {
+ uint64_t rx_ctrl_pkt;
+ uint64_t tx_ctrl_pkt;
+ uint64_t rx_echo_pkt;
+ uint64_t tx_echo_pkt;
+};
+
+struct bfd_session_vxlan_info {
+ uint32_t vnid;
+ uint32_t decay_min_rx;
+ uint8_t forwarding_if_rx;
+ uint8_t cpath_down;
+ uint8_t check_tnl_key;
+ uint8_t local_dst_mac[ETHERNET_ADDRESS_LENGTH];
+ uint8_t peer_dst_mac[ETHERNET_ADDRESS_LENGTH];
+ struct in_addr local_dst_ip;
+ struct in_addr peer_dst_ip;
+};
+
+/* bfd_session shortcut label forwarding. */
+struct peer_label;
+
+/*
+ * 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;
+
+ /* Timers */
+ struct bfd_timers timers;
+ struct bfd_timers new_timers;
+ uint32_t up_min_tx;
+ uint64_t detect_TO;
+ struct thread *echo_recvtimer_ev;
+ struct thread *recvtimer_ev;
+ uint64_t xmt_TO;
+ uint64_t echo_xmt_TO;
+ struct thread *xmttimer_ev;
+ struct thread *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 peer_label *pl;
+ union {
+ struct bfd_shop_key shop;
+ struct bfd_mhop_key mhop;
+ };
+ int sock;
+
+ struct sockaddr_any local_address;
+ struct sockaddr_any local_ip;
+ int ifindex;
+ uint8_t local_mac[ETHERNET_ADDRESS_LENGTH];
+ uint8_t peer_mac[ETHERNET_ADDRESS_LENGTH];
+ uint16_t ip_id;
+
+ /* BFD session flags */
+ enum bfd_session_flags flags;
+
+ uint8_t echo_pkt[BFD_ECHO_PKT_TOT_LEN]; /* Save the Echo Packet
+ * which will be transmitted
+ */
+ struct bfd_session_stats stats;
+ struct bfd_session_vxlan_info vxlan_info;
+
+ 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. */
+
+ /* VTY context data. */
+ QOBJ_FIELDS;
+};
+DECLARE_QOBJ_TYPE(bfd_session);
+
+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_vrf {
+ int vrf_id;
+ char name[MAXNAMELEN + 1];
+} bfd_vrf;
+
+struct bfd_iface {
+ int vrf_id;
+ char ifname[MAXNAMELEN + 1];
+} bfd_iface;
+
+
+/* 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 MSEC_PER_SEC 1000L
+#define NSEC_PER_MSEC 1000000L
+
+#define BFD_DEF_DEMAND 0
+#define BFD_DEFDETECTMULT 3
+#define BFD_DEFDESIREDMINTX (300 * MSEC_PER_SEC)
+#define BFD_DEFREQUIREDMINRX (300 * MSEC_PER_SEC)
+#define BFD_DEF_REQ_MIN_ECHO (50 * MSEC_PER_SEC)
+#define BFD_DEF_SLOWTX (2000 * MSEC_PER_SEC)
+#define BFD_DEF_MHOP_TTL 5
+#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 49142
+#define BFD_SRCPORTMAX 65536
+#define BFD_DEFDESTPORT 3784
+#define BFD_DEF_ECHO_PORT 3785
+#define BFD_DEF_MHOP_DEST_PORT 4784
+#define BFD_CMD_STRING_LEN (MAXNAMELEN + 50)
+#define BFD_BUFFER_LEN (BFD_CMD_STRING_LEN + MAXNAMELEN + 1)
+
+/*
+ * 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 thread *bcs_ev;
+ struct thread *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);
+int control_notify_config(const char *op, struct bfd_session *bs);
+int control_accept(struct thread *t);
+
+
+/*
+ * bfdd.c
+ *
+ * Daemon specific code.
+ */
+struct bfd_global {
+ int bg_shop;
+ int bg_mhop;
+ int bg_shop6;
+ int bg_mhop6;
+ int bg_echo;
+ int bg_vxlan;
+ struct thread *bg_ev[6];
+
+ int bg_csock;
+ struct thread *bg_csockev;
+ struct bcslist bg_bcslist;
+
+ struct pllist bg_pllist;
+};
+extern struct bfd_global bglobal;
+extern struct bfd_diag_str_list diag_list[];
+extern 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);
+
+
+/*
+ * log.c
+ *
+ * Contains code that does the logging procedures. Might implement multiple
+ * backends (e.g. zebra log, syslog or other logging lib).
+ */
+enum blog_level {
+ /* level vs syslog equivalent */
+ BLOG_DEBUG = 0, /* LOG_DEBUG */
+ BLOG_INFO = 1, /* LOG_INFO */
+ BLOG_WARNING = 2, /* LOG_WARNING */
+ BLOG_ERROR = 3, /* LOG_ERR */
+ BLOG_FATAL = 4, /* LOG_CRIT */
+};
+
+void log_init(int foreground, enum blog_level level,
+ struct frr_daemon_info *fdi);
+void log_info(const char *fmt, ...);
+void log_debug(const char *fmt, ...);
+void log_warning(const char *fmt, ...);
+void log_error(const char *fmt, ...);
+void log_fatal(const char *fmt, ...);
+
+
+/*
+ * bfd_packet.c
+ *
+ * Contains the code related with receiving/seding, packing/unpacking BFD data.
+ */
+int bp_set_ttlv6(int sd);
+int bp_set_ttl(int sd);
+int bp_set_tosv6(int sd);
+int bp_set_tos(int sd);
+int bp_bind_dev(int sd, const char *dev);
+
+int bp_udp_shop(void);
+int bp_udp_mhop(void);
+int bp_udp6_shop(void);
+int bp_udp6_mhop(void);
+int bp_peer_socket(struct bfd_peer_cfg *bpc);
+int bp_peer_socketv6(struct bfd_peer_cfg *bpc);
+
+void ptm_bfd_snd(struct bfd_session *bfd, int fbit);
+void ptm_bfd_echo_snd(struct bfd_session *bfd);
+
+int bfd_recv_cb(struct thread *t);
+
+uint16_t checksum(uint16_t *buf, int len);
+
+
+/*
+ * event.c
+ *
+ * Contains the code related with event loop.
+ */
+typedef void (*bfd_ev_cb)(struct thread *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.
+ */
+struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc);
+int ptm_bfd_ses_del(struct bfd_peer_cfg *bpc);
+void ptm_bfd_ses_dn(struct bfd_session *bfd, uint8_t diag);
+void ptm_bfd_ses_up(struct bfd_session *bfd);
+void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen);
+void ptm_bfd_echo_stop(struct bfd_session *bfd, int polling);
+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, char *port_name,
+ struct sockaddr_any *peer,
+ struct sockaddr_any *local,
+ char *vrf_name, 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);
+const char *satostr(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(struct bfd_session *bs);
+
+/* BFD hash data structures interface */
+void bfd_initialize(void);
+void bfd_shutdown(void);
+struct bfd_session *bfd_id_lookup(uint32_t id);
+struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop);
+struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop);
+struct bfd_vrf *bfd_vrf_lookup(int vrf_id);
+struct bfd_iface *bfd_iface_lookup(const char *ifname);
+
+struct bfd_session *bfd_id_delete(uint32_t id);
+struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop);
+struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop);
+struct bfd_vrf *bfd_vrf_delete(int vrf_id);
+struct bfd_iface *bfd_iface_delete(const char *ifname);
+
+bool bfd_id_insert(struct bfd_session *bs);
+bool bfd_shop_insert(struct bfd_session *bs);
+bool bfd_mhop_insert(struct bfd_session *bs);
+bool bfd_vrf_insert(struct bfd_vrf *vrf);
+bool bfd_iface_insert(struct bfd_iface *iface);
+
+typedef void (*hash_iter_func)(struct hash_backet *hb, void *arg);
+void bfd_id_iterate(hash_iter_func hif, void *arg);
+void bfd_shop_iterate(hash_iter_func hif, void *arg);
+void bfd_mhop_iterate(hash_iter_func hif, void *arg);
+void bfd_vrf_iterate(hash_iter_func hif, void *arg);
+void bfd_iface_iterate(hash_iter_func hif, void *arg);
+
+/* Export callback functions for `event.c`. */
+extern struct thread_master *master;
+
+int bfd_recvtimer_cb(struct thread *t);
+int bfd_echo_recvtimer_cb(struct thread *t);
+int bfd_xmt_cb(struct thread *t);
+int bfd_echo_xmt_cb(struct thread *t);
+
+
+/*
+ * bfdd_vty.c
+ *
+ * BFD daemon vty shell commands.
+ */
+void bfdd_vty_init(void);
+
+
+/*
+ * ptm_adapter.c
+ */
+void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv);
+void bfdd_zclient_stop(void);
+
+int ptm_bfd_notify(struct bfd_session *bs);
+
+
+/*
+ * OS compatibility functions.
+ */
+struct udp_psuedo_header {
+ uint32_t saddr;
+ uint32_t daddr;
+ uint8_t reserved;
+ uint8_t protocol;
+ uint16_t len;
+};
+
+#define UDP_PSUEDO_HDR_LEN sizeof(struct udp_psuedo_header)
+
+#if defined(BFD_LINUX) || defined(BFD_BSD)
+int ptm_bfd_fetch_ifindex(const char *ifname);
+void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac);
+void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen);
+int ptm_bfd_echo_sock_init(void);
+int ptm_bfd_vxlan_sock_init(void);
+#endif /* BFD_LINUX || BFD_BSD */
+
+#ifdef BFD_LINUX
+uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len);
+#endif /* BFD_LINUX */
+
+#ifdef BFD_BSD
+uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len);
+ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen,
+ struct sockaddr_storage *ss, socklen_t *sslen,
+ uint8_t *ttl, uint32_t *id);
+#endif /* BFD_BSD */
+
+#endif /* _BFD_H_ */
diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c
new file mode 100644
index 0000000000..19cb8547ab
--- /dev/null
+++ b/bfdd/bfd_packet.c
@@ -0,0 +1,1533 @@
+/*********************************************************************
+ * Copyright 2017 Cumulus Networks, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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 "bfd.h"
+
+/*
+ * Definitions
+ */
+
+/* iov for BFD control frames */
+#define CMSG_HDR_LEN sizeof(struct cmsghdr)
+#define CMSG_TTL_LEN (CMSG_HDR_LEN + sizeof(uint32_t))
+#define CMSG_IN_PKT_INFO_LEN (CMSG_HDR_LEN + sizeof(struct in_pktinfo) + 4)
+#define CMSG_IN6_PKT_INFO_LEN \
+ (CMSG_HDR_LEN + sizeof(struct in6_addr) + sizeof(int) + 4)
+
+struct bfd_raw_echo_pkt {
+#ifdef BFD_LINUX
+ struct iphdr ip;
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ struct ip ip;
+#endif /* BFD_BSD */
+ struct udphdr udp;
+ struct bfd_echo_pkt data;
+};
+
+#if 0 /* TODO: VxLAN support. */
+struct bfd_raw_ctrl_pkt {
+ struct iphdr ip;
+ struct udphdr udp;
+ struct bfd_pkt data;
+};
+#endif
+
+struct vxlan_hdr {
+ uint32_t flags;
+ uint32_t vnid;
+};
+
+#define IP_ECHO_PKT_LEN (IP_HDR_LEN + UDP_HDR_LEN + BFD_ECHO_PKT_LEN)
+#define UDP_ECHO_PKT_LEN (UDP_HDR_LEN + BFD_ECHO_PKT_LEN)
+#define IP_CTRL_PKT_LEN (IP_HDR_LEN + UDP_HDR_LEN + BFD_PKT_LEN)
+#define UDP_CTRL_PKT_LEN (UDP_HDR_LEN + BFD_PKT_LEN)
+
+static uint8_t msgbuf[BFD_PKT_LEN];
+static struct iovec msgiov = {&(msgbuf[0]), sizeof(msgbuf)};
+static uint8_t cmsgbuf[255];
+
+static struct sockaddr_in msgaddr;
+static struct msghdr msghdr = {(void *)&msgaddr, sizeof(msgaddr), &msgiov, 1,
+ (void *)&cmsgbuf, sizeof(cmsgbuf), 0};
+
+static uint8_t cmsgbuf6[255];
+
+static struct sockaddr_in6 msgaddr6;
+static struct msghdr msghdr6 = {(void *)&msgaddr6, sizeof(msgaddr6), &msgiov, 1,
+ (void *)&cmsgbuf6, sizeof(cmsgbuf6), 0};
+
+static int ttlval = BFD_TTL_VAL;
+static int tosval = BFD_TOS_VAL;
+static int rcvttl = BFD_RCV_TTL_VAL;
+
+/*
+ * Prototypes
+ */
+static uint16_t ptm_bfd_gen_IP_ID(struct bfd_session *bfd);
+static void ptm_bfd_echo_pkt_create(struct bfd_session *bfd);
+static int ptm_bfd_echo_loopback(uint8_t *pkt, int pkt_len, struct sockaddr *ss,
+ socklen_t sslen);
+static void ptm_bfd_vxlan_pkt_snd(struct bfd_session *bfd, int fbit);
+static int ptm_bfd_process_echo_pkt(int s);
+static bool
+ptm_bfd_validate_vxlan_pkt(struct bfd_session *bfd,
+ struct bfd_session_vxlan_info *vxlan_info);
+
+static void bfd_sd_reschedule(int sd);
+static ssize_t bfd_recv_ipv4(int sd, bool is_mhop, char *port, size_t portlen,
+ char *vrfname, size_t vrfnamelen,
+ struct sockaddr_any *local,
+ struct sockaddr_any *peer);
+static ssize_t bfd_recv_ipv6(int sd, bool is_mhop, char *port, size_t portlen,
+ char *vrfname, size_t vrfnamelen,
+ struct sockaddr_any *local,
+ struct sockaddr_any *peer);
+
+/* 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
+ */
+uint16_t checksum(uint16_t *buf, int len)
+{
+ int nbytes = len;
+ int sum = 0;
+ uint16_t csum = 0;
+ int size = sizeof(uint16_t);
+
+ while (nbytes > 1) {
+ sum += *buf++;
+ nbytes -= size;
+ }
+
+ if (nbytes == 1) {
+ *(uint8_t *)(&csum) = *(uint8_t *)buf;
+ sum += csum;
+ }
+
+ sum = (sum >> 16) + (sum & 0xFFFF);
+ sum += (sum >> 16);
+ csum = ~sum;
+ return csum;
+}
+
+static uint16_t ptm_bfd_gen_IP_ID(struct bfd_session *bfd)
+{
+ return (++bfd->ip_id);
+}
+
+static int _ptm_bfd_send(struct bfd_session *bs, bool use_layer2,
+ uint16_t *port, const void *data, size_t datalen)
+{
+ struct sockaddr *sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+#ifdef BFD_LINUX
+ struct sockaddr_ll dll;
+#endif /* BFD_LINUX */
+ socklen_t slen;
+ ssize_t rv;
+ int sd = -1;
+
+ if (use_layer2) {
+#ifdef BFD_LINUX
+ memset(&dll, 0, sizeof(dll));
+ dll.sll_family = AF_PACKET;
+ dll.sll_protocol = htons(ETH_P_IP);
+ memcpy(dll.sll_addr, bs->peer_mac, ETHERNET_ADDRESS_LENGTH);
+ dll.sll_halen = htons(ETHERNET_ADDRESS_LENGTH);
+ dll.sll_ifindex = bs->ifindex;
+
+ sd = bglobal.bg_echo;
+ sa = (struct sockaddr *)&dll;
+ slen = sizeof(dll);
+#else
+ /*
+ * TODO: implement layer 2 send for *BSDs. This is
+ * needed for VxLAN.
+ */
+ log_warning("packet-send: not implemented");
+ return -1;
+#endif
+ } else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) {
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_addr = bs->shop.peer.sa_sin6.sin6_addr;
+ sin6.sin6_port =
+ (port) ? *port
+ : (BFD_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;
+ sin.sin_addr = bs->shop.peer.sa_sin.sin_addr;
+ sin.sin_port =
+ (port) ? *port
+ : (BFD_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) {
+ log_debug("packet-send: send failure: %s", strerror(errno));
+ return -1;
+ }
+ if (rv < (ssize_t)datalen)
+ log_debug("packet-send: send partial", strerror(errno));
+
+ return 0;
+}
+
+static void ptm_bfd_echo_pkt_create(struct bfd_session *bfd)
+{
+ struct bfd_raw_echo_pkt ep;
+ uint8_t *pkt = bfd->echo_pkt;
+
+ memset(&ep, 0, sizeof(ep));
+ memset(bfd->echo_pkt, 0, sizeof(bfd->echo_pkt));
+
+ /* Construct ethernet header information */
+ memcpy(pkt, bfd->peer_mac, ETHERNET_ADDRESS_LENGTH);
+ pkt = pkt + ETHERNET_ADDRESS_LENGTH;
+ memcpy(pkt, bfd->local_mac, ETHERNET_ADDRESS_LENGTH);
+ pkt = pkt + ETHERNET_ADDRESS_LENGTH;
+#ifdef BFD_LINUX
+ pkt[0] = ETH_P_IP / 256;
+ pkt[1] = ETH_P_IP % 256;
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ pkt[0] = ETHERTYPE_IP / 256;
+ pkt[1] = ETHERTYPE_IP % 256;
+#endif /* BFD_BSD */
+ pkt += 2;
+
+ /* Construct IP header information */
+#ifdef BFD_LINUX
+ ep.ip.version = 4;
+ ep.ip.ihl = 5;
+ ep.ip.tos = 0;
+ ep.ip.tot_len = htons(IP_ECHO_PKT_LEN);
+ ep.ip.id = htons(ptm_bfd_gen_IP_ID(bfd));
+ ep.ip.frag_off = 0;
+ ep.ip.ttl = BFD_TTL_VAL;
+ ep.ip.protocol = IPPROTO_UDP;
+ ep.ip.saddr = bfd->local_ip.sa_sin.sin_addr.s_addr;
+ ep.ip.daddr = bfd->shop.peer.sa_sin.sin_addr.s_addr;
+ ep.ip.check = checksum((uint16_t *)&ep.ip, IP_HDR_LEN);
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ ep.ip.ip_v = 4;
+ ep.ip.ip_hl = 5;
+ ep.ip.ip_tos = 0;
+ ep.ip.ip_len = htons(IP_ECHO_PKT_LEN);
+ ep.ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd));
+ ep.ip.ip_off = 0;
+ ep.ip.ip_ttl = BFD_TTL_VAL;
+ ep.ip.ip_p = IPPROTO_UDP;
+ ep.ip.ip_src = bfd->local_ip.sa_sin.sin_addr;
+ ep.ip.ip_dst = bfd->shop.peer.sa_sin.sin_addr;
+ ep.ip.ip_sum = checksum((uint16_t *)&ep.ip, IP_HDR_LEN);
+#endif /* BFD_BSD */
+
+ /* Construct UDP header information */
+#ifdef BFD_LINUX
+ ep.udp.source = htons(BFD_DEF_ECHO_PORT);
+ ep.udp.dest = htons(BFD_DEF_ECHO_PORT);
+ ep.udp.len = htons(UDP_ECHO_PKT_LEN);
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ ep.udp.uh_sport = htons(BFD_DEF_ECHO_PORT);
+ ep.udp.uh_dport = htons(BFD_DEF_ECHO_PORT);
+ ep.udp.uh_ulen = htons(UDP_ECHO_PKT_LEN);
+#endif /* BFD_BSD */
+
+ /* Construct Echo packet information */
+ ep.data.ver = BFD_ECHO_VERSION;
+ ep.data.len = BFD_ECHO_PKT_LEN;
+ ep.data.my_discr = htonl(bfd->discrs.my_discr);
+#ifdef BFD_LINUX
+ ep.udp.check =
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ ep.udp.uh_sum =
+#endif /* BFD_BSD */
+ udp4_checksum(&ep.ip, (uint8_t *)&ep.udp,
+ UDP_ECHO_PKT_LEN);
+
+ memcpy(pkt, &ep, sizeof(ep));
+}
+
+void ptm_bfd_echo_snd(struct bfd_session *bfd)
+{
+ struct bfd_raw_echo_pkt *ep;
+ bool use_layer2 = false;
+ const void *pkt;
+ size_t pktlen;
+ uint16_t port = htons(BFD_DEF_ECHO_PORT);
+
+ if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) {
+ ptm_bfd_echo_pkt_create(bfd);
+ BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE);
+ } else {
+ /* just update the checksum and ip Id */
+ ep = (struct bfd_raw_echo_pkt *)(bfd->echo_pkt + ETH_HDR_LEN);
+#ifdef BFD_LINUX
+ ep->ip.id = htons(ptm_bfd_gen_IP_ID(bfd));
+ ep->ip.check = 0;
+ ep->ip.check = checksum((uint16_t *)&ep->ip, IP_HDR_LEN);
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ ep->ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd));
+ ep->ip.ip_sum = 0;
+ ep->ip.ip_sum = checksum((uint16_t *)&ep->ip, IP_HDR_LEN);
+#endif /* BFD_BSD */
+ }
+
+ if (use_layer2) {
+ pkt = bfd->echo_pkt;
+ pktlen = BFD_ECHO_PKT_TOT_LEN;
+ } else {
+ pkt = &bfd->echo_pkt[ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN];
+ pktlen = BFD_ECHO_PKT_TOT_LEN
+ - (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN);
+ }
+
+ if (_ptm_bfd_send(bfd, use_layer2, &port, pkt, pktlen) != 0) {
+ log_debug("echo-packet: send failure: %s", strerror(errno));
+ return;
+ }
+
+ bfd->stats.tx_echo_pkt++;
+}
+
+static int ptm_bfd_echo_loopback(uint8_t *pkt, int pkt_len, struct sockaddr *ss,
+ socklen_t sslen)
+{
+#ifdef BFD_LINUX
+ struct bfd_raw_echo_pkt *ep =
+ (struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN);
+ uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH];
+ uint32_t temp_ip;
+ struct ethhdr *eth = (struct ethhdr *)pkt;
+
+ /* swap the mac addresses */
+ memcpy(temp_mac, eth->h_source, ETHERNET_ADDRESS_LENGTH);
+ memcpy(eth->h_source, eth->h_dest, ETHERNET_ADDRESS_LENGTH);
+ memcpy(eth->h_dest, temp_mac, ETHERNET_ADDRESS_LENGTH);
+
+ /* swap ip addresses */
+ temp_ip = ep->ip.saddr;
+ ep->ip.saddr = ep->ip.daddr;
+ ep->ip.daddr = temp_ip;
+
+ ep->ip.ttl = ep->ip.ttl - 1;
+ ep->ip.check = 0;
+ ep->ip.check = checksum((uint16_t *)ep, IP_HDR_LEN);
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD_FILTER
+ struct bfd_raw_echo_pkt_t *ep =
+ (struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN);
+ uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH];
+ struct in_addr temp_ip;
+ struct ether_header *ether = (struct ether_header *)pkt;
+
+ /*
+ * TODO: this is not yet implemented and requires BPF code for
+ * OmniOS, NetBSD and FreeBSD9.
+ */
+
+ /* swap the mac addresses */
+ memcpy(temp_mac, ether->ether_shost, ETHERNET_ADDRESS_LENGTH);
+ memcpy(ether->ether_shost, ether->ether_dhost, ETHERNET_ADDRESS_LENGTH);
+ memcpy(ether->ether_dhost, temp_mac, ETHERNET_ADDRESS_LENGTH);
+
+ /* swap ip addresses */
+ temp_ip = ep->ip.ip_src;
+ ep->ip.ip_src = ep->ip.ip_dst;
+ ep->ip.ip_dst = temp_ip;
+
+ ep->ip.ip_ttl = ep->ip.ip_ttl - 1;
+ ep->ip.ip_sum = 0;
+ ep->ip.ip_sum = checksum((uint16_t *)ep, IP_HDR_LEN);
+#endif /* BFD_BSD_FILTER */
+
+ if (sendto(bglobal.bg_echo, pkt, pkt_len, 0, ss, sslen) < 0) {
+ log_debug("echo-loopback: send failure: %s", strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ptm_bfd_vxlan_pkt_snd(struct bfd_session *bfd
+ __attribute__((__unused__)),
+ int fbit __attribute__((__unused__)))
+{
+#if 0 /* TODO: VxLAN support. */
+ struct bfd_raw_ctrl_pkt cp;
+ uint8_t vxlan_pkt[BFD_VXLAN_PKT_TOT_LEN];
+ uint8_t *pkt = vxlan_pkt;
+ struct sockaddr_in sin;
+ struct vxlan_hdr *vhdr;
+
+ memset(vxlan_pkt, 0, sizeof(vxlan_pkt));
+ memset(&cp, 0, sizeof(cp));
+
+ /* Construct VxLAN header information */
+ vhdr = (struct vxlan_hdr *)pkt;
+ vhdr->flags = htonl(0x08000000);
+ vhdr->vnid = htonl(bfd->vxlan_info.vnid << 8);
+ pkt += VXLAN_HDR_LEN;
+
+ /* Construct ethernet header information */
+ memcpy(pkt, bfd->vxlan_info.peer_dst_mac, ETHERNET_ADDRESS_LENGTH);
+ pkt = pkt + ETHERNET_ADDRESS_LENGTH;
+ memcpy(pkt, bfd->vxlan_info.local_dst_mac, ETHERNET_ADDRESS_LENGTH);
+ pkt = pkt + ETHERNET_ADDRESS_LENGTH;
+ pkt[0] = ETH_P_IP / 256;
+ pkt[1] = ETH_P_IP % 256;
+ pkt += 2;
+
+ /* Construct IP header information */
+ cp.ip.version = 4;
+ cp.ip.ihl = 5;
+ cp.ip.tos = 0;
+ cp.ip.tot_len = htons(IP_CTRL_PKT_LEN);
+ cp.ip.id = ptm_bfd_gen_IP_ID(bfd);
+ cp.ip.frag_off = 0;
+ cp.ip.ttl = BFD_TTL_VAL;
+ cp.ip.protocol = IPPROTO_UDP;
+ cp.ip.daddr = bfd->vxlan_info.peer_dst_ip.s_addr;
+ cp.ip.saddr = bfd->vxlan_info.local_dst_ip.s_addr;
+ cp.ip.check = checksum((uint16_t *)&cp.ip, IP_HDR_LEN);
+
+ /* Construct UDP header information */
+ cp.udp.source = htons(BFD_DEFDESTPORT);
+ cp.udp.dest = htons(BFD_DEFDESTPORT);
+ cp.udp.len = htons(UDP_CTRL_PKT_LEN);
+
+ /* Construct BFD control packet information */
+ cp.data.diag = bfd->local_diag;
+ BFD_SETVER(cp.data.diag, BFD_VERSION);
+ BFD_SETSTATE(cp.data.flags, bfd->ses_state);
+ BFD_SETDEMANDBIT(cp.data.flags, BFD_DEF_DEMAND);
+ BFD_SETPBIT(cp.data.flags, bfd->polling);
+ BFD_SETFBIT(cp.data.flags, fbit);
+ cp.data.detect_mult = bfd->detect_mult;
+ cp.data.len = BFD_PKT_LEN;
+ cp.data.discrs.my_discr = htonl(bfd->discrs.my_discr);
+ cp.data.discrs.remote_discr = htonl(bfd->discrs.remote_discr);
+ cp.data.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx);
+ cp.data.timers.required_min_rx = htonl(bfd->timers.required_min_rx);
+ cp.data.timers.required_min_echo = htonl(bfd->timers.required_min_echo);
+
+ cp.udp.check =
+ udp4_checksum(&cp.ip, (uint8_t *)&cp.udp, UDP_CTRL_PKT_LEN);
+
+ memcpy(pkt, &cp, sizeof(cp));
+ sin.sin_family = AF_INET;
+ sin.sin_addr = bfd->shop.peer.sa_sin.sin_addr;
+ sin.sin_port = htons(4789);
+
+ if (sendto(bfd->sock, vxlan_pkt, BFD_VXLAN_PKT_TOT_LEN, 0,
+ (struct sockaddr *)&sin, sizeof(struct sockaddr_in))
+ < 0) {
+ ERRLOG("Error sending vxlan bfd pkt: %s", strerror(errno));
+ } else {
+ bfd->stats.tx_ctrl_pkt++;
+ }
+#endif
+}
+
+static int ptm_bfd_process_echo_pkt(int s)
+{
+ uint32_t my_discr = 0;
+ struct sockaddr_storage ss;
+ socklen_t sslen = sizeof(ss);
+ uint8_t rx_pkt[BFD_RX_BUF_LEN];
+ ssize_t pkt_len = sizeof(rx_pkt);
+ struct bfd_session *bfd;
+#ifdef BFD_LINUX
+ struct bfd_raw_echo_pkt *ep;
+
+ /*
+ * valgrind: memset() ss so valgrind doesn't complain about
+ * uninitialized memory.
+ */
+ memset(&ss, 0, sizeof(ss));
+ pkt_len = recvfrom(s, rx_pkt, sizeof(rx_pkt), MSG_DONTWAIT,
+ (struct sockaddr *)&ss, &sslen);
+ if (pkt_len <= 0) {
+ if (errno != EAGAIN)
+ log_error("echo-packet: read failure: %s",
+ strerror(errno));
+
+ return -1;
+ }
+
+ /* Check if we have at least the basic headers to send back. */
+ if (pkt_len < BFD_ECHO_PKT_TOT_LEN) {
+ log_debug("echo-packet: too short (got %ld, expected %d)",
+ pkt_len, BFD_ECHO_PKT_TOT_LEN);
+ return -1;
+ }
+
+ ep = (struct bfd_raw_echo_pkt *)(rx_pkt + ETH_HDR_LEN);
+ /* if TTL = 255, assume that the received echo packet has
+ * to be looped back
+ */
+ if (ep->ip.ttl == BFD_TTL_VAL)
+ return ptm_bfd_echo_loopback(rx_pkt, pkt_len,
+ (struct sockaddr *)&ss,
+ sizeof(struct sockaddr_ll));
+
+ my_discr = ntohl(ep->data.my_discr);
+ if (ep->data.my_discr == 0) {
+ log_debug("echo-packet: 'my discriminator' is zero");
+ return -1;
+ }
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ int rv;
+ uint8_t ttl;
+
+ /*
+ * bsd_echo_sock_read() already treats invalid TTL values and
+ * zeroed discriminators.
+ */
+ rv = bsd_echo_sock_read(s, rx_pkt, &pkt_len, &ss, &sslen, &ttl,
+ &my_discr);
+ if (rv == -1)
+ return -1;
+
+ if (ttl == BFD_TTL_VAL)
+ return ptm_bfd_echo_loopback(rx_pkt, pkt_len,
+ (struct sockaddr *)&ss, sslen);
+#endif /* BFD_BSD */
+
+ /* Your discriminator not zero - use it to find session */
+ bfd = bfd_id_lookup(my_discr);
+ if (bfd == NULL) {
+ log_debug("echo-packet: no matching session (id:%u)", my_discr);
+ return -1;
+ }
+
+ if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) {
+ log_debug("echo-packet: echo disabled [%s]", my_discr,
+ bs_to_string(bfd));
+ return -1;
+ }
+
+ bfd->stats.rx_echo_pkt++;
+
+ /* Compute detect time */
+ bfd->echo_detect_TO = bfd->remote_detect_mult * bfd->echo_xmt_TO;
+
+ /* Update echo receive timeout. */
+ bfd_echo_recvtimer_update(bfd);
+
+ return 0;
+}
+
+void ptm_bfd_snd(struct bfd_session *bfd, int fbit)
+{
+ struct bfd_pkt cp;
+
+ /* if the BFD session is for VxLAN tunnel, then construct and
+ * send bfd raw packet
+ */
+ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) {
+ ptm_bfd_vxlan_pkt_snd(bfd, fbit);
+ return;
+ }
+
+ /* 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);
+ BFD_SETDEMANDBIT(cp.flags, BFD_DEF_DEMAND);
+ BFD_SETPBIT(cp.flags, bfd->polling);
+ BFD_SETFBIT(cp.flags, fbit);
+ 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->new_timers.desired_min_tx);
+ cp.timers.required_min_rx =
+ htonl(bfd->new_timers.required_min_rx);
+ } else {
+ cp.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx);
+ cp.timers.required_min_rx = htonl(bfd->timers.required_min_rx);
+ }
+ cp.timers.required_min_echo = htonl(bfd->timers.required_min_echo);
+
+ if (_ptm_bfd_send(bfd, false, NULL, &cp, BFD_PKT_LEN) != 0)
+ return;
+
+ bfd->stats.tx_ctrl_pkt++;
+}
+
+#if 0 /* TODO VxLAN Support */
+static struct bfd_pkt *
+ptm_bfd_process_vxlan_pkt(int s, ptm_sockevent_e se, void *udata, int *ifindex,
+ struct sockaddr_in *sin,
+ struct bfd_session_vxlan_info_t *vxlan_info,
+ uint8_t *rx_pkt, int *mlen)
+{
+ struct sockaddr_ll sll;
+ uint32_t from_len = sizeof(struct sockaddr_ll);
+ struct bfd_raw_ctrl_pkt *cp;
+ uint8_t *pkt = rx_pkt;
+ struct iphdr *iph;
+ struct ethhdr *inner_ethh;
+
+ *mlen = recvfrom(s, rx_pkt, BFD_RX_BUF_LEN, MSG_DONTWAIT,
+ (struct sockaddr *)&sll, &from_len);
+
+ if (*mlen < 0) {
+ if (errno != EAGAIN)
+ ERRLOG("Error receiving from BFD Vxlan socket %d: %m",
+ s);
+ return NULL;
+ }
+
+ iph = (struct iphdr *)(pkt + ETH_HDR_LEN);
+ pkt = pkt + ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN;
+ vxlan_info->vnid = ntohl(*((int *)(pkt + 4)));
+ vxlan_info->vnid = vxlan_info->vnid >> 8;
+
+ pkt = pkt + VXLAN_HDR_LEN;
+ inner_ethh = (struct ethhdr *)pkt;
+
+ cp = (struct bfd_raw_ctrl_pkt *)(pkt + ETH_HDR_LEN);
+
+ /* Discard the non BFD packets */
+ if (ntohs(cp->udp.dest) != BFD_DEFDESTPORT)
+ return NULL;
+
+ *ifindex = sll.sll_ifindex;
+ sin->sin_addr.s_addr = iph->saddr;
+ sin->sin_port = ntohs(cp->udp.dest);
+
+ vxlan_info->local_dst_ip.s_addr = cp->ip.daddr;
+ memcpy(vxlan_info->local_dst_mac, inner_ethh->h_dest,
+ ETHERNET_ADDRESS_LENGTH);
+
+ return &cp->data;
+}
+#endif /* VxLAN */
+
+static bool
+ptm_bfd_validate_vxlan_pkt(struct bfd_session *bfd,
+ struct bfd_session_vxlan_info *vxlan_info)
+{
+ if (bfd->vxlan_info.check_tnl_key && (vxlan_info->vnid != 0)) {
+ log_error("vxlan-packet: vnid not zero: %d", vxlan_info->vnid);
+ return false;
+ }
+
+ if (bfd->vxlan_info.local_dst_ip.s_addr
+ != vxlan_info->local_dst_ip.s_addr) {
+ log_error("vxlan-packet: wrong inner destination",
+ inet_ntoa(vxlan_info->local_dst_ip));
+ return false;
+ }
+
+ if (memcmp(bfd->vxlan_info.local_dst_mac, vxlan_info->local_dst_mac,
+ ETHERNET_ADDRESS_LENGTH)) {
+ log_error(
+ "vxlan-packet: wrong inner mac: %02x:%02x:%02x:%02x:%02x:%02x",
+ vxlan_info->local_dst_mac[0],
+ vxlan_info->local_dst_mac[1],
+ vxlan_info->local_dst_mac[2],
+ vxlan_info->local_dst_mac[3],
+ vxlan_info->local_dst_mac[4],
+ vxlan_info->local_dst_mac[5]);
+ return false;
+ }
+
+ return true;
+}
+
+static ssize_t bfd_recv_ipv4(int sd, bool is_mhop, char *port, size_t portlen,
+ char *vrfname, size_t vrfnamelen,
+ struct sockaddr_any *local,
+ struct sockaddr_any *peer)
+{
+ struct cmsghdr *cm;
+ int ifindex;
+ ssize_t mlen;
+
+ memset(port, 0, portlen);
+ memset(vrfname, 0, vrfnamelen);
+ memset(local, 0, sizeof(*local));
+ memset(peer, 0, sizeof(*peer));
+
+ mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT);
+ if (mlen == -1) {
+ if (errno != EAGAIN)
+ log_error("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 ttl;
+
+ memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl));
+ if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) {
+ log_debug(
+ "ipv4-recv: invalid TTL from %s (expected %d, got %d flags %d)",
+ satostr(peer), ttl, BFD_TTL_VAL,
+ msghdr.msg_flags);
+ return -1;
+ }
+ 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;
+ fetch_portname_from_ifindex(pi->ipi_ifindex, port,
+ portlen);
+ break;
+ }
+#endif /* BFD_LINUX */
+#ifdef BFD_BSD
+ case IP_RECVTTL: {
+ uint8_t ttl;
+
+ memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl));
+ if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) {
+ log_debug(
+ "ipv4-recv: invalid TTL from %s (expected %d, got %d flags %d)",
+ satostr(peer), ttl, BFD_TTL_VAL,
+ msghdr.msg_flags);
+ return -1;
+ }
+ 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;
+ 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 (port[0] == 0) {
+ ifindex = getsockopt_ifindex(AF_INET, &msghdr);
+ if (ifindex > 0)
+ fetch_portname_from_ifindex(ifindex, port, portlen);
+ }
+
+ return mlen;
+}
+
+ssize_t bfd_recv_ipv6(int sd, bool is_mhop, char *port, size_t portlen,
+ char *vrfname, size_t vrfnamelen,
+ struct sockaddr_any *local, struct sockaddr_any *peer)
+{
+ struct cmsghdr *cm;
+ struct in6_pktinfo *pi6 = NULL;
+ int ifindex = 0;
+ ssize_t mlen;
+
+ memset(port, 0, portlen);
+ memset(vrfname, 0, vrfnamelen);
+ memset(local, 0, sizeof(*local));
+ memset(peer, 0, sizeof(*peer));
+
+ mlen = recvmsg(sd, &msghdr6, MSG_DONTWAIT);
+ if (mlen == -1) {
+ if (errno != EAGAIN)
+ log_error("ipv4-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), 4);
+ if ((is_mhop == false) && (ttlval != BFD_TTL_VAL)) {
+ log_debug(
+ "ipv6-recv: invalid TTL from %s (expected %d, got %d flags %d)",
+ satostr(peer), ttlval, BFD_TTL_VAL,
+ msghdr.msg_flags);
+ return -1;
+ }
+ } else if (cm->cmsg_type == IPV6_PKTINFO) {
+ pi6 = (struct in6_pktinfo *)CMSG_DATA(cm);
+ if (pi6) {
+ local->sa_sin.sin_family = AF_INET6;
+ local->sa_sin6.sin6_addr = pi6->ipi6_addr;
+ fetch_portname_from_ifindex(pi6->ipi6_ifindex,
+ port, portlen);
+ 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(int sd)
+{
+ if (sd == bglobal.bg_shop) {
+ bglobal.bg_ev[0] = NULL;
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop,
+ &bglobal.bg_ev[0]);
+ } else if (sd == bglobal.bg_mhop) {
+ bglobal.bg_ev[1] = NULL;
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop,
+ &bglobal.bg_ev[1]);
+ } else if (sd == bglobal.bg_shop6) {
+ bglobal.bg_ev[2] = NULL;
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6,
+ &bglobal.bg_ev[2]);
+ } else if (sd == bglobal.bg_mhop6) {
+ bglobal.bg_ev[3] = NULL;
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6,
+ &bglobal.bg_ev[3]);
+ } else if (sd == bglobal.bg_echo) {
+ bglobal.bg_ev[4] = NULL;
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo,
+ &bglobal.bg_ev[4]);
+ } else if (sd == bglobal.bg_vxlan) {
+ bglobal.bg_ev[5] = NULL;
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan,
+ &bglobal.bg_ev[5]);
+ }
+}
+
+static void cp_debug(bool mhop, struct sockaddr_any *peer,
+ struct sockaddr_any *local, const char *port,
+ const char *vrf, const char *fmt, ...)
+{
+ char buf[512], peerstr[128], localstr[128], portstr[64], vrfstr[64];
+ va_list vl;
+
+ 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 (port[0])
+ snprintf(portstr, sizeof(portstr), " port:%s", port);
+ else
+ portstr[0] = 0;
+
+ if (vrf[0])
+ snprintf(vrfstr, sizeof(vrfstr), " vrf:%s", port);
+ else
+ vrfstr[0] = 0;
+
+ va_start(vl, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, vl);
+ va_end(vl);
+
+ log_debug("control-packet: %s [mhop:%s%s%s%s%s]", buf,
+ mhop ? "yes" : "no", peerstr, localstr, portstr, vrfstr);
+}
+
+int bfd_recv_cb(struct thread *t)
+{
+ int sd = THREAD_FD(t);
+ struct bfd_session *bfd;
+ struct bfd_pkt *cp;
+ bool is_mhop, is_vxlan;
+ ssize_t mlen = 0;
+ uint32_t oldEchoXmt_TO, oldXmtTime;
+ struct sockaddr_any local, peer;
+ char port[MAXNAMELEN + 1], vrfname[MAXNAMELEN + 1];
+ struct bfd_session_vxlan_info vxlan_info;
+
+ /* Schedule next read. */
+ bfd_sd_reschedule(sd);
+
+ /* Handle echo packets. */
+ if (sd == bglobal.bg_echo) {
+ ptm_bfd_process_echo_pkt(sd);
+ return 0;
+ }
+
+ /* Handle control packets. */
+ is_mhop = is_vxlan = false;
+ if (sd == bglobal.bg_shop || sd == bglobal.bg_mhop) {
+ is_mhop = sd == bglobal.bg_mhop;
+ mlen = bfd_recv_ipv4(sd, is_mhop, port, sizeof(port), vrfname,
+ sizeof(vrfname), &local, &peer);
+ } else if (sd == bglobal.bg_shop6 || sd == bglobal.bg_mhop6) {
+ is_mhop = sd == bglobal.bg_mhop6;
+ mlen = bfd_recv_ipv6(sd, is_mhop, port, sizeof(port), vrfname,
+ sizeof(vrfname), &local, &peer);
+ }
+#if 0 /* TODO vxlan handling */
+ cp = ptm_bfd_process_vxlan_pkt(s, se, udata, &local_ifindex,
+ &sin, &vxlan_info, rx_pkt, &mlen);
+ if (!cp)
+ return -1;
+
+ is_vxlan = true;
+ /* keep in network-byte order */
+ peer.ip4_addr.s_addr = sin.sin_addr.s_addr;
+ peer.family = AF_INET;
+ strcpy(peer_addr, inet_ntoa(sin.sin_addr));
+#endif
+
+ /* Implement RFC 5880 6.8.6 */
+ if (mlen < BFD_PKT_LEN) {
+ cp_debug(is_mhop, &peer, &local, port, vrfname,
+ "too small (%ld bytes)", mlen);
+ return 0;
+ }
+
+ /*
+ * Parse the control header for inconsistencies:
+ * - Invalid version;
+ * - Bad multiplier configuration;
+ * - Short packets;
+ * - Invalid discriminator;
+ */
+ cp = (struct bfd_pkt *)(msghdr.msg_iov->iov_base);
+ if (BFD_GETVER(cp->diag) != BFD_VERSION) {
+ cp_debug(is_mhop, &peer, &local, port, vrfname,
+ "bad version %d", BFD_GETVER(cp->diag));
+ return 0;
+ }
+
+ if (cp->detect_mult == 0) {
+ cp_debug(is_mhop, &peer, &local, port, vrfname,
+ "detect multiplier set to zero");
+ return 0;
+ }
+
+ if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) {
+ cp_debug(is_mhop, &peer, &local, port, vrfname, "too small");
+ return 0;
+ }
+
+ if (cp->discrs.my_discr == 0) {
+ cp_debug(is_mhop, &peer, &local, port, vrfname,
+ "'my discriminator' is zero");
+ return 0;
+ }
+
+ /* Find the session that this packet belongs. */
+ bfd = ptm_bfd_sess_find(cp, port, &peer, &local, vrfname, is_mhop);
+ if (bfd == NULL) {
+ cp_debug(is_mhop, &peer, &local, port, vrfname,
+ "no session found");
+ return 0;
+ }
+
+ /* Handle VxLAN cases. */
+ if (is_vxlan && !ptm_bfd_validate_vxlan_pkt(bfd, &vxlan_info))
+ return 0;
+
+ bfd->stats.rx_ctrl_pkt++;
+
+ /*
+ * Multi hop: validate packet TTL.
+ * Single hop: set local address that received the packet.
+ */
+ if (is_mhop) {
+ if ((BFD_TTL_VAL - bfd->mh_ttl) > ttlval) {
+ cp_debug(is_mhop, &peer, &local, port, vrfname,
+ "exceeded max hop count (expected %d, got %d)",
+ bfd->mh_ttl, ttlval);
+ return 0;
+ }
+ } else if (bfd->local_ip.sa_sin.sin_family == AF_UNSPEC) {
+ bfd->local_ip = local;
+ }
+
+ /*
+ * If no interface was detected, save the interface where the
+ * packet came in.
+ */
+ if (bfd->ifindex == 0)
+ bfd->ifindex = ptm_bfd_fetch_ifindex(port);
+
+ /* 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, port, vrfname,
+ "remote discriminator mismatch (expected %d, got %d)",
+ bfd->discrs.remote_discr, ntohl(cp->discrs.my_discr));
+
+ bfd->discrs.remote_discr = ntohl(cp->discrs.my_discr);
+
+ /* If received the Final bit, the new values should take effect */
+ if (bfd->polling && BFD_GETFBIT(cp->flags)) {
+ bfd->timers.desired_min_tx = bfd->new_timers.desired_min_tx;
+ bfd->timers.required_min_rx = bfd->new_timers.required_min_rx;
+ bfd->new_timers.desired_min_tx = 0;
+ bfd->new_timers.required_min_rx = 0;
+ bfd->polling = 0;
+ }
+
+ if (!bfd->demand_mode) {
+ /* Compute detect time */
+ bfd->detect_TO = cp->detect_mult
+ * ((bfd->timers.required_min_rx
+ > ntohl(cp->timers.desired_min_tx))
+ ? bfd->timers.required_min_rx
+ : ntohl(cp->timers.desired_min_tx));
+ bfd->remote_detect_mult = cp->detect_mult;
+ } else
+ cp_debug(is_mhop, &peer, &local, port, vrfname,
+ "unsupported demand mode");
+
+ /* Save remote diagnostics before state switch. */
+ bfd->remote_diag = cp->diag & BFD_DIAGMASK;
+
+ /* State switch from section 6.8.6 */
+ if (BFD_GETSTATE(cp->flags) == PTM_BFD_ADM_DOWN) {
+ if (bfd->ses_state != PTM_BFD_DOWN)
+ ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN);
+ } else {
+ switch (bfd->ses_state) {
+ case (PTM_BFD_DOWN):
+ if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT)
+ ptm_bfd_ses_up(bfd);
+ else if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN)
+ bfd->ses_state = PTM_BFD_INIT;
+ break;
+ case (PTM_BFD_INIT):
+ if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT
+ || BFD_GETSTATE(cp->flags) == PTM_BFD_UP)
+ ptm_bfd_ses_up(bfd);
+ break;
+ case (PTM_BFD_UP):
+ if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN)
+ ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN);
+ break;
+ }
+ }
+
+ /*
+ * Handle echo packet status:
+ * - Start echo packets if configured and permitted
+ * (required_min_echo > 0);
+ * - Stop echo packets if not allowed (required_min_echo == 0);
+ * - Recalculate echo packet interval;
+ */
+ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO)) {
+ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) {
+ if (!ntohl(cp->timers.required_min_echo)) {
+ ptm_bfd_echo_stop(bfd, 1);
+ } else {
+ oldEchoXmt_TO = bfd->echo_xmt_TO;
+ bfd->echo_xmt_TO =
+ bfd->timers.required_min_echo;
+ if (ntohl(cp->timers.required_min_echo)
+ > bfd->echo_xmt_TO)
+ bfd->echo_xmt_TO = ntohl(
+ cp->timers.required_min_echo);
+ if (oldEchoXmt_TO != bfd->echo_xmt_TO)
+ ptm_bfd_echo_start(bfd);
+ }
+ } else if (ntohl(cp->timers.required_min_echo)) {
+ bfd->echo_xmt_TO = bfd->timers.required_min_echo;
+ if (ntohl(cp->timers.required_min_echo)
+ > bfd->echo_xmt_TO)
+ bfd->echo_xmt_TO =
+ ntohl(cp->timers.required_min_echo);
+ ptm_bfd_echo_start(bfd);
+ }
+ }
+
+ if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) {
+ bfd->echo_xmt_TO = bfd->timers.required_min_echo;
+ if (ntohl(cp->timers.required_min_echo) > bfd->echo_xmt_TO)
+ bfd->echo_xmt_TO = ntohl(cp->timers.required_min_echo);
+ }
+
+ /* Calculate new transmit time */
+ oldXmtTime = bfd->xmt_TO;
+ bfd->xmt_TO =
+ (bfd->timers.desired_min_tx > ntohl(cp->timers.required_min_rx))
+ ? bfd->timers.desired_min_tx
+ : ntohl(cp->timers.required_min_rx);
+
+ /* If transmit time has changed, and too much time until next xmt,
+ * restart
+ */
+ if (BFD_GETPBIT(cp->flags)) {
+ ptm_bfd_xmt_TO(bfd, 1);
+ } else if (oldXmtTime != bfd->xmt_TO) {
+ /* XXX add some skid to this as well */
+ ptm_bfd_start_xmt_timer(bfd, false);
+ }
+
+ /* Restart detection timer (packet received) */
+ if (!bfd->demand_mode)
+ bfd_recvtimer_update(bfd);
+
+ /*
+ * Save the timers and state sent by the remote end
+ * for debugging and statistics.
+ */
+ if (BFD_GETFBIT(cp->flags)) {
+ 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);
+
+ control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bfd);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Sockets creation.
+ */
+
+
+/*
+ * IPv4 sockets
+ */
+int bp_set_ttl(int sd)
+{
+ if (setsockopt(sd, IPPROTO_IP, IP_TTL, &ttlval, sizeof(ttlval)) == -1) {
+ log_warning("%s: setsockopt(IP_TTL): %s", __func__,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int bp_set_tos(int sd)
+{
+ if (setsockopt(sd, IPPROTO_IP, IP_TOS, &tosval, sizeof(tosval)) == -1) {
+ log_warning("%s: setsockopt(IP_TOS): %s", __func__,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void bp_set_ipopts(int sd)
+{
+ if (bp_set_ttl(sd) != 0)
+ log_fatal("%s: TTL configuration failed", __func__);
+
+ if (setsockopt(sd, IPPROTO_IP, IP_RECVTTL, &rcvttl, sizeof(rcvttl))
+ == -1)
+ log_fatal("%s: setsockopt(IP_RECVTTL): %s", __func__,
+ 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)
+ log_fatal("%s: setsockopt(IP_PKTINFO): %s", __func__,
+ 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)
+ log_fatal("%s: setsockopt(IP_RECVDSTADDR): %s", __func__,
+ strerror(errno));
+
+ /* Find out interface where the packet came in. */
+ if (setsockopt_ifindex(AF_INET, sd, yes) == -1)
+ log_fatal("%s: setsockopt_ipv4_ifindex: %s", __func__,
+ 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)
+ log_fatal("%s: bind: %s", __func__, strerror(errno));
+}
+
+int bp_udp_shop(void)
+{
+ int sd;
+
+ sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC);
+ if (sd == -1)
+ log_fatal("%s: socket: %s", __func__, strerror(errno));
+
+ bp_set_ipopts(sd);
+ bp_bind_ip(sd, BFD_DEFDESTPORT);
+
+ return sd;
+}
+
+int bp_udp_mhop(void)
+{
+ int sd;
+
+ sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC);
+ if (sd == -1)
+ log_fatal("%s: socket: %s", __func__, strerror(errno));
+
+ bp_set_ipopts(sd);
+ bp_bind_ip(sd, BFD_DEF_MHOP_DEST_PORT);
+
+ return sd;
+}
+
+int bp_peer_socket(struct bfd_peer_cfg *bpc)
+{
+ int sd, pcount;
+ struct sockaddr_in sin;
+ static int srcPort = BFD_SRCPORTINIT;
+
+ sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC);
+ if (sd == -1) {
+ log_error("ipv4-new: failed to create socket: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ if (!bpc->bpc_has_vxlan) {
+ /* Set TTL to 255 for all transmitted packets */
+ if (bp_set_ttl(sd) != 0) {
+ close(sd);
+ return -1;
+ }
+ }
+
+ /* Set TOS to CS6 for all transmitted packets */
+ if (bp_set_tos(sd) != 0) {
+ close(sd);
+ return -1;
+ }
+
+ /* dont bind-to-device incase of vxlan */
+ if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) {
+ if (bp_bind_dev(sd, bpc->bpc_localif) != 0) {
+ close(sd);
+ return -1;
+ }
+ } else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) {
+ if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) {
+ close(sd);
+ return -1;
+ }
+ }
+
+ /* Find an available source port in the proper range */
+ memset(&sin, 0, sizeof(sin));
+ sin = bpc->bpc_local.sa_sin;
+ sin.sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ sin.sin_len = sizeof(sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ if (bpc->bpc_mhop || bpc->bpc_has_vxlan)
+ sin.sin_addr = bpc->bpc_local.sa_sin.sin_addr;
+ else
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ pcount = 0;
+ do {
+ if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) {
+ /* Searched all ports, none available */
+ log_error("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(struct bfd_peer_cfg *bpc)
+{
+ int sd, pcount, ifindex;
+ struct sockaddr_in6 sin6;
+ static int srcPort = BFD_SRCPORTINIT;
+
+ sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC);
+ if (sd == -1) {
+ log_error("ipv6-new: failed to create socket: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ if (!bpc->bpc_has_vxlan) {
+ /* Set TTL to 255 for all transmitted packets */
+ if (bp_set_ttlv6(sd) != 0) {
+ close(sd);
+ return -1;
+ }
+ }
+
+ /* Set TOS to CS6 for all transmitted packets */
+ if (bp_set_tosv6(sd) != 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 */
+ sin6 = bpc->bpc_local.sa_sin6;
+ ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif);
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr))
+ sin6.sin6_scope_id = ifindex;
+
+ if (bpc->bpc_has_localif) {
+ if (bp_bind_dev(sd, bpc->bpc_localif) != 0) {
+ close(sd);
+ return -1;
+ }
+ } else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) {
+ if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) {
+ close(sd);
+ return -1;
+ }
+ }
+
+ pcount = 0;
+ do {
+ if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) {
+ /* Searched all ports, none available */
+ log_error("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)
+{
+ if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval,
+ sizeof(ttlval))
+ == -1) {
+ log_warning("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+int bp_set_tosv6(int sd)
+{
+ if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &tosval, sizeof(tosval))
+ == -1) {
+ log_warning("%s: setsockopt(IPV6_TCLASS): %s", __func__,
+ strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void bp_set_ipv6opts(int sd)
+{
+ static int ipv6_pktinfo = BFD_IPV6_PKT_INFO_VAL;
+ static int ipv6_only = BFD_IPV6_ONLY_VAL;
+
+ if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval,
+ sizeof(ttlval))
+ == -1)
+ log_fatal("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__,
+ strerror(errno));
+
+ if (setsockopt_ipv6_hoplimit(sd, rcvttl) == -1)
+ log_fatal("%s: setsockopt(IPV6_HOPLIMIT): %s", __func__,
+ strerror(errno));
+
+ if (setsockopt_ipv6_pktinfo(sd, ipv6_pktinfo) == -1)
+ log_fatal("%s: setsockopt(IPV6_PKTINFO): %s", __func__,
+ strerror(errno));
+
+ if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only,
+ sizeof(ipv6_only))
+ == -1)
+ log_fatal("%s: setsockopt(IPV6_V6ONLY): %s", __func__,
+ 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)
+ log_fatal("%s: bind: %s", __func__, strerror(errno));
+}
+
+int bp_udp6_shop(void)
+{
+ int sd;
+
+ sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC);
+ if (sd == -1)
+ log_fatal("%s: socket: %s", __func__, strerror(errno));
+
+ bp_set_ipv6opts(sd);
+ bp_bind_ipv6(sd, BFD_DEFDESTPORT);
+
+ return sd;
+}
+
+int bp_udp6_mhop(void)
+{
+ int sd;
+
+ sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC);
+ if (sd == -1)
+ log_fatal("%s: socket: %s", __func__, strerror(errno));
+
+ bp_set_ipv6opts(sd);
+ bp_bind_ipv6(sd, BFD_DEF_MHOP_DEST_PORT);
+
+ return sd;
+}
diff --git a/bfdd/bfdctl.h b/bfdd/bfdctl.h
new file mode 100644
index 0000000000..940efd1614
--- /dev/null
+++ b/bfdd/bfdctl.h
@@ -0,0 +1,160 @@
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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_ECHOINTERVAL 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_vxlan;
+ unsigned int bpc_vxlan;
+
+ 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_echointerval;
+ uint64_t bpc_echointerval;
+
+ bool bpc_echo;
+ bool bpc_createonly;
+ bool bpc_shutdown;
+
+ /* 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 0000000000..144619088d
--- /dev/null
+++ b/bfdd/bfdd.c
@@ -0,0 +1,236 @@
+/*
+ * BFD daemon code
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <zebra.h>
+
+#include "bfd.h"
+#include "lib/version.h"
+
+
+/*
+ * FRR related code.
+ */
+DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon");
+DEFINE_MTYPE(BFDD, BFDD_TMP, "short-lived temporary memory");
+DEFINE_MTYPE(BFDD, BFDD_CONFIG, "long-lived configuration memory");
+DEFINE_MTYPE(BFDD, BFDD_LABEL, "long-lived label memory");
+DEFINE_MTYPE(BFDD, BFDD_CONTROL, "long-lived control socket memory");
+DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "short-lived control notification data");
+
+/* Master of threads. */
+struct thread_master *master;
+
+/* BFDd privileges */
+static zebra_capabilities_t _caps_p[] = {ZCAP_BIND};
+
+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,
+};
+
+void socket_close(int *s)
+{
+ if (*s <= 0)
+ return;
+
+ if (close(*s) != 0)
+ log_error("%s: close(%d): (%d) %s", __func__, *s, errno,
+ strerror(errno));
+
+ *s = -1;
+}
+
+static void sigusr1_handler(void)
+{
+ zlog_rotate();
+}
+
+static void sigterm_handler(void)
+{
+ /* 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();
+
+ /* Close all descriptors. */
+ socket_close(&bglobal.bg_echo);
+ socket_close(&bglobal.bg_shop);
+ socket_close(&bglobal.bg_mhop);
+ socket_close(&bglobal.bg_shop6);
+ socket_close(&bglobal.bg_mhop6);
+ socket_close(&bglobal.bg_vxlan);
+
+ /* Terminate and free() FRR related memory. */
+ frr_fini();
+
+ exit(0);
+}
+
+static struct quagga_signal_t bfd_signals[] = {
+ {
+ .signal = SIGUSR1,
+ .handler = &sigusr1_handler,
+ },
+ {
+ .signal = SIGTERM,
+ .handler = &sigterm_handler,
+ },
+ {
+ .signal = SIGINT,
+ .handler = &sigterm_handler,
+ },
+};
+
+FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617,
+ .proghelp = "Implementation of the BFD protocol.",
+ .signals = bfd_signals, .n_signals = array_size(bfd_signals),
+ .privs = &bfdd_privs)
+
+#define OPTION_CTLSOCK 1001
+static struct option longopts[] = {
+ {"bfdctl", required_argument, NULL, OPTION_CTLSOCK},
+ {0}
+};
+
+
+/*
+ * BFD daemon related code.
+ */
+struct bfd_global bglobal;
+
+struct bfd_diag_str_list diag_list[] = {
+ {.str = "NeighDown", .type = BFD_DIAGNEIGHDOWN},
+ {.str = "DetectTime", .type = BFD_DIAGDETECTTIME},
+ {.str = "AdminDown", .type = BFD_DIAGADMINDOWN},
+ {.str = NULL},
+};
+
+struct bfd_state_str_list state_list[] = {
+ {.str = "AdminDown", .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 void bg_init(void)
+{
+ TAILQ_INIT(&bglobal.bg_bcslist);
+
+ bglobal.bg_shop = bp_udp_shop();
+ bglobal.bg_mhop = bp_udp_mhop();
+ bglobal.bg_shop6 = bp_udp6_shop();
+ bglobal.bg_mhop6 = bp_udp6_mhop();
+ bglobal.bg_echo = ptm_bfd_echo_sock_init();
+ bglobal.bg_vxlan = ptm_bfd_vxlan_sock_init();
+}
+
+int main(int argc, char *argv[])
+{
+ const char *ctl_path = BFDD_CONTROL_SOCKET;
+ int opt;
+
+ frr_preinit(&bfdd_di, argc, argv);
+ frr_opt_add("", longopts,
+ " --bfdctl Specify bfdd control socket\n");
+
+ while (true) {
+ opt = frr_getopt(argc, argv, NULL);
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case OPTION_CTLSOCK:
+ ctl_path = optarg;
+ break;
+
+ default:
+ frr_help_exit(1);
+ break;
+ }
+ }
+
+#if 0 /* TODO add support for JSON configuration files. */
+ parse_config(conf);
+#endif
+
+ /* Initialize logging API. */
+ log_init(1, BLOG_DEBUG, &bfdd_di);
+
+ /* Initialize system sockets. */
+ bg_init();
+
+ /* Initialize control socket. */
+ control_init(ctl_path);
+
+ /* Initialize FRR infrastructure. */
+ master = frr_init();
+
+ /* Initialize BFD data structures. */
+ bfd_initialize();
+
+ /* Initialize zebra connection. */
+ bfdd_zclient_init(&bfdd_privs);
+
+ /* Add descriptors to the event loop. */
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop,
+ &bglobal.bg_ev[0]);
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop,
+ &bglobal.bg_ev[1]);
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6,
+ &bglobal.bg_ev[2]);
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6,
+ &bglobal.bg_ev[3]);
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo,
+ &bglobal.bg_ev[4]);
+#if 0 /* TODO VxLAN support. */
+ thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan,
+ &bglobal.bg_ev[5]);
+#endif
+ thread_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();
+
+ frr_run(master);
+ /* NOTREACHED */
+
+ return 0;
+}
diff --git a/bfdd/bfdd.conf.sample b/bfdd/bfdd.conf.sample
new file mode 100644
index 0000000000..9981e262bc
--- /dev/null
+++ b/bfdd/bfdd.conf.sample
@@ -0,0 +1,5 @@
+password zebra
+!
+log stdout
+!
+line vty
diff --git a/bfdd/bfdd_vty.c b/bfdd/bfdd_vty.c
new file mode 100644
index 0000000000..bb5f23c407
--- /dev/null
+++ b/bfdd/bfdd_vty.c
@@ -0,0 +1,870 @@
+/*
+ * BFD daemon code
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <zebra.h>
+
+#include "lib/command.h"
+#include "lib/json.h"
+#include "lib/log.h"
+#include "lib/vty.h"
+
+#include "bfd.h"
+
+#ifndef VTYSH_EXTRACT_PL
+#include "bfdd/bfdd_vty_clippy.c"
+#endif
+
+/*
+ * Commands help string 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 int bfdd_write_config(struct vty *vty);
+static int bfdd_peer_write_config(struct vty *vty);
+static void _bfdd_peer_write_config(struct hash_backet *hb, void *arg);
+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 struct json_object *__display_peer_json(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, bool use_json);
+static void _display_peer_iter(struct hash_backet *hb, void *arg);
+static void _display_peer_json_iter(struct hash_backet *hb, void *arg);
+
+
+/*
+ * Commands definition.
+ */
+DEFUN_NOSH(bfd_enter, bfd_enter_cmd, "bfd", "Configure BFD peers\n")
+{
+ vty->node = BFD_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN_NOSH(
+ bfd_peer_enter, bfd_peer_enter_cmd,
+ "peer <A.B.C.D|X:X::X:X> [{multihop|local-address <A.B.C.D|X:X::X:X>|interface 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)
+{
+ bool mhop;
+ int idx;
+ struct bfd_session *bs;
+ const char *peer, *ifname, *local, *vrfname;
+ struct bfd_peer_cfg bpc;
+ struct sockaddr_any psa, lsa, *lsap;
+ char errormsg[128];
+
+ vrfname = peer = ifname = local = NULL;
+
+ /* Gather all provided information. */
+ peer = argv[1]->arg;
+
+ idx = 0;
+ mhop = argv_find(argv, argc, "multihop", &idx);
+
+ idx = 0;
+ if (argv_find(argv, argc, "interface", &idx))
+ ifname = argv[idx + 1]->arg;
+
+ idx = 0;
+ if (argv_find(argv, argc, "local-address", &idx))
+ local = argv[idx + 1]->arg;
+
+ idx = 0;
+ if (argv_find(argv, argc, "vrf", &idx))
+ vrfname = argv[idx + 1]->arg;
+
+ if (vrfname && ifname) {
+ vty_out(vty, "%% VRF is not mixable with interface\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ strtosa(peer, &psa);
+ if (local) {
+ strtosa(local, &lsa);
+ lsap = &lsa;
+ } else
+ lsap = NULL;
+
+ if (bfd_configure_peer(&bpc, mhop, &psa, lsap, ifname, vrfname,
+ errormsg, sizeof(errormsg))
+ != 0) {
+ vty_out(vty, "%% Invalid peer configuration: %s\n", errormsg);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ bs = bs_peer_find(&bpc);
+ if (bs == NULL) {
+ bs = ptm_bfd_sess_new(&bpc);
+ if (bs == NULL) {
+ vty_out(vty, "%% Failed to add peer.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ VTY_PUSH_CONTEXT(BFD_PEER_NODE, bs);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_peer_detectmultiplier, bfd_peer_detectmultiplier_cmd,
+ "detect-multiplier (2-255)$multiplier",
+ "Configure peer detection multiplier\n"
+ "Configure peer detection multiplier value\n")
+{
+ struct bfd_session *bs;
+
+ bs = VTY_GET_CONTEXT(bfd_session);
+ if (bs->detect_mult == multiplier)
+ return CMD_SUCCESS;
+
+ bs->detect_mult = multiplier;
+ bfd_set_polling(bs);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_peer_recvinterval, bfd_peer_recvinterval_cmd,
+ "receive-interval (10-60000)$interval",
+ "Configure peer receive interval\n"
+ "Configure peer receive interval value in milliseconds\n")
+{
+ struct bfd_session *bs;
+
+ bs = VTY_GET_CONTEXT(bfd_session);
+ if (bs->timers.required_min_rx == (uint32_t)(interval * 1000))
+ return CMD_SUCCESS;
+
+ bs->timers.required_min_rx = interval * 1000;
+ bfd_set_polling(bs);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_peer_txinterval, bfd_peer_txinterval_cmd,
+ "transmit-interval (10-60000)$interval",
+ "Configure peer transmit interval\n"
+ "Configure peer transmit interval value in milliseconds\n")
+{
+ struct bfd_session *bs;
+
+ bs = VTY_GET_CONTEXT(bfd_session);
+ if (bs->up_min_tx == (uint32_t)(interval * 1000))
+ return CMD_SUCCESS;
+
+ bs->up_min_tx = interval * 1000;
+ bfd_set_polling(bs);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_peer_echointerval, bfd_peer_echointerval_cmd,
+ "echo-interval (10-60000)$interval",
+ "Configure peer echo interval\n"
+ "Configure peer echo interval value in milliseconds\n")
+{
+ struct bfd_session *bs;
+
+ bs = VTY_GET_CONTEXT(bfd_session);
+ if (bs->timers.required_min_echo == (uint32_t)(interval * 1000))
+ return CMD_SUCCESS;
+
+ bs->timers.required_min_echo = interval * 1000;
+ bfd_set_polling(bs);
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_peer_shutdown, bfd_peer_shutdown_cmd, "[no] shutdown",
+ NO_STR "Disable BFD peer")
+{
+ struct bfd_session *bs;
+
+ bs = VTY_GET_CONTEXT(bfd_session);
+ if (no) {
+ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return CMD_SUCCESS;
+
+ BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+
+ /* Change and notify state change. */
+ bs->ses_state = PTM_BFD_DOWN;
+ control_notify(bs);
+
+ /* Enable all timers. */
+ bfd_recvtimer_update(bs);
+ bfd_xmttimer_update(bs, bs->xmt_TO);
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) {
+ bfd_echo_recvtimer_update(bs);
+ bfd_echo_xmttimer_update(bs, bs->echo_xmt_TO);
+ }
+ } else {
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return CMD_SUCCESS;
+
+ BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+
+ /* 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);
+
+ ptm_bfd_snd(bs, 0);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_peer_echo, bfd_peer_echo_cmd, "[no] echo-mode",
+ NO_STR "Configure echo mode\n")
+{
+ struct bfd_session *bs;
+
+ bs = VTY_GET_CONTEXT(bfd_session);
+ if (no) {
+ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ return CMD_SUCCESS;
+
+ BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
+ ptm_bfd_echo_stop(bs, 0);
+ } else {
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ return CMD_SUCCESS;
+
+ BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
+ /* Apply setting immediately. */
+ if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) {
+ ptm_bfd_echo_start(bs);
+ bfd_echo_recvtimer_update(bs);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_peer_label, bfd_peer_label_cmd, "label WORD$label",
+ "Register peer label\n"
+ "Register peer label identification\n")
+{
+ struct bfd_session *bs;
+
+ /* Validate label length. */
+ if (strlen(label) >= MAXNAMELEN) {
+ vty_out(vty, "%% Label name is too long\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ bs = VTY_GET_CONTEXT(bfd_session);
+ if (bfd_session_update_label(bs, label) == -1) {
+ vty_out(vty, "%% Failed to update peer label.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_no_peer, bfd_no_peer_cmd,
+ "no peer <A.B.C.D|X:X::X:X>$peer [{multihop|local-address <A.B.C.D|X:X::X:X>$local|interface IFNAME$ifname|vrf NAME$vrfname}]",
+ 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 idx;
+ bool mhop;
+ struct bfd_peer_cfg bpc;
+ struct sockaddr_any psa, lsa, *lsap;
+ char errormsg[128];
+
+ strtosa(peer_str, &psa);
+ if (local) {
+ 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 CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (ptm_bfd_ses_del(&bpc) != 0) {
+ vty_out(vty, "%% Failed to remove peer.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+/*
+ * Show commands helper functions
+ */
+static void _display_peer(struct vty *vty, struct bfd_session *bs)
+{
+ char buf[256];
+ time_t now;
+
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ vty_out(vty, "\tpeer %s", satostr(&bs->mhop.peer));
+ vty_out(vty, " multihop");
+ vty_out(vty, " local-address %s", satostr(&bs->mhop.local));
+ if (bs->mhop.vrf_name[0])
+ vty_out(vty, " vrf %s", bs->mhop.vrf_name);
+ vty_out(vty, "\n");
+ } else {
+ vty_out(vty, "\tpeer %s", satostr(&bs->shop.peer));
+ if (bs->local_address.sa_sin.sin_family != AF_UNSPEC)
+ vty_out(vty, " local-address %s",
+ satostr(&bs->local_address));
+ if (bs->shop.port_name[0])
+ vty_out(vty, " interface %s", bs->shop.port_name);
+ vty_out(vty, "\n");
+ }
+
+ if (bs->pl)
+ vty_out(vty, "\t\tlabel: %s\n", bs->pl->pl_label);
+
+ vty_out(vty, "\t\tID: %u\n", bs->discrs.my_discr);
+ vty_out(vty, "\t\tRemote ID: %u\n", bs->discrs.remote_discr);
+
+ 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->uptime.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\tLocal timers:\n");
+ vty_out(vty, "\t\t\tReceive interval: %" PRIu32 "ms\n",
+ bs->timers.required_min_rx / 1000);
+ vty_out(vty, "\t\t\tTransmission interval: %" PRIu32 "ms",
+ bs->timers.desired_min_tx / 1000);
+ if (bs->up_min_tx != bs->timers.desired_min_tx)
+ vty_out(vty, " (configured %" PRIu32 "ms)\n",
+ bs->up_min_tx / 1000);
+ else
+ vty_out(vty, "\n");
+
+ vty_out(vty, "\t\t\tEcho transmission interval: ");
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ vty_out(vty, "%" PRIu32 "ms\n",
+ bs->timers.required_min_echo / 1000);
+ else
+ vty_out(vty, "disabled\n");
+
+ vty_out(vty, "\t\tRemote timers:\n");
+ vty_out(vty, "\t\t\tReceive interval: %" PRIu32 "ms\n",
+ bs->remote_timers.required_min_rx / 1000);
+ vty_out(vty, "\t\t\tTransmission interval: %" PRIu32 "ms\n",
+ bs->remote_timers.desired_min_tx / 1000);
+ vty_out(vty, "\t\t\tEcho transmission interval: %" PRIu32 "ms\n",
+ bs->remote_timers.required_min_echo / 1000);
+
+ vty_out(vty, "\n");
+}
+
+static struct json_object *__display_peer_json(struct bfd_session *bs)
+{
+ struct json_object *jo = json_object_new_object();
+
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ json_object_boolean_true_add(jo, "multihop");
+ json_object_string_add(jo, "peer", satostr(&bs->mhop.peer));
+ json_object_string_add(jo, "local", satostr(&bs->mhop.local));
+ if (bs->mhop.vrf_name[0])
+ json_object_string_add(jo, "vrf", bs->mhop.vrf_name);
+ } else {
+ json_object_boolean_false_add(jo, "multihop");
+ json_object_string_add(jo, "peer", satostr(&bs->shop.peer));
+ if (bs->local_address.sa_sin.sin_family != AF_UNSPEC)
+ json_object_string_add(jo, "local",
+ satostr(&bs->local_address));
+ if (bs->shop.port_name[0])
+ json_object_string_add(jo, "interface",
+ bs->shop.port_name);
+ }
+
+ if (bs->pl)
+ json_object_string_add(jo, "label", bs->pl->pl_label);
+
+ json_object_int_add(jo, "id", bs->discrs.my_discr);
+ json_object_int_add(jo, "remote-id", bs->discrs.remote_discr);
+
+ 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->uptime.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));
+
+ 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);
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ json_object_int_add(jo, "echo-interval",
+ bs->timers.required_min_echo / 1000);
+ else
+ json_object_int_add(jo, "echo-interval", 0);
+
+ 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-interval",
+ bs->remote_timers.required_min_echo / 1000);
+
+ return jo;
+}
+
+static void _display_peer_json(struct vty *vty, struct bfd_session *bs)
+{
+ struct json_object *jo = __display_peer_json(bs);
+
+ vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0));
+ json_object_free(jo);
+}
+
+static void _display_peer_iter(struct hash_backet *hb, void *arg)
+{
+ struct vty *vty = arg;
+ struct bfd_session *bs = hb->data;
+
+ _display_peer(vty, bs);
+}
+
+static void _display_peer_json_iter(struct hash_backet *hb, void *arg)
+{
+ struct json_object *jo = arg, *jon = NULL;
+ struct bfd_session *bs = hb->data;
+
+ jon = __display_peer_json(bs);
+ if (jon == NULL) {
+ log_warning("%s: not enough memory", __func__);
+ return;
+ }
+
+ json_object_array_add(jo, jon);
+}
+
+static void _display_all_peers(struct vty *vty, bool use_json)
+{
+ struct json_object *jo;
+
+ if (use_json == false) {
+ bfd_id_iterate(_display_peer_iter, vty);
+ return;
+ }
+
+ jo = json_object_new_array();
+ bfd_id_iterate(_display_peer_json_iter, jo);
+
+ vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0));
+ json_object_free(jo);
+}
+
+DEFPY(bfd_show_peers, bfd_show_peers_cmd, "show bfd peers [json]",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ "BFD peers status\n"
+ JSON_STR)
+{
+ bool json = use_json(argc, argv);
+
+ if (json) {
+ _display_all_peers(vty, true);
+ } else {
+ vty_out(vty, "BFD Peers:\n");
+ _display_all_peers(vty, false);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFPY(bfd_show_peer, bfd_show_peer_cmd,
+ "show bfd 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|vrf NAME$vrfname}]> [json]",
+ SHOW_STR
+ "Bidirection Forwarding Detection\n"
+ "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
+ VRF_STR VRF_NAME_STR
+ JSON_STR)
+{
+ 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 {
+ strtosa(peer_str, &psa);
+ if (local) {
+ 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 CMD_WARNING_CONFIG_FAILED;
+ }
+
+ bs = bs_peer_find(&bpc);
+ }
+
+ /* 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)
+ vty_out(vty, " local-address %s", local_str);
+ if (vrfname)
+ vty_out(vty, " vrf %s", vrfname);
+ vty_out(vty, "'\n");
+
+ 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;
+}
+
+
+/*
+ * Function definitions.
+ */
+
+/*
+ * Configuration rules:
+ *
+ * Single hop:
+ * peer + (optional vxlan or 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 = true;
+ bpc->bpc_detectmultiplier = BPC_DEF_DETECTMULTIPLIER;
+ bpc->bpc_recvinterval = BPC_DEF_RECEIVEINTERVAL;
+ bpc->bpc_txinterval = BPC_DEF_TRANSMITINTERVAL;
+ bpc->bpc_echointerval = BPC_DEF_ECHOINTERVAL;
+ 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;
+
+ if (peer) {
+ bpc->bpc_peer = *peer;
+ } else {
+ /* Peer configuration is mandatory. */
+ snprintf(ebuf, ebuflen, "no peer configured");
+ return -1;
+ }
+
+ bpc->bpc_mhop = mhop;
+
+#if 0
+ /* Handle VxLAN configuration. */
+ if (vxlan >= 0) {
+ if (vxlan > ((1 << 24) - 1)) {
+ snprintf(ebuf, ebuflen, "invalid VxLAN %d", vxlan);
+ return -1;
+ }
+ if (bpc->bpc_mhop) {
+ snprintf(ebuf, ebuflen,
+ "multihop doesn't accept VxLAN");
+ return -1;
+ }
+
+ bpc->bpc_vxlan = vxlan;
+ }
+#endif /* VxLAN */
+
+ /* Handle interface specification configuration. */
+ if (ifname) {
+ if (bpc->bpc_mhop) {
+ snprintf(ebuf, ebuflen,
+ "multihop doesn't accept interface names");
+ return -1;
+ }
+
+ 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;
+ }
+ }
+
+ return 0;
+}
+static int bfdd_write_config(struct vty *vty)
+{
+ vty_out(vty, "bfd\n");
+ vty_out(vty, "!\n");
+ return 0;
+}
+
+static void _bfdd_peer_write_config(struct hash_backet *hb, void *arg)
+{
+ struct vty *vty = arg;
+ struct bfd_session *bs = hb->data;
+
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ vty_out(vty, " peer %s", satostr(&bs->mhop.peer));
+ vty_out(vty, " multihop");
+ vty_out(vty, " local-address %s", satostr(&bs->mhop.local));
+ if (bs->mhop.vrf_name[0])
+ vty_out(vty, " vrf %s", bs->mhop.vrf_name);
+ vty_out(vty, "\n");
+ } else {
+ vty_out(vty, " peer %s", satostr(&bs->shop.peer));
+ if (bs->local_address.sa_sin.sin_family != AF_UNSPEC)
+ vty_out(vty, " local-address %s",
+ satostr(&bs->local_address));
+ if (bs->shop.port_name[0])
+ vty_out(vty, " interface %s", bs->shop.port_name);
+ vty_out(vty, "\n");
+ }
+
+ if (bs->detect_mult != BPC_DEF_DETECTMULTIPLIER)
+ vty_out(vty, " detect-multiplier %d\n", bs->detect_mult);
+ if (bs->timers.required_min_rx != (BPC_DEF_RECEIVEINTERVAL * 1000))
+ vty_out(vty, " receive-interval %" PRIu32 "\n",
+ bs->timers.required_min_rx / 1000);
+ if (bs->up_min_tx != (BPC_DEF_TRANSMITINTERVAL * 1000))
+ vty_out(vty, " transmit-interval %" PRIu32 "\n",
+ bs->up_min_tx / 1000);
+ if (bs->timers.required_min_echo != (BPC_DEF_ECHOINTERVAL * 1000))
+ vty_out(vty, " echo-interval %" PRIu32 "\n",
+ bs->timers.required_min_echo / 1000);
+ if (bs->pl)
+ vty_out(vty, " label %s\n", bs->pl->pl_label);
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO))
+ vty_out(vty, " echo-mode\n");
+
+ vty_out(vty, " %sshutdown\n",
+ BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) ? "" : "no ");
+
+ vty_out(vty, " !\n");
+}
+
+static int bfdd_peer_write_config(struct vty *vty)
+{
+ bfd_id_iterate(_bfdd_peer_write_config, vty);
+ return 1;
+}
+
+struct cmd_node bfd_node = {
+ BFD_NODE,
+ "%s(config-bfd)# ",
+ 1,
+};
+
+struct cmd_node bfd_peer_node = {
+ BFD_PEER_NODE,
+ "%s(config-bfd-peer)# ",
+ 1,
+};
+
+void bfdd_vty_init(void)
+{
+ install_element(ENABLE_NODE, &bfd_show_peers_cmd);
+ install_element(ENABLE_NODE, &bfd_show_peer_cmd);
+ install_element(CONFIG_NODE, &bfd_enter_cmd);
+
+ /* Install BFD node and commands. */
+ install_node(&bfd_node, bfdd_write_config);
+ install_default(BFD_NODE);
+ install_element(BFD_NODE, &bfd_peer_enter_cmd);
+ install_element(BFD_NODE, &bfd_no_peer_cmd);
+
+ /* Install BFD peer node. */
+ install_node(&bfd_peer_node, bfdd_peer_write_config);
+ install_default(BFD_PEER_NODE);
+ install_element(BFD_PEER_NODE, &bfd_peer_detectmultiplier_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_recvinterval_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_txinterval_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_echointerval_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_shutdown_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_echo_cmd);
+ install_element(BFD_PEER_NODE, &bfd_peer_label_cmd);
+}
diff --git a/bfdd/bsd.c b/bfdd/bsd.c
new file mode 100644
index 0000000000..34a3a1a801
--- /dev/null
+++ b/bfdd/bsd.c
@@ -0,0 +1,290 @@
+/*
+ * *BSD specific code
+ *
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <zebra.h>
+
+#ifdef BFD_BSD
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <ifaddrs.h>
+
+#include "bfd.h"
+
+/*
+ * Prototypes
+ */
+static const char *sockaddr_to_string(const void *sv, char *buf, size_t buflen);
+
+/*
+ * Definitions.
+ */
+static const char *sockaddr_to_string(const void *sv, char *buf, size_t buflen)
+{
+ const struct sockaddr *sa = sv;
+ const struct sockaddr_in *sin = sv;
+ const struct sockaddr_in6 *sin6 = sv;
+ int unknown = 1;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ if (inet_ntop(AF_INET, &sin->sin_addr, buf, buflen) != NULL)
+ unknown = 0;
+ break;
+
+ case AF_INET6:
+ if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, buflen) != NULL)
+ unknown = 0;
+ break;
+ }
+ if (unknown == 0)
+ return buf;
+
+ snprintf(buf, buflen, "unknown (af=%d)", sa->sa_family);
+ return buf;
+}
+
+int ptm_bfd_fetch_ifindex(const char *ifname)
+{
+ return if_nametoindex(ifname);
+}
+
+void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct if_data *ifi;
+ struct sockaddr_dl *sdl;
+ size_t maclen;
+
+ /* Always clean the target, zeroed macs mean failure. */
+ memset(mac, 0, ETHERNET_ADDRESS_LENGTH);
+
+ if (getifaddrs(&ifap) != 0)
+ return;
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+ /* Find interface with that name. */
+ if (strcmp(ifa->ifa_name, ifname) != 0)
+ continue;
+ /* Skip non link addresses. We want the MAC address. */
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ continue;
+
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ ifi = (struct if_data *)ifa->ifa_data;
+ /* Skip non ethernet related data. */
+ if (ifi->ifi_type != IFT_ETHER)
+ continue;
+
+ if (sdl->sdl_alen != ETHERNET_ADDRESS_LENGTH)
+ log_warning("%s:%d mac address length %d (expected %d)",
+ __func__, __LINE__, sdl->sdl_alen,
+ ETHERNET_ADDRESS_LENGTH);
+
+ maclen = (sdl->sdl_alen > ETHERNET_ADDRESS_LENGTH)
+ ? ETHERNET_ADDRESS_LENGTH
+ : sdl->sdl_alen;
+ memcpy(mac, LLADDR(sdl), maclen);
+ break;
+ }
+
+ freeifaddrs(ifap);
+}
+
+
+/* Was _fetch_portname_from_ifindex() */
+void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen)
+{
+ char ifname_tmp[IF_NAMESIZE];
+
+ /* Set ifname to empty to signalize failures. */
+ memset(ifname, 0, ifnamelen);
+
+ if (if_indextoname(ifindex, ifname_tmp) == NULL)
+ return;
+
+ if (strlcpy(ifname, ifname_tmp, ifnamelen) > ifnamelen)
+ log_warning("%s:%d interface name truncated", __func__,
+ __LINE__);
+}
+
+int ptm_bfd_echo_sock_init(void)
+{
+ int s, ttl, yes = 1;
+ struct sockaddr_in sin;
+
+ s = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC);
+ if (s == -1) {
+ log_error("echo-socket: creation failed: %s", strerror(errno));
+ return -1;
+ }
+
+ memset(&sin, 0, sizeof(sin));
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ /* OmniOS doesn't have this field, but uses this code. */
+ sin.sin_len = sizeof(sin);
+#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(3785);
+ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
+ log_error("echo-socket: bind failure: %s", strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ if (setsockopt(s, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) == -1) {
+ log_error("echo-socket: setsockopt(IP_RECVTTL): %s",
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ ttl = BFD_TTL_VAL;
+ if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) {
+ log_error("echo-socket: setsockopt(IP_TTL): %s",
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+
+ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen,
+ struct sockaddr_storage *ss, socklen_t *sslen,
+ uint8_t *ttl, uint32_t *id)
+{
+ struct cmsghdr *cmsg;
+ struct bfd_echo_pkt *bep;
+ ssize_t readlen;
+ struct iovec iov;
+ struct msghdr msg;
+ uint8_t msgctl[255];
+ char errbuf[255];
+
+ /* Prepare socket read. */
+ memset(ss, 0, sizeof(*ss));
+ memset(&msg, 0, sizeof(msg));
+ iov.iov_base = buf;
+ iov.iov_len = *buflen;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = msgctl;
+ msg.msg_controllen = sizeof(msgctl);
+ msg.msg_name = ss;
+ msg.msg_namelen = *sslen;
+
+ /* Read the socket and treat errors. */
+ readlen = recvmsg(sd, &msg, 0);
+ if (readlen == 0) {
+ log_error("%s: recvmsg: socket closed", __func__);
+ return -1;
+ }
+ if (readlen == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ return -1;
+
+ log_error("%s: recvmsg: (%d) %s", __func__, errno,
+ strerror(errno));
+ return -1;
+ }
+ /* Short packet, better not risk reading it. */
+ if (readlen < (ssize_t)sizeof(*bep)) {
+ log_warning("%s: short packet (%ld of %d) from %s", __func__,
+ readlen, sizeof(*bep),
+ sockaddr_to_string(ss, errbuf, sizeof(errbuf)));
+ return -1;
+ }
+ *buflen = readlen;
+
+ /* Read TTL information. */
+ *ttl = 0;
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level != IPPROTO_IP)
+ continue;
+ if (cmsg->cmsg_type != IP_RECVTTL)
+ continue;
+
+ *ttl = *(uint8_t *)CMSG_DATA(cmsg);
+ break;
+ }
+ if (*ttl == 0) {
+ log_debug("%s: failed to read TTL", __func__);
+ return -1;
+ }
+
+ /* Read my discriminator from BFD Echo packet. */
+ bep = (struct bfd_echo_pkt *)buf;
+ *id = bep->my_discr;
+ if (*id == 0) {
+ log_debug("%s: invalid packet discriminator from: %s", __func__,
+ sockaddr_to_string(ss, errbuf, sizeof(errbuf)));
+ return -1;
+ }
+
+ /* Set the returned sockaddr new length. */
+ *sslen = msg.msg_namelen;
+
+ return 0;
+}
+
+int ptm_bfd_vxlan_sock_init(void)
+{
+ /* TODO: not supported yet. */
+ return -1;
+}
+
+int bp_bind_dev(int sd, const char *dev)
+{
+ /*
+ * *BSDs don't support `SO_BINDTODEVICE`, instead you must
+ * manually specify the main address of the interface or use
+ * BPF on the socket descriptor.
+ */
+ return 0;
+}
+
+uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len)
+{
+ char *ptr;
+ struct udp_psuedo_header pudp_hdr;
+ uint16_t csum;
+
+ pudp_hdr.saddr = ip->ip_src.s_addr;
+ pudp_hdr.daddr = ip->ip_dst.s_addr;
+ pudp_hdr.reserved = 0;
+ pudp_hdr.protocol = ip->ip_p;
+ pudp_hdr.len = htons(len);
+
+ ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len);
+ memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN);
+ memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len);
+
+ csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len);
+ XFREE(MTYPE_BFDD_TMP, ptr);
+ return csum;
+}
+
+#endif /* BFD_BSD */
diff --git a/bfdd/config.c b/bfdd/config.c
new file mode 100644
index 0000000000..0e0d8b7d70
--- /dev/null
+++ b/bfdd/config.c
@@ -0,0 +1,606 @@
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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"
+
+/*
+ * 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_ses_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);
+ log_warning("%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_echointerval = BFD_DEF_REQ_MIN_ECHO;
+
+ switch (plt) {
+ case PLT_IPV4:
+ log_debug("ipv4 peers %d:", allen);
+ bpc.bpc_ipv4 = true;
+ break;
+ case PLT_IPV6:
+ log_debug("ipv6 peers %d:", allen);
+ bpc.bpc_ipv4 = false;
+ break;
+ case PLT_LABEL:
+ log_debug("label peers %d:", allen);
+ if (parse_peer_label_config(jo_val, &bpc) != 0) {
+ error++;
+ continue;
+ }
+ break;
+
+ default:
+ error++;
+ log_error("%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;
+
+ log_debug("\tpeer: %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);
+ log_debug("\tmultihop: %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) {
+ log_info(
+ "%s:%d failed to parse peer-address '%s'",
+ __func__, __LINE__, sval);
+ error++;
+ }
+ log_debug("\tpeer-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) {
+ log_info(
+ "%s:%d failed to parse local-address '%s'",
+ __func__, __LINE__, sval);
+ error++;
+ }
+ log_debug("\tlocal-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)) {
+ log_debug("\tlocal-interface: %s (truncated)");
+ error++;
+ } else {
+ log_debug("\tlocal-interface: %s", sval);
+ }
+ } else if (strcmp(key, "vxlan") == 0) {
+ bpc->bpc_vxlan = json_object_get_int64(jo_val);
+ bpc->bpc_has_vxlan = true;
+ log_debug("\tvxlan: %ld", bpc->bpc_vxlan);
+ } 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)) {
+ log_debug("\tvrf-name: %s (truncated)", sval);
+ error++;
+ } else {
+ log_debug("\tvrf-name: %s", sval);
+ }
+ } else if (strcmp(key, "detect-multiplier") == 0) {
+ bpc->bpc_detectmultiplier =
+ json_object_get_int64(jo_val);
+ bpc->bpc_has_detectmultiplier = true;
+ log_debug("\tdetect-multiplier: %llu",
+ bpc->bpc_detectmultiplier);
+ } else if (strcmp(key, "receive-interval") == 0) {
+ bpc->bpc_recvinterval = json_object_get_int64(jo_val);
+ bpc->bpc_has_recvinterval = true;
+ log_debug("\treceive-interval: %llu",
+ bpc->bpc_recvinterval);
+ } else if (strcmp(key, "transmit-interval") == 0) {
+ bpc->bpc_txinterval = json_object_get_int64(jo_val);
+ bpc->bpc_has_txinterval = true;
+ log_debug("\ttransmit-interval: %llu",
+ bpc->bpc_txinterval);
+ } else if (strcmp(key, "echo-interval") == 0) {
+ bpc->bpc_echointerval = json_object_get_int64(jo_val);
+ bpc->bpc_has_echointerval = true;
+ log_debug("\techo-interval: %llu",
+ bpc->bpc_echointerval);
+ } else if (strcmp(key, "create-only") == 0) {
+ bpc->bpc_createonly = json_object_get_boolean(jo_val);
+ log_debug("\tcreate-only: %s",
+ bpc->bpc_createonly ? "true" : "false");
+ } else if (strcmp(key, "shutdown") == 0) {
+ bpc->bpc_shutdown = json_object_get_boolean(jo_val);
+ log_debug("\tshutdown: %s",
+ bpc->bpc_shutdown ? "true" : "false");
+ } else if (strcmp(key, "echo-mode") == 0) {
+ bpc->bpc_echo = json_object_get_boolean(jo_val);
+ log_debug("\techo-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)) {
+ log_debug("\tlabel: %s (truncated)", sval);
+ error++;
+ } else {
+ log_debug("\tlabel: %s", sval);
+ }
+ } else {
+ sval = json_object_get_string(jo_val);
+ log_warning("%s:%d invalid configuration: '%s: %s'",
+ __func__, __LINE__, key, sval);
+ error++;
+ }
+ }
+
+ if (bpc->bpc_peer.sa_sin.sin_family == 0) {
+ log_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;
+
+ log_debug("\tpeer-label: %s", sval);
+
+ /* Translate the label into BFD address keys. */
+ bpc->bpc_ipv4 = !BFD_CHECK_FLAG(pl->pl_bs->flags, BFD_SESS_FLAG_IPV6);
+ bpc->bpc_mhop = BFD_CHECK_FLAG(pl->pl_bs->flags, BFD_SESS_FLAG_MH);
+ if (bpc->bpc_mhop) {
+ bpc->bpc_peer = pl->pl_bs->mhop.peer;
+ bpc->bpc_local = pl->pl_bs->mhop.local;
+ if (pl->pl_bs->mhop.vrf_name[0]) {
+ bpc->bpc_has_vrfname = true;
+ strlcpy(bpc->bpc_vrfname, pl->pl_bs->mhop.vrf_name,
+ sizeof(bpc->bpc_vrfname));
+ }
+ } else {
+ bpc->bpc_peer = pl->pl_bs->shop.peer;
+ if (pl->pl_bs->shop.port_name[0]) {
+ bpc->bpc_has_localif = true;
+ strlcpy(bpc->bpc_localif, pl->pl_bs->shop.port_name,
+ sizeof(bpc->bpc_localif));
+ }
+ }
+
+ 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->up_min_tx / 1000);
+ json_object_int_add(resp, "echo-interval",
+ bs->timers.required_min_echo / 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-interval",
+ bs->remote_timers.required_min_echo / 1000);
+
+ if (BFD_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 (BFD_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)
+{
+ /* Add peer 'key' information. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6))
+ json_object_boolean_true_add(jo, "ipv6");
+ else
+ json_object_boolean_false_add(jo, "ipv6");
+
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ json_object_boolean_true_add(jo, "multihop");
+ json_object_string_add(jo, "peer-address",
+ satostr(&bs->mhop.peer));
+ json_object_string_add(jo, "local-address",
+ satostr(&bs->mhop.local));
+ if (strlen(bs->mhop.vrf_name) > 0)
+ json_object_string_add(jo, "vrf-name",
+ bs->mhop.vrf_name);
+ } else {
+ json_object_boolean_false_add(jo, "multihop");
+ json_object_string_add(jo, "peer-address",
+ satostr(&bs->shop.peer));
+ if (bs->local_address.sa_sin.sin_family != AF_UNSPEC)
+ json_object_string_add(jo, "local-address",
+ satostr(&bs->local_address));
+ if (strlen(bs->shop.port_name) > 0)
+ json_object_string_add(jo, "local-interface",
+ bs->shop.port_name);
+ }
+
+ 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 (pl == NULL)
+ return NULL;
+
+ if (strlcpy(pl->pl_label, label, sizeof(pl->pl_label))
+ > sizeof(pl->pl_label))
+ log_warning("%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 0000000000..7e586dbb45
--- /dev/null
+++ b/bfdd/control.c
@@ -0,0 +1,895 @@
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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 int control_read(struct thread *t);
+static int control_write(struct thread *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_backet *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) {
+ log_warning("%s: fcntl F_GETFL: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ flags |= O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) == -1) {
+ log_warning("%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) {
+ log_error("%s: socket: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ umval = umask(0);
+ if (bind(sd, (struct sockaddr *)&sun_, sizeof(sun_)) == -1) {
+ log_error("%s: bind: %s", __func__, strerror(errno));
+ close(sd);
+ return -1;
+ }
+ umask(umval);
+
+ if (listen(sd, SOMAXCONN) == -1) {
+ log_error("%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;
+
+ if (bglobal.bg_csockev) {
+ thread_cancel(bglobal.bg_csockev);
+ bglobal.bg_csockev = NULL;
+ }
+
+ socket_close(&bglobal.bg_csock);
+
+ while (!TAILQ_EMPTY(&bglobal.bg_bcslist)) {
+ bcs = TAILQ_FIRST(&bglobal.bg_bcslist);
+ control_free(bcs);
+ }
+}
+
+int control_accept(struct thread *t)
+{
+ int csock, sd = THREAD_FD(t);
+
+ csock = accept(sd, NULL, 0);
+ if (csock == -1) {
+ log_warning("%s: accept: %s", __func__, strerror(errno));
+ return 0;
+ }
+
+ if (control_new(csock) == NULL)
+ close(csock);
+
+ bglobal.bg_csockev = NULL;
+ thread_add_read(master, control_accept, NULL, sd, &bglobal.bg_csockev);
+
+ return 0;
+}
+
+
+/*
+ * Client handling
+ */
+struct bfd_control_socket *control_new(int sd)
+{
+ struct bfd_control_socket *bcs;
+
+ bcs = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bcs));
+ if (bcs == NULL)
+ return NULL;
+
+ /* Disable notifications by default. */
+ bcs->bcs_notify = 0;
+
+ bcs->bcs_sd = sd;
+ thread_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;
+
+ if (bcs->bcs_ev) {
+ thread_cancel(bcs->bcs_ev);
+ bcs->bcs_ev = NULL;
+ }
+
+ if (bcs->bcs_outev) {
+ thread_cancel(bcs->bcs_outev);
+ bcs->bcs_outev = NULL;
+ }
+
+ 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));
+ if (bnp == NULL) {
+ log_warning("%s: calloc: %s", __func__, strerror(errno));
+ return NULL;
+ }
+
+ 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));
+ if (bcq == NULL) {
+ log_warning("%s: calloc: %s", __func__, strerror(errno));
+ return NULL;
+ }
+
+ 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;
+ thread_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+
+ return 1;
+
+empty_list:
+ if (bcs->bcs_outev) {
+ thread_cancel(bcs->bcs_outev);
+ bcs->bcs_outev = NULL;
+ }
+ 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);
+ if (bcq == NULL)
+ return -1;
+
+ 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. */
+ thread_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_buf = NULL;
+ bcb->bcb_pos = 0;
+ bcb->bcb_left = 0;
+}
+
+static int control_read(struct thread *t)
+{
+ struct bfd_control_socket *bcs = THREAD_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 0;
+ }
+ if (bread < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ goto schedule_next_read;
+
+ log_warning("%s: read: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return 0;
+ }
+
+ /* Validate header fields. */
+ plen = ntohl(bcm.bcm_length);
+ if (plen < 2) {
+ log_debug("%s: client closed due small message length: %d",
+ __func__, bcm.bcm_length);
+ control_free(bcs);
+ return 0;
+ }
+
+ if (bcm.bcm_ver != BMV_VERSION_1) {
+ log_debug("%s: client closed due bad version: %d", __func__,
+ bcm.bcm_ver);
+ control_free(bcs);
+ return 0;
+ }
+
+ /* 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) {
+ log_warning("%s: not enough memory for message size: %u",
+ __func__, bcb->bcb_left);
+ control_free(bcs);
+ return 0;
+ }
+
+ 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 0;
+ }
+ if (bread < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ goto schedule_next_read;
+
+ log_warning("%s: read: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return 0;
+ }
+
+ 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 (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:
+ log_debug("%s: unhandled message type: %d", __func__,
+ bcm.bcm_type);
+ control_response(bcs, 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;
+ thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev);
+
+ return 0;
+}
+
+static int control_write(struct thread *t)
+{
+ struct bfd_control_socket *bcs = THREAD_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 0;
+ }
+ if (bwrite < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
+ bcs->bcs_outev = NULL;
+ thread_add_write(master, control_write, bcs,
+ bcs->bcs_sd, &bcs->bcs_outev);
+ return 0;
+ }
+
+ log_warning("%s: write: %s", __func__, strerror(errno));
+ control_free(bcs);
+ return 0;
+ }
+
+ bcb->bcb_pos += bwrite;
+ bcb->bcb_left -= bwrite;
+ if (bcb->bcb_left > 0) {
+ bcs->bcs_outev = NULL;
+ thread_add_write(master, control_write, bcs, bcs->bcs_sd,
+ &bcs->bcs_outev);
+ return 0;
+ }
+
+ control_queue_dequeue(bcs);
+
+ return 0;
+}
+
+
+/*
+ * 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_backet *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;
+
+ if (control_notifypeer_new(bcs, bs) == NULL)
+ return -1;
+
+ /* 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) {
+ log_warning("%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);
+ if (bcm == NULL) {
+ log_warning("%s: malloc: %s", __func__, strerror(errno));
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+ return;
+ }
+
+ 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) {
+ log_warning("%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);
+ if (bcm == NULL) {
+ log_warning("%s: malloc: %s", __func__, strerror(errno));
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+ return;
+ }
+
+ 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)
+{
+ struct bfd_control_socket *bcs;
+ struct bfd_notify_peer *bnp;
+
+ /* Notify zebra listeners as well. */
+ ptm_bfd_notify(bs);
+
+ /*
+ * 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) {
+ log_warning("%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);
+ if (bcm == NULL) {
+ log_warning("%s: malloc: %s", __func__, strerror(errno));
+ XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr);
+ return;
+ }
+
+ 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/event.c b/bfdd/event.c
new file mode 100644
index 0000000000..ba12f5b4e8
--- /dev/null
+++ b/bfdd/event.c
@@ -0,0 +1,155 @@
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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};
+
+ /* Don't add event if peer is deactivated. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return;
+
+ tv_normalize(&tv);
+#ifdef BFD_EVENT_DEBUG
+ log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec);
+#endif /* BFD_EVENT_DEBUG */
+
+ /* Remove previous schedule if any. */
+ if (bs->recvtimer_ev)
+ bfd_recvtimer_delete(bs);
+
+ thread_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};
+
+ /* Don't add event if peer is deactivated. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return;
+
+ tv_normalize(&tv);
+#ifdef BFD_EVENT_DEBUG
+ log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec);
+#endif /* BFD_EVENT_DEBUG */
+
+ /* Remove previous schedule if any. */
+ if (bs->echo_recvtimer_ev)
+ bfd_echo_recvtimer_delete(bs);
+
+ thread_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};
+
+ /* Don't add event if peer is deactivated. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return;
+
+ tv_normalize(&tv);
+#ifdef BFD_EVENT_DEBUG
+ log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec);
+#endif /* BFD_EVENT_DEBUG */
+
+ /* Remove previous schedule if any. */
+ if (bs->xmttimer_ev)
+ bfd_xmttimer_delete(bs);
+
+ thread_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};
+
+ /* Don't add event if peer is deactivated. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN))
+ return;
+
+ tv_normalize(&tv);
+#ifdef BFD_EVENT_DEBUG
+ log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec);
+#endif /* BFD_EVENT_DEBUG */
+
+ /* Remove previous schedule if any. */
+ if (bs->echo_xmttimer_ev)
+ bfd_echo_xmttimer_delete(bs);
+
+ thread_add_timer_tv(master, bfd_echo_xmt_cb, bs, &tv,
+ &bs->echo_xmttimer_ev);
+}
+
+void bfd_recvtimer_delete(struct bfd_session *bs)
+{
+ if (bs->recvtimer_ev == NULL)
+ return;
+
+ thread_cancel(bs->recvtimer_ev);
+ bs->recvtimer_ev = NULL;
+}
+
+void bfd_echo_recvtimer_delete(struct bfd_session *bs)
+{
+ if (bs->echo_recvtimer_ev == NULL)
+ return;
+
+ thread_cancel(bs->echo_recvtimer_ev);
+ bs->echo_recvtimer_ev = NULL;
+}
+
+void bfd_xmttimer_delete(struct bfd_session *bs)
+{
+ if (bs->xmttimer_ev == NULL)
+ return;
+
+ thread_cancel(bs->xmttimer_ev);
+ bs->xmttimer_ev = NULL;
+}
+
+void bfd_echo_xmttimer_delete(struct bfd_session *bs)
+{
+ if (bs->echo_xmttimer_ev == NULL)
+ return;
+
+ thread_cancel(bs->echo_xmttimer_ev);
+ bs->echo_xmttimer_ev = NULL;
+}
diff --git a/bfdd/linux.c b/bfdd/linux.c
new file mode 100644
index 0000000000..5f24ef4d19
--- /dev/null
+++ b/bfdd/linux.c
@@ -0,0 +1,221 @@
+/*
+ * Linux specific code
+ *
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <zebra.h>
+
+#ifdef BFD_LINUX
+
+/* XXX: fix compilation error on Ubuntu 16.04 or older. */
+#ifndef _UAPI_IPV6_H
+#define _UAPI_IPV6_H
+#endif /* _UAPI_IPV6_H */
+
+#include <linux/filter.h>
+#include <linux/if_packet.h>
+
+#include <netinet/if_ether.h>
+
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+#include "bfd.h"
+
+/* Berkeley Packet filter code to filter out BFD Echo packets.
+ * tcpdump -dd "(udp dst port 3785)"
+ */
+static struct sock_filter bfd_echo_filter[] = {
+ {0x28, 0, 0, 0x0000000c}, {0x15, 0, 4, 0x000086dd},
+ {0x30, 0, 0, 0x00000014}, {0x15, 0, 11, 0x00000011},
+ {0x28, 0, 0, 0x00000038}, {0x15, 8, 9, 0x00000ec9},
+ {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, 0x0000ffff}, {0x6, 0, 0, 0x00000000},
+};
+
+/* Berkeley Packet filter code to filter out BFD vxlan packets.
+ * tcpdump -dd "(udp dst port 4789)"
+ */
+static struct sock_filter bfd_vxlan_filter[] = {
+ {0x28, 0, 0, 0x0000000c}, {0x15, 0, 4, 0x000086dd},
+ {0x30, 0, 0, 0x00000014}, {0x15, 0, 11, 0x00000011},
+ {0x28, 0, 0, 0x00000038}, {0x15, 8, 9, 0x000012b5},
+ {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, 0x000012b5},
+ {0x6, 0, 0, 0x0000ffff}, {0x6, 0, 0, 0x00000000},
+};
+
+
+/*
+ * Definitions.
+ */
+int ptm_bfd_fetch_ifindex(const char *ifname)
+{
+ struct ifreq ifr;
+
+ if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name))
+ > sizeof(ifr.ifr_name))
+ log_error("interface-to-index: name truncated ('%s' -> '%s')",
+ ifr.ifr_name, ifname);
+
+ if (ioctl(bglobal.bg_shop, SIOCGIFINDEX, &ifr) == -1) {
+ log_error("interface-to-index: %s translation failed: %s",
+ ifname, strerror(errno));
+ return -1;
+ }
+
+ return ifr.ifr_ifindex;
+}
+
+void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac)
+{
+ struct ifreq ifr;
+
+ if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name))
+ > sizeof(ifr.ifr_name))
+ log_error("interface-mac: name truncated ('%s' -> '%s')",
+ ifr.ifr_name, ifname);
+
+ if (ioctl(bglobal.bg_shop, SIOCGIFHWADDR, &ifr) == -1) {
+ log_error("interface-mac: %s MAC retrieval failed: %s", ifname,
+ strerror(errno));
+ return;
+ }
+
+ memcpy(mac, ifr.ifr_hwaddr.sa_data, ETHERNET_ADDRESS_LENGTH);
+}
+
+
+/* Was _fetch_portname_from_ifindex() */
+void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen)
+{
+ struct ifreq ifr;
+
+ ifname[0] = 0;
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_ifindex = ifindex;
+
+ if (ioctl(bglobal.bg_shop, SIOCGIFNAME, &ifr) == -1) {
+ log_error("index-to-interface: index %d failure: %s", ifindex,
+ strerror(errno));
+ return;
+ }
+
+ if (strlcpy(ifname, ifr.ifr_name, ifnamelen) >= ifnamelen)
+ log_debug("index-to-interface: name truncated '%s' -> '%s'",
+ ifr.ifr_name, ifname);
+}
+
+int ptm_bfd_echo_sock_init(void)
+{
+ int s;
+ struct sock_fprog bpf = {.len = sizeof(bfd_echo_filter)
+ / sizeof(bfd_echo_filter[0]),
+ .filter = bfd_echo_filter};
+
+ s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
+ if (s == -1) {
+ log_error("echo-socket: creation failure: %s", strerror(errno));
+ return -1;
+ }
+
+ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf))
+ == -1) {
+ log_error("echo-socket: setsockopt(SO_ATTACH_FILTER): %s",
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+
+int ptm_bfd_vxlan_sock_init(void)
+{
+ int s;
+ struct sock_fprog bpf = {.len = sizeof(bfd_vxlan_filter)
+ / sizeof(bfd_vxlan_filter[0]),
+ .filter = bfd_vxlan_filter};
+
+ s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
+ if (s == -1) {
+ log_error("vxlan-socket: creation failure: %s",
+ strerror(errno));
+ return -1;
+ }
+
+ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf))
+ == -1) {
+ log_error("vxlan-socket: setsockopt(SO_ATTACH_FILTER): %s",
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+
+int bp_bind_dev(int sd __attribute__((__unused__)),
+ const char *dev __attribute__((__unused__)))
+{
+ /*
+ * TODO: implement this differently. It is not possible to
+ * SO_BINDTODEVICE after the daemon has dropped its privileges.
+ */
+#if 0
+ size_t devlen = strlen(dev) + 1;
+
+ if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, dev, devlen) == -1) {
+ log_warning("%s: setsockopt(SO_BINDTODEVICE, \"%s\"): %s",
+ __func__, dev, strerror(errno));
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len)
+{
+ char *ptr;
+ struct udp_psuedo_header pudp_hdr;
+ uint16_t csum;
+
+ pudp_hdr.saddr = iph->saddr;
+ pudp_hdr.daddr = iph->daddr;
+ pudp_hdr.reserved = 0;
+ pudp_hdr.protocol = iph->protocol;
+ pudp_hdr.len = htons(len);
+
+ ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len);
+ memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN);
+ memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len);
+
+ csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len);
+ XFREE(MTYPE_BFDD_TMP, ptr);
+ return csum;
+}
+
+#endif /* BFD_LINUX */
diff --git a/bfdd/log.c b/bfdd/log.c
new file mode 100644
index 0000000000..d81d7cd9b3
--- /dev/null
+++ b/bfdd/log.c
@@ -0,0 +1,125 @@
+/*********************************************************************
+ * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * log.c: implements an abstraction between loggers interface. Implement all
+ * log backends in this file.
+ *
+ * Authors
+ * -------
+ * Rafael Zalamena <rzalamena@opensourcerouting.org>
+ */
+
+#include <zebra.h>
+
+#include "bfd.h"
+
+#include "lib/log_int.h"
+
+void log_msg(int level, const char *fmt, va_list vl);
+
+
+static int log_fg;
+static int log_level = BLOG_DEBUG;
+
+void log_init(int foreground, enum blog_level level,
+ struct frr_daemon_info *fdi)
+{
+ log_fg = foreground;
+ log_level = level;
+
+ openzlog(fdi->progname, fdi->logname, 0,
+ LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON);
+}
+
+void log_msg(int level, const char *fmt, va_list vl)
+{
+ if (level < log_level)
+ return;
+
+ switch (level) {
+ case BLOG_DEBUG:
+ vzlog(LOG_DEBUG, fmt, vl);
+ break;
+
+ case BLOG_INFO:
+ vzlog(LOG_INFO, fmt, vl);
+ break;
+
+ case BLOG_WARNING:
+ vzlog(LOG_WARNING, fmt, vl);
+ break;
+
+ case BLOG_ERROR:
+ vzlog(LOG_ERR, fmt, vl);
+ break;
+
+ case BLOG_FATAL:
+ vzlog(LOG_EMERG, fmt, vl);
+ break;
+
+ default:
+ vfprintf(stderr, fmt, vl);
+ break;
+ }
+}
+
+void log_info(const char *fmt, ...)
+{
+ va_list vl;
+
+ va_start(vl, fmt);
+ log_msg(BLOG_INFO, fmt, vl);
+ va_end(vl);
+}
+
+void log_debug(const char *fmt, ...)
+{
+ va_list vl;
+
+ va_start(vl, fmt);
+ log_msg(BLOG_DEBUG, fmt, vl);
+ va_end(vl);
+}
+
+void log_error(const char *fmt, ...)
+{
+ va_list vl;
+
+ va_start(vl, fmt);
+ log_msg(BLOG_ERROR, fmt, vl);
+ va_end(vl);
+}
+
+void log_warning(const char *fmt, ...)
+{
+ va_list vl;
+
+ va_start(vl, fmt);
+ log_msg(BLOG_WARNING, fmt, vl);
+ va_end(vl);
+}
+
+void log_fatal(const char *fmt, ...)
+{
+ va_list vl;
+
+ va_start(vl, fmt);
+ log_msg(BLOG_FATAL, fmt, vl);
+ va_end(vl);
+
+ exit(1);
+}
diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c
new file mode 100644
index 0000000000..ca44be6541
--- /dev/null
+++ b/bfdd/ptm_adapter.c
@@ -0,0 +1,718 @@
+/*
+ * BFD PTM adapter code
+ * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF")
+ *
+ * FRR is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * FRR is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FRR; see the file COPYING. If not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <zebra.h>
+
+#include "lib/libfrr.h"
+#include "lib/queue.h"
+#include "lib/stream.h"
+#include "lib/zclient.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, struct sockaddr_any *sa);
+
+static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa);
+static int _ptm_msg_read(struct stream *msg, int command,
+ 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);
+static void bfdd_dest_deregister(struct stream *msg);
+static void bfdd_client_register(struct stream *msg);
+static void bfdd_client_deregister(struct stream *msg);
+
+/*
+ * Functions
+ */
+#ifdef BFD_DEBUG
+static void debug_printbpc(const char *func, unsigned int line,
+ struct bfd_peer_cfg *bpc);
+
+static void debug_printbpc(const char *func, unsigned int line,
+ struct bfd_peer_cfg *bpc)
+{
+ char addr[3][128];
+ char timers[3][128];
+
+ addr[0][0] = addr[1][0] = addr[2][0] = timers[0][0] = timers[1][0] =
+ timers[2][0] = 0;
+
+ 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)
+ snprintf(timers[0], sizeof(timers[0]), " rx:%lu",
+ bpc->bpc_recvinterval);
+
+ if (bpc->bpc_has_txinterval)
+ snprintf(timers[1], sizeof(timers[1]), " tx:%lu",
+ bpc->bpc_recvinterval);
+
+ if (bpc->bpc_has_detectmultiplier)
+ snprintf(timers[2], sizeof(timers[2]), " detect-multiplier:%d",
+ bpc->bpc_detectmultiplier);
+
+ log_debug("%s:%d: %s %s%s%s%s%s%s", func, line,
+ bpc->bpc_mhop ? "multi-hop" : "single-hop", addr[0], addr[1],
+ addr[2], timers[0], timers[1], timers[2]);
+}
+
+#define DEBUG_PRINTBPC(bpc) debug_printbpc(__FILE__, __LINE__, (bpc))
+#else
+#define DEBUG_PRINTBPC(bpc)
+#endif /* BFD_DEBUG */
+
+static int _ptm_msg_address(struct stream *msg, struct sockaddr_any *sa)
+{
+ switch (sa->sa_sin.sin_family) {
+ case AF_INET:
+ stream_putc(msg, sa->sa_sin.sin_family);
+ stream_put_in_addr(msg, &sa->sa_sin.sin_addr);
+ stream_putc(msg, 32);
+ break;
+
+ case AF_INET6:
+ stream_putc(msg, sa->sa_sin6.sin6_family);
+ stream_put(msg, &sa->sa_sin6.sin6_addr,
+ sizeof(sa->sa_sin6.sin6_addr));
+ stream_putc(msg, 128);
+ break;
+
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+int ptm_bfd_notify(struct bfd_session *bs)
+{
+ struct stream *msg;
+ struct sockaddr_any sac;
+
+ /*
+ * 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
+ *
+ * Commands: ZEBRA_BFD_DEST_REPLAY
+ *
+ * q(64), l(32), w(16), c(8)
+ */
+ msg = zclient->obuf;
+ stream_reset(msg);
+
+ /* TODO: VRF handling */
+ 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. */
+ stream_putl(msg, bs->ifindex);
+
+ /* BFD destination prefix information. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH))
+ _ptm_msg_address(msg, &bs->mhop.peer);
+ else
+ _ptm_msg_address(msg, &bs->shop.peer);
+
+ /* BFD status */
+ switch (bs->ses_state) {
+ case PTM_BFD_UP:
+ stream_putl(msg, BFD_STATUS_UP);
+ break;
+
+ case PTM_BFD_ADM_DOWN:
+ 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. */
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ _ptm_msg_address(msg, &bs->mhop.local);
+ } else {
+ if (bs->local_address.sa_sin.sin_family)
+ _ptm_msg_address(msg, &bs->local_address);
+ else if (bs->local_address.sa_sin.sin_family)
+ _ptm_msg_address(msg, &bs->local_ip);
+ else {
+ sac = bs->shop.peer;
+ switch (sac.sa_sin.sin_family) {
+ case AF_INET:
+ memset(&sac.sa_sin.sin_addr, 0,
+ sizeof(sac.sa_sin.sin_family));
+ break;
+ case AF_INET6:
+ memset(&sac.sa_sin6.sin6_addr, 0,
+ sizeof(sac.sa_sin6.sin6_family));
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+
+ /* No local address found yet, so send zeroes. */
+ _ptm_msg_address(msg, &sac);
+ }
+ }
+
+ /* 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:
+ log_warning("%s: invalid family: %d", __func__, family);
+ break;
+ }
+
+stream_failure:
+ memset(sa, 0, sizeof(*sa));
+}
+
+static int _ptm_msg_read(struct stream *msg, int command,
+ struct bfd_peer_cfg *bpc, struct ptm_client **pc)
+{
+ uint32_t pid;
+ uint8_t ttl;
+ uint8_t ifnamelen;
+
+ /*
+ * Register/Deregister/Update Message format:
+ * - 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: destination ipv4
+ * - AF_INET6:
+ * - 16 bytes: destination IPv6
+ * - c: ttl
+ * - no multihop
+ * - AF_INET6:
+ * - w: family
+ * - 16 bytes: ipv6 address
+ * - c: ifname length
+ * - X bytes: interface 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);
+ if (*pc == NULL) {
+ log_debug("%s: failed to allocate memory", __func__);
+ return -1;
+ }
+
+ /* 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. */
+ if (command != ZEBRA_BFD_DEST_DEREGISTER) {
+ 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);
+ if (bpc->bpc_mhop) {
+ /* Read multihop source address and TTL. */
+ _ptm_msg_read_address(msg, &bpc->bpc_local);
+ STREAM_GETC(msg, ttl);
+
+ /*
+ * TODO: use TTL for something. The line below removes
+ * an unused variable compiler warning.
+ */
+ ttl = ttl;
+ } else {
+ /* If target is IPv6, then we must obtain local address. */
+ if (bpc->bpc_ipv4 == false)
+ _ptm_msg_read_address(msg, &bpc->bpc_local);
+
+ /*
+ * Read interface name and make sure it fits our data
+ * structure, otherwise fail.
+ */
+ STREAM_GETC(msg, ifnamelen);
+ if (ifnamelen > sizeof(bpc->bpc_localif)) {
+ log_error("%s: interface name is too big", __func__);
+ 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;
+ }
+ }
+
+ /* Sanity check: peer and local address must match IP types. */
+ if (bpc->bpc_local.sa_sin.sin_family != 0
+ && (bpc->bpc_local.sa_sin.sin_family
+ != bpc->bpc_peer.sa_sin.sin_family)) {
+ log_warning("%s: peer family doesn't match local type",
+ __func__);
+ return -1;
+ }
+
+ return 0;
+
+stream_failure:
+ return -1;
+}
+
+static void bfdd_dest_register(struct stream *msg)
+{
+ 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_REGISTER, &bpc, &pc) == -1)
+ return;
+
+ DEBUG_PRINTBPC(&bpc);
+
+ /* Find or start new BFD session. */
+ bs = bs_peer_find(&bpc);
+ if (bs == NULL) {
+ bs = ptm_bfd_sess_new(&bpc);
+ if (bs == NULL) {
+ log_debug("%s: failed to create BFD session", __func__);
+ return;
+ }
+ } else {
+ /* Don't try to change echo/shutdown state. */
+ bpc.bpc_echo = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO);
+ bpc.bpc_shutdown =
+ BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN);
+ }
+
+ /* Create client peer notification register. */
+ pcn = pcn_new(pc, bs);
+ if (pcn == NULL) {
+ log_error("%s: failed to registrate notifications", __func__);
+ return;
+ }
+
+ ptm_bfd_notify(bs);
+}
+
+static void bfdd_dest_deregister(struct stream *msg)
+{
+ 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, &bpc, &pc) == -1)
+ return;
+
+ DEBUG_PRINTBPC(&bpc);
+
+ /* Find or start new BFD session. */
+ bs = bs_peer_find(&bpc);
+ if (bs == NULL) {
+ log_debug("%s: failed to create BFD session", __func__);
+ return;
+ }
+
+ /* Unregister client peer notification. */
+ pcn = pcn_lookup(pc, bs);
+ pcn_free(pcn);
+}
+
+/*
+ * header: command, VRF
+ * l: pid
+ */
+static void bfdd_client_register(struct stream *msg)
+{
+ struct ptm_client *pc;
+ uint32_t pid;
+
+ /* Find or allocate process context data. */
+ STREAM_GETL(msg, pid);
+
+ pc = pc_new(pid);
+ if (pc == NULL) {
+ log_error("%s: failed to register client: %u", __func__, pid);
+ return;
+ }
+
+ return;
+
+stream_failure:
+ log_error("%s: failed to register client", __func__);
+}
+
+/*
+ * 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) {
+ log_debug("%s: failed to find client: %u", __func__, pid);
+ return;
+ }
+
+ pc_free(pc);
+
+ return;
+
+stream_failure:
+ log_error("%s: failed to deregister client", __func__);
+}
+
+static int bfdd_replay(int cmd, struct zclient *zc, uint16_t len, vrf_id_t vid)
+{
+ struct stream *msg = zc->ibuf;
+ uint32_t rcmd;
+
+ STREAM_GETL(msg, rcmd);
+
+ switch (rcmd) {
+ case ZEBRA_BFD_DEST_REGISTER:
+ case ZEBRA_BFD_DEST_UPDATE:
+ bfdd_dest_register(msg);
+ break;
+ case ZEBRA_BFD_DEST_DEREGISTER:
+ bfdd_dest_deregister(msg);
+ break;
+ case ZEBRA_BFD_CLIENT_REGISTER:
+ bfdd_client_register(msg);
+ break;
+ case ZEBRA_BFD_CLIENT_DEREGISTER:
+ bfdd_client_deregister(msg);
+ break;
+
+ default:
+ log_debug("%s: invalid message type %u", __func__, rcmd);
+ return -1;
+ }
+
+ return 0;
+
+stream_failure:
+ log_error("%s: failed to find command", __func__);
+ 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));
+
+ zclient_send_message(zclient);
+}
+
+void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv)
+{
+ zclient = zclient_new_notify(master, &zclient_options_default);
+ assert(zclient != NULL);
+ zclient_init(zclient, ZEBRA_ROUTE_BFD, 0, bfdd_priv);
+
+ /*
+ * 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.
+ */
+ zclient->bfd_dest_replay = bfdd_replay;
+
+ /* Send replay request on zebra connect. */
+ zclient->zebra_connected = bfdd_zebra_connected;
+}
+
+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));
+ if (pc == NULL)
+ return NULL;
+
+ 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;
+
+ if (pc == NULL)
+ return;
+
+ 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));
+ if (pcn == NULL)
+ return NULL;
+
+ 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;
+
+ if (pcn == NULL)
+ return;
+
+ /* Handle session de-registration. */
+ bs = pcn->pcn_bs;
+ pcn->pcn_bs = NULL;
+ bs->refcount--;
+
+ /* 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 0000000000..86923f5cec
--- /dev/null
+++ b/bfdd/subdir.am
@@ -0,0 +1,33 @@
+#
+# bfdd
+#
+
+if BFDD
+noinst_LIBRARIES += bfdd/libbfd.a
+sbin_PROGRAMS += bfdd/bfdd
+dist_examples_DATA += bfdd/bfdd.conf.sample
+endif
+
+bfdd_libbfd_a_SOURCES = \
+ bfdd/bfd.c \
+ bfdd/bfdd_vty.c \
+ bfdd/bfd_packet.c \
+ bfdd/bsd.c \
+ bfdd/config.c \
+ bfdd/control.c \
+ bfdd/event.c \
+ bfdd/linux.c \
+ bfdd/log.c \
+ bfdd/ptm_adapter.c \
+ # end
+
+bfdd/bfdd_vty_clippy.c: $(CLIPPY_DEPS)
+bfdd/bfdd_vty.$(OBJEXT): bfdd/bfdd_vty_clippy.c
+
+noinst_HEADERS += \
+ bfdd/bfdctl.h \
+ bfdd/bfd.h \
+ # end
+
+bfdd_bfdd_SOURCES = bfdd/bfdd.c
+bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la
diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c
index c7d6249870..47dffd146a 100644
--- a/bgpd/bgp_bfd.c
+++ b/bgpd/bgp_bfd.c
@@ -509,9 +509,13 @@ void bgp_bfd_peer_config_write(struct vty *vty, struct peer *peer, char *addr)
bfd_info = (struct bfd_info *)peer->bfd_info;
if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG))
+#if HAVE_BFDD > 0
+ vty_out(vty, " neighbor %s bfd\n", addr);
+#else
vty_out(vty, " neighbor %s bfd %d %d %d\n", addr,
bfd_info->detect_mult, bfd_info->required_min_rx,
bfd_info->desired_min_tx);
+#endif /* HAVE_BFDD */
if (bfd_info->type != BFD_TYPE_NOT_CONFIGURED)
vty_out(vty, " neighbor %s bfd %s\n", addr,
@@ -556,7 +560,12 @@ DEFUN (neighbor_bfd,
return CMD_SUCCESS;
}
-DEFUN (neighbor_bfd_param,
+#if HAVE_BFDD > 0
+DEFUN_HIDDEN(
+#else
+DEFUN(
+#endif /* HAVE_BFDD */
+ neighbor_bfd_param,
neighbor_bfd_param_cmd,
"neighbor <A.B.C.D|X:X::X:X|WORD> bfd (2-255) (50-60000) (50-60000)",
NEIGHBOR_STR
@@ -628,14 +637,21 @@ DEFUN_HIDDEN (neighbor_bfd_type,
DEFUN (no_neighbor_bfd,
no_neighbor_bfd_cmd,
+#if HAVE_BFDD > 0
+ "no neighbor <A.B.C.D|X:X::X:X|WORD> bfd",
+#else
"no neighbor <A.B.C.D|X:X::X:X|WORD> bfd [(2-255) (50-60000) (50-60000)]",
+#endif /* HAVE_BFDD */
NO_STR
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"Disables BFD support\n"
+#if HAVE_BFDD == 0
"Detect Multiplier\n"
"Required min receive interval\n"
- "Desired min transmit interval\n")
+ "Desired min transmit interval\n"
+#endif /* !HAVE_BFDD */
+)
{
int idx_peer = 2;
struct peer *peer;
diff --git a/configure.ac b/configure.ac
index 42c2963ee6..afebc66e08 100755
--- a/configure.ac
+++ b/configure.ac
@@ -454,6 +454,8 @@ AC_ARG_ENABLE([numeric_version],
AS_HELP_STRING([--enable-numeric-version], [Only numeric digits allowed in version (for Alpine)]))
AC_ARG_ENABLE([gcov],
AS_HELP_STRING([--enable-gcov], [Add code coverage information]))
+AC_ARG_ENABLE(bfdd,
+ AS_HELP_STRING([--disable-bfdd], [do not build bfdd]))
AS_IF([test "${enable_clippy_only}" != "yes"], [
AC_CHECK_HEADERS(json-c/json.h)
@@ -1370,6 +1372,30 @@ AS_IF([test "${enable_ldpd}" != "no"], [
AC_DEFINE(HAVE_LDPD, 1, ldpd)
])
+if test "$enable_bfdd" = "no"; then
+ AC_DEFINE(HAVE_BFDD, 0, bfdd)
+ BFDD=""
+else
+ AC_DEFINE(HAVE_BFDD, 1, bfdd)
+ BFDD="bfdd"
+
+ case $host_os in
+ linux*)
+ AC_DEFINE(BFD_LINUX, 1, bfdd)
+ ;;
+
+ *)
+ AC_DEFINE(BFD_BSD, 1, bfdd)
+ ;;
+ esac
+fi
+
+AM_CONDITIONAL(BFDD, [test "x$BFDD" = "xbfdd"])
+
+if test $ac_cv_lib_json_c_json_object_get = no -a "x$BFDD" = "xbfdd"; then
+ AC_MSG_ERROR(["you must use json-c library to use bfdd"])
+fi
+
NHRPD=""
case "$host_os" in
linux*)
@@ -1813,15 +1839,58 @@ dnl order to check no alternative allocator
dnl has been specified, which might not provide
dnl mallinfo, e.g. such as Umem on Solaris.
dnl -----------------------------------------
-AC_CHECK_HEADER([malloc.h],
- [AC_MSG_CHECKING(whether mallinfo is available)
- AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <malloc.h>]],
- [[struct mallinfo ac_x; ac_x = mallinfo ();]])],
- [AC_MSG_RESULT(yes)
- AC_DEFINE(HAVE_MALLINFO,,mallinfo)],
- AC_MSG_RESULT(no)
- )
- ], [], FRR_INCLUDES)
+AC_CHECK_HEADERS([malloc.h malloc/malloc.h],,, [FRR_INCLUDES])
+
+AC_MSG_CHECKING(whether mallinfo is available)
+AC_LINK_IFELSE([AC_LANG_PROGRAM([FRR_INCLUDES [
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_MALLOC_MALLOC_H
+#include <malloc/malloc.h>
+#endif
+]], [[
+struct mallinfo ac_x; ac_x = mallinfo ();
+]])], [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_MALLINFO,,mallinfo)
+], [
+ AC_MSG_RESULT(no)
+])
+
+AC_MSG_CHECKING(whether malloc_usable_size is available)
+AC_LINK_IFELSE([AC_LANG_PROGRAM([FRR_INCLUDES [
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_MALLOC_MALLOC_H
+#include <malloc/malloc.h>
+#endif
+]], [[
+size_t ac_x; ac_x = malloc_usable_size(NULL);
+]])], [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_MALLOC_USABLE_SIZE,,malloc_usable_size)
+], [
+ AC_MSG_RESULT(no)
+
+ AC_MSG_CHECKING(whether malloc_size is available)
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_MALLOC_MALLOC_H
+#include <malloc/malloc.h>
+#endif
+]], [[
+size_t ac_x; ac_x = malloc_size(NULL);
+]])], [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_MALLOC_SIZE,,malloc_size)
+ ], [
+ AC_MSG_RESULT(no)
+ ])
+])
dnl ------
dnl ZeroMQ
@@ -1882,6 +1951,7 @@ AC_SUBST(frr_statedir)
AC_DEFINE_UNQUOTED(LDPD_SOCKET, "$frr_statedir/ldpd.sock",ldpd control socket)
AC_DEFINE_UNQUOTED(ZEBRA_SERV_PATH, "$frr_statedir/zserv.api",zebra api socket)
+AC_DEFINE_UNQUOTED(BFDD_CONTROL_SOCKET, "$frr_statedir/bfdd.sock", bfdd control socket)
AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$frr_statedir",daemon vty directory)
dnl autoconf does this, but it does it too late...
diff --git a/debianpkg/backports/ubuntu12.04/debian/rules b/debianpkg/backports/ubuntu12.04/debian/rules
index 01ad81d371..3a6c80297e 100755
--- a/debianpkg/backports/ubuntu12.04/debian/rules
+++ b/debianpkg/backports/ubuntu12.04/debian/rules
@@ -134,6 +134,7 @@ override_dh_auto_configure:
--enable-poll=yes \
$(USE_CUMULUS) \
$(USE_PIM) \
+ --disable-bfdd \
--enable-dependency-tracking \
$(USE_BGP_VNC) \
$(shell dpkg-buildflags --export=configure); \
diff --git a/debianpkg/backports/ubuntu14.04/debian/rules b/debianpkg/backports/ubuntu14.04/debian/rules
index f7b9428658..f7468d6f79 100755
--- a/debianpkg/backports/ubuntu14.04/debian/rules
+++ b/debianpkg/backports/ubuntu14.04/debian/rules
@@ -16,6 +16,7 @@ WANT_CUMULUS_MODE ?= 0
WANT_MULTIPATH ?= 1
WANT_SNMP ?= 0
WANT_RPKI ?= 0
+WANT_BFD ?= 1
# NOTES:
#
@@ -108,6 +109,12 @@ else
USE_RPKI=--disable-rpki
endif
+ifeq ($(WANT_BFD), 1)
+ USE_BFD=--enable-bfdd
+else
+ USE_BFD=--disable-bfdd
+endif
+
ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
DEBIAN_JOBS := $(subst parallel=,,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
endif
@@ -159,6 +166,7 @@ override_dh_auto_configure:
--enable-dependency-tracking \
$(USE_BGP_VNC) \
$(USE_RPKI) \
+ $(USE_BFD) \
$(shell dpkg-buildflags --export=configure); \
fi
diff --git a/debianpkg/rules b/debianpkg/rules
index 9c84c06516..c1cb865490 100755
--- a/debianpkg/rules
+++ b/debianpkg/rules
@@ -16,6 +16,7 @@ WANT_CUMULUS_MODE ?= 0
WANT_MULTIPATH ?= 1
WANT_SNMP ?= 0
WANT_RPKI ?= 0
+WANT_BFD ?= 1
# NOTES:
#
@@ -108,6 +109,12 @@ else
USE_RPKI=--disable-rpki
endif
+ifeq ($(WANT_BFD), 1)
+ USE_BFD=--enable-bfdd
+else
+ USE_BFD=--disable-bfdd
+endif
+
ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
DEBIAN_JOBS := $(subst parallel=,,$(filter parallel=%,$(DEB_BUILD_OPTIONS)))
endif
@@ -160,6 +167,7 @@ override_dh_auto_configure:
--enable-dependency-tracking \
$(USE_BGP_VNC) \
$(USE_RPKI) \
+ $(USE_BFD) \
$(shell dpkg-buildflags --export=configure); \
fi
diff --git a/doc/Makefile.am b/doc/Makefile.am
index fbceb8b04c..1f6a0d87fd 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -90,6 +90,10 @@ if STATICD
man_MANS += $(MANPAGE_BUILDDIR)/staticd.8
endif
+if BFDD
+man_MANS += $(MANPAGE_BUILDDIR)/bfdd.8
+endif
+
# Automake is particular about manpages. It is aware of them and has some
# special facilities for handling them, but it assumes that manpages are always
# given in groff source and so these facilities are limited to simply
@@ -159,6 +163,8 @@ EXTRA_DIST = frr-sphinx.mk \
manpages/vtysh.rst \
manpages/watchfrr.rst \
manpages/zebra.rst \
+ manpages/bfdd.rst \
+ manpages/bfd-options.rst \
developer/bgpd.rst \
developer/bgp-typecodes.rst \
developer/building-frr-on-alpine.rst \
@@ -234,7 +240,8 @@ EXTRA_DIST = frr-sphinx.mk \
user/vnc.rst \
user/vtysh.rst \
user/zebra.rst \
- user/flowspec.rst \
+ user/bfd.rst \
+ user/flowspec.rst \
mpls/ChangeLog.opaque.txt \
mpls/ospfd.conf \
mpls/cli_summary.txt \
diff --git a/doc/manpages/bfd-options.rst b/doc/manpages/bfd-options.rst
new file mode 100644
index 0000000000..e335ed120b
--- /dev/null
+++ b/doc/manpages/bfd-options.rst
@@ -0,0 +1,10 @@
+BFD SOCKET
+----------
+
+The following option controls the BFD daemon control socket location.
+
+.. option:: --bfdctl bfd-control-socket
+
+ Opens the BFD daemon control socket located at the pointed location.
+
+ (default: |INSTALL_PREFIX_STATE|/bfdd.sock)
diff --git a/doc/manpages/bfdd.rst b/doc/manpages/bfdd.rst
new file mode 100644
index 0000000000..1f8b1475f4
--- /dev/null
+++ b/doc/manpages/bfdd.rst
@@ -0,0 +1,40 @@
+****
+BFDD
+****
+
+.. include:: defines.rst
+.. |DAEMON| replace:: bfdd
+
+SYNOPSIS
+========
+|DAEMON| |synopsis-options-hv|
+
+|DAEMON| |synopsis-options|
+
+DESCRIPTION
+===========
+|DAEMON| is a communication failure detection component that works with
+the FRRouting routing engine.
+
+OPTIONS
+=======
+OPTIONS available for the |DAEMON| command:
+
+.. include:: common-options.rst
+.. include:: bfd-options.rst
+
+FILES
+=====
+
+|INSTALL_PREFIX_SBIN|/|DAEMON|
+ The default location of the |DAEMON| binary.
+
+|INSTALL_PREFIX_ETC|/|DAEMON|.conf
+ The default location of the |DAEMON| config file.
+
+$(PWD)/|DAEMON|.log
+ If the |DAEMON| process is configured to output logs to a file, then you
+ will find this file in the directory where you started |DAEMON|.
+
+.. include:: epilogue.rst
+
diff --git a/doc/manpages/common-options.rst b/doc/manpages/common-options.rst
index 1b2eb18dea..1e99010505 100644
--- a/doc/manpages/common-options.rst
+++ b/doc/manpages/common-options.rst
@@ -124,6 +124,7 @@ These following options control the daemon's VTY (interactive command line) inte
eigrpd 2613
pbrd 2615
staticd 2616
+ bfdd 2617
Port 2607 is used for ospfd's Opaque LSA API, while port 2600 is used for the (insecure) TCP-ZEBRA interface.
diff --git a/doc/manpages/conf.py b/doc/manpages/conf.py
index 4d5797f613..e540d236ea 100644
--- a/doc/manpages/conf.py
+++ b/doc/manpages/conf.py
@@ -332,6 +332,7 @@ man_pages = [
('watchfrr', 'watchfrr', 'a program to monitor the status of FRRouting daemons', [], 8),
('vtysh', 'vtysh', 'an integrated shell for FRRouting.', [], 1),
('frr', 'frr', 'a systemd interaction script', [], 1),
+ ('bfdd', 'bfdd', fwfrr.format("a bfd"), [], 8),
]
# -- Options for Texinfo output -------------------------------------------
diff --git a/doc/user/basic.rst b/doc/user/basic.rst
index cb46080055..da22bb2f86 100644
--- a/doc/user/basic.rst
+++ b/doc/user/basic.rst
@@ -320,6 +320,67 @@ Terminal Mode Commands
Shows the current configuration of the logging system. This includes the
status of all logging destinations.
+.. index:: show memory
+.. clicmd:: show memory
+
+ Show information on how much memory is used for which specific things in
+ |PACKAGE_NAME|. Output may vary depending on system capabilities but will
+ generally look something like this:
+
+ ::
+
+ frr# show memory
+ System allocator statistics:
+ Total heap allocated: 1584 KiB
+ Holding block headers: 0 bytes
+ Used small blocks: 0 bytes
+ Used ordinary blocks: 1484 KiB
+ Free small blocks: 2096 bytes
+ Free ordinary blocks: 100 KiB
+ Ordinary blocks: 2
+ Small blocks: 60
+ Holding blocks: 0
+ (see system documentation for 'mallinfo' for meaning)
+ --- qmem libfrr ---
+ Buffer : 3 24 72
+ Buffer data : 1 4120 4120
+ Host config : 3 (variably sized) 72
+ Command Tokens : 3427 72 247160
+ Command Token Text : 2555 (variably sized) 83720
+ Command Token Help : 2555 (variably sized) 61720
+ Command Argument : 2 (variably sized) 48
+ Command Argument Name : 641 (variably sized) 15672
+ [...]
+ --- qmem Label Manager ---
+ --- qmem zebra ---
+ ZEBRA VRF : 1 912 920
+ Route Entry : 11 80 968
+ Static route : 1 192 200
+ RIB destination : 8 48 448
+ RIB table info : 4 16 96
+ Nexthop tracking object : 1 200 200
+ Zebra Name Space : 1 312 312
+ --- qmem Table Manager ---
+
+ To understand system allocator statistics, refer to your system's
+ :manpage:`mallinfo(3)` man page.
+
+ Below these statistics, statistics on individual memory allocation types
+ in |PACKAGE_NAME| (so-called `MTYPEs`) is printed:
+
+ * the first column of numbers is the current count of allocations made for
+ the type (the number decreases when items are freed.)
+ * the second column is the size of each item. This is only available if
+ allocations on a type are always made with the same size.
+ * the third column is the total amount of memory allocated for the
+ particular type, including padding applied by malloc. This means that
+ the number may be larger than the first column multiplied by the second.
+ Overhead incurred by malloc's bookkeeping is not included in this, and
+ the column may be missing if system support is not available.
+
+ When executing this command from ``vtysh``, each of the daemons' memory
+ usage is printed sequentially.
+
.. index:: logmsg LEVEL MESSAGE
.. clicmd:: logmsg LEVEL MESSAGE
diff --git a/doc/user/bfd.rst b/doc/user/bfd.rst
new file mode 100644
index 0000000000..0e0fd23ece
--- /dev/null
+++ b/doc/user/bfd.rst
@@ -0,0 +1,302 @@
+.. _bfd:
+
+**********************************
+Bidirectional Forwarding Detection
+**********************************
+
+:abbr:`BFD (Bidirectional Forwarding Detection)` stands for
+Bidirectional Forwarding Detection and it is described and extended by
+the following RFCs:
+
+* :rfc:`5880`
+* :rfc:`5881`
+* :rfc:`5883`
+
+Currently, there are two implementations of the BFD commands in FRR:
+
+* :abbr:`PTM (Prescriptive Topology Manager)`: an external daemon which
+ implements BFD;
+* ``bfdd``: a BFD implementation that is able to talk with remote peers;
+
+This document will focus on the later implementation: *bfdd*.
+
+
+.. _bfd-starting:
+
+Starting BFD
+============
+
+*bfdd* default configuration file is :file:`bfdd.conf`. *bfdd* searches
+the current directory first then |INSTALL_PREFIX_ETC|/bfdd.conf. All of
+*bfdd*'s command must be configured in :file:`bfdd.conf`.
+
+*bfdd* specific invocation options are described below. Common options
+may also be specified (:ref:`common-invocation-options`).
+
+.. program:: bfdd
+
+.. option:: --bfdctl <unix-socket>
+
+ Set the BFD daemon control socket location. If using a non-default
+ socket location.
+
+ /usr/lib/frr/bfdd --bfdctl /tmp/bfdd.sock
+
+
+ The default UNIX socket location is:
+
+ #define BFDD_CONTROL_SOCKET "|INSTALL_PREFIX_STATE|/bfdd.sock"
+
+
+.. _bfd-commands:
+
+BFDd Commands
+=============
+
+.. index:: bfd
+.. clicmd:: bfd
+
+ Opens the BFD daemon configuration node.
+
+.. index:: peer <A.B.C.D|X:X::X:X> [{multihop|local-address <A.B.C.D|X:X::X:X>|interface IFNAME|vrf NAME}]
+.. clicmd:: peer <A.B.C.D|X:X::X:X> [{multihop|local-address <A.B.C.D|X:X::X:X>|interface IFNAME|vrf NAME}]
+
+ Creates and configures a new BFD peer to listen and talk to.
+
+ `multihop` tells the BFD daemon that we should expect packets with
+ TTL less than 254 (because it will take more than one hop) and to
+ listen on the multihop port (4784). When using multi-hop mode
+ `echo-mode` will not work (see :rfc:`5883` section 3).
+
+ `local-address` provides a local address that we should bind our
+ peer listener to and the address we should use to send the packets.
+ This option is mandatory for IPv6.
+
+ `interface` selects which interface we should use. This option
+ conflicts with `vrf`.
+
+ `vrf` selects which domain we want to use.
+
+.. index:: no peer <A.B.C.D|X:X::X:X>$peer [{multihop|local-address <A.B.C.D|X:X::X:X>$local|interface IFNAME$ifname|vrf NAME$vrfname}]
+.. clicmd:: no peer <A.B.C.D|X:X::X:X>$peer [{multihop|local-address <A.B.C.D|X:X::X:X>$local|interface IFNAME$ifname|vrf NAME$vrfname}]
+
+ Stops and removes the selected peer.
+
+.. index:: show bfd peers [json]
+.. clicmd:: show bfd peers [json]
+
+ Show all configured BFD peers information and current status.
+
+.. index:: show bfd 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|vrf NAME$vrfname}]> [json]
+.. clicmd:: show bfd 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|vrf NAME$vrfname}]> [json]
+
+ Show status for a specific BFD peer.
+
+
+.. _bfd-peer-config:
+
+Peer Configurations
+-------------------
+
+.. index:: detect-multiplier (2-255)
+.. clicmd:: detect-multiplier (2-255)
+
+ Configures the detection multiplier to determine packet loss. The
+ remote transmission interval will be multiplied by this value to
+ determine the connection loss detection timer. The default value is
+ 3.
+
+ Example: when the local system has `detect-multiplier 3` and the
+ remote system has `transmission interval 300`, the local system will
+ detect failures only after 900 milliseconds without receiving
+ packets.
+
+.. index:: receive-interval (10-60000)
+.. clicmd:: receive-interval (10-60000)
+
+ Configures the minimum interval that this system is capable of
+ receiving control packets. The default value is 300 milliseconds.
+
+.. index:: transmit-interval (10-60000)
+.. clicmd:: transmit-interval (10-60000)
+
+ The minimum transmission interval (less jitter) that this system
+ wants to use to send BFD control packets.
+
+.. index:: echo-interval (10-60000)
+.. clicmd:: echo-interval (10-60000)
+
+ Configures the minimal echo receive transmission interval that this
+ system is capable of handling.
+
+.. index:: [no] echo-mode
+.. clicmd:: [no] echo-mode
+
+ Enables or disables the echo transmission mode. This mode is disabled
+ by default.
+
+ It is recommended that the transmission interval of control packets
+ to be increased after enabling echo-mode to reduce bandwidth usage.
+ For example: `transmission-interval 2000`.
+
+ Echo mode is not supported on multi-hop setups (see :rfc:`5883`
+ section 3).
+
+.. index:: [no] shutdown
+.. clicmd:: [no] shutdown
+
+ Enables or disables the peer. When the peer is disabled an
+ 'administrative down' message is sent to the remote peer.
+
+.. index:: label WORD
+.. clicmd:: label WORD
+
+ Labels a peer with the provided word. This word can be referenced
+ later on other daemons to refer to a specific peer.
+
+
+.. _bfd-bgp-peer-config:
+
+BGP BFD Configuration
+---------------------
+
+.. index:: neighbor <A.B.C.D|X:X::X:X|WORD> bfd
+.. clicmd:: neighbor <A.B.C.D|X:X::X:X|WORD> bfd
+
+ Listen for BFD events registered on the same target as this BGP
+ neighbor. When BFD peer goes down it immediately asks BGP to shutdown
+ the connection with its neighbor and, when it goes back up, notify
+ BGP to try to connect to it.
+
+.. index:: no neighbor <A.B.C.D|X:X::X:X|WORD> bfd
+.. clicmd:: no neighbor <A.B.C.D|X:X::X:X|WORD> bfd
+
+ Removes any notification registration for this neighbor.
+
+
+.. _bfd-configuration:
+
+Configuration
+=============
+
+Before applying ``bfdd`` rules to integrated daemons (like BGPd), we must
+create the corresponding peers inside the ``bfd`` configuration node.
+
+Here is an example of BFD configuration:
+
+::
+
+ bfd
+ peer 192.168.0.1
+ label home-peer
+ no shutdown
+ !
+ !
+ router bgp 65530
+ neighbor 192.168.0.1 remote-as 65531
+ neighbor 192.168.0.1 bfd
+ neighbor 192.168.0.2 remote-as 65530
+ neighbor 192.168.0.2 bfd
+ neighbor 192.168.0.3 remote-as 65532
+ neighbor 192.168.0.3 bfd
+ !
+
+Peers can be identified by its address (use ``multihop`` when you need
+to specify a multi hop peer) or can be specified manually by a label.
+
+Here are the available peer configurations:
+
+::
+
+ bfd
+
+ ! configure a peer on an specific interface
+ peer 192.168.0.1 interface eth0
+ no shutdown
+ !
+
+ ! configure a multihop peer
+ peer 192.168.0.2 multihop local-address 192.168.0.3
+ shutdown
+ !
+
+ ! configure a peer in a different vrf
+ peer 192.168.0.3 vrf foo
+ shutdown
+ !
+
+ ! configure a peer with every option possible
+ peer 192.168.0.4
+ label peer-label
+ detect-multiplier 50
+ receive-interval 60000
+ transmit-interval 3000
+ shutdown
+ !
+
+ ! remove a peer
+ no peer 192.168.0.3 vrf foo
+
+
+.. _bfd-status:
+
+Status
+======
+
+You can inspect the current BFD peer status with the following commands:
+
+::
+
+ frr# show bfd peers
+ BFD Peers:
+ peer 192.168.0.1
+ ID: 1
+ Remote ID: 1
+ Status: up
+ Uptime: 1 minute(s), 51 second(s)
+ Diagnostics: ok
+ Remote diagnostics: ok
+ Local timers:
+ Receive interval: 300ms
+ Transmission interval: 300ms
+ Echo transmission interval: disabled
+ Remote timers:
+ Receive interval: 300ms
+ Transmission interval: 300ms
+ Echo transmission interval: 50ms
+
+ peer 192.168.1.1
+ label: router3-peer
+ ID: 2
+ Remote ID: 2
+ Status: up
+ Uptime: 1 minute(s), 53 second(s)
+ Diagnostics: ok
+ Remote diagnostics: ok
+ Local timers:
+ Receive interval: 300ms
+ Transmission interval: 300ms
+ Echo transmission interval: disabled
+ Remote timers:
+ Receive interval: 300ms
+ Transmission interval: 300ms
+ Echo transmission interval: 50ms
+
+ frr# show bfd peer 192.168.1.1
+ BFD Peer:
+ peer 192.168.1.1
+ label: router3-peer
+ ID: 2
+ Remote ID: 2
+ Status: up
+ Uptime: 3 minute(s), 4 second(s)
+ Diagnostics: ok
+ Remote diagnostics: ok
+ Local timers:
+ Receive interval: 300ms
+ Transmission interval: 300ms
+ Echo transmission interval: disabled
+ Remote timers:
+ Receive interval: 300ms
+ Transmission interval: 300ms
+ Echo transmission interval: 50ms
diff --git a/doc/user/index.rst b/doc/user/index.rst
index 14688e05d8..5818551343 100644
--- a/doc/user/index.rst
+++ b/doc/user/index.rst
@@ -39,6 +39,7 @@ Protocols
:maxdepth: 2
zebra
+ bfd
bgp
babeld
ldpd
diff --git a/doc/user/installation.rst b/doc/user/installation.rst
index 158e2c8595..3da5a6cd30 100644
--- a/doc/user/installation.rst
+++ b/doc/user/installation.rst
@@ -103,6 +103,10 @@ options from the list below.
Do not build bgpd.
+.. option:: --disable-bfdd
+
+ Do not build bfdd.
+
.. option:: --disable-bgp-announce
Make *bgpd* which does not make bgp announcements at all. This
diff --git a/doc/user/overview.rst b/doc/user/overview.rst
index 4886b57594..369f17dcba 100644
--- a/doc/user/overview.rst
+++ b/doc/user/overview.rst
@@ -245,6 +245,14 @@ FRR implements the following RFCs:
- :rfc:`7552`
:t:`Updates to LDP for IPv6, R. Asati, C. Pignataro, K. Raza, V. Manral,
and R. Papneja. June 2015.`
+- :rfc:`5880`
+ :t:`Bidirectional Forwarding Detection (BFD), D. Katz, D. Ward. June 2010`
+- :rfc:`5881`
+ :t:`Bidirectional Forwarding Detection (BFD) for IPv4 and IPv6 (Single Hop),
+ D. Katz, D. Ward. June 2010`
+- :rfc:`5883`
+ :t:`Bidirectional Forwarding Detection (BFD) for Multihop Paths, D. Katz,
+ D. Ward. June 2010`
**When SNMP support is enabled, the following RFCs are also supported:**
diff --git a/doc/user/setup.rst b/doc/user/setup.rst
index e9fd44a347..68ce14982b 100644
--- a/doc/user/setup.rst
+++ b/doc/user/setup.rst
@@ -31,6 +31,7 @@ systemd. The file initially looks like this:
sharpd=no
staticd=no
pbrd=no
+ bfdd=no
To enable a particular daemon, simply change the corresponding 'no' to 'yes'.
Subsequent service restarts should start the daemon.
@@ -66,6 +67,7 @@ This file has several parts. Here is an example:
sharpd_options=" --daemon -A 127.0.0.1"
staticd_options=" --daemon -A 127.0.0.1"
pbrd_options=" --daemon -A 127.0.0.1"
+ bfdd_options=" --daemon -A 127.0.0.1"
# The list of daemons to watch is automatically generated by the init script.
watchfrr_enable=yes
@@ -136,6 +138,7 @@ add the following entries to :file:`/etc/services`.
pimd 2611/tcp # PIMd vty
ldpd 2612/tcp # LDPd vty
eigprd 2613/tcp # EIGRPd vty
+ bfdd 2617/tcp # bfdd vty
If you use a FreeBSD newer than 2.2.8, the above entries are already added to
diff --git a/lib/command.c b/lib/command.c
index a98654dd23..7eda239ee4 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -143,6 +143,8 @@ const char *node_names[] = {
*/
"bgp ipv6 flowspec", /* BGP_FLOWSPECV6_NODE
*/
+ "bfd", /* BFD_NODE */
+ "bfd peer", /* BFD_PEER_NODE */
};
/* clang-format on */
@@ -987,6 +989,9 @@ enum node_type node_parent(enum node_type node)
case LDP_PSEUDOWIRE_NODE:
ret = LDP_L2VPN_NODE;
break;
+ case BFD_PEER_NODE:
+ ret = BFD_NODE;
+ break;
default:
ret = CONFIG_NODE;
break;
@@ -1433,6 +1438,7 @@ void cmd_exit(struct vty *vty)
case RMAP_NODE:
case PBRMAP_NODE:
case VTY_NODE:
+ case BFD_NODE:
vty->node = CONFIG_NODE;
break;
case BGP_IPV4_NODE:
@@ -1474,6 +1480,9 @@ void cmd_exit(struct vty *vty)
case LINK_PARAMS_NODE:
vty->node = INTERFACE_NODE;
break;
+ case BFD_PEER_NODE:
+ vty->node = BFD_NODE;
+ break;
default:
break;
}
@@ -1544,6 +1553,8 @@ DEFUN (config_end,
case KEYCHAIN_KEY_NODE:
case VTY_NODE:
case LINK_PARAMS_NODE:
+ case BFD_NODE:
+ case BFD_PEER_NODE:
vty_config_unlock(vty);
vty->node = ENABLE_NODE;
break;
diff --git a/lib/command.h b/lib/command.h
index da9b92ec6d..75b69507ec 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -139,6 +139,8 @@ enum node_type {
connections.*/
BGP_FLOWSPECV4_NODE, /* BGP IPv4 FLOWSPEC Address-Family */
BGP_FLOWSPECV6_NODE, /* BGP IPv6 FLOWSPEC Address-Family */
+ BFD_NODE, /* BFD protocol mode. */
+ BFD_PEER_NODE, /* BFD peer configuration mode. */
NODE_TYPE_MAX, /* maximum */
};
diff --git a/lib/json.h b/lib/json.h
index 675d852af7..788d1d6efa 100644
--- a/lib/json.h
+++ b/lib/json.h
@@ -23,6 +23,20 @@
#if defined(HAVE_JSON_C_JSON_H)
#include <json-c/json.h>
+
+/*
+ * FRR style JSON iteration.
+ * Usage: JSON_FOREACH(...) { ... }
+ */
+#define JSON_FOREACH(jo, joi, join) \
+ /* struct json_object *jo; */ \
+ /* struct json_object_iterator joi; */ \
+ /* struct json_object_iterator join; */ \
+ for ((joi) = json_object_iter_begin((jo)), \
+ (join) = json_object_iter_end((jo)); \
+ json_object_iter_equal(&(joi), &(join)) == 0; \
+ json_object_iter_next(&(joi)))
+
#else
#include <json/json.h>
diff --git a/lib/memory.c b/lib/memory.c
index e279b17d12..87cba69fc5 100644
--- a/lib/memory.c
+++ b/lib/memory.c
@@ -17,6 +17,12 @@
#include <zebra.h>
#include <stdlib.h>
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#ifdef HAVE_MALLOC_MALLOC_H
+#include <malloc/malloc.h>
+#endif
#include "memory.h"
#include "log.h"
@@ -28,7 +34,7 @@ DEFINE_MGROUP(LIB, "libfrr")
DEFINE_MTYPE(LIB, TMP, "Temporary memory")
DEFINE_MTYPE(LIB, PREFIX_FLOWSPEC, "Prefix Flowspec")
-static inline void mt_count_alloc(struct memtype *mt, size_t size)
+static inline void mt_count_alloc(struct memtype *mt, size_t size, void *ptr)
{
size_t oldsize;
@@ -41,12 +47,24 @@ static inline void mt_count_alloc(struct memtype *mt, size_t size)
if (oldsize != 0 && oldsize != size && oldsize != SIZE_VAR)
atomic_store_explicit(&mt->size, SIZE_VAR,
memory_order_relaxed);
+
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ size_t mallocsz = malloc_usable_size(ptr);
+
+ atomic_fetch_add_explicit(&mt->total, mallocsz, memory_order_relaxed);
+#endif
}
-static inline void mt_count_free(struct memtype *mt)
+static inline void mt_count_free(struct memtype *mt, void *ptr)
{
assert(mt->n_alloc);
atomic_fetch_sub_explicit(&mt->n_alloc, 1, memory_order_relaxed);
+
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ size_t mallocsz = malloc_usable_size(ptr);
+
+ atomic_fetch_sub_explicit(&mt->total, mallocsz, memory_order_relaxed);
+#endif
}
static inline void *mt_checkalloc(struct memtype *mt, void *ptr, size_t size)
@@ -58,7 +76,7 @@ static inline void *mt_checkalloc(struct memtype *mt, void *ptr, size_t size)
}
return NULL;
}
- mt_count_alloc(mt, size);
+ mt_count_alloc(mt, size, ptr);
return ptr;
}
@@ -75,7 +93,7 @@ void *qcalloc(struct memtype *mt, size_t size)
void *qrealloc(struct memtype *mt, void *ptr, size_t size)
{
if (ptr)
- mt_count_free(mt);
+ mt_count_free(mt, ptr);
return mt_checkalloc(mt, ptr ? realloc(ptr, size) : malloc(size), size);
}
@@ -87,7 +105,7 @@ void *qstrdup(struct memtype *mt, const char *str)
void qfree(struct memtype *mt, void *ptr)
{
if (ptr)
- mt_count_free(mt);
+ mt_count_free(mt, ptr);
free(ptr);
}
diff --git a/lib/memory.h b/lib/memory.h
index 1fbbbe4231..c39f34e3a7 100644
--- a/lib/memory.h
+++ b/lib/memory.h
@@ -24,12 +24,20 @@
#define array_size(ar) (sizeof(ar) / sizeof(ar[0]))
+#if defined(HAVE_MALLOC_SIZE) && !defined(HAVE_MALLOC_USABLE_SIZE)
+#define malloc_usable_size(x) malloc_size(x)
+#define HAVE_MALLOC_USABLE_SIZE
+#endif
+
#define SIZE_VAR ~0UL
struct memtype {
struct memtype *next, **ref;
const char *name;
_Atomic size_t n_alloc;
_Atomic size_t size;
+#ifdef HAVE_MALLOC_USABLE_SIZE
+ _Atomic size_t total;
+#endif
};
struct memgroup {
diff --git a/lib/memory_vty.c b/lib/memory_vty.c
index 972914bf24..9ee2e52ec7 100644
--- a/lib/memory_vty.c
+++ b/lib/memory_vty.c
@@ -21,9 +21,12 @@
#include <zebra.h>
/* malloc.h is generally obsolete, however GNU Libc mallinfo wants it. */
-#if (defined(GNU_LINUX) && defined(HAVE_MALLINFO))
+#ifdef HAVE_MALLOC_H
#include <malloc.h>
-#endif /* HAVE_MALLINFO */
+#endif
+#ifdef HAVE_MALLOC_MALLOC_H
+#include <malloc/malloc.h>
+#endif
#include <dlfcn.h>
#include <link.h>
@@ -76,12 +79,21 @@ static int qmem_walker(void *arg, struct memgroup *mg, struct memtype *mt)
if (mt->n_alloc != 0) {
char size[32];
snprintf(size, sizeof(size), "%6zu", mt->size);
- vty_out(vty, "%-30s: %10zu %s\n", mt->name,
+
+#ifdef HAVE_MALLOC_USABLE_SIZE
+#define TSTR " %9zu"
+#define TARG , mt->total
+#else
+#define TSTR ""
+#define TARG
+#endif
+ vty_out(vty, "%-30s: %10zu %-16s"TSTR"\n", mt->name,
mt->n_alloc,
mt->size == 0 ? ""
: mt->size == SIZE_VAR
? "(variably sized)"
- : size);
+ : size
+ TARG);
}
}
return 0;
diff --git a/lib/route_types.txt b/lib/route_types.txt
index cfa55e468c..72f59a1b78 100644
--- a/lib/route_types.txt
+++ b/lib/route_types.txt
@@ -81,6 +81,7 @@ ZEBRA_ROUTE_BGP_DIRECT_EXT, bgp-direct-to-nve-groups, NULL, 'e', 0, 0, 0, "BGP2V
ZEBRA_ROUTE_BABEL, babel, babeld, 'A', 1, 1, 1, "Babel"
ZEBRA_ROUTE_SHARP, sharp, sharpd, 'D', 1, 1, 1, "SHARP"
ZEBRA_ROUTE_PBR, pbr, pbrd, 'F', 1, 1, 0, "PBR"
+ZEBRA_ROUTE_BFD, bfd, bfdd, '-', 0, 0, 0, "BFD"
ZEBRA_ROUTE_ALL, wildcard, none, '-', 0, 0, 0, "-"
@@ -107,3 +108,4 @@ ZEBRA_ROUTE_VNC_DIRECT, "VNC direct (not via zebra) routes"
ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)"
ZEBRA_ROUTE_SHARP, "Super Happy Advanced Routing Protocol (sharpd)"
ZEBRA_ROUTE_PBR, "Policy Based Routing (PBR)"
+ZEBRA_ROUTE_BFD, "Bidirectional Fowarding Detection (BFD)"
diff --git a/lib/vty.c b/lib/vty.c
index 87cae8773a..15e934b707 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -814,6 +814,8 @@ static void vty_end_config(struct vty *vty)
case KEYCHAIN_KEY_NODE:
case VTY_NODE:
case BGP_EVPN_VNI_NODE:
+ case BFD_NODE:
+ case BFD_PEER_NODE:
vty_config_unlock(vty);
vty->node = ENABLE_NODE;
break;
@@ -1210,6 +1212,8 @@ static void vty_stop_input(struct vty *vty)
case KEYCHAIN_NODE:
case KEYCHAIN_KEY_NODE:
case VTY_NODE:
+ case BFD_NODE:
+ case BFD_PEER_NODE:
vty_config_unlock(vty);
vty->node = ENABLE_NODE;
break;
diff --git a/lib/zclient.h b/lib/zclient.h
index 49419b3df3..962b1707c9 100644
--- a/lib/zclient.h
+++ b/lib/zclient.h
@@ -108,6 +108,7 @@ typedef enum {
ZEBRA_VRF_LABEL,
ZEBRA_INTERFACE_VRF_UPDATE,
ZEBRA_BFD_CLIENT_REGISTER,
+ ZEBRA_BFD_CLIENT_DEREGISTER,
ZEBRA_INTERFACE_ENABLE_RADV,
ZEBRA_INTERFACE_DISABLE_RADV,
ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB,
diff --git a/ospf6d/ospf6_bfd.c b/ospf6d/ospf6_bfd.c
index 7955121365..e7284a6659 100644
--- a/ospf6d/ospf6_bfd.c
+++ b/ospf6d/ospf6_bfd.c
@@ -276,11 +276,14 @@ void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi,
*/
void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi)
{
+#if HAVE_BFDD == 0
struct bfd_info *bfd_info;
+#endif /* ! HAVE_BFDD */
if (!oi->bfd_info)
return;
+#if HAVE_BFDD == 0
bfd_info = (struct bfd_info *)oi->bfd_info;
if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG))
@@ -288,6 +291,7 @@ void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi)
bfd_info->detect_mult, bfd_info->required_min_rx,
bfd_info->desired_min_tx);
else
+#endif /* ! HAVE_BFDD */
vty_out(vty, " ipv6 ospf6 bfd\n");
}
@@ -329,7 +333,12 @@ DEFUN (ipv6_ospf6_bfd,
return CMD_SUCCESS;
}
-DEFUN (ipv6_ospf6_bfd_param,
+#if HAVE_BFDD > 0
+DEFUN_HIDDEN(
+#else
+DEFUN(
+#endif /* HAVE_BFDD */
+ ipv6_ospf6_bfd_param,
ipv6_ospf6_bfd_param_cmd,
"ipv6 ospf6 bfd (2-255) (50-60000) (50-60000)",
IP6_STR
diff --git a/ospfd/ospf_bfd.c b/ospfd/ospf_bfd.c
index 0f7fb50778..df41897660 100644
--- a/ospfd/ospf_bfd.c
+++ b/ospfd/ospf_bfd.c
@@ -290,17 +290,21 @@ void ospf_bfd_info_nbr_create(struct ospf_interface *oi,
void ospf_bfd_write_config(struct vty *vty, struct ospf_if_params *params)
{
+#if HAVE_BFDD == 0
struct bfd_info *bfd_info;
+#endif /* ! HAVE_BFDD */
if (!params->bfd_info)
return;
+#if HAVE_BFDD == 0
bfd_info = (struct bfd_info *)params->bfd_info;
if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG))
vty_out(vty, " ip ospf bfd %d %d %d\n", bfd_info->detect_mult,
bfd_info->required_min_rx, bfd_info->desired_min_tx);
else
+#endif /* ! HAVE_BFDD */
vty_out(vty, " ip ospf bfd\n");
}
@@ -373,7 +377,12 @@ DEFUN (ip_ospf_bfd,
return CMD_SUCCESS;
}
-DEFUN (ip_ospf_bfd_param,
+#if HAVE_BFDD > 0
+DEFUN_HIDDEN(
+#else
+DEFUN(
+#endif /* HAVE_BFDD */
+ ip_ospf_bfd_param,
ip_ospf_bfd_param_cmd,
"ip ospf bfd (2-255) (50-60000) (50-60000)",
"IP Information\n"
@@ -407,14 +416,21 @@ DEFUN (ip_ospf_bfd_param,
DEFUN (no_ip_ospf_bfd,
no_ip_ospf_bfd_cmd,
+#if HAVE_BFDD > 0
+ "no ip ospf bfd",
+#else
"no ip ospf bfd [(2-255) (50-60000) (50-60000)]",
+#endif /* HAVE_BFDD */
NO_STR
"IP Information\n"
"OSPF interface commands\n"
"Disables BFD support\n"
+#if HAVE_BFDD == 0
"Detect Multiplier\n"
"Required min receive interval\n"
- "Desired min transmit interval\n")
+ "Desired min transmit interval\n"
+#endif /* !HAVE_BFDD */
+)
{
VTY_DECLVAR_CONTEXT(interface, ifp);
struct ospf_if_params *params;
diff --git a/pimd/pim_bfd.c b/pimd/pim_bfd.c
index 4a3cf715da..f7a217b514 100644
--- a/pimd/pim_bfd.c
+++ b/pimd/pim_bfd.c
@@ -51,10 +51,12 @@ void pim_bfd_write_config(struct vty *vty, struct interface *ifp)
if (!bfd_info)
return;
+#if HAVE_BFDD == 0
if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG))
vty_out(vty, " ip pim bfd %d %d %d\n", bfd_info->detect_mult,
bfd_info->required_min_rx, bfd_info->desired_min_tx);
else
+#endif /* ! HAVE_BFDD */
vty_out(vty, " ip pim bfd\n");
}
diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c
index 15717aa7a4..6eb4303fb7 100644
--- a/pimd/pim_cmd.c
+++ b/pimd/pim_cmd.c
@@ -7542,7 +7542,12 @@ DEFUN (no_ip_pim_bfd,
return CMD_SUCCESS;
}
-DEFUN (ip_pim_bfd_param,
+#if HAVE_BFDD > 0
+DEFUN_HIDDEN(
+#else
+DEFUN(
+#endif /* HAVE_BFDD */
+ ip_pim_bfd_param,
ip_pim_bfd_param_cmd,
"ip pim bfd (2-255) (50-60000) (50-60000)",
IP_STR
@@ -7580,12 +7585,14 @@ DEFUN (ip_pim_bfd_param,
return CMD_SUCCESS;
}
+#if HAVE_BFDD == 0
ALIAS(no_ip_pim_bfd, no_ip_pim_bfd_param_cmd,
"no ip pim bfd (2-255) (50-60000) (50-60000)", NO_STR IP_STR PIM_STR
"Enables BFD support\n"
"Detect Multiplier\n"
"Required min receive interval\n"
"Desired min transmit interval\n")
+#endif /* !HAVE_BFDD */
static int ip_msdp_peer_cmd_worker(struct pim_instance *pim, struct vty *vty,
const char *peer, const char *local)
@@ -8901,5 +8908,7 @@ void pim_cmd_init(void)
install_element(INTERFACE_NODE, &ip_pim_bfd_cmd);
install_element(INTERFACE_NODE, &ip_pim_bfd_param_cmd);
install_element(INTERFACE_NODE, &no_ip_pim_bfd_cmd);
+#if HAVE_BFDD == 0
install_element(INTERFACE_NODE, &no_ip_pim_bfd_param_cmd);
+#endif /* !HAVE_BFDD */
}
diff --git a/redhat/daemons b/redhat/daemons
index f9dbffea4d..de708cf4fd 100644
--- a/redhat/daemons
+++ b/redhat/daemons
@@ -52,6 +52,8 @@ babeld=no
sharpd=no
pbrd=no
staticd=no
+bfdd=no
+
#
# Command line options for the daemons
#
@@ -70,6 +72,7 @@ babeld_options=("-A 127.0.0.1")
sharpd_options=("-A 127.0.0.1")
pbrd_options=("-A 127.0.0.1")
staticd_options=("-A 127.0.0.1")
+bfdd_options=("-A 127.0.0.1")
#
# If the vtysh_enable is yes, then the unified config is read
diff --git a/redhat/frr.init b/redhat/frr.init
index 740aa5b64d..2e33aee173 100755
--- a/redhat/frr.init
+++ b/redhat/frr.init
@@ -7,7 +7,7 @@
#
# chkconfig: 2345 15 85
#
-# description: FRRouting (FRR) is a routing suite for IP routing protocols
+# description: FRRouting (FRR) is a routing suite for IP routing protocols
# like BGP, OSPF, RIP and others. This script contols the main
# daemon "frr" as well as the individual protocol daemons.
#
@@ -20,7 +20,7 @@
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start/Stop the FRR Routing daemons
-# Description: FRRouting (FRR) is a routing suite for IP routing protocols
+# Description: FRRouting (FRR) is a routing suite for IP routing protocols
# like BGP, OSPF, RIP and others. This script contols the main
# daemon "frr" as well as the individual protocol daemons.
### END INIT INFO
@@ -33,7 +33,7 @@ V_PATH=/var/run/frr
# Local Daemon selection may be done by using /etc/frr/daemons.
# See /usr/share/doc/frr/README.Debian.gz for further information.
# Keep zebra first and do not list watchfrr!
-DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd pimd pbrd ldpd nhrpd eigrpd babeld staticd sharpd"
+DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd pimd pbrd ldpd nhrpd eigrpd babeld staticd sharpd bfdd"
MAX_INSTANCES=5
RELOAD_SCRIPT=/usr/lib/frr/frr-reload.py
diff --git a/redhat/frr.logrotate b/redhat/frr.logrotate
index 25a5587787..654d355fd7 100644
--- a/redhat/frr.logrotate
+++ b/redhat/frr.logrotate
@@ -86,3 +86,10 @@
endscript
}
+/var/log/frr/bfdd.log {
+ notifempty
+ missingok
+ postrotate
+ /bin/kill -USR1 `cat /var/run/frr/bfdd.pid 2> /dev/null` 2> /dev/null || true
+ endscript
+}
diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in
index f5b116978e..25b48506a6 100644
--- a/redhat/frr.spec.in
+++ b/redhat/frr.spec.in
@@ -11,6 +11,7 @@
#################### FRRouting (FRR) configure options #####################
# with-feature options
%{!?with_babeld: %global with_babeld 1 }
+%{!?with_bfdd: %global with_bfdd 1 }
%{!?with_bgp_vnc: %global with_bgp_vnc 0 }
%{!?with_cumulus: %global with_cumulus 0 }
%{!?with_eigrpd: %global with_eigrpd 1 }
@@ -85,7 +86,7 @@
%{!?frr_gid: %global frr_gid 92 }
%{!?vty_gid: %global vty_gid 85 }
-%define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd
+%define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd bfdd
%if %{with_ldpd}
%define daemon_ldpd ldpd
@@ -129,7 +130,13 @@
%define daemon_watchfrr ""
%endif
-%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd}
+%if %{with_bfdd}
+ %define daemon_bfdd bfdd
+%else
+ %define daemon_bfdd ""
+%endif
+
+%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd}
#release sub-revision (the two digits after the CONFDATE)
%{!?release_rev: %global release_rev 01 }
@@ -193,7 +200,7 @@ protocol. It takes multi-server and multi-thread approach to resolve
the current complexity of the Internet.
FRRouting supports BGP4, OSPFv2, OSPFv3, ISIS, RIP, RIPng, PIM, LDP
-NHRP, Babel, PBR and EIGRP.
+NHRP, Babel, PBR, EIGRP and BFD.
FRRouting is a fork of Quagga.
@@ -331,9 +338,14 @@ developing OSPF-API and frr applications.
--enable-systemd \
%endif
%if %{with_rpki}
- --enable-rpki
+ --enable-rpki \
+%else
+ --disable-rpki \
+%endif
+%if %{with_bfdd}
+ --enable-bfdd
%else
- --disable-rpki
+ --disable-bfdd
%endif
make %{?_smp_mflags} MAKEINFO="makeinfo --no-split" SPHINXBUILD=%{sphinx}
@@ -444,6 +456,9 @@ zebra_spec_add_service isisd 2608/tcp "ISISd vty"
%if %{with_eigrpd}
zebra_spec_add_service eigrpd 2613/tcp "EIGRPd vty"
%endif
+%if %{with_bfdd}
+ zebra_spec_add_service bfdd 2617/tcp "BFDd vty"
+%endif
%if "%{initsystem}" == "systemd"
for daemon in %all_daemons ; do
@@ -591,6 +606,9 @@ fi
%if %{with_babeld}
%{_sbindir}/babeld
%endif
+%if %{with_bfdd}
+ %{_sbindir}/bfdd
+%endif
%{_libdir}/lib*.so.0
%{_libdir}/lib*.so.0.*
%if %{with_fpm}
@@ -644,6 +662,9 @@ fi
%changelog
+* Sun May 28 2018 Rafael Zalamena <rzalamena@opensourcerouting.org> - %{version}
+- Add BFDd support
+
* Sun May 20 2018 Martin Winter <mwinter@opensourcerouting.org>
- Fixed RPKI RPM build
diff --git a/tools/checkpatch.pl b/tools/checkpatch.pl
index c1fab1029a..55b3e1e564 100755
--- a/tools/checkpatch.pl
+++ b/tools/checkpatch.pl
@@ -4181,7 +4181,9 @@ sub process {
} elsif ($op eq ',') {
my $rtrim_before = 0;
my $space_after = 0;
- if ($ctx =~ /Wx./) {
+ if ($line=~/\#\s*define/) {
+ # ignore , spacing in macros
+ } elsif ($ctx =~ /Wx./) {
if (ERROR("SPACING",
"space prohibited before that '$op' $at\n" . $hereptr)) {
$line_fixed = 1;
@@ -4847,6 +4849,7 @@ sub process {
my $ctx = '';
my $has_flow_statement = 0;
my $has_arg_concat = 0;
+ my $complex = 0;
($dstat, $dcond, $ln, $cnt, $off) =
ctx_statement_block($linenr, $realcnt, 0);
$ctx = $dstat;
@@ -4865,6 +4868,7 @@ sub process {
$define_args = substr($define_args, 1, length($define_args) - 2);
$define_args =~ s/\s*//g;
@def_args = split(",", $define_args);
+ $complex = 1;
}
$dstat =~ s/$;//g;
@@ -4932,7 +4936,7 @@ sub process {
} elsif ($dstat =~ /;/) {
ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE",
"Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx");
- } else {
+ } elsif ($complex) {
ERROR("COMPLEX_MACRO",
"Macros with complex values should be enclosed in parentheses\n" . "$herectx");
}
diff --git a/tools/etc/frr/daemons b/tools/etc/frr/daemons
index 9a96c0490a..474b299d90 100644
--- a/tools/etc/frr/daemons
+++ b/tools/etc/frr/daemons
@@ -35,3 +35,4 @@ eigrpd=no
babeld=no
sharpd=no
pbrd=no
+bfdd=no
diff --git a/tools/etc/frr/daemons.conf b/tools/etc/frr/daemons.conf
index 04a857f47d..640437f441 100644
--- a/tools/etc/frr/daemons.conf
+++ b/tools/etc/frr/daemons.conf
@@ -19,6 +19,7 @@ babeld_options=" --daemon -A 127.0.0.1"
sharpd_options=" --daemon -A 127.0.0.1"
pbrd_options=" --daemon -A 127.0.0.1"
staticd_options=" --daemon -A 127.0.0.1"
+bfdd_options=" --daemon -A 127.0.0.1"
# The list of daemons to watch is automatically generated by the init script.
watchfrr_enable=yes
diff --git a/tools/frr b/tools/frr
index 91c9091448..0b170d33fd 100755
--- a/tools/frr
+++ b/tools/frr
@@ -21,7 +21,7 @@ V_PATH=/var/run/frr
# Local Daemon selection may be done by using /etc/frr/daemons.
# See /usr/share/doc/frr/README.Debian.gz for further information.
# Keep zebra first and do not list watchfrr!
-DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd"
+DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd"
MAX_INSTANCES=5
RELOAD_SCRIPT=/usr/lib/frr/frr-reload.py
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index 9f81b42e32..936640c83a 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -150,6 +150,10 @@ if STATICD
vtysh_scan += $(top_srcdir)/staticd/static_vty.c
endif
+if BFDD
+vtysh_scan += $(top_srcdir)/bfdd/bfdd_vty.c
+endif
+
vtysh_cmd_FILES = $(vtysh_scan) \
$(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \
$(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 229337d82f..48a90a695c 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -134,6 +134,7 @@ struct vtysh_client vtysh_client[] = {
{.fd = -1, .name = "watchfrr", .flag = VTYSH_WATCHFRR, .next = NULL},
{.fd = -1, .name = "pbrd", .flag = VTYSH_PBRD, .next = NULL},
{.fd = -1, .name = "staticd", .flag = VTYSH_STATICD, .next = NULL},
+ {.fd = -1, .name = "bfdd", .flag = VTYSH_BFDD, .next = NULL},
};
enum vtysh_write_integrated vtysh_write_integrated =
@@ -1254,6 +1255,18 @@ struct cmd_node link_params_node = {
static struct cmd_node rpki_node = {RPKI_NODE, "%s(config-rpki)# ", 1};
#endif
+#if HAVE_BFDD > 0
+static struct cmd_node bfd_node = {
+ BFD_NODE,
+ "%s(config-bfd)# ",
+};
+
+static struct cmd_node bfd_peer_node = {
+ BFD_PEER_NODE,
+ "%s(config-bfd-peer)# ",
+};
+#endif /* HAVE_BFDD */
+
/* Defined in lib/vty.c */
extern struct cmd_node vty_node;
@@ -1680,6 +1693,32 @@ DEFUNSH(VTYSH_PBRD, vtysh_pbr_map, vtysh_pbr_map_cmd,
return CMD_SUCCESS;
}
+#if HAVE_BFDD > 0
+DEFUNSH(VTYSH_BFDD, bfd_enter, bfd_enter_cmd, "bfd", "Configure BFD peers\n")
+{
+ vty->node = BFD_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUNSH(VTYSH_BFDD, bfd_peer_enter, bfd_peer_enter_cmd,
+ "peer <A.B.C.D|X:X::X:X> [{multihop|local-address <A.B.C.D|X:X::X:X>|interface IFNAME|vrf NAME}]",
+ "Configure peer\n"
+ "IPv4 peer address\n"
+ "IPv6 peer address\n"
+ "Configure multihop\n"
+ "Configure local address\n"
+ "IPv4 local address\n"
+ "IPv6 local address\n"
+ INTERFACE_STR
+ "Configure interface name to use\n"
+ "Configure VRF\n"
+ "Configure VRF name\n")
+{
+ vty->node = BFD_PEER_NODE;
+ return CMD_SUCCESS;
+}
+#endif /* HAVE_BFDD */
+
DEFSH(VTYSH_PBRD, vtysh_no_pbr_map_cmd, "no pbr-map WORD [seq (1-700)]",
NO_STR
"Delete pbr-map\n"
@@ -1749,6 +1788,7 @@ static int vtysh_exit(struct vty *vty)
case PBRMAP_NODE:
case VTY_NODE:
case KEYCHAIN_NODE:
+ case BFD_NODE:
vtysh_execute("end");
vtysh_execute("configure terminal");
vty->node = CONFIG_NODE;
@@ -1792,6 +1832,9 @@ static int vtysh_exit(struct vty *vty)
case LINK_PARAMS_NODE:
vty->node = INTERFACE_NODE;
break;
+ case BFD_PEER_NODE:
+ vty->node = BFD_NODE;
+ break;
default:
break;
}
@@ -1988,6 +2031,17 @@ DEFUNSH(VTYSH_ISISD, vtysh_quit_isisd, vtysh_quit_isisd_cmd, "quit",
return vtysh_exit_isisd(self, vty, argc, argv);
}
+#if HAVE_BFDD > 0
+DEFUNSH(VTYSH_BFDD, vtysh_exit_bfdd, vtysh_exit_bfdd_cmd, "exit",
+ "Exit current mode and down to previous mode\n")
+{
+ return vtysh_exit(vty);
+}
+
+ALIAS(vtysh_exit_bfdd, vtysh_quit_bfdd_cmd, "quit",
+ "Exit current mode and down to previous mode\n")
+#endif
+
DEFUNSH(VTYSH_ALL, vtysh_exit_line_vty, vtysh_exit_line_vty_cmd, "exit",
"Exit current mode and down to previous mode\n")
{
@@ -3422,6 +3476,10 @@ void vtysh_init_vty(void)
#if defined(HAVE_RPKI)
install_node(&rpki_node, NULL);
#endif
+#if HAVE_BFDD > 0
+ install_node(&bfd_node, NULL);
+ install_node(&bfd_peer_node, NULL);
+#endif /* HAVE_BFDD */
struct cmd_node *node;
for (unsigned int i = 0; i < vector_active(cmdvec); i++) {
@@ -3516,6 +3574,21 @@ void vtysh_init_vty(void)
install_element(RMAP_NODE, &vtysh_quit_rmap_cmd);
install_element(PBRMAP_NODE, &vtysh_exit_pbr_map_cmd);
install_element(PBRMAP_NODE, &vtysh_quit_pbr_map_cmd);
+#if HAVE_BFDD > 0
+ /* Enter node. */
+ install_element(CONFIG_NODE, &bfd_enter_cmd);
+ install_element(BFD_NODE, &bfd_peer_enter_cmd);
+
+ /* Exit/quit node. */
+ install_element(BFD_NODE, &vtysh_exit_bfdd_cmd);
+ install_element(BFD_NODE, &vtysh_quit_bfdd_cmd);
+ install_element(BFD_PEER_NODE, &vtysh_exit_bfdd_cmd);
+ install_element(BFD_PEER_NODE, &vtysh_quit_bfdd_cmd);
+
+ /* End/exit all. */
+ install_element(BFD_NODE, &vtysh_end_all_cmd);
+ install_element(BFD_PEER_NODE, &vtysh_end_all_cmd);
+#endif /* HAVE_BFDD */
install_element(VTY_NODE, &vtysh_exit_line_vty_cmd);
install_element(VTY_NODE, &vtysh_quit_line_vty_cmd);
diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index e6ed5659cf..c8e4c025e0 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -24,22 +24,23 @@
#include "memory.h"
DECLARE_MGROUP(MVTYSH)
-#define VTYSH_ZEBRA 0x0001
-#define VTYSH_RIPD 0x0002
-#define VTYSH_RIPNGD 0x0004
-#define VTYSH_OSPFD 0x0008
-#define VTYSH_OSPF6D 0x0010
-#define VTYSH_BGPD 0x0020
-#define VTYSH_ISISD 0x0040
-#define VTYSH_PIMD 0x0080
-#define VTYSH_LDPD 0x0100
-#define VTYSH_WATCHFRR 0x0200
-#define VTYSH_NHRPD 0x0400
-#define VTYSH_EIGRPD 0x0800
-#define VTYSH_BABELD 0x1000
-#define VTYSH_SHARPD 0x2000
-#define VTYSH_PBRD 0x4000
-#define VTYSH_STATICD 0x8000
+#define VTYSH_ZEBRA 0x00001
+#define VTYSH_RIPD 0x00002
+#define VTYSH_RIPNGD 0x00004
+#define VTYSH_OSPFD 0x00008
+#define VTYSH_OSPF6D 0x00010
+#define VTYSH_BGPD 0x00020
+#define VTYSH_ISISD 0x00040
+#define VTYSH_PIMD 0x00080
+#define VTYSH_LDPD 0x00100
+#define VTYSH_WATCHFRR 0x00200
+#define VTYSH_NHRPD 0x00400
+#define VTYSH_EIGRPD 0x00800
+#define VTYSH_BABELD 0x01000
+#define VTYSH_SHARPD 0x02000
+#define VTYSH_PBRD 0x04000
+#define VTYSH_STATICD 0x08000
+#define VTYSH_BFDD 0x10000
#define VTYSH_WAS_ACTIVE (-2)
@@ -48,7 +49,7 @@ DECLARE_MGROUP(MVTYSH)
/* watchfrr is not in ALL since library CLI functions should not be
* run on it (logging & co. should stay in a fixed/frozen config, and
* things like prefix lists are not even initialised) */
-#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD
+#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD|VTYSH_BFDD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_EIGRPD|VTYSH_SHARPD
#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_PBRD
#define VTYSH_NS VTYSH_ZEBRA
diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c
index 52ab28dfda..42f08342c0 100644
--- a/vtysh/vtysh_config.c
+++ b/vtysh/vtysh_config.c
@@ -318,6 +318,8 @@ void vtysh_config_parse_line(void *arg, const char *line)
config = config_get(PROTOCOL_NODE, line);
else if (strncmp(line, "mpls", strlen("mpls")) == 0)
config = config_get(MPLS_NODE, line);
+ else if (strncmp(line, "bfd", strlen("bfd")) == 0)
+ config = config_get(BFD_NODE, line);
else {
if (strncmp(line, "log", strlen("log")) == 0
|| strncmp(line, "hostname", strlen("hostname"))
diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c
index ad574d7e8b..22c0dd623c 100644
--- a/zebra/zapi_msg.c
+++ b/zebra/zapi_msg.c
@@ -3015,6 +3015,9 @@ void (*zserv_handlers[])(ZAPI_HANDLER_ARGS) = {
[ZEBRA_BFD_DEST_UPDATE] = zebra_ptm_bfd_dst_register,
[ZEBRA_BFD_DEST_REGISTER] = zebra_ptm_bfd_dst_register,
[ZEBRA_BFD_DEST_DEREGISTER] = zebra_ptm_bfd_dst_deregister,
+#if HAVE_BFDD > 0
+ [ZEBRA_BFD_DEST_REPLAY] = zebra_ptm_bfd_dst_replay,
+#endif /* HAVE_BFDD */
[ZEBRA_VRF_UNREGISTER] = zread_vrf_unregister,
[ZEBRA_VRF_LABEL] = zread_vrf_label,
[ZEBRA_BFD_CLIENT_REGISTER] = zebra_ptm_bfd_client_register,
diff --git a/zebra/zebra_ptm.c b/zebra/zebra_ptm.c
index 5975c4058b..cc2d5d411d 100644
--- a/zebra/zebra_ptm.c
+++ b/zebra/zebra_ptm.c
@@ -39,6 +39,15 @@
#include "zebra_vrf.h"
#include "version.h"
+/*
+ * Choose the BFD implementation that we'll use.
+ *
+ * There are two implementations:
+ * - PTM BFD: which uses an external daemon;
+ * - bfdd: FRR's own BFD daemon;
+ */
+#if HAVE_BFDD == 0
+
#define ZEBRA_PTM_RECONNECT_TIME_INITIAL 1 /* initial reconnect is 1s */
#define ZEBRA_PTM_RECONNECT_TIME_MAX 300
@@ -1141,3 +1150,432 @@ void zebra_ptm_if_write(struct vty *vty, struct zebra_if *zebra_ifp)
if (zebra_ifp->ptm_enable == ZEBRA_IF_PTM_ENABLE_OFF)
vty_out(vty, " no ptm-enable\n");
}
+
+#else /* HAVE_BFDD */
+
+#include "zebra/zebra_memory.h"
+
+/*
+ * Data structures.
+ */
+struct ptm_process {
+ struct zserv *pp_zs;
+ pid_t pp_pid;
+
+ TAILQ_ENTRY(ptm_process) pp_entry;
+};
+TAILQ_HEAD(ppqueue, ptm_process) ppqueue;
+
+DEFINE_MTYPE_STATIC(ZEBRA, ZEBRA_PTM_BFD_PROCESS,
+ "PTM BFD process registration table.");
+
+/*
+ * Prototypes.
+ */
+static struct ptm_process *pp_new(pid_t pid, struct zserv *zs);
+static struct ptm_process *pp_lookup_byzs(struct zserv *zs);
+static void pp_free(struct ptm_process *pp);
+static void pp_free_all(void);
+
+static void zebra_ptm_send_bfdd(struct stream *msg);
+static void zebra_ptm_send_clients(struct stream *msg);
+static int _zebra_ptm_bfd_client_deregister(struct zserv *zs);
+static void _zebra_ptm_reroute(struct zserv *zs, struct stream *msg,
+ uint32_t command);
+
+
+/*
+ * Process PID registration.
+ */
+static struct ptm_process *pp_new(pid_t pid, struct zserv *zs)
+{
+ struct ptm_process *pp;
+
+#ifdef PTM_DEBUG
+ /* Sanity check: more than one client can't have the same PID. */
+ TAILQ_FOREACH(pp, &ppqueue, pp_entry) {
+ if (pp->pp_pid == pid && pp->pp_zs != zs)
+ zlog_err("%s:%d pid and client pointer doesn't match",
+ __FILE__, __LINE__);
+ }
+#endif /* PTM_DEBUG */
+
+ /* Lookup for duplicates. */
+ pp = pp_lookup_byzs(zs);
+ if (pp != NULL)
+ return pp;
+
+ /* Allocate and register new process. */
+ pp = XCALLOC(MTYPE_ZEBRA_PTM_BFD_PROCESS, sizeof(*pp));
+ if (pp == NULL)
+ return NULL;
+
+ pp->pp_pid = pid;
+ pp->pp_zs = zs;
+ TAILQ_INSERT_HEAD(&ppqueue, pp, pp_entry);
+
+ return pp;
+}
+
+static struct ptm_process *pp_lookup_byzs(struct zserv *zs)
+{
+ struct ptm_process *pp;
+
+ TAILQ_FOREACH(pp, &ppqueue, pp_entry) {
+ if (pp->pp_zs != zs)
+ continue;
+
+ break;
+ }
+
+ return pp;
+}
+
+static void pp_free(struct ptm_process *pp)
+{
+ if (pp == NULL)
+ return;
+
+ TAILQ_REMOVE(&ppqueue, pp, pp_entry);
+ XFREE(MTYPE_ZEBRA_PTM_BFD_PROCESS, pp);
+}
+
+static void pp_free_all(void)
+{
+ struct ptm_process *pp;
+
+ while (!TAILQ_EMPTY(&ppqueue)) {
+ pp = TAILQ_FIRST(&ppqueue);
+ pp_free(pp);
+ }
+}
+
+
+/*
+ * Use the FRR's internal daemon implementation.
+ */
+static void zebra_ptm_send_bfdd(struct stream *msg)
+{
+ struct listnode *node;
+ struct zserv *client;
+ struct stream *msgc;
+
+ /* Create copy for replication. */
+ msgc = stream_dup(msg);
+ if (msgc == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+
+ /* Send message to all running BFDd daemons. */
+ for (ALL_LIST_ELEMENTS_RO(zebrad.client_list, node, client)) {
+ if (client->proto != ZEBRA_ROUTE_BFD)
+ continue;
+
+ zserv_send_message(client, msg);
+
+ /* Allocate more messages. */
+ msg = stream_dup(msgc);
+ if (msg == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+ }
+
+ stream_free(msgc);
+}
+
+static void zebra_ptm_send_clients(struct stream *msg)
+{
+ struct listnode *node;
+ struct zserv *client;
+ struct stream *msgc;
+
+ /* Create copy for replication. */
+ msgc = stream_dup(msg);
+ if (msgc == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+
+ /* Send message to all running client daemons. */
+ for (ALL_LIST_ELEMENTS_RO(zebrad.client_list, node, client)) {
+ switch (client->proto) {
+ case ZEBRA_ROUTE_BGP:
+ case ZEBRA_ROUTE_OSPF:
+ case ZEBRA_ROUTE_OSPF6:
+ case ZEBRA_ROUTE_PIM:
+ break;
+
+ default:
+ /* NOTHING: skip this daemon. */
+ continue;
+ }
+
+ zserv_send_message(client, msg);
+
+ /* Allocate more messages. */
+ msg = stream_dup(msgc);
+ if (msg == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+ }
+
+ stream_free(msgc);
+}
+
+static int _zebra_ptm_bfd_client_deregister(struct zserv *zs)
+{
+ struct stream *msg;
+ struct ptm_process *pp;
+
+ /* Filter daemons that must receive this treatment. */
+ switch (zs->proto) {
+ case ZEBRA_ROUTE_BGP:
+ case ZEBRA_ROUTE_OSPF:
+ case ZEBRA_ROUTE_OSPF6:
+ case ZEBRA_ROUTE_PIM:
+ break;
+
+ case ZEBRA_ROUTE_BFD:
+ /* Don't try to send BFDd messages to itself. */
+ return 0;
+
+ default:
+ /* Unsupported daemon. */
+ return 0;
+ }
+
+ /* Find daemon pid by zebra connection pointer. */
+ pp = pp_lookup_byzs(zs);
+ if (pp == NULL) {
+ zlog_err("%s:%d failed to find process pid registration",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ /* Generate, send message and free() daemon related data. */
+ msg = stream_new(ZEBRA_MAX_PACKET_SIZ);
+ if (msg == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return 0;
+ }
+
+ /*
+ * The message type will be BFD_DEST_REPLY so we can use only
+ * one callback at the `bfdd` side, however the real command
+ * number will be included right after the zebra header.
+ */
+ zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, 0);
+ stream_putl(msg, ZEBRA_BFD_CLIENT_DEREGISTER);
+
+ /* Put process PID. */
+ stream_putl(msg, pp->pp_pid);
+
+ /* Update the data pointers. */
+ stream_putw_at(msg, 0, stream_get_endp(msg));
+
+ zebra_ptm_send_bfdd(msg);
+
+ pp_free(pp);
+
+ return 0;
+}
+
+void zebra_ptm_init(void)
+{
+ /* Initialize the ptm process information list. */
+ TAILQ_INIT(&ppqueue);
+
+ /*
+ * Send deregistration messages to BFD daemon when some other
+ * daemon closes. This will help avoid sending daemons
+ * unnecessary notification messages.
+ */
+ hook_register(zserv_client_close, _zebra_ptm_bfd_client_deregister);
+}
+
+void zebra_ptm_finish(void)
+{
+ /* Remove the client disconnect hook and free all memory. */
+ hook_unregister(zserv_client_close, _zebra_ptm_bfd_client_deregister);
+ pp_free_all();
+}
+
+
+/*
+ * Message handling.
+ */
+static void _zebra_ptm_reroute(struct zserv *zs, struct stream *msg,
+ uint32_t command)
+{
+ struct stream *msgc;
+ size_t zmsglen, zhdrlen;
+ pid_t ppid;
+
+ /*
+ * Don't modify message in the zebra API. In order to do that we
+ * need to allocate a new message stream and copy the message
+ * provided by zebra.
+ */
+ msgc = stream_new(ZEBRA_MAX_PACKET_SIZ);
+ if (msgc == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+
+ /* Calculate our header size plus the message contents. */
+ zhdrlen = ZEBRA_HEADER_SIZE + sizeof(uint32_t);
+ zmsglen = msg->endp - msg->getp;
+ memcpy(msgc->data + zhdrlen, msg->data + msg->getp, zmsglen);
+
+ /*
+ * The message type will be BFD_DEST_REPLY so we can use only
+ * one callback at the `bfdd` side, however the real command
+ * number will be included right after the zebra header.
+ */
+ zclient_create_header(msgc, ZEBRA_BFD_DEST_REPLAY, 0);
+ stream_putl(msgc, command);
+
+ /* Update the data pointers. */
+ msgc->getp = 0;
+ msgc->endp = zhdrlen + zmsglen;
+ stream_putw_at(msgc, 0, stream_get_endp(msgc));
+
+ zebra_ptm_send_bfdd(msgc);
+
+ /* Registrate process PID for shutdown hook. */
+ STREAM_GETL(msg, ppid);
+ pp_new(ppid, zs);
+
+ return;
+
+stream_failure:
+ zlog_err("%s:%d failed to registrate client pid", __FILE__, __LINE__);
+}
+
+void zebra_ptm_bfd_dst_register(ZAPI_HANDLER_ARGS)
+{
+ if (IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bfd_dst_register msg from client %s: length=%d",
+ zebra_route_string(client->proto), hdr->length);
+
+ _zebra_ptm_reroute(client, msg, ZEBRA_BFD_DEST_REGISTER);
+}
+
+void zebra_ptm_bfd_dst_deregister(ZAPI_HANDLER_ARGS)
+{
+ if (IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bfd_dst_deregister msg from client %s: length=%d",
+ zebra_route_string(client->proto), hdr->length);
+
+ _zebra_ptm_reroute(client, msg, ZEBRA_BFD_DEST_DEREGISTER);
+}
+
+void zebra_ptm_bfd_client_register(ZAPI_HANDLER_ARGS)
+{
+ if (IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bfd_client_register msg from client %s: length=%d",
+ zebra_route_string(client->proto), hdr->length);
+
+ _zebra_ptm_reroute(client, msg, ZEBRA_BFD_CLIENT_REGISTER);
+}
+
+void zebra_ptm_bfd_dst_replay(ZAPI_HANDLER_ARGS)
+{
+ struct stream *msgc;
+ size_t zmsglen, zhdrlen;
+ uint32_t cmd;
+
+ /*
+ * NOTE:
+ * Replay messages with HAVE_BFDD are meant to be replayed to
+ * the client daemons. These messages are composed and
+ * originated from the `bfdd` daemon.
+ */
+ if (IS_ZEBRA_DEBUG_EVENT)
+ zlog_debug("bfd_dst_update msg from client %s: length=%d",
+ zebra_route_string(client->proto), hdr->length);
+
+ /*
+ * Client messages must be re-routed, otherwise do the `bfdd`
+ * special treatment.
+ */
+ if (client->proto != ZEBRA_ROUTE_BFD) {
+ _zebra_ptm_reroute(client, msg, ZEBRA_BFD_DEST_REPLAY);
+ return;
+ }
+
+ /* Figure out if this is an DEST_UPDATE or DEST_REPLAY. */
+ if (stream_getl2(msg, &cmd) == false) {
+ zlog_err("%s: expected at least 4 bytes (command)", __func__);
+ return;
+ }
+
+ /*
+ * Don't modify message in the zebra API. In order to do that we
+ * need to allocate a new message stream and copy the message
+ * provided by zebra.
+ */
+ msgc = stream_new(ZEBRA_MAX_PACKET_SIZ);
+ if (msgc == NULL) {
+ zlog_warn("%s: not enough memory", __func__);
+ return;
+ }
+
+ /* Calculate our header size plus the message contents. */
+ if (cmd != ZEBRA_BFD_DEST_REPLAY) {
+ zhdrlen = ZEBRA_HEADER_SIZE;
+ zmsglen = msg->endp - msg->getp;
+ memcpy(msgc->data + zhdrlen, msg->data + msg->getp, zmsglen);
+
+ zclient_create_header(msgc, cmd, zvrf_id(zvrf));
+
+ msgc->getp = 0;
+ msgc->endp = zhdrlen + zmsglen;
+ } else
+ zclient_create_header(msgc, cmd, zvrf_id(zvrf));
+
+ /* Update the data pointers. */
+ stream_putw_at(msgc, 0, stream_get_endp(msgc));
+
+ zebra_ptm_send_clients(msgc);
+}
+
+/*
+ * Unused functions.
+ */
+void zebra_ptm_if_init(struct zebra_if *zifp __attribute__((__unused__)))
+{
+ /* NOTHING */
+}
+
+int zebra_ptm_get_enable_state(void)
+{
+ return 1;
+}
+
+void zebra_ptm_show_status(struct vty *vty __attribute__((__unused__)),
+ struct interface *ifp __attribute__((__unused__)))
+{
+ /* NOTHING */
+}
+
+void zebra_ptm_write(struct vty *vty __attribute__((__unused__)))
+{
+ /* NOTHING */
+}
+
+void zebra_ptm_if_write(struct vty *vty __attribute__((__unused__)),
+ struct zebra_if *zifp __attribute__((__unused__)))
+{
+ /* NOTHING */
+}
+void zebra_ptm_if_set_ptm_state(struct interface *i __attribute__((__unused__)),
+ struct zebra_if *zi __attribute__((__unused__)))
+{
+ /* NOTHING */
+}
+
+#endif /* HAVE_BFDD */
diff --git a/zebra/zebra_ptm.h b/zebra/zebra_ptm.h
index 0e55574a02..ada4f7b4f7 100644
--- a/zebra/zebra_ptm.h
+++ b/zebra/zebra_ptm.h
@@ -69,6 +69,9 @@ int zebra_ptm_get_enable_state(void);
void zebra_ptm_bfd_dst_register(ZAPI_HANDLER_ARGS);
void zebra_ptm_bfd_dst_deregister(ZAPI_HANDLER_ARGS);
void zebra_ptm_bfd_client_register(ZAPI_HANDLER_ARGS);
+#if HAVE_BFDD > 0
+void zebra_ptm_bfd_dst_replay(ZAPI_HANDLER_ARGS);
+#endif /* HAVE_BFDD */
void zebra_ptm_show_status(struct vty *vty, struct interface *ifp);
void zebra_ptm_if_init(struct zebra_if *zebra_ifp);