Discovery.
authorBen Pfaff <blp@nicira.com>
Tue, 15 Jul 2008 18:20:05 +0000 (11:20 -0700)
committerBen Pfaff <blp@nicira.com>
Fri, 18 Jul 2008 21:16:40 +0000 (14:16 -0700)
INSTALL
secchan/secchan.8.in
secchan/secchan.c

diff --git a/INSTALL b/INSTALL
index fbacb67bcd94ee161c7918049ab1f1cb248ab400..742a18de4dae47c853e0559e02e61692a7e754a1 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -402,48 +402,72 @@ following instructions to set up remote switches:
    network.  There are two ways to do this:
 
       - Use a "control network" that is completely separate from the
-        "data network" to be controlled.  To do so, configure a
-        network device (one that has not been added to the datapath
-        with "dpctl addif") to access the control network in the usual
-        way.
+        "data network" to be controlled ("out-of-band control").  To
+        do so, configure a network device (one that has not been added
+        to the datapath with "dpctl addif") to access the control
+        network in the usual way.
 
-      - Use the same network for control and for data.  For this
-        purpose, each datapath nl:K has a corresponding virtual
-        network device named ofK.  Start by bringing up of0 before you
-        start the secure channel:
+      - Use the same network for control and for data ("in-band
+        control").  For this purpose, each datapath nl:K has a
+        corresponding virtual network device named ofK.
 
-           # ifconfig of0 up
+        When in-band control is used, the location of the controller
+        may be configured manually or discovered automatically:
 
-        Before the secure channel starts up, the of0 device cannot
-        send or receive any packets, so the next step depends on
-        whether connectivity is required to configure the device's IP
-        address:
+            * Manual configuration: Start by bringing up of0 before
+              you start the secure channel:
 
-           . If the switch has a static IP address, you may configure
-             its IP address now, e.g.:
+                 # ifconfig of0 up
 
-                # ifconfig of0 192.168.1.1
+              Before the secure channel starts up, the of0 device
+              cannot send or receive any packets, so the next step
+              depends on whether connectivity is required to configure
+              the device's IP address:
 
-           . If the switch does not have a static IP address, e.g. its
-             IP address is obtained dynamically via DHCP, then proceed
-             to step 4.  The DHCP client will not be able to contact
-             the DHCP server until the secure channel has started up.
+                 . If the switch has a static IP address, you may
+                   configure its IP address now, e.g.:
+
+                      # ifconfig of0 192.168.1.1
+
+                 . If the switch does not have a static IP address,
+                   e.g. its IP address is obtained dynamically via
+                   DHCP, then proceed to step 4.  The DHCP client will
+                   not be able to contact the DHCP server until the
+                   secure channel has started up.
+
+            * Controller discovery: No special setup is required at
+              the switch, but you must specially configure a DHCP
+              server to give out the switch's IP address and to tell
+              it the location of the controller.  See secchan(8) for
+              details.
 
 4. Run secchan on the datapath host to start the secure channel
    connecting the datapath to a remote controller.  (See secchan(8)
-   for usage details).  The channel should be configured to connect to
-   the controller's IP address on the port configured in step 2.
+   for usage details).  The details depend on how you configured the
+   network in step 3:
+
+      - If you are using in-band control and controller discovery,
+        invoke secchan something like this:
+
+           # secchan -v nl:0
+
+        The secure channel should connect to the controller after it
+        obtains its own IP address and the controller's location via
+        DHCP.  This can take a few seconds.  Switch setup is now
+        complete.
 
-   If the controller is running on host 192.168.1.2 port 975 (the
-   default port) and the datapath ID is 0, the secchan invocation
-   would look like:
+      - Otherwise, the secure channel should be configured to connect
+        to the controller's IP address on the port configured in step
+        2.  If the controller is running on host 192.168.1.2 port 975
+        (the default port) and the datapath ID is 0, the secchan
+        invocation would look like:
 
-      # secchan -v nl:0 tcp:192.168.1.2
+           # secchan -v nl:0 tcp:192.168.1.2
 
-   If you are using separate control and data networks, or if the
-   networks are combined and the switch has a static IP address, the
-   secure channel should quickly connect to the controller.  Setup is
-   now complete.  Otherwise, proceed to step 5.
+        If you are using out-of-band control, or if you are using
+        in-band control and the switch has a static IP address, the
+        secure channel should quickly connect to the controller.
+        Setup is now complete.  Otherwise, proceed to step 5.
 
 5. If you are using the same network for control and data, and the
    switch obtains its IP address dynamically, then you may now obtain
