summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/net/bfd.c991
-rw-r--r--sys/net/bfd.h71
2 files changed, 1062 insertions, 0 deletions
diff --git a/sys/net/bfd.c b/sys/net/bfd.c
new file mode 100644
index 00000000000..5c83e4e181e
--- /dev/null
+++ b/sys/net/bfd.c
@@ -0,0 +1,991 @@
+/* $OpenBSD: bfd.c,v 1.1 2016/09/03 14:14:20 phessler Exp $ */
+
+/*
+ * Copyright (c) 2016 Peter Hessler <phessler@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Support for Bi-directional Forwarding Detection (RFC 5880 / 5881)
+ */
+
+#include <sys/errno.h>
+#include <sys/param.h>
+
+#include <sys/task.h>
+#include <sys/pool.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <net/bfd.h>
+
+/*
+ * RFC 5880 Page 7
+ * The Mandatory Section of a BFD Control packet has the following
+ * format:
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | My Discriminator |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Your Discriminator |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Desired Min TX Interval |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Required Min RX Interval |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Required Min Echo RX Interval |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *
+ * An optional Authentication Section MAY be present:
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Auth Type | Auth Len | Authentication Data... |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+
+/* BFD on-wire format */
+struct bfd_header {
+ u_int8_t bfd_ver_diag;
+ u_int8_t bfd_sta_flags;
+
+ u_int8_t bfd_detect_multi; /* detection time multiplier */
+ u_int8_t bfd_length; /* in bytes */
+ u_int32_t bfd_my_discriminator; /* From this system */
+ u_int32_t bfd_your_discriminator; /* Received */
+ u_int32_t bfd_desired_min_tx_interval; /* in microseconds */
+ u_int32_t bfd_required_min_rx_interval; /* in microseconds */
+ u_int32_t bfd_required_min_echo_interval; /* in microseconds */
+} __packed;
+
+/* optional authentication on-wire format */
+struct bfd_auth_header {
+ u_int8_t bfd_auth_type;
+ u_int8_t bfd_auth_len;
+ u_int16_t bfd_auth_data;
+} __packed;
+
+#define BFD_VERSION 1 /* RFC 5880 Page 6 */
+#define BFD_VER(x) (((x) & 0xe0) >> 5)
+#define BFD_DIAG(x) ((x) & 0x1f)
+#define BFD_STATE(x) (((x) & 0xf0) >> 6)
+#define BFD_FLAGS(x) ((x) & 0x0f)
+#define BFD_HDRLEN 24 /* RFC 5880 Page 37 */
+#define BFD_AUTH_SIMPLE_LEN 16 + 3 /* RFC 5880 Page 10 */
+#define BFD_AUTH_MD5_LEN 24 /* RFC 5880 Page 11 */
+#define BFD_AUTH_SHA1_LEN 28 /* RFC 5880 Page 12 */
+
+#define BFD_MODE_ACTIVE 1
+#define BFD_MODE_PASSIVE 2
+
+/* Diagnostic Code (RFC 5880 Page 8) */
+#define BFD_DIAG_NONE 0
+#define BFD_DIAG_EXPIRED 1
+#define BFD_DIAG_ECHO_FAILED 2
+#define BFD_DIAG_NEIGHBOR_SIGDOWN 3
+#define BFD_DIAG_FIB_RESET 4
+#define BFD_DIAG_PATH_DOWN 5
+#define BFD_DIAG_CONCAT_PATH_DOWN 6
+#define BFD_DIAG_ADMIN_DOWN 7
+#define BFD_DIAG_CONCAT_REVERSE_DOWN 8
+
+/* State (RFC 5880 Page 8) */
+#define BFD_STATE_ADMINDOWN 0
+#define BFD_STATE_DOWN 1
+#define BFD_STATE_INIT 2
+#define BFD_STATE_UP 3
+
+/* Flags (RFC 5880 Page 8) */
+#define BFD_FLAG_P 0x20
+#define BFD_FLAG_F 0x10
+#define BFD_FLAG_C 0x08
+#define BFD_FLAG_A 0x04
+#define BFD_FLAG_D 0x02
+#define BFD_FLAG_M 0x01
+
+
+/* Auth Type (RFC 5880 Page 10) */
+#define BFD_AUTH_TYPE_RESERVED 0
+#define BFD_AUTH_TYPE_SIMPLE 1
+#define BFD_AUTH_KEYED_MD5 2
+#define BFD_AUTH_METICULOUS_MD5 3
+#define BFD_AUTH_KEYED_SHA1 4
+#define BFD_AUTH_METICULOUS_SHA1 5
+
+#define BFD_UDP_PORT_CONTROL 3784
+#define BFD_UDP_PORT_ECHO 3785
+
+#define BFD_SECOND 1000000 /* 1,000,000 us == 1 second */
+/* We cannot handle more often than 10ms, so force a minimum */
+#define BFD_MINIMUM 10000 /* 10,000 us == 10 ms */
+
+
+/* These spellings and capitalizations match RFC 5880 6.8.1*/
+/* Do not change */
+struct bfd_state {
+ u_int32_t SessionState;
+ u_int32_t RemoteSessionState;
+ u_int32_t LocalDiscr; /* Unique identifier */
+ u_int32_t RemoteDiscr; /* Unique identifier */
+ u_int32_t LocalDiag;
+ u_int32_t RemoteDiag;
+ u_int32_t DesiredMinTxInterval;
+ u_int32_t RequiredMinRxInterval;
+ u_int32_t RemoteMinRxInterval;
+ u_int32_t DemandMode;
+ u_int32_t RemoteDemandMode;
+ u_int32_t DetectMult; /* Detection Time Multiplier*/
+ u_int32_t AuthType;
+ u_int32_t RcvAuthSeq;
+ u_int32_t XmitAuthSeq;
+ u_int32_t AuthSeqKnown;
+};
+
+struct pool bfd_pool, bfd_pool_peer;
+struct taskq *bfdtq;
+
+struct bfd_softc *bfd_lookup(struct rtentry *);
+struct socket *bfd_listener(struct bfd_softc *, u_int);
+struct socket *bfd_sender(struct bfd_softc *, u_int);
+void bfd_input(struct bfd_softc *, struct mbuf *);
+void bfd_set_state(struct bfd_softc *, int);
+
+int bfd_send(struct bfd_softc *, struct mbuf *);
+void bfd_send_control(void *);
+
+void bfd_start_task(void *);
+void bfd_send_task(void *);
+void bfd_timeout_rx(void *);
+void bfd_timeout_tx(void *);
+
+void bfd_upcall(struct socket *, caddr_t, int);
+void bfd_senddown(struct bfd_softc *);
+void bfd_reset(struct bfd_softc *);
+
+#ifdef DDB
+void bfd_debug(struct bfd_softc *);
+extern void db_print_sa(struct sockaddr *); /* XXX - sys/net/route.c */
+#endif
+
+
+TAILQ_HEAD(bfd_queue, bfd_softc) bfd_queue;
+
+/*
+ * allocate a new bfd session
+ */
+int
+bfd_rtalloc(struct rtentry *rt, struct bfd_flags *flags)
+{
+ struct bfd_softc *sc;
+
+ /* make sure we don't already have this setup */
+ TAILQ_FOREACH(sc, &bfd_queue, bfd_next) {
+ if (sc->sc_rt == rt)
+ return (EADDRINUSE);
+ }
+
+ /* XXX - do we need to force RTM_RESOLVE? */
+
+ /* Do our necessary memory allocations upfront */
+ if ((sc = pool_get(&bfd_pool, PR_WAITOK | PR_ZERO)) == NULL)
+ goto nomem;
+ if ((sc->sc_peer = pool_get(&bfd_pool_peer, PR_WAITOK | PR_ZERO)) == NULL)
+ goto nomem2;
+
+ sc->sc_rt = rt;
+// rtref(sc->sc_rt); /* we depend on this route not going away */
+
+ bfd_reset(sc);
+ sc->sc_peer->LocalDiscr = arc4random(); /* XXX - MUST be globally unique */
+
+ if (!timeout_initialized(&sc->sc_timo_rx))
+ timeout_set(&sc->sc_timo_rx, bfd_timeout_rx, sc);
+ if (!timeout_initialized(&sc->sc_timo_tx))
+ timeout_set(&sc->sc_timo_tx, bfd_timeout_tx, sc);
+
+ TAILQ_INSERT_TAIL(&bfd_queue, sc, bfd_next);
+
+ task_set(&sc->sc_bfd_task, bfd_start_task, sc);
+ task_add(bfdtq, &sc->sc_bfd_task);
+
+ return (0);
+
+ pool_put(&bfd_pool_peer, sc);
+ nomem2:
+ pool_put(&bfd_pool, sc);
+ nomem:
+ return (ENOMEM);
+}
+
+/*
+ * remove and free a bfd session
+ */
+int
+bfd_rtfree(struct rtentry *rt)
+{
+ struct bfd_softc *sc;
+
+ if ((sc = bfd_lookup(rt)) == NULL)
+ return (ENOENT);
+
+ timeout_del(&sc->sc_timo_rx);
+ timeout_del(&sc->sc_timo_tx);
+ task_del(bfdtq, &sc->sc_bfd_send_task);
+
+/* XXX - punt this off to a task */
+ TAILQ_REMOVE(&bfd_queue, sc, bfd_next);
+
+ /* send suicide packets immediately */
+ if (rtisvalid(sc->sc_rt))
+ bfd_senddown(sc);
+
+ soclose(sc->sc_so);
+// sc->sc_so->so_upcall = NULL;
+ sc->sc_so = NULL;
+ pool_put(&bfd_pool_peer, sc->sc_peer);
+ pool_put(&bfd_pool, sc);
+
+ return (0);
+}
+
+/*
+ * Create and initialize the global bfd framework
+ */
+void
+bfdinit(void)
+{
+ pool_init(&bfd_pool, sizeof(struct bfd_softc), 0, 0, 0,
+ "bfd_softc", NULL);
+ pool_setipl(&bfd_pool, IPL_SOFTNET);
+ pool_init(&bfd_pool_peer, sizeof(struct bfd_softc), 0, 0, 0,
+ "bfd_softc_peer", NULL);
+ pool_setipl(&bfd_pool_peer, IPL_SOFTNET);
+
+ /* XXX - requires biglock because of sobind */
+ bfdtq = taskq_create("bfd", 1, IPL_SOFTNET, 0 /* | TASKQ_MPSAFE */);
+ if (bfdtq == NULL)
+ panic("unable to create BFD taskq");
+
+ TAILQ_INIT(&bfd_queue);
+}
+
+/*
+ * Destroy all bfd sessions and remove the tasks
+ *
+ */
+void
+bfddestroy(void)
+{
+ struct bfd_softc *sc;
+ int s;
+
+ /* send suicide packets immediately */
+ while ((sc = TAILQ_FIRST(&bfd_queue))) {
+ bfd_rtfree(sc->sc_rt);
+ }
+
+ s = splsoftnet();
+
+ taskq_destroy(bfdtq);
+ pool_destroy(&bfd_pool);
+ pool_destroy(&bfd_pool_peer);
+
+ splx(s);
+}
+
+/*
+ * End of public interfaces.
+ *
+ * Everything below this line should not be used outside of this file.
+ */
+
+/*
+ * Return the matching bfd
+ */
+struct bfd_softc *
+bfd_lookup(struct rtentry *rt)
+{
+ struct bfd_softc *sc;
+
+ TAILQ_FOREACH(sc, &bfd_queue, bfd_next) {
+ if (sc->sc_rt == rt)
+ return (sc);
+ }
+ return (NULL);
+}
+
+/*
+ * Task to listen and kick off the bfd process
+ */
+void
+bfd_start_task(void *arg)
+{
+ struct bfd_softc *sc = (struct bfd_softc *)arg;
+//struct rtentry *rt = sc->sc_rt;
+//struct ifnet *ifp;
+
+//ifp = if_get(rt->rt_ifidx);
+//printf("%s: if %s dest: ", __func__, ifp->if_xname);
+//db_print_sa(rt->rt_ifa->ifa_addr);
+
+ /* start listeners */
+ sc->sc_so = bfd_listener(sc, BFD_UDP_PORT_CONTROL);
+ if (!sc->sc_so)
+ printf("bfd_listener(%d) failed\n",
+ BFD_UDP_PORT_CONTROL);
+ sc->sc_soecho = bfd_listener(sc, BFD_UDP_PORT_ECHO);
+ if (!sc->sc_soecho)
+ printf("bfd_listener(%d) failed\n",
+ BFD_UDP_PORT_ECHO);
+
+ /* start sending */
+ sc->sc_sosend = bfd_sender(sc, BFD_UDP_PORT_CONTROL);
+ if (sc->sc_sosend) {
+ task_set(&sc->sc_bfd_send_task, bfd_send_task, sc);
+ task_add(bfdtq, &sc->sc_bfd_send_task);
+ }
+
+ return;
+}
+
+void
+bfd_send_task(void *arg)
+{
+ struct bfd_softc *sc = (struct bfd_softc *)arg;
+//struct rtentry *rt = sc->sc_rt;
+//struct ifnet *ifp;
+
+//ifp = if_get(rt->rt_ifidx);
+//printf("%s: if %s dest: ", __func__, ifp->if_xname);
+
+//bfd_debug(sc);
+// bfd_send_control(sc);
+printf("%s: timeout_tx: %u\n", __func__, sc->mintx);
+ if (!timeout_pending(&sc->sc_timo_tx))
+ timeout_add_usec(&sc->sc_timo_tx, sc->mintx);
+
+ return;
+}
+
+/*
+ * Setup a bfd listener socket
+ */
+struct socket *
+bfd_listener(struct bfd_softc *sc, u_int port)
+{
+ struct proc *p = curproc;
+ struct rtentry *rt = sc->sc_rt;
+ struct sockaddr *src = rt->rt_ifa->ifa_addr;
+ struct sockaddr *dst = rt->rt_gateway;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct socket *so;
+ struct mbuf *m;
+ int error;
+
+ /* sa_family and sa_len must be equal */
+ if (src->sa_family != dst->sa_family || src->sa_len != dst->sa_len)
+ return (NULL);
+
+ error = socreate(dst->sa_family, &so, SOCK_DGRAM, 0);
+ if (error) {
+ printf("%s: socreate error %d\n",
+ __func__, error);
+ return (NULL);
+ }
+ MGET(m, M_WAIT, MT_SONAME);
+ m->m_len = src->sa_len;
+ sa = mtod(m, struct sockaddr *);
+ memcpy(sa, src, src->sa_len);
+ switch(sa->sa_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)sa;
+ sin->sin_port = htons(port);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)sa;
+ sin6->sin6_port = htons(port);
+ break;
+ default:
+ break;
+ }
+
+ error = sobind(so, m, p);
+ if (error) {
+ soclose(so);
+ printf("%s: sobind error %d\n",
+ __func__, error);
+ return (NULL);
+ }
+ so->so_upcallarg = (caddr_t)sc;
+ so->so_upcall = bfd_upcall;
+
+ return (so);
+}
+
+/*
+ * Setup the bfd sending process
+ */
+struct socket *
+bfd_sender(struct bfd_softc *sc, u_int port)
+{
+ struct socket *so;
+ struct rtentry *rt = sc->sc_rt;
+ struct proc *p = curproc;
+ struct mbuf *m, *mopt;
+ struct sockaddr *src = rt->rt_ifa->ifa_addr;
+ struct sockaddr *dst = rt->rt_gateway;
+ struct sockaddr *sa;
+ struct sockaddr_in6 *sin6;
+ struct sockaddr_in *sin;
+ int error, s, *ip;
+
+ /* sa_family and sa_len must be equal */
+ if (src->sa_family != dst->sa_family || src->sa_len != dst->sa_len)
+ return (NULL);
+
+ s = splsoftnet();
+ error = socreate(dst->sa_family, &so, SOCK_DGRAM, 0);
+ splx(s);
+
+ if (error)
+ return (NULL);
+
+ MGET(mopt, M_WAIT, MT_SOOPTS);
+ mopt->m_len = sizeof(int);
+ ip = mtod(mopt, int *);
+ *ip = IP_PORTRANGE_HIGH;
+ error = sosetopt(so, IPPROTO_IP, IP_PORTRANGE, mopt);
+ if (error) {
+ printf("%s: sosetopt error %d\n",
+ __func__, error);
+ goto close;
+ }
+
+ MGET(mopt, M_WAIT, MT_SOOPTS);
+ mopt->m_len = sizeof(int);
+ ip = mtod(mopt, int *);
+ *ip = 255; /* XXX - use a #define */
+ error = sosetopt(so, IPPROTO_IP, IP_TTL, mopt);
+ if (error) {
+ printf("%s: sosetopt error %d\n",
+ __func__, error);
+ goto close;
+ }
+
+ MGET(m, M_WAIT, MT_SONAME);
+ m->m_len = src->sa_len;
+ sa = mtod(m, struct sockaddr *);
+ memcpy(sa, src, src->sa_len);
+ switch(sa->sa_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)sa;
+ sin->sin_port = 0;
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)sa;
+ sin6->sin6_port = 0;
+ break;
+ default:
+ break;
+ }
+
+ s = splsoftnet();
+ error = sobind(so, m, p);
+ splx(s);
+ if (error) {
+ printf("%s: sobind error %d\n",
+ __func__, error);
+ goto close;
+ }
+
+ memcpy(sa, dst, dst->sa_len);
+ switch(sa->sa_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)sa;
+ sin->sin_port = ntohs(port);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)sa;
+ sin6->sin6_port = ntohs(port);
+ break;
+ default:
+ break;
+ }
+
+ s = splsoftnet();
+ error = soconnect(so, m);
+ splx(s);
+
+ if (error && error != ECONNREFUSED) {
+ printf("%s: soconnect error %d\n",
+ __func__, error);
+ goto close;
+ }
+
+ m_free(m);
+
+ return (so);
+
+ close:
+ m_free(m);
+ soclose(so);
+
+ return (NULL);
+
+}
+
+/*
+ * Will be called per-received packet
+ */
+void
+bfd_upcall(struct socket *so, caddr_t arg, int waitflag)
+{
+ struct bfd_softc *sc = (struct bfd_softc *)arg;
+ struct mbuf *m;
+ struct uio uio;
+ int flags, error;
+
+ uio.uio_procp = NULL;
+ do {
+ uio.uio_resid = 1000000000;
+ flags = MSG_DONTWAIT;
+ error = soreceive(so, NULL, &uio, &m, NULL, &flags, 0);
+ if (error && error != EAGAIN) {
+ /* XXX - handle errors */
+printf("%s soreceive error %d\n", __func__, error);
+ return;
+ }
+ if (m != NULL)
+ bfd_input(sc, m);
+ } while (m != NULL);
+
+ return;
+}
+
+
+void
+bfd_timeout_tx(void *v)
+{
+ struct bfd_softc *sc = v;
+ struct rtentry *rt = sc->sc_rt;
+
+ if (ISSET(rt->rt_gwroute->rt_flags, RTF_UP)) {
+ bfd_send_control(sc);
+ } else {
+ sc->error++;
+ sc->sc_peer->LocalDiag = BFD_DIAG_ADMIN_DOWN;
+ if (sc->sc_peer->SessionState > BFD_STATE_DOWN) {
+ bfd_reset(sc);
+ bfd_set_state(sc, BFD_STATE_DOWN);
+ }
+ }
+
+ /* XXX - we're getting lucky with timing, need a better mechanism */
+printf("%s: timeout_tx: %u\n", __func__, sc->mintx);
+ if (!timeout_pending(&sc->sc_timo_tx))
+ timeout_add_usec(&sc->sc_timo_tx, sc->mintx);
+}
+
+/*
+ * Triggers when we do not receive a valid packet in time
+ */
+void
+bfd_timeout_rx(void *v)
+{
+ struct bfd_softc *sc = v;
+// struct rtentry *rt = sc->sc_rt;
+
+
+bfd_debug(sc);
+
+ if (((sc->sc_peer->SessionState > BFD_STATE_DOWN) &&
+ (++sc->error >= sc->sc_peer->DetectMult)) /* ||
+ (!ISSET(rt->rt_gwroute->rt_flags, RTF_UP)) */) {
+ sc->sc_peer->LocalDiag = BFD_DIAG_EXPIRED;
+printf("%s: failed, sc->error %u\n", __func__, sc->error);
+ bfd_reset(sc);
+ bfd_set_state(sc, BFD_STATE_DOWN);
+
+ return;
+ }
+printf("%s: error #%u\n", __func__, sc->error);
+
+printf("%s: timeout_rx: %u\n", __func__, sc->minrx);
+ timeout_add_usec(&sc->sc_timo_rx, sc->minrx);
+
+}
+
+/*
+ * Tell our neighbor that we are going down
+ */
+void
+bfd_senddown(struct bfd_softc *sc)
+{
+printf("%s", __func__);
+//bfd_debug(sc);
+
+ /* If we are down, return early */
+ if (sc->state < BFD_STATE_INIT);
+ return;
+
+ sc->sc_peer->SessionState = BFD_STATE_ADMINDOWN;
+ if (sc->sc_peer->LocalDiag == 0)
+ sc->sc_peer->LocalDiag = BFD_DIAG_ADMIN_DOWN;
+printf("%s: sc->sc_peer->LocalDiag %u", __func__, sc->sc_peer->LocalDiag);
+
+ bfd_send_control(sc);
+
+ return;
+}
+
+/*
+ * Clean a BFD peer to defaults
+ */
+void
+bfd_reset(struct bfd_softc *sc)
+{
+if (sc->error)
+printf("%s: error=%u\n", __func__, sc->error);
+
+ /* Clean */
+ sc->sc_peer->RemoteDiscr = 0;
+ sc->sc_peer->DemandMode = 0;
+ sc->sc_peer->RemoteDemandMode = 0;
+ sc->sc_peer->AuthType = 0;
+ sc->sc_peer->RcvAuthSeq = 0;
+ sc->sc_peer->XmitAuthSeq = 0;
+ sc->sc_peer->AuthSeqKnown = 0;
+ sc->sc_peer->LocalDiag = 0;
+
+ sc->mode = BFD_MODE_ACTIVE;
+ sc->state = BFD_STATE_DOWN;
+
+ /* Set RFC mandated values */
+ sc->sc_peer->SessionState = BFD_STATE_DOWN;
+ sc->sc_peer->RemoteSessionState = BFD_STATE_DOWN;
+ sc->sc_peer->DesiredMinTxInterval = BFD_SECOND;
+ sc->sc_peer->RequiredMinRxInterval = BFD_SECOND; /* rfc5880 6.8.18 */
+ sc->sc_peer->RemoteMinRxInterval = 1;
+ sc->sc_peer->DetectMult = 3; /* XXX - MUST be nonzero */
+// sc->sc_peer->LocalDiscr = arc4random(); /* XXX - MUST be globally unique */
+// sc->sc_peer->RcvAuthSeq = arc4random();
+
+// sc->ttl = MAXTTL;
+
+ sc->mintx = sc->sc_peer->DesiredMinTxInterval;
+ sc->minrx = sc->sc_peer->RemoteMinRxInterval;
+ sc->multiplier = sc->sc_peer->DetectMult;
+printf("%s: localdiscr: 0x%x\n", __func__, sc->sc_peer->LocalDiscr);
+
+ return;
+}
+
+void
+bfd_input(struct bfd_softc *sc, struct mbuf *m)
+{
+ struct bfd_header *peer;
+ struct bfd_auth_header *auth;
+ struct mbuf *mp, *mp0;
+ u_int ver, diag, state, flags;
+ int offp;
+
+ mp = m_pulldown(m, 0, sizeof(*peer), &offp);
+
+ if (mp == NULL)
+ return;
+ peer = (struct bfd_header *)(mp->m_data + offp);
+
+#if 0
+ /* XXX check TTL security */
+ if (peer->ttl != MAXTTL)
+ goto discard;
+#endif
+
+ /* We only support BFD Version 1 */
+ if (( ver = BFD_VER(peer->bfd_ver_diag)) != 1)
+ goto discard;
+
+ diag = BFD_DIAG(peer->bfd_ver_diag);
+ state = BFD_STATE(peer->bfd_sta_flags);
+ flags = BFD_FLAGS(peer->bfd_sta_flags);
+
+ if (peer->bfd_length + offp != mp->m_len) {
+ printf("%s: bad len %d != %d\n", __func__, peer->bfd_length + offp, mp->m_len);
+ goto discard;
+ }
+
+ if (peer->bfd_detect_multi == 0)
+ goto discard;
+ if (ntohl(peer->bfd_my_discriminator) == 0)
+ goto discard;
+ if (ntohl(peer->bfd_your_discriminator) == 0 &&
+ BFD_STATE(peer->bfd_sta_flags) > BFD_STATE_DOWN)
+ goto discard;
+ if ((ntohl(peer->bfd_your_discriminator) != 0) &&
+ (ntohl(peer->bfd_your_discriminator) != sc->sc_peer->LocalDiscr)) {
+ sc->error++;
+printf("%s: peer your discr 0x%x != local 0x%x\n",
+ __func__, ntohl(peer->bfd_your_discriminator), sc->sc_peer->LocalDiscr);
+ sc->sc_peer->LocalDiag = BFD_DIAG_EXPIRED;
+ bfd_senddown(sc);
+ goto discard;
+ }
+
+ if ((flags & BFD_FLAG_A) && sc->sc_peer->AuthType == 0)
+ goto discard;
+ if (!(flags & BFD_FLAG_A) && sc->sc_peer->AuthType != 0)
+ goto discard;
+ if (flags & BFD_FLAG_A) {
+ mp0 = m_pulldown(mp, 0, sizeof(*auth), &offp);
+ if (mp0 == NULL)
+ goto discard;
+ auth = (struct bfd_auth_header *)(mp0->m_data + offp);
+#if 0
+ if (bfd_process_auth(sc, auth) != 0)
+ goto discard;
+#endif
+ }
+
+ if ((sc->sc_peer->RemoteDiscr == 0) &&
+ (ntohl(peer->bfd_my_discriminator) != 0))
+ sc->sc_peer->RemoteDiscr = ntohl(peer->bfd_my_discriminator);
+
+ if (sc->sc_peer->RemoteDiscr != ntohl(peer->bfd_my_discriminator))
+ goto discard;
+
+ sc->sc_peer->RemoteSessionState = state;
+ sc->error = 0;
+ sc->sc_peer->RemoteMinRxInterval = peer->bfd_required_min_rx_interval;
+
+ if (sc->sc_peer->RemoteMinRxInterval < BFD_MINIMUM)
+ sc->sc_peer->RemoteMinRxInterval = BFD_MINIMUM;
+
+ if (sc->sc_peer->RemoteMinRxInterval > 30 * BFD_SECOND) {
+printf("%s: RemoteMinRxInterval is massive: %u\n", __func__,
+ sc->sc_peer->RemoteMinRxInterval);
+ sc->sc_peer->RemoteMinRxInterval = BFD_SECOND;
+ }
+
+ sc->minrx = sc->sc_peer->RequiredMinRxInterval;
+ /* rfc5880 6.8.7 */
+ sc->mintx = max(sc->sc_peer->RemoteMinRxInterval,
+ sc->sc_peer->DesiredMinTxInterval);
+
+//printf("%s1 \n", __func__);
+ if (sc->sc_peer->RemoteSessionState == BFD_STATE_ADMINDOWN) {
+ if (sc->sc_peer->SessionState != BFD_STATE_DOWN) {
+ sc->sc_peer->LocalDiag = BFD_DIAG_NEIGHBOR_SIGDOWN;
+ sc->sc_peer->SessionState = BFD_STATE_DOWN;
+ }
+ goto discard;
+ }
+
+//printf("%s2 \n", __func__);
+
+ /* According the to pseudo-code RFC 5880 page 34 */
+ if (sc->sc_peer->SessionState == BFD_STATE_DOWN) {
+printf("%s: BFD_STATE_DOWN remote 0x%x ", __func__, ntohl(peer->bfd_my_discriminator));
+printf("local 0x%x\n", ntohl(peer->bfd_your_discriminator));
+bfd_debug(sc);
+ if (sc->sc_peer->RemoteSessionState == BFD_STATE_DOWN)
+ sc->sc_peer->SessionState = BFD_STATE_INIT;
+ else if (sc->sc_peer->RemoteSessionState == BFD_STATE_INIT) {
+printf("%s: set BFD_STATE_UP\n", __func__);
+ sc->sc_peer->LocalDiag = 0;
+ bfd_set_state(sc, BFD_STATE_UP);
+ }
+ } else if (sc->sc_peer->SessionState == BFD_STATE_INIT) {
+printf("%s: BFD_STATE_INIT remote 0x%x ", __func__, ntohl(peer->bfd_my_discriminator));
+printf("local 0x%x\n", ntohl(peer->bfd_your_discriminator));
+
+ if (sc->sc_peer->RemoteSessionState >= BFD_STATE_INIT) {
+printf("%s: set BFD_STATE_UP\n", __func__);
+ sc->sc_peer->LocalDiag = 0;
+ bfd_set_state(sc, BFD_STATE_UP);
+ } else {
+//printf("%s2b \n", __func__);
+ goto discard;
+ }
+ } else {
+ if (sc->sc_peer->RemoteSessionState == BFD_STATE_DOWN) {
+printf("%s: set BFD_STATE_DOWN\n", __func__);
+ sc->sc_peer->LocalDiag = BFD_DIAG_NEIGHBOR_SIGDOWN;
+ bfd_set_state(sc, BFD_STATE_DOWN);
+ goto discard;
+ }
+ }
+//printf("%s3 \n", __func__);
+
+ if (sc->sc_peer->SessionState == BFD_STATE_UP) {
+ sc->sc_peer->LocalDiag = 0;
+ sc->sc_peer->DemandMode = 1;
+ sc->sc_peer->RemoteDemandMode = (flags & BFD_FLAG_D);
+ }
+
+
+ discard:
+ m_free(m);
+
+ task_add(bfdtq, &sc->sc_bfd_send_task);
+ timeout_add_usec(&sc->sc_timo_rx, sc->minrx);
+
+ return;
+}
+
+void
+bfd_set_state(struct bfd_softc *sc, int state)
+{
+ struct ifnet *ifp;
+ struct rtentry *rt = sc->sc_rt;
+ int new_state;
+
+ ifp = if_get(rt->rt_ifidx);
+ sc->sc_peer->SessionState = state;
+
+ if (!rtisvalid(rt))
+ sc->sc_peer->SessionState = BFD_STATE_ADMINDOWN;
+
+ switch (sc->sc_peer->SessionState) {
+ case BFD_STATE_ADMINDOWN:
+ new_state = RTM_BFD;
+// rt->rt_flags &= ~RTF_BFDUP;
+// rt->rt_flags |= RTF_BFDDOWN;
+ break;
+ case BFD_STATE_DOWN:
+ new_state = RTM_BFD;
+// rt->rt_flags &= ~RTF_BFDUP;
+// rt->rt_flags |= RTF_BFDDOWN;
+ break;
+ case BFD_STATE_UP:
+ new_state = RTM_BFD;
+// rt->rt_flags &= ~RTF_BFDDOWN;
+// rt->rt_flags |= RTF_BFDUP;
+ break;
+ }
+
+printf("%s: BFD set linkstate %u (oldstate: %u)\n", ifp->if_xname, new_state, state);
+ rt_sendmsg(rt, new_state, ifp->if_rdomain);
+
+ return;
+}
+
+void
+bfd_send_control(void *x)
+{
+ struct bfd_softc *sc = x;
+ struct mbuf *m;
+ struct bfd_header bfd;
+ int error;
+
+ MGETHDR(m, M_WAIT, MT_DATA);
+ MCLGET(m, M_WAIT);
+
+ m->m_len = m->m_pkthdr.len = sizeof(bfd);
+
+ memset(&bfd, 0xff, sizeof(bfd)); /* canary */
+
+ bfd.bfd_ver_diag = ((BFD_VERSION << 5) | (sc->sc_peer->LocalDiag));
+ bfd.bfd_sta_flags = (sc->sc_peer->SessionState << 6);
+ bfd.bfd_detect_multi = sc->sc_peer->DetectMult;
+ bfd.bfd_length = BFD_HDRLEN;
+ bfd.bfd_my_discriminator = htonl(sc->sc_peer->LocalDiscr);
+ bfd.bfd_your_discriminator = htonl(sc->sc_peer->RemoteDiscr);
+
+ bfd.bfd_desired_min_tx_interval =
+ htonl(sc->sc_peer->DesiredMinTxInterval);
+ bfd.bfd_required_min_rx_interval =
+ htonl(sc->sc_peer->RequiredMinRxInterval);
+ bfd.bfd_required_min_echo_interval =
+ htonl(sc->sc_peer->RemoteMinRxInterval);
+
+ m_copyback(m, 0, BFD_HDRLEN, &bfd, M_NOWAIT);
+
+ error = bfd_send(sc, m);
+
+ if (error) {
+ if (!(error == EHOSTDOWN || error == ECONNREFUSED)) {
+ printf("%s: %u\n", __func__, error);
+ }
+ }
+}
+
+int
+bfd_send(struct bfd_softc *sc, struct mbuf *m)
+{
+ struct rtentry *rt = sc->sc_rt;
+
+ if (!ISSET(rt->rt_gwroute->rt_flags, RTF_UP)) {
+ m_free(m);
+ return (EHOSTDOWN);
+ }
+
+ return (sosend(sc->sc_sosend, NULL, NULL, m, NULL, MSG_DONTWAIT));
+}
+
+
+
+#ifdef DDB
+
+/*
+ * Print debug information about this bfd instance
+ */
+void
+bfd_debug(struct bfd_softc *sc)
+{
+ struct ifnet *ifp;
+ struct rtentry *rt = sc->sc_rt;
+
+ ifp = if_get(rt->rt_ifidx);
+
+// printf("%s if: %s AF: %x ",
+// __func__, ifp->if_xname,
+// rt->rt_gateway->sa_family);
+//printf("gw: "); db_print_sa(rt->rt_gateway);
+// printf("\n");
+
+ printf("session state: %u ", sc->state);
+ printf("mode: %u ", sc->mode);
+ printf("error: %u ", sc->error);
+ printf("minrx: %u ", sc->minrx);
+ printf("mintx: %u ", sc->mintx);
+ printf("multiplier: %u ", sc->multiplier);
+ printf("\n");
+ printf("\t");
+ printf("session state: %u ", sc->sc_peer->SessionState);
+ printf("local diag: %u ", sc->sc_peer->LocalDiag);
+ printf("\n");
+ printf("\t");
+ printf("remote discriminator: 0x%x ", sc->sc_peer->RemoteDiscr);
+ printf("local discriminator: 0x%x ", sc->sc_peer->LocalDiscr);
+ printf("\n");
+ printf("\t");
+ printf("remote session state: %u ", sc->sc_peer->RemoteSessionState);
+ printf("remote diag: %u ", sc->sc_peer->RemoteDiag);
+ printf("remote min rx: %u ", sc->sc_peer->RemoteMinRxInterval);
+ printf("\n");
+}
+#endif /* DDB */
diff --git a/sys/net/bfd.h b/sys/net/bfd.h
new file mode 100644
index 00000000000..cbbaab5ef7b
--- /dev/null
+++ b/sys/net/bfd.h
@@ -0,0 +1,71 @@
+/* $OpenBSD: bfd.h,v 1.1 2016/09/03 14:14:20 phessler Exp $ */
+
+/*
+ * Copyright (c) 2016 Peter Hessler <phessler@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Support for Bi-directional Forwarding Detection (RFC 5880 / 5881)
+ */
+
+
+#ifndef _NET_BFD_H_
+#define _NET_BFD_H_
+
+/* Public Interface */
+
+struct bfd_msghdr {
+ u_short rtm_msglen; /* to skip over non-understood messages */
+ u_char rtm_version; /* future binary compatibility */
+ u_char rtm_type; /* message type */
+ u_short rtm_hdrlen; /* sizeof(rt_msghdr) to skip over the header */
+
+ u_int16_t mode; /* */
+ u_int32_t minimum; /* minimum time (us) to send */
+ u_int32_t rx; /* minimum window (us) to receive */
+ u_int16_t multiplier; /* Retry backoff multiplier */
+
+};
+
+
+struct bfd_softc {
+ TAILQ_ENTRY(bfd_softc) bfd_next;
+ struct socket *sc_so;
+ struct socket *sc_soecho;
+ struct socket *sc_sosend;
+ struct rtentry *sc_rt;
+ struct bfd_state *sc_peer;
+ struct task sc_bfd_task;
+ struct task sc_bfd_send_task;
+ struct timeout sc_timo_rx;
+ struct timeout sc_timo_tx;
+ int state;
+ int mode;
+ int error;
+ int minrx;
+ int mintx;
+ int multiplier;
+};
+
+struct bfd_flags {
+ int version;
+};
+
+int bfd_rtalloc(struct rtentry *, struct bfd_flags *);
+int bfd_rtfree(struct rtentry *);
+void bfdinit(void);
+void bfddestroy(void);
+
+#endif /* _NET_BFD_H_ */