From e9e2c950d7423071e8577a12c072a5309f02f8c1 Mon Sep 17 00:00:00 2001 From: Rafael Zalamena Date: Wed, 27 Jun 2018 11:29:02 -0300 Subject: [PATCH] bfdd: imported new daemon source code Import source code from external `bfdd` daemon ported from Cumulus PTM. Signed-off-by: Rafael Zalamena --- bfdd/.gitignore | 3 + bfdd/bfd.c | 1385 ++++++++++++++++++++++++++++++++++++++ bfdd/bfd.h | 617 +++++++++++++++++ bfdd/bfd_packet.c | 1488 +++++++++++++++++++++++++++++++++++++++++ bfdd/bfdctl.h | 160 +++++ bfdd/bfdd.c | 234 +++++++ bfdd/bfdd.conf.sample | 5 + bfdd/bsd.c | 290 ++++++++ bfdd/config.c | 606 +++++++++++++++++ bfdd/control.c | 901 +++++++++++++++++++++++++ bfdd/event.c | 155 +++++ bfdd/linux.c | 218 ++++++ bfdd/log.c | 129 ++++ bfdd/subdir.am | 28 + 14 files changed, 6219 insertions(+) create mode 100644 bfdd/.gitignore create mode 100644 bfdd/bfd.c create mode 100644 bfdd/bfd.h create mode 100644 bfdd/bfd_packet.c create mode 100644 bfdd/bfdctl.h create mode 100644 bfdd/bfdd.c create mode 100644 bfdd/bfdd.conf.sample create mode 100644 bfdd/bsd.c create mode 100644 bfdd/config.c create mode 100644 bfdd/control.c create mode 100644 bfdd/event.c create mode 100644 bfdd/linux.c create mode 100644 bfdd/log.c create mode 100644 bfdd/subdir.am diff --git a/bfdd/.gitignore b/bfdd/.gitignore new file mode 100644 index 0000000000..e554d1b33f --- /dev/null +++ b/bfdd/.gitignore @@ -0,0 +1,3 @@ +# ignore binary files +*.a +bfdd diff --git a/bfdd/bfd.c b/bfdd/bfd.c new file mode 100644 index 0000000000..16a3a688f9 --- /dev/null +++ b/bfdd/bfd.c @@ -0,0 +1,1385 @@ +/********************************************************************* + * Copyright 2013 Cumulus Networks, LLC. All rights reserved. + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd.c: implements the BFD protocol. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/hash.h" +#include "lib/jhash.h" + +#include "bfd.h" + +DEFINE_QOBJ_TYPE(bfd_session); + +/* + * Prototypes + */ +static uint32_t ptm_bfd_gen_ID(void); +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd); +static void bfd_session_free(struct bfd_session *bs); +static struct bfd_session *bfd_session_new(int sd); +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, + uint32_t ldisc); +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc); +static const char *get_diag_str(int diag); + + +/* + * Functions + */ +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bs; + struct peer_label *pl; + struct bfd_mhop_key mhop; + struct bfd_shop_key shop; + + /* Try to find label first. */ + if (bpc->bpc_has_label) { + pl = pl_find(bpc->bpc_label); + if (pl != NULL) { + bs = pl->pl_bs; + return bs; + } + } + + /* Otherwise fallback to peer/local hash lookup. */ + if (bpc->bpc_mhop) { + memset(&mhop, 0, sizeof(mhop)); + mhop.peer = bpc->bpc_peer; + mhop.local = bpc->bpc_local; + if (bpc->bpc_has_vrfname) + strlcpy(mhop.vrf_name, bpc->bpc_vrfname, + sizeof(mhop.vrf_name)); + + bs = bfd_mhop_lookup(mhop); + } else { + memset(&shop, 0, sizeof(shop)); + shop.peer = bpc->bpc_peer; + if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) + strlcpy(shop.port_name, bpc->bpc_localif, + sizeof(shop.port_name)); + + bs = bfd_shop_lookup(shop); + } + + return bs; +} + +static uint32_t ptm_bfd_gen_ID(void) +{ + static uint32_t sessionID = 1; + + return (sessionID++); +} + +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo) +{ + uint64_t jitter, xmt_TO; + int maxpercent; + + xmt_TO = is_echo ? bfd->echo_xmt_TO : bfd->xmt_TO; + + /* + * From section 6.5.2: trasmit interval should be randomly jittered + * between + * 75% and 100% of nominal value, unless detect_mult is 1, then should + * be + * between 75% and 90%. + */ + maxpercent = (bfd->detect_mult == 1) ? 16 : 26; + jitter = (xmt_TO * (75 + (random() % maxpercent))) / 100; + /* XXX remove that division above */ + + if (is_echo) + bfd_echo_xmttimer_update(bfd, jitter); + else + bfd_xmttimer_update(bfd, jitter); +} + +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd) +{ + /* Send the scheduled echo packet */ + ptm_bfd_echo_snd(bfd); + + /* Restart the timer for next time */ + ptm_bfd_start_xmt_timer(bfd, true); +} + +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit) +{ + /* Send the scheduled control packet */ + ptm_bfd_snd(bfd, fbit); + + /* Restart the timer for next time */ + ptm_bfd_start_xmt_timer(bfd, false); +} + +void ptm_bfd_echo_stop(struct bfd_session *bfd, int polling) +{ + bfd->echo_xmt_TO = 0; + bfd->echo_detect_TO = 0; + BFD_UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + + bfd_echo_xmttimer_delete(bfd); + bfd_echo_recvtimer_delete(bfd); + + if (polling) { + bfd->polling = polling; + bfd->new_timers.desired_min_tx = bfd->up_min_tx; + bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; + ptm_bfd_snd(bfd, 0); + } +} + +void ptm_bfd_echo_start(struct bfd_session *bfd) +{ + bfd->echo_detect_TO = (bfd->remote_detect_mult * bfd->echo_xmt_TO); + ptm_bfd_echo_xmt_TO(bfd); + + bfd->polling = 1; + bfd->new_timers.desired_min_tx = bfd->up_min_tx; + bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; + ptm_bfd_snd(bfd, 0); +} + +void ptm_bfd_ses_up(struct bfd_session *bfd) +{ + bfd->local_diag = 0; + bfd->ses_state = PTM_BFD_UP; + bfd->polling = 1; + monotime(&bfd->uptime); + + /* If the peer is capable to receiving Echo pkts */ + if (bfd->echo_xmt_TO && !BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MH)) { + ptm_bfd_echo_start(bfd); + } else { + bfd->new_timers.desired_min_tx = bfd->up_min_tx; + bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; + ptm_bfd_snd(bfd, 0); + } + + control_notify(bfd); + + INFOLOG("Session 0x%x up peer %s", bfd->discrs.my_discr, + satostr(&bfd->shop.peer)); +} + +void ptm_bfd_ses_dn(struct bfd_session *bfd, uint8_t diag) +{ + int old_state = bfd->ses_state; + + bfd->local_diag = diag; + bfd->discrs.remote_discr = 0; + bfd->ses_state = PTM_BFD_DOWN; + bfd->polling = 0; + bfd->demand_mode = 0; + monotime(&bfd->downtime); + + ptm_bfd_snd(bfd, 0); + + /* only signal clients when going from up->down state */ + if (old_state == PTM_BFD_UP) + control_notify(bfd); + + INFOLOG("Session 0x%x down peer %s Rsn %s prev st %s", + bfd->discrs.my_discr, satostr(&bfd->shop.peer), + get_diag_str(bfd->local_diag), state_list[old_state].str); + + /* Stop echo packet transmission if they are active */ + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) + ptm_bfd_echo_stop(bfd, 0); +} + +static int ptm_bfd_get_vrf_name(char *port_name, char *vrf_name) +{ + struct bfd_iface *iface; + struct bfd_vrf *vrf; + + if ((port_name == NULL) || (vrf_name == NULL)) + return -1; + + iface = bfd_iface_lookup(port_name); + if (iface) { + vrf = bfd_vrf_lookup(iface->vrf_id); + if (vrf) { + strlcpy(vrf_name, vrf->name, sizeof(vrf->name)); + return 0; + } + } + return -1; +} + +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, + uint32_t ldisc) +{ + struct bfd_session *bs; + + bs = bfd_id_lookup(ldisc); + if (bs == NULL) + return NULL; + + /* Remove unused fields. */ + switch (sa->sa_sin.sin_family) { + case AF_INET: + sa->sa_sin.sin_port = 0; + if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin)) == 0) + return bs; + break; + case AF_INET6: + sa->sa_sin6.sin6_port = 0; + if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin6)) == 0) + return bs; + break; + } + + return NULL; +} + +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, char *port_name, + struct sockaddr_any *peer, + struct sockaddr_any *local, + char *vrf_name, bool is_mhop) +{ + struct bfd_session *l_bfd = NULL; + struct bfd_mhop_key mhop; + struct bfd_shop_key shop; + char peer_addr[64]; + char local_addr[64]; + char vrf_name_buf[MAXNAMELEN + 1]; + + /* peer, local are in network-byte order */ + strlcpy(peer_addr, satostr(peer), sizeof(peer_addr)); + strlcpy(local_addr, satostr(local), sizeof(local_addr)); + + if (cp) { + if (cp->discrs.remote_discr) { + uint32_t ldisc = ntohl(cp->discrs.remote_discr); + /* Your discriminator not zero - use it to find session + */ + l_bfd = bfd_find_disc(peer, ldisc); + + if (l_bfd) + return l_bfd; + + DLOG("Can't find session for yourDisc 0x%x from %s", + ldisc, peer_addr); + } else if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN + || BFD_GETSTATE(cp->flags) == PTM_BFD_ADM_DOWN) { + + if (is_mhop) { + memset(&mhop, 0, sizeof(mhop)); + mhop.peer = *peer; + mhop.local = *local; + if (vrf_name && strlen(vrf_name)) { + strlcpy(mhop.vrf_name, vrf_name, + sizeof(mhop.vrf_name)); + } else if (port_name) { + memset(vrf_name_buf, 0, + sizeof(vrf_name_buf)); + if (ptm_bfd_get_vrf_name(port_name, + vrf_name_buf) + != -1) { + strlcpy(mhop.vrf_name, + vrf_name_buf, + sizeof(mhop.vrf_name)); + } + } + + /* Your discriminator zero - + * use peer address and local address to + * find session + */ + l_bfd = bfd_mhop_lookup(mhop); + } else { + memset(&shop, 0, sizeof(shop)); + shop.peer = *peer; + if (strlen(port_name)) + strlcpy(shop.port_name, port_name, + sizeof(shop.port_name)); + /* Your discriminator zero - + * use peer address and port to find + * session + */ + l_bfd = bfd_shop_lookup(shop); + } + if (l_bfd) { + /* XXX maybe remoteDiscr should be checked for + * remoteHeard cases + */ + return l_bfd; + } + } + if (is_mhop) + DLOG("Can't find multi hop session peer/local %s/%s in vrf %s port %s", + peer_addr, local_addr, + strlen(mhop.vrf_name) ? mhop.vrf_name : "N/A", + port_name ? port_name : "N/A"); + else + DLOG("Can't find single hop session for peer/port %s/%s", + peer_addr, port_name); + } else if (peer->sa_sin.sin_addr.s_addr + || !IN6_IS_ADDR_UNSPECIFIED(&peer->sa_sin6.sin6_addr)) { + + if (is_mhop) { + memset(&mhop, 0, sizeof(mhop)); + mhop.peer = *peer; + mhop.local = *local; + if (vrf_name && strlen(vrf_name)) + strlcpy(mhop.vrf_name, vrf_name, + sizeof(mhop.vrf_name)); + + l_bfd = bfd_mhop_lookup(mhop); + } else { + memset(&shop, 0, sizeof(shop)); + shop.peer = *peer; + if (strlen(port_name)) { + strlcpy(shop.port_name, port_name, + sizeof(shop.port_name)); + } + + l_bfd = bfd_shop_lookup(shop); + } + + if (l_bfd) { + /* XXX maybe remoteDiscr should be checked for + * remoteHeard cases + */ + return l_bfd; + } + + DLOG("Can't find session for peer %s", peer_addr); + } + + return (NULL); +} + +#if 0 /* TODO VxLAN Support */ +static void +_update_vxlan_sess_parms(struct bfd_session *bfd, bfd_sess_parms *sess_parms) +{ + struct bfd_session_vxlan_info *vxlan_info = &bfd->vxlan_info; + bfd_parms_list *parms = &sess_parms->parms; + + vxlan_info->vnid = parms->vnid; + vxlan_info->check_tnl_key = parms->check_tnl_key; + vxlan_info->forwarding_if_rx = parms->forwarding_if_rx; + vxlan_info->cpath_down = parms->cpath_down; + vxlan_info->decay_min_rx = parms->decay_min_rx; + + inet_aton(parms->local_dst_ip, &vxlan_info->local_dst_ip); + inet_aton(parms->remote_dst_ip, &vxlan_info->peer_dst_ip); + + memcpy(vxlan_info->local_dst_mac, parms->local_dst_mac, ETH_ALEN); + memcpy(vxlan_info->peer_dst_mac, parms->remote_dst_mac, ETH_ALEN); + + /* The interface may change for Vxlan BFD sessions, so update + * the local mac and ifindex + */ + bfd->ifindex = sess_parms->ifindex; + memcpy(bfd->local_mac, sess_parms->local_mac, sizeof(bfd->local_mac)); +} +#endif /* VxLAN support */ + +int bfd_xmt_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + + ptm_bfd_xmt_TO(bs, 0); + + return 0; +} + +int bfd_echo_xmt_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + + ptm_bfd_echo_xmt_TO(bs); + + return 0; +} + +/* Was ptm_bfd_detect_TO() */ +int bfd_recvtimer_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + uint8_t old_state; + + old_state = bs->ses_state; + + switch (bs->ses_state) { + case PTM_BFD_INIT: + case PTM_BFD_UP: + ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME); + INFOLOG("%s Detect timeout on session 0x%x with peer %s, in state %d", + __func__, bs->discrs.my_discr, satostr(&bs->shop.peer), + bs->ses_state); + bfd_recvtimer_update(bs); + break; + + default: + /* Second detect time expiration, zero remote discr (section + * 6.5.1) + */ + bs->discrs.remote_discr = 0; + break; + } + + if (old_state != bs->ses_state) { + DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]", + bs->discrs.my_discr, satostr(&bs->shop.peer), + state_list[old_state].str, state_list[bs->ses_state].str); + } + + return 0; +} + +/* Was ptm_bfd_echo_detect_TO() */ +int bfd_echo_recvtimer_cb(struct thread *t) +{ + struct bfd_session *bs = THREAD_ARG(t); + uint8_t old_state; + + old_state = bs->ses_state; + + switch (bs->ses_state) { + case PTM_BFD_INIT: + case PTM_BFD_UP: + ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME); + INFOLOG("%s Detect timeout on session 0x%x with peer %s, in state %d", + __func__, bs->discrs.my_discr, satostr(&bs->shop.peer), + bs->ses_state); + break; + } + + if (old_state != bs->ses_state) { + DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]", + bs->discrs.my_discr, satostr(&bs->shop.peer), + state_list[old_state].str, state_list[bs->ses_state].str); + } + + return 0; +} + +static struct bfd_session *bfd_session_new(int sd) +{ + struct bfd_session *bs; + + bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs)); + if (bs == NULL) + return NULL; + + QOBJ_REG(bs, bfd_session); + + bs->up_min_tx = BFD_DEFDESIREDMINTX; + bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX; + bs->timers.required_min_echo = BFD_DEF_REQ_MIN_ECHO; + bs->detect_mult = BFD_DEFDETECTMULT; + bs->mh_ttl = BFD_DEF_MHOP_TTL; + + bs->sock = sd; + monotime(&bs->uptime); + bs->downtime = bs->uptime; + + return bs; +} + +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel) +{ + /* New label treatment: + * - Check if the label is taken; + * - Try to allocate the memory for it and register; + */ + if (bs->pl == NULL) { + if (pl_find(nlabel) != NULL) { + /* Someone is already using it. */ + return -1; + } + + if (pl_new(nlabel, bs) == NULL) + return -1; + + return 0; + } + + /* + * Test label change consistency: + * - Do nothing if it's the same label; + * - Check if the future label is already taken; + * - Change label; + */ + if (strcmp(nlabel, bs->pl->pl_label) == 0) + return -1; + if (pl_find(nlabel) != NULL) + return -1; + + strlcpy(bs->pl->pl_label, nlabel, sizeof(bs->pl->pl_label)); + return 0; +} + +static void _bfd_session_update(struct bfd_session *bs, + struct bfd_peer_cfg *bpc) +{ + if (bpc->bpc_echo) { + /* Check if echo mode is already active. */ + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + goto skip_echo; + + BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + ptm_bfd_echo_start(bs); + + /* Activate/update echo receive timeout timer. */ + bfd_echo_recvtimer_update(bs); + } else { + /* Check if echo mode is already disabled. */ + if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) + goto skip_echo; + + BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); + ptm_bfd_echo_stop(bs, 0); + } + +skip_echo: + if (bpc->bpc_has_txinterval) + bs->up_min_tx = bpc->bpc_txinterval * 1000; + + if (bpc->bpc_has_recvinterval) + bs->timers.required_min_rx = bpc->bpc_recvinterval * 1000; + + if (bpc->bpc_has_detectmultiplier) + bs->detect_mult = bpc->bpc_detectmultiplier; + + if (bpc->bpc_has_echointerval) + bs->timers.required_min_echo = bpc->bpc_echointerval * 1000; + + if (bpc->bpc_has_label) + bfd_session_update_label(bs, bpc->bpc_label); + + if (bpc->bpc_shutdown) { + /* Check if already shutdown. */ + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) + return; + + BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Disable all events. */ + bfd_recvtimer_delete(bs); + bfd_echo_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + bfd_echo_xmttimer_delete(bs); + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_ADM_DOWN; + control_notify(bs); + + ptm_bfd_snd(bs, 0); + } else { + /* Check if already working. */ + if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) + return; + + BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + + /* Change and notify state change. */ + bs->ses_state = PTM_BFD_DOWN; + control_notify(bs); + + /* Enable all timers. */ + bfd_recvtimer_update(bs); + bfd_xmttimer_update(bs, bs->xmt_TO); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) { + bfd_echo_recvtimer_update(bs); + bfd_echo_xmttimer_update(bs, bs->echo_xmt_TO); + } + } +} + +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc) +{ + /* User didn't want to update, return failure. */ + if (bpc->bpc_createonly) + return -1; + + _bfd_session_update(bs, bpc); + + /* TODO add VxLAN support. */ + + control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); + + return 0; +} + +static void bfd_session_free(struct bfd_session *bs) +{ + if (bs->sock != -1) + close(bs->sock); + + bfd_recvtimer_delete(bs); + bfd_echo_recvtimer_delete(bs); + bfd_xmttimer_delete(bs); + bfd_echo_xmttimer_delete(bs); + + bfd_id_delete(bs->discrs.my_discr); + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + bfd_mhop_delete(bs->mhop); + else + bfd_shop_delete(bs->shop); + + pl_free(bs->pl); + + QOBJ_UNREG(bs); + XFREE(MTYPE_BFDD_CONFIG, bs); +} + +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bfd, *l_bfd; + int psock; + + /* check to see if this needs a new session */ + l_bfd = bs_peer_find(bpc); + if (l_bfd) { + /* Requesting a duplicated peer means update configuration. */ + if (bfd_session_update(l_bfd, bpc) == 0) + return l_bfd; + else + return NULL; + } + + /* + * Get socket for transmitting control packets. Note that if we + * could use the destination port (3784) for the source + * port we wouldn't need a socket per session. + */ + if (bpc->bpc_ipv4) { + psock = bp_peer_socket(bpc); + if (psock == -1) { + ERRLOG("Can't get socket for new session: %s", + strerror(errno)); + return NULL; + } + } else { + psock = bp_peer_socketv6(bpc); + if (psock == -1) { + ERRLOG("Can't get IPv6 socket for new session: %s", + strerror(errno)); + return NULL; + } + } + + /* Get memory */ + bfd = bfd_session_new(psock); + if (bfd == NULL) { + ERRLOG("Can't malloc memory for new session: %s", + strerror(errno)); + return NULL; + } + + if (bpc->bpc_has_localif && !bpc->bpc_mhop) { + bfd->ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); + ptm_bfd_fetch_local_mac(bpc->bpc_localif, bfd->local_mac); + } + + if (bpc->bpc_has_vxlan) + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN); + + if (bpc->bpc_ipv4 == false) + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6); + + /* Initialize the session */ + bfd->ses_state = PTM_BFD_DOWN; + bfd->discrs.my_discr = ptm_bfd_gen_ID(); + bfd->discrs.remote_discr = 0; + bfd->local_ip = bpc->bpc_local; + bfd->local_address = bpc->bpc_local; + bfd->timers.desired_min_tx = bfd->up_min_tx; + bfd->detect_TO = (bfd->detect_mult * BFD_DEF_SLOWTX); + + /* Use detect_TO first for slow detection, then use recvtimer_update. */ + bfd_recvtimer_update(bfd); + + bfd_id_insert(bfd); + + if (bpc->bpc_mhop) { + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH); + bfd->mhop.peer = bpc->bpc_peer; + bfd->mhop.local = bpc->bpc_local; + if (bpc->bpc_has_vrfname) + strlcpy(bfd->mhop.vrf_name, bpc->bpc_vrfname, + sizeof(bfd->mhop.vrf_name)); + + bfd_mhop_insert(bfd); + } else { + bfd->shop.peer = bpc->bpc_peer; + if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) + strlcpy(bfd->shop.port_name, bpc->bpc_localif, + sizeof(bfd->shop.port_name)); + + bfd_shop_insert(bfd); + } + + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) { + static uint8_t bfd_def_vxlan_dmac[] = {0x00, 0x23, 0x20, + 0x00, 0x00, 0x01}; + memcpy(bfd->peer_mac, bfd_def_vxlan_dmac, + sizeof(bfd_def_vxlan_dmac)); + } +#if 0 /* TODO */ + else if (event->rmac) { + if (sscanf(event->rmac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &bfd->peer_mac[0], &bfd->peer_mac[1], &bfd->peer_mac[2], + &bfd->peer_mac[3], &bfd->peer_mac[4], &bfd->peer_mac[5]) + != 6) + DLOG("%s: Assigning remote mac = %s", __func__, + event->rmac); + } +#endif + + /* + * XXX: session update triggers echo start, so we must have our + * discriminator ID set first. + */ + _bfd_session_update(bfd, bpc); + + /* Start transmitting with slow interval until peer responds */ + bfd->xmt_TO = BFD_DEF_SLOWTX; + + ptm_bfd_xmt_TO(bfd, 0); + + if (bpc->bpc_mhop) { + INFOLOG("Created new session 0x%x with vrf %s peer %s local %s", + bfd->discrs.my_discr, + (bpc->bpc_has_vrfname) ? bfd->mhop.vrf_name : "N/A", + satostr(&bfd->mhop.peer), satostr(&bfd->mhop.local)); + } else { + INFOLOG("Created new session 0x%x with peer %s port %s", + bfd->discrs.my_discr, satostr(&bfd->shop.peer), + bfd->shop.port_name[0] ? bfd->shop.port_name : "N/A"); + } + + control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd); + + return bfd; +} + +int ptm_bfd_ses_del(struct bfd_peer_cfg *bpc) +{ + struct bfd_session *bs; + + /* Find session and call free(). */ + bs = bs_peer_find(bpc); + if (bs == NULL) + return -1; + + /* This pointer is being referenced, don't let it be deleted. */ + if (bs->refcount > 0) { + zlog_debug("%s: trying to free in-use session: %" PRIu64 + " references", + __func__, bs->refcount); + return -1; + } + + if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { + INFOLOG("Deleting session 0x%x with vrf %s peer %s local %s", + bs->discrs.my_discr, + bpc->bpc_has_vrfname ? bpc->bpc_vrfname : "N/A", + satostr(&bs->mhop.peer), satostr(&bs->mhop.local)); + } else { + INFOLOG("Deleting session 0x%x with peer %s port %s", + bs->discrs.my_discr, satostr(&bs->shop.peer), + bs->shop.port_name); + } + + control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs); + + bfd_session_free(bs); + + return 0; +} + + +/* + * Helper functions. + */ +static const char *get_diag_str(int diag) +{ + for (int i = 0; diag_list[i].str; i++) { + if (diag_list[i].type == diag) + return diag_list[i].str; + } + return "N/A"; +} + +const char *satostr(struct sockaddr_any *sa) +{ +#define INETSTR_BUFCOUNT 8 + static char buf[INETSTR_BUFCOUNT][INET6_ADDRSTRLEN]; + static int bufidx; + struct sockaddr_in *sin = &sa->sa_sin; + struct sockaddr_in6 *sin6 = &sa->sa_sin6; + + bufidx += (bufidx + 1) % INETSTR_BUFCOUNT; + buf[bufidx][0] = 0; + + switch (sin->sin_family) { + case AF_INET: + inet_ntop(AF_INET, &sin->sin_addr, buf[bufidx], + sizeof(buf[bufidx])); + break; + case AF_INET6: + inet_ntop(AF_INET6, &sin6->sin6_addr, buf[bufidx], + sizeof(buf[bufidx])); + break; + + default: + strlcpy(buf[bufidx], "unknown", sizeof(buf[bufidx])); + break; + } + + return buf[bufidx]; +} + +const char *diag2str(uint8_t diag) +{ + switch (diag) { + case 0: + return "ok"; + case 1: + return "control detection time expired"; + case 2: + return "echo function failed"; + case 3: + return "neighbor signaled session down"; + case 4: + return "forwarding plane reset"; + case 5: + return "path down"; + case 6: + return "concatenated path down"; + case 7: + return "administratively down"; + case 8: + return "reverse concatenated path down"; + default: + return "unknown"; + } +} + +int strtosa(const char *addr, struct sockaddr_any *sa) +{ + memset(sa, 0, sizeof(*sa)); + + if (inet_pton(AF_INET, addr, &sa->sa_sin.sin_addr) == 1) { + sa->sa_sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin.sin_len = sizeof(sa->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return 0; + } + + if (inet_pton(AF_INET6, addr, &sa->sa_sin6.sin6_addr) == 1) { + sa->sa_sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + return 0; + } + + return -1; +} + +void integer2timestr(uint64_t time, char *buf, size_t buflen) +{ + unsigned int year, month, day, hour, minute, second; + int rv; + +#define MINUTES (60) +#define HOURS (24 * MINUTES) +#define DAYS (30 * HOURS) +#define MONTHS (12 * DAYS) +#define YEARS (MONTHS) + if (time >= YEARS) { + year = time / YEARS; + time -= year * YEARS; + + rv = snprintf(buf, buflen, "%u year(s), ", year); + buf += rv; + buflen -= rv; + } + if (time >= MONTHS) { + month = time / MONTHS; + time -= month * MONTHS; + + rv = snprintf(buf, buflen, "%u month(s), ", month); + buf += rv; + buflen -= rv; + } + if (time >= DAYS) { + day = time / DAYS; + time -= day * DAYS; + + rv = snprintf(buf, buflen, "%u day(s), ", day); + buf += rv; + buflen -= rv; + } + if (time >= HOURS) { + hour = time / HOURS; + time -= hour * HOURS; + + rv = snprintf(buf, buflen, "%u hour(s), ", hour); + buf += rv; + buflen -= rv; + } + if (time >= MINUTES) { + minute = time / MINUTES; + time -= minute * MINUTES; + + rv = snprintf(buf, buflen, "%u minute(s), ", minute); + buf += rv; + buflen -= rv; + } + second = time % MINUTES; + snprintf(buf, buflen, "%u second(s)", second); +} + + +/* + * BFD hash data structures to find sessions. + */ +static struct hash *bfd_id_hash; +static struct hash *bfd_shop_hash; +static struct hash *bfd_mhop_hash; +static struct hash *bfd_vrf_hash; +static struct hash *bfd_iface_hash; + +static unsigned int bfd_id_hash_do(void *p); +static int bfd_id_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_shop_hash_do(void *p); +static int bfd_shop_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_mhop_hash_do(void *p); +static int bfd_mhop_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_vrf_hash_do(void *p); +static int bfd_vrf_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_iface_hash_do(void *p); +static int bfd_iface_hash_cmp(const void *n1, const void *n2); + +static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop); +static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop); +static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop); +static int _iface_key(struct bfd_iface *iface, const char *ifname); + +static void _bfd_free(struct hash_backet *hb, + void *arg __attribute__((__unused__))); +static void _vrf_free(void *arg); +static void _iface_free(void *arg); + +/* BFD hash for our discriminator. */ +static unsigned int bfd_id_hash_do(void *p) +{ + struct bfd_session *bs = p; + + return jhash_1word(bs->discrs.my_discr, 0); +} + +static int bfd_id_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + return bs1->discrs.my_discr == bs2->discrs.my_discr; +} + +/* BFD hash for single hop. */ +static unsigned int bfd_shop_hash_do(void *p) +{ + struct bfd_session *bs = p; + + return jhash(&bs->shop, sizeof(bs->shop), 0); +} + +static int bfd_shop_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + return memcmp(&bs1->shop, &bs2->shop, sizeof(bs1->shop)) == 0; +} + +/* BFD hash for multi hop. */ +static unsigned int bfd_mhop_hash_do(void *p) +{ + struct bfd_session *bs = p; + + return jhash(&bs->mhop, sizeof(bs->mhop), 0); +} + +static int bfd_mhop_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_session *bs1 = n1, *bs2 = n2; + + return memcmp(&bs1->mhop, &bs2->mhop, sizeof(bs1->mhop)) == 0; +} + +/* BFD hash for VRFs. */ +static unsigned int bfd_vrf_hash_do(void *p) +{ + struct bfd_vrf *vrf = p; + + return jhash_1word(vrf->vrf_id, 0); +} + +static int bfd_vrf_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_vrf *v1 = n1, *v2 = n2; + + return v1->vrf_id == v2->vrf_id; +} + +/* BFD hash for interfaces. */ +static unsigned int bfd_iface_hash_do(void *p) +{ + struct bfd_iface *iface = p; + + return string_hash_make(iface->ifname); +} + +static int bfd_iface_hash_cmp(const void *n1, const void *n2) +{ + const struct bfd_iface *i1 = n1, *i2 = n2; + + return strcmp(i1->ifname, i2->ifname) == 0; +} + +/* Helper functions */ +static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop) +{ + bs->shop = *shop; + + /* Remove unused fields. */ + switch (bs->shop.peer.sa_sin.sin_family) { + case AF_INET: + bs->shop.peer.sa_sin.sin_port = 0; + break; + case AF_INET6: + bs->shop.peer.sa_sin6.sin6_port = 0; + break; + } +} + +static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop) +{ + _shop_key(bs, shop); + memset(bs->shop.port_name, 0, sizeof(bs->shop.port_name)); +} + +static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop) +{ + bs->mhop = *mhop; + + /* Remove unused fields. */ + switch (bs->mhop.peer.sa_sin.sin_family) { + case AF_INET: + bs->mhop.peer.sa_sin.sin_port = 0; + bs->mhop.local.sa_sin.sin_port = 0; + break; + case AF_INET6: + bs->mhop.peer.sa_sin6.sin6_port = 0; + bs->mhop.local.sa_sin6.sin6_port = 0; + break; + } +} + +static int _iface_key(struct bfd_iface *iface, const char *ifname) +{ + size_t slen = sizeof(iface->ifname); + + memset(iface->ifname, 0, slen); + if (strlcpy(iface->ifname, ifname, slen) >= slen) + return -1; + + return 0; +} + + +/* + * Hash public interface / exported functions. + */ + +/* Lookup functions. */ +struct bfd_session *bfd_id_lookup(uint32_t id) +{ + struct bfd_session bs; + + bs.discrs.my_discr = id; + + return hash_lookup(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop) +{ + struct bfd_session bs, *bsp; + + _shop_key(&bs, &shop); + + bsp = hash_lookup(bfd_shop_hash, &bs); + if (bsp == NULL && bs.shop.port_name[0] != 0) { + /* + * Since the local interface spec is optional, try + * searching the key without it as well. + */ + _shop_key2(&bs, &shop); + bsp = hash_lookup(bfd_shop_hash, &bs); + } + + return bsp; +} + +struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop) +{ + struct bfd_session bs; + + _mhop_key(&bs, &mhop); + + return hash_lookup(bfd_shop_hash, &bs); +} + +struct bfd_vrf *bfd_vrf_lookup(int vrf_id) +{ + struct bfd_vrf vrf; + + vrf.vrf_id = vrf_id; + + return hash_lookup(bfd_vrf_hash, &vrf); +} + +struct bfd_iface *bfd_iface_lookup(const char *ifname) +{ + struct bfd_iface iface; + + if (_iface_key(&iface, ifname) != 0) + return NULL; + + return hash_lookup(bfd_iface_hash, &iface); +} + +/* + * Delete functions. + * + * Delete functions searches and remove the item from the hash and + * returns a pointer to the removed item data. If the item was not found + * then it returns NULL. + * + * The data stored inside the hash is not free()ed, so you must do it + * manually after getting the pointer back. + */ +struct bfd_session *bfd_id_delete(uint32_t id) +{ + struct bfd_session bs; + + bs.discrs.my_discr = id; + + return hash_release(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop) +{ + struct bfd_session bs, *bsp; + + _shop_key(&bs, &shop); + bsp = hash_release(bfd_shop_hash, &bs); + if (bsp == NULL && shop.port_name[0] != 0) { + /* + * Since the local interface spec is optional, try + * searching the key without it as well. + */ + _shop_key2(&bs, &shop); + bsp = hash_release(bfd_shop_hash, &bs); + } + + return bsp; +} + +struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop) +{ + struct bfd_session bs; + + _mhop_key(&bs, &mhop); + + return hash_release(bfd_mhop_hash, &bs); +} + +struct bfd_vrf *bfd_vrf_delete(int vrf_id) +{ + struct bfd_vrf vrf; + + vrf.vrf_id = vrf_id; + + return hash_release(bfd_vrf_hash, &vrf); +} + +struct bfd_iface *bfd_iface_delete(const char *ifname) +{ + struct bfd_iface iface; + + if (_iface_key(&iface, ifname) != 0) + return NULL; + + return hash_release(bfd_iface_hash, &iface); +} + +/* Iteration functions. */ +void bfd_id_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_id_hash, hif, arg); +} + +void bfd_shop_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_shop_hash, hif, arg); +} + +void bfd_mhop_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_mhop_hash, hif, arg); +} + +void bfd_vrf_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_vrf_hash, hif, arg); +} + +void bfd_iface_iterate(hash_iter_func hif, void *arg) +{ + hash_iterate(bfd_iface_hash, hif, arg); +} + +/* + * Insert functions. + * + * Inserts session into hash and returns `true` on success, otherwise + * `false`. + */ +bool bfd_id_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_id_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_shop_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_shop_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_mhop_insert(struct bfd_session *bs) +{ + return (hash_get(bfd_mhop_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_vrf_insert(struct bfd_vrf *vrf) +{ + return (hash_get(bfd_vrf_hash, vrf, hash_alloc_intern) == vrf); +} + +bool bfd_iface_insert(struct bfd_iface *iface) +{ + return (hash_get(bfd_iface_hash, iface, hash_alloc_intern) == iface); +} + +void bfd_initialize(void) +{ + bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp, + "BFD discriminator hash"); + bfd_shop_hash = hash_create(bfd_shop_hash_do, bfd_shop_hash_cmp, + "BFD single hop hash"); + bfd_mhop_hash = hash_create(bfd_mhop_hash_do, bfd_mhop_hash_cmp, + "BFD multihop hop hash"); + bfd_vrf_hash = + hash_create(bfd_vrf_hash_do, bfd_vrf_hash_cmp, "BFD VRF hash"); + bfd_iface_hash = hash_create(bfd_iface_hash_do, bfd_iface_hash_cmp, + "BFD interface hash"); +} + +static void _bfd_free(struct hash_backet *hb, + void *arg __attribute__((__unused__))) +{ + struct bfd_session *bs = hb->data; + + bfd_session_free(bs); +} + +static void _vrf_free(void *arg) +{ + struct bfd_vrf *vrf = arg; + + XFREE(MTYPE_BFDD_CONFIG, vrf); +} + +static void _iface_free(void *arg) +{ + struct bfd_iface *iface = arg; + + XFREE(MTYPE_BFDD_CONFIG, iface); +} + +void bfd_shutdown(void) +{ + /* + * Close and free all BFD sessions. + * + * _bfd_free() will call bfd_session_free() which will take care + * of removing the session from all hashes, so we just run an + * assert() here to make sure it really happened. + */ + bfd_id_iterate(_bfd_free, NULL); + assert(bfd_shop_hash->count == 0); + assert(bfd_mhop_hash->count == 0); + + /* Clean the VRF and interface hashes. */ + hash_clean(bfd_vrf_hash, _vrf_free); + hash_clean(bfd_iface_hash, _iface_free); + + /* Now free the hashes themselves. */ + hash_free(bfd_id_hash); + hash_free(bfd_shop_hash); + hash_free(bfd_mhop_hash); + hash_free(bfd_vrf_hash); + hash_free(bfd_iface_hash); +} diff --git a/bfdd/bfd.h b/bfdd/bfd.h new file mode 100644 index 0000000000..e4907ecceb --- /dev/null +++ b/bfdd/bfd.h @@ -0,0 +1,617 @@ +/********************************************************************* + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd.h: implements the BFD protocol. + */ + +#ifndef _BFD_H_ +#define _BFD_H_ + +#include + +#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, ...); + +/* Compatibility code: code to avoid touching ported code debug messages. */ +#define DLOG(fmt, args...) log_debug(fmt, ##args) +#define INFOLOG(fmt, args...) log_info(fmt, ##args) +#define ERRLOG(fmt, args...) log_error(fmt, ##args) +#define CRITLOG(fmt, args...) log_fatal(fmt, ##args) + +/* + * bfd_packet.c + * + * Contains the code related with receiving/seding, packing/unpacking BFD data. + */ +int bp_set_ttlv6(int sd); +int bp_set_ttl(int sd); +int bp_set_tosv6(int sd); +int bp_set_tos(int sd); +int bp_bind_dev(int sd, const char *dev); + +int bp_udp_shop(void); +int bp_udp_mhop(void); +int bp_udp6_shop(void); +int bp_udp6_mhop(void); +int bp_peer_socket(struct bfd_peer_cfg *bpc); +int bp_peer_socketv6(struct bfd_peer_cfg *bpc); + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit); +void ptm_bfd_echo_snd(struct bfd_session *bfd); + +int bfd_recv_cb(struct thread *t); + +uint16_t checksum(uint16_t *buf, int len); + + +/* + * event.c + * + * Contains the code related with event loop. + */ +typedef void (*bfd_ev_cb)(struct thread *t); + +void bfd_recvtimer_update(struct bfd_session *bs); +void bfd_echo_recvtimer_update(struct bfd_session *bs); +void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter); +void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter); + +void bfd_xmttimer_delete(struct bfd_session *bs); +void bfd_echo_xmttimer_delete(struct bfd_session *bs); +void bfd_recvtimer_delete(struct bfd_session *bs); +void bfd_echo_recvtimer_delete(struct bfd_session *bs); + +void bfd_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_echo_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); +void bfd_echo_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); + + +/* + * bfd.c + * + * BFD protocol specific code. + */ +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc); +int ptm_bfd_ses_del(struct bfd_peer_cfg *bpc); +void ptm_bfd_ses_dn(struct bfd_session *bfd, uint8_t diag); +void ptm_bfd_ses_up(struct bfd_session *bfd); +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen); +void ptm_bfd_echo_stop(struct bfd_session *bfd, int polling); +void ptm_bfd_echo_start(struct bfd_session *bfd); +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit); +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo); +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, char *port_name, + struct sockaddr_any *peer, + struct sockaddr_any *local, + char *vrf_name, bool is_mhop); + +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc); +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel); +const char *satostr(struct sockaddr_any *sa); +const char *diag2str(uint8_t diag); +int strtosa(const char *addr, struct sockaddr_any *sa); +void integer2timestr(uint64_t time, char *buf, size_t buflen); + +/* BFD hash data structures interface */ +void bfd_initialize(void); +void bfd_shutdown(void); +struct bfd_session *bfd_id_lookup(uint32_t id); +struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop); +struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop); +struct bfd_vrf *bfd_vrf_lookup(int vrf_id); +struct bfd_iface *bfd_iface_lookup(const char *ifname); + +struct bfd_session *bfd_id_delete(uint32_t id); +struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop); +struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop); +struct bfd_vrf *bfd_vrf_delete(int vrf_id); +struct bfd_iface *bfd_iface_delete(const char *ifname); + +bool bfd_id_insert(struct bfd_session *bs); +bool bfd_shop_insert(struct bfd_session *bs); +bool bfd_mhop_insert(struct bfd_session *bs); +bool bfd_vrf_insert(struct bfd_vrf *vrf); +bool bfd_iface_insert(struct bfd_iface *iface); + +typedef void (*hash_iter_func)(struct hash_backet *hb, void *arg); +void bfd_id_iterate(hash_iter_func hif, void *arg); +void bfd_shop_iterate(hash_iter_func hif, void *arg); +void bfd_mhop_iterate(hash_iter_func hif, void *arg); +void bfd_vrf_iterate(hash_iter_func hif, void *arg); +void bfd_iface_iterate(hash_iter_func hif, void *arg); + +/* Export callback functions for `event.c`. */ +extern struct thread_master *master; + +int bfd_recvtimer_cb(struct thread *t); +int bfd_echo_recvtimer_cb(struct thread *t); +int bfd_xmt_cb(struct thread *t); +int bfd_echo_xmt_cb(struct thread *t); + + +/* + * OS compatibility functions. + */ +struct udp_psuedo_header { + uint32_t saddr; + uint32_t daddr; + uint8_t reserved; + uint8_t protocol; + uint16_t len; +}; + +#define UDP_PSUEDO_HDR_LEN sizeof(struct udp_psuedo_header) + +#if defined(BFD_LINUX) || defined(BFD_BSD) +int ptm_bfd_fetch_ifindex(const char *ifname); +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac); +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen); +int ptm_bfd_echo_sock_init(void); +int ptm_bfd_vxlan_sock_init(void); +#endif /* BFD_LINUX || BFD_BSD */ + +#ifdef BFD_LINUX +uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len); +#endif /* BFD_LINUX */ + +#ifdef BFD_BSD +uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len); +ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen, + struct sockaddr_storage *ss, socklen_t *sslen, + uint8_t *ttl, uint32_t *id); +#endif /* BFD_BSD */ + +#endif /* _BFD_H_ */ diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c new file mode 100644 index 0000000000..79aa706a53 --- /dev/null +++ b/bfdd/bfd_packet.c @@ -0,0 +1,1488 @@ +/********************************************************************* + * Copyright 2017 Cumulus Networks, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd_packet.c: implements the BFD protocol packet handling. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include + +#ifdef BFD_LINUX +#include +#endif /* BFD_LINUX */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#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("%s: not implemented"); + return -1; +#endif + } else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = bs->shop.peer.sa_sin6.sin6_addr; + sin6.sin6_port = + (port) ? *port + : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? htons(BFD_DEF_MHOP_DEST_PORT) + : htons(BFD_DEFDESTPORT); + + sd = bs->sock; + sa = (struct sockaddr *)&sin6; + slen = sizeof(sin6); + } else { + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr = bs->shop.peer.sa_sin.sin_addr; + sin.sin_port = + (port) ? *port + : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) + ? htons(BFD_DEF_MHOP_DEST_PORT) + : htons(BFD_DEFDESTPORT); + + sd = bs->sock; + sa = (struct sockaddr *)&sin; + slen = sizeof(sin); + } + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sa->sa_len = slen; +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + rv = sendto(sd, data, datalen, 0, sa, slen); + if (rv <= 0) { + log_debug("%s:%d: sendto: (%d) %s", __func__, __LINE__, errno, + strerror(errno)); + return -1; + } + if (rv < (ssize_t)datalen) { + log_debug("%s:%d: sendto: sent partial data", __func__, + __LINE__); + } + + return 0; +} + +static void ptm_bfd_echo_pkt_create(struct bfd_session *bfd) +{ + struct bfd_raw_echo_pkt ep; + uint8_t *pkt = bfd->echo_pkt; + + memset(&ep, 0, sizeof(ep)); + memset(bfd->echo_pkt, 0, sizeof(bfd->echo_pkt)); + + /* Construct ethernet header information */ + memcpy(pkt, bfd->peer_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; + memcpy(pkt, bfd->local_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; +#ifdef BFD_LINUX + pkt[0] = ETH_P_IP / 256; + pkt[1] = ETH_P_IP % 256; +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + pkt[0] = ETHERTYPE_IP / 256; + pkt[1] = ETHERTYPE_IP % 256; +#endif /* BFD_BSD */ + pkt += 2; + + /* Construct IP header information */ +#ifdef BFD_LINUX + ep.ip.version = 4; + ep.ip.ihl = 5; + ep.ip.tos = 0; + ep.ip.tot_len = htons(IP_ECHO_PKT_LEN); + ep.ip.id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep.ip.frag_off = 0; + ep.ip.ttl = BFD_TTL_VAL; + ep.ip.protocol = IPPROTO_UDP; + ep.ip.saddr = bfd->local_ip.sa_sin.sin_addr.s_addr; + ep.ip.daddr = bfd->shop.peer.sa_sin.sin_addr.s_addr; + ep.ip.check = checksum((uint16_t *)&ep.ip, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep.ip.ip_v = 4; + ep.ip.ip_hl = 5; + ep.ip.ip_tos = 0; + ep.ip.ip_len = htons(IP_ECHO_PKT_LEN); + ep.ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep.ip.ip_off = 0; + ep.ip.ip_ttl = BFD_TTL_VAL; + ep.ip.ip_p = IPPROTO_UDP; + ep.ip.ip_src = bfd->local_ip.sa_sin.sin_addr; + ep.ip.ip_dst = bfd->shop.peer.sa_sin.sin_addr; + ep.ip.ip_sum = checksum((uint16_t *)&ep.ip, IP_HDR_LEN); +#endif /* BFD_BSD */ + + /* Construct UDP header information */ +#ifdef BFD_LINUX + ep.udp.source = htons(BFD_DEF_ECHO_PORT); + ep.udp.dest = htons(BFD_DEF_ECHO_PORT); + ep.udp.len = htons(UDP_ECHO_PKT_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep.udp.uh_sport = htons(BFD_DEF_ECHO_PORT); + ep.udp.uh_dport = htons(BFD_DEF_ECHO_PORT); + ep.udp.uh_ulen = htons(UDP_ECHO_PKT_LEN); +#endif /* BFD_BSD */ + + /* Construct Echo packet information */ + ep.data.ver = BFD_ECHO_VERSION; + ep.data.len = BFD_ECHO_PKT_LEN; + ep.data.my_discr = htonl(bfd->discrs.my_discr); +#ifdef BFD_LINUX + ep.udp.check = +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep.udp.uh_sum = +#endif /* BFD_BSD */ + udp4_checksum(&ep.ip, (uint8_t *)&ep.udp, + UDP_ECHO_PKT_LEN); + + memcpy(pkt, &ep, sizeof(ep)); +} + +void ptm_bfd_echo_snd(struct bfd_session *bfd) +{ + struct bfd_raw_echo_pkt *ep; + bool use_layer2 = false; + const void *pkt; + size_t pktlen; + uint16_t port = htons(BFD_DEF_ECHO_PORT); + + if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + ptm_bfd_echo_pkt_create(bfd); + BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + } else { + /* just update the checksum and ip Id */ + ep = (struct bfd_raw_echo_pkt *)(bfd->echo_pkt + ETH_HDR_LEN); +#ifdef BFD_LINUX + ep->ip.id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep->ip.check = 0; + ep->ip.check = checksum((uint16_t *)&ep->ip, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + ep->ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd)); + ep->ip.ip_sum = 0; + ep->ip.ip_sum = checksum((uint16_t *)&ep->ip, IP_HDR_LEN); +#endif /* BFD_BSD */ + } + + if (use_layer2) { + pkt = bfd->echo_pkt; + pktlen = BFD_ECHO_PKT_TOT_LEN; + } else { + pkt = &bfd->echo_pkt[ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN]; + pktlen = BFD_ECHO_PKT_TOT_LEN + - (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN); + } + + if (_ptm_bfd_send(bfd, use_layer2, &port, pkt, pktlen) != 0) { + ERRLOG("%s: _ptm_bfd_send: %s", __func__, strerror(errno)); + return; + } + + bfd->stats.tx_echo_pkt++; +} + +static int ptm_bfd_echo_loopback(uint8_t *pkt, int pkt_len, struct sockaddr *ss, + socklen_t sslen) +{ +#ifdef BFD_LINUX + struct bfd_raw_echo_pkt *ep = + (struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN); + uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH]; + uint32_t temp_ip; + struct ethhdr *eth = (struct ethhdr *)pkt; + + /* swap the mac addresses */ + memcpy(temp_mac, eth->h_source, ETHERNET_ADDRESS_LENGTH); + memcpy(eth->h_source, eth->h_dest, ETHERNET_ADDRESS_LENGTH); + memcpy(eth->h_dest, temp_mac, ETHERNET_ADDRESS_LENGTH); + + /* swap ip addresses */ + temp_ip = ep->ip.saddr; + ep->ip.saddr = ep->ip.daddr; + ep->ip.daddr = temp_ip; + + ep->ip.ttl = ep->ip.ttl - 1; + ep->ip.check = 0; + ep->ip.check = checksum((uint16_t *)ep, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD_FILTER + struct bfd_raw_echo_pkt_t *ep = + (struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN); + uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH]; + struct in_addr temp_ip; + struct ether_header *ether = (struct ether_header *)pkt; + + /* + * TODO: this is not yet implemented and requires BPF code for + * OmniOS, NetBSD and FreeBSD9. + */ + + /* swap the mac addresses */ + memcpy(temp_mac, ether->ether_shost, ETHERNET_ADDRESS_LENGTH); + memcpy(ether->ether_shost, ether->ether_dhost, ETHERNET_ADDRESS_LENGTH); + memcpy(ether->ether_dhost, temp_mac, ETHERNET_ADDRESS_LENGTH); + + /* swap ip addresses */ + temp_ip = ep->ip.ip_src; + ep->ip.ip_src = ep->ip.ip_dst; + ep->ip.ip_dst = temp_ip; + + ep->ip.ip_ttl = ep->ip.ip_ttl - 1; + ep->ip.ip_sum = 0; + ep->ip.ip_sum = checksum((uint16_t *)ep, IP_HDR_LEN); +#endif /* BFD_BSD_FILTER */ + + if (sendto(bglobal.bg_echo, pkt, pkt_len, 0, ss, sslen) < 0) { + ERRLOG("%s: sendto: %s", __func__, strerror(errno)); + return -1; + } + + return 0; +} + +static void ptm_bfd_vxlan_pkt_snd(struct bfd_session *bfd + __attribute__((__unused__)), + int fbit __attribute__((__unused__))) +{ +#if 0 /* TODO: VxLAN support. */ + struct bfd_raw_ctrl_pkt cp; + uint8_t vxlan_pkt[BFD_VXLAN_PKT_TOT_LEN]; + uint8_t *pkt = vxlan_pkt; + struct sockaddr_in sin; + struct vxlan_hdr *vhdr; + + memset(vxlan_pkt, 0, sizeof(vxlan_pkt)); + memset(&cp, 0, sizeof(cp)); + + /* Construct VxLAN header information */ + vhdr = (struct vxlan_hdr *)pkt; + vhdr->flags = htonl(0x08000000); + vhdr->vnid = htonl(bfd->vxlan_info.vnid << 8); + pkt += VXLAN_HDR_LEN; + + /* Construct ethernet header information */ + memcpy(pkt, bfd->vxlan_info.peer_dst_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; + memcpy(pkt, bfd->vxlan_info.local_dst_mac, ETHERNET_ADDRESS_LENGTH); + pkt = pkt + ETHERNET_ADDRESS_LENGTH; + pkt[0] = ETH_P_IP / 256; + pkt[1] = ETH_P_IP % 256; + pkt += 2; + + /* Construct IP header information */ + cp.ip.version = 4; + cp.ip.ihl = 5; + cp.ip.tos = 0; + cp.ip.tot_len = htons(IP_CTRL_PKT_LEN); + cp.ip.id = ptm_bfd_gen_IP_ID(bfd); + cp.ip.frag_off = 0; + cp.ip.ttl = BFD_TTL_VAL; + cp.ip.protocol = IPPROTO_UDP; + cp.ip.daddr = bfd->vxlan_info.peer_dst_ip.s_addr; + cp.ip.saddr = bfd->vxlan_info.local_dst_ip.s_addr; + cp.ip.check = checksum((uint16_t *)&cp.ip, IP_HDR_LEN); + + /* Construct UDP header information */ + cp.udp.source = htons(BFD_DEFDESTPORT); + cp.udp.dest = htons(BFD_DEFDESTPORT); + cp.udp.len = htons(UDP_CTRL_PKT_LEN); + + /* Construct BFD control packet information */ + cp.data.diag = bfd->local_diag; + BFD_SETVER(cp.data.diag, BFD_VERSION); + BFD_SETSTATE(cp.data.flags, bfd->ses_state); + BFD_SETDEMANDBIT(cp.data.flags, BFD_DEF_DEMAND); + BFD_SETPBIT(cp.data.flags, bfd->polling); + BFD_SETFBIT(cp.data.flags, fbit); + cp.data.detect_mult = bfd->detect_mult; + cp.data.len = BFD_PKT_LEN; + cp.data.discrs.my_discr = htonl(bfd->discrs.my_discr); + cp.data.discrs.remote_discr = htonl(bfd->discrs.remote_discr); + cp.data.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx); + cp.data.timers.required_min_rx = htonl(bfd->timers.required_min_rx); + cp.data.timers.required_min_echo = htonl(bfd->timers.required_min_echo); + + cp.udp.check = + udp4_checksum(&cp.ip, (uint8_t *)&cp.udp, UDP_CTRL_PKT_LEN); + + memcpy(pkt, &cp, sizeof(cp)); + sin.sin_family = AF_INET; + sin.sin_addr = bfd->shop.peer.sa_sin.sin_addr; + sin.sin_port = htons(4789); + + if (sendto(bfd->sock, vxlan_pkt, BFD_VXLAN_PKT_TOT_LEN, 0, + (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) + < 0) { + ERRLOG("Error sending vxlan bfd pkt: %s", strerror(errno)); + } else { + bfd->stats.tx_ctrl_pkt++; + } +#endif +} + +static int ptm_bfd_process_echo_pkt(int s) +{ + uint32_t my_discr = 0; + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + uint8_t rx_pkt[BFD_RX_BUF_LEN]; + ssize_t pkt_len = sizeof(rx_pkt); + struct bfd_session *bfd; +#ifdef BFD_LINUX + struct bfd_raw_echo_pkt *ep; + + /* + * valgrind: memset() ss so valgrind doesn't complain about + * uninitialized memory. + */ + memset(&ss, 0, sizeof(ss)); + pkt_len = recvfrom(s, rx_pkt, sizeof(rx_pkt), MSG_DONTWAIT, + (struct sockaddr *)&ss, &sslen); + if (pkt_len <= 0) { + if (errno != EAGAIN) + ERRLOG("%s: recvfrom: %s", __func__, strerror(errno)); + return -1; + } + + /* Check if we have at least the basic headers to send back. */ + if (pkt_len < HEADERS_MIN_LEN) { + INFOLOG("Received short echo packet"); + return -1; + } + + ep = (struct bfd_raw_echo_pkt *)(rx_pkt + ETH_HDR_LEN); + /* if TTL = 255, assume that the received echo packet has + * to be looped back + */ + if (ep->ip.ttl == BFD_TTL_VAL) + return ptm_bfd_echo_loopback(rx_pkt, pkt_len, + (struct sockaddr *)&ss, + sizeof(struct sockaddr_ll)); + + /* Packet is too small for us to process */ + if (pkt_len < BFD_ECHO_PKT_TOT_LEN) { + INFOLOG("Received short echo packet"); + return -1; + } + + my_discr = ntohl(ep->data.my_discr); + if (ep->data.my_discr == 0) { + INFOLOG("My discriminator is zero in echo pkt from 0x%x", + ntohl(ep->ip.saddr)); + return -1; + } +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + int rv; + uint8_t ttl; + + /* + * bsd_echo_sock_read() already treats invalid TTL values and + * zeroed discriminators. + */ + rv = bsd_echo_sock_read(s, rx_pkt, &pkt_len, &ss, &sslen, &ttl, + &my_discr); + if (rv == -1) + return -1; + + if (ttl == BFD_TTL_VAL) + return ptm_bfd_echo_loopback(rx_pkt, pkt_len, + (struct sockaddr *)&ss, sslen); +#endif /* BFD_BSD */ + + /* Your discriminator not zero - use it to find session */ + bfd = bfd_id_lookup(my_discr); + if (bfd == NULL) { + INFOLOG("Failed to extract session from echo packet"); + return -1; + } + + if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + INFOLOG("BFD echo not active - ignore echo packet"); + return -1; + } + + bfd->stats.rx_echo_pkt++; + + /* Compute detect time */ + bfd->echo_detect_TO = bfd->remote_detect_mult * bfd->echo_xmt_TO; + + /* Update echo receive timeout. */ + bfd_echo_recvtimer_update(bfd); + + return 0; +} + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit) +{ + struct bfd_pkt cp; + + /* if the BFD session is for VxLAN tunnel, then construct and + * send bfd raw packet + */ + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) { + ptm_bfd_vxlan_pkt_snd(bfd, fbit); + return; + } + + /* Set fields according to section 6.5.7 */ + cp.diag = bfd->local_diag; + BFD_SETVER(cp.diag, BFD_VERSION); + cp.flags = 0; + BFD_SETSTATE(cp.flags, bfd->ses_state); + BFD_SETDEMANDBIT(cp.flags, BFD_DEF_DEMAND); + BFD_SETPBIT(cp.flags, bfd->polling); + BFD_SETFBIT(cp.flags, fbit); + cp.detect_mult = bfd->detect_mult; + cp.len = BFD_PKT_LEN; + cp.discrs.my_discr = htonl(bfd->discrs.my_discr); + cp.discrs.remote_discr = htonl(bfd->discrs.remote_discr); + if (bfd->polling) { + cp.timers.desired_min_tx = + htonl(bfd->new_timers.desired_min_tx); + cp.timers.required_min_rx = + htonl(bfd->new_timers.required_min_rx); + } else { + cp.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx); + cp.timers.required_min_rx = htonl(bfd->timers.required_min_rx); + } + cp.timers.required_min_echo = htonl(bfd->timers.required_min_echo); + + if (_ptm_bfd_send(bfd, false, NULL, &cp, BFD_PKT_LEN) != 0) { + ERRLOG("Error sending control pkt: %s", strerror(errno)); + return; + } + + bfd->stats.tx_ctrl_pkt++; +} + +#if 0 /* TODO VxLAN Support */ +static struct bfd_pkt * +ptm_bfd_process_vxlan_pkt(int s, ptm_sockevent_e se, void *udata, int *ifindex, + struct sockaddr_in *sin, + struct bfd_session_vxlan_info_t *vxlan_info, + uint8_t *rx_pkt, int *mlen) +{ + struct sockaddr_ll sll; + uint32_t from_len = sizeof(struct sockaddr_ll); + struct bfd_raw_ctrl_pkt *cp; + uint8_t *pkt = rx_pkt; + struct iphdr *iph; + struct ethhdr *inner_ethh; + + *mlen = recvfrom(s, rx_pkt, BFD_RX_BUF_LEN, MSG_DONTWAIT, + (struct sockaddr *)&sll, &from_len); + + if (*mlen < 0) { + if (errno != EAGAIN) + ERRLOG("Error receiving from BFD Vxlan socket %d: %m", + s); + return NULL; + } + + iph = (struct iphdr *)(pkt + ETH_HDR_LEN); + pkt = pkt + ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN; + vxlan_info->vnid = ntohl(*((int *)(pkt + 4))); + vxlan_info->vnid = vxlan_info->vnid >> 8; + + pkt = pkt + VXLAN_HDR_LEN; + inner_ethh = (struct ethhdr *)pkt; + + cp = (struct bfd_raw_ctrl_pkt *)(pkt + ETH_HDR_LEN); + + /* Discard the non BFD packets */ + if (ntohs(cp->udp.dest) != BFD_DEFDESTPORT) + return NULL; + + *ifindex = sll.sll_ifindex; + sin->sin_addr.s_addr = iph->saddr; + sin->sin_port = ntohs(cp->udp.dest); + + vxlan_info->local_dst_ip.s_addr = cp->ip.daddr; + memcpy(vxlan_info->local_dst_mac, inner_ethh->h_dest, + ETHERNET_ADDRESS_LENGTH); + + return &cp->data; +} +#endif /* VxLAN */ + +static bool +ptm_bfd_validate_vxlan_pkt(struct bfd_session *bfd, + struct bfd_session_vxlan_info *vxlan_info) +{ + if (bfd->vxlan_info.check_tnl_key && (vxlan_info->vnid != 0)) { + ERRLOG("Error Rx BFD Vxlan pkt with non-zero vnid %d", + vxlan_info->vnid); + return false; + } + + if (bfd->vxlan_info.local_dst_ip.s_addr + != vxlan_info->local_dst_ip.s_addr) { + ERRLOG("Error Rx BFD Vxlan pkt with wrong inner dst IP %s", + inet_ntoa(vxlan_info->local_dst_ip)); + return false; + } + + if (memcmp(bfd->vxlan_info.local_dst_mac, vxlan_info->local_dst_mac, + ETHERNET_ADDRESS_LENGTH)) { + ERRLOG("Error Rx BFD Vxlan pkt with wrong inner dst MAC %02x:%02x:%02x:%02x:%02x:%02x", + vxlan_info->local_dst_mac[0], + vxlan_info->local_dst_mac[1], + vxlan_info->local_dst_mac[2], + vxlan_info->local_dst_mac[3], + vxlan_info->local_dst_mac[4], + vxlan_info->local_dst_mac[5]); + return false; + } + + return true; +} + +static ssize_t bfd_recv_ipv4(int sd, bool is_mhop, char *port, size_t portlen, + char *vrfname, size_t vrfnamelen, + struct sockaddr_any *local, + struct sockaddr_any *peer) +{ + struct cmsghdr *cm; + int ifindex; + ssize_t mlen; + + memset(port, 0, portlen); + memset(vrfname, 0, vrfnamelen); + memset(local, 0, sizeof(*local)); + memset(peer, 0, sizeof(*peer)); + + mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT); + if (mlen == -1) { + if (errno != EAGAIN) { + ERRLOG("Error receiving from BFD socket: %s", + strerror(errno)); + } + return -1; + } + + /* Get source address */ + peer->sa_sin = *((struct sockaddr_in *)(msghdr.msg_name)); + + /* Get and check TTL */ + for (cm = CMSG_FIRSTHDR(&msghdr); cm != NULL; + cm = CMSG_NXTHDR(&msghdr, cm)) { + if (cm->cmsg_level != IPPROTO_IP) + continue; + + switch (cm->cmsg_type) { +#ifdef BFD_LINUX + case IP_TTL: { + uint32_t ttl; + + memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl)); + if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) { + INFOLOG("Received pkt with invalid TTL %u from %s flags: %d", + ttl, satostr(peer), msghdr.msg_flags); + return -1; + } + break; + } + + case IP_PKTINFO: { + struct in_pktinfo *pi = + (struct in_pktinfo *)CMSG_DATA(cm); + + if (pi == NULL) + break; + + local->sa_sin.sin_family = AF_INET; + local->sa_sin.sin_addr = pi->ipi_addr; + fetch_portname_from_ifindex(pi->ipi_ifindex, port, + portlen); + break; + } +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + case IP_RECVTTL: { + uint8_t ttl; + + memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl)); + if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) { + INFOLOG("Received pkt with invalid TTL %u from %s flags: %d", + ttl, satostr(peer), msghdr.msg_flags); + return -1; + } + break; + } + + case IP_RECVDSTADDR: { + struct in_addr ia; + + memcpy(&ia, CMSG_DATA(cm), sizeof(ia)); + local->sa_sin.sin_family = AF_INET; + local->sa_sin.sin_addr = ia; + break; + } +#endif /* BFD_BSD */ + + default: + /* + * On *BSDs we expect to land here when skipping + * the IP_RECVIF header. It will be handled by + * getsockopt_ifindex() below. + */ + /* NOTHING */ + break; + } + } + + /* OS agnostic way of getting interface name. */ + if (port[0] == 0) { + ifindex = getsockopt_ifindex(AF_INET, &msghdr); + if (ifindex > 0) + fetch_portname_from_ifindex(ifindex, port, portlen); + } + + return mlen; +} + +ssize_t bfd_recv_ipv6(int sd, bool is_mhop, char *port, size_t portlen, + char *vrfname, size_t vrfnamelen, + struct sockaddr_any *local, struct sockaddr_any *peer) +{ + struct cmsghdr *cm; + struct in6_pktinfo *pi6 = NULL; + ssize_t mlen; + + memset(port, 0, portlen); + memset(vrfname, 0, vrfnamelen); + memset(local, 0, sizeof(*local)); + memset(peer, 0, sizeof(*peer)); + + mlen = recvmsg(sd, &msghdr6, MSG_DONTWAIT); + if (mlen == -1) { + if (errno != EAGAIN) { + ERRLOG("Error receiving from BFD socket: %s", + strerror(errno)); + } + return -1; + } + + /* Get source address */ + peer->sa_sin6 = *((struct sockaddr_in6 *)(msghdr6.msg_name)); + + /* Get and check TTL */ + for (cm = CMSG_FIRSTHDR(&msghdr6); cm != NULL; + cm = CMSG_NXTHDR(&msghdr6, cm)) { + if (cm->cmsg_level != IPPROTO_IPV6) + continue; + + if (cm->cmsg_type == IPV6_HOPLIMIT) { + memcpy(&ttlval, CMSG_DATA(cm), 4); + if ((is_mhop == false) && (ttlval != BFD_TTL_VAL)) { + INFOLOG("Received pkt with invalid TTL %u from %s flags: %d", + ttlval, satostr(peer), + msghdr.msg_flags); + return -1; + } + } else if (cm->cmsg_type == IPV6_PKTINFO) { + pi6 = (struct in6_pktinfo *)CMSG_DATA(cm); + if (pi6) { + local->sa_sin.sin_family = AF_INET6; + local->sa_sin6.sin6_addr = pi6->ipi6_addr; + fetch_portname_from_ifindex(pi6->ipi6_ifindex, + port, portlen); + } + } + } + + return mlen; +} + +static void bfd_sd_reschedule(int sd) +{ + if (sd == bglobal.bg_shop) { + bglobal.bg_ev[0] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop, + &bglobal.bg_ev[0]); + } else if (sd == bglobal.bg_mhop) { + bglobal.bg_ev[1] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop, + &bglobal.bg_ev[1]); + } else if (sd == bglobal.bg_shop6) { + bglobal.bg_ev[2] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6, + &bglobal.bg_ev[2]); + } else if (sd == bglobal.bg_mhop6) { + bglobal.bg_ev[3] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6, + &bglobal.bg_ev[3]); + } else if (sd == bglobal.bg_echo) { + bglobal.bg_ev[4] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo, + &bglobal.bg_ev[4]); + } else if (sd == bglobal.bg_vxlan) { + bglobal.bg_ev[5] = NULL; + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan, + &bglobal.bg_ev[5]); + } +} + +int bfd_recv_cb(struct thread *t) +{ + int sd = THREAD_FD(t); + struct bfd_session *bfd; + struct bfd_pkt *cp; + bool is_mhop, is_vxlan; + ssize_t mlen = 0; + uint8_t old_state; + uint32_t oldEchoXmt_TO, oldXmtTime; + struct sockaddr_any local, peer; + char port[MAXNAMELEN + 1], vrfname[MAXNAMELEN + 1]; + struct bfd_session_vxlan_info vxlan_info; + + /* Schedule next read. */ + bfd_sd_reschedule(sd); + + if (sd == bglobal.bg_echo) { + ptm_bfd_process_echo_pkt(sd); + return 0; + } + + is_mhop = is_vxlan = false; + if (sd == bglobal.bg_shop || sd == bglobal.bg_mhop) { + is_mhop = sd == bglobal.bg_mhop; + mlen = bfd_recv_ipv4(sd, is_mhop, port, sizeof(port), vrfname, + sizeof(vrfname), &local, &peer); + } else if (sd == bglobal.bg_shop6 || sd == bglobal.bg_mhop6) { + is_mhop = sd == bglobal.bg_mhop6; + mlen = bfd_recv_ipv6(sd, is_mhop, port, sizeof(port), vrfname, + sizeof(vrfname), &local, &peer); + } +#if 0 /* TODO vxlan handling */ + cp = ptm_bfd_process_vxlan_pkt(s, se, udata, &local_ifindex, + &sin, &vxlan_info, rx_pkt, &mlen); + if (!cp) + return -1; + + is_vxlan = true; + /* keep in network-byte order */ + peer.ip4_addr.s_addr = sin.sin_addr.s_addr; + peer.family = AF_INET; + strcpy(peer_addr, inet_ntoa(sin.sin_addr)); +#endif + + /* Implement RFC 5880 6.8.6 */ + if (mlen < BFD_PKT_LEN) { + INFOLOG("Received short packet from %s", satostr(&peer)); + return 0; + } + + cp = (struct bfd_pkt *)(msghdr.msg_iov->iov_base); + if (BFD_GETVER(cp->diag) != BFD_VERSION) { + INFOLOG("Received bad version %d from %s", BFD_GETVER(cp->diag), + satostr(&peer)); + return 0; + } + + if (cp->detect_mult == 0) { + INFOLOG("Detect Mult is zero in pkt from %s", satostr(&peer)); + return 0; + } + + if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) { + INFOLOG("Invalid length %d in control pkt from %s", cp->len, + satostr(&peer)); + return 0; + } + + if (cp->discrs.my_discr == 0) { + INFOLOG("My discriminator is zero in pkt from %s", + satostr(&peer)); + return 0; + } + + bfd = ptm_bfd_sess_find(cp, port, &peer, &local, vrfname, is_mhop); + if (bfd == NULL) { + DLOG("Failed to generate session from remote packet"); + return 0; + } + + if (is_vxlan && !ptm_bfd_validate_vxlan_pkt(bfd, &vxlan_info)) + return 0; + + bfd->stats.rx_ctrl_pkt++; + if (is_mhop) { + if ((BFD_TTL_VAL - bfd->mh_ttl) > ttlval) { + DLOG("Exceeded max hop count of %d, dropped pkt from %s with TTL %d", + bfd->mh_ttl, satostr(&peer), ttlval); + return 0; + } + } else if (bfd->local_ip.sa_sin.sin_family == AF_UNSPEC) { + bfd->local_ip = local; + } + + /* + * If no interface was detected, save the interface where the + * packet came in. + */ + if (bfd->ifindex == 0) + bfd->ifindex = ptm_bfd_fetch_ifindex(port); + + if ((bfd->discrs.remote_discr != 0) + && (bfd->discrs.remote_discr != ntohl(cp->discrs.my_discr))) { + DLOG("My Discriminator mismatch in pkt from %s, Expected %d Got %d", + satostr(&peer), bfd->discrs.remote_discr, + ntohl(cp->discrs.my_discr)); + } + + bfd->discrs.remote_discr = ntohl(cp->discrs.my_discr); + + /* If received the Final bit, the new values should take effect */ + if (bfd->polling && BFD_GETFBIT(cp->flags)) { + bfd->timers.desired_min_tx = bfd->new_timers.desired_min_tx; + bfd->timers.required_min_rx = bfd->new_timers.required_min_rx; + bfd->new_timers.desired_min_tx = 0; + bfd->new_timers.required_min_rx = 0; + bfd->polling = 0; + } + + if (!bfd->demand_mode) { + /* Compute detect time */ + bfd->detect_TO = cp->detect_mult + * ((bfd->timers.required_min_rx + > ntohl(cp->timers.desired_min_tx)) + ? bfd->timers.required_min_rx + : ntohl(cp->timers.desired_min_tx)); + bfd->remote_detect_mult = cp->detect_mult; + } else { + ERRLOG("Unsupport BFD mode detected"); + } + + /* Save remote diagnostics before state switch. */ + bfd->remote_diag = cp->diag & BFD_DIAGMASK; + + /* State switch from section 6.8.6 */ + old_state = bfd->ses_state; + if (BFD_GETSTATE(cp->flags) == PTM_BFD_ADM_DOWN) { + if (bfd->ses_state != PTM_BFD_DOWN) + ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN); + } else { + switch (bfd->ses_state) { + case (PTM_BFD_DOWN): + if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT) + ptm_bfd_ses_up(bfd); + else if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN) + bfd->ses_state = PTM_BFD_INIT; + break; + case (PTM_BFD_INIT): + if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT + || BFD_GETSTATE(cp->flags) == PTM_BFD_UP) + ptm_bfd_ses_up(bfd); + break; + case (PTM_BFD_UP): + if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN) + ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN); + break; + } + } + + if (old_state != bfd->ses_state) { + DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]", + bfd->discrs.my_discr, satostr(&peer), + state_list[old_state].str, state_list[bfd->ses_state].str); + } + + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO)) { + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + if (!ntohl(cp->timers.required_min_echo)) { + ptm_bfd_echo_stop(bfd, 1); + } else { + oldEchoXmt_TO = bfd->echo_xmt_TO; + bfd->echo_xmt_TO = + bfd->timers.required_min_echo; + if (ntohl(cp->timers.required_min_echo) + > bfd->echo_xmt_TO) + bfd->echo_xmt_TO = ntohl( + cp->timers.required_min_echo); + if (oldEchoXmt_TO != bfd->echo_xmt_TO) + ptm_bfd_echo_start(bfd); + } + } else if (ntohl(cp->timers.required_min_echo)) { + bfd->echo_xmt_TO = bfd->timers.required_min_echo; + if (ntohl(cp->timers.required_min_echo) + > bfd->echo_xmt_TO) + bfd->echo_xmt_TO = + ntohl(cp->timers.required_min_echo); + ptm_bfd_echo_start(bfd); + } + } + + if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { + bfd->echo_xmt_TO = bfd->timers.required_min_echo; + if (ntohl(cp->timers.required_min_echo) > bfd->echo_xmt_TO) + bfd->echo_xmt_TO = ntohl(cp->timers.required_min_echo); + } + + /* Calculate new transmit time */ + oldXmtTime = bfd->xmt_TO; + bfd->xmt_TO = + (bfd->timers.desired_min_tx > ntohl(cp->timers.required_min_rx)) + ? bfd->timers.desired_min_tx + : ntohl(cp->timers.required_min_rx); + + /* If transmit time has changed, and too much time until next xmt, + * restart + */ + if (BFD_GETPBIT(cp->flags)) { + ptm_bfd_xmt_TO(bfd, 1); + } else if (oldXmtTime != bfd->xmt_TO) { + /* XXX add some skid to this as well */ + ptm_bfd_start_xmt_timer(bfd, false); + } + + if (!bfd->demand_mode) { + /* Restart detection timer (packet received) */ + bfd_recvtimer_update(bfd); + } else { + ERRLOG("Unsupport BFD mode detected"); + } + + /* + * Save the timers and state sent by the remote end + * for debugging and statistics. + */ + if (BFD_GETFBIT(cp->flags)) { + bfd->remote_timers.desired_min_tx = + ntohl(cp->timers.desired_min_tx); + bfd->remote_timers.required_min_rx = + ntohl(cp->timers.required_min_rx); + bfd->remote_timers.required_min_echo = + ntohl(cp->timers.required_min_echo); + + control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bfd); + } + + return 0; +} + + +/* + * Sockets creation. + */ + + +/* + * IPv4 sockets + */ +int bp_set_ttl(int sd) +{ + if (setsockopt(sd, IPPROTO_IP, IP_TTL, &ttlval, sizeof(ttlval)) == -1) { + log_warning("%s: setsockopt(IP_TTL): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +int bp_set_tos(int sd) +{ + if (setsockopt(sd, IPPROTO_IP, IP_TOS, &tosval, sizeof(tosval)) == -1) { + log_warning("%s: setsockopt(IP_TOS): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +static void bp_set_ipopts(int sd) +{ + if (bp_set_ttl(sd) != 0) + log_fatal("%s: TTL configuration failed", __func__); + + if (setsockopt(sd, IPPROTO_IP, IP_RECVTTL, &rcvttl, sizeof(rcvttl)) + == -1) + log_fatal("%s: setsockopt(IP_RECVTTL): %s", __func__, + strerror(errno)); + +#ifdef BFD_LINUX + int pktinfo = BFD_PKT_INFO_VAL; + /* Figure out address and interface to do the peer matching. */ + if (setsockopt(sd, IPPROTO_IP, IP_PKTINFO, &pktinfo, sizeof(pktinfo)) + == -1) + log_fatal("%s: setsockopt(IP_PKTINFO): %s", __func__, + strerror(errno)); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD + int yes = 1; + + /* Find out our address for peer matching. */ + if (setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) == -1) + log_fatal("%s: setsockopt(IP_RECVDSTADDR): %s", __func__, + strerror(errno)); + + /* Find out interface where the packet came in. */ + if (setsockopt_ifindex(AF_INET, sd, yes) == -1) + log_fatal("%s: setsockopt_ipv4_ifindex: %s", __func__, + strerror(errno)); +#endif /* BFD_BSD */ +} + +static void bp_bind_ip(int sd, uint16_t port) +{ + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(port); + if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) + log_fatal("%s: bind: %s", __func__, strerror(errno)); +} + +int bp_udp_shop(void) +{ + int sd; + + sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipopts(sd); + bp_bind_ip(sd, BFD_DEFDESTPORT); + + return sd; +} + +int bp_udp_mhop(void) +{ + int sd; + + sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipopts(sd); + bp_bind_ip(sd, BFD_DEF_MHOP_DEST_PORT); + + return sd; +} + +int bp_peer_socket(struct bfd_peer_cfg *bpc) +{ + int sd, pcount; + struct sockaddr_in sin; + static int srcPort = BFD_SRCPORTINIT; + + sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + return -1; + + if (!bpc->bpc_has_vxlan) { + /* Set TTL to 255 for all transmitted packets */ + if (bp_set_ttl(sd) != 0) { + close(sd); + return -1; + } + } + + /* Set TOS to CS6 for all transmitted packets */ + if (bp_set_tos(sd) != 0) { + close(sd); + return -1; + } + + /* dont bind-to-device incase of vxlan */ + if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) { + if (bp_bind_dev(sd, bpc->bpc_localif) != 0) { + close(sd); + return -1; + } + } else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) { + if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) { + close(sd); + return -1; + } + } + + /* Find an available source port in the proper range */ + memset(&sin, 0, sizeof(sin)); + sin = bpc->bpc_local.sa_sin; + sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + if (bpc->bpc_mhop || bpc->bpc_has_vxlan) + sin.sin_addr = bpc->bpc_local.sa_sin.sin_addr; + else + sin.sin_addr.s_addr = INADDR_ANY; + + pcount = 0; + do { + if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { + /* Searched all ports, none available */ + ERRLOG("Can't find source port for new session: %s", + strerror(errno)); + close(sd); + return -1; + } + if (srcPort >= BFD_SRCPORTMAX) + srcPort = BFD_SRCPORTINIT; + sin.sin_port = htons(srcPort++); + } while (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0); + + return sd; +} + + +/* + * IPv6 sockets + */ + +int bp_peer_socketv6(struct bfd_peer_cfg *bpc) +{ + int sd, pcount, ifindex; + struct sockaddr_in6 sin6; + static int srcPort = BFD_SRCPORTINIT; + + sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + return -1; + + if (!bpc->bpc_has_vxlan) { + /* Set TTL to 255 for all transmitted packets */ + if (bp_set_ttlv6(sd) != 0) { + close(sd); + return -1; + } + } + + /* Set TOS to CS6 for all transmitted packets */ + if (bp_set_tosv6(sd) != 0) { + close(sd); + return -1; + } + + /* Find an available source port in the proper range */ + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + sin6 = bpc->bpc_local.sa_sin6; + if (sin6.sin6_family != AF_INET6) { +#if 0 /* XXX what is this? */ + ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); + if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) + sin6.sin6_scope_id = ifindex; +#endif + } else if (bpc->bpc_has_localif) { + ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); + sin6.sin6_scope_id = ifindex; + } + + if (bpc->bpc_has_localif) { + if (bp_bind_dev(sd, bpc->bpc_localif) != 0) { + close(sd); + return -1; + } + } else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) { + if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) { + close(sd); + return -1; + } + } + + pcount = 0; + do { + if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { + /* Searched all ports, none available */ + ERRLOG("Can't find source port for new session: %s", + strerror(errno)); + close(sd); + return -1; + } + if (srcPort >= BFD_SRCPORTMAX) + srcPort = BFD_SRCPORTINIT; + sin6.sin6_port = htons(srcPort++); + } while (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0); + + return sd; +} + +int bp_set_ttlv6(int sd) +{ + if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval, + sizeof(ttlval)) + == -1) { + log_warning("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +int bp_set_tosv6(int sd) +{ + if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &tosval, sizeof(tosval)) + == -1) { + log_warning("%s: setsockopt(IPV6_TCLASS): %s", __func__, + strerror(errno)); + return -1; + } + + return 0; +} + +static void bp_set_ipv6opts(int sd) +{ + static int ipv6_pktinfo = BFD_IPV6_PKT_INFO_VAL; + static int ipv6_only = BFD_IPV6_ONLY_VAL; + + if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval, + sizeof(ttlval)) + == -1) + log_fatal("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__, + strerror(errno)); + + if (setsockopt_ipv6_hoplimit(sd, rcvttl) == -1) + log_fatal("%s: setsockopt(IPV6_HOPLIMIT): %s", __func__, + strerror(errno)); + + if (setsockopt_ipv6_pktinfo(sd, ipv6_pktinfo) == -1) + log_fatal("%s: setsockopt(IPV6_PKTINFO): %s", __func__, + strerror(errno)); + + if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, + sizeof(ipv6_only)) + == -1) + log_fatal("%s: setsockopt(IPV6_V6ONLY): %s", __func__, + strerror(errno)); +} + +static void bp_bind_ipv6(int sd, uint16_t port) +{ + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_any; + sin6.sin6_port = htons(port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + if (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1) + log_fatal("%s: bind: %s", __func__, strerror(errno)); +} + +int bp_udp6_shop(void) +{ + int sd; + + sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipv6opts(sd); + bp_bind_ipv6(sd, BFD_DEFDESTPORT); + + return sd; +} + +int bp_udp6_mhop(void) +{ + int sd; + + sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); + if (sd == -1) + log_fatal("%s: socket: %s", __func__, strerror(errno)); + + bp_set_ipv6opts(sd); + bp_bind_ipv6(sd, BFD_DEF_MHOP_DEST_PORT); + + return sd; +} diff --git a/bfdd/bfdctl.h b/bfdd/bfdctl.h new file mode 100644 index 0000000000..940efd1614 --- /dev/null +++ b/bfdd/bfdctl.h @@ -0,0 +1,160 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfdctl.h: all BFDd control socket protocol definitions. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#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 0000000000..3313c8137c --- /dev/null +++ b/bfdd/bfdd.c @@ -0,0 +1,234 @@ +/* + * BFD daemon code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "bfd.h" +#include "lib/version.h" +#include "lib/libfrr.h" + +/* + * FRR related code. + */ +DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon"); +DEFINE_MTYPE(BFDD, BFDD_TMP, "short-lived temporary memory"); +DEFINE_MTYPE(BFDD, BFDD_CONFIG, "long-lived configuration memory"); +DEFINE_MTYPE(BFDD, BFDD_LABEL, "long-lived label memory"); +DEFINE_MTYPE(BFDD, BFDD_CONTROL, "long-lived control socket memory"); +DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "short-lived control notification data"); + +/* Master of threads. */ +struct thread_master *master; + +/* BFDd privileges */ +static zebra_capabilities_t _caps_p[] = {ZCAP_BIND}; + +struct zebra_privs_t bfdd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +void socket_close(int *s) +{ + if (*s <= 0) + return; + + if (close(*s) != 0) + log_error("%s: close(%d): (%d) %s", __func__, *s, errno, + strerror(errno)); + + *s = -1; +} + +static void sigusr1_handler(void) +{ + zlog_rotate(); +} + +static void sigterm_handler(void) +{ + /* Signalize shutdown. */ + frr_early_fini(); + + /* Shutdown controller to avoid receiving anymore commands. */ + control_shutdown(); + + /* Shutdown and free all protocol related memory. */ + bfd_shutdown(); + + /* Close all descriptors. */ + socket_close(&bglobal.bg_echo); + socket_close(&bglobal.bg_shop); + socket_close(&bglobal.bg_mhop); + socket_close(&bglobal.bg_shop6); + socket_close(&bglobal.bg_mhop6); + socket_close(&bglobal.bg_vxlan); + + /* Terminate and free() FRR related memory. */ + frr_fini(); + + exit(0); +} + +static struct quagga_signal_t bfd_signals[] = { + { + .signal = SIGUSR1, + .handler = &sigusr1_handler, + }, + { + .signal = SIGTERM, + .handler = &sigterm_handler, + }, + { + .signal = SIGINT, + .handler = &sigterm_handler, + }, +}; + +FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617, + .proghelp = "Implementation of the BFD protocol.", + .signals = bfd_signals, .n_signals = array_size(bfd_signals), + .privs = &bfdd_privs) + +#define OPTION_CTLSOCK 1001 +static struct option longopts[] = { + {"bfdctl", required_argument, NULL, OPTION_CTLSOCK}, + {0} +}; + + +/* + * BFD daemon related code. + */ +struct bfd_global bglobal; + +struct bfd_diag_str_list diag_list[] = { + {.str = "NeighDown", .type = BFD_DIAGNEIGHDOWN}, + {.str = "DetectTime", .type = BFD_DIAGDETECTTIME}, + {.str = "AdminDown", .type = BFD_DIAGADMINDOWN}, + {.str = NULL}, +}; + +struct bfd_state_str_list state_list[] = { + {.str = "AdminDown", .type = PTM_BFD_ADM_DOWN}, + {.str = "Down", .type = PTM_BFD_DOWN}, + {.str = "Init", .type = PTM_BFD_INIT}, + {.str = "Up", .type = PTM_BFD_UP}, + {.str = NULL}, +}; + + +static void bg_init(void) +{ + TAILQ_INIT(&bglobal.bg_bcslist); + + bglobal.bg_shop = bp_udp_shop(); + bglobal.bg_mhop = bp_udp_mhop(); + bglobal.bg_shop6 = bp_udp6_shop(); + bglobal.bg_mhop6 = bp_udp6_mhop(); + bglobal.bg_echo = ptm_bfd_echo_sock_init(); + bglobal.bg_vxlan = ptm_bfd_vxlan_sock_init(); +} + +int main(int argc, char *argv[]) +{ + const char *ctl_path = BFDD_CONTROL_SOCKET; + int opt; + + frr_preinit(&bfdd_di, argc, argv); + frr_opt_add("", longopts, + " --bfdctl Specify bfdd control socket\n"); + + while (true) { + opt = frr_getopt(argc, argv, NULL); + if (opt == EOF) + break; + + switch (opt) { + case OPTION_CTLSOCK: + ctl_path = optarg; + break; + + default: + frr_help_exit(1); + break; + } + } + +#if 0 /* TODO add support for JSON configuration files. */ + parse_config(conf); +#endif + + /* Initialize logging API. */ + log_init(1, BLOG_DEBUG, &bfdd_di); + + /* Initialize system sockets. */ + bg_init(); + + /* Initialize control socket. */ + control_init(ctl_path); + + /* Initialize FRR infrastructure. */ + master = frr_init(); + + /* Initialize BFD data structures. */ + bfd_initialize(); + + /* Add descriptors to the event loop. */ + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop, + &bglobal.bg_ev[0]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop, + &bglobal.bg_ev[1]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6, + &bglobal.bg_ev[2]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6, + &bglobal.bg_ev[3]); + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo, + &bglobal.bg_ev[4]); +#if 0 /* TODO VxLAN support. */ + thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan, + &bglobal.bg_ev[5]); +#endif + thread_add_read(master, control_accept, NULL, bglobal.bg_csock, + &bglobal.bg_csockev); + + /* read configuration file and daemonize */ + frr_config_fork(); + + frr_run(master); + /* NOTREACHED */ + + return 0; +} diff --git a/bfdd/bfdd.conf.sample b/bfdd/bfdd.conf.sample new file mode 100644 index 0000000000..9981e262bc --- /dev/null +++ b/bfdd/bfdd.conf.sample @@ -0,0 +1,5 @@ +password zebra +! +log stdout +! +line vty diff --git a/bfdd/bsd.c b/bfdd/bsd.c new file mode 100644 index 0000000000..3d196dfb9f --- /dev/null +++ b/bfdd/bsd.c @@ -0,0 +1,290 @@ +/* + * *BSD specific code + * + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#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) { + ERRLOG("%s: socket: %s", __func__, strerror(errno)); + return -1; + } + + memset(&sin, 0, sizeof(sin)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + /* OmniOS doesn't have this field, but uses this code. */ + sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ + sin.sin_family = AF_INET; + sin.sin_port = htons(3785); + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) { + log_error("%s: bind: %s", __func__, strerror(errno)); + close(s); + return -1; + } + + if (setsockopt(s, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) == -1) { + log_error("%s: setsockopt(IP_RECVTTL): %s", __func__, + strerror(errno)); + close(s); + return -1; + } + + ttl = BFD_TTL_VAL; + if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) { + log_error("%s: setsockopt(IP_TTL): %s", __func__, + strerror(errno)); + close(s); + return -1; + } + + return s; +} + +ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen, + struct sockaddr_storage *ss, socklen_t *sslen, + uint8_t *ttl, uint32_t *id) +{ + struct cmsghdr *cmsg; + struct bfd_echo_pkt *bep; + ssize_t readlen; + struct iovec iov; + struct msghdr msg; + uint8_t msgctl[255]; + char errbuf[255]; + + /* Prepare socket read. */ + memset(ss, 0, sizeof(*ss)); + memset(&msg, 0, sizeof(msg)); + iov.iov_base = buf; + iov.iov_len = *buflen; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = msgctl; + msg.msg_controllen = sizeof(msgctl); + msg.msg_name = ss; + msg.msg_namelen = *sslen; + + /* Read the socket and treat errors. */ + readlen = recvmsg(sd, &msg, 0); + if (readlen == 0) { + log_error("%s: recvmsg: socket closed", __func__); + return -1; + } + if (readlen == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return -1; + + log_error("%s: recvmsg: (%d) %s", __func__, errno, + strerror(errno)); + return -1; + } + /* Short packet, better not risk reading it. */ + if (readlen < (ssize_t)sizeof(*bep)) { + log_warning("%s: short packet (%ld of %d) from %s", __func__, + readlen, sizeof(*bep), + sockaddr_to_string(ss, errbuf, sizeof(errbuf))); + return -1; + } + *buflen = readlen; + + /* Read TTL information. */ + *ttl = 0; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != IPPROTO_IP) + continue; + if (cmsg->cmsg_type != IP_RECVTTL) + continue; + + *ttl = *(uint8_t *)CMSG_DATA(cmsg); + break; + } + if (*ttl == 0) { + log_debug("%s: failed to read TTL", __func__); + return -1; + } + + /* Read my discriminator from BFD Echo packet. */ + bep = (struct bfd_echo_pkt *)buf; + *id = bep->my_discr; + if (*id == 0) { + log_debug("%s: invalid packet discriminator from: %s", __func__, + sockaddr_to_string(ss, errbuf, sizeof(errbuf))); + return -1; + } + + /* Set the returned sockaddr new length. */ + *sslen = msg.msg_namelen; + + return 0; +} + +int ptm_bfd_vxlan_sock_init(void) +{ + /* TODO: not supported yet. */ + return -1; +} + +int bp_bind_dev(int sd, const char *dev) +{ + /* + * *BSDs don't support `SO_BINDTODEVICE`, instead you must + * manually specify the main address of the interface or use + * BPF on the socket descriptor. + */ + return 0; +} + +uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len) +{ + char *ptr; + struct udp_psuedo_header pudp_hdr; + uint16_t csum; + + pudp_hdr.saddr = ip->ip_src.s_addr; + pudp_hdr.daddr = ip->ip_dst.s_addr; + pudp_hdr.reserved = 0; + pudp_hdr.protocol = ip->ip_p; + pudp_hdr.len = htons(len); + + ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len); + memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN); + memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len); + + csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len); + XFREE(MTYPE_BFDD_TMP, ptr); + return csum; +} + +#endif /* BFD_BSD */ diff --git a/bfdd/config.c b/bfdd/config.c new file mode 100644 index 0000000000..0e0d8b7d70 --- /dev/null +++ b/bfdd/config.c @@ -0,0 +1,606 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * config.c: implements the BFD daemon configuration handling. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#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 0000000000..39c7871678 --- /dev/null +++ b/bfdd/control.c @@ -0,0 +1,901 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * control.c: implements the BFD daemon control socket. It will be used + * to talk with clients daemon/scripts/consumers. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#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; + + /* + * PERFORMANCE: reuse the bfd_control_msg allocated data for + * all control sockets to avoid wasting memory. + */ + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + /* + * Test for all notifications first, then search for + * specific peers. + */ + if ((bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) == 0) { + bnp = control_notifypeer_find(bcs, bs); + /* + * If the notification is not configured here, + * don't send it. + */ + if (bnp == NULL) + continue; + } + + _control_notify(bcs, bs); + } + + return 0; +} + +static void _control_notify_config(struct bfd_control_socket *bcs, + const char *op, struct bfd_session *bs) +{ + struct bfd_control_msg *bcm; + char *jsonstr; + size_t jsonstrlen; + + /* Generate JSON response. */ + jsonstr = config_notify_config(op, bs); + if (jsonstr == NULL) { + log_warning("%s: config_notify_config: failed to get JSON str", + __func__); + return; + } + + /* Allocate data and answer. */ + jsonstrlen = strlen(jsonstr); + bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, + sizeof(struct bfd_control_msg) + jsonstrlen); + if (bcm == NULL) { + log_warning("%s: malloc: %s", __func__, strerror(errno)); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + return; + } + + bcm->bcm_length = htonl(jsonstrlen); + bcm->bcm_ver = BMV_VERSION_1; + bcm->bcm_type = BMT_NOTIFY; + bcm->bcm_id = htons(BCM_NOTIFY_ID); + memcpy(bcm->bcm_data, jsonstr, jsonstrlen); + XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + + control_queue_enqueue(bcs, bcm); +} + +int control_notify_config(const char *op, struct bfd_session *bs) +{ + struct bfd_control_socket *bcs; + struct bfd_notify_peer *bnp; + + /* Remove the control sockets notification for this peer. */ + if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0 && bs->refcount > 0) { + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + bnp = control_notifypeer_find(bcs, bs); + if (bnp) + control_notifypeer_free(bcs, bnp); + } + } + + /* + * PERFORMANCE: reuse the bfd_control_msg allocated data for + * all control sockets to avoid wasting memory. + */ + TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { + /* + * Test for all notifications first, then search for + * specific peers. + */ + if ((bcs->bcs_notify & BCM_NOTIFY_CONFIG) == 0) + continue; + + _control_notify_config(bcs, op, bs); + } + + return 0; +} diff --git a/bfdd/event.c b/bfdd/event.c new file mode 100644 index 0000000000..ba12f5b4e8 --- /dev/null +++ b/bfdd/event.c @@ -0,0 +1,155 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * event.c: implements the BFD loop event handlers. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#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 0000000000..8a68511d53 --- /dev/null +++ b/bfdd/linux.c @@ -0,0 +1,218 @@ +/* + * Linux specific code + * + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include + +#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)) { + CRITLOG("Interface name %s truncated", ifr.ifr_name); + } + + if (ioctl(bglobal.bg_shop, SIOCGIFINDEX, &ifr) == -1) { + CRITLOG("Getting ifindex for %s failed: %s", ifname, + strerror(errno)); + return -1; + } + + return ifr.ifr_ifindex; +} + +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac) +{ + struct ifreq ifr; + + if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) + > sizeof(ifr.ifr_name)) { + CRITLOG("Interface name %s truncated", ifr.ifr_name); + } + + if (ioctl(bglobal.bg_shop, SIOCGIFHWADDR, &ifr) == -1) { + CRITLOG("Getting mac address for %s failed: %s", ifname, + strerror(errno)); + return; + } + + memcpy(mac, ifr.ifr_hwaddr.sa_data, ETHERNET_ADDRESS_LENGTH); +} + + +/* Was _fetch_portname_from_ifindex() */ +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen) +{ + struct ifreq ifr; + + ifname[0] = 0; + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_ifindex = ifindex; + + if (ioctl(bglobal.bg_shop, SIOCGIFNAME, &ifr) == -1) { + CRITLOG("Getting ifname for ifindex %d failed: %s", ifindex, + strerror(errno)); + return; + } + + strlcpy(ifname, ifr.ifr_name, ifnamelen); +} + +int ptm_bfd_echo_sock_init(void) +{ + int s; + struct sock_fprog bpf = {.len = sizeof(bfd_echo_filter) + / sizeof(bfd_echo_filter[0]), + .filter = bfd_echo_filter}; + + s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + if (s == -1) { + ERRLOG("%s: socket: %s", __func__, strerror(errno)); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) + == -1) { + ERRLOG("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__, + strerror(errno)); + close(s); + return -1; + } + + return s; +} + +int ptm_bfd_vxlan_sock_init(void) +{ + int s; + struct sock_fprog bpf = {.len = sizeof(bfd_vxlan_filter) + / sizeof(bfd_vxlan_filter[0]), + .filter = bfd_vxlan_filter}; + + s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + if (s == -1) { + ERRLOG("%s: socket: %s", __func__, strerror(errno)); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) + == -1) { + ERRLOG("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__, + strerror(errno)); + close(s); + return -1; + } + + return s; +} + +int bp_bind_dev(int sd __attribute__((__unused__)), + const char *dev __attribute__((__unused__))) +{ + /* + * TODO: implement this differently. It is not possible to + * SO_BINDTODEVICE after the daemon has dropped its privileges. + */ +#if 0 + size_t devlen = strlen(dev) + 1; + + if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, dev, devlen) == -1) { + log_warning("%s: setsockopt(SO_BINDTODEVICE, \"%s\"): %s", + __func__, dev, strerror(errno)); + return -1; + } +#endif + + return 0; +} + +uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len) +{ + char *ptr; + struct udp_psuedo_header pudp_hdr; + uint16_t csum; + + pudp_hdr.saddr = iph->saddr; + pudp_hdr.daddr = iph->daddr; + pudp_hdr.reserved = 0; + pudp_hdr.protocol = iph->protocol; + pudp_hdr.len = htons(len); + + ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len); + memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN); + memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len); + + csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len); + XFREE(MTYPE_BFDD_TMP, ptr); + return csum; +} + +#endif /* BFD_LINUX */ diff --git a/bfdd/log.c b/bfdd/log.c new file mode 100644 index 0000000000..8561956d6a --- /dev/null +++ b/bfdd/log.c @@ -0,0 +1,129 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * log.c: implements an abstraction between loggers interface. Implement all + * log backends in this file. + * + * Authors + * ------- + * Rafael Zalamena + */ + +#include + +#include +#include +#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/subdir.am b/bfdd/subdir.am new file mode 100644 index 0000000000..fe3daaf507 --- /dev/null +++ b/bfdd/subdir.am @@ -0,0 +1,28 @@ +# +# bfdd +# + +if BFDD +noinst_LIBRARIES += bfdd/libbfd.a +sbin_PROGRAMS += bfdd/bfdd +dist_examples_DATA += bfdd/bfdd.conf.sample +endif + +bfdd_libbfd_a_SOURCES = \ + bfdd/bfd.c \ + bfdd/bfd_packet.c \ + bfdd/bsd.c \ + bfdd/config.c \ + bfdd/control.c \ + bfdd/event.c \ + bfdd/linux.c \ + bfdd/log.c \ + # end + +noinst_HEADERS += \ + bfdd/bfdctl.h \ + bfdd/bfd.h \ + # end + +bfdd_bfdd_SOURCES = bfdd/bfdd.c +bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la -- 2.39.5