diff options
Diffstat (limited to 'utilities/ovs-discover.c')
-rw-r--r-- | utilities/ovs-discover.c | 405 |
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); +} |