tests: Add test suite for packets.h.
[openvswitch] / utilities / ovs-discover.c
1 /*
2  * Copyright (c) 2008, 2009, 2010 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 #include "vlog.h"
37
38 VLOG_DEFINE_THIS_MODULE(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 OVS_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     proctitle_init(argc, argv);
79     set_program_name(argv[0]);
80     parse_options(argc, argv);
81
82     argc -= optind;
83     argv += optind;
84     if (argc < 1) {
85         ovs_fatal(0, "need at least one non-option argument; "
86                   "use --help for usage");
87     }
88
89     ifaces = xmalloc(argc * sizeof *ifaces);
90     n_ifaces = 0;
91     for (i = 0; i < argc; i++) {
92         if (iface_init(&ifaces[n_ifaces], argv[i])) {
93             n_ifaces++;
94         }
95     }
96     if (!n_ifaces) {
97         ovs_fatal(0, "failed to initialize any DHCP clients");
98     }
99
100     for (i = 0; i < n_ifaces; i++) {
101         struct iface *iface = &ifaces[i];
102         dhclient_init(iface->dhcp, 0);
103     }
104     fatal_signal_add_hook(release_ifaces, NULL, NULL, true);
105
106     retval = regcomp(&accept_controller_regex, accept_controller_re,
107                      REG_NOSUB | REG_EXTENDED);
108     if (retval) {
109         size_t length = regerror(retval, &accept_controller_regex, NULL, 0);
110         char *buffer = xmalloc(length);
111         regerror(retval, &accept_controller_regex, buffer, length);
112         ovs_fatal(0, "%s: %s", accept_controller_re, buffer);
113     }
114
115     retval = unixctl_server_create(NULL, &unixctl);
116     if (retval) {
117         exit(EXIT_FAILURE);
118     }
119
120     die_if_already_running();
121
122     signal(SIGPIPE, SIG_IGN);
123     for (;;) {
124         for (i = 0; i < n_ifaces; i++) {
125             struct iface *iface = &ifaces[i];
126             dhclient_run(iface->dhcp);
127             if (dhclient_changed(iface->dhcp)) {
128                 bool is_bound = dhclient_is_bound(iface->dhcp);
129                 int j;
130
131                 /* Configure network device. */
132                 if (!exit_without_bind) {
133                     dhclient_configure_netdev(iface->dhcp);
134                     dhclient_update_resolv_conf(iface->dhcp);
135                 }
136
137                 if (is_bound) {
138                     static bool detached = false;
139                     struct ds ds;
140
141                     /* Disable timeout, since discovery was successful. */
142                     time_alarm(0);
143
144                     /* Print discovered parameters. */
145                     ds_init(&ds);
146                     dhcp_msg_to_string(dhclient_get_config(iface->dhcp),
147                                        true, &ds);
148                     fputs(ds_cstr(&ds), stdout);
149                     putchar('\n');
150                     fflush(stdout);
151                     ds_destroy(&ds);
152
153                     /* Exit if the user requested it. */
154                     if (exit_without_bind) {
155                         VLOG_DBG("exiting because of successful binding on %s "
156                                  "and --exit-without-bind specified",
157                                  iface->name);
158                         exit(0);
159                     }
160                     if (exit_after_bind) {
161                         VLOG_DBG("exiting because of successful binding on %s "
162                                  "and --exit-after-bind specified",
163                                  iface->name);
164                         exit(0);
165                     }
166
167                     /* Detach into background, if we haven't already. */
168                     if (!detached) {
169                         detached = true;
170                         daemonize();
171                     }
172                 }
173
174                 /* We only want an address on a single one of our interfaces.
175                  * So: if we have an address on this interface, stop looking
176                  * for one on the others; if we don't have an address on this
177                  * interface, start looking everywhere. */
178                 for (j = 0; j < n_ifaces; j++) {
179                     struct iface *if2 = &ifaces[j];
180                     if (iface != if2) {
181                         if (is_bound) {
182                             dhclient_release(if2->dhcp);
183                         } else {
184                             dhclient_init(if2->dhcp, 0);
185                         }
186                     }
187                 }
188             }
189         }
190         unixctl_server_run(unixctl);
191         for (i = 0; i < n_ifaces; i++) {
192             struct iface *iface = &ifaces[i];
193             dhclient_wait(iface->dhcp);
194         }
195         unixctl_server_wait(unixctl);
196         poll_block();
197     }
198
199     return 0;
200 }
201
202 static bool
203 iface_init(struct iface *iface, const char *netdev_name)
204 {
205     int retval;
206
207     iface->name = netdev_name;
208     iface->dhcp = NULL;
209
210     if (exit_after_bind) {
211         /* Bring this interface up permanently, so that the bound address
212          * persists past program termination. */
213         struct netdev *netdev;
214
215         retval = netdev_open_default(iface->name, &netdev);
216         if (retval) {
217             ovs_error(retval, "Could not open %s device", iface->name);
218             return false;
219         }
220         retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
221         if (retval) {
222             ovs_error(retval, "Could not bring %s device up", iface->name);
223             return false;
224         }
225         netdev_close(netdev);
226     }
227
228     retval = dhclient_create(iface->name, modify_dhcp_request,
229                              validate_dhcp_offer, NULL, &iface->dhcp);
230     if (retval) {
231         ovs_error(retval, "%s: failed to initialize DHCP client", iface->name);
232         return false;
233     }
234
235     return true;
236 }
237
238 static void
239 release_ifaces(void *aux OVS_UNUSED)
240 {
241     int i;
242
243     for (i = 0; i < n_ifaces; i++) {
244         struct dhclient *dhcp = ifaces[i].dhcp;
245         dhclient_release(dhcp);
246         if (dhclient_changed(dhcp)) {
247             dhclient_configure_netdev(dhcp);
248         }
249     }
250 }
251
252 static void
253 modify_dhcp_request(struct dhcp_msg *msg, void *aux OVS_UNUSED)
254 {
255     dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
256 }
257
258 static bool
259 validate_dhcp_offer(const struct dhcp_msg *msg, void *aux OVS_UNUSED)
260 {
261     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
262     char *vconn_name;
263     bool accept;
264
265     vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
266     if (!vconn_name) {
267         VLOG_WARN_RL(&rl, "rejecting DHCP offer missing controller vconn");
268         return false;
269     }
270     accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0);
271     free(vconn_name);
272     return accept;
273 }
274
275 static void
276 parse_options(int argc, char *argv[])
277 {
278     enum {
279         OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
280         OPT_EXIT_WITHOUT_BIND,
281         OPT_EXIT_AFTER_BIND,
282         OPT_NO_DETACH,
283         VLOG_OPTION_ENUMS
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         {"help",        no_argument, 0, 'h'},
294         {"version",     no_argument, 0, 'V'},
295         VLOG_LONG_OPTIONS,
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         VLOG_OPTION_HANDLERS
356
357         case '?':
358             exit(EXIT_FAILURE);
359
360         default:
361             abort();
362         }
363     }
364     free(short_options);
365
366     if ((exit_without_bind + exit_after_bind + !detach_after_bind) > 1) {
367         ovs_fatal(0, "--exit-without-bind, --exit-after-bind, and --no-detach "
368                   "are mutually exclusive");
369     }
370     if (detach_after_bind) {
371         set_detach();
372     }
373 }
374
375 static void
376 usage(void)
377 {
378     printf("%s: a tool for discovering OpenFlow controllers.\n"
379            "usage: %s [OPTIONS] NETDEV [NETDEV...]\n"
380            "where each NETDEV is a network device on which to perform\n"
381            "controller discovery.\n"
382            "\nOrdinarily, ovs-discover runs in the foreground until it\n"
383            "obtains an IP address and discovers an OpenFlow controller via\n"
384            "DHCP, then it prints information about the controller to stdout\n"
385            "and detaches to the background to maintain the IP address lease.\n"
386            "\nNetworking options:\n"
387            "  --accept-vconn=REGEX    accept matching discovered controllers\n"
388            "  --exit-without-bind     exit after discovery, without binding\n"
389            "  --exit-after-bind       exit after discovery, after binding\n"
390            "  --no-detach             do not detach after discovery\n",
391            program_name, program_name);
392     vlog_usage();
393     printf("\nOther options:\n"
394            "  -t, --timeout=SECS      give up discovery after SECS seconds\n"
395            "  --pidfile[=FILE]        create pidfile (default: %s/%s.pid)\n"
396            "  --overwrite-pidfile     with --pidfile, start even if already "
397                                       "running\n"
398            "  -h, --help              display this help message\n"
399            "  -V, --version           display version information\n",
400            ovs_rundir(), program_name);
401     exit(EXIT_SUCCESS);
402 }