X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fcfm.c;h=8d90829a77122521b1e3aa069b1e178a714a3e37;hb=9b80f761bed9a32c1b0eb22ee3361966057ea973;hp=245ce448789048de75abddba51590d64adf850bd;hpb=348f01e3e36b67f86ac2f9f90c7574d9e1b85d73;p=sliver-openvswitch.git diff --git a/lib/cfm.c b/lib/cfm.c index 245ce4487..8d90829a7 100644 --- a/lib/cfm.c +++ b/lib/cfm.c @@ -49,8 +49,13 @@ static const uint8_t eth_addr_ccm_x[6] = { /* A 'ccm' represents a Continuity Check Message from the 802.1ag * specification. Continuity Check Messages are broadcast periodically so that - * hosts can determine whom they have connectivity to. */ -#define CCM_LEN 74 + * hosts can determine whom they have connectivity to. + * + * The minimum length of a CCM as specified by IEEE 802.1ag is 75 bytes. + * Previous versions of Open vSwitch generated 74-byte CCM messages, so we + * accept such messages too. */ +#define CCM_LEN 75 +#define CCM_ACCEPT_LEN 74 #define CCM_MAID_LEN 48 #define CCM_OPCODE 1 /* CFM message opcode meaning CCM. */ #define CCM_RDI_MASK 0x80 @@ -66,7 +71,11 @@ struct ccm { /* Defined by ITU-T Y.1731 should be zero */ ovs_be16 interval_ms_x; /* Transmission interval in ms. */ ovs_be64 mpid64; /* MPID in extended mode. */ - uint8_t zero[6]; + uint8_t opdown; /* Operationally down. */ + uint8_t zero[5]; + + /* TLV space. */ + uint8_t end_tlv; } __attribute__((packed)); BUILD_ASSERT_DECL(CCM_LEN == sizeof(struct ccm)); @@ -76,18 +85,30 @@ struct cfm { uint64_t mpid; bool extended; /* Extended mode. */ - bool fault; /* Indicates connectivity fault. */ - bool unexpected_recv; /* Received an unexpected CCM. */ + int fault; /* Connectivity fault status. */ + int recv_fault; /* Bit mask of faults occuring on receive. */ + bool opup; /* Operational State. */ + bool remote_opup; /* Remote Operational State. */ + + int fault_override; /* Manual override of 'fault' status. + Ignored if negative. */ 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. */ + uint16_t ccm_vlan; /* Vlan tag of CCM PDUs. */ + uint8_t ccm_pcp; /* Priority of CCM PDUs. */ uint8_t maid[CCM_MAID_LEN]; /* The MAID of this CFM. */ struct timer tx_timer; /* Send CCM when expired. */ struct timer fault_timer; /* Check for faults when expired. */ struct hmap remote_mps; /* Remote MPs. */ + + /* Result of cfm_get_remote_mpids(). Updated only during fault check to + * avoid flapping. */ + uint64_t *rmps_array; /* Cache of remote_mps. */ + size_t rmps_array_len; /* Number of rmps in 'rmps_array'. */ }; /* Remote MPs represent foreign network entities that are configured to have @@ -99,13 +120,15 @@ struct remote_mp { bool recv; /* CCM was received since last fault check. */ bool rdi; /* Remote Defect Indicator. Indicates remote_mp isn't receiving CCMs that it's expecting to. */ + bool opup; /* Operational State. */ + uint32_t seq; /* Most recently received sequence number. */ }; -static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 30); static struct hmap all_cfms = HMAP_INITIALIZER(&all_cfms); -static void cfm_unixctl_show(struct unixctl_conn *, const char *args, - void *aux); +static unixctl_cb_func cfm_unixctl_show; +static unixctl_cb_func cfm_unixctl_set_fault; static const uint8_t * cfm_ccm_addr(const struct cfm *cfm) @@ -113,6 +136,36 @@ cfm_ccm_addr(const struct cfm *cfm) return cfm->extended ? eth_addr_ccm_x : eth_addr_ccm; } +/* Returns the string representation of the given cfm_fault_reason 'reason'. */ +const char * +cfm_fault_reason_to_str(int reason) { + switch (reason) { +#define CFM_FAULT_REASON(NAME, STR) case CFM_FAULT_##NAME: return #STR; + CFM_FAULT_REASONS +#undef CFM_FAULT_REASON + default: return ""; + } +} + +static void +ds_put_cfm_fault(struct ds *ds, int fault) +{ + size_t length = ds->length; + int i; + + for (i = 0; i < CFM_FAULT_N_REASONS; i++) { + int reason = 1 << i; + + if (fault & reason) { + ds_put_format(ds, "%s ", cfm_fault_reason_to_str(reason)); + } + } + + if (ds->length > length) { + ds_truncate(ds, ds->length - 1); + } +} + static void cfm_generate_maid(struct cfm *cfm) { @@ -215,7 +268,10 @@ lookup_remote_mp(const struct cfm *cfm, uint64_t mpid) void cfm_init(void) { - unixctl_command_register("cfm/show", cfm_unixctl_show, NULL); + unixctl_command_register("cfm/show", "[interface]", 0, 1, cfm_unixctl_show, + NULL); + unixctl_command_register("cfm/set-fault", "[interface] normal|false|true", + 1, 2, cfm_unixctl_set_fault, NULL); } /* Allocates a 'cfm' object called 'name'. 'cfm' should be initialized by @@ -230,6 +286,8 @@ cfm_create(const char *name) hmap_init(&cfm->remote_mps); cfm_generate_maid(cfm); hmap_insert(&all_cfms, &cfm->hmap_node, hash_string(cfm->name, 0)); + cfm->remote_opup = true; + cfm->fault_override = -1; return cfm; } @@ -249,6 +307,7 @@ cfm_destroy(struct cfm *cfm) hmap_destroy(&cfm->remote_mps); hmap_remove(&all_cfms, &cfm->hmap_node); + free(cfm->rmps_array); free(cfm->name); free(cfm); } @@ -260,10 +319,17 @@ cfm_run(struct cfm *cfm) if (timer_expired(&cfm->fault_timer)) { long long int interval = cfm_fault_interval(cfm); struct remote_mp *rmp, *rmp_next; + bool old_cfm_fault = cfm->fault; - cfm->fault = cfm->unexpected_recv; - cfm->unexpected_recv = false; + cfm->fault = cfm->recv_fault; + cfm->recv_fault = 0; + cfm->rmps_array_len = 0; + free(cfm->rmps_array); + cfm->rmps_array = xmalloc(hmap_count(&cfm->remote_mps) * + sizeof *cfm->rmps_array); + + cfm->remote_opup = true; HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->remote_mps) { if (!rmp->recv) { @@ -277,19 +343,34 @@ cfm_run(struct cfm *cfm) if (rmp->mpid == cfm->mpid) { VLOG_WARN_RL(&rl,"%s: received CCM with local MPID" " %"PRIu64, cfm->name, rmp->mpid); - cfm->fault = true; + cfm->fault |= CFM_FAULT_LOOPBACK; } if (rmp->rdi) { VLOG_DBG("%s: RDI bit flagged from RMP %"PRIu64, cfm->name, rmp->mpid); - cfm->fault = true; + cfm->fault |= CFM_FAULT_RDI; + } + + if (!rmp->opup) { + cfm->remote_opup = rmp->opup; } + + cfm->rmps_array[cfm->rmps_array_len++] = rmp->mpid; } } if (hmap_is_empty(&cfm->remote_mps)) { - cfm->fault = true; + cfm->fault |= CFM_FAULT_RECV; + } + + if (old_cfm_fault != cfm->fault) { + struct ds ds = DS_EMPTY_INITIALIZER; + + ds_put_cfm_fault(&ds, cfm->fault); + VLOG_INFO_RL(&rl, "%s: CFM fault status changed: %s", cfm->name, + ds_cstr_ro(&ds)); + ds_destroy(&ds); } timer_set_duration(&cfm->fault_timer, interval); @@ -313,8 +394,14 @@ cfm_compose_ccm(struct cfm *cfm, struct ofpbuf *packet, struct ccm *ccm; timer_set_duration(&cfm->tx_timer, cfm->ccm_interval_ms); - ccm = eth_compose(packet, cfm_ccm_addr(cfm), eth_src, ETH_TYPE_CFM, - sizeof *ccm); + eth_compose(packet, cfm_ccm_addr(cfm), eth_src, ETH_TYPE_CFM, sizeof *ccm); + + if (cfm->ccm_vlan || cfm->ccm_pcp) { + uint16_t tci = cfm->ccm_vlan | (cfm->ccm_pcp << VLAN_PCP_SHIFT); + eth_push_vlan(packet, htons(tci)); + } + + ccm = packet->l3; ccm->mdlevel_version = 0; ccm->opcode = CCM_OPCODE; ccm->tlv_offset = 70; @@ -322,13 +409,16 @@ cfm_compose_ccm(struct cfm *cfm, struct ofpbuf *packet, ccm->flags = cfm->ccm_interval; memcpy(ccm->maid, cfm->maid, sizeof ccm->maid); memset(ccm->zero, 0, sizeof ccm->zero); + ccm->end_tlv = 0; if (cfm->extended) { ccm->mpid = htons(hash_mpid(cfm->mpid)); ccm->mpid64 = htonll(cfm->mpid); + ccm->opdown = !cfm->opup; } else { ccm->mpid = htons(cfm->mpid); ccm->mpid64 = htonll(0); + ccm->opdown = 0; } if (cfm->ccm_interval == 0) { @@ -344,7 +434,6 @@ cfm_compose_ccm(struct cfm *cfm, struct ofpbuf *packet, void cfm_wait(struct cfm *cfm) { - timer_wait(&cfm->tx_timer); timer_wait(&cfm->fault_timer); } @@ -362,9 +451,12 @@ cfm_configure(struct cfm *cfm, const struct cfm_settings *s) cfm->mpid = s->mpid; cfm->extended = s->extended; + cfm->opup = s->opup; interval = ms_to_ccm_interval(s->interval); interval_ms = ccm_interval_to_ms(interval); + cfm->ccm_vlan = s->ccm_vlan & VLAN_VID_MASK; + cfm->ccm_pcp = s->ccm_pcp & (VLAN_PCP_MASK >> VLAN_PCP_SHIFT); if (cfm->extended && interval_ms != s->interval) { interval = 0; interval_ms = MIN(s->interval, UINT16_MAX); @@ -399,7 +491,7 @@ cfm_process_heartbeat(struct cfm *cfm, const struct ofpbuf *p) struct eth_header *eth; eth = p->l2; - ccm = ofpbuf_at(p, (uint8_t *)p->l3 - (uint8_t *)p->data, CCM_LEN); + ccm = ofpbuf_at(p, (uint8_t *)p->l3 - (uint8_t *)p->data, CCM_ACCEPT_LEN); if (!ccm) { VLOG_INFO_RL(&rl, "%s: Received an unparseable 802.1ag CCM heartbeat.", @@ -423,7 +515,7 @@ cfm_process_heartbeat(struct cfm *cfm, const struct ofpbuf *p) * bonds. Furthermore, faults can be maliciously triggered by crafting * invalid CCMs. */ if (memcmp(ccm->maid, cfm->maid, sizeof ccm->maid)) { - cfm->unexpected_recv = true; + cfm->recv_fault |= CFM_FAULT_MAID; VLOG_WARN_RL(&rl, "%s: Received unexpected remote MAID from MAC " ETH_ADDR_FMT, cfm->name, ETH_ADDR_ARGS(eth->eth_src)); } else { @@ -433,8 +525,17 @@ cfm_process_heartbeat(struct cfm *cfm, const struct ofpbuf *p) struct remote_mp *rmp; uint64_t ccm_mpid; + uint32_t ccm_seq; + bool ccm_opdown; - ccm_mpid = cfm->extended ? ntohll(ccm->mpid64) : ntohs(ccm->mpid); + if (cfm->extended) { + ccm_mpid = ntohll(ccm->mpid64); + ccm_opdown = ccm->opdown; + } else { + ccm_mpid = ntohs(ccm->mpid); + ccm_opdown = false; + } + ccm_seq = ntohl(ccm->seq); if (ccm_interval != cfm->ccm_interval) { VLOG_WARN_RL(&rl, "%s: received a CCM with an invalid interval" @@ -450,36 +551,73 @@ cfm_process_heartbeat(struct cfm *cfm, const struct ofpbuf *p) } rmp = lookup_remote_mp(cfm, ccm_mpid); + if (!rmp) { + if (hmap_count(&cfm->remote_mps) < CFM_MAX_RMPS) { + rmp = xzalloc(sizeof *rmp); + hmap_insert(&cfm->remote_mps, &rmp->node, hash_mpid(ccm_mpid)); + } else { + cfm->recv_fault |= CFM_FAULT_OVERFLOW; + VLOG_WARN_RL(&rl, + "%s: dropped CCM with MPID %"PRIu64" from MAC " + ETH_ADDR_FMT, cfm->name, ccm_mpid, + ETH_ADDR_ARGS(eth->eth_src)); + } + } + + VLOG_DBG("%s: received CCM (seq %"PRIu32") (mpid %"PRIu64")" + " (interval %"PRIu8") (RDI %s)", cfm->name, ccm_seq, + ccm_mpid, ccm_interval, ccm_rdi ? "true" : "false"); + if (rmp) { - rmp->recv = true; - rmp->rdi = ccm_rdi; - } else if (hmap_count(&cfm->remote_mps) < CFM_MAX_RMPS) { - rmp = xmalloc(sizeof *rmp); + if (rmp->seq && ccm_seq != (rmp->seq + 1)) { + VLOG_WARN_RL(&rl, "%s: (mpid %"PRIu64") detected sequence" + " numbers which indicate possible connectivity" + " problems (previous %"PRIu32") (current %"PRIu32 + ")", cfm->name, ccm_mpid, rmp->seq, ccm_seq); + } + rmp->mpid = ccm_mpid; rmp->recv = true; + rmp->seq = ccm_seq; rmp->rdi = ccm_rdi; - hmap_insert(&cfm->remote_mps, &rmp->node, hash_mpid(rmp->mpid)); - } else { - cfm->unexpected_recv = true; - VLOG_WARN_RL(&rl, "%s: dropped CCM with MPID %"PRIu64" from MAC " - ETH_ADDR_FMT, cfm->name, ccm_mpid, - ETH_ADDR_ARGS(eth->eth_src)); + rmp->opup = !ccm_opdown; } - - VLOG_DBG("%s: received CCM (seq %"PRIu32") (mpid %"PRIu64")" - " (interval %"PRIu8") (RDI %s)", cfm->name, ntohl(ccm->seq), - ccm_mpid, ccm_interval, ccm_rdi ? "true" : "false"); } } -/* Gets the fault status of 'cfm'. Returns true when 'cfm' has detected - * connectivity problems, false otherwise. */ -bool +/* Gets the fault status of 'cfm'. Returns a bit mask of 'cfm_fault_reason's + * indicating the cause of the connectivity fault, or zero if there is no + * fault. */ +int cfm_get_fault(const struct cfm *cfm) { + if (cfm->fault_override >= 0) { + return cfm->fault_override ? CFM_FAULT_OVERRIDE : 0; + } return cfm->fault; } +/* Gets the operational state of 'cfm'. 'cfm' is considered operationally down + * if it has received a CCM with the operationally down bit set from any of its + * remote maintenance points. Returns true if 'cfm' is operationally up. False + * otherwise. */ +bool +cfm_get_opup(const struct cfm *cfm) +{ + return cfm->remote_opup; +} + +/* Populates 'rmps' with an array of remote maintenance points reachable by + * 'cfm'. The number of remote maintenance points is written to 'n_rmps'. + * 'cfm' retains ownership of the array written to 'rmps' */ +void +cfm_get_remote_mpids(const struct cfm *cfm, const uint64_t **rmps, + size_t *n_rmps) +{ + *rmps = cfm->rmps_array; + *n_rmps = cfm->rmps_array_len; +} + static struct cfm * cfm_find(const char *name) { @@ -494,38 +632,96 @@ cfm_find(const char *name) } static void -cfm_unixctl_show(struct unixctl_conn *conn, - const char *args, void *aux OVS_UNUSED) +cfm_print_details(struct ds *ds, const struct cfm *cfm) { - struct ds ds = DS_EMPTY_INITIALIZER; - const struct cfm *cfm; struct remote_mp *rmp; - cfm = cfm_find(args); - if (!cfm) { - unixctl_command_reply(conn, 501, "no such CFM object"); - return; - } + ds_put_format(ds, "---- %s ----\n", cfm->name); + ds_put_format(ds, "MPID %"PRIu64":%s%s\n", cfm->mpid, + cfm->extended ? " extended" : "", + cfm->fault_override >= 0 ? " fault_override" : ""); - ds_put_format(&ds, "MPID %"PRIu64":%s%s\n", cfm->mpid, - cfm->fault ? " fault" : "", - cfm->unexpected_recv ? " unexpected_recv" : ""); - ds_put_format(&ds, "\tinterval: %dms\n", cfm->ccm_interval_ms); - ds_put_format(&ds, "\tnext CCM tx: %lldms\n", + if (cfm_get_fault(cfm)) { + ds_put_cstr(ds, "\tfault: "); + ds_put_cfm_fault(ds, cfm_get_fault(cfm)); + ds_put_cstr(ds, "\n"); + } + + ds_put_format(ds, "\topstate: %s\n", cfm->opup ? "up" : "down"); + ds_put_format(ds, "\tremote_opstate: %s\n", + cfm->remote_opup ? "up" : "down"); + ds_put_format(ds, "\tinterval: %dms\n", cfm->ccm_interval_ms); + ds_put_format(ds, "\tnext CCM tx: %lldms\n", timer_msecs_until_expired(&cfm->tx_timer)); - ds_put_format(&ds, "\tnext fault check: %lldms\n", + ds_put_format(ds, "\tnext fault check: %lldms\n", timer_msecs_until_expired(&cfm->fault_timer)); - ds_put_cstr(&ds, "\n"); HMAP_FOR_EACH (rmp, node, &cfm->remote_mps) { - ds_put_format(&ds, "Remote MPID %"PRIu64":%s\n", + ds_put_format(ds, "Remote MPID %"PRIu64":%s\n", rmp->mpid, rmp->rdi ? " rdi" : ""); - ds_put_format(&ds, "\trecv since check: %s", + ds_put_format(ds, "\trecv since check: %s\n", rmp->recv ? "true" : "false"); + ds_put_format(ds, "\topstate: %s\n", rmp->opup? "up" : "down"); + } +} + +static void +cfm_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[], + void *aux OVS_UNUSED) +{ + struct ds ds = DS_EMPTY_INITIALIZER; + const struct cfm *cfm; + + if (argc > 1) { + cfm = cfm_find(argv[1]); + if (!cfm) { + unixctl_command_reply_error(conn, "no such CFM object"); + return; + } + cfm_print_details(&ds, cfm); + } else { + HMAP_FOR_EACH (cfm, hmap_node, &all_cfms) { + cfm_print_details(&ds, cfm); + } } - unixctl_command_reply(conn, 200, ds_cstr(&ds)); + unixctl_command_reply(conn, ds_cstr(&ds)); ds_destroy(&ds); } + +static void +cfm_unixctl_set_fault(struct unixctl_conn *conn, int argc, const char *argv[], + void *aux OVS_UNUSED) +{ + const char *fault_str = argv[argc - 1]; + int fault_override; + struct cfm *cfm; + + if (!strcasecmp("true", fault_str)) { + fault_override = 1; + } else if (!strcasecmp("false", fault_str)) { + fault_override = 0; + } else if (!strcasecmp("normal", fault_str)) { + fault_override = -1; + } else { + unixctl_command_reply_error(conn, "unknown fault string"); + return; + } + + if (argc > 2) { + cfm = cfm_find(argv[1]); + if (!cfm) { + unixctl_command_reply_error(conn, "no such CFM object"); + return; + } + cfm->fault_override = fault_override; + } else { + HMAP_FOR_EACH (cfm, hmap_node, &all_cfms) { + cfm->fault_override = fault_override; + } + } + + unixctl_command_reply(conn, "OK"); +}