• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ustp/bridge_track.c

  1 /*****************************************************************************
  2   Copyright (c) 2006 EMC Corporation.
  3   Copyright (c) 2011 Factor-SPE
  4 
  5   This program is free software; you can redistribute it and/or modify it
  6   under the terms of the GNU General Public License as published by the Free
  7   Software Foundation; either version 2 of the License, or (at your option)
  8   any later version.
  9 
 10   This program is distributed in the hope that it will be useful, but WITHOUT
 11   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 12   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 13   more details.
 14 
 15   You should have received a copy of the GNU General Public License along with
 16   this program; if not, write to the Free Software Foundation, Inc., 59
 17   Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 18 
 19   The full GNU General Public License is included in this distribution in the
 20   file called LICENSE.
 21 
 22   Authors: Srinivas Aji <Aji_Srinivas@emc.com>
 23   Authors: Vitalii Demianets <dvitasgs@gmail.com>
 24 
 25 ******************************************************************************/
 26 #define _GNU_SOURCE
 27 #include <string.h>
 28 #include <unistd.h>
 29 #include <fcntl.h>
 30 #include <linux/param.h>
 31 #include <netinet/in.h>
 32 #include <linux/if_bridge.h>
 33 #include <asm/byteorder.h>
 34 #include <sys/types.h>
 35 #include <sys/socket.h>
 36 #include <net/if.h>
 37 #include <dirent.h>
 38 
 39 #include "bridge_ctl.h"
 40 #include "bridge_track.h"
 41 #include "netif_utils.h"
 42 #include "packet.h"
 43 #include "log.h"
 44 #include "mstp.h"
 45 #include "driver.h"
 46 #include "libnetlink.h"
 47 
 48 #ifndef SYSFS_CLASS_NET
 49 #define SYSFS_CLASS_NET "/sys/class/net"
 50 #endif
 51 
 52 static LIST_HEAD(bridges);
 53 
 54 static bridge_t * create_br(int if_index)
 55 {
 56     bridge_t *br;
 57     TST((br = calloc(1, sizeof(*br))) != NULL, NULL);
 58 
 59     /* Init system dependent info */
 60     br->sysdeps.if_index = if_index;
 61     if (!if_indextoname(if_index, br->sysdeps.name))
 62         goto err;
 63     if (get_hwaddr(br->sysdeps.name, br->sysdeps.macaddr))
 64         goto err;
 65 
 66     INFO("Add bridge %s", br->sysdeps.name);
 67     if(!MSTP_IN_bridge_create(br, br->sysdeps.macaddr))
 68         goto err;
 69 
 70     list_add_tail(&br->list, &bridges);
 71     return br;
 72 err:
 73     free(br);
 74     return NULL;
 75 }
 76 
 77 static bridge_t * find_br(int if_index)
 78 {
 79     bridge_t *br;
 80     list_for_each_entry(br, &bridges, list)
 81     {
 82         if(br->sysdeps.if_index == if_index)
 83             return br;
 84     }
 85     return NULL;
 86 }
 87 
 88 static port_t * create_if(bridge_t * br, int if_index)
 89 {
 90     port_t *prt;
 91     TST((prt = calloc(1, sizeof(*prt))) != NULL, NULL);
 92 
 93     /* Init system dependent info */
 94     prt->sysdeps.if_index = if_index;
 95     if (!if_indextoname(if_index, prt->sysdeps.name))
 96         goto err;
 97     if (get_hwaddr(prt->sysdeps.name, prt->sysdeps.macaddr))
 98         goto err;
 99 
100     int portno;
101     if(0 > (portno = get_bridge_portno(prt->sysdeps.name)))
102     {
103         ERROR("Couldn't get port number for %s", prt->sysdeps.name);
104         goto err;
105     }
106     if((0 == portno) || (portno > MAX_PORT_NUMBER))
107     {
108         ERROR("Port number for %s is invalid (%d)", prt->sysdeps.name, portno);
109         goto err;
110     }
111 
112     INFO("Add iface %s as port#%d to bridge %s", prt->sysdeps.name,
113          portno, br->sysdeps.name);
114     prt->bridge = br;
115     if(!MSTP_IN_port_create_and_add_tail(prt, portno))
116         goto err;
117 
118     return prt;
119 err:
120     free(prt);
121     return NULL;
122 }
123 
124 static port_t * find_if(bridge_t * br, int if_index)
125 {
126     port_t *prt;
127     list_for_each_entry(prt, &br->ports, br_list)
128     {
129         if(prt->sysdeps.if_index == if_index)
130             return prt;
131     }
132     return NULL;
133 }
134 
135 static inline void delete_if(port_t *prt)
136 {
137     MSTP_IN_delete_port(prt);
138     free(prt);
139 }
140 
141 static inline bool delete_if_byindex(bridge_t * br, int if_index)
142 {
143     port_t *prt;
144     if(!(prt = find_if(br, if_index)))
145         return false;
146     delete_if(prt);
147     return true;
148 }
149 
150 static bool delete_br_byindex(int if_index)
151 {
152     bridge_t *br;
153     if(!(br = find_br(if_index)))
154         return false;
155 
156     INFO("Delete bridge %s (%d)", br->sysdeps.name, if_index);
157 
158     list_del(&br->list);
159     MSTP_IN_delete_bridge(br);
160     free(br);
161     return true;
162 }
163 
164 void bridge_one_second(void)
165 {
166     bridge_t *br;
167     list_for_each_entry(br, &bridges, list)
168         MSTP_IN_one_second(br);
169 }
170 
171 /* New MAC address is stored in addr, which also holds the old value on entry.
172    Return true if the address changed */
173 static bool check_mac_address(char *name, __u8 *addr)
174 {
175     __u8 temp_addr[ETH_ALEN];
176     if(get_hwaddr(name, temp_addr))
177     {
178         LOG("Error getting hw address: %s", name);
179         /* Error. Ignore the new value */
180         return false;
181     }
182     if(memcmp(addr, temp_addr, sizeof(temp_addr)) == 0)
183         return false;
184     else
185     {
186         memcpy(addr, temp_addr, sizeof(temp_addr));
187         return true;
188     }
189 }
190 
191 static void set_br_up(bridge_t * br, bool up)
192 {
193     bool changed = false;
194 
195     if(up != br->sysdeps.up)
196     {
197         INFO("%s was %s. Set %s", br->sysdeps.name,
198              br->sysdeps.up ? "up" : "down", up ? "up" : "down");
199         br->sysdeps.up = up;
200         changed = true;
201     }
202 
203     if(check_mac_address(br->sysdeps.name, br->sysdeps.macaddr))
204     {
205         /* MAC address changed */
206         /* Notify bridge address change */
207         MSTP_IN_set_bridge_address(br, br->sysdeps.macaddr);
208     }
209 
210     if(changed)
211         MSTP_IN_set_bridge_enable(br, br->sysdeps.up);
212 }
213 
214 static void set_if_up(port_t *prt, bool up)
215 {
216     INFO("Port %s : %s", prt->sysdeps.name, (up ? "up" : "down"));
217     int speed = -1;
218     int duplex = -1;
219     bool changed = false;
220     bool bpdu_filter;
221 
222     if(check_mac_address(prt->sysdeps.name, prt->sysdeps.macaddr))
223     {
224         /* MAC address changed */
225         if(check_mac_address(prt->bridge->sysdeps.name,
226            prt->bridge->sysdeps.macaddr))
227         {
228             /* Notify bridge address change */
229             MSTP_IN_set_bridge_address(prt->bridge,
230                                        prt->bridge->sysdeps.macaddr);
231         }
232     }
233 
234     if(!up)
235     { /* Down */
236         if(prt->sysdeps.up)
237         {
238             prt->sysdeps.up = false;
239             changed = true;
240         }
241     }
242     else
243     { /* Up */
244         int r = ethtool_get_speed_duplex(prt->sysdeps.name, &speed, &duplex);
245         if((r < 0) || (speed < 0))
246             speed = 10;
247         if((r < 0) || (duplex < 0))
248             duplex = 1; /* Assume full duplex */
249 
250         if(speed != prt->sysdeps.speed)
251         {
252             prt->sysdeps.speed = speed;
253             changed = true;
254         }
255         if(duplex != prt->sysdeps.duplex)
256         {
257             prt->sysdeps.duplex = duplex;
258             changed = true;
259         }
260         if(!prt->sysdeps.up)
261         {
262             prt->sysdeps.up = true;
263             changed = true;
264         }
265 
266         bpdu_filter = get_bpdu_filter(prt->sysdeps.name);
267         if (bpdu_filter != prt->bpduFilterPort) {
268             CIST_PortConfig cfg = {
269                     .bpdu_filter_port = bpdu_filter,
270                     .set_bpdu_filter_port = true
271             };
272 
273             MSTP_IN_set_cist_port_config(prt, &cfg);
274         }
275     }
276     if(changed)
277         MSTP_IN_set_port_enable(prt, prt->sysdeps.up, prt->sysdeps.speed,
278                                 prt->sysdeps.duplex);
279 }
280 
281 /* br_index == if_index means: interface is bridge master */
282 int bridge_notify(int br_index, int if_index, bool newlink, unsigned flags)
283 {
284     port_t *prt;
285     bridge_t *br = NULL, *other_br;
286     bool up = !!(flags & IFF_UP);
287     bool running = up && (flags & IFF_RUNNING);
288 
289     LOG("br_index %d, if_index %d, newlink %d, up %d, running %d",
290         br_index, if_index, newlink, up, running);
291 
292     if((br_index >= 0) && (br_index != if_index))
293     {
294         if(!(br = find_br(br_index)))
295             return -2; /* bridge not in list */
296         int br_flags = get_flags(br->sysdeps.name);
297         if(br_flags >= 0)
298             set_br_up(br, !!(br_flags & IFF_UP));
299     }
300 
301     if(br)
302     {
303         if(!(prt = find_if(br, if_index)))
304         {
305             if(!newlink)
306             {
307                 INFO("Got DELLINK for unknown port %d on "
308                      "bridge %d", if_index, br_index);
309                 return -1;
310             }
311             /* Check if this interface is slave of another bridge */
312             list_for_each_entry(other_br, &bridges, list)
313             {
314                 if(other_br != br)
315                     if(delete_if_byindex(other_br, if_index))
316                     {
317                         INFO("Device %d has come to bridge %d. "
318                              "Missed notify for deletion from bridge %d",
319                              if_index, br_index, other_br->sysdeps.if_index);
320                         break;
321                     }
322             }
323             prt = create_if(br, if_index);
324         }
325         if(!prt)
326         {
327             ERROR("Couldn't create data for interface %d (master %d)",
328                   if_index, br_index);
329             return -1;
330         }
331         if(!newlink)
332         {
333             delete_if(prt);
334             return 0;
335         }
336         set_if_up(prt, running); /* And speed and duplex */
337     }
338     else
339     { /* Interface is not a bridge slave */
340         if(!newlink)
341         {
342             /* DELLINK not from bridge means interface unregistered. */
343             /* Cleanup removed bridge or removed bridge slave */
344             if(!delete_br_byindex(if_index))
345                 list_for_each_entry(br, &bridges, list)
346                 {
347                     if(delete_if_byindex(br, if_index))
348                         break;
349                 }
350             return 0;
351         }
352         else
353         { /* This may be a new link */
354             if(br_index == if_index)
355             {
356                 if(!(br = find_br(br_index)))
357                     return -2; /* bridge not in list */
358                 set_br_up(br, up);
359             }
360         }
361     }
362     return 0;
363 }
364 
365 struct llc_header
366 {
367     __u8 dest_addr[ETH_ALEN];
368     __u8 src_addr[ETH_ALEN];
369     __be16 len8023;
370     __u8 d_sap;
371     __u8 s_sap;
372     __u8 llc_ctrl;
373 } __attribute__((packed));
374 
375 /* LLC_PDU_xxx defines snitched from linux/net/llc_pdu.h */
376 #define LLC_PDU_LEN_U   3   /* header and 1 control byte */
377 #define LLC_PDU_TYPE_U  3   /* first two bits */
378 
379 /* 7.12.3 of 802.1D */
380 #define LLC_SAP_BSPAN   0x42
381 static const __u8 bridge_group_address[ETH_ALEN] =
382 {
383     0x01, 0x80, 0xc2, 0x00, 0x00, 0x00
384 };
385 
386 void bridge_bpdu_rcv(int if_index, const unsigned char *data, int len)
387 {
388     port_t *prt = NULL;
389     bridge_t *br;
390 
391     LOG("ifindex %d, len %d", if_index, len);
392 
393     list_for_each_entry(br, &bridges, list)
394     {
395         if((prt = find_if(br, if_index)))
396             break;
397     }
398     if(!prt)
399         return;
400 
401     /* sanity checks */
402     TSTM(br == prt->bridge,, "Bridge mismatch. This bridge is '%s' but port "
403         "'%s' belongs to bridge '%s'", br->sysdeps.name, prt->bridge->sysdeps.name);
404     TSTM(prt->sysdeps.up,, "Port '%s' should be up", prt->sysdeps.name);
405 
406     /* Validate Ethernet and LLC header,
407      * maybe we can skip this check thanks to Berkeley filter in packet socket?
408      */
409     struct llc_header *h;
410     unsigned int l;
411     TST(len > sizeof(struct llc_header),);
412     h = (struct llc_header *)data;
413     TST(0 == memcmp(h->dest_addr, bridge_group_address, ETH_ALEN),
414              INFO("ifindex %d, len %d, %02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
415                   if_index, len,
416                   h->dest_addr[0], h->dest_addr[1], h->dest_addr[2],
417                   h->dest_addr[3], h->dest_addr[4], h->dest_addr[5])
418        );
419     l = __be16_to_cpu(h->len8023);
420     TST(l <= ETH_DATA_LEN && l <= len - ETH_HLEN && l >= LLC_PDU_LEN_U, );
421     TST(h->d_sap == LLC_SAP_BSPAN && h->s_sap == LLC_SAP_BSPAN && (h->llc_ctrl & 0x3) == LLC_PDU_TYPE_U,);
422 
423     MSTP_IN_rx_bpdu(prt,
424                     /* Don't include LLC header */
425                     (bpdu_t *)(data + sizeof(*h)), l - LLC_PDU_LEN_U);
426 }
427 
428 static int br_set_state(struct rtnl_handle *rth, unsigned ifindex, __u8 state)
429 {
430     struct
431     {
432         struct nlmsghdr n;
433         struct ifinfomsg ifi;
434         char buf[256];
435     } req;
436 
437     memset(&req, 0, sizeof(req));
438 
439     req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
440     req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE;
441     req.n.nlmsg_type = RTM_SETLINK;
442     req.ifi.ifi_family = AF_BRIDGE;
443     req.ifi.ifi_index = ifindex;
444 
445     addattr8(&req.n, sizeof(req.buf), IFLA_PROTINFO, state);
446 
447     return rtnl_talk(rth, &req.n, 0, 0, NULL, NULL, NULL);
448 }
449 
450 static int br_flush_port(char *ifname)
451 {
452     char fname[128];
453     snprintf(fname, sizeof(fname), SYSFS_CLASS_NET "/%s/brport/flush", ifname);
454     int fd = open(fname, O_WRONLY);
455     TSTM(0 <= fd, -1, "Couldn't open flush file %s for write: %m", fname);
456     int write_result = write(fd, "1", 1);
457     close(fd);
458     TST(1 == write_result, -1);
459     return 0;
460 }
461 
462 static int br_set_ageing_time(char *brname, unsigned int ageing_time)
463 {
464     char fname[128], str_time[32];
465     snprintf(fname, sizeof(fname), SYSFS_CLASS_NET "/%s/bridge/ageing_time",
466              brname);
467     int fd = open(fname, O_WRONLY);
468     TSTM(0 <= fd, -1, "Couldn't open file %s for write: %m", fname);
469     int len = sprintf(str_time, "%u", ageing_time * HZ);
470     int write_result = write(fd, str_time, len);
471     close(fd);
472     TST(len == write_result, -1);
473     return 0;
474 }
475 
476 /* External actions for MSTP protocol */
477 
478 void MSTP_OUT_set_state(per_tree_port_t *ptp, int new_state)
479 {
480     char * state_name;
481     port_t *prt = ptp->port;
482     bridge_t *br = prt->bridge;
483 
484     if(ptp->state == new_state)
485         return;
486     ptp->state = driver_set_new_state(ptp, new_state);
487 
488     switch(ptp->state)
489     {
490         case BR_STATE_LISTENING:
491             state_name = "listening";
492             break;
493         case BR_STATE_LEARNING:
494             state_name = "learning";
495             break;
496         case BR_STATE_FORWARDING:
497             state_name = "forwarding";
498             ++(prt->num_trans_fwd);
499             break;
500         case BR_STATE_BLOCKING:
501             state_name = "blocking";
502             ++(prt->num_trans_blk);
503             break;
504         default:
505         case BR_STATE_DISABLED:
506             state_name = "disabled";
507             break;
508     }
509     INFO_MSTINAME(br, prt, ptp, "entering %s state", state_name);
510 
511     /* Translate new CIST state to the kernel bridge code */
512     if(0 == ptp->MSTID)
513     { /* CIST */
514         if(0 > br_set_state(&rth_state, prt->sysdeps.if_index, ptp->state))
515             INFO_PRTNAME(br, prt, "Couldn't set kernel bridge state %s",
516                           state_name);
517     }
518 }
519 
520 /* This function initiates process of flushing
521  * all entries for the given port in all FIDs for the
522  * given tree.
523  * When this process finishes, implementation should signal
524  * this by calling MSTP_IN_all_fids_flushed(per_tree_port_t *ptp)
525  */
526 void MSTP_OUT_flush_all_fids(per_tree_port_t * ptp)
527 {
528     port_t *prt = ptp->port;
529     bridge_t *br = prt->bridge;
530 
531     /* Translate CIST flushing to the kernel bridge code */
532     if(0 == ptp->MSTID)
533     { /* CIST */
534         if(0 > br_flush_port(prt->sysdeps.name))
535             ERROR_PRTNAME(br, prt,
536                           "Couldn't flush kernel bridge forwarding database");
537     }
538     /* Completion signal MSTP_IN_all_fids_flushed will be called by driver */
539     INFO_MSTINAME(br, prt, ptp, "Flushing forwarding database");
540     driver_flush_all_fids(ptp);
541 }
542 
543 void MSTP_OUT_set_ageing_time(port_t *prt, unsigned int ageingTime)
544 {
545     unsigned int actual_ageing_time;
546     bridge_t *br = prt->bridge;
547 
548     actual_ageing_time = driver_set_ageing_time(prt, ageingTime);
549     INFO_PRTNAME(br, prt, "Setting new ageing time to %u", actual_ageing_time);
550 
551     /*
552      * Translate new ageing time to the kernel bridge code.
553      * Kernel bridging code does not support per-port ageing time,
554      * so set ageing time for the whole bridge.
555      */
556     if(0 > br_set_ageing_time(br->sysdeps.name, actual_ageing_time))
557         ERROR_BRNAME(br, "Couldn't set new ageing time in kernel bridge");
558 }
559 
560 void MSTP_OUT_tx_bpdu(port_t *prt, bpdu_t * bpdu, int size)
561 {
562     char *bpdu_type, *tcflag;
563     bridge_t *br = prt->bridge;
564 
565     switch(bpdu->protocolVersion)
566     {
567         case protoSTP:
568             switch(bpdu->bpduType)
569             {
570                 case bpduTypeConfig:
571                     bpdu_type = "STP-Config";
572                     break;
573                 case bpduTypeTCN:
574                     bpdu_type = "STP-TCN";
575                     break;
576                 default:
577                     bpdu_type = "STP-UnknownType";
578             }
579             break;
580         case protoRSTP:
581             bpdu_type = "RST";
582             break;
583         case protoMSTP:
584             bpdu_type = "MST";
585             break;
586         default:
587             bpdu_type = "UnknownProto";
588     }
589 
590     ++(prt->num_tx_bpdu);
591     if((protoSTP == bpdu->protocolVersion) && (bpduTypeTCN == bpdu->bpduType))
592     {
593         ++(prt->num_tx_tcn);
594         LOG_PRTNAME(br, prt, "sending %s BPDU", bpdu_type);
595     }
596     else
597     {
598         tcflag = "";
599         if(bpdu->flags & (1 << offsetTc))
600         {
601             ++(prt->num_tx_tcn);
602             tcflag = ", tcFlag";
603         }
604         LOG_PRTNAME(br, prt, "sending %s BPDU%s", bpdu_type, tcflag);
605     }
606 
607     struct llc_header h;
608     memcpy(h.dest_addr, bridge_group_address, ETH_ALEN);
609     memcpy(h.src_addr, prt->sysdeps.macaddr, ETH_ALEN);
610     h.len8023 = __cpu_to_be16(size + LLC_PDU_LEN_U);
611     h.d_sap = h.s_sap = LLC_SAP_BSPAN;
612     h.llc_ctrl = LLC_PDU_TYPE_U;
613 
614     struct iovec iov[2] =
615     {
616         { .iov_base = &h, .iov_len = sizeof(h) },
617         { .iov_base = bpdu, .iov_len = size }
618     };
619 
620     packet_send(prt->sysdeps.if_index, iov, 2, sizeof(h) + size);
621 }
622 
623 void MSTP_OUT_shutdown_port(port_t *prt)
624 {
625     if(0 > if_shutdown(prt->sysdeps.name))
626         ERROR_PRTNAME(prt->bridge, prt, "Couldn't shutdown port");
627 }
628 
629 static int not_dot_dotdot(const struct dirent *entry)
630 {
631         const char *n = entry->d_name;
632 
633         return strcmp(n, ".") || strcmp(n, "..");
634 }
635 
636 static int get_port_list(const char *br_ifname, struct dirent ***namelist)
637 {
638         char buf[256];
639 
640         snprintf(buf, sizeof(buf), SYSFS_CLASS_NET "/%.230s/brif", br_ifname);
641 
642         return scandir(buf, namelist, not_dot_dotdot, versionsort);
643 }
644 
645 int bridge_create(int bridge_idx, CIST_BridgeConfig *cfg)
646 {
647         struct dirent **namelist;
648         int *port_list, n_ports;
649         bridge_t *br, *other_br;
650     port_t *port, *tmp;
651         int flags;
652         bool found;
653         int i;
654 
655         br = find_br(bridge_idx);
656         if (!br)
657                 br = create_br(bridge_idx);
658         if (!br)
659                 return -1;
660 
661         MSTP_IN_set_cist_bridge_config(br, cfg);
662 
663         flags = get_flags(br->sysdeps.name);
664         if (flags >= 0)
665                 set_br_up(br, !!(flags & IFF_UP));
666 
667         n_ports = get_port_list(br->sysdeps.name, &namelist);
668         port_list = alloca(n_ports * sizeof(*port_list));
669 
670         for (i = 0; i < n_ports; i++) {
671                 port_list[i] = if_nametoindex(namelist[i]->d_name);
672                 free(namelist[i]);
673         }
674         free(namelist);
675 
676     list_for_each_entry_safe(port, tmp, &br->ports, br_list) {
677                 found = false;
678                 for (i = 0; i < n_ports; i++) {
679                         if (port->sysdeps.if_index != port_list[i])
680                                 continue;
681                         found = true;
682                         break;
683                 }
684 
685                 if (found)
686                         continue;
687 
688                 delete_if(port);
689         }
690 
691         for (i = 0; i < n_ports; i++) {
692                 port = find_if(br, port_list[i]);
693                 if (port)
694                         continue;
695 
696                 list_for_each_entry(other_br, &bridges, list) {
697                         if (br == other_br)
698                                 continue;
699 
700                         delete_if_byindex(other_br, port_list[i]);
701                 }
702 
703                 port = find_if(br, port_list[i]);
704                 if (!port)
705                         port = create_if(br, port_list[i]);
706                 if (!port)
707                         continue;
708 
709                 flags = get_flags(port->sysdeps.name);
710                 if (flags < 0)
711                         continue;
712 
713                 set_if_up(port, !(~flags & (IFF_UP | IFF_RUNNING)));
714         }
715 
716         return 0;
717 }
718 
719 void bridge_delete(int index)
720 {
721         delete_br_byindex(index);
722 }
723 
724 int bridge_track_fini(void)
725 {
726     INFO("Stopping all bridges");
727     bridge_t *br;
728     list_for_each_entry(br, &bridges, list)
729     {
730         set_br_up(br, false);
731     }
732     return 0;
733 }
734 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt