Make the secure channel and controller send out keepalives.
authorBen Pfaff <blp@nicira.com>
Tue, 1 Jul 2008 18:14:45 +0000 (11:14 -0700)
committerBen Pfaff <blp@nicira.com>
Tue, 1 Jul 2008 18:14:45 +0000 (11:14 -0700)
It can happen that the secure channel thinks that it is connected
to the controller, but in fact the connection has dropped.  For
example, a switch intermediate between the secure channel and the
controller could have a flow inserted, due to a malfunction, that
causes packets in one direction or the other to be dropped.  When
this happens, the Linux kernel's TCP implementation will continue
retransmitting packets for somewhere between 13 and 30 minutes,
depending on the connection's retransmission timeout (according
to linux/Documentation/networking/ip-sysctl.txt) with the default
Linux settings.

We want to detect the lost connection and re-connect much faster
than that.  We use OpenFlow echo request and reply messages: if
no message is received from the controller for N seconds, we
send out an echo request.  If no reply (actually, any message at
all) is received for an additional N seconds, then we assume
that the connection has been lost, and reconnect.

include/rconn.h
include/vconn.h
lib/rconn.c
lib/vconn.c
secchan/secchan.8.in
secchan/secchan.c
switch/switch.c

index af476cb..17fcef8 100644 (file)
@@ -52,7 +52,8 @@
 
 struct vconn;
 
-struct rconn *rconn_new(const char *name, int txq_limit);
+struct rconn *rconn_new(const char *name, int txq_limit,
+                        int inactivity_probe_interval);
 struct rconn *rconn_new_from_vconn(const char *name, int txq_limit,
                                    struct vconn *);
 void rconn_destroy(struct rconn *);
index e4de5ca..1e0105b 100644 (file)
@@ -82,6 +82,7 @@ struct buffer *make_buffered_packet_out(uint32_t buffer_id,
                                         uint16_t in_port, uint16_t out_port);
 struct buffer *make_unbuffered_packet_out(const struct buffer *packet,
                                           uint16_t in_port, uint16_t out_port);
+struct buffer *make_echo_request(void);
 struct buffer *make_echo_reply(const struct ofp_header *rq);
 \f
 /* Provider interface. */
index 5a5f4b1..254f356 100644 (file)
@@ -59,21 +59,36 @@ struct rconn {
     int backoff;
     time_t last_connected;
     unsigned int packets_sent;
+
+    /* 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
+     * a response. */
+    int probe_interval;         /* Secs of inactivity before sending probe. */
+    time_t probe_sent;          /* Time at which last probe sent, or 0 if none
+                                 * has been sent since 'last_connected'. */
 };
 
 static struct rconn *create_rconn(const char *name, int txq_limit,
-                                  struct vconn *);
+                                  int probe_interval, struct vconn *);
 static int try_send(struct rconn *);
 static void disconnect(struct rconn *, int error);
+static time_t probe_deadline(const struct rconn *);
 
 /* Creates and returns a new rconn that connects (and re-connects as necessary)
  * to the vconn named 'name'.
  *
- * 'txq_limit' is the maximum length of the send queue, in packets. */
+ * 'txq_limit' is the maximum length of the send queue, in packets.
+ *
+ * 'probe_interval' is a number of seconds.  If the interval passes once
+ * without an OpenFlow message being received from the peer, the rconn sends
+ * out an "echo request" message.  If the interval passes again without a
+ * message being received, the rconn disconnects and re-connects to the peer.
+ * Setting 'probe_interval' to 0 disables this behavior.  */
 struct rconn *
