treewide: Remove trailing whitespace
[sliver-openvswitch.git] / lib / rconn.c
index 09c0e9d..06f3626 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 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.
@@ -22,6 +22,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include "coverage.h"
+#include "ofp-util.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "poll-loop.h"
 #include "timeval.h"
 #include "util.h"
 #include "vconn.h"
-
-#define THIS_MODULE VLM_rconn
 #include "vlog.h"
 
+VLOG_DEFINE_THIS_MODULE(rconn)
+
 #define STATES                                  \
     STATE(VOID, 1 << 0)                         \
     STATE(BACKOFF, 1 << 1)                      \
@@ -64,7 +65,8 @@ struct rconn {
     time_t state_entered;
 
     struct vconn *vconn;
-    char *name;
+    char *name;                 /* Human-readable descriptive name. */
+    char *target;               /* vconn name, passed to vconn_open(). */
     bool reliable;
 
     struct ovs_queue txq;
@@ -76,6 +78,7 @@ struct rconn {
     time_t last_connected;
     unsigned int packets_sent;
     unsigned int seqno;
+    int last_error;
 
     /* In S_ACTIVE and S_IDLE, probably_admitted reports whether we believe
      * that the peer has made a (positive) admission control decision on our
@@ -89,7 +92,7 @@ struct rconn {
     time_t last_admitted;
 
     /* These values are simply for statistics reporting, not used directly by
-     * anything internal to the rconn (or the secchan for that matter). */
+     * anything internal to the rconn (or ofproto for that matter). */
     unsigned int packets_received;
     unsigned int n_attempted_connections, n_successful_connections;
     time_t creation_time;
@@ -111,6 +114,18 @@ struct rconn {
      * a response. */
     int probe_interval;         /* Secs of inactivity before sending probe. */
 
+    /* When we create a vconn we obtain these values, to save them past the end
+     * of the vconn's lifetime.  Otherwise, in-band control will only allow
+     * traffic when a vconn is actually open, but it is nice to allow ARP to
+     * complete even between connection attempts, and it is also polite to
+     * allow traffic from other switches to go through to the controller
+     * whether or not we are connected.
+     *
+     * We don't cache the local port, because that changes from one connection
+     * attempt to the next. */
+    uint32_t local_ip, remote_ip;
+    uint16_t remote_port;
+
     /* Messages sent or received are copied to the monitor connections. */
 #define MAX_MONITORS 8
     struct vconn *monitors[8];
@@ -121,32 +136,18 @@ static unsigned int elapsed_in_this_state(const struct rconn *);
 static unsigned int timeout(const struct rconn *);
 static bool timed_out(const struct rconn *);
 static void state_transition(struct rconn *, enum state);
+static void rconn_set_target__(struct rconn *,
+                               const char *target, const char *name);
 static int try_send(struct rconn *);
-static int reconnect(struct rconn *);
+static void reconnect(struct rconn *);
+static void report_error(struct rconn *, int error);
 static void disconnect(struct rconn *, int error);
 static void flush_queue(struct rconn *);
 static void question_connectivity(struct rconn *);
 static void copy_to_monitor(struct rconn *, const struct ofpbuf *);
 static bool is_connected_state(enum state);
 static bool is_admitted_msg(const struct ofpbuf *);
-
-/* Creates a new rconn, connects it (reliably) to 'name', and returns it. */
-struct rconn *
-rconn_new(const char *name, int inactivity_probe_interval, int max_backoff)
-{
-    struct rconn *rc = rconn_create(inactivity_probe_interval, max_backoff);
-    rconn_connect(rc, name);
-    return rc;
-}
-
-/* Creates a new rconn, connects it (unreliably) to 'vconn', and returns it. */
-struct rconn *
-rconn_new_from_vconn(const char *name, struct vconn *vconn) 
-{
-    struct rconn *rc = rconn_create(60, 0);
-    rconn_connect_unreliably(rc, name, vconn);
-    return rc;
-}
+static bool rconn_logging_connection_attempts__(const struct rconn *);
 
 /* Creates and returns a new rconn.
  *
@@ -159,17 +160,21 @@ rconn_new_from_vconn(const char *name, struct vconn *vconn)
  * 'max_backoff' is the maximum number of seconds between attempts to connect
  * to the peer.  The actual interval starts at 1 second and doubles on each
  * failure until it reaches 'max_backoff'.  If 0 is specified, the default of
- * 8 seconds is used. */
+ * 8 seconds is used.
+ *
+ * The new rconn is initially unconnected.  Use rconn_connect() or
+ * rconn_connect_unreliably() to connect it. */
 struct rconn *
 rconn_create(int probe_interval, int max_backoff)
 {
-    struct rconn *rc = xcalloc(1, sizeof *rc);
+    struct rconn *rc = xzalloc(sizeof *rc);
 
     rc->state = S_VOID;
     rc->state_entered = time_now();
 
     rc->vconn = NULL;
     rc->name = xstrdup("void");
+    rc->target = xstrdup("void");
     rc->reliable = false;
 
     queue_init(&rc->txq);
@@ -232,24 +237,37 @@ rconn_get_probe_interval(const struct rconn *rc)
     return rc->probe_interval;
 }
 
-int
-rconn_connect(struct rconn *rc, const char *name)
+/* Drops any existing connection on 'rc', then sets up 'rc' to connect to
+ * 'target' and reconnect as needed.  'target' should be a remote OpenFlow
+ * target in a form acceptable to vconn_open().
+ *
+ * If 'name' is nonnull, then it is used in log messages in place of 'target'.
+ * It should presumably give more information to a human reader than 'target',
+ * but it need not be acceptable to vconn_open(). */
+void
+rconn_connect(struct rconn *rc, const char *target, const char *name)
 {
     rconn_disconnect(rc);
-    free(rc->name);
-    rc->name = xstrdup(name);
+    rconn_set_target__(rc, target, name);
     rc->reliable = true;
-    return reconnect(rc);
+    reconnect(rc);
 }
 
+/* Drops any existing connection on 'rc', then configures 'rc' to use
+ * 'vconn'.  If the connection on 'vconn' drops, 'rc' will not reconnect on it
+ * own.
+ *
+ * By default, the target obtained from vconn_get_name(vconn) is used in log
+ * messages.  If 'name' is nonnull, then it is used instead.  It should
+ * presumably give more information to a human reader than the target, but it
+ * need not be acceptable to vconn_open(). */
 void
 rconn_connect_unreliably(struct rconn *rc,
-                         const char *name, struct vconn *vconn)
+                         struct vconn *vconn, const char *name)
 {
     assert(vconn != NULL);
     rconn_disconnect(rc);
-    free(rc->name);
-    rc->name = xstrdup(name);
+    rconn_set_target__(rc, vconn_get_name(vconn), name);
     rc->reliable = false;
     rc->vconn = vconn;
     rc->last_connected = time_now();
@@ -261,6 +279,7 @@ void
 rconn_reconnect(struct rconn *rc)
 {
     if (rc->state & (S_ACTIVE | S_IDLE)) {
+        VLOG_INFO("%s: disconnecting", rc->name);
         disconnect(rc, 0);
     }
 }
@@ -273,8 +292,7 @@ rconn_disconnect(struct rconn *rc)
             vconn_close(rc->vconn);
             rc->vconn = NULL;
         }
-        free(rc->name);
-        rc->name = xstrdup("void");
+        rconn_set_target__(rc, "void", NULL);
         rc->reliable = false;
 
         rc->backoff = 0;
@@ -292,6 +310,7 @@ rconn_destroy(struct rconn *rc)
         size_t i;
 
         free(rc->name);
+        free(rc->target);
         vconn_close(rc->vconn);
         flush_queue(rc);
         queue_destroy(&rc->txq);
@@ -303,34 +322,38 @@ rconn_destroy(struct rconn *rc)
 }
 
 static unsigned int
-timeout_VOID(const struct rconn *rc UNUSED)
+timeout_VOID(const struct rconn *rc OVS_UNUSED)
 {
     return UINT_MAX;
 }
 
 static void
-run_VOID(struct rconn *rc UNUSED)
+run_VOID(struct rconn *rc OVS_UNUSED)
 {
     /* Nothing to do. */
 }
 
-static int
+static void
 reconnect(struct rconn *rc)
 {
     int retval;
 
-    VLOG_INFO("%s: connecting...", rc->name);
+    if (rconn_logging_connection_attempts__(rc)) {
+        VLOG_INFO("%s: connecting...", rc->name);
+    }
     rc->n_attempted_connections++;
-    retval = vconn_open(rc->name, OFP_VERSION, &rc->vconn);
+    retval = vconn_open(rc->target, OFP_VERSION, &rc->vconn);
     if (!retval) {
+        rc->remote_ip = vconn_get_remote_ip(rc->vconn);
+        rc->local_ip = vconn_get_local_ip(rc->vconn);
+        rc->remote_port = vconn_get_remote_port(rc->vconn);
         rc->backoff_deadline = time_now() + rc->backoff;
         state_transition(rc, S_CONNECTING);
     } else {
         VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(retval));
         rc->backoff_deadline = TIME_MAX; /* Prevent resetting backoff. */
-        disconnect(rc, 0);
+        disconnect(rc, retval);
     }
-    return retval;
 }
 
 static unsigned int
