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 */
53static 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*/
60static int
61flush_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
102static void
103get_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 */
115static unsigned long
116get_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
124static int
125get_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
139static int
140get_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
159static const char *
160bls_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*/
173static int
174set_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 */
224static int
225set_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*/
234static int
235tx_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
257static const char *
258get_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*/
267static int
268get_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*/
276static int
277get_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
351static void
352trace(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
361static 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
376void
377rstp_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 */
395void
396rstp_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 */
446void
447rstp_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
478boolean_t
479rstp_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
560failure:
561	(void) STP_IN_port_remove(port->vlan_id, port->port_index);
562	port->stp_added = B_FALSE;
563	return (B_FALSE);
564}
565