-rconn_new(const char *name, int txq_limit
+rconn_new(const char *name, int txq_limit, int probe_interval)
 {
-    return create_rconn(name, txq_limit, NULL);
+    return create_rconn(name, txq_limit, probe_interval, NULL);
 }
 
 /* Creates and returns a new rconn that is initially connected to 'vconn' and
@@ -85,7 +100,7 @@ struct rconn *
 rconn_new_from_vconn(const char *name, int txq_limit, struct vconn *vconn)
 {
     assert(vconn != NULL);
-    return create_rconn(name, txq_limit, vconn);
+    return create_rconn(name, txq_limit, 0, vconn);
 }
 
 /* Disconnects 'rc' and frees the underlying storage. */
@@ -134,6 +149,22 @@ rconn_run(struct rconn *rc)
             disconnect(rc, 0);
         }
     } else {
+        if (rc->probe_interval) {
+            time_t now = time(0);
+            if (now >= probe_deadline(rc)) {
+                if (!rc->probe_sent) {
+                    queue_push_tail(&rc->txq, make_echo_request());
+                    rc->probe_sent = now;
+                    VLOG_DBG("%s: idle %d seconds, sending inactivity probe",
+                             rc->name, (int) (now - rc->last_connected)); 
+                } else {
+                    VLOG_ERR("%s: no response to inactivity probe after %d "
+                             "seconds, disconnecting",
+                             rc->name, (int) (now - rc->probe_sent));
+                    disconnect(rc, 0);
+                }
+            }
+        }
         while (rc->txq.n > 0) {
             int error = try_send(rc);
             if (error == EAGAIN) {
@@ -149,17 +180,31 @@ rconn_run(struct rconn *rc)
 /* Causes the next call to poll_block() to wake up when rconn_run() should be
  * called on 'rc'. */
 void
-rconn_run_wait(struct rconn *rc) 
+rconn_run_wait(struct rconn *rc)
 {
     if (rc->vconn) {
         if (rc->txq.n) {
             vconn_wait(rc->vconn, WAIT_SEND);
         }
+        if (rc->probe_interval) {
+            poll_timer_wait((probe_deadline(rc) - time(0)) * 1000);
+        }
     } else {
         poll_timer_wait((rc->backoff_deadline - time(0)) * 1000);
     }
 }
 
+/* Returns the time at which, should nothing be received, we should send out an
+ * inactivity probe (if none has yet been sent) or conclude that the connection
+ * is dead (if a probe has already been sent). */
+static time_t
+probe_deadline(const struct rconn *rc) 
+{
+    assert(rc->probe_interval);
+    return (rc->probe_interval
+            + (rc->probe_sent ? rc->probe_sent : rc->last_connected));
+}
+
 /* Attempts to receive a packet from 'rc'.  If successful, returns the packet;
  * otherwise, returns a null pointer.  The caller is responsible for freeing
  * the packet (with buffer_delete()). */
@@ -171,6 +216,7 @@ rconn_recv(struct rconn *rc)
         int error = vconn_recv(rc->vconn, &buffer);
         if (!error) {
             rc->last_connected = time(0);
+            rc->probe_sent = 0;
             return buffer;
         } else if (error != EAGAIN) {
             disconnect(rc, error); 
@@ -280,7 +326,8 @@ rconn_disconnected_duration(const struct rconn *rconn)
 }
 \f
 static struct rconn *
-create_rconn(const char *name, int txq_limit, struct vconn *vconn)
+create_rconn(const char *name, int txq_limit, int probe_interval,
+             struct vconn *vconn)
 {
     struct rconn *rc = xmalloc(sizeof *rc);
     assert(txq_limit > 0);
@@ -293,6 +340,9 @@ create_rconn(const char *name, int txq_limit, struct vconn *vconn)
     rc->backoff_deadline = 0;
     rc->backoff = 0;
     rc->last_connected = time(0);
+    rc->probe_interval = (probe_interval
+                              ? MAX(5, probe_interval) : 0);
+    rc->probe_sent = 0;
     rc->packets_sent = 0;
     return rc;
 }
@@ -345,4 +395,5 @@ disconnect(struct rconn *rc, int error)
                   rc->name, rc->backoff);
     }
     rc->backoff_deadline = now + rc->backoff;
+    rc->probe_sent = 0;
 }
index 1fa83fa..6af60e7 100644 (file)
@@ -440,6 +440,20 @@ make_buffered_packet_out(uint32_t buffer_id,
     return out;
 }
 
+/* Creates and returns an OFPT_ECHO_REQUEST message with an empty payload. */
+struct buffer *
+make_echo_request(void)
+{
+    struct ofp_header *rq;
+    struct buffer *out = buffer_new(sizeof *rq);
+    rq = buffer_put_uninit(out, sizeof *rq);
+    rq->version = OFP_VERSION;
+    rq->type = OFPT_ECHO_REQUEST;
+    rq->length = htons(sizeof *rq);
+    rq->xid = 0;
+    return out;
+}
+
 /* Creates and returns an OFPT_ECHO_REPLY message matching the
  * OFPT_ECHO_REQUEST message in 'rq'. */
 struct buffer *
index 4a85021..b1826b9 100644 (file)
@@ -97,22 +97,30 @@ switch at all.
 
 If this option is set to \fBopen\fR (the default), \fBsecchan\fR will
 take over responsibility for setting up flows in the local datapath
-when the controller connection stays down for long enough.  In this
-``fail open'' mode, \fBsecchan\fR causes the datapath to act like an
-ordinary MAC-learning switch.  \fBsecchan\fR will continue to retry
-connection to the controller in the background and, when the
-connection succeeds, it discontinues its fail-open behavior.
+when no message has been received from the controller for three times
+the inactivity probe interval (see below), or 45 seconds by default.
+In this ``fail open'' mode, \fBsecchan\fR causes the datapath to act
+like an ordinary MAC-learning switch.  \fBsecchan\fR will continue to
+retry connection to the controller in the background and, when the
+connection succeeds, it discontinues its fail-open behavior.  The
+secure channel enters the fail-open mode when
 
 If this option is set to \fBclosed\fR, then \fBsecchan\fR will not
 set up flows on its own when the controller connection fails.
 
 .TP
