2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-01 06:45:17 +00:00

bfd: Implement Bidirectional Forwarding Detection.

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@nicira.com>
This commit is contained in:
Ethan Jackson
2012-06-08 12:42:42 -07:00
parent fe7744e84b
commit ccc096898c
13 changed files with 1164 additions and 7 deletions

View File

@@ -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 \

873
lib/bfd.c Normal file
View File

@@ -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);
}

46
lib/bfd.h Normal file
View File

@@ -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 */

View File

@@ -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:

View File

@@ -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 */

View File

@@ -23,6 +23,7 @@
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "compiler.h"

View File

@@ -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,

View File

@@ -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'.
*

View File

@@ -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

View File

@@ -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 *);

View File

@@ -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);
@@ -2175,6 +2179,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;
@@ -2207,6 +2212,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);
}
}
}
}

View File

@@ -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"},

View File

@@ -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