#include <config.h>
#include "cfm.h"
-#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "flow.h"
#include "hash.h"
#include "hmap.h"
+#include "netdev.h"
#include "ofpbuf.h"
#include "packets.h"
#include "poll-loop.h"
#define CCM_RDI_MASK 0x80
#define CFM_HEALTH_INTERVAL 6
struct ccm {
- uint8_t mdlevel_version; /* MD Level and Version */
- uint8_t opcode;
- uint8_t flags;
- uint8_t tlv_offset;
+ uint8_t mdlevel_version; /* MD Level and Version */
+ uint8_t opcode;
+ uint8_t flags;
+ uint8_t tlv_offset;
ovs_be32 seq;
ovs_be16 mpid;
- uint8_t maid[CCM_MAID_LEN];
+ uint8_t maid[CCM_MAID_LEN];
/* 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 opdown; /* Operationally down. */
- uint8_t zero[5];
+ uint8_t zero[5];
/* TLV space. */
uint8_t end_tlv;
BUILD_ASSERT_DECL(CCM_LEN == sizeof(struct ccm));
struct cfm {
- char *name; /* Name of this CFM object. */
+ const char *name; /* Name of this CFM object. */
struct hmap_node hmap_node; /* Node in all_cfms list. */
+ struct netdev *netdev;
+ uint64_t rx_packets; /* Packets received by 'netdev'. */
+
uint64_t mpid;
+ bool check_tnl_key; /* Verify the tunnel key of inbound packets? */
bool extended; /* Extended mode. */
+ bool demand; /* Demand mode. */
+ bool booted; /* A full fault interval has occurred. */
enum cfm_fault_reason fault; /* Connectivity fault status. */
- enum cfm_fault_reason recv_fault; /* Bit mask of faults occuring on
+ enum cfm_fault_reason recv_fault; /* Bit mask of faults occurring on
receive. */
bool opup; /* Operational State. */
bool remote_opup; /* Remote Operational State. */
received. */
int health_interval; /* Number of fault_intervals since health was
recomputed. */
-
+ long long int last_tx; /* Last CCM transmission time. */
};
/* Remote MPs represent foreign network entities that are configured to have
uint32_t seq; /* Most recently received sequence number. */
uint8_t num_health_ccm; /* Number of received ccm frames every
CFM_HEALTH_INTERVAL * 'fault_interval'. */
+ long long int last_rx; /* Last CCM reception time. */
};
static unixctl_cb_func cfm_unixctl_show;
static unixctl_cb_func cfm_unixctl_set_fault;
+static uint64_t
+cfm_rx_packets(const struct cfm *cfm)
+{
+ struct netdev_stats stats;
+
+ if (!netdev_get_stats(cfm->netdev, &stats)) {
+ return stats.rx_packets;
+ } else {
+ return 0;
+ }
+}
+
static const uint8_t *
cfm_ccm_addr(const struct cfm *cfm)
{
}
static void
-ds_put_cfm_fault(struct ds *ds, int old_fault, int new_fault)
+ds_put_cfm_fault(struct ds *ds, int fault)
{
int i;
for (i = 0; i < CFM_FAULT_N_REASONS; i++) {
int reason = 1 << i;
- if ((old_fault | new_fault) & reason) {
- ds_put_format(ds, " %s%s",
- (!(old_fault & reason) ? "+"
- : !(new_fault & reason) ? "-"
- : ""),
- cfm_fault_reason_to_str(reason));
+ if (fault & reason) {
+ ds_put_format(ds, "%s ", cfm_fault_reason_to_str(reason));
}
}
+
+ ds_chomp(ds, ' ');
}
static void
md_len = strlen(ovs_md_name);
ma_len = strlen(ovs_ma_name);
- assert(md_len && ma_len && md_len + ma_len + 4 <= CCM_MAID_LEN);
+ ovs_assert(md_len && ma_len && md_len + ma_len + 4 <= CCM_MAID_LEN);
cfm->maid[0] = 4; /* MD name string format. */
cfm->maid[1] = md_len; /* MD name size. */
/* Allocates a 'cfm' object called 'name'. 'cfm' should be initialized by
* cfm_configure() before use. */
struct cfm *
-cfm_create(const char *name)
+cfm_create(const struct netdev *netdev)
{
struct cfm *cfm;
cfm = xzalloc(sizeof *cfm);
- cfm->name = xstrdup(name);
+ cfm->netdev = netdev_ref(netdev);
+ cfm->name = netdev_get_name(cfm->netdev);
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;
cfm->health = -1;
+ cfm->last_tx = 0;
return cfm;
}
hmap_destroy(&cfm->remote_mps);
hmap_remove(&all_cfms, &cfm->hmap_node);
+ netdev_close(cfm->netdev);
free(cfm->rmps_array);
- free(cfm->name);
free(cfm);
}
long long int interval = cfm_fault_interval(cfm);
struct remote_mp *rmp, *rmp_next;
bool old_cfm_fault = cfm->fault;
+ bool demand_override;
cfm->fault = cfm->recv_fault;
cfm->recv_fault = 0;
cfm->health = (rmp->num_health_ccm * 100) / exp_ccm_recvd;
cfm->health = MIN(cfm->health, 100);
rmp->num_health_ccm = 0;
- assert(cfm->health >= 0 && cfm->health <= 100);
+ ovs_assert(cfm->health >= 0 && cfm->health <= 100);
}
cfm->health_interval = 0;
}
cfm->health_interval++;
- HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->remote_mps) {
+ demand_override = false;
+ if (cfm->demand) {
+ uint64_t rx_packets = cfm_rx_packets(cfm);
+ demand_override = hmap_count(&cfm->remote_mps) == 1
+ && rx_packets > cfm->rx_packets;
+ cfm->rx_packets = rx_packets;
+ }
+ HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->remote_mps) {
if (!rmp->recv) {
- VLOG_DBG("%s: no CCM from RMP %"PRIu64" in the last %lldms",
- cfm->name, rmp->mpid, interval);
- hmap_remove(&cfm->remote_mps, &rmp->node);
- free(rmp);
+ VLOG_INFO("%s: Received no CCM from RMP %"PRIu64" in the last"
+ " %lldms", cfm->name, rmp->mpid,
+ time_msec() - rmp->last_rx);
+ if (!demand_override) {
+ hmap_remove(&cfm->remote_mps, &rmp->node);
+ free(rmp);
+ }
} else {
rmp->recv = false;
if (old_cfm_fault != cfm->fault && !VLOG_DROP_INFO(&rl)) {
struct ds ds = DS_EMPTY_INITIALIZER;
- ds_put_cfm_fault(&ds, old_cfm_fault, cfm->fault);
- VLOG_INFO("%s: CFM fault status changed:%s", cfm->name,
- ds_cstr_ro(&ds));
+ ds_put_cstr(&ds, "from [");
+ ds_put_cfm_fault(&ds, old_cfm_fault);
+ ds_put_cstr(&ds, "] to [");
+ ds_put_cfm_fault(&ds, cfm->fault);
+ ds_put_char(&ds, ']');
+ VLOG_INFO("%s: CFM faults changed %s.", cfm->name, ds_cstr(&ds));
ds_destroy(&ds);
}
+ cfm->booted = true;
timer_set_duration(&cfm->fault_timer, interval);
+ VLOG_DBG("%s: new fault interval", cfm->name);
}
}
}
if (cfm->ccm_interval == 0) {
- assert(cfm->extended);
+ ovs_assert(cfm->extended);
ccm->interval_ms_x = htons(cfm->ccm_interval_ms);
+ } else {
+ ccm->interval_ms_x = htons(0);
}
- if (hmap_is_empty(&cfm->remote_mps)) {
+ if (cfm->booted && hmap_is_empty(&cfm->remote_mps)) {
ccm->flags |= CCM_RDI_MASK;
}
+
+ if (cfm->last_tx) {
+ long long int delay = time_msec() - cfm->last_tx;
+ if (delay > (cfm->ccm_interval_ms * 3 / 2)) {
+ VLOG_WARN("%s: long delay of %lldms (expected %dms) sending CCM"
+ " seq %"PRIu32, cfm->name, delay, cfm->ccm_interval_ms,
+ cfm->seq);
+ }
+ }
+ cfm->last_tx = time_msec();
}
void
}
cfm->mpid = s->mpid;
+ cfm->check_tnl_key = s->check_tnl_key;
cfm->extended = s->extended;
cfm->opup = s->opup;
interval = ms_to_ccm_interval(s->interval);
interval_ms = MIN(s->interval, UINT16_MAX);
}
+ if (cfm->extended && s->demand) {
+ interval_ms = MAX(interval_ms, 500);
+ if (!cfm->demand) {
+ cfm->demand = true;
+ cfm->rx_packets = cfm_rx_packets(cfm);
+ }
+ } else {
+ cfm->demand = false;
+ }
+
if (interval != cfm->ccm_interval || interval_ms != cfm->ccm_interval_ms) {
cfm->ccm_interval = interval;
cfm->ccm_interval_ms = interval_ms;
return true;
}
+/* Must be called when the netdev owned by 'cfm' should change. */
+void
+cfm_set_netdev(struct cfm *cfm, const struct netdev *netdev)
+{
+ if (cfm->netdev != netdev) {
+ netdev_close(cfm->netdev);
+ cfm->netdev = netdev_ref(netdev);
+ }
+}
+
/* Returns true if 'cfm' should process packets from 'flow'. */
bool
cfm_should_process_flow(const struct cfm *cfm, const struct flow *flow)
{
return (ntohs(flow->dl_type) == ETH_TYPE_CFM
- && eth_addr_equals(flow->dl_dst, cfm_ccm_addr(cfm)));
+ && eth_addr_equals(flow->dl_dst, cfm_ccm_addr(cfm))
+ && (!cfm->check_tnl_key || flow->tunnel.tun_id == htonll(0)));
}
/* Updates internal statistics relevant to packet 'p'. Should be called on
}
if (rmp->seq && ccm_seq != (rmp->seq + 1)) {
- cfm_fault |= CFM_FAULT_SEQUENCE;
VLOG_WARN_RL(&rl, "%s: (mpid %"PRIu64") detected sequence"
" numbers which indicate possible connectivity"
" problems (previous %"PRIu32") (current %"PRIu32
cfm->recv_fault |= cfm_fault;
rmp->seq = ccm_seq;
rmp->opup = !ccm_opdown;
+ rmp->last_rx = time_msec();
}
}
}
/* 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
+ * remote maintenance points. Returns 1 if 'cfm' is operationally up, 0 if
+ * 'cfm' is operationally down, or -1 if 'cfm' has no operational state
+ * (because it isn't in extended mode). */
+int
cfm_get_opup(const struct cfm *cfm)
{
- return cfm->remote_opup;
+ if (cfm->extended) {
+ return cfm->remote_opup;
+ } else {
+ return -1;
+ }
}
/* Populates 'rmps' with an array of remote maintenance points reachable by
fault = cfm_get_fault(cfm);
if (fault) {
- ds_put_cstr(ds, "\tfault:");
- ds_put_cfm_fault(ds, fault, fault);
+ ds_put_cstr(ds, "\tfault: ");
+ ds_put_cfm_fault(ds, fault);
ds_put_cstr(ds, "\n");
}