@@ -363,12 +386,17 @@ run_CONNECTING(struct rconn *rc)
         state_transition(rc, S_ACTIVE);
         rc->last_connected = rc->state_entered;
     } else if (retval != EAGAIN) {
-        VLOG_INFO("%s: connection failed (%s)", rc->name, strerror(retval));
+        if (rconn_logging_connection_attempts__(rc)) {
+            VLOG_INFO("%s: connection failed (%s)",
+                      rc->name, strerror(retval));
+        }
         disconnect(rc, retval);
     } else if (timed_out(rc)) {
-        VLOG_INFO("%s: connection timed out", rc->name);
+        if (rconn_logging_connection_attempts__(rc)) {
+            VLOG_INFO("%s: connection timed out", rc->name);
+        }
         rc->backoff_deadline = TIME_MAX; /* Prevent resetting backoff. */
-        disconnect(rc, 0);
+        disconnect(rc, ETIMEDOUT);
     }
 }
 
@@ -433,7 +461,7 @@ run_IDLE(struct rconn *rc)
         VLOG_ERR("%s: no response to inactivity probe after %u "
                  "seconds, disconnecting",
                  rc->name, elapsed_in_this_state(rc));
-        disconnect(rc, 0);
+        disconnect(rc, ETIMEDOUT);
     } else {
         do_tx_work(rc);
     }
