1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * bridged - bridging control daemon. This module provides functions related 29 * to the librstp (Rapid Spanning Tree Protocol) library. 30 */ 31 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 #include <stdarg.h> 36 #include <string.h> 37 #include <sys/types.h> 38 #include <syslog.h> 39 #include <kstat.h> 40 #include <libdlpi.h> 41 #include <libdladm.h> 42 #include <libdllink.h> 43 #include <libdlstat.h> 44 #include <stp_in.h> 45 #include <stp_vectors.h> 46 #include <net/if_types.h> 47 #include <net/bridge.h> 48 #include <sys/ethernet.h> 49 50 #include "global.h" 51 52 /* current engine configuration; access protected by engine_lock */ 53 static UID_STP_CFG_T uid_cfg; 54 55 /* 56 * Our implementation doesn't have per-VLAN forwarding entries, so we just 57 * flush by the port. If port number is zero, then flush entries. 58 */ 59 /*ARGSUSED1*/ 60 static int 61 flush_lt(int port_index, int vlan_id, LT_FLASH_TYPE_T type, char *reason) 62 { 63 struct portdata *pd; 64 const char *portname; 65 bridge_flushfwd_t bff; 66 67 if (port_index > nextport || port_index < 0) 68 return (0); 69 70 if (port_index == 0) { 71 type = LT_FLASH_ONLY_THE_PORT; 72 portname = "all"; 73 bff.bff_linkid = DATALINK_INVALID_LINKID; 74 } else { 75 pd = allports[port_index - 1]; 76 portname = pd->name; 77 bff.bff_linkid = pd->linkid; 78 } 79 80 if (debugging) { 81 syslog(LOG_DEBUG, "flush forwarding %s %s: %s", 82 type == LT_FLASH_ONLY_THE_PORT ? "to" : "except for", 83 portname, reason); 84 } 85 86 bff.bff_exclude = (type == LT_FLASH_ALL_PORTS_EXCLUDE_THIS); 87 88 /* 89 * If flushing fails, we can't return. The only safe thing to do is to 90 * tear down the bridge so that we're not harming the network. 91 */ 92 if (strioctl(control_fd, BRIOC_FLUSHFWD, &bff, sizeof (bff)) == -1) { 93 syslog(LOG_ERR, "cannot flush forwarding entries on %s %s: %m", 94 instance_name, portname); 95 unlock_engine(); 96 exit(EXIT_FAILURE); 97 } 98 99 return (0); 100 } 101 102 static void 103 get_port_mac(int port_index, unsigned char *mac) 104 { 105 struct portdata *pd; 106 107 if (port_index > nextport || port_index <= 0) 108 return; 109 110 pd = allports[port_index - 1]; 111 (void) memcpy(mac, pd->mac_addr, ETHERADDRL); 112 } 113 114 /* Returns speed in megabits per second */ 115 static unsigned long 116 get_port_oper_speed(unsigned int port_index) 117 { 118 if (port_index > nextport || port_index == 0) 119 return (1000UL); 120 else 121 return (allports[port_index - 1]->speed); 122 } 123 124 static int 125 get_port_link_status(int port_index) 126 { 127 struct portdata *pd; 128 129 if (port_index > nextport || port_index <= 0) { 130 return (0); 131 } else { 132 pd = allports[port_index - 1]; 133 return (pd->phys_status && pd->admin_status && 134 protect == DLADM_BRIDGE_PROT_STP && !pd->sdu_failed ? 135 1 : 0); 136 } 137 } 138 139 static int 140 get_duplex(int port_index) 141 { 142 struct portdata *pd; 143 link_duplex_t link_duplex; 144 dladm_status_t status; 145 146 if (port_index > nextport || port_index <= 0) 147 return (False); 148 149 pd = allports[port_index - 1]; 150 status = dladm_get_single_mac_stat(dlhandle, pd->linkid, "link_duplex", 151 KSTAT_DATA_UINT32, &link_duplex); 152 153 if (status == DLADM_STATUS_OK && link_duplex == LINK_DUPLEX_FULL) 154 return (True); 155 else 156 return (False); 157 } 158 159 static const char * 160 bls_state(bridge_state_t bstate) 161 { 162 switch (bstate) { 163 case BLS_LEARNING: 164 return ("learning"); 165 case BLS_FORWARDING: 166 return ("forwarding"); 167 default: 168 return ("block/listen"); 169 } 170 } 171 172 /*ARGSUSED1*/ 173 static int 174 set_port_state(int port_index, int vlan_id, RSTP_PORT_STATE state) 175 { 176 struct portdata *pd; 177 bridge_setstate_t bss; 178 179 if (port_index > nextport || port_index <= 0) 180 return (1); 181 182 pd = allports[port_index - 1]; 183 184 if (debugging) 185 syslog(LOG_DEBUG, "setting port state on port %d (%s) to %d", 186 port_index, pd->name, state); 187 switch (state) { 188 case UID_PORT_LEARNING: 189 bss.bss_state = BLS_LEARNING; 190 break; 191 case UID_PORT_FORWARDING: 192 bss.bss_state = BLS_FORWARDING; 193 break; 194 default: 195 bss.bss_state = BLS_BLOCKLISTEN; 196 break; 197 } 198 bss.bss_linkid = pd->linkid; 199 if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) { 200 syslog(LOG_ERR, "cannot set STP state on %s from %s to %s: %m", 201 pd->name, bls_state(pd->state), bls_state(bss.bss_state)); 202 /* 203 * If we've been unsuccessful in disabling forwarding, then the 204 * only safe thing to do is to make the daemon exit, so that 205 * the kernel will be forced to destroy the bridge state and 206 * terminate all forwarding. 207 */ 208 if (pd->state == BLS_FORWARDING && 209 bss.bss_state != BLS_FORWARDING) { 210 unlock_engine(); 211 exit(EXIT_FAILURE); 212 } 213 } else { 214 pd->state = bss.bss_state; 215 } 216 return (0); 217 } 218 219 /* 220 * Our hardware doesn't actually do anything different when STP is enabled or 221 * disabled, so this function does nothing. It would be possible to open and 222 * close the DLPI stream here, if such a thing were necessary. 223 */ 224 static int 225 set_hardware_mode(int vlan_id, UID_STP_MODE_T mode) 226 { 227 if (debugging) 228 syslog(LOG_DEBUG, "setting hardware mode on vlan %d to %d", 229 vlan_id, mode); 230 return (0); 231 } 232 233 /*ARGSUSED1*/ 234 static int 235 tx_bpdu(int port_index, int vlan_id, unsigned char *bpdu, size_t bpdu_len) 236 { 237 struct portdata *pdp; 238 int rc; 239 240 if (port_index > nextport || port_index <= 0) 241 return (1); 242 243 pdp = allports[port_index - 1]; 244 rc = dlpi_send(pdp->dlpi, NULL, 0, bpdu, bpdu_len, NULL); 245 if (rc == DLPI_SUCCESS) { 246 if (debugging) 247 syslog(LOG_DEBUG, "transmitted %d byte BPDU on %s", 248 bpdu_len, pdp->name); 249 return (0); 250 } else { 251 syslog(LOG_WARNING, "failed to send to %s: %s", pdp->name, 252 dlpi_strerror(rc)); 253 return (1); 254 } 255 } 256 257 static const char * 258 get_port_name(int port_index) 259 { 260 if (port_index > nextport || port_index <= 0) 261 return ("unknown"); 262 else 263 return (allports[port_index - 1]->name); 264 } 265 266 /*ARGSUSED*/ 267 static int 268 get_init_stpm_cfg(int vlan_id, UID_STP_CFG_T *cfg) 269 { 270 /* under engine_lock because it's a callback from the engine */ 271 *cfg = uid_cfg; 272 return (0); 273 } 274 275 /*ARGSUSED*/ 276 static int 277 get_init_port_cfg(int vlan_id, int port_index, UID_STP_PORT_CFG_T *cfg) 278 { 279 struct portdata *pdp; 280 uint_t propval, valcnt; 281 datalink_id_t linkid; 282 dladm_status_t status; 283 284 if (port_index > nextport || port_index <= 0) 285 return (1); 286 287 pdp = allports[port_index - 1]; 288 289 cfg->field_mask = 0; 290 cfg->port_priority = DEF_PORT_PRIO; 291 cfg->admin_non_stp = DEF_ADMIN_NON_STP; 292 cfg->admin_edge = DEF_ADMIN_EDGE; 293 cfg->admin_port_path_cost = ADMIN_PORT_PATH_COST_AUTO; 294 cfg->admin_point2point = DEF_P2P; 295 296 valcnt = 1; 297 linkid = pdp->linkid; 298 status = dladm_get_linkprop_values(dlhandle, linkid, 299 DLADM_PROP_VAL_PERSISTENT, "stp_priority", &propval, &valcnt); 300 if (status == DLADM_STATUS_OK) { 301 cfg->port_priority = propval; 302 cfg->field_mask |= PT_CFG_PRIO; 303 } 304 status = dladm_get_linkprop_values(dlhandle, linkid, 305 DLADM_PROP_VAL_PERSISTENT, "stp", &propval, &valcnt); 306 if (status == DLADM_STATUS_OK) { 307 cfg->admin_non_stp = !propval; 308 cfg->field_mask |= PT_CFG_NON_STP; 309 } 310 status = dladm_get_linkprop_values(dlhandle, linkid, 311 DLADM_PROP_VAL_PERSISTENT, "stp_edge", &propval, &valcnt); 312 if (status == DLADM_STATUS_OK) { 313 cfg->admin_edge = propval; 314 cfg->field_mask |= PT_CFG_EDGE; 315 } 316 status = dladm_get_linkprop_values(dlhandle, linkid, 317 DLADM_PROP_VAL_PERSISTENT, "stp_cost", &propval, &valcnt); 318 if (status == DLADM_STATUS_OK) { 319 cfg->admin_port_path_cost = propval; 320 cfg->field_mask |= PT_CFG_COST; 321 } 322 status = dladm_get_linkprop_values(dlhandle, linkid, 323 DLADM_PROP_VAL_PERSISTENT, "stp_p2p", &propval, &valcnt); 324 if (status == DLADM_STATUS_OK) { 325 cfg->admin_point2point = propval; 326 cfg->field_mask |= PT_CFG_P2P; 327 } 328 329 /* 330 * mcheck is special. It is actually a command, but the 802 documents 331 * define it as a variable that spontaneously resets itself. We need 332 * to handle that behavior here. 333 */ 334 status = dladm_get_linkprop_values(dlhandle, linkid, 335 DLADM_PROP_VAL_PERSISTENT, "stp_mcheck", &propval, &valcnt); 336 if (status == DLADM_STATUS_OK && propval != 0) { 337 char *pval = "0"; 338 339 cfg->field_mask |= PT_CFG_MCHECK; 340 (void) dladm_set_linkprop(dlhandle, linkid, "stp_mcheck", &pval, 341 1, DLADM_OPT_ACTIVE|DLADM_OPT_PERSIST|DLADM_OPT_NOREFRESH); 342 } 343 344 pdp->admin_non_stp = cfg->admin_non_stp; 345 if (!pdp->admin_non_stp) 346 pdp->bpdu_protect = B_FALSE; 347 348 return (0); 349 } 350 351 static void 352 trace(const char *fmt, ...) 353 { 354 va_list ap; 355 356 va_start(ap, fmt); 357 vsyslog(LOG_DEBUG, fmt, ap); 358 va_end(ap); 359 } 360 361 static STP_VECTORS_T stp_vectors = { 362 flush_lt, 363 get_port_mac, 364 get_port_oper_speed, 365 get_port_link_status, 366 get_duplex, 367 set_port_state, 368 set_hardware_mode, 369 tx_bpdu, 370 get_port_name, 371 get_init_stpm_cfg, 372 get_init_port_cfg, 373 trace 374 }; 375 376 void 377 rstp_init(void) 378 { 379 dladm_status_t status; 380 char buf[DLADM_STRSIZE]; 381 382 STP_IN_init(&stp_vectors); 383 status = dladm_bridge_get_properties(instance_name, &uid_cfg, &protect); 384 if (status != DLADM_STATUS_OK) { 385 syslog(LOG_ERR, "%s: unable to read properties: %s", 386 instance_name, dladm_status2str(status, buf)); 387 exit(EXIT_FAILURE); 388 } 389 } 390 391 /* 392 * This is called by a normal refresh operation. It gets the engine properties 393 * and resets. 394 */ 395 void 396 rstp_refresh(void) 397 { 398 dladm_status_t status; 399 int rc; 400 char buf[DLADM_STRSIZE]; 401 UID_STP_CFG_T new_cfg; 402 dladm_bridge_prot_t new_prot; 403 404 status = dladm_bridge_get_properties(instance_name, &new_cfg, 405 &new_prot); 406 if (status != DLADM_STATUS_OK) { 407 syslog(LOG_ERR, "%s: unable to refresh bridge properties: %s", 408 instance_name, dladm_status2str(status, buf)); 409 } else { 410 if (debugging && (protect != new_prot || 411 uid_cfg.stp_enabled != new_cfg.stp_enabled)) { 412 syslog(LOG_DEBUG, "loop protection %s->%s, STP %d->%d", 413 dladm_bridge_prot2str(protect), 414 dladm_bridge_prot2str(new_prot), 415 uid_cfg.stp_enabled, new_cfg.stp_enabled); 416 } 417 418 /* 419 * The engine doesn't take kindly to parameter changes while 420 * running. Disable first if we must do this. 421 */ 422 if (uid_cfg.stp_enabled && 423 memcmp(&uid_cfg, &new_cfg, sizeof (uid_cfg)) != 0) { 424 syslog(LOG_DEBUG, "resetting state machine"); 425 uid_cfg.stp_enabled = STP_DISABLED; 426 rc = STP_IN_stpm_set_cfg(0, &uid_cfg); 427 if (rc != 0) 428 syslog(LOG_ERR, "STP machine reset config: %s", 429 STP_IN_get_error_explanation(rc)); 430 } 431 432 uid_cfg = new_cfg; 433 protect = new_prot; 434 rc = STP_IN_stpm_set_cfg(0, &uid_cfg); 435 if (rc != 0) 436 syslog(LOG_ERR, "STP machine set config: %s", 437 STP_IN_get_error_explanation(rc)); 438 } 439 } 440 441 /* 442 * This is called when a port changes its MAC address. If it's the main port, 443 * the one that supplies us our bridge ID, then we must choose a new ID, and to 444 * do that we shut the bridge down and bring it back up. 445 */ 446 void 447 rstp_change_mac(struct portdata *port, const unsigned char *newaddr) 448 { 449 unsigned short prio; 450 unsigned char mac[ETHERADDRL]; 451 int rc; 452 char curid[ETHERADDRL * 3]; 453 char newmac[ETHERADDRL * 3]; 454 455 (void) _link_ntoa(port->mac_addr, curid, ETHERADDRL, IFT_OTHER); 456 (void) _link_ntoa(newaddr, newmac, ETHERADDRL, IFT_OTHER); 457 STP_IN_get_bridge_id(port->vlan_id, &prio, mac); 458 if (memcmp(port->mac_addr, mac, ETHERADDRL) == 0) { 459 syslog(LOG_NOTICE, "bridge ID must change: ID %s on %s changed " 460 "to %s", curid, port->name, newmac); 461 uid_cfg.stp_enabled = STP_DISABLED; 462 if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0) 463 syslog(LOG_ERR, "STP machine set config: %s", 464 STP_IN_get_error_explanation(rc)); 465 (void) memcpy(port->mac_addr, newaddr, ETHERADDRL); 466 uid_cfg.stp_enabled = STP_ENABLED; 467 if ((rc = STP_IN_stpm_set_cfg(0, &uid_cfg)) != 0) 468 syslog(LOG_ERR, "STP machine set config: %s", 469 STP_IN_get_error_explanation(rc)); 470 } else { 471 syslog(LOG_DEBUG, 472 "MAC address on %s changed from %s to %s", port->name, 473 curid, newmac); 474 (void) memcpy(port->mac_addr, newaddr, ETHERADDRL); 475 } 476 } 477 478 boolean_t 479 rstp_add_port(struct portdata *port) 480 { 481 int rc; 482 UID_STP_PORT_CFG_T portcfg; 483 bridge_vlanenab_t bve; 484 bridge_setstate_t bss; 485 486 if (!port->stp_added && 487 (rc = STP_IN_port_add(port->vlan_id, port->port_index)) != 0) { 488 syslog(LOG_ERR, "STP add %s %d: %s", port->name, 489 port->port_index, STP_IN_get_error_explanation(rc)); 490 return (B_FALSE); 491 } 492 port->stp_added = B_TRUE; 493 494 /* guaranteed to succeed at this point */ 495 (void) get_init_port_cfg(port->vlan_id, port->port_index, &portcfg); 496 497 /* 498 * Restore state when reenabling STP engine, set fixed state when 499 * disabling. For TRILL, we don't control forwarding at all, but we 500 * need to turn off our controls for TRILL to do its thing. 501 */ 502 bss.bss_linkid = port->linkid; 503 if (protect != DLADM_BRIDGE_PROT_STP) { 504 bss.bss_state = port->state = BLS_BLOCKLISTEN; 505 } else if (portcfg.admin_non_stp) { 506 bss.bss_state = port->admin_status && !port->sdu_failed && 507 !port->bpdu_protect ? BLS_FORWARDING : BLS_BLOCKLISTEN; 508 } else { 509 bss.bss_state = port->state; 510 } 511 if (strioctl(control_fd, BRIOC_SETSTATE, &bss, sizeof (bss)) == -1) { 512 syslog(LOG_ERR, "cannot set STP state on %s: %m", port->name); 513 goto failure; 514 } 515 516 rc = STP_IN_enable_port(port->port_index, 517 port->admin_status && port->phys_status && !port->sdu_failed && 518 protect == DLADM_BRIDGE_PROT_STP); 519 if (rc != 0) { 520 syslog(LOG_ERR, "STP enable %s %d: %s", port->name, 521 port->port_index, STP_IN_get_error_explanation(rc)); 522 goto failure; 523 } 524 525 if (debugging) { 526 rc = STP_IN_dbg_set_port_trace("all", True, 0, 527 port->port_index); 528 } else { 529 /* return to default debug state */ 530 rc = STP_IN_dbg_set_port_trace("all", False, 0, 531 port->port_index); 532 if (rc == 0) 533 rc = STP_IN_dbg_set_port_trace("sttrans", True, 0, 534 port->port_index); 535 } 536 if (rc != 0) { 537 syslog(LOG_ERR, "STP trace %s %d: %s", port->name, 538 port->port_index, STP_IN_get_error_explanation(rc)); 539 goto failure; 540 } 541 542 /* Clear out the kernel's allowed VLAN set; second walk will set */ 543 bve.bve_linkid = port->linkid; 544 bve.bve_vlan = 0; 545 bve.bve_onoff = B_FALSE; 546 if (strioctl(control_fd, BRIOC_VLANENAB, &bve, sizeof (bve)) == -1) { 547 syslog(LOG_ERR, "unable to disable VLANs on %s: %m", 548 port->name); 549 goto failure; 550 } 551 552 if ((rc = STP_IN_port_set_cfg(0, port->port_index, &portcfg)) != 0) { 553 syslog(LOG_ERR, "STP port configure %s %d: %s", port->name, 554 port->port_index, STP_IN_get_error_explanation(rc)); 555 goto failure; 556 } 557 558 return (B_TRUE); 559 560 failure: 561 (void) STP_IN_port_remove(port->vlan_id, port->port_index); 562 port->stp_added = B_FALSE; 563 return (B_FALSE); 564 } 565