diff --git a/lib/automake.mk b/lib/automake.mk index 4ebbb19af..719ae4889 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 000000000..12cf3e96a --- /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 000000000..f7007bb91 --- /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 16322d6ac..39e88f1f5 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 7e629948a..20d7bb3bc 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 d4788242d..aacb19a7c 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 acdcaf317..56cb7450d 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 e7bc5ab33..7dbd37837 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 db8f6ebac..52573e516 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 dd6fce7cf..bec2d58e8 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