[ovs-dev] [PATCH 4/4] ovs: Implement 802.1ag Connectivity Fault Management

Ethan Jackson ethan at nicira.com
Sun Nov 28 03:04:57 UTC 2010


This commit implements a subset of the 802.1ag specification for
Connectivity Fault Management (CFM) using Continuity Check Messages
(CCM).  When CFM is configured on an interface CCMs are broadcast
at regular intervals to detect missing or unexpected connectivity.
---
 lib/automake.mk            |    2 +
 lib/cfm.c                  |  440 ++++++++++++++++++++++++++++++++++++++++++++
 lib/cfm.h                  |   85 +++++++++
 lib/packets.h              |   18 ++
 lib/vlog-modules.def       |    1 +
 utilities/ovs-vsctl.8.in   |    5 +
 utilities/ovs-vsctl.c      |   10 +
 vswitchd/bridge.c          |  200 ++++++++++++++++++++
 vswitchd/vswitch.ovsschema |   71 +++++++
 vswitchd/vswitch.xml       |   81 ++++++++
 10 files changed, 913 insertions(+), 0 deletions(-)
 create mode 100644 lib/cfm.c
 create mode 100644 lib/cfm.h

diff --git a/lib/automake.mk b/lib/automake.mk
index 4ebbb19..719ae48 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -17,6 +17,8 @@ lib_libopenvswitch_a_SOURCES = \
 	lib/byte-order.h \
 	lib/byteq.c \
 	lib/byteq.h \
+	lib/cfm.c \
+	lib/cfm.h \
 	lib/classifier.c \
 	lib/classifier.h \
 	lib/command-line.c \