index dc1c7883c8b812dca5a194301f53ec545e1ec8a0..512ee920de4d8a2a37dd720aa8f0167872de00e3 100644 (file)
@@ -5,7 +5,7 @@ secchan \- secure channel connecting an OpenFlow datapath to a controller
 
 .SH SYNOPSIS
 .B secchan
-[\fIoptions\fR] \fBnl:\fIdp_idx\fR \fIremote\fR
+[\fIoptions\fR] \fBnl:\fIdp_idx\fR [\fIremote\fR]
 
 .SH DESCRIPTION
 The \fBsecchan\fR program sets up a secure channel between a local
@@ -18,7 +18,7 @@ to relay.  Within this argument, \fIdp_idx\fR is the number of a
 datapath that has been created with
 .BR dpctl (8).
 
-The mandatory \fIcontroller\fR argument specifies how to connect to
+The optional \fIcontroller\fR argument specifies how to connect to
 the OpenFlow controller.  It takes one of the following forms:
 
 .TP
@@ -32,6 +32,9 @@ The specified SSL \fIport\fR (default: 976) on the given remote
 The specified TCP \fIport\fR (default: 975) on the given remote
 \fIhost\fR.
 
+If \fIcontroller\fR is omitted, \fBsecchan\fR attempts to discover the
+location of the controller automatically (see below).
+
 .SH "CONTACTING THE CONTROLLER"
 The OpenFlow switch must be able to contact the OpenFlow controller
 over the network.  It can do so in one of two ways:
@@ -56,7 +59,71 @@ addif\fR.  This configuration is often more convenient than
 out-of-band control, because it is not necessary to maintain two
 independent networks.
 
-Using \fBsecchan\fR in a network with in-band control requires
+With in-band control, the location of the controller can be configured
+manually or discovered automatically:
+
+.RS
+.IP "controller discovery"
+To make \fBsecchan\fR discover the location of the controller
+automatically, do not specify the location of the controller on the
+\fBsecchan\fR command line.
+
+In this mode, \fBsecchan\fR will broadcast a DHCP request with vendor
+class identifier \fBOpenFlow\fR across the network devices added to
+the datapath with \fBdpctl addif\fR.  It will accept any valid DHCP
+reply that has the same vendor class identifier and includes a
+vendor-specific option with code 1 whose contents are a string
+specifying the location of the controller in the same format used on
+the \fBsecchan\fR command line (e.g. \fBssl:192.168.0.1\fR).
+
+The following ISC DHCP server configuration file assigns the IP
+address range 192.168.0.20 through 192.168.0.30 to OpenFlow switches
+that follow the switch protocol and addresses 192.168.0.1 through
+192.168.0.10 to all other DHCP clients:
+
+default-lease-time 600;
+.br
+max-lease-time 7200;
+.br
+option space openflow;
+.br
+option openflow.controller-vconn code 1 = text;
+.br
+class "OpenFlow" {
+.br
+  match if option vendor-class-identifier = "OpenFlow";
+.br
+  vendor-option-space openflow;
+.br
+  option openflow.controller-vconn "tcp:192.168.0.10";
+.br
+  option vendor-class-identifier "OpenFlow";
+.br
+}
+.br
+subnet 192.168.0.0 netmask 255.255.255.0 {
+.br
+    pool {
+.br
+        allow members of "OpenFlow";
+.br
+        range 192.168.0.20 192.168.0.30;
+.br
+    }
+.br
+    pool {
+.br
+        deny members of "OpenFlow";
+.br
+        range 192.168.0.1 192.168.0.10;
+.br
+    }
+.br
+}
+.br
+
+.IP "manual configuration"
+Configuring in-band control manually requires
 additional setup before starting \fBsecchan\fR.  At a minimum, the
 special OpenFlow network device \fBof\fIn\fR, where \fIn\fR is same as
 the \fIdp_idx\fR specified on the \fBsecchan\fR command line, must be
@@ -85,8 +152,28 @@ e.g. it obtains its IP address dynamically via DHCP, the DHCP client
 will not be able to contact the DHCP server until the secure channel
 has started up.  Thus, start \fBsecchan\fR without configuring
 \fBof\fIn\fR further, and start the DHCP client afterward.
+.RE
 
 .SH OPTIONS