-\fB-d\fR, \fB--fail-open-delay=\fIsecs\fR
-Sets the number of seconds of failed controller connection attempts
-after which the switch enters fail-open mode.  The default is 30
-seconds.
+\fB--inactivity-probe=\fIsecs\fR
+When the secure channel is connected to the controller, the secure
+channel waits for a message to be received from the controller for
+\fIsecs\fR seconds before it sends a inactivity probe to the
+controller.  After sending the inactivity probe, if no response is
+received for an additional \fIsecs\fR seconds, the secure channel
+assumes that the connection has been broken and attempts to reconnect.
+The default is 15 seconds, and the minimum value is 5 seconds.
 
-This option has no effect when \fB--fail=closed\fR is specified.
+When fail-open mode is configured, changing the inactivity probe
+interval also changes the interval before entering fail-open mode (see
+above).
 
 .TP
 \fB--max-idle=\fIsecs\fR|\fBpermanent\fR
index cb77050..0ef6023 100644 (file)
@@ -103,9 +103,9 @@ static struct mac_learning *local_ml;
 /* -f, --fail: Behavior when the connection to the controller fails. */
 static enum fail_mode fail_mode = FAIL_OPEN;
 
-/* -d, --fail-open-delay: Number of seconds after which to fail open, when
* fail_mode is FAIL_OPEN. */
-static int fail_open_delay = 30;
+/* --inactivity-probe: Number of seconds without receiving a message from the
  controller before sending an inactivity probe. */
+static int probe_interval = 15;
 
 /* --max-idle: Idle time to assign to flows created by learning switch when in
  * fail-open mode. */
@@ -194,7 +194,8 @@ main(int argc, char *argv[])
 
     daemonize();
 
-    relay_create(rconn_new(argv[optind], 1), rconn_new(argv[optind + 1], 1),
+    relay_create(rconn_new(argv[optind], 1, 0),
+                 rconn_new(argv[optind + 1], 1, probe_interval),
                  false);
     for (;;) {
         struct relay *r, *n;
@@ -441,7 +442,7 @@ fail_open_hook(struct relay *r)
     }
 
     disconnected_duration = rconn_disconnected_duration(remote);
-    if (disconnected_duration < fail_open_delay) {
+    if (disconnected_duration < probe_interval * 3) {
         /* It's not time to fail open yet. */
         if (r->lswitch && rconn_is_connected(remote)) {
             /* We're connected, so drop the learning switch. */
@@ -467,10 +468,10 @@ fail_open_hook(struct relay *r)
 static void
 parse_options(int argc, char *argv[]) 
 {
-    enum { OPT_MAX_IDLE = UCHAR_MAX + 1 };
+    enum { OPT_INACTIVITY_PROBE = UCHAR_MAX + 1, OPT_MAX_IDLE };
     static struct option long_options[] = {
         {"fail",        required_argument, 0, 'f'},
-        {"fail-open-delay", required_argument, 0, 'd'},
+        {"inactivity-probe", required_argument, 0, OPT_INACTIVITY_PROBE},
         {"max-idle",    required_argument, 0, OPT_MAX_IDLE},
         {"listen",      required_argument, 0, 'l'},
         {"detach",      no_argument, 0, 'D'},
@@ -503,11 +504,10 @@ parse_options(int argc, char *argv[])
             }
             break;
 
-        case 'd':
-            fail_open_delay = atoi(optarg);
-            if (fail_open_delay < 1) {
-                fatal(0,
-                      "-d or --fail-open-delay argument must be at least 1");
+        case OPT_INACTIVITY_PROBE:
+            probe_interval = atoi(optarg);
+            if (probe_interval < 5) {
+                fatal(0, "--inactivity-probe argument must be at least 5");
             }
             break;
 
@@ -574,8 +574,7 @@ usage(void)
            "  -f, --fail=open|closed  when controller connection fails:\n"
            "                            closed: drop all packets\n"
            "                            open (default): act as learning switch\n"
-           "  -d, --fail-open-delay=SECS  number of seconds after which to\n"
-           "                          fail open if --fail=open (default: 30)\n"
+           "  --inactivity-probe=SECS time between inactivity probes\n"
            "  --max-idle=SECS         max idle for flows set up by secchan\n"
            "  -l, --listen=METHOD     allow management connections on METHOD\n"
            "                          (a passive OpenFlow connection method)\n"
index 9432a02..5284fdb 100644 (file)
@@ -77,7 +77,7 @@ main(int argc, char *argv[])
         fatal(0, "missing controller argument; use --help for usage");
     }
 
-    error = dp_new(&dp, dpid, rconn_new(argv[optind], 128));
+    error = dp_new(&dp, dpid, rconn_new(argv[optind], 128, 60));
     if (listen_vconn_name) {
         struct vconn *listen_vconn;
         int retval;