xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.lib/bridged/events.c (revision 726fad2a65f16c200a03969c29cb5c86c2d427db)
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 handles events and general
29  * port-related operations.
30  */
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <fcntl.h>
36 #include <string.h>
37 #include <sys/types.h>
38 #include <syslog.h>
39 #include <libdlpi.h>
40 #include <libdladm.h>
41 #include <libdllink.h>
42 #include <libdlbridge.h>
43 #include <libdlvlan.h>
44 #include <libdlstat.h>
45 #include <stp_in.h>
46 #include <stp_vectors.h>
47 #include <net/if_types.h>
48 #include <net/bridge.h>
49 #include <sys/ethernet.h>
50 
51 #include "global.h"
52 
53 int refresh_count = 1;	/* never zero */
54 dladm_bridge_prot_t protect = DLADM_BRIDGE_PROT_STP;
55 
56 /*
57  * The 'allports' array is an array of pointers to the struct portdata
58  * structures.  We reallocate 'allports' as needed, but the portdata must
59  * remain where it's initially allocated, because libdlpi's notification
60  * mechanism has a copy of a pointer to this structure.
61  */
62 uint_t nextport;
63 struct portdata **allports;
64 
65 /* Port allocation increment (arbitrary) */
66 #define	ALLOCINCR	10
67 static uint_t numports;
68 
69 static datalink_id_t main_linkid;
70 
71 int control_fd;
72 
73 static void
74 linkdown(void)
75 {
76 	(void) dladm_destroy_datalink_id(dlhandle, main_linkid,
77 	    DLADM_OPT_ACTIVE);
78 }
79 
80 void
81 open_bridge_control(void)
82 {
83 	bridge_newbridge_t bnb;
84 	dladm_status_t status;
85 	char buf[DLADM_STRSIZE];
86 
87 	if ((control_fd = open(BRIDGE_CTLPATH, O_RDWR | O_NONBLOCK)) == -1) {
88 		perror(BRIDGE_CTLPATH);
89 		exit(EXIT_FAILURE);
90 	}
91 	(void) snprintf(bnb.bnb_name, sizeof (bnb.bnb_name), "%s0",
92 	    instance_name);
93 	status = dladm_name2info(dlhandle, bnb.bnb_name, &bnb.bnb_linkid, NULL,
94 	    NULL, NULL);
95 	if (status != DLADM_STATUS_OK) {
96 		(void) fprintf(stderr, "bridged: %s: %s\n", bnb.bnb_name,
97 		    dladm_status2str(status, buf));
98 		exit(EXIT_FAILURE);
99 	}
100 	if (strioctl(control_fd, BRIOC_NEWBRIDGE, &bnb, sizeof (bnb)) == -1) {
101 		perror("NEWBRIDGE");
102 		exit(EXIT_FAILURE);
103 	}
104 	main_linkid = bnb.bnb_linkid;
105 	if (strioctl(control_fd, BRIOC_TABLEMAX, &tablemax,
106 	    sizeof (tablemax)) == -1) {
107 		syslog(LOG_ERR, "cannot set table max %lu on bridge %s: %m",
108 		    tablemax, instance_name);
109 		exit(EXIT_FAILURE);
110 	}
111 	/*
112 	 * This covers for any previous incarnation where we might have crashed
113 	 * or been SIGKILL'd and failed to take down the datalink.
114 	 */
115 	linkdown();
116 	(void) atexit(linkdown);
117 	status = dladm_up_datalink_id(dlhandle, bnb.bnb_linkid);
118 	if (status != DLADM_STATUS_OK) {
119 		(void) fprintf(stderr, "bridged: %s link up: %s\n",
120 		    bnb.bnb_name, dladm_status2str(status, buf));
121 		exit(EXIT_FAILURE);
122 	}
123 }
124 
125 struct portdata *
126 find_by_linkid(datalink_id_t linkid)
127 {
128 	int i;
129 	struct portdata *port;
130 
131 	for (i = 0; i < nextport; i++) {
132 		port = allports[i];
133 		if (port->linkid == linkid)
134 			return (port);
135 	}
136 	return (NULL);
137 }
138 
139 /*ARGSUSED2*/
140 static int
141 set_vlan(dladm_handle_t handle, datalink_id_t linkid, void *arg)
142 {
143 	struct portdata *port;
144 	dladm_status_t status;
145 	dladm_vlan_attr_t vinfo;
146 	char pointless[DLADM_STRSIZE];
147 	bridge_vlanenab_t bve;
148 
149 	status = dladm_vlan_info(handle, linkid, &vinfo, DLADM_OPT_ACTIVE);
150 	if (status != DLADM_STATUS_OK) {
151 		syslog(LOG_DEBUG, "can't get VLAN info on link ID %u: %s",
152 		    linkid, dladm_status2str(status, pointless));
153 		return (DLADM_WALK_CONTINUE);
154 	}
155 
156 	port = find_by_linkid(vinfo.dv_linkid);
157 	if (port == NULL || !port->kern_added)
158 		return (DLADM_WALK_CONTINUE);
159 
160 	bve.bve_linkid = port->linkid;
161 	bve.bve_vlan = vinfo.dv_vid;
162 	bve.bve_onoff = B_TRUE;
163 	if (strioctl(control_fd, BRIOC_VLANENAB, &bve, sizeof (bve)) == -1) {
164 		syslog(LOG_ERR, "unable to enable VLAN %d on linkid %u: %m",
165 		    vinfo.dv_vid, port->linkid);
166 		return (DLADM_WALK_TERMINATE);
167 	} else {
168 		return (DLADM_WALK_CONTINUE);
169 	}
170 }
171 
172 /*
173  * If the named port already exists, then update its configuration.  If it
174  * doesn't, then create and enable it.
175  */
176 static void
177 update_port(int vlan_id, const char *portname, datalink_id_t linkid,
178     datalink_class_t class)
179 {
180 	int posn;
181 	struct portdata *port;
182 	struct pollfd *fds;
183 	int port_index;
184 	struct {
185 		datalink_id_t linkid;
186 		char linkname[MAXLINKNAMELEN];
187 	} adddata;
188 	bridge_setpvid_t bsv;
189 	uint_t propval, valcnt;
190 	dladm_status_t status;
191 
192 	for (posn = 0; posn < nextport; posn++) {
193 		if (allports[posn]->linkid == linkid)
194 			break;
195 	}
196 
197 	/* If we need to allocate more array space, then do so in chunks. */
198 	if (posn >= numports) {
199 		struct portdata **newarr;
200 
201 		newarr = realloc(allports,
202 		    sizeof (*newarr) * (nextport + ALLOCINCR));
203 		if (newarr != NULL)
204 			allports = newarr;
205 		fds = realloc(fdarray,
206 		    sizeof (*fds) * (nextport + ALLOCINCR + FDOFFSET));
207 		if (fds != NULL)
208 			fdarray = fds;
209 		if (newarr == NULL || fds == NULL) {
210 			syslog(LOG_ERR, "unable to add %s; no memory",
211 			    portname);
212 			return;
213 		}
214 		numports = nextport + ALLOCINCR;
215 	}
216 
217 	port_index = posn + 1;
218 	fds = fdarray + posn + FDOFFSET;
219 
220 	/* If our linkid search ran to the end, then this is a new port. */
221 	if (posn == nextport) {
222 		if ((port = calloc(1, sizeof (*port))) == NULL) {
223 			syslog(LOG_ERR, "unable to add %s; no memory",
224 			    portname);
225 			return;
226 		}
227 		allports[posn] = port;
228 		port->vlan_id = vlan_id;
229 		port->linkid = linkid;
230 		port->port_index = port_index;
231 		port->phys_status = B_TRUE;
232 		port->admin_status = B_TRUE;
233 		port->state = BLS_BLOCKLISTEN;
234 		nextport++;
235 	} else {
236 		/* Located port by linkid; we're just updating existing data */
237 		port = allports[posn];
238 
239 		/*
240 		 * If it changed name, then close and reopen so we log under
241 		 * the most current name for this port.
242 		 */
243 		if (port->name != NULL && strcmp(portname, port->name) != 0) {
244 			if (port->dlpi != NULL)
245 				dlpi_close(port->dlpi);
246 			port->dlpi = NULL;
247 			port->name = NULL;
248 			fds->fd = -1;
249 			fds->events = 0;
250 		}
251 	}
252 
253 	/*
254 	 * If the port is not yet attached to the bridge in the kernel, then do
255 	 * that now.
256 	 */
257 	if (!port->kern_added) {
258 		adddata.linkid = linkid;
259 		(void) strlcpy(adddata.linkname, portname,
260 		    sizeof (adddata.linkname));
261 		if (strioctl(control_fd, BRIOC_ADDLINK, &adddata,
262 		    sizeof (adddata.linkid) + strlen(adddata.linkname)) == -1) {
263 			syslog(LOG_ERR, "cannot bridge %s: %m", portname);
264 			goto failure;
265 		}
266 		port->kern_added = B_TRUE;
267 	}
268 
269 	port->referenced = B_TRUE;
270 
271 	valcnt = 1;
272 	status = dladm_get_linkprop_values(dlhandle, linkid,
273 	    DLADM_PROP_VAL_PERSISTENT, "forward", &propval, &valcnt);
274 	if (status == DLADM_STATUS_OK)
275 		port->admin_status = propval;
276 
277 	bsv.bsv_vlan = 1;
278 	status = dladm_get_linkprop_values(dlhandle, linkid,
279 	    DLADM_PROP_VAL_PERSISTENT, "default_tag", &propval, &valcnt);
280 	if (status == DLADM_STATUS_OK)
281 		bsv.bsv_vlan = propval;
282 
283 	bsv.bsv_linkid = linkid;
284 	if (strioctl(control_fd, BRIOC_SETPVID, &bsv, sizeof (bsv)) == -1) {
285 		syslog(LOG_ERR, "can't set PVID on %s: %m", portname);
286 		goto failure;
287 	}
288 
289 	if (port->dlpi == NULL) {
290 		if (!port_dlpi_open(portname, port, class))
291 			goto failure;
292 		fds->fd = dlpi_fd(port->dlpi);
293 		fds->events = POLLIN;
294 	}
295 
296 	if (rstp_add_port(port))
297 		return;
298 
299 failure:
300 	if (port->dlpi != NULL) {
301 		dlpi_close(port->dlpi);
302 		port->dlpi = NULL;
303 		port->name = NULL;
304 		fds->fd = -1;
305 		fds->events = 0;
306 	}
307 	if (port->kern_added) {
308 		if (strioctl(control_fd, BRIOC_REMLINK, &port->linkid,
309 		    sizeof (port->linkid)) == -1)
310 			syslog(LOG_ERR, "cannot remove from bridge %s: %m",
311 			    portname);
312 		else
313 			port->kern_added = B_FALSE;
314 	}
315 	if (posn + 1 == nextport) {
316 		free(port);
317 		nextport--;
318 	}
319 }
320 
321 /*ARGSUSED2*/
322 static int
323 update_link(dladm_handle_t handle, datalink_id_t linkid, void *arg)
324 {
325 	dladm_status_t status;
326 	dladm_conf_t conf;
327 	char bridge[MAXLINKNAMELEN], linkname[MAXLINKNAMELEN];
328 	char pointless[DLADM_STRSIZE];
329 	datalink_class_t class;
330 
331 	status = dladm_read_conf(handle, linkid, &conf);
332 	if (status != DLADM_STATUS_OK) {
333 		syslog(LOG_DEBUG, "can't get status on link ID %u: %s", linkid,
334 		    dladm_status2str(status, pointless));
335 		return (DLADM_WALK_CONTINUE);
336 	}
337 
338 	status = dladm_bridge_getlink(handle, linkid, bridge, sizeof (bridge));
339 	if (status == DLADM_STATUS_OK && strcmp(bridge, instance_name) == 0) {
340 		status = dladm_datalink_id2info(handle, linkid, NULL, &class,
341 		    NULL, linkname, sizeof (linkname));
342 		if (status == DLADM_STATUS_OK) {
343 			update_port(0, linkname, linkid, class);
344 		} else {
345 			syslog(LOG_ERR, "unable to get link info for ID %u: %s",
346 			    linkid, dladm_status2str(status, pointless));
347 		}
348 	} else if (debugging) {
349 		if (status != DLADM_STATUS_OK)
350 			syslog(LOG_DEBUG,
351 			    "unable to get bridge data for ID %u: %s",
352 			    linkid, dladm_status2str(status, pointless));
353 		else
354 			syslog(LOG_DEBUG, "link ID %u is on bridge %s, not %s",
355 			    linkid, bridge, instance_name);
356 	}
357 	dladm_destroy_conf(handle, conf);
358 	return (DLADM_WALK_CONTINUE);
359 }
360 
361 /*
362  * Refresh action - reread configuration properties.
363  */
364 static void
365 handle_refresh(int sigfd)
366 {
367 	int i;
368 	struct portdata *pdp;
369 	struct pollfd *fdp;
370 	char buf[16];
371 	dladm_status_t status;
372 	boolean_t new_debug;
373 	uint32_t new_tablemax;
374 
375 	/* Drain signal events from pipe */
376 	if (sigfd != -1)
377 		(void) read(sigfd, buf, sizeof (buf));
378 
379 	status = dladm_bridge_get_privprop(instance_name, &new_debug,
380 	    &new_tablemax);
381 	if (status == DLADM_STATUS_OK) {
382 		if (debugging && !new_debug)
383 			syslog(LOG_DEBUG, "disabling debugging");
384 		debugging = new_debug;
385 		if (new_tablemax != tablemax) {
386 			syslog(LOG_DEBUG, "changed tablemax from %lu to %lu",
387 			    tablemax, new_tablemax);
388 			if (strioctl(control_fd, BRIOC_TABLEMAX, &new_tablemax,
389 			    sizeof (tablemax)) == -1)
390 				syslog(LOG_ERR, "cannot set table max "
391 				    "%lu on bridge %s: %m", tablemax,
392 				    instance_name);
393 			else
394 				tablemax = new_tablemax;
395 		}
396 	} else {
397 		syslog(LOG_ERR, "%s: unable to refresh bridge properties: %s",
398 		    instance_name, dladm_status2str(status, buf));
399 	}
400 
401 	rstp_refresh();
402 
403 	for (i = 0; i < nextport; i++)
404 		allports[i]->referenced = B_FALSE;
405 
406 	/*
407 	 * libdladm doesn't guarantee anything about link ordering in a walk,
408 	 * so we do this walk twice: once to pick up the ports, and a second
409 	 * time to get the enabled VLANs on all ports.
410 	 */
411 	(void) dladm_walk_datalink_id(update_link, dlhandle, NULL,
412 	    DATALINK_CLASS_ALL, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
413 
414 	(void) dladm_walk_datalink_id(set_vlan, dlhandle, NULL,
415 	    DATALINK_CLASS_VLAN, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
416 
417 	/*
418 	 * If any ports now show up as unreferenced, then they've been removed
419 	 * from the configuration.
420 	 */
421 	for (i = 0; i < nextport; i++) {
422 		pdp = allports[i];
423 		fdp = fdarray + i + FDOFFSET;
424 		if (!pdp->referenced) {
425 			if (pdp->stp_added) {
426 				(void) STP_IN_port_remove(pdp->vlan_id,
427 				    pdp->port_index);
428 				pdp->stp_added = B_FALSE;
429 			}
430 			if (pdp->dlpi != NULL) {
431 				dlpi_close(pdp->dlpi);
432 				pdp->dlpi = NULL;
433 				pdp->name = NULL;
434 				fdp->fd = -1;
435 				fdp->events = 0;
436 			}
437 			if (pdp->kern_added) {
438 				if (strioctl(control_fd, BRIOC_REMLINK,
439 				    &pdp->linkid, sizeof (pdp->linkid)) == -1)
440 					syslog(LOG_ERR, "cannot remove linkid "
441 					    "%u from bridge %s: %m",
442 					    pdp->linkid, instance_name);
443 				pdp->kern_added = B_FALSE;
444 			}
445 		}
446 	}
447 
448 	if (++refresh_count == 0)
449 		refresh_count = 1;
450 }
451 
452 /*
453  * Handle messages on the common control stream.  This currently just deals
454  * with port SDU mismatches.
455  */
456 static void
457 handle_control(void)
458 {
459 	bridge_ctl_t bc;
460 	ssize_t retv;
461 	struct portdata *port;
462 	int rc;
463 
464 	retv = read(control_fd, &bc, sizeof (bc));
465 	if (retv != sizeof (bc))
466 		return;
467 	if ((port = find_by_linkid(bc.bc_linkid)) == NULL)
468 		return;
469 	if (port->sdu_failed == bc.bc_failed)
470 		return;
471 	port->sdu_failed = bc.bc_failed;
472 	if (!port->phys_status || !port->admin_status ||
473 	    protect != DLADM_BRIDGE_PROT_STP)
474 		return;
475 	if (port->admin_non_stp) {
476 		bridge_setstate_t bss;
477 
478 		bss.bss_linkid = port->linkid;
479 		bss.bss_state = !port->sdu_failed && !port->bpdu_protect ?
480 		    BLS_FORWARDING : BLS_BLOCKLISTEN;
481 		if (strioctl(control_fd, BRIOC_SETSTATE, &bss,
482 		    sizeof (bss)) == -1) {
483 			syslog(LOG_ERR, "cannot set STP state on %s: %m",
484 			    port->name);
485 		}
486 	}
487 	if ((rc = STP_IN_enable_port(port->port_index, !bc.bc_failed)) != 0)
488 		syslog(LOG_ERR, "STP can't %s port %s for SDU failure: %s",
489 		    port->name, bc.bc_failed ? "disable" : "enable",
490 		    STP_IN_get_error_explanation(rc));
491 }
492 
493 static void
494 receive_packet(struct portdata *port)
495 {
496 	int rc;
497 	size_t buflen;
498 	uint16_t buffer[ETHERMAX / sizeof (uint16_t)];
499 	struct ether_header *eh;
500 	char sender[ETHERADDRL * 3];
501 
502 	buflen = sizeof (buffer);
503 	rc = dlpi_recv(port->dlpi, NULL, NULL, buffer, &buflen, 1, NULL);
504 	if (rc != DLPI_SUCCESS) {
505 		if (rc != DLPI_ETIMEDOUT)
506 			syslog(LOG_ERR, "receive failure on %s: %s", port->name,
507 			    dlpi_strerror(rc));
508 		return;
509 	}
510 
511 	/*
512 	 * If we're administratively disabled, then don't deliver packets to
513 	 * the STP state machine.  It will re-enable the port because it uses
514 	 * the same variable for both link status and administrative state.
515 	 */
516 	if (!port->admin_status || protect != DLADM_BRIDGE_PROT_STP) {
517 		if (debugging)
518 			syslog(LOG_DEBUG,
519 			    "discard BPDU on non-forwarding interface %s",
520 			    port->name);
521 		return;
522 	}
523 
524 	/*
525 	 * There's a mismatch between the librstp and libdlpi expectations on
526 	 * receive.  librstp wants the packet to start with the 802 length
527 	 * field, not the destination address.
528 	 */
529 	eh = (struct ether_header *)buffer;
530 	rc = STP_IN_check_bpdu_header((BPDU_T *)&eh->ether_type, buflen);
531 
532 	/*
533 	 * Note that we attempt to avoid calling the relatively expensive
534 	 * _link_ntoa function unless we're going to use the result.  In normal
535 	 * usage, we don't need this string.
536 	 */
537 	if (rc == 0) {
538 		if (port->admin_non_stp && !port->bpdu_protect) {
539 			bridge_setstate_t bss;
540 
541 			(void) _link_ntoa(eh->ether_shost.ether_addr_octet,
542 			    sender, ETHERADDRL, IFT_OTHER);
543 			syslog(LOG_WARNING, "unexpected BPDU on %s from %s; "
544 			    "forwarding disabled", port->name, sender);
545 			port->bpdu_protect = B_TRUE;
546 			bss.bss_linkid = port->linkid;
547 			bss.bss_state = BLS_BLOCKLISTEN;
548 			if (strioctl(control_fd, BRIOC_SETSTATE, &bss,
549 			    sizeof (bss)) == -1) {
550 				syslog(LOG_ERR, "cannot set STP state on "
551 				    "%s: %m", port->name);
552 			}
553 			return;
554 		}
555 		if (debugging) {
556 			(void) _link_ntoa(eh->ether_shost.ether_addr_octet,
557 			    sender, ETHERADDRL, IFT_OTHER);
558 			syslog(LOG_DEBUG, "got BPDU from %s on %s; %d bytes",
559 			    sender, port->name, buflen);
560 		}
561 		rc = STP_IN_rx_bpdu(port->vlan_id, port->port_index,
562 		    (BPDU_T *)&eh->ether_type, buflen);
563 	}
564 	if (rc != 0) {
565 		(void) _link_ntoa(eh->ether_shost.ether_addr_octet, sender,
566 		    ETHERADDRL, IFT_OTHER);
567 		syslog(LOG_DEBUG,
568 		    "discarded malformed packet on %s from %s: %s",
569 		    port->name, sender, STP_IN_get_error_explanation(rc));
570 	}
571 }
572 
573 void
574 get_dladm_speed(struct portdata *port)
575 {
576 	dladm_status_t status;
577 	uint64_t ifspeed;
578 
579 	status = dladm_get_single_mac_stat(dlhandle, port->linkid, "ifspeed",
580 	    KSTAT_DATA_UINT64, &ifspeed);
581 	if (status == DLADM_STATUS_OK && ifspeed != 0)
582 		port->speed = ifspeed / 1000000;
583 	else
584 		port->speed = 10UL;
585 }
586 
587 void
588 enable_forwarding(struct portdata *port)
589 {
590 	bridge_setstate_t bss;
591 
592 	bss.bss_linkid = port->linkid;
593 	bss.bss_state = BLS_FORWARDING;
594 	if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1)
595 		syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name);
596 }
597 
598 void
599 event_loop(void)
600 {
601 	int i;
602 	hrtime_t last_time, now;
603 	int tout;
604 
605 	if (lock_engine() != 0) {
606 		syslog(LOG_ERR, "mutex lock");
607 		exit(EXIT_FAILURE);
608 	}
609 
610 	/* Bootstrap configuration */
611 	handle_refresh(-1);
612 
613 	last_time = gethrtime();
614 	while (!shutting_down) {
615 		now = gethrtime();
616 		if (now - last_time >= 1000000000ll) {
617 			(void) STP_IN_one_second();
618 			tout = 1000;
619 			last_time = now;
620 		} else {
621 			tout = 1000 - (now - last_time) / 1000000ll;
622 		}
623 		unlock_engine();
624 		(void) poll(fdarray, nextport + FDOFFSET, tout);
625 		if (lock_engine() != 0) {
626 			syslog(LOG_ERR, "mutex lock");
627 			exit(EXIT_FAILURE);
628 		}
629 		if (fdarray[0].revents & POLLIN)
630 			handle_refresh(fdarray[0].fd);
631 		if (fdarray[1].revents & POLLIN)
632 			handle_control();
633 		for (i = 0; i < nextport; i++) {
634 			if (fdarray[i + FDOFFSET].revents & POLLIN)
635 				receive_packet(allports[i]);
636 		}
637 	}
638 	unlock_engine();
639 }
640