diff --git a/lib/cfm.c b/lib/cfm.c
new file mode 100644
index 0000000..1ebe353
--- /dev/null
+++ b/lib/cfm.c
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2010 Nicira Networks.
+ *
+ * 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 "cfm.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "flow.h"
+#include "hash.h"
+#include "hmap.h"
+#include "ofpbuf.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "timeval.h"
+#include "vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(cfm);
+
+#define CCM_OPCODE 1              /* CFM message opcode meaning CCM. */
+#define DEST_ADDR  0x0180C2000030 /* Destination for MD level 0 CCMs. */
+
+struct cfm_internal {
+    struct cfm cfm;
+    uint32_t seq;          /* The sequence number of our last CCM. */
+
+    uint8_t ccm_interval;  /* The CCM transmission interval. */
+    int ccm_interval_ms;   /* 'ccm_interval' in milliseconds. */
+
+    long long ccm_sent;    /* The time we last sent a CCM. */
+    long long fault_check; /* The time we last checked for faults. */
+};
+
+static int
+ccm_interval_to_ms(uint8_t interval)
+{
+    switch (interval) {
+    case 0:  NOT_REACHED(); /* Explicitly not supported by 802.1ag. */
+    case 1:  return 3;      /* Not recommended due to timer resolution. */
+    case 2:  return 10;     /* Not recommended due to timer resolution. */
+    case 3:  return 100;
+    case 4:  return 1000;
+    case 5:  return 10000;
+    case 6:  return 60000;
+    case 7:  return 600000;
+    default: NOT_REACHED(); /* Explicitly not supported by 802.1ag. */
+    }
+
+    NOT_REACHED();
+}
+
+static uint8_t
+ms_to_ccm_interval(int interval_ms)
+{
+    uint8_t i;
+
+    for (i = 7; i > 0; i--) {
+        if (ccm_interval_to_ms(i) <= interval_ms) {
+            return i;
+        }
+    }
+
+    return 1;
+}
+
+static struct cfm_internal *
+cfm_to_internal(struct cfm *cfm)
+{
+    return CONTAINER_OF(cfm, struct cfm_internal, cfm);
+}
+
+static uint32_t
+hash_mpid(uint8_t mpid)
+{
+    return hash_int(mpid, 0);
+}
+
+static bool
+cfm_is_valid_mpid(uint32_t mpid)
+{
+    /* 802.1ag specification requires MPIDs to be within the range [1, 8191] */
+    return mpid >= 1 && mpid <= 8191;
+}
+
+static struct remote_mp *
+lookup_remote_mp(const struct hmap *hmap, uint16_t mpid)
+{
+    struct remote_mp *rmp;
+
+    HMAP_FOR_EACH_IN_BUCKET (rmp, node, hash_mpid(mpid), hmap) {
+        if (rmp->mpid == mpid) {
+            return rmp;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ofpbuf *
+compose_ccm(struct cfm_internal *cfmi)
+{
+    struct ccm *ccm;
+    struct ofpbuf *packet;
+    struct eth_header *eth;
+
+    packet = xzalloc(sizeof *packet);
+
+    ofpbuf_init(packet, ETH_HEADER_LEN + CCM_LEN);
+
+    eth = ofpbuf_put_zeros(packet, ETH_HEADER_LEN);
+    ccm = ofpbuf_put_zeros(packet, CCM_LEN);
+
+    eth_addr_from_uint64(DEST_ADDR, eth->eth_dst);
+    memcpy(eth->eth_src, cfmi->cfm.eth_src, sizeof eth->eth_src);
+    eth->eth_type = htons(ETH_TYPE_CFM);
+
+    ccm->mdlevel_version = 0;
+    ccm->opcode          = CCM_OPCODE;
+    ccm->tlv_offset      = 70;
+    ccm->seq             = htonl(++cfmi->seq);
+    ccm->mpid            = htons(cfmi->cfm.mpid);
+    ccm->flags           = cfmi->ccm_interval;
+    memcpy(ccm->maid, cfmi->cfm.maid, sizeof ccm->maid);
+    return packet;
+}
+
+/* Allocates a 'cfm' object.  This object should have it's 'mpid', 'maid',
+ * 'eth_src', and 'interval' filled out.  When changes are made to the 'cfm'
+ * object, cfm_configure should be called before using it. */
+struct cfm *
+cfm_create(void)
+{
+    struct cfm *cfm;
+    struct cfm_internal *cfmi;
+
+    cfmi = xzalloc(sizeof *cfmi);
+    cfm  = &cfmi->cfm;
+
+    hmap_init(&cfm->remote_mps);
+    hmap_init(&cfm->x_remote_mps);
+    hmap_init(&cfm->x_remote_maids);
+    return cfm;
+}
+
+void
+cfm_destroy(struct cfm *cfm)
+{
+    struct remote_mp *rmp, *rmp_next;
+    struct remote_maid *rmaid, *rmaid_next;
+
+    if (!cfm) {
+        return;
+    }
+
+    HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->remote_mps) {
+        hmap_remove(&cfm->remote_mps, &rmp->node);
+        free(rmp);
+    }
+
+    HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->x_remote_mps) {
+        hmap_remove(&cfm->x_remote_mps, &rmp->node);
+        free(rmp);
+    }
+
+    HMAP_FOR_EACH_SAFE (rmaid, rmaid_next, node, &cfm->x_remote_maids) {
+        hmap_remove(&cfm->x_remote_maids, &rmaid->node);
+        free(rmaid);
+    }
+
+    hmap_destroy(&cfm->remote_mps);
+    hmap_destroy(&cfm->x_remote_mps);
+    hmap_destroy(&cfm->x_remote_maids);
+    free(cfm_to_internal(cfm));
+}
+
+struct ofpbuf *
+cfm_run(struct cfm *cfm)
+{
+    long long now = time_msec();
+    struct cfm_internal *cfmi = cfm_to_internal(cfm);
+
+    /* According to the 802.1ag specification we should assume every other MP
+     * with the same MAID has the same transmission interval that we have.  If
+     * an MP has a different interval, cfm_process_heartbeat will register it
+     * as a fault (likely due to a configuration error).  Thus we can check all
+     * MPs at once making this quite a bit simpler.
+     *
+     * According to the specification we should check when (ccm_interval_ms *
+     * 3.5)ms have passed.  We changed the multiplier to 4 to avoid messy
+     * floating point arithmetic and add a bit of wiggle room. */
+    if (now >= cfmi->fault_check + cfmi->ccm_interval_ms * 4) {
+        bool fault;
+        struct remote_mp *rmp, *rmp_next;
+        struct remote_maid *rmaid, *rmaid_next;
+
+        fault = false;
+
+        HMAP_FOR_EACH (rmp, node, &cfm->remote_mps) {
+            rmp->fault = rmp->fault || cfmi->fault_check > rmp->recv_time;
+            fault      = rmp->fault || fault;
+        }
+
+        HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->x_remote_mps) {
+            if (cfmi->fault_check > rmp->recv_time) {
+                hmap_remove(&cfm->x_remote_mps, &rmp->node);
+                free(rmp);
+            }
+        }
+
+        HMAP_FOR_EACH_SAFE (rmaid, rmaid_next, node, &cfm->x_remote_maids) {
+            if (cfmi->fault_check > rmaid->recv_time) {
+                hmap_remove(&cfm->x_remote_maids, &rmaid->node);
+                free(rmaid);
+            }
+        }
+
+        fault = fault || !hmap_is_empty(&cfm->x_remote_mps)
+            || !hmap_is_empty(&cfm->x_remote_maids);
+
+        cfm->fault        = fault;
+        cfmi->fault_check = now;
+    }
+
+    if (now >= cfmi->ccm_sent + cfmi->ccm_interval_ms) {
+        cfmi->ccm_sent = now;
+        return compose_ccm(cfmi);
+    }
+
+    return NULL;
+}
+
+void
+cfm_wait(struct cfm *cfm)
+{
+    long long wait;
+    struct cfm_internal *cfmi = cfm_to_internal(cfm);
+
+    wait = MIN(cfmi->ccm_sent + cfmi->ccm_interval_ms,
+               cfmi->fault_check + cfmi->ccm_interval_ms * 4);
+    poll_timer_wait_until(wait);
+}
+
+/* Should be called whenever a client of the cfm library changes the internals
+ * of 'cfm'. Returns true if 'cfm' is valid. */
+bool
+cfm_configure(struct cfm *cfm)
+{
+    struct cfm_internal *cfmi;
+
+    if (!cfm_is_valid_mpid(cfm->mpid) || !cfm->interval) {
+        return false;
+    }
+
+    cfmi                  = cfm_to_internal(cfm);
+    cfmi->ccm_interval    = ms_to_ccm_interval(cfm->interval);
+    cfmi->ccm_interval_ms = ccm_interval_to_ms(cfmi->ccm_interval);
+
+    /* Force a resend and check in case anything changed. */
+    cfmi->ccm_sent    = 0;
+    cfmi->fault_check = 0;
+    return true;
+}
+
+/* Given an array of MPIDs, updates the 'remote_mps' map of 'cfm' to reflect
+ * it.  Invalid MPIDs are skipped. */
+void
+cfm_update_remote_mps(struct cfm *cfm, const uint16_t *mpids, size_t n_mpids)
+{
+    size_t i;
+    struct hmap new_rmps;
+    struct remote_mp *rmp, *rmp_next;
+
+    hmap_init(&new_rmps);
+
+    for (i = 0; i < n_mpids; i++) {
+        uint16_t mpid = mpids[i];
+
+        if (!cfm_is_valid_mpid(mpid)
+            || lookup_remote_mp(&new_rmps, mpid)) {
+            continue;
+        }
+
+        if ((rmp = lookup_remote_mp(&cfm->remote_mps, mpid))) {
+            hmap_remove(&cfm->remote_mps, &rmp->node);
+        } else if ((rmp = lookup_remote_mp(&cfm->x_remote_mps, mpid))) {
+            hmap_remove(&cfm->x_remote_mps, &rmp->node);
+        } else {
+            rmp = xzalloc(sizeof *rmp);
+            rmp->mpid = mpid;
+        }
+
+        hmap_insert(&new_rmps, &rmp->node, hash_mpid(mpid));
+    }
+
+    hmap_swap(&new_rmps, &cfm->remote_mps);
+
+    HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &new_rmps) {
+        hmap_remove(&new_rmps, &rmp->node);
+        free(rmp);
+    }
+
+    hmap_destroy(&new_rmps);
+}
+
+/* Finds a 'remote_mp' with 'mpid' in 'cfm'.  If no such 'remote_mp' exists
+ * returns NULL. */
+const struct remote_mp *
+cfm_get_remote_mp(const struct cfm *cfm, uint16_t mpid)
+{
+    return lookup_remote_mp(&cfm->remote_mps, mpid);
+}
+
+/* Generates 'maid' from 'md_name' and 'ma_name'.  A NULL parameter indicates
+ * the default should be used. Returns false if unsuccessful. */
+bool
+cfm_generate_maid(const char *md_name, const char *ma_name,
+                  uint8_t maid[CCM_MAID_LEN])
+{
+    uint8_t *ma_p;
+    size_t md_len, ma_len;
+
+    if (!md_name) {
+        md_name = "ovs";
+    }
+
+    if (!ma_name) {
+        ma_name = "ovs";
+    }
+
+    memset(maid, 0, CCM_MAID_LEN);
+
+    md_len = strlen(md_name);
+    ma_len = strlen(ma_name);
+
+    if (!md_len || !ma_len || md_len + ma_len + 4 > CCM_MAID_LEN) {
+        return false;
+    }
+
+    maid[0] = 4;                       /* MD name string format. */
+    maid[1] = md_len;                  /* MD name size. */
+    memcpy(&maid[2], md_name, md_len); /* MD name. */
+
+    ma_p    = maid + 2 + md_len;
+    ma_p[0] = 2;                       /* MA name string format. */
+    ma_p[1] = ma_len;                  /* MA name size. */
+    memcpy(&ma_p[2], ma_name, ma_len); /* MA name. */
+    return true;
+}
+
+/* Returns true if the CFM library should process packets from 'flow'. */
+bool
+cfm_should_process_flow(const struct flow *flow)
+{
+    return ntohs(flow->dl_type) == ETH_TYPE_CFM &&
+        eth_addr_to_uint64(flow->dl_dst) == DEST_ADDR;
+}
+
+/* Updates internal statistics relevant to packet 'p'.  Should be called on
+ * every packet whose flow returned true when passed to
+ * cfm_should_process_flow. */
+void
+cfm_process_heartbeat(struct cfm *cfm, const struct ofpbuf *p)
+{
+    struct ccm *ccm;
+    uint16_t ccm_mpid;
+    uint32_t ccm_seq;
+    uint8_t ccm_interval;
+    struct remote_mp *rmp;
+
+    struct cfm_internal *cfmi        = cfm_to_internal(cfm);
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
+
+    ccm = ofpbuf_at(p, (uint8_t *)p->l3 - (uint8_t *)p->data, CCM_LEN);
+
+    if (!ccm) {
+        VLOG_INFO_RL(&rl, "Received an un-parseable 802.1ag CCM heartbeat.");
+        return;
+    }
+
+    if (ccm->opcode != CCM_OPCODE) {
+        VLOG_INFO_RL(&rl, "Received an unsupported 802.1ag message. "
+                     "(opcode %u)", ccm->opcode);
+        return;
+    }
+
+    if (memcmp(ccm->maid, cfm->maid, sizeof ccm->maid)) {
+        uint32_t hash;
+        struct remote_maid *rmaid;
+
+        hash = hash_bytes(ccm->maid, sizeof ccm->maid, 0);
+
+        HMAP_FOR_EACH_IN_BUCKET (rmaid, node, hash, &cfm->x_remote_maids) {
+            if (memcmp(rmaid->maid, ccm->maid, sizeof rmaid->maid) == 0) {
+                rmaid->recv_time = time_msec();
+                return;
+            }
+        }
+
+        rmaid            = xzalloc(sizeof *rmaid);
+        rmaid->recv_time = time_msec();
+        memcpy(rmaid->maid, ccm->maid, sizeof rmaid->maid);
+        hmap_insert(&cfm->x_remote_maids, &rmaid->node, hash);
+        return;
+    }
+
+    ccm_mpid     = ntohs(ccm->mpid);
+    ccm_seq      = ntohl(ccm->seq);
+    ccm_interval = ccm->flags & 0x7;
+
+    rmp = lookup_remote_mp(&cfm->remote_mps, ccm_mpid);
+
+    if (!rmp) {
+        rmp = lookup_remote_mp(&cfm->x_remote_mps, ccm_mpid);
+    }
+
+    if (!rmp) {
+        rmp       = xzalloc(sizeof *rmp);
+        rmp->mpid = ccm_mpid;
+        hmap_insert(&cfm->x_remote_mps, &rmp->node, hash_mpid(ccm_mpid));
+    }
+
+    rmp->recv_seq  = ccm_seq;
+    rmp->recv_time = time_msec();
+    rmp->fault     = ccm_interval != cfmi->ccm_interval;
+}
diff --git a/lib/cfm.h b/lib/cfm.h
new file mode 100644
index 0000000..69f0b0f
--- /dev/null
+++ b/lib/cfm.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2010 Nicira Networks.
+ *
+ * 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 CFM_H
+#define CFM_H 1
+
+#include <stdint.h>
+
+#include "hmap.h"
+#include "packets.h"
+
+struct flow;
+
+/* A 'cfm' represent a local Maintenance Point (MP) and it's Connectivity Fault
+ * Management (CFM) state machine. It's configuration variables should be set
+ * by clients of the CFM library. */
+struct cfm {
+    /* Configuration Variables. */
+    uint16_t mpid;              /* The MPID of this CFM. */
+    uint8_t maid[CCM_MAID_LEN]; /* The MAID of this CFM. */
+    int interval;               /* The requested transmission interval. */
+    uint8_t eth_src[ETH_ADDR_LEN];
+
+    /* Statistics. */
+    struct hmap remote_mps;     /* Expected remote MPs. */
+    struct hmap x_remote_mps;   /* Unexpected remote MPs. */
+    struct hmap x_remote_maids; /* Unexpected remote MAIDs. */
+    bool fault;                 /* Indicates connectivity vaults. */
+};
+
+/* Remote MPs represent foreign network entities that are configured to have
+ * the same MAID as this CFM instance. */
+struct remote_mp {
+    uint16_t mpid;         /* The Maintenance Point ID of this 'remote_mp'. */
+    struct hmap_node node; /* In 'cfm' 'remote_mps' or 'x_remote_mps'. */
+
+    long long recv_time; /* Time the most recent CCM was received. */
+    uint32_t recv_seq;   /* The most recently received CCM sequence number. */
+    bool fault;          /* Indicates a connectivity fault. */
+};
+
+/* Remote MAIDs keep track of incoming CCM messages which have a different MAID
+ * than this CFM instance. */
+struct remote_maid {
+    uint8_t maid[CCM_MAID_LEN]; /* The remote MAID. */
+    struct hmap_node node;      /* In 'cfm' 'x_remote_maids'. */
+
+    long long recv_time; /* Most recent receive time for this 'remote_maid'. */
+};
+
+struct cfm *cfm_create(void);
+
+void cfm_destroy(struct cfm *);
+
+struct ofpbuf *cfm_run(struct cfm *);
+
+void cfm_wait(struct cfm *);
+
+bool cfm_configure(struct cfm *);
+
+void cfm_update_remote_mps(struct cfm *, const uint16_t *mpid, size_t n_mpids);
+
+const struct remote_mp *cfm_get_remote_mp(const struct cfm *, uint16_t mpid);
+
+bool cfm_generate_maid(const char *md_name, const char *ma_name,
+                       uint8_t maid[CCM_MAID_LEN]);
+
+bool cfm_should_process_flow(const struct flow *);
+
+void cfm_process_heartbeat(struct cfm *, const struct ofpbuf *packet);
+
+#endif /* cfm.h */
diff --git a/lib/packets.h b/lib/packets.h
index 16322d6..39e88f1 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -153,6 +153,7 @@ void compose_benign_packet(struct ofpbuf *, const char *tag,
 #define ETH_TYPE_IP            0x0800
 #define ETH_TYPE_ARP           0x0806
 #define ETH_TYPE_VLAN          0x8100
+#define ETH_TYPE_CFM           0x8902
 
 #define ETH_HEADER_LEN 14
 #define ETH_PAYLOAD_MIN 46
@@ -236,6 +237,23 @@ struct vlan_eth_header {
 } __attribute__((packed));
 BUILD_ASSERT_DECL(VLAN_ETH_HEADER_LEN == sizeof(struct vlan_eth_header));
 
+/* A 'ccm' represents a Continuity Check Message from the 802.1ag specification.
+ * Continuity Check Messages are broadcast periodically so that hosts can
+ * determine who they have connectivity to. */
+#define CCM_LEN 74
+#define CCM_MAID_LEN 48
+struct ccm {
+    uint8_t  mdlevel_version; /* MD Level and Version */
+    uint8_t  opcode;
+    uint8_t  flags;
+    uint8_t  tlv_offset;
+    uint32_t seq;
+    uint16_t mpid;
+    uint8_t  maid[CCM_MAID_LEN];
+    uint8_t  zero[16]; /* Defined by ITU-T Y.1731 should be zero */
+} __attribute__((packed));
+BUILD_ASSERT_DECL(CCM_LEN == sizeof(struct ccm));
+
 /* The "(void) (ip)[0]" below has no effect on the value, since it's the first
  * argument of a comma expression, but it makes sure that 'ip' is a pointer.
  * This is useful since a common mistake is to pass an integer instead of a
diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def
index 7e62994..20d7bb3 100644
--- a/lib/vlog-modules.def
+++ b/lib/vlog-modules.def
@@ -18,6 +18,7 @@
 VLOG_MODULE(backtrace)
 VLOG_MODULE(brcompatd)
 VLOG_MODULE(bridge)
+VLOG_MODULE(cfm)
 VLOG_MODULE(collectors)
 VLOG_MODULE(controller)
 VLOG_MODULE(coverage)
diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
index d478824..ab61f50 100644
--- a/utilities/ovs-vsctl.8.in
+++ b/utilities/ovs-vsctl.8.in
@@ -463,6 +463,11 @@ specifying \fB.\fR as the record name.
 .IP "\fBsFlow\fR"
 An sFlow configuration attached to a bridge.  Records may be
 identified by bridge name.
+.IP "\fBMonitor\fR"
+Connectivity Monitoring attached to an interface.  Records may be
+identified by interface name.
+.IP "\fBMaintenance_Point\fR"
+Maintenance Point managed by a Monitor.
 .PP
 Record names must be specified in full and with correct
 capitalization.  Names of tables and columns are not case-sensitive,
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
index acdcaf3..56cb745 100644
--- a/utilities/ovs-vsctl.c
+++ b/utilities/ovs-vsctl.c
@@ -1973,6 +1973,16 @@ static const struct vsctl_table_class tables[] = {
      {{&ovsrec_table_port, &ovsrec_port_col_name, &ovsrec_port_col_qos},
       {NULL, NULL, NULL}}},
 
+    {&ovsrec_table_monitor,
+     {{&ovsrec_table_interface,
+       &ovsrec_interface_col_name,
+       &ovsrec_interface_col_monitor},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_maintenance_point,
+     {{NULL, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
     {&ovsrec_table_queue,
      {{NULL, NULL, NULL},
       {NULL, NULL, NULL}}},
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index e7bc5ab..cf06084 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -32,6 +32,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include "bitmap.h"
+#include "cfm.h"
 #include "classifier.h"
 #include "coverage.h"
 #include "dirs.h"
@@ -91,6 +92,7 @@ struct iface {
     struct netdev *netdev;      /* Network device. */
     bool enabled;               /* May be chosen for flows? */
     const char *type;           /* Usually same as cfg->type. */
+    struct cfm *cfm;            /* Connectivity Fault Management */
     const struct ovsrec_interface *cfg;
 };
 
