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