summaryrefslogtreecommitdiff
path: root/utilities/ovs-discover.c
diff options
context:
space:
mode:
Diffstat (limited to 'utilities/ovs-discover.c')
-rw-r--r--utilities/ovs-discover.c405
1 files changed, 405 insertions, 0 deletions
diff --git a/utilities/ovs-discover.c b/utilities/ovs-discover.c
new file mode 100644
index 000000000..b664321ff
--- /dev/null
+++ b/utilities/ovs-discover.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#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 "dirs.h"
+#include "dynamic-string.h"
+#include "fatal-signal.h"
+#include "netdev.h"
+#include "poll-loop.h"
+#include "timeval.h"
+#include "unixctl.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovs_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[])
+{
+ struct unixctl_server *unixctl;
+ int retval;
+ int i;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+
+ argc -= optind;
+ argv += optind;
+ if (argc < 1) {
+ ovs_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) {
+ ovs_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, true);
+
+ 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);
+ ovs_fatal(0, "%s: %s", accept_controller_re, buffer);
+ }
+
+ retval = unixctl_server_create(NULL, &unixctl);
+ if (retval) {
+ ovs_fatal(retval, "Could not listen for unixctl connections");
+ }
+
+ die_if_already_running();
+
+ 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);
+ dhclient_update_resolv_conf(iface->dhcp);
+ }
+
+ if (is_bound) {
+ static bool detached = false;
+ struct ds ds;
+
+ /* Disable timeout, since discovery was successful. */
+ time_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);
+ }
+ }
+ }
+ }
+ }
+ unixctl_server_run(unixctl);
+ for (i = 0; i < n_ifaces; i++) {
+ struct iface *iface = &ifaces[i];
+ dhclient_wait(iface->dhcp);
+ }
+ unixctl_server_wait(unixctl);
+ 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) {
+ ovs_error(retval, "Could not open %s device", iface->name);
+ return false;
+ }
+ retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
+ if (retval) {
+ ovs_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) {
+ ovs_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 UNUSED)
+{
+ dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
+}
+
+static bool
+validate_dhcp_offer(const struct dhcp_msg *msg, void *aux UNUSED)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
+ char *vconn_name;
+ bool accept;
+
+ vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
+ if (!vconn_name) {
+ VLOG_WARN_RL(&rl, "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'},
+ {"force", no_argument, 0, 'f'},
+ {"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 'f':
+ ignore_existing_pidfile();
+ break;
+
+ case 't':
+ timeout = strtoul(optarg, NULL, 10);
+ if (timeout <= 0) {
+ ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+ optarg);
+ } else {
+ time_alarm(timeout);
+ }
+ signal(SIGALRM, SIG_DFL);
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 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) {
+ ovs_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, ovs-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",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -t, --timeout=SECS give up discovery after SECS seconds\n"
+ " -P, --pidfile[=FILE] create pidfile (default: %s/%s.pid)\n"
+ " -f, --force with -P, start even if already running\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n",
+ ovs_rundir, program_name);
+ exit(EXIT_SUCCESS);
+}