Add "fail open" feature to secchan.
[sliver-openvswitch.git] / secchan / secchan.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 <errno.h>
35 #include <getopt.h>
36 #include <inttypes.h>
37 #include <netinet/in.h>
38 #include <poll.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <time.h>
42 #include <unistd.h>
43
44 #include "buffer.h"
45 #include "command-line.h"
46 #include "compiler.h"
47 #include "fault.h"
48 #include "flow.h"
49 #include "learning-switch.h"
50 #include "list.h"
51 #include "mac-learning.h"
52 #include "netdev.h"
53 #include "openflow.h"
54 #include "packets.h"
55 #include "poll-loop.h"
56 #include "rconn.h"
57 #include "util.h"
58 #include "vconn-ssl.h"
59 #include "vconn.h"
60 #include "vlog-socket.h"
61
62 #include "vlog.h"
63 #define THIS_MODULE VLM_secchan
64
65 #include "ofp-print.h"
66
67 static const char *listen_vconn_name;
68
69 struct half {
70     struct rconn *rconn;
71     struct buffer *rxbuf;
72 };
73
74 /* Behavior when the connection to the controller fails. */
75 enum fail_mode {
76     FAIL_OPEN,                  /* Act as learning switch. */
77     FAIL_CLOSED                 /* Drop all packets. */
78 };
79
80 struct relay {
81     struct list node;
82
83 #define HALF_LOCAL 0
84 #define HALF_REMOTE 1
85     struct half halves[2];
86
87     bool is_mgmt_conn;
88     struct lswitch *lswitch;
89 };
90
91 static struct list relays = LIST_INITIALIZER(&relays);
92
93 /* Enable the local port? */
94 static int local_port;
95
96 /* MAC address of local port. */
97 static uint8_t local_mac[ETH_ADDR_LEN];
98
99 /* MAC learning table for local port. */
100 static struct mac_learning *local_ml;
101
102 /* -f, --fail: Behavior when the connection to the controller fails. */
103 static enum fail_mode fail_mode;
104
105 /* -d, --fail-open-delay: Number of seconds after which to fail open, when
106  * fail_mode is FAIL_OPEN. */
107 static int fail_open_delay = 30;
108
109 static void parse_options(int argc, char *argv[]);
110 static void usage(void) NO_RETURN;
111
112 static void new_management_connection(const char *nl_name, struct vconn *new_remote);
113 static struct relay *relay_create(struct rconn *local, struct rconn *remote,
114                                   bool is_mgmt_conn);
115 static void relay_run(struct relay *);
116 static void relay_wait(struct relay *);
117 static void relay_destroy(struct relay *);
118
119 static bool local_hook(struct relay *r);
120 static bool fail_open_hook(struct relay *r);
121
122 int
123 main(int argc, char *argv[])
124 {
125     struct vconn *listen_vconn;
126     struct netdev *of_device;
127     const char *nl_name;
128     char of_name[16];
129     int retval;
130
131     set_program_name(argv[0]);
132     register_fault_handlers();
133     vlog_init();
134     parse_options(argc, argv);
135
136     if (argc - optind != 2) {
137         fatal(0,
138               "need exactly two non-option arguments; use --help for usage");
139     }
140     nl_name = argv[optind];
141     if (strncmp(nl_name, "nl:", 3)
142         || strlen(nl_name) < 4
143         || nl_name[strspn(nl_name + 3, "0123456789") + 3]) {
144         fatal(0, "%s: argument is not of the form \"nl:DP_ID\"", nl_name);
145     }
146
147     if (listen_vconn_name) {
148         retval = vconn_open(listen_vconn_name, &listen_vconn);
149         if (retval && retval != EAGAIN) {
150             fatal(retval, "opening %s", listen_vconn_name);
151         }
152         if (!vconn_is_passive(listen_vconn)) {
153             fatal(0, "%s is not a passive vconn", listen_vconn_name);
154         }
155     } else {
156         listen_vconn = NULL;
157     }
158
159     snprintf(of_name, sizeof of_name, "of%s", nl_name + 3);
160     retval = netdev_open(of_name, &of_device);
161     if (!retval) {
162         enum netdev_flags flags;
163         retval = netdev_get_flags(of_device, &flags);
164         if (!retval) {
165             if (flags & NETDEV_UP) {
166                 struct in6_addr in6;
167
168                 local_port = true;
169                 memcpy(local_mac, netdev_get_etheraddr(of_device),
170                        ETH_ADDR_LEN);
171                 if (netdev_get_in6(of_device, &in6)) {
172                     VLOG_WARN("Ignoring IPv6 address on %s device: "
173                               "IPv6 not supported", of_name);
174                 }
175                 local_ml = mac_learning_create();
176             }
177         } else {
178             error(retval, "Could not get flags for %s device", of_name);
179         }
180         netdev_close(of_device);
181     } else {
182         error(retval, "Could not open %s device", of_name);
183     }
184
185     retval = vlog_server_listen(NULL, NULL);
186     if (retval) {
187         fatal(retval, "Could not listen for vlog connections");
188     }
189
190     relay_create(rconn_new(argv[optind], 1), rconn_new(argv[optind + 1], 1),
191                  false);
192     for (;;) {
193         struct relay *r, *n;
194
195         /* Do work. */
196         LIST_FOR_EACH_SAFE (r, n, struct relay, node, &relays) {
197             relay_run(r);
198         }
199         if (listen_vconn) {
200             for (;;) {
201                 struct vconn *new_remote;
202                 retval = vconn_accept(listen_vconn, &new_remote);
203                 if (retval) {
204                     if (retval != EAGAIN) {
205                         VLOG_WARN("accept failed (%s)", strerror(retval));
206                     }
207                     break;
208                 }
209                 new_management_connection(nl_name, new_remote);
210             }
211         }
212
213         /* Wait for something to happen. */
214         LIST_FOR_EACH (r, struct relay, node, &relays) {
215             relay_wait(r);
216         }
217         if (listen_vconn) {
218             vconn_accept_wait(listen_vconn);
219         }
220         poll_block();
221     }
222
223     return 0;
224 }
225
226 static void
227 new_management_connection(const char *nl_name, struct vconn *new_remote)
228 {
229     char *nl_name_without_subscription;
230     struct vconn *new_local;
231     struct rconn *r1, *r2;
232     int retval;
233
234     /* nl:123 or nl:123:1 opens a netlink connection to local datapath 123.  We
235      * only accept the former syntax in main().
236      *
237      * nl:123:0 opens a netlink connection to local datapath 123 without
238      * obtaining a subscription for ofp_packet_in or ofp_flow_expired
239      * messages.*/
240     nl_name_without_subscription = xasprintf("%s:0", nl_name);
241     retval = vconn_open(nl_name_without_subscription, &new_local);
242     if (retval) {
243         VLOG_ERR("could not connect to %s (%s)",
244                  nl_name_without_subscription, strerror(retval));
245         vconn_close(new_remote);
246         free(nl_name_without_subscription);
247         return;
248     }
249
250     /* Add it to the relay list. */
251     r1 = rconn_new_from_vconn(nl_name_without_subscription, 1, new_local);
252     r2 = rconn_new_from_vconn("passive", 1, new_remote);
253     relay_create(r1, r2, true);
254
255     free(nl_name_without_subscription);
256 }
257
258 static struct relay *
259 relay_create(struct rconn *local, struct rconn *remote, bool is_mgmt_conn)
260 {
261     struct relay *r;
262     int i;
263
264     r = xmalloc(sizeof *r);
265     r->halves[HALF_LOCAL].rconn = local;
266     r->halves[HALF_REMOTE].rconn = remote;
267     for (i = 0; i < 2; i++) {
268         r->halves[i].rxbuf = NULL;
269     }
270     r->is_mgmt_conn = is_mgmt_conn;
271     r->lswitch = NULL;
272     list_push_back(&relays, &r->node);
273     return r;
274 }
275
276 static void
277 relay_run(struct relay *r)
278 {
279     int iteration;
280     int i;
281
282     for (i = 0; i < 2; i++) {
283         rconn_run(r->halves[i].rconn);
284     }
285
286     /* Limit the number of iterations to prevent other tasks from starving. */
287     for (iteration = 0; iteration < 50; iteration++) {
288         bool progress = false;
289         for (i = 0; i < 2; i++) {
290             struct half *this = &r->halves[i];
291             struct half *peer = &r->halves[!i];
292
293             if (!this->rxbuf) {
294                 this->rxbuf = rconn_recv(this->rconn);
295                 if (this->rxbuf && !r->is_mgmt_conn && i == HALF_LOCAL
296                     && (local_hook(r) || fail_open_hook(r))) {
297                     buffer_delete(this->rxbuf);
298                     this->rxbuf = NULL;
299                 }
300             }
301
302             if (this->rxbuf) {
303                 int retval = rconn_send(peer->rconn, this->rxbuf);
304                 if (retval != EAGAIN) {
305                     if (!retval) {
306                         progress = true;
307                     } else {
308                         buffer_delete(this->rxbuf);
309                     }
310                     this->rxbuf = NULL;
311                 }
312             }
313         }
314         if (!progress) {
315             break;
316         }
317     }
318
319     for (i = 0; i < 2; i++) {
320         struct half *this = &r->halves[i];
321         if (!rconn_is_alive(this->rconn)) {
322             relay_destroy(r);
323             return;
324         }
325     }
326 }
327
328 static void
329 relay_wait(struct relay *r)
330 {
331     int i;
332
333     for (i = 0; i < 2; i++) {
334         struct half *this = &r->halves[i];
335
336         rconn_run_wait(this->rconn);
337         if (!this->rxbuf) {
338             rconn_recv_wait(this->rconn);
339         }
340     }
341 }
342
343 static void
344 relay_destroy(struct relay *r)
345 {
346     int i;
347
348     list_remove(&r->node);
349     for (i = 0; i < 2; i++) {
350         struct half *this = &r->halves[i];
351         rconn_destroy(this->rconn);
352         buffer_delete(this->rxbuf);
353     }
354     free(r);
355 }
356
357 static bool
358 local_hook(struct relay *r)
359 {
360     struct rconn *rc = r->halves[HALF_LOCAL].rconn;
361     struct buffer *msg = r->halves[HALF_LOCAL].rxbuf;
362     struct ofp_packet_in *opi;
363     struct ofp_header *oh;
364     size_t pkt_ofs, pkt_len;
365     struct buffer pkt, *b;
366     struct flow flow;
367     uint16_t in_port, out_port;
368
369     if (!local_port) {
370         return false;
371     }
372
373     oh = msg->data;
374     if (oh->type != OFPT_PACKET_IN) {
375         return false;
376     }
377     if (msg->size < offsetof (struct ofp_packet_in, data)) {
378         VLOG_WARN("packet too short (%zu bytes) for packet_in", msg->size);
379         return false;
380     }
381
382     /* Extract flow data from 'opi' into 'flow'. */
383     opi = msg->data;
384     in_port = ntohs(opi->in_port);
385     pkt_ofs = offsetof(struct ofp_packet_in, data);
386     pkt_len = ntohs(opi->header.length) - pkt_ofs;
387     pkt.data = opi->data;
388     pkt.size = pkt_len;
389     flow_extract(&pkt, in_port, &flow);
390
391     /* Deal with local stuff. */
392     if (!rconn_is_connected(r->halves[HALF_REMOTE].rconn)
393         && eth_addr_is_broadcast(flow.dl_dst)) {
394         out_port = OFPP_FLOOD;
395     } else if (in_port == OFPP_LOCAL) {
396         out_port = mac_learning_lookup(local_ml, flow.dl_dst);
397     } else if (eth_addr_equals(flow.dl_dst, local_mac)) {
398         out_port = OFPP_LOCAL;
399         if (mac_learning_learn(local_ml, flow.dl_src, in_port)) {
400             VLOG_DBG("learned that "ETH_ADDR_FMT" is on port %"PRIu16,
401                      ETH_ADDR_ARGS(flow.dl_src), in_port);
402         }
403     } else {
404         return false;
405     }
406
407     /* Add new flow. */
408     if (out_port != OFPP_FLOOD) {
409         b = make_add_simple_flow(&flow, ntohl(opi->buffer_id), out_port);
410         if (rconn_force_send(rc, b)) {
411             buffer_delete(b);
412         }
413     }
414
415     /* If the switch didn't buffer the packet, we need to send a copy. */
416     if (out_port == OFPP_FLOOD || ntohl(opi->buffer_id) == UINT32_MAX) {
417         b = make_unbuffered_packet_out(&pkt, in_port, out_port);
418         if (rconn_force_send(rc, b)) {
419             buffer_delete(b);
420         }
421     }
422     return true;
423 }
424
425 static bool
426 fail_open_hook(struct relay *r)
427 {
428     struct buffer *msg = r->halves[HALF_LOCAL].rxbuf;
429     struct rconn *local = r->halves[HALF_LOCAL].rconn;
430     struct rconn *remote = r->halves[HALF_REMOTE].rconn;
431     int disconnected_duration;
432
433     if (fail_mode == FAIL_CLOSED) {
434         /* We fail closed, so there's never anything to do. */
435         return false;
436     }
437
438     disconnected_duration = rconn_disconnected_duration(remote);
439     if (disconnected_duration < fail_open_delay) {
440         /* It's not time to fail open yet. */
441         if (r->lswitch && rconn_is_connected(remote)) {
442             /* We're connected, so drop the learning switch. */
443             VLOG_WARN("No longer in fail-open mode");
444             lswitch_destroy(r->lswitch);
445             r->lswitch = NULL;
446         }
447         return false;
448     }
449
450     if (!r->lswitch) {
451         VLOG_WARN("Could not connect to controller for %d seconds, "
452                   "failing open", disconnected_duration);
453         r->lswitch = lswitch_create(local, true, true);
454     }
455
456     /* Do switching. */
457     lswitch_process_packet(r->lswitch, local, msg);
458     rconn_run(local);
459     return true;
460 }
461
462 static void
463 parse_options(int argc, char *argv[]) 
464 {
465     static struct option long_options[] = {
466         {"fail",        required_argument, 0, 'f'},
467         {"fail-open-delay", required_argument, 0, 'd'},
468         {"listen",      required_argument, 0, 'l'},
469         {"verbose",     optional_argument, 0, 'v'},
470         {"help",        no_argument, 0, 'h'},
471         {"version",     no_argument, 0, 'V'},
472         VCONN_SSL_LONG_OPTIONS
473         {0, 0, 0, 0},
474     };
475     char *short_options = long_options_to_short_options(long_options);
476     
477     for (;;) {
478         int c;
479
480         c = getopt_long(argc, argv, short_options, long_options, NULL);
481         if (c == -1) {
482             break;
483         }
484
485         switch (c) {
486         case 'f':
487             if (!strcmp(optarg, "open")) {
488                 fail_mode = FAIL_OPEN;
489             } else if (!strcmp(optarg, "closed")) {
490                 fail_mode = FAIL_CLOSED;
491             } else {
492                 fatal(0,
493                       "-f or --fail argument must be \"open\" or \"closed\"");
494             }
495             break;
496
497         case 'd':
498             fail_open_delay = atoi(optarg);
499             if (fail_open_delay < 1) {
500                 fatal(0,
501                       "-d or --fail-open-delay argument must be at least 1");
502             }
503             break;
504
505         case 'l':
506             if (listen_vconn_name) {
507                 fatal(0, "-l or --listen may be only specified once");
508             }
509             listen_vconn_name = optarg;
510             break;
511
512         case 'h':
513             usage();
514
515         case 'V':
516             printf("%s "VERSION" compiled "__DATE__" "__TIME__"\n", argv[0]);
517             exit(EXIT_SUCCESS);
518
519         case 'v':
520             vlog_set_verbosity(optarg);
521             break;
522
523         VCONN_SSL_OPTION_HANDLERS
524
525         case '?':
526             exit(EXIT_FAILURE);
527
528         default:
529             abort();
530         }
531     }
532     free(short_options);
533 }
534
535 static void
536 usage(void)
537 {
538     printf("%s: Secure Channel, a relay for OpenFlow messages.\n"
539            "usage: %s [OPTIONS] LOCAL REMOTE\n"
540            "where LOCAL and REMOTE are active OpenFlow connection methods.\n",
541            program_name, program_name);
542     vconn_usage(true, true);
543     printf("\nNetworking options:\n"
544            "  -f, --fail=open|closed  when controller connection fails:\n"
545            "                            closed (default): drop all packets\n"
546            "                            open: act as learning switch\n"
547            "  -d, --fail-open-delay=SECS  number of seconds after which to\n"
548            "                          fail open if --fail=open (default: 30)\n"
549            "  -l, --listen=METHOD     allow management connections on METHOD\n"
550            "                          (a passive OpenFlow connection method)\n"
551            "\nOther options:\n"
552            "  -v, --verbose           set maximum verbosity level\n"
553            "  -h, --help              display this help message\n"
554            "  -V, --version           display version information\n");
555     exit(EXIT_SUCCESS);
556 }