Merge citrix into master.
[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         fatal_signal_block();
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         fatal_signal_unblock();
199         poll_block();
200     }
201
202     return 0;
203 }
204
205 static bool
206 iface_init(struct iface *iface, const char *netdev_name)
207 {
208     int retval;
209
210     iface->name = netdev_name;
211     iface->dhcp = NULL;
212
213     if (exit_after_bind) {
214         /* Bring this interface up permanently, so that the bound address
215          * persists past program termination. */
216         struct netdev *netdev;
217
218         retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
219         if (retval) {
220             ovs_error(retval, "Could not open %s device", iface->name);
221             return false;
222         }
223         retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
224         if (retval) {
225             ovs_error(retval, "Could not bring %s device up", iface->name);
226             return false;
227         }
228         netdev_close(netdev);
229     }
230
231     retval = dhclient_create(iface->name, modify_dhcp_request,
232                              validate_dhcp_offer, NULL, &iface->dhcp);
233     if (retval) {
234         ovs_error(retval, "%s: failed to initialize DHCP client", iface->name);
235         return false;
236     }
237
238     return true;
239 }
240
241 static void
242 release_ifaces(void *aux UNUSED)
243 {
244     int i;
245
246     for (i = 0; i < n_ifaces; i++) {
247         struct dhclient *dhcp = ifaces[i].dhcp;
248         dhclient_release(dhcp);
249         if (dhclient_changed(dhcp)) {
250             dhclient_configure_netdev(dhcp);
251         }
252     }
253 }
254
255 static void
256 modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED)
257 {
258     dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
259 }
260
261 static bool
262 validate_dhcp_offer(const struct dhcp_msg *msg, void *aux UNUSED)
263 {
264     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
265     char *vconn_name;
266     bool accept;
267
268     vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
269     if (!vconn_name) {
270         VLOG_WARN_RL(&rl, "rejecting DHCP offer missing controller vconn");
271         return false;
272     }
273     accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0);
274     free(vconn_name);
275     return accept;
276 }
277
278 static void
279 parse_options(int argc, char *argv[])
280 {
281     enum {
282         OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
283         OPT_EXIT_WITHOUT_BIND,
284         OPT_EXIT_AFTER_BIND,
285         OPT_NO_DETACH
286     };
287     static struct option long_options[] = {
288         {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
289         {"exit-without-bind", no_argument, 0, OPT_EXIT_WITHOUT_BIND},
290         {"exit-after-bind", no_argument, 0, OPT_EXIT_AFTER_BIND},
291         {"no-detach",   no_argument, 0, OPT_NO_DETACH},
292         {"timeout",     required_argument, 0, 't'},
293         {"pidfile",     optional_argument, 0, OPT_PIDFILE},
294         {"overwrite-pidfile", no_argument, 0, OPT_OVERWRITE_PIDFILE},
295         {"verbose",     optional_argument, 0, 'v'},
296         {"help",        no_argument, 0, 'h'},
297         {"version",     no_argument, 0, 'V'},
298         {0, 0, 0, 0},
299     };
300     char *short_options = long_options_to_short_options(long_options);
301     bool detach_after_bind = true;
302
303     for (;;) {
304         unsigned long int timeout;
305         int c;
306
307         c = getopt_long(argc, argv, short_options, long_options, NULL);
308         if (c == -1) {
309             break;
310         }
311
312         switch (c) {
313         case OPT_ACCEPT_VCONN:
314             accept_controller_re = (optarg[0] == '^'
315                                     ? optarg
316                                     : xasprintf("^%s", optarg));
317             break;
318
319         case OPT_EXIT_WITHOUT_BIND:
320             exit_without_bind = true;
321             break;
322
323         case OPT_EXIT_AFTER_BIND:
324             exit_after_bind = true;
325             break;
326
327         case OPT_NO_DETACH:
328             detach_after_bind = false;
329             break;
330
331         case OPT_PIDFILE:
332             set_pidfile(optarg);
333             break;
334
335         case OPT_OVERWRITE_PIDFILE:
336             ignore_existing_pidfile();
337             break;
338
339         case 't':
340             timeout = strtoul(optarg, NULL, 10);
341             if (timeout <= 0) {
342                 ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
343                           optarg);
344             } else {
345                 time_alarm(timeout);
346             }
347             signal(SIGALRM, SIG_DFL);
348             break;
349
350         case 'h':
351             usage();
352
353         case 'V':
354             OVS_PRINT_VERSION(0, 0);
355             exit(EXIT_SUCCESS);
356
357         case 'v':
358             vlog_set_verbosity(optarg);
359             break;
360
361         case '?':
362             exit(EXIT_FAILURE);
363
364         default:
365             abort();
366         }
367     }
368     free(short_options);
369
370     if ((exit_without_bind + exit_after_bind + !detach_after_bind) > 1) {
371         ovs_fatal(0, "--exit-without-bind, --exit-after-bind, and --no-detach "
372                   "are mutually exclusive");
373     }
374     if (detach_after_bind) {
375         set_detach();
376     }
377 }
378
379 static void
380 usage(void)
381 {
382     printf("%s: a tool for discovering OpenFlow controllers.\n"
383            "usage: %s [OPTIONS] NETDEV [NETDEV...]\n"
384            "where each NETDEV is a network device on which to perform\n"
385            "controller discovery.\n"
386            "\nOrdinarily, ovs-discover runs in the foreground until it\n"
387            "obtains an IP address and discovers an OpenFlow controller via\n"
388            "DHCP, then it prints information about the controller to stdout\n"
389            "and detaches to the background to maintain the IP address lease.\n"
390            "\nNetworking options:\n"
391            "  --accept-vconn=REGEX    accept matching discovered controllers\n"
392            "  --exit-without-bind     exit after discovery, without binding\n"
393            "  --exit-after-bind       exit after discovery, after binding\n"
394            "  --no-detach             do not detach after discovery\n",
395            program_name, program_name);
396     vlog_usage();
397     printf("\nOther options:\n"
398            "  -t, --timeout=SECS      give up discovery after SECS seconds\n"
399            "  --pidfile[=FILE]        create pidfile (default: %s/%s.pid)\n"
400            "  --overwrite-pidfile     with --pidfile, start even if already "
401                                       "running\n"
402            "  -h, --help              display this help message\n"
403            "  -V, --version           display version information\n",
404            ovs_rundir, program_name);
405     exit(EXIT_SUCCESS);
406 }