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 \
--- /dev/null
+/*
+ * 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 + 2);
+
+ ofpbuf_reserve(packet, 2);
+
+ 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 its '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));
+}
+
+/* Should be run periodically to update fault statistics and generate CCM
+ * messages. If necessary, returns a packet which the caller is responsible
+ * for sending, un-initing, and deallocating. Otherwise returns NULL. */
+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_time = time_msec();
+ rmp->fault = ccm_interval != cfmi->ccm_interval;
+}
--- /dev/null
+/*
+ * 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 its Connectivity Fault
+ * Management (CFM) state machine. Its 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. */
+ 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 */
#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
} __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
VLOG_MODULE(backtrace)
VLOG_MODULE(brcompatd)
VLOG_MODULE(bridge)
+VLOG_MODULE(cfm)
VLOG_MODULE(collectors)
VLOG_MODULE(controller)
VLOG_MODULE(coverage)
.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,
database. To delete them, use "\fBovs\-vsctl list Queue\fR" to find
their UUIDs, then "\fBovs\-vsctl destroy Queue \fIuuid1\fR
\fIuuid2\fR" to destroy each of them.)
+.SS "Connectivity Monitoring"
+.PP
+Create a Monitor which manages a couple of remote Maintenance Points on eth0.
+.IP
+.B "ovs\-vsctl \-\- set Interface eth0 Monitor=@newmon \(rs"
+.IP
+.B "\-\- \-\-id=@newmon create Monitor mpid=1 remote_mps=@mp2,@mp3 \(rs"
+.IP
+.B "\-\- \-\-id=@mp2 create Maintenance_Point mpid=2 \(rs"
+.IP
+.B "\-\- \-\-id=@mp3 create Maintenance_Point mpid=3"
+.PP
+Deconfigure the Monitor record from above:
+.IP
+.B "ovs\-vsctl clear Interface eth0 Monitor"
.SS "NetFlow"
.PP
Configure bridge \fBbr0\fR to send NetFlow records to UDP port 5566 on
{{&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}}},
#include <sys/types.h>
#include <unistd.h>
#include "bitmap.h"
+#include "cfm.h"
#include "classifier.h"
#include "coverage.h"
#include "dirs.h"
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;
};
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 *);
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);
}
return eth_addr_to_uint64(hash);
}
+static void
+iface_refresh_cfm_stats(struct iface *iface)
+{
+ size_t i;
+ struct cfm *cfm;
+ const struct ovsrec_monitor *mon;
+
+ mon = iface->cfg->monitor;
+ cfm = iface->cfm;
+
+ if (!cfm || !mon) {
+ return;
+ }
+
+ for (i = 0; i < mon->n_remote_mps; i++) {
+ const struct ovsrec_maintenance_point *mp;
+ const struct remote_mp *rmp;
+
+ mp = mon->remote_mps[i];
+ rmp = cfm_get_remote_mp(cfm, mp->mpid);
+
+ ovsrec_maintenance_point_set_fault(mp, &rmp->fault, 1);
+ }
+
+ if (hmap_is_empty(&cfm->x_remote_mps)) {
+ ovsrec_monitor_set_unexpected_remote_mpids(mon, 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(mon, x_remote_mps, length);
+ free(x_remote_mps);
+ }
+
+ if (hmap_is_empty(&cfm->x_remote_maids)) {
+ ovsrec_monitor_set_unexpected_remote_maids(mon, 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(mon, x_remote_maids, length);
+
+ for (i = 0; i < length; i++) {
+ free(x_remote_maids[i]);
+ }
+ free(x_remote_maids);
+ }
+
+ ovsrec_monitor_set_fault(mon, &cfm->fault, 1);
+}
+
static void
iface_refresh_stats(struct iface *iface)
{
for (j = 0; j < port->n_ifaces; j++) {
struct iface *iface = port->ifaces[j];
iface_refresh_stats(iface);
+ iface_refresh_cfm_stats(iface);
}
}
}
bridge_wait(void)
{
struct bridge *br;
+ struct iface *iface;
LIST_FOR_EACH (br, node, &all_bridges) {
ofproto_wait(br->ofproto);
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);
bridge_run_one(struct bridge *br)
{
int error;
+ struct iface *iface;
error = ofproto_run1(br->ofproto);
if (error) {
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;
}
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);
}
\f
/* 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)
{
bond_send_learning_packets(port);
}
+ cfm_destroy(iface->cfm);
+
free(iface->name);
free(iface);
}
}
}
+
+static void
+iface_update_cfm(struct iface *iface)
+{
+ size_t i;
+ struct cfm *cfm;
+ uint16_t *remote_mps;
+ struct ovsrec_monitor *mon;
+ uint8_t ea[ETH_ADDR_LEN], maid[CCM_MAID_LEN];
+
+ mon = iface->cfg->monitor;
+
+ if (!mon) {
+ 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(mon->md_name, mon->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 = mon->mpid;
+ cfm->interval = mon->interval ? *mon->interval : 1000;
+
+ memcpy(cfm->eth_src, ea, sizeof cfm->eth_src);
+ memcpy(cfm->maid, maid, sizeof cfm->maid);
+
+ remote_mps = xzalloc(mon->n_remote_mps * sizeof *remote_mps);
+ for(i = 0; i < mon->n_remote_mps; i++) {
+ remote_mps[i] = mon->remote_mps[i]->mpid;
+ }
+ cfm_update_remote_mps(cfm, remote_mps, mon->n_remote_mps);
+ free(remote_mps);
+
+ if (!cfm_configure(iface->cfm)) {
+ cfm_destroy(iface->cfm);
+ iface->cfm = NULL;
+ }
+}
\f
/* Port mirroring. */
"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": {
"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},
+ "fault": {
+ "type": {
+ "key": { "type": "boolean"},
+ "min": 0,
+ "max": 1},
+ "ephemeral": true}}},
"QoS": {
"columns": {
"type": {
</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
</column>
</table>
+ <table name="Monitor" title="Connectivity Monitor configuration">
+ <p>
+ A <ref table="Monitor"/> attaches to an <ref table="Interface"/> to
+ implement 802.1ag Connectivity Fault Management (CFM). CFM allows a
+ group of Maintenance Points (MPs) called a Maintenance Association (MA)
+ to detect connectivity problems with each other. MPs within a MA should
+ have complete and exclusive interconnectivity. This is verified by
+ occasionally broadcasting Continuity Check Messages (CCMs) at a
+ configurable transmission interval. A <ref table="Monitor"/> is
+ responsible for collecting data about other MPs in its MA and
+ broadcasting CCMs.
+ </p>
+
+ <group title="Monitor Configuration">
+ <column name="mpid">
+ A Maintenance Point ID (MPID) uniquely identifies each endpoint within
+ a Maintenance Association (see <ref column="ma_name"/>). The MPID is
+ used to identify this <ref table="Monitor"/> to other endpoints in the
+ MA.
+ </column>
+
+ <column name="remote_mps">
+ A set of <ref table="Maintenance_Points"/> which this
+ <ref table="Monitor"/> should have connectivity to. If this
+ <ref table="Monitor"/> does not have connectivity to any MPs in this
+ set, or has connectivity to any MPs not in this set, a fault is
+ signaled.
+ </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
+ <code>ovs</code> if unset.
+ </column>
+
+ <column name="md_name">
+ A Maintenance Domain name pairs with a Maintenance Association name to
+ uniquely identify a MA. Defaults to <code>ovs</code> if unset.
+ </column>
+
+ <column name="interval">
+ The transmission interval of CCMs in milliseconds. Three missed CCMs
+ indicate a connectivity fault. Defaults to 1000ms.
+ </column>
+ </group>
+
+ <group title="Monitor Status">
+ <column name="unexpected_remote_mpids">
+ A set of MPIDs representing MPs to which this <ref table="Monitor"/>
+ has detected connectivity that are not in the
+ <ref column="remote_mps"/> set. This <ref table="Monitor"/> should not
+ have connectivity to any MPs not listed in <ref column="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 <ref table="Monitor"/> has detected connectivity to. A
+ <ref table="Monitor"/> should not have connectivity to a Maintenance
+ Association other than its 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>
+ </group>
+ </table>
+
+ <table name="Maintenance_Point" title="Maintenance Point configuration">
+ <p>
+ A <ref table="Maintenance_Point"/> represents a MP which a
+ <ref table="Monitor"/> has or should have connectivity to.
+ </p>
+
+ <group title="Maintenance_Point Configuration">
+ <column name="mpid">
+ A Maintenance Point ID (MPID) uniquely identifies each endpoint within
+ a Maintenance Association. All MPs within a MA should have a unique
+ MPID.
+ </column>
+ </group>
+
+ <group title="Maintenance_Point Status">
+ <column name="fault">
+ Indicates a connectivity fault.
+ </column>
+ </group>
+ </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