@@ -258,6 +260,9 @@ static struct iface *iface_from_dp_ifidx(const struct bridge *,
 static void iface_set_mac(struct iface *);
 static void iface_set_ofport(const struct ovsrec_interface *, int64_t ofport);
 static void iface_update_qos(struct iface *, const struct ovsrec_qos *);
+static void iface_update_cfm(struct iface *);
+static void iface_refresh_cfm_stats(struct iface *iface);
+static void iface_send_packet(struct iface *, struct ofpbuf *packet);
 
 static void shash_from_ovs_idl_map(char **keys, char **values, size_t n,
                                    struct shash *);
@@ -919,6 +924,13 @@ bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
         iterate_and_prune_ifaces(br, set_iface_properties, NULL);
     }
 
+    LIST_FOR_EACH (br, node, &all_bridges) {
+        struct iface *iface;
+        HMAP_FOR_EACH (iface, dp_ifidx_node, &br->ifaces) {
+            iface_update_cfm(iface);
+        }
+    }
+
     free(managers);
 }
 
@@ -1138,6 +1150,88 @@ dpid_from_hash(const void *data, size_t n)
 }
 
 static void
+iface_refresh_cfm_stats(struct iface *iface)
+{
+    size_t i;
+    struct cfm *cfm;
+    const struct ovsrec_monitor *cfg;
+
+    cfg = iface->cfg->monitor;
+    cfm = iface->cfm;
+
+    if (!cfm || !cfg) {
+        return;
+    }
+
+    for (i = 0; i < cfg->n_remote_mps; i++) {
+        const struct ovsrec_maintenance_point *mp;
+        const struct remote_mp *rmp;
+        int64_t rseq, rtime;
+
+        mp = cfg->remote_mps[i];
+        rmp = cfm_get_remote_mp(cfm, mp->mpid);
+
+        rseq  = rmp->recv_seq;
+        rtime = rmp->recv_time;
+
+        ovsrec_maintenance_point_set_fault(mp, &rmp->fault, 1);
+        ovsrec_maintenance_point_set_received_sequence(mp, &rseq, 1);
+        ovsrec_maintenance_point_set_received_time(mp, &rtime, 1);
+    }
+
+    if (hmap_is_empty(&cfm->x_remote_mps)) {
+        ovsrec_monitor_set_unexpected_remote_mpids(cfg, NULL, 0);
+    } else {
+        size_t length;
+        struct remote_mp *rmp;
+        int64_t *x_remote_mps;
+
+        length = hmap_count(&cfm->x_remote_mps);
+        x_remote_mps = xzalloc(length * sizeof *x_remote_mps);
+
+        i = 0;
+        HMAP_FOR_EACH(rmp, node, &cfm->x_remote_mps) {
+            x_remote_mps[i++] = rmp->mpid;
+        }
+
+        ovsrec_monitor_set_unexpected_remote_mpids(cfg, x_remote_mps, length);
+        free(x_remote_mps);
+    }
+
+    if (hmap_is_empty(&cfm->x_remote_maids)) {
+        ovsrec_monitor_set_unexpected_remote_maids(cfg, NULL, 0);
+    } else {
+        size_t length;
+        char **x_remote_maids;
+        struct remote_maid *rmaid;
+
+        length = hmap_count(&cfm->x_remote_maids);
+        x_remote_maids = xzalloc(length * sizeof *x_remote_maids);
+
+        i = 0;
+        HMAP_FOR_EACH(rmaid, node, &cfm->x_remote_maids) {
+            size_t j;
+
+            x_remote_maids[i] = xzalloc(CCM_MAID_LEN * 2 + 1);
+
+            for (j = 0; j < CCM_MAID_LEN; j++) {
+                 snprintf(&x_remote_maids[i][j * 2], 3, "%02hhx",
+                          rmaid->maid[j]);
+            }
+            i++;
+        }
+        ovsrec_monitor_set_unexpected_remote_maids(cfg, x_remote_maids, length);
+
+        for (i = 0; i < length; i++) {
+            free(x_remote_maids[i]);
+        }
+        free(x_remote_maids);
+    }
+
+    ovsrec_monitor_set_fault(cfg, &cfm->fault, 1);
+}
+
+static void
 iface_refresh_stats(struct iface *iface)
 {
     struct iface_stat {
@@ -1269,6 +1363,7 @@ bridge_run(void)
                     for (j = 0; j < port->n_ifaces; j++) {
                         struct iface *iface = port->ifaces[j];
                         iface_refresh_stats(iface);
+                        iface_refresh_cfm_stats(iface);
                     }
                 }
             }
