dhcp: New function dhcp_option_equals().
[sliver-openvswitch.git] / lib / rconn.c
index bb7d3fe..9bb2b5c 100644 (file)
@@ -31,6 +31,7 @@
  * derivatives without specific, written prior permission.
  */
 
+#include <config.h>
 #include "rconn.h"
 #include <assert.h>
 #include <errno.h>
@@ -76,7 +77,6 @@ state_name(enum state state)
 struct rconn {
     enum state state;
     time_t state_entered;
-    unsigned int min_timeout;
 
     struct vconn *vconn;
     char *name;
@@ -93,6 +93,16 @@ struct rconn {
 
     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
@@ -103,11 +113,13 @@ struct rconn {
 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 *
@@ -150,7 +162,6 @@ rconn_create(int txq_limit, int probe_interval, int max_backoff)
 
     rc->state = S_VOID;
     rc->state_entered = time(0);
-    rc->min_timeout = 0;
 
     rc->vconn = NULL;
     rc->name = xstrdup("void");
@@ -168,19 +179,22 @@ rconn_create(int txq_limit, int probe_interval, int max_backoff)
 
     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
@@ -226,13 +240,19 @@ rconn_destroy(struct rconn *rc)
     }
 }
 
+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;
@@ -246,32 +266,46 @@ reconnect(struct rconn *rc)
         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);
@@ -289,28 +323,43 @@ do_tx_work(struct rconn *rc)
     }
 }
 
-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));
@@ -329,7 +378,6 @@ rconn_run(struct rconn *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
@@ -345,14 +393,10 @@ rconn_run(struct rconn *rc)
 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);
@@ -463,7 +507,7 @@ rconn_get_name(const struct rconn *rc)
 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. */
@@ -478,7 +522,7 @@ rconn_is_connected(const struct rconn *rconn)
 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
@@ -488,6 +532,22 @@ rconn_get_ip(const struct rconn *rconn)
 {
     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. */
@@ -542,6 +602,9 @@ disconnect(struct rconn *rc, int error)
         }
         rc->backoff_deadline = now + rc->backoff;
         state_transition(rc, S_BACKOFF);
+        if (now - rc->last_connected > 60) {
+            question_connectivity(rc);
+        }
     } else {
         rconn_disconnect(rc);
     }
@@ -553,11 +616,22 @@ elapsed_in_this_state(const struct rconn *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
@@ -580,3 +654,13 @@ sat_mul(unsigned int x, unsigned int y)
     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;
+    }
+}