5fed6531b9
This way it becomes possible to more reliably refer to the controller and the PKI server using hostnames.
412 lines
13 KiB
C
412 lines
13 KiB
C
/* 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);
|
|
dhclient_update_resolv_conf(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);
|
|
}
|