xref: /titanic_50/usr/src/cmd/cmd-inet/usr.lib/bridged/rstp.c (revision 1f0c339e39c0c782f9d4c4f3f11d373e24e1d76f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * bridged - bridging control daemon.  This module provides functions related
29  * to the librstp (Rapid Spanning Tree Protocol) library.
30  */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <stdarg.h>
36 #include <string.h>
37 #include <sys/types.h>
38 #include <syslog.h>
39 #include <kstat.h>
40 #include <libdlpi.h>
41 #include <libdladm.h>
42 #include <libdllink.h>
43 #include <libdlstat.h>
44 #include <stp_in.h>
45 #include <stp_vectors.h>
46 #include <net/if_types.h>
47 #include <net/bridge.h>
48 #include <sys/ethernet.h>
49 
50 #include "global.h"
51 
52 /* current engine configuration; access protected by engine_lock */
53 static UID_STP_CFG_T uid_cfg;
54 
55 /*
56  * Our implementation doesn't have per-VLAN forwarding entries, so we just
57  * flush by the port.  If port number is zero, then flush entries.
58  */
59 /*ARGSUSED1*/
60 static int
61 flush_lt(int port_index, int vlan_id, LT_FLASH_TYPE_T type, char *reason)
62 {
63 	struct portdata *pd;
64 	const char *portname;
65 	bridge_flushfwd_t bff;
66 
67 	if (port_index > nextport || port_index < 0)
68 		return (0);
69 
70 	if (port_index == 0) {
71 		type = LT_FLASH_ONLY_THE_PORT;
72 		portname = "all";
73 		bff.bff_linkid = DATALINK_INVALID_LINKID;
74 	} else {
75 		pd = allports[port_index - 1];
76 		portname = pd->name;
77 		bff.bff_linkid = pd->linkid;
78 	}
79 
80 	if (debugging) {
81 		syslog(LOG_DEBUG, "flush forwarding %s %s: %s",
82 		    type == LT_FLASH_ONLY_THE_PORT ? "to" : "except for",
83 		    portname, reason);
84 	}
85 
86 	bff.bff_exclude = (type == LT_FLASH_ALL_PORTS_EXCLUDE_THIS);
87 
88 	/*
89 	 * If flushing fails, we can't return.  The only safe thing to do is to
90 	 * tear down the bridge so that we're not harming the network.
91 	 */
92 	if (strioctl(control_fd, BRIOC_FLUSHFWD, &bff, sizeof (bff)) == -1) {
93 		syslog(LOG_ERR, "cannot flush forwarding entries on %s %s: %m",
94 		    instance_name, portname);
95 		unlock_engine();
96 		exit(EXIT_FAILURE);
97 	}
98 
99 	return (0);
100 }
101 
102 static void
103 get_port_mac(int port_index, unsigned char *mac)
104 {
105 	struct portdata *pd;
106 
107 	if (port_index > nextport || port_index <= 0)
108 		return;
109 
110 	pd = allports[port_index - 1];
111 	(void) memcpy(mac, pd->mac_addr, ETHERADDRL);
112 }
113 
114 /* Returns speed in megabits per second */
115 static unsigned long
116 get_port_oper_speed(unsigned int port_index)
117 {
118 	if (port_index > nextport || port_index == 0)
119 		return (1000UL);
120 	else
121 		return (allports[port_index - 1]->speed);
122 }
123 
124 static int
125 get_port_link_status(int port_index)
126 {
127 	struct portdata *pd;
128 
129 	if (port_index > nextport || port_index <= 0) {
130 		return (0);
131 	} else {
132 		pd = allports[port_index - 1];
133 		return (pd->phys_status && pd->admin_status &&
134 		    protect == DLADM_BRIDGE_PROT_STP && !pd->sdu_failed ?
135 		    1 : 0);
136 	}
137 }
138 
139 static int
140 get_duplex(int port_index)
141 {
142 	struct portdata *pd;
143 	link_duplex_t link_duplex;
144 	dladm_status_t status;
145 
146 	if (port_index > nextport || port_index <= 0)
147 		return (False);
148 
149 	pd = allports[port_index - 1];
150 	status = dladm_get_single_mac_stat(dlhandle, pd->linkid, "link_duplex",
151 	    KSTAT_DATA_UINT32, &link_duplex);
152 
153 	if (status == DLADM_STATUS_OK && link_duplex == LINK_DUPLEX_FULL)
154 		return (True);
155 	else
156 		return (False);
157 }
158 
159 static const char *
160 bls_state(bridge_state_t bstate)
161 {
162 	switch (bstate) {
163 	case BLS_LEARNING:
164 		return ("learning");
165 	case BLS_FORWARDING:
166 		return ("forwarding");
167 	default:
168 		return ("block/listen");
169 	}
170 }
171 
172 /*ARGSUSED1*/
173 static int
174 set_port_state(int port_index, int vlan_id, RSTP_PORT_STATE state)
175 {
176 	struct portdata *pd;
177 	bridge_setstate_t bss;
178 
179 	if (port_index > nextport || port_index <= 0)
180 		return (1);
181 
182 	pd = allports[port_index - 1];
183 
184 	if (debugging)
185 		syslog(LOG_DEBUG, "setting port state on port %d (%s) to %d",
186 		    port_index, pd->name, state);
187 	switch (state) {
188 	case UID_PORT_LEARNING:
189 		bss.bss_state = BLS_LEARNING;
190 		break;
191 	case UID_PORT_FORWARDING:
192 		bss.bss_state = BLS_FORWARDING;
193 		break;
194 	default:
195 		bss.bss_state = BLS_BLOCKLISTEN;
196 		break;
197 	}
198 	bss.bss_linkid = pd->linkid;
199 	if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) {
200 		syslog(LOG_ERR, "cannot set STP state on %s from %s to %s: %m",
201 		    pd->name, bls_state(pd->state), bls_state(bss.bss_state));
202 		/*
203 		 * If we've been unsuccessful in disabling forwarding, then the
204 		 * only safe thing to do is to make the daemon exit, so that
205 		 * the kernel will be forced to destroy the bridge state and
206 		 * terminate all forwarding.
207 		 */
208 		if (pd->state == BLS_FORWARDING &&
209 		    bss.bss_state != BLS_FORWARDING) {
210 			unlock_engine();
211 			exit(EXIT_FAILURE);
212 		}
213 	} else {
214 		pd->state = bss.bss_state;
215 	}
216 	return (0);
217 }
218 
219 /*
220  * Our hardware doesn't actually do anything different when STP is enabled or
221  * disabled, so this function does nothing.  It would be possible to open and
222  * close the DLPI stream here, if such a thing were necessary.
223  */
224 static int
225 set_hardware_mode(int vlan_id, UID_STP_MODE_T mode)
226 {
227 	if (debugging)
228 		syslog(LOG_DEBUG, "setting hardware mode on vlan %d to %d",
229 		    vlan_id, mode);
230 	return (0);
231 }
232 
233 /*ARGSUSED1*/
234 static int
235 tx_bpdu(int port_index, int vlan_id, unsigned char *bpdu, size_t bpdu_len)
236 {
237 	struct portdata *pdp;
238 	int rc;
239 
240 	if (port_index > nextport || port_index <= 0)
241 		return (1);
242 
243 	pdp = allports[port_index - 1];
244 	rc = dlpi_send(pdp->dlpi, NULL, 0, bpdu, bpdu_len, NULL);
245 	if (rc == DLPI_SUCCESS) {
246 		if (debugging)
247 			syslog(LOG_DEBUG, "transmitted %d byte BPDU on %s",
248 			    bpdu_len, pdp->name);
249 		return (0);
250 	} else {
251 		syslog(LOG_WARNING, "failed to send to %s: %s", pdp->name,
252 		    dlpi_strerror(rc));
253 		return (1);
254 	}
255 }
256 
257 static const char *
258 get_port_name(int port_index)
259 {
260 	if (port_index > nextport || port_index <= 0)
261 		return ("unknown");
262 	else
263 		return (allports[port_index - 1]->name);
264 }
265 
266 /*ARGSUSED*/
267 static int
268 get_init_stpm_cfg(int vlan_id, UID_STP_CFG_T *cfg)
269 {
270 	/* under engine_lock because it's a callback from the engine */
271 	*cfg = uid_cfg;
272 	return (0);
273 }
274 
275 /*ARGSUSED*/
276 static int
277 get_init_port_cfg(int vlan_id, int port_index, UID_STP_PORT_CFG_T *cfg)
278 {
279 	struct portdata *pdp;
280 	uint_t propval, valcnt;
281 	datalink_id_t linkid;
282 	dladm_status_t status;
283 
284 	if (port_index > nextport || port_index <= 0)
285 		return (1);
286 
287 	pdp = allports[port_index - 1];
288 
289 	cfg->field_mask = 0;
290 	cfg->port_priority = DEF_PORT_PRIO;
291 	cfg->admin_non_stp = DEF_ADMIN_NON_STP;
292 	cfg->admin_edge = DEF_ADMIN_EDGE;
293 	cfg->admin_port_path_cost = ADMIN_PORT_PATH_COST_AUTO;
294 	cfg->admin_point2point = DEF_P2P;
295 
296 	valcnt = 1;
297 	linkid = pdp->linkid;
298 	status = dladm_get_linkprop_values(dlhandle, linkid,
299 	    DLADM_PROP_VAL_PERSISTENT, "stp_priority", &propval, &valcnt);
300 	if (status == DLADM_STATUS_OK) {
301 		cfg->port_priority = propval;
302 		cfg->field_mask |= PT_CFG_PRIO;
303 	}
304 	status = dladm_get_linkprop_values(dlhandle, linkid,
305 	    DLADM_PROP_VAL_PERSISTENT, "stp", &propval, &valcnt);
306 	if (status == DLADM_STATUS_OK) {
307 		cfg->admin_non_stp = !propval;
308 		cfg->field_mask |= PT_CFG_NON_STP;
309 	}
310 	status = dladm_get_linkprop_values(dlhandle, linkid,
311 	    DLADM_PROP_VAL_PERSISTENT, "stp_edge", &propval, &valcnt);
312 	if (status == DLADM_STATUS_OK) {
313 		cfg->admin_edge = propval;
314 		cfg->field_mask |= PT_CFG_EDGE;
315 	}
316 	status = dladm_get_linkprop_values(dlhandle, linkid,
317 	    DLADM_PROP_VAL_PERSISTENT, "stp_cost", &propval, &valcnt);
318 	if (status == DLADM_STATUS_OK) {
319 		cfg->admin_port_path_cost = propval;
320 		cfg->field_mask |= PT_CFG_COST;
321 	}
322 	status = dladm_get_linkprop_values(dlhandle, linkid,
323 	    DLADM_PROP_VAL_PERSISTENT, "stp_p2p", &propval, &valcnt);
324 	if (status == DLADM_STATUS_OK) {
325 		cfg->admin_point2point = propval;
326 		cfg->field_mask |= PT_CFG_P2P;
327 	}
328 
329 	/*
330 	 * mcheck is special.  It is actually a command, but the 802 documents
331 	 * define it as a variable that spontaneously resets itself.  We need
332 	 * to handle that behavior here.
333 	 */
334 	status = dladm_get_linkprop_values(dlhandle, linkid,
335 	    DLADM_PROP_VAL_PERSISTENT, "stp_mcheck", &propval, &valcnt);
336 	if (status == DLADM_STATUS_OK && propval != 0) {
337 		char *pval = "0";
338 
339 		cfg->field_mask |= PT_CFG_MCHECK;
340 		(void) dladm_set_linkprop(dlhandle, linkid, "stp_mcheck", &pval,
341 		    1, DLADM_OPT_ACTIVE|DLADM_OPT_PERSIST|DLADM_OPT_NOREFRESH);
342 	}
343 
344 	pdp->admin_non_stp = cfg->admin_non_stp;
345 	if (!pdp->admin_non_stp)
346 		pdp->bpdu_protect = B_FALSE;
347 
348 	return (0);
349 }
350 
351 static void
352 trace(const char *fmt, ...)
353 {
354 	va_list ap;
355 
356 	va_start(ap, fmt);
357 	vsyslog(LOG_DEBUG, fmt, ap);
358 	va_end(ap);
359 }
360 
361 static STP_VECTORS_T stp_vectors = {
362 	flush_lt,
363 	get_port_mac,
364 	get_port_oper_speed,
365 	get_port_link_status,
366 	get_duplex,
367 	set_port_state,
368 	set_hardware_mode,
369 	tx_bpdu,
370 	get_port_name,
371 	get_init_stpm_cfg,
372 	get_init_port_cfg,
373 	trace
374 };
375 
376 void
377 rstp_init(void)
378 {
379 	dladm_status_t status;
380 	char buf[DLADM_STRSIZE];
381 
382 	STP_IN_init(&stp_vectors);
383 	status = dladm_bridge_get_properties(instance_name, &uid_cfg, &protect);
384 	if (status != DLADM_STATUS_OK) {
385 		syslog(LOG_ERR, "%s: unable to read properties: %s",
386 		    instance_name, dladm_status2str(status, buf));
387 		exit(EXIT_FAILURE);
388 	}
389 }
390 
391 /*
392  * This is called by a normal refresh operation.  It gets the engine properties
393  * and resets.
394  */
395 void
396 rstp_refresh(void)
397 {
398 	dladm_status_t status;
399 	int rc;
400 	char buf[DLADM_STRSIZE];
401 	UID_STP_CFG_T new_cfg;
402 	dladm_bridge_prot_t new_prot;
403 
404 	status = dladm_bridge_get_properties(instance_name, &new_cfg,
405 	    &new_prot);
406 	if (status != DLADM_STATUS_OK) {
407 		syslog(LOG_ERR, "%s: unable to refresh bridge properties: %s",
408 		    instance_name, dladm_status2str(status, buf));
409 	} else {
410 		if (debugging && (protect != new_prot ||
411 		    uid_cfg.stp_enabled != new_cfg.stp_enabled)) {
412 			syslog(LOG_DEBUG, "loop protection %s->%s, STP %d->%d",
413 			    dladm_bridge_prot2str(protect),
414 			    dladm_bridge_prot2str(new_prot),
415 			    uid_cfg.stp_enabled, new_cfg.stp_enabled);
416 		}
417 
418 		/*
419 		 * The engine doesn't take kindly to parameter changes while
420 		 * running.  Disable first if we must do this.
421 		 */
422 		if (uid_cfg.stp_enabled &&
423 		    memcmp(&uid_cfg, &new_cfg, sizeof (uid_cfg)) != 0) {
424 			syslog(LOG_DEBUG, "resetting state machine");
425 			uid_cfg.stp_enabled = STP_DISABLED;
426 			rc = STP_IN_stpm_set_cfg(0, &uid_cfg);
427 			if (rc != 0)
428 				syslog(LOG_ERR, "STP machine reset config: %s",
429 				    STP_IN_get_error_explanation(rc));
430 		}
431 
432 		uid_cfg = new_cfg;
433 		protect = new_prot;
434 		rc = STP_IN_stpm_set_cfg(0, &uid_cfg);
435 		if (rc != 0)
436 			syslog(LOG_ERR, "STP machine set config: %s",
437 			    STP_IN_get_error_explanation(rc));
438 	}
439 }
440 
441 /*
442  * This is called when a port changes its MAC address.  If it's the main port,
443  * the one that supplies us our bridge ID, then we must choose a new ID, and to
444  * do that we shut the bridge down and bring it back up.
445  */
446 void
447 rstp_change_mac(struct portdata *port, const unsigned char *newaddr)
448 {
449 	unsigned short prio;
450 	unsigned char mac[ETHERADDRL];
451 	int rc;
452 	char curid[ETHERADDRL * 3];
453 	char newmac[ETHERADDRL * 3];
454 
455 	(void) _link_ntoa(port->mac_addr, curid, ETHERADDRL, IFT_OTHER);
456 	(void) _link_ntoa(newaddr, newmac, ETHERADDRL, IFT_OTHER);
457 	STP_IN_get_bridge_id(port->vlan_id, &prio, mac);
458 	if (memcmp(port->mac_addr, mac, ETHERADDRL) == 0) {
459 		syslog(LOG_NOTICE, "bridge ID must change: ID %s on %s changed "
460 		    "to %s", curid, port->name, newmac);
461 		uid_cfg.stp_enabled = STP_DISABLED;
462 		if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0)
463 			syslog(LOG_ERR, "STP machine set config: %s",
464 			    STP_IN_get_error_explanation(rc));
465 		(void) memcpy(port->mac_addr, newaddr, ETHERADDRL);
466 		uid_cfg.stp_enabled = STP_ENABLED;
467 		if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0)
468 			syslog(LOG_ERR, "STP machine set config: %s",
469 			    STP_IN_get_error_explanation(rc));
470 	} else {
471 		syslog(LOG_DEBUG,
472 		    "MAC address on %s changed from %s to %s", port->name,
473 		    curid, newmac);
474 		(void) memcpy(port->mac_addr, newaddr, ETHERADDRL);
475 	}
476 }
477 
478 boolean_t
479 rstp_add_port(struct portdata *port)
480 {
481 	int rc;
482 	UID_STP_PORT_CFG_T portcfg;
483 	bridge_vlanenab_t bve;
484 	bridge_setstate_t bss;
485 
486 	if (!port->stp_added &&
487 	    (rc = STP_IN_port_add(port->vlan_id, port->port_index)) != 0) {
488 		syslog(LOG_ERR, "STP add %s %d: %s", port->name,
489 		    port->port_index, STP_IN_get_error_explanation(rc));
490 		return (B_FALSE);
491 	}
492 	port->stp_added = B_TRUE;
493 
494 	/* guaranteed to succeed at this point */
495 	(void) get_init_port_cfg(port->vlan_id, port->port_index, &portcfg);
496 
497 	/*
498 	 * Restore state when reenabling STP engine, set fixed state when
499 	 * disabling.  For TRILL, we don't control forwarding at all, but we
500 	 * need to turn off our controls for TRILL to do its thing.
501 	 */
502 	bss.bss_linkid = port->linkid;
503 	if (protect != DLADM_BRIDGE_PROT_STP) {
504 		bss.bss_state = port->state = BLS_BLOCKLISTEN;
505 	} else if (portcfg.admin_non_stp) {
506 		bss.bss_state = port->admin_status && !port->sdu_failed &&
507 		    !port->bpdu_protect ? BLS_FORWARDING : BLS_BLOCKLISTEN;
508 	} else {
509 		bss.bss_state = port->state;
510 	}
511 	if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) {
512 		syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name);
513 		goto failure;
514 	}
515 
516 	rc = STP_IN_enable_port(port->port_index,
517 	    port->admin_status && port->phys_status && !port->sdu_failed &&
518 	    protect == DLADM_BRIDGE_PROT_STP);
519 	if (rc != 0) {
520 		syslog(LOG_ERR, "STP enable %s %d: %s", port->name,
521 		    port->port_index, STP_IN_get_error_explanation(rc));
522 		goto failure;
523 	}
524 
525 	if (debugging) {
526 		rc = STP_IN_dbg_set_port_trace("all", True, 0,
527 		    port->port_index);
528 	} else {
529 		/* return to default debug state */
530 		rc = STP_IN_dbg_set_port_trace("all", False, 0,
531 		    port->port_index);
532 		if (rc == 0)
533 			rc = STP_IN_dbg_set_port_trace("sttrans", True, 0,
534 			    port->port_index);
535 	}
536 	if (rc != 0) {
537 		syslog(LOG_ERR, "STP trace %s %d: %s", port->name,
538 		    port->port_index, STP_IN_get_error_explanation(rc));
539 		goto failure;
540 	}
541 
542 	/* Clear out the kernel's allowed VLAN set; second walk will set */
543 	bve.bve_linkid = port->linkid;
544 	bve.bve_vlan = 0;
545 	bve.bve_onoff = B_FALSE;
546 	if (strioctl(control_fd, BRIOC_VLANENAB, &bve, sizeof (bve)) == -1) {
547 		syslog(LOG_ERR, "unable to disable VLANs on %s: %m",
548 		    port->name);
549 		goto failure;
550 	}
551 
552 	if ((rc = STP_IN_port_set_cfg(0, port->port_index, &portcfg)) != 0) {
553 		syslog(LOG_ERR, "STP port configure %s %d: %s", port->name,
554 		    port->port_index, STP_IN_get_error_explanation(rc));
555 		goto failure;
556 	}
557 
558 	return (B_TRUE);
559 
560 failure:
561 	(void) STP_IN_port_remove(port->vlan_id, port->port_index);
562 	port->stp_added = B_FALSE;
563 	return (B_FALSE);
564 }
565