New program ofp-discover.
authorBen Pfaff <blp@nicira.com>
Tue, 22 Jul 2008 21:44:10 +0000 (14:44 -0700)
committerBen Pfaff <blp@nicira.com>
Wed, 23 Jul 2008 20:12:21 +0000 (13:12 -0700)
include/vlog.h
secchan/secchan.8.in
utilities/Makefile.am
utilities/ofp-discover.8.in [new file with mode: 0644]
utilities/ofp-discover.c [new file with mode: 0644]

index fe5b432..2ecaa41 100644 (file)
@@ -76,6 +76,7 @@ enum vlog_facility vlog_get_facility_val(const char *name);
         VLOG_MODULE(mac_learning)               \
         VLOG_MODULE(netdev)                     \
         VLOG_MODULE(netlink)                    \
+        VLOG_MODULE(ofp_discover)               \
         VLOG_MODULE(poll_loop)                  \
         VLOG_MODULE(secchan)                    \
         VLOG_MODULE(rconn)                      \
index 512ee92..4340623 100644 (file)
@@ -160,8 +160,8 @@ has started up.  Thus, start \fBsecchan\fR without configuring
 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.
+with a POSIX extended 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
@@ -322,6 +322,7 @@ Prints version information to the console.
 .SH "SEE ALSO"
 
 .BR dpctl (8),
+.BR ofp-discover (8),
 .BR controller (8),
 .BR ofp-pki (8),
 .BR vlogconf (8),
index 6202bdb..d9a8b28 100644 (file)
@@ -1,14 +1,14 @@
 include ../Make.vars
 
-bin_PROGRAMS = vlogconf dpctl
+bin_PROGRAMS = vlogconf dpctl ofp-discover
 bin_SCRIPTS = ofp-pki
 noinst_SCRIPTS = ofp-pki-cgi
 
-EXTRA_DIST = ofp-pki.in ofp-pki-cgi.in ofp-pki.8.in
-DISTCLEANFILES = ofp-pki ofp-pki-cgi ofp-pki.8
+EXTRA_DIST = ofp-pki.in ofp-pki-cgi.in ofp-pki.8.in ofp-discover.8.in
+DISTCLEANFILES = ofp-pki ofp-pki-cgi ofp-pki.8 ofp-discover.8
 
 dist_man_MANS = vlogconf.8 dpctl.8
-man_MANS = ofp-pki.8
+man_MANS = ofp-pki.8 ofp-discover.8
 
 dpctl_SOURCES = dpctl.c
 dpctl_LDADD = ../lib/libopenflow.a $(FAULT_LIBS) $(SSL_LIBS)
@@ -16,6 +16,9 @@ dpctl_LDADD = ../lib/libopenflow.a $(FAULT_LIBS) $(SSL_LIBS)
 vlogconf_SOURCES = vlogconf.c
 vlogconf_LDADD = ../lib/libopenflow.a
 
+ofp_discover_SOURCES = ofp-discover.c
+ofp_discover_LDADD = ../lib/libopenflow.a
+
 pkidir = $(pkgdatadir)/pki
 
 ofp-pki: ofp-pki.in Makefile
@@ -26,3 +29,5 @@ ofp-pki-cgi: ofp-pki-cgi.in Makefile
        chmod +x ofp-pki-cgi
 ofp-pki.8: ofp-pki.8.in Makefile
        ($(do_subst) && $(ro_man)) < $(srcdir)/ofp-pki.8.in > ofp-pki.8