+.TP
+\fB--accept-vconn=\fIregex\fR
+When \fBsecchan\fR performs controller discovery (see \fBCONTACTING
+THE CONTROLLER\fR, above, for more information about controller
+discovery), it validates the controller location obtained via DHCP
+with a regular expression.  Only controllers whose names match the
+regular expression will be accepted.
+
+The default regular expression is \fBssl:.*\fR (meaning that only SSL
+controller connections will be accepted) when any of the SSL
+configuration options \fB--private-key\fR, \fB--certificate\fR, or
+\fB--ca-cert\fR is specified.  The default is \fB.*\fR otherwise
+(meaning that any controller will be accepted).
+
+The \fIregex\fR is implicitly anchored at the beginning of the
+controller location string, as if it begins with \fB^\fR.
+
+When controller discovery is not performed, this option has no effect.
+
 .TP
 \fB-f\fR, \fB--fail=\fR[\fBopen\fR|\fBclosed\fR]
 The controller is, ordinarily, responsible for setting up all flows on
index b21802c603f49587bc3fecc85b4678c66c692905..7fb50c97b13b021755f6bfa2257ded0cbd1e9d7c 100644 (file)
@@ -1,4 +1,4 @@
- /* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
  * Junior University
  * 
  * We are making the OpenFlow specification and associated documentation
@@ -36,6 +36,7 @@
 #include <inttypes.h>
 #include <netinet/in.h>
 #include <poll.h>
+#include <regex.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -45,6 +46,8 @@
 #include "command-line.h"
 #include "compiler.h"
 #include "daemon.h"
+#include "dhcp.h"
+#include "dhcp-client.h"
 #include "fault.h"
 #include "flow.h"
 #include "learning-switch.h"
@@ -91,8 +94,10 @@ struct relay {
 
 static struct list relays = LIST_INITIALIZER(&relays);
 
-/* Enable the local port? */
-static int local_port;
+/* Mode of operation.  Note that autodiscovery implies in-band
+ * communication. */
+static bool autodiscovery;      /* Discover the controller automatically? */
+static bool in_band;            /* Connect to controller in-band? */
 
 /* MAC address of local port. */
 static uint8_t local_mac[ETH_ADDR_LEN];
@@ -100,6 +105,9 @@ static uint8_t local_mac[ETH_ADDR_LEN];
 /* MAC learning table for local port. */
 static struct mac_learning *local_ml;
 
+/* Controller vconn name, or null to perform controller autodiscovery. */
+static char *controller_name = NULL;
+
 /* -f, --fail: Behavior when the connection to the controller fails. */
 static enum fail_mode fail_mode = FAIL_OPEN;
 
@@ -118,6 +126,14 @@ static int max_idle = 15;
  * seconds. */
 static int max_backoff = 15;
 
+/* DHCP client, for controller autodiscovery. */
+static struct dhclient *dhcp;
+
+/* --accept-vconn: Regular expression specifying the class of controller vconns
+ * that we will accept during autodiscovery. */
+static const char *accept_controller_re;
+static regex_t accept_controller_regex;
+
 static void parse_options(int argc, char *argv[]);
 static void usage(void) NO_RETURN;
 
@@ -132,6 +148,9 @@ static bool local_hook(struct relay *r);
 static bool failing_open(struct relay *r);
 static bool fail_open_hook(struct relay *r);
 
