diff options
| author | Rafael Zalamena <rzalamena@opensourcerouting.org> | 2018-06-27 11:29:02 -0300 | 
|---|---|---|
| committer | Rafael Zalamena <rzalamena@opensourcerouting.org> | 2018-08-08 18:24:53 -0300 | 
| commit | e9e2c950d7423071e8577a12c072a5309f02f8c1 (patch) | |
| tree | 6d89f3c28fc3829affb30484aa41be57ba925d05 | |
| parent | 8519fe88c9fc6a3e217148fd2edd8d815269ec8a (diff) | |
bfdd: imported new daemon source code
Import source code from external `bfdd` daemon ported from Cumulus PTM.
Signed-off-by: Rafael Zalamena <rzalamena@opensourcerouting.org>
| -rw-r--r-- | bfdd/.gitignore | 3 | ||||
| -rw-r--r-- | bfdd/bfd.c | 1385 | ||||
| -rw-r--r-- | bfdd/bfd.h | 617 | ||||
| -rw-r--r-- | bfdd/bfd_packet.c | 1488 | ||||
| -rw-r--r-- | bfdd/bfdctl.h | 160 | ||||
| -rw-r--r-- | bfdd/bfdd.c | 234 | ||||
| -rw-r--r-- | bfdd/bfdd.conf.sample | 5 | ||||
| -rw-r--r-- | bfdd/bsd.c | 290 | ||||
| -rw-r--r-- | bfdd/config.c | 606 | ||||
| -rw-r--r-- | bfdd/control.c | 901 | ||||
| -rw-r--r-- | bfdd/event.c | 155 | ||||
| -rw-r--r-- | bfdd/linux.c | 218 | ||||
| -rw-r--r-- | bfdd/log.c | 129 | ||||
| -rw-r--r-- | bfdd/subdir.am | 28 | 
14 files changed, 6219 insertions, 0 deletions
diff --git a/bfdd/.gitignore b/bfdd/.gitignore new file mode 100644 index 0000000000..e554d1b33f --- /dev/null +++ b/bfdd/.gitignore @@ -0,0 +1,3 @@ +# ignore binary files +*.a +bfdd diff --git a/bfdd/bfd.c b/bfdd/bfd.c new file mode 100644 index 0000000000..16a3a688f9 --- /dev/null +++ b/bfdd/bfd.c @@ -0,0 +1,1385 @@ +/********************************************************************* + * Copyright 2013 Cumulus Networks, LLC.  All rights reserved. + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd.c: implements the BFD protocol. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include <zebra.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <sys/ioctl.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <netdb.h> +#include <time.h> +#include <signal.h> + +#include "lib/hash.h" +#include "lib/jhash.h" + +#include "bfd.h" + +DEFINE_QOBJ_TYPE(bfd_session); + +/* + * Prototypes + */ +static uint32_t ptm_bfd_gen_ID(void); +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd); +static void bfd_session_free(struct bfd_session *bs); +static struct bfd_session *bfd_session_new(int sd); +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, +					 uint32_t ldisc); +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc); +static const char *get_diag_str(int diag); + + +/* + * Functions + */ +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc) +{ +	struct bfd_session *bs; +	struct peer_label *pl; +	struct bfd_mhop_key mhop; +	struct bfd_shop_key shop; + +	/* Try to find label first. */ +	if (bpc->bpc_has_label) { +		pl = pl_find(bpc->bpc_label); +		if (pl != NULL) { +			bs = pl->pl_bs; +			return bs; +		} +	} + +	/* Otherwise fallback to peer/local hash lookup. */ +	if (bpc->bpc_mhop) { +		memset(&mhop, 0, sizeof(mhop)); +		mhop.peer = bpc->bpc_peer; +		mhop.local = bpc->bpc_local; +		if (bpc->bpc_has_vrfname) +			strlcpy(mhop.vrf_name, bpc->bpc_vrfname, +				sizeof(mhop.vrf_name)); + +		bs = bfd_mhop_lookup(mhop); +	} else { +		memset(&shop, 0, sizeof(shop)); +		shop.peer = bpc->bpc_peer; +		if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) +			strlcpy(shop.port_name, bpc->bpc_localif, +				sizeof(shop.port_name)); + +		bs = bfd_shop_lookup(shop); +	} + +	return bs; +} + +static uint32_t ptm_bfd_gen_ID(void) +{ +	static uint32_t sessionID = 1; + +	return (sessionID++); +} + +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo) +{ +	uint64_t jitter, xmt_TO; +	int maxpercent; + +	xmt_TO = is_echo ? bfd->echo_xmt_TO : bfd->xmt_TO; + +	/* +	 * From section 6.5.2: trasmit interval should be randomly jittered +	 * between +	 * 75% and 100% of nominal value, unless detect_mult is 1, then should +	 * be +	 * between 75% and 90%. +	 */ +	maxpercent = (bfd->detect_mult == 1) ? 16 : 26; +	jitter = (xmt_TO * (75 + (random() % maxpercent))) / 100; +	/* XXX remove that division above */ + +	if (is_echo) +		bfd_echo_xmttimer_update(bfd, jitter); +	else +		bfd_xmttimer_update(bfd, jitter); +} + +static void ptm_bfd_echo_xmt_TO(struct bfd_session *bfd) +{ +	/* Send the scheduled echo  packet */ +	ptm_bfd_echo_snd(bfd); + +	/* Restart the timer for next time */ +	ptm_bfd_start_xmt_timer(bfd, true); +} + +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit) +{ +	/* Send the scheduled control packet */ +	ptm_bfd_snd(bfd, fbit); + +	/* Restart the timer for next time */ +	ptm_bfd_start_xmt_timer(bfd, false); +} + +void ptm_bfd_echo_stop(struct bfd_session *bfd, int polling) +{ +	bfd->echo_xmt_TO = 0; +	bfd->echo_detect_TO = 0; +	BFD_UNSET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); + +	bfd_echo_xmttimer_delete(bfd); +	bfd_echo_recvtimer_delete(bfd); + +	if (polling) { +		bfd->polling = polling; +		bfd->new_timers.desired_min_tx = bfd->up_min_tx; +		bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; +		ptm_bfd_snd(bfd, 0); +	} +} + +void ptm_bfd_echo_start(struct bfd_session *bfd) +{ +	bfd->echo_detect_TO = (bfd->remote_detect_mult * bfd->echo_xmt_TO); +	ptm_bfd_echo_xmt_TO(bfd); + +	bfd->polling = 1; +	bfd->new_timers.desired_min_tx = bfd->up_min_tx; +	bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; +	ptm_bfd_snd(bfd, 0); +} + +void ptm_bfd_ses_up(struct bfd_session *bfd) +{ +	bfd->local_diag = 0; +	bfd->ses_state = PTM_BFD_UP; +	bfd->polling = 1; +	monotime(&bfd->uptime); + +	/* If the peer is capable to receiving Echo pkts */ +	if (bfd->echo_xmt_TO && !BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_MH)) { +		ptm_bfd_echo_start(bfd); +	} else { +		bfd->new_timers.desired_min_tx = bfd->up_min_tx; +		bfd->new_timers.required_min_rx = bfd->timers.required_min_rx; +		ptm_bfd_snd(bfd, 0); +	} + +	control_notify(bfd); + +	INFOLOG("Session 0x%x up peer %s", bfd->discrs.my_discr, +		satostr(&bfd->shop.peer)); +} + +void ptm_bfd_ses_dn(struct bfd_session *bfd, uint8_t diag) +{ +	int old_state = bfd->ses_state; + +	bfd->local_diag = diag; +	bfd->discrs.remote_discr = 0; +	bfd->ses_state = PTM_BFD_DOWN; +	bfd->polling = 0; +	bfd->demand_mode = 0; +	monotime(&bfd->downtime); + +	ptm_bfd_snd(bfd, 0); + +	/* only signal clients when going from up->down state */ +	if (old_state == PTM_BFD_UP) +		control_notify(bfd); + +	INFOLOG("Session 0x%x down peer %s Rsn %s prev st %s", +		bfd->discrs.my_discr, satostr(&bfd->shop.peer), +		get_diag_str(bfd->local_diag), state_list[old_state].str); + +	/* Stop echo packet transmission if they are active */ +	if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) +		ptm_bfd_echo_stop(bfd, 0); +} + +static int ptm_bfd_get_vrf_name(char *port_name, char *vrf_name) +{ +	struct bfd_iface *iface; +	struct bfd_vrf *vrf; + +	if ((port_name == NULL) || (vrf_name == NULL)) +		return -1; + +	iface = bfd_iface_lookup(port_name); +	if (iface) { +		vrf = bfd_vrf_lookup(iface->vrf_id); +		if (vrf) { +			strlcpy(vrf_name, vrf->name, sizeof(vrf->name)); +			return 0; +		} +	} +	return -1; +} + +static struct bfd_session *bfd_find_disc(struct sockaddr_any *sa, +					 uint32_t ldisc) +{ +	struct bfd_session *bs; + +	bs = bfd_id_lookup(ldisc); +	if (bs == NULL) +		return NULL; + +	/* Remove unused fields. */ +	switch (sa->sa_sin.sin_family) { +	case AF_INET: +		sa->sa_sin.sin_port = 0; +		if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin)) == 0) +			return bs; +		break; +	case AF_INET6: +		sa->sa_sin6.sin6_port = 0; +		if (memcmp(sa, &bs->shop.peer, sizeof(sa->sa_sin6)) == 0) +			return bs; +		break; +	} + +	return NULL; +} + +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, char *port_name, +				      struct sockaddr_any *peer, +				      struct sockaddr_any *local, +				      char *vrf_name, bool is_mhop) +{ +	struct bfd_session *l_bfd = NULL; +	struct bfd_mhop_key mhop; +	struct bfd_shop_key shop; +	char peer_addr[64]; +	char local_addr[64]; +	char vrf_name_buf[MAXNAMELEN + 1]; + +	/* peer, local are in network-byte order */ +	strlcpy(peer_addr, satostr(peer), sizeof(peer_addr)); +	strlcpy(local_addr, satostr(local), sizeof(local_addr)); + +	if (cp) { +		if (cp->discrs.remote_discr) { +			uint32_t ldisc = ntohl(cp->discrs.remote_discr); +			/* Your discriminator not zero - use it to find session +			 */ +			l_bfd = bfd_find_disc(peer, ldisc); + +			if (l_bfd) +				return l_bfd; + +			DLOG("Can't find session for yourDisc 0x%x from %s", +			     ldisc, peer_addr); +		} else if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN +			   || BFD_GETSTATE(cp->flags) == PTM_BFD_ADM_DOWN) { + +			if (is_mhop) { +				memset(&mhop, 0, sizeof(mhop)); +				mhop.peer = *peer; +				mhop.local = *local; +				if (vrf_name && strlen(vrf_name)) { +					strlcpy(mhop.vrf_name, vrf_name, +						sizeof(mhop.vrf_name)); +				} else if (port_name) { +					memset(vrf_name_buf, 0, +					       sizeof(vrf_name_buf)); +					if (ptm_bfd_get_vrf_name(port_name, +								 vrf_name_buf) +					    != -1) { +						strlcpy(mhop.vrf_name, +							vrf_name_buf, +							sizeof(mhop.vrf_name)); +					} +				} + +				/* Your discriminator zero - +				 *     use peer address and local address to +				 * find session +				 */ +				l_bfd = bfd_mhop_lookup(mhop); +			} else { +				memset(&shop, 0, sizeof(shop)); +				shop.peer = *peer; +				if (strlen(port_name)) +					strlcpy(shop.port_name, port_name, +						sizeof(shop.port_name)); +				/* Your discriminator zero - +				 *      use peer address and port to find +				 * session +				 */ +				l_bfd = bfd_shop_lookup(shop); +			} +			if (l_bfd) { +				/* XXX maybe remoteDiscr should be checked for +				 * remoteHeard cases +				 */ +				return l_bfd; +			} +		} +		if (is_mhop) +			DLOG("Can't find multi hop session peer/local %s/%s in vrf %s port %s", +			     peer_addr, local_addr, +			     strlen(mhop.vrf_name) ? mhop.vrf_name : "N/A", +			     port_name ? port_name : "N/A"); +		else +			DLOG("Can't find single hop session for peer/port %s/%s", +			     peer_addr, port_name); +	} else if (peer->sa_sin.sin_addr.s_addr +		   || !IN6_IS_ADDR_UNSPECIFIED(&peer->sa_sin6.sin6_addr)) { + +		if (is_mhop) { +			memset(&mhop, 0, sizeof(mhop)); +			mhop.peer = *peer; +			mhop.local = *local; +			if (vrf_name && strlen(vrf_name)) +				strlcpy(mhop.vrf_name, vrf_name, +					sizeof(mhop.vrf_name)); + +			l_bfd = bfd_mhop_lookup(mhop); +		} else { +			memset(&shop, 0, sizeof(shop)); +			shop.peer = *peer; +			if (strlen(port_name)) { +				strlcpy(shop.port_name, port_name, +					sizeof(shop.port_name)); +			} + +			l_bfd = bfd_shop_lookup(shop); +		} + +		if (l_bfd) { +			/* XXX maybe remoteDiscr should be checked for +			 * remoteHeard cases +			 */ +			return l_bfd; +		} + +		DLOG("Can't find session for peer %s", peer_addr); +	} + +	return (NULL); +} + +#if 0  /* TODO VxLAN Support */ +static void +_update_vxlan_sess_parms(struct bfd_session *bfd, bfd_sess_parms *sess_parms) +{ +	struct bfd_session_vxlan_info *vxlan_info = &bfd->vxlan_info; +	bfd_parms_list *parms = &sess_parms->parms; + +	vxlan_info->vnid = parms->vnid; +	vxlan_info->check_tnl_key = parms->check_tnl_key; +	vxlan_info->forwarding_if_rx = parms->forwarding_if_rx; +	vxlan_info->cpath_down = parms->cpath_down; +	vxlan_info->decay_min_rx = parms->decay_min_rx; + +	inet_aton(parms->local_dst_ip, &vxlan_info->local_dst_ip); +	inet_aton(parms->remote_dst_ip, &vxlan_info->peer_dst_ip); + +	memcpy(vxlan_info->local_dst_mac, parms->local_dst_mac, ETH_ALEN); +	memcpy(vxlan_info->peer_dst_mac, parms->remote_dst_mac, ETH_ALEN); + +	/* The interface may change for Vxlan BFD sessions, so update +	 * the local mac and ifindex +	 */ +	bfd->ifindex = sess_parms->ifindex; +	memcpy(bfd->local_mac, sess_parms->local_mac, sizeof(bfd->local_mac)); +} +#endif /* VxLAN support */ + +int bfd_xmt_cb(struct thread *t) +{ +	struct bfd_session *bs = THREAD_ARG(t); + +	ptm_bfd_xmt_TO(bs, 0); + +	return 0; +} + +int bfd_echo_xmt_cb(struct thread *t) +{ +	struct bfd_session *bs = THREAD_ARG(t); + +	ptm_bfd_echo_xmt_TO(bs); + +	return 0; +} + +/* Was ptm_bfd_detect_TO() */ +int bfd_recvtimer_cb(struct thread *t) +{ +	struct bfd_session *bs = THREAD_ARG(t); +	uint8_t old_state; + +	old_state = bs->ses_state; + +	switch (bs->ses_state) { +	case PTM_BFD_INIT: +	case PTM_BFD_UP: +		ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME); +		INFOLOG("%s Detect timeout on session 0x%x with peer %s, in state %d", +			__func__, bs->discrs.my_discr, satostr(&bs->shop.peer), +			bs->ses_state); +		bfd_recvtimer_update(bs); +		break; + +	default: +		/* Second detect time expiration, zero remote discr (section +		 * 6.5.1) +		 */ +		bs->discrs.remote_discr = 0; +		break; +	} + +	if (old_state != bs->ses_state) { +		DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]", +		     bs->discrs.my_discr, satostr(&bs->shop.peer), +		     state_list[old_state].str, state_list[bs->ses_state].str); +	} + +	return 0; +} + +/* Was ptm_bfd_echo_detect_TO() */ +int bfd_echo_recvtimer_cb(struct thread *t) +{ +	struct bfd_session *bs = THREAD_ARG(t); +	uint8_t old_state; + +	old_state = bs->ses_state; + +	switch (bs->ses_state) { +	case PTM_BFD_INIT: +	case PTM_BFD_UP: +		ptm_bfd_ses_dn(bs, BFD_DIAGDETECTTIME); +		INFOLOG("%s Detect timeout on session 0x%x with peer %s, in state %d", +			__func__, bs->discrs.my_discr, satostr(&bs->shop.peer), +			bs->ses_state); +		break; +	} + +	if (old_state != bs->ses_state) { +		DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]", +		     bs->discrs.my_discr, satostr(&bs->shop.peer), +		     state_list[old_state].str, state_list[bs->ses_state].str); +	} + +	return 0; +} + +static struct bfd_session *bfd_session_new(int sd) +{ +	struct bfd_session *bs; + +	bs = XCALLOC(MTYPE_BFDD_CONFIG, sizeof(*bs)); +	if (bs == NULL) +		return NULL; + +	QOBJ_REG(bs, bfd_session); + +	bs->up_min_tx = BFD_DEFDESIREDMINTX; +	bs->timers.required_min_rx = BFD_DEFREQUIREDMINRX; +	bs->timers.required_min_echo = BFD_DEF_REQ_MIN_ECHO; +	bs->detect_mult = BFD_DEFDETECTMULT; +	bs->mh_ttl = BFD_DEF_MHOP_TTL; + +	bs->sock = sd; +	monotime(&bs->uptime); +	bs->downtime = bs->uptime; + +	return bs; +} + +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel) +{ +	/* New label treatment: +	 * - Check if the label is taken; +	 * - Try to allocate the memory for it and register; +	 */ +	if (bs->pl == NULL) { +		if (pl_find(nlabel) != NULL) { +			/* Someone is already using it. */ +			return -1; +		} + +		if (pl_new(nlabel, bs) == NULL) +			return -1; + +		return 0; +	} + +	/* +	 * Test label change consistency: +	 * - Do nothing if it's the same label; +	 * - Check if the future label is already taken; +	 * - Change label; +	 */ +	if (strcmp(nlabel, bs->pl->pl_label) == 0) +		return -1; +	if (pl_find(nlabel) != NULL) +		return -1; + +	strlcpy(bs->pl->pl_label, nlabel, sizeof(bs->pl->pl_label)); +	return 0; +} + +static void _bfd_session_update(struct bfd_session *bs, +				struct bfd_peer_cfg *bpc) +{ +	if (bpc->bpc_echo) { +		/* Check if echo mode is already active. */ +		if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) +			goto skip_echo; + +		BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); +		ptm_bfd_echo_start(bs); + +		/* Activate/update echo receive timeout timer. */ +		bfd_echo_recvtimer_update(bs); +	} else { +		/* Check if echo mode is already disabled. */ +		if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) +			goto skip_echo; + +		BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_ECHO); +		ptm_bfd_echo_stop(bs, 0); +	} + +skip_echo: +	if (bpc->bpc_has_txinterval) +		bs->up_min_tx = bpc->bpc_txinterval * 1000; + +	if (bpc->bpc_has_recvinterval) +		bs->timers.required_min_rx = bpc->bpc_recvinterval * 1000; + +	if (bpc->bpc_has_detectmultiplier) +		bs->detect_mult = bpc->bpc_detectmultiplier; + +	if (bpc->bpc_has_echointerval) +		bs->timers.required_min_echo = bpc->bpc_echointerval * 1000; + +	if (bpc->bpc_has_label) +		bfd_session_update_label(bs, bpc->bpc_label); + +	if (bpc->bpc_shutdown) { +		/* Check if already shutdown. */ +		if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) +			return; + +		BFD_SET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + +		/* Disable all events. */ +		bfd_recvtimer_delete(bs); +		bfd_echo_recvtimer_delete(bs); +		bfd_xmttimer_delete(bs); +		bfd_echo_xmttimer_delete(bs); + +		/* Change and notify state change. */ +		bs->ses_state = PTM_BFD_ADM_DOWN; +		control_notify(bs); + +		ptm_bfd_snd(bs, 0); +	} else { +		/* Check if already working. */ +		if (!BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) +			return; + +		BFD_UNSET_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN); + +		/* Change and notify state change. */ +		bs->ses_state = PTM_BFD_DOWN; +		control_notify(bs); + +		/* Enable all timers. */ +		bfd_recvtimer_update(bs); +		bfd_xmttimer_update(bs, bs->xmt_TO); +		if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) { +			bfd_echo_recvtimer_update(bs); +			bfd_echo_xmttimer_update(bs, bs->echo_xmt_TO); +		} +	} +} + +static int bfd_session_update(struct bfd_session *bs, struct bfd_peer_cfg *bpc) +{ +	/* User didn't want to update, return failure. */ +	if (bpc->bpc_createonly) +		return -1; + +	_bfd_session_update(bs, bpc); + +	/* TODO add VxLAN support. */ + +	control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bs); + +	return 0; +} + +static void bfd_session_free(struct bfd_session *bs) +{ +	if (bs->sock != -1) +		close(bs->sock); + +	bfd_recvtimer_delete(bs); +	bfd_echo_recvtimer_delete(bs); +	bfd_xmttimer_delete(bs); +	bfd_echo_xmttimer_delete(bs); + +	bfd_id_delete(bs->discrs.my_discr); +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) +		bfd_mhop_delete(bs->mhop); +	else +		bfd_shop_delete(bs->shop); + +	pl_free(bs->pl); + +	QOBJ_UNREG(bs); +	XFREE(MTYPE_BFDD_CONFIG, bs); +} + +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc) +{ +	struct bfd_session *bfd, *l_bfd; +	int psock; + +	/* check to see if this needs a new session */ +	l_bfd = bs_peer_find(bpc); +	if (l_bfd) { +		/* Requesting a duplicated peer means update configuration. */ +		if (bfd_session_update(l_bfd, bpc) == 0) +			return l_bfd; +		else +			return NULL; +	} + +	/* +	 * Get socket for transmitting control packets.  Note that if we +	 * could use the destination port (3784) for the source +	 * port we wouldn't need a socket per session. +	 */ +	if (bpc->bpc_ipv4) { +		psock = bp_peer_socket(bpc); +		if (psock == -1) { +			ERRLOG("Can't get socket for new session: %s", +			       strerror(errno)); +			return NULL; +		} +	} else { +		psock = bp_peer_socketv6(bpc); +		if (psock == -1) { +			ERRLOG("Can't get IPv6 socket for new session: %s", +			       strerror(errno)); +			return NULL; +		} +	} + +	/* Get memory */ +	bfd = bfd_session_new(psock); +	if (bfd == NULL) { +		ERRLOG("Can't malloc memory for new session: %s", +		       strerror(errno)); +		return NULL; +	} + +	if (bpc->bpc_has_localif && !bpc->bpc_mhop) { +		bfd->ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); +		ptm_bfd_fetch_local_mac(bpc->bpc_localif, bfd->local_mac); +	} + +	if (bpc->bpc_has_vxlan) +		BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN); + +	if (bpc->bpc_ipv4 == false) +		BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_IPV6); + +	/* Initialize the session */ +	bfd->ses_state = PTM_BFD_DOWN; +	bfd->discrs.my_discr = ptm_bfd_gen_ID(); +	bfd->discrs.remote_discr = 0; +	bfd->local_ip = bpc->bpc_local; +	bfd->local_address = bpc->bpc_local; +	bfd->timers.desired_min_tx = bfd->up_min_tx; +	bfd->detect_TO = (bfd->detect_mult * BFD_DEF_SLOWTX); + +	/* Use detect_TO first for slow detection, then use recvtimer_update. */ +	bfd_recvtimer_update(bfd); + +	bfd_id_insert(bfd); + +	if (bpc->bpc_mhop) { +		BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_MH); +		bfd->mhop.peer = bpc->bpc_peer; +		bfd->mhop.local = bpc->bpc_local; +		if (bpc->bpc_has_vrfname) +			strlcpy(bfd->mhop.vrf_name, bpc->bpc_vrfname, +				sizeof(bfd->mhop.vrf_name)); + +		bfd_mhop_insert(bfd); +	} else { +		bfd->shop.peer = bpc->bpc_peer; +		if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) +			strlcpy(bfd->shop.port_name, bpc->bpc_localif, +				sizeof(bfd->shop.port_name)); + +		bfd_shop_insert(bfd); +	} + +	if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) { +		static uint8_t bfd_def_vxlan_dmac[] = {0x00, 0x23, 0x20, +						       0x00, 0x00, 0x01}; +		memcpy(bfd->peer_mac, bfd_def_vxlan_dmac, +		       sizeof(bfd_def_vxlan_dmac)); +	} +#if 0 /* TODO */ +	else if (event->rmac) { +		if (sscanf(event->rmac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", +		    &bfd->peer_mac[0], &bfd->peer_mac[1], &bfd->peer_mac[2], +		    &bfd->peer_mac[3], &bfd->peer_mac[4], &bfd->peer_mac[5]) +		    != 6) +			DLOG("%s: Assigning remote mac = %s", __func__, +			     event->rmac); +	} +#endif + +	/* +	 * XXX: session update triggers echo start, so we must have our +	 * discriminator ID set first. +	 */ +	_bfd_session_update(bfd, bpc); + +	/* Start transmitting with slow interval until peer responds */ +	bfd->xmt_TO = BFD_DEF_SLOWTX; + +	ptm_bfd_xmt_TO(bfd, 0); + +	if (bpc->bpc_mhop) { +		INFOLOG("Created new session 0x%x with vrf %s peer %s local %s", +			bfd->discrs.my_discr, +			(bpc->bpc_has_vrfname) ? bfd->mhop.vrf_name : "N/A", +			satostr(&bfd->mhop.peer), satostr(&bfd->mhop.local)); +	} else { +		INFOLOG("Created new session 0x%x with peer %s port %s", +			bfd->discrs.my_discr, satostr(&bfd->shop.peer), +			bfd->shop.port_name[0] ? bfd->shop.port_name : "N/A"); +	} + +	control_notify_config(BCM_NOTIFY_CONFIG_ADD, bfd); + +	return bfd; +} + +int ptm_bfd_ses_del(struct bfd_peer_cfg *bpc) +{ +	struct bfd_session *bs; + +	/* Find session and call free(). */ +	bs = bs_peer_find(bpc); +	if (bs == NULL) +		return -1; + +	/* This pointer is being referenced, don't let it be deleted. */ +	if (bs->refcount > 0) { +		zlog_debug("%s: trying to free in-use session: %" PRIu64 +			   " references", +			   __func__, bs->refcount); +		return -1; +	} + +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { +		INFOLOG("Deleting session 0x%x with vrf %s peer %s local %s", +			bs->discrs.my_discr, +			bpc->bpc_has_vrfname ? bpc->bpc_vrfname : "N/A", +			satostr(&bs->mhop.peer), satostr(&bs->mhop.local)); +	} else { +		INFOLOG("Deleting session 0x%x with peer %s port %s", +			bs->discrs.my_discr, satostr(&bs->shop.peer), +			bs->shop.port_name); +	} + +	control_notify_config(BCM_NOTIFY_CONFIG_DELETE, bs); + +	bfd_session_free(bs); + +	return 0; +} + + +/* + * Helper functions. + */ +static const char *get_diag_str(int diag) +{ +	for (int i = 0; diag_list[i].str; i++) { +		if (diag_list[i].type == diag) +			return diag_list[i].str; +	} +	return "N/A"; +} + +const char *satostr(struct sockaddr_any *sa) +{ +#define INETSTR_BUFCOUNT 8 +	static char buf[INETSTR_BUFCOUNT][INET6_ADDRSTRLEN]; +	static int bufidx; +	struct sockaddr_in *sin = &sa->sa_sin; +	struct sockaddr_in6 *sin6 = &sa->sa_sin6; + +	bufidx += (bufidx + 1) % INETSTR_BUFCOUNT; +	buf[bufidx][0] = 0; + +	switch (sin->sin_family) { +	case AF_INET: +		inet_ntop(AF_INET, &sin->sin_addr, buf[bufidx], +			  sizeof(buf[bufidx])); +		break; +	case AF_INET6: +		inet_ntop(AF_INET6, &sin6->sin6_addr, buf[bufidx], +			  sizeof(buf[bufidx])); +		break; + +	default: +		strlcpy(buf[bufidx], "unknown", sizeof(buf[bufidx])); +		break; +	} + +	return buf[bufidx]; +} + +const char *diag2str(uint8_t diag) +{ +	switch (diag) { +	case 0: +		return "ok"; +	case 1: +		return "control detection time expired"; +	case 2: +		return "echo function failed"; +	case 3: +		return "neighbor signaled session down"; +	case 4: +		return "forwarding plane reset"; +	case 5: +		return "path down"; +	case 6: +		return "concatenated path down"; +	case 7: +		return "administratively down"; +	case 8: +		return "reverse concatenated path down"; +	default: +		return "unknown"; +	} +} + +int strtosa(const char *addr, struct sockaddr_any *sa) +{ +	memset(sa, 0, sizeof(*sa)); + +	if (inet_pton(AF_INET, addr, &sa->sa_sin.sin_addr) == 1) { +		sa->sa_sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +		sa->sa_sin.sin_len = sizeof(sa->sa_sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +		return 0; +	} + +	if (inet_pton(AF_INET6, addr, &sa->sa_sin6.sin6_addr) == 1) { +		sa->sa_sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +		sa->sa_sin6.sin6_len = sizeof(sa->sa_sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +		return 0; +	} + +	return -1; +} + +void integer2timestr(uint64_t time, char *buf, size_t buflen) +{ +	unsigned int year, month, day, hour, minute, second; +	int rv; + +#define MINUTES (60) +#define HOURS (24 * MINUTES) +#define DAYS (30 * HOURS) +#define MONTHS (12 * DAYS) +#define YEARS (MONTHS) +	if (time >= YEARS) { +		year = time / YEARS; +		time -= year * YEARS; + +		rv = snprintf(buf, buflen, "%u year(s), ", year); +		buf += rv; +		buflen -= rv; +	} +	if (time >= MONTHS) { +		month = time / MONTHS; +		time -= month * MONTHS; + +		rv = snprintf(buf, buflen, "%u month(s), ", month); +		buf += rv; +		buflen -= rv; +	} +	if (time >= DAYS) { +		day = time / DAYS; +		time -= day * DAYS; + +		rv = snprintf(buf, buflen, "%u day(s), ", day); +		buf += rv; +		buflen -= rv; +	} +	if (time >= HOURS) { +		hour = time / HOURS; +		time -= hour * HOURS; + +		rv = snprintf(buf, buflen, "%u hour(s), ", hour); +		buf += rv; +		buflen -= rv; +	} +	if (time >= MINUTES) { +		minute = time / MINUTES; +		time -= minute * MINUTES; + +		rv = snprintf(buf, buflen, "%u minute(s), ", minute); +		buf += rv; +		buflen -= rv; +	} +	second = time % MINUTES; +	snprintf(buf, buflen, "%u second(s)", second); +} + + +/* + * BFD hash data structures to find sessions. + */ +static struct hash *bfd_id_hash; +static struct hash *bfd_shop_hash; +static struct hash *bfd_mhop_hash; +static struct hash *bfd_vrf_hash; +static struct hash *bfd_iface_hash; + +static unsigned int bfd_id_hash_do(void *p); +static int bfd_id_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_shop_hash_do(void *p); +static int bfd_shop_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_mhop_hash_do(void *p); +static int bfd_mhop_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_vrf_hash_do(void *p); +static int bfd_vrf_hash_cmp(const void *n1, const void *n2); +static unsigned int bfd_iface_hash_do(void *p); +static int bfd_iface_hash_cmp(const void *n1, const void *n2); + +static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop); +static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop); +static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop); +static int _iface_key(struct bfd_iface *iface, const char *ifname); + +static void _bfd_free(struct hash_backet *hb, +		      void *arg __attribute__((__unused__))); +static void _vrf_free(void *arg); +static void _iface_free(void *arg); + +/* BFD hash for our discriminator. */ +static unsigned int bfd_id_hash_do(void *p) +{ +	struct bfd_session *bs = p; + +	return jhash_1word(bs->discrs.my_discr, 0); +} + +static int bfd_id_hash_cmp(const void *n1, const void *n2) +{ +	const struct bfd_session *bs1 = n1, *bs2 = n2; + +	return bs1->discrs.my_discr == bs2->discrs.my_discr; +} + +/* BFD hash for single hop. */ +static unsigned int bfd_shop_hash_do(void *p) +{ +	struct bfd_session *bs = p; + +	return jhash(&bs->shop, sizeof(bs->shop), 0); +} + +static int bfd_shop_hash_cmp(const void *n1, const void *n2) +{ +	const struct bfd_session *bs1 = n1, *bs2 = n2; + +	return memcmp(&bs1->shop, &bs2->shop, sizeof(bs1->shop)) == 0; +} + +/* BFD hash for multi hop. */ +static unsigned int bfd_mhop_hash_do(void *p) +{ +	struct bfd_session *bs = p; + +	return jhash(&bs->mhop, sizeof(bs->mhop), 0); +} + +static int bfd_mhop_hash_cmp(const void *n1, const void *n2) +{ +	const struct bfd_session *bs1 = n1, *bs2 = n2; + +	return memcmp(&bs1->mhop, &bs2->mhop, sizeof(bs1->mhop)) == 0; +} + +/* BFD hash for VRFs. */ +static unsigned int bfd_vrf_hash_do(void *p) +{ +	struct bfd_vrf *vrf = p; + +	return jhash_1word(vrf->vrf_id, 0); +} + +static int bfd_vrf_hash_cmp(const void *n1, const void *n2) +{ +	const struct bfd_vrf *v1 = n1, *v2 = n2; + +	return v1->vrf_id == v2->vrf_id; +} + +/* BFD hash for interfaces. */ +static unsigned int bfd_iface_hash_do(void *p) +{ +	struct bfd_iface *iface = p; + +	return string_hash_make(iface->ifname); +} + +static int bfd_iface_hash_cmp(const void *n1, const void *n2) +{ +	const struct bfd_iface *i1 = n1, *i2 = n2; + +	return strcmp(i1->ifname, i2->ifname) == 0; +} + +/* Helper functions */ +static void _shop_key(struct bfd_session *bs, const struct bfd_shop_key *shop) +{ +	bs->shop = *shop; + +	/* Remove unused fields. */ +	switch (bs->shop.peer.sa_sin.sin_family) { +	case AF_INET: +		bs->shop.peer.sa_sin.sin_port = 0; +		break; +	case AF_INET6: +		bs->shop.peer.sa_sin6.sin6_port = 0; +		break; +	} +} + +static void _shop_key2(struct bfd_session *bs, const struct bfd_shop_key *shop) +{ +	_shop_key(bs, shop); +	memset(bs->shop.port_name, 0, sizeof(bs->shop.port_name)); +} + +static void _mhop_key(struct bfd_session *bs, const struct bfd_mhop_key *mhop) +{ +	bs->mhop = *mhop; + +	/* Remove unused fields. */ +	switch (bs->mhop.peer.sa_sin.sin_family) { +	case AF_INET: +		bs->mhop.peer.sa_sin.sin_port = 0; +		bs->mhop.local.sa_sin.sin_port = 0; +		break; +	case AF_INET6: +		bs->mhop.peer.sa_sin6.sin6_port = 0; +		bs->mhop.local.sa_sin6.sin6_port = 0; +		break; +	} +} + +static int _iface_key(struct bfd_iface *iface, const char *ifname) +{ +	size_t slen = sizeof(iface->ifname); + +	memset(iface->ifname, 0, slen); +	if (strlcpy(iface->ifname, ifname, slen) >= slen) +		return -1; + +	return 0; +} + + +/* + * Hash public interface / exported functions. + */ + +/* Lookup functions. */ +struct bfd_session *bfd_id_lookup(uint32_t id) +{ +	struct bfd_session bs; + +	bs.discrs.my_discr = id; + +	return hash_lookup(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop) +{ +	struct bfd_session bs, *bsp; + +	_shop_key(&bs, &shop); + +	bsp = hash_lookup(bfd_shop_hash, &bs); +	if (bsp == NULL && bs.shop.port_name[0] != 0) { +		/* +		 * Since the local interface spec is optional, try +		 * searching the key without it as well. +		 */ +		_shop_key2(&bs, &shop); +		bsp = hash_lookup(bfd_shop_hash, &bs); +	} + +	return bsp; +} + +struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop) +{ +	struct bfd_session bs; + +	_mhop_key(&bs, &mhop); + +	return hash_lookup(bfd_shop_hash, &bs); +} + +struct bfd_vrf *bfd_vrf_lookup(int vrf_id) +{ +	struct bfd_vrf vrf; + +	vrf.vrf_id = vrf_id; + +	return hash_lookup(bfd_vrf_hash, &vrf); +} + +struct bfd_iface *bfd_iface_lookup(const char *ifname) +{ +	struct bfd_iface iface; + +	if (_iface_key(&iface, ifname) != 0) +		return NULL; + +	return hash_lookup(bfd_iface_hash, &iface); +} + +/* + * Delete functions. + * + * Delete functions searches and remove the item from the hash and + * returns a pointer to the removed item data. If the item was not found + * then it returns NULL. + * + * The data stored inside the hash is not free()ed, so you must do it + * manually after getting the pointer back. + */ +struct bfd_session *bfd_id_delete(uint32_t id) +{ +	struct bfd_session bs; + +	bs.discrs.my_discr = id; + +	return hash_release(bfd_id_hash, &bs); +} + +struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop) +{ +	struct bfd_session bs, *bsp; + +	_shop_key(&bs, &shop); +	bsp = hash_release(bfd_shop_hash, &bs); +	if (bsp == NULL && shop.port_name[0] != 0) { +		/* +		 * Since the local interface spec is optional, try +		 * searching the key without it as well. +		 */ +		_shop_key2(&bs, &shop); +		bsp = hash_release(bfd_shop_hash, &bs); +	} + +	return bsp; +} + +struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop) +{ +	struct bfd_session bs; + +	_mhop_key(&bs, &mhop); + +	return hash_release(bfd_mhop_hash, &bs); +} + +struct bfd_vrf *bfd_vrf_delete(int vrf_id) +{ +	struct bfd_vrf vrf; + +	vrf.vrf_id = vrf_id; + +	return hash_release(bfd_vrf_hash, &vrf); +} + +struct bfd_iface *bfd_iface_delete(const char *ifname) +{ +	struct bfd_iface iface; + +	if (_iface_key(&iface, ifname) != 0) +		return NULL; + +	return hash_release(bfd_iface_hash, &iface); +} + +/* Iteration functions. */ +void bfd_id_iterate(hash_iter_func hif, void *arg) +{ +	hash_iterate(bfd_id_hash, hif, arg); +} + +void bfd_shop_iterate(hash_iter_func hif, void *arg) +{ +	hash_iterate(bfd_shop_hash, hif, arg); +} + +void bfd_mhop_iterate(hash_iter_func hif, void *arg) +{ +	hash_iterate(bfd_mhop_hash, hif, arg); +} + +void bfd_vrf_iterate(hash_iter_func hif, void *arg) +{ +	hash_iterate(bfd_vrf_hash, hif, arg); +} + +void bfd_iface_iterate(hash_iter_func hif, void *arg) +{ +	hash_iterate(bfd_iface_hash, hif, arg); +} + +/* + * Insert functions. + * + * Inserts session into hash and returns `true` on success, otherwise + * `false`. + */ +bool bfd_id_insert(struct bfd_session *bs) +{ +	return (hash_get(bfd_id_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_shop_insert(struct bfd_session *bs) +{ +	return (hash_get(bfd_shop_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_mhop_insert(struct bfd_session *bs) +{ +	return (hash_get(bfd_mhop_hash, bs, hash_alloc_intern) == bs); +} + +bool bfd_vrf_insert(struct bfd_vrf *vrf) +{ +	return (hash_get(bfd_vrf_hash, vrf, hash_alloc_intern) == vrf); +} + +bool bfd_iface_insert(struct bfd_iface *iface) +{ +	return (hash_get(bfd_iface_hash, iface, hash_alloc_intern) == iface); +} + +void bfd_initialize(void) +{ +	bfd_id_hash = hash_create(bfd_id_hash_do, bfd_id_hash_cmp, +				  "BFD discriminator hash"); +	bfd_shop_hash = hash_create(bfd_shop_hash_do, bfd_shop_hash_cmp, +				    "BFD single hop hash"); +	bfd_mhop_hash = hash_create(bfd_mhop_hash_do, bfd_mhop_hash_cmp, +				    "BFD multihop hop hash"); +	bfd_vrf_hash = +		hash_create(bfd_vrf_hash_do, bfd_vrf_hash_cmp, "BFD VRF hash"); +	bfd_iface_hash = hash_create(bfd_iface_hash_do, bfd_iface_hash_cmp, +				     "BFD interface hash"); +} + +static void _bfd_free(struct hash_backet *hb, +		      void *arg __attribute__((__unused__))) +{ +	struct bfd_session *bs = hb->data; + +	bfd_session_free(bs); +} + +static void _vrf_free(void *arg) +{ +	struct bfd_vrf *vrf = arg; + +	XFREE(MTYPE_BFDD_CONFIG, vrf); +} + +static void _iface_free(void *arg) +{ +	struct bfd_iface *iface = arg; + +	XFREE(MTYPE_BFDD_CONFIG, iface); +} + +void bfd_shutdown(void) +{ +	/* +	 * Close and free all BFD sessions. +	 * +	 * _bfd_free() will call bfd_session_free() which will take care +	 * of removing the session from all hashes, so we just run an +	 * assert() here to make sure it really happened. +	 */ +	bfd_id_iterate(_bfd_free, NULL); +	assert(bfd_shop_hash->count == 0); +	assert(bfd_mhop_hash->count == 0); + +	/* Clean the VRF and interface hashes. */ +	hash_clean(bfd_vrf_hash, _vrf_free); +	hash_clean(bfd_iface_hash, _iface_free); + +	/* Now free the hashes themselves. */ +	hash_free(bfd_id_hash); +	hash_free(bfd_shop_hash); +	hash_free(bfd_mhop_hash); +	hash_free(bfd_vrf_hash); +	hash_free(bfd_iface_hash); +} diff --git a/bfdd/bfd.h b/bfdd/bfd.h new file mode 100644 index 0000000000..e4907ecceb --- /dev/null +++ b/bfdd/bfd.h @@ -0,0 +1,617 @@ +/********************************************************************* + * Copyright 2014,2015,2016,2017 Cumulus Networks, Inc.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd.h: implements the BFD protocol. + */ + +#ifndef _BFD_H_ +#define _BFD_H_ + +#include <netinet/in.h> + +#include <stdbool.h> +#include <stdarg.h> +#include <stdint.h> + +#include "lib/hash.h" +#include "lib/libfrr.h" +#include "lib/qobj.h" +#include "lib/queue.h" + +#include "bfdctl.h" + +#define ETHERNET_ADDRESS_LENGTH 6 + +#ifdef BFD_DEBUG +#define BFDD_JSON_CONV_OPTIONS (JSON_C_TO_STRING_PRETTY) +#else +#define BFDD_JSON_CONV_OPTIONS (0) +#endif + +DECLARE_MGROUP(BFDD); +DECLARE_MTYPE(BFDD_TMP); +DECLARE_MTYPE(BFDD_CONFIG); +DECLARE_MTYPE(BFDD_LABEL); +DECLARE_MTYPE(BFDD_CONTROL); +DECLARE_MTYPE(BFDD_NOTIFICATION); + +struct bfd_timers { +	uint32_t desired_min_tx; +	uint32_t required_min_rx; +	uint32_t required_min_echo; +}; + +struct bfd_discrs { +	uint32_t my_discr; +	uint32_t remote_discr; +}; + +/* + * Format of control packet.  From section 4) + */ +struct bfd_pkt { +	union { +		uint32_t byteFields; +		struct { +			uint8_t diag; +			uint8_t flags; +			uint8_t detect_mult; +			uint8_t len; +		}; +	}; +	struct bfd_discrs discrs; +	struct bfd_timers timers; +}; + +/* + * Format of Echo packet. + */ +struct bfd_echo_pkt { +	union { +		uint32_t byteFields; +		struct { +			uint8_t ver; +			uint8_t len; +			uint16_t reserved; +		}; +	}; +	uint32_t my_discr; +	uint8_t pad[16]; +}; + + +/* Macros for manipulating control packets */ +#define BFD_VERMASK 0x03 +#define BFD_DIAGMASK 0x1F +#define BFD_GETVER(diag) ((diag >> 5) & BFD_VERMASK) +#define BFD_SETVER(diag, val) ((diag) |= (val & BFD_VERMASK) << 5) +#define BFD_VERSION 1 +#define BFD_PBIT 0x20 +#define BFD_FBIT 0x10 +#define BFD_CBIT 0x08 +#define BFD_ABIT 0x04 +#define BFD_DEMANDBIT 0x02 +#define BFD_DIAGNEIGHDOWN 3 +#define BFD_DIAGDETECTTIME 1 +#define BFD_DIAGADMINDOWN 7 +#define BFD_SETDEMANDBIT(flags, val)                                           \ +	{                                                                      \ +		if ((val))                                                     \ +			flags |= BFD_DEMANDBIT;                                \ +	} +#define BFD_SETPBIT(flags, val)                                                \ +	{                                                                      \ +		if ((val))                                                     \ +			flags |= BFD_PBIT;                                     \ +	} +#define BFD_GETPBIT(flags) (flags & BFD_PBIT) +#define BFD_SETFBIT(flags, val)                                                \ +	{                                                                      \ +		if ((val))                                                     \ +			flags |= BFD_FBIT;                                     \ +	} +#define BFD_GETFBIT(flags) (flags & BFD_FBIT) +#define BFD_SETSTATE(flags, val)                                               \ +	{                                                                      \ +		if ((val))                                                     \ +			flags |= (val & 0x3) << 6;                             \ +	} +#define BFD_GETSTATE(flags) ((flags >> 6) & 0x3) +#define BFD_ECHO_VERSION 1 +#define BFD_ECHO_PKT_LEN sizeof(struct bfd_echo_pkt) +#define BFD_CTRL_PKT_LEN sizeof(struct bfd_pkt) +#define IP_HDR_LEN 20 +#define UDP_HDR_LEN 8 +#define ETH_HDR_LEN 14 +#define VXLAN_HDR_LEN 8 +#define HEADERS_MIN_LEN (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN) +#define BFD_ECHO_PKT_TOT_LEN                                                   \ +	((int)(ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN + BFD_ECHO_PKT_LEN)) +#define BFD_VXLAN_PKT_TOT_LEN                                                  \ +	((int)(VXLAN_HDR_LEN + ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN          \ +	       + BFD_CTRL_PKT_LEN)) +#define BFD_RX_BUF_LEN 160 + +/* BFD session flags */ +enum bfd_session_flags { +	BFD_SESS_FLAG_NONE = 0, +	BFD_SESS_FLAG_ECHO = 1 << 0,	/* BFD Echo functionality */ +	BFD_SESS_FLAG_ECHO_ACTIVE = 1 << 1, /* BFD Echo Packets are being sent +					     * actively +					     */ +	BFD_SESS_FLAG_MH = 1 << 2,	  /* BFD Multi-hop session */ +	BFD_SESS_FLAG_VXLAN = 1 << 3,       /* BFD Multi-hop session which is +					     * used to monitor vxlan tunnel +					     */ +	BFD_SESS_FLAG_IPV6 = 1 << 4,	/* BFD IPv6 session */ +	BFD_SESS_FLAG_SEND_EVT_ACTIVE = 1 << 5, /* send event timer active */ +	BFD_SESS_FLAG_SEND_EVT_IGNORE = 1 << 6, /* ignore send event when timer +						 * expires +						 */ +	BFD_SESS_FLAG_SHUTDOWN = 1 << 7,	/* disable BGP peer function */ +}; + +#define BFD_SET_FLAG(field, flag) (field |= flag) +#define BFD_UNSET_FLAG(field, flag) (field &= ~flag) +#define BFD_CHECK_FLAG(field, flag) (field & flag) + +/* BFD session hash keys */ +struct bfd_shop_key { +	struct sockaddr_any peer; +	char port_name[MAXNAMELEN + 1]; +}; + +struct bfd_mhop_key { +	struct sockaddr_any peer; +	struct sockaddr_any local; +	char vrf_name[MAXNAMELEN + 1]; +}; + +struct bfd_session_stats { +	uint64_t rx_ctrl_pkt; +	uint64_t tx_ctrl_pkt; +	uint64_t rx_echo_pkt; +	uint64_t tx_echo_pkt; +}; + +struct bfd_session_vxlan_info { +	uint32_t vnid; +	uint32_t decay_min_rx; +	uint8_t forwarding_if_rx; +	uint8_t cpath_down; +	uint8_t check_tnl_key; +	uint8_t local_dst_mac[ETHERNET_ADDRESS_LENGTH]; +	uint8_t peer_dst_mac[ETHERNET_ADDRESS_LENGTH]; +	struct in_addr local_dst_ip; +	struct in_addr peer_dst_ip; +}; + +/* bfd_session shortcut label forwarding. */ +struct peer_label; + +/* + * Session state information + */ +struct bfd_session { + +	/* protocol state per RFC 5880*/ +	uint8_t ses_state; +	struct bfd_discrs discrs; +	uint8_t local_diag; +	uint8_t demand_mode; +	uint8_t detect_mult; +	uint8_t remote_detect_mult; +	uint8_t mh_ttl; + +	/* Timers */ +	struct bfd_timers timers; +	struct bfd_timers new_timers; +	uint32_t up_min_tx; +	uint64_t detect_TO; +	struct thread *echo_recvtimer_ev; +	struct thread *recvtimer_ev; +	uint64_t xmt_TO; +	uint64_t echo_xmt_TO; +	struct thread *xmttimer_ev; +	struct thread *echo_xmttimer_ev; +	uint64_t echo_detect_TO; + +	/* software object state */ +	uint8_t polling; + +	/* This and the localDiscr are the keys to state info */ +	struct peer_label *pl; +	union { +		struct bfd_shop_key shop; +		struct bfd_mhop_key mhop; +	}; +	int sock; + +	struct sockaddr_any local_address; +	struct sockaddr_any local_ip; +	int ifindex; +	uint8_t local_mac[ETHERNET_ADDRESS_LENGTH]; +	uint8_t peer_mac[ETHERNET_ADDRESS_LENGTH]; +	uint16_t ip_id; + +	/* BFD session flags */ +	enum bfd_session_flags flags; + +	uint8_t echo_pkt[BFD_ECHO_PKT_TOT_LEN]; /* Save the Echo Packet +						 * which will be transmitted +						 */ +	struct bfd_session_stats stats; +	struct bfd_session_vxlan_info vxlan_info; + +	struct timeval uptime;   /* last up time */ +	struct timeval downtime; /* last down time */ + +	/* Remote peer data (for debugging mostly) */ +	uint8_t remote_diag; +	struct bfd_timers remote_timers; + +	uint64_t refcount; /* number of pointers referencing this. */ + +	/* VTY context data. */ +	QOBJ_FIELDS; +}; +DECLARE_QOBJ_TYPE(bfd_session); + +struct peer_label { +	TAILQ_ENTRY(peer_label) pl_entry; + +	struct bfd_session *pl_bs; +	char pl_label[MAXNAMELEN]; +}; +TAILQ_HEAD(pllist, peer_label); + +struct bfd_diag_str_list { +	const char *str; +	int type; +}; + +struct bfd_state_str_list { +	const char *str; +	int type; +}; + +struct bfd_vrf { +	int vrf_id; +	char name[MAXNAMELEN + 1]; +} bfd_vrf; + +struct bfd_iface { +	int vrf_id; +	char ifname[MAXNAMELEN + 1]; +} bfd_iface; + + +/* States defined per 4.1 */ +#define PTM_BFD_ADM_DOWN 0 +#define PTM_BFD_DOWN 1 +#define PTM_BFD_INIT 2 +#define PTM_BFD_UP 3 + + +/* Various constants */ +/* Retrieved from ptm_timer.h from Cumulus PTM sources. */ +#define MSEC_PER_SEC 1000L +#define NSEC_PER_MSEC 1000000L + +#define BFD_DEF_DEMAND 0 +#define BFD_DEFDETECTMULT 3 +#define BFD_DEFDESIREDMINTX (300 * MSEC_PER_SEC) +#define BFD_DEFREQUIREDMINRX (300 * MSEC_PER_SEC) +#define BFD_DEF_REQ_MIN_ECHO (50 * MSEC_PER_SEC) +#define BFD_DEF_SLOWTX (2000 * MSEC_PER_SEC) +#define BFD_DEF_MHOP_TTL 5 +#define BFD_PKT_LEN 24 /* Length of control packet */ +#define BFD_TTL_VAL 255 +#define BFD_RCV_TTL_VAL 1 +#define BFD_TOS_VAL 0xC0 +#define BFD_PKT_INFO_VAL 1 +#define BFD_IPV6_PKT_INFO_VAL 1 +#define BFD_IPV6_ONLY_VAL 1 +#define BFD_SRCPORTINIT 49142 +#define BFD_SRCPORTMAX 65536 +#define BFD_DEFDESTPORT 3784 +#define BFD_DEF_ECHO_PORT 3785 +#define BFD_DEF_MHOP_DEST_PORT 4784 +#define BFD_CMD_STRING_LEN (MAXNAMELEN + 50) +#define BFD_BUFFER_LEN (BFD_CMD_STRING_LEN + MAXNAMELEN + 1) + +/* + * control.c + * + * Daemon control code to speak with local consumers. + */ + +/* See 'bfdctrl.h' for client protocol definitions. */ + +struct bfd_control_buffer { +	size_t bcb_left; +	size_t bcb_pos; +	union { +		struct bfd_control_msg *bcb_bcm; +		uint8_t *bcb_buf; +	}; +}; + +struct bfd_control_queue { +	TAILQ_ENTRY(bfd_control_queue) bcq_entry; + +	struct bfd_control_buffer bcq_bcb; +}; +TAILQ_HEAD(bcqueue, bfd_control_queue); + +struct bfd_notify_peer { +	TAILQ_ENTRY(bfd_notify_peer) bnp_entry; + +	struct bfd_session *bnp_bs; +}; +TAILQ_HEAD(bnplist, bfd_notify_peer); + +struct bfd_control_socket { +	TAILQ_ENTRY(bfd_control_socket) bcs_entry; + +	int bcs_sd; +	struct thread *bcs_ev; +	struct thread *bcs_outev; +	struct bcqueue bcs_bcqueue; + +	/* Notification data */ +	uint64_t bcs_notify; +	struct bnplist bcs_bnplist; + +	enum bc_msg_version bcs_version; +	enum bc_msg_type bcs_type; + +	/* Message buffering */ +	struct bfd_control_buffer bcs_bin; +	struct bfd_control_buffer *bcs_bout; +}; +TAILQ_HEAD(bcslist, bfd_control_socket); + +int control_init(const char *path); +void control_shutdown(void); +int control_notify(struct bfd_session *bs); +int control_notify_config(const char *op, struct bfd_session *bs); +int control_accept(struct thread *t); + + +/* + * bfdd.c + * + * Daemon specific code. + */ +struct bfd_global { +	int bg_shop; +	int bg_mhop; +	int bg_shop6; +	int bg_mhop6; +	int bg_echo; +	int bg_vxlan; +	struct thread *bg_ev[6]; + +	int bg_csock; +	struct thread *bg_csockev; +	struct bcslist bg_bcslist; + +	struct pllist bg_pllist; +}; +extern struct bfd_global bglobal; +extern struct bfd_diag_str_list diag_list[]; +extern struct bfd_state_str_list state_list[]; + +void socket_close(int *s); + + +/* + * config.c + * + * Contains the code related with loading/reloading configuration. + */ +int parse_config(const char *fname); +int config_request_add(const char *jsonstr); +int config_request_del(const char *jsonstr); +char *config_response(const char *status, const char *error); +char *config_notify(struct bfd_session *bs); +char *config_notify_config(const char *op, struct bfd_session *bs); + +typedef int (*bpc_handle)(struct bfd_peer_cfg *, void *arg); +int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, +			  bpc_handle bh); + +struct peer_label *pl_new(const char *label, struct bfd_session *bs); +struct peer_label *pl_find(const char *label); +void pl_free(struct peer_label *pl); + + +/* + * log.c + * + * Contains code that does the logging procedures. Might implement multiple + * backends (e.g. zebra log, syslog or other logging lib). + */ +enum blog_level { +	/* level vs syslog equivalent */ +	BLOG_DEBUG = 0,   /* LOG_DEBUG */ +	BLOG_INFO = 1,    /* LOG_INFO */ +	BLOG_WARNING = 2, /* LOG_WARNING */ +	BLOG_ERROR = 3,   /* LOG_ERR */ +	BLOG_FATAL = 4,   /* LOG_CRIT */ +}; + +void log_init(int foreground, enum blog_level level, +	      struct frr_daemon_info *fdi); +void log_info(const char *fmt, ...); +void log_debug(const char *fmt, ...); +void log_warning(const char *fmt, ...); +void log_error(const char *fmt, ...); +void log_fatal(const char *fmt, ...); + +/* Compatibility code: code to avoid touching ported code debug messages. */ +#define DLOG(fmt, args...) log_debug(fmt, ##args) +#define INFOLOG(fmt, args...) log_info(fmt, ##args) +#define ERRLOG(fmt, args...) log_error(fmt, ##args) +#define CRITLOG(fmt, args...) log_fatal(fmt, ##args) + +/* + * bfd_packet.c + * + * Contains the code related with receiving/seding, packing/unpacking BFD data. + */ +int bp_set_ttlv6(int sd); +int bp_set_ttl(int sd); +int bp_set_tosv6(int sd); +int bp_set_tos(int sd); +int bp_bind_dev(int sd, const char *dev); + +int bp_udp_shop(void); +int bp_udp_mhop(void); +int bp_udp6_shop(void); +int bp_udp6_mhop(void); +int bp_peer_socket(struct bfd_peer_cfg *bpc); +int bp_peer_socketv6(struct bfd_peer_cfg *bpc); + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit); +void ptm_bfd_echo_snd(struct bfd_session *bfd); + +int bfd_recv_cb(struct thread *t); + +uint16_t checksum(uint16_t *buf, int len); + + +/* + * event.c + * + * Contains the code related with event loop. + */ +typedef void (*bfd_ev_cb)(struct thread *t); + +void bfd_recvtimer_update(struct bfd_session *bs); +void bfd_echo_recvtimer_update(struct bfd_session *bs); +void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter); +void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter); + +void bfd_xmttimer_delete(struct bfd_session *bs); +void bfd_echo_xmttimer_delete(struct bfd_session *bs); +void bfd_recvtimer_delete(struct bfd_session *bs); +void bfd_echo_recvtimer_delete(struct bfd_session *bs); + +void bfd_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_echo_recvtimer_assign(struct bfd_session *bs, bfd_ev_cb cb, int sd); +void bfd_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); +void bfd_echo_xmttimer_assign(struct bfd_session *bs, bfd_ev_cb cb); + + +/* + * bfd.c + * + * BFD protocol specific code. + */ +struct bfd_session *ptm_bfd_sess_new(struct bfd_peer_cfg *bpc); +int ptm_bfd_ses_del(struct bfd_peer_cfg *bpc); +void ptm_bfd_ses_dn(struct bfd_session *bfd, uint8_t diag); +void ptm_bfd_ses_up(struct bfd_session *bfd); +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen); +void ptm_bfd_echo_stop(struct bfd_session *bfd, int polling); +void ptm_bfd_echo_start(struct bfd_session *bfd); +void ptm_bfd_xmt_TO(struct bfd_session *bfd, int fbit); +void ptm_bfd_start_xmt_timer(struct bfd_session *bfd, bool is_echo); +struct bfd_session *ptm_bfd_sess_find(struct bfd_pkt *cp, char *port_name, +				      struct sockaddr_any *peer, +				      struct sockaddr_any *local, +				      char *vrf_name, bool is_mhop); + +struct bfd_session *bs_peer_find(struct bfd_peer_cfg *bpc); +int bfd_session_update_label(struct bfd_session *bs, const char *nlabel); +const char *satostr(struct sockaddr_any *sa); +const char *diag2str(uint8_t diag); +int strtosa(const char *addr, struct sockaddr_any *sa); +void integer2timestr(uint64_t time, char *buf, size_t buflen); + +/* BFD hash data structures interface */ +void bfd_initialize(void); +void bfd_shutdown(void); +struct bfd_session *bfd_id_lookup(uint32_t id); +struct bfd_session *bfd_shop_lookup(struct bfd_shop_key shop); +struct bfd_session *bfd_mhop_lookup(struct bfd_mhop_key mhop); +struct bfd_vrf *bfd_vrf_lookup(int vrf_id); +struct bfd_iface *bfd_iface_lookup(const char *ifname); + +struct bfd_session *bfd_id_delete(uint32_t id); +struct bfd_session *bfd_shop_delete(struct bfd_shop_key shop); +struct bfd_session *bfd_mhop_delete(struct bfd_mhop_key mhop); +struct bfd_vrf *bfd_vrf_delete(int vrf_id); +struct bfd_iface *bfd_iface_delete(const char *ifname); + +bool bfd_id_insert(struct bfd_session *bs); +bool bfd_shop_insert(struct bfd_session *bs); +bool bfd_mhop_insert(struct bfd_session *bs); +bool bfd_vrf_insert(struct bfd_vrf *vrf); +bool bfd_iface_insert(struct bfd_iface *iface); + +typedef void (*hash_iter_func)(struct hash_backet *hb, void *arg); +void bfd_id_iterate(hash_iter_func hif, void *arg); +void bfd_shop_iterate(hash_iter_func hif, void *arg); +void bfd_mhop_iterate(hash_iter_func hif, void *arg); +void bfd_vrf_iterate(hash_iter_func hif, void *arg); +void bfd_iface_iterate(hash_iter_func hif, void *arg); + +/* Export callback functions for `event.c`. */ +extern struct thread_master *master; + +int bfd_recvtimer_cb(struct thread *t); +int bfd_echo_recvtimer_cb(struct thread *t); +int bfd_xmt_cb(struct thread *t); +int bfd_echo_xmt_cb(struct thread *t); + + +/* + * OS compatibility functions. + */ +struct udp_psuedo_header { +	uint32_t saddr; +	uint32_t daddr; +	uint8_t reserved; +	uint8_t protocol; +	uint16_t len; +}; + +#define UDP_PSUEDO_HDR_LEN sizeof(struct udp_psuedo_header) + +#if defined(BFD_LINUX) || defined(BFD_BSD) +int ptm_bfd_fetch_ifindex(const char *ifname); +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac); +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen); +int ptm_bfd_echo_sock_init(void); +int ptm_bfd_vxlan_sock_init(void); +#endif /* BFD_LINUX || BFD_BSD */ + +#ifdef BFD_LINUX +uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len); +#endif /* BFD_LINUX */ + +#ifdef BFD_BSD +uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len); +ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen, +			   struct sockaddr_storage *ss, socklen_t *sslen, +			   uint8_t *ttl, uint32_t *id); +#endif /* BFD_BSD */ + +#endif /* _BFD_H_ */ diff --git a/bfdd/bfd_packet.c b/bfdd/bfd_packet.c new file mode 100644 index 0000000000..79aa706a53 --- /dev/null +++ b/bfdd/bfd_packet.c @@ -0,0 +1,1488 @@ +/********************************************************************* + * Copyright 2017 Cumulus Networks, Inc.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfd_packet.c: implements the BFD protocol packet handling. + * + * Authors + * ------- + * Shrijeet Mukherjee [shm@cumulusnetworks.com] + * Kanna Rajagopal [kanna@cumulusnetworks.com] + * Radhika Mahankali [Radhika@cumulusnetworks.com] + */ + +#include <zebra.h> + +#ifdef BFD_LINUX +#include <linux/if_packet.h> +#endif /* BFD_LINUX */ + +#include <arpa/inet.h> +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <stdint.h> +#include <unistd.h> + +#include "lib/sockopt.h" + +#include "bfd.h" + +/* + * Definitions + */ + +/* iov for BFD control frames */ +#define CMSG_HDR_LEN sizeof(struct cmsghdr) +#define CMSG_TTL_LEN (CMSG_HDR_LEN + sizeof(uint32_t)) +#define CMSG_IN_PKT_INFO_LEN (CMSG_HDR_LEN + sizeof(struct in_pktinfo) + 4) +#define CMSG_IN6_PKT_INFO_LEN                                                  \ +	(CMSG_HDR_LEN + sizeof(struct in6_addr) + sizeof(int) + 4) + +struct bfd_raw_echo_pkt { +#ifdef BFD_LINUX +	struct iphdr ip; +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +	struct ip ip; +#endif /* BFD_BSD */ +	struct udphdr udp; +	struct bfd_echo_pkt data; +}; + +#if 0 /* TODO: VxLAN support. */ +struct bfd_raw_ctrl_pkt { +	struct iphdr ip; +	struct udphdr udp; +	struct bfd_pkt data; +}; +#endif + +struct vxlan_hdr { +	uint32_t flags; +	uint32_t vnid; +}; + +#define IP_ECHO_PKT_LEN (IP_HDR_LEN + UDP_HDR_LEN + BFD_ECHO_PKT_LEN) +#define UDP_ECHO_PKT_LEN (UDP_HDR_LEN + BFD_ECHO_PKT_LEN) +#define IP_CTRL_PKT_LEN (IP_HDR_LEN + UDP_HDR_LEN + BFD_PKT_LEN) +#define UDP_CTRL_PKT_LEN (UDP_HDR_LEN + BFD_PKT_LEN) + +static uint8_t msgbuf[BFD_PKT_LEN]; +static struct iovec msgiov = {&(msgbuf[0]), sizeof(msgbuf)}; +static uint8_t cmsgbuf[255]; + +static struct sockaddr_in msgaddr; +static struct msghdr msghdr = {(void *)&msgaddr, sizeof(msgaddr), &msgiov, 1, +			       (void *)&cmsgbuf, sizeof(cmsgbuf), 0}; + +static uint8_t cmsgbuf6[255]; + +static struct sockaddr_in6 msgaddr6; +static struct msghdr msghdr6 = {(void *)&msgaddr6, sizeof(msgaddr6), &msgiov, 1, +				(void *)&cmsgbuf6, sizeof(cmsgbuf6), 0}; + +static int ttlval = BFD_TTL_VAL; +static int tosval = BFD_TOS_VAL; +static int rcvttl = BFD_RCV_TTL_VAL; + +/* + * Prototypes + */ +static uint16_t ptm_bfd_gen_IP_ID(struct bfd_session *bfd); +static void ptm_bfd_echo_pkt_create(struct bfd_session *bfd); +static int ptm_bfd_echo_loopback(uint8_t *pkt, int pkt_len, struct sockaddr *ss, +				 socklen_t sslen); +static void ptm_bfd_vxlan_pkt_snd(struct bfd_session *bfd, int fbit); +static int ptm_bfd_process_echo_pkt(int s); +static bool +ptm_bfd_validate_vxlan_pkt(struct bfd_session *bfd, +			   struct bfd_session_vxlan_info *vxlan_info); + +static void bfd_sd_reschedule(int sd); +static ssize_t bfd_recv_ipv4(int sd, bool is_mhop, char *port, size_t portlen, +			     char *vrfname, size_t vrfnamelen, +			     struct sockaddr_any *local, +			     struct sockaddr_any *peer); +static ssize_t bfd_recv_ipv6(int sd, bool is_mhop, char *port, size_t portlen, +			     char *vrfname, size_t vrfnamelen, +			     struct sockaddr_any *local, +			     struct sockaddr_any *peer); + +/* socket related prototypes */ +static void bp_set_ipopts(int sd); +static void bp_bind_ip(int sd, uint16_t port); +static void bp_set_ipv6opts(int sd); +static void bp_bind_ipv6(int sd, uint16_t port); + + +/* + * Functions + */ +uint16_t checksum(uint16_t *buf, int len) +{ +	int nbytes = len; +	int sum = 0; +	uint16_t csum = 0; +	int size = sizeof(uint16_t); + +	while (nbytes > 1) { +		sum += *buf++; +		nbytes -= size; +	} + +	if (nbytes == 1) { +		*(uint8_t *)(&csum) = *(uint8_t *)buf; +		sum += csum; +	} + +	sum = (sum >> 16) + (sum & 0xFFFF); +	sum += (sum >> 16); +	csum = ~sum; +	return csum; +} + +static uint16_t ptm_bfd_gen_IP_ID(struct bfd_session *bfd) +{ +	return (++bfd->ip_id); +} + +static int _ptm_bfd_send(struct bfd_session *bs, bool use_layer2, +			 uint16_t *port, const void *data, size_t datalen) +{ +	struct sockaddr *sa; +	struct sockaddr_in sin; +	struct sockaddr_in6 sin6; +#ifdef BFD_LINUX +	struct sockaddr_ll dll; +#endif /* BFD_LINUX */ +	socklen_t slen; +	ssize_t rv; +	int sd = -1; + +	if (use_layer2) { +#ifdef BFD_LINUX +		memset(&dll, 0, sizeof(dll)); +		dll.sll_family = AF_PACKET; +		dll.sll_protocol = htons(ETH_P_IP); +		memcpy(dll.sll_addr, bs->peer_mac, ETHERNET_ADDRESS_LENGTH); +		dll.sll_halen = htons(ETHERNET_ADDRESS_LENGTH); +		dll.sll_ifindex = bs->ifindex; + +		sd = bglobal.bg_echo; +		sa = (struct sockaddr *)&dll; +		slen = sizeof(dll); +#else +		/* +		 * TODO: implement layer 2 send for *BSDs. This is +		 * needed for VxLAN. +		 */ +		log_warning("%s: not implemented"); +		return -1; +#endif +	} else if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) { +		memset(&sin6, 0, sizeof(sin6)); +		sin6.sin6_family = AF_INET6; +		sin6.sin6_addr = bs->shop.peer.sa_sin6.sin6_addr; +		sin6.sin6_port = +			(port) ? *port +			       : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) +					 ? htons(BFD_DEF_MHOP_DEST_PORT) +					 : htons(BFD_DEFDESTPORT); + +		sd = bs->sock; +		sa = (struct sockaddr *)&sin6; +		slen = sizeof(sin6); +	} else { +		memset(&sin, 0, sizeof(sin)); +		sin.sin_family = AF_INET; +		sin.sin_addr = bs->shop.peer.sa_sin.sin_addr; +		sin.sin_port = +			(port) ? *port +			       : (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) +					 ? htons(BFD_DEF_MHOP_DEST_PORT) +					 : htons(BFD_DEFDESTPORT); + +		sd = bs->sock; +		sa = (struct sockaddr *)&sin; +		slen = sizeof(sin); +	} + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +	sa->sa_len = slen; +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +	rv = sendto(sd, data, datalen, 0, sa, slen); +	if (rv <= 0) { +		log_debug("%s:%d: sendto: (%d) %s", __func__, __LINE__, errno, +			  strerror(errno)); +		return -1; +	} +	if (rv < (ssize_t)datalen) { +		log_debug("%s:%d: sendto: sent partial data", __func__, +			  __LINE__); +	} + +	return 0; +} + +static void ptm_bfd_echo_pkt_create(struct bfd_session *bfd) +{ +	struct bfd_raw_echo_pkt ep; +	uint8_t *pkt = bfd->echo_pkt; + +	memset(&ep, 0, sizeof(ep)); +	memset(bfd->echo_pkt, 0, sizeof(bfd->echo_pkt)); + +	/* Construct ethernet header information */ +	memcpy(pkt, bfd->peer_mac, ETHERNET_ADDRESS_LENGTH); +	pkt = pkt + ETHERNET_ADDRESS_LENGTH; +	memcpy(pkt, bfd->local_mac, ETHERNET_ADDRESS_LENGTH); +	pkt = pkt + ETHERNET_ADDRESS_LENGTH; +#ifdef BFD_LINUX +	pkt[0] = ETH_P_IP / 256; +	pkt[1] = ETH_P_IP % 256; +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +	pkt[0] = ETHERTYPE_IP / 256; +	pkt[1] = ETHERTYPE_IP % 256; +#endif /* BFD_BSD */ +	pkt += 2; + +	/* Construct IP header information */ +#ifdef BFD_LINUX +	ep.ip.version = 4; +	ep.ip.ihl = 5; +	ep.ip.tos = 0; +	ep.ip.tot_len = htons(IP_ECHO_PKT_LEN); +	ep.ip.id = htons(ptm_bfd_gen_IP_ID(bfd)); +	ep.ip.frag_off = 0; +	ep.ip.ttl = BFD_TTL_VAL; +	ep.ip.protocol = IPPROTO_UDP; +	ep.ip.saddr = bfd->local_ip.sa_sin.sin_addr.s_addr; +	ep.ip.daddr = bfd->shop.peer.sa_sin.sin_addr.s_addr; +	ep.ip.check = checksum((uint16_t *)&ep.ip, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +	ep.ip.ip_v = 4; +	ep.ip.ip_hl = 5; +	ep.ip.ip_tos = 0; +	ep.ip.ip_len = htons(IP_ECHO_PKT_LEN); +	ep.ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd)); +	ep.ip.ip_off = 0; +	ep.ip.ip_ttl = BFD_TTL_VAL; +	ep.ip.ip_p = IPPROTO_UDP; +	ep.ip.ip_src = bfd->local_ip.sa_sin.sin_addr; +	ep.ip.ip_dst = bfd->shop.peer.sa_sin.sin_addr; +	ep.ip.ip_sum = checksum((uint16_t *)&ep.ip, IP_HDR_LEN); +#endif /* BFD_BSD */ + +	/* Construct UDP header information */ +#ifdef BFD_LINUX +	ep.udp.source = htons(BFD_DEF_ECHO_PORT); +	ep.udp.dest = htons(BFD_DEF_ECHO_PORT); +	ep.udp.len = htons(UDP_ECHO_PKT_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +	ep.udp.uh_sport = htons(BFD_DEF_ECHO_PORT); +	ep.udp.uh_dport = htons(BFD_DEF_ECHO_PORT); +	ep.udp.uh_ulen = htons(UDP_ECHO_PKT_LEN); +#endif /* BFD_BSD */ + +	/* Construct Echo packet information */ +	ep.data.ver = BFD_ECHO_VERSION; +	ep.data.len = BFD_ECHO_PKT_LEN; +	ep.data.my_discr = htonl(bfd->discrs.my_discr); +#ifdef BFD_LINUX +	ep.udp.check = +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +	ep.udp.uh_sum = +#endif /* BFD_BSD */ +		udp4_checksum(&ep.ip, (uint8_t *)&ep.udp, +			      UDP_ECHO_PKT_LEN); + +	memcpy(pkt, &ep, sizeof(ep)); +} + +void ptm_bfd_echo_snd(struct bfd_session *bfd) +{ +	struct bfd_raw_echo_pkt *ep; +	bool use_layer2 = false; +	const void *pkt; +	size_t pktlen; +	uint16_t port = htons(BFD_DEF_ECHO_PORT); + +	if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { +		ptm_bfd_echo_pkt_create(bfd); +		BFD_SET_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE); +	} else { +		/* just update the checksum and ip Id */ +		ep = (struct bfd_raw_echo_pkt *)(bfd->echo_pkt + ETH_HDR_LEN); +#ifdef BFD_LINUX +		ep->ip.id = htons(ptm_bfd_gen_IP_ID(bfd)); +		ep->ip.check = 0; +		ep->ip.check = checksum((uint16_t *)&ep->ip, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +		ep->ip.ip_id = htons(ptm_bfd_gen_IP_ID(bfd)); +		ep->ip.ip_sum = 0; +		ep->ip.ip_sum = checksum((uint16_t *)&ep->ip, IP_HDR_LEN); +#endif /* BFD_BSD */ +	} + +	if (use_layer2) { +		pkt = bfd->echo_pkt; +		pktlen = BFD_ECHO_PKT_TOT_LEN; +	} else { +		pkt = &bfd->echo_pkt[ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN]; +		pktlen = BFD_ECHO_PKT_TOT_LEN +			 - (ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN); +	} + +	if (_ptm_bfd_send(bfd, use_layer2, &port, pkt, pktlen) != 0) { +		ERRLOG("%s: _ptm_bfd_send: %s", __func__, strerror(errno)); +		return; +	} + +	bfd->stats.tx_echo_pkt++; +} + +static int ptm_bfd_echo_loopback(uint8_t *pkt, int pkt_len, struct sockaddr *ss, +				 socklen_t sslen) +{ +#ifdef BFD_LINUX +	struct bfd_raw_echo_pkt *ep = +		(struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN); +	uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH]; +	uint32_t temp_ip; +	struct ethhdr *eth = (struct ethhdr *)pkt; + +	/* swap the mac addresses */ +	memcpy(temp_mac, eth->h_source, ETHERNET_ADDRESS_LENGTH); +	memcpy(eth->h_source, eth->h_dest, ETHERNET_ADDRESS_LENGTH); +	memcpy(eth->h_dest, temp_mac, ETHERNET_ADDRESS_LENGTH); + +	/* swap ip addresses */ +	temp_ip = ep->ip.saddr; +	ep->ip.saddr = ep->ip.daddr; +	ep->ip.daddr = temp_ip; + +	ep->ip.ttl = ep->ip.ttl - 1; +	ep->ip.check = 0; +	ep->ip.check = checksum((uint16_t *)ep, IP_HDR_LEN); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD_FILTER +	struct bfd_raw_echo_pkt_t *ep = +		(struct bfd_raw_echo_pkt *)(pkt + ETH_HDR_LEN); +	uint8_t temp_mac[ETHERNET_ADDRESS_LENGTH]; +	struct in_addr temp_ip; +	struct ether_header *ether = (struct ether_header *)pkt; + +	/* +	 * TODO: this is not yet implemented and requires BPF code for +	 * OmniOS, NetBSD and FreeBSD9. +	 */ + +	/* swap the mac addresses */ +	memcpy(temp_mac, ether->ether_shost, ETHERNET_ADDRESS_LENGTH); +	memcpy(ether->ether_shost, ether->ether_dhost, ETHERNET_ADDRESS_LENGTH); +	memcpy(ether->ether_dhost, temp_mac, ETHERNET_ADDRESS_LENGTH); + +	/* swap ip addresses */ +	temp_ip = ep->ip.ip_src; +	ep->ip.ip_src = ep->ip.ip_dst; +	ep->ip.ip_dst = temp_ip; + +	ep->ip.ip_ttl = ep->ip.ip_ttl - 1; +	ep->ip.ip_sum = 0; +	ep->ip.ip_sum = checksum((uint16_t *)ep, IP_HDR_LEN); +#endif /* BFD_BSD_FILTER */ + +	if (sendto(bglobal.bg_echo, pkt, pkt_len, 0, ss, sslen) < 0) { +		ERRLOG("%s: sendto: %s", __func__, strerror(errno)); +		return -1; +	} + +	return 0; +} + +static void ptm_bfd_vxlan_pkt_snd(struct bfd_session *bfd +				  __attribute__((__unused__)), +				  int fbit __attribute__((__unused__))) +{ +#if 0 /* TODO: VxLAN support. */ +	struct bfd_raw_ctrl_pkt cp; +	uint8_t vxlan_pkt[BFD_VXLAN_PKT_TOT_LEN]; +	uint8_t *pkt = vxlan_pkt; +	struct sockaddr_in sin; +	struct vxlan_hdr *vhdr; + +	memset(vxlan_pkt, 0, sizeof(vxlan_pkt)); +	memset(&cp, 0, sizeof(cp)); + +	/* Construct VxLAN header information */ +	vhdr = (struct vxlan_hdr *)pkt; +	vhdr->flags = htonl(0x08000000); +	vhdr->vnid = htonl(bfd->vxlan_info.vnid << 8); +	pkt += VXLAN_HDR_LEN; + +	/* Construct ethernet header information */ +	memcpy(pkt, bfd->vxlan_info.peer_dst_mac, ETHERNET_ADDRESS_LENGTH); +	pkt = pkt + ETHERNET_ADDRESS_LENGTH; +	memcpy(pkt, bfd->vxlan_info.local_dst_mac, ETHERNET_ADDRESS_LENGTH); +	pkt = pkt + ETHERNET_ADDRESS_LENGTH; +	pkt[0] = ETH_P_IP / 256; +	pkt[1] = ETH_P_IP % 256; +	pkt += 2; + +	/* Construct IP header information */ +	cp.ip.version = 4; +	cp.ip.ihl = 5; +	cp.ip.tos = 0; +	cp.ip.tot_len = htons(IP_CTRL_PKT_LEN); +	cp.ip.id = ptm_bfd_gen_IP_ID(bfd); +	cp.ip.frag_off = 0; +	cp.ip.ttl = BFD_TTL_VAL; +	cp.ip.protocol = IPPROTO_UDP; +	cp.ip.daddr = bfd->vxlan_info.peer_dst_ip.s_addr; +	cp.ip.saddr = bfd->vxlan_info.local_dst_ip.s_addr; +	cp.ip.check = checksum((uint16_t *)&cp.ip, IP_HDR_LEN); + +	/* Construct UDP header information */ +	cp.udp.source = htons(BFD_DEFDESTPORT); +	cp.udp.dest = htons(BFD_DEFDESTPORT); +	cp.udp.len = htons(UDP_CTRL_PKT_LEN); + +	/* Construct BFD control packet information */ +	cp.data.diag = bfd->local_diag; +	BFD_SETVER(cp.data.diag, BFD_VERSION); +	BFD_SETSTATE(cp.data.flags, bfd->ses_state); +	BFD_SETDEMANDBIT(cp.data.flags, BFD_DEF_DEMAND); +	BFD_SETPBIT(cp.data.flags, bfd->polling); +	BFD_SETFBIT(cp.data.flags, fbit); +	cp.data.detect_mult = bfd->detect_mult; +	cp.data.len = BFD_PKT_LEN; +	cp.data.discrs.my_discr = htonl(bfd->discrs.my_discr); +	cp.data.discrs.remote_discr = htonl(bfd->discrs.remote_discr); +	cp.data.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx); +	cp.data.timers.required_min_rx = htonl(bfd->timers.required_min_rx); +	cp.data.timers.required_min_echo = htonl(bfd->timers.required_min_echo); + +	cp.udp.check = +		udp4_checksum(&cp.ip, (uint8_t *)&cp.udp, UDP_CTRL_PKT_LEN); + +	memcpy(pkt, &cp, sizeof(cp)); +	sin.sin_family = AF_INET; +	sin.sin_addr = bfd->shop.peer.sa_sin.sin_addr; +	sin.sin_port = htons(4789); + +	if (sendto(bfd->sock, vxlan_pkt, BFD_VXLAN_PKT_TOT_LEN, 0, +		   (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) +	    < 0) { +		ERRLOG("Error sending vxlan bfd pkt: %s", strerror(errno)); +	} else { +		bfd->stats.tx_ctrl_pkt++; +	} +#endif +} + +static int ptm_bfd_process_echo_pkt(int s) +{ +	uint32_t my_discr = 0; +	struct sockaddr_storage ss; +	socklen_t sslen = sizeof(ss); +	uint8_t rx_pkt[BFD_RX_BUF_LEN]; +	ssize_t pkt_len = sizeof(rx_pkt); +	struct bfd_session *bfd; +#ifdef BFD_LINUX +	struct bfd_raw_echo_pkt *ep; + +	/* +	 * valgrind: memset() ss so valgrind doesn't complain about +	 * uninitialized memory. +	 */ +	memset(&ss, 0, sizeof(ss)); +	pkt_len = recvfrom(s, rx_pkt, sizeof(rx_pkt), MSG_DONTWAIT, +			   (struct sockaddr *)&ss, &sslen); +	if (pkt_len <= 0) { +		if (errno != EAGAIN) +			ERRLOG("%s: recvfrom: %s", __func__, strerror(errno)); +		return -1; +	} + +	/* Check if we have at least the basic headers to send back. */ +	if (pkt_len < HEADERS_MIN_LEN) { +		INFOLOG("Received short echo packet"); +		return -1; +	} + +	ep = (struct bfd_raw_echo_pkt *)(rx_pkt + ETH_HDR_LEN); +	/* if TTL = 255, assume that the received echo packet has +	 * to be looped back +	 */ +	if (ep->ip.ttl == BFD_TTL_VAL) +		return ptm_bfd_echo_loopback(rx_pkt, pkt_len, +					     (struct sockaddr *)&ss, +					     sizeof(struct sockaddr_ll)); + +	/* Packet is too small for us to process */ +	if (pkt_len < BFD_ECHO_PKT_TOT_LEN) { +		INFOLOG("Received short echo packet"); +		return -1; +	} + +	my_discr = ntohl(ep->data.my_discr); +	if (ep->data.my_discr == 0) { +		INFOLOG("My discriminator is zero in echo pkt from 0x%x", +			ntohl(ep->ip.saddr)); +		return -1; +	} +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +	int rv; +	uint8_t ttl; + +	/* +	 * bsd_echo_sock_read() already treats invalid TTL values and +	 * zeroed discriminators. +	 */ +	rv = bsd_echo_sock_read(s, rx_pkt, &pkt_len, &ss, &sslen, &ttl, +				&my_discr); +	if (rv == -1) +		return -1; + +	if (ttl == BFD_TTL_VAL) +		return ptm_bfd_echo_loopback(rx_pkt, pkt_len, +					     (struct sockaddr *)&ss, sslen); +#endif /* BFD_BSD */ + +	/* Your discriminator not zero - use it to find session */ +	bfd = bfd_id_lookup(my_discr); +	if (bfd == NULL) { +		INFOLOG("Failed to extract session from echo packet"); +		return -1; +	} + +	if (!BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { +		INFOLOG("BFD echo not active - ignore echo packet"); +		return -1; +	} + +	bfd->stats.rx_echo_pkt++; + +	/* Compute detect time */ +	bfd->echo_detect_TO = bfd->remote_detect_mult * bfd->echo_xmt_TO; + +	/* Update echo receive timeout. */ +	bfd_echo_recvtimer_update(bfd); + +	return 0; +} + +void ptm_bfd_snd(struct bfd_session *bfd, int fbit) +{ +	struct bfd_pkt cp; + +	/* if the BFD session is for VxLAN tunnel, then construct and +	 * send bfd raw packet +	 */ +	if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_VXLAN)) { +		ptm_bfd_vxlan_pkt_snd(bfd, fbit); +		return; +	} + +	/* Set fields according to section 6.5.7 */ +	cp.diag = bfd->local_diag; +	BFD_SETVER(cp.diag, BFD_VERSION); +	cp.flags = 0; +	BFD_SETSTATE(cp.flags, bfd->ses_state); +	BFD_SETDEMANDBIT(cp.flags, BFD_DEF_DEMAND); +	BFD_SETPBIT(cp.flags, bfd->polling); +	BFD_SETFBIT(cp.flags, fbit); +	cp.detect_mult = bfd->detect_mult; +	cp.len = BFD_PKT_LEN; +	cp.discrs.my_discr = htonl(bfd->discrs.my_discr); +	cp.discrs.remote_discr = htonl(bfd->discrs.remote_discr); +	if (bfd->polling) { +		cp.timers.desired_min_tx = +			htonl(bfd->new_timers.desired_min_tx); +		cp.timers.required_min_rx = +			htonl(bfd->new_timers.required_min_rx); +	} else { +		cp.timers.desired_min_tx = htonl(bfd->timers.desired_min_tx); +		cp.timers.required_min_rx = htonl(bfd->timers.required_min_rx); +	} +	cp.timers.required_min_echo = htonl(bfd->timers.required_min_echo); + +	if (_ptm_bfd_send(bfd, false, NULL, &cp, BFD_PKT_LEN) != 0) { +		ERRLOG("Error sending control pkt: %s", strerror(errno)); +		return; +	} + +	bfd->stats.tx_ctrl_pkt++; +} + +#if 0  /* TODO VxLAN Support */ +static struct bfd_pkt * +ptm_bfd_process_vxlan_pkt(int s, ptm_sockevent_e se, void *udata, int *ifindex, +			  struct sockaddr_in *sin, +			  struct bfd_session_vxlan_info_t *vxlan_info, +			  uint8_t *rx_pkt, int *mlen) +{ +	struct sockaddr_ll sll; +	uint32_t from_len = sizeof(struct sockaddr_ll); +	struct bfd_raw_ctrl_pkt *cp; +	uint8_t *pkt = rx_pkt; +	struct iphdr *iph; +	struct ethhdr *inner_ethh; + +	*mlen = recvfrom(s, rx_pkt, BFD_RX_BUF_LEN, MSG_DONTWAIT, +			 (struct sockaddr *)&sll, &from_len); + +	if (*mlen < 0) { +		if (errno != EAGAIN) +			ERRLOG("Error receiving from BFD Vxlan socket %d: %m", +			       s); +		return NULL; +	} + +	iph = (struct iphdr *)(pkt + ETH_HDR_LEN); +	pkt = pkt + ETH_HDR_LEN + IP_HDR_LEN + UDP_HDR_LEN; +	vxlan_info->vnid = ntohl(*((int *)(pkt + 4))); +	vxlan_info->vnid = vxlan_info->vnid >> 8; + +	pkt = pkt + VXLAN_HDR_LEN; +	inner_ethh = (struct ethhdr *)pkt; + +	cp = (struct bfd_raw_ctrl_pkt *)(pkt + ETH_HDR_LEN); + +	/* Discard the non BFD packets */ +	if (ntohs(cp->udp.dest) != BFD_DEFDESTPORT) +		return NULL; + +	*ifindex = sll.sll_ifindex; +	sin->sin_addr.s_addr = iph->saddr; +	sin->sin_port = ntohs(cp->udp.dest); + +	vxlan_info->local_dst_ip.s_addr = cp->ip.daddr; +	memcpy(vxlan_info->local_dst_mac, inner_ethh->h_dest, +	       ETHERNET_ADDRESS_LENGTH); + +	return &cp->data; +} +#endif /* VxLAN */ + +static bool +ptm_bfd_validate_vxlan_pkt(struct bfd_session *bfd, +			   struct bfd_session_vxlan_info *vxlan_info) +{ +	if (bfd->vxlan_info.check_tnl_key && (vxlan_info->vnid != 0)) { +		ERRLOG("Error Rx BFD Vxlan pkt with non-zero vnid %d", +		       vxlan_info->vnid); +		return false; +	} + +	if (bfd->vxlan_info.local_dst_ip.s_addr +	    != vxlan_info->local_dst_ip.s_addr) { +		ERRLOG("Error Rx BFD Vxlan pkt with wrong inner dst IP %s", +		       inet_ntoa(vxlan_info->local_dst_ip)); +		return false; +	} + +	if (memcmp(bfd->vxlan_info.local_dst_mac, vxlan_info->local_dst_mac, +		   ETHERNET_ADDRESS_LENGTH)) { +		ERRLOG("Error Rx BFD Vxlan pkt with wrong inner dst MAC %02x:%02x:%02x:%02x:%02x:%02x", +		       vxlan_info->local_dst_mac[0], +		       vxlan_info->local_dst_mac[1], +		       vxlan_info->local_dst_mac[2], +		       vxlan_info->local_dst_mac[3], +		       vxlan_info->local_dst_mac[4], +		       vxlan_info->local_dst_mac[5]); +		return false; +	} + +	return true; +} + +static ssize_t bfd_recv_ipv4(int sd, bool is_mhop, char *port, size_t portlen, +			     char *vrfname, size_t vrfnamelen, +			     struct sockaddr_any *local, +			     struct sockaddr_any *peer) +{ +	struct cmsghdr *cm; +	int ifindex; +	ssize_t mlen; + +	memset(port, 0, portlen); +	memset(vrfname, 0, vrfnamelen); +	memset(local, 0, sizeof(*local)); +	memset(peer, 0, sizeof(*peer)); + +	mlen = recvmsg(sd, &msghdr, MSG_DONTWAIT); +	if (mlen == -1) { +		if (errno != EAGAIN) { +			ERRLOG("Error receiving from BFD socket: %s", +			       strerror(errno)); +		} +		return -1; +	} + +	/* Get source address */ +	peer->sa_sin = *((struct sockaddr_in *)(msghdr.msg_name)); + +	/* Get and check TTL */ +	for (cm = CMSG_FIRSTHDR(&msghdr); cm != NULL; +	     cm = CMSG_NXTHDR(&msghdr, cm)) { +		if (cm->cmsg_level != IPPROTO_IP) +			continue; + +		switch (cm->cmsg_type) { +#ifdef BFD_LINUX +		case IP_TTL: { +			uint32_t ttl; + +			memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl)); +			if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) { +				INFOLOG("Received pkt with invalid TTL %u from %s flags: %d", +					ttl, satostr(peer), msghdr.msg_flags); +				return -1; +			} +			break; +		} + +		case IP_PKTINFO: { +			struct in_pktinfo *pi = +				(struct in_pktinfo *)CMSG_DATA(cm); + +			if (pi == NULL) +				break; + +			local->sa_sin.sin_family = AF_INET; +			local->sa_sin.sin_addr = pi->ipi_addr; +			fetch_portname_from_ifindex(pi->ipi_ifindex, port, +						    portlen); +			break; +		} +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +		case IP_RECVTTL: { +			uint8_t ttl; + +			memcpy(&ttl, CMSG_DATA(cm), sizeof(ttl)); +			if ((is_mhop == false) && (ttl != BFD_TTL_VAL)) { +				INFOLOG("Received pkt with invalid TTL %u from %s flags: %d", +					ttl, satostr(peer), msghdr.msg_flags); +				return -1; +			} +			break; +		} + +		case IP_RECVDSTADDR: { +			struct in_addr ia; + +			memcpy(&ia, CMSG_DATA(cm), sizeof(ia)); +			local->sa_sin.sin_family = AF_INET; +			local->sa_sin.sin_addr = ia; +			break; +		} +#endif /* BFD_BSD */ + +		default: +			/* +			 * On *BSDs we expect to land here when skipping +			 * the IP_RECVIF header. It will be handled by +			 * getsockopt_ifindex() below. +			 */ +			/* NOTHING */ +			break; +		} +	} + +	/* OS agnostic way of getting interface name. */ +	if (port[0] == 0) { +		ifindex = getsockopt_ifindex(AF_INET, &msghdr); +		if (ifindex > 0) +			fetch_portname_from_ifindex(ifindex, port, portlen); +	} + +	return mlen; +} + +ssize_t bfd_recv_ipv6(int sd, bool is_mhop, char *port, size_t portlen, +		      char *vrfname, size_t vrfnamelen, +		      struct sockaddr_any *local, struct sockaddr_any *peer) +{ +	struct cmsghdr *cm; +	struct in6_pktinfo *pi6 = NULL; +	ssize_t mlen; + +	memset(port, 0, portlen); +	memset(vrfname, 0, vrfnamelen); +	memset(local, 0, sizeof(*local)); +	memset(peer, 0, sizeof(*peer)); + +	mlen = recvmsg(sd, &msghdr6, MSG_DONTWAIT); +	if (mlen == -1) { +		if (errno != EAGAIN) { +			ERRLOG("Error receiving from BFD socket: %s", +			       strerror(errno)); +		} +		return -1; +	} + +	/* Get source address */ +	peer->sa_sin6 = *((struct sockaddr_in6 *)(msghdr6.msg_name)); + +	/* Get and check TTL */ +	for (cm = CMSG_FIRSTHDR(&msghdr6); cm != NULL; +	     cm = CMSG_NXTHDR(&msghdr6, cm)) { +		if (cm->cmsg_level != IPPROTO_IPV6) +			continue; + +		if (cm->cmsg_type == IPV6_HOPLIMIT) { +			memcpy(&ttlval, CMSG_DATA(cm), 4); +			if ((is_mhop == false) && (ttlval != BFD_TTL_VAL)) { +				INFOLOG("Received pkt with invalid TTL %u from %s flags: %d", +					ttlval, satostr(peer), +					msghdr.msg_flags); +				return -1; +			} +		} else if (cm->cmsg_type == IPV6_PKTINFO) { +			pi6 = (struct in6_pktinfo *)CMSG_DATA(cm); +			if (pi6) { +				local->sa_sin.sin_family = AF_INET6; +				local->sa_sin6.sin6_addr = pi6->ipi6_addr; +				fetch_portname_from_ifindex(pi6->ipi6_ifindex, +							    port, portlen); +			} +		} +	} + +	return mlen; +} + +static void bfd_sd_reschedule(int sd) +{ +	if (sd == bglobal.bg_shop) { +		bglobal.bg_ev[0] = NULL; +		thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop, +				&bglobal.bg_ev[0]); +	} else if (sd == bglobal.bg_mhop) { +		bglobal.bg_ev[1] = NULL; +		thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop, +				&bglobal.bg_ev[1]); +	} else if (sd == bglobal.bg_shop6) { +		bglobal.bg_ev[2] = NULL; +		thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6, +				&bglobal.bg_ev[2]); +	} else if (sd == bglobal.bg_mhop6) { +		bglobal.bg_ev[3] = NULL; +		thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6, +				&bglobal.bg_ev[3]); +	} else if (sd == bglobal.bg_echo) { +		bglobal.bg_ev[4] = NULL; +		thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo, +				&bglobal.bg_ev[4]); +	} else if (sd == bglobal.bg_vxlan) { +		bglobal.bg_ev[5] = NULL; +		thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan, +				&bglobal.bg_ev[5]); +	} +} + +int bfd_recv_cb(struct thread *t) +{ +	int sd = THREAD_FD(t); +	struct bfd_session *bfd; +	struct bfd_pkt *cp; +	bool is_mhop, is_vxlan; +	ssize_t mlen = 0; +	uint8_t old_state; +	uint32_t oldEchoXmt_TO, oldXmtTime; +	struct sockaddr_any local, peer; +	char port[MAXNAMELEN + 1], vrfname[MAXNAMELEN + 1]; +	struct bfd_session_vxlan_info vxlan_info; + +	/* Schedule next read. */ +	bfd_sd_reschedule(sd); + +	if (sd == bglobal.bg_echo) { +		ptm_bfd_process_echo_pkt(sd); +		return 0; +	} + +	is_mhop = is_vxlan = false; +	if (sd == bglobal.bg_shop || sd == bglobal.bg_mhop) { +		is_mhop = sd == bglobal.bg_mhop; +		mlen = bfd_recv_ipv4(sd, is_mhop, port, sizeof(port), vrfname, +				     sizeof(vrfname), &local, &peer); +	} else if (sd == bglobal.bg_shop6 || sd == bglobal.bg_mhop6) { +		is_mhop = sd == bglobal.bg_mhop6; +		mlen = bfd_recv_ipv6(sd, is_mhop, port, sizeof(port), vrfname, +				     sizeof(vrfname), &local, &peer); +	} +#if 0 /* TODO vxlan handling */ +	cp = ptm_bfd_process_vxlan_pkt(s, se, udata, &local_ifindex, +				       &sin, &vxlan_info, rx_pkt, &mlen); +	if (!cp) +		return -1; + +	is_vxlan = true; +	/* keep in network-byte order */ +	peer.ip4_addr.s_addr = sin.sin_addr.s_addr; +	peer.family = AF_INET; +	strcpy(peer_addr, inet_ntoa(sin.sin_addr)); +#endif + +	/* Implement RFC 5880 6.8.6 */ +	if (mlen < BFD_PKT_LEN) { +		INFOLOG("Received short packet from %s", satostr(&peer)); +		return 0; +	} + +	cp = (struct bfd_pkt *)(msghdr.msg_iov->iov_base); +	if (BFD_GETVER(cp->diag) != BFD_VERSION) { +		INFOLOG("Received bad version %d from %s", BFD_GETVER(cp->diag), +			satostr(&peer)); +		return 0; +	} + +	if (cp->detect_mult == 0) { +		INFOLOG("Detect Mult is zero in pkt from %s", satostr(&peer)); +		return 0; +	} + +	if ((cp->len < BFD_PKT_LEN) || (cp->len > mlen)) { +		INFOLOG("Invalid length %d in control pkt from %s", cp->len, +			satostr(&peer)); +		return 0; +	} + +	if (cp->discrs.my_discr == 0) { +		INFOLOG("My discriminator is zero in pkt from %s", +			satostr(&peer)); +		return 0; +	} + +	bfd = ptm_bfd_sess_find(cp, port, &peer, &local, vrfname, is_mhop); +	if (bfd == NULL) { +		DLOG("Failed to generate session from remote packet"); +		return 0; +	} + +	if (is_vxlan && !ptm_bfd_validate_vxlan_pkt(bfd, &vxlan_info)) +		return 0; + +	bfd->stats.rx_ctrl_pkt++; +	if (is_mhop) { +		if ((BFD_TTL_VAL - bfd->mh_ttl) > ttlval) { +			DLOG("Exceeded max hop count of %d, dropped pkt from %s with TTL %d", +			     bfd->mh_ttl, satostr(&peer), ttlval); +			return 0; +		} +	} else if (bfd->local_ip.sa_sin.sin_family == AF_UNSPEC) { +		bfd->local_ip = local; +	} + +	/* +	 * If no interface was detected, save the interface where the +	 * packet came in. +	 */ +	if (bfd->ifindex == 0) +		bfd->ifindex = ptm_bfd_fetch_ifindex(port); + +	if ((bfd->discrs.remote_discr != 0) +	    && (bfd->discrs.remote_discr != ntohl(cp->discrs.my_discr))) { +		DLOG("My Discriminator mismatch in pkt from %s, Expected %d Got %d", +		     satostr(&peer), bfd->discrs.remote_discr, +		     ntohl(cp->discrs.my_discr)); +	} + +	bfd->discrs.remote_discr = ntohl(cp->discrs.my_discr); + +	/* If received the Final bit, the new values should take effect */ +	if (bfd->polling && BFD_GETFBIT(cp->flags)) { +		bfd->timers.desired_min_tx = bfd->new_timers.desired_min_tx; +		bfd->timers.required_min_rx = bfd->new_timers.required_min_rx; +		bfd->new_timers.desired_min_tx = 0; +		bfd->new_timers.required_min_rx = 0; +		bfd->polling = 0; +	} + +	if (!bfd->demand_mode) { +		/* Compute detect time */ +		bfd->detect_TO = cp->detect_mult +				 * ((bfd->timers.required_min_rx +				     > ntohl(cp->timers.desired_min_tx)) +					    ? bfd->timers.required_min_rx +					    : ntohl(cp->timers.desired_min_tx)); +		bfd->remote_detect_mult = cp->detect_mult; +	} else { +		ERRLOG("Unsupport BFD mode detected"); +	} + +	/* Save remote diagnostics before state switch. */ +	bfd->remote_diag = cp->diag & BFD_DIAGMASK; + +	/* State switch from section 6.8.6 */ +	old_state = bfd->ses_state; +	if (BFD_GETSTATE(cp->flags) == PTM_BFD_ADM_DOWN) { +		if (bfd->ses_state != PTM_BFD_DOWN) +			ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN); +	} else { +		switch (bfd->ses_state) { +		case (PTM_BFD_DOWN): +			if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT) +				ptm_bfd_ses_up(bfd); +			else if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN) +				bfd->ses_state = PTM_BFD_INIT; +			break; +		case (PTM_BFD_INIT): +			if (BFD_GETSTATE(cp->flags) == PTM_BFD_INIT +			    || BFD_GETSTATE(cp->flags) == PTM_BFD_UP) +				ptm_bfd_ses_up(bfd); +			break; +		case (PTM_BFD_UP): +			if (BFD_GETSTATE(cp->flags) == PTM_BFD_DOWN) +				ptm_bfd_ses_dn(bfd, BFD_DIAGNEIGHDOWN); +			break; +		} +	} + +	if (old_state != bfd->ses_state) { +		DLOG("BFD Sess %d [%s] Old State [%s] : New State [%s]", +		     bfd->discrs.my_discr, satostr(&peer), +		     state_list[old_state].str, state_list[bfd->ses_state].str); +	} + +	if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO)) { +		if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { +			if (!ntohl(cp->timers.required_min_echo)) { +				ptm_bfd_echo_stop(bfd, 1); +			} else { +				oldEchoXmt_TO = bfd->echo_xmt_TO; +				bfd->echo_xmt_TO = +					bfd->timers.required_min_echo; +				if (ntohl(cp->timers.required_min_echo) +				    > bfd->echo_xmt_TO) +					bfd->echo_xmt_TO = ntohl( +						cp->timers.required_min_echo); +				if (oldEchoXmt_TO != bfd->echo_xmt_TO) +					ptm_bfd_echo_start(bfd); +			} +		} else if (ntohl(cp->timers.required_min_echo)) { +			bfd->echo_xmt_TO = bfd->timers.required_min_echo; +			if (ntohl(cp->timers.required_min_echo) +			    > bfd->echo_xmt_TO) +				bfd->echo_xmt_TO = +					ntohl(cp->timers.required_min_echo); +			ptm_bfd_echo_start(bfd); +		} +	} + +	if (BFD_CHECK_FLAG(bfd->flags, BFD_SESS_FLAG_ECHO_ACTIVE)) { +		bfd->echo_xmt_TO = bfd->timers.required_min_echo; +		if (ntohl(cp->timers.required_min_echo) > bfd->echo_xmt_TO) +			bfd->echo_xmt_TO = ntohl(cp->timers.required_min_echo); +	} + +	/* Calculate new transmit time */ +	oldXmtTime = bfd->xmt_TO; +	bfd->xmt_TO = +		(bfd->timers.desired_min_tx > ntohl(cp->timers.required_min_rx)) +			? bfd->timers.desired_min_tx +			: ntohl(cp->timers.required_min_rx); + +	/* If transmit time has changed, and too much time until next xmt, +	 * restart +	 */ +	if (BFD_GETPBIT(cp->flags)) { +		ptm_bfd_xmt_TO(bfd, 1); +	} else if (oldXmtTime != bfd->xmt_TO) { +		/* XXX add some skid to this as well */ +		ptm_bfd_start_xmt_timer(bfd, false); +	} + +	if (!bfd->demand_mode) { +		/* Restart detection timer (packet received) */ +		bfd_recvtimer_update(bfd); +	} else { +		ERRLOG("Unsupport BFD mode detected"); +	} + +	/* +	 * Save the timers and state sent by the remote end +	 * for debugging and statistics. +	 */ +	if (BFD_GETFBIT(cp->flags)) { +		bfd->remote_timers.desired_min_tx = +			ntohl(cp->timers.desired_min_tx); +		bfd->remote_timers.required_min_rx = +			ntohl(cp->timers.required_min_rx); +		bfd->remote_timers.required_min_echo = +			ntohl(cp->timers.required_min_echo); + +		control_notify_config(BCM_NOTIFY_CONFIG_UPDATE, bfd); +	} + +	return 0; +} + + +/* + * Sockets creation. + */ + + +/* + * IPv4 sockets + */ +int bp_set_ttl(int sd) +{ +	if (setsockopt(sd, IPPROTO_IP, IP_TTL, &ttlval, sizeof(ttlval)) == -1) { +		log_warning("%s: setsockopt(IP_TTL): %s", __func__, +			    strerror(errno)); +		return -1; +	} + +	return 0; +} + +int bp_set_tos(int sd) +{ +	if (setsockopt(sd, IPPROTO_IP, IP_TOS, &tosval, sizeof(tosval)) == -1) { +		log_warning("%s: setsockopt(IP_TOS): %s", __func__, +			    strerror(errno)); +		return -1; +	} + +	return 0; +} + +static void bp_set_ipopts(int sd) +{ +	if (bp_set_ttl(sd) != 0) +		log_fatal("%s: TTL configuration failed", __func__); + +	if (setsockopt(sd, IPPROTO_IP, IP_RECVTTL, &rcvttl, sizeof(rcvttl)) +	    == -1) +		log_fatal("%s: setsockopt(IP_RECVTTL): %s", __func__, +			  strerror(errno)); + +#ifdef BFD_LINUX +	int pktinfo = BFD_PKT_INFO_VAL; +	/* Figure out address and interface to do the peer matching. */ +	if (setsockopt(sd, IPPROTO_IP, IP_PKTINFO, &pktinfo, sizeof(pktinfo)) +	    == -1) +		log_fatal("%s: setsockopt(IP_PKTINFO): %s", __func__, +			  strerror(errno)); +#endif /* BFD_LINUX */ +#ifdef BFD_BSD +	int yes = 1; + +	/* Find out our address for peer matching. */ +	if (setsockopt(sd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) == -1) +		log_fatal("%s: setsockopt(IP_RECVDSTADDR): %s", __func__, +			  strerror(errno)); + +	/* Find out interface where the packet came in. */ +	if (setsockopt_ifindex(AF_INET, sd, yes) == -1) +		log_fatal("%s: setsockopt_ipv4_ifindex: %s", __func__, +			  strerror(errno)); +#endif /* BFD_BSD */ +} + +static void bp_bind_ip(int sd, uint16_t port) +{ +	struct sockaddr_in sin; + +	memset(&sin, 0, sizeof(sin)); +	sin.sin_family = AF_INET; +	sin.sin_addr.s_addr = htonl(INADDR_ANY); +	sin.sin_port = htons(port); +	if (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) == -1) +		log_fatal("%s: bind: %s", __func__, strerror(errno)); +} + +int bp_udp_shop(void) +{ +	int sd; + +	sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); +	if (sd == -1) +		log_fatal("%s: socket: %s", __func__, strerror(errno)); + +	bp_set_ipopts(sd); +	bp_bind_ip(sd, BFD_DEFDESTPORT); + +	return sd; +} + +int bp_udp_mhop(void) +{ +	int sd; + +	sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); +	if (sd == -1) +		log_fatal("%s: socket: %s", __func__, strerror(errno)); + +	bp_set_ipopts(sd); +	bp_bind_ip(sd, BFD_DEF_MHOP_DEST_PORT); + +	return sd; +} + +int bp_peer_socket(struct bfd_peer_cfg *bpc) +{ +	int sd, pcount; +	struct sockaddr_in sin; +	static int srcPort = BFD_SRCPORTINIT; + +	sd = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); +	if (sd == -1) +		return -1; + +	if (!bpc->bpc_has_vxlan) { +		/* Set TTL to 255 for all transmitted packets */ +		if (bp_set_ttl(sd) != 0) { +			close(sd); +			return -1; +		} +	} + +	/* Set TOS to CS6 for all transmitted packets */ +	if (bp_set_tos(sd) != 0) { +		close(sd); +		return -1; +	} + +	/* dont bind-to-device incase of vxlan */ +	if (!bpc->bpc_has_vxlan && bpc->bpc_has_localif) { +		if (bp_bind_dev(sd, bpc->bpc_localif) != 0) { +			close(sd); +			return -1; +		} +	} else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) { +		if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) { +			close(sd); +			return -1; +		} +	} + +	/* Find an available source port in the proper range */ +	memset(&sin, 0, sizeof(sin)); +	sin = bpc->bpc_local.sa_sin; +	sin.sin_family = AF_INET; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +	sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +	if (bpc->bpc_mhop || bpc->bpc_has_vxlan) +		sin.sin_addr = bpc->bpc_local.sa_sin.sin_addr; +	else +		sin.sin_addr.s_addr = INADDR_ANY; + +	pcount = 0; +	do { +		if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { +			/* Searched all ports, none available */ +			ERRLOG("Can't find source port for new session: %s", +			       strerror(errno)); +			close(sd); +			return -1; +		} +		if (srcPort >= BFD_SRCPORTMAX) +			srcPort = BFD_SRCPORTINIT; +		sin.sin_port = htons(srcPort++); +	} while (bind(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0); + +	return sd; +} + + +/* + * IPv6 sockets + */ + +int bp_peer_socketv6(struct bfd_peer_cfg *bpc) +{ +	int sd, pcount, ifindex; +	struct sockaddr_in6 sin6; +	static int srcPort = BFD_SRCPORTINIT; + +	sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); +	if (sd == -1) +		return -1; + +	if (!bpc->bpc_has_vxlan) { +		/* Set TTL to 255 for all transmitted packets */ +		if (bp_set_ttlv6(sd) != 0) { +			close(sd); +			return -1; +		} +	} + +	/* Set TOS to CS6 for all transmitted packets */ +	if (bp_set_tosv6(sd) != 0) { +		close(sd); +		return -1; +	} + +	/* Find an available source port in the proper range */ +	memset(&sin6, 0, sizeof(sin6)); +	sin6.sin6_family = AF_INET6; +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +	sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +	sin6 = bpc->bpc_local.sa_sin6; +	if (sin6.sin6_family != AF_INET6) { +#if 0 /* XXX what is this? */ +		ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); +		if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) +			sin6.sin6_scope_id = ifindex; +#endif +	} else if (bpc->bpc_has_localif) { +		ifindex = ptm_bfd_fetch_ifindex(bpc->bpc_localif); +		sin6.sin6_scope_id = ifindex; +	} + +	if (bpc->bpc_has_localif) { +		if (bp_bind_dev(sd, bpc->bpc_localif) != 0) { +			close(sd); +			return -1; +		} +	} else if (bpc->bpc_mhop && bpc->bpc_has_vrfname) { +		if (bp_bind_dev(sd, bpc->bpc_vrfname) != 0) { +			close(sd); +			return -1; +		} +	} + +	pcount = 0; +	do { +		if ((++pcount) > (BFD_SRCPORTMAX - BFD_SRCPORTINIT)) { +			/* Searched all ports, none available */ +			ERRLOG("Can't find source port for new session: %s", +			       strerror(errno)); +			close(sd); +			return -1; +		} +		if (srcPort >= BFD_SRCPORTMAX) +			srcPort = BFD_SRCPORTINIT; +		sin6.sin6_port = htons(srcPort++); +	} while (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0); + +	return sd; +} + +int bp_set_ttlv6(int sd) +{ +	if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval, +		       sizeof(ttlval)) +	    == -1) { +		log_warning("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__, +			    strerror(errno)); +		return -1; +	} + +	return 0; +} + +int bp_set_tosv6(int sd) +{ +	if (setsockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, &tosval, sizeof(tosval)) +	    == -1) { +		log_warning("%s: setsockopt(IPV6_TCLASS): %s", __func__, +			    strerror(errno)); +		return -1; +	} + +	return 0; +} + +static void bp_set_ipv6opts(int sd) +{ +	static int ipv6_pktinfo = BFD_IPV6_PKT_INFO_VAL; +	static int ipv6_only = BFD_IPV6_ONLY_VAL; + +	if (setsockopt(sd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttlval, +		       sizeof(ttlval)) +	    == -1) +		log_fatal("%s: setsockopt(IPV6_UNICAST_HOPS): %s", __func__, +			  strerror(errno)); + +	if (setsockopt_ipv6_hoplimit(sd, rcvttl) == -1) +		log_fatal("%s: setsockopt(IPV6_HOPLIMIT): %s", __func__, +			  strerror(errno)); + +	if (setsockopt_ipv6_pktinfo(sd, ipv6_pktinfo) == -1) +		log_fatal("%s: setsockopt(IPV6_PKTINFO): %s", __func__, +			  strerror(errno)); + +	if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, +		       sizeof(ipv6_only)) +	    == -1) +		log_fatal("%s: setsockopt(IPV6_V6ONLY): %s", __func__, +			  strerror(errno)); +} + +static void bp_bind_ipv6(int sd, uint16_t port) +{ +	struct sockaddr_in6 sin6; + +	memset(&sin6, 0, sizeof(sin6)); +	sin6.sin6_family = AF_INET6; +	sin6.sin6_addr = in6addr_any; +	sin6.sin6_port = htons(port); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +	sin6.sin6_len = sizeof(sin6); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +	if (bind(sd, (struct sockaddr *)&sin6, sizeof(sin6)) == -1) +		log_fatal("%s: bind: %s", __func__, strerror(errno)); +} + +int bp_udp6_shop(void) +{ +	int sd; + +	sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); +	if (sd == -1) +		log_fatal("%s: socket: %s", __func__, strerror(errno)); + +	bp_set_ipv6opts(sd); +	bp_bind_ipv6(sd, BFD_DEFDESTPORT); + +	return sd; +} + +int bp_udp6_mhop(void) +{ +	int sd; + +	sd = socket(AF_INET6, SOCK_DGRAM, PF_UNSPEC); +	if (sd == -1) +		log_fatal("%s: socket: %s", __func__, strerror(errno)); + +	bp_set_ipv6opts(sd); +	bp_bind_ipv6(sd, BFD_DEF_MHOP_DEST_PORT); + +	return sd; +} diff --git a/bfdd/bfdctl.h b/bfdd/bfdctl.h new file mode 100644 index 0000000000..940efd1614 --- /dev/null +++ b/bfdd/bfdctl.h @@ -0,0 +1,160 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * bfdctl.h: all BFDd control socket protocol definitions. + * + * Authors + * ------- + * Rafael Zalamena <rzalamena@opensourcerouting.org> + */ + +#ifndef _BFDCTRL_H_ +#define _BFDCTRL_H_ + +#include <netinet/in.h> + +#include <stdbool.h> +#include <stdint.h> + +/* + * Auxiliary definitions + */ +struct sockaddr_any { +	union { +		struct sockaddr_in sa_sin; +		struct sockaddr_in6 sa_sin6; +	}; +}; + +#ifndef MAXNAMELEN +#define MAXNAMELEN 32 +#endif + +#define BPC_DEF_DETECTMULTIPLIER 3 +#define BPC_DEF_RECEIVEINTERVAL 300  /* milliseconds */ +#define BPC_DEF_TRANSMITINTERVAL 300 /* milliseconds */ +#define BPC_DEF_ECHOINTERVAL 50      /* milliseconds */ + +/* Peer status */ +enum bfd_peer_status { +	BPS_SHUTDOWN = 0, /* == PTM_BFD_ADM_DOWN, "adm-down" */ +	BPS_DOWN = 1,     /* == PTM_BFD_DOWN, "down" */ +	BPS_INIT = 2,     /* == PTM_BFD_INIT, "init" */ +	BPS_UP = 3,       /* == PTM_BFD_UP, "up" */ +}; + +struct bfd_peer_cfg { +	bool bpc_mhop; +	bool bpc_ipv4; +	struct sockaddr_any bpc_peer; +	struct sockaddr_any bpc_local; + +	bool bpc_has_label; +	char bpc_label[MAXNAMELEN]; + +	bool bpc_has_vxlan; +	unsigned int bpc_vxlan; + +	bool bpc_has_localif; +	char bpc_localif[MAXNAMELEN + 1]; + +	bool bpc_has_vrfname; +	char bpc_vrfname[MAXNAMELEN + 1]; + +	bool bpc_has_detectmultiplier; +	uint8_t bpc_detectmultiplier; + +	bool bpc_has_recvinterval; +	uint64_t bpc_recvinterval; + +	bool bpc_has_txinterval; +	uint64_t bpc_txinterval; + +	bool bpc_has_echointerval; +	uint64_t bpc_echointerval; + +	bool bpc_echo; +	bool bpc_createonly; +	bool bpc_shutdown; + +	/* Status information */ +	enum bfd_peer_status bpc_bps; +	uint32_t bpc_id; +	uint32_t bpc_remoteid; +	uint8_t bpc_diag; +	uint8_t bpc_remotediag; +	uint8_t bpc_remote_detectmultiplier; +	uint64_t bpc_remote_recvinterval; +	uint64_t bpc_remote_txinterval; +	uint64_t bpc_remote_echointerval; +	uint64_t bpc_lastevent; +}; + + +/* + * Protocol definitions + */ +enum bc_msg_version { +	BMV_VERSION_1 = 1, +}; + +enum bc_msg_type { +	BMT_RESPONSE = 1, +	BMT_REQUEST_ADD = 2, +	BMT_REQUEST_DEL = 3, +	BMT_NOTIFY = 4, +	BMT_NOTIFY_ADD = 5, +	BMT_NOTIFY_DEL = 6, +}; + +/* Notify flags to use with bcm_notify. */ +#define BCM_NOTIFY_ALL ((uint64_t)-1) +#define BCM_NOTIFY_PEER_STATE (1ULL << 0) +#define BCM_NOTIFY_CONFIG (1ULL << 1) +#define BCM_NOTIFY_NONE 0 + +/* Response 'status' definitions. */ +#define BCM_RESPONSE_OK "ok" +#define BCM_RESPONSE_ERROR "error" + +/* Notify operation. */ +#define BCM_NOTIFY_PEER_STATUS "status" +#define BCM_NOTIFY_CONFIG_ADD "add" +#define BCM_NOTIFY_CONFIG_DELETE "delete" +#define BCM_NOTIFY_CONFIG_UPDATE "update" + +/* Notification special ID. */ +#define BCM_NOTIFY_ID 0 + +struct bfd_control_msg { +	/* Total length without the header. */ +	uint32_t bcm_length; +	/* +	 * Message request/response id. +	 * All requests will have a correspondent response with the +	 * same id. +	 */ +	uint16_t bcm_id; +	/* Message type. */ +	uint8_t bcm_type; +	/* Message version. */ +	uint8_t bcm_ver; +	/* Message payload. */ +	uint8_t bcm_data[0]; +}; + +#endif diff --git a/bfdd/bfdd.c b/bfdd/bfdd.c new file mode 100644 index 0000000000..3313c8137c --- /dev/null +++ b/bfdd/bfdd.c @@ -0,0 +1,234 @@ +/* + * BFD daemon code + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING.  If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "bfd.h" +#include "lib/version.h" +#include "lib/libfrr.h" + +/* + * FRR related code. + */ +DEFINE_MGROUP(BFDD, "Bidirectional Forwarding Detection Daemon"); +DEFINE_MTYPE(BFDD, BFDD_TMP, "short-lived temporary memory"); +DEFINE_MTYPE(BFDD, BFDD_CONFIG, "long-lived configuration memory"); +DEFINE_MTYPE(BFDD, BFDD_LABEL, "long-lived label memory"); +DEFINE_MTYPE(BFDD, BFDD_CONTROL, "long-lived control socket memory"); +DEFINE_MTYPE(BFDD, BFDD_NOTIFICATION, "short-lived control notification data"); + +/* Master of threads. */ +struct thread_master *master; + +/* BFDd privileges */ +static zebra_capabilities_t _caps_p[] = {ZCAP_BIND}; + +struct zebra_privs_t bfdd_privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) +	.user = FRR_USER, +	.group = FRR_GROUP, +#endif +#if defined(VTY_GROUP) +	.vty_group = VTY_GROUP, +#endif +	.caps_p = _caps_p, +	.cap_num_p = array_size(_caps_p), +	.cap_num_i = 0, +}; + +void socket_close(int *s) +{ +	if (*s <= 0) +		return; + +	if (close(*s) != 0) +		log_error("%s: close(%d): (%d) %s", __func__, *s, errno, +			  strerror(errno)); + +	*s = -1; +} + +static void sigusr1_handler(void) +{ +	zlog_rotate(); +} + +static void sigterm_handler(void) +{ +	/* Signalize shutdown. */ +	frr_early_fini(); + +	/* Shutdown controller to avoid receiving anymore commands. */ +	control_shutdown(); + +	/* Shutdown and free all protocol related memory. */ +	bfd_shutdown(); + +	/* Close all descriptors. */ +	socket_close(&bglobal.bg_echo); +	socket_close(&bglobal.bg_shop); +	socket_close(&bglobal.bg_mhop); +	socket_close(&bglobal.bg_shop6); +	socket_close(&bglobal.bg_mhop6); +	socket_close(&bglobal.bg_vxlan); + +	/* Terminate and free() FRR related memory. */ +	frr_fini(); + +	exit(0); +} + +static struct quagga_signal_t bfd_signals[] = { +	{ +		.signal = SIGUSR1, +		.handler = &sigusr1_handler, +	}, +	{ +		.signal = SIGTERM, +		.handler = &sigterm_handler, +	}, +	{ +		.signal = SIGINT, +		.handler = &sigterm_handler, +	}, +}; + +FRR_DAEMON_INFO(bfdd, BFD, .vty_port = 2617, +		.proghelp = "Implementation of the BFD protocol.", +		.signals = bfd_signals, .n_signals = array_size(bfd_signals), +		.privs = &bfdd_privs) + +#define OPTION_CTLSOCK 1001 +static struct option longopts[] = { +	{"bfdctl", required_argument, NULL, OPTION_CTLSOCK}, +	{0} +}; + + +/* + * BFD daemon related code. + */ +struct bfd_global bglobal; + +struct bfd_diag_str_list diag_list[] = { +	{.str = "NeighDown", .type = BFD_DIAGNEIGHDOWN}, +	{.str = "DetectTime", .type = BFD_DIAGDETECTTIME}, +	{.str = "AdminDown", .type = BFD_DIAGADMINDOWN}, +	{.str = NULL}, +}; + +struct bfd_state_str_list state_list[] = { +	{.str = "AdminDown", .type = PTM_BFD_ADM_DOWN}, +	{.str = "Down", .type = PTM_BFD_DOWN}, +	{.str = "Init", .type = PTM_BFD_INIT}, +	{.str = "Up", .type = PTM_BFD_UP}, +	{.str = NULL}, +}; + + +static void bg_init(void) +{ +	TAILQ_INIT(&bglobal.bg_bcslist); + +	bglobal.bg_shop = bp_udp_shop(); +	bglobal.bg_mhop = bp_udp_mhop(); +	bglobal.bg_shop6 = bp_udp6_shop(); +	bglobal.bg_mhop6 = bp_udp6_mhop(); +	bglobal.bg_echo = ptm_bfd_echo_sock_init(); +	bglobal.bg_vxlan = ptm_bfd_vxlan_sock_init(); +} + +int main(int argc, char *argv[]) +{ +	const char *ctl_path = BFDD_CONTROL_SOCKET; +	int opt; + +	frr_preinit(&bfdd_di, argc, argv); +	frr_opt_add("", longopts, +		    "      --bfdctl       Specify bfdd control socket\n"); + +	while (true) { +		opt = frr_getopt(argc, argv, NULL); +		if (opt == EOF) +			break; + +		switch (opt) { +		case OPTION_CTLSOCK: +			ctl_path = optarg; +			break; + +		default: +			frr_help_exit(1); +			break; +		} +	} + +#if 0 /* TODO add support for JSON configuration files. */ +	parse_config(conf); +#endif + +	/* Initialize logging API. */ +	log_init(1, BLOG_DEBUG, &bfdd_di); + +	/* Initialize system sockets. */ +	bg_init(); + +	/* Initialize control socket. */ +	control_init(ctl_path); + +	/* Initialize FRR infrastructure. */ +	master = frr_init(); + +	/* Initialize BFD data structures. */ +	bfd_initialize(); + +	/* Add descriptors to the event loop. */ +	thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop, +			&bglobal.bg_ev[0]); +	thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop, +			&bglobal.bg_ev[1]); +	thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_shop6, +			&bglobal.bg_ev[2]); +	thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_mhop6, +			&bglobal.bg_ev[3]); +	thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_echo, +			&bglobal.bg_ev[4]); +#if 0 /* TODO VxLAN support. */ +	thread_add_read(master, bfd_recv_cb, NULL, bglobal.bg_vxlan, +			&bglobal.bg_ev[5]); +#endif +	thread_add_read(master, control_accept, NULL, bglobal.bg_csock, +			&bglobal.bg_csockev); + +	/* read configuration file and daemonize  */ +	frr_config_fork(); + +	frr_run(master); +	/* NOTREACHED */ + +	return 0; +} diff --git a/bfdd/bfdd.conf.sample b/bfdd/bfdd.conf.sample new file mode 100644 index 0000000000..9981e262bc --- /dev/null +++ b/bfdd/bfdd.conf.sample @@ -0,0 +1,5 @@ +password zebra +! +log stdout +! +line vty diff --git a/bfdd/bsd.c b/bfdd/bsd.c new file mode 100644 index 0000000000..3d196dfb9f --- /dev/null +++ b/bfdd/bsd.c @@ -0,0 +1,290 @@ +/* + * *BSD specific code + * + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING.  If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#ifdef BFD_BSD + +#include <net/if.h> +#include <net/if_types.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <ifaddrs.h> + +#include "bfd.h" + +/* + * Prototypes + */ +static const char *sockaddr_to_string(const void *sv, char *buf, size_t buflen); + +/* + * Definitions. + */ +static const char *sockaddr_to_string(const void *sv, char *buf, size_t buflen) +{ +	const struct sockaddr *sa = sv; +	const struct sockaddr_in *sin = sv; +	const struct sockaddr_in6 *sin6 = sv; +	int unknown = 1; + +	switch (sa->sa_family) { +	case AF_INET: +		if (inet_ntop(AF_INET, &sin->sin_addr, buf, buflen) != NULL) +			unknown = 0; +		break; + +	case AF_INET6: +		if (inet_ntop(AF_INET6, &sin6->sin6_addr, buf, buflen) != NULL) +			unknown = 0; +		break; +	} +	if (unknown == 0) +		return buf; + +	snprintf(buf, buflen, "unknown (af=%d)", sa->sa_family); +	return buf; +} + +int ptm_bfd_fetch_ifindex(const char *ifname) +{ +	return if_nametoindex(ifname); +} + +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac) +{ +	struct ifaddrs *ifap, *ifa; +	struct if_data *ifi; +	struct sockaddr_dl *sdl; +	size_t maclen; + +	/* Always clean the target, zeroed macs mean failure. */ +	memset(mac, 0, ETHERNET_ADDRESS_LENGTH); + +	if (getifaddrs(&ifap) != 0) +		return; + +	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { +		/* Find interface with that name. */ +		if (strcmp(ifa->ifa_name, ifname) != 0) +			continue; +		/* Skip non link addresses. We want the MAC address. */ +		if (ifa->ifa_addr->sa_family != AF_LINK) +			continue; + +		sdl = (struct sockaddr_dl *)ifa->ifa_addr; +		ifi = (struct if_data *)ifa->ifa_data; +		/* Skip non ethernet related data. */ +		if (ifi->ifi_type != IFT_ETHER) +			continue; + +		if (sdl->sdl_alen != ETHERNET_ADDRESS_LENGTH) +			log_warning("%s:%d mac address length %d (expected %d)", +				    __func__, __LINE__, sdl->sdl_alen, +				    ETHERNET_ADDRESS_LENGTH); + +		maclen = (sdl->sdl_alen > ETHERNET_ADDRESS_LENGTH) +				 ? ETHERNET_ADDRESS_LENGTH +				 : sdl->sdl_alen; +		memcpy(mac, LLADDR(sdl), maclen); +		break; +	} + +	freeifaddrs(ifap); +} + + +/* Was _fetch_portname_from_ifindex() */ +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen) +{ +	char ifname_tmp[IF_NAMESIZE]; + +	/* Set ifname to empty to signalize failures. */ +	memset(ifname, 0, ifnamelen); + +	if (if_indextoname(ifindex, ifname_tmp) == NULL) +		return; + +	if (strlcpy(ifname, ifname_tmp, ifnamelen) > ifnamelen) +		log_warning("%s:%d interface name truncated", __func__, +			    __LINE__); +} + +int ptm_bfd_echo_sock_init(void) +{ +	int s, ttl, yes = 1; +	struct sockaddr_in sin; + +	s = socket(AF_INET, SOCK_DGRAM, PF_UNSPEC); +	if (s == -1) { +		ERRLOG("%s: socket: %s", __func__, strerror(errno)); +		return -1; +	} + +	memset(&sin, 0, sizeof(sin)); +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN +	/* OmniOS doesn't have this field, but uses this code. */ +	sin.sin_len = sizeof(sin); +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +	sin.sin_family = AF_INET; +	sin.sin_port = htons(3785); +	if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) { +		log_error("%s: bind: %s", __func__, strerror(errno)); +		close(s); +		return -1; +	} + +	if (setsockopt(s, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) == -1) { +		log_error("%s: setsockopt(IP_RECVTTL): %s", __func__, +			  strerror(errno)); +		close(s); +		return -1; +	} + +	ttl = BFD_TTL_VAL; +	if (setsockopt(s, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) { +		log_error("%s: setsockopt(IP_TTL): %s", __func__, +			  strerror(errno)); +		close(s); +		return -1; +	} + +	return s; +} + +ssize_t bsd_echo_sock_read(int sd, uint8_t *buf, ssize_t *buflen, +			   struct sockaddr_storage *ss, socklen_t *sslen, +			   uint8_t *ttl, uint32_t *id) +{ +	struct cmsghdr *cmsg; +	struct bfd_echo_pkt *bep; +	ssize_t readlen; +	struct iovec iov; +	struct msghdr msg; +	uint8_t msgctl[255]; +	char errbuf[255]; + +	/* Prepare socket read. */ +	memset(ss, 0, sizeof(*ss)); +	memset(&msg, 0, sizeof(msg)); +	iov.iov_base = buf; +	iov.iov_len = *buflen; +	msg.msg_iov = &iov; +	msg.msg_iovlen = 1; +	msg.msg_control = msgctl; +	msg.msg_controllen = sizeof(msgctl); +	msg.msg_name = ss; +	msg.msg_namelen = *sslen; + +	/* Read the socket and treat errors. */ +	readlen = recvmsg(sd, &msg, 0); +	if (readlen == 0) { +		log_error("%s: recvmsg: socket closed", __func__); +		return -1; +	} +	if (readlen == -1) { +		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) +			return -1; + +		log_error("%s: recvmsg: (%d) %s", __func__, errno, +			  strerror(errno)); +		return -1; +	} +	/* Short packet, better not risk reading it. */ +	if (readlen < (ssize_t)sizeof(*bep)) { +		log_warning("%s: short packet (%ld of %d) from %s", __func__, +			    readlen, sizeof(*bep), +			    sockaddr_to_string(ss, errbuf, sizeof(errbuf))); +		return -1; +	} +	*buflen = readlen; + +	/* Read TTL information. */ +	*ttl = 0; +	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; +	     cmsg = CMSG_NXTHDR(&msg, cmsg)) { +		if (cmsg->cmsg_level != IPPROTO_IP) +			continue; +		if (cmsg->cmsg_type != IP_RECVTTL) +			continue; + +		*ttl = *(uint8_t *)CMSG_DATA(cmsg); +		break; +	} +	if (*ttl == 0) { +		log_debug("%s: failed to read TTL", __func__); +		return -1; +	} + +	/* Read my discriminator from BFD Echo packet. */ +	bep = (struct bfd_echo_pkt *)buf; +	*id = bep->my_discr; +	if (*id == 0) { +		log_debug("%s: invalid packet discriminator from: %s", __func__, +			  sockaddr_to_string(ss, errbuf, sizeof(errbuf))); +		return -1; +	} + +	/* Set the returned sockaddr new length. */ +	*sslen = msg.msg_namelen; + +	return 0; +} + +int ptm_bfd_vxlan_sock_init(void) +{ +	/* TODO: not supported yet. */ +	return -1; +} + +int bp_bind_dev(int sd, const char *dev) +{ +	/* +	 * *BSDs don't support `SO_BINDTODEVICE`, instead you must +	 * manually specify the main address of the interface or use +	 * BPF on the socket descriptor. +	 */ +	return 0; +} + +uint16_t udp4_checksum(struct ip *ip, uint8_t *buf, int len) +{ +	char *ptr; +	struct udp_psuedo_header pudp_hdr; +	uint16_t csum; + +	pudp_hdr.saddr = ip->ip_src.s_addr; +	pudp_hdr.daddr = ip->ip_dst.s_addr; +	pudp_hdr.reserved = 0; +	pudp_hdr.protocol = ip->ip_p; +	pudp_hdr.len = htons(len); + +	ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len); +	memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN); +	memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len); + +	csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len); +	XFREE(MTYPE_BFDD_TMP, ptr); +	return csum; +} + +#endif /* BFD_BSD */ diff --git a/bfdd/config.c b/bfdd/config.c new file mode 100644 index 0000000000..0e0d8b7d70 --- /dev/null +++ b/bfdd/config.c @@ -0,0 +1,606 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * config.c: implements the BFD daemon configuration handling. + * + * Authors + * ------- + * Rafael Zalamena <rzalamena@opensourcerouting.org> + */ + +#include <zebra.h> + +#include <string.h> + +#include "lib/json.h" + +#include "bfd.h" + +/* + * Definitions + */ +enum peer_list_type { +	PLT_IPV4, +	PLT_IPV6, +	PLT_LABEL, +}; + + +/* + * Prototypes + */ +static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg); +static int parse_list(struct json_object *jo, enum peer_list_type plt, +		      bpc_handle h, void *arg); +static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc); +static int parse_peer_label_config(struct json_object *jo, +				   struct bfd_peer_cfg *bpc); + +static int config_add(struct bfd_peer_cfg *bpc, void *arg); +static int config_del(struct bfd_peer_cfg *bpc, void *arg); + +static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs); + + +/* + * Implementation + */ +static int config_add(struct bfd_peer_cfg *bpc, +		      void *arg __attribute__((unused))) +{ +	return ptm_bfd_sess_new(bpc) == NULL; +} + +static int config_del(struct bfd_peer_cfg *bpc, +		      void *arg __attribute__((unused))) +{ +	return ptm_bfd_ses_del(bpc) != 0; +} + +static int parse_config_json(struct json_object *jo, bpc_handle h, void *arg) +{ +	const char *key, *sval; +	struct json_object *jo_val; +	struct json_object_iterator joi, join; +	int error = 0; + +	JSON_FOREACH (jo, joi, join) { +		key = json_object_iter_peek_name(&joi); +		jo_val = json_object_iter_peek_value(&joi); + +		if (strcmp(key, "ipv4") == 0) { +			error += parse_list(jo_val, PLT_IPV4, h, arg); +		} else if (strcmp(key, "ipv6") == 0) { +			error += parse_list(jo_val, PLT_IPV6, h, arg); +		} else if (strcmp(key, "label") == 0) { +			error += parse_list(jo_val, PLT_LABEL, h, arg); +		} else { +			sval = json_object_get_string(jo_val); +			log_warning("%s:%d invalid configuration: %s", __func__, +				    __LINE__, sval); +			error++; +		} +	} + +	/* +	 * Our callers never call free() on json_object and only expect +	 * the return value, so lets free() it here. +	 */ +	json_object_put(jo); + +	return error; +} + +int parse_config(const char *fname) +{ +	struct json_object *jo; + +	jo = json_object_from_file(fname); +	if (jo == NULL) +		return -1; + +	return parse_config_json(jo, config_add, NULL); +} + +static int parse_list(struct json_object *jo, enum peer_list_type plt, +		      bpc_handle h, void *arg) +{ +	struct json_object *jo_val; +	struct bfd_peer_cfg bpc; +	int allen, idx; +	int error = 0, result; + +	allen = json_object_array_length(jo); +	for (idx = 0; idx < allen; idx++) { +		jo_val = json_object_array_get_idx(jo, idx); + +		/* Set defaults. */ +		memset(&bpc, 0, sizeof(bpc)); +		bpc.bpc_detectmultiplier = BFD_DEFDETECTMULT; +		bpc.bpc_recvinterval = BFD_DEFREQUIREDMINRX; +		bpc.bpc_txinterval = BFD_DEFDESIREDMINTX; +		bpc.bpc_echointerval = BFD_DEF_REQ_MIN_ECHO; + +		switch (plt) { +		case PLT_IPV4: +			log_debug("ipv4 peers %d:", allen); +			bpc.bpc_ipv4 = true; +			break; +		case PLT_IPV6: +			log_debug("ipv6 peers %d:", allen); +			bpc.bpc_ipv4 = false; +			break; +		case PLT_LABEL: +			log_debug("label peers %d:", allen); +			if (parse_peer_label_config(jo_val, &bpc) != 0) { +				error++; +				continue; +			} +			break; + +		default: +			error++; +			log_error("%s:%d: unsupported peer type", __func__, +				  __LINE__); +			break; +		} + +		result = parse_peer_config(jo_val, &bpc); +		error += result; +		if (result == 0) +			error += (h(&bpc, arg) != 0); +	} + +	return error; +} + +static int parse_peer_config(struct json_object *jo, struct bfd_peer_cfg *bpc) +{ +	const char *key, *sval; +	struct json_object *jo_val; +	struct json_object_iterator joi, join; +	int family_type = (bpc->bpc_ipv4) ? AF_INET : AF_INET6; +	int error = 0; + +	log_debug("\tpeer: %s", bpc->bpc_ipv4 ? "ipv4" : "ipv6"); + +	JSON_FOREACH (jo, joi, join) { +		key = json_object_iter_peek_name(&joi); +		jo_val = json_object_iter_peek_value(&joi); + +		if (strcmp(key, "multihop") == 0) { +			bpc->bpc_mhop = json_object_get_boolean(jo_val); +			log_debug("\tmultihop: %s", +				  bpc->bpc_mhop ? "true" : "false"); +		} else if (strcmp(key, "peer-address") == 0) { +			sval = json_object_get_string(jo_val); +			if (strtosa(sval, &bpc->bpc_peer) != 0 +			    || bpc->bpc_peer.sa_sin.sin_family != family_type) { +				log_info( +					"%s:%d failed to parse peer-address '%s'", +					__func__, __LINE__, sval); +				error++; +			} +			log_debug("\tpeer-address: %s", sval); +		} else if (strcmp(key, "local-address") == 0) { +			sval = json_object_get_string(jo_val); +			if (strtosa(sval, &bpc->bpc_local) != 0 +			    || bpc->bpc_local.sa_sin.sin_family +				       != family_type) { +				log_info( +					"%s:%d failed to parse local-address '%s'", +					__func__, __LINE__, sval); +				error++; +			} +			log_debug("\tlocal-address: %s", sval); +		} else if (strcmp(key, "local-interface") == 0) { +			bpc->bpc_has_localif = true; +			sval = json_object_get_string(jo_val); +			if (strlcpy(bpc->bpc_localif, sval, +				    sizeof(bpc->bpc_localif)) +			    > sizeof(bpc->bpc_localif)) { +				log_debug("\tlocal-interface: %s (truncated)"); +				error++; +			} else { +				log_debug("\tlocal-interface: %s", sval); +			} +		} else if (strcmp(key, "vxlan") == 0) { +			bpc->bpc_vxlan = json_object_get_int64(jo_val); +			bpc->bpc_has_vxlan = true; +			log_debug("\tvxlan: %ld", bpc->bpc_vxlan); +		} else if (strcmp(key, "vrf-name") == 0) { +			bpc->bpc_has_vrfname = true; +			sval = json_object_get_string(jo_val); +			if (strlcpy(bpc->bpc_vrfname, sval, +				    sizeof(bpc->bpc_vrfname)) +			    > sizeof(bpc->bpc_vrfname)) { +				log_debug("\tvrf-name: %s (truncated)", sval); +				error++; +			} else { +				log_debug("\tvrf-name: %s", sval); +			} +		} else if (strcmp(key, "detect-multiplier") == 0) { +			bpc->bpc_detectmultiplier = +				json_object_get_int64(jo_val); +			bpc->bpc_has_detectmultiplier = true; +			log_debug("\tdetect-multiplier: %llu", +				  bpc->bpc_detectmultiplier); +		} else if (strcmp(key, "receive-interval") == 0) { +			bpc->bpc_recvinterval = json_object_get_int64(jo_val); +			bpc->bpc_has_recvinterval = true; +			log_debug("\treceive-interval: %llu", +				  bpc->bpc_recvinterval); +		} else if (strcmp(key, "transmit-interval") == 0) { +			bpc->bpc_txinterval = json_object_get_int64(jo_val); +			bpc->bpc_has_txinterval = true; +			log_debug("\ttransmit-interval: %llu", +				  bpc->bpc_txinterval); +		} else if (strcmp(key, "echo-interval") == 0) { +			bpc->bpc_echointerval = json_object_get_int64(jo_val); +			bpc->bpc_has_echointerval = true; +			log_debug("\techo-interval: %llu", +				  bpc->bpc_echointerval); +		} else if (strcmp(key, "create-only") == 0) { +			bpc->bpc_createonly = json_object_get_boolean(jo_val); +			log_debug("\tcreate-only: %s", +				  bpc->bpc_createonly ? "true" : "false"); +		} else if (strcmp(key, "shutdown") == 0) { +			bpc->bpc_shutdown = json_object_get_boolean(jo_val); +			log_debug("\tshutdown: %s", +				  bpc->bpc_shutdown ? "true" : "false"); +		} else if (strcmp(key, "echo-mode") == 0) { +			bpc->bpc_echo = json_object_get_boolean(jo_val); +			log_debug("\techo-mode: %s", +				  bpc->bpc_echo ? "true" : "false"); +		} else if (strcmp(key, "label") == 0) { +			bpc->bpc_has_label = true; +			sval = json_object_get_string(jo_val); +			if (strlcpy(bpc->bpc_label, sval, +				    sizeof(bpc->bpc_label)) +			    > sizeof(bpc->bpc_label)) { +				log_debug("\tlabel: %s (truncated)", sval); +				error++; +			} else { +				log_debug("\tlabel: %s", sval); +			} +		} else { +			sval = json_object_get_string(jo_val); +			log_warning("%s:%d invalid configuration: '%s: %s'", +				    __func__, __LINE__, key, sval); +			error++; +		} +	} + +	if (bpc->bpc_peer.sa_sin.sin_family == 0) { +		log_debug("%s:%d no peer address provided", __func__, __LINE__); +		error++; +	} + +	return error; +} + +static int parse_peer_label_config(struct json_object *jo, +				   struct bfd_peer_cfg *bpc) +{ +	struct peer_label *pl; +	struct json_object *label; +	const char *sval; + +	/* Get label and translate it to BFD daemon key. */ +	if (!json_object_object_get_ex(jo, "label", &label)) +		return 1; + +	sval = json_object_get_string(label); + +	pl = pl_find(sval); +	if (pl == NULL) +		return 1; + +	log_debug("\tpeer-label: %s", sval); + +	/* Translate the label into BFD address keys. */ +	bpc->bpc_ipv4 = !BFD_CHECK_FLAG(pl->pl_bs->flags, BFD_SESS_FLAG_IPV6); +	bpc->bpc_mhop = BFD_CHECK_FLAG(pl->pl_bs->flags, BFD_SESS_FLAG_MH); +	if (bpc->bpc_mhop) { +		bpc->bpc_peer = pl->pl_bs->mhop.peer; +		bpc->bpc_local = pl->pl_bs->mhop.local; +		if (pl->pl_bs->mhop.vrf_name[0]) { +			bpc->bpc_has_vrfname = true; +			strlcpy(bpc->bpc_vrfname, pl->pl_bs->mhop.vrf_name, +				sizeof(bpc->bpc_vrfname)); +		} +	} else { +		bpc->bpc_peer = pl->pl_bs->shop.peer; +		if (pl->pl_bs->shop.port_name[0]) { +			bpc->bpc_has_localif = true; +			strlcpy(bpc->bpc_localif, pl->pl_bs->shop.port_name, +				sizeof(bpc->bpc_localif)); +		} +	} + +	return 0; +} + + +/* + * Control socket JSON parsing. + */ +int config_request_add(const char *jsonstr) +{ +	struct json_object *jo; + +	jo = json_tokener_parse(jsonstr); +	if (jo == NULL) +		return -1; + +	return parse_config_json(jo, config_add, NULL); +} + +int config_request_del(const char *jsonstr) +{ +	struct json_object *jo; + +	jo = json_tokener_parse(jsonstr); +	if (jo == NULL) +		return -1; + +	return parse_config_json(jo, config_del, NULL); +} + +char *config_response(const char *status, const char *error) +{ +	struct json_object *resp, *jo; +	char *jsonstr; + +	resp = json_object_new_object(); +	if (resp == NULL) +		return NULL; + +	/* Add 'status' response key. */ +	jo = json_object_new_string(status); +	if (jo == NULL) { +		json_object_put(resp); +		return NULL; +	} + +	json_object_object_add(resp, "status", jo); + +	/* Add 'error' response key. */ +	if (error != NULL) { +		jo = json_object_new_string(error); +		if (jo == NULL) { +			json_object_put(resp); +			return NULL; +		} + +		json_object_object_add(resp, "error", jo); +	} + +	/* Generate JSON response. */ +	jsonstr = XSTRDUP( +		MTYPE_BFDD_NOTIFICATION, +		json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); +	json_object_put(resp); + +	return jsonstr; +} + +char *config_notify(struct bfd_session *bs) +{ +	struct json_object *resp; +	char *jsonstr; +	time_t now; + +	resp = json_object_new_object(); +	if (resp == NULL) +		return NULL; + +	json_object_string_add(resp, "op", BCM_NOTIFY_PEER_STATUS); + +	json_object_add_peer(resp, bs); + +	/* Add status information */ +	json_object_int_add(resp, "id", bs->discrs.my_discr); +	json_object_int_add(resp, "remote-id", bs->discrs.my_discr); + +	switch (bs->ses_state) { +	case PTM_BFD_UP: +		json_object_string_add(resp, "state", "up"); + +		now = monotime(NULL); +		json_object_int_add(resp, "uptime", now - bs->uptime.tv_sec); +		break; +	case PTM_BFD_ADM_DOWN: +		json_object_string_add(resp, "state", "adm-down"); +		break; +	case PTM_BFD_DOWN: +		json_object_string_add(resp, "state", "down"); + +		now = monotime(NULL); +		json_object_int_add(resp, "downtime", +				    now - bs->downtime.tv_sec); +		break; +	case PTM_BFD_INIT: +		json_object_string_add(resp, "state", "init"); +		break; + +	default: +		json_object_string_add(resp, "state", "unknown"); +		break; +	} + +	json_object_int_add(resp, "diagnostics", bs->local_diag); +	json_object_int_add(resp, "remote-diagnostics", bs->remote_diag); + +	/* Generate JSON response. */ +	jsonstr = XSTRDUP( +		MTYPE_BFDD_NOTIFICATION, +		json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); +	json_object_put(resp); + +	return jsonstr; +} + +char *config_notify_config(const char *op, struct bfd_session *bs) +{ +	struct json_object *resp; +	char *jsonstr; + +	resp = json_object_new_object(); +	if (resp == NULL) +		return NULL; + +	json_object_string_add(resp, "op", op); + +	json_object_add_peer(resp, bs); + +	/* On peer deletion we don't need to add any additional information. */ +	if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0) +		goto skip_config; + +	json_object_int_add(resp, "detect-multiplier", bs->detect_mult); +	json_object_int_add(resp, "receive-interval", +			    bs->timers.required_min_rx / 1000); +	json_object_int_add(resp, "transmit-interval", bs->up_min_tx / 1000); +	json_object_int_add(resp, "echo-interval", +			    bs->timers.required_min_echo / 1000); + +	json_object_int_add(resp, "remote-detect-multiplier", +			    bs->remote_detect_mult); +	json_object_int_add(resp, "remote-receive-interval", +			    bs->remote_timers.required_min_rx / 1000); +	json_object_int_add(resp, "remote-transmit-interval", +			    bs->remote_timers.desired_min_tx / 1000); +	json_object_int_add(resp, "remote-echo-interval", +			    bs->remote_timers.required_min_echo / 1000); + +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_ECHO)) +		json_object_boolean_true_add(resp, "echo-mode"); +	else +		json_object_boolean_false_add(resp, "echo-mode"); + +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) +		json_object_boolean_true_add(resp, "shutdown"); +	else +		json_object_boolean_false_add(resp, "shutdown"); + +skip_config: +	/* Generate JSON response. */ +	jsonstr = XSTRDUP( +		MTYPE_BFDD_NOTIFICATION, +		json_object_to_json_string_ext(resp, BFDD_JSON_CONV_OPTIONS)); +	json_object_put(resp); + +	return jsonstr; +} + +int config_notify_request(struct bfd_control_socket *bcs, const char *jsonstr, +			  bpc_handle bh) +{ +	struct json_object *jo; + +	jo = json_tokener_parse(jsonstr); +	if (jo == NULL) +		return -1; + +	return parse_config_json(jo, bh, bcs); +} + +static int json_object_add_peer(struct json_object *jo, struct bfd_session *bs) +{ +	/* Add peer 'key' information. */ +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_IPV6)) +		json_object_boolean_true_add(jo, "ipv6"); +	else +		json_object_boolean_false_add(jo, "ipv6"); + +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_MH)) { +		json_object_boolean_true_add(jo, "multihop"); +		json_object_string_add(jo, "peer-address", +				       satostr(&bs->mhop.peer)); +		json_object_string_add(jo, "local-address", +				       satostr(&bs->mhop.local)); +		if (strlen(bs->mhop.vrf_name) > 0) +			json_object_string_add(jo, "vrf-name", +					       bs->mhop.vrf_name); +	} else { +		json_object_boolean_false_add(jo, "multihop"); +		json_object_string_add(jo, "peer-address", +				       satostr(&bs->shop.peer)); +		if (bs->local_address.sa_sin.sin_family != AF_UNSPEC) +			json_object_string_add(jo, "local-address", +					       satostr(&bs->local_address)); +		if (strlen(bs->shop.port_name) > 0) +			json_object_string_add(jo, "local-interface", +					       bs->shop.port_name); +	} + +	if (bs->pl) +		json_object_string_add(jo, "label", bs->pl->pl_label); + +	return 0; +} + + +/* + * Label handling + */ +struct peer_label *pl_find(const char *label) +{ +	struct peer_label *pl; + +	TAILQ_FOREACH (pl, &bglobal.bg_pllist, pl_entry) { +		if (strcmp(pl->pl_label, label) != 0) +			continue; + +		return pl; +	} + +	return NULL; +} + +struct peer_label *pl_new(const char *label, struct bfd_session *bs) +{ +	struct peer_label *pl; + +	pl = XCALLOC(MTYPE_BFDD_LABEL, sizeof(*pl)); +	if (pl == NULL) +		return NULL; + +	if (strlcpy(pl->pl_label, label, sizeof(pl->pl_label)) +	    > sizeof(pl->pl_label)) +		log_warning("%s:%d: label was truncated", __func__, __LINE__); + +	pl->pl_bs = bs; +	bs->pl = pl; + +	TAILQ_INSERT_HEAD(&bglobal.bg_pllist, pl, pl_entry); + +	return pl; +} + +void pl_free(struct peer_label *pl) +{ +	if (pl == NULL) +		return; + +	/* Remove the pointer back. */ +	pl->pl_bs->pl = NULL; + +	TAILQ_REMOVE(&bglobal.bg_pllist, pl, pl_entry); +	XFREE(MTYPE_BFDD_LABEL, pl); +} diff --git a/bfdd/control.c b/bfdd/control.c new file mode 100644 index 0000000000..39c7871678 --- /dev/null +++ b/bfdd/control.c @@ -0,0 +1,901 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * control.c: implements the BFD daemon control socket. It will be used + * to talk with clients daemon/scripts/consumers. + * + * Authors + * ------- + * Rafael Zalamena <rzalamena@opensourcerouting.org> + */ + +#include <zebra.h> + +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "bfd.h" + +/* + * Prototypes + */ +static int sock_set_nonblock(int fd); +struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs); +static void control_queue_free(struct bfd_control_socket *bcs, +			       struct bfd_control_queue *bcq); +static int control_queue_dequeue(struct bfd_control_socket *bcs); +static int control_queue_enqueue(struct bfd_control_socket *bcs, +				 struct bfd_control_msg *bcm); +static int control_queue_enqueue_first(struct bfd_control_socket *bcs, +				       struct bfd_control_msg *bcm); +struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, +					       struct bfd_session *bs); +static void control_notifypeer_free(struct bfd_control_socket *bcs, +				    struct bfd_notify_peer *bnp); +struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, +						struct bfd_session *bs); + + +struct bfd_control_socket *control_new(int sd); +static void control_free(struct bfd_control_socket *bcs); +static void control_reset_buf(struct bfd_control_buffer *bcb); +static int control_read(struct thread *t); +static int control_write(struct thread *t); + +static void control_handle_request_add(struct bfd_control_socket *bcs, +				       struct bfd_control_msg *bcm); +static void control_handle_request_del(struct bfd_control_socket *bcs, +				       struct bfd_control_msg *bcm); +static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg); +static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg); +static void control_handle_notify_add(struct bfd_control_socket *bcs, +				      struct bfd_control_msg *bcm); +static void control_handle_notify_del(struct bfd_control_socket *bcs, +				      struct bfd_control_msg *bcm); +static void _control_handle_notify(struct hash_backet *hb, void *arg); +static void control_handle_notify(struct bfd_control_socket *bcs, +				  struct bfd_control_msg *bcm); +static void control_response(struct bfd_control_socket *bcs, uint16_t id, +			     const char *status, const char *error); + +static void _control_notify_config(struct bfd_control_socket *bcs, +				   const char *op, struct bfd_session *bs); +static void _control_notify(struct bfd_control_socket *bcs, +			    struct bfd_session *bs); + + +/* + * Functions + */ +static int sock_set_nonblock(int fd) +{ +	int flags; + +	flags = fcntl(fd, F_GETFL, 0); +	if (flags == -1) { +		log_warning("%s: fcntl F_GETFL: %s", __func__, strerror(errno)); +		return -1; +	} + +	flags |= O_NONBLOCK; +	if (fcntl(fd, F_SETFL, flags) == -1) { +		log_warning("%s: fcntl F_SETFL: %s", __func__, strerror(errno)); +		return -1; +	} + +	return 0; +} + +int control_init(const char *path) +{ +	int sd; +	mode_t umval; +	struct sockaddr_un sun_ = { +		.sun_family = AF_UNIX, +		.sun_path = BFDD_CONTROL_SOCKET, +	}; + +	if (path) +		strlcpy(sun_.sun_path, path, sizeof(sun_.sun_path)); + +	/* Remove previously created sockets. */ +	unlink(sun_.sun_path); + +	sd = socket(AF_UNIX, SOCK_STREAM, PF_UNSPEC); +	if (sd == -1) { +		log_error("%s: socket: %s", __func__, strerror(errno)); +		return -1; +	} + +	umval = umask(0); +	if (bind(sd, (struct sockaddr *)&sun_, sizeof(sun_)) == -1) { +		log_error("%s: bind: %s", __func__, strerror(errno)); +		close(sd); +		return -1; +	} +	umask(umval); + +	if (listen(sd, SOMAXCONN) == -1) { +		log_error("%s: listen: %s", __func__, strerror(errno)); +		close(sd); +		return -1; +	} + +	sock_set_nonblock(sd); + +	bglobal.bg_csock = sd; + +	return 0; +} + +void control_shutdown(void) +{ +	struct bfd_control_socket *bcs; + +	if (bglobal.bg_csockev) { +		thread_cancel(bglobal.bg_csockev); +		bglobal.bg_csockev = NULL; +	} + +	socket_close(&bglobal.bg_csock); + +	while (!TAILQ_EMPTY(&bglobal.bg_bcslist)) { +		bcs = TAILQ_FIRST(&bglobal.bg_bcslist); +		control_free(bcs); +	} +} + +int control_accept(struct thread *t) +{ +	int csock, sd = THREAD_FD(t); + +	csock = accept(sd, NULL, 0); +	if (csock == -1) { +		log_warning("%s: accept: %s", __func__, strerror(errno)); +		return 0; +	} + +	if (control_new(csock) == NULL) +		close(csock); + +	bglobal.bg_csockev = NULL; +	thread_add_read(master, control_accept, NULL, sd, &bglobal.bg_csockev); + +	return 0; +} + + +/* + * Client handling + */ +struct bfd_control_socket *control_new(int sd) +{ +	struct bfd_control_socket *bcs; + +	bcs = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bcs)); +	if (bcs == NULL) +		return NULL; + +	/* Disable notifications by default. */ +	bcs->bcs_notify = 0; + +	bcs->bcs_sd = sd; +	thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); + +	TAILQ_INIT(&bcs->bcs_bcqueue); +	TAILQ_INIT(&bcs->bcs_bnplist); +	TAILQ_INSERT_TAIL(&bglobal.bg_bcslist, bcs, bcs_entry); + +	return bcs; +} + +static void control_free(struct bfd_control_socket *bcs) +{ +	struct bfd_control_queue *bcq; +	struct bfd_notify_peer *bnp; + +	if (bcs->bcs_ev) { +		thread_cancel(bcs->bcs_ev); +		bcs->bcs_ev = NULL; +	} + +	if (bcs->bcs_outev) { +		thread_cancel(bcs->bcs_outev); +		bcs->bcs_outev = NULL; +	} + +	close(bcs->bcs_sd); + +	TAILQ_REMOVE(&bglobal.bg_bcslist, bcs, bcs_entry); + +	/* Empty output queue. */ +	while (!TAILQ_EMPTY(&bcs->bcs_bcqueue)) { +		bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); +		control_queue_free(bcs, bcq); +	} + +	/* Empty notification list. */ +	while (!TAILQ_EMPTY(&bcs->bcs_bnplist)) { +		bnp = TAILQ_FIRST(&bcs->bcs_bnplist); +		control_notifypeer_free(bcs, bnp); +	} + +	control_reset_buf(&bcs->bcs_bin); +	XFREE(MTYPE_BFDD_CONTROL, bcs); +} + +struct bfd_notify_peer *control_notifypeer_new(struct bfd_control_socket *bcs, +					       struct bfd_session *bs) +{ +	struct bfd_notify_peer *bnp; + +	bnp = control_notifypeer_find(bcs, bs); +	if (bnp) +		return bnp; + +	bnp = XCALLOC(MTYPE_BFDD_CONTROL, sizeof(*bnp)); +	if (bnp == NULL) { +		log_warning("%s: calloc: %s", __func__, strerror(errno)); +		return NULL; +	} + +	TAILQ_INSERT_TAIL(&bcs->bcs_bnplist, bnp, bnp_entry); +	bnp->bnp_bs = bs; +	bs->refcount++; + +	return bnp; +} + +static void control_notifypeer_free(struct bfd_control_socket *bcs, +				    struct bfd_notify_peer *bnp) +{ +	TAILQ_REMOVE(&bcs->bcs_bnplist, bnp, bnp_entry); +	bnp->bnp_bs->refcount--; +	XFREE(MTYPE_BFDD_CONTROL, bnp); +} + +struct bfd_notify_peer *control_notifypeer_find(struct bfd_control_socket *bcs, +						struct bfd_session *bs) +{ +	struct bfd_notify_peer *bnp; + +	TAILQ_FOREACH (bnp, &bcs->bcs_bnplist, bnp_entry) { +		if (bnp->bnp_bs == bs) +			return bnp; +	} + +	return NULL; +} + +struct bfd_control_queue *control_queue_new(struct bfd_control_socket *bcs) +{ +	struct bfd_control_queue *bcq; + +	bcq = XCALLOC(MTYPE_BFDD_NOTIFICATION, sizeof(*bcq)); +	if (bcq == NULL) { +		log_warning("%s: calloc: %s", __func__, strerror(errno)); +		return NULL; +	} + +	control_reset_buf(&bcq->bcq_bcb); +	TAILQ_INSERT_TAIL(&bcs->bcs_bcqueue, bcq, bcq_entry); + +	return bcq; +} + +static void control_queue_free(struct bfd_control_socket *bcs, +			       struct bfd_control_queue *bcq) +{ +	control_reset_buf(&bcq->bcq_bcb); +	TAILQ_REMOVE(&bcs->bcs_bcqueue, bcq, bcq_entry); +	XFREE(MTYPE_BFDD_NOTIFICATION, bcq); +} + +static int control_queue_dequeue(struct bfd_control_socket *bcs) +{ +	struct bfd_control_queue *bcq; + +	/* List is empty, nothing to do. */ +	if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) +		goto empty_list; + +	bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); +	control_queue_free(bcs, bcq); + +	/* Get the next buffer to send. */ +	if (TAILQ_EMPTY(&bcs->bcs_bcqueue)) +		goto empty_list; + +	bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); +	bcs->bcs_bout = &bcq->bcq_bcb; + +	bcs->bcs_outev = NULL; +	thread_add_write(master, control_write, bcs, bcs->bcs_sd, +			 &bcs->bcs_outev); + +	return 1; + +empty_list: +	if (bcs->bcs_outev) { +		thread_cancel(bcs->bcs_outev); +		bcs->bcs_outev = NULL; +	} +	bcs->bcs_bout = NULL; +	return 0; +} + +static int control_queue_enqueue(struct bfd_control_socket *bcs, +				 struct bfd_control_msg *bcm) +{ +	struct bfd_control_queue *bcq; +	struct bfd_control_buffer *bcb; + +	bcq = control_queue_new(bcs); +	if (bcq == NULL) +		return -1; + +	bcb = &bcq->bcq_bcb; +	bcb->bcb_left = sizeof(struct bfd_control_msg) + ntohl(bcm->bcm_length); +	bcb->bcb_pos = 0; +	bcb->bcb_bcm = bcm; + +	/* If this is the first item, then dequeue and start using it. */ +	if (bcs->bcs_bout == NULL) { +		bcs->bcs_bout = bcb; + +		/* New messages, active write events. */ +		thread_add_write(master, control_write, bcs, bcs->bcs_sd, +				 &bcs->bcs_outev); +	} + +	return 0; +} + +static int control_queue_enqueue_first(struct bfd_control_socket *bcs, +				       struct bfd_control_msg *bcm) +{ +	struct bfd_control_queue *bcq, *bcqn; +	struct bfd_control_buffer *bcb; + +	/* Enqueue it somewhere. */ +	if (control_queue_enqueue(bcs, bcm) == -1) +		return -1; + +	/* +	 * The item is either the first or the last. So we must first +	 * check the best case where the item is already the first. +	 */ +	bcq = TAILQ_FIRST(&bcs->bcs_bcqueue); +	bcb = &bcq->bcq_bcb; +	if (bcm == bcb->bcb_bcm) +		return 0; + +	/* +	 * The item was not the first, so it is the last. We'll try to +	 * assign it to the head of the queue, however if there is a +	 * transfer in progress, then we have to make the item as the +	 * next one. +	 * +	 * Interrupting the transfer of in progress message will cause +	 * the client to lose track of the message position/data. +	 */ +	bcqn = TAILQ_LAST(&bcs->bcs_bcqueue, bcqueue); +	TAILQ_REMOVE(&bcs->bcs_bcqueue, bcqn, bcq_entry); +	if (bcb->bcb_pos != 0) { +		/* +		 * First position is already being sent, insert into +		 * second position. +		 */ +		TAILQ_INSERT_AFTER(&bcs->bcs_bcqueue, bcq, bcqn, bcq_entry); +	} else { +		/* +		 * Old message didn't start being sent, we still have +		 * time to put this one in the head of the queue. +		 */ +		TAILQ_INSERT_HEAD(&bcs->bcs_bcqueue, bcqn, bcq_entry); +		bcb = &bcqn->bcq_bcb; +		bcs->bcs_bout = bcb; +	} + +	return 0; +} + +static void control_reset_buf(struct bfd_control_buffer *bcb) +{ +	/* Get ride of old data. */ +	XFREE(MTYPE_BFDD_NOTIFICATION, bcb->bcb_buf); +	bcb->bcb_buf = NULL; +	bcb->bcb_pos = 0; +	bcb->bcb_left = 0; +} + +static int control_read(struct thread *t) +{ +	struct bfd_control_socket *bcs = THREAD_ARG(t); +	struct bfd_control_buffer *bcb = &bcs->bcs_bin; +	int sd = bcs->bcs_sd; +	struct bfd_control_msg bcm; +	ssize_t bread; +	size_t plen; + +	/* +	 * Check if we have already downloaded message content, if so then skip +	 * to +	 * download the rest of it and process. +	 * +	 * Otherwise download a new message header and allocate the necessary +	 * memory. +	 */ +	if (bcb->bcb_buf != NULL) +		goto skip_header; + +	bread = read(sd, &bcm, sizeof(bcm)); +	if (bread == 0) { +		control_free(bcs); +		return 0; +	} +	if (bread < 0) { +		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) +			goto schedule_next_read; + +		log_warning("%s: read: %s", __func__, strerror(errno)); +		control_free(bcs); +		return 0; +	} + +	/* Validate header fields. */ +	plen = ntohl(bcm.bcm_length); +	if (plen < 2) { +		log_debug("%s: client closed due small message length: %d", +			  __func__, bcm.bcm_length); +		control_free(bcs); +		return 0; +	} + +	if (bcm.bcm_ver != BMV_VERSION_1) { +		log_debug("%s: client closed due bad version: %d", __func__, +			  bcm.bcm_ver); +		control_free(bcs); +		return 0; +	} + +	/* Prepare the buffer to load the message. */ +	bcs->bcs_version = bcm.bcm_ver; +	bcs->bcs_type = bcm.bcm_type; + +	bcb->bcb_pos = sizeof(bcm); +	bcb->bcb_left = plen; +	bcb->bcb_buf = XMALLOC(MTYPE_BFDD_NOTIFICATION, +			       sizeof(bcm) + bcb->bcb_left + 1); +	if (bcb->bcb_buf == NULL) { +		log_warning("%s: not enough memory for message size: %u", +			    __func__, bcb->bcb_left); +		control_free(bcs); +		return 0; +	} + +	memcpy(bcb->bcb_buf, &bcm, sizeof(bcm)); + +	/* Terminate data string with NULL for later processing. */ +	bcb->bcb_buf[sizeof(bcm) + bcb->bcb_left] = 0; + +skip_header: +	/* Download the remaining data of the message and process it. */ +	bread = read(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); +	if (bread == 0) { +		control_free(bcs); +		return 0; +	} +	if (bread < 0) { +		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) +			goto schedule_next_read; + +		log_warning("%s: read: %s", __func__, strerror(errno)); +		control_free(bcs); +		return 0; +	} + +	bcb->bcb_pos += bread; +	bcb->bcb_left -= bread; +	/* We need more data, return to wait more. */ +	if (bcb->bcb_left > 0) +		goto schedule_next_read; + +	switch (bcm.bcm_type) { +	case BMT_REQUEST_ADD: +		control_handle_request_add(bcs, bcb->bcb_bcm); +		break; +	case BMT_REQUEST_DEL: +		control_handle_request_del(bcs, bcb->bcb_bcm); +		break; +	case BMT_NOTIFY: +		control_handle_notify(bcs, bcb->bcb_bcm); +		break; +	case BMT_NOTIFY_ADD: +		control_handle_notify_add(bcs, bcb->bcb_bcm); +		break; +	case BMT_NOTIFY_DEL: +		control_handle_notify_del(bcs, bcb->bcb_bcm); +		break; + +	default: +		log_debug("%s: unhandled message type: %d", __func__, +			  bcm.bcm_type); +		control_response(bcs, bcm.bcm_id, BCM_RESPONSE_ERROR, +				 "invalid message type"); +		break; +	} + +	bcs->bcs_version = 0; +	bcs->bcs_type = 0; +	control_reset_buf(bcb); + +schedule_next_read: +	bcs->bcs_ev = NULL; +	thread_add_read(master, control_read, bcs, sd, &bcs->bcs_ev); + +	return 0; +} + +static int control_write(struct thread *t) +{ +	struct bfd_control_socket *bcs = THREAD_ARG(t); +	struct bfd_control_buffer *bcb = bcs->bcs_bout; +	int sd = bcs->bcs_sd; +	ssize_t bwrite; + +	bwrite = write(sd, &bcb->bcb_buf[bcb->bcb_pos], bcb->bcb_left); +	if (bwrite == 0) { +		control_free(bcs); +		return 0; +	} +	if (bwrite < 0) { +		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { +			bcs->bcs_outev = NULL; +			thread_add_write(master, control_write, bcs, +					 bcs->bcs_sd, &bcs->bcs_outev); +			return 0; +		} + +		log_warning("%s: write: %s", __func__, strerror(errno)); +		control_free(bcs); +		return 0; +	} + +	bcb->bcb_pos += bwrite; +	bcb->bcb_left -= bwrite; +	if (bcb->bcb_left > 0) { +		bcs->bcs_outev = NULL; +		thread_add_write(master, control_write, bcs, bcs->bcs_sd, +				 &bcs->bcs_outev); +		return 0; +	} + +	control_queue_dequeue(bcs); + +	return 0; +} + + +/* + * Message processing + */ +static void control_handle_request_add(struct bfd_control_socket *bcs, +				       struct bfd_control_msg *bcm) +{ +	const char *json = (const char *)bcm->bcm_data; + +	if (config_request_add(json) == 0) +		control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); +	else +		control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, +				 "request add failed"); +} + +static void control_handle_request_del(struct bfd_control_socket *bcs, +				       struct bfd_control_msg *bcm) +{ +	const char *json = (const char *)bcm->bcm_data; + +	if (config_request_del(json) == 0) +		control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); +	else +		control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, +				 "request del failed"); +} + +static struct bfd_session *_notify_find_peer(struct bfd_peer_cfg *bpc) +{ +	struct peer_label *pl; + +	if (bpc->bpc_has_label) { +		pl = pl_find(bpc->bpc_label); +		if (pl) +			return pl->pl_bs; +	} + +	return bs_peer_find(bpc); +} + +static void _control_handle_notify(struct hash_backet *hb, void *arg) +{ +	struct bfd_control_socket *bcs = arg; +	struct bfd_session *bs = hb->data; + +	/* Notify peer configuration. */ +	if (bcs->bcs_notify & BCM_NOTIFY_CONFIG) +		_control_notify_config(bcs, BCM_NOTIFY_CONFIG_ADD, bs); + +	/* Notify peer status. */ +	if (bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) +		_control_notify(bcs, bs); +} + +static void control_handle_notify(struct bfd_control_socket *bcs, +				  struct bfd_control_msg *bcm) +{ +	memcpy(&bcs->bcs_notify, bcm->bcm_data, sizeof(bcs->bcs_notify)); + +	control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); + +	/* +	 * If peer asked for notification configuration, send everything that +	 * was configured until the moment to sync up. +	 */ +	if (bcs->bcs_notify & (BCM_NOTIFY_CONFIG | BCM_NOTIFY_PEER_STATE)) +		bfd_id_iterate(_control_handle_notify, bcs); +} + +static int notify_add_cb(struct bfd_peer_cfg *bpc, void *arg) +{ +	struct bfd_control_socket *bcs = arg; +	struct bfd_session *bs = _notify_find_peer(bpc); + +	if (bs == NULL) +		return -1; + +	if (control_notifypeer_new(bcs, bs) == NULL) +		return -1; + +	/* Notify peer status. */ +	_control_notify(bcs, bs); + +	return 0; +} + +static int notify_del_cb(struct bfd_peer_cfg *bpc, void *arg) +{ +	struct bfd_control_socket *bcs = arg; +	struct bfd_session *bs = _notify_find_peer(bpc); +	struct bfd_notify_peer *bnp; + +	if (bs == NULL) +		return -1; + +	bnp = control_notifypeer_find(bcs, bs); +	if (bnp) +		control_notifypeer_free(bcs, bnp); + +	return 0; +} + +static void control_handle_notify_add(struct bfd_control_socket *bcs, +				      struct bfd_control_msg *bcm) +{ +	const char *json = (const char *)bcm->bcm_data; + +	if (config_notify_request(bcs, json, notify_add_cb) == 0) { +		control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); +		return; +	} + +	control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, +			 "failed to parse notify data"); +} + +static void control_handle_notify_del(struct bfd_control_socket *bcs, +				      struct bfd_control_msg *bcm) +{ +	const char *json = (const char *)bcm->bcm_data; + +	if (config_notify_request(bcs, json, notify_del_cb) == 0) { +		control_response(bcs, bcm->bcm_id, BCM_RESPONSE_OK, NULL); +		return; +	} + +	control_response(bcs, bcm->bcm_id, BCM_RESPONSE_ERROR, +			 "failed to parse notify data"); +} + + +/* + * Internal functions used by the BFD daemon. + */ +static void control_response(struct bfd_control_socket *bcs, uint16_t id, +			     const char *status, const char *error) +{ +	struct bfd_control_msg *bcm; +	char *jsonstr; +	size_t jsonstrlen; + +	/* Generate JSON response. */ +	jsonstr = config_response(status, error); +	if (jsonstr == NULL) { +		log_warning("%s: config_response: failed to get JSON str", +			    __func__); +		return; +	} + +	/* Allocate data and answer. */ +	jsonstrlen = strlen(jsonstr); +	bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, +		      sizeof(struct bfd_control_msg) + jsonstrlen); +	if (bcm == NULL) { +		log_warning("%s: malloc: %s", __func__, strerror(errno)); +		XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); +		return; +	} + +	bcm->bcm_length = htonl(jsonstrlen); +	bcm->bcm_ver = BMV_VERSION_1; +	bcm->bcm_type = BMT_RESPONSE; +	bcm->bcm_id = id; +	memcpy(bcm->bcm_data, jsonstr, jsonstrlen); +	XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + +	control_queue_enqueue_first(bcs, bcm); +} + +static void _control_notify(struct bfd_control_socket *bcs, +			    struct bfd_session *bs) +{ +	struct bfd_control_msg *bcm; +	char *jsonstr; +	size_t jsonstrlen; + +	/* Generate JSON response. */ +	jsonstr = config_notify(bs); +	if (jsonstr == NULL) { +		log_warning("%s: config_notify: failed to get JSON str", +			    __func__); +		return; +	} + +	/* Allocate data and answer. */ +	jsonstrlen = strlen(jsonstr); +	bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, +		      sizeof(struct bfd_control_msg) + jsonstrlen); +	if (bcm == NULL) { +		log_warning("%s: malloc: %s", __func__, strerror(errno)); +		XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); +		return; +	} + +	bcm->bcm_length = htonl(jsonstrlen); +	bcm->bcm_ver = BMV_VERSION_1; +	bcm->bcm_type = BMT_NOTIFY; +	bcm->bcm_id = htons(BCM_NOTIFY_ID); +	memcpy(bcm->bcm_data, jsonstr, jsonstrlen); +	XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + +	control_queue_enqueue(bcs, bcm); +} + +int control_notify(struct bfd_session *bs) +{ +	struct bfd_control_socket *bcs; +	struct bfd_notify_peer *bnp; + +	/* +	 * PERFORMANCE: reuse the bfd_control_msg allocated data for +	 * all control sockets to avoid wasting memory. +	 */ +	TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { +		/* +		 * Test for all notifications first, then search for +		 * specific peers. +		 */ +		if ((bcs->bcs_notify & BCM_NOTIFY_PEER_STATE) == 0) { +			bnp = control_notifypeer_find(bcs, bs); +			/* +			 * If the notification is not configured here, +			 * don't send it. +			 */ +			if (bnp == NULL) +				continue; +		} + +		_control_notify(bcs, bs); +	} + +	return 0; +} + +static void _control_notify_config(struct bfd_control_socket *bcs, +				   const char *op, struct bfd_session *bs) +{ +	struct bfd_control_msg *bcm; +	char *jsonstr; +	size_t jsonstrlen; + +	/* Generate JSON response. */ +	jsonstr = config_notify_config(op, bs); +	if (jsonstr == NULL) { +		log_warning("%s: config_notify_config: failed to get JSON str", +			    __func__); +		return; +	} + +	/* Allocate data and answer. */ +	jsonstrlen = strlen(jsonstr); +	bcm = XMALLOC(MTYPE_BFDD_NOTIFICATION, +		      sizeof(struct bfd_control_msg) + jsonstrlen); +	if (bcm == NULL) { +		log_warning("%s: malloc: %s", __func__, strerror(errno)); +		XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); +		return; +	} + +	bcm->bcm_length = htonl(jsonstrlen); +	bcm->bcm_ver = BMV_VERSION_1; +	bcm->bcm_type = BMT_NOTIFY; +	bcm->bcm_id = htons(BCM_NOTIFY_ID); +	memcpy(bcm->bcm_data, jsonstr, jsonstrlen); +	XFREE(MTYPE_BFDD_NOTIFICATION, jsonstr); + +	control_queue_enqueue(bcs, bcm); +} + +int control_notify_config(const char *op, struct bfd_session *bs) +{ +	struct bfd_control_socket *bcs; +	struct bfd_notify_peer *bnp; + +	/* Remove the control sockets notification for this peer. */ +	if (strcmp(op, BCM_NOTIFY_CONFIG_DELETE) == 0 && bs->refcount > 0) { +		TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { +			bnp = control_notifypeer_find(bcs, bs); +			if (bnp) +				control_notifypeer_free(bcs, bnp); +		} +	} + +	/* +	 * PERFORMANCE: reuse the bfd_control_msg allocated data for +	 * all control sockets to avoid wasting memory. +	 */ +	TAILQ_FOREACH (bcs, &bglobal.bg_bcslist, bcs_entry) { +		/* +		 * Test for all notifications first, then search for +		 * specific peers. +		 */ +		if ((bcs->bcs_notify & BCM_NOTIFY_CONFIG) == 0) +			continue; + +		_control_notify_config(bcs, op, bs); +	} + +	return 0; +} diff --git a/bfdd/event.c b/bfdd/event.c new file mode 100644 index 0000000000..ba12f5b4e8 --- /dev/null +++ b/bfdd/event.c @@ -0,0 +1,155 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * event.c: implements the BFD loop event handlers. + * + * Authors + * ------- + * Rafael Zalamena <rzalamena@opensourcerouting.org> + */ + +#include <zebra.h> + +#include "bfd.h" + +void tv_normalize(struct timeval *tv); + +void tv_normalize(struct timeval *tv) +{ +	/* Remove seconds part from microseconds. */ +	tv->tv_sec = tv->tv_usec / 1000000; +	tv->tv_usec = tv->tv_usec % 1000000; +} + +void bfd_recvtimer_update(struct bfd_session *bs) +{ +	struct timeval tv = {.tv_sec = 0, .tv_usec = bs->detect_TO}; + +	/* Don't add event if peer is deactivated. */ +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) +		return; + +	tv_normalize(&tv); +#ifdef BFD_EVENT_DEBUG +	log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); +#endif /* BFD_EVENT_DEBUG */ + +	/* Remove previous schedule if any. */ +	if (bs->recvtimer_ev) +		bfd_recvtimer_delete(bs); + +	thread_add_timer_tv(master, bfd_recvtimer_cb, bs, &tv, +			    &bs->recvtimer_ev); +} + +void bfd_echo_recvtimer_update(struct bfd_session *bs) +{ +	struct timeval tv = {.tv_sec = 0, .tv_usec = bs->echo_detect_TO}; + +	/* Don't add event if peer is deactivated. */ +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) +		return; + +	tv_normalize(&tv); +#ifdef BFD_EVENT_DEBUG +	log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); +#endif /* BFD_EVENT_DEBUG */ + +	/* Remove previous schedule if any. */ +	if (bs->echo_recvtimer_ev) +		bfd_echo_recvtimer_delete(bs); + +	thread_add_timer_tv(master, bfd_echo_recvtimer_cb, bs, &tv, +			    &bs->echo_recvtimer_ev); +} + +void bfd_xmttimer_update(struct bfd_session *bs, uint64_t jitter) +{ +	struct timeval tv = {.tv_sec = 0, .tv_usec = jitter}; + +	/* Don't add event if peer is deactivated. */ +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) +		return; + +	tv_normalize(&tv); +#ifdef BFD_EVENT_DEBUG +	log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); +#endif /* BFD_EVENT_DEBUG */ + +	/* Remove previous schedule if any. */ +	if (bs->xmttimer_ev) +		bfd_xmttimer_delete(bs); + +	thread_add_timer_tv(master, bfd_xmt_cb, bs, &tv, &bs->xmttimer_ev); +} + +void bfd_echo_xmttimer_update(struct bfd_session *bs, uint64_t jitter) +{ +	struct timeval tv = {.tv_sec = 0, .tv_usec = jitter}; + +	/* Don't add event if peer is deactivated. */ +	if (BFD_CHECK_FLAG(bs->flags, BFD_SESS_FLAG_SHUTDOWN)) +		return; + +	tv_normalize(&tv); +#ifdef BFD_EVENT_DEBUG +	log_debug("%s: sec = %ld, usec = %ld", __func__, tv.tv_sec, tv.tv_usec); +#endif /* BFD_EVENT_DEBUG */ + +	/* Remove previous schedule if any. */ +	if (bs->echo_xmttimer_ev) +		bfd_echo_xmttimer_delete(bs); + +	thread_add_timer_tv(master, bfd_echo_xmt_cb, bs, &tv, +			    &bs->echo_xmttimer_ev); +} + +void bfd_recvtimer_delete(struct bfd_session *bs) +{ +	if (bs->recvtimer_ev == NULL) +		return; + +	thread_cancel(bs->recvtimer_ev); +	bs->recvtimer_ev = NULL; +} + +void bfd_echo_recvtimer_delete(struct bfd_session *bs) +{ +	if (bs->echo_recvtimer_ev == NULL) +		return; + +	thread_cancel(bs->echo_recvtimer_ev); +	bs->echo_recvtimer_ev = NULL; +} + +void bfd_xmttimer_delete(struct bfd_session *bs) +{ +	if (bs->xmttimer_ev == NULL) +		return; + +	thread_cancel(bs->xmttimer_ev); +	bs->xmttimer_ev = NULL; +} + +void bfd_echo_xmttimer_delete(struct bfd_session *bs) +{ +	if (bs->echo_xmttimer_ev == NULL) +		return; + +	thread_cancel(bs->echo_xmttimer_ev); +	bs->echo_xmttimer_ev = NULL; +} diff --git a/bfdd/linux.c b/bfdd/linux.c new file mode 100644 index 0000000000..8a68511d53 --- /dev/null +++ b/bfdd/linux.c @@ -0,0 +1,218 @@ +/* + * Linux specific code + * + * Copyright (C) 2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING.  If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#ifdef BFD_LINUX + +/* XXX: fix compilation error on Ubuntu 16.04 or older. */ +#ifndef _UAPI_IPV6_H +#define _UAPI_IPV6_H +#endif /* _UAPI_IPV6_H */ + +#include <linux/filter.h> +#include <linux/if_packet.h> + +#include <netinet/if_ether.h> + +#include <net/if.h> +#include <sys/ioctl.h> + +#include "bfd.h" + +/* Berkeley Packet filter code to filter out BFD Echo packets. + * tcpdump -dd "(udp dst port 3785)" + */ +static struct sock_filter bfd_echo_filter[] = { +	{0x28, 0, 0, 0x0000000c}, {0x15, 0, 4, 0x000086dd}, +	{0x30, 0, 0, 0x00000014}, {0x15, 0, 11, 0x00000011}, +	{0x28, 0, 0, 0x00000038}, {0x15, 8, 9, 0x00000ec9}, +	{0x15, 0, 8, 0x00000800}, {0x30, 0, 0, 0x00000017}, +	{0x15, 0, 6, 0x00000011}, {0x28, 0, 0, 0x00000014}, +	{0x45, 4, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, +	{0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x00000ec9}, +	{0x6, 0, 0, 0x0000ffff},  {0x6, 0, 0, 0x00000000}, +}; + +/* Berkeley Packet filter code to filter out BFD vxlan packets. + * tcpdump -dd "(udp dst port 4789)" + */ +static struct sock_filter bfd_vxlan_filter[] = { +	{0x28, 0, 0, 0x0000000c}, {0x15, 0, 4, 0x000086dd}, +	{0x30, 0, 0, 0x00000014}, {0x15, 0, 11, 0x00000011}, +	{0x28, 0, 0, 0x00000038}, {0x15, 8, 9, 0x000012b5}, +	{0x15, 0, 8, 0x00000800}, {0x30, 0, 0, 0x00000017}, +	{0x15, 0, 6, 0x00000011}, {0x28, 0, 0, 0x00000014}, +	{0x45, 4, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e}, +	{0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x000012b5}, +	{0x6, 0, 0, 0x0000ffff},  {0x6, 0, 0, 0x00000000}, +}; + + +/* + * Definitions. + */ +int ptm_bfd_fetch_ifindex(const char *ifname) +{ +	struct ifreq ifr; + +	if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) +	    > sizeof(ifr.ifr_name)) { +		CRITLOG("Interface name %s truncated", ifr.ifr_name); +	} + +	if (ioctl(bglobal.bg_shop, SIOCGIFINDEX, &ifr) == -1) { +		CRITLOG("Getting ifindex for %s failed: %s", ifname, +			strerror(errno)); +		return -1; +	} + +	return ifr.ifr_ifindex; +} + +void ptm_bfd_fetch_local_mac(const char *ifname, uint8_t *mac) +{ +	struct ifreq ifr; + +	if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) +	    > sizeof(ifr.ifr_name)) { +		CRITLOG("Interface name %s truncated", ifr.ifr_name); +	} + +	if (ioctl(bglobal.bg_shop, SIOCGIFHWADDR, &ifr) == -1) { +		CRITLOG("Getting mac address for %s failed: %s", ifname, +			strerror(errno)); +		return; +	} + +	memcpy(mac, ifr.ifr_hwaddr.sa_data, ETHERNET_ADDRESS_LENGTH); +} + + +/* Was _fetch_portname_from_ifindex() */ +void fetch_portname_from_ifindex(int ifindex, char *ifname, size_t ifnamelen) +{ +	struct ifreq ifr; + +	ifname[0] = 0; + +	memset(&ifr, 0, sizeof(ifr)); +	ifr.ifr_ifindex = ifindex; + +	if (ioctl(bglobal.bg_shop, SIOCGIFNAME, &ifr) == -1) { +		CRITLOG("Getting ifname for ifindex %d failed: %s", ifindex, +			strerror(errno)); +		return; +	} + +	strlcpy(ifname, ifr.ifr_name, ifnamelen); +} + +int ptm_bfd_echo_sock_init(void) +{ +	int s; +	struct sock_fprog bpf = {.len = sizeof(bfd_echo_filter) +					/ sizeof(bfd_echo_filter[0]), +				 .filter = bfd_echo_filter}; + +	s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); +	if (s == -1) { +		ERRLOG("%s: socket: %s", __func__, strerror(errno)); +		return -1; +	} + +	if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) +	    == -1) { +		ERRLOG("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__, +		       strerror(errno)); +		close(s); +		return -1; +	} + +	return s; +} + +int ptm_bfd_vxlan_sock_init(void) +{ +	int s; +	struct sock_fprog bpf = {.len = sizeof(bfd_vxlan_filter) +					/ sizeof(bfd_vxlan_filter[0]), +				 .filter = bfd_vxlan_filter}; + +	s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); +	if (s == -1) { +		ERRLOG("%s: socket: %s", __func__, strerror(errno)); +		return -1; +	} + +	if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) +	    == -1) { +		ERRLOG("%s: setsockopt(SO_ATTACH_FILTER): %s", __func__, +		       strerror(errno)); +		close(s); +		return -1; +	} + +	return s; +} + +int bp_bind_dev(int sd __attribute__((__unused__)), +		const char *dev __attribute__((__unused__))) +{ +	/* +	 * TODO: implement this differently. It is not possible to +	 * SO_BINDTODEVICE after the daemon has dropped its privileges. +	 */ +#if 0 +	size_t devlen = strlen(dev) + 1; + +	if (setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, dev, devlen) == -1) { +		log_warning("%s: setsockopt(SO_BINDTODEVICE, \"%s\"): %s", +			    __func__, dev, strerror(errno)); +		return -1; +	} +#endif + +	return 0; +} + +uint16_t udp4_checksum(struct iphdr *iph, uint8_t *buf, int len) +{ +	char *ptr; +	struct udp_psuedo_header pudp_hdr; +	uint16_t csum; + +	pudp_hdr.saddr = iph->saddr; +	pudp_hdr.daddr = iph->daddr; +	pudp_hdr.reserved = 0; +	pudp_hdr.protocol = iph->protocol; +	pudp_hdr.len = htons(len); + +	ptr = XMALLOC(MTYPE_BFDD_TMP, UDP_PSUEDO_HDR_LEN + len); +	memcpy(ptr, &pudp_hdr, UDP_PSUEDO_HDR_LEN); +	memcpy(ptr + UDP_PSUEDO_HDR_LEN, buf, len); + +	csum = checksum((uint16_t *)ptr, UDP_PSUEDO_HDR_LEN + len); +	XFREE(MTYPE_BFDD_TMP, ptr); +	return csum; +} + +#endif /* BFD_LINUX */ diff --git a/bfdd/log.c b/bfdd/log.c new file mode 100644 index 0000000000..8561956d6a --- /dev/null +++ b/bfdd/log.c @@ -0,0 +1,129 @@ +/********************************************************************* + * Copyright 2017-2018 Network Device Education Foundation, Inc. ("NetDEF") + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * log.c: implements an abstraction between loggers interface. Implement all + * log backends in this file. + * + * Authors + * ------- + * Rafael Zalamena <rzalamena@opensourcerouting.org> + */ + +#include <zebra.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "bfd.h" + +#include "lib/log_int.h" + +void log_msg(int level, const char *fmt, va_list vl); + + +static int log_fg; +static int log_level = BLOG_DEBUG; + +void log_init(int foreground, enum blog_level level, +	      struct frr_daemon_info *fdi) +{ +	log_fg = foreground; +	log_level = level; + +	openzlog(fdi->progname, fdi->logname, 0, +		 LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON); +} + +void log_msg(int level, const char *fmt, va_list vl) +{ +	if (level < log_level) +		return; + +	switch (level) { +	case BLOG_DEBUG: +		vzlog(LOG_DEBUG, fmt, vl); +		break; + +	case BLOG_INFO: +		vzlog(LOG_INFO, fmt, vl); +		break; + +	case BLOG_WARNING: +		vzlog(LOG_WARNING, fmt, vl); +		break; + +	case BLOG_ERROR: +		vzlog(LOG_ERR, fmt, vl); +		break; + +	case BLOG_FATAL: +		vzlog(LOG_EMERG, fmt, vl); +		break; + +	default: +		vfprintf(stderr, fmt, vl); +		break; +	} +} + +void log_info(const char *fmt, ...) +{ +	va_list vl; + +	va_start(vl, fmt); +	log_msg(BLOG_INFO, fmt, vl); +	va_end(vl); +} + +void log_debug(const char *fmt, ...) +{ +	va_list vl; + +	va_start(vl, fmt); +	log_msg(BLOG_DEBUG, fmt, vl); +	va_end(vl); +} + +void log_error(const char *fmt, ...) +{ +	va_list vl; + +	va_start(vl, fmt); +	log_msg(BLOG_ERROR, fmt, vl); +	va_end(vl); +} + +void log_warning(const char *fmt, ...) +{ +	va_list vl; + +	va_start(vl, fmt); +	log_msg(BLOG_WARNING, fmt, vl); +	va_end(vl); +} + +void log_fatal(const char *fmt, ...) +{ +	va_list vl; + +	va_start(vl, fmt); +	log_msg(BLOG_FATAL, fmt, vl); +	va_end(vl); + +	exit(1); +} diff --git a/bfdd/subdir.am b/bfdd/subdir.am new file mode 100644 index 0000000000..fe3daaf507 --- /dev/null +++ b/bfdd/subdir.am @@ -0,0 +1,28 @@ +# +# bfdd +# + +if BFDD +noinst_LIBRARIES += bfdd/libbfd.a +sbin_PROGRAMS += bfdd/bfdd +dist_examples_DATA += bfdd/bfdd.conf.sample +endif + +bfdd_libbfd_a_SOURCES = \ +	bfdd/bfd.c \ +	bfdd/bfd_packet.c \ +	bfdd/bsd.c \ +	bfdd/config.c \ +	bfdd/control.c \ +	bfdd/event.c \ +	bfdd/linux.c \ +	bfdd/log.c \ +	# end + +noinst_HEADERS += \ +	bfdd/bfdctl.h \ +	bfdd/bfd.h \ +	# end + +bfdd_bfdd_SOURCES = bfdd/bfdd.c +bfdd_bfdd_LDADD = bfdd/libbfd.a lib/libfrr.la  | 
