[ovs-dev] [bfd] bfd: Implement Bidirectional Forwarding Detection.

Ethan Jackson ethan at nicira.com
Mon May 6 19:11:24 UTC 2013


Traditionally, Open vSwitch has used a variant of 802.1ag "CFM" for
interface liveness detection.  This has served us well until now,
but has several serious drawbacks which have steadily become more
inconvenient.  First, the 802.1ag standard does not implement
several useful features forcing us to (optionally) break
compatibility.  Second, 802.1.ag is not particularly popular
outside of carrier grade networking equipment.  Third, 802.1ag is
simply quite awkward.

In an effort to solve the aforementioned problems, this patch
implements BFD which is ubiquitous, well designed, straight
forward, and implements required features in a standard way.  The
initial cut of the protocol focuses on getting the basics of the
specification correct, leaving performance optimizations, and
advanced features as future work.  The protocol should be
considered experimental pending future testing.

Signed-off-by: Ethan Jackson <ethan at nicira.com>
---
 lib/automake.mk            |    2 +
 lib/bfd.c                  |  873 ++++++++++++++++++++++++++++++++++++++++++++
 lib/bfd.h                  |   46 +++
 lib/odp-util.c             |    2 +
 lib/odp-util.h             |    7 +-
 lib/util.h                 |    1 +
 ofproto/ofproto-dpif.c     |   64 +++-
 ofproto/ofproto-provider.h |   16 +
 ofproto/ofproto.c          |   39 ++
 ofproto/ofproto.h          |    7 +
 vswitchd/bridge.c          |   12 +
 vswitchd/vswitch.ovsschema |   10 +-
 vswitchd/vswitch.xml       |   92 +++++
 13 files changed, 1164 insertions(+), 7 deletions(-)
 create mode 100644 lib/bfd.c
 create mode 100644 lib/bfd.h

diff --git a/lib/automake.mk b/lib/automake.mk
index 1d58604..f340c60 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -12,6 +12,8 @@ lib_libopenvswitch_a_SOURCES = \
 	lib/aes128.h \
 	lib/backtrace.c \
 	lib/backtrace.h \
+	lib/bfd.c \
+	lib/bfd.h \
 	lib/bitmap.c \
 	lib/bitmap.h \
 	lib/bond.c \
