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