* derivatives without specific, written prior permission.
*/
+#include <config.h>
#include "rconn.h"
#include <assert.h>
#include <errno.h>
struct rconn {
enum state state;
time_t state_entered;
- unsigned int min_timeout;
struct vconn *vconn;
char *name;
unsigned int packets_sent;
+ /* If we can't connect to the peer, it could be for any number of reasons.
+ * Usually, one would assume it is because the peer is not running or
+ * because the network is partitioned. But it could also be because the
+ * network topology has changed, in which case the upper layer will need to
+ * reassess it (in particular, obtain a new IP address via DHCP and find
+ * the new location of the controller). We set this flag when we suspect
+ * that this could be the case. */
+ bool questionable_connectivity;
+ time_t last_questioned;
+
/* Throughout this file, "probe" is shorthand for "inactivity probe".
* When nothing has been received from the peer for a while, we send out
* an echo request as an inactivity probe packet. We should receive back
static unsigned int sat_add(unsigned int x, unsigned int y);
static unsigned int sat_mul(unsigned int x, unsigned int y);
static unsigned int elapsed_in_this_state(const struct rconn *);
-static bool timeout(struct rconn *, unsigned int secs);
+static unsigned int timeout(const struct rconn *);
+static bool timed_out(const struct rconn *);
static void state_transition(struct rconn *, enum state);
static int try_send(struct rconn *);
-static void reconnect(struct rconn *);
+static int reconnect(struct rconn *);
static void disconnect(struct rconn *, int error);
+static void question_connectivity(struct rconn *);
/* Creates a new rconn, connects it (reliably) to 'name', and returns it. */
struct rconn *
rc->state = S_VOID;
rc->state_entered = time(0);
- rc->min_timeout = 0;
rc->vconn = NULL;
rc->name = xstrdup("void");
rc->packets_sent = 0;
+ rc->questionable_connectivity = false;
+ rc->last_questioned = time(0);
+
rc->probe_interval = probe_interval ? MAX(5, probe_interval) : 0;
return rc;
}
-void
+int
rconn_connect(struct rconn *rc, const char *name)
{
rconn_disconnect(rc);
free(rc->name);
rc->name = xstrdup(name);
rc->reliable = true;
- reconnect(rc);
+ return reconnect(rc);
}
void
}
}
+static unsigned int
+timeout_VOID(const struct rconn *rc)
+{
+ return UINT_MAX;
+}
+
static void
run_VOID(struct rconn *rc)
{
/* Nothing to do. */
}
-static void
+static int
reconnect(struct rconn *rc)
{
int retval;
VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(retval));
disconnect(rc, 0);
}
+ return retval;
+}
+
+static unsigned int
+timeout_BACKOFF(const struct rconn *rc)
+{
+ return rc->backoff;
}
static void
run_BACKOFF(struct rconn *rc)
{
- if (timeout(rc, rc->backoff)) {
+ if (timed_out(rc)) {
reconnect(rc);
}
}
+static unsigned int
+timeout_CONNECTING(const struct rconn *rc)
+{
+ return MAX(1, rc->backoff);
+}
+
static void
run_CONNECTING(struct rconn *rc)
{
- int error = vconn_connect(rc->vconn);
- if (!error) {
+ int retval = vconn_connect(rc->vconn);
+ if (!retval) {
VLOG_WARN("%s: connected", rc->name);
if (vconn_is_passive(rc->vconn)) {
- fatal(0, "%s: passive vconn not supported in switch",
- rc->name);
+ error(0, "%s: passive vconn not supported", rc->name);
+ state_transition(rc, S_VOID);
+ } else {
+ state_transition(rc, S_ACTIVE);
+ rc->last_connected = rc->state_entered;
}
- state_transition(rc, S_ACTIVE);
- rc->last_connected = rc->state_entered;
- } else if (error != EAGAIN) {
- VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(error));
- disconnect(rc, error);
- } else if (timeout(rc, MAX(1, rc->backoff))) {
+ } else if (retval != EAGAIN) {
+ VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(retval));
+ disconnect(rc, retval);
+ } else if (timed_out(rc)) {
VLOG_WARN("%s: connection timed out", rc->name);
rc->backoff_deadline = TIME_MAX; /* Prevent resetting backoff. */
disconnect(rc, 0);
}
}
-static void
-run_ACTIVE(struct rconn *rc)
+static unsigned int
+timeout_ACTIVE(const struct rconn *rc)
{
if (rc->probe_interval) {
unsigned int base = MAX(rc->last_received, rc->state_entered);
unsigned int arg = base + rc->probe_interval - rc->state_entered;
- if (timeout(rc, arg)) {
- queue_push_tail(&rc->txq, make_echo_request());
- VLOG_DBG("%s: idle %u seconds, sending inactivity probe",
- rc->name, (unsigned int) (time(0) - base));
- state_transition(rc, S_IDLE);
- return;
- }
+ return arg;
+ }
+ return UINT_MAX;
+}
+
+static void
+run_ACTIVE(struct rconn *rc)
+{
+ if (timed_out(rc)) {
+ unsigned int base = MAX(rc->last_received, rc->state_entered);
+ queue_push_tail(&rc->txq, make_echo_request());
+ VLOG_DBG("%s: idle %u seconds, sending inactivity probe",
+ rc->name, (unsigned int) (time(0) - base));
+ state_transition(rc, S_IDLE);
+ return;
}
do_tx_work(rc);
}
+static unsigned int
+timeout_IDLE(const struct rconn *rc)
+{
+ return rc->probe_interval;
+}
+
static void
run_IDLE(struct rconn *rc)
{
- if (timeout(rc, rc->probe_interval)) {
+ if (timed_out(rc)) {
+ question_connectivity(rc);
VLOG_ERR("%s: no response to inactivity probe after %u "
"seconds, disconnecting",
rc->name, elapsed_in_this_state(rc));
int old_state;
do {
old_state = rc->state;
- rc->min_timeout = UINT_MAX;
switch (rc->state) {
#define STATE(NAME, VALUE) case S_##NAME: run_##NAME(rc); break;
STATES
void
rconn_run_wait(struct rconn *rc)
{
- if (rc->min_timeout != UINT_MAX) {
- poll_timer_wait(sat_mul(rc->min_timeout, 1000));
+ unsigned int timeo = timeout(rc);
+ if (timeo != UINT_MAX) {
+ poll_timer_wait(sat_mul(timeo, 1000));
}
- /* Reset timeout to 1 second. This will have no effect ordinarily, because
- * rconn_run() will typically set it back to a higher value. If, however,
- * the caller fails to call rconn_run() before its next call to
- * rconn_wait() we won't potentially block forever. */
- rc->min_timeout = 1;
if ((rc->state & (S_ACTIVE | S_IDLE)) && rc->txq.n) {
vconn_wait(rc->vconn, WAIT_SEND);
bool
rconn_is_alive(const struct rconn *rconn)
{
- return rconn->reliable || rconn->vconn;
+ return rconn->state != S_VOID;
}
/* Returns true if 'rconn' is connected, false otherwise. */
int
rconn_disconnected_duration(const struct rconn *rconn)
{
- return rconn_is_connected(rconn) ? 0 : time(0) - rconn->last_connected;
+ return rconn_is_connected(rconn) ? 0 : time(0) - rconn->last_received;
}
/* Returns the IP address of the peer, or 0 if the peer is not connected over
{
return rconn->vconn ? vconn_get_ip(rconn->vconn) : 0;
}
+
+/* If 'rconn' can't connect to the peer, it could be for any number of reasons.
+ * Usually, one would assume it is because the peer is not running or because
+ * the network is partitioned. But it could also be because the network
+ * topology has changed, in which case the upper layer will need to reassess it
+ * (in particular, obtain a new IP address via DHCP and find the new location
+ * of the controller). When this appears that this might be the case, this
+ * function returns true. It also clears the questionability flag and prevents
+ * it from being set again for some time. */
+bool
+rconn_is_connectivity_questionable(struct rconn *rconn)
+{
+ bool questionable = rconn->questionable_connectivity;
+ rconn->questionable_connectivity = false;
+ return questionable;
+}
\f
/* Tries to send a packet from 'rc''s send buffer. Returns 0 if successful,
* otherwise a positive errno value. */
}
rc->backoff_deadline = now + rc->backoff;
state_transition(rc, S_BACKOFF);
+ if (now - rc->last_connected > 60) {
+ question_connectivity(rc);
+ }
} else {
rconn_disconnect(rc);
}
return time(0) - rc->state_entered;
}
+static unsigned int
+timeout(const struct rconn *rc)
+{
+ switch (rc->state) {
+#define STATE(NAME, VALUE) case S_##NAME: return timeout_##NAME(rc);
+ STATES
+#undef STATE
+ default:
+ NOT_REACHED();
+ }
+}
+
static bool
-timeout(struct rconn *rc, unsigned int secs)
+timed_out(const struct rconn *rc)
{
- rc->min_timeout = MIN(rc->min_timeout, secs);
- return time(0) >= sat_add(rc->state_entered, secs);
+ return time(0) >= sat_add(rc->state_entered, timeout(rc));
}
static void
assert(y);
return x <= UINT_MAX / y ? x * y : UINT_MAX;
}
+
+static void
+question_connectivity(struct rconn *rc)
+{
+ time_t now = time(0);
+ if (now - rc->last_questioned > 60) {
+ rc->questionable_connectivity = true;
+ rc->last_questioned = now;
+ }
+}