+static void modify_dhcp_request(struct dhcp_msg *, void *aux);
+static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
+
 int
 main(int argc, char *argv[])
 {
@@ -147,16 +166,31 @@ main(int argc, char *argv[])
     vlog_init();
     parse_options(argc, argv);
 
-    if (argc - optind != 2) {
-        fatal(0,
-              "need exactly two non-option arguments; use --help for usage");
+    argc -= optind;
+    argv += optind;
+    if (argc < 1 || argc > 2) {
+        fatal(0, "need one or two non-option arguments; use --help for usage");
     }
-    nl_name = argv[optind];
+    nl_name = argv[0];
     if (strncmp(nl_name, "nl:", 3)
         || strlen(nl_name) < 4
         || nl_name[strspn(nl_name + 3, "0123456789") + 3]) {
         fatal(0, "%s: argument is not of the form \"nl:DP_IDX\"", nl_name);
     }
+    controller_name = argc > 1 ? xstrdup(argv[1]) : NULL;
+    autodiscovery = controller_name == NULL;
+
+    if (!accept_controller_re) {
+        accept_controller_re = vconn_ssl_is_configured() ? "^ssl:.*" : ".*";
+    }
+    retval = regcomp(&accept_controller_regex, accept_controller_re,
+                     REG_NOSUB | REG_EXTENDED);
+    if (retval) {
+        size_t length = regerror(retval, &accept_controller_regex, NULL, 0);
+        char *buffer = xmalloc(length);
+        regerror(retval, &accept_controller_regex, buffer, length);
+        fatal(0, "%s: %s", accept_controller_re, buffer);
+    }
 
     if (listen_vconn_name) {
         retval = vconn_open(listen_vconn_name, &listen_vconn);
@@ -174,12 +208,20 @@ main(int argc, char *argv[])
     retval = netdev_open(of_name, NETDEV_ETH_TYPE_NONE, &of_device);
     if (!retval) {
         enum netdev_flags flags;
+
+        if (autodiscovery) {
+            retval = netdev_turn_flags_on(of_device, NETDEV_UP, true);
+            if (retval) {
+                fatal(retval, "Could not bring %s device up", of_name);
+            }
+        }
+
         retval = netdev_get_flags(of_device, &flags);
         if (!retval) {
             if (flags & NETDEV_UP) {
                 struct in6_addr in6;
 
-                local_port = true;
+                in_band = true;
                 memcpy(local_mac, netdev_get_etheraddr(of_device),
                        ETH_ADDR_LEN);
                 if (netdev_get_in6(of_device, &in6)) {
@@ -187,13 +229,26 @@ main(int argc, char *argv[])
                               "IPv6 not supported", of_name);
                 }
                 local_ml = mac_learning_create();
-            }
+            } 
         } else {
             error(retval, "Could not get flags for %s device", of_name);
         }
     } else {
         error(retval, "Could not open %s device", of_name);
     }
+    if (autodiscovery && !in_band) {
+        fatal(retval, "In autodiscovery mode but failed to configure "
+              "in-band control");
+    }
+
+    if (autodiscovery) {
+        retval = dhclient_create(of_name, modify_dhcp_request,
+                                 validate_dhcp_offer, NULL, &dhcp);
+        if (retval) {
+            fatal(retval, "Failed to initialize DHCP client");
+        }
+        dhclient_init(dhcp, 0);
+    }
 
     retval = vlog_server_listen(NULL, NULL);
     if (retval) {
@@ -203,15 +258,14 @@ main(int argc, char *argv[])
     daemonize();
 
     local_rconn = rconn_create(1, 0, max_backoff);
-    retval = rconn_connect(local_rconn, nl_name);
-    if (retval == EAFNOSUPPORT) {
-        fatal(0, "No support for %s vconn", nl_name);
-    }
+    rconn_connect(local_rconn, nl_name);
 
     remote_rconn = rconn_create(1, probe_interval, max_backoff);
-    retval = rconn_connect(remote_rconn, argv[optind + 1]);
-    if (retval == EAFNOSUPPORT) {
-        fatal(0, "No support for %s vconn", argv[optind + 1]);
+    if (controller_name) {
+        retval = rconn_connect(remote_rconn, controller_name);
+        if (retval == EAFNOSUPPORT) {
+            fatal(0, "No support for %s vconn", controller_name);
+        }
     }
     controller_relay = relay_create(local_rconn, remote_rconn, false);
     for (;;) {
@@ -234,7 +288,32 @@ main(int argc, char *argv[])
                 new_management_connection(nl_name, new_remote);
             }
         }
-        failing_open(controller_relay);
+        if (controller_relay) {
+            /* FIXME: should also fail open when controller_relay is NULL. */
+            failing_open(controller_relay); 
+        }
+        if (dhcp) {
+            if (rconn_is_connectivity_questionable(remote_rconn)) {
+                dhclient_force_renew(dhcp, 15);
+            }
+            dhclient_run(dhcp);
+            if (dhclient_changed(dhcp)) {
+                free(controller_name);
+                if (dhclient_is_bound(dhcp)) {
+                    controller_name = dhcp_msg_get_string(
+                        dhclient_get_config(dhcp),
+                        DHCP_CODE_OFP_CONTROLLER_VCONN);
+                    VLOG_WARN("%s: discovered controller",
+                              controller_name);
+                    rconn_connect(remote_rconn, controller_name);
+                } else if (controller_name) {
+                    VLOG_WARN("%s: discover controller no longer available",
+                              controller_name);
+                    controller_name = NULL;
+                    rconn_disconnect(remote_rconn);
+                }
+            }
+        }
 
         /* Wait for something to happen. */
         LIST_FOR_EACH (r, struct relay, node, &relays) {
@@ -243,6 +322,9 @@ main(int argc, char *argv[])
         if (listen_vconn) {
             vconn_accept_wait(listen_vconn);
         }
+        if (dhcp) {
+            dhclient_wait(dhcp);
+        }
         poll_block();
     }
 
@@ -274,8 +356,10 @@ new_management_connection(const char *nl_name, struct vconn *new_remote)
     }
 
     /* Add it to the relay list. */
-    r1 = rconn_new_from_vconn(nl_name_without_subscription, 1, new_local);
-    r2 = rconn_new_from_vconn("passive", 1, new_remote);
+    r1 = rconn_create(1, 0, 0);
+    rconn_connect_unreliably(r1, nl_name_without_subscription, new_local);
+    r2 = rconn_create(1, 0, 0);
+    rconn_connect_unreliably(r2, "passive", new_remote);
     relay_create(r1, r2, true);
 
     free(nl_name_without_subscription);
@@ -342,11 +426,13 @@ relay_run(struct relay *r)
         }
     }
 
