summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafael Zalamena <rzalamena@opensourcerouting.org>2018-06-27 11:29:02 -0300
committerRafael Zalamena <rzalamena@opensourcerouting.org>2018-08-08 18:24:53 -0300
commite9e2c950d7423071e8577a12c072a5309f02f8c1 (patch)
tree6d89f3c28fc3829affb30484aa41be57ba925d05
parent8519fe88c9fc6a3e217148fd2edd8d815269ec8a (diff)
bfdd: imported new daemon source code
Import source code from external `bfdd` daemon ported from Cumulus PTM. Signed-off-by: Rafael Zalamena <rzalamena@opensourcerouting.org>
-rw-r--r--bfdd/.gitignore3
-rw-r--r--bfdd/bfd.c1385
-rw-r--r--bfdd/bfd.h617
-rw-r--r--bfdd/bfd_packet.c1488
-rw-r--r--bfdd/bfdctl.h160
-rw-r--r--bfdd/bfdd.c234
-rw-r--r--bfdd/bfdd.conf.sample5
-rw-r--r--bfdd/bsd.c290
-rw-r--r--bfdd/config.c606
-rw-r--r--bfdd/control.c901
-rw-r--r--bfdd/event.c155
-rw-r--r--bfdd/linux.c218
-rw-r--r--bfdd/log.c129
-rw-r--r--bfdd/subdir.am28
14 files changed, 6219 insertions, 0 deletions
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..16a3a688f9
--- /dev/null
+++ b/bfdd/bfd.c
@@ -0,0 +1,1385 @@
+/*********************************************************************
+ * 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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <time.h>
+#include <signal.h>
+
+#include "lib/hash.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)
+{
+ 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);
+
+ INFOLOG("Session 0x%x up peer %s", bfd->discrs.my_discr,
+ satostr(&bfd->shop.peer));
+}
+
+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);
+
+ INFOLOG("Session 0x%x down peer %s Rsn %s prev st %s",
+ bfd->discrs.my_discr, satostr(&bfd->shop.peer),
+ get_diag_str(bfd->local_diag), state_list[old_state].str);
+
+ /* 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);
+}
+
+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 peer_addr[64];
+ char local_addr[64];
+ char vrf_name_buf[MAXNAMELEN + 1];
+
+ /* peer, local are in network-byte order */
+ strlcpy(peer_addr, satostr(peer), sizeof(peer_addr));
+ strlcpy(local_addr, satostr(local), sizeof(local_addr));
+
+ if (cp) {
+ if (cp->discrs.remote_discr) {
+ uint32_t ldisc = ntohl(cp->discrs.remote_discr);
+ /* Your discriminator not zero - use it to find session
+ */
+ l_bfd = bfd_find_disc(peer, ldisc);
+
+ if (l_bfd)
+ return l_bfd;
+
+ DLOG("Can't find session for yourDisc 0x%x from %s",
+ ldisc, peer_addr);
+ } else if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN
+ || BFD_GETSTATE(cp->flags) == PTM_BFD_ADM_DOWN) {
+
+ if (is_mhop) {
+ memset(&mhop, 0, sizeof(mhop));
+ mhop.peer = *peer;
+ mhop.local = *local;
+ if (vrf_name && strlen(vrf_name)) {
+ strlcpy(mhop.vrf_name, vrf_name,
+ sizeof(mhop.vrf_name));
+ } else if (port_name) {
+ memset(vrf_name_buf, 0,
+ sizeof(vrf_name_buf));
+ if (ptm_bfd_get_vrf_name(port_name,
+ vrf_name_buf)
+ != -1) {
+ strlcpy(mhop.vrf_name,
+ vrf_name_buf,
+ sizeof(mhop.vrf_name));
+ }
+ }
+
+ /* Your discriminator zero -
+ * use peer address and local address to
+ * find session
+ */
+ l_bfd = bfd_mhop_lookup(mhop);
+ } else {
+ memset(&shop, 0, sizeof(shop));
+ shop.peer = *peer;
+ if (strlen(port_name))
+ strlcpy(shop.port_name, port_name,
+ sizeof(shop.port_name));
+ /* Your discriminator zero -
+ * use peer address and port to find
+ * session
+ */
+ l_bfd = bfd_shop_lookup(shop);
+ }
+ if (l_bfd) {
+ /* XXX maybe remoteDiscr should be checked for
+ * remoteHeard cases
+ */
+ return l_bfd;
+ }
+ }
+ if (is_mhop)
+ DLOG("Can't find multi hop session peer/local %s/%s in vrf %s port %s",
+ peer_addr, local_addr,
+ strlen(mhop.vrf_name) ? mhop.vrf_name : "N/A",
+ port_name ? port_name : "N/A");
+ else
+ DLOG("Can't find single hop session for peer/port %s/%s",
+ peer_addr, port_name);
+ } else if (peer->sa_sin.sin_addr.s_addr
+ || !IN6_IS_ADDR_UNSPECIFIED(&peer->sa_sin6.sin6_addr)) {
+
+ if (is_mhop) {
+ memset(&mhop, 0, sizeof(mhop));
+ mhop.peer = *peer;
+ mhop.local = *local;
+ if (vrf_name && strlen(vrf_name))
+ strlcpy(mhop.vrf_name, vrf_name,
+ sizeof(mhop.vrf_name));
+
+ l_bfd = bfd_mhop_lookup(mhop);
+ } else {
+ memset(&shop, 0, sizeof(shop));
+ shop.peer = *peer;
+ if (strlen(port_name)) {
+ strlcpy(shop.port_name, port_name,
+ sizeof(shop.port_name));
+ }
+
+ l_bfd = bfd_shop_lookup(shop);
+ }
+
+ if (l_bfd) {
+ /* XXX maybe remoteDiscr should be checked for
+ * remoteHeard cases
+ */
+ return l_bfd;
+ }
+
+ DLOG("Can't find session for peer %s", peer_addr);
+ }
+
+ return (NULL);
+}
+
+#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);
+ uint8_t old_state;
+
+ old_state = bs->ses_state;
+
+ switch (bs->ses_state) {
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME);
+ INFOLOG("%s Detect timeout on session 0x%x with peer %s, in state %d",
+ __func__, bs->discrs.my_discr, satostr(&bs->shop.peer),
+ bs->ses_state);
+ bfd_recvtimer_update(bs);
+ break;
+
+ default:
+ /* Second detect time expiration, zero remote discr (section
+ * 6.5.1)
+ */
+ bs->discrs.remote_discr = 0;
+ break;
+ }
+
+ if (old_state != bs->ses_state) {
+ DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]",
+ bs->discrs.my_discr, satostr(&bs->shop.peer),
+ state_list[old_state].str, state_list[bs->ses_state].str);
+ }
+
+ return 0;
+}
+
+/* Was ptm_bfd_echo_detect_TO() */
+int bfd_echo_recvtimer_cb(struct thread *t)
+{
+ struct bfd_session *bs = THREAD_ARG(t);
+ uint8_t old_state;
+
+ old_state = bs->ses_state;
+
+ switch (bs->ses_state) {
+ case PTM_BFD_INIT:
+ case PTM_BFD_UP:
+ ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME);
+ INFOLOG("%s Detect timeout on session 0x%x with peer %s, in state %d",
+ __func__, bs->discrs.my_discr, satostr(&bs->shop.peer),
+ bs->ses_state);
+ break;
+ }
+
+ if (old_state != bs->ses_state) {
+ DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]",
+ bs->discrs.my_discr, satostr(&bs->shop.peer),
+ state_list[old_state].str, state_list[bs->ses_state].str);
+ }
+
+ 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) {
+ ERRLOG("Can't get socket for new session: %s",
+ strerror(errno));
+ return NULL;
+ }
+ } else {
+ psock = bp_peer_socketv6(bpc);
+ if (psock == -1) {
+ ERRLOG("Can't get IPv6 socket for new session: %s",
+ strerror(errno));
+ return NULL;
+ }
+ }
+
+ /* Get memory */
+ bfd = bfd_session_new(psock);
+ if (bfd == NULL) {
+ ERRLOG("Can't malloc memory for new session: %s",
+ strerror(errno));
+ 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);
+
+ /* 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);
+
+ if (bpc->bpc_mhop) {
+ INFOLOG("Created new session 0x%x with vrf %s peer %s local %s",
+ bfd->discrs.my_discr,
+ (bpc->bpc_has_vrfname) ? bfd->mhop.vrf_name : "N/A",
+ satostr(&bfd->mhop.peer), satostr(&bfd->mhop.local));
+ } else {
+ INFOLOG("Created new session 0x%x with peer %s port %s",
+ bfd->discrs.my_discr, satostr(&bfd->shop.peer),
+ bfd->shop.port_name[0] ? bfd->shop.port_name : "N/A");
+ }
+
+ 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) {
+ zlog_debug("%s: trying to free in-use session: %" PRIu64
+ " references",
+ __func__, bs->refcount);
+ return -1;
+ }
+
+ if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) {
+ INFOLOG("Deleting session 0x%x with vrf %s peer %s local %s",
+ bs->discrs.my_discr,
+ bpc->bpc_has_vrfname ? bpc->bpc_vrfname : "N/A",
+ satostr(&bs->mhop.peer), satostr(&bs->mhop.local));
+ } else {
+ INFOLOG("Deleting session 0x%x with peer %s port %s",
+ bs->discrs.my_discr, satostr(&bs->shop.peer),
+ bs->shop.port_name);
+ }
+
+ control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs);
+
+ bfd_session_free(bs);
+
+ return 0;
+}
+
+
+/*
+ * 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);
+}
+
+
+/*
+ * 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..e4907ecceb
--- /dev/null
+++ b/bfdd/bfd.h
@@ -0,0 +1,617 @@
+/*********************************************************************
+ * 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, ...);
+
+/* Compatibility code: code to avoid touching ported code debug messages. */
+#define DLOG(fmt, args...) log_debug(fmt, ##args)
+#define INFOLOG(fmt, args...) log_info(fmt, ##args)
+#define ERRLOG(fmt, args...) log_error(fmt, ##args)
+#define CRITLOG(fmt, args...) log_fatal(fmt, ##args)
+
+/*
+ * 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);
+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);
+
+/* 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);
+
+
+/*
+ * 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..79aa706a53
--- /dev/null
+++ b/bfdd/bfd_packet.c
@@ -0,0 +1,1488 @@
+/*********************************************************************
+ * 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 <arpa/inet.h>
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdint.h>
+#include <unistd.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("%s: 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("%s:%d: sendto: (%d) %s", __func__, __LINE__, errno,
+ strerror(errno));
+ return -1;
+ }
+ if (rv < (ssize_t)datalen) {
+ log_debug("%s:%d: sendto: sent partial data", __func__,
+ __LINE__);
+ }
+
+ 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) {
+ ERRLOG("%s: _ptm_bfd_send: %s", __func__, 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) {
+ ERRLOG("%s: sendto: %s", __func__, 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)
+ ERRLOG("%s: recvfrom: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ /* Check if we have at least the basic headers to send back. */
+ if (pkt_len < HEADERS_MIN_LEN) {
+ INFOLOG("Received short echo packet");
+ 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));
+
+ /* Packet is too small for us to process */
+ if (pkt_len < BFD_ECHO_PKT_TOT_LEN) {
+ INFOLOG("Received short echo packet");
+ return -1;
+ }
+
+ my_discr = ntohl(ep->data.my_discr);
+ if (ep->data.my_discr == 0) {
+ INFOLOG("My discriminator is zero in echo pkt from 0x%x",
+ ntohl(ep->ip.saddr));
+ 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) {
+ INFOLOG("Failed to extract session from echo packet");
+ return -1;
+ }
+
+ if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) {
+ INFOLOG("BFD echo not active - ignore echo packet");
+ 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) {
+ ERRLOG("Error sending control pkt: %s", strerror(errno));
+ 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)) {
+ ERRLOG("Error Rx BFD Vxlan pkt with non-zero vnid %d",
+ vxlan_info->vnid);
+ return false;
+ }
+
+ if (bfd->vxlan_info.local_dst_ip.s_addr
+ != vxlan_info->local_dst_ip.s_addr) {
+ ERRLOG("Error Rx BFD Vxlan pkt with wrong inner dst IP %s",
+ 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)) {
+ ERRLOG("Error Rx BFD Vxlan pkt with wrong inner dst 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) {
+ ERRLOG("Error receiving from BFD socket: %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)) {
+ INFOLOG("Received pkt with invalid TTL %u from %s flags: %d",
+ ttl, satostr(peer), 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)) {
+ INFOLOG("Received pkt with invalid TTL %u from %s flags: %d",
+ ttl, satostr(peer), 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;
+ 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) {
+ ERRLOG("Error receiving from BFD socket: %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)) {
+ INFOLOG("Received pkt with invalid TTL %u from %s flags: %d",
+ ttlval, satostr(peer),
+ 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);
+ }
+ }
+ }
+
+ 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]);
+ }
+}
+
+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;
+ uint8_t old_state;
+ 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);
+
+ if (sd == bglobal.bg_echo) {
+ ptm_bfd_process_echo_pkt(sd);
+ return 0;
+ }
+
+ 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) {
+ INFOLOG("Received short packet from %s", satostr(&peer));
+ return 0;
+ }
+
+ cp = (struct bfd_pkt *)(msghdr.msg_iov->iov_base);
+ if (BFD_GETVER(cp->diag) != BFD_VERSION) {
+ INFOLOG("Received bad version %d from %s", BFD_GETVER(cp->diag),
+ satostr(&peer));
+ return 0;
+ }
+
+ if (cp->detect_mult == 0) {
+ INFOLOG("Detect Mult is zero in pkt from %s", satostr(&peer));
+ return 0;
+ }
+
+ if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) {
+ INFOLOG("Invalid length %d in control pkt from %s", cp->len,
+ satostr(&peer));
+ return 0;
+ }
+
+ if (cp->discrs.my_discr == 0) {
+ INFOLOG("My discriminator is zero in pkt from %s",
+ satostr(&peer));
+ return 0;
+ }
+
+ bfd = ptm_bfd_sess_find(cp, port, &peer, &local, vrfname, is_mhop);
+ if (bfd == NULL) {
+ DLOG("Failed to generate session from remote packet");
+ return 0;
+ }
+
+ if (is_vxlan && !ptm_bfd_validate_vxlan_pkt(bfd, &vxlan_info))
+ return 0;
+
+ bfd->stats.rx_ctrl_pkt++;
+ if (is_mhop) {
+ if ((BFD_TTL_VAL - bfd->mh_ttl) > ttlval) {
+ DLOG("Exceeded max hop count of %d, dropped pkt from %s with TTL %d",
+ bfd->mh_ttl, satostr(&peer), 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);
+
+ if ((bfd->discrs.remote_discr != 0)
+ && (bfd->discrs.remote_discr != ntohl(cp->discrs.my_discr))) {
+ DLOG("My Discriminator mismatch in pkt from %s, Expected %d Got %d",
+ satostr(&peer), 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 {
+ ERRLOG("Unsupport BFD mode detected");
+ }
+
+ /* Save remote diagnostics before state switch. */
+ bfd->remote_diag = cp->diag & BFD_DIAGMASK;
+
+ /* State switch from section 6.8.6 */
+ old_state = bfd->ses_state;
+ 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;
+ }
+ }
+
+ if (old_state != bfd->ses_state) {
+ DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]",
+ bfd->discrs.my_discr, satostr(&peer),
+ state_list[old_state].str, state_list[bfd->ses_state].str);
+ }
+
+ 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);
+ }
+
+ if (!bfd->demand_mode) {
+ /* Restart detection timer (packet received) */
+ bfd_recvtimer_update(bfd);
+ } else {
+ ERRLOG("Unsupport BFD mode detected");
+ }
+
+ /*
+ * 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)
+ 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 */
+ ERRLOG("Can't find source port for new session: %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)
+ 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;
+ if (sin6.sin6_family != AF_INET6) {
+#if 0 /* XXX what is this? */
+ ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif);
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr))
+ sin6.sin6_scope_id = ifindex;
+#endif
+ } else if (bpc->bpc_has_localif) {
+ ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif);
+ 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 */
+ ERRLOG("Can't find source port for new session: %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..3313c8137c
--- /dev/null
+++ b/bfdd/bfdd.c
@@ -0,0 +1,234 @@
+/*
+ * 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 <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bfd.h"
+#include "lib/version.h"
+#include "lib/libfrr.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();
+
+ /* 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();
+
+ /* 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);
+
+ /* 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/bsd.c b/bfdd/bsd.c
new file mode 100644
index 0000000000..3d196dfb9f
--- /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) {
+ ERRLOG("%s: socket: %s", __func__, 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("%s: bind: %s", __func__, strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ if (setsockopt(s, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) == -1) {
+ log_error("%s: setsockopt(IP_RECVTTL): %s", __func__,
+ strerror(errno));
+ close(s);
+ return -1;
+ }
+
+ ttl = BFD_TTL_VAL;
+ if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) {
+ log_error("%s: setsockopt(IP_TTL): %s", __func__,
+ 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..39c7871678
--- /dev/null
+++ b/bfdd/control.c
@@ -0,0 +1,901 @@
+/*********************************************************************
+ * 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/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.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;
+
+ /*
+ * 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..8a68511d53
--- /dev/null
+++ b/bfdd/linux.c
@@ -0,0 +1,218 @@
+/*
+ * 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)) {
+ CRITLOG("Interface name %s truncated", ifr.ifr_name);
+ }
+
+ if (ioctl(bglobal.bg_shop, SIOCGIFINDEX, &ifr) == -1) {
+ CRITLOG("Getting ifindex for %s 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)) {
+ CRITLOG("Interface name %s truncated", ifr.ifr_name);
+ }
+
+ if (ioctl(bglobal.bg_shop, SIOCGIFHWADDR, &ifr) == -1) {
+ CRITLOG("Getting mac address for %s 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) {
+ CRITLOG("Getting ifname for ifindex %d failed: %s", ifindex,
+ strerror(errno));
+ return;
+ }
+
+ strlcpy(ifname, ifr.ifr_name, ifnamelen);
+}
+
+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) {
+ ERRLOG("%s: socket: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf))
+ == -1) {
+ ERRLOG("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__,
+ 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) {
+ ERRLOG("%s: socket: %s", __func__, strerror(errno));
+ return -1;
+ }
+
+ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf))
+ == -1) {
+ ERRLOG("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__,
+ 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..8561956d6a
--- /dev/null
+++ b/bfdd/log.c
@@ -0,0 +1,129 @@
+/*********************************************************************
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.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/subdir.am b/bfdd/subdir.am
new file mode 100644
index 0000000000..fe3daaf507
--- /dev/null
+++ b/bfdd/subdir.am
@@ -0,0 +1,28 @@
+#
+# 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/bfd_packet.c \
+ bfdd/bsd.c \
+ bfdd/config.c \
+ bfdd/control.c \
+ bfdd/event.c \
+ bfdd/linux.c \
+ bfdd/log.c \
+ # end
+
+noinst_HEADERS += \
+ bfdd/bfdctl.h \
+ bfdd/bfd.h \
+ # end
+
+bfdd_bfdd_SOURCES = bfdd/bfdd.c
+bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la