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