# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # cmd/cmd-inet/usr.lib/bridged/Makefile # PROG= bridged MANIFEST= bridge.xml OBJS= dlpi.o door.o events.o main.o rstp.o SRCS= $(OBJS:%.o=%.c) include ../../../Makefile.cmd ROOTMANIFESTDIR= $(ROOTSVCNETWORK) .KEEP_STATE: all: $(PROG) LDLIBS += -lsocket -lrstp -ldlpi -ldladm -lumem CFLAGS += $(CCVERBOSE) CPPFLAGS += -D__SUN__ .PARALLEL: $(OBJS) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib install: $(PROG) $(ROOTLIBPROG) $(ROOTMANIFEST) clean: $(RM) $(OBJS) lint: lint_SRCS include ../../../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * bridged - bridging control daemon. This module provides DLPI-specific * functions for interface to libdlpi. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" static const uchar_t bridge_group_address[] = BRIDGE_GROUP_ADDRESS; static const ushort_t bpdu_filter[] = { ENF_PUSHWORD | 0, /* check for 1:80:c2:0:0:0 dest. */ ENF_PUSHLIT | ENF_CAND, #ifdef _BIG_ENDIAN 0x0180, #else 0x8001, #endif ENF_PUSHWORD | 1, ENF_PUSHLIT | ENF_CAND, #ifdef _BIG_ENDIAN 0xC200, #else 0x00C2, #endif ENF_PUSHWORD | 2, ENF_PUSHZERO | ENF_CAND, ENF_PUSHWORD | 7, /* check for SSAP/DSAP 42 42 */ ENF_PUSHLIT | ENF_CAND, 0x4242, }; /* * Because we're called by dlpi_recv(), we're called with the engine lock held. */ /*ARGSUSED*/ static void dlpi_notify(dlpi_handle_t dlpi, dlpi_notifyinfo_t *info, void *arg) { struct portdata *port = arg; int rc; switch (info->dni_note) { case DL_NOTE_SPEED: /* libdlpi gives us Kbps, and we want Mbps */ if (port->speed == info->dni_speed / 1000) break; port->speed = info->dni_speed / 1000; if ((rc = STP_IN_changed_port_speed(port->port_index, port->speed)) != 0) syslog(LOG_ERR, "STP can't change port speed on %s: %s", port->name, STP_IN_get_error_explanation(rc)); break; case DL_NOTE_PHYS_ADDR: if (memcmp(info->dni_physaddr, port->mac_addr, ETHERADDRL) != 0) rstp_change_mac(port, info->dni_physaddr); break; case DL_NOTE_LINK_DOWN: if (!port->phys_status) break; port->phys_status = B_FALSE; if (!port->admin_status || protect != DLADM_BRIDGE_PROT_STP || port->sdu_failed) break; if ((rc = STP_IN_enable_port(port->port_index, False)) != 0) syslog(LOG_ERR, "STP can't disable port %s: %s", port->name, STP_IN_get_error_explanation(rc)); break; case DL_NOTE_LINK_UP: if (port->phys_status) break; port->phys_status = B_TRUE; if (!port->admin_status || protect != DLADM_BRIDGE_PROT_STP || port->sdu_failed) { port->bpdu_protect = B_FALSE; break; } /* * If we're not running STP, and the link state has just come * up, then clear out any protection shutdown state, and allow * us to forward again. */ if (port->admin_non_stp && port->bpdu_protect) { port->bpdu_protect = B_FALSE; enable_forwarding(port); } if ((rc = STP_IN_enable_port(port->port_index, True)) != 0) syslog(LOG_ERR, "STP can't enable port %s: %s", port->name, STP_IN_get_error_explanation(rc)); break; } } boolean_t port_dlpi_open(const char *portname, struct portdata *port, datalink_class_t class) { uchar_t addrbuf[DLPI_PHYSADDR_MAX]; size_t alen = DLPI_PHYSADDR_MAX; int rc; char addrstr[ETHERADDRL * 3]; /* * We use DLPI 'raw' mode so that we get access to the received * Ethernet 802 length field. libdlpi otherwise eats this value. Note * that 'raw' mode support is required in order to use snoop, so it's * expected to be common, even if it's not documented. */ rc = dlpi_open(portname, &port->dlpi, DLPI_RAW); if (rc != DLPI_SUCCESS) { syslog(LOG_ERR, "can't open %s: %s", portname, dlpi_strerror(rc)); return (B_FALSE); } port->phys_status = B_TRUE; port->sdu_failed = B_FALSE; port->bpdu_protect = B_FALSE; /* * Now that the driver is open, we can get at least the initial value * of the interface speed. We need to do this before establishing the * notify callback, so that it can update us later. */ get_dladm_speed(port); /* * Save off the libdlpi port name, as it's dynamically allocated, and * the name we're passed is not. */ port->name = dlpi_linkname(port->dlpi); /* * We can't bind SAP 0 or enable multicast on an etherstub. It's ok, * though, because there's no real hardware involved. */ if (class != DATALINK_CLASS_ETHERSTUB) { if ((rc = dlpi_bind(port->dlpi, 0, NULL)) != DLPI_SUCCESS) { syslog(LOG_ERR, "can't bind %s: %s", portname, dlpi_strerror(rc)); return (B_FALSE); } if ((rc = dlpi_enabmulti(port->dlpi, bridge_group_address, sizeof (bridge_group_address))) != DLPI_SUCCESS) { syslog(LOG_ERR, "can't enable multicast on %s: %s", portname, dlpi_strerror(rc)); return (B_FALSE); } } if ((rc = dlpi_enabnotify(port->dlpi, DL_NOTE_PHYS_ADDR | DL_NOTE_LINK_DOWN | DL_NOTE_LINK_UP | DL_NOTE_SPEED, dlpi_notify, port, &port->notifyid)) != DLPI_SUCCESS) { syslog(LOG_WARNING, "no DLPI notification on %s: %s", portname, dlpi_strerror(rc)); } rc = dlpi_get_physaddr(port->dlpi, DL_CURR_PHYS_ADDR, addrbuf, &alen); if (rc != DLPI_SUCCESS) { syslog(LOG_ERR, "unable to get MAC address on %s: %s", port->name, dlpi_strerror(rc)); return (B_FALSE); } if (alen != ETHERADDRL) { syslog(LOG_ERR, "bad MAC address length %d on %s", alen, port->name); return (B_FALSE); } (void) memcpy(port->mac_addr, addrbuf, ETHERADDRL); if (class != DATALINK_CLASS_ETHERSTUB) { int fd = dlpi_fd(port->dlpi); int lowflag = 1; if (strioctl(fd, DLIOCLOWLINK, &lowflag, sizeof (lowflag)) != 0) syslog(LOG_WARNING, "low-link notify failed on %s: %m", portname); if (ioctl(fd, I_PUSH, "pfmod") == 0) { struct packetfilt pf; pf.Pf_Priority = 0; pf.Pf_FilterLen = sizeof (bpdu_filter) / sizeof (*bpdu_filter); (void) memcpy(pf.Pf_Filter, bpdu_filter, sizeof (bpdu_filter)); if (strioctl(fd, PFIOCSETF, &pf, sizeof (pf)) == -1) syslog(LOG_WARNING, "pfil ioctl failed on %s: %m", portname); } else { syslog(LOG_WARNING, "pfil push failed on %s: %m", portname); } } if (debugging) { (void) _link_ntoa(port->mac_addr, addrstr, ETHERADDRL, IFT_OTHER); syslog(LOG_DEBUG, "got MAC address %s on %s", addrstr, port->name); } return (B_TRUE); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * bridged - bridging control daemon. This module provides the door-based * interface used by user applications to gather bridge status information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #define DOOR_DIRMODE 0755 #define DOOR_FILEMODE 0444 static int door_fd = -1; static char doorname[MAXPATHLEN]; /*ARGSUSED*/ static void bridge_door_server(void *cookie, char *argp, size_t arg_size, door_desc_t *dp, uint_t ndesc) { /* LINTED: alignment */ bridge_door_cmd_t *bdc = (bridge_door_cmd_t *)argp; int retv = EINVAL; bridge_door_cfg_t bdcf; UID_STP_STATE_T smstate; UID_STP_PORT_CFG_T portcfg; UID_STP_PORT_STATE_T portstate; struct portdata *pdp; int twoints[2]; if (arg_size < sizeof (*bdc) || lock_engine() != 0) { (void) door_return((char *)&retv, sizeof (retv), NULL, 0); return; } switch (bdc->bdc_type) { case bdcBridgeGetConfig: if ((retv = STP_IN_stpm_get_cfg(0, &bdcf.bdcf_cfg)) != 0) break; bdcf.bdcf_prot = protect; unlock_engine(); (void) door_return((char *)&bdcf, sizeof (bdcf), NULL, 0); return; case bdcBridgeGetState: if ((retv = STP_IN_stpm_get_state(0, &smstate)) != 0) break; unlock_engine(); (void) door_return((char *)&smstate, sizeof (smstate), NULL, 0); return; case bdcBridgeGetPorts: { datalink_id_t *dlp; int *rbuf; size_t rlen; int i, nports; if (nextport == 0) { twoints[0] = 0; rbuf = twoints; rlen = sizeof (twoints); } else { rlen = sizeof (int) + nextport * sizeof (datalink_id_t); rbuf = alloca(rlen); dlp = (datalink_id_t *)(rbuf + 1); for (i = nports = 0; i < nextport; i++) { if (allports[i]->kern_added) dlp[nports++] = allports[i]->linkid; } rbuf[0] = nports; rlen = sizeof (int) + nports * sizeof (datalink_id_t); } unlock_engine(); (void) door_return((char *)rbuf, rlen, NULL, 0); return; } case bdcBridgeGetRefreshCount: twoints[0] = refresh_count; twoints[1] = 0; unlock_engine(); (void) door_return((char *)twoints, sizeof (twoints), NULL, 0); return; case bdcPortGetConfig: if ((pdp = find_by_linkid(bdc->bdc_linkid)) == NULL) break; retv = STP_IN_port_get_cfg(0, pdp->port_index, &portcfg); if (retv != 0) break; unlock_engine(); (void) door_return((char *)&portcfg, sizeof (portcfg), NULL, 0); return; case bdcPortGetState: if ((pdp = find_by_linkid(bdc->bdc_linkid)) == NULL) break; portstate.port_no = pdp->port_index; if ((retv = STP_IN_port_get_state(0, &portstate)) != 0) break; if (pdp->sdu_failed) portstate.state = UID_PORT_BADSDU; else if (protect != DLADM_BRIDGE_PROT_STP) portstate.state = UID_PORT_NON_STP; else if (pdp->admin_non_stp && pdp->bpdu_protect) portstate.state = UID_PORT_DISABLED; unlock_engine(); (void) door_return((char *)&portstate, sizeof (portstate), NULL, 0); return; case bdcPortGetForwarding: if ((pdp = find_by_linkid(bdc->bdc_linkid)) == NULL) break; twoints[0] = pdp->admin_status ? 1 : 0; twoints[1] = 0; unlock_engine(); (void) door_return((char *)twoints, sizeof (twoints), NULL, 0); return; } unlock_engine(); (void) door_return((char *)&retv, sizeof (retv), NULL, 0); } static void cleanup_door(void) { if (door_fd != -1) { (void) door_revoke(door_fd); door_fd = -1; } if (doorname[0] != '\0') { (void) unlink(doorname); doorname[0] = '\0'; } } void init_door(void) { int fd; /* Make sure that the control directory exists */ (void) mkdir(DOOR_DIRNAME, DOOR_DIRMODE); /* Each instance gets a separate door. */ (void) snprintf(doorname, sizeof (doorname), "%s/%s", DOOR_DIRNAME, instance_name); /* Do a low-overhead "touch" on the file that will be the door node. */ fd = open(doorname, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_NONBLOCK, DOOR_FILEMODE); if (fd != -1) { (void) close(fd); } else if (errno != EEXIST) { syslog(LOG_ERR, "unable to create control door node: %m"); exit(EXIT_FAILURE); } (void) atexit(cleanup_door); /* Create the door. */ door_fd = door_create(bridge_door_server, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL); if (door_fd == -1) { syslog(LOG_ERR, "unable to create control door: %m"); exit(EXIT_FAILURE); } /* Attach the door to the file. */ (void) fdetach(doorname); if (fattach(door_fd, doorname) == -1) { syslog(LOG_ERR, "unable to attach control door: %m"); exit(EXIT_FAILURE); } } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * bridged - bridging control daemon. This module handles events and general * port-related operations. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" int refresh_count = 1; /* never zero */ dladm_bridge_prot_t protect = DLADM_BRIDGE_PROT_STP; /* * The 'allports' array is an array of pointers to the struct portdata * structures. We reallocate 'allports' as needed, but the portdata must * remain where it's initially allocated, because libdlpi's notification * mechanism has a copy of a pointer to this structure. */ uint_t nextport; struct portdata **allports; /* Port allocation increment (arbitrary) */ #define ALLOCINCR 10 static uint_t numports; static datalink_id_t main_linkid; int control_fd; static void linkdown(void) { (void) dladm_destroy_datalink_id(dlhandle, main_linkid, DLADM_OPT_ACTIVE); } void open_bridge_control(void) { bridge_newbridge_t bnb; dladm_status_t status; char buf[DLADM_STRSIZE]; if ((control_fd = open(BRIDGE_CTLPATH, O_RDWR | O_NONBLOCK)) == -1) { perror(BRIDGE_CTLPATH); exit(EXIT_FAILURE); } (void) snprintf(bnb.bnb_name, sizeof (bnb.bnb_name), "%s0", instance_name); status = dladm_name2info(dlhandle, bnb.bnb_name, &bnb.bnb_linkid, NULL, NULL, NULL); if (status != DLADM_STATUS_OK) { (void) fprintf(stderr, "bridged: %s: %s\n", bnb.bnb_name, dladm_status2str(status, buf)); exit(EXIT_FAILURE); } if (strioctl(control_fd, BRIOC_NEWBRIDGE, &bnb, sizeof (bnb)) == -1) { perror("NEWBRIDGE"); exit(EXIT_FAILURE); } main_linkid = bnb.bnb_linkid; if (strioctl(control_fd, BRIOC_TABLEMAX, &tablemax, sizeof (tablemax)) == -1) { syslog(LOG_ERR, "cannot set table max %lu on bridge %s: %m", tablemax, instance_name); exit(EXIT_FAILURE); } /* * This covers for any previous incarnation where we might have crashed * or been SIGKILL'd and failed to take down the datalink. */ linkdown(); (void) atexit(linkdown); status = dladm_up_datalink_id(dlhandle, bnb.bnb_linkid); if (status != DLADM_STATUS_OK) { (void) fprintf(stderr, "bridged: %s link up: %s\n", bnb.bnb_name, dladm_status2str(status, buf)); exit(EXIT_FAILURE); } } struct portdata * find_by_linkid(datalink_id_t linkid) { int i; struct portdata *port; for (i = 0; i < nextport; i++) { port = allports[i]; if (port->linkid == linkid) return (port); } return (NULL); } /*ARGSUSED2*/ static int set_vlan(dladm_handle_t handle, datalink_id_t linkid, void *arg) { struct portdata *port; dladm_status_t status; dladm_vlan_attr_t vinfo; char pointless[DLADM_STRSIZE]; bridge_vlanenab_t bve; status = dladm_vlan_info(handle, linkid, &vinfo, DLADM_OPT_ACTIVE); if (status != DLADM_STATUS_OK) { syslog(LOG_DEBUG, "can't get VLAN info on link ID %u: %s", linkid, dladm_status2str(status, pointless)); return (DLADM_WALK_CONTINUE); } port = find_by_linkid(vinfo.dv_linkid); if (port == NULL || !port->kern_added) return (DLADM_WALK_CONTINUE); bve.bve_linkid = port->linkid; bve.bve_vlan = vinfo.dv_vid; bve.bve_onoff = B_TRUE; if (strioctl(control_fd, BRIOC_VLANENAB, &bve, sizeof (bve)) == -1) { syslog(LOG_ERR, "unable to enable VLAN %d on linkid %u: %m", vinfo.dv_vid, port->linkid); return (DLADM_WALK_TERMINATE); } else { return (DLADM_WALK_CONTINUE); } } /* * If the named port already exists, then update its configuration. If it * doesn't, then create and enable it. */ static void update_port(int vlan_id, const char *portname, datalink_id_t linkid, datalink_class_t class) { int posn; struct portdata *port; struct pollfd *fds; int port_index; struct { datalink_id_t linkid; char linkname[MAXLINKNAMELEN]; } adddata; bridge_setpvid_t bsv; uint_t propval, valcnt; dladm_status_t status; for (posn = 0; posn < nextport; posn++) { if (allports[posn]->linkid == linkid) break; } /* If we need to allocate more array space, then do so in chunks. */ if (posn >= numports) { struct portdata **newarr; newarr = realloc(allports, sizeof (*newarr) * (nextport + ALLOCINCR)); if (newarr != NULL) allports = newarr; fds = realloc(fdarray, sizeof (*fds) * (nextport + ALLOCINCR + FDOFFSET)); if (fds != NULL) fdarray = fds; if (newarr == NULL || fds == NULL) { syslog(LOG_ERR, "unable to add %s; no memory", portname); return; } numports = nextport + ALLOCINCR; } port_index = posn + 1; fds = fdarray + posn + FDOFFSET; /* If our linkid search ran to the end, then this is a new port. */ if (posn == nextport) { if ((port = calloc(1, sizeof (*port))) == NULL) { syslog(LOG_ERR, "unable to add %s; no memory", portname); return; } allports[posn] = port; port->vlan_id = vlan_id; port->linkid = linkid; port->port_index = port_index; port->phys_status = B_TRUE; port->admin_status = B_TRUE; port->state = BLS_BLOCKLISTEN; nextport++; } else { /* Located port by linkid; we're just updating existing data */ port = allports[posn]; /* * If it changed name, then close and reopen so we log under * the most current name for this port. */ if (port->name != NULL && strcmp(portname, port->name) != 0) { if (port->dlpi != NULL) dlpi_close(port->dlpi); port->dlpi = NULL; port->name = NULL; fds->fd = -1; fds->events = 0; } } /* * If the port is not yet attached to the bridge in the kernel, then do * that now. */ if (!port->kern_added) { adddata.linkid = linkid; (void) strlcpy(adddata.linkname, portname, sizeof (adddata.linkname)); if (strioctl(control_fd, BRIOC_ADDLINK, &adddata, sizeof (adddata.linkid) + strlen(adddata.linkname)) == -1) { syslog(LOG_ERR, "cannot bridge %s: %m", portname); goto failure; } port->kern_added = B_TRUE; } port->referenced = B_TRUE; valcnt = 1; status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "forward", &propval, &valcnt); if (status == DLADM_STATUS_OK) port->admin_status = propval; bsv.bsv_vlan = 1; status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "default_tag", &propval, &valcnt); if (status == DLADM_STATUS_OK) bsv.bsv_vlan = propval; bsv.bsv_linkid = linkid; if (strioctl(control_fd, BRIOC_SETPVID, &bsv, sizeof (bsv)) == -1) { syslog(LOG_ERR, "can't set PVID on %s: %m", portname); goto failure; } if (port->dlpi == NULL) { if (!port_dlpi_open(portname, port, class)) goto failure; fds->fd = dlpi_fd(port->dlpi); fds->events = POLLIN; } if (rstp_add_port(port)) return; failure: if (port->dlpi != NULL) { dlpi_close(port->dlpi); port->dlpi = NULL; port->name = NULL; fds->fd = -1; fds->events = 0; } if (port->kern_added) { if (strioctl(control_fd, BRIOC_REMLINK, &port->linkid, sizeof (port->linkid)) == -1) syslog(LOG_ERR, "cannot remove from bridge %s: %m", portname); else port->kern_added = B_FALSE; } if (posn + 1 == nextport) { free(port); nextport--; } } /*ARGSUSED2*/ static int update_link(dladm_handle_t handle, datalink_id_t linkid, void *arg) { dladm_status_t status; char bridge[MAXLINKNAMELEN], linkname[MAXLINKNAMELEN]; char pointless[DLADM_STRSIZE]; datalink_class_t class; status = dladm_bridge_getlink(handle, linkid, bridge, sizeof (bridge)); if (status == DLADM_STATUS_OK && strcmp(bridge, instance_name) == 0) { status = dladm_datalink_id2info(handle, linkid, NULL, &class, NULL, linkname, sizeof (linkname)); if (status == DLADM_STATUS_OK) { update_port(0, linkname, linkid, class); } else { syslog(LOG_ERR, "unable to get link info for ID %u: %s", linkid, dladm_status2str(status, pointless)); } } else if (debugging) { if (status != DLADM_STATUS_OK) syslog(LOG_DEBUG, "unable to get bridge data for ID %u: %s", linkid, dladm_status2str(status, pointless)); else syslog(LOG_DEBUG, "link ID %u is on bridge %s, not %s", linkid, bridge, instance_name); } return (DLADM_WALK_CONTINUE); } /* * Refresh action - reread configuration properties. */ static void handle_refresh(int sigfd) { int i; struct portdata *pdp; struct pollfd *fdp; char buf[16]; dladm_status_t status; boolean_t new_debug; uint32_t new_tablemax; /* Drain signal events from pipe */ if (sigfd != -1) (void) read(sigfd, buf, sizeof (buf)); status = dladm_bridge_get_privprop(instance_name, &new_debug, &new_tablemax); if (status == DLADM_STATUS_OK) { if (debugging && !new_debug) syslog(LOG_DEBUG, "disabling debugging"); debugging = new_debug; if (new_tablemax != tablemax) { syslog(LOG_DEBUG, "changed tablemax from %lu to %lu", tablemax, new_tablemax); if (strioctl(control_fd, BRIOC_TABLEMAX, &new_tablemax, sizeof (tablemax)) == -1) syslog(LOG_ERR, "cannot set table max " "%lu on bridge %s: %m", tablemax, instance_name); else tablemax = new_tablemax; } } else { syslog(LOG_ERR, "%s: unable to refresh bridge properties: %s", instance_name, dladm_status2str(status, buf)); } rstp_refresh(); for (i = 0; i < nextport; i++) allports[i]->referenced = B_FALSE; /* * libdladm doesn't guarantee anything about link ordering in a walk, * so we do this walk twice: once to pick up the ports, and a second * time to get the enabled VLANs on all ports. */ (void) dladm_walk_datalink_id(update_link, dlhandle, NULL, DATALINK_CLASS_ALL, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); (void) dladm_walk_datalink_id(set_vlan, dlhandle, NULL, DATALINK_CLASS_VLAN, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); /* * If any ports now show up as unreferenced, then they've been removed * from the configuration. */ for (i = 0; i < nextport; i++) { pdp = allports[i]; fdp = fdarray + i + FDOFFSET; if (!pdp->referenced) { if (pdp->stp_added) { (void) STP_IN_port_remove(pdp->vlan_id, pdp->port_index); pdp->stp_added = B_FALSE; } if (pdp->dlpi != NULL) { dlpi_close(pdp->dlpi); pdp->dlpi = NULL; pdp->name = NULL; fdp->fd = -1; fdp->events = 0; } if (pdp->kern_added) { if (strioctl(control_fd, BRIOC_REMLINK, &pdp->linkid, sizeof (pdp->linkid)) == -1) syslog(LOG_ERR, "cannot remove linkid " "%u from bridge %s: %m", pdp->linkid, instance_name); pdp->kern_added = B_FALSE; } } } if (++refresh_count == 0) refresh_count = 1; } /* * Handle messages on the common control stream. This currently just deals * with port SDU mismatches. */ static void handle_control(void) { bridge_ctl_t bc; ssize_t retv; struct portdata *port; int rc; retv = read(control_fd, &bc, sizeof (bc)); if (retv != sizeof (bc)) return; if ((port = find_by_linkid(bc.bc_linkid)) == NULL) return; if (port->sdu_failed == bc.bc_failed) return; port->sdu_failed = bc.bc_failed; if (!port->phys_status || !port->admin_status || protect != DLADM_BRIDGE_PROT_STP) return; if (port->admin_non_stp) { bridge_setstate_t bss; bss.bss_linkid = port->linkid; bss.bss_state = !port->sdu_failed && !port->bpdu_protect ? BLS_FORWARDING : BLS_BLOCKLISTEN; if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) { syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name); } } if ((rc = STP_IN_enable_port(port->port_index, !bc.bc_failed)) != 0) syslog(LOG_ERR, "STP can't %s port %s for SDU failure: %s", port->name, bc.bc_failed ? "disable" : "enable", STP_IN_get_error_explanation(rc)); } static void receive_packet(struct portdata *port) { int rc; size_t buflen; uint16_t buffer[ETHERMAX / sizeof (uint16_t)]; struct ether_header *eh; char sender[ETHERADDRL * 3]; buflen = sizeof (buffer); rc = dlpi_recv(port->dlpi, NULL, NULL, buffer, &buflen, 1, NULL); if (rc != DLPI_SUCCESS) { if (rc != DLPI_ETIMEDOUT) syslog(LOG_ERR, "receive failure on %s: %s", port->name, dlpi_strerror(rc)); return; } /* * If we're administratively disabled, then don't deliver packets to * the STP state machine. It will re-enable the port because it uses * the same variable for both link status and administrative state. */ if (!port->admin_status || protect != DLADM_BRIDGE_PROT_STP) { if (debugging) syslog(LOG_DEBUG, "discard BPDU on non-forwarding interface %s", port->name); return; } /* * There's a mismatch between the librstp and libdlpi expectations on * receive. librstp wants the packet to start with the 802 length * field, not the destination address. */ eh = (struct ether_header *)buffer; rc = STP_IN_check_bpdu_header((BPDU_T *)&eh->ether_type, buflen); /* * Note that we attempt to avoid calling the relatively expensive * _link_ntoa function unless we're going to use the result. In normal * usage, we don't need this string. */ if (rc == 0) { if (port->admin_non_stp && !port->bpdu_protect) { bridge_setstate_t bss; (void) _link_ntoa(eh->ether_shost.ether_addr_octet, sender, ETHERADDRL, IFT_OTHER); syslog(LOG_WARNING, "unexpected BPDU on %s from %s; " "forwarding disabled", port->name, sender); port->bpdu_protect = B_TRUE; bss.bss_linkid = port->linkid; bss.bss_state = BLS_BLOCKLISTEN; if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) { syslog(LOG_ERR, "cannot set STP state on " "%s: %m", port->name); } return; } if (debugging) { (void) _link_ntoa(eh->ether_shost.ether_addr_octet, sender, ETHERADDRL, IFT_OTHER); syslog(LOG_DEBUG, "got BPDU from %s on %s; %d bytes", sender, port->name, buflen); } rc = STP_IN_rx_bpdu(port->vlan_id, port->port_index, (BPDU_T *)&eh->ether_type, buflen); } if (rc != 0) { (void) _link_ntoa(eh->ether_shost.ether_addr_octet, sender, ETHERADDRL, IFT_OTHER); syslog(LOG_DEBUG, "discarded malformed packet on %s from %s: %s", port->name, sender, STP_IN_get_error_explanation(rc)); } } void get_dladm_speed(struct portdata *port) { dladm_status_t status; uint64_t ifspeed; status = dladm_get_single_mac_stat(dlhandle, port->linkid, "ifspeed", KSTAT_DATA_UINT64, &ifspeed); if (status == DLADM_STATUS_OK && ifspeed != 0) port->speed = ifspeed / 1000000; else port->speed = 10UL; } void enable_forwarding(struct portdata *port) { bridge_setstate_t bss; bss.bss_linkid = port->linkid; bss.bss_state = BLS_FORWARDING; if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name); } void event_loop(void) { int i; hrtime_t last_time, now; int tout; if (lock_engine() != 0) { syslog(LOG_ERR, "mutex lock"); exit(EXIT_FAILURE); } /* Bootstrap configuration */ handle_refresh(-1); last_time = gethrtime(); while (!shutting_down) { now = gethrtime(); if (now - last_time >= 1000000000ll) { (void) STP_IN_one_second(); tout = 1000; last_time = now; } else { tout = 1000 - (now - last_time) / 1000000ll; } unlock_engine(); (void) poll(fdarray, nextport + FDOFFSET, tout); if (lock_engine() != 0) { syslog(LOG_ERR, "mutex lock"); exit(EXIT_FAILURE); } if (fdarray[0].revents & POLLIN) handle_refresh(fdarray[0].fd); if (fdarray[1].revents & POLLIN) handle_control(); for (i = 0; i < nextport; i++) { if (fdarray[i + FDOFFSET].revents & POLLIN) receive_packet(allports[i]); } } unlock_engine(); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _BRIDGED_GLOBAL_H #define _BRIDGED_GLOBAL_H /* * Globally visible symbols within the "bridged" bridging daemon */ #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif struct portdata { int vlan_id; int port_index; unsigned int speed; boolean_t phys_status; /* physical layer status */ boolean_t admin_status; /* administrative status */ boolean_t kern_added; /* set when added to kernel bridge */ boolean_t stp_added; /* set when added to STP machine */ boolean_t referenced; /* used for refresh */ boolean_t sdu_failed; /* set for non-matching max SDU */ boolean_t admin_non_stp; /* copy of STP library config */ boolean_t bpdu_protect; /* BPDU seen when non-STP */ bridge_state_t state; dlpi_handle_t dlpi; dlpi_notifyid_t notifyid; datalink_id_t linkid; const char *name; uchar_t mac_addr[ETHERADDRL]; }; /* Number of reserved (internal) fdarray entries */ #define FDOFFSET 2 /* main.c */ extern int lock_engine(void); extern void unlock_engine(void); extern ssize_t strioctl(int, int, void *, size_t); extern struct portdata *find_by_linkid(datalink_id_t); extern void get_dladm_speed(struct portdata *); extern void enable_forwarding(struct portdata *); extern boolean_t debugging; extern uint32_t tablemax; extern const char *instance_name; extern dladm_handle_t dlhandle; extern boolean_t shutting_down; extern struct pollfd *fdarray; /* door.c */ extern void init_door(void); /* dlpi.c */ extern boolean_t port_dlpi_open(const char *, struct portdata *, datalink_class_t); /* rstp.c */ extern void rstp_init(void); extern void rstp_refresh(void); extern void rstp_change_mac(struct portdata *, const unsigned char *); extern boolean_t rstp_add_port(struct portdata *); /* events.c */ extern void open_bridge_control(void); extern void event_loop(void); extern int refresh_count; extern dladm_bridge_prot_t protect; extern uint_t nextport; extern struct portdata **allports; extern int control_fd; #ifdef __cplusplus } #endif #endif /* _BRIDGED_GLOBAL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * bridged - bridging control daemon. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" boolean_t debugging; uint32_t tablemax; const char *instance_name = "default"; struct pollfd *fdarray; dladm_handle_t dlhandle; boolean_t shutting_down; static pthread_t sighand; /* * engine_lock is held while the main loop is busy calling librstp functions. * Door threads take the lock to protect the library from reentrancy. */ static pthread_mutex_t engine_lock = PTHREAD_MUTEX_INITIALIZER; /* * These wrapper functions allow the other components in the daemon to remain * ignorant of pthreads details. */ int lock_engine(void) { return (pthread_mutex_lock(&engine_lock)); } void unlock_engine(void) { (void) pthread_mutex_unlock(&engine_lock); } /* * Utility function for STREAMS ioctls. */ ssize_t strioctl(int fd, int cmd, void *buf, size_t buflen) { int retv; struct strioctl ic; ic.ic_cmd = cmd; ic.ic_timout = 0; ic.ic_dp = buf; ic.ic_len = buflen; if ((retv = ioctl(fd, I_STR, &ic)) != 0) return (retv); else return (ic.ic_len); } static void daemonize(void) { pid_t pid; /* * A little bit of magic here. By the first fork+setsid, we * disconnect from our current controlling terminal and become * a session group leader. By forking again without calling * setsid again, we make certain that we are not the session * group leader and can never reacquire a controlling terminal. */ if ((pid = fork()) == (pid_t)-1) { syslog(LOG_ERR, "fork 1 failed"); exit(EXIT_FAILURE); } if (pid != 0) { (void) wait(NULL); _exit(EXIT_SUCCESS); } if (setsid() == (pid_t)-1) { syslog(LOG_ERR, "setsid"); exit(EXIT_FAILURE); } if ((pid = fork()) == (pid_t)-1) { syslog(LOG_ERR, "fork 2 failed"); exit(EXIT_FAILURE); } if (pid != 0) _exit(EXIT_SUCCESS); (void) chdir("/"); (void) umask(022); } static void * sighandler(void *arg) { sigset_t sigset; int sig; int sigfd = (int)(uintptr_t)arg; (void) sigfillset(&sigset); for (;;) { sig = sigwait(&sigset); switch (sig) { case SIGHUP: (void) write(sigfd, "", 1); break; default: if (debugging) syslog(LOG_NOTICE, "%s signal, shutting down", strsignal(sig)); shutting_down = B_TRUE; break; } /* if we're shutting down, exit this thread */ if (shutting_down) return (NULL); } } static void init_signalhandling(void) { pthread_attr_t attr; int err; sigset_t new; int fildes[2]; if ((fdarray = malloc(FDOFFSET * sizeof (struct pollfd))) == NULL) { syslog(LOG_ERR, "unable to allocate fdarray: %m"); exit(EXIT_FAILURE); } if (pipe(fildes) != 0) { syslog(LOG_ERR, "unable to create signal pipe: %m"); exit(EXIT_FAILURE); } fdarray[0].fd = fildes[0]; fdarray[0].events = POLLIN; assert(control_fd != -1); fdarray[1].fd = control_fd; fdarray[1].events = POLLIN; (void) sigfillset(&new); (void) pthread_sigmask(SIG_BLOCK, &new, NULL); (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); err = pthread_create(&sighand, &attr, sighandler, (void *)(uintptr_t)fildes[1]); if (err != 0) { syslog(LOG_ERR, "cannot create signal handling thread: %s", strerror(err)); exit(EXIT_FAILURE); } (void) pthread_attr_destroy(&attr); } int main(int argc, char **argv) { dladm_status_t status; char buf[DLADM_STRSIZE]; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); shutting_down = B_FALSE; openlog("bridged", LOG_PID | LOG_NDELAY, LOG_DAEMON); if (argc != 2) { syslog(LOG_ERR, "instance name is required"); exit(EXIT_FAILURE); } instance_name = argv[1]; if ((status = dladm_open(&dlhandle)) != DLADM_STATUS_OK) { syslog(LOG_ERR, "%s: unable to open datalink control: %s", instance_name, dladm_status2str(status, buf)); exit(EXIT_FAILURE); } status = dladm_bridge_get_privprop(instance_name, &debugging, &tablemax); if (status != DLADM_STATUS_OK) { syslog(LOG_ERR, "%s: unable to read properties: %s", instance_name, dladm_status2str(status, buf)); exit(EXIT_FAILURE); } /* Get the properties once so that we have the right initial values */ rstp_init(); open_bridge_control(); daemonize(); init_signalhandling(); init_door(); if (debugging) syslog(LOG_INFO, "bridged started: instance %s", instance_name); event_loop(); (void) pthread_cancel(sighand); (void) pthread_join(sighand, NULL); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * bridged - bridging control daemon. This module provides functions related * to the librstp (Rapid Spanning Tree Protocol) library. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" /* current engine configuration; access protected by engine_lock */ static UID_STP_CFG_T uid_cfg; /* * Our implementation doesn't have per-VLAN forwarding entries, so we just * flush by the port. If port number is zero, then flush entries. */ /*ARGSUSED1*/ static int flush_lt(int port_index, int vlan_id, LT_FLASH_TYPE_T type, char *reason) { struct portdata *pd; const char *portname; bridge_flushfwd_t bff; if (port_index > nextport || port_index < 0) return (0); if (port_index == 0) { type = LT_FLASH_ONLY_THE_PORT; portname = "all"; bff.bff_linkid = DATALINK_INVALID_LINKID; } else { pd = allports[port_index - 1]; portname = pd->name; bff.bff_linkid = pd->linkid; } if (debugging) { syslog(LOG_DEBUG, "flush forwarding %s %s: %s", type == LT_FLASH_ONLY_THE_PORT ? "to" : "except for", portname, reason); } bff.bff_exclude = (type == LT_FLASH_ALL_PORTS_EXCLUDE_THIS); /* * If flushing fails, we can't return. The only safe thing to do is to * tear down the bridge so that we're not harming the network. */ if (strioctl(control_fd, BRIOC_FLUSHFWD, &bff, sizeof (bff)) == -1) { syslog(LOG_ERR, "cannot flush forwarding entries on %s %s: %m", instance_name, portname); unlock_engine(); exit(EXIT_FAILURE); } return (0); } static void get_port_mac(int port_index, unsigned char *mac) { struct portdata *pd; if (port_index > nextport || port_index <= 0) return; pd = allports[port_index - 1]; (void) memcpy(mac, pd->mac_addr, ETHERADDRL); } /* Returns speed in megabits per second */ static unsigned long get_port_oper_speed(unsigned int port_index) { if (port_index > nextport || port_index == 0) return (1000UL); else return (allports[port_index - 1]->speed); } static int get_port_link_status(int port_index) { struct portdata *pd; if (port_index > nextport || port_index <= 0) { return (0); } else { pd = allports[port_index - 1]; return (pd->phys_status && pd->admin_status && protect == DLADM_BRIDGE_PROT_STP && !pd->sdu_failed ? 1 : 0); } } static int get_duplex(int port_index) { struct portdata *pd; link_duplex_t link_duplex; dladm_status_t status; if (port_index > nextport || port_index <= 0) return (False); pd = allports[port_index - 1]; status = dladm_get_single_mac_stat(dlhandle, pd->linkid, "link_duplex", KSTAT_DATA_UINT32, &link_duplex); if (status == DLADM_STATUS_OK && link_duplex == LINK_DUPLEX_FULL) return (True); else return (False); } static const char * bls_state(bridge_state_t bstate) { switch (bstate) { case BLS_LEARNING: return ("learning"); case BLS_FORWARDING: return ("forwarding"); default: return ("block/listen"); } } /*ARGSUSED1*/ static int set_port_state(int port_index, int vlan_id, RSTP_PORT_STATE state) { struct portdata *pd; bridge_setstate_t bss; if (port_index > nextport || port_index <= 0) return (1); pd = allports[port_index - 1]; if (debugging) syslog(LOG_DEBUG, "setting port state on port %d (%s) to %d", port_index, pd->name, state); switch (state) { case UID_PORT_LEARNING: bss.bss_state = BLS_LEARNING; break; case UID_PORT_FORWARDING: bss.bss_state = BLS_FORWARDING; break; default: bss.bss_state = BLS_BLOCKLISTEN; break; } bss.bss_linkid = pd->linkid; if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) { syslog(LOG_ERR, "cannot set STP state on %s from %s to %s: %m", pd->name, bls_state(pd->state), bls_state(bss.bss_state)); /* * If we've been unsuccessful in disabling forwarding, then the * only safe thing to do is to make the daemon exit, so that * the kernel will be forced to destroy the bridge state and * terminate all forwarding. */ if (pd->state == BLS_FORWARDING && bss.bss_state != BLS_FORWARDING) { unlock_engine(); exit(EXIT_FAILURE); } } else { pd->state = bss.bss_state; } return (0); } /* * Our hardware doesn't actually do anything different when STP is enabled or * disabled, so this function does nothing. It would be possible to open and * close the DLPI stream here, if such a thing were necessary. */ static int set_hardware_mode(int vlan_id, UID_STP_MODE_T mode) { if (debugging) syslog(LOG_DEBUG, "setting hardware mode on vlan %d to %d", vlan_id, mode); return (0); } /*ARGSUSED1*/ static int tx_bpdu(int port_index, int vlan_id, unsigned char *bpdu, size_t bpdu_len) { struct portdata *pdp; int rc; if (port_index > nextport || port_index <= 0) return (1); pdp = allports[port_index - 1]; rc = dlpi_send(pdp->dlpi, NULL, 0, bpdu, bpdu_len, NULL); if (rc == DLPI_SUCCESS) { if (debugging) syslog(LOG_DEBUG, "transmitted %d byte BPDU on %s", bpdu_len, pdp->name); return (0); } else { syslog(LOG_WARNING, "failed to send to %s: %s", pdp->name, dlpi_strerror(rc)); return (1); } } static const char * get_port_name(int port_index) { if (port_index > nextport || port_index <= 0) return ("unknown"); else return (allports[port_index - 1]->name); } /*ARGSUSED*/ static int get_init_stpm_cfg(int vlan_id, UID_STP_CFG_T *cfg) { /* under engine_lock because it's a callback from the engine */ *cfg = uid_cfg; return (0); } /*ARGSUSED*/ static int get_init_port_cfg(int vlan_id, int port_index, UID_STP_PORT_CFG_T *cfg) { struct portdata *pdp; uint_t propval, valcnt; datalink_id_t linkid; dladm_status_t status; if (port_index > nextport || port_index <= 0) return (1); pdp = allports[port_index - 1]; cfg->field_mask = 0; cfg->port_priority = DEF_PORT_PRIO; cfg->admin_non_stp = DEF_ADMIN_NON_STP; cfg->admin_edge = DEF_ADMIN_EDGE; cfg->admin_port_path_cost = ADMIN_PORT_PATH_COST_AUTO; cfg->admin_point2point = DEF_P2P; valcnt = 1; linkid = pdp->linkid; status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "stp_priority", &propval, &valcnt); if (status == DLADM_STATUS_OK) { cfg->port_priority = propval; cfg->field_mask |= PT_CFG_PRIO; } status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "stp", &propval, &valcnt); if (status == DLADM_STATUS_OK) { cfg->admin_non_stp = !propval; cfg->field_mask |= PT_CFG_NON_STP; } status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "stp_edge", &propval, &valcnt); if (status == DLADM_STATUS_OK) { cfg->admin_edge = propval; cfg->field_mask |= PT_CFG_EDGE; } status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "stp_cost", &propval, &valcnt); if (status == DLADM_STATUS_OK) { cfg->admin_port_path_cost = propval; cfg->field_mask |= PT_CFG_COST; } status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "stp_p2p", &propval, &valcnt); if (status == DLADM_STATUS_OK) { cfg->admin_point2point = propval; cfg->field_mask |= PT_CFG_P2P; } /* * mcheck is special. It is actually a command, but the 802 documents * define it as a variable that spontaneously resets itself. We need * to handle that behavior here. */ status = dladm_get_linkprop_values(dlhandle, linkid, DLADM_PROP_VAL_PERSISTENT, "stp_mcheck", &propval, &valcnt); if (status == DLADM_STATUS_OK && propval != 0) { char *pval = "0"; cfg->field_mask |= PT_CFG_MCHECK; (void) dladm_set_linkprop(dlhandle, linkid, "stp_mcheck", &pval, 1, DLADM_OPT_ACTIVE|DLADM_OPT_PERSIST|DLADM_OPT_NOREFRESH); } pdp->admin_non_stp = cfg->admin_non_stp; if (!pdp->admin_non_stp) pdp->bpdu_protect = B_FALSE; return (0); } static void trace(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vsyslog(LOG_DEBUG, fmt, ap); va_end(ap); } static STP_VECTORS_T stp_vectors = { flush_lt, get_port_mac, get_port_oper_speed, get_port_link_status, get_duplex, set_port_state, set_hardware_mode, tx_bpdu, get_port_name, get_init_stpm_cfg, get_init_port_cfg, trace }; void rstp_init(void) { dladm_status_t status; char buf[DLADM_STRSIZE]; STP_IN_init(&stp_vectors); status = dladm_bridge_get_properties(instance_name, &uid_cfg, &protect); if (status != DLADM_STATUS_OK) { syslog(LOG_ERR, "%s: unable to read properties: %s", instance_name, dladm_status2str(status, buf)); exit(EXIT_FAILURE); } } /* * This is called by a normal refresh operation. It gets the engine properties * and resets. */ void rstp_refresh(void) { dladm_status_t status; int rc; char buf[DLADM_STRSIZE]; UID_STP_CFG_T new_cfg; dladm_bridge_prot_t new_prot; status = dladm_bridge_get_properties(instance_name, &new_cfg, &new_prot); if (status != DLADM_STATUS_OK) { syslog(LOG_ERR, "%s: unable to refresh bridge properties: %s", instance_name, dladm_status2str(status, buf)); } else { if (debugging && (protect != new_prot || uid_cfg.stp_enabled != new_cfg.stp_enabled)) { syslog(LOG_DEBUG, "loop protection %s->%s, STP %d->%d", dladm_bridge_prot2str(protect), dladm_bridge_prot2str(new_prot), uid_cfg.stp_enabled, new_cfg.stp_enabled); } /* * The engine doesn't take kindly to parameter changes while * running. Disable first if we must do this. */ if (uid_cfg.stp_enabled && memcmp(&uid_cfg, &new_cfg, sizeof (uid_cfg)) != 0) { syslog(LOG_DEBUG, "resetting state machine"); uid_cfg.stp_enabled = STP_DISABLED; rc = STP_IN_stpm_set_cfg(0, &uid_cfg); if (rc != 0) syslog(LOG_ERR, "STP machine reset config: %s", STP_IN_get_error_explanation(rc)); } uid_cfg = new_cfg; protect = new_prot; rc = STP_IN_stpm_set_cfg(0, &uid_cfg); if (rc != 0) syslog(LOG_ERR, "STP machine set config: %s", STP_IN_get_error_explanation(rc)); } } /* * This is called when a port changes its MAC address. If it's the main port, * the one that supplies us our bridge ID, then we must choose a new ID, and to * do that we shut the bridge down and bring it back up. */ void rstp_change_mac(struct portdata *port, const unsigned char *newaddr) { unsigned short prio; unsigned char mac[ETHERADDRL]; int rc; char curid[ETHERADDRL * 3]; char newmac[ETHERADDRL * 3]; (void) _link_ntoa(port->mac_addr, curid, ETHERADDRL, IFT_OTHER); (void) _link_ntoa(newaddr, newmac, ETHERADDRL, IFT_OTHER); STP_IN_get_bridge_id(port->vlan_id, &prio, mac); if (memcmp(port->mac_addr, mac, ETHERADDRL) == 0) { syslog(LOG_NOTICE, "bridge ID must change: ID %s on %s changed " "to %s", curid, port->name, newmac); uid_cfg.stp_enabled = STP_DISABLED; if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0) syslog(LOG_ERR, "STP machine set config: %s", STP_IN_get_error_explanation(rc)); (void) memcpy(port->mac_addr, newaddr, ETHERADDRL); uid_cfg.stp_enabled = STP_ENABLED; if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0) syslog(LOG_ERR, "STP machine set config: %s", STP_IN_get_error_explanation(rc)); } else { syslog(LOG_DEBUG, "MAC address on %s changed from %s to %s", port->name, curid, newmac); (void) memcpy(port->mac_addr, newaddr, ETHERADDRL); } } boolean_t rstp_add_port(struct portdata *port) { int rc; UID_STP_PORT_CFG_T portcfg; bridge_vlanenab_t bve; bridge_setstate_t bss; if (!port->stp_added && (rc = STP_IN_port_add(port->vlan_id, port->port_index)) != 0) { syslog(LOG_ERR, "STP add %s %d: %s", port->name, port->port_index, STP_IN_get_error_explanation(rc)); return (B_FALSE); } port->stp_added = B_TRUE; /* guaranteed to succeed at this point */ (void) get_init_port_cfg(port->vlan_id, port->port_index, &portcfg); /* * Restore state when reenabling STP engine, set fixed state when * disabling. For TRILL, we don't control forwarding at all, but we * need to turn off our controls for TRILL to do its thing. */ bss.bss_linkid = port->linkid; if (protect != DLADM_BRIDGE_PROT_STP) { bss.bss_state = port->state = BLS_BLOCKLISTEN; } else if (portcfg.admin_non_stp) { bss.bss_state = port->admin_status && !port->sdu_failed && !port->bpdu_protect ? BLS_FORWARDING : BLS_BLOCKLISTEN; } else { bss.bss_state = port->state; } if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) { syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name); goto failure; } rc = STP_IN_enable_port(port->port_index, port->admin_status && port->phys_status && !port->sdu_failed && protect == DLADM_BRIDGE_PROT_STP); if (rc != 0) { syslog(LOG_ERR, "STP enable %s %d: %s", port->name, port->port_index, STP_IN_get_error_explanation(rc)); goto failure; } if (debugging) { rc = STP_IN_dbg_set_port_trace("all", True, 0, port->port_index); } else { /* return to default debug state */ rc = STP_IN_dbg_set_port_trace("all", False, 0, port->port_index); if (rc == 0) rc = STP_IN_dbg_set_port_trace("sttrans", True, 0, port->port_index); } if (rc != 0) { syslog(LOG_ERR, "STP trace %s %d: %s", port->name, port->port_index, STP_IN_get_error_explanation(rc)); goto failure; } /* Clear out the kernel's allowed VLAN set; second walk will set */ bve.bve_linkid = port->linkid; bve.bve_vlan = 0; bve.bve_onoff = B_FALSE; if (strioctl(control_fd, BRIOC_VLANENAB, &bve, sizeof (bve)) == -1) { syslog(LOG_ERR, "unable to disable VLANs on %s: %m", port->name); goto failure; } if ((rc = STP_IN_port_set_cfg(0, port->port_index, &portcfg)) != 0) { syslog(LOG_ERR, "STP port configure %s %d: %s", port->name, port->port_index, STP_IN_get_error_explanation(rc)); goto failure; } return (B_TRUE); failure: (void) STP_IN_port_remove(port->vlan_id, port->port_index); port->stp_added = B_FALSE; return (B_FALSE); }