@@ -1285,6 +1380,7 @@ void
 bridge_wait(void)
 {
     struct bridge *br;
+    struct iface *iface;
 
     LIST_FOR_EACH (br, node, &all_bridges) {
         ofproto_wait(br->ofproto);
@@ -1294,6 +1390,12 @@ bridge_wait(void)
 
         mac_learning_wait(br->ml);
         bond_wait(br);
+
+        HMAP_FOR_EACH (iface, dp_ifidx_node, &br->ifaces) {
+            if (iface->cfm) {
+                cfm_wait(iface->cfm);
+            }
+        }
     }
     ovsdb_idl_wait(idl);
     poll_timer_wait_until(stats_timer);
@@ -1494,6 +1596,7 @@ static int
 bridge_run_one(struct bridge *br)
 {
     int error;
+    struct iface *iface;
 
     error = ofproto_run1(br->ofproto);
     if (error) {
@@ -1506,6 +1609,21 @@ bridge_run_one(struct bridge *br)
     error = ofproto_run2(br->ofproto, br->flush);
     br->flush = false;
 
+    HMAP_FOR_EACH (iface, dp_ifidx_node, &br->ifaces) {
+        struct ofpbuf *packet;
+
+        if (!iface->cfm) {
+            continue;
+        }
+
+        packet = cfm_run(iface->cfm);
+        if (packet) {
+            iface_send_packet(iface, packet);
+            ofpbuf_uninit(packet);
+            free(packet);
+        }
+    }
+
     return error;
 }
 
@@ -2634,10 +2752,20 @@ bridge_normal_ofhook_cb(const struct flow *flow, const struct ofpbuf *packet,
                         struct odp_actions *actions, tag_type *tags,
                         uint16_t *nf_output_iface, void *br_)
 {
+    struct iface *iface;
     struct bridge *br = br_;
 
     COVERAGE_INC(bridge_process_flow);
 
+    iface = iface_from_dp_ifidx(br, flow->in_port);
+
+    if (cfm_should_process_flow(flow)) {
+        if (packet && iface->cfm) {
+            cfm_process_heartbeat(iface->cfm, packet);
+        }
+        return false;
+    }
+
     return process_flow(br, flow, packet, actions, tags, nf_output_iface);
 }
 
@@ -3778,6 +3906,26 @@ port_update_vlan_compat(struct port *port)
 
 /* Interface functions. */
 
+static void
+iface_send_packet(struct iface *iface, struct ofpbuf *packet)
+{
+    struct flow flow;
+    union ofp_action action;
+
+    memset(&action, 0, sizeof action);
+    action.output.type = htons(OFPAT_OUTPUT);
+    action.output.len  = htons(sizeof action);
+    action.output.port = htons(odp_port_to_ofp_port(iface->dp_ifidx));
+
+    flow_extract(packet, 0, ODPP_NONE, &flow);
+
+    if (ofproto_send_packet(iface->port->bridge->ofproto, &flow, &action, 1,
+                            packet)) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+        VLOG_WARN_RL(&rl, "interface %s: Failed to send packet.", iface->name);
+    }
+}
+
 static struct iface *
 iface_create(struct port *port, const struct ovsrec_interface *if_cfg)
 {
@@ -3839,6 +3987,8 @@ iface_destroy(struct iface *iface)
             bond_send_learning_packets(port);
         }
 
+        cfm_destroy(iface->cfm);
+
         free(iface->name);
         free(iface);
 
@@ -3975,6 +4125,56 @@ iface_update_qos(struct iface *iface, const struct ovsrec_qos *qos)
         }
     }
 }
