--- /dev/null
+.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)
--- /dev/null
+/* 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);
+}