]> git.ipfire.org Git - people/ms/mstpd.git/blob - bridge_track.c
ba261ca478420f8020bf38cc879c9ba0a646b247
[people/ms/mstpd.git] / 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 <vitas@nppfactor.kiev.ua>
24
25 ******************************************************************************/
26
27 #include <string.h>
28 #include <linux/if_bridge.h>
29 #include <asm/byteorder.h>
30
31 #include "bridge_ctl.h"
32 #include "netif_utils.h"
33 #include "packet.h"
34 #include "log.h"
35 #include "mstp.h"
36
37 static LIST_HEAD(bridges);
38
39 static bridge_t * create_br(int if_index)
40 {
41 bridge_t *br;
42 TST((br = calloc(1, sizeof(*br))) != NULL, NULL);
43
44 /* Init system dependent info */
45 br->sysdeps.if_index = if_index;
46 if_indextoname(if_index, br->sysdeps.name);
47 get_hwaddr(br->sysdeps.name, br->sysdeps.macaddr);
48
49 INFO("Add bridge %s", br->sysdeps.name);
50 if(!MSTP_IN_bridge_create(br, br->sysdeps.macaddr))
51 {
52 free(br);
53 return NULL;
54 }
55
56 list_add_tail(&br->list, &bridges);
57 return br;
58 }
59
60 static bridge_t * find_br(int if_index)
61 {
62 bridge_t *br;
63 list_for_each_entry(br, &bridges, list)
64 {
65 if(br->sysdeps.if_index == if_index)
66 return br;
67 }
68 return NULL;
69 }
70
71 static port_t * create_if(bridge_t * br, int if_index)
72 {
73 port_t *ifc;
74 TST((ifc = calloc(1, sizeof(*ifc))) != NULL, NULL);
75
76 /* Init system dependent info */
77 ifc->sysdeps.if_index = if_index;
78 if_indextoname(if_index, ifc->sysdeps.name);
79 get_hwaddr(ifc->sysdeps.name, ifc->sysdeps.macaddr);
80
81 int portno;
82 if(0 > (portno = get_bridge_portno(ifc->sysdeps.name)))
83 {
84 ERROR("Couldn't get port number for %s", ifc->sysdeps.name);
85 free(ifc);
86 return NULL;
87 }
88 if((0 == portno) || (portno > MAX_PORT_NUMBER))
89 {
90 ERROR("Port number for %s is invalid (%d)", ifc->sysdeps.name, portno);
91 free(ifc);
92 return NULL;
93 }
94
95 INFO("Add iface %s as port#%d to bridge %s", ifc->sysdeps.name,
96 portno, br->sysdeps.name);
97 ifc->bridge = br;
98 if(!MSTP_IN_port_create_and_add_tail(ifc, portno))
99 {
100 free(ifc);
101 return NULL;
102 }
103
104 return ifc;
105 }
106
107 static port_t * find_if(bridge_t * br, int if_index)
108 {
109 port_t *ifc;
110 list_for_each_entry(ifc, &br->ports, br_list)
111 {
112 if(ifc->sysdeps.if_index == if_index)
113 return ifc;
114 }
115 return NULL;
116 }
117
118 static inline void delete_if(port_t * ifc)
119 {
120 list_del(&ifc->br_list);
121 MSTP_IN_delete_port(ifc);
122 free(ifc);
123 }
124
125 static inline bool delete_if_byindex(bridge_t * br, int if_index)
126 {
127 port_t *ifc;
128 if(!(ifc = find_if(br, if_index)))
129 return false;
130 delete_if(ifc);
131 return true;
132 }
133
134 static bool delete_br_byindex(int if_index)
135 {
136 bridge_t *br;
137 if(!(br = find_br(if_index)))
138 return false;
139 list_del(&br->list);
140 MSTP_IN_delete_bridge(br);
141 free(br);
142 return true;
143 }
144
145 void bridge_one_second(void)
146 {
147 bridge_t *br;
148 list_for_each_entry(br, &bridges, list)
149 MSTP_IN_one_second(br);
150 }
151
152 /* New MAC address is stored in addr, which also holds the old value on entry.
153 Return true if the address changed */
154 static bool check_mac_address(char *name, __u8 *addr)
155 {
156 __u8 temp_addr[ETH_ALEN];
157 if(get_hwaddr(name, temp_addr))
158 {
159 LOG("Error getting hw address: %s", name);
160 /* Error. Ignore the new value */
161 return false;
162 }
163 if(memcmp(addr, temp_addr, sizeof(temp_addr)) == 0)
164 return false;
165 else
166 {
167 memcpy(addr, temp_addr, sizeof(temp_addr));
168 return true;
169 }
170 }
171
172 static int stp_enabled(bridge_t * br)
173 {
174 char path[40 + IFNAMSIZ];
175 sprintf(path, "/sys/class/net/%s/bridge/stp_state", br->sysdeps.name);
176 FILE *f = fopen(path, "r");
177 int enabled = 0;
178 if(!f || (1 != fscanf(f, "%d", &enabled)))
179 ERROR("Can't read from %s", path);
180 fclose(f);
181 INFO("STP on %s state %d", br->sysdeps.name, enabled);
182
183 return enabled == 2; /* ie user mode STP */
184 }
185
186 static void set_br_up(bridge_t * br, bool up)
187 {
188 int stp_up = stp_enabled(br);
189 INFO("%s was %s stp was %s", br->sysdeps.name,
190 br->sysdeps.up ? "up" : "down", br->sysdeps.stp_up ? "up" : "down");
191 INFO("Set bridge %s %s stp %s" , br->sysdeps.name,
192 up ? "up" : "down", stp_up ? "up" : "down");
193
194 bool changed = false;
195
196 if(up != br->sysdeps.up)
197 {
198 br->sysdeps.up = up;
199 changed = true;
200 }
201
202 if(br->sysdeps.stp_up != stp_up)
203 {
204 br->sysdeps.stp_up = stp_up;
205 changed = true;
206 }
207
208 if(check_mac_address(br->sysdeps.name, br->sysdeps.macaddr))
209 {
210 /* MAC address changed */
211 /* Notify bridge address change */
212 MSTP_IN_set_bridge_address(br, br->sysdeps.macaddr);
213 }
214
215 if(changed)
216 MSTP_IN_set_bridge_enable(br, br->sysdeps.up && br->sysdeps.stp_up);
217 }
218
219 static void set_if_up(port_t * ifc, bool up)
220 {
221 INFO("Port %s : %s", ifc->sysdeps.name, (up ? "up" : "down"));
222 int speed = -1;
223 int duplex = -1;
224 bool changed = false;
225
226 if(check_mac_address(ifc->sysdeps.name, ifc->sysdeps.macaddr))
227 {
228 /* MAC address changed */
229 if(check_mac_address(ifc->bridge->sysdeps.name,
230 ifc->bridge->sysdeps.macaddr))
231 {
232 /* Notify bridge address change */
233 MSTP_IN_set_bridge_address(ifc->bridge,
234 ifc->bridge->sysdeps.macaddr);
235 }
236 }
237
238 if(!up)
239 { /* Down */
240 if(ifc->sysdeps.up)
241 {
242 ifc->sysdeps.up = false;
243 changed = true;
244 }
245 }
246 else
247 { /* Up */
248 int r = ethtool_get_speed_duplex(ifc->sysdeps.name, &speed, &duplex);
249 if((r < 0) || (speed < 0))
250 speed = 10;
251 if((r < 0) || (duplex < 0))
252 duplex = 0; /* Assume half duplex */
253
254 if(speed != ifc->sysdeps.speed)
255 {
256 ifc->sysdeps.speed = speed;
257 changed = true;
258 }
259 if(duplex != ifc->sysdeps.duplex)
260 {
261 ifc->sysdeps.duplex = duplex;
262 changed = true;
263 }
264 if(!ifc->sysdeps.up)
265 {
266 ifc->sysdeps.up = true;
267 changed = true;
268 }
269 }
270 if(changed)
271 MSTP_IN_set_port_enable(ifc, ifc->sysdeps.up, ifc->sysdeps.speed,
272 ifc->sysdeps.duplex);
273 }
274
275 /* br_index == if_index means: interface is bridge master */
276 int bridge_notify(int br_index, int if_index, bool newlink, bool up)
277 {
278 port_t *ifc;
279 bridge_t *br = NULL, *other_br;
280
281 LOG("br_index %d, if_index %d, newlink %d, up %d",
282 br_index, if_index, newlink, up);
283
284 if((br_index >= 0) && (br_index != if_index))
285 {
286 if(!(br = find_br(br_index)))
287 br = create_br(br_index);
288 if(!br)
289 {
290 ERROR("Couldn't create data for bridge interface %d", br_index);
291 return -1;
292 }
293 int br_up = ethtool_get_link(br->sysdeps.name);
294 if(br_up >= 0)
295 set_br_up(br, !!br_up);
296 }
297
298 if(br)
299 {
300 if(!(ifc = find_if(br, if_index)))
301 {
302 if(!newlink)
303 {
304 INFO("Got DELLINK for unknown port %d on "
305 "bridge %d", if_index, br_index);
306 return -1;
307 }
308 /* Check if this interface is slave of another bridge */
309 list_for_each_entry(other_br, &bridges, list)
310 {
311 if(other_br != br)
312 if(delete_if_byindex(other_br, if_index))
313 {
314 INFO("Device %d has come to bridge %d. "
315 "Missed notify for deletion from bridge %d",
316 if_index, br_index, other_br->sysdeps.if_index);
317 break;
318 }
319 }
320 ifc = create_if(br, if_index);
321 }
322 if(!ifc)
323 {
324 ERROR("Couldn't create data for interface %d (master %d)",
325 if_index, br_index);
326 return -1;
327 }
328 if(!newlink)
329 {
330 delete_if(ifc);
331 return 0;
332 }
333 set_if_up(ifc, up); /* And speed and duplex */
334 }
335 else
336 { /* Interface is not a bridge slave */
337 if(!newlink)
338 {
339 /* DELLINK not from bridge means interface unregistered. */
340 /* Cleanup removed bridge or removed bridge slave */
341 if(!delete_br_byindex(if_index))
342 list_for_each_entry(br, &bridges, list)
343 {
344 if(delete_if_byindex(br, if_index))
345 break;
346 }
347 return 0;
348 }
349 else
350 { /* This may be a new link */
351 if(br_index == if_index)
352 {
353 if(!(br = find_br(br_index)))
354 {
355 if(!(br = create_br(br_index)))
356 {
357 ERROR("Couldn't create data for bridge interface %d",
358 br_index);
359 return -1;
360 }
361 }
362 set_br_up(br, up);
363 }
364 }
365 }
366 return 0;
367 }
368
369 struct llc_header
370 {
371 __u8 dest_addr[ETH_ALEN];
372 __u8 src_addr[ETH_ALEN];
373 __be16 len8023;
374 __u8 d_sap;
375 __u8 s_sap;
376 __u8 llc_ctrl;
377 } __attribute__((packed));
378
379 /* LLC_PDU_xxx defines snitched from linux/net/llc_pdu.h */
380 #define LLC_PDU_LEN_U 3 /* header and 1 control byte */
381 #define LLC_PDU_TYPE_U 3 /* first two bits */
382
383 /* 7.12.3 of 802.1D */
384 #define LLC_SAP_BSPAN 0x42
385 static const __u8 bridge_group_address[ETH_ALEN] =
386 {
387 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00
388 };
389
390 void bridge_bpdu_rcv(int if_index, const unsigned char *data, int len)
391 {
392 port_t *ifc = NULL;
393 bridge_t *br;
394
395 LOG("ifindex %d, len %d", if_index, len);
396
397 list_for_each_entry(br, &bridges, list)
398 {
399 if((ifc = find_if(br, if_index)))
400 break;
401 }
402 if(!ifc)
403 return;
404
405 /* sanity checks */
406 TST(br == ifc->bridge,);
407 TST(ifc->sysdeps.up,);
408 if(!br->sysdeps.stp_up)
409 return;
410
411 /* Validate Ethernet and LLC header,
412 * maybe we can skip this check thanks to Berkeley filter in packet socket?
413 */
414 struct llc_header *h;
415 unsigned int l;
416 TST(len > sizeof(struct llc_header),);
417 h = (struct llc_header *)data;
418 TST(0 == memcmp(h->dest_addr, bridge_group_address, ETH_ALEN),
419 INFO("ifindex %d, len %d, %02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
420 if_index, len,
421 h->dest_addr[0], h->dest_addr[1], h->dest_addr[2],
422 h->dest_addr[3], h->dest_addr[4], h->dest_addr[5])
423 );
424 l = __be16_to_cpu(h->len8023);
425 TST(l <= ETH_DATA_LEN && l <= len - ETH_HLEN && l >= LLC_PDU_LEN_U, );
426 TST(h->d_sap == LLC_SAP_BSPAN && h->s_sap == LLC_SAP_BSPAN && (h->llc_ctrl & 0x3) == LLC_PDU_TYPE_U,);
427
428 MSTP_IN_rx_bpdu(ifc,
429 /* Don't include LLC header */
430 (bpdu_t *)(data + sizeof(*h)), l - LLC_PDU_LEN_U);
431 }
432
433 /* External actions for MSTP protocol */
434
435 void MSTP_OUT_set_state(per_tree_port_t *ptp, int new_state)
436 {
437 char * state_name;
438 port_t *ifc = ptp->port;
439 bridge_t *br = ifc->bridge;
440
441 switch(new_state)
442 {
443 case BR_STATE_LISTENING:
444 state_name = "listening";
445 break;
446 case BR_STATE_LEARNING:
447 state_name = "learning";
448 break;
449 case BR_STATE_FORWARDING:
450 state_name = "forwarding";
451 break;
452 case BR_STATE_BLOCKING:
453 state_name = "blocking";
454 break;
455 default:
456 ERROR_MSTINAME(br, ifc, ptp, "attempt to set invalid state %d",
457 new_state);
458 new_state = BR_STATE_DISABLED;
459 case BR_STATE_DISABLED:
460 state_name = "disabled";
461 break;
462 }
463
464 if(ptp->state == new_state)
465 return;
466
467 /* TODO: command driver to put the br:port:tree into new state */
468
469 ptp->state = new_state;
470 INFO_MSTINAME(br, ifc, ptp, "entering %s state", state_name);
471 }
472
473 /* This function initiates process of flushing
474 * all entries for the given port in all FIDs for the
475 * given tree.
476 * When this process finishes, implementation should signal
477 * this by calling MSTP_IN_all_fids_flushed(per_tree_port_t *ptp)
478 */
479 void MSTP_OUT_flush_all_fids(per_tree_port_t * ptp)
480 {
481 /* TODO: do real flushing.
482 * Make it asynchronous, with completion function calling
483 * MSTP_IN_all_fids_flushed(ptp)
484 */
485 MSTP_IN_all_fids_flushed(ptp);
486 }
487
488 /* ageingTime < 0 => command driver to use its internal setting */
489 void MSTP_OUT_set_ageing_time(bridge_t * br, int ageingTime)
490 {
491 /* TODO: do set new ageing time */
492 }
493
494 void MSTP_OUT_tx_bpdu(port_t * ifc, bpdu_t * bpdu, int size)
495 {
496 char *bpdu_type;
497 bridge_t *br = ifc->bridge;
498
499 switch(bpdu->protocolVersion)
500 {
501 case protoSTP:
502 switch(bpdu->bpduType)
503 {
504 case bpduTypeConfig:
505 bpdu_type = "STP-Config";
506 break;
507 case bpduTypeTCN:
508 bpdu_type = "STP-TCN";
509 break;
510 default:
511 bpdu_type = "STP-UnknownType";
512 }
513 break;
514 case protoRSTP:
515 bpdu_type = "RST";
516 break;
517 case protoMSTP:
518 bpdu_type = "MST";
519 break;
520 default:
521 bpdu_type = "UnknownProto";
522 }
523
524 LOG_PRTNAME(br, ifc, "sending %s BPDU", bpdu_type);
525
526 struct llc_header h;
527 memcpy(h.dest_addr, bridge_group_address, ETH_ALEN);
528 memcpy(h.src_addr, ifc->sysdeps.macaddr, ETH_ALEN);
529 h.len8023 = __cpu_to_be16(size + LLC_PDU_LEN_U);
530 h.d_sap = h.s_sap = LLC_SAP_BSPAN;
531 h.llc_ctrl = LLC_PDU_TYPE_U;
532
533 struct iovec iov[2] =
534 {
535 { .iov_base = &h, .iov_len = sizeof(h) },
536 { .iov_base = bpdu, .iov_len = size }
537 };
538
539 packet_send(ifc->sysdeps.if_index, iov, 2, sizeof(h) + size);
540 }
541
542 /* User interface commands */
543
544 #define CTL_CHECK_BRIDGE \
545 bridge_t *br = find_br(br_index); \
546 if(NULL == br) \
547 { \
548 ERROR("Couldn't find bridge with index %d", br_index); \
549 return -1; \
550 }
551
552 #define CTL_CHECK_BRIDGE_PORT \
553 CTL_CHECK_BRIDGE; \
554 port_t *prt = find_if(br, port_index); \
555 if(NULL == prt) \
556 { \
557 ERROR_BRNAME(br, "Couldn't find port with index %d", port_index); \
558 return -1; \
559 }
560
561 #define CTL_CHECK_BRIDGE_TREE \
562 CTL_CHECK_BRIDGE; \
563 tree_t *tree; \
564 bool found = false; \
565 __be16 MSTID = __cpu_to_be16(mstid); \
566 list_for_each_entry(tree, &br->trees, bridge_list) \
567 if(tree->MSTID == MSTID) \
568 { \
569 found = true; \
570 break; \
571 } \
572 if(!found) \
573 { \
574 ERROR_BRNAME(br, "Couldn't find MSTI with ID %hu", mstid); \
575 return -1; \
576 }
577
578 #define CTL_CHECK_BRIDGE_PERTREEPORT \
579 CTL_CHECK_BRIDGE_PORT; \
580 per_tree_port_t *ptp; \
581 bool found = false; \
582 __be16 MSTID = __cpu_to_be16(mstid); \
583 list_for_each_entry(ptp, &prt->trees, port_list) \
584 if(ptp->MSTID == MSTID) \
585 { \
586 found = true; \
587 break; \
588 } \
589 if(!found) \
590 { \
591 ERROR_PRTNAME(br, prt, "Couldn't find MSTI with ID %hu", mstid); \
592 return -1; \
593 }
594
595 int CTL_get_cist_bridge_status(int br_index, CIST_BridgeStatus *status,
596 char *root_port_name)
597 {
598 tree_t *cist;
599 per_tree_port_t *ptp;
600
601 CTL_CHECK_BRIDGE;
602 MSTP_IN_get_cist_bridge_status(br, status);
603
604 /* find root port name by root_port_id */
605 cist = GET_CIST_TREE(br);
606 *root_port_name = '\0';
607 list_for_each_entry(ptp, &cist->ports, tree_list)
608 if(ptp->portId == status->root_port_id)
609 {
610 strncpy(root_port_name, ptp->port->sysdeps.name, IFNAMSIZ);
611 break;
612 }
613 return 0;
614 }
615
616 int CTL_get_msti_bridge_status(int br_index, __u16 mstid,
617 MSTI_BridgeStatus *status, char *root_port_name)
618 {
619 per_tree_port_t *ptp;
620
621 CTL_CHECK_BRIDGE_TREE;
622 MSTP_IN_get_msti_bridge_status(tree, status);
623
624 /* find root port name by root_port_id */
625 *root_port_name = '\0';
626 list_for_each_entry(ptp, &tree->ports, tree_list)
627 if(ptp->portId == status->root_port_id)
628 {
629 strncpy(root_port_name, ptp->port->sysdeps.name, IFNAMSIZ);
630 break;
631 }
632 return 0;
633 }
634
635 int CTL_set_cist_bridge_config(int br_index, CIST_BridgeConfig *cfg)
636 {
637 CTL_CHECK_BRIDGE;
638 return MSTP_IN_set_cist_bridge_config(br, cfg);
639 }
640
641 int CTL_set_msti_bridge_config(int br_index, __u16 mstid, __u8 bridge_priority)
642 {
643 CTL_CHECK_BRIDGE_TREE;
644 return MSTP_IN_set_msti_bridge_config(tree, bridge_priority);
645 }
646
647 int CTL_get_cist_port_status(int br_index, int port_index,
648 CIST_PortStatus *status)
649 {
650 CTL_CHECK_BRIDGE_PORT;
651 MSTP_IN_get_cist_port_status(prt, status);
652 return 0;
653 }
654
655 int CTL_get_msti_port_status(int br_index, int port_index, __u16 mstid,
656 MSTI_PortStatus *status)
657 {
658 CTL_CHECK_BRIDGE_PERTREEPORT;
659 MSTP_IN_get_msti_port_status(ptp, status);
660 return 0;
661 }
662
663 int CTL_set_cist_port_config(int br_index, int port_index,
664 CIST_PortConfig *cfg)
665 {
666 CTL_CHECK_BRIDGE_PORT;
667 return MSTP_IN_set_cist_port_config(prt, cfg);
668 }
669
670 int CTL_set_msti_port_config(int br_index, int port_index, __u16 mstid,
671 MSTI_PortConfig *cfg)
672 {
673 CTL_CHECK_BRIDGE_PERTREEPORT;
674 return MSTP_IN_set_msti_port_config(ptp, cfg);
675 }
676
677 int CTL_port_mcheck(int br_index, int port_index)
678 {
679 CTL_CHECK_BRIDGE_PORT;
680 return MSTP_IN_port_mcheck(prt);
681 }
682
683 int CTL_set_debug_level(int level)
684 {
685 INFO("level %d", level);
686 log_level = level;
687 return 0;
688 }
689
690 int CTL_get_mstilist(int br_index, int *num_mstis, __u16 *mstids)
691 {
692 CTL_CHECK_BRIDGE;
693 return MSTP_IN_get_mstilist(br, num_mstis, mstids) ? 0 : -1;
694 }
695
696 int CTL_create_msti(int br_index, __u16 mstid)
697 {
698 CTL_CHECK_BRIDGE;
699 return MSTP_IN_create_msti(br, mstid) ? 0 : -1;
700 }
701
702 int CTL_delete_msti(int br_index, __u16 mstid)
703 {
704 CTL_CHECK_BRIDGE;
705 return MSTP_IN_delete_msti(br, mstid) ? 0 : -1;
706 }
707
708 int CTL_get_mstconfid(int br_index, mst_configuration_identifier_t *cfg)
709 {
710 CTL_CHECK_BRIDGE;
711 *cfg = br->MstConfigId;
712 return 0;
713 }
714
715 int CTL_set_mstconfid(int br_index, __u16 revision, char *name)
716 {
717 CTL_CHECK_BRIDGE;
718 MSTP_IN_set_mst_config_id(br, revision, name);
719 return 0;
720 }
721
722 int CTL_get_vids2fids(int br_index, __u16 *vids2fids)
723 {
724 CTL_CHECK_BRIDGE;
725 memcpy(vids2fids, br->vid2fid, sizeof(br->vid2fid));
726 return 0;
727 }
728
729 int CTL_get_fids2mstids(int br_index, __u16 *fids2mstids)
730 {
731 CTL_CHECK_BRIDGE;
732 int i;
733 for(i = 0; i < COUNT_OF(br->fid2mstid); ++i)
734 fids2mstids[i] = __be16_to_cpu(br->fid2mstid[i]);
735 return 0;
736 }
737
738 int CTL_set_vid2fid(int br_index, __u16 vid, __u16 fid)
739 {
740 CTL_CHECK_BRIDGE;
741 return MSTP_IN_set_vid2fid(br, vid, fid) ? 0 : -1;
742 }
743
744 int CTL_set_fid2mstid(int br_index, __u16 fid, __u16 mstid)
745 {
746 CTL_CHECK_BRIDGE;
747 return MSTP_IN_set_fid2mstid(br, fid, mstid) ? 0 : -1;
748 }
749
750 int CTL_set_vids2fids(int br_index, __u16 *vids2fids)
751 {
752 CTL_CHECK_BRIDGE;
753 return MSTP_IN_set_all_vids2fids(br, vids2fids) ? 0 : -1;
754 }
755
756 int CTL_set_fids2mstids(int br_index, __u16 *fids2mstids)
757 {
758 CTL_CHECK_BRIDGE;
759 return MSTP_IN_set_all_fids2mstids(br, fids2mstids) ? 0 : -1;
760 }