diff --git a/lib/bfd.c b/lib/bfd.c
new file mode 100644
index 0000000..95dad2d
--- /dev/null
+++ b/lib/bfd.c
@@ -0,0 +1,873 @@
+/* Copyright (c) 2013 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. */
+
+#include <config.h>
+#include "bfd.h"
+
+#include <arpa/inet.h>
+
+#include "csum.h"
+#include "dpif.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "hash.h"
+#include "hmap.h"
+#include "list.h"
+#include "netlink.h"
+#include "odp-util.h"
+#include "ofpbuf.h"
+#include "openvswitch/types.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "random.h"
+#include "smap.h"
+#include "timeval.h"
+#include "unixctl.h"
+#include "util.h"
+#include "vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(bfd);
+
+/* XXX Finish BFD.
+ *
+ * The goal of this module is to replace CFM with something both more flexible
+ * and standards compliant.  In service of this goal, the following needs to be
+ * done.
+ *
+ * - Compliance
+ *   * Implement Demand mode.
+ *   * Go through the RFC line by line and verify we comply.
+ *   * Test against a hardware implementation.  Preferably a popular one.
+ *   * Delete BFD packets with nw_ttl != 255 in the datapath to prevent DOS
+ *     attacks.
+ *
+ * - Unit tests.
+ *
+ * - BFD show into ovs-bugtool.
+ *
+ * - Set TOS/PCP on inner BFD frame, and outer tunnel header when encapped.
+ *
+ * - CFM "check_tnl_key" option equivalent.
+ *
+ * - CFM "fault override" equivalent.
+ *
+ * - Sending BFD messages should be in its own thread/process.
+ *
+ * - Scale testing.  How does it operate when there are large number of bfd
+ *   sessions?  Do we ever have random flaps?  What's the CPU utilization?
+ *
+ * - Rely on data traffic for liveness by using BFD demand mode.
+ *   If we're receiving traffic on a port, we can safely assume it's up (modulo
+ *   unidrectional failures).  BFD has a demand mode in which it can stay quiet
+ *   unless it feels the need to check the status of the port.  Using this, we
+ *   can implement a strategy in which BFD only sends control messages on dark
+ *   interfaces.
+ *
+ * - Depending on how one interprets the spec, it appears that a BFD session
+ *   can never change bfd.LocalDiag to "No Diagnostic".  We should verify that
+ *   this is what hardware implementations actually do.  Seems like "No
+ *   Diagnostic" should be set once a BFD session state goes UP. */
+
+#define BFD_VERSION 1
+
+enum flags {
+    FLAG_MULTIPOINT = 1 << 0,
+    FLAG_DEMAND = 1 << 1,
+    FLAG_AUTH = 1 << 2,
+    FLAG_CTL = 1 << 3,
+    FLAG_FINAL = 1 << 4,
+    FLAG_POLL = 1 << 5
+};
+
+enum state {
+    STATE_ADMIN_DOWN = 0 << 6,
+    STATE_DOWN = 1 << 6,
+    STATE_INIT = 2 << 6,
+    STATE_UP = 3 << 6
+};
+
+enum diag {
+    DIAG_NONE = 0,                /* No Diagnostic. */
+    DIAG_EXPIRED = 1,             /* Control Detection Time Expired. */
+    DIAG_ECHO_FAILED = 2,         /* Echo Function Failed. */
+    DIAG_RMT_DOWN = 3,            /* Neighbor Signaled Session Down. */
+    DIAG_FWD_RESET = 4,           /* Forwarding Plane Reset. */
+    DIAG_PATH_DOWN = 5,           /* Path Down. */
+    DIAG_CPATH_DOWN = 6,          /* Concatenated Path Down. */
+    DIAG_ADMIN_DOWN = 7,          /* Administratively Down. */
+    DIAG_RCPATH_DOWN = 8          /* Reverse Concatenated Path Down. */
+};
+
+/* RFC 5880 Section 4.1
+ *  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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |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                 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */
+struct msg {
+    uint8_t vers_diag;    /* Version and diagnostic. */
+    uint8_t flags;        /* 2bit State field followed by flags. */
+    uint8_t mult;         /* Fault detection multiplier. */
+    uint8_t length;       /* Length of this BFD message. */
+    ovs_be32 my_disc;     /* My discriminator. */
+    ovs_be32 your_disc;   /* Your discriminator. */
+    ovs_be32 min_tx;      /* Desired minimum tx interval. */
+    ovs_be32 min_rx;      /* Required minimum rx interval. */
+    ovs_be32 min_rx_echo; /* Required minimum echo rx interval. */
+};
+BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct msg));
+
+#define DIAG_MASK 0x1f
+#define VERS_SHIFT 5
+#define STATE_MASK 0xC0
+#define FLAGS_MASK 0x3f
+
+struct bfd {
+    struct hmap_node node;        /* In 'all_bfds'. */
+    uint32_t disc;                /* bfd.LocalDiscr. Key in 'all_bfds' hmap. */
+
+    char *name;                   /* Name used for logging. */
+
+    bool cpath_down;              /* Concatenated Path Down. */
+    uint8_t mult;                 /* bfd.DetectMult. */
+
+    enum state state;             /* bfd.SessionState. */
+    enum state rmt_state;         /* bfd.RemoteSessionState. */
+
+    enum diag diag;               /* bfd.LocalDiag. */
+    enum diag rmt_diag;           /* Remote diagnostic. */
+
+    enum flags flags;             /* Flags sent on messages. */
+    enum flags rmt_flags;         /* Flags last received. */
+
+    uint32_t rmt_disc;            /* bfd.RemoteDiscr. */
+
+    uint16_t udp_src;             /* UDP source port. */
+
+    /* All timers in milliseconds. */
+    long long int rmt_min_rx;     /* bfd.RemoteMinRxInterval. */
+    long long int rmt_min_tx;     /* Remote minimum TX interval. */
+
+    long long int cfg_min_tx;     /* Configured minimum TX rate. */
+    long long int cfg_min_rx;     /* Configured required minimum RX rate. */
+    long long int poll_min_tx;    /* Min TX negotating in a poll sequence. */
+    long long int poll_min_rx;    /* Min RX negotating in a poll sequence. */
+    long long int min_tx;         /* bfd.DesiredMinTxInterval. */
+    long long int min_rx;         /* bfd.RequiredMinRxInterval. */
+
+    long long int last_tx;        /* Last TX time. */
+    long long int next_tx;        /* Next TX time. */
+    long long int detect_time;    /* RFC 5880 6.8.4 Detection time. */
+};
+
+static bool bfd_in_poll(const struct bfd *);
+static void bfd_poll(struct bfd *bfd);
+static const char *bfd_diag_str(enum diag);
+static const char *bfd_state_str(enum state);
+static long long int bfd_min_tx(const struct bfd *);
+static long long int bfd_tx_interval(const struct bfd *);
+static long long int bfd_rx_interval(const struct bfd *);
+static void bfd_set_next_tx(struct bfd *);
+static void bfd_set_state(struct bfd *, enum state, enum diag);
+static uint32_t generate_discriminator(void);
+static void bfd_put_details(struct ds *, const struct bfd *);
+static void bfd_unixctl_show(struct unixctl_conn *, int argc,
+                             const char *argv[], void *aux OVS_UNUSED);
+static void log_msg(enum vlog_level, const struct msg *, const char *message,
+                    const struct bfd *);
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 20);
+static struct hmap all_bfds = HMAP_INITIALIZER(&all_bfds);
+
+/* Returns true if the interface on which 'bfd' is running may be used to
+ * forward traffic according to the BFD session state. */
+bool
+bfd_forwarding(const struct bfd *bfd)
+{
+    return bfd->state == STATE_UP
+        && bfd->rmt_diag != DIAG_PATH_DOWN
+        && bfd->rmt_diag != DIAG_CPATH_DOWN
+        && bfd->rmt_diag != DIAG_RCPATH_DOWN;
+}
+
+/* Returns a 'smap' of key value pairs representing the status of 'bfd'
+ * intended for the OVS database. */
+void
+bfd_get_status(const struct bfd *bfd, struct smap *smap)
+{
+    smap_add(smap, "forwarding", bfd_forwarding(bfd) ? "true" : "false");
+    smap_add(smap, "state", bfd_state_str(bfd->state));
+    smap_add(smap, "diagnostic", bfd_diag_str(bfd->diag));
+
+    if (bfd->state != STATE_DOWN) {
+        smap_add(smap, "remote_state", bfd_state_str(bfd->rmt_state));
+        smap_add(smap, "remote_diagnostic", bfd_diag_str(bfd->rmt_diag));
+    }
+}
+
+/* Initializes, destroys, or reconfigures the BFD session 'bfd' (named 'name'),
+ * according to the database configuration contained in 'cfg'.  Takes ownership
+ * of 'bfd', which may be NULL.  Returns a BFD object which may be used as a
+ * handle for the session, or NULL if BFD is not enabled according to 'cfg'. */
+struct bfd *
+bfd_configure(struct bfd *bfd, const char *name,
+              const struct smap *cfg)
+{
+    static uint16_t udp_src = 0;
+    static bool init = false;
+
+    long long int min_tx, min_rx;
+    bool cpath_down;
+
+    if (!init) {
+        unixctl_command_register("bfd/show", "[interface]", 0, 1,
+                                 bfd_unixctl_show, NULL);
+        init = true;
+    }
+
+    if (!smap_get_bool(cfg, "enable", false)) {
+        if (bfd) {
+            hmap_remove(&all_bfds, &bfd->node);
+            free(bfd->name);
+            free(bfd);
+        }
+        return NULL;
+    }
+
+    if (!bfd) {
+        bfd = xzalloc(sizeof *bfd);
+        bfd->name = xstrdup(name);
+        bfd->disc = generate_discriminator();
+        hmap_insert(&all_bfds, &bfd->node, bfd->disc);
+
+        bfd->diag = DIAG_NONE;
+        bfd->min_tx = 1000;
+        bfd->mult = 3;
+
+        /* RFC 5881 section 4
+         * The source port MUST be in the range 49152 through 65535.  The same
+         * UDP source port number MUST be used for all BFD Control packets
+         * associated with a particular session.  The source port number SHOULD
+         * be unique among all BFD sessions on the system. */
+        bfd->udp_src = (udp_src++ % 16384) + 49152;
+
+        bfd_set_state(bfd, STATE_DOWN, DIAG_NONE);
+    }
+
+    min_tx = smap_get_int(cfg, "min_tx", 100);
+    min_tx = MAX(min_tx, 100);
+    if (bfd->cfg_min_tx != min_tx) {
+        bfd->cfg_min_tx = min_tx;
+        if (bfd->state != STATE_UP
+            || (!bfd_in_poll(bfd) && bfd->cfg_min_tx < bfd->min_tx)) {
+            bfd->min_tx = bfd->cfg_min_tx;
+        }
+        bfd_poll(bfd);
+    }
+
+    min_rx = smap_get_int(cfg, "min_rx", 1000);
+    min_rx = MAX(min_rx, 100);
+    if (bfd->cfg_min_rx != min_rx) {
+        bfd->cfg_min_rx = min_rx;
+        if (bfd->state != STATE_UP
+            || (!bfd_in_poll(bfd) && bfd->cfg_min_rx > bfd->min_rx)) {
+            bfd->min_rx = bfd->cfg_min_rx;
+        }
+        bfd_poll(bfd);
+    }
+
+    cpath_down = smap_get_bool(cfg, "cpath_down", false);
+    if (bfd->cpath_down != cpath_down) {
+        bfd->cpath_down = cpath_down;
+        if (bfd->diag == DIAG_NONE || bfd->diag == DIAG_CPATH_DOWN) {
+            bfd_set_state(bfd, bfd->state, DIAG_NONE);
+        }
+        bfd_poll(bfd);
+    }
+    return bfd;
+}
+
+void
+bfd_wait(const struct bfd *bfd)
+{
+    if (bfd->flags & FLAG_FINAL) {
+        poll_immediate_wake();
+    }
+
+    poll_timer_wait_until(bfd->next_tx);
+    if (bfd->state > STATE_DOWN) {
+        poll_timer_wait_until(bfd->detect_time);
+    }
+}
+
+void
+bfd_run(struct bfd *bfd)
+{
+    if (bfd->state > STATE_DOWN && time_msec() >= bfd->detect_time) {
+        bfd_set_state(bfd, STATE_DOWN, DIAG_EXPIRED);
+    }
+
+    if (bfd->min_tx != bfd->cfg_min_tx || bfd->min_rx != bfd->cfg_min_rx) {
+        bfd_poll(bfd);
+    }
+}
+
+bool
+bfd_should_send_packet(const struct bfd *bfd)
+{
+    return bfd->flags & FLAG_FINAL || time_msec() >= bfd->next_tx;
+}
+
+void
+bfd_put_packet(struct bfd *bfd, struct ofpbuf *p,
+               uint8_t eth_src[ETH_ADDR_LEN])
+{
+    long long int min_tx, min_rx;
+    struct udp_header *udp;
+    struct eth_header *eth;
+    struct ip_header *ip;
+    struct msg *msg;
+
+    if (bfd->next_tx) {
+        long long int delay = time_msec() - bfd->next_tx;
+        long long int interval = bfd_tx_interval(bfd);
+        if (delay > interval * 3 / 2) {
+            VLOG_WARN("%s: long delay of %lldms (expected %lldms) sending BFD"
+                      " control message", bfd->name, delay, interval);
+        }
+    }
+
+    /* RFC 5880 Section 6.5
+     * A BFD Control packet MUST NOT have both the Poll (P) and Final (F) bits
+     * set. */
+    ovs_assert(!(bfd->flags & FLAG_POLL) || !(bfd->flags & FLAG_FINAL));
+
+    ofpbuf_reserve(p, 2); /* Properly align after the ethernet header. */
+    eth = ofpbuf_put_uninit(p, sizeof *eth);
+    memcpy(eth->eth_dst, eth_addr_broadcast, ETH_ADDR_LEN);
+    memcpy(eth->eth_src, eth_src, ETH_ADDR_LEN);
+    eth->eth_type = htons(ETH_TYPE_IP);
+
+    ip = ofpbuf_put_zeros(p, sizeof *ip);
+    ip->ip_ihl_ver = IP_IHL_VER(5, 4);
+    ip->ip_tot_len = htons(sizeof *ip + sizeof *udp + sizeof *msg);
+    ip->ip_ttl = 255;
+    ip->ip_proto = IPPROTO_UDP;
+    ip->ip_src = htonl(0xA9FE0100); /* 169.254.1.0 Link Local. */
+    ip->ip_dst = htonl(0xA9FE0101); /* 169.254.1.1 Link Local. */
+    ip->ip_csum = csum(ip, sizeof *ip);
+
+    udp = ofpbuf_put_zeros(p, sizeof *udp);
+    udp->udp_src = htons(bfd->udp_src);
+    udp->udp_dst = htons(BFD_DEST_PORT);
+    udp->udp_len = htons(sizeof *udp + sizeof *msg);
+
+    msg = ofpbuf_put_uninit(p, sizeof *msg);
+    msg->vers_diag = (BFD_VERSION << 5) | bfd->diag;
+    msg->flags = (bfd->state & STATE_MASK) | bfd->flags;
+
+    msg->mult = bfd->mult;
+    msg->length = BFD_PACKET_LEN;
+    msg->my_disc = htonl(bfd->disc);
+    msg->your_disc = htonl(bfd->rmt_disc);
+    msg->min_rx_echo = htonl(0);
+
+    if (bfd_in_poll(bfd)) {
+        min_tx = bfd->poll_min_tx;
+        min_rx = bfd->poll_min_rx;
+    } else {
+        min_tx = bfd_min_tx(bfd);
+        min_rx = bfd->min_rx;
+    }
+
+    msg->min_tx = htonl(min_tx * 1000);
+    msg->min_rx = htonl(min_rx * 1000);
+
+    bfd->flags &= ~FLAG_FINAL;
+
+    log_msg(VLL_DBG, msg, "Sending BFD Message", bfd);
+
+    bfd->last_tx = time_msec();
+    bfd_set_next_tx(bfd);
+}
+
+bool
+bfd_should_process_flow(const struct flow *flow)
+{
+    return (flow->dl_type == htons(ETH_TYPE_IP)
+            && flow->nw_proto == IPPROTO_UDP
+            && flow->tp_dst == htons(3784));
+}
+
+void
+bfd_process_packet(struct bfd *bfd, const struct flow *flow,
+                   const struct ofpbuf *p)
+{
+    uint32_t rmt_min_rx, pkt_your_disc;
+    enum state rmt_state;
+    enum flags flags;
+    uint8_t version;
+    struct msg *msg;
+
+    /* This function is designed to follow section RFC 5880 6.8.6 closely. */
+
+    if (flow->nw_ttl != 255) {
+        /* XXX Should drop in the kernel to prevent DOS. */
+        return;
+    }
+
+    msg = ofpbuf_at(p, (uint8_t *)p->l7 - (uint8_t *)p->data, BFD_PACKET_LEN);
+    if (!msg) {
+        VLOG_INFO_RL(&rl, "%s: Received unparseable BFD control message.",
+                     bfd->name);
+        return;
+    }
+
+    /* RFC 5880 Section 6.8.6
+     * If the Length field is greater than the payload of the encapsulating
+     * protocol, the packet MUST be discarded.
+     *
+     * Note that we make this check implicity.  Above we use ofpbuf_at() to
+     * ensure that there are at least BFD_PACKET_LEN bytes in the payload of
+     * the encapsulating protocol.  Below we require msg->length to be exactly
+     * BFD_PACKET_LEN bytes. */
+
+    flags = msg->flags & FLAGS_MASK;
+    rmt_state = msg->flags & STATE_MASK;
+    version = msg->vers_diag >> VERS_SHIFT;
+
+    log_msg(VLL_DBG, msg, "Received BFD control message", bfd);
+
+    if (version != BFD_VERSION) {
+        log_msg(VLL_WARN, msg, "Incorrect version", bfd);
+        return;
+    }
+
+    /* Technically this should happen after the length check. We don't support
+     * authentication however, so it's simpler to do the check first. */
+    if (flags & FLAG_AUTH) {
+        log_msg(VLL_WARN, msg, "Authenticated control message with"
+                   " authentication disabled", bfd);
+        return;
+    }
+
+    if (msg->length != BFD_PACKET_LEN) {
+        log_msg(VLL_WARN, msg, "Unexpected length", bfd);
+        if (msg->length < BFD_PACKET_LEN) {
+            return;
+        }
+    }
+
+    if (!msg->mult) {
+        log_msg(VLL_WARN, msg, "Zero multiplier", bfd);
+        return;
+    }
+
+    if (flags & FLAG_MULTIPOINT) {
+        log_msg(VLL_WARN, msg, "Unsupported multipoint flag", bfd);
+        return;
+    }
+
+    if (!msg->my_disc) {
+        log_msg(VLL_WARN, msg, "NULL my_disc", bfd);
+        return;
+    }
+
+    pkt_your_disc = ntohl(msg->your_disc);
+    if (pkt_your_disc) {
+        /* Technically, we should use the your discriminator field to figure
+         * out which 'struct bfd' this packet is destined towards.  That way a
+         * bfd session could migrate from one interface to another
+         * transparently.  This doesn't fit in with the OVS structure very
+         * well, so in this respect, we are not compliant. */
+       if (pkt_your_disc != bfd->disc) {
+           log_msg(VLL_WARN, msg, "Incorrect your_disc", bfd);
+           return;
+       }
+    } else if (rmt_state > STATE_DOWN) {
+        log_msg(VLL_WARN, msg, "Null your_disc", bfd);
+        return;
+    }
+
+    bfd->rmt_disc = ntohl(msg->my_disc);
+    bfd->rmt_state = rmt_state;
+    bfd->rmt_flags = flags;
+    bfd->rmt_diag = msg->vers_diag & DIAG_MASK;
+
+    if (flags & FLAG_FINAL && bfd_in_poll(bfd)) {
+        bfd->min_tx = bfd->poll_min_tx;
+        bfd->min_rx = bfd->poll_min_rx;
+        bfd->flags &= ~FLAG_POLL;
+        log_msg(VLL_INFO, msg, "Poll sequence terminated", bfd);
+    }
+
+    if (flags & FLAG_POLL) {
+        /* RFC 5880 Section 6.5
+         * When the other system receives a Poll, it immediately transmits a
+         * BFD Control packet with the Final (F) bit set, independent of any
+         * periodic BFD Control packets it may be sending
+         * (see section 6.8.7). */
+        bfd->flags &= ~FLAG_POLL;
+        bfd->flags |= FLAG_FINAL;
+    }
+
+    rmt_min_rx = MAX(ntohl(msg->min_rx) / 1000, 1);
+    if (bfd->rmt_min_rx != rmt_min_rx) {
+        bfd->rmt_min_rx = rmt_min_rx;
+        bfd_set_next_tx(bfd);
+        log_msg(VLL_INFO, msg, "New remote min_rx", bfd);
+    }
+
+    bfd->rmt_min_tx = MAX(ntohl(msg->min_tx) / 1000, 1);
+    bfd->detect_time = bfd_rx_interval(bfd) * bfd->mult + time_msec();
+
+    if (bfd->state == STATE_ADMIN_DOWN) {
+        VLOG_DBG_RL(&rl, "Administratively down, dropping control message.");
+        return;
+    }
+
+    if (rmt_state == STATE_ADMIN_DOWN) {
+        if (bfd->state != STATE_DOWN) {
+            bfd_set_state(bfd, STATE_DOWN, DIAG_RMT_DOWN);
+        }
+    } else {
+        switch (bfd->state) {
+        case STATE_DOWN:
+            if (rmt_state == STATE_DOWN) {
+                bfd_set_state(bfd, STATE_INIT, bfd->diag);
+            } else if (rmt_state == STATE_INIT) {
+                bfd_set_state(bfd, STATE_UP, bfd->diag);
+            }
+            break;
+        case STATE_INIT:
+            if (rmt_state > STATE_DOWN) {
+                bfd_set_state(bfd, STATE_UP, bfd->diag);
+            }
+            break;
+        case STATE_UP:
+            if (rmt_state <= STATE_DOWN) {
+                bfd_set_state(bfd, STATE_DOWN, DIAG_RMT_DOWN);
+                log_msg(VLL_INFO, msg, "Remote signaled STATE_DOWN", bfd);
+            }
+            break;
+        case STATE_ADMIN_DOWN:
+        default:
+            NOT_REACHED();
+        }
+    }
+    /* XXX: RFC 5880 Section 6.8.6 Demand mode related calculations here. */
+}
+
+/* Helpers. */
+static bool
+bfd_in_poll(const struct bfd *bfd)
+{
+    return (bfd->flags & FLAG_POLL) != 0;
+}
+
+static void
+bfd_poll(struct bfd *bfd)
+{
+    if (bfd->state > STATE_DOWN && !bfd_in_poll(bfd)
+        && !(bfd->flags & FLAG_FINAL)) {
+        bfd->poll_min_tx = bfd->cfg_min_tx;
+        bfd->poll_min_rx = bfd->cfg_min_rx;
+        bfd->flags |= FLAG_POLL;
+        bfd->next_tx = 0;
+        VLOG_INFO_RL(&rl, "%s: Initiating poll sequence", bfd->name);
+    }
+}
+
+static long long int
+bfd_min_tx(const struct bfd *bfd)
+{
+    /* RFC 5880 Section 6.8.3
+     * When bfd.SessionState is not Up, the system MUST set
+     * bfd.DesiredMinTxInterval to a value of not less than one second
+     * (1,000,000 microseconds).  This is intended to ensure that the
+     * bandwidth consumed by BFD sessions that are not Up is negligible,
+     * particularly in the case where a neighbor may not be running BFD. */
+    return (bfd->state == STATE_UP ? bfd->min_tx : MAX(bfd->min_tx, 1000));
+}
+
+static long long int
+bfd_tx_interval(const struct bfd *bfd)
+{
+    long long int interval = bfd_min_tx(bfd);
+    return MAX(interval, bfd->rmt_min_rx);
+}
+
+static long long int
+bfd_rx_interval(const struct bfd *bfd)
+{
+    return MAX(bfd->min_rx, bfd->rmt_min_tx);
+}
+
+static void
+bfd_set_next_tx(struct bfd *bfd)
+{
+    long long int interval = bfd_tx_interval(bfd);
+    interval -= interval * random_range(26) / 100;
+    bfd->next_tx = bfd->last_tx + interval;
+}
+
+static const char *
+bfd_flag_str(enum flags flags)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    static char flag_str[128];
+
+    if (!flags) {
+        return "none";
+    }
+
+    if (flags & FLAG_MULTIPOINT) {
+        ds_put_cstr(&ds, "multipoint ");
+    }
+
+    if (flags & FLAG_DEMAND) {
+        ds_put_cstr(&ds, "demand ");
+    }
+
+    if (flags & FLAG_AUTH) {
+        ds_put_cstr(&ds, "auth ");
+    }
+
+    if (flags & FLAG_CTL) {
+        ds_put_cstr(&ds, "ctl ");
+    }
+
+    if (flags & FLAG_FINAL) {
+        ds_put_cstr(&ds, "final ");
+    }
+
+    if (flags & FLAG_POLL) {
+        ds_put_cstr(&ds, "poll ");
+    }
+
+    ovs_strlcpy(flag_str, ds_cstr(&ds), sizeof flag_str);
+    ds_destroy(&ds);
+    return flag_str;
+}
+
+static const char *
+bfd_state_str(enum state state)
+{
+    switch (state) {
+    case STATE_ADMIN_DOWN: return "admin_down";
+    case STATE_DOWN: return "down";
+    case STATE_INIT: return "init";
+    case STATE_UP: return "up";
+    default: return "invalid";
+    }
+}
+
+static const char *
+bfd_diag_str(enum diag diag) {
+    switch (diag) {
+    case DIAG_NONE: return "No Diagnostic";
+    case DIAG_EXPIRED: return "Control Detection Time Expired";
+    case DIAG_ECHO_FAILED: return "Echo Function Failed";
+    case DIAG_RMT_DOWN: return "Neighbor Signaled Session Down";
+    case DIAG_FWD_RESET: return "Forwarding Plane Reset";
+    case DIAG_PATH_DOWN: return "Path Down";
+    case DIAG_CPATH_DOWN: return "Concatenated Path Down";
+    case DIAG_ADMIN_DOWN: return "Administratively Down";
+    case DIAG_RCPATH_DOWN: return "Reverse Concatenated Path Down";
+    default: return "Invalid Diagnostic";
+    }
+};
+
+static void
+log_msg(enum vlog_level level, const struct msg *p, const char *message,
+        const struct bfd *bfd)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+
+    if (vlog_should_drop(THIS_MODULE, level, &rl)) {
+        return;
+    }
+
+    ds_put_format(&ds,
+                  "%s: %s."
+                  "\n\tvers:%"PRIu8" diag:\"%s\" state:%s mult:%"PRIu8
+                  " length:%"PRIu8
+                  "\n\tflags: %s"
+                  "\n\tmy_disc:0x%"PRIx32" your_disc:0x%"PRIx32
+                  "\n\tmin_tx:%"PRIu32"us (%"PRIu32"ms)"
+                  "\n\tmin_rx:%"PRIu32"us (%"PRIu32"ms)"
+                  "\n\tmin_rx_echo:%"PRIu32"us (%"PRIu32"ms)",
+                  bfd->name, message, p->vers_diag >> VERS_SHIFT,
+                  bfd_diag_str(p->vers_diag & DIAG_MASK),
+                  bfd_state_str(p->flags & STATE_MASK),
+                  p->mult, p->length, bfd_flag_str(p->flags & FLAGS_MASK),
+                  ntohl(p->my_disc), ntohl(p->your_disc),
+                  ntohl(p->min_tx), ntohl(p->min_tx) / 1000,
+                  ntohl(p->min_rx), ntohl(p->min_rx) / 1000,
+                  ntohl(p->min_rx_echo), ntohl(p->min_rx_echo) / 1000);
+    bfd_put_details(&ds, bfd);
+    VLOG(level, "%s", ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+
+static void
+bfd_set_state(struct bfd *bfd, enum state state, enum diag diag)
+{
+    if (diag == DIAG_NONE && bfd->cpath_down) {
+        diag = DIAG_CPATH_DOWN;
+    }
+
+    if (bfd->state != state || bfd->diag != diag) {
+        if (!VLOG_DROP_INFO(&rl)) {
+            struct ds ds = DS_EMPTY_INITIALIZER;
+
+            ds_put_format(&ds, "%s: BFD state change: %s->%s"
+                          " \"%s\"->\"%s\".\n",
+                          bfd->name, bfd_state_str(bfd->state),
+                          bfd_state_str(state), bfd_diag_str(bfd->diag),
+                          bfd_diag_str(diag));
+            bfd_put_details(&ds, bfd);
+            VLOG_INFO("%s", ds_cstr(&ds));
+            ds_destroy(&ds);
+        }
+
+        bfd->state = state;
+        bfd->diag = diag;
+
+        if (bfd->state <= STATE_DOWN) {
+            bfd->rmt_state = STATE_DOWN;
+            bfd->rmt_diag = DIAG_NONE;
+            bfd->rmt_min_rx = 1;
+            bfd->rmt_flags = 0;
+            bfd->rmt_disc = 0;
+            bfd->rmt_min_tx = 0;
+        }
+    }
+}
+
+static uint32_t
+generate_discriminator(void)
+{
+    uint32_t disc = 0;
+
+    /* RFC 5880 Section 6.8.1
+     * It SHOULD be set to a random (but still unique) value to improve
+     * security.  The value is otherwise outside the scope of this
+     * specification. */
+
+    while (!disc) {
+        struct bfd *bfd;
+
+        /* 'disc' is by defnition random, so there's no reason to waste time
+         * hashing it. */
+        disc = random_uint32();
+        HMAP_FOR_EACH_IN_BUCKET (bfd, node, disc, &all_bfds) {
+            if (bfd->disc == disc) {
+                disc = 0;
+                break;
+            }
+        }
+    }
+
+    return disc;
+}
+
+static struct bfd *
+bfd_find_by_name(const char *name)
+{
+    struct bfd *bfd;
+
+    HMAP_FOR_EACH (bfd, node, &all_bfds) {
+        if (!strcmp(bfd->name, name)) {
+            return bfd;
+        }
+    }
+    return NULL;
+}
+
+static void
+bfd_put_details(struct ds *ds, const struct bfd *bfd)
+{
+    ds_put_format(ds, "\tForwarding: %s\n",
+                  bfd_forwarding(bfd) ? "true" : "false");
+    ds_put_format(ds, "\tDetect Multiplier: %d\n", bfd->mult);
+    ds_put_format(ds, "\tConcatenated Path Down: %s\n",
+                  bfd->cpath_down ? "true" : "false");
+    ds_put_format(ds, "\tTX Interval: Approx %lldms\n", bfd_tx_interval(bfd));
+    ds_put_format(ds, "\tRX Interval: Approx %lldms\n", bfd_rx_interval(bfd));
+    ds_put_format(ds, "\tDetect Time: now %+lldms\n",
+                  time_msec() - bfd->detect_time);
+    ds_put_format(ds, "\tNext TX Time: now %+lldms\n",
+                  time_msec() - bfd->next_tx);
+    ds_put_format(ds, "\tLast TX Time: now %+lldms\n",
+                  time_msec() - bfd->last_tx);
+
+    ds_put_cstr(ds, "\n");
+
+    ds_put_format(ds, "\tLocal Flags: %s\n", bfd_flag_str(bfd->flags));
+    ds_put_format(ds, "\tLocal Session State: %s\n",
+                  bfd_state_str(bfd->state));
+    ds_put_format(ds, "\tLocal Diagnostic: %s\n", bfd_diag_str(bfd->diag));
+    ds_put_format(ds, "\tLocal Discriminator: 0x%"PRIx32"\n", bfd->disc);
+    ds_put_format(ds, "\tLocal Minimum TX Interval: %lldms\n",
+                  bfd_min_tx(bfd));
+    ds_put_format(ds, "\tLocal Minimum RX Interval: %lldms\n", bfd->min_rx);
+
+    ds_put_cstr(ds, "\n");
+
+    ds_put_format(ds, "\tRemote Flags: %s\n", bfd_flag_str(bfd->rmt_flags));
+    ds_put_format(ds, "\tRemote Session State: %s\n",
+                  bfd_state_str(bfd->rmt_state));
+    ds_put_format(ds, "\tRemote Diagnostic: %s\n",
+                  bfd_diag_str(bfd->rmt_diag));
+    ds_put_format(ds, "\tRemote Discriminator: 0x%"PRIx32"\n", bfd->rmt_disc);
+    ds_put_format(ds, "\tRemote Minimum TX Interval: %lldms\n",
+                  bfd->rmt_min_tx);
+    ds_put_format(ds, "\tRemote Minimum RX Interval: %lldms\n",
+                  bfd->rmt_min_rx);
+}
+
+static void
+bfd_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[],
+                 void *aux OVS_UNUSED)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct bfd *bfd;
+
+    if (argc > 1) {
+        bfd = bfd_find_by_name(argv[1]);
+        if (!bfd) {
+            unixctl_command_reply_error(conn, "no such bfd object");
+            return;
+        }
+        bfd_put_details(&ds, bfd);
+    } else {
+        HMAP_FOR_EACH (bfd, node, &all_bfds) {
+            ds_put_format(&ds, "---- %s ----\n", bfd->name);
+            bfd_put_details(&ds, bfd);
+        }
+    }
+    unixctl_command_reply(conn, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
diff --git a/lib/bfd.h b/lib/bfd.h
new file mode 100644
index 0000000..21203b3
--- /dev/null
+++ b/lib/bfd.h
@@ -0,0 +1,46 @@
+/* Copyright (c) 2012 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.  */
+
+#ifndef BFD_H
+#define BFD_H 1
+
+#define BFD_PACKET_LEN 24
+#define BFD_DEST_PORT 3784
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+struct bfd;
+struct flow;
+struct ofpbuf;
+struct smap;
+
+void bfd_wait(const struct bfd *);
+void bfd_run(struct bfd *);
+
+bool bfd_should_send_packet(const struct bfd *);
+void bfd_put_packet(struct bfd *bfd, struct ofpbuf *packet,
+                    uint8_t eth_src[6]);
+
+bool bfd_should_process_flow(const struct flow *);
+void bfd_process_packet(struct bfd *, const struct flow *,
+                        const struct ofpbuf *);
+
+struct bfd *bfd_configure(struct bfd *, const char *name,
+                          const struct smap *smap);
+
+bool bfd_forwarding(const struct bfd *);
+void bfd_get_status(const struct bfd *, struct smap *);
+
+#endif /* bfd.h */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index ae09267..5bf94fe 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -183,6 +183,8 @@ slow_path_reason_to_string(uint32_t data)
         return "stp";
     case SLOW_IN_BAND:
         return "in_band";
+    case SLOW_BFD:
+        return "bfd";
     case SLOW_CONTROLLER:
         return "controller";
     case SLOW_MATCH:
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 0b34383..a981d17 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -179,14 +179,15 @@ enum slow_path_reason {
     SLOW_LACP = 1 << 1,         /* LACP packets need per-packet processing. */
     SLOW_STP = 1 << 2,          /* STP packets need per-packet processing. */
     SLOW_IN_BAND = 1 << 3,      /* In-band control needs every packet. */
+    SLOW_BFD = 1 << 4,          /* BFD packets need per-packet processing. */
 
-    /* Mutually exclusive with SLOW_CFM, SLOW_LACP, SLOW_STP.
+    /* Mutually exclusive with SLOW_BFD, SLOW_CFM, SLOW_LACP, SLOW_STP.
      * Could possibly appear with SLOW_IN_BAND. */
-    SLOW_CONTROLLER = 1 << 4,   /* Packets must go to OpenFlow controller. */
+    SLOW_CONTROLLER = 1 << 5,   /* Packets must go to OpenFlow controller. */
 
     /* This can appear on its own, or, theoretically at least, along with any
      * other combination of reasons. */
-    SLOW_MATCH = 1 << 5,        /* Datapath can't match specifically enough. */
+    SLOW_MATCH = 1 << 6,        /* Datapath can't match specifically enough. */
 };
 
 #endif /* odp-util.h */
diff --git a/lib/util.h b/lib/util.h
index 4f6a201..f5589e3 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -23,6 +23,7 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include "compiler.h"
 
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 89a4668..0272e4c 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -20,6 +20,7 @@
 
 #include <errno.h>
 
+#include "bfd.h"
 #include "bond.h"
 #include "bundle.h"
 #include "byte-order.h"
@@ -531,6 +532,7 @@ struct ofport_dpif {
     struct ofbundle *bundle;    /* Bundle that contains this port, if any. */
     struct list bundle_node;    /* In struct ofbundle's "ports" list. */
     struct cfm *cfm;            /* Connectivity Fault Management, if any. */
+    struct bfd *bfd;            /* BFD, if any. */
     tag_type tag;               /* Tag associated with this port. */
     bool may_enable;            /* May be enabled in bonds. */
     long long int carrier_seq;  /* Carrier status changes. */
@@ -1778,6 +1780,7 @@ port_construct(struct ofport *port_)
     ofproto->backer->need_revalidate = REV_RECONFIGURE;
     port->bundle = NULL;
     port->cfm = NULL;
+    port->bfd = NULL;
     port->tag = tag_create_random();
     port->may_enable = true;
     port->stp_port = NULL;
@@ -1994,6 +1997,35 @@ get_cfm_status(const struct ofport *ofport_,
         return false;
     }
 }
+
+static int
+set_bfd(struct ofport *ofport_, const struct smap *cfg)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport_->ofproto);
+    struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+    struct bfd *old;
+
+    old = ofport->bfd;
+    ofport->bfd = bfd_configure(old, netdev_get_name(ofport->up.netdev), cfg);
+    if (ofport->bfd != old) {
+        ofproto->backer->need_revalidate = REV_RECONFIGURE;
+    }
+
+    return 0;
+}
+
+static int
+get_bfd_status(struct ofport *ofport_, struct smap *smap)
+{
+    struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+
+    if (ofport->bfd) {
+        bfd_get_status(ofport->bfd, smap);
+        return 0;
+    } else {
+        return ENOENT;
+    }
+}
 
 /* Spanning Tree. */
 