@@ -446,6 +474,15 @@ void
 rconn_run(struct rconn *rc)
 {
     int old_state;
+    size_t i;
+
+    if (rc->vconn) {
+        vconn_run(rc->vconn);
+    }
+    for (i = 0; i < rc->n_monitors; i++) {
+        vconn_run(rc->monitors[i]);
+    }
+
     do {
         old_state = rc->state;
         switch (rc->state) {
@@ -463,11 +500,20 @@ rconn_run(struct rconn *rc)
 void
 rconn_run_wait(struct rconn *rc)
 {
-    unsigned int timeo = timeout(rc);
+    unsigned int timeo;
+    size_t i;
+
+    if (rc->vconn) {
+        vconn_run_wait(rc->vconn);
+    }
+    for (i = 0; i < rc->n_monitors; i++) {
+        vconn_run_wait(rc->monitors[i]);
+    }
+
+    timeo = timeout(rc);
     if (timeo != UINT_MAX) {
-        unsigned int expires = sat_add(rc->state_entered, timeo);
-        unsigned int remaining = sat_sub(expires, time_now());
-        poll_timer_wait(sat_mul(remaining, 1000));
+        long long int expires = sat_add(rc->state_entered, timeo);
+        poll_timer_wait_until(expires * 1000);
     }
 
     if ((rc->state & (S_ACTIVE | S_IDLE)) && rc->txq.n) {
@@ -486,7 +532,7 @@ rconn_recv(struct rconn *rc)
         int error = vconn_recv(rc->vconn, &buffer);
         if (!error) {
             copy_to_monitor(rc, buffer);
-            if (is_admitted_msg(buffer)
+            if (rc->probably_admitted || is_admitted_msg(buffer)
                 || time_now() - rc->last_connected >= 30) {
                 rc->probably_admitted = true;
                 rc->last_admitted = time_now();
@@ -498,6 +544,7 @@ rconn_recv(struct rconn *rc)
             }
             return buffer;
         } else if (error != EAGAIN) {
+            report_error(rc, error);
             disconnect(rc, error);
         }
     }
@@ -534,7 +581,7 @@ rconn_send(struct rconn *rc, struct ofpbuf *b,
     if (rconn_is_connected(rc)) {
         COVERAGE_INC(rconn_queued);
         copy_to_monitor(rc, b);
-        b->private = counter;
+        b->private_p = counter;
         if (counter) {
             rconn_packet_counter_inc(counter);
         }
@@ -602,13 +649,31 @@ rconn_add_monitor(struct rconn *rc, struct vconn *vconn)
     }
 }
 
-/* Returns 'rc''s name (the 'name' argument passed to rconn_new()). */
+/* Returns 'rc''s name.  This is a name for human consumption, appropriate for
+ * use in log messages.  It is not necessarily a name that may be passed
+ * directly to, e.g., vconn_open(). */
 const char *
 rconn_get_name(const struct rconn *rc)
 {
     return rc->name;
 }
 
+/* Sets 'rc''s name to 'new_name'. */
+void
+rconn_set_name(struct rconn *rc, const char *new_name)
+{
+    free(rc->name);
+    rc->name = xstrdup(new_name);
+}
+
+/* Returns 'rc''s target.  This is intended to be a string that may be passed
+ * directly to, e.g., vconn_open(). */
+const char *
+rconn_get_target(const struct rconn *rc)
+{
+    return rc->target;
+}
+
 /* Returns true if 'rconn' is connected or in the process of reconnecting,
  * false if 'rconn' is disconnected and will not reconnect on its own. */
 bool
@@ -624,46 +689,53 @@ rconn_is_connected(const struct rconn *rconn)
     return is_connected_state(rconn->state);
 }
 
-/* Returns 0 if 'rconn' is connected.  Otherwise, if 'rconn' is in a "failure
- * mode" (that is, it is not connected), returns the number of seconds that it
- * has been in failure mode, ignoring any times that it connected but the
- * controller's admission control policy caused it to be quickly
- * disconnected. */
+/* Returns true if 'rconn' is connected and thought to have been accepted by
+ * the peer's admission-control policy. */
+bool
+rconn_is_admitted(const struct rconn *rconn)
+{
+    return (rconn_is_connected(rconn)
+            && rconn->last_admitted >= rconn->last_connected);
+}
+
+/* Returns 0 if 'rconn' is currently connected and considered to have been
+ * accepted by the peer's admission-control policy, otherwise the number of
+ * seconds since 'rconn' was last in such a state. */
 int
 rconn_failure_duration(const struct rconn *rconn)
 {
-    return rconn_is_connected(rconn) ? 0 : time_now() - rconn->last_admitted;
+    return rconn_is_admitted(rconn) ? 0 : time_now() - rconn->last_admitted;
 }
 
-/* Returns the IP address of the peer, or 0 if the peer is not connected over
- * an IP-based protocol or if its IP address is not known. */
+/* Returns the IP address of the peer, or 0 if the peer's IP address is not
+ * known. */
 uint32_t
-rconn_get_remote_ip(const struct rconn *rconn) 
+rconn_get_remote_ip(const struct rconn *rconn)
 {
-    return rconn->vconn ? vconn_get_remote_ip(rconn->vconn) : 0;
+    return rconn->remote_ip;
 }
 
-/* Returns the transport port of the peer, or 0 if the peer does not 
- * contain a port or if the port is not known. */
+/* Returns the transport port of the peer, or 0 if the peer's port is not
+ * known. */
 uint16_t
-rconn_get_remote_port(const struct rconn *rconn) 
+rconn_get_remote_port(const struct rconn *rconn)
 {
-    return rconn->vconn ? vconn_get_remote_port(rconn->vconn) : 0;
+    return rconn->remote_port;
 }
 
 /* Returns the IP address used to connect to the peer, or 0 if the
- * connection is not an IP-based protocol or if its IP address is not 
+ * connection is not an IP-based protocol or if its IP address is not
  * known. */
 uint32_t
-rconn_get_local_ip(const struct rconn *rconn) 
+rconn_get_local_ip(const struct rconn *rconn)
 {
-    return rconn->vconn ? vconn_get_local_ip(rconn->vconn) : 0;
+    return rconn->local_ip;
 }
 
 /* Returns the transport port used to connect to the peer, or 0 if the
  * connection does not contain a port or if the port is not known. */
 uint16_t
-rconn_get_local_port(const struct rconn *rconn) 
+rconn_get_local_port(const struct rconn *rconn)
 {
     return rconn->vconn ? vconn_get_local_port(rconn->vconn) : 0;
 }
@@ -769,6 +841,22 @@ rconn_get_connection_seqno(const struct rconn *rc)
 {
     return rc->seqno;
 }
+
+/* Returns a value that explains why 'rc' last disconnected:
+ *
+ *   - 0 means that the last disconnection was caused by a call to
+ *     rconn_disconnect(), or that 'rc' is new and has not yet completed its
+ *     initial connection or connection attempt.
+ *
+ *   - EOF means that the connection was closed in the normal way by the peer.
+ *
+ *   - A positive integer is an errno value that represents the error.
+ */
+int
+rconn_get_last_error(const struct rconn *rc)
+{
+    return rc->last_error;
+}
 \f
 struct rconn_packet_counter *
 rconn_packet_counter_create(void)
@@ -805,6 +893,23 @@ rconn_packet_counter_dec(struct rconn_packet_counter *c)
     }
 }
 \f
+/* Set rc->target and rc->name to 'target' and 'name', respectively.  If 'name'
+ * is null, 'target' is used.
+ *
+ * Also, clear out the cached IP address and port information, since changing
+ * the target also likely changes these values. */
+static void
+rconn_set_target__(struct rconn *rc, const char *target, const char *name)
+{
+    free(rc->name);
+    rc->name = xstrdup(name ? name : target);
+    free(rc->target);
+    rc->target = xstrdup(target);
+    rc->local_ip = 0;
+    rc->remote_ip = 0;
+    rc->remote_port = 0;
+}
+
 /* Tries to send a packet from 'rc''s send buffer.  Returns 0 if successful,
  * otherwise a positive errno value. */
 static int
@@ -812,10 +917,11 @@ try_send(struct rconn *rc)
 {
     int retval = 0;
     struct ofpbuf *next = rc->txq.head->next;
-    struct rconn_packet_counter *counter = rc->txq.head->private;
+    struct rconn_packet_counter *counter = rc->txq.head->private_p;
     retval = vconn_send(rc->vconn, rc->txq.head);
     if (retval) {
         if (retval != EAGAIN) {
+            report_error(rc, retval);
             disconnect(rc, retval);
         }
         return retval;
@@ -829,26 +935,41 @@ try_send(struct rconn *rc)
     return 0;
 }
 
-/* Disconnects 'rc'.  'error' is used only for logging purposes.  If it is
- * nonzero, then it should be EOF to indicate the connection was closed by the
- * peer in a normal fashion or a positive errno value. */
+/* Reports that 'error' caused 'rc' to disconnect.  'error' may be a positive
+ * errno value, or it may be EOF to indicate that the connection was closed
+ * normally. */
+static void
+report_error(struct rconn *rc, int error)
+{
+    if (error == EOF) {
+        /* If 'rc' isn't reliable, then we don't really expect this connection
+         * to last forever anyway (probably it's a connection that we received
+         * via accept()), so use DBG level to avoid cluttering the logs. */
+        enum vlog_level level = rc->reliable ? VLL_INFO : VLL_DBG;
+        VLOG(level, "%s: connection closed by peer", rc->name);
+    } else {
+        VLOG_WARN("%s: connection dropped (%s)", rc->name, strerror(error));
+    }
+}
+
+/* Disconnects 'rc' and records 'error' as the error that caused 'rc''s last
+ * disconnection:
+ *
+ *   - 0 means that this disconnection is due to a request by 'rc''s client,
+ *     not due to any kind of network error.
+ *
+ *   - EOF means that the connection was closed in the normal way by the peer.
+ *
+ *   - A positive integer is an errno value that represents the error.
+ */
 static void
 disconnect(struct rconn *rc, int error)
 {
+    rc->last_error = error;
     if (rc->reliable) {
         time_t now = time_now();
 
         if (rc->state & (S_CONNECTING | S_ACTIVE | S_IDLE)) {
-            if (error > 0) {
-                VLOG_WARN("%s: connection dropped (%s)",
-                          rc->name, strerror(error));
-            } else if (error == EOF) {
-                if (rc->reliable) {
-                    VLOG_INFO("%s: connection closed by peer", rc->name);
-                }
-            } else {
-                VLOG_INFO("%s: connection dropped", rc->name);
-            }
             vconn_close(rc->vconn);
             rc->vconn = NULL;
             flush_queue(rc);
@@ -856,10 +977,17 @@ disconnect(struct rconn *rc, int error)
 
         if (now >= rc->backoff_deadline) {
             rc->backoff = 1;
-        } else {
-            rc->backoff = MIN(rc->max_backoff, MAX(1, 2 * rc->backoff));
-            VLOG_INFO("%s: waiting %d seconds before reconnect\n",
+        } else if (rc->backoff < rc->max_backoff / 2) {
+            rc->backoff = MAX(1, 2 * rc->backoff);
+            VLOG_INFO("%s: waiting %d seconds before reconnect",
                       rc->name, rc->backoff);
+        } else {
+            if (rconn_logging_connection_attempts__(rc)) {
+                VLOG_INFO("%s: continuing to retry connections in the "
+                          "background but suppressing further logging",
+                          rc->name);
+            }
+            rc->backoff = rc->max_backoff;
         }
         rc->backoff_deadline = now + rc->backoff;
         state_transition(rc, S_BACKOFF);
@@ -881,7 +1009,7 @@ flush_queue(struct rconn *rc)
     }
     while (rc->txq.n > 0) {
         struct ofpbuf *b = queue_pop_head(&rc->txq);
-        struct rconn_packet_counter *counter = b->private;
+        struct rconn_packet_counter *counter = b->private_p;
         if (counter) {
             rconn_packet_counter_dec(counter);
         }
@@ -931,7 +1059,7 @@ state_transition(struct rconn *rc, enum state state)
 }
 
 static void
-question_connectivity(struct rconn *rc) 
+question_connectivity(struct rconn *rc)
 {
     time_t now = time_now();
     if (now - rc->last_questioned > 60) {
@@ -969,7 +1097,7 @@ copy_to_monitor(struct rconn *rc, const struct ofpbuf *b)
 }
 
 static bool
-is_connected_state(enum state state) 
+is_connected_state(enum state state)
 {
     return (state & (S_ACTIVE | S_IDLE)) != 0;
 }
@@ -991,3 +1119,12 @@ is_admitted_msg(const struct ofpbuf *b)
                                 (1u << OFPT_GET_CONFIG_REPLY) |
                                 (1u << OFPT_SET_CONFIG)));
 }
+
+/* Returns true if 'rc' is currently logging information about connection
+ * attempts, false if logging should be suppressed because 'rc' hasn't
+ * successuflly connected in too long. */
+static bool
+rconn_logging_connection_attempts__(const struct rconn *rc)
+{
+    return rc->backoff < rc->max_backoff;
+}