-    for (i = 0; i < 2; i++) {
-        struct half *this = &r->halves[i];
-        if (!rconn_is_alive(this->rconn)) {
-            relay_destroy(r);
-            return;
+    if (r->is_mgmt_conn) {
+        for (i = 0; i < 2; i++) {
+            struct half *this = &r->halves[i];
+            if (!rconn_is_alive(this->rconn)) {
+                relay_destroy(r);
+                return;
+            }
         }
     }
 }
@@ -450,7 +536,7 @@ local_hook(struct relay *r)
     struct flow flow;
     uint16_t in_port, out_port;
 
-    if (!local_port) {
+    if (!in_band) {
         return false;
     }
 
@@ -562,15 +648,39 @@ fail_open_hook(struct relay *r)
     }
 }
 
+static void
+modify_dhcp_request(struct dhcp_msg *msg, void *aux)
+{
+    dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
+}
+
+static bool
+validate_dhcp_offer(const struct dhcp_msg *msg, void *aux)
+{
+    char *vconn_name;
+    bool accept;
+
+    vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
+    if (!vconn_name) {
+        VLOG_WARN("rejecting DHCP offer missing controller vconn");
+        return false;
+    }
+    accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0);
+    free(vconn_name);
+    return accept;
+}
+
 static void
 parse_options(int argc, char *argv[]) 
 {
     enum {
-        OPT_INACTIVITY_PROBE = UCHAR_MAX + 1,
+        OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
+        OPT_INACTIVITY_PROBE,
         OPT_MAX_IDLE,
         OPT_MAX_BACKOFF
     };
     static struct option long_options[] = {
+        {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
         {"fail",        required_argument, 0, 'f'},
         {"inactivity-probe", required_argument, 0, OPT_INACTIVITY_PROBE},
         {"max-idle",    required_argument, 0, OPT_MAX_IDLE},
@@ -595,6 +705,12 @@ parse_options(int argc, char *argv[])
         }
 
         switch (c) {
+        case OPT_ACCEPT_VCONN:
+            accept_controller_re = (optarg[0] == '^'
+                                    ? optarg
+                                    : xasprintf("^%s", optarg));
+            break;
+
         case 'f':
             if (!strcmp(optarg, "open")) {
                 fail_mode = FAIL_OPEN;
@@ -676,12 +792,14 @@ static void
 usage(void)
 {
     printf("%s: secure channel, a relay for OpenFlow messages.\n"
-           "usage: %s [OPTIONS] nl:DP_IDX CONTROLLER\n"
-           "where nl:DP_IDX is a datapath that has been added with dpctl\n"
-           "and CONTROLLER is an active OpenFlow connection method.\n",
+           "usage: %s [OPTIONS] nl:DP_IDX [CONTROLLER]\n"
+           "where nl:DP_IDX is a datapath that has been added with dpctl.\n"
+           "CONTROLLER is an active OpenFlow connection method; if it is\n"
+           "omitted, then secchan performs controller autodiscovery.\n",
            program_name, program_name);
     vconn_usage(true, true);
     printf("\nNetworking options:\n"
+           "  --accept-vconn=REGEX    accept matching discovered controllers\n"
            "  -f, --fail=open|closed  when controller connection fails:\n"
            "                            closed: drop all packets\n"
            "                            open (default): act as learning switch\n"