3aa28fad88dd490c9ad583e2f98f8220cbf465e5
[sliver-openvswitch.git] / utilities / ovs-discover.c
1 /*
2  * Copyright (c) 2008, 2009 Nicira Networks.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <config.h>
18 #include <getopt.h>
19 #include <limits.h>
20 #include <regex.h>
21 #include <signal.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include "command-line.h"
25 #include "daemon.h"
26 #include "dhcp-client.h"
27 #include "dhcp.h"
28 #include "dirs.h"
29 #include "dynamic-string.h"
30 #include "fatal-signal.h"
31 #include "netdev.h"
32 #include "poll-loop.h"
33 #include "timeval.h"
34 #include "unixctl.h"
35 #include "util.h"
36
37 #include "vlog.h"
38 #define THIS_MODULE VLM_ovs_discover
39
40 struct iface {
41     const char *name;
42     struct dhclient *dhcp;
43 };
44
45 /* The interfaces that we serve. */
46 static struct iface *ifaces;
47 static int n_ifaces;
48
49 /* --accept-vconn: Regular expression specifying the class of controller vconns
50  * that we will accept during autodiscovery. */
51 static const char *accept_controller_re = "tcp:.*";
52 static regex_t accept_controller_regex;
53
54 /* --exit-without-bind: Exit after discovering the controller, without binding
55  * the network device to an IP address? */
56 static bool exit_without_bind;
57
58 /* --exit-after-bind: Exit after discovering the controller, after binding the
59  * network device to an IP address? */
60 static bool exit_after_bind;
61
62 static bool iface_init(struct iface *, const char *netdev_name);
63 static void release_ifaces(void *aux UNUSED);
64
65 static void parse_options(int argc, char *argv[]);
66 static void usage(void) NO_RETURN;
67
68 static void modify_dhcp_request(struct dhcp_msg *, void *aux);
69 static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
70
71 int
72 main(int argc, char *argv[])
73 {
74     struct unixctl_server *unixctl;
75     int retval;
76     int i;
77
78     set_program_name(argv[0]);
79     time_init();
80     vlog_init();
81     parse_options(argc, argv);
82
83     argc -= optind;
84     argv += optind;
85     if (argc < 1) {
86         ovs_fatal(0, "need at least one non-option argument; "
87                   "use --help for usage");
88     }
89
90     ifaces = xmalloc(argc * sizeof *ifaces);
91     n_ifaces = 0;
92     for (i = 0; i < argc; i++) {
93         if (iface_init(&ifaces[n_ifaces], argv[i])) {
94             n_ifaces++;
95         }
96     }
97     if (!n_ifaces) {
98         ovs_fatal(0, "failed to initialize any DHCP clients");
99     }
100
101     for (i = 0; i < n_ifaces; i++) {
102         struct iface *iface = &ifaces[i];
103         dhclient_init(iface->dhcp, 0);
104     }
105     fatal_signal_add_hook(release_ifaces, NULL, true);
106
107     retval = regcomp(&accept_controller_regex, accept_controller_re,
108                      REG_NOSUB | REG_EXTENDED);
109     if (retval) {
110         size_t length = regerror(retval, &accept_controller_regex, NULL, 0);
111         char *buffer = xmalloc(length);
112         regerror(retval, &accept_controller_regex, buffer, length);
113         ovs_fatal(0, "%s: %s", accept_controller_re, buffer);
114     }
115
116     retval = unixctl_server_create(NULL, &unixctl);
117     if (retval) {
118         ovs_fatal(retval, "Could not listen for unixctl connections");
119     }
120
121     die_if_already_running();
122
123     signal(SIGPIPE, SIG_IGN);
124     for (;;) {
125         for (i = 0; i < n_ifaces; i++) {
126             struct iface *iface = &ifaces[i];
127             dhclient_run(iface->dhcp);
128             if (dhclient_changed(iface->dhcp)) {
129                 bool is_bound = dhclient_is_bound(iface->dhcp);
130                 int j;
131
132                 /* Configure network device. */
133                 if (!exit_without_bind) {
134                     dhclient_configure_netdev(iface->dhcp);
135                     dhclient_update_resolv_conf(iface->dhcp);
136                 }
137
138                 if (is_bound) {
139                     static bool detached = false;
140                     struct ds ds;
141
142                     /* Disable timeout, since discovery was successful. */
143                     time_alarm(0);
144
145                     /* Print discovered parameters. */
146                     ds_init(&ds);
147                     dhcp_msg_to_string(dhclient_get_config(iface->dhcp),
148                                        true, &ds);
149                     fputs(ds_cstr(&ds), stdout);
150                     putchar('\n');
151                     fflush(stdout);
152                     ds_destroy(&ds);
153
154                     /* Exit if the user requested it. */
155                     if (exit_without_bind) {
156                         VLOG_DBG("exiting because of successful binding on %s "
157                                  "and --exit-without-bind specified",
158                                  iface->name);
159                         exit(0);
160                     }
161                     if (exit_after_bind) {
162                         VLOG_DBG("exiting because of successful binding on %s "
163                                  "and --exit-after-bind specified",
164                                  iface->name);
165                         exit(0);
166                     }
167
168                     /* Detach into background, if we haven't already. */
169                     if (!detached) {
170                         detached = true;
171                         daemonize();
172                     }
173                 }
174
175                 /* We only want an address on a single one of our interfaces.
176                  * So: if we have an address on this interface, stop looking
177                  * for one on the others; if we don't have an address on this
178                  * interface, start looking everywhere. */
179                 for (j = 0; j < n_ifaces; j++) {
180                     struct iface *if2 = &ifaces[j];
181                     if (iface != if2) {
182                         if (is_bound) {
183                             dhclient_release(if2->dhcp);
184                         } else {
185                             dhclient_init(if2->dhcp, 0);
186                         }
187                     }
188                 }
189             }
190         }
191         unixctl_server_run(unixctl);
192         for (i = 0; i < n_ifaces; i++) {
193             struct iface *iface = &ifaces[i];
194             dhclient_wait(iface->dhcp);
195         }
196         unixctl_server_wait(unixctl);
197         poll_block();
198     }
199
200     return 0;
201 }
202
203 static bool
204 iface_init(struct iface *iface, const char *netdev_name)
205 {
206     int retval;
207
208     iface->name = netdev_name;
209     iface->dhcp = NULL;
210
211     if (exit_after_bind) {
212         /* Bring this interface up permanently, so that the bound address
213          * persists past program termination. */
214         struct netdev *netdev;
215
216         retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
217         if (retval) {
218             ovs_error(retval, "Could not open %s device", iface->name);
219             return false;
220         }
221         retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
222         if (retval) {
223             ovs_error(retval, "Could not bring %s device up", iface->name);
224             return false;
225         }
226         netdev_close(netdev);
227     }
228
229     retval = dhclient_create(iface->name, modify_dhcp_request,
230                              validate_dhcp_offer, NULL, &iface->dhcp);
231     if (retval) {
232         ovs_error(retval, "%s: failed to initialize DHCP client", iface->name);
233         return false;
234     }
235
236     return true;
237 }
238
239 static void
240 release_ifaces(void *aux UNUSED)
241 {
242     int i;
243
244     for (i = 0; i < n_ifaces; i++) {
245         struct dhclient *dhcp = ifaces[i].dhcp;
246         dhclient_release(dhcp);
247         if (dhclient_changed(dhcp)) {
248             dhclient_configure_netdev(dhcp);
249         }
250     }
251 }
252
253 static void
254 modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED)
255 {
256     dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
257 }
258
259 static bool
260 validate_dhcp_offer(const struct dhcp_msg *msg, void *aux UNUSED)
261 {
262     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
263     char *vconn_name;
264     bool accept;
265
266     vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
267     if (!vconn_name) {
268         VLOG_WARN_RL(&rl, "rejecting DHCP offer missing controller vconn");
269         return false;
270     }
271     accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0);
272     free(vconn_name);
273     return accept;
274 }
275
276 static void
277 parse_options(int argc, char *argv[])
278 {
279     enum {
280         OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
281         OPT_EXIT_WITHOUT_BIND,
282         OPT_EXIT_AFTER_BIND,
283         OPT_NO_DETACH
284     };
285     static struct option long_options[] = {
286         {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
287         {"exit-without-bind", no_argument, 0, OPT_EXIT_WITHOUT_BIND},
288         {"exit-after-bind", no_argument, 0, OPT_EXIT_AFTER_BIND},
289         {"no-detach",   no_argument, 0, OPT_NO_DETACH},
290         {"timeout",     required_argument, 0, 't'},
291         {"pidfile",     optional_argument, 0, OPT_PIDFILE},
292         {"overwrite-pidfile", no_argument, 0, OPT_OVERWRITE_PIDFILE},
293         {"verbose",     optional_argument, 0, 'v'},
294         {"help",        no_argument, 0, 'h'},
295         {"version",     no_argument, 0, 'V'},
296         {0, 0, 0, 0},
297     };
298     char *short_options = long_options_to_short_options(long_options);
299     bool detach_after_bind = true;
300
301     for (;;) {
302         unsigned long int timeout;
303         int c;
304
305         c = getopt_long(argc, argv, short_options, long_options, NULL);
306         if (c == -1) {
307             break;
308         }
309
310         switch (c) {
311         case OPT_ACCEPT_VCONN:
312             accept_controller_re = (optarg[0] == '^'
313                                     ? optarg
314                                     : xasprintf("^%s", optarg));
315             break;
316
317         case OPT_EXIT_WITHOUT_BIND:
318             exit_without_bind = true;
319             break;
320
321         case OPT_EXIT_AFTER_BIND:
322             exit_after_bind = true;
323             break;
324
325         case OPT_NO_DETACH:
326             detach_after_bind = false;
327             break;
328
329         case OPT_PIDFILE:
330             set_pidfile(optarg);
331             break;
332
333         case OPT_OVERWRITE_PIDFILE:
334             ignore_existing_pidfile();
335             break;
336
337         case 't':
338             timeout = strtoul(optarg, NULL, 10);
339             if (timeout <= 0) {
340                 ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
341                           optarg);
342             } else {
343                 time_alarm(timeout);
344             }
345             signal(SIGALRM, SIG_DFL);
346             break;
347
348         case 'h':
349             usage();
350
351         case 'V':
352             OVS_PRINT_VERSION(0, 0);
353             exit(EXIT_SUCCESS);
354
355         case 'v':
356             vlog_set_verbosity(optarg);
357             break;
358
359         case '?':
360             exit(EXIT_FAILURE);
361
362         default:
363             abort();
364         }
365     }
366     free(short_options);
367
368     if ((exit_without_bind + exit_after_bind + !detach_after_bind) > 1) {
369         ovs_fatal(0, "--exit-without-bind, --exit-after-bind, and --no-detach "
370                   "are mutually exclusive");
371     }
372     if (detach_after_bind) {
373         set_detach();
374     }
375 }
376
377 static void
378 usage(void)
379 {
380     printf("%s: a tool for discovering OpenFlow controllers.\n"
381            "usage: %s [OPTIONS] NETDEV [NETDEV...]\n"
382            "where each NETDEV is a network device on which to perform\n"
383            "controller discovery.\n"
384            "\nOrdinarily, ovs-discover runs in the foreground until it\n"
385            "obtains an IP address and discovers an OpenFlow controller via\n"
386            "DHCP, then it prints information about the controller to stdout\n"
387            "and detaches to the background to maintain the IP address lease.\n"
388            "\nNetworking options:\n"
389            "  --accept-vconn=REGEX    accept matching discovered controllers\n"
390            "  --exit-without-bind     exit after discovery, without binding\n"
391            "  --exit-after-bind       exit after discovery, after binding\n"
392            "  --no-detach             do not detach after discovery\n",
393            program_name, program_name);
394     vlog_usage();
395     printf("\nOther options:\n"
396            "  -t, --timeout=SECS      give up discovery after SECS seconds\n"
397            "  --pidfile[=FILE]        create pidfile (default: %s/%s.pid)\n"
398            "  --overwrite-pidfile     with --pidfile, start even if already "
399                                       "running\n"
400            "  -h, --help              display this help message\n"
401            "  -V, --version           display version information\n",
402            ovs_rundir, program_name);
403     exit(EXIT_SUCCESS);
404 }