diff --git a/.clang-format b/.clang-format index cc5a95baf6a9..3c6a2784ceff 100644 --- a/.clang-format +++ b/.clang-format @@ -43,6 +43,7 @@ ForEachMacros: - SPLAY_FOREACH - FOR_ALL_INTERFACES - FOR_ALL_INTERFACES_ADDRESSES + - JSON_FOREACH # zebra - RE_DEST_FOREACH_ROUTE - RE_DEST_FOREACH_ROUTE_SAFE diff --git a/Makefile.am b/Makefile.am index 8c96f39f397a..3e268f703da3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -56,6 +56,7 @@ include sharpd/subdir.am include pimd/subdir.am include pbrd/subdir.am include staticd/subdir.am +include bfdd/subdir.am SUBDIRS = . @LIBRFP@ @RFPTEST@ \ @BGPD@ \ diff --git a/bfdd/.gitignore b/bfdd/.gitignore new file mode 100644 index 000000000000..e554d1b33f68 --- /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 000000000000..28b6beadcbee --- /dev/null +++ b/bfdd/bfd.c @@ -0,0 +1,1294 @@ +/********************************************************************* + * Copyright 2013 Cumulus Networks, LLC. All rights reserved. + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd.c: implements the BFD protocol. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include + +#include "lib/jhash.h" + +#include "bfd.h" + +DEFINE_QOBJ_TYPE(bfd_session); + +/* + * Prototypes + */ +static uint32_t ptm_bfd_gen_ID(void); +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd); +static void bfd_session_free(struct bfd_session *bs); +static struct bfd_session *bfd_session_new(int sd); +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, + uint32_t ldisc); +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc); +static const char *get_diag_str(int diag); + + +/* + * Functions + */ +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bs; + struct peer_label *pl; + struct bfd_mhop_key mhop; + struct bfd_shop_key shop; + + /* Try to find label first. */ + if (bpc->bpc_has_label) { + pl = pl_find(bpc->bpc_label); + if (pl != NULL) { + bs = pl->pl_bs; + return bs; + } + } + + /* Otherwise fallback to peer/local hash lookup. */ + if (bpc->bpc_mhop) { + memset(&mhop, 0, sizeof(mhop)); + mhop.peer = bpc->bpc_peer; + mhop.local = bpc->bpc_local; + if (bpc->bpc_has_vrfname) + strlcpy(mhop.vrf_name, bpc->bpc_vrfname, + sizeof(mhop.vrf_name)); + + bs = bfd_mhop_lookup(mhop); + } else { + memset(&shop, 0, sizeof(shop)); + shop.peer = bpc->bpc_peer; + if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) + strlcpy(shop.port_name, bpc->bpc_localif, + sizeof(shop.port_name)); + + bs = bfd_shop_lookup(shop); + } + + return bs; +} + +static uint32_t ptm_bfd_gen_ID(void) +{ + static uint32_t sessionID = 1; + + return (sessionID++); +} + +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo) +{ + uint64_t jitter, xmt_TO; + int maxpercent; + + xmt_TO = is_echo ? bfd->echo_xmt_TO : bfd->xmt_TO; + + /* + * From section 6.5.2: trasmit interval should be randomly jittered + * between + * 75% and 100% of nominal value, unless detect_mult is 1, then should + * be + * between 75% and 90%. + */ + maxpercent = (bfd->detect_mult == 1) ? 16 : 26; + jitter = (xmt_TO * (75 + (random() % maxpercent))) / 100; + /* XXX remove that division above */ + + if (is_echo) + bfd_echo_xmttimer_update(bfd, jitter); + else + bfd_xmttimer_update(bfd, jitter); +} + +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd) +{ + /* Send the scheduled echo packet */ + ptm_bfd_echo_snd(bfd); + + /* Restart the timer for next time */ + ptm_bfd_start_xmt_timer(bfd, true); +} + +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit) +{ + /* Send the scheduled control packet */ + ptm_bfd_snd(bfd, fbit); + + /* Restart the timer for next time */ + ptm_bfd_start_xmt_timer(bfd, false); +} + +void ptm_bfd_echo_stop(struct bfd_session *bfd, int polling) +{ + bfd->echo_xmt_TO = 0; + bfd->echo_detect_TO = 0; + BFD_UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + + bfd_echo_xmttimer_delete(bfd); + bfd_echo_recvtimer_delete(bfd); + + if (polling) { + bfd->polling = polling; + bfd->new_timers.desired_min_tx = bfd->up_min_tx; + bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; + ptm_bfd_snd(bfd, 0); + } +} + +void ptm_bfd_echo_start(struct bfd_session *bfd) +{ + bfd->echo_detect_TO = (bfd->remote_detect_mult * bfd->echo_xmt_TO); + ptm_bfd_echo_xmt_TO(bfd); + + bfd->polling = 1; + bfd->new_timers.desired_min_tx = bfd->up_min_tx; + bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; + ptm_bfd_snd(bfd, 0); +} + +void ptm_bfd_ses_up(struct bfd_session *bfd) +{ + int old_state = bfd->ses_state; + + bfd->local_diag = 0; + bfd->ses_state = PTM_BFD_UP; + bfd->polling = 1; + monotime(&bfd->uptime); + + /* If the peer is capable to receiving Echo pkts */ + if (bfd->echo_xmt_TO && !BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MH)) { + ptm_bfd_echo_start(bfd); + } else { + bfd->new_timers.desired_min_tx = bfd->up_min_tx; + bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; + ptm_bfd_snd(bfd, 0); + } + + control_notify(bfd); + + if (old_state != bfd->ses_state) + log_info("state-change: [%s] %s -> %s", bs_to_string(bfd), + state_list[old_state].str, + state_list[bfd->ses_state].str); +} + +void ptm_bfd_ses_dn(struct bfd_session *bfd, uint8_t diag) +{ + int old_state = bfd->ses_state; + + bfd->local_diag = diag; + bfd->discrs.remote_discr = 0; + bfd->ses_state = PTM_BFD_DOWN; + bfd->polling = 0; + bfd->demand_mode = 0; + monotime(&bfd->downtime); + + ptm_bfd_snd(bfd, 0); + + /* only signal clients when going from up->down state */ + if (old_state == PTM_BFD_UP) + control_notify(bfd); + + /* Stop echo packet transmission if they are active */ + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) + ptm_bfd_echo_stop(bfd, 0); + + if (old_state != bfd->ses_state) + log_info("state-change: [%s] %s -> %s reason:%s", + bs_to_string(bfd), state_list[old_state].str, + state_list[bfd->ses_state].str, + get_diag_str(bfd->local_diag)); +} + +static int ptm_bfd_get_vrf_name(char *port_name, char *vrf_name) +{ + struct bfd_iface *iface; + struct bfd_vrf *vrf; + + if ((port_name == NULL) || (vrf_name == NULL)) + return -1; + + iface = bfd_iface_lookup(port_name); + if (iface) { + vrf = bfd_vrf_lookup(iface->vrf_id); + if (vrf) { + strlcpy(vrf_name, vrf->name, sizeof(vrf->name)); + return 0; + } + } + return -1; +} + +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, + uint32_t ldisc) +{ + struct bfd_session *bs; + + bs = bfd_id_lookup(ldisc); + if (bs == NULL) + return NULL; + + /* Remove unused fields. */ + switch (sa->sa_sin.sin_family) { + case AF_INET: + sa->sa_sin.sin_port = 0; + if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin)) == 0) + return bs; + break; + case AF_INET6: + sa->sa_sin6.sin6_port = 0; + if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin6)) == 0) + return bs; + break; + } + + return NULL; +} + +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, char *port_name, + struct sockaddr_any *peer, + struct sockaddr_any *local, + char *vrf_name, bool is_mhop) +{ + struct bfd_session *l_bfd = NULL; + struct bfd_mhop_key mhop; + struct bfd_shop_key shop; + char vrf_buf[MAXNAMELEN]; + + /* Find our session using the ID signaled by the remote end. */ + if (cp->discrs.remote_discr) + return bfd_find_disc(peer, ntohl(cp->discrs.remote_discr)); + + /* Search for session without using discriminator. */ + if (is_mhop) { + memset(&mhop, 0, sizeof(mhop)); + mhop.peer = *peer; + mhop.local = *local; + if (vrf_name && vrf_name[0]) { + strlcpy(mhop.vrf_name, vrf_name, sizeof(mhop.vrf_name)); + } else if (port_name && port_name[0]) { + memset(vrf_buf, 0, sizeof(vrf_buf)); + if (ptm_bfd_get_vrf_name(port_name, vrf_buf) != -1) + strlcpy(mhop.vrf_name, vrf_buf, + sizeof(mhop.vrf_name)); + } + + l_bfd = bfd_mhop_lookup(mhop); + } else { + memset(&shop, 0, sizeof(shop)); + shop.peer = *peer; + if (port_name && port_name[0]) + strlcpy(shop.port_name, port_name, + sizeof(shop.port_name)); + + l_bfd = bfd_shop_lookup(shop); + } + + /* XXX maybe remoteDiscr should be checked for remoteHeard cases. */ + return l_bfd; +} + +#if 0 /* TODO VxLAN Support */ +static void +_update_vxlan_sess_parms(struct bfd_session *bfd, bfd_sess_parms *sess_parms) +{ + struct bfd_session_vxlan_info *vxlan_info = &bfd->vxlan_info; + bfd_parms_list *parms = &sess_parms->parms; + + vxlan_info->vnid = parms->vnid; + vxlan_info->check_tnl_key = parms->check_tnl_key; + vxlan_info->forwarding_if_rx = parms->forwarding_if_rx; + vxlan_info->cpath_down = parms->cpath_down; + vxlan_info->decay_min_rx = parms->decay_min_rx; + + inet_aton(parms->local_dst_ip, &vxlan_info->local_dst_ip); + inet_aton(parms->remote_dst_ip, &vxlan_info->peer_dst_ip); + + memcpy(vxlan_info->local_dst_mac, parms->local_dst_mac, ETH_ALEN); + memcpy(vxlan_info->peer_dst_mac, parms->remote_dst_mac, ETH_ALEN); + + /* The interface may change for Vxlan BFD sessions, so update + * the local mac and ifindex + */ + bfd->ifindex = sess_parms->ifindex; + memcpy(bfd->local_mac, sess_parms->local_mac, sizeof(bfd->local_mac)); +} +#endif /* VxLAN support */ + +int bfd_xmt_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + + ptm_bfd_xmt_TO(bs, 0); + + return 0; +} + +int bfd_echo_xmt_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + + ptm_bfd_echo_xmt_TO(bs); + + return 0; +} + +/* Was ptm_bfd_detect_TO() */ +int bfd_recvtimer_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + + switch (bs->ses_state) { + case PTM_BFD_INIT: + case PTM_BFD_UP: + ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME); + bfd_recvtimer_update(bs); + break; + + default: + /* Second detect time expiration, zero remote discr (section + * 6.5.1) + */ + bs->discrs.remote_discr = 0; + break; + } + + return 0; +} + +/* Was ptm_bfd_echo_detect_TO() */ +int bfd_echo_recvtimer_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + + switch (bs->ses_state) { + case PTM_BFD_INIT: + case PTM_BFD_UP: + ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME); + break; + } + + return 0; +} + +static struct bfd_session *bfd_session_new(int sd) +{ + struct bfd_session *bs; + + bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs)); + if (bs == NULL) + return NULL; + + QOBJ_REG(bs, bfd_session); + + bs->up_min_tx = BFD_DEFDESIREDMINTX; + bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX; + bs->timers.required_min_echo = BFD_DEF_REQ_MIN_ECHO; + bs->detect_mult = BFD_DEFDETECTMULT; + bs->mh_ttl = BFD_DEF_MHOP_TTL; + + bs->sock = sd; + monotime(&bs->uptime); + bs->downtime = bs->uptime; + + return bs; +} + +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel) +{ + /* New label treatment: + * - Check if the label is taken; + * - Try to allocate the memory for it and register; + */ + if (bs->pl == NULL) { + if (pl_find(nlabel) != NULL) { + /* Someone is already using it. */ + return -1; + } + + if (pl_new(nlabel, bs) == NULL) + return -1; + + return 0; + } + + /* + * Test label change consistency: + * - Do nothing if it's the same label; + * - Check if the future label is already taken; + * - Change label; + */ + if (strcmp(nlabel, bs->pl->pl_label) == 0) + return -1; + if (pl_find(nlabel) != NULL) + return -1; + + strlcpy(bs->pl->pl_label, nlabel, sizeof(bs->pl->pl_label)); + return 0; +} + +static void _bfd_session_update(struct bfd_session *bs, + struct bfd_peer_cfg *bpc) +{ + if (bpc->bpc_echo) { + /* Check if echo mode is already active. */ + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + goto skip_echo; + + BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + ptm_bfd_echo_start(bs); + + /* Activate/update echo receive timeout timer. */ + bfd_echo_recvtimer_update(bs); + } else { + /* Check if echo mode is already disabled. */ + if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + goto skip_echo; + + BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + ptm_bfd_echo_stop(bs, 0); + } + +skip_echo: + if (bpc->bpc_has_txinterval) + bs->up_min_tx = bpc->bpc_txinterval * 1000; + + if (bpc->bpc_has_recvinterval) + bs->timers.required_min_rx = bpc->bpc_recvinterval * 1000; + + if (bpc->bpc_has_detectmultiplier) + bs->detect_mult = bpc->bpc_detectmultiplier; + + if (bpc->bpc_has_echointerval) + bs->timers.required_min_echo = bpc->bpc_echointerval * 1000; + + if (bpc->bpc_has_label) + bfd_session_update_label(bs, bpc->bpc_label); + + if (bpc->bpc_shutdown) { + /* Check if already shutdown. */ + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) + return; + + BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Disable all events. */ + bfd_recvtimer_delete(bs); + bfd_echo_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + bfd_echo_xmttimer_delete(bs); + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_ADM_DOWN; + control_notify(bs); + + ptm_bfd_snd(bs, 0); + } else { + /* Check if already working. */ + if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) + return; + + BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_DOWN; + control_notify(bs); + + /* Enable all timers. */ + bfd_recvtimer_update(bs); + bfd_xmttimer_update(bs, bs->xmt_TO); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) { + bfd_echo_recvtimer_update(bs); + bfd_echo_xmttimer_update(bs, bs->echo_xmt_TO); + } + } +} + +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc) +{ + /* User didn't want to update, return failure. */ + if (bpc->bpc_createonly) + return -1; + + _bfd_session_update(bs, bpc); + + /* TODO add VxLAN support. */ + + control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); + + return 0; +} + +static void bfd_session_free(struct bfd_session *bs) +{ + if (bs->sock != -1) + close(bs->sock); + + bfd_recvtimer_delete(bs); + bfd_echo_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + bfd_echo_xmttimer_delete(bs); + + bfd_id_delete(bs->discrs.my_discr); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + bfd_mhop_delete(bs->mhop); + else + bfd_shop_delete(bs->shop); + + pl_free(bs->pl); + + QOBJ_UNREG(bs); + XFREE(MTYPE_BFDD_CONFIG, bs); +} + +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bfd, *l_bfd; + int psock; + + /* check to see if this needs a new session */ + l_bfd = bs_peer_find(bpc); + if (l_bfd) { + /* Requesting a duplicated peer means update configuration. */ + if (bfd_session_update(l_bfd, bpc) == 0) + return l_bfd; + else + return NULL; + } + + /* + * Get socket for transmitting control packets. Note that if we + * could use the destination port (3784) for the source + * port we wouldn't need a socket per session. + */ + if (bpc->bpc_ipv4) { + psock = bp_peer_socket(bpc); + if (psock == -1) + return NULL; + } else { + psock = bp_peer_socketv6(bpc); + if (psock == -1) + return NULL; + } + + /* Get memory */ + bfd = bfd_session_new(psock); + if (bfd == NULL) { + log_error("session-new: allocation failed"); + return NULL; + } + + if (bpc->bpc_has_localif && !bpc->bpc_mhop) { + bfd->ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); + ptm_bfd_fetch_local_mac(bpc->bpc_localif, bfd->local_mac); + } + + if (bpc->bpc_has_vxlan) + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN); + + if (bpc->bpc_ipv4 == false) { + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6); + + /* Set the IPv6 scope id for link-local addresses. */ + if (IN6_IS_ADDR_LINKLOCAL(&bpc->bpc_local.sa_sin6.sin6_addr)) + bpc->bpc_local.sa_sin6.sin6_scope_id = bfd->ifindex; + if (IN6_IS_ADDR_LINKLOCAL(&bpc->bpc_peer.sa_sin6.sin6_addr)) + bpc->bpc_peer.sa_sin6.sin6_scope_id = bfd->ifindex; + } + + /* Initialize the session */ + bfd->ses_state = PTM_BFD_DOWN; + bfd->discrs.my_discr = ptm_bfd_gen_ID(); + bfd->discrs.remote_discr = 0; + bfd->local_ip = bpc->bpc_local; + bfd->local_address = bpc->bpc_local; + bfd->timers.desired_min_tx = bfd->up_min_tx; + bfd->detect_TO = (bfd->detect_mult * BFD_DEF_SLOWTX); + + /* Use detect_TO first for slow detection, then use recvtimer_update. */ + bfd_recvtimer_update(bfd); + + bfd_id_insert(bfd); + + if (bpc->bpc_mhop) { + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH); + bfd->mhop.peer = bpc->bpc_peer; + bfd->mhop.local = bpc->bpc_local; + if (bpc->bpc_has_vrfname) + strlcpy(bfd->mhop.vrf_name, bpc->bpc_vrfname, + sizeof(bfd->mhop.vrf_name)); + + bfd_mhop_insert(bfd); + } else { + bfd->shop.peer = bpc->bpc_peer; + if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) + strlcpy(bfd->shop.port_name, bpc->bpc_localif, + sizeof(bfd->shop.port_name)); + + bfd_shop_insert(bfd); + } + + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) { + static uint8_t bfd_def_vxlan_dmac[] = {0x00, 0x23, 0x20, + 0x00, 0x00, 0x01}; + memcpy(bfd->peer_mac, bfd_def_vxlan_dmac, + sizeof(bfd_def_vxlan_dmac)); + } +#if 0 /* TODO */ + else if (event->rmac) { + if (sscanf(event->rmac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &bfd->peer_mac[0], &bfd->peer_mac[1], &bfd->peer_mac[2], + &bfd->peer_mac[3], &bfd->peer_mac[4], &bfd->peer_mac[5]) + != 6) + DLOG("%s: Assigning remote mac = %s", __func__, + event->rmac); + } +#endif + + /* + * XXX: session update triggers echo start, so we must have our + * discriminator ID set first. + */ + _bfd_session_update(bfd, bpc); + + /* Start transmitting with slow interval until peer responds */ + bfd->xmt_TO = BFD_DEF_SLOWTX; + + ptm_bfd_xmt_TO(bfd, 0); + + log_info("session-new: %s", bs_to_string(bfd)); + + control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd); + + return bfd; +} + +int ptm_bfd_ses_del(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bs; + + /* Find session and call free(). */ + bs = bs_peer_find(bpc); + if (bs == NULL) + return -1; + + /* This pointer is being referenced, don't let it be deleted. */ + if (bs->refcount > 0) { + log_error("session-delete: refcount failure: %" PRIu64 + " references", + bs->refcount); + return -1; + } + + log_info("session-delete: %s", bs_to_string(bs)); + + control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs); + + bfd_session_free(bs); + + return 0; +} + +void bfd_set_polling(struct bfd_session *bs) +{ + bs->new_timers.desired_min_tx = bs->up_min_tx; + bs->new_timers.required_min_rx = bs->timers.required_min_rx; + bs->new_timers.required_min_echo = bs->timers.required_min_echo; + bs->polling = 1; +} + + +/* + * Helper functions. + */ +static const char *get_diag_str(int diag) +{ + for (int i = 0; diag_list[i].str; i++) { + if (diag_list[i].type == diag) + return diag_list[i].str; + } + return "N/A"; +} + +const char *satostr(struct sockaddr_any *sa) +{ +#define INETSTR_BUFCOUNT 8 + static char buf[INETSTR_BUFCOUNT][INET6_ADDRSTRLEN]; + static int bufidx; + struct sockaddr_in *sin = &sa->sa_sin; + struct sockaddr_in6 *sin6 = &sa->sa_sin6; + + bufidx += (bufidx + 1) % INETSTR_BUFCOUNT; + buf[bufidx][0] = 0; + + switch (sin->sin_family) { + case AF_INET: + inet_ntop(AF_INET, &sin->sin_addr, buf[bufidx], + sizeof(buf[bufidx])); + break; + case AF_INET6: + inet_ntop(AF_INET6, &sin6->sin6_addr, buf[bufidx], + sizeof(buf[bufidx])); + break; + + default: + strlcpy(buf[bufidx], "unknown", sizeof(buf[bufidx])); + break; + } + + return buf[bufidx]; +} + +const char *diag2str(uint8_t diag) +{ + switch (diag) { + case 0: + return "ok"; + case 1: + return "control detection time expired"; + case 2: + return "echo function failed"; + case 3: + return "neighbor signaled session down"; + case 4: + return "forwarding plane reset"; + case 5: + return "path down"; + case 6: + return "concatenated path down"; + case 7: + return "administratively down"; + case 8: + return "reverse concatenated path down"; + default: + return "unknown"; + } +} + +int strtosa(const char *addr, struct sockaddr_any *sa) +{ + memset(sa, 0, sizeof(*sa)); + + if (inet_pton(AF_INET, addr, &sa->sa_sin.sin_addr) == 1) { + sa->sa_sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin.sin_len = sizeof(sa->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return 0; + } + + if (inet_pton(AF_INET6, addr, &sa->sa_sin6.sin6_addr) == 1) { + sa->sa_sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return 0; + } + + return -1; +} + +void integer2timestr(uint64_t time, char *buf, size_t buflen) +{ + unsigned int year, month, day, hour, minute, second; + int rv; + +#define MINUTES (60) +#define HOURS (24 * MINUTES) +#define DAYS (30 * HOURS) +#define MONTHS (12 * DAYS) +#define YEARS (MONTHS) + if (time >= YEARS) { + year = time / YEARS; + time -= year * YEARS; + + rv = snprintf(buf, buflen, "%u year(s), ", year); + buf += rv; + buflen -= rv; + } + if (time >= MONTHS) { + month = time / MONTHS; + time -= month * MONTHS; + + rv = snprintf(buf, buflen, "%u month(s), ", month); + buf += rv; + buflen -= rv; + } + if (time >= DAYS) { + day = time / DAYS; + time -= day * DAYS; + + rv = snprintf(buf, buflen, "%u day(s), ", day); + buf += rv; + buflen -= rv; + } + if (time >= HOURS) { + hour = time / HOURS; + time -= hour * HOURS; + + rv = snprintf(buf, buflen, "%u hour(s), ", hour); + buf += rv; + buflen -= rv; + } + if (time >= MINUTES) { + minute = time / MINUTES; + time -= minute * MINUTES; + + rv = snprintf(buf, buflen, "%u minute(s), ", minute); + buf += rv; + buflen -= rv; + } + second = time % MINUTES; + snprintf(buf, buflen, "%u second(s)", second); +} + +const char *bs_to_string(struct bfd_session *bs) +{ + static char buf[256]; + int pos; + bool is_mhop = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH); + + pos = snprintf(buf, sizeof(buf), "mhop:%s", is_mhop ? "yes" : "no"); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + pos += snprintf(buf + pos, sizeof(buf) - pos, + " peer:%s local:%s", satostr(&bs->mhop.peer), + satostr(&bs->mhop.local)); + + if (bs->mhop.vrf_name[0]) + snprintf(buf + pos, sizeof(buf) - pos, " vrf:%s", + bs->mhop.vrf_name); + } else { + pos += snprintf(buf + pos, sizeof(buf) - pos, " peer:%s", + satostr(&bs->shop.peer)); + + if (bs->local_address.sa_sin.sin_family) + pos += snprintf(buf + pos, sizeof(buf) - pos, + " local:%s", + satostr(&bs->local_address)); + + if (bs->shop.port_name[0]) + snprintf(buf + pos, sizeof(buf) - pos, " interface:%s", + bs->shop.port_name); + } + + return buf; +} + + +/* + * BFD hash data structures to find sessions. + */ +static struct hash *bfd_id_hash; +static struct hash *bfd_shop_hash; +static struct hash *bfd_mhop_hash; +static struct hash *bfd_vrf_hash; +static struct hash *bfd_iface_hash; + +static unsigned int bfd_id_hash_do(void *p); +static int bfd_id_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_shop_hash_do(void *p); +static int bfd_shop_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_mhop_hash_do(void *p); +static int bfd_mhop_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_vrf_hash_do(void *p); +static int bfd_vrf_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_iface_hash_do(void *p); +static int bfd_iface_hash_cmp(const void *n1, const void *n2); + +static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop); +static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop); +static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop); +static int _iface_key(struct bfd_iface *iface, const char *ifname); + +static void _bfd_free(struct hash_backet *hb, + void *arg __attribute__((__unused__))); +static void _vrf_free(void *arg); +static void _iface_free(void *arg); + +/* BFD hash for our discriminator. */ +static unsigned int bfd_id_hash_do(void *p) +{ + struct bfd_session *bs = p; + + return jhash_1word(bs->discrs.my_discr, 0); +} + +static int bfd_id_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + return bs1->discrs.my_discr == bs2->discrs.my_discr; +} + +/* BFD hash for single hop. */ +static unsigned int bfd_shop_hash_do(void *p) +{ + struct bfd_session *bs = p; + + return jhash(&bs->shop, sizeof(bs->shop), 0); +} + +static int bfd_shop_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + return memcmp(&bs1->shop, &bs2->shop, sizeof(bs1->shop)) == 0; +} + +/* BFD hash for multi hop. */ +static unsigned int bfd_mhop_hash_do(void *p) +{ + struct bfd_session *bs = p; + + return jhash(&bs->mhop, sizeof(bs->mhop), 0); +} + +static int bfd_mhop_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + return memcmp(&bs1->mhop, &bs2->mhop, sizeof(bs1->mhop)) == 0; +} + +/* BFD hash for VRFs. */ +static unsigned int bfd_vrf_hash_do(void *p) +{ + struct bfd_vrf *vrf = p; + + return jhash_1word(vrf->vrf_id, 0); +} + +static int bfd_vrf_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_vrf *v1 = n1, *v2 = n2; + + return v1->vrf_id == v2->vrf_id; +} + +/* BFD hash for interfaces. */ +static unsigned int bfd_iface_hash_do(void *p) +{ + struct bfd_iface *iface = p; + + return string_hash_make(iface->ifname); +} + +static int bfd_iface_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_iface *i1 = n1, *i2 = n2; + + return strcmp(i1->ifname, i2->ifname) == 0; +} + +/* Helper functions */ +static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop) +{ + bs->shop = *shop; + + /* Remove unused fields. */ + switch (bs->shop.peer.sa_sin.sin_family) { + case AF_INET: + bs->shop.peer.sa_sin.sin_port = 0; + break; + case AF_INET6: + bs->shop.peer.sa_sin6.sin6_port = 0; + break; + } +} + +static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop) +{ + _shop_key(bs, shop); + memset(bs->shop.port_name, 0, sizeof(bs->shop.port_name)); +} + +static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop) +{ + bs->mhop = *mhop; + + /* Remove unused fields. */ + switch (bs->mhop.peer.sa_sin.sin_family) { + case AF_INET: + bs->mhop.peer.sa_sin.sin_port = 0; + bs->mhop.local.sa_sin.sin_port = 0; + break; + case AF_INET6: + bs->mhop.peer.sa_sin6.sin6_port = 0; + bs->mhop.local.sa_sin6.sin6_port = 0; + break; + } +} + +static int _iface_key(struct bfd_iface *iface, const char *ifname) +{ + size_t slen = sizeof(iface->ifname); + + memset(iface->ifname, 0, slen); + if (strlcpy(iface->ifname, ifname, slen) >= slen) + return -1; + + return 0; +} + +/* + * Hash public interface / exported functions. + */ + +/* Lookup functions. */ +struct bfd_session *bfd_id_lookup(uint32_t id) +{ + struct bfd_session bs; + + bs.discrs.my_discr = id; + + return hash_lookup(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop) +{ + struct bfd_session bs, *bsp; + + _shop_key(&bs, &shop); + + bsp = hash_lookup(bfd_shop_hash, &bs); + if (bsp == NULL && bs.shop.port_name[0] != 0) { + /* + * Since the local interface spec is optional, try + * searching the key without it as well. + */ + _shop_key2(&bs, &shop); + bsp = hash_lookup(bfd_shop_hash, &bs); + } + + return bsp; +} + +struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop) +{ + struct bfd_session bs; + + _mhop_key(&bs, &mhop); + + return hash_lookup(bfd_shop_hash, &bs); +} + +struct bfd_vrf *bfd_vrf_lookup(int vrf_id) +{ + struct bfd_vrf vrf; + + vrf.vrf_id = vrf_id; + + return hash_lookup(bfd_vrf_hash, &vrf); +} + +struct bfd_iface *bfd_iface_lookup(const char *ifname) +{ + struct bfd_iface iface; + + if (_iface_key(&iface, ifname) != 0) + return NULL; + + return hash_lookup(bfd_iface_hash, &iface); +} + +/* + * Delete functions. + * + * Delete functions searches and remove the item from the hash and + * returns a pointer to the removed item data. If the item was not found + * then it returns NULL. + * + * The data stored inside the hash is not free()ed, so you must do it + * manually after getting the pointer back. + */ +struct bfd_session *bfd_id_delete(uint32_t id) +{ + struct bfd_session bs; + + bs.discrs.my_discr = id; + + return hash_release(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop) +{ + struct bfd_session bs, *bsp; + + _shop_key(&bs, &shop); + bsp = hash_release(bfd_shop_hash, &bs); + if (bsp == NULL && shop.port_name[0] != 0) { + /* + * Since the local interface spec is optional, try + * searching the key without it as well. + */ + _shop_key2(&bs, &shop); + bsp = hash_release(bfd_shop_hash, &bs); + } + + return bsp; +} + +struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop) +{ + struct bfd_session bs; + + _mhop_key(&bs, &mhop); + + return hash_release(bfd_mhop_hash, &bs); +} + +struct bfd_vrf *bfd_vrf_delete(int vrf_id) +{ + struct bfd_vrf vrf; + + vrf.vrf_id = vrf_id; + + return hash_release(bfd_vrf_hash, &vrf); +} + +struct bfd_iface *bfd_iface_delete(const char *ifname) +{ + struct bfd_iface iface; + + if (_iface_key(&iface, ifname) != 0) + return NULL; + + return hash_release(bfd_iface_hash, &iface); +} + +/* Iteration functions. */ +void bfd_id_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_id_hash, hif, arg); +} + +void bfd_shop_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_shop_hash, hif, arg); +} + +void bfd_mhop_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_mhop_hash, hif, arg); +} + +void bfd_vrf_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_vrf_hash, hif, arg); +} + +void bfd_iface_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_iface_hash, hif, arg); +} + +/* + * Insert functions. + * + * Inserts session into hash and returns `true` on success, otherwise + * `false`. + */ +bool bfd_id_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_id_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_shop_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_shop_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_mhop_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_mhop_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_vrf_insert(struct bfd_vrf *vrf) +{ + return (hash_get(bfd_vrf_hash, vrf, hash_alloc_intern) == vrf); +} + +bool bfd_iface_insert(struct bfd_iface *iface) +{ + return (hash_get(bfd_iface_hash, iface, hash_alloc_intern) == iface); +} + +void bfd_initialize(void) +{ + bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp, + "BFD discriminator hash"); + bfd_shop_hash = hash_create(bfd_shop_hash_do, bfd_shop_hash_cmp, + "BFD single hop hash"); + bfd_mhop_hash = hash_create(bfd_mhop_hash_do, bfd_mhop_hash_cmp, + "BFD multihop hop hash"); + bfd_vrf_hash = + hash_create(bfd_vrf_hash_do, bfd_vrf_hash_cmp, "BFD VRF hash"); + bfd_iface_hash = hash_create(bfd_iface_hash_do, bfd_iface_hash_cmp, + "BFD interface hash"); +} + +static void _bfd_free(struct hash_backet *hb, + void *arg __attribute__((__unused__))) +{ + struct bfd_session *bs = hb->data; + + bfd_session_free(bs); +} + +static void _vrf_free(void *arg) +{ + struct bfd_vrf *vrf = arg; + + XFREE(MTYPE_BFDD_CONFIG, vrf); +} + +static void _iface_free(void *arg) +{ + struct bfd_iface *iface = arg; + + XFREE(MTYPE_BFDD_CONFIG, iface); +} + +void bfd_shutdown(void) +{ + /* + * Close and free all BFD sessions. + * + * _bfd_free() will call bfd_session_free() which will take care + * of removing the session from all hashes, so we just run an + * assert() here to make sure it really happened. + */ + bfd_id_iterate(_bfd_free, NULL); + assert(bfd_shop_hash->count == 0); + assert(bfd_mhop_hash->count == 0); + + /* Clean the VRF and interface hashes. */ + hash_clean(bfd_vrf_hash, _vrf_free); + hash_clean(bfd_iface_hash, _iface_free); + + /* Now free the hashes themselves. */ + hash_free(bfd_id_hash); + hash_free(bfd_shop_hash); + hash_free(bfd_mhop_hash); + hash_free(bfd_vrf_hash); + hash_free(bfd_iface_hash); +} diff --git a/bfdd/bfd.h b/bfdd/bfd.h new file mode 100644 index 000000000000..40aecaa67bf2 --- /dev/null +++ b/bfdd/bfd.h @@ -0,0 +1,631 @@ +/********************************************************************* + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd.h: implements the BFD protocol. + */ + +#ifndef _BFD_H_ +#define _BFD_H_ + +#include + +#include +#include +#include + +#include "lib/hash.h" +#include "lib/libfrr.h" +#include "lib/qobj.h" +#include "lib/queue.h" + +#include "bfdctl.h" + +#define ETHERNET_ADDRESS_LENGTH 6 + +#ifdef BFD_DEBUG +#define BFDD_JSON_CONV_OPTIONS (JSON_C_TO_STRING_PRETTY) +#else +#define BFDD_JSON_CONV_OPTIONS (0) +#endif + +DECLARE_MGROUP(BFDD); +DECLARE_MTYPE(BFDD_TMP); +DECLARE_MTYPE(BFDD_CONFIG); +DECLARE_MTYPE(BFDD_LABEL); +DECLARE_MTYPE(BFDD_CONTROL); +DECLARE_MTYPE(BFDD_NOTIFICATION); + +struct bfd_timers { + uint32_t desired_min_tx; + uint32_t required_min_rx; + uint32_t required_min_echo; +}; + +struct bfd_discrs { + uint32_t my_discr; + uint32_t remote_discr; +}; + +/* + * Format of control packet. From section 4) + */ +struct bfd_pkt { + union { + uint32_t byteFields; + struct { + uint8_t diag; + uint8_t flags; + uint8_t detect_mult; + uint8_t len; + }; + }; + struct bfd_discrs discrs; + struct bfd_timers timers; +}; + +/* + * Format of Echo packet. + */ +struct bfd_echo_pkt { + union { + uint32_t byteFields; + struct { + uint8_t ver; + uint8_t len; + uint16_t reserved; + }; + }; + uint32_t my_discr; + uint8_t pad[16]; +}; + + +/* Macros for manipulating control packets */ +#define BFD_VERMASK 0x03 +#define BFD_DIAGMASK 0x1F +#define BFD_GETVER(diag) ((diag >> 5) & BFD_VERMASK) +#define BFD_SETVER(diag, val) ((diag) |= (val & BFD_VERMASK) << 5) +#define BFD_VERSION 1 +#define BFD_PBIT 0x20 +#define BFD_FBIT 0x10 +#define BFD_CBIT 0x08 +#define BFD_ABIT 0x04 +#define BFD_DEMANDBIT 0x02 +#define BFD_DIAGNEIGHDOWN 3 +#define BFD_DIAGDETECTTIME 1 +#define BFD_DIAGADMINDOWN 7 +#define BFD_SETDEMANDBIT(flags, val) \ + { \ + if ((val)) \ + flags |= BFD_DEMANDBIT; \ + } +#define BFD_SETPBIT(flags, val) \ + { \ + if ((val)) \ + flags |= BFD_PBIT; \ + } +#define BFD_GETPBIT(flags) (flags & BFD_PBIT) +#define BFD_SETFBIT(flags, val) \ + { \ + if ((val)) \ + flags |= BFD_FBIT; \ + } +#define BFD_GETFBIT(flags) (flags & BFD_FBIT) +#define BFD_SETSTATE(flags, val) \ + { \ + if ((val)) \ + flags |= (val & 0x3) << 6; \ + } +#define BFD_GETSTATE(flags) ((flags >> 6) & 0x3) +#define BFD_ECHO_VERSION 1 +#define BFD_ECHO_PKT_LEN sizeof(struct bfd_echo_pkt) +#define BFD_CTRL_PKT_LEN sizeof(struct bfd_pkt) +#define IP_HDR_LEN 20 +#define UDP_HDR_LEN 8 +#define ETH_HDR_LEN 14 +#define VXLAN_HDR_LEN 8 +#define HEADERS_MIN_LEN (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN) +#define BFD_ECHO_PKT_TOT_LEN \ + ((int)(ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN + BFD_ECHO_PKT_LEN)) +#define BFD_VXLAN_PKT_TOT_LEN \ + ((int)(VXLAN_HDR_LEN + ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN \ + + BFD_CTRL_PKT_LEN)) +#define BFD_RX_BUF_LEN 160 + +/* BFD session flags */ +enum bfd_session_flags { + BFD_SESS_FLAG_NONE = 0, + BFD_SESS_FLAG_ECHO = 1 << 0, /* BFD Echo functionality */ + BFD_SESS_FLAG_ECHO_ACTIVE = 1 << 1, /* BFD Echo Packets are being sent + * actively + */ + BFD_SESS_FLAG_MH = 1 << 2, /* BFD Multi-hop session */ + BFD_SESS_FLAG_VXLAN = 1 << 3, /* BFD Multi-hop session which is + * used to monitor vxlan tunnel + */ + BFD_SESS_FLAG_IPV6 = 1 << 4, /* BFD IPv6 session */ + BFD_SESS_FLAG_SEND_EVT_ACTIVE = 1 << 5, /* send event timer active */ + BFD_SESS_FLAG_SEND_EVT_IGNORE = 1 << 6, /* ignore send event when timer + * expires + */ + BFD_SESS_FLAG_SHUTDOWN = 1 << 7, /* disable BGP peer function */ +}; + +#define BFD_SET_FLAG(field, flag) (field |= flag) +#define BFD_UNSET_FLAG(field, flag) (field &= ~flag) +#define BFD_CHECK_FLAG(field, flag) (field & flag) + +/* BFD session hash keys */ +struct bfd_shop_key { + struct sockaddr_any peer; + char port_name[MAXNAMELEN + 1]; +}; + +struct bfd_mhop_key { + struct sockaddr_any peer; + struct sockaddr_any local; + char vrf_name[MAXNAMELEN + 1]; +}; + +struct bfd_session_stats { + uint64_t rx_ctrl_pkt; + uint64_t tx_ctrl_pkt; + uint64_t rx_echo_pkt; + uint64_t tx_echo_pkt; +}; + +struct bfd_session_vxlan_info { + uint32_t vnid; + uint32_t decay_min_rx; + uint8_t forwarding_if_rx; + uint8_t cpath_down; + uint8_t check_tnl_key; + uint8_t local_dst_mac[ETHERNET_ADDRESS_LENGTH]; + uint8_t peer_dst_mac[ETHERNET_ADDRESS_LENGTH]; + struct in_addr local_dst_ip; + struct in_addr peer_dst_ip; +}; + +/* bfd_session shortcut label forwarding. */ +struct peer_label; + +/* + * Session state information + */ +struct bfd_session { + + /* protocol state per RFC 5880*/ + uint8_t ses_state; + struct bfd_discrs discrs; + uint8_t local_diag; + uint8_t demand_mode; + uint8_t detect_mult; + uint8_t remote_detect_mult; + uint8_t mh_ttl; + + /* Timers */ + struct bfd_timers timers; + struct bfd_timers new_timers; + uint32_t up_min_tx; + uint64_t detect_TO; + struct thread *echo_recvtimer_ev; + struct thread *recvtimer_ev; + uint64_t xmt_TO; + uint64_t echo_xmt_TO; + struct thread *xmttimer_ev; + struct thread *echo_xmttimer_ev; + uint64_t echo_detect_TO; + + /* software object state */ + uint8_t polling; + + /* This and the localDiscr are the keys to state info */ + struct peer_label *pl; + union { + struct bfd_shop_key shop; + struct bfd_mhop_key mhop; + }; + int sock; + + struct sockaddr_any local_address; + struct sockaddr_any local_ip; + int ifindex; + uint8_t local_mac[ETHERNET_ADDRESS_LENGTH]; + uint8_t peer_mac[ETHERNET_ADDRESS_LENGTH]; + uint16_t ip_id; + + /* BFD session flags */ + enum bfd_session_flags flags; + + uint8_t echo_pkt[BFD_ECHO_PKT_TOT_LEN]; /* Save the Echo Packet + * which will be transmitted + */ + struct bfd_session_stats stats; + struct bfd_session_vxlan_info vxlan_info; + + struct timeval uptime; /* last up time */ + struct timeval downtime; /* last down time */ + + /* Remote peer data (for debugging mostly) */ + uint8_t remote_diag; + struct bfd_timers remote_timers; + + uint64_t refcount; /* number of pointers referencing this. */ + + /* VTY context data. */ + QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(bfd_session); + +struct peer_label { + TAILQ_ENTRY(peer_label) pl_entry; + + struct bfd_session *pl_bs; + char pl_label[MAXNAMELEN]; +}; +TAILQ_HEAD(pllist, peer_label); + +struct bfd_diag_str_list { + const char *str; + int type; +}; + +struct bfd_state_str_list { + const char *str; + int type; +}; + +struct bfd_vrf { + int vrf_id; + char name[MAXNAMELEN + 1]; +} bfd_vrf; + +struct bfd_iface { + int vrf_id; + char ifname[MAXNAMELEN + 1]; +} bfd_iface; + + +/* States defined per 4.1 */ +#define PTM_BFD_ADM_DOWN 0 +#define PTM_BFD_DOWN 1 +#define PTM_BFD_INIT 2 +#define PTM_BFD_UP 3 + + +/* Various constants */ +/* Retrieved from ptm_timer.h from Cumulus PTM sources. */ +#define MSEC_PER_SEC 1000L +#define NSEC_PER_MSEC 1000000L + +#define BFD_DEF_DEMAND 0 +#define BFD_DEFDETECTMULT 3 +#define BFD_DEFDESIREDMINTX (300 * MSEC_PER_SEC) +#define BFD_DEFREQUIREDMINRX (300 * MSEC_PER_SEC) +#define BFD_DEF_REQ_MIN_ECHO (50 * MSEC_PER_SEC) +#define BFD_DEF_SLOWTX (2000 * MSEC_PER_SEC) +#define BFD_DEF_MHOP_TTL 5 +#define BFD_PKT_LEN 24 /* Length of control packet */ +#define BFD_TTL_VAL 255 +#define BFD_RCV_TTL_VAL 1 +#define BFD_TOS_VAL 0xC0 +#define BFD_PKT_INFO_VAL 1 +#define BFD_IPV6_PKT_INFO_VAL 1 +#define BFD_IPV6_ONLY_VAL 1 +#define BFD_SRCPORTINIT 49142 +#define BFD_SRCPORTMAX 65536 +#define BFD_DEFDESTPORT 3784 +#define BFD_DEF_ECHO_PORT 3785 +#define BFD_DEF_MHOP_DEST_PORT 4784 +#define BFD_CMD_STRING_LEN (MAXNAMELEN + 50) +#define BFD_BUFFER_LEN (BFD_CMD_STRING_LEN + MAXNAMELEN + 1) + +/* + * control.c + * + * Daemon control code to speak with local consumers. + */ + +/* See 'bfdctrl.h' for client protocol definitions. */ + +struct bfd_control_buffer { + size_t bcb_left; + size_t bcb_pos; + union { + struct bfd_control_msg *bcb_bcm; + uint8_t *bcb_buf; + }; +}; + +struct bfd_control_queue { + TAILQ_ENTRY(bfd_control_queue) bcq_entry; + + struct bfd_control_buffer bcq_bcb; +}; +TAILQ_HEAD(bcqueue, bfd_control_queue); + +struct bfd_notify_peer { + TAILQ_ENTRY(bfd_notify_peer) bnp_entry; + + struct bfd_session *bnp_bs; +}; +TAILQ_HEAD(bnplist, bfd_notify_peer); + +struct bfd_control_socket { + TAILQ_ENTRY(bfd_control_socket) bcs_entry; + + int bcs_sd; + struct thread *bcs_ev; + struct thread *bcs_outev; + struct bcqueue bcs_bcqueue; + + /* Notification data */ + uint64_t bcs_notify; + struct bnplist bcs_bnplist; + + enum bc_msg_version bcs_version; + enum bc_msg_type bcs_type; + + /* Message buffering */ + struct bfd_control_buffer bcs_bin; + struct bfd_control_buffer *bcs_bout; +}; +TAILQ_HEAD(bcslist, bfd_control_socket); + +int control_init(const char *path); +void control_shutdown(void); +int control_notify(struct bfd_session *bs); +int control_notify_config(const char *op, struct bfd_session *bs); +int control_accept(struct thread *t); + + +/* + * bfdd.c + * + * Daemon specific code. + */ +struct bfd_global { + int bg_shop; + int bg_mhop; + int bg_shop6; + int bg_mhop6; + int bg_echo; + int bg_vxlan; + struct thread *bg_ev[6]; + + int bg_csock; + struct thread *bg_csockev; + struct bcslist bg_bcslist; + + struct pllist bg_pllist; +}; +extern struct bfd_global bglobal; +extern struct bfd_diag_str_list diag_list[]; +extern struct bfd_state_str_list state_list[]; + +void socket_close(int *s); + + +/* + * config.c + * + * Contains the code related with loading/reloading configuration. + */ +int parse_config(const char *fname); +int config_request_add(const char *jsonstr); +int config_request_del(const char *jsonstr); +char *config_response(const char *status, const char *error); +char *config_notify(struct bfd_session *bs); +char *config_notify_config(const char *op, struct bfd_session *bs); + +typedef int (*bpc_handle)(struct bfd_peer_cfg *, void *arg); +int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, + bpc_handle bh); + +struct peer_label *pl_new(const char *label, struct bfd_session *bs); +struct peer_label *pl_find(const char *label); +void pl_free(struct peer_label *pl); + + +/* + * log.c + * + * Contains code that does the logging procedures. Might implement multiple + * backends (e.g. zebra log, syslog or other logging lib). + */ +enum blog_level { + /* level vs syslog equivalent */ + BLOG_DEBUG = 0, /* LOG_DEBUG */ + BLOG_INFO = 1, /* LOG_INFO */ + BLOG_WARNING = 2, /* LOG_WARNING */ + BLOG_ERROR = 3, /* LOG_ERR */ + BLOG_FATAL = 4, /* LOG_CRIT */ +}; + +void log_init(int foreground, enum blog_level level, + struct frr_daemon_info *fdi); +void log_info(const char *fmt, ...); +void log_debug(const char *fmt, ...); +void log_warning(const char *fmt, ...); +void log_error(const char *fmt, ...); +void log_fatal(const char *fmt, ...); + + +/* + * bfd_packet.c + * + * Contains the code related with receiving/seding, packing/unpacking BFD data. + */ +int bp_set_ttlv6(int sd); +int bp_set_ttl(int sd); +int bp_set_tosv6(int sd); +int bp_set_tos(int sd); +int bp_bind_dev(int sd, const char *dev); + +int bp_udp_shop(void); +int bp_udp_mhop(void); +int bp_udp6_shop(void); +int bp_udp6_mhop(void); +int bp_peer_socket(struct bfd_peer_cfg *bpc); +int bp_peer_socketv6(struct bfd_peer_cfg *bpc); + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit); +void ptm_bfd_echo_snd(struct bfd_session *bfd); + +int bfd_recv_cb(struct thread *t); + +uint16_t checksum(uint16_t *buf, int len); + + +/* + * event.c + * + * Contains the code related with event loop. + */ +typedef void (*bfd_ev_cb)(struct thread *t); + +void bfd_recvtimer_update(struct bfd_session *bs); +void bfd_echo_recvtimer_update(struct bfd_session *bs); +void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter); +void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter); + +void bfd_xmttimer_delete(struct bfd_session *bs); +void bfd_echo_xmttimer_delete(struct bfd_session *bs); +void bfd_recvtimer_delete(struct bfd_session *bs); +void bfd_echo_recvtimer_delete(struct bfd_session *bs); + +void bfd_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_echo_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); +void bfd_echo_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); + + +/* + * bfd.c + * + * BFD protocol specific code. + */ +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc); +int ptm_bfd_ses_del(struct bfd_peer_cfg *bpc); +void ptm_bfd_ses_dn(struct bfd_session *bfd, uint8_t diag); +void ptm_bfd_ses_up(struct bfd_session *bfd); +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen); +void ptm_bfd_echo_stop(struct bfd_session *bfd, int polling); +void ptm_bfd_echo_start(struct bfd_session *bfd); +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit); +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo); +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, char *port_name, + struct sockaddr_any *peer, + struct sockaddr_any *local, + char *vrf_name, bool is_mhop); + +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc); +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel); +void bfd_set_polling(struct bfd_session *bs); +const char *satostr(struct sockaddr_any *sa); +const char *diag2str(uint8_t diag); +int strtosa(const char *addr, struct sockaddr_any *sa); +void integer2timestr(uint64_t time, char *buf, size_t buflen); +const char *bs_to_string(struct bfd_session *bs); + +/* BFD hash data structures interface */ +void bfd_initialize(void); +void bfd_shutdown(void); +struct bfd_session *bfd_id_lookup(uint32_t id); +struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop); +struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop); +struct bfd_vrf *bfd_vrf_lookup(int vrf_id); +struct bfd_iface *bfd_iface_lookup(const char *ifname); + +struct bfd_session *bfd_id_delete(uint32_t id); +struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop); +struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop); +struct bfd_vrf *bfd_vrf_delete(int vrf_id); +struct bfd_iface *bfd_iface_delete(const char *ifname); + +bool bfd_id_insert(struct bfd_session *bs); +bool bfd_shop_insert(struct bfd_session *bs); +bool bfd_mhop_insert(struct bfd_session *bs); +bool bfd_vrf_insert(struct bfd_vrf *vrf); +bool bfd_iface_insert(struct bfd_iface *iface); + +typedef void (*hash_iter_func)(struct hash_backet *hb, void *arg); +void bfd_id_iterate(hash_iter_func hif, void *arg); +void bfd_shop_iterate(hash_iter_func hif, void *arg); +void bfd_mhop_iterate(hash_iter_func hif, void *arg); +void bfd_vrf_iterate(hash_iter_func hif, void *arg); +void bfd_iface_iterate(hash_iter_func hif, void *arg); + +/* Export callback functions for `event.c`. */ +extern struct thread_master *master; + +int bfd_recvtimer_cb(struct thread *t); +int bfd_echo_recvtimer_cb(struct thread *t); +int bfd_xmt_cb(struct thread *t); +int bfd_echo_xmt_cb(struct thread *t); + + +/* + * bfdd_vty.c + * + * BFD daemon vty shell commands. + */ +void bfdd_vty_init(void); + + +/* + * ptm_adapter.c + */ +void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv); +void bfdd_zclient_stop(void); + +int ptm_bfd_notify(struct bfd_session *bs); + + +/* + * OS compatibility functions. + */ +struct udp_psuedo_header { + uint32_t saddr; + uint32_t daddr; + uint8_t reserved; + uint8_t protocol; + uint16_t len; +}; + +#define UDP_PSUEDO_HDR_LEN sizeof(struct udp_psuedo_header) + +#if defined(BFD_LINUX) || defined(BFD_BSD) +int ptm_bfd_fetch_ifindex(const char *ifname); +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac); +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen); +int ptm_bfd_echo_sock_init(void); +int ptm_bfd_vxlan_sock_init(void); +#endif /* BFD_LINUX || BFD_BSD */ + +#ifdef BFD_LINUX +uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len); +#endif /* BFD_LINUX */ + +#ifdef BFD_BSD +uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len); +ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen, + struct sockaddr_storage *ss, socklen_t *sslen, + uint8_t *ttl, uint32_t *id); +#endif /* BFD_BSD */ + +#endif /* _BFD_H_ */ diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c new file mode 100644 index 000000000000..19cb8547ab33 --- /dev/null +++ b/bfdd/bfd_packet.c @@ -0,0 +1,1533 @@ +/********************************************************************* + * Copyright 2017 Cumulus Networks, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd_packet.c: implements the BFD protocol packet handling. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include + +#ifdef BFD_LINUX +#include +#endif /* BFD_LINUX */ + +#include +#include + +#include "lib/sockopt.h" + +#include "bfd.h" + +/* + * Definitions + */ + +/* iov for BFD control frames */ +#define CMSG_HDR_LEN sizeof(struct cmsghdr) +#define CMSG_TTL_LEN (CMSG_HDR_LEN + sizeof(uint32_t)) +#define CMSG_IN_PKT_INFO_LEN (CMSG_HDR_LEN + sizeof(struct in_pktinfo) + 4) +#define CMSG_IN6_PKT_INFO_LEN \ + (CMSG_HDR_LEN + sizeof(struct in6_addr) + sizeof(int) + 4) + +struct bfd_raw_echo_pkt { +#ifdef BFD_LINUX + struct iphdr ip; +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + struct ip ip; +#endif /* BFD_BSD */ + struct udphdr udp; + struct bfd_echo_pkt data; +}; + +#if 0 /* TODO: VxLAN support. */ +struct bfd_raw_ctrl_pkt { + struct iphdr ip; + struct udphdr udp; + struct bfd_pkt data; +}; +#endif + +struct vxlan_hdr { + uint32_t flags; + uint32_t vnid; +}; + +#define IP_ECHO_PKT_LEN (IP_HDR_LEN + UDP_HDR_LEN + BFD_ECHO_PKT_LEN) +#define UDP_ECHO_PKT_LEN (UDP_HDR_LEN + BFD_ECHO_PKT_LEN) +#define IP_CTRL_PKT_LEN (IP_HDR_LEN + UDP_HDR_LEN + BFD_PKT_LEN) +#define UDP_CTRL_PKT_LEN (UDP_HDR_LEN + BFD_PKT_LEN) + +static uint8_t msgbuf[BFD_PKT_LEN]; +static struct iovec msgiov = {&(msgbuf[0]), sizeof(msgbuf)}; +static uint8_t cmsgbuf[255]; + +static struct sockaddr_in msgaddr; +static struct msghdr msghdr = {(void *)&msgaddr, sizeof(msgaddr), &msgiov, 1, + (void *)&cmsgbuf, sizeof(cmsgbuf), 0}; + +static uint8_t cmsgbuf6[255]; + +static struct sockaddr_in6 msgaddr6; +static struct msghdr msghdr6 = {(void *)&msgaddr6, sizeof(msgaddr6), &msgiov, 1, + (void *)&cmsgbuf6, sizeof(cmsgbuf6), 0}; + +static int ttlval = BFD_TTL_VAL; +static int tosval = BFD_TOS_VAL; +static int rcvttl = BFD_RCV_TTL_VAL; + +/* + * Prototypes + */ +static uint16_t ptm_bfd_gen_IP_ID(struct bfd_session *bfd); +static void ptm_bfd_echo_pkt_create(struct bfd_session *bfd); +static int ptm_bfd_echo_loopback(uint8_t *pkt, int pkt_len, struct sockaddr *ss, + socklen_t sslen); +static void ptm_bfd_vxlan_pkt_snd(struct bfd_session *bfd, int fbit); +static int ptm_bfd_process_echo_pkt(int s); +static bool +ptm_bfd_validate_vxlan_pkt(struct bfd_session *bfd, + struct bfd_session_vxlan_info *vxlan_info); + +static void bfd_sd_reschedule(int sd); +static ssize_t bfd_recv_ipv4(int sd, bool is_mhop, char *port, size_t portlen, + char *vrfname, size_t vrfnamelen, + struct sockaddr_any *local, + struct sockaddr_any *peer); +static ssize_t bfd_recv_ipv6(int sd, bool is_mhop, char *port, size_t portlen, + char *vrfname, size_t vrfnamelen, + struct sockaddr_any *local, + struct sockaddr_any *peer); + +/* socket related prototypes */ +static void bp_set_ipopts(int sd); +static void bp_bind_ip(int sd, uint16_t port); +static void bp_set_ipv6opts(int sd); +static void bp_bind_ipv6(int sd, uint16_t port); + + +/* + * Functions + */ +uint16_t checksum(uint16_t *buf, int len) +{ + int nbytes = len; + int sum = 0; + uint16_t csum = 0; + int size = sizeof(uint16_t); + + while (nbytes > 1) { + sum += *buf++; + nbytes -= size; + } + + if (nbytes == 1) { + *(uint8_t *)(&csum) = *(uint8_t *)buf; + sum += csum; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + csum = ~sum; + return csum; +} + +static uint16_t ptm_bfd_gen_IP_ID(struct bfd_session *bfd) +{ + return (++bfd->ip_id); +} + +static int _ptm_bfd_send(struct bfd_session *bs, bool use_layer2, + uint16_t *port, const void *data, size_t datalen) +{ + struct sockaddr *sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +#ifdef BFD_LINUX + struct sockaddr_ll dll; +#endif /* BFD_LINUX */ + socklen_t slen; + ssize_t rv; + int sd = -1; + + if (use_layer2) { +#ifdef BFD_LINUX + memset(&dll, 0, sizeof(dll)); + dll.sll_family = AF_PACKET; + dll.sll_protocol = htons(ETH_P_IP); + memcpy(dll.sll_addr, bs->peer_mac, ETHERNET_ADDRESS_LENGTH); + dll.sll_halen = htons(ETHERNET_ADDRESS_LENGTH); + dll.sll_ifindex = bs->ifindex; + + sd = bglobal.bg_echo; + sa = (struct sockaddr *)&dll; + slen = sizeof(dll); +#else + /* + * TODO: implement layer 2 send for *BSDs. This is + * needed for VxLAN. + */ + log_warning("packet-send: not implemented"); + return -1; +#endif + } else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = bs->shop.peer.sa_sin6.sin6_addr; + sin6.sin6_port = + (port) ? *port + : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? htons(BFD_DEF_MHOP_DEST_PORT) + : htons(BFD_DEFDESTPORT); + + sd = bs->sock; + sa = (struct sockaddr *)&sin6; + slen = sizeof(sin6); + } else { + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr = bs->shop.peer.sa_sin.sin_addr; + sin.sin_port = + (port) ? *port + : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? htons(BFD_DEF_MHOP_DEST_PORT) + : htons(BFD_DEFDESTPORT); + + sd = bs->sock; + sa = (struct sockaddr *)&sin; + slen = sizeof(sin); + } + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_len = slen; +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + rv = sendto(sd, data, datalen, 0, sa, slen); + if (rv <= 0) { + log_debug("packet-send: send failure: %s", strerror(errno)); + return -1; + } + if (rv < (ssize_t)datalen) + log_debug("packet-send: send partial", strerror(errno)); + + return 0; +} + +static void ptm_bfd_echo_pkt_create(struct bfd_session *bfd) +{ + struct bfd_raw_echo_pkt ep; + uint8_t *pkt = bfd->echo_pkt; + + memset(&ep, 0, sizeof(ep)); + memset(bfd->echo_pkt, 0, sizeof(bfd->echo_pkt)); + + /* Construct ethernet header information */ + memcpy(pkt, bfd->peer_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; + memcpy(pkt, bfd->local_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; +#ifdef BFD_LINUX + pkt[0] = ETH_P_IP / 256; + pkt[1] = ETH_P_IP % 256; +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + pkt[0] = ETHERTYPE_IP / 256; + pkt[1] = ETHERTYPE_IP % 256; +#endif /* BFD_BSD */ + pkt += 2; + + /* Construct IP header information */ +#ifdef BFD_LINUX + ep.ip.version = 4; + ep.ip.ihl = 5; + ep.ip.tos = 0; + ep.ip.tot_len = htons(IP_ECHO_PKT_LEN); + ep.ip.id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep.ip.frag_off = 0; + ep.ip.ttl = BFD_TTL_VAL; + ep.ip.protocol = IPPROTO_UDP; + ep.ip.saddr = bfd->local_ip.sa_sin.sin_addr.s_addr; + ep.ip.daddr = bfd->shop.peer.sa_sin.sin_addr.s_addr; + ep.ip.check = checksum((uint16_t *)&ep.ip, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep.ip.ip_v = 4; + ep.ip.ip_hl = 5; + ep.ip.ip_tos = 0; + ep.ip.ip_len = htons(IP_ECHO_PKT_LEN); + ep.ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep.ip.ip_off = 0; + ep.ip.ip_ttl = BFD_TTL_VAL; + ep.ip.ip_p = IPPROTO_UDP; + ep.ip.ip_src = bfd->local_ip.sa_sin.sin_addr; + ep.ip.ip_dst = bfd->shop.peer.sa_sin.sin_addr; + ep.ip.ip_sum = checksum((uint16_t *)&ep.ip, IP_HDR_LEN); +#endif /* BFD_BSD */ + + /* Construct UDP header information */ +#ifdef BFD_LINUX + ep.udp.source = htons(BFD_DEF_ECHO_PORT); + ep.udp.dest = htons(BFD_DEF_ECHO_PORT); + ep.udp.len = htons(UDP_ECHO_PKT_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep.udp.uh_sport = htons(BFD_DEF_ECHO_PORT); + ep.udp.uh_dport = htons(BFD_DEF_ECHO_PORT); + ep.udp.uh_ulen = htons(UDP_ECHO_PKT_LEN); +#endif /* BFD_BSD */ + + /* Construct Echo packet information */ + ep.data.ver = BFD_ECHO_VERSION; + ep.data.len = BFD_ECHO_PKT_LEN; + ep.data.my_discr = htonl(bfd->discrs.my_discr); +#ifdef BFD_LINUX + ep.udp.check = +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep.udp.uh_sum = +#endif /* BFD_BSD */ + udp4_checksum(&ep.ip, (uint8_t *)&ep.udp, + UDP_ECHO_PKT_LEN); + + memcpy(pkt, &ep, sizeof(ep)); +} + +void ptm_bfd_echo_snd(struct bfd_session *bfd) +{ + struct bfd_raw_echo_pkt *ep; + bool use_layer2 = false; + const void *pkt; + size_t pktlen; + uint16_t port = htons(BFD_DEF_ECHO_PORT); + + if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + ptm_bfd_echo_pkt_create(bfd); + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + } else { + /* just update the checksum and ip Id */ + ep = (struct bfd_raw_echo_pkt *)(bfd->echo_pkt + ETH_HDR_LEN); +#ifdef BFD_LINUX + ep->ip.id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep->ip.check = 0; + ep->ip.check = checksum((uint16_t *)&ep->ip, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep->ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep->ip.ip_sum = 0; + ep->ip.ip_sum = checksum((uint16_t *)&ep->ip, IP_HDR_LEN); +#endif /* BFD_BSD */ + } + + if (use_layer2) { + pkt = bfd->echo_pkt; + pktlen = BFD_ECHO_PKT_TOT_LEN; + } else { + pkt = &bfd->echo_pkt[ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN]; + pktlen = BFD_ECHO_PKT_TOT_LEN + - (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN); + } + + if (_ptm_bfd_send(bfd, use_layer2, &port, pkt, pktlen) != 0) { + log_debug("echo-packet: send failure: %s", strerror(errno)); + return; + } + + bfd->stats.tx_echo_pkt++; +} + +static int ptm_bfd_echo_loopback(uint8_t *pkt, int pkt_len, struct sockaddr *ss, + socklen_t sslen) +{ +#ifdef BFD_LINUX + struct bfd_raw_echo_pkt *ep = + (struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN); + uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH]; + uint32_t temp_ip; + struct ethhdr *eth = (struct ethhdr *)pkt; + + /* swap the mac addresses */ + memcpy(temp_mac, eth->h_source, ETHERNET_ADDRESS_LENGTH); + memcpy(eth->h_source, eth->h_dest, ETHERNET_ADDRESS_LENGTH); + memcpy(eth->h_dest, temp_mac, ETHERNET_ADDRESS_LENGTH); + + /* swap ip addresses */ + temp_ip = ep->ip.saddr; + ep->ip.saddr = ep->ip.daddr; + ep->ip.daddr = temp_ip; + + ep->ip.ttl = ep->ip.ttl - 1; + ep->ip.check = 0; + ep->ip.check = checksum((uint16_t *)ep, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD_FILTER + struct bfd_raw_echo_pkt_t *ep = + (struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN); + uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH]; + struct in_addr temp_ip; + struct ether_header *ether = (struct ether_header *)pkt; + + /* + * TODO: this is not yet implemented and requires BPF code for + * OmniOS, NetBSD and FreeBSD9. + */ + + /* swap the mac addresses */ + memcpy(temp_mac, ether->ether_shost, ETHERNET_ADDRESS_LENGTH); + memcpy(ether->ether_shost, ether->ether_dhost, ETHERNET_ADDRESS_LENGTH); + memcpy(ether->ether_dhost, temp_mac, ETHERNET_ADDRESS_LENGTH); + + /* swap ip addresses */ + temp_ip = ep->ip.ip_src; + ep->ip.ip_src = ep->ip.ip_dst; + ep->ip.ip_dst = temp_ip; + + ep->ip.ip_ttl = ep->ip.ip_ttl - 1; + ep->ip.ip_sum = 0; + ep->ip.ip_sum = checksum((uint16_t *)ep, IP_HDR_LEN); +#endif /* BFD_BSD_FILTER */ + + if (sendto(bglobal.bg_echo, pkt, pkt_len, 0, ss, sslen) < 0) { + log_debug("echo-loopback: send failure: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static void ptm_bfd_vxlan_pkt_snd(struct bfd_session *bfd + __attribute__((__unused__)), + int fbit __attribute__((__unused__))) +{ +#if 0 /* TODO: VxLAN support. */ + struct bfd_raw_ctrl_pkt cp; + uint8_t vxlan_pkt[BFD_VXLAN_PKT_TOT_LEN]; + uint8_t *pkt = vxlan_pkt; + struct sockaddr_in sin; + struct vxlan_hdr *vhdr; + + memset(vxlan_pkt, 0, sizeof(vxlan_pkt)); + memset(&cp, 0, sizeof(cp)); + + /* Construct VxLAN header information */ + vhdr = (struct vxlan_hdr *)pkt; + vhdr->flags = htonl(0x08000000); + vhdr->vnid = htonl(bfd->vxlan_info.vnid << 8); + pkt += VXLAN_HDR_LEN; + + /* Construct ethernet header information */ + memcpy(pkt, bfd->vxlan_info.peer_dst_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; + memcpy(pkt, bfd->vxlan_info.local_dst_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; + pkt[0] = ETH_P_IP / 256; + pkt[1] = ETH_P_IP % 256; + pkt += 2; + + /* Construct IP header information */ + cp.ip.version = 4; + cp.ip.ihl = 5; + cp.ip.tos = 0; + cp.ip.tot_len = htons(IP_CTRL_PKT_LEN); + cp.ip.id = ptm_bfd_gen_IP_ID(bfd); + cp.ip.frag_off = 0; + cp.ip.ttl = BFD_TTL_VAL; + cp.ip.protocol = IPPROTO_UDP; + cp.ip.daddr = bfd->vxlan_info.peer_dst_ip.s_addr; + cp.ip.saddr = bfd->vxlan_info.local_dst_ip.s_addr; + cp.ip.check = checksum((uint16_t *)&cp.ip, IP_HDR_LEN); + + /* Construct UDP header information */ + cp.udp.source = htons(BFD_DEFDESTPORT); + cp.udp.dest = htons(BFD_DEFDESTPORT); + cp.udp.len = htons(UDP_CTRL_PKT_LEN); + + /* Construct BFD control packet information */ + cp.data.diag = bfd->local_diag; + BFD_SETVER(cp.data.diag, BFD_VERSION); + BFD_SETSTATE(cp.data.flags, bfd->ses_state); + BFD_SETDEMANDBIT(cp.data.flags, BFD_DEF_DEMAND); + BFD_SETPBIT(cp.data.flags, bfd->polling); + BFD_SETFBIT(cp.data.flags, fbit); + cp.data.detect_mult = bfd->detect_mult; + cp.data.len = BFD_PKT_LEN; + cp.data.discrs.my_discr = htonl(bfd->discrs.my_discr); + cp.data.discrs.remote_discr = htonl(bfd->discrs.remote_discr); + cp.data.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx); + cp.data.timers.required_min_rx = htonl(bfd->timers.required_min_rx); + cp.data.timers.required_min_echo = htonl(bfd->timers.required_min_echo); + + cp.udp.check = + udp4_checksum(&cp.ip, (uint8_t *)&cp.udp, UDP_CTRL_PKT_LEN); + + memcpy(pkt, &cp, sizeof(cp)); + sin.sin_family = AF_INET; + sin.sin_addr = bfd->shop.peer.sa_sin.sin_addr; + sin.sin_port = htons(4789); + + if (sendto(bfd->sock, vxlan_pkt, BFD_VXLAN_PKT_TOT_LEN, 0, + (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) + < 0) { + ERRLOG("Error sending vxlan bfd pkt: %s", strerror(errno)); + } else { + bfd->stats.tx_ctrl_pkt++; + } +#endif +} + +static int ptm_bfd_process_echo_pkt(int s) +{ + uint32_t my_discr = 0; + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + uint8_t rx_pkt[BFD_RX_BUF_LEN]; + ssize_t pkt_len = sizeof(rx_pkt); + struct bfd_session *bfd; +#ifdef BFD_LINUX + struct bfd_raw_echo_pkt *ep; + + /* + * valgrind: memset() ss so valgrind doesn't complain about + * uninitialized memory. + */ + memset(&ss, 0, sizeof(ss)); + pkt_len = recvfrom(s, rx_pkt, sizeof(rx_pkt), MSG_DONTWAIT, + (struct sockaddr *)&ss, &sslen); + if (pkt_len <= 0) { + if (errno != EAGAIN) + log_error("echo-packet: read failure: %s", + strerror(errno)); + + return -1; + } + + /* Check if we have at least the basic headers to send back. */ + if (pkt_len < BFD_ECHO_PKT_TOT_LEN) { + log_debug("echo-packet: too short (got %ld, expected %d)", + pkt_len, BFD_ECHO_PKT_TOT_LEN); + return -1; + } + + ep = (struct bfd_raw_echo_pkt *)(rx_pkt + ETH_HDR_LEN); + /* if TTL = 255, assume that the received echo packet has + * to be looped back + */ + if (ep->ip.ttl == BFD_TTL_VAL) + return ptm_bfd_echo_loopback(rx_pkt, pkt_len, + (struct sockaddr *)&ss, + sizeof(struct sockaddr_ll)); + + my_discr = ntohl(ep->data.my_discr); + if (ep->data.my_discr == 0) { + log_debug("echo-packet: 'my discriminator' is zero"); + return -1; + } +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + int rv; + uint8_t ttl; + + /* + * bsd_echo_sock_read() already treats invalid TTL values and + * zeroed discriminators. + */ + rv = bsd_echo_sock_read(s, rx_pkt, &pkt_len, &ss, &sslen, &ttl, + &my_discr); + if (rv == -1) + return -1; + + if (ttl == BFD_TTL_VAL) + return ptm_bfd_echo_loopback(rx_pkt, pkt_len, + (struct sockaddr *)&ss, sslen); +#endif /* BFD_BSD */ + + /* Your discriminator not zero - use it to find session */ + bfd = bfd_id_lookup(my_discr); + if (bfd == NULL) { + log_debug("echo-packet: no matching session (id:%u)", my_discr); + return -1; + } + + if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + log_debug("echo-packet: echo disabled [%s]", my_discr, + bs_to_string(bfd)); + return -1; + } + + bfd->stats.rx_echo_pkt++; + + /* Compute detect time */ + bfd->echo_detect_TO = bfd->remote_detect_mult * bfd->echo_xmt_TO; + + /* Update echo receive timeout. */ + bfd_echo_recvtimer_update(bfd); + + return 0; +} + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit) +{ + struct bfd_pkt cp; + + /* if the BFD session is for VxLAN tunnel, then construct and + * send bfd raw packet + */ + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) { + ptm_bfd_vxlan_pkt_snd(bfd, fbit); + return; + } + + /* Set fields according to section 6.5.7 */ + cp.diag = bfd->local_diag; + BFD_SETVER(cp.diag, BFD_VERSION); + cp.flags = 0; + BFD_SETSTATE(cp.flags, bfd->ses_state); + BFD_SETDEMANDBIT(cp.flags, BFD_DEF_DEMAND); + BFD_SETPBIT(cp.flags, bfd->polling); + BFD_SETFBIT(cp.flags, fbit); + cp.detect_mult = bfd->detect_mult; + cp.len = BFD_PKT_LEN; + cp.discrs.my_discr = htonl(bfd->discrs.my_discr); + cp.discrs.remote_discr = htonl(bfd->discrs.remote_discr); + if (bfd->polling) { + cp.timers.desired_min_tx = + htonl(bfd->new_timers.desired_min_tx); + cp.timers.required_min_rx = + htonl(bfd->new_timers.required_min_rx); + } else { + cp.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx); + cp.timers.required_min_rx = htonl(bfd->timers.required_min_rx); + } + cp.timers.required_min_echo = htonl(bfd->timers.required_min_echo); + + if (_ptm_bfd_send(bfd, false, NULL, &cp, BFD_PKT_LEN) != 0) + return; + + bfd->stats.tx_ctrl_pkt++; +} + +#if 0 /* TODO VxLAN Support */ +static struct bfd_pkt * +ptm_bfd_process_vxlan_pkt(int s, ptm_sockevent_e se, void *udata, int *ifindex, + struct sockaddr_in *sin, + struct bfd_session_vxlan_info_t *vxlan_info, + uint8_t *rx_pkt, int *mlen) +{ + struct sockaddr_ll sll; + uint32_t from_len = sizeof(struct sockaddr_ll); + struct bfd_raw_ctrl_pkt *cp; + uint8_t *pkt = rx_pkt; + struct iphdr *iph; + struct ethhdr *inner_ethh; + + *mlen = recvfrom(s, rx_pkt, BFD_RX_BUF_LEN, MSG_DONTWAIT, + (struct sockaddr *)&sll, &from_len); + + if (*mlen < 0) { + if (errno != EAGAIN) + ERRLOG("Error receiving from BFD Vxlan socket %d: %m", + s); + return NULL; + } + + iph = (struct iphdr *)(pkt + ETH_HDR_LEN); + pkt = pkt + ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN; + vxlan_info->vnid = ntohl(*((int *)(pkt + 4))); + vxlan_info->vnid = vxlan_info->vnid >> 8; + + pkt = pkt + VXLAN_HDR_LEN; + inner_ethh = (struct ethhdr *)pkt; + + cp = (struct bfd_raw_ctrl_pkt *)(pkt + ETH_HDR_LEN); + + /* Discard the non BFD packets */ + if (ntohs(cp->udp.dest) != BFD_DEFDESTPORT) + return NULL; + + *ifindex = sll.sll_ifindex; + sin->sin_addr.s_addr = iph->saddr; + sin->sin_port = ntohs(cp->udp.dest); + + vxlan_info->local_dst_ip.s_addr = cp->ip.daddr; + memcpy(vxlan_info->local_dst_mac, inner_ethh->h_dest, + ETHERNET_ADDRESS_LENGTH); + + return &cp->data; +} +#endif /* VxLAN */ + +static bool +ptm_bfd_validate_vxlan_pkt(struct bfd_session *bfd, + struct bfd_session_vxlan_info *vxlan_info) +{ + if (bfd->vxlan_info.check_tnl_key && (vxlan_info->vnid != 0)) { + log_error("vxlan-packet: vnid not zero: %d", vxlan_info->vnid); + return false; + } + + if (bfd->vxlan_info.local_dst_ip.s_addr + != vxlan_info->local_dst_ip.s_addr) { + log_error("vxlan-packet: wrong inner destination", + inet_ntoa(vxlan_info->local_dst_ip)); + return false; + } + + if (memcmp(bfd->vxlan_info.local_dst_mac, vxlan_info->local_dst_mac, + ETHERNET_ADDRESS_LENGTH)) { + log_error( + "vxlan-packet: wrong inner mac: %02x:%02x:%02x:%02x:%02x:%02x", + vxlan_info->local_dst_mac[0], + vxlan_info->local_dst_mac[1], + vxlan_info->local_dst_mac[2], + vxlan_info->local_dst_mac[3], + vxlan_info->local_dst_mac[4], + vxlan_info->local_dst_mac[5]); + return false; + } + + return true; +} + +static ssize_t bfd_recv_ipv4(int sd, bool is_mhop, char *port, size_t portlen, + char *vrfname, size_t vrfnamelen, + struct sockaddr_any *local, + struct sockaddr_any *peer) +{ + struct cmsghdr *cm; + int ifindex; + ssize_t mlen; + + memset(port, 0, portlen); + memset(vrfname, 0, vrfnamelen); + memset(local, 0, sizeof(*local)); + memset(peer, 0, sizeof(*peer)); + + mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT); + if (mlen == -1) { + if (errno != EAGAIN) + log_error("ipv4-recv: recv failed: %s", + strerror(errno)); + + return -1; + } + + /* Get source address */ + peer->sa_sin = *((struct sockaddr_in *)(msghdr.msg_name)); + + /* Get and check TTL */ + for (cm = CMSG_FIRSTHDR(&msghdr); cm != NULL; + cm = CMSG_NXTHDR(&msghdr, cm)) { + if (cm->cmsg_level != IPPROTO_IP) + continue; + + switch (cm->cmsg_type) { +#ifdef BFD_LINUX + case IP_TTL: { + uint32_t ttl; + + memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl)); + if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) { + log_debug( + "ipv4-recv: invalid TTL from %s (expected %d, got %d flags %d)", + satostr(peer), ttl, BFD_TTL_VAL, + msghdr.msg_flags); + return -1; + } + break; + } + + case IP_PKTINFO: { + struct in_pktinfo *pi = + (struct in_pktinfo *)CMSG_DATA(cm); + + if (pi == NULL) + break; + + local->sa_sin.sin_family = AF_INET; + local->sa_sin.sin_addr = pi->ipi_addr; + fetch_portname_from_ifindex(pi->ipi_ifindex, port, + portlen); + break; + } +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + case IP_RECVTTL: { + uint8_t ttl; + + memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl)); + if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) { + log_debug( + "ipv4-recv: invalid TTL from %s (expected %d, got %d flags %d)", + satostr(peer), ttl, BFD_TTL_VAL, + msghdr.msg_flags); + return -1; + } + break; + } + + case IP_RECVDSTADDR: { + struct in_addr ia; + + memcpy(&ia, CMSG_DATA(cm), sizeof(ia)); + local->sa_sin.sin_family = AF_INET; + local->sa_sin.sin_addr = ia; + break; + } +#endif /* BFD_BSD */ + + default: + /* + * On *BSDs we expect to land here when skipping + * the IP_RECVIF header. It will be handled by + * getsockopt_ifindex() below. + */ + /* NOTHING */ + break; + } + } + + /* OS agnostic way of getting interface name. */ + if (port[0] == 0) { + ifindex = getsockopt_ifindex(AF_INET, &msghdr); + if (ifindex > 0) + fetch_portname_from_ifindex(ifindex, port, portlen); + } + + return mlen; +} + +ssize_t bfd_recv_ipv6(int sd, bool is_mhop, char *port, size_t portlen, + char *vrfname, size_t vrfnamelen, + struct sockaddr_any *local, struct sockaddr_any *peer) +{ + struct cmsghdr *cm; + struct in6_pktinfo *pi6 = NULL; + int ifindex = 0; + ssize_t mlen; + + memset(port, 0, portlen); + memset(vrfname, 0, vrfnamelen); + memset(local, 0, sizeof(*local)); + memset(peer, 0, sizeof(*peer)); + + mlen = recvmsg(sd, &msghdr6, MSG_DONTWAIT); + if (mlen == -1) { + if (errno != EAGAIN) + log_error("ipv4-recv: recv failed: %s", + strerror(errno)); + + return -1; + } + + /* Get source address */ + peer->sa_sin6 = *((struct sockaddr_in6 *)(msghdr6.msg_name)); + + /* Get and check TTL */ + for (cm = CMSG_FIRSTHDR(&msghdr6); cm != NULL; + cm = CMSG_NXTHDR(&msghdr6, cm)) { + if (cm->cmsg_level != IPPROTO_IPV6) + continue; + + if (cm->cmsg_type == IPV6_HOPLIMIT) { + memcpy(&ttlval, CMSG_DATA(cm), 4); + if ((is_mhop == false) && (ttlval != BFD_TTL_VAL)) { + log_debug( + "ipv6-recv: invalid TTL from %s (expected %d, got %d flags %d)", + satostr(peer), ttlval, BFD_TTL_VAL, + msghdr.msg_flags); + return -1; + } + } else if (cm->cmsg_type == IPV6_PKTINFO) { + pi6 = (struct in6_pktinfo *)CMSG_DATA(cm); + if (pi6) { + local->sa_sin.sin_family = AF_INET6; + local->sa_sin6.sin6_addr = pi6->ipi6_addr; + fetch_portname_from_ifindex(pi6->ipi6_ifindex, + port, portlen); + ifindex = pi6->ipi6_ifindex; + } + } + } + + /* Set scope ID for link local addresses. */ + if (IN6_IS_ADDR_LINKLOCAL(&peer->sa_sin6.sin6_addr)) + peer->sa_sin6.sin6_scope_id = ifindex; + if (IN6_IS_ADDR_LINKLOCAL(&local->sa_sin6.sin6_addr)) + local->sa_sin6.sin6_scope_id = ifindex; + + return mlen; +} + +static void bfd_sd_reschedule(int sd) +{ + if (sd == bglobal.bg_shop) { + bglobal.bg_ev[0] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop, + &bglobal.bg_ev[0]); + } else if (sd == bglobal.bg_mhop) { + bglobal.bg_ev[1] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop, + &bglobal.bg_ev[1]); + } else if (sd == bglobal.bg_shop6) { + bglobal.bg_ev[2] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6, + &bglobal.bg_ev[2]); + } else if (sd == bglobal.bg_mhop6) { + bglobal.bg_ev[3] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6, + &bglobal.bg_ev[3]); + } else if (sd == bglobal.bg_echo) { + bglobal.bg_ev[4] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo, + &bglobal.bg_ev[4]); + } else if (sd == bglobal.bg_vxlan) { + bglobal.bg_ev[5] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan, + &bglobal.bg_ev[5]); + } +} + +static void cp_debug(bool mhop, struct sockaddr_any *peer, + struct sockaddr_any *local, const char *port, + const char *vrf, const char *fmt, ...) +{ + char buf[512], peerstr[128], localstr[128], portstr[64], vrfstr[64]; + va_list vl; + + if (peer->sa_sin.sin_family) + snprintf(peerstr, sizeof(peerstr), " peer:%s", satostr(peer)); + else + peerstr[0] = 0; + + if (local->sa_sin.sin_family) + snprintf(localstr, sizeof(localstr), " local:%s", + satostr(local)); + else + localstr[0] = 0; + + if (port[0]) + snprintf(portstr, sizeof(portstr), " port:%s", port); + else + portstr[0] = 0; + + if (vrf[0]) + snprintf(vrfstr, sizeof(vrfstr), " vrf:%s", port); + else + vrfstr[0] = 0; + + va_start(vl, fmt); + vsnprintf(buf, sizeof(buf), fmt, vl); + va_end(vl); + + log_debug("control-packet: %s [mhop:%s%s%s%s%s]", buf, + mhop ? "yes" : "no", peerstr, localstr, portstr, vrfstr); +} + +int bfd_recv_cb(struct thread *t) +{ + int sd = THREAD_FD(t); + struct bfd_session *bfd; + struct bfd_pkt *cp; + bool is_mhop, is_vxlan; + ssize_t mlen = 0; + uint32_t oldEchoXmt_TO, oldXmtTime; + struct sockaddr_any local, peer; + char port[MAXNAMELEN + 1], vrfname[MAXNAMELEN + 1]; + struct bfd_session_vxlan_info vxlan_info; + + /* Schedule next read. */ + bfd_sd_reschedule(sd); + + /* Handle echo packets. */ + if (sd == bglobal.bg_echo) { + ptm_bfd_process_echo_pkt(sd); + return 0; + } + + /* Handle control packets. */ + is_mhop = is_vxlan = false; + if (sd == bglobal.bg_shop || sd == bglobal.bg_mhop) { + is_mhop = sd == bglobal.bg_mhop; + mlen = bfd_recv_ipv4(sd, is_mhop, port, sizeof(port), vrfname, + sizeof(vrfname), &local, &peer); + } else if (sd == bglobal.bg_shop6 || sd == bglobal.bg_mhop6) { + is_mhop = sd == bglobal.bg_mhop6; + mlen = bfd_recv_ipv6(sd, is_mhop, port, sizeof(port), vrfname, + sizeof(vrfname), &local, &peer); + } +#if 0 /* TODO vxlan handling */ + cp = ptm_bfd_process_vxlan_pkt(s, se, udata, &local_ifindex, + &sin, &vxlan_info, rx_pkt, &mlen); + if (!cp) + return -1; + + is_vxlan = true; + /* keep in network-byte order */ + peer.ip4_addr.s_addr = sin.sin_addr.s_addr; + peer.family = AF_INET; + strcpy(peer_addr, inet_ntoa(sin.sin_addr)); +#endif + + /* Implement RFC 5880 6.8.6 */ + if (mlen < BFD_PKT_LEN) { + cp_debug(is_mhop, &peer, &local, port, vrfname, + "too small (%ld bytes)", mlen); + return 0; + } + + /* + * Parse the control header for inconsistencies: + * - Invalid version; + * - Bad multiplier configuration; + * - Short packets; + * - Invalid discriminator; + */ + cp = (struct bfd_pkt *)(msghdr.msg_iov->iov_base); + if (BFD_GETVER(cp->diag) != BFD_VERSION) { + cp_debug(is_mhop, &peer, &local, port, vrfname, + "bad version %d", BFD_GETVER(cp->diag)); + return 0; + } + + if (cp->detect_mult == 0) { + cp_debug(is_mhop, &peer, &local, port, vrfname, + "detect multiplier set to zero"); + return 0; + } + + if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) { + cp_debug(is_mhop, &peer, &local, port, vrfname, "too small"); + return 0; + } + + if (cp->discrs.my_discr == 0) { + cp_debug(is_mhop, &peer, &local, port, vrfname, + "'my discriminator' is zero"); + return 0; + } + + /* Find the session that this packet belongs. */ + bfd = ptm_bfd_sess_find(cp, port, &peer, &local, vrfname, is_mhop); + if (bfd == NULL) { + cp_debug(is_mhop, &peer, &local, port, vrfname, + "no session found"); + return 0; + } + + /* Handle VxLAN cases. */ + if (is_vxlan && !ptm_bfd_validate_vxlan_pkt(bfd, &vxlan_info)) + return 0; + + bfd->stats.rx_ctrl_pkt++; + + /* + * Multi hop: validate packet TTL. + * Single hop: set local address that received the packet. + */ + if (is_mhop) { + if ((BFD_TTL_VAL - bfd->mh_ttl) > ttlval) { + cp_debug(is_mhop, &peer, &local, port, vrfname, + "exceeded max hop count (expected %d, got %d)", + bfd->mh_ttl, ttlval); + return 0; + } + } else if (bfd->local_ip.sa_sin.sin_family == AF_UNSPEC) { + bfd->local_ip = local; + } + + /* + * If no interface was detected, save the interface where the + * packet came in. + */ + if (bfd->ifindex == 0) + bfd->ifindex = ptm_bfd_fetch_ifindex(port); + + /* Log remote discriminator changes. */ + if ((bfd->discrs.remote_discr != 0) + && (bfd->discrs.remote_discr != ntohl(cp->discrs.my_discr))) + cp_debug(is_mhop, &peer, &local, port, vrfname, + "remote discriminator mismatch (expected %d, got %d)", + bfd->discrs.remote_discr, ntohl(cp->discrs.my_discr)); + + bfd->discrs.remote_discr = ntohl(cp->discrs.my_discr); + + /* If received the Final bit, the new values should take effect */ + if (bfd->polling && BFD_GETFBIT(cp->flags)) { + bfd->timers.desired_min_tx = bfd->new_timers.desired_min_tx; + bfd->timers.required_min_rx = bfd->new_timers.required_min_rx; + bfd->new_timers.desired_min_tx = 0; + bfd->new_timers.required_min_rx = 0; + bfd->polling = 0; + } + + if (!bfd->demand_mode) { + /* Compute detect time */ + bfd->detect_TO = cp->detect_mult + * ((bfd->timers.required_min_rx + > ntohl(cp->timers.desired_min_tx)) + ? bfd->timers.required_min_rx + : ntohl(cp->timers.desired_min_tx)); + bfd->remote_detect_mult = cp->detect_mult; + } else + cp_debug(is_mhop, &peer, &local, port, vrfname, + "unsupported demand mode"); + + /* Save remote diagnostics before state switch. */ + bfd->remote_diag = cp->diag & BFD_DIAGMASK; + + /* State switch from section 6.8.6 */ + if (BFD_GETSTATE(cp->flags) == PTM_BFD_ADM_DOWN) { + if (bfd->ses_state != PTM_BFD_DOWN) + ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN); + } else { + switch (bfd->ses_state) { + case (PTM_BFD_DOWN): + if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT) + ptm_bfd_ses_up(bfd); + else if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN) + bfd->ses_state = PTM_BFD_INIT; + break; + case (PTM_BFD_INIT): + if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT + || BFD_GETSTATE(cp->flags) == PTM_BFD_UP) + ptm_bfd_ses_up(bfd); + break; + case (PTM_BFD_UP): + if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN) + ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN); + break; + } + } + + /* + * Handle echo packet status: + * - Start echo packets if configured and permitted + * (required_min_echo > 0); + * - Stop echo packets if not allowed (required_min_echo == 0); + * - Recalculate echo packet interval; + */ + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO)) { + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + if (!ntohl(cp->timers.required_min_echo)) { + ptm_bfd_echo_stop(bfd, 1); + } else { + oldEchoXmt_TO = bfd->echo_xmt_TO; + bfd->echo_xmt_TO = + bfd->timers.required_min_echo; + if (ntohl(cp->timers.required_min_echo) + > bfd->echo_xmt_TO) + bfd->echo_xmt_TO = ntohl( + cp->timers.required_min_echo); + if (oldEchoXmt_TO != bfd->echo_xmt_TO) + ptm_bfd_echo_start(bfd); + } + } else if (ntohl(cp->timers.required_min_echo)) { + bfd->echo_xmt_TO = bfd->timers.required_min_echo; + if (ntohl(cp->timers.required_min_echo) + > bfd->echo_xmt_TO) + bfd->echo_xmt_TO = + ntohl(cp->timers.required_min_echo); + ptm_bfd_echo_start(bfd); + } + } + + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + bfd->echo_xmt_TO = bfd->timers.required_min_echo; + if (ntohl(cp->timers.required_min_echo) > bfd->echo_xmt_TO) + bfd->echo_xmt_TO = ntohl(cp->timers.required_min_echo); + } + + /* Calculate new transmit time */ + oldXmtTime = bfd->xmt_TO; + bfd->xmt_TO = + (bfd->timers.desired_min_tx > ntohl(cp->timers.required_min_rx)) + ? bfd->timers.desired_min_tx + : ntohl(cp->timers.required_min_rx); + + /* If transmit time has changed, and too much time until next xmt, + * restart + */ + if (BFD_GETPBIT(cp->flags)) { + ptm_bfd_xmt_TO(bfd, 1); + } else if (oldXmtTime != bfd->xmt_TO) { + /* XXX add some skid to this as well */ + ptm_bfd_start_xmt_timer(bfd, false); + } + + /* Restart detection timer (packet received) */ + if (!bfd->demand_mode) + bfd_recvtimer_update(bfd); + + /* + * Save the timers and state sent by the remote end + * for debugging and statistics. + */ + if (BFD_GETFBIT(cp->flags)) { + bfd->remote_timers.desired_min_tx = + ntohl(cp->timers.desired_min_tx); + bfd->remote_timers.required_min_rx = + ntohl(cp->timers.required_min_rx); + bfd->remote_timers.required_min_echo = + ntohl(cp->timers.required_min_echo); + + control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bfd); + } + + return 0; +} + + +/* + * Sockets creation. + */ + + +/* + * IPv4 sockets + */ +int bp_set_ttl(int sd) +{ + if (setsockopt(sd, IPPROTO_IP, IP_TTL, &ttlval, sizeof(ttlval)) == -1) { + log_warning("%s: setsockopt(IP_TTL): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +int bp_set_tos(int sd) +{ + if (setsockopt(sd, IPPROTO_IP, IP_TOS, &tosval, sizeof(tosval)) == -1) { + log_warning("%s: setsockopt(IP_TOS): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +static void bp_set_ipopts(int sd) +{ + if (bp_set_ttl(sd) != 0) + log_fatal("%s: TTL configuration failed", __func__); + + if (setsockopt(sd, IPPROTO_IP, IP_RECVTTL, &rcvttl, sizeof(rcvttl)) + == -1) + log_fatal("%s: setsockopt(IP_RECVTTL): %s", __func__, + strerror(errno)); + +#ifdef BFD_LINUX + int pktinfo = BFD_PKT_INFO_VAL; + /* Figure out address and interface to do the peer matching. */ + if (setsockopt(sd, IPPROTO_IP, IP_PKTINFO, &pktinfo, sizeof(pktinfo)) + == -1) + log_fatal("%s: setsockopt(IP_PKTINFO): %s", __func__, + strerror(errno)); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + int yes = 1; + + /* Find out our address for peer matching. */ + if (setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) == -1) + log_fatal("%s: setsockopt(IP_RECVDSTADDR): %s", __func__, + strerror(errno)); + + /* Find out interface where the packet came in. */ + if (setsockopt_ifindex(AF_INET, sd, yes) == -1) + log_fatal("%s: setsockopt_ipv4_ifindex: %s", __func__, + strerror(errno)); +#endif /* BFD_BSD */ +} + +static void bp_bind_ip(int sd, uint16_t port) +{ + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(port); + if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) + log_fatal("%s: bind: %s", __func__, strerror(errno)); +} + +int bp_udp_shop(void) +{ + int sd; + + sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipopts(sd); + bp_bind_ip(sd, BFD_DEFDESTPORT); + + return sd; +} + +int bp_udp_mhop(void) +{ + int sd; + + sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipopts(sd); + bp_bind_ip(sd, BFD_DEF_MHOP_DEST_PORT); + + return sd; +} + +int bp_peer_socket(struct bfd_peer_cfg *bpc) +{ + int sd, pcount; + struct sockaddr_in sin; + static int srcPort = BFD_SRCPORTINIT; + + sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) { + log_error("ipv4-new: failed to create socket: %s", + strerror(errno)); + return -1; + } + + if (!bpc->bpc_has_vxlan) { + /* Set TTL to 255 for all transmitted packets */ + if (bp_set_ttl(sd) != 0) { + close(sd); + return -1; + } + } + + /* Set TOS to CS6 for all transmitted packets */ + if (bp_set_tos(sd) != 0) { + close(sd); + return -1; + } + + /* dont bind-to-device incase of vxlan */ + if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) { + if (bp_bind_dev(sd, bpc->bpc_localif) != 0) { + close(sd); + return -1; + } + } else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) { + if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) { + close(sd); + return -1; + } + } + + /* Find an available source port in the proper range */ + memset(&sin, 0, sizeof(sin)); + sin = bpc->bpc_local.sa_sin; + sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + if (bpc->bpc_mhop || bpc->bpc_has_vxlan) + sin.sin_addr = bpc->bpc_local.sa_sin.sin_addr; + else + sin.sin_addr.s_addr = INADDR_ANY; + + pcount = 0; + do { + if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { + /* Searched all ports, none available */ + log_error("ipv4-new: failed to bind port: %s", + strerror(errno)); + close(sd); + return -1; + } + if (srcPort >= BFD_SRCPORTMAX) + srcPort = BFD_SRCPORTINIT; + sin.sin_port = htons(srcPort++); + } while (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0); + + return sd; +} + + +/* + * IPv6 sockets + */ + +int bp_peer_socketv6(struct bfd_peer_cfg *bpc) +{ + int sd, pcount, ifindex; + struct sockaddr_in6 sin6; + static int srcPort = BFD_SRCPORTINIT; + + sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) { + log_error("ipv6-new: failed to create socket: %s", + strerror(errno)); + return -1; + } + + if (!bpc->bpc_has_vxlan) { + /* Set TTL to 255 for all transmitted packets */ + if (bp_set_ttlv6(sd) != 0) { + close(sd); + return -1; + } + } + + /* Set TOS to CS6 for all transmitted packets */ + if (bp_set_tosv6(sd) != 0) { + close(sd); + return -1; + } + + /* Find an available source port in the proper range */ + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + sin6 = bpc->bpc_local.sa_sin6; + ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); + if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = ifindex; + + if (bpc->bpc_has_localif) { + if (bp_bind_dev(sd, bpc->bpc_localif) != 0) { + close(sd); + return -1; + } + } else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) { + if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) { + close(sd); + return -1; + } + } + + pcount = 0; + do { + if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { + /* Searched all ports, none available */ + log_error("ipv6-new: failed to bind port: %s", + strerror(errno)); + close(sd); + return -1; + } + if (srcPort >= BFD_SRCPORTMAX) + srcPort = BFD_SRCPORTINIT; + sin6.sin6_port = htons(srcPort++); + } while (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0); + + return sd; +} + +int bp_set_ttlv6(int sd) +{ + if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval, + sizeof(ttlval)) + == -1) { + log_warning("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +int bp_set_tosv6(int sd) +{ + if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &tosval, sizeof(tosval)) + == -1) { + log_warning("%s: setsockopt(IPV6_TCLASS): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +static void bp_set_ipv6opts(int sd) +{ + static int ipv6_pktinfo = BFD_IPV6_PKT_INFO_VAL; + static int ipv6_only = BFD_IPV6_ONLY_VAL; + + if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval, + sizeof(ttlval)) + == -1) + log_fatal("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__, + strerror(errno)); + + if (setsockopt_ipv6_hoplimit(sd, rcvttl) == -1) + log_fatal("%s: setsockopt(IPV6_HOPLIMIT): %s", __func__, + strerror(errno)); + + if (setsockopt_ipv6_pktinfo(sd, ipv6_pktinfo) == -1) + log_fatal("%s: setsockopt(IPV6_PKTINFO): %s", __func__, + strerror(errno)); + + if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, + sizeof(ipv6_only)) + == -1) + log_fatal("%s: setsockopt(IPV6_V6ONLY): %s", __func__, + strerror(errno)); +} + +static void bp_bind_ipv6(int sd, uint16_t port) +{ + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_any; + sin6.sin6_port = htons(port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + if (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1) + log_fatal("%s: bind: %s", __func__, strerror(errno)); +} + +int bp_udp6_shop(void) +{ + int sd; + + sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipv6opts(sd); + bp_bind_ipv6(sd, BFD_DEFDESTPORT); + + return sd; +} + +int bp_udp6_mhop(void) +{ + int sd; + + sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipv6opts(sd); + bp_bind_ipv6(sd, BFD_DEF_MHOP_DEST_PORT); + + return sd; +} diff --git a/bfdd/bfdctl.h b/bfdd/bfdctl.h new file mode 100644 index 000000000000..940efd161447 --- /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 + */ + +#ifndef _BFDCTRL_H_ +#define _BFDCTRL_H_ + +#include + +#include +#include + +/* + * 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 000000000000..144619088d3a --- /dev/null +++ b/bfdd/bfdd.c @@ -0,0 +1,236 @@ +/* + * BFD daemon code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#include "bfd.h" +#include "lib/version.h" + + +/* + * FRR related code. + */ +DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon"); +DEFINE_MTYPE(BFDD, BFDD_TMP, "short-lived temporary memory"); +DEFINE_MTYPE(BFDD, BFDD_CONFIG, "long-lived configuration memory"); +DEFINE_MTYPE(BFDD, BFDD_LABEL, "long-lived label memory"); +DEFINE_MTYPE(BFDD, BFDD_CONTROL, "long-lived control socket memory"); +DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "short-lived control notification data"); + +/* Master of threads. */ +struct thread_master *master; + +/* BFDd privileges */ +static zebra_capabilities_t _caps_p[] = {ZCAP_BIND}; + +struct zebra_privs_t bfdd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +void socket_close(int *s) +{ + if (*s <= 0) + return; + + if (close(*s) != 0) + log_error("%s: close(%d): (%d) %s", __func__, *s, errno, + strerror(errno)); + + *s = -1; +} + +static void sigusr1_handler(void) +{ + zlog_rotate(); +} + +static void sigterm_handler(void) +{ + /* Signalize shutdown. */ + frr_early_fini(); + + /* Stop receiving message from zebra. */ + bfdd_zclient_stop(); + + /* Shutdown controller to avoid receiving anymore commands. */ + control_shutdown(); + + /* Shutdown and free all protocol related memory. */ + bfd_shutdown(); + + /* Close all descriptors. */ + socket_close(&bglobal.bg_echo); + socket_close(&bglobal.bg_shop); + socket_close(&bglobal.bg_mhop); + socket_close(&bglobal.bg_shop6); + socket_close(&bglobal.bg_mhop6); + socket_close(&bglobal.bg_vxlan); + + /* Terminate and free() FRR related memory. */ + frr_fini(); + + exit(0); +} + +static struct quagga_signal_t bfd_signals[] = { + { + .signal = SIGUSR1, + .handler = &sigusr1_handler, + }, + { + .signal = SIGTERM, + .handler = &sigterm_handler, + }, + { + .signal = SIGINT, + .handler = &sigterm_handler, + }, +}; + +FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617, + .proghelp = "Implementation of the BFD protocol.", + .signals = bfd_signals, .n_signals = array_size(bfd_signals), + .privs = &bfdd_privs) + +#define OPTION_CTLSOCK 1001 +static struct option longopts[] = { + {"bfdctl", required_argument, NULL, OPTION_CTLSOCK}, + {0} +}; + + +/* + * BFD daemon related code. + */ +struct bfd_global bglobal; + +struct bfd_diag_str_list diag_list[] = { + {.str = "NeighDown", .type = BFD_DIAGNEIGHDOWN}, + {.str = "DetectTime", .type = BFD_DIAGDETECTTIME}, + {.str = "AdminDown", .type = BFD_DIAGADMINDOWN}, + {.str = NULL}, +}; + +struct bfd_state_str_list state_list[] = { + {.str = "AdminDown", .type = PTM_BFD_ADM_DOWN}, + {.str = "Down", .type = PTM_BFD_DOWN}, + {.str = "Init", .type = PTM_BFD_INIT}, + {.str = "Up", .type = PTM_BFD_UP}, + {.str = NULL}, +}; + + +static void bg_init(void) +{ + TAILQ_INIT(&bglobal.bg_bcslist); + + bglobal.bg_shop = bp_udp_shop(); + bglobal.bg_mhop = bp_udp_mhop(); + bglobal.bg_shop6 = bp_udp6_shop(); + bglobal.bg_mhop6 = bp_udp6_mhop(); + bglobal.bg_echo = ptm_bfd_echo_sock_init(); + bglobal.bg_vxlan = ptm_bfd_vxlan_sock_init(); +} + +int main(int argc, char *argv[]) +{ + const char *ctl_path = BFDD_CONTROL_SOCKET; + int opt; + + frr_preinit(&bfdd_di, argc, argv); + frr_opt_add("", longopts, + " --bfdctl Specify bfdd control socket\n"); + + while (true) { + opt = frr_getopt(argc, argv, NULL); + if (opt == EOF) + break; + + switch (opt) { + case OPTION_CTLSOCK: + ctl_path = optarg; + break; + + default: + frr_help_exit(1); + break; + } + } + +#if 0 /* TODO add support for JSON configuration files. */ + parse_config(conf); +#endif + + /* Initialize logging API. */ + log_init(1, BLOG_DEBUG, &bfdd_di); + + /* Initialize system sockets. */ + bg_init(); + + /* Initialize control socket. */ + control_init(ctl_path); + + /* Initialize FRR infrastructure. */ + master = frr_init(); + + /* Initialize BFD data structures. */ + bfd_initialize(); + + /* Initialize zebra connection. */ + bfdd_zclient_init(&bfdd_privs); + + /* Add descriptors to the event loop. */ + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop, + &bglobal.bg_ev[0]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop, + &bglobal.bg_ev[1]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6, + &bglobal.bg_ev[2]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6, + &bglobal.bg_ev[3]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo, + &bglobal.bg_ev[4]); +#if 0 /* TODO VxLAN support. */ + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan, + &bglobal.bg_ev[5]); +#endif + thread_add_read(master, control_accept, NULL, bglobal.bg_csock, + &bglobal.bg_csockev); + + /* Install commands. */ + bfdd_vty_init(); + + /* read configuration file and daemonize */ + frr_config_fork(); + + frr_run(master); + /* NOTREACHED */ + + return 0; +} diff --git a/bfdd/bfdd.conf.sample b/bfdd/bfdd.conf.sample new file mode 100644 index 000000000000..9981e262bcf4 --- /dev/null +++ b/bfdd/bfdd.conf.sample @@ -0,0 +1,5 @@ +password zebra +! +log stdout +! +line vty diff --git a/bfdd/bfdd_vty.c b/bfdd/bfdd_vty.c new file mode 100644 index 000000000000..bb5f23c40751 --- /dev/null +++ b/bfdd/bfdd_vty.c @@ -0,0 +1,870 @@ +/* + * BFD daemon code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#include "lib/command.h" +#include "lib/json.h" +#include "lib/log.h" +#include "lib/vty.h" + +#include "bfd.h" + +#ifndef VTYSH_EXTRACT_PL +#include "bfdd/bfdd_vty_clippy.c" +#endif + +/* + * Commands help string definitions. + */ +#define PEER_STR "Configure peer\n" +#define INTERFACE_NAME_STR "Configure interface name to use\n" +#define PEER_IPV4_STR "IPv4 peer address\n" +#define PEER_IPV6_STR "IPv6 peer address\n" +#define MHOP_STR "Configure multihop\n" +#define LOCAL_STR "Configure local address\n" +#define LOCAL_IPV4_STR "IPv4 local address\n" +#define LOCAL_IPV6_STR "IPv6 local address\n" +#define LOCAL_INTF_STR "Configure local interface name to use\n" +#define VRF_STR "Configure VRF\n" +#define VRF_NAME_STR "Configure VRF name\n" + +/* + * Prototypes + */ +static int bfdd_write_config(struct vty *vty); +static int bfdd_peer_write_config(struct vty *vty); +static void _bfdd_peer_write_config(struct hash_backet *hb, void *arg); +static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop, + const struct sockaddr_any *peer, + const struct sockaddr_any *local, + const char *ifname, const char *vrfname, + char *ebuf, size_t ebuflen); + +static struct json_object *__display_peer_json(struct bfd_session *bs); +static void _display_peer_json(struct vty *vty, struct bfd_session *bs); +static void _display_peer(struct vty *vty, struct bfd_session *bs); +static void _display_all_peers(struct vty *vty, bool use_json); +static void _display_peer_iter(struct hash_backet *hb, void *arg); +static void _display_peer_json_iter(struct hash_backet *hb, void *arg); + + +/* + * Commands definition. + */ +DEFUN_NOSH(bfd_enter, bfd_enter_cmd, "bfd", "Configure BFD peers\n") +{ + vty->node = BFD_NODE; + return CMD_SUCCESS; +} + +DEFUN_NOSH( + bfd_peer_enter, bfd_peer_enter_cmd, + "peer [{multihop|local-address |interface IFNAME|vrf NAME}]", + PEER_STR PEER_IPV4_STR PEER_IPV6_STR + MHOP_STR + LOCAL_STR LOCAL_IPV4_STR LOCAL_IPV6_STR + INTERFACE_STR + LOCAL_INTF_STR + VRF_STR VRF_NAME_STR) +{ + bool mhop; + int idx; + struct bfd_session *bs; + const char *peer, *ifname, *local, *vrfname; + struct bfd_peer_cfg bpc; + struct sockaddr_any psa, lsa, *lsap; + char errormsg[128]; + + vrfname = peer = ifname = local = NULL; + + /* Gather all provided information. */ + peer = argv[1]->arg; + + idx = 0; + mhop = argv_find(argv, argc, "multihop", &idx); + + idx = 0; + if (argv_find(argv, argc, "interface", &idx)) + ifname = argv[idx + 1]->arg; + + idx = 0; + if (argv_find(argv, argc, "local-address", &idx)) + local = argv[idx + 1]->arg; + + idx = 0; + if (argv_find(argv, argc, "vrf", &idx)) + vrfname = argv[idx + 1]->arg; + + if (vrfname && ifname) { + vty_out(vty, "%% VRF is not mixable with interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + strtosa(peer, &psa); + if (local) { + strtosa(local, &lsa); + lsap = &lsa; + } else + lsap = NULL; + + if (bfd_configure_peer(&bpc, mhop, &psa, lsap, ifname, vrfname, + errormsg, sizeof(errormsg)) + != 0) { + vty_out(vty, "%% Invalid peer configuration: %s\n", errormsg); + return CMD_WARNING_CONFIG_FAILED; + } + + bs = bs_peer_find(&bpc); + if (bs == NULL) { + bs = ptm_bfd_sess_new(&bpc); + if (bs == NULL) { + vty_out(vty, "%% Failed to add peer.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + VTY_PUSH_CONTEXT(BFD_PEER_NODE, bs); + + return CMD_SUCCESS; +} + +DEFPY(bfd_peer_detectmultiplier, bfd_peer_detectmultiplier_cmd, + "detect-multiplier (2-255)$multiplier", + "Configure peer detection multiplier\n" + "Configure peer detection multiplier value\n") +{ + struct bfd_session *bs; + + bs = VTY_GET_CONTEXT(bfd_session); + if (bs->detect_mult == multiplier) + return CMD_SUCCESS; + + bs->detect_mult = multiplier; + bfd_set_polling(bs); + + return CMD_SUCCESS; +} + +DEFPY(bfd_peer_recvinterval, bfd_peer_recvinterval_cmd, + "receive-interval (10-60000)$interval", + "Configure peer receive interval\n" + "Configure peer receive interval value in milliseconds\n") +{ + struct bfd_session *bs; + + bs = VTY_GET_CONTEXT(bfd_session); + if (bs->timers.required_min_rx == (uint32_t)(interval * 1000)) + return CMD_SUCCESS; + + bs->timers.required_min_rx = interval * 1000; + bfd_set_polling(bs); + + return CMD_SUCCESS; +} + +DEFPY(bfd_peer_txinterval, bfd_peer_txinterval_cmd, + "transmit-interval (10-60000)$interval", + "Configure peer transmit interval\n" + "Configure peer transmit interval value in milliseconds\n") +{ + struct bfd_session *bs; + + bs = VTY_GET_CONTEXT(bfd_session); + if (bs->up_min_tx == (uint32_t)(interval * 1000)) + return CMD_SUCCESS; + + bs->up_min_tx = interval * 1000; + bfd_set_polling(bs); + + return CMD_SUCCESS; +} + +DEFPY(bfd_peer_echointerval, bfd_peer_echointerval_cmd, + "echo-interval (10-60000)$interval", + "Configure peer echo interval\n" + "Configure peer echo interval value in milliseconds\n") +{ + struct bfd_session *bs; + + bs = VTY_GET_CONTEXT(bfd_session); + if (bs->timers.required_min_echo == (uint32_t)(interval * 1000)) + return CMD_SUCCESS; + + bs->timers.required_min_echo = interval * 1000; + bfd_set_polling(bs); + + return CMD_SUCCESS; +} + +DEFPY(bfd_peer_shutdown, bfd_peer_shutdown_cmd, "[no] shutdown", + NO_STR "Disable BFD peer") +{ + struct bfd_session *bs; + + bs = VTY_GET_CONTEXT(bfd_session); + if (no) { + if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) + return CMD_SUCCESS; + + BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_DOWN; + control_notify(bs); + + /* Enable all timers. */ + bfd_recvtimer_update(bs); + bfd_xmttimer_update(bs, bs->xmt_TO); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) { + bfd_echo_recvtimer_update(bs); + bfd_echo_xmttimer_update(bs, bs->echo_xmt_TO); + } + } else { + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) + return CMD_SUCCESS; + + BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Disable all events. */ + bfd_recvtimer_delete(bs); + bfd_echo_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + bfd_echo_xmttimer_delete(bs); + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_ADM_DOWN; + control_notify(bs); + + ptm_bfd_snd(bs, 0); + } + + return CMD_SUCCESS; +} + +DEFPY(bfd_peer_echo, bfd_peer_echo_cmd, "[no] echo-mode", + NO_STR "Configure echo mode\n") +{ + struct bfd_session *bs; + + bs = VTY_GET_CONTEXT(bfd_session); + if (no) { + if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + return CMD_SUCCESS; + + BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + ptm_bfd_echo_stop(bs, 0); + } else { + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + return CMD_SUCCESS; + + BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + /* Apply setting immediately. */ + if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) { + ptm_bfd_echo_start(bs); + bfd_echo_recvtimer_update(bs); + } + } + + return CMD_SUCCESS; +} + +DEFPY(bfd_peer_label, bfd_peer_label_cmd, "label WORD$label", + "Register peer label\n" + "Register peer label identification\n") +{ + struct bfd_session *bs; + + /* Validate label length. */ + if (strlen(label) >= MAXNAMELEN) { + vty_out(vty, "%% Label name is too long\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + bs = VTY_GET_CONTEXT(bfd_session); + if (bfd_session_update_label(bs, label) == -1) { + vty_out(vty, "%% Failed to update peer label.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFPY(bfd_no_peer, bfd_no_peer_cmd, + "no peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrfname}]", + NO_STR + PEER_STR PEER_IPV4_STR PEER_IPV6_STR + MHOP_STR + LOCAL_STR LOCAL_IPV4_STR LOCAL_IPV6_STR + INTERFACE_STR + LOCAL_INTF_STR + VRF_STR VRF_NAME_STR) +{ + int idx; + bool mhop; + struct bfd_peer_cfg bpc; + struct sockaddr_any psa, lsa, *lsap; + char errormsg[128]; + + strtosa(peer_str, &psa); + if (local) { + strtosa(local_str, &lsa); + lsap = &lsa; + } else { + lsap = NULL; + } + + idx = 0; + mhop = argv_find(argv, argc, "multihop", &idx); + + if (bfd_configure_peer(&bpc, mhop, &psa, lsap, ifname, vrfname, + errormsg, sizeof(errormsg)) + != 0) { + vty_out(vty, "%% Invalid peer configuration: %s\n", errormsg); + return CMD_WARNING_CONFIG_FAILED; + } + + if (ptm_bfd_ses_del(&bpc) != 0) { + vty_out(vty, "%% Failed to remove peer.\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + + +/* + * Show commands helper functions + */ +static void _display_peer(struct vty *vty, struct bfd_session *bs) +{ + char buf[256]; + time_t now; + + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + vty_out(vty, "\tpeer %s", satostr(&bs->mhop.peer)); + vty_out(vty, " multihop"); + vty_out(vty, " local-address %s", satostr(&bs->mhop.local)); + if (bs->mhop.vrf_name[0]) + vty_out(vty, " vrf %s", bs->mhop.vrf_name); + vty_out(vty, "\n"); + } else { + vty_out(vty, "\tpeer %s", satostr(&bs->shop.peer)); + if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) + vty_out(vty, " local-address %s", + satostr(&bs->local_address)); + if (bs->shop.port_name[0]) + vty_out(vty, " interface %s", bs->shop.port_name); + vty_out(vty, "\n"); + } + + if (bs->pl) + vty_out(vty, "\t\tlabel: %s\n", bs->pl->pl_label); + + vty_out(vty, "\t\tID: %u\n", bs->discrs.my_discr); + vty_out(vty, "\t\tRemote ID: %u\n", bs->discrs.remote_discr); + + vty_out(vty, "\t\tStatus: "); + switch (bs->ses_state) { + case PTM_BFD_ADM_DOWN: + vty_out(vty, "shutdown\n"); + break; + case PTM_BFD_DOWN: + vty_out(vty, "down\n"); + + now = monotime(NULL); + integer2timestr(now - bs->uptime.tv_sec, buf, sizeof(buf)); + vty_out(vty, "\t\tDowntime: %s\n", buf); + break; + case PTM_BFD_INIT: + vty_out(vty, "init\n"); + break; + case PTM_BFD_UP: + vty_out(vty, "up\n"); + + now = monotime(NULL); + integer2timestr(now - bs->uptime.tv_sec, buf, sizeof(buf)); + vty_out(vty, "\t\tUptime: %s\n", buf); + break; + + default: + vty_out(vty, "unknown\n"); + break; + } + + vty_out(vty, "\t\tDiagnostics: %s\n", diag2str(bs->local_diag)); + vty_out(vty, "\t\tRemote diagnostics: %s\n", diag2str(bs->remote_diag)); + + vty_out(vty, "\t\tLocal timers:\n"); + vty_out(vty, "\t\t\tReceive interval: %" PRIu32 "ms\n", + bs->timers.required_min_rx / 1000); + vty_out(vty, "\t\t\tTransmission interval: %" PRIu32 "ms", + bs->timers.desired_min_tx / 1000); + if (bs->up_min_tx != bs->timers.desired_min_tx) + vty_out(vty, " (configured %" PRIu32 "ms)\n", + bs->up_min_tx / 1000); + else + vty_out(vty, "\n"); + + vty_out(vty, "\t\t\tEcho transmission interval: "); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + vty_out(vty, "%" PRIu32 "ms\n", + bs->timers.required_min_echo / 1000); + else + vty_out(vty, "disabled\n"); + + vty_out(vty, "\t\tRemote timers:\n"); + vty_out(vty, "\t\t\tReceive interval: %" PRIu32 "ms\n", + bs->remote_timers.required_min_rx / 1000); + vty_out(vty, "\t\t\tTransmission interval: %" PRIu32 "ms\n", + bs->remote_timers.desired_min_tx / 1000); + vty_out(vty, "\t\t\tEcho transmission interval: %" PRIu32 "ms\n", + bs->remote_timers.required_min_echo / 1000); + + vty_out(vty, "\n"); +} + +static struct json_object *__display_peer_json(struct bfd_session *bs) +{ + struct json_object *jo = json_object_new_object(); + + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + json_object_boolean_true_add(jo, "multihop"); + json_object_string_add(jo, "peer", satostr(&bs->mhop.peer)); + json_object_string_add(jo, "local", satostr(&bs->mhop.local)); + if (bs->mhop.vrf_name[0]) + json_object_string_add(jo, "vrf", bs->mhop.vrf_name); + } else { + json_object_boolean_false_add(jo, "multihop"); + json_object_string_add(jo, "peer", satostr(&bs->shop.peer)); + if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) + json_object_string_add(jo, "local", + satostr(&bs->local_address)); + if (bs->shop.port_name[0]) + json_object_string_add(jo, "interface", + bs->shop.port_name); + } + + if (bs->pl) + json_object_string_add(jo, "label", bs->pl->pl_label); + + json_object_int_add(jo, "id", bs->discrs.my_discr); + json_object_int_add(jo, "remote-id", bs->discrs.remote_discr); + + switch (bs->ses_state) { + case PTM_BFD_ADM_DOWN: + json_object_string_add(jo, "status", "shutdown"); + break; + case PTM_BFD_DOWN: + json_object_string_add(jo, "status", "down"); + json_object_int_add(jo, "downtime", + monotime(NULL) - bs->uptime.tv_sec); + break; + case PTM_BFD_INIT: + json_object_string_add(jo, "status", "init"); + break; + case PTM_BFD_UP: + json_object_string_add(jo, "status", "up"); + json_object_int_add(jo, "uptime", + monotime(NULL) - bs->uptime.tv_sec); + break; + + default: + json_object_string_add(jo, "status", "unknown"); + break; + } + + json_object_string_add(jo, "diagnostic", diag2str(bs->local_diag)); + json_object_string_add(jo, "remote-diagnostic", + diag2str(bs->remote_diag)); + + json_object_int_add(jo, "receive-interval", + bs->timers.required_min_rx / 1000); + json_object_int_add(jo, "transmit-interval", + bs->timers.desired_min_tx / 1000); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + json_object_int_add(jo, "echo-interval", + bs->timers.required_min_echo / 1000); + else + json_object_int_add(jo, "echo-interval", 0); + + json_object_int_add(jo, "remote-receive-interval", + bs->remote_timers.required_min_rx / 1000); + json_object_int_add(jo, "remote-transmit-interval", + bs->remote_timers.desired_min_tx / 1000); + json_object_int_add(jo, "remote-echo-interval", + bs->remote_timers.required_min_echo / 1000); + + return jo; +} + +static void _display_peer_json(struct vty *vty, struct bfd_session *bs) +{ + struct json_object *jo = __display_peer_json(bs); + + vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); + json_object_free(jo); +} + +static void _display_peer_iter(struct hash_backet *hb, void *arg) +{ + struct vty *vty = arg; + struct bfd_session *bs = hb->data; + + _display_peer(vty, bs); +} + +static void _display_peer_json_iter(struct hash_backet *hb, void *arg) +{ + struct json_object *jo = arg, *jon = NULL; + struct bfd_session *bs = hb->data; + + jon = __display_peer_json(bs); + if (jon == NULL) { + log_warning("%s: not enough memory", __func__); + return; + } + + json_object_array_add(jo, jon); +} + +static void _display_all_peers(struct vty *vty, bool use_json) +{ + struct json_object *jo; + + if (use_json == false) { + bfd_id_iterate(_display_peer_iter, vty); + return; + } + + jo = json_object_new_array(); + bfd_id_iterate(_display_peer_json_iter, jo); + + vty_out(vty, "%s\n", json_object_to_json_string_ext(jo, 0)); + json_object_free(jo); +} + +DEFPY(bfd_show_peers, bfd_show_peers_cmd, "show bfd peers [json]", + SHOW_STR + "Bidirection Forwarding Detection\n" + "BFD peers status\n" + JSON_STR) +{ + bool json = use_json(argc, argv); + + if (json) { + _display_all_peers(vty, true); + } else { + vty_out(vty, "BFD Peers:\n"); + _display_all_peers(vty, false); + } + + return CMD_SUCCESS; +} + +DEFPY(bfd_show_peer, bfd_show_peer_cmd, + "show bfd peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrfname}]> [json]", + SHOW_STR + "Bidirection Forwarding Detection\n" + "BFD peers status\n" + "Peer label\n" + PEER_IPV4_STR PEER_IPV6_STR + MHOP_STR + LOCAL_STR LOCAL_IPV4_STR LOCAL_IPV6_STR + INTERFACE_STR + LOCAL_INTF_STR + VRF_STR VRF_NAME_STR + JSON_STR) +{ + int idx; + bool mhop; + struct bfd_session *bs = NULL; + struct peer_label *pl; + struct bfd_peer_cfg bpc; + struct sockaddr_any psa, lsa, *lsap; + char errormsg[128]; + + /* Look up the BFD peer. */ + if (label) { + pl = pl_find(label); + if (pl) + bs = pl->pl_bs; + } else { + strtosa(peer_str, &psa); + if (local) { + strtosa(local_str, &lsa); + lsap = &lsa; + } else + lsap = NULL; + + idx = 0; + mhop = argv_find(argv, argc, "multihop", &idx); + + if (bfd_configure_peer(&bpc, mhop, &psa, lsap, ifname, vrfname, + errormsg, sizeof(errormsg)) + != 0) { + vty_out(vty, "%% Invalid peer configuration: %s\n", + errormsg); + return CMD_WARNING_CONFIG_FAILED; + } + + bs = bs_peer_find(&bpc); + } + + /* Find peer data. */ + if (bs == NULL) { + vty_out(vty, "%% Unable to find 'peer %s", + label ? label : peer_str); + if (ifname) + vty_out(vty, " interface %s", ifname); + if (local) + vty_out(vty, " local-address %s", local_str); + if (vrfname) + vty_out(vty, " vrf %s", vrfname); + vty_out(vty, "'\n"); + + return CMD_WARNING_CONFIG_FAILED; + } + + if (use_json(argc, argv)) { + _display_peer_json(vty, bs); + } else { + vty_out(vty, "BFD Peer:\n"); + _display_peer(vty, bs); + } + + return CMD_SUCCESS; +} + + +/* + * Function definitions. + */ + +/* + * Configuration rules: + * + * Single hop: + * peer + (optional vxlan or interface name) + * + * Multi hop: + * peer + local + (optional vrf) + * + * Anything else is misconfiguration. + */ +static int bfd_configure_peer(struct bfd_peer_cfg *bpc, bool mhop, + const struct sockaddr_any *peer, + const struct sockaddr_any *local, + const char *ifname, const char *vrfname, + char *ebuf, size_t ebuflen) +{ + memset(bpc, 0, sizeof(*bpc)); + + /* Defaults */ + bpc->bpc_shutdown = true; + bpc->bpc_detectmultiplier = BPC_DEF_DETECTMULTIPLIER; + bpc->bpc_recvinterval = BPC_DEF_RECEIVEINTERVAL; + bpc->bpc_txinterval = BPC_DEF_TRANSMITINTERVAL; + bpc->bpc_echointerval = BPC_DEF_ECHOINTERVAL; + bpc->bpc_lastevent = monotime(NULL); + + /* Safety check: when no error buf is provided len must be zero. */ + if (ebuf == NULL) + ebuflen = 0; + + /* Peer is always mandatory. */ + if (peer == NULL) { + snprintf(ebuf, ebuflen, "peer must not be empty"); + return -1; + } + + /* Validate address families. */ + if (peer->sa_sin.sin_family == AF_INET) { + if (local && local->sa_sin.sin_family != AF_INET) { + snprintf(ebuf, ebuflen, + "local is IPv6, but peer is IPv4"); + return -1; + } + + bpc->bpc_ipv4 = true; + } else if (peer->sa_sin.sin_family == AF_INET6) { + if (local && local->sa_sin.sin_family != AF_INET6) { + snprintf(ebuf, ebuflen, + "local is IPv4, but peer is IPv6"); + return -1; + } + + bpc->bpc_ipv4 = false; + } else { + snprintf(ebuf, ebuflen, "invalid peer address family"); + return -1; + } + + /* Copy local and/or peer addresses. */ + if (local) + bpc->bpc_local = *local; + + if (peer) { + bpc->bpc_peer = *peer; + } else { + /* Peer configuration is mandatory. */ + snprintf(ebuf, ebuflen, "no peer configured"); + return -1; + } + + bpc->bpc_mhop = mhop; + +#if 0 + /* Handle VxLAN configuration. */ + if (vxlan >= 0) { + if (vxlan > ((1 << 24) - 1)) { + snprintf(ebuf, ebuflen, "invalid VxLAN %d", vxlan); + return -1; + } + if (bpc->bpc_mhop) { + snprintf(ebuf, ebuflen, + "multihop doesn't accept VxLAN"); + return -1; + } + + bpc->bpc_vxlan = vxlan; + } +#endif /* VxLAN */ + + /* Handle interface specification configuration. */ + if (ifname) { + if (bpc->bpc_mhop) { + snprintf(ebuf, ebuflen, + "multihop doesn't accept interface names"); + return -1; + } + + bpc->bpc_has_localif = true; + if (strlcpy(bpc->bpc_localif, ifname, sizeof(bpc->bpc_localif)) + > sizeof(bpc->bpc_localif)) { + snprintf(ebuf, ebuflen, "interface name too long"); + return -1; + } + } + + /* Handle VRF configuration. */ + if (vrfname) { + bpc->bpc_has_vrfname = true; + if (strlcpy(bpc->bpc_vrfname, vrfname, sizeof(bpc->bpc_vrfname)) + > sizeof(bpc->bpc_vrfname)) { + snprintf(ebuf, ebuflen, "vrf name too long"); + return -1; + } + } + + return 0; +} +static int bfdd_write_config(struct vty *vty) +{ + vty_out(vty, "bfd\n"); + vty_out(vty, "!\n"); + return 0; +} + +static void _bfdd_peer_write_config(struct hash_backet *hb, void *arg) +{ + struct vty *vty = arg; + struct bfd_session *bs = hb->data; + + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + vty_out(vty, " peer %s", satostr(&bs->mhop.peer)); + vty_out(vty, " multihop"); + vty_out(vty, " local-address %s", satostr(&bs->mhop.local)); + if (bs->mhop.vrf_name[0]) + vty_out(vty, " vrf %s", bs->mhop.vrf_name); + vty_out(vty, "\n"); + } else { + vty_out(vty, " peer %s", satostr(&bs->shop.peer)); + if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) + vty_out(vty, " local-address %s", + satostr(&bs->local_address)); + if (bs->shop.port_name[0]) + vty_out(vty, " interface %s", bs->shop.port_name); + vty_out(vty, "\n"); + } + + if (bs->detect_mult != BPC_DEF_DETECTMULTIPLIER) + vty_out(vty, " detect-multiplier %d\n", bs->detect_mult); + if (bs->timers.required_min_rx != (BPC_DEF_RECEIVEINTERVAL * 1000)) + vty_out(vty, " receive-interval %" PRIu32 "\n", + bs->timers.required_min_rx / 1000); + if (bs->up_min_tx != (BPC_DEF_TRANSMITINTERVAL * 1000)) + vty_out(vty, " transmit-interval %" PRIu32 "\n", + bs->up_min_tx / 1000); + if (bs->timers.required_min_echo != (BPC_DEF_ECHOINTERVAL * 1000)) + vty_out(vty, " echo-interval %" PRIu32 "\n", + bs->timers.required_min_echo / 1000); + if (bs->pl) + vty_out(vty, " label %s\n", bs->pl->pl_label); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + vty_out(vty, " echo-mode\n"); + + vty_out(vty, " %sshutdown\n", + BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN) ? "" : "no "); + + vty_out(vty, " !\n"); +} + +static int bfdd_peer_write_config(struct vty *vty) +{ + bfd_id_iterate(_bfdd_peer_write_config, vty); + return 1; +} + +struct cmd_node bfd_node = { + BFD_NODE, + "%s(config-bfd)# ", + 1, +}; + +struct cmd_node bfd_peer_node = { + BFD_PEER_NODE, + "%s(config-bfd-peer)# ", + 1, +}; + +void bfdd_vty_init(void) +{ + install_element(ENABLE_NODE, &bfd_show_peers_cmd); + install_element(ENABLE_NODE, &bfd_show_peer_cmd); + install_element(CONFIG_NODE, &bfd_enter_cmd); + + /* Install BFD node and commands. */ + install_node(&bfd_node, bfdd_write_config); + install_default(BFD_NODE); + install_element(BFD_NODE, &bfd_peer_enter_cmd); + install_element(BFD_NODE, &bfd_no_peer_cmd); + + /* Install BFD peer node. */ + install_node(&bfd_peer_node, bfdd_peer_write_config); + install_default(BFD_PEER_NODE); + install_element(BFD_PEER_NODE, &bfd_peer_detectmultiplier_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_recvinterval_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_txinterval_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_echointerval_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_shutdown_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_echo_cmd); + install_element(BFD_PEER_NODE, &bfd_peer_label_cmd); +} diff --git a/bfdd/bsd.c b/bfdd/bsd.c new file mode 100644 index 000000000000..34a3a1a801e2 --- /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 + +#ifdef BFD_BSD + +#include +#include +#include +#include + +#include + +#include "bfd.h" + +/* + * Prototypes + */ +static const char *sockaddr_to_string(const void *sv, char *buf, size_t buflen); + +/* + * Definitions. + */ +static const char *sockaddr_to_string(const void *sv, char *buf, size_t buflen) +{ + const struct sockaddr *sa = sv; + const struct sockaddr_in *sin = sv; + const struct sockaddr_in6 *sin6 = sv; + int unknown = 1; + + switch (sa->sa_family) { + case AF_INET: + if (inet_ntop(AF_INET, &sin->sin_addr, buf, buflen) != NULL) + unknown = 0; + break; + + case AF_INET6: + if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, buflen) != NULL) + unknown = 0; + break; + } + if (unknown == 0) + return buf; + + snprintf(buf, buflen, "unknown (af=%d)", sa->sa_family); + return buf; +} + +int ptm_bfd_fetch_ifindex(const char *ifname) +{ + return if_nametoindex(ifname); +} + +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac) +{ + struct ifaddrs *ifap, *ifa; + struct if_data *ifi; + struct sockaddr_dl *sdl; + size_t maclen; + + /* Always clean the target, zeroed macs mean failure. */ + memset(mac, 0, ETHERNET_ADDRESS_LENGTH); + + if (getifaddrs(&ifap) != 0) + return; + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + /* Find interface with that name. */ + if (strcmp(ifa->ifa_name, ifname) != 0) + continue; + /* Skip non link addresses. We want the MAC address. */ + if (ifa->ifa_addr->sa_family != AF_LINK) + continue; + + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + ifi = (struct if_data *)ifa->ifa_data; + /* Skip non ethernet related data. */ + if (ifi->ifi_type != IFT_ETHER) + continue; + + if (sdl->sdl_alen != ETHERNET_ADDRESS_LENGTH) + log_warning("%s:%d mac address length %d (expected %d)", + __func__, __LINE__, sdl->sdl_alen, + ETHERNET_ADDRESS_LENGTH); + + maclen = (sdl->sdl_alen > ETHERNET_ADDRESS_LENGTH) + ? ETHERNET_ADDRESS_LENGTH + : sdl->sdl_alen; + memcpy(mac, LLADDR(sdl), maclen); + break; + } + + freeifaddrs(ifap); +} + + +/* Was _fetch_portname_from_ifindex() */ +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen) +{ + char ifname_tmp[IF_NAMESIZE]; + + /* Set ifname to empty to signalize failures. */ + memset(ifname, 0, ifnamelen); + + if (if_indextoname(ifindex, ifname_tmp) == NULL) + return; + + if (strlcpy(ifname, ifname_tmp, ifnamelen) > ifnamelen) + log_warning("%s:%d interface name truncated", __func__, + __LINE__); +} + +int ptm_bfd_echo_sock_init(void) +{ + int s, ttl, yes = 1; + struct sockaddr_in sin; + + s = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); + if (s == -1) { + log_error("echo-socket: creation failed: %s", strerror(errno)); + return -1; + } + + memset(&sin, 0, sizeof(sin)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + /* OmniOS doesn't have this field, but uses this code. */ + sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + sin.sin_family = AF_INET; + sin.sin_port = htons(3785); + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) { + log_error("echo-socket: bind failure: %s", strerror(errno)); + close(s); + return -1; + } + + if (setsockopt(s, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) == -1) { + log_error("echo-socket: setsockopt(IP_RECVTTL): %s", + strerror(errno)); + close(s); + return -1; + } + + ttl = BFD_TTL_VAL; + if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) { + log_error("echo-socket: setsockopt(IP_TTL): %s", + strerror(errno)); + close(s); + return -1; + } + + return s; +} + +ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen, + struct sockaddr_storage *ss, socklen_t *sslen, + uint8_t *ttl, uint32_t *id) +{ + struct cmsghdr *cmsg; + struct bfd_echo_pkt *bep; + ssize_t readlen; + struct iovec iov; + struct msghdr msg; + uint8_t msgctl[255]; + char errbuf[255]; + + /* Prepare socket read. */ + memset(ss, 0, sizeof(*ss)); + memset(&msg, 0, sizeof(msg)); + iov.iov_base = buf; + iov.iov_len = *buflen; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = msgctl; + msg.msg_controllen = sizeof(msgctl); + msg.msg_name = ss; + msg.msg_namelen = *sslen; + + /* Read the socket and treat errors. */ + readlen = recvmsg(sd, &msg, 0); + if (readlen == 0) { + log_error("%s: recvmsg: socket closed", __func__); + return -1; + } + if (readlen == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return -1; + + log_error("%s: recvmsg: (%d) %s", __func__, errno, + strerror(errno)); + return -1; + } + /* Short packet, better not risk reading it. */ + if (readlen < (ssize_t)sizeof(*bep)) { + log_warning("%s: short packet (%ld of %d) from %s", __func__, + readlen, sizeof(*bep), + sockaddr_to_string(ss, errbuf, sizeof(errbuf))); + return -1; + } + *buflen = readlen; + + /* Read TTL information. */ + *ttl = 0; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != IPPROTO_IP) + continue; + if (cmsg->cmsg_type != IP_RECVTTL) + continue; + + *ttl = *(uint8_t *)CMSG_DATA(cmsg); + break; + } + if (*ttl == 0) { + log_debug("%s: failed to read TTL", __func__); + return -1; + } + + /* Read my discriminator from BFD Echo packet. */ + bep = (struct bfd_echo_pkt *)buf; + *id = bep->my_discr; + if (*id == 0) { + log_debug("%s: invalid packet discriminator from: %s", __func__, + sockaddr_to_string(ss, errbuf, sizeof(errbuf))); + return -1; + } + + /* Set the returned sockaddr new length. */ + *sslen = msg.msg_namelen; + + return 0; +} + +int ptm_bfd_vxlan_sock_init(void) +{ + /* TODO: not supported yet. */ + return -1; +} + +int bp_bind_dev(int sd, const char *dev) +{ + /* + * *BSDs don't support `SO_BINDTODEVICE`, instead you must + * manually specify the main address of the interface or use + * BPF on the socket descriptor. + */ + return 0; +} + +uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len) +{ + char *ptr; + struct udp_psuedo_header pudp_hdr; + uint16_t csum; + + pudp_hdr.saddr = ip->ip_src.s_addr; + pudp_hdr.daddr = ip->ip_dst.s_addr; + pudp_hdr.reserved = 0; + pudp_hdr.protocol = ip->ip_p; + pudp_hdr.len = htons(len); + + ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len); + memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN); + memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len); + + csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len); + XFREE(MTYPE_BFDD_TMP, ptr); + return csum; +} + +#endif /* BFD_BSD */ diff --git a/bfdd/config.c b/bfdd/config.c new file mode 100644 index 000000000000..0e0d8b7d706f --- /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 + */ + +#include + +#include + +#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 000000000000..7e586dbb45f4 --- /dev/null +++ b/bfdd/control.c @@ -0,0 +1,895 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * control.c: implements the BFD daemon control socket. It will be used + * to talk with clients daemon/scripts/consumers. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#include + +#include + +#include "bfd.h" + +/* + * Prototypes + */ +static int sock_set_nonblock(int fd); +struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs); +static void control_queue_free(struct bfd_control_socket *bcs, + struct bfd_control_queue *bcq); +static int control_queue_dequeue(struct bfd_control_socket *bcs); +static int control_queue_enqueue(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static int control_queue_enqueue_first(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, + struct bfd_session *bs); +static void control_notifypeer_free(struct bfd_control_socket *bcs, + struct bfd_notify_peer *bnp); +struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, + struct bfd_session *bs); + + +struct bfd_control_socket *control_new(int sd); +static void control_free(struct bfd_control_socket *bcs); +static void control_reset_buf(struct bfd_control_buffer *bcb); +static int control_read(struct thread *t); +static int control_write(struct thread *t); + +static void control_handle_request_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void control_handle_request_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg); +static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg); +static void control_handle_notify_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void control_handle_notify_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void _control_handle_notify(struct hash_backet *hb, void *arg); +static void control_handle_notify(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm); +static void control_response(struct bfd_control_socket *bcs, uint16_t id, + const char *status, const char *error); + +static void _control_notify_config(struct bfd_control_socket *bcs, + const char *op, struct bfd_session *bs); +static void _control_notify(struct bfd_control_socket *bcs, + struct bfd_session *bs); + + +/* + * Functions + */ +static int sock_set_nonblock(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + log_warning("%s: fcntl F_GETFL: %s", __func__, strerror(errno)); + return -1; + } + + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) == -1) { + log_warning("%s: fcntl F_SETFL: %s", __func__, strerror(errno)); + return -1; + } + + return 0; +} + +int control_init(const char *path) +{ + int sd; + mode_t umval; + struct sockaddr_un sun_ = { + .sun_family = AF_UNIX, + .sun_path = BFDD_CONTROL_SOCKET, + }; + + if (path) + strlcpy(sun_.sun_path, path, sizeof(sun_.sun_path)); + + /* Remove previously created sockets. */ + unlink(sun_.sun_path); + + sd = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC); + if (sd == -1) { + log_error("%s: socket: %s", __func__, strerror(errno)); + return -1; + } + + umval = umask(0); + if (bind(sd, (struct sockaddr *)&sun_, sizeof(sun_)) == -1) { + log_error("%s: bind: %s", __func__, strerror(errno)); + close(sd); + return -1; + } + umask(umval); + + if (listen(sd, SOMAXCONN) == -1) { + log_error("%s: listen: %s", __func__, strerror(errno)); + close(sd); + return -1; + } + + sock_set_nonblock(sd); + + bglobal.bg_csock = sd; + + return 0; +} + +void control_shutdown(void) +{ + struct bfd_control_socket *bcs; + + if (bglobal.bg_csockev) { + thread_cancel(bglobal.bg_csockev); + bglobal.bg_csockev = NULL; + } + + socket_close(&bglobal.bg_csock); + + while (!TAILQ_EMPTY(&bglobal.bg_bcslist)) { + bcs = TAILQ_FIRST(&bglobal.bg_bcslist); + control_free(bcs); + } +} + +int control_accept(struct thread *t) +{ + int csock, sd = THREAD_FD(t); + + csock = accept(sd, NULL, 0); + if (csock == -1) { + log_warning("%s: accept: %s", __func__, strerror(errno)); + return 0; + } + + if (control_new(csock) == NULL) + close(csock); + + bglobal.bg_csockev = NULL; + thread_add_read(master, control_accept, NULL, sd, &bglobal.bg_csockev); + + return 0; +} + + +/* + * Client handling + */ +struct bfd_control_socket *control_new(int sd) +{ + struct bfd_control_socket *bcs; + + bcs = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bcs)); + if (bcs == NULL) + return NULL; + + /* Disable notifications by default. */ + bcs->bcs_notify = 0; + + bcs->bcs_sd = sd; + thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); + + TAILQ_INIT(&bcs->bcs_bcqueue); + TAILQ_INIT(&bcs->bcs_bnplist); + TAILQ_INSERT_TAIL(&bglobal.bg_bcslist, bcs, bcs_entry); + + return bcs; +} + +static void control_free(struct bfd_control_socket *bcs) +{ + struct bfd_control_queue *bcq; + struct bfd_notify_peer *bnp; + + if (bcs->bcs_ev) { + thread_cancel(bcs->bcs_ev); + bcs->bcs_ev = NULL; + } + + if (bcs->bcs_outev) { + thread_cancel(bcs->bcs_outev); + bcs->bcs_outev = NULL; + } + + close(bcs->bcs_sd); + + TAILQ_REMOVE(&bglobal.bg_bcslist, bcs, bcs_entry); + + /* Empty output queue. */ + while (!TAILQ_EMPTY(&bcs->bcs_bcqueue)) { + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + control_queue_free(bcs, bcq); + } + + /* Empty notification list. */ + while (!TAILQ_EMPTY(&bcs->bcs_bnplist)) { + bnp = TAILQ_FIRST(&bcs->bcs_bnplist); + control_notifypeer_free(bcs, bnp); + } + + control_reset_buf(&bcs->bcs_bin); + XFREE(MTYPE_BFDD_CONTROL, bcs); +} + +struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, + struct bfd_session *bs) +{ + struct bfd_notify_peer *bnp; + + bnp = control_notifypeer_find(bcs, bs); + if (bnp) + return bnp; + + bnp = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bnp)); + if (bnp == NULL) { + log_warning("%s: calloc: %s", __func__, strerror(errno)); + return NULL; + } + + TAILQ_INSERT_TAIL(&bcs->bcs_bnplist, bnp, bnp_entry); + bnp->bnp_bs = bs; + bs->refcount++; + + return bnp; +} + +static void control_notifypeer_free(struct bfd_control_socket *bcs, + struct bfd_notify_peer *bnp) +{ + TAILQ_REMOVE(&bcs->bcs_bnplist, bnp, bnp_entry); + bnp->bnp_bs->refcount--; + XFREE(MTYPE_BFDD_CONTROL, bnp); +} + +struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, + struct bfd_session *bs) +{ + struct bfd_notify_peer *bnp; + + TAILQ_FOREACH (bnp, &bcs->bcs_bnplist, bnp_entry) { + if (bnp->bnp_bs == bs) + return bnp; + } + + return NULL; +} + +struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs) +{ + struct bfd_control_queue *bcq; + + bcq = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*bcq)); + if (bcq == NULL) { + log_warning("%s: calloc: %s", __func__, strerror(errno)); + return NULL; + } + + control_reset_buf(&bcq->bcq_bcb); + TAILQ_INSERT_TAIL(&bcs->bcs_bcqueue, bcq, bcq_entry); + + return bcq; +} + +static void control_queue_free(struct bfd_control_socket *bcs, + struct bfd_control_queue *bcq) +{ + control_reset_buf(&bcq->bcq_bcb); + TAILQ_REMOVE(&bcs->bcs_bcqueue, bcq, bcq_entry); + XFREE(MTYPE_BFDD_NOTIFICATION, bcq); +} + +static int control_queue_dequeue(struct bfd_control_socket *bcs) +{ + struct bfd_control_queue *bcq; + + /* List is empty, nothing to do. */ + if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) + goto empty_list; + + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + control_queue_free(bcs, bcq); + + /* Get the next buffer to send. */ + if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) + goto empty_list; + + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + bcs->bcs_bout = &bcq->bcq_bcb; + + bcs->bcs_outev = NULL; + thread_add_write(master, control_write, bcs, bcs->bcs_sd, + &bcs->bcs_outev); + + return 1; + +empty_list: + if (bcs->bcs_outev) { + thread_cancel(bcs->bcs_outev); + bcs->bcs_outev = NULL; + } + bcs->bcs_bout = NULL; + return 0; +} + +static int control_queue_enqueue(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + struct bfd_control_queue *bcq; + struct bfd_control_buffer *bcb; + + bcq = control_queue_new(bcs); + if (bcq == NULL) + return -1; + + bcb = &bcq->bcq_bcb; + bcb->bcb_left = sizeof(struct bfd_control_msg) + ntohl(bcm->bcm_length); + bcb->bcb_pos = 0; + bcb->bcb_bcm = bcm; + + /* If this is the first item, then dequeue and start using it. */ + if (bcs->bcs_bout == NULL) { + bcs->bcs_bout = bcb; + + /* New messages, active write events. */ + thread_add_write(master, control_write, bcs, bcs->bcs_sd, + &bcs->bcs_outev); + } + + return 0; +} + +static int control_queue_enqueue_first(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + struct bfd_control_queue *bcq, *bcqn; + struct bfd_control_buffer *bcb; + + /* Enqueue it somewhere. */ + if (control_queue_enqueue(bcs, bcm) == -1) + return -1; + + /* + * The item is either the first or the last. So we must first + * check the best case where the item is already the first. + */ + bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); + bcb = &bcq->bcq_bcb; + if (bcm == bcb->bcb_bcm) + return 0; + + /* + * The item was not the first, so it is the last. We'll try to + * assign it to the head of the queue, however if there is a + * transfer in progress, then we have to make the item as the + * next one. + * + * Interrupting the transfer of in progress message will cause + * the client to lose track of the message position/data. + */ + bcqn = TAILQ_LAST(&bcs->bcs_bcqueue, bcqueue); + TAILQ_REMOVE(&bcs->bcs_bcqueue, bcqn, bcq_entry); + if (bcb->bcb_pos != 0) { + /* + * First position is already being sent, insert into + * second position. + */ + TAILQ_INSERT_AFTER(&bcs->bcs_bcqueue, bcq, bcqn, bcq_entry); + } else { + /* + * Old message didn't start being sent, we still have + * time to put this one in the head of the queue. + */ + TAILQ_INSERT_HEAD(&bcs->bcs_bcqueue, bcqn, bcq_entry); + bcb = &bcqn->bcq_bcb; + bcs->bcs_bout = bcb; + } + + return 0; +} + +static void control_reset_buf(struct bfd_control_buffer *bcb) +{ + /* Get ride of old data. */ + XFREE(MTYPE_BFDD_NOTIFICATION, bcb->bcb_buf); + bcb->bcb_buf = NULL; + bcb->bcb_pos = 0; + bcb->bcb_left = 0; +} + +static int control_read(struct thread *t) +{ + struct bfd_control_socket *bcs = THREAD_ARG(t); + struct bfd_control_buffer *bcb = &bcs->bcs_bin; + int sd = bcs->bcs_sd; + struct bfd_control_msg bcm; + ssize_t bread; + size_t plen; + + /* + * Check if we have already downloaded message content, if so then skip + * to + * download the rest of it and process. + * + * Otherwise download a new message header and allocate the necessary + * memory. + */ + if (bcb->bcb_buf != NULL) + goto skip_header; + + bread = read(sd, &bcm, sizeof(bcm)); + if (bread == 0) { + control_free(bcs); + return 0; + } + if (bread < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + goto schedule_next_read; + + log_warning("%s: read: %s", __func__, strerror(errno)); + control_free(bcs); + return 0; + } + + /* Validate header fields. */ + plen = ntohl(bcm.bcm_length); + if (plen < 2) { + log_debug("%s: client closed due small message length: %d", + __func__, bcm.bcm_length); + control_free(bcs); + return 0; + } + + if (bcm.bcm_ver != BMV_VERSION_1) { + log_debug("%s: client closed due bad version: %d", __func__, + bcm.bcm_ver); + control_free(bcs); + return 0; + } + + /* Prepare the buffer to load the message. */ + bcs->bcs_version = bcm.bcm_ver; + bcs->bcs_type = bcm.bcm_type; + + bcb->bcb_pos = sizeof(bcm); + bcb->bcb_left = plen; + bcb->bcb_buf = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(bcm) + bcb->bcb_left + 1); + if (bcb->bcb_buf == NULL) { + log_warning("%s: not enough memory for message size: %u", + __func__, bcb->bcb_left); + control_free(bcs); + return 0; + } + + memcpy(bcb->bcb_buf, &bcm, sizeof(bcm)); + + /* Terminate data string with NULL for later processing. */ + bcb->bcb_buf[sizeof(bcm) + bcb->bcb_left] = 0; + +skip_header: + /* Download the remaining data of the message and process it. */ + bread = read(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); + if (bread == 0) { + control_free(bcs); + return 0; + } + if (bread < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + goto schedule_next_read; + + log_warning("%s: read: %s", __func__, strerror(errno)); + control_free(bcs); + return 0; + } + + bcb->bcb_pos += bread; + bcb->bcb_left -= bread; + /* We need more data, return to wait more. */ + if (bcb->bcb_left > 0) + goto schedule_next_read; + + switch (bcm.bcm_type) { + case BMT_REQUEST_ADD: + control_handle_request_add(bcs, bcb->bcb_bcm); + break; + case BMT_REQUEST_DEL: + control_handle_request_del(bcs, bcb->bcb_bcm); + break; + case BMT_NOTIFY: + control_handle_notify(bcs, bcb->bcb_bcm); + break; + case BMT_NOTIFY_ADD: + control_handle_notify_add(bcs, bcb->bcb_bcm); + break; + case BMT_NOTIFY_DEL: + control_handle_notify_del(bcs, bcb->bcb_bcm); + break; + + default: + log_debug("%s: unhandled message type: %d", __func__, + bcm.bcm_type); + control_response(bcs, bcm.bcm_id, BCM_RESPONSE_ERROR, + "invalid message type"); + break; + } + + bcs->bcs_version = 0; + bcs->bcs_type = 0; + control_reset_buf(bcb); + +schedule_next_read: + bcs->bcs_ev = NULL; + thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); + + return 0; +} + +static int control_write(struct thread *t) +{ + struct bfd_control_socket *bcs = THREAD_ARG(t); + struct bfd_control_buffer *bcb = bcs->bcs_bout; + int sd = bcs->bcs_sd; + ssize_t bwrite; + + bwrite = write(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); + if (bwrite == 0) { + control_free(bcs); + return 0; + } + if (bwrite < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + bcs->bcs_outev = NULL; + thread_add_write(master, control_write, bcs, + bcs->bcs_sd, &bcs->bcs_outev); + return 0; + } + + log_warning("%s: write: %s", __func__, strerror(errno)); + control_free(bcs); + return 0; + } + + bcb->bcb_pos += bwrite; + bcb->bcb_left -= bwrite; + if (bcb->bcb_left > 0) { + bcs->bcs_outev = NULL; + thread_add_write(master, control_write, bcs, bcs->bcs_sd, + &bcs->bcs_outev); + return 0; + } + + control_queue_dequeue(bcs); + + return 0; +} + + +/* + * Message processing + */ +static void control_handle_request_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_request_add(json) == 0) + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + else + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "request add failed"); +} + +static void control_handle_request_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_request_del(json) == 0) + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + else + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "request del failed"); +} + +static struct bfd_session *_notify_find_peer(struct bfd_peer_cfg *bpc) +{ + struct peer_label *pl; + + if (bpc->bpc_has_label) { + pl = pl_find(bpc->bpc_label); + if (pl) + return pl->pl_bs; + } + + return bs_peer_find(bpc); +} + +static void _control_handle_notify(struct hash_backet *hb, void *arg) +{ + struct bfd_control_socket *bcs = arg; + struct bfd_session *bs = hb->data; + + /* Notify peer configuration. */ + if (bcs->bcs_notify & BCM_NOTIFY_CONFIG) + _control_notify_config(bcs, BCM_NOTIFY_CONFIG_ADD, bs); + + /* Notify peer status. */ + if (bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) + _control_notify(bcs, bs); +} + +static void control_handle_notify(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + memcpy(&bcs->bcs_notify, bcm->bcm_data, sizeof(bcs->bcs_notify)); + + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + + /* + * If peer asked for notification configuration, send everything that + * was configured until the moment to sync up. + */ + if (bcs->bcs_notify & (BCM_NOTIFY_CONFIG | BCM_NOTIFY_PEER_STATE)) + bfd_id_iterate(_control_handle_notify, bcs); +} + +static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg) +{ + struct bfd_control_socket *bcs = arg; + struct bfd_session *bs = _notify_find_peer(bpc); + + if (bs == NULL) + return -1; + + if (control_notifypeer_new(bcs, bs) == NULL) + return -1; + + /* Notify peer status. */ + _control_notify(bcs, bs); + + return 0; +} + +static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg) +{ + struct bfd_control_socket *bcs = arg; + struct bfd_session *bs = _notify_find_peer(bpc); + struct bfd_notify_peer *bnp; + + if (bs == NULL) + return -1; + + bnp = control_notifypeer_find(bcs, bs); + if (bnp) + control_notifypeer_free(bcs, bnp); + + return 0; +} + +static void control_handle_notify_add(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_notify_request(bcs, json, notify_add_cb) == 0) { + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + return; + } + + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "failed to parse notify data"); +} + +static void control_handle_notify_del(struct bfd_control_socket *bcs, + struct bfd_control_msg *bcm) +{ + const char *json = (const char *)bcm->bcm_data; + + if (config_notify_request(bcs, json, notify_del_cb) == 0) { + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + return; + } + + control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, + "failed to parse notify data"); +} + + +/* + * Internal functions used by the BFD daemon. + */ +static void control_response(struct bfd_control_socket *bcs, uint16_t id, + const char *status, const char *error) +{ + struct bfd_control_msg *bcm; + char *jsonstr; + size_t jsonstrlen; + + /* Generate JSON response. */ + jsonstr = config_response(status, error); + if (jsonstr == NULL) { + log_warning("%s: config_response: failed to get JSON str", + __func__); + return; + } + + /* Allocate data and answer. */ + jsonstrlen = strlen(jsonstr); + bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(struct bfd_control_msg) + jsonstrlen); + if (bcm == NULL) { + log_warning("%s: malloc: %s", __func__, strerror(errno)); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + return; + } + + bcm->bcm_length = htonl(jsonstrlen); + bcm->bcm_ver = BMV_VERSION_1; + bcm->bcm_type = BMT_RESPONSE; + bcm->bcm_id = id; + memcpy(bcm->bcm_data, jsonstr, jsonstrlen); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + + control_queue_enqueue_first(bcs, bcm); +} + +static void _control_notify(struct bfd_control_socket *bcs, + struct bfd_session *bs) +{ + struct bfd_control_msg *bcm; + char *jsonstr; + size_t jsonstrlen; + + /* Generate JSON response. */ + jsonstr = config_notify(bs); + if (jsonstr == NULL) { + log_warning("%s: config_notify: failed to get JSON str", + __func__); + return; + } + + /* Allocate data and answer. */ + jsonstrlen = strlen(jsonstr); + bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(struct bfd_control_msg) + jsonstrlen); + if (bcm == NULL) { + log_warning("%s: malloc: %s", __func__, strerror(errno)); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + return; + } + + bcm->bcm_length = htonl(jsonstrlen); + bcm->bcm_ver = BMV_VERSION_1; + bcm->bcm_type = BMT_NOTIFY; + bcm->bcm_id = htons(BCM_NOTIFY_ID); + memcpy(bcm->bcm_data, jsonstr, jsonstrlen); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + + control_queue_enqueue(bcs, bcm); +} + +int control_notify(struct bfd_session *bs) +{ + struct bfd_control_socket *bcs; + struct bfd_notify_peer *bnp; + + /* Notify zebra listeners as well. */ + ptm_bfd_notify(bs); + + /* + * PERFORMANCE: reuse the bfd_control_msg allocated data for + * all control sockets to avoid wasting memory. + */ + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + /* + * Test for all notifications first, then search for + * specific peers. + */ + if ((bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) == 0) { + bnp = control_notifypeer_find(bcs, bs); + /* + * If the notification is not configured here, + * don't send it. + */ + if (bnp == NULL) + continue; + } + + _control_notify(bcs, bs); + } + + return 0; +} + +static void _control_notify_config(struct bfd_control_socket *bcs, + const char *op, struct bfd_session *bs) +{ + struct bfd_control_msg *bcm; + char *jsonstr; + size_t jsonstrlen; + + /* Generate JSON response. */ + jsonstr = config_notify_config(op, bs); + if (jsonstr == NULL) { + log_warning("%s: config_notify_config: failed to get JSON str", + __func__); + return; + } + + /* Allocate data and answer. */ + jsonstrlen = strlen(jsonstr); + bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(struct bfd_control_msg) + jsonstrlen); + if (bcm == NULL) { + log_warning("%s: malloc: %s", __func__, strerror(errno)); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + return; + } + + bcm->bcm_length = htonl(jsonstrlen); + bcm->bcm_ver = BMV_VERSION_1; + bcm->bcm_type = BMT_NOTIFY; + bcm->bcm_id = htons(BCM_NOTIFY_ID); + memcpy(bcm->bcm_data, jsonstr, jsonstrlen); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + + control_queue_enqueue(bcs, bcm); +} + +int control_notify_config(const char *op, struct bfd_session *bs) +{ + struct bfd_control_socket *bcs; + struct bfd_notify_peer *bnp; + + /* Remove the control sockets notification for this peer. */ + if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0 && bs->refcount > 0) { + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + bnp = control_notifypeer_find(bcs, bs); + if (bnp) + control_notifypeer_free(bcs, bnp); + } + } + + /* + * PERFORMANCE: reuse the bfd_control_msg allocated data for + * all control sockets to avoid wasting memory. + */ + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + /* + * Test for all notifications first, then search for + * specific peers. + */ + if ((bcs->bcs_notify & BCM_NOTIFY_CONFIG) == 0) + continue; + + _control_notify_config(bcs, op, bs); + } + + return 0; +} diff --git a/bfdd/event.c b/bfdd/event.c new file mode 100644 index 000000000000..ba12f5b4e800 --- /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 + */ + +#include + +#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 000000000000..5f24ef4d19cc --- /dev/null +++ b/bfdd/linux.c @@ -0,0 +1,221 @@ +/* + * Linux specific code + * + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#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 +#include + +#include + +#include +#include + +#include "bfd.h" + +/* Berkeley Packet filter code to filter out BFD Echo packets. + * tcpdump -dd "(udp dst port 3785)" + */ +static struct sock_filter bfd_echo_filter[] = { + {0x28, 0, 0, 0x0000000c}, {0x15, 0, 4, 0x000086dd}, + {0x30, 0, 0, 0x00000014}, {0x15, 0, 11, 0x00000011}, + {0x28, 0, 0, 0x00000038}, {0x15, 8, 9, 0x00000ec9}, + {0x15, 0, 8, 0x00000800}, {0x30, 0, 0, 0x00000017}, + {0x15, 0, 6, 0x00000011}, {0x28, 0, 0, 0x00000014}, + {0x45, 4, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, + {0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x00000ec9}, + {0x6, 0, 0, 0x0000ffff}, {0x6, 0, 0, 0x00000000}, +}; + +/* Berkeley Packet filter code to filter out BFD vxlan packets. + * tcpdump -dd "(udp dst port 4789)" + */ +static struct sock_filter bfd_vxlan_filter[] = { + {0x28, 0, 0, 0x0000000c}, {0x15, 0, 4, 0x000086dd}, + {0x30, 0, 0, 0x00000014}, {0x15, 0, 11, 0x00000011}, + {0x28, 0, 0, 0x00000038}, {0x15, 8, 9, 0x000012b5}, + {0x15, 0, 8, 0x00000800}, {0x30, 0, 0, 0x00000017}, + {0x15, 0, 6, 0x00000011}, {0x28, 0, 0, 0x00000014}, + {0x45, 4, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, + {0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x000012b5}, + {0x6, 0, 0, 0x0000ffff}, {0x6, 0, 0, 0x00000000}, +}; + + +/* + * Definitions. + */ +int ptm_bfd_fetch_ifindex(const char *ifname) +{ + struct ifreq ifr; + + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) + > sizeof(ifr.ifr_name)) + log_error("interface-to-index: name truncated ('%s' -> '%s')", + ifr.ifr_name, ifname); + + if (ioctl(bglobal.bg_shop, SIOCGIFINDEX, &ifr) == -1) { + log_error("interface-to-index: %s translation failed: %s", + ifname, strerror(errno)); + return -1; + } + + return ifr.ifr_ifindex; +} + +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac) +{ + struct ifreq ifr; + + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) + > sizeof(ifr.ifr_name)) + log_error("interface-mac: name truncated ('%s' -> '%s')", + ifr.ifr_name, ifname); + + if (ioctl(bglobal.bg_shop, SIOCGIFHWADDR, &ifr) == -1) { + log_error("interface-mac: %s MAC retrieval failed: %s", ifname, + strerror(errno)); + return; + } + + memcpy(mac, ifr.ifr_hwaddr.sa_data, ETHERNET_ADDRESS_LENGTH); +} + + +/* Was _fetch_portname_from_ifindex() */ +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen) +{ + struct ifreq ifr; + + ifname[0] = 0; + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_ifindex = ifindex; + + if (ioctl(bglobal.bg_shop, SIOCGIFNAME, &ifr) == -1) { + log_error("index-to-interface: index %d failure: %s", ifindex, + strerror(errno)); + return; + } + + if (strlcpy(ifname, ifr.ifr_name, ifnamelen) >= ifnamelen) + log_debug("index-to-interface: name truncated '%s' -> '%s'", + ifr.ifr_name, ifname); +} + +int ptm_bfd_echo_sock_init(void) +{ + int s; + struct sock_fprog bpf = {.len = sizeof(bfd_echo_filter) + / sizeof(bfd_echo_filter[0]), + .filter = bfd_echo_filter}; + + s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + if (s == -1) { + log_error("echo-socket: creation failure: %s", strerror(errno)); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) + == -1) { + log_error("echo-socket: setsockopt(SO_ATTACH_FILTER): %s", + strerror(errno)); + close(s); + return -1; + } + + return s; +} + +int ptm_bfd_vxlan_sock_init(void) +{ + int s; + struct sock_fprog bpf = {.len = sizeof(bfd_vxlan_filter) + / sizeof(bfd_vxlan_filter[0]), + .filter = bfd_vxlan_filter}; + + s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + if (s == -1) { + log_error("vxlan-socket: creation failure: %s", + strerror(errno)); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) + == -1) { + log_error("vxlan-socket: setsockopt(SO_ATTACH_FILTER): %s", + strerror(errno)); + close(s); + return -1; + } + + return s; +} + +int bp_bind_dev(int sd __attribute__((__unused__)), + const char *dev __attribute__((__unused__))) +{ + /* + * TODO: implement this differently. It is not possible to + * SO_BINDTODEVICE after the daemon has dropped its privileges. + */ +#if 0 + size_t devlen = strlen(dev) + 1; + + if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, dev, devlen) == -1) { + log_warning("%s: setsockopt(SO_BINDTODEVICE, \"%s\"): %s", + __func__, dev, strerror(errno)); + return -1; + } +#endif + + return 0; +} + +uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len) +{ + char *ptr; + struct udp_psuedo_header pudp_hdr; + uint16_t csum; + + pudp_hdr.saddr = iph->saddr; + pudp_hdr.daddr = iph->daddr; + pudp_hdr.reserved = 0; + pudp_hdr.protocol = iph->protocol; + pudp_hdr.len = htons(len); + + ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len); + memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN); + memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len); + + csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len); + XFREE(MTYPE_BFDD_TMP, ptr); + return csum; +} + +#endif /* BFD_LINUX */ diff --git a/bfdd/log.c b/bfdd/log.c new file mode 100644 index 000000000000..d81d7cd9b38e --- /dev/null +++ b/bfdd/log.c @@ -0,0 +1,125 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * log.c: implements an abstraction between loggers interface. Implement all + * log backends in this file. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#include + +#include "bfd.h" + +#include "lib/log_int.h" + +void log_msg(int level, const char *fmt, va_list vl); + + +static int log_fg; +static int log_level = BLOG_DEBUG; + +void log_init(int foreground, enum blog_level level, + struct frr_daemon_info *fdi) +{ + log_fg = foreground; + log_level = level; + + openzlog(fdi->progname, fdi->logname, 0, + LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void log_msg(int level, const char *fmt, va_list vl) +{ + if (level < log_level) + return; + + switch (level) { + case BLOG_DEBUG: + vzlog(LOG_DEBUG, fmt, vl); + break; + + case BLOG_INFO: + vzlog(LOG_INFO, fmt, vl); + break; + + case BLOG_WARNING: + vzlog(LOG_WARNING, fmt, vl); + break; + + case BLOG_ERROR: + vzlog(LOG_ERR, fmt, vl); + break; + + case BLOG_FATAL: + vzlog(LOG_EMERG, fmt, vl); + break; + + default: + vfprintf(stderr, fmt, vl); + break; + } +} + +void log_info(const char *fmt, ...) +{ + va_list vl; + + va_start(vl, fmt); + log_msg(BLOG_INFO, fmt, vl); + va_end(vl); +} + +void log_debug(const char *fmt, ...) +{ + va_list vl; + + va_start(vl, fmt); + log_msg(BLOG_DEBUG, fmt, vl); + va_end(vl); +} + +void log_error(const char *fmt, ...) +{ + va_list vl; + + va_start(vl, fmt); + log_msg(BLOG_ERROR, fmt, vl); + va_end(vl); +} + +void log_warning(const char *fmt, ...) +{ + va_list vl; + + va_start(vl, fmt); + log_msg(BLOG_WARNING, fmt, vl); + va_end(vl); +} + +void log_fatal(const char *fmt, ...) +{ + va_list vl; + + va_start(vl, fmt); + log_msg(BLOG_FATAL, fmt, vl); + va_end(vl); + + exit(1); +} diff --git a/bfdd/ptm_adapter.c b/bfdd/ptm_adapter.c new file mode 100644 index 000000000000..ca44be6541ac --- /dev/null +++ b/bfdd/ptm_adapter.c @@ -0,0 +1,718 @@ +/* + * BFD PTM adapter code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#include "lib/libfrr.h" +#include "lib/queue.h" +#include "lib/stream.h" +#include "lib/zclient.h" + +#include "lib/bfd.h" + +#include "bfd.h" + +/* + * Data structures + */ +struct ptm_client_notification { + struct bfd_session *pcn_bs; + struct ptm_client *pcn_pc; + + TAILQ_ENTRY(ptm_client_notification) pcn_entry; +}; +TAILQ_HEAD(pcnqueue, ptm_client_notification); + +struct ptm_client { + uint32_t pc_pid; + struct pcnqueue pc_pcnqueue; + + TAILQ_ENTRY(ptm_client) pc_entry; +}; +TAILQ_HEAD(pcqueue, ptm_client); + +static struct pcqueue pcqueue; +static struct zclient *zclient; + + +/* + * Prototypes + */ +static int _ptm_msg_address(struct stream *msg, struct sockaddr_any *sa); + +static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa); +static int _ptm_msg_read(struct stream *msg, int command, + struct bfd_peer_cfg *bpc, struct ptm_client **pc); + +static struct ptm_client *pc_lookup(uint32_t pid); +static struct ptm_client *pc_new(uint32_t pid); +static void pc_free(struct ptm_client *pc); +static void pc_free_all(void); +static struct ptm_client_notification *pcn_new(struct ptm_client *pc, + struct bfd_session *bs); +static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc, + struct bfd_session *bs); +static void pcn_free(struct ptm_client_notification *pcn); + + +static void bfdd_dest_register(struct stream *msg); +static void bfdd_dest_deregister(struct stream *msg); +static void bfdd_client_register(struct stream *msg); +static void bfdd_client_deregister(struct stream *msg); + +/* + * Functions + */ +#ifdef BFD_DEBUG +static void debug_printbpc(const char *func, unsigned int line, + struct bfd_peer_cfg *bpc); + +static void debug_printbpc(const char *func, unsigned int line, + struct bfd_peer_cfg *bpc) +{ + char addr[3][128]; + char timers[3][128]; + + addr[0][0] = addr[1][0] = addr[2][0] = timers[0][0] = timers[1][0] = + timers[2][0] = 0; + + snprintf(addr[0], sizeof(addr[0]), "peer:%s", satostr(&bpc->bpc_peer)); + if (bpc->bpc_local.sa_sin.sin_family) + snprintf(addr[1], sizeof(addr[1]), " local:%s", + satostr(&bpc->bpc_local)); + + if (bpc->bpc_has_localif) + snprintf(addr[2], sizeof(addr[2]), " ifname:%s", + bpc->bpc_localif); + + if (bpc->bpc_has_vrfname) + snprintf(addr[2], sizeof(addr[2]), " vrf:%s", bpc->bpc_vrfname); + + if (bpc->bpc_has_recvinterval) + snprintf(timers[0], sizeof(timers[0]), " rx:%lu", + bpc->bpc_recvinterval); + + if (bpc->bpc_has_txinterval) + snprintf(timers[1], sizeof(timers[1]), " tx:%lu", + bpc->bpc_recvinterval); + + if (bpc->bpc_has_detectmultiplier) + snprintf(timers[2], sizeof(timers[2]), " detect-multiplier:%d", + bpc->bpc_detectmultiplier); + + log_debug("%s:%d: %s %s%s%s%s%s%s", func, line, + bpc->bpc_mhop ? "multi-hop" : "single-hop", addr[0], addr[1], + addr[2], timers[0], timers[1], timers[2]); +} + +#define DEBUG_PRINTBPC(bpc) debug_printbpc(__FILE__, __LINE__, (bpc)) +#else +#define DEBUG_PRINTBPC(bpc) +#endif /* BFD_DEBUG */ + +static int _ptm_msg_address(struct stream *msg, struct sockaddr_any *sa) +{ + switch (sa->sa_sin.sin_family) { + case AF_INET: + stream_putc(msg, sa->sa_sin.sin_family); + stream_put_in_addr(msg, &sa->sa_sin.sin_addr); + stream_putc(msg, 32); + break; + + case AF_INET6: + stream_putc(msg, sa->sa_sin6.sin6_family); + stream_put(msg, &sa->sa_sin6.sin6_addr, + sizeof(sa->sa_sin6.sin6_addr)); + stream_putc(msg, 128); + break; + + default: + return -1; + } + + return 0; +} + +int ptm_bfd_notify(struct bfd_session *bs) +{ + struct stream *msg; + struct sockaddr_any sac; + + /* + * Message format: + * - header: command, vrf + * - l: interface index + * - c: family + * - AF_INET: + * - 4 bytes: ipv4 + * - AF_INET6: + * - 16 bytes: ipv6 + * - c: prefix length + * - l: bfd status + * - c: family + * - AF_INET: + * - 4 bytes: ipv4 + * - AF_INET6: + * - 16 bytes: ipv6 + * - c: prefix length + * + * Commands: ZEBRA_BFD_DEST_REPLAY + * + * q(64), l(32), w(16), c(8) + */ + msg = zclient->obuf; + stream_reset(msg); + + /* TODO: VRF handling */ + zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT); + + /* This header will be handled by `zebra_ptm.c`. */ + stream_putl(msg, ZEBRA_INTERFACE_BFD_DEST_UPDATE); + + /* NOTE: Interface is a shortcut to avoid comparing source address. */ + stream_putl(msg, bs->ifindex); + + /* BFD destination prefix information. */ + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + _ptm_msg_address(msg, &bs->mhop.peer); + else + _ptm_msg_address(msg, &bs->shop.peer); + + /* BFD status */ + switch (bs->ses_state) { + case PTM_BFD_UP: + stream_putl(msg, BFD_STATUS_UP); + break; + + case PTM_BFD_ADM_DOWN: + case PTM_BFD_DOWN: + case PTM_BFD_INIT: + stream_putl(msg, BFD_STATUS_DOWN); + break; + + default: + stream_putl(msg, BFD_STATUS_UNKNOWN); + break; + } + + /* BFD source prefix information. */ + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + _ptm_msg_address(msg, &bs->mhop.local); + } else { + if (bs->local_address.sa_sin.sin_family) + _ptm_msg_address(msg, &bs->local_address); + else if (bs->local_address.sa_sin.sin_family) + _ptm_msg_address(msg, &bs->local_ip); + else { + sac = bs->shop.peer; + switch (sac.sa_sin.sin_family) { + case AF_INET: + memset(&sac.sa_sin.sin_addr, 0, + sizeof(sac.sa_sin.sin_family)); + break; + case AF_INET6: + memset(&sac.sa_sin6.sin6_addr, 0, + sizeof(sac.sa_sin6.sin6_family)); + break; + + default: + assert(false); + break; + } + + /* No local address found yet, so send zeroes. */ + _ptm_msg_address(msg, &sac); + } + } + + /* Write packet size. */ + stream_putw_at(msg, 0, stream_get_endp(msg)); + + return zclient_send_message(zclient); +} + +static void _ptm_msg_read_address(struct stream *msg, struct sockaddr_any *sa) +{ + uint16_t family; + + STREAM_GETW(msg, family); + + switch (family) { + case AF_INET: + sa->sa_sin.sin_family = family; + STREAM_GET(&sa->sa_sin.sin_addr, msg, + sizeof(sa->sa_sin.sin_addr)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin.sin_len = sizeof(sa->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return; + + case AF_INET6: + sa->sa_sin6.sin6_family = family; + STREAM_GET(&sa->sa_sin6.sin6_addr, msg, + sizeof(sa->sa_sin6.sin6_addr)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return; + + default: + log_warning("%s: invalid family: %d", __func__, family); + break; + } + +stream_failure: + memset(sa, 0, sizeof(*sa)); +} + +static int _ptm_msg_read(struct stream *msg, int command, + struct bfd_peer_cfg *bpc, struct ptm_client **pc) +{ + uint32_t pid; + uint8_t ttl; + uint8_t ifnamelen; + + /* + * Register/Deregister/Update Message format: + * - header: Command, VRF + * - l: pid + * - w: family + * - AF_INET: + * - l: destination ipv4 + * - AF_INET6: + * - 16 bytes: destination IPv6 + * - command != ZEBRA_BFD_DEST_DEREGISTER + * - l: min_rx + * - l: min_tx + * - c: detect multiplier + * - c: is_multihop? + * - multihop: + * - w: family + * - AF_INET: + * - l: destination ipv4 + * - AF_INET6: + * - 16 bytes: destination IPv6 + * - c: ttl + * - no multihop + * - AF_INET6: + * - w: family + * - 16 bytes: ipv6 address + * - c: ifname length + * - X bytes: interface name + * + * q(64), l(32), w(16), c(8) + */ + + /* Initialize parameters return values. */ + memset(bpc, 0, sizeof(*bpc)); + *pc = NULL; + + /* Find or allocate process context data. */ + STREAM_GETL(msg, pid); + + *pc = pc_new(pid); + if (*pc == NULL) { + log_debug("%s: failed to allocate memory", __func__); + return -1; + } + + /* Register/update peer information. */ + _ptm_msg_read_address(msg, &bpc->bpc_peer); + + /* Determine IP type from peer destination. */ + bpc->bpc_ipv4 = (bpc->bpc_peer.sa_sin.sin_family == AF_INET); + + /* Get peer configuration. */ + if (command != ZEBRA_BFD_DEST_DEREGISTER) { + STREAM_GETL(msg, bpc->bpc_recvinterval); + bpc->bpc_has_recvinterval = + (bpc->bpc_recvinterval != BPC_DEF_RECEIVEINTERVAL); + + STREAM_GETL(msg, bpc->bpc_txinterval); + bpc->bpc_has_txinterval = + (bpc->bpc_txinterval != BPC_DEF_TRANSMITINTERVAL); + + STREAM_GETC(msg, bpc->bpc_detectmultiplier); + bpc->bpc_has_detectmultiplier = + (bpc->bpc_detectmultiplier != BPC_DEF_DETECTMULTIPLIER); + } + + /* Read (single|multi)hop and its options. */ + STREAM_GETC(msg, bpc->bpc_mhop); + if (bpc->bpc_mhop) { + /* Read multihop source address and TTL. */ + _ptm_msg_read_address(msg, &bpc->bpc_local); + STREAM_GETC(msg, ttl); + + /* + * TODO: use TTL for something. The line below removes + * an unused variable compiler warning. + */ + ttl = ttl; + } else { + /* If target is IPv6, then we must obtain local address. */ + if (bpc->bpc_ipv4 == false) + _ptm_msg_read_address(msg, &bpc->bpc_local); + + /* + * Read interface name and make sure it fits our data + * structure, otherwise fail. + */ + STREAM_GETC(msg, ifnamelen); + if (ifnamelen > sizeof(bpc->bpc_localif)) { + log_error("%s: interface name is too big", __func__); + return -1; + } + + bpc->bpc_has_localif = ifnamelen > 0; + if (bpc->bpc_has_localif) { + STREAM_GET(bpc->bpc_localif, msg, ifnamelen); + bpc->bpc_localif[ifnamelen] = 0; + } + } + + /* Sanity check: peer and local address must match IP types. */ + if (bpc->bpc_local.sa_sin.sin_family != 0 + && (bpc->bpc_local.sa_sin.sin_family + != bpc->bpc_peer.sa_sin.sin_family)) { + log_warning("%s: peer family doesn't match local type", + __func__); + return -1; + } + + return 0; + +stream_failure: + return -1; +} + +static void bfdd_dest_register(struct stream *msg) +{ + struct ptm_client *pc; + struct ptm_client_notification *pcn; + struct bfd_session *bs; + struct bfd_peer_cfg bpc; + + /* Read the client context and peer data. */ + if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_REGISTER, &bpc, &pc) == -1) + return; + + DEBUG_PRINTBPC(&bpc); + + /* Find or start new BFD session. */ + bs = bs_peer_find(&bpc); + if (bs == NULL) { + bs = ptm_bfd_sess_new(&bpc); + if (bs == NULL) { + log_debug("%s: failed to create BFD session", __func__); + return; + } + } else { + /* Don't try to change echo/shutdown state. */ + bpc.bpc_echo = BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + bpc.bpc_shutdown = + BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + } + + /* Create client peer notification register. */ + pcn = pcn_new(pc, bs); + if (pcn == NULL) { + log_error("%s: failed to registrate notifications", __func__); + return; + } + + ptm_bfd_notify(bs); +} + +static void bfdd_dest_deregister(struct stream *msg) +{ + struct ptm_client *pc; + struct ptm_client_notification *pcn; + struct bfd_session *bs; + struct bfd_peer_cfg bpc; + + /* Read the client context and peer data. */ + if (_ptm_msg_read(msg, ZEBRA_BFD_DEST_DEREGISTER, &bpc, &pc) == -1) + return; + + DEBUG_PRINTBPC(&bpc); + + /* Find or start new BFD session. */ + bs = bs_peer_find(&bpc); + if (bs == NULL) { + log_debug("%s: failed to create BFD session", __func__); + return; + } + + /* Unregister client peer notification. */ + pcn = pcn_lookup(pc, bs); + pcn_free(pcn); +} + +/* + * header: command, VRF + * l: pid + */ +static void bfdd_client_register(struct stream *msg) +{ + struct ptm_client *pc; + uint32_t pid; + + /* Find or allocate process context data. */ + STREAM_GETL(msg, pid); + + pc = pc_new(pid); + if (pc == NULL) { + log_error("%s: failed to register client: %u", __func__, pid); + return; + } + + return; + +stream_failure: + log_error("%s: failed to register client", __func__); +} + +/* + * header: command, VRF + * l: pid + */ +static void bfdd_client_deregister(struct stream *msg) +{ + struct ptm_client *pc; + uint32_t pid; + + /* Find or allocate process context data. */ + STREAM_GETL(msg, pid); + + pc = pc_lookup(pid); + if (pc == NULL) { + log_debug("%s: failed to find client: %u", __func__, pid); + return; + } + + pc_free(pc); + + return; + +stream_failure: + log_error("%s: failed to deregister client", __func__); +} + +static int bfdd_replay(int cmd, struct zclient *zc, uint16_t len, vrf_id_t vid) +{ + struct stream *msg = zc->ibuf; + uint32_t rcmd; + + STREAM_GETL(msg, rcmd); + + switch (rcmd) { + case ZEBRA_BFD_DEST_REGISTER: + case ZEBRA_BFD_DEST_UPDATE: + bfdd_dest_register(msg); + break; + case ZEBRA_BFD_DEST_DEREGISTER: + bfdd_dest_deregister(msg); + break; + case ZEBRA_BFD_CLIENT_REGISTER: + bfdd_client_register(msg); + break; + case ZEBRA_BFD_CLIENT_DEREGISTER: + bfdd_client_deregister(msg); + break; + + default: + log_debug("%s: invalid message type %u", __func__, rcmd); + return -1; + } + + return 0; + +stream_failure: + log_error("%s: failed to find command", __func__); + return -1; +} + +static void bfdd_zebra_connected(struct zclient *zc) +{ + struct stream *msg = zc->obuf; + + /* Clean-up and free ptm clients data memory. */ + pc_free_all(); + + /* + * The replay is an empty message just to trigger client daemons + * configuration replay. + */ + stream_reset(msg); + zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, VRF_DEFAULT); + stream_putl(msg, ZEBRA_BFD_DEST_REPLAY); + stream_putw_at(msg, 0, stream_get_endp(msg)); + + zclient_send_message(zclient); +} + +void bfdd_zclient_init(struct zebra_privs_t *bfdd_priv) +{ + zclient = zclient_new_notify(master, &zclient_options_default); + assert(zclient != NULL); + zclient_init(zclient, ZEBRA_ROUTE_BFD, 0, bfdd_priv); + + /* + * We'll receive all messages through replay, however it will + * contain a special field with the real command inside so we + * avoid having to create too many handlers. + */ + zclient->bfd_dest_replay = bfdd_replay; + + /* Send replay request on zebra connect. */ + zclient->zebra_connected = bfdd_zebra_connected; +} + +void bfdd_zclient_stop(void) +{ + zclient_stop(zclient); + + /* Clean-up and free ptm clients data memory. */ + pc_free_all(); +} + + +/* + * Client handling. + */ +static struct ptm_client *pc_lookup(uint32_t pid) +{ + struct ptm_client *pc; + + TAILQ_FOREACH (pc, &pcqueue, pc_entry) { + if (pc->pc_pid != pid) + continue; + + break; + } + + return pc; +} + +static struct ptm_client *pc_new(uint32_t pid) +{ + struct ptm_client *pc; + + /* Look up first, if not found create the client. */ + pc = pc_lookup(pid); + if (pc != NULL) + return pc; + + /* Allocate the client data and save it. */ + pc = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*pc)); + if (pc == NULL) + return NULL; + + pc->pc_pid = pid; + TAILQ_INSERT_HEAD(&pcqueue, pc, pc_entry); + return pc; +} + +static void pc_free(struct ptm_client *pc) +{ + struct ptm_client_notification *pcn; + + if (pc == NULL) + return; + + TAILQ_REMOVE(&pcqueue, pc, pc_entry); + + while (!TAILQ_EMPTY(&pc->pc_pcnqueue)) { + pcn = TAILQ_FIRST(&pc->pc_pcnqueue); + pcn_free(pcn); + } + + XFREE(MTYPE_BFDD_CONTROL, pc); +} + +static void pc_free_all(void) +{ + struct ptm_client *pc; + + while (!TAILQ_EMPTY(&pcqueue)) { + pc = TAILQ_FIRST(&pcqueue); + pc_free(pc); + } +} + +static struct ptm_client_notification *pcn_new(struct ptm_client *pc, + struct bfd_session *bs) +{ + struct ptm_client_notification *pcn; + + /* Try to find an existing pcn fist. */ + pcn = pcn_lookup(pc, bs); + if (pcn != NULL) + return pcn; + + /* Save the client notification data. */ + pcn = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*pcn)); + if (pcn == NULL) + return NULL; + + TAILQ_INSERT_HEAD(&pc->pc_pcnqueue, pcn, pcn_entry); + pcn->pcn_pc = pc; + pcn->pcn_bs = bs; + bs->refcount++; + + return pcn; +} + +static struct ptm_client_notification *pcn_lookup(struct ptm_client *pc, + struct bfd_session *bs) +{ + struct ptm_client_notification *pcn; + + TAILQ_FOREACH (pcn, &pc->pc_pcnqueue, pcn_entry) { + if (pcn->pcn_bs != bs) + continue; + + break; + } + + return pcn; +} + +static void pcn_free(struct ptm_client_notification *pcn) +{ + struct ptm_client *pc; + struct bfd_session *bs; + + if (pcn == NULL) + return; + + /* Handle session de-registration. */ + bs = pcn->pcn_bs; + pcn->pcn_bs = NULL; + bs->refcount--; + + /* Handle ptm_client deregistration. */ + pc = pcn->pcn_pc; + pcn->pcn_pc = NULL; + TAILQ_REMOVE(&pc->pc_pcnqueue, pcn, pcn_entry); + + XFREE(MTYPE_BFDD_NOTIFICATION, pcn); +} diff --git a/bfdd/subdir.am b/bfdd/subdir.am new file mode 100644 index 000000000000..86923f5cec50 --- /dev/null +++ b/bfdd/subdir.am @@ -0,0 +1,33 @@ +# +# bfdd +# + +if BFDD +noinst_LIBRARIES += bfdd/libbfd.a +sbin_PROGRAMS += bfdd/bfdd +dist_examples_DATA += bfdd/bfdd.conf.sample +endif + +bfdd_libbfd_a_SOURCES = \ + bfdd/bfd.c \ + bfdd/bfdd_vty.c \ + bfdd/bfd_packet.c \ + bfdd/bsd.c \ + bfdd/config.c \ + bfdd/control.c \ + bfdd/event.c \ + bfdd/linux.c \ + bfdd/log.c \ + bfdd/ptm_adapter.c \ + # end + +bfdd/bfdd_vty_clippy.c: $(CLIPPY_DEPS) +bfdd/bfdd_vty.$(OBJEXT): bfdd/bfdd_vty_clippy.c + +noinst_HEADERS += \ + bfdd/bfdctl.h \ + bfdd/bfd.h \ + # end + +bfdd_bfdd_SOURCES = bfdd/bfdd.c +bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la diff --git a/bgpd/bgp_bfd.c b/bgpd/bgp_bfd.c index c7d624987062..47dffd146ab6 100644 --- a/bgpd/bgp_bfd.c +++ b/bgpd/bgp_bfd.c @@ -509,9 +509,13 @@ void bgp_bfd_peer_config_write(struct vty *vty, struct peer *peer, char *addr) bfd_info = (struct bfd_info *)peer->bfd_info; if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) +#if HAVE_BFDD > 0 + vty_out(vty, " neighbor %s bfd\n", addr); +#else vty_out(vty, " neighbor %s bfd %d %d %d\n", addr, bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); +#endif /* HAVE_BFDD */ if (bfd_info->type != BFD_TYPE_NOT_CONFIGURED) vty_out(vty, " neighbor %s bfd %s\n", addr, @@ -556,7 +560,12 @@ DEFUN (neighbor_bfd, return CMD_SUCCESS; } -DEFUN (neighbor_bfd_param, +#if HAVE_BFDD > 0 +DEFUN_HIDDEN( +#else +DEFUN( +#endif /* HAVE_BFDD */ + neighbor_bfd_param, neighbor_bfd_param_cmd, "neighbor bfd (2-255) (50-60000) (50-60000)", NEIGHBOR_STR @@ -628,14 +637,21 @@ DEFUN_HIDDEN (neighbor_bfd_type, DEFUN (no_neighbor_bfd, no_neighbor_bfd_cmd, +#if HAVE_BFDD > 0 + "no neighbor bfd", +#else "no neighbor bfd [(2-255) (50-60000) (50-60000)]", +#endif /* HAVE_BFDD */ NO_STR NEIGHBOR_STR NEIGHBOR_ADDR_STR2 "Disables BFD support\n" +#if HAVE_BFDD == 0 "Detect Multiplier\n" "Required min receive interval\n" - "Desired min transmit interval\n") + "Desired min transmit interval\n" +#endif /* !HAVE_BFDD */ +) { int idx_peer = 2; struct peer *peer; diff --git a/configure.ac b/configure.ac index 42c2963ee671..af0c3462634e 100755 --- a/configure.ac +++ b/configure.ac @@ -454,6 +454,8 @@ AC_ARG_ENABLE([numeric_version], AS_HELP_STRING([--enable-numeric-version], [Only numeric digits allowed in version (for Alpine)])) AC_ARG_ENABLE([gcov], AS_HELP_STRING([--enable-gcov], [Add code coverage information])) +AC_ARG_ENABLE(bfdd, + AS_HELP_STRING([--disable-bfdd], [do not build bfdd])) AS_IF([test "${enable_clippy_only}" != "yes"], [ AC_CHECK_HEADERS(json-c/json.h) @@ -1370,6 +1372,30 @@ AS_IF([test "${enable_ldpd}" != "no"], [ AC_DEFINE(HAVE_LDPD, 1, ldpd) ]) +if test "$enable_bfdd" = "no"; then + AC_DEFINE(HAVE_BFDD, 0, bfdd) + BFDD="" +else + AC_DEFINE(HAVE_BFDD, 1, bfdd) + BFDD="bfdd" + + case $host_os in + linux*) + AC_DEFINE(BFD_LINUX, 1, bfdd) + ;; + + *) + AC_DEFINE(BFD_BSD, 1, bfdd) + ;; + esac +fi + +AM_CONDITIONAL(BFDD, [test "x$BFDD" = "xbfdd"]) + +if test $ac_cv_lib_json_c_json_object_get = no -a "x$BFDD" = "xbfdd"; then + AC_MSG_ERROR(["you must use json-c library to use bfdd"]) +fi + NHRPD="" case "$host_os" in linux*) @@ -1882,6 +1908,7 @@ AC_SUBST(frr_statedir) AC_DEFINE_UNQUOTED(LDPD_SOCKET, "$frr_statedir/ldpd.sock",ldpd control socket) AC_DEFINE_UNQUOTED(ZEBRA_SERV_PATH, "$frr_statedir/zserv.api",zebra api socket) +AC_DEFINE_UNQUOTED(BFDD_CONTROL_SOCKET, "$frr_statedir/bfdd.sock", bfdd control socket) AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$frr_statedir",daemon vty directory) dnl autoconf does this, but it does it too late... diff --git a/debianpkg/backports/ubuntu12.04/debian/rules b/debianpkg/backports/ubuntu12.04/debian/rules index 01ad81d371a8..3a6c80297eb5 100755 --- a/debianpkg/backports/ubuntu12.04/debian/rules +++ b/debianpkg/backports/ubuntu12.04/debian/rules @@ -134,6 +134,7 @@ override_dh_auto_configure: --enable-poll=yes \ $(USE_CUMULUS) \ $(USE_PIM) \ + --disable-bfdd \ --enable-dependency-tracking \ $(USE_BGP_VNC) \ $(shell dpkg-buildflags --export=configure); \ diff --git a/debianpkg/backports/ubuntu14.04/debian/rules b/debianpkg/backports/ubuntu14.04/debian/rules index f7b9428658bc..f7468d6f791f 100755 --- a/debianpkg/backports/ubuntu14.04/debian/rules +++ b/debianpkg/backports/ubuntu14.04/debian/rules @@ -16,6 +16,7 @@ WANT_CUMULUS_MODE ?= 0 WANT_MULTIPATH ?= 1 WANT_SNMP ?= 0 WANT_RPKI ?= 0 +WANT_BFD ?= 1 # NOTES: # @@ -108,6 +109,12 @@ else USE_RPKI=--disable-rpki endif +ifeq ($(WANT_BFD), 1) + USE_BFD=--enable-bfdd +else + USE_BFD=--disable-bfdd +endif + ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) DEBIAN_JOBS := $(subst parallel=,,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) endif @@ -159,6 +166,7 @@ override_dh_auto_configure: --enable-dependency-tracking \ $(USE_BGP_VNC) \ $(USE_RPKI) \ + $(USE_BFD) \ $(shell dpkg-buildflags --export=configure); \ fi diff --git a/debianpkg/rules b/debianpkg/rules index 9c84c065161f..c1cb865490f3 100755 --- a/debianpkg/rules +++ b/debianpkg/rules @@ -16,6 +16,7 @@ WANT_CUMULUS_MODE ?= 0 WANT_MULTIPATH ?= 1 WANT_SNMP ?= 0 WANT_RPKI ?= 0 +WANT_BFD ?= 1 # NOTES: # @@ -108,6 +109,12 @@ else USE_RPKI=--disable-rpki endif +ifeq ($(WANT_BFD), 1) + USE_BFD=--enable-bfdd +else + USE_BFD=--disable-bfdd +endif + ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) DEBIAN_JOBS := $(subst parallel=,,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) endif @@ -160,6 +167,7 @@ override_dh_auto_configure: --enable-dependency-tracking \ $(USE_BGP_VNC) \ $(USE_RPKI) \ + $(USE_BFD) \ $(shell dpkg-buildflags --export=configure); \ fi diff --git a/doc/Makefile.am b/doc/Makefile.am index fbceb8b04cbf..1f6a0d87fd98 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -90,6 +90,10 @@ if STATICD man_MANS += $(MANPAGE_BUILDDIR)/staticd.8 endif +if BFDD +man_MANS += $(MANPAGE_BUILDDIR)/bfdd.8 +endif + # Automake is particular about manpages. It is aware of them and has some # special facilities for handling them, but it assumes that manpages are always # given in groff source and so these facilities are limited to simply @@ -159,6 +163,8 @@ EXTRA_DIST = frr-sphinx.mk \ manpages/vtysh.rst \ manpages/watchfrr.rst \ manpages/zebra.rst \ + manpages/bfdd.rst \ + manpages/bfd-options.rst \ developer/bgpd.rst \ developer/bgp-typecodes.rst \ developer/building-frr-on-alpine.rst \ @@ -234,7 +240,8 @@ EXTRA_DIST = frr-sphinx.mk \ user/vnc.rst \ user/vtysh.rst \ user/zebra.rst \ - user/flowspec.rst \ + user/bfd.rst \ + user/flowspec.rst \ mpls/ChangeLog.opaque.txt \ mpls/ospfd.conf \ mpls/cli_summary.txt \ diff --git a/doc/manpages/bfd-options.rst b/doc/manpages/bfd-options.rst new file mode 100644 index 000000000000..e335ed120bde --- /dev/null +++ b/doc/manpages/bfd-options.rst @@ -0,0 +1,10 @@ +BFD SOCKET +---------- + +The following option controls the BFD daemon control socket location. + +.. option:: --bfdctl bfd-control-socket + + Opens the BFD daemon control socket located at the pointed location. + + (default: |INSTALL_PREFIX_STATE|/bfdd.sock) diff --git a/doc/manpages/bfdd.rst b/doc/manpages/bfdd.rst new file mode 100644 index 000000000000..1f8b1475f473 --- /dev/null +++ b/doc/manpages/bfdd.rst @@ -0,0 +1,40 @@ +**** +BFDD +**** + +.. include:: defines.rst +.. |DAEMON| replace:: bfdd + +SYNOPSIS +======== +|DAEMON| |synopsis-options-hv| + +|DAEMON| |synopsis-options| + +DESCRIPTION +=========== +|DAEMON| is a communication failure detection component that works with +the FRRouting routing engine. + +OPTIONS +======= +OPTIONS available for the |DAEMON| command: + +.. include:: common-options.rst +.. include:: bfd-options.rst + +FILES +===== + +|INSTALL_PREFIX_SBIN|/|DAEMON| + The default location of the |DAEMON| binary. + +|INSTALL_PREFIX_ETC|/|DAEMON|.conf + The default location of the |DAEMON| config file. + +$(PWD)/|DAEMON|.log + If the |DAEMON| process is configured to output logs to a file, then you + will find this file in the directory where you started |DAEMON|. + +.. include:: epilogue.rst + diff --git a/doc/manpages/common-options.rst b/doc/manpages/common-options.rst index 1b2eb18dea0b..1e99010505b4 100644 --- a/doc/manpages/common-options.rst +++ b/doc/manpages/common-options.rst @@ -124,6 +124,7 @@ These following options control the daemon's VTY (interactive command line) inte eigrpd 2613 pbrd 2615 staticd 2616 + bfdd 2617 Port 2607 is used for ospfd's Opaque LSA API, while port 2600 is used for the (insecure) TCP-ZEBRA interface. diff --git a/doc/manpages/conf.py b/doc/manpages/conf.py index 4d5797f61393..e540d236ea14 100644 --- a/doc/manpages/conf.py +++ b/doc/manpages/conf.py @@ -332,6 +332,7 @@ ('watchfrr', 'watchfrr', 'a program to monitor the status of FRRouting daemons', [], 8), ('vtysh', 'vtysh', 'an integrated shell for FRRouting.', [], 1), ('frr', 'frr', 'a systemd interaction script', [], 1), + ('bfdd', 'bfdd', fwfrr.format("a bfd"), [], 8), ] # -- Options for Texinfo output ------------------------------------------- diff --git a/doc/user/bfd.rst b/doc/user/bfd.rst new file mode 100644 index 000000000000..0e0fd23ecef9 --- /dev/null +++ b/doc/user/bfd.rst @@ -0,0 +1,302 @@ +.. _bfd: + +********************************** +Bidirectional Forwarding Detection +********************************** + +:abbr:`BFD (Bidirectional Forwarding Detection)` stands for +Bidirectional Forwarding Detection and it is described and extended by +the following RFCs: + +* :rfc:`5880` +* :rfc:`5881` +* :rfc:`5883` + +Currently, there are two implementations of the BFD commands in FRR: + +* :abbr:`PTM (Prescriptive Topology Manager)`: an external daemon which + implements BFD; +* ``bfdd``: a BFD implementation that is able to talk with remote peers; + +This document will focus on the later implementation: *bfdd*. + + +.. _bfd-starting: + +Starting BFD +============ + +*bfdd* default configuration file is :file:`bfdd.conf`. *bfdd* searches +the current directory first then |INSTALL_PREFIX_ETC|/bfdd.conf. All of +*bfdd*'s command must be configured in :file:`bfdd.conf`. + +*bfdd* specific invocation options are described below. Common options +may also be specified (:ref:`common-invocation-options`). + +.. program:: bfdd + +.. option:: --bfdctl + + Set the BFD daemon control socket location. If using a non-default + socket location. + + /usr/lib/frr/bfdd --bfdctl /tmp/bfdd.sock + + + The default UNIX socket location is: + + #define BFDD_CONTROL_SOCKET "|INSTALL_PREFIX_STATE|/bfdd.sock" + + +.. _bfd-commands: + +BFDd Commands +============= + +.. index:: bfd +.. clicmd:: bfd + + Opens the BFD daemon configuration node. + +.. index:: peer [{multihop|local-address |interface IFNAME|vrf NAME}] +.. clicmd:: peer [{multihop|local-address |interface IFNAME|vrf NAME}] + + Creates and configures a new BFD peer to listen and talk to. + + `multihop` tells the BFD daemon that we should expect packets with + TTL less than 254 (because it will take more than one hop) and to + listen on the multihop port (4784). When using multi-hop mode + `echo-mode` will not work (see :rfc:`5883` section 3). + + `local-address` provides a local address that we should bind our + peer listener to and the address we should use to send the packets. + This option is mandatory for IPv6. + + `interface` selects which interface we should use. This option + conflicts with `vrf`. + + `vrf` selects which domain we want to use. + +.. index:: no peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrfname}] +.. clicmd:: no peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrfname}] + + Stops and removes the selected peer. + +.. index:: show bfd peers [json] +.. clicmd:: show bfd peers [json] + + Show all configured BFD peers information and current status. + +.. index:: show bfd peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrfname}]> [json] +.. clicmd:: show bfd peer $peer [{multihop|local-address $local|interface IFNAME$ifname|vrf NAME$vrfname}]> [json] + + Show status for a specific BFD peer. + + +.. _bfd-peer-config: + +Peer Configurations +------------------- + +.. index:: detect-multiplier (2-255) +.. clicmd:: detect-multiplier (2-255) + + Configures the detection multiplier to determine packet loss. The + remote transmission interval will be multiplied by this value to + determine the connection loss detection timer. The default value is + 3. + + Example: when the local system has `detect-multiplier 3` and the + remote system has `transmission interval 300`, the local system will + detect failures only after 900 milliseconds without receiving + packets. + +.. index:: receive-interval (10-60000) +.. clicmd:: receive-interval (10-60000) + + Configures the minimum interval that this system is capable of + receiving control packets. The default value is 300 milliseconds. + +.. index:: transmit-interval (10-60000) +.. clicmd:: transmit-interval (10-60000) + + The minimum transmission interval (less jitter) that this system + wants to use to send BFD control packets. + +.. index:: echo-interval (10-60000) +.. clicmd:: echo-interval (10-60000) + + Configures the minimal echo receive transmission interval that this + system is capable of handling. + +.. index:: [no] echo-mode +.. clicmd:: [no] echo-mode + + Enables or disables the echo transmission mode. This mode is disabled + by default. + + It is recommended that the transmission interval of control packets + to be increased after enabling echo-mode to reduce bandwidth usage. + For example: `transmission-interval 2000`. + + Echo mode is not supported on multi-hop setups (see :rfc:`5883` + section 3). + +.. index:: [no] shutdown +.. clicmd:: [no] shutdown + + Enables or disables the peer. When the peer is disabled an + 'administrative down' message is sent to the remote peer. + +.. index:: label WORD +.. clicmd:: label WORD + + Labels a peer with the provided word. This word can be referenced + later on other daemons to refer to a specific peer. + + +.. _bfd-bgp-peer-config: + +BGP BFD Configuration +--------------------- + +.. index:: neighbor bfd +.. clicmd:: neighbor bfd + + Listen for BFD events registered on the same target as this BGP + neighbor. When BFD peer goes down it immediately asks BGP to shutdown + the connection with its neighbor and, when it goes back up, notify + BGP to try to connect to it. + +.. index:: no neighbor bfd +.. clicmd:: no neighbor bfd + + Removes any notification registration for this neighbor. + + +.. _bfd-configuration: + +Configuration +============= + +Before applying ``bfdd`` rules to integrated daemons (like BGPd), we must +create the corresponding peers inside the ``bfd`` configuration node. + +Here is an example of BFD configuration: + +:: + + bfd + peer 192.168.0.1 + label home-peer + no shutdown + ! + ! + router bgp 65530 + neighbor 192.168.0.1 remote-as 65531 + neighbor 192.168.0.1 bfd + neighbor 192.168.0.2 remote-as 65530 + neighbor 192.168.0.2 bfd + neighbor 192.168.0.3 remote-as 65532 + neighbor 192.168.0.3 bfd + ! + +Peers can be identified by its address (use ``multihop`` when you need +to specify a multi hop peer) or can be specified manually by a label. + +Here are the available peer configurations: + +:: + + bfd + + ! configure a peer on an specific interface + peer 192.168.0.1 interface eth0 + no shutdown + ! + + ! configure a multihop peer + peer 192.168.0.2 multihop local-address 192.168.0.3 + shutdown + ! + + ! configure a peer in a different vrf + peer 192.168.0.3 vrf foo + shutdown + ! + + ! configure a peer with every option possible + peer 192.168.0.4 + label peer-label + detect-multiplier 50 + receive-interval 60000 + transmit-interval 3000 + shutdown + ! + + ! remove a peer + no peer 192.168.0.3 vrf foo + + +.. _bfd-status: + +Status +====== + +You can inspect the current BFD peer status with the following commands: + +:: + + frr# show bfd peers + BFD Peers: + peer 192.168.0.1 + ID: 1 + Remote ID: 1 + Status: up + Uptime: 1 minute(s), 51 second(s) + Diagnostics: ok + Remote diagnostics: ok + Local timers: + Receive interval: 300ms + Transmission interval: 300ms + Echo transmission interval: disabled + Remote timers: + Receive interval: 300ms + Transmission interval: 300ms + Echo transmission interval: 50ms + + peer 192.168.1.1 + label: router3-peer + ID: 2 + Remote ID: 2 + Status: up + Uptime: 1 minute(s), 53 second(s) + Diagnostics: ok + Remote diagnostics: ok + Local timers: + Receive interval: 300ms + Transmission interval: 300ms + Echo transmission interval: disabled + Remote timers: + Receive interval: 300ms + Transmission interval: 300ms + Echo transmission interval: 50ms + + frr# show bfd peer 192.168.1.1 + BFD Peer: + peer 192.168.1.1 + label: router3-peer + ID: 2 + Remote ID: 2 + Status: up + Uptime: 3 minute(s), 4 second(s) + Diagnostics: ok + Remote diagnostics: ok + Local timers: + Receive interval: 300ms + Transmission interval: 300ms + Echo transmission interval: disabled + Remote timers: + Receive interval: 300ms + Transmission interval: 300ms + Echo transmission interval: 50ms diff --git a/doc/user/index.rst b/doc/user/index.rst index 14688e05d8e3..5818551343ae 100644 --- a/doc/user/index.rst +++ b/doc/user/index.rst @@ -39,6 +39,7 @@ Protocols :maxdepth: 2 zebra + bfd bgp babeld ldpd diff --git a/doc/user/installation.rst b/doc/user/installation.rst index 158e2c859524..3da5a6cd3037 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -103,6 +103,10 @@ options from the list below. Do not build bgpd. +.. option:: --disable-bfdd + + Do not build bfdd. + .. option:: --disable-bgp-announce Make *bgpd* which does not make bgp announcements at all. This diff --git a/doc/user/overview.rst b/doc/user/overview.rst index 4886b5759428..369f17dcba1f 100644 --- a/doc/user/overview.rst +++ b/doc/user/overview.rst @@ -245,6 +245,14 @@ FRR implements the following RFCs: - :rfc:`7552` :t:`Updates to LDP for IPv6, R. Asati, C. Pignataro, K. Raza, V. Manral, and R. Papneja. June 2015.` +- :rfc:`5880` + :t:`Bidirectional Forwarding Detection (BFD), D. Katz, D. Ward. June 2010` +- :rfc:`5881` + :t:`Bidirectional Forwarding Detection (BFD) for IPv4 and IPv6 (Single Hop), + D. Katz, D. Ward. June 2010` +- :rfc:`5883` + :t:`Bidirectional Forwarding Detection (BFD) for Multihop Paths, D. Katz, + D. Ward. June 2010` **When SNMP support is enabled, the following RFCs are also supported:** diff --git a/doc/user/setup.rst b/doc/user/setup.rst index e9fd44a34781..68ce14982be9 100644 --- a/doc/user/setup.rst +++ b/doc/user/setup.rst @@ -31,6 +31,7 @@ systemd. The file initially looks like this: sharpd=no staticd=no pbrd=no + bfdd=no To enable a particular daemon, simply change the corresponding 'no' to 'yes'. Subsequent service restarts should start the daemon. @@ -66,6 +67,7 @@ This file has several parts. Here is an example: sharpd_options=" --daemon -A 127.0.0.1" staticd_options=" --daemon -A 127.0.0.1" pbrd_options=" --daemon -A 127.0.0.1" + bfdd_options=" --daemon -A 127.0.0.1" # The list of daemons to watch is automatically generated by the init script. watchfrr_enable=yes @@ -136,6 +138,7 @@ add the following entries to :file:`/etc/services`. pimd 2611/tcp # PIMd vty ldpd 2612/tcp # LDPd vty eigprd 2613/tcp # EIGRPd vty + bfdd 2617/tcp # bfdd vty If you use a FreeBSD newer than 2.2.8, the above entries are already added to diff --git a/lib/command.c b/lib/command.c index a98654dd2331..7eda239ee4a1 100644 --- a/lib/command.c +++ b/lib/command.c @@ -143,6 +143,8 @@ const char *node_names[] = { */ "bgp ipv6 flowspec", /* BGP_FLOWSPECV6_NODE */ + "bfd", /* BFD_NODE */ + "bfd peer", /* BFD_PEER_NODE */ }; /* clang-format on */ @@ -987,6 +989,9 @@ enum node_type node_parent(enum node_type node) case LDP_PSEUDOWIRE_NODE: ret = LDP_L2VPN_NODE; break; + case BFD_PEER_NODE: + ret = BFD_NODE; + break; default: ret = CONFIG_NODE; break; @@ -1433,6 +1438,7 @@ void cmd_exit(struct vty *vty) case RMAP_NODE: case PBRMAP_NODE: case VTY_NODE: + case BFD_NODE: vty->node = CONFIG_NODE; break; case BGP_IPV4_NODE: @@ -1474,6 +1480,9 @@ void cmd_exit(struct vty *vty) case LINK_PARAMS_NODE: vty->node = INTERFACE_NODE; break; + case BFD_PEER_NODE: + vty->node = BFD_NODE; + break; default: break; } @@ -1544,6 +1553,8 @@ DEFUN (config_end, case KEYCHAIN_KEY_NODE: case VTY_NODE: case LINK_PARAMS_NODE: + case BFD_NODE: + case BFD_PEER_NODE: vty_config_unlock(vty); vty->node = ENABLE_NODE; break; diff --git a/lib/command.h b/lib/command.h index da9b92ec6db2..75b69507ec7b 100644 --- a/lib/command.h +++ b/lib/command.h @@ -139,6 +139,8 @@ enum node_type { connections.*/ BGP_FLOWSPECV4_NODE, /* BGP IPv4 FLOWSPEC Address-Family */ BGP_FLOWSPECV6_NODE, /* BGP IPv6 FLOWSPEC Address-Family */ + BFD_NODE, /* BFD protocol mode. */ + BFD_PEER_NODE, /* BFD peer configuration mode. */ NODE_TYPE_MAX, /* maximum */ }; diff --git a/lib/json.h b/lib/json.h index 675d852af718..788d1d6efa1d 100644 --- a/lib/json.h +++ b/lib/json.h @@ -23,6 +23,20 @@ #if defined(HAVE_JSON_C_JSON_H) #include + +/* + * FRR style JSON iteration. + * Usage: JSON_FOREACH(...) { ... } + */ +#define JSON_FOREACH(jo, joi, join) \ + /* struct json_object *jo; */ \ + /* struct json_object_iterator joi; */ \ + /* struct json_object_iterator join; */ \ + for ((joi) = json_object_iter_begin((jo)), \ + (join) = json_object_iter_end((jo)); \ + json_object_iter_equal(&(joi), &(join)) == 0; \ + json_object_iter_next(&(joi))) + #else #include diff --git a/lib/route_types.txt b/lib/route_types.txt index cfa55e468cd3..72f59a1b7860 100644 --- a/lib/route_types.txt +++ b/lib/route_types.txt @@ -81,6 +81,7 @@ ZEBRA_ROUTE_BGP_DIRECT_EXT, bgp-direct-to-nve-groups, NULL, 'e', 0, 0, 0, "BGP2V ZEBRA_ROUTE_BABEL, babel, babeld, 'A', 1, 1, 1, "Babel" ZEBRA_ROUTE_SHARP, sharp, sharpd, 'D', 1, 1, 1, "SHARP" ZEBRA_ROUTE_PBR, pbr, pbrd, 'F', 1, 1, 0, "PBR" +ZEBRA_ROUTE_BFD, bfd, bfdd, '-', 0, 0, 0, "BFD" ZEBRA_ROUTE_ALL, wildcard, none, '-', 0, 0, 0, "-" @@ -107,3 +108,4 @@ ZEBRA_ROUTE_VNC_DIRECT, "VNC direct (not via zebra) routes" ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)" ZEBRA_ROUTE_SHARP, "Super Happy Advanced Routing Protocol (sharpd)" ZEBRA_ROUTE_PBR, "Policy Based Routing (PBR)" +ZEBRA_ROUTE_BFD, "Bidirectional Fowarding Detection (BFD)" diff --git a/lib/vty.c b/lib/vty.c index 87cae8773ae6..15e934b7077a 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -814,6 +814,8 @@ static void vty_end_config(struct vty *vty) case KEYCHAIN_KEY_NODE: case VTY_NODE: case BGP_EVPN_VNI_NODE: + case BFD_NODE: + case BFD_PEER_NODE: vty_config_unlock(vty); vty->node = ENABLE_NODE; break; @@ -1210,6 +1212,8 @@ static void vty_stop_input(struct vty *vty) case KEYCHAIN_NODE: case KEYCHAIN_KEY_NODE: case VTY_NODE: + case BFD_NODE: + case BFD_PEER_NODE: vty_config_unlock(vty); vty->node = ENABLE_NODE; break; diff --git a/lib/zclient.h b/lib/zclient.h index 49419b3df339..962b1707c948 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -108,6 +108,7 @@ typedef enum { ZEBRA_VRF_LABEL, ZEBRA_INTERFACE_VRF_UPDATE, ZEBRA_BFD_CLIENT_REGISTER, + ZEBRA_BFD_CLIENT_DEREGISTER, ZEBRA_INTERFACE_ENABLE_RADV, ZEBRA_INTERFACE_DISABLE_RADV, ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB, diff --git a/ospf6d/ospf6_bfd.c b/ospf6d/ospf6_bfd.c index 7955121365ce..e7284a665935 100644 --- a/ospf6d/ospf6_bfd.c +++ b/ospf6d/ospf6_bfd.c @@ -276,11 +276,14 @@ void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi, */ void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi) { +#if HAVE_BFDD == 0 struct bfd_info *bfd_info; +#endif /* ! HAVE_BFDD */ if (!oi->bfd_info) return; +#if HAVE_BFDD == 0 bfd_info = (struct bfd_info *)oi->bfd_info; if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) @@ -288,6 +291,7 @@ void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi) bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); else +#endif /* ! HAVE_BFDD */ vty_out(vty, " ipv6 ospf6 bfd\n"); } @@ -329,7 +333,12 @@ DEFUN (ipv6_ospf6_bfd, return CMD_SUCCESS; } -DEFUN (ipv6_ospf6_bfd_param, +#if HAVE_BFDD > 0 +DEFUN_HIDDEN( +#else +DEFUN( +#endif /* HAVE_BFDD */ + ipv6_ospf6_bfd_param, ipv6_ospf6_bfd_param_cmd, "ipv6 ospf6 bfd (2-255) (50-60000) (50-60000)", IP6_STR diff --git a/ospfd/ospf_bfd.c b/ospfd/ospf_bfd.c index 0f7fb50778df..df41897660ac 100644 --- a/ospfd/ospf_bfd.c +++ b/ospfd/ospf_bfd.c @@ -290,17 +290,21 @@ void ospf_bfd_info_nbr_create(struct ospf_interface *oi, void ospf_bfd_write_config(struct vty *vty, struct ospf_if_params *params) { +#if HAVE_BFDD == 0 struct bfd_info *bfd_info; +#endif /* ! HAVE_BFDD */ if (!params->bfd_info) return; +#if HAVE_BFDD == 0 bfd_info = (struct bfd_info *)params->bfd_info; if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) vty_out(vty, " ip ospf bfd %d %d %d\n", bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); else +#endif /* ! HAVE_BFDD */ vty_out(vty, " ip ospf bfd\n"); } @@ -373,7 +377,12 @@ DEFUN (ip_ospf_bfd, return CMD_SUCCESS; } -DEFUN (ip_ospf_bfd_param, +#if HAVE_BFDD > 0 +DEFUN_HIDDEN( +#else +DEFUN( +#endif /* HAVE_BFDD */ + ip_ospf_bfd_param, ip_ospf_bfd_param_cmd, "ip ospf bfd (2-255) (50-60000) (50-60000)", "IP Information\n" @@ -407,14 +416,21 @@ DEFUN (ip_ospf_bfd_param, DEFUN (no_ip_ospf_bfd, no_ip_ospf_bfd_cmd, +#if HAVE_BFDD > 0 + "no ip ospf bfd", +#else "no ip ospf bfd [(2-255) (50-60000) (50-60000)]", +#endif /* HAVE_BFDD */ NO_STR "IP Information\n" "OSPF interface commands\n" "Disables BFD support\n" +#if HAVE_BFDD == 0 "Detect Multiplier\n" "Required min receive interval\n" - "Desired min transmit interval\n") + "Desired min transmit interval\n" +#endif /* !HAVE_BFDD */ +) { VTY_DECLVAR_CONTEXT(interface, ifp); struct ospf_if_params *params; diff --git a/pimd/pim_bfd.c b/pimd/pim_bfd.c index 4a3cf715dabb..f7a217b514f4 100644 --- a/pimd/pim_bfd.c +++ b/pimd/pim_bfd.c @@ -51,10 +51,12 @@ void pim_bfd_write_config(struct vty *vty, struct interface *ifp) if (!bfd_info) return; +#if HAVE_BFDD == 0 if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG)) vty_out(vty, " ip pim bfd %d %d %d\n", bfd_info->detect_mult, bfd_info->required_min_rx, bfd_info->desired_min_tx); else +#endif /* ! HAVE_BFDD */ vty_out(vty, " ip pim bfd\n"); } diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index 15717aa7a4bd..6eb4303fb712 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -7542,7 +7542,12 @@ DEFUN (no_ip_pim_bfd, return CMD_SUCCESS; } -DEFUN (ip_pim_bfd_param, +#if HAVE_BFDD > 0 +DEFUN_HIDDEN( +#else +DEFUN( +#endif /* HAVE_BFDD */ + ip_pim_bfd_param, ip_pim_bfd_param_cmd, "ip pim bfd (2-255) (50-60000) (50-60000)", IP_STR @@ -7580,12 +7585,14 @@ DEFUN (ip_pim_bfd_param, return CMD_SUCCESS; } +#if HAVE_BFDD == 0 ALIAS(no_ip_pim_bfd, no_ip_pim_bfd_param_cmd, "no ip pim bfd (2-255) (50-60000) (50-60000)", NO_STR IP_STR PIM_STR "Enables BFD support\n" "Detect Multiplier\n" "Required min receive interval\n" "Desired min transmit interval\n") +#endif /* !HAVE_BFDD */ static int ip_msdp_peer_cmd_worker(struct pim_instance *pim, struct vty *vty, const char *peer, const char *local) @@ -8901,5 +8908,7 @@ void pim_cmd_init(void) install_element(INTERFACE_NODE, &ip_pim_bfd_cmd); install_element(INTERFACE_NODE, &ip_pim_bfd_param_cmd); install_element(INTERFACE_NODE, &no_ip_pim_bfd_cmd); +#if HAVE_BFDD == 0 install_element(INTERFACE_NODE, &no_ip_pim_bfd_param_cmd); +#endif /* !HAVE_BFDD */ } diff --git a/redhat/daemons b/redhat/daemons index f9dbffea4d8e..de708cf4fd7e 100644 --- a/redhat/daemons +++ b/redhat/daemons @@ -52,6 +52,8 @@ babeld=no sharpd=no pbrd=no staticd=no +bfdd=no + # # Command line options for the daemons # @@ -70,6 +72,7 @@ babeld_options=("-A 127.0.0.1") sharpd_options=("-A 127.0.0.1") pbrd_options=("-A 127.0.0.1") staticd_options=("-A 127.0.0.1") +bfdd_options=("-A 127.0.0.1") # # If the vtysh_enable is yes, then the unified config is read diff --git a/redhat/frr.init b/redhat/frr.init index 740aa5b64dfb..2e33aee1730c 100755 --- a/redhat/frr.init +++ b/redhat/frr.init @@ -7,7 +7,7 @@ # # chkconfig: 2345 15 85 # -# description: FRRouting (FRR) is a routing suite for IP routing protocols +# description: FRRouting (FRR) is a routing suite for IP routing protocols # like BGP, OSPF, RIP and others. This script contols the main # daemon "frr" as well as the individual protocol daemons. # @@ -20,7 +20,7 @@ # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start/Stop the FRR Routing daemons -# Description: FRRouting (FRR) is a routing suite for IP routing protocols +# Description: FRRouting (FRR) is a routing suite for IP routing protocols # like BGP, OSPF, RIP and others. This script contols the main # daemon "frr" as well as the individual protocol daemons. ### END INIT INFO @@ -33,7 +33,7 @@ V_PATH=/var/run/frr # Local Daemon selection may be done by using /etc/frr/daemons. # See /usr/share/doc/frr/README.Debian.gz for further information. # Keep zebra first and do not list watchfrr! -DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd pimd pbrd ldpd nhrpd eigrpd babeld staticd sharpd" +DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd pimd pbrd ldpd nhrpd eigrpd babeld staticd sharpd bfdd" MAX_INSTANCES=5 RELOAD_SCRIPT=/usr/lib/frr/frr-reload.py diff --git a/redhat/frr.logrotate b/redhat/frr.logrotate index 25a5587787f0..654d355fd7ea 100644 --- a/redhat/frr.logrotate +++ b/redhat/frr.logrotate @@ -86,3 +86,10 @@ endscript } +/var/log/frr/bfdd.log { + notifempty + missingok + postrotate + /bin/kill -USR1 `cat /var/run/frr/bfdd.pid 2> /dev/null` 2> /dev/null || true + endscript +} diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in index f5b116978e4e..25b48506a6ed 100644 --- a/redhat/frr.spec.in +++ b/redhat/frr.spec.in @@ -11,6 +11,7 @@ #################### FRRouting (FRR) configure options ##################### # with-feature options %{!?with_babeld: %global with_babeld 1 } +%{!?with_bfdd: %global with_bfdd 1 } %{!?with_bgp_vnc: %global with_bgp_vnc 0 } %{!?with_cumulus: %global with_cumulus 0 } %{!?with_eigrpd: %global with_eigrpd 1 } @@ -85,7 +86,7 @@ %{!?frr_gid: %global frr_gid 92 } %{!?vty_gid: %global vty_gid 85 } -%define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd +%define daemon_list zebra ripd ospfd bgpd isisd ripngd ospf6d pbrd staticd bfdd %if %{with_ldpd} %define daemon_ldpd ldpd @@ -129,7 +130,13 @@ %define daemon_watchfrr "" %endif -%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} +%if %{with_bfdd} + %define daemon_bfdd bfdd +%else + %define daemon_bfdd "" +%endif + +%define all_daemons %{daemon_list} %{daemon_ldpd} %{daemon_pimd} %{daemon_nhrpd} %{daemon_eigrpd} %{daemon_babeld} %{daemon_watchfrr} %{daemon_pbrd} %{daemon_bfdd} #release sub-revision (the two digits after the CONFDATE) %{!?release_rev: %global release_rev 01 } @@ -193,7 +200,7 @@ protocol. It takes multi-server and multi-thread approach to resolve the current complexity of the Internet. FRRouting supports BGP4, OSPFv2, OSPFv3, ISIS, RIP, RIPng, PIM, LDP -NHRP, Babel, PBR and EIGRP. +NHRP, Babel, PBR, EIGRP and BFD. FRRouting is a fork of Quagga. @@ -331,9 +338,14 @@ developing OSPF-API and frr applications. --enable-systemd \ %endif %if %{with_rpki} - --enable-rpki + --enable-rpki \ +%else + --disable-rpki \ +%endif +%if %{with_bfdd} + --enable-bfdd %else - --disable-rpki + --disable-bfdd %endif make %{?_smp_mflags} MAKEINFO="makeinfo --no-split" SPHINXBUILD=%{sphinx} @@ -444,6 +456,9 @@ zebra_spec_add_service isisd 2608/tcp "ISISd vty" %if %{with_eigrpd} zebra_spec_add_service eigrpd 2613/tcp "EIGRPd vty" %endif +%if %{with_bfdd} + zebra_spec_add_service bfdd 2617/tcp "BFDd vty" +%endif %if "%{initsystem}" == "systemd" for daemon in %all_daemons ; do @@ -591,6 +606,9 @@ fi %if %{with_babeld} %{_sbindir}/babeld %endif +%if %{with_bfdd} + %{_sbindir}/bfdd +%endif %{_libdir}/lib*.so.0 %{_libdir}/lib*.so.0.* %if %{with_fpm} @@ -644,6 +662,9 @@ fi %changelog +* Sun May 28 2018 Rafael Zalamena - %{version} +- Add BFDd support + * Sun May 20 2018 Martin Winter - Fixed RPKI RPM build diff --git a/tools/etc/frr/daemons b/tools/etc/frr/daemons index 9a96c0490ae0..474b299d9000 100644 --- a/tools/etc/frr/daemons +++ b/tools/etc/frr/daemons @@ -35,3 +35,4 @@ eigrpd=no babeld=no sharpd=no pbrd=no +bfdd=no diff --git a/tools/etc/frr/daemons.conf b/tools/etc/frr/daemons.conf index 04a857f47d26..640437f441ce 100644 --- a/tools/etc/frr/daemons.conf +++ b/tools/etc/frr/daemons.conf @@ -19,6 +19,7 @@ babeld_options=" --daemon -A 127.0.0.1" sharpd_options=" --daemon -A 127.0.0.1" pbrd_options=" --daemon -A 127.0.0.1" staticd_options=" --daemon -A 127.0.0.1" +bfdd_options=" --daemon -A 127.0.0.1" # The list of daemons to watch is automatically generated by the init script. watchfrr_enable=yes diff --git a/tools/frr b/tools/frr index 91c9091448a4..0b170d33fd62 100755 --- a/tools/frr +++ b/tools/frr @@ -21,7 +21,7 @@ V_PATH=/var/run/frr # Local Daemon selection may be done by using /etc/frr/daemons. # See /usr/share/doc/frr/README.Debian.gz for further information. # Keep zebra first and do not list watchfrr! -DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd" +DAEMONS="zebra bgpd ripd ripngd ospfd ospf6d isisd babeld pimd ldpd nhrpd eigrpd sharpd pbrd staticd bfdd" MAX_INSTANCES=5 RELOAD_SCRIPT=/usr/lib/frr/frr-reload.py diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am index 9f81b42e3282..936640c83aac 100644 --- a/vtysh/Makefile.am +++ b/vtysh/Makefile.am @@ -150,6 +150,10 @@ if STATICD vtysh_scan += $(top_srcdir)/staticd/static_vty.c endif +if BFDD +vtysh_scan += $(top_srcdir)/bfdd/bfdd_vty.c +endif + vtysh_cmd_FILES = $(vtysh_scan) \ $(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \ $(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \ diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 229337d82f5c..48a90a695c96 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -134,6 +134,7 @@ struct vtysh_client vtysh_client[] = { {.fd = -1, .name = "watchfrr", .flag = VTYSH_WATCHFRR, .next = NULL}, {.fd = -1, .name = "pbrd", .flag = VTYSH_PBRD, .next = NULL}, {.fd = -1, .name = "staticd", .flag = VTYSH_STATICD, .next = NULL}, + {.fd = -1, .name = "bfdd", .flag = VTYSH_BFDD, .next = NULL}, }; enum vtysh_write_integrated vtysh_write_integrated = @@ -1254,6 +1255,18 @@ struct cmd_node link_params_node = { static struct cmd_node rpki_node = {RPKI_NODE, "%s(config-rpki)# ", 1}; #endif +#if HAVE_BFDD > 0 +static struct cmd_node bfd_node = { + BFD_NODE, + "%s(config-bfd)# ", +}; + +static struct cmd_node bfd_peer_node = { + BFD_PEER_NODE, + "%s(config-bfd-peer)# ", +}; +#endif /* HAVE_BFDD */ + /* Defined in lib/vty.c */ extern struct cmd_node vty_node; @@ -1680,6 +1693,32 @@ DEFUNSH(VTYSH_PBRD, vtysh_pbr_map, vtysh_pbr_map_cmd, return CMD_SUCCESS; } +#if HAVE_BFDD > 0 +DEFUNSH(VTYSH_BFDD, bfd_enter, bfd_enter_cmd, "bfd", "Configure BFD peers\n") +{ + vty->node = BFD_NODE; + return CMD_SUCCESS; +} + +DEFUNSH(VTYSH_BFDD, bfd_peer_enter, bfd_peer_enter_cmd, + "peer [{multihop|local-address |interface IFNAME|vrf NAME}]", + "Configure peer\n" + "IPv4 peer address\n" + "IPv6 peer address\n" + "Configure multihop\n" + "Configure local address\n" + "IPv4 local address\n" + "IPv6 local address\n" + INTERFACE_STR + "Configure interface name to use\n" + "Configure VRF\n" + "Configure VRF name\n") +{ + vty->node = BFD_PEER_NODE; + return CMD_SUCCESS; +} +#endif /* HAVE_BFDD */ + DEFSH(VTYSH_PBRD, vtysh_no_pbr_map_cmd, "no pbr-map WORD [seq (1-700)]", NO_STR "Delete pbr-map\n" @@ -1749,6 +1788,7 @@ static int vtysh_exit(struct vty *vty) case PBRMAP_NODE: case VTY_NODE: case KEYCHAIN_NODE: + case BFD_NODE: vtysh_execute("end"); vtysh_execute("configure terminal"); vty->node = CONFIG_NODE; @@ -1792,6 +1832,9 @@ static int vtysh_exit(struct vty *vty) case LINK_PARAMS_NODE: vty->node = INTERFACE_NODE; break; + case BFD_PEER_NODE: + vty->node = BFD_NODE; + break; default: break; } @@ -1988,6 +2031,17 @@ DEFUNSH(VTYSH_ISISD, vtysh_quit_isisd, vtysh_quit_isisd_cmd, "quit", return vtysh_exit_isisd(self, vty, argc, argv); } +#if HAVE_BFDD > 0 +DEFUNSH(VTYSH_BFDD, vtysh_exit_bfdd, vtysh_exit_bfdd_cmd, "exit", + "Exit current mode and down to previous mode\n") +{ + return vtysh_exit(vty); +} + +ALIAS(vtysh_exit_bfdd, vtysh_quit_bfdd_cmd, "quit", + "Exit current mode and down to previous mode\n") +#endif + DEFUNSH(VTYSH_ALL, vtysh_exit_line_vty, vtysh_exit_line_vty_cmd, "exit", "Exit current mode and down to previous mode\n") { @@ -3422,6 +3476,10 @@ void vtysh_init_vty(void) #if defined(HAVE_RPKI) install_node(&rpki_node, NULL); #endif +#if HAVE_BFDD > 0 + install_node(&bfd_node, NULL); + install_node(&bfd_peer_node, NULL); +#endif /* HAVE_BFDD */ struct cmd_node *node; for (unsigned int i = 0; i < vector_active(cmdvec); i++) { @@ -3516,6 +3574,21 @@ void vtysh_init_vty(void) install_element(RMAP_NODE, &vtysh_quit_rmap_cmd); install_element(PBRMAP_NODE, &vtysh_exit_pbr_map_cmd); install_element(PBRMAP_NODE, &vtysh_quit_pbr_map_cmd); +#if HAVE_BFDD > 0 + /* Enter node. */ + install_element(CONFIG_NODE, &bfd_enter_cmd); + install_element(BFD_NODE, &bfd_peer_enter_cmd); + + /* Exit/quit node. */ + install_element(BFD_NODE, &vtysh_exit_bfdd_cmd); + install_element(BFD_NODE, &vtysh_quit_bfdd_cmd); + install_element(BFD_PEER_NODE, &vtysh_exit_bfdd_cmd); + install_element(BFD_PEER_NODE, &vtysh_quit_bfdd_cmd); + + /* End/exit all. */ + install_element(BFD_NODE, &vtysh_end_all_cmd); + install_element(BFD_PEER_NODE, &vtysh_end_all_cmd); +#endif /* HAVE_BFDD */ install_element(VTY_NODE, &vtysh_exit_line_vty_cmd); install_element(VTY_NODE, &vtysh_quit_line_vty_cmd); diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index e6ed5659cffb..c8e4c025e035 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -24,22 +24,23 @@ #include "memory.h" DECLARE_MGROUP(MVTYSH) -#define VTYSH_ZEBRA 0x0001 -#define VTYSH_RIPD 0x0002 -#define VTYSH_RIPNGD 0x0004 -#define VTYSH_OSPFD 0x0008 -#define VTYSH_OSPF6D 0x0010 -#define VTYSH_BGPD 0x0020 -#define VTYSH_ISISD 0x0040 -#define VTYSH_PIMD 0x0080 -#define VTYSH_LDPD 0x0100 -#define VTYSH_WATCHFRR 0x0200 -#define VTYSH_NHRPD 0x0400 -#define VTYSH_EIGRPD 0x0800 -#define VTYSH_BABELD 0x1000 -#define VTYSH_SHARPD 0x2000 -#define VTYSH_PBRD 0x4000 -#define VTYSH_STATICD 0x8000 +#define VTYSH_ZEBRA 0x00001 +#define VTYSH_RIPD 0x00002 +#define VTYSH_RIPNGD 0x00004 +#define VTYSH_OSPFD 0x00008 +#define VTYSH_OSPF6D 0x00010 +#define VTYSH_BGPD 0x00020 +#define VTYSH_ISISD 0x00040 +#define VTYSH_PIMD 0x00080 +#define VTYSH_LDPD 0x00100 +#define VTYSH_WATCHFRR 0x00200 +#define VTYSH_NHRPD 0x00400 +#define VTYSH_EIGRPD 0x00800 +#define VTYSH_BABELD 0x01000 +#define VTYSH_SHARPD 0x02000 +#define VTYSH_PBRD 0x04000 +#define VTYSH_STATICD 0x08000 +#define VTYSH_BFDD 0x10000 #define VTYSH_WAS_ACTIVE (-2) @@ -48,7 +49,7 @@ DECLARE_MGROUP(MVTYSH) /* watchfrr is not in ALL since library CLI functions should not be * run on it (logging & co. should stay in a fixed/frozen config, and * things like prefix lists are not even initialised) */ -#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD +#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_LDPD|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_SHARPD|VTYSH_PBRD|VTYSH_STATICD|VTYSH_BFDD #define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD|VTYSH_EIGRPD|VTYSH_SHARPD #define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_PIMD|VTYSH_NHRPD|VTYSH_EIGRPD|VTYSH_BABELD|VTYSH_PBRD #define VTYSH_NS VTYSH_ZEBRA diff --git a/vtysh/vtysh_config.c b/vtysh/vtysh_config.c index 52ab28dfda70..42f08342c0b2 100644 --- a/vtysh/vtysh_config.c +++ b/vtysh/vtysh_config.c @@ -318,6 +318,8 @@ void vtysh_config_parse_line(void *arg, const char *line) config = config_get(PROTOCOL_NODE, line); else if (strncmp(line, "mpls", strlen("mpls")) == 0) config = config_get(MPLS_NODE, line); + else if (strncmp(line, "bfd", strlen("bfd")) == 0) + config = config_get(BFD_NODE, line); else { if (strncmp(line, "log", strlen("log")) == 0 || strncmp(line, "hostname", strlen("hostname")) diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index ad574d7e8b91..22c0dd623ca0 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -3015,6 +3015,9 @@ void (*zserv_handlers[])(ZAPI_HANDLER_ARGS) = { [ZEBRA_BFD_DEST_UPDATE] = zebra_ptm_bfd_dst_register, [ZEBRA_BFD_DEST_REGISTER] = zebra_ptm_bfd_dst_register, [ZEBRA_BFD_DEST_DEREGISTER] = zebra_ptm_bfd_dst_deregister, +#if HAVE_BFDD > 0 + [ZEBRA_BFD_DEST_REPLAY] = zebra_ptm_bfd_dst_replay, +#endif /* HAVE_BFDD */ [ZEBRA_VRF_UNREGISTER] = zread_vrf_unregister, [ZEBRA_VRF_LABEL] = zread_vrf_label, [ZEBRA_BFD_CLIENT_REGISTER] = zebra_ptm_bfd_client_register, diff --git a/zebra/zebra_ptm.c b/zebra/zebra_ptm.c index 5975c4058b99..cc2d5d411dd5 100644 --- a/zebra/zebra_ptm.c +++ b/zebra/zebra_ptm.c @@ -39,6 +39,15 @@ #include "zebra_vrf.h" #include "version.h" +/* + * Choose the BFD implementation that we'll use. + * + * There are two implementations: + * - PTM BFD: which uses an external daemon; + * - bfdd: FRR's own BFD daemon; + */ +#if HAVE_BFDD == 0 + #define ZEBRA_PTM_RECONNECT_TIME_INITIAL 1 /* initial reconnect is 1s */ #define ZEBRA_PTM_RECONNECT_TIME_MAX 300 @@ -1141,3 +1150,432 @@ void zebra_ptm_if_write(struct vty *vty, struct zebra_if *zebra_ifp) if (zebra_ifp->ptm_enable == ZEBRA_IF_PTM_ENABLE_OFF) vty_out(vty, " no ptm-enable\n"); } + +#else /* HAVE_BFDD */ + +#include "zebra/zebra_memory.h" + +/* + * Data structures. + */ +struct ptm_process { + struct zserv *pp_zs; + pid_t pp_pid; + + TAILQ_ENTRY(ptm_process) pp_entry; +}; +TAILQ_HEAD(ppqueue, ptm_process) ppqueue; + +DEFINE_MTYPE_STATIC(ZEBRA, ZEBRA_PTM_BFD_PROCESS, + "PTM BFD process registration table."); + +/* + * Prototypes. + */ +static struct ptm_process *pp_new(pid_t pid, struct zserv *zs); +static struct ptm_process *pp_lookup_byzs(struct zserv *zs); +static void pp_free(struct ptm_process *pp); +static void pp_free_all(void); + +static void zebra_ptm_send_bfdd(struct stream *msg); +static void zebra_ptm_send_clients(struct stream *msg); +static int _zebra_ptm_bfd_client_deregister(struct zserv *zs); +static void _zebra_ptm_reroute(struct zserv *zs, struct stream *msg, + uint32_t command); + + +/* + * Process PID registration. + */ +static struct ptm_process *pp_new(pid_t pid, struct zserv *zs) +{ + struct ptm_process *pp; + +#ifdef PTM_DEBUG + /* Sanity check: more than one client can't have the same PID. */ + TAILQ_FOREACH(pp, &ppqueue, pp_entry) { + if (pp->pp_pid == pid && pp->pp_zs != zs) + zlog_err("%s:%d pid and client pointer doesn't match", + __FILE__, __LINE__); + } +#endif /* PTM_DEBUG */ + + /* Lookup for duplicates. */ + pp = pp_lookup_byzs(zs); + if (pp != NULL) + return pp; + + /* Allocate and register new process. */ + pp = XCALLOC(MTYPE_ZEBRA_PTM_BFD_PROCESS, sizeof(*pp)); + if (pp == NULL) + return NULL; + + pp->pp_pid = pid; + pp->pp_zs = zs; + TAILQ_INSERT_HEAD(&ppqueue, pp, pp_entry); + + return pp; +} + +static struct ptm_process *pp_lookup_byzs(struct zserv *zs) +{ + struct ptm_process *pp; + + TAILQ_FOREACH(pp, &ppqueue, pp_entry) { + if (pp->pp_zs != zs) + continue; + + break; + } + + return pp; +} + +static void pp_free(struct ptm_process *pp) +{ + if (pp == NULL) + return; + + TAILQ_REMOVE(&ppqueue, pp, pp_entry); + XFREE(MTYPE_ZEBRA_PTM_BFD_PROCESS, pp); +} + +static void pp_free_all(void) +{ + struct ptm_process *pp; + + while (!TAILQ_EMPTY(&ppqueue)) { + pp = TAILQ_FIRST(&ppqueue); + pp_free(pp); + } +} + + +/* + * Use the FRR's internal daemon implementation. + */ +static void zebra_ptm_send_bfdd(struct stream *msg) +{ + struct listnode *node; + struct zserv *client; + struct stream *msgc; + + /* Create copy for replication. */ + msgc = stream_dup(msg); + if (msgc == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + + /* Send message to all running BFDd daemons. */ + for (ALL_LIST_ELEMENTS_RO(zebrad.client_list, node, client)) { + if (client->proto != ZEBRA_ROUTE_BFD) + continue; + + zserv_send_message(client, msg); + + /* Allocate more messages. */ + msg = stream_dup(msgc); + if (msg == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + } + + stream_free(msgc); +} + +static void zebra_ptm_send_clients(struct stream *msg) +{ + struct listnode *node; + struct zserv *client; + struct stream *msgc; + + /* Create copy for replication. */ + msgc = stream_dup(msg); + if (msgc == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + + /* Send message to all running client daemons. */ + for (ALL_LIST_ELEMENTS_RO(zebrad.client_list, node, client)) { + switch (client->proto) { + case ZEBRA_ROUTE_BGP: + case ZEBRA_ROUTE_OSPF: + case ZEBRA_ROUTE_OSPF6: + case ZEBRA_ROUTE_PIM: + break; + + default: + /* NOTHING: skip this daemon. */ + continue; + } + + zserv_send_message(client, msg); + + /* Allocate more messages. */ + msg = stream_dup(msgc); + if (msg == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + } + + stream_free(msgc); +} + +static int _zebra_ptm_bfd_client_deregister(struct zserv *zs) +{ + struct stream *msg; + struct ptm_process *pp; + + /* Filter daemons that must receive this treatment. */ + switch (zs->proto) { + case ZEBRA_ROUTE_BGP: + case ZEBRA_ROUTE_OSPF: + case ZEBRA_ROUTE_OSPF6: + case ZEBRA_ROUTE_PIM: + break; + + case ZEBRA_ROUTE_BFD: + /* Don't try to send BFDd messages to itself. */ + return 0; + + default: + /* Unsupported daemon. */ + return 0; + } + + /* Find daemon pid by zebra connection pointer. */ + pp = pp_lookup_byzs(zs); + if (pp == NULL) { + zlog_err("%s:%d failed to find process pid registration", + __FILE__, __LINE__); + return -1; + } + + /* Generate, send message and free() daemon related data. */ + msg = stream_new(ZEBRA_MAX_PACKET_SIZ); + if (msg == NULL) { + zlog_warn("%s: not enough memory", __func__); + return 0; + } + + /* + * The message type will be BFD_DEST_REPLY so we can use only + * one callback at the `bfdd` side, however the real command + * number will be included right after the zebra header. + */ + zclient_create_header(msg, ZEBRA_BFD_DEST_REPLAY, 0); + stream_putl(msg, ZEBRA_BFD_CLIENT_DEREGISTER); + + /* Put process PID. */ + stream_putl(msg, pp->pp_pid); + + /* Update the data pointers. */ + stream_putw_at(msg, 0, stream_get_endp(msg)); + + zebra_ptm_send_bfdd(msg); + + pp_free(pp); + + return 0; +} + +void zebra_ptm_init(void) +{ + /* Initialize the ptm process information list. */ + TAILQ_INIT(&ppqueue); + + /* + * Send deregistration messages to BFD daemon when some other + * daemon closes. This will help avoid sending daemons + * unnecessary notification messages. + */ + hook_register(zserv_client_close, _zebra_ptm_bfd_client_deregister); +} + +void zebra_ptm_finish(void) +{ + /* Remove the client disconnect hook and free all memory. */ + hook_unregister(zserv_client_close, _zebra_ptm_bfd_client_deregister); + pp_free_all(); +} + + +/* + * Message handling. + */ +static void _zebra_ptm_reroute(struct zserv *zs, struct stream *msg, + uint32_t command) +{ + struct stream *msgc; + size_t zmsglen, zhdrlen; + pid_t ppid; + + /* + * Don't modify message in the zebra API. In order to do that we + * need to allocate a new message stream and copy the message + * provided by zebra. + */ + msgc = stream_new(ZEBRA_MAX_PACKET_SIZ); + if (msgc == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + + /* Calculate our header size plus the message contents. */ + zhdrlen = ZEBRA_HEADER_SIZE + sizeof(uint32_t); + zmsglen = msg->endp - msg->getp; + memcpy(msgc->data + zhdrlen, msg->data + msg->getp, zmsglen); + + /* + * The message type will be BFD_DEST_REPLY so we can use only + * one callback at the `bfdd` side, however the real command + * number will be included right after the zebra header. + */ + zclient_create_header(msgc, ZEBRA_BFD_DEST_REPLAY, 0); + stream_putl(msgc, command); + + /* Update the data pointers. */ + msgc->getp = 0; + msgc->endp = zhdrlen + zmsglen; + stream_putw_at(msgc, 0, stream_get_endp(msgc)); + + zebra_ptm_send_bfdd(msgc); + + /* Registrate process PID for shutdown hook. */ + STREAM_GETL(msg, ppid); + pp_new(ppid, zs); + + return; + +stream_failure: + zlog_err("%s:%d failed to registrate client pid", __FILE__, __LINE__); +} + +void zebra_ptm_bfd_dst_register(ZAPI_HANDLER_ARGS) +{ + if (IS_ZEBRA_DEBUG_EVENT) + zlog_debug("bfd_dst_register msg from client %s: length=%d", + zebra_route_string(client->proto), hdr->length); + + _zebra_ptm_reroute(client, msg, ZEBRA_BFD_DEST_REGISTER); +} + +void zebra_ptm_bfd_dst_deregister(ZAPI_HANDLER_ARGS) +{ + if (IS_ZEBRA_DEBUG_EVENT) + zlog_debug("bfd_dst_deregister msg from client %s: length=%d", + zebra_route_string(client->proto), hdr->length); + + _zebra_ptm_reroute(client, msg, ZEBRA_BFD_DEST_DEREGISTER); +} + +void zebra_ptm_bfd_client_register(ZAPI_HANDLER_ARGS) +{ + if (IS_ZEBRA_DEBUG_EVENT) + zlog_debug("bfd_client_register msg from client %s: length=%d", + zebra_route_string(client->proto), hdr->length); + + _zebra_ptm_reroute(client, msg, ZEBRA_BFD_CLIENT_REGISTER); +} + +void zebra_ptm_bfd_dst_replay(ZAPI_HANDLER_ARGS) +{ + struct stream *msgc; + size_t zmsglen, zhdrlen; + uint32_t cmd; + + /* + * NOTE: + * Replay messages with HAVE_BFDD are meant to be replayed to + * the client daemons. These messages are composed and + * originated from the `bfdd` daemon. + */ + if (IS_ZEBRA_DEBUG_EVENT) + zlog_debug("bfd_dst_update msg from client %s: length=%d", + zebra_route_string(client->proto), hdr->length); + + /* + * Client messages must be re-routed, otherwise do the `bfdd` + * special treatment. + */ + if (client->proto != ZEBRA_ROUTE_BFD) { + _zebra_ptm_reroute(client, msg, ZEBRA_BFD_DEST_REPLAY); + return; + } + + /* Figure out if this is an DEST_UPDATE or DEST_REPLAY. */ + if (stream_getl2(msg, &cmd) == false) { + zlog_err("%s: expected at least 4 bytes (command)", __func__); + return; + } + + /* + * Don't modify message in the zebra API. In order to do that we + * need to allocate a new message stream and copy the message + * provided by zebra. + */ + msgc = stream_new(ZEBRA_MAX_PACKET_SIZ); + if (msgc == NULL) { + zlog_warn("%s: not enough memory", __func__); + return; + } + + /* Calculate our header size plus the message contents. */ + if (cmd != ZEBRA_BFD_DEST_REPLAY) { + zhdrlen = ZEBRA_HEADER_SIZE; + zmsglen = msg->endp - msg->getp; + memcpy(msgc->data + zhdrlen, msg->data + msg->getp, zmsglen); + + zclient_create_header(msgc, cmd, zvrf_id(zvrf)); + + msgc->getp = 0; + msgc->endp = zhdrlen + zmsglen; + } else + zclient_create_header(msgc, cmd, zvrf_id(zvrf)); + + /* Update the data pointers. */ + stream_putw_at(msgc, 0, stream_get_endp(msgc)); + + zebra_ptm_send_clients(msgc); +} + +/* + * Unused functions. + */ +void zebra_ptm_if_init(struct zebra_if *zifp __attribute__((__unused__))) +{ + /* NOTHING */ +} + +int zebra_ptm_get_enable_state(void) +{ + return 1; +} + +void zebra_ptm_show_status(struct vty *vty __attribute__((__unused__)), + struct interface *ifp __attribute__((__unused__))) +{ + /* NOTHING */ +} + +void zebra_ptm_write(struct vty *vty __attribute__((__unused__))) +{ + /* NOTHING */ +} + +void zebra_ptm_if_write(struct vty *vty __attribute__((__unused__)), + struct zebra_if *zifp __attribute__((__unused__))) +{ + /* NOTHING */ +} +void zebra_ptm_if_set_ptm_state(struct interface *i __attribute__((__unused__)), + struct zebra_if *zi __attribute__((__unused__))) +{ + /* NOTHING */ +} + +#endif /* HAVE_BFDD */ diff --git a/zebra/zebra_ptm.h b/zebra/zebra_ptm.h index 0e55574a02b0..ada4f7b4f7da 100644 --- a/zebra/zebra_ptm.h +++ b/zebra/zebra_ptm.h @@ -69,6 +69,9 @@ int zebra_ptm_get_enable_state(void); void zebra_ptm_bfd_dst_register(ZAPI_HANDLER_ARGS); void zebra_ptm_bfd_dst_deregister(ZAPI_HANDLER_ARGS); void zebra_ptm_bfd_client_register(ZAPI_HANDLER_ARGS); +#if HAVE_BFDD > 0 +void zebra_ptm_bfd_dst_replay(ZAPI_HANDLER_ARGS); +#endif /* HAVE_BFDD */ void zebra_ptm_show_status(struct vty *vty, struct interface *ifp); void zebra_ptm_if_init(struct zebra_if *zebra_ifp);