New program ofp-discover.
[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 "util.h"
50 #include "vlog-socket.h"
51
52 #include "vlog.h"
53 #define THIS_MODULE VLM_ofp_discover
54
55 struct iface {
56     const char *name;
57     struct dhclient *dhcp;
58 };
59
60 /* The interfaces that we serve. */
61 static struct iface *ifaces;
62 static int n_ifaces;
63
64 /* --accept-vconn: Regular expression specifying the class of controller vconns
65  * that we will accept during autodiscovery. */
66 static const char *accept_controller_re = ".*";
67 static regex_t accept_controller_regex;
68
69 /* --exit-without-bind: Exit after discovering the controller, without binding
70  * the network device to an IP address? */
71 static bool exit_without_bind;
72
73 /* --exit-after-bind: Exit after discovering the controller, after binding the
74  * network device to an IP address? */
75 static bool exit_after_bind;
76
77 static bool iface_init(struct iface *, const char *netdev_name);
78 static void release_ifaces(void *aux UNUSED);
79
80 static void parse_options(int argc, char *argv[]);
81 static void usage(void) NO_RETURN;
82
83 static void modify_dhcp_request(struct dhcp_msg *, void *aux);
84 static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux);
85
86 int
87 main(int argc, char *argv[])
88 {
89     int retval;
90     int i;
91
92     set_program_name(argv[0]);
93     vlog_init();
94     parse_options(argc, argv);
95
96     argc -= optind;
97     argv += optind;
98     if (argc < 1) {
99         fatal(0, "need at least one non-option argument; "
100               "use --help for usage");
101     }
102
103     ifaces = xmalloc(argc * sizeof *ifaces);
104     n_ifaces = 0;
105     for (i = 0; i < argc; i++) {
106         if (iface_init(&ifaces[n_ifaces], argv[i])) {
107             n_ifaces++;
108         }
109     }
110     if (!n_ifaces) {
111         fatal(0, "failed to initialize any DHCP clients");
112     }
113
114     for (i = 0; i < n_ifaces; i++) {
115         struct iface *iface = &ifaces[i];
116         dhclient_init(iface->dhcp, 0);
117     }
118     fatal_signal_add_hook(release_ifaces, NULL);
119
120     retval = regcomp(&accept_controller_regex, accept_controller_re,
121                      REG_NOSUB | REG_EXTENDED);
122     if (retval) {
123         size_t length = regerror(retval, &accept_controller_regex, NULL, 0);
124         char *buffer = xmalloc(length);
125         regerror(retval, &accept_controller_regex, buffer, length);
126         fatal(0, "%s: %s", accept_controller_re, buffer);
127     }
128
129     retval = vlog_server_listen(NULL, NULL);
130     if (retval) {
131         fatal(retval, "Could not listen for vlog connections");
132     }
133
134     signal(SIGPIPE, SIG_IGN);
135     for (;;) {
136         fatal_signal_block();
137         for (i = 0; i < n_ifaces; i++) {
138             struct iface *iface = &ifaces[i];
139             dhclient_run(iface->dhcp);
140             if (dhclient_changed(iface->dhcp)) {
141                 bool is_bound = dhclient_is_bound(iface->dhcp);
142                 int j;
143
144                 /* Configure network device. */
145                 if (!exit_without_bind) {
146                     dhclient_configure_netdev(iface->dhcp);
147                 }
148
149                 if (is_bound) {
150                     static bool detached = false;
151                     struct ds ds;
152
153                     /* Disable timeout, since discovery was successful. */
154                     alarm(0);
155
156                     /* Print discovered parameters. */
157                     ds_init(&ds);
158                     dhcp_msg_to_string(dhclient_get_config(iface->dhcp),
159                                        true, &ds);
160                     fputs(ds_cstr(&ds), stdout);
161                     putchar('\n');
162                     fflush(stdout);
163                     ds_destroy(&ds);
164
165                     /* Exit if the user requested it. */
166                     if (exit_without_bind) {
167                         VLOG_DBG("exiting because of successful binding on %s "
168                                  "and --exit-without-bind specified",
169                                  iface->name);
170                         exit(0);
171                     }
172                     if (exit_after_bind) {
173                         VLOG_DBG("exiting because of successful binding on %s "
174                                  "and --exit-after-bind specified",
175                                  iface->name);
176                         exit(0);
177                     }
178
179                     /* Detach into background, if we haven't already. */
180                     if (!detached) {
181                         detached = true;
182                         daemonize();
183                     }
184                 }
185
186                 /* We only want an address on a single one of our interfaces.
187                  * So: if we have an address on this interface, stop looking
188                  * for one on the others; if we don't have an address on this
189                  * interface, start looking everywhere. */
190                 for (j = 0; j < n_ifaces; j++) {
191                     struct iface *if2 = &ifaces[j];
192                     if (iface != if2) {
193                         if (is_bound) {
194                             dhclient_release(if2->dhcp);
195                         } else {
196                             dhclient_init(if2->dhcp, 0);
197                         }
198                     }
199                 }
200             }
201         }
202         for (i = 0; i < n_ifaces; i++) {
203             struct iface *iface = &ifaces[i];
204             dhclient_wait(iface->dhcp);
205         }
206         fatal_signal_unblock();
207         poll_block();
208     }
209
210     return 0;
211 }
212
213 static bool
214 iface_init(struct iface *iface, const char *netdev_name)
215 {
216     int retval;
217
218     iface->name = netdev_name;
219     iface->dhcp = NULL;
220
221     if (exit_after_bind) {
222         /* Bring this interface up permanently, so that the bound address
223          * persists past program termination. */
224         struct netdev *netdev;
225
226         retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
227         if (retval) {
228             error(retval, "Could not open %s device", iface->name);
229             return false;
230         }
231         retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
232         if (retval) {
233             error(retval, "Could not bring %s device up", iface->name);
234             return false;
235         }
236         netdev_close(netdev);
237     }
238
239     retval = dhclient_create(iface->name, modify_dhcp_request,
240                              validate_dhcp_offer, NULL, &iface->dhcp);
241     if (retval) {
242         error(retval, "%s: failed to initialize DHCP client", iface->name);
243         return false;
244     }
245
246     return true;
247 }
248
249 static void
250 release_ifaces(void *aux UNUSED)
251 {
252     int i;
253
254     for (i = 0; i < n_ifaces; i++) {
255         struct dhclient *dhcp = ifaces[i].dhcp;
256         dhclient_release(dhcp);
257         if (dhclient_changed(dhcp)) {
258             dhclient_configure_netdev(dhcp);
259         }
260     }
261 }
262
263 static void
264 modify_dhcp_request(struct dhcp_msg *msg, void *aux)
265 {
266     dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow");
267 }
268
269 static bool
270 validate_dhcp_offer(const struct dhcp_msg *msg, void *aux)
271 {
272     char *vconn_name;
273     bool accept;
274
275     vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN);
276     if (!vconn_name) {
277         VLOG_WARN("rejecting DHCP offer missing controller vconn");
278         return false;
279     }
280     accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0);
281     free(vconn_name);
282     return accept;
283 }
284
285 static void
286 parse_options(int argc, char *argv[])
287 {
288     enum {
289         OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
290         OPT_EXIT_WITHOUT_BIND,
291         OPT_EXIT_AFTER_BIND,
292         OPT_NO_DETACH,
293     };
294     static struct option long_options[] = {
295         {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
296         {"exit-without-bind", no_argument, 0, OPT_EXIT_WITHOUT_BIND},
297         {"exit-after-bind", no_argument, 0, OPT_EXIT_AFTER_BIND},
298         {"no-detach",   no_argument, 0, OPT_NO_DETACH},
299         {"timeout",     required_argument, 0, 't'},
300         {"pidfile",     optional_argument, 0, 'P'},
301         {"verbose",     optional_argument, 0, 'v'},
302         {"help",        no_argument, 0, 'h'},
303         {"version",     no_argument, 0, 'V'},
304         {0, 0, 0, 0},
305     };
306     char *short_options = long_options_to_short_options(long_options);
307     bool detach_after_bind = true;
308
309     for (;;) {
310         unsigned long int timeout;
311         int c;
312
313         c = getopt_long(argc, argv, short_options, long_options, NULL);
314         if (c == -1) {
315             break;
316         }
317
318         switch (c) {
319         case OPT_ACCEPT_VCONN:
320             accept_controller_re = (optarg[0] == '^'
321                                     ? optarg
322                                     : xasprintf("^%s", optarg));
323             break;
324
325         case OPT_EXIT_WITHOUT_BIND:
326             exit_without_bind = true;
327             break;
328
329         case OPT_EXIT_AFTER_BIND:
330             exit_after_bind = true;
331             break;
332
333         case OPT_NO_DETACH:
334             detach_after_bind = false;
335             break;
336
337         case 'P':
338             set_pidfile(optarg);
339             break;
340
341         case 't':
342             timeout = strtoul(optarg, NULL, 10);
343             if (timeout <= 0) {
344                 fatal(0, "value %s on -t or --timeout is not at least 1",
345                       optarg);
346             } else if (timeout < UINT_MAX) {
347                 /* Add 1 because historical implementations allow an alarm to
348                  * occur up to a second early. */
349                 alarm(timeout + 1);
350             } else {
351                 alarm(UINT_MAX);
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 }