summaryrefslogtreecommitdiff
path: root/utilities/ovs-discover.c
blob: b664321ff392d400b827d2d047524ed4fcbc7d23 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
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);
}