+
+static void
+iface_update_cfm(struct iface *iface)
+{
+    size_t i;
+    struct cfm *cfm;
+    uint16_t *remote_mps;
+    struct ovsrec_monitor *cfg;
+    uint8_t ea[ETH_ADDR_LEN], maid[CCM_MAID_LEN];
+
+    cfg = iface->cfg->monitor;
+
+    if (!cfg) {
+        return;
+    }
+
+    if (netdev_get_etheraddr(iface->netdev, ea)) {
+        VLOG_WARN("interface %s: Failed to get ethernet address. "
+                  "Skipping Monitor.", iface->name);
+        return;
+    }
+
+    if (!cfm_generate_maid(cfg->md_name, cfg->ma_name, maid)) {
+        VLOG_WARN("interface %s: Failed to generate MAID.", iface->name);
+        return;
+    }
+
+    if (!iface->cfm) {
+        iface->cfm = cfm_create();
+    }
+
+    cfm           = iface->cfm;
+    cfm->mpid     = cfg->mpid;
+    cfm->interval = cfg->interval ? *cfg->interval : 1000;
+
+    memcpy(cfm->eth_src, ea, sizeof cfm->eth_src);
+    memcpy(cfm->maid, maid, sizeof cfm->maid);
+
+    remote_mps = xzalloc(cfg->n_remote_mps * sizeof *remote_mps);
+    for(i = 0; i < cfg->n_remote_mps; i++) {
+        remote_mps[i] = cfg->remote_mps[i]->mpid;
+    }
+    cfm_update_remote_mps(cfm, remote_mps, cfg->n_remote_mps);
+    free(remote_mps);
+
+    if (!cfm_configure(iface->cfm)) {
+        cfm_destroy(iface->cfm);
+        iface->cfm = NULL;
+    }
+}
 
 /* Port mirroring. */
 
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index db8f6eb..6d57ca7 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -141,6 +141,11 @@
        "ofport": {
          "type": {"key": "integer", "min": 0, "max": 1},
          "ephemeral": true},
