70c53ee82c4f63f892ce76fbcc2de82ea6a6223c
[sliver-openvswitch.git] / utilities / ofp-discover.c
1 /* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
2  * Junior University
3  *
4  * We are making the OpenFlow specification and associated documentation
5  * (Software) available for public use and benefit with the expectation
6  * that others will use, modify and enhance the Software and contribute
7  * those enhancements back to the community. However, since we would
8  * like to make the Software available for broadest use, with as few
9  * restrictions as possible permission is hereby granted, free of
10  * charge, to any person obtaining a copy of this Software to deal in
11  * the Software under the copyrights without restriction, including
12  * without limitation the rights to use, copy, modify, merge, publish,
13  * distribute, sublicense, and/or sell copies of the Software, and to
14  * permit persons to whom the Software is furnished to do so, subject to
15  * the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
24  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
25  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27  * SOFTWARE.
28  *
29  * The name and trademarks of copyright holder(s) may NOT be used in
30  * advertising or publicity pertaining to the Software or any
31  * derivatives without specific, written prior permission.
32  */
33
34 #include <config.h>
35 #include <getopt.h>
36 #include <limits.h>
37 #include <regex.h>
38 #include <signal.h>
39 #include <stdlib.h>
40 #include <unistd.h>
41 #include "command-line.h"
42 #include "daemon.h"
43 #include "dhcp-client.h"
44 #include "dhcp.h"
45 #include "dynamic-string.h"
46 #include "fatal-signal.h"
47 #include "netdev.h"
48 #include "poll-loop.h"
49 #include "timeval.h"
50 #include "util.h"
51 #include "vlog-socket.h"
52
53 #include "vlog.h"
54 #define THIS_MODULE VLM_ofp_discover
55
56 struct iface {
57     const char *name;
58     struct dhclient *dhcp;
59 };
60
61 /* The interfaces that we serve. */
62 static struct iface *ifaces;
63 static int n_ifaces;
64
65 /* --accept-vconn: Regular expression specifying the class of controller vconns
66  * that we will accept during autodiscovery. */
67 static const char *accept_controller_re = ".*";
68 static regex_t accept_controller_regex;
69
70 /* --exit-without-bind: Exit after discovering the controller, without binding
71  * the network device to an IP address? */
72 static bool exit_without_bind;
73
74 /* --exit-after-bind: Exit after discovering the controller, after binding the
75  * network device to an IP address? */
76 static bool exit_after_bind;
77
78 static bool iface_init(struct iface *, const char *netdev_name);
79 static void release_ifaces(void *aux UNUSED);
80
81 static void parse_options(int argc, char *argv[]);
82 static void usage(void) NO_RETURN;
83
84 static void modify_dhcp_request(struct dhcp_msg *, void *aux);
85 static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
86
87 int
88 main(int argc, char *argv[])
89 {
90     int retval;
91     int i;
92
93     set_program_name(argv[0]);
94     time_init();
95     vlog_init();
96     parse_options(argc, argv);
97
98     argc -= optind;
99     argv += optind;
100     if (argc < 1) {
101         fatal(0, "need at least one non-option argument; "
102               "use --help for usage");
103     }
104
105     ifaces = xmalloc(argc * sizeof *ifaces);
106     n_ifaces = 0;
107     for (i = 0; i < argc; i++) {
108         if (iface_init(&ifaces[n_ifaces], argv[i])) {
109             n_ifaces++;
110         }
111     }
112     if (!n_ifaces) {
113         fatal(0, "failed to initialize any DHCP clients");
114     }
115
116     for (i = 0; i < n_ifaces; i++) {
117         struct iface *iface = &ifaces[i];
118         dhclient_init(iface->dhcp, 0);
119     }
120     fatal_signal_add_hook(release_ifaces, NULL);
121
122     retval = regcomp(&accept_controller_regex, accept_controller_re,
123                      REG_NOSUB | REG_EXTENDED);
124     if (retval) {
125         size_t length = regerror(retval, &accept_controller_regex, NULL, 0);
126         char *buffer = xmalloc(length);
127         regerror(retval, &accept_controller_regex, buffer, length);
128         fatal(0, "%s: %s", accept_controller_re, buffer);
129     }
130
131     retval = vlog_server_listen(NULL, NULL);
132     if (retval) {
133         fatal(retval, "Could not listen for vlog connections");
134     }
135
136     signal(SIGPIPE, SIG_IGN);
137     for (;;) {
138         fatal_signal_block();
139         for (i = 0; i < n_ifaces; i++) {
140             struct iface *iface = &ifaces[i];
141             dhclient_run(iface->dhcp);
142             if (dhclient_changed(iface->dhcp)) {
143                 bool is_bound = dhclient_is_bound(iface->dhcp);
144                 int j;
145
146                 /* Configure network device. */
147                 if (!exit_without_bind) {
148                     dhclient_configure_netdev(iface->dhcp);
149                     dhclient_update_resolv_conf(iface->dhcp);
150                 }
151
152                 if (is_bound) {
153                     static bool detached = false;
154                     struct ds ds;
155
156                     /* Disable timeout, since discovery was successful. */
157                     time_alarm(0);
158
159                     /* Print discovered parameters. */
160                     ds_init(&ds);
161                     dhcp_msg_to_string(dhclient_get_config(iface->dhcp),
162                                        true, &ds);
163                     fputs(ds_cstr(&ds), stdout);
164                     putchar('\n');
165                     fflush(stdout);
166                     ds_destroy(&ds);
167
168                     /* Exit if the user requested it. */
169                     if (exit_without_bind) {
170                         VLOG_DBG("exiting because of successful binding on %s "
171                                  "and --exit-without-bind specified",
172                                  iface->name);
173                         exit(0);
174                     }
175                     if (exit_after_bind) {
176                         VLOG_DBG("exiting because of successful binding on %s "
177                                  "and --exit-after-bind specified",
178                                  iface->name);
179                         exit(0);
180                     }
181
182                     /* Detach into background, if we haven't already. */
183                     if (!detached) {
184                         detached = true;
185                         daemonize();
186                     }
187                 }
188
189                 /* We only want an address on a single one of our interfaces.
190                  * So: if we have an address on this interface, stop looking
191                  * for one on the others; if we don't have an address on this
192                  * interface, start looking everywhere. */
193                 for (j = 0; j < n_ifaces; j++) {
194                     struct iface *if2 = &ifaces[j];
195                     if (iface != if2) {
196                         if (is_bound) {
197                             dhclient_release(if2->dhcp);
198                         } else {
199                             dhclient_init(if2->dhcp, 0);
200                         }
201                     }
202                 }
203             }
204         }
205         for (i = 0; i < n_ifaces; i++) {
206             struct iface *iface = &ifaces[i];
207             dhclient_wait(iface->dhcp);
208         }
209         fatal_signal_unblock();
210         poll_block();
211     }
212
213     return 0;
214 }
215
216 static bool
217 iface_init(struct iface *iface, const char *netdev_name)
218 {
219     int retval;
220
221     iface->name = netdev_name;
222     iface->dhcp = NULL;
223
224     if (exit_after_bind) {
225         /* Bring this interface up permanently, so that the bound address
226          * persists past program termination. */
227         struct netdev *netdev;
228
229         retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
230         if (retval) {
231             error(retval, "Could not open %s device", iface->name);
232             return false;
233         }
234         retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
235         if (retval) {
236             error(retval, "Could not bring %s device up", iface->name);
237             return false;
238         }
239         netdev_close(netdev);
240     }
241
242     retval = dhclient_create(iface->name, modify_dhcp_request,
243                              validate_dhcp_offer, NULL, &iface->dhcp);
244     if (retval) {
245         error(retval, "%s: failed to initialize DHCP client", iface->name);
246         return false;
247     }
248
249     return true;
250 }
251
252 static void
253 release_ifaces(void *aux UNUSED)
254 {
255     int i;
256
257     for (i = 0; i < n_ifaces; i++) {
258         struct dhclient *dhcp = ifaces[i].dhcp;
259         dhclient_release(dhcp);
260         if (dhclient_changed(dhcp)) {
261             dhclient_configure_netdev(dhcp);
262         }
263     }
264 }
265
266 static void
267 modify_dhcp_request(struct dhcp_msg *msg, void *aux)
268 {
269     dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
270 }
271
272 static bool
273 validate_dhcp_offer(const struct dhcp_msg *msg, void *aux)
274 {
275     static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
276     char *vconn_name;
277     bool accept;
278
279     vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
280     if (!vconn_name) {
281         VLOG_WARN_RL(&rl, "rejecting DHCP offer missing controller vconn");
282         return false;
283     }
284     accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0);
285     free(vconn_name);
286     return accept;
287 }
288
289 static void
290 parse_options(int argc, char *argv[])
291 {
292     enum {
293         OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
294         OPT_EXIT_WITHOUT_BIND,
295         OPT_EXIT_AFTER_BIND,
296         OPT_NO_DETACH,
297     };
298     static struct option long_options[] = {
299         {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
300         {"exit-without-bind", no_argument, 0, OPT_EXIT_WITHOUT_BIND},
301         {"exit-after-bind", no_argument, 0, OPT_EXIT_AFTER_BIND},
302         {"no-detach",   no_argument, 0, OPT_NO_DETACH},
303         {"timeout",     required_argument, 0, 't'},
304         {"pidfile",     optional_argument, 0, 'P'},
305         {"verbose",     optional_argument, 0, 'v'},
306         {"help",        no_argument, 0, 'h'},
307         {"version",     no_argument, 0, 'V'},
308         {0, 0, 0, 0},
309     };
310     char *short_options = long_options_to_short_options(long_options);
311     bool detach_after_bind = true;
312
313     for (;;) {
314         unsigned long int timeout;
315         int c;
316
317         c = getopt_long(argc, argv, short_options, long_options, NULL);
318         if (c == -1) {
319             break;
320         }
321
322         switch (c) {
323         case OPT_ACCEPT_VCONN:
324             accept_controller_re = (optarg[0] == '^'
325                                     ? optarg
326                                     : xasprintf("^%s", optarg));
327             break;
328
329         case OPT_EXIT_WITHOUT_BIND:
330             exit_without_bind = true;
331             break;
332
333         case OPT_EXIT_AFTER_BIND:
334             exit_after_bind = true;
335             break;
336
337         case OPT_NO_DETACH:
338             detach_after_bind = false;
339             break;
340
341         case 'P':
342             set_pidfile(optarg);
343             break;
344
345         case 't':
346             timeout = strtoul(optarg, NULL, 10);
347             if (timeout <= 0) {
348                 fatal(0, "value %s on -t or --timeout is not at least 1",
349                       optarg);
350             } else {
351                 time_alarm(timeout);
352             }
353             signal(SIGALRM, SIG_DFL);
354             break;
355
356         case 'h':
357             usage();
358
359         case 'V':
360             printf("%s "VERSION" compiled "__DATE__" "__TIME__"\n", argv[0]);
361             exit(EXIT_SUCCESS);
362
363         case 'v':
364             vlog_set_verbosity(optarg);
365             break;
366
367         case '?':
368             exit(EXIT_FAILURE);
369
370         default:
371             abort();
372         }
373     }
374     free(short_options);
375
376     if ((exit_without_bind + exit_after_bind + !detach_after_bind) > 1) {
377         fatal(0, "--exit-without-bind, --exit-after-bind, and --no-detach "
378               "are mutually exclusive");
379     }
380     if (detach_after_bind) {
381         set_detach();
382     }
383 }
384
385 static void
386 usage(void)
387 {
388     printf("%s: a tool for discovering OpenFlow controllers.\n"
389            "usage: %s [OPTIONS] NETDEV [NETDEV...]\n"
390            "where each NETDEV is a network device on which to perform\n"
391            "controller discovery.\n"
392            "\nOrdinarily, ofp-discover runs in the foreground until it\n"
393            "obtains an IP address and discovers an OpenFlow controller via\n"
394            "DHCP, then it prints information about the controller to stdout\n"
395            "and detaches to the background to maintain the IP address lease.\n"
396            "\nNetworking options:\n"
397            "  --accept-vconn=REGEX    accept matching discovered controllers\n"
398            "  --exit-without-bind     exit after discovery, without binding\n"
399            "  --exit-after-bind       exit after discovery, after binding\n"
400            "  --no-detach             do not detach after discovery\n"
401            "\nOther options:\n"
402            "  -t, --timeout=SECS      give up discovery after SECS seconds\n"
403            "  -P, --pidfile[=FILE]    create pidfile (default: %s/%s.pid)\n"
404            "  -v, --verbose=MODULE[:FACILITY[:LEVEL]]  set logging levels\n"
405            "  -v, --verbose           set maximum verbosity level\n"
406            "  -h, --help              display this help message\n"
407            "  -V, --version           display version information\n",
408            program_name, program_name, RUNDIR, program_name);
409     exit(EXIT_SUCCESS);
410 }