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 *);
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. */
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
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. */
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) {
/* 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()). */
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);
}
\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);
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;
}
rc->name, rc->backoff);
}
rc->backoff_deadline = now + rc->backoff;
+ rc->probe_sent = 0;
}
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 *
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
/* -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. */
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;
}
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. */
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'},
}
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;
" -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"
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;