From: Ethan Jackson Date: Tue, 16 Nov 2010 00:20:01 +0000 (-0800) Subject: ovs: Implement 802.1ag Connectivity Fault Management X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b31bcf60cf4fbabca0182196c97b3004e42f53e5;p=openvswitch ovs: Implement 802.1ag Connectivity Fault Management 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. --- diff --git a/lib/automake.mk b/lib/automake.mk index 4ebbb19a..719ae488 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 00000000..12cf3e96 --- /dev/null +++ b/lib/cfm.c @@ -0,0 +1,444 @@ +/* + * 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 +#include "cfm.h" + +#include +#include +#include + +#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; +} diff --git a/lib/cfm.h b/lib/cfm.h new file mode 100644 index 00000000..f7007bb9 --- /dev/null +++ b/lib/cfm.h @@ -0,0 +1,84 @@ +/* + * 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 + +#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 */ diff --git a/lib/packets.h b/lib/packets.h index 16322d6a..39e88f1f 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 7e629948..20d7bb3b 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 d4788242..aacb19a7 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, @@ -693,6 +698,21 @@ then delete the QoS record: 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 diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c index acdcaf31..56cb7450 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 e7bc5ab3..7dbd3783 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -32,6 +32,7 @@ #include #include #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); } @@ -1137,6 +1149,82 @@ dpid_from_hash(const void *data, size_t n) 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) { @@ -1269,6 +1357,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 +1374,7 @@ void bridge_wait(void) { struct bridge *br; + struct iface *iface; LIST_FOR_EACH (br, node, &all_bridges) { ofproto_wait(br->ofproto); @@ -1294,6 +1384,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 +1590,7 @@ static int bridge_run_one(struct bridge *br) { int error; + struct iface *iface; error = ofproto_run1(br->ofproto); if (error) { @@ -1506,6 +1603,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 +2746,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 +3900,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 +3981,8 @@ iface_destroy(struct iface *iface) bond_send_learning_packets(port); } + cfm_destroy(iface->cfm); + free(iface->name); free(iface); @@ -3975,6 +4119,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 *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; + } +} /* Port mirroring. */ diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema index db8f6eba..52573e51 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,62 @@ "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": { diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index dd6fce7c..bec2d58e 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -892,6 +892,11 @@ + + + Connectivity monitor configuration for this interface. + + Key-value pairs for use by external frameworks that integrate with Open vSwitch, rather than by Open vSwitch itself. System @@ -1140,6 +1145,98 @@ + +

+ A attaches to an 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 is + responsible for collecting data about other MPs in its MA and + broadcasting CCMs. +

+ + + + A Maintenance Point ID (MPID) uniquely identifies each endpoint within + a Maintenance Association (see ). The MPID is + used to identify this to other endpoints in the + MA. + + + + A set of which this + should have connectivity to. If this + does not have connectivity to any MPs in this + set, or has connectivity to any MPs not in this set, a fault is + signaled. + + + + 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. + + + + A Maintenance Domain name pairs with a Maintenance Association name to + uniquely identify a MA. Defaults to ovs if unset. + + + + The transmission interval of CCMs in milliseconds. Three missed CCMs + indicate a connectivity fault. Defaults to 1000ms. + + + + + + A set of MPIDs representing MPs to which this + has detected connectivity that are not in the + set. This should not + have connectivity to any MPs not listed in . + Thus, if this set is non-empty a fault is indicated. + + + + A set of MAIDs representing foreign Maintenance Associations (MAs) + which this has detected connectivity to. A + should not have connectivity to a Maintenance + Association other than its own. Thus, if this set is non-empty a fault + is indicated. + + + + Indicates a Connectivity Fault caused by a configuration error, a down + remote MP, or unexpected connectivity to a remote MAID or remote MP. + + +
+ + +

+ A represents a MP which a + has or should have connectivity to. +

+ + + + A Maintenance Point ID (MPID) uniquely identifies each endpoint within + a Maintenance Association. All MPs within a MA should have a unique + MPID. + + + + + + Indicates a connectivity fault. + + +
+

A port mirror within a .

A port mirror configures a bridge to send selected frames to special