+ofp-discover.8: ofp-discover.8.in Makefile
+       ($(do_subst) && $(ro_man)) < $(srcdir)/ofp-discover.8.in > ofp-discover.8
diff --git a/utilities/ofp-discover.8.in b/utilities/ofp-discover.8.in
new file mode 100644 (file)
index 0000000..19923a3
--- /dev/null
@@ -0,0 +1,131 @@
+.TH ofp\-discover 8 "May 2008" "OpenFlow" "OpenFlow Manual"
+
+.SH NAME
+ofp\-discover \- controller discovery utility
+
+.SH SYNOPSIS
+.B ofp\-discover
+[\fIoptions\fR] \fInetdev\fR [\fInetdev\fR...]
+
+.SH DESCRIPTION
+The \fBofp\-discover\fR program attempts to discover the location of
+an OpenFlow controller on one of the network devices listed on the
+command line.  It repeatedly broadcasts a DHCP request with vendor
+class identifier \fBOpenFlow\fR on each network device until it
+receives an acceptable DHCP response.  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).
+
+When \fBofp\-discover\fR receives an acceptable response, it prints
+the details of the response on \fBstdout\fR.  Then, by default, it
+configures the network device on which the response was received with
+the received IP address, netmask, and default gateway, and detaches
+itself to the background.
+
+.SH OPTIONS
+.TP
+\fB--accept-vconn=\fIregex\fR
+By default, \fBofp\-discover\fR accepts any controller location
+advertised over DHCP.  With this option, only controllers whose names
+match POSIX extended regular expression \fIregex\fR will be accepted.
+Specifying \fBssl:.*\fR for \fIregex\fR, for example, would cause only
+SSL controller connections to be accepted.
+
+The \fIregex\fR is implicitly anchored at the beginning of the
+controller location string, as if it begins with \fB^\fR.
+
+.TP
+\fB--exit-without-bind\fR
+By default, \fBofp\-discover\fR binds the network device that receives
+the first acceptable response to the IP address received over DHCP.
+With this option, the configuration of the network device is not
+changed at all, except to bring it up if it is initially down, and
+\fBofp\-discover\fR will exit immediately after it receives an
+acceptable DHCP response.
+
+This option is mutually exclusive with \fB--exit-after-bind\fR and
+\fB--no-detach\fR.
+
+.TP
+\fB--exit-after-bind\fR
+By default, after it receives an acceptable DHCP response,
+\fBofp\-discover\fR detaches itself from the foreground session and
+runs in the background maintaining the DHCP lease as necessary.  With
+this option, \fBofp\-discover\fR will exit immediately after it
+receives an acceptable DHCP response and configures the network device
+with the received IP address.  The address obtained via DHCP could
+therefore be used past the expiration of its lease.
+
+This option is mutually exclusive with \fB--exit-without-bind\fR and
+\fB--no-detach\fR.
+
+.TP
+\fB--no-detach\fR
+By default, \fBofp\-discover\fR runs in the foreground until it obtains
+an acceptable DHCP response, then it detaches itself from the
+foreground session and run as a background process.  This option
+prevents \fBofp\-discover\fR from detaching, causing it to run in the
+foreground even after it obtains a DHCP response.
+
+This option is mutually exclusive with \fB--exit-without-bind\fR and
+\fB--exit-after-bind\fR.
+
+.TP
+\fB-P\fR[\fIpidfile\fR], \fB--pidfile\fR[\fB=\fIpidfile\fR]
+Causes a file (by default, \fBofp\-discover.pid\fR) to be created indicating
+the PID of the running process.  If \fIpidfile\fR is not specified, or
+if it does not begin with \fB/\fR, then it is created in
+\fB@rundir@\fR.
+
+The \fIpidfile\fR is created when \fBofp\-discover\fR detaches, so
+this this option has no effect when one of \fB--exit-without-bind\fR,
+\fB--exit-after-bind\fR, or \fB--no-detach\fR is also given.
+
+.TP
+.BR \-h ", " \-\^\-help
+Prints a brief help message to the console.
+
+.TP
+\fB-v\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--verbose=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]
+Sets the logging level for \fImodule\fR in \fIfacility\fR to
+\fIlevel\fR.  The \fImodule\fR may be any valid module name (as
+displayed by the \fB--list\fR action on \fBvlogconf\fR(8)), or the
+special name \fBANY\fR to set the logging levels for all modules.  The
+\fIfacility\fR may be \fBsyslog\fR or \fBconsole\fR to set the levels
+for logging to the system log or to the console, respectively, or
+\fBANY\fR to set the logging levels for both facilities.  If it is
+omitted, \fIfacility\fR defaults to \fBANY\fR.  The \fIlevel\fR must
+be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or \fBdbg\fR, designating
+the minimum severity of a message for it to be logged.  If it is
+omitted, \fIlevel\fR defaults to \fBdbg\fR.
+
+.TP
+\fB-v\fR, \fB--verbose\fR
+Sets the maximum logging verbosity level, equivalent to
+\fB--verbose=ANY:ANY:dbg\fR.
+
+.TP
+.BR \-V ", " \-\^\-version
+Prints version information to the console.
+
+.SH BUGS
+
+If the network devices specified on the command line have been added
+to an OpenFlow switch with \fBdpctl addif\fR, then controller
+discovery will fail because \fBofp\-discover\fR will not be able to
+see DHCP responses, even though tools such as \fBtcpdump\fR(8) and
+\fBwireshark\fR(1) can see them on the wire.  This is because of the
+structure of the Linux kernel networking stack, which hands packets
+first to programs that listen for all arriving packets, then to
+OpenFlow, then to programs that listen for a specific kind of packet.
+OpenFlow consumes all the packets handed to it, so tools like
+\fBtcpdump\fR that look at all packets will see packets arriving on
+OpenFlow interfaces, but \fRofp\-discover\fR, which listens only for
+arriving IP packets, will not.
+
+.SH "SEE ALSO"
+
+.BR secchan (8),
+.BR ofp-pki (8)
diff --git a/utilities/ofp-discover.c b/utilities/ofp-discover.c
new file mode 100644 (file)
index 0000000..5c78d43
--- /dev/null
@@ -0,0 +1,410 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include <config.h>
+#include <getopt.h>
+#include <limits.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "command-line.h"
+#include "daemon.h"
+#include "dhcp-client.h"
+#include "dhcp.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "netdev.h"
+#include "poll-loop.h"
+#include "util.h"
+#include "vlog-socket.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ofp_discover
+
+struct iface {
+    const char *name;
+    struct dhclient *dhcp;
+};
+
+/* The interfaces that we serve. */
+static struct iface *ifaces;
+static int n_ifaces;
+
+/* --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;
+
+/* --exit-without-bind: Exit after discovering the controller, without binding
+ * the network device to an IP address? */
+static bool exit_without_bind;
+
+/* --exit-after-bind: Exit after discovering the controller, after binding the
+ * network device to an IP address? */
+static bool exit_after_bind;
+
+static bool iface_init(struct iface *, const char *netdev_name);
+static void release_ifaces(void *aux UNUSED);
+
+static void parse_options(int argc, char *argv[]);
+static void usage(void) NO_RETURN;
+
+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[])
+{
+    int retval;
+    int i;
+
+    set_program_name(argv[0]);
+    vlog_init();
+    parse_options(argc, argv);
+
+    argc -= optind;
+    argv += optind;
+    if (argc < 1) {
+        fatal(0, "need at least one non-option argument; "
+              "use --help for usage");
+    }
+
+    ifaces = xmalloc(argc * sizeof *ifaces);
+    n_ifaces = 0;
+    for (i = 0; i < argc; i++) {
+        if (iface_init(&ifaces[n_ifaces], argv[i])) {
+            n_ifaces++;
+        }
+    }
+    if (!n_ifaces) {
+        fatal(0, "failed to initialize any DHCP clients");
+    }
+
+    for (i = 0; i < n_ifaces; i++) {
+        struct iface *iface = &ifaces[i];
+        dhclient_init(iface->dhcp, 0);
+    }
+    fatal_signal_add_hook(release_ifaces, NULL);
+
+    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);
+    }
+
+    retval = vlog_server_listen(NULL, NULL);
+    if (retval) {
+        fatal(retval, "Could not listen for vlog connections");
+    }
+
+    signal(SIGPIPE, SIG_IGN);
+    for (;;) {
+        fatal_signal_block();
+        for (i = 0; i < n_ifaces; i++) {
+            struct iface *iface = &ifaces[i];
+            dhclient_run(iface->dhcp);
+            if (dhclient_changed(iface->dhcp)) {
+                bool is_bound = dhclient_is_bound(iface->dhcp);
+                int j;
+
+                /* Configure network device. */
+                if (!exit_without_bind) {
+                    dhclient_configure_netdev(iface->dhcp);
+                }
+
+                if (is_bound) {
+                    static bool detached = false;
+                    struct ds ds;
+
+                    /* Disable timeout, since discovery was successful. */
+                    alarm(0);
+
+                    /* Print discovered parameters. */
+                    ds_init(&ds);
+                    dhcp_msg_to_string(dhclient_get_config(iface->dhcp),
+                                       true, &ds);
+                    fputs(ds_cstr(&ds), stdout);
+                    putchar('\n');
+                    fflush(stdout);
+                    ds_destroy(&ds);
+
+                    /* Exit if the user requested it. */
+                    if (exit_without_bind) {
+                        VLOG_DBG("exiting because of successful binding on %s "
+                                 "and --exit-without-bind specified",
+                                 iface->name);
+                        exit(0);
+                    }
+                    if (exit_after_bind) {
+                        VLOG_DBG("exiting because of successful binding on %s "
+                                 "and --exit-after-bind specified",
+                                 iface->name);
+                        exit(0);
+                    }
+
+                    /* Detach into background, if we haven't already. */
+                    if (!detached) {
+                        detached = true;
+                        daemonize();
+                    }
+                }
+
+                /* We only want an address on a single one of our interfaces.
+                 * So: if we have an address on this interface, stop looking
+                 * for one on the others; if we don't have an address on this
+                 * interface, start looking everywhere. */
+                for (j = 0; j < n_ifaces; j++) {
+                    struct iface *if2 = &ifaces[j];
+                    if (iface != if2) {
+                        if (is_bound) {
+                            dhclient_release(if2->dhcp);
+                        } else {
+                            dhclient_init(if2->dhcp, 0);
+                        }
+                    }
+                }
+            }
+        }
+        for (i = 0; i < n_ifaces; i++) {
+            struct iface *iface = &ifaces[i];
+            dhclient_wait(iface->dhcp);
+        }
+        fatal_signal_unblock();
+        poll_block();
+    }
+
+    return 0;
+}
+
+static bool
+iface_init(struct iface *iface, const char *netdev_name)
+{
+    int retval;
+
+    iface->name = netdev_name;
+    iface->dhcp = NULL;
+
+    if (exit_after_bind) {
+        /* Bring this interface up permanently, so that the bound address
+         * persists past program termination. */
+        struct netdev *netdev;
+
+        retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
+        if (retval) {
+            error(retval, "Could not open %s device", iface->name);
+            return false;
+        }
+        retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
+        if (retval) {
+            error(retval, "Could not bring %s device up", iface->name);
+            return false;
+        }
+        netdev_close(netdev);
+    }
+
+    retval = dhclient_create(iface->name, modify_dhcp_request,
+                             validate_dhcp_offer, NULL, &iface->dhcp);
+    if (retval) {
+        error(retval, "%s: failed to initialize DHCP client", iface->name);
+        return false;
+    }
+
+    return true;
+}
+
+static void
+release_ifaces(void *aux UNUSED)
+{
+    int i;
+
+    for (i = 0; i < n_ifaces; i++) {
+        struct dhclient *dhcp = ifaces[i].dhcp;
+        dhclient_release(dhcp);
+        if (dhclient_changed(dhcp)) {
+            dhclient_configure_netdev(dhcp);
+        }
+    }
+}
+
+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_ACCEPT_VCONN = UCHAR_MAX + 1,
+        OPT_EXIT_WITHOUT_BIND,
+        OPT_EXIT_AFTER_BIND,
+        OPT_NO_DETACH,
+    };
+    static struct option long_options[] = {
+        {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
+        {"exit-without-bind", no_argument, 0, OPT_EXIT_WITHOUT_BIND},
+        {"exit-after-bind", no_argument, 0, OPT_EXIT_AFTER_BIND},
+        {"no-detach",   no_argument, 0, OPT_NO_DETACH},
+        {"timeout",     required_argument, 0, 't'},
+        {"pidfile",     optional_argument, 0, 'P'},
+        {"verbose",     optional_argument, 0, 'v'},
+        {"help",        no_argument, 0, 'h'},
+        {"version",     no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+    bool detach_after_bind = true;
+
+    for (;;) {
+        unsigned long int timeout;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_ACCEPT_VCONN:
+            accept_controller_re = (optarg[0] == '^'
+                                    ? optarg
+                                    : xasprintf("^%s", optarg));
+            break;
+
+        case OPT_EXIT_WITHOUT_BIND:
+            exit_without_bind = true;
+            break;
+
+        case OPT_EXIT_AFTER_BIND:
+            exit_after_bind = true;
+            break;
+
+        case OPT_NO_DETACH:
+            detach_after_bind = false;
+            break;
+
+        case 'P':
+            set_pidfile(optarg);
+            break;
+
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout <= 0) {
+                fatal(0, "value %s on -t or --timeout is not at least 1",
+                      optarg);
+            } else if (timeout < UINT_MAX) {
+                /* Add 1 because historical implementations allow an alarm to
+                 * occur up to a second early. */
+                alarm(timeout + 1);
+            } else {
+                alarm(UINT_MAX);
+            }
+            signal(SIGALRM, SIG_DFL);
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            printf("%s "VERSION" compiled "__DATE__" "__TIME__"\n", argv[0]);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    if ((exit_without_bind + exit_after_bind + !detach_after_bind) > 1) {
+        fatal(0, "--exit-without-bind, --exit-after-bind, and --no-detach "
+              "are mutually exclusive");
+    }
+    if (detach_after_bind) {
+        set_detach();
+    }
+}
+
+static void
+usage(void)
+{
+    printf("%s: a tool for discovering OpenFlow controllers.\n"
+           "usage: %s [OPTIONS] NETDEV [NETDEV...]\n"
+           "where each NETDEV is a network device on which to perform\n"
+           "controller discovery.\n"
+           "\nOrdinarily, ofp-discover runs in the foreground until it\n"
+           "obtains an IP address and discovers an OpenFlow controller via\n"
+           "DHCP, then it prints information about the controller to stdout\n"
+           "and detaches to the background to maintain the IP address lease.\n"
+           "\nNetworking options:\n"
+           "  --accept-vconn=REGEX    accept matching discovered controllers\n"
+           "  --exit-without-bind     exit after discovery, without binding\n"
+           "  --exit-after-bind       exit after discovery, after binding\n"
+           "  --no-detach             do not detach after discovery\n"
+           "\nOther options:\n"
+           "  -t, --timeout=SECS      give up discovery after SECS seconds\n"
+           "  -P, --pidfile[=FILE]    create pidfile (default: %s/%s.pid)\n"
+           "  -v, --verbose=MODULE[:FACILITY[:LEVEL]]  set logging levels\n"
+           "  -v, --verbose           set maximum verbosity level\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n",
+           program_name, program_name, RUNDIR, program_name);
+    exit(EXIT_SUCCESS);
+}