#
# 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
bridge
The "bridge" service provides Ethernet bridging and related protocols.
/*
* 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);
}