+       "monitor": {
+         "type": {
+           "key": {"type": "uuid", "refTable": "Monitor"},
+           "min": 0,
+           "max": 1}},
        "other_config": {
          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
        "statistics": {
@@ -149,6 +154,72 @@
        "status": {
          "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"},
          "ephemeral": true}}},
+   "Monitor": {
+     "columns": {
+       "mpid": {
+         "type" : {
+           "key": { "type": "integer", "minInteger": 1, "maxInteger": 8191}}},
+       "md_name": {
+         "type" : {
+           "key": { "type": "string", "minLength": 1, "maxLength": 43},
+           "min": 0,
+           "max": 1}},
+       "ma_name": {
+         "type" : {
+           "key": { "type": "string", "minLength": 1, "maxLength": 43},
+           "min": 0,
+           "max": 1}},
+       "interval": {
+         "type": {
+           "key": { "type": "integer", "minInteger": 100},
+           "min": 0,
+           "max": 1}},
+       "remote_mps": {
+         "type": {
+           "key": { "type": "uuid", "refTable": "Maintenance_Point"},
+           "min": 0,
+           "max": "unlimited"},
+         "ephemeral": true},
+       "unexpected_remote_mpids": {
+         "type": {
+           "key": { "type": "integer"},
+           "min": 0,
+           "max": "unlimited"},
+         "ephemeral": true},
+       "unexpected_remote_maids": {
+         "type": {
+           "key": "string",
+           "min": 0,
+           "max": "unlimited"},
+         "ephemeral": true},
+       "fault": {
+         "type": {
+           "key": { "type": "boolean"},
+           "min": 0,
+           "max": 1},
+         "ephemeral": true}}},
+   "Maintenance_Point": {
+     "columns": {
+       "mpid": {
+         "type" : {
+           "key": { "type": "integer", "minInteger": 1, "maxInteger": 8191}},
+         "mutable": false},
+       "received_time": {
+         "type": {
+           "key": { "type": "integer"},
+           "min": 0,
+           "max": 1}},
+       "received_sequence": {
+         "type": {
+           "key": { "type": "integer"},
+           "min": 0,
+           "max": 1}},
+       "fault": {
+         "type": {
+           "key": { "type": "boolean"},
+           "min": 0,
+           "max": 1},
+         "ephemeral": true}}},
    "QoS": {
      "columns": {
        "type": {
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index dd6fce7..51e2c8a 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -892,6 +892,11 @@
     </group>
 
     <group title="Other Features">
+
+      <column name="monitor">
+        Connectivity monitor configuration for this interface.
+      </column>
+
       <column name="external_ids">
         Key-value pairs for use by external frameworks that integrate
         with Open vSwitch, rather than by Open vSwitch itself.  System
@@ -1140,6 +1145,82 @@
     </column>
   </table>
 
+  <table name="Monitor" title="Connectivity Monitor configuration">
+    <p>Connectivity Monitor configuration for each interface that references
+      it. Implements a subset of 802.1ag Connectivity Fault Management.  </p>
+
+    <column name="mpid">
+      A Maintenance Point ID (MPID) uniquely identifies each end point within a
+      Maintenance Association (see ma_name).  This MPID is used to identify
+      this Monitor to other end points in the Maintenance Association.
+    </column>
+
+    <column name="remote_mps">
+      A set of Maintenance Points (MPs) which this Monitor should have
+      connectivity to.  If this Monitor does not have connectivity to any MPs
+      in this set, or has connectivity to and MPs not in this set, a fault is
+      signaled.
+    </column>
+
+    <column name="unexpected_remote_mpids">
+      A set of MPIDs representing MPs to which this Monitor has detected
+      connectivity that are not in the remote_mps set.  This Monitor should not
+      have connectivity to any MPs not listed in remote_mps.  Thus, if this set
+      is non-empty a fault is indicated.
+    </column>
+
+    <column name="unexpected_remote_maids">
+      A set of MAIDs representing foreign Maintenance Associations (MAs) which
+      this monitor has detected connectivity to. A Monitor should not have
+      connectivity to a Maintenance Association other than it's own.  Thus, if
+      this set is non-empty a fault is indicated.
+    </column>
+
+    <column name="fault">
+      Indicates a Connectivity Fault caused by a configuration error, a down
+      remote MP, or unexpected connectivity to a remote MAID or remote MP.
+    </column>
+
+    <column name="ma_name">
+      A Maintenance Association (MA) name pairs with a Maintenance Domain (MD)
+      name to uniquely identify a MA.  A MA is a group of endpoints who have
+      complete and exclusive interconnectivity. Defaults to "ovs" if unset.
+    </column>
+
+    <column name="md_name">
+      A Maintenance Domain name pairs with a Maintenance Association name to
+      uniquely identify a MA. Defaults to "ovs" if unset.
+    </column>
+
+    <column name="interval">
+      The transmission interval of Continuity Check Messages (CCMs) in
+      milliseconds.  Three missed CCMs indicate a connectivity fault.  Defaults
+      to 1000ms.
+    </column>
+
+  </table>
+
+  <table name="Maintenance_Point" title="Maintenance Point configuration">
+
+    <column name="mpid">
+      A Maintenance Point ID (MPID) uniquely identifies each end point within a
+      Maintenance Association.
+    </column>
+
+    <column name="received_time">
+      The time in milliseconds a Continuity Check Message (CCM) was most
+      recently received.
+    </column>
+
+    <column name="received_sequence">
+      The sequence number of the most recently received CCM.
+    </column>
+
+    <column name="fault">
+      Indicates a connectivity fault.
+    </column>
+  </table>
+
   <table name="Mirror" title="Port mirroring (SPAN/RSPAN).">
     <p>A port mirror within a <ref table="Bridge"/>.</p>
     <p>A port mirror configures a bridge to send selected frames to special
-- 
1.7.3.2





More information about the dev mailing list