@@ -3133,6 +3165,15 @@ port_run_fast(struct ofport_dpif *ofport)
         send_packet(ofport, &packet);
         ofpbuf_uninit(&packet);
     }
+
+    if (ofport->bfd && bfd_should_send_packet(ofport->bfd)) {
+        struct ofpbuf packet;
+
+        ofpbuf_init(&packet, 0);
+        bfd_put_packet(ofport->bfd, &packet, ofport->up.pp.hw_addr);
+        send_packet(ofport, &packet);
+        ofpbuf_uninit(&packet);
+    }
 }
 
 static void
@@ -3163,6 +3204,11 @@ port_run(struct ofport_dpif *ofport)
         }
     }
 
+    if (ofport->bfd) {
+        bfd_run(ofport->bfd);
+        enable = enable && bfd_forwarding(ofport->bfd);
+    }
+
     if (ofport->bundle) {
         enable = enable && lacp_slave_may_enable(ofport->bundle->lacp, ofport);
         if (carrier_changed) {
@@ -3187,6 +3233,10 @@ port_wait(struct ofport_dpif *ofport)
     if (ofport->cfm) {
         cfm_wait(ofport->cfm);
     }
+
+    if (ofport->bfd) {
+        bfd_wait(ofport->bfd);
+    }
 }
 
 static int
@@ -3510,6 +3560,11 @@ process_special(struct ofproto_dpif *ofproto, const struct flow *flow,
             cfm_process_heartbeat(ofport->cfm, packet);
         }
         return SLOW_CFM;
+    } else if (ofport->bfd && bfd_should_process_flow(flow)) {
+        if (packet) {
+            bfd_process_packet(ofport->bfd, flow, packet);
+        }
+        return SLOW_BFD;
     } else if (ofport->bundle && ofport->bundle->lacp
                && flow->dl_type == htons(ETH_TYPE_LACP)) {
         if (packet) {
@@ -4527,7 +4582,7 @@ expire_subfacets(struct ofproto_dpif *ofproto, int dp_max_idle)
                         &ofproto->subfacets) {
         long long int cutoff;
 
-        cutoff = (subfacet->slow & (SLOW_CFM | SLOW_LACP | SLOW_STP)
+        cutoff = (subfacet->slow & (SLOW_CFM | SLOW_BFD | SLOW_LACP | SLOW_STP)
                   ? special_cutoff
                   : normal_cutoff);
         if (subfacet->used < cutoff) {
@@ -5896,7 +5951,7 @@ compose_slow_path(const struct ofproto_dpif *ofproto, const struct flow *flow,
     cookie.slow_path.reason = slow;
 
     ofpbuf_use_stack(&buf, stub, stub_size);
-    if (slow & (SLOW_CFM | SLOW_LACP | SLOW_STP)) {
+    if (slow & (SLOW_CFM | SLOW_BFD | SLOW_LACP | SLOW_STP)) {
         uint32_t pid = dpif_port_get_pid(ofproto->backer->dpif, UINT32_MAX);
         odp_put_userspace_action(pid, &cookie, sizeof cookie.slow_path, &buf);
     } else {
@@ -8322,6 +8377,9 @@ ofproto_trace(struct ofproto_dpif *ofproto, const struct flow *flow,
                 case SLOW_STP:
                     ds_put_cstr(ds, "\n\t- Consists of STP packets.");
                     break;
+                case SLOW_BFD:
+                    ds_put_cstr(ds, "\n\t- Consists of BFD packets.");
+                    break;
                 case SLOW_IN_BAND:
                     ds_put_cstr(ds, "\n\t- Needs in-band special case "
                                 "processing.");
@@ -9046,6 +9104,8 @@ const struct ofproto_class ofproto_dpif_class = {
     set_ipfix,
     set_cfm,
     get_cfm_status,
+    set_bfd,
+    get_bfd_status,
     set_stp,
     get_stp_status,
     set_stp_port,
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 2f429e0..b9d6f0d 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -33,6 +33,7 @@
 struct match;
 struct ofpact;
 struct ofputil_flow_mod;
+struct bfd_cfg;
 
 /* An OpenFlow switch.
  *
@@ -1144,6 +1145,21 @@ struct ofproto_class {
     bool (*get_cfm_status)(const struct ofport *ofport,
                            struct ofproto_cfm_status *status);
 
+    /* Configures BFD on 'ofport'.
+     *
+     * If 'cfg' is NULL, or 'cfg' does not contain the key value pair
+     * "enable=true", removes BFD from 'ofport'.  Otherwise, configures BFD
+     * according to 'cfg'.
+     *
+     * EOPNOTSUPP as a return value indicates that this ofproto_class does not
+     * support BFD, as does a null pointer. */
+    int (*set_bfd)(struct ofport *ofport, const struct smap *cfg);
+
+    /* Populates 'smap' with the status of BFD on 'ofport'.  Returns 0 on
+     * success, or a positive errno.  EOPNOTSUPP as a return value indicates
+     * that this ofproto_class does not support BFD, as does a null pointer. */
+    int (*get_bfd_status)(struct ofport *ofport, struct smap *smap);
+
     /* Configures spanning tree protocol (STP) on 'ofproto' using the
      * settings defined in 's'.
      *
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 85fe781..262493e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -799,6 +799,45 @@ ofproto_port_set_cfm(struct ofproto *ofproto, uint16_t ofp_port,
     }
 }
 
+/* Configures BFD on 'ofp_port' in 'ofproto'.  This function has no effect if
+ * 'ofproto' does not have a port 'ofp_port'. */
+void
+ofproto_port_set_bfd(struct ofproto *ofproto, uint16_t ofp_port,
+                     const struct smap *cfg)
+{
+    struct ofport *ofport;
+    int error;
+
+    ofport = ofproto_get_port(ofproto, ofp_port);
+    if (!ofport) {
+        VLOG_WARN("%s: cannot configure bfd on nonexistent port %"PRIu16,
+                  ofproto->name, ofp_port);
+    }
+
+    error = (ofproto->ofproto_class->set_bfd
+             ? ofproto->ofproto_class->set_bfd(ofport, cfg)
+             : EOPNOTSUPP);
+    if (error) {
+        VLOG_WARN("%s: bfd configuration on port %"PRIu16" (%s) failed (%s)",
+                  ofproto->name, ofp_port, netdev_get_name(ofport->netdev),
+                  strerror(error));
+    }
+}
+
+/* Populates 'status' with key value pairs indicating the status of the BFD
+ * session on 'ofp_port'.  This information is intended to be populated in the
+ * OVS database.  Has no effect if 'ofp_port' is not na OpenFlow port in
+ * 'ofproto'. */
+int
+ofproto_port_get_bfd_status(struct ofproto *ofproto, uint16_t ofp_port,
+                            struct smap *status)
+{
+    struct ofport *ofport = ofproto_get_port(ofproto, ofp_port);
+    return (ofport && ofproto->ofproto_class->get_bfd_status
+            ? ofproto->ofproto_class->get_bfd_status(ofport, status)
+            : EOPNOTSUPP);
+}
+
 /* Checks the status of LACP negotiation for 'ofp_port' within ofproto.
  * Returns 1 if LACP partner information for 'ofp_port' is up-to-date,
  * 0 if LACP partner information is not current (generally indicating a
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index bb799b5..acb1790 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -33,12 +33,15 @@
 extern "C" {
 #endif
 
+struct bfd_cfg;
+struct cfm_settings;
 struct cls_rule;
 struct netdev;
 struct ofproto;
 struct ofport;
 struct shash;
 struct simap;
+struct smap;
 struct netdev_stats;
 
 struct ofproto_controller_info {
@@ -255,6 +258,10 @@ void ofproto_port_unregister(struct ofproto *, uint16_t ofp_port);
 void ofproto_port_clear_cfm(struct ofproto *, uint16_t ofp_port);
 void ofproto_port_set_cfm(struct ofproto *, uint16_t ofp_port,
                           const struct cfm_settings *);
+void ofproto_port_set_bfd(struct ofproto *, uint16_t ofp_port,
+                          const struct smap *cfg);
+int ofproto_port_get_bfd_status(struct ofproto *, uint16_t ofp_port,
+                                struct smap *);
 int ofproto_port_is_lacp_current(struct ofproto *, uint16_t ofp_port);
 int ofproto_port_set_stp(struct ofproto *, uint16_t ofp_port,
                          const struct ofproto_port_stp_settings *);
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index eb1ab3c..6943ff3 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -18,6 +18,7 @@
 #include <errno.h>
 #include <inttypes.h>
 #include <stdlib.h>
+#include "bfd.h"
 #include "bitmap.h"
 #include "bond.h"
 #include "cfm.h"
@@ -373,6 +374,7 @@ bridge_init(const char *remote)
     ovsdb_idl_omit_alert(idl, &ovsrec_interface_col_cfm_remote_mpids);
     ovsdb_idl_omit_alert(idl, &ovsrec_interface_col_cfm_health);
     ovsdb_idl_omit_alert(idl, &ovsrec_interface_col_cfm_remote_opstate);
+    ovsdb_idl_omit_alert(idl, &ovsrec_interface_col_bfd_status);
     ovsdb_idl_omit_alert(idl, &ovsrec_interface_col_lacp_current);
     ovsdb_idl_omit(idl, &ovsrec_interface_col_external_ids);
 
@@ -605,6 +607,8 @@ bridge_reconfigure_continue(const struct ovsrec_open_vswitch *ovs_cfg)
                 iface_configure_cfm(iface);
                 iface_configure_qos(iface, port->cfg->qos);
                 iface_set_mac(iface);
+                ofproto_port_set_bfd(br->ofproto, iface->ofp_port,
+                                     &iface->cfg->bfd);
             }
         }
         bridge_configure_mirrors(br);
@@ -2170,6 +2174,7 @@ instant_stats_run(void)
 
             HMAP_FOR_EACH (iface, name_node, &br->iface_by_name) {
                 enum netdev_flags flags;
+                struct smap smap;
                 const char *link_state;
                 int64_t link_resets;
                 int current, error;
@@ -2202,6 +2207,13 @@ instant_stats_run(void)
                 ovsrec_interface_set_link_resets(iface->cfg, &link_resets, 1);
 
                 iface_refresh_cfm_stats(iface);
+
+                smap_init(&smap);
+                if (!ofproto_port_get_bfd_status(br->ofproto, iface->ofp_port,
+                                                 &smap)) {
+                    ovsrec_interface_set_bfd_status(iface->cfg, &smap);
+                    smap_destroy(&smap);
+                }
             }
         }
     }
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index 3f2ce3a..bb3ca48 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@
 {"name": "Open_vSwitch",
- "version": "7.1.0",
- "cksum": "432130924 19191",
+ "version": "7.2.0",
+ "cksum": "543912409 19436",
  "tables": {
    "Open_vSwitch": {
      "columns": {
@@ -196,6 +196,12 @@
                    "maxInteger": 65279},
            "min": 0,
            "max": 1}},
+       "bfd": {
+           "type": {"key": "string", "value": "string",
+               "min": 0, "max": "unlimited"}},
+       "bfd_status": {
+           "type": {"key": "string", "value": "string",
+               "min": 0, "max": "unlimited"}},
        "cfm_mpid": {
          "type": {
            "key": {"type": "integer"},
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index b5bae78..3388b81 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -1695,6 +1695,98 @@
       </column>
     </group>
 
+    <group title="Bidirectional Forwarding Detection (BFD)">
+        <p>
+            BFD, defined in RFC 5880 and RFC 5881, allows point to point
+            detection of connectivity failures by occasional transmission of
+            BFD control messages.  It is implemented in Open vSwitch to serve
+            as a more popular and standards compliant alternative to CFM.
+        </p>
+
+        <p>
+            BFD operates by regularly transmitting BFD control messages at a
+            rate negotiated independently in each direction.  Each endpoint
+            specifies the rate at which it expects to receive control messages,
+            and the rate at which it's willing to transmit them.  Open vSwitch
+            uses a detection multiplier of three, meaning that an endpoint
+            which fails to receive BFD control messages for a period of three
+            times the expected reception rate, will signal a connectivity
+            fault.  In the case of a unidirectional connectivity issue, the
+            system not receiving BFD control messages will signal the problem
+            to its peer in the messages is transmists.
+        </p>
+
+        <p>
+            The Open vSwitch implementation of BFD aims to comply faithfully
+            with the requirements put forth in RFC 5880.  Currently, the only
+            known omission is ``Demand Mode'', which we hope to include in
+            future.  Open vSwitch does not implement the optional
+            Authentication or ``Echo Mode'' features.
+        </p>
+
+      <column name="bfd" key="enable">
+          When <code>true</code> BFD is enabled on this
+          <ref table="Interface"/>, otherwise it's disabled.  Defaults to
+          <code>false</code>.
+      </column>
+
+      <column name="bfd" key="min_rx"
+          type='{"type": "integer", "minInteger": 1}'>
+          The fastest rate, in milliseconds, at which this BFD session is
+          willing to receive BFD control messages.  The actual rate may be
+          slower if the remote endpoint isn't willing to transmit as quickly as
+          specified.  Defaults to <code>1000</code>.
+      </column>
+
+      <column name="bfd" key="min_tx"
+          type='{"type": "integer", "minInteger": 1}'>
+          The fastest rate, in milliseconds, at which this BFD session is
+          willing to transmit BFD control messages.  The actual rate may be
+          slower if the remote endpoint isn't willing to receive as quickly as
+          specified.  Defaults to <code>100</code>.
+      </column>
+
+      <column name="bfd" key="cpath_down" type='{"type": "boolean"}'>
+          Concatenated path down may be used when the local system should not
+          have traffic forwarded to it for some reason other than a connectivty
+          failure on the interface being monitored.  When a controller thinks
+          this may be the case, it may set <code>cpath_down</code> to
+          <code>true</code> which may cause the remote BFD session not to
+          forward traffic to this <ref table="Interface"/>. Defaults to
+          <code>false</code>.
+      </column>
+
+      <column name="bfd_status" key="state"
+          type='{"type": "string",
+          "enum": ["set", ["admin_down", "down", "init", "up"]]}'>
+          State of the BFD session.  The BFD session is fully healthy and
+          negotiated if <code>UP</code>.
+      </column>
+
+      <column name="bfd_status" key="forwarding" type='{"type": "boolean"}'>
+          True if the BFD session believes this <ref table="Interface"/> may be
+          used to forward traffic.  Typically this means the local session is
+          signaling <code>UP</code>, and the remote system isn't signaling a
+          problem such as concatenated path down.
+      </column>
+
+      <column name="bfd_status" key="diagnostic">
+          A short message indicating what the BFD session thinks is wrong in
+          case of a problem.
+      </column>
+
+      <column name="bfd_status" key="remote_state"
+          type='{"type": "string",
+          "enum": ["set", ["admin_down", "down", "init", "up"]]}'>
+          State of the remote endpoint's BFD session.
+      </column>
+
+      <column name="bfd_status" key="remote_diagnostic">
+          A short message indicating what the remote endpoint's BFD session
+          thinks is wrong in case of a problem.
+      </column>
+    </group>
+
     <group title="Connectivity Fault Management">
       <p>
         802.1ag Connectivity Fault Management (CFM) allows a group of
-- 
1.7.9.5




More information about the dev mailing list