From 9efd308e957c26ab42a5210cc9fc7300c7d021f0 Mon Sep 17 00:00:00 2001 From: Daniele Venturino Date: Fri, 22 Aug 2014 09:01:34 -0700 Subject: Rapid Spanning Tree Protocol (IEEE 802.1D). This is the v5 from June 12th, 2014, rebased to OVS master, further changes in following patches. Signed-off by: Daniele Venturino Signed-off by: Martino Fornasa Signed-off-by: Jarno Rajahalme Acked-by: Daniele Venturino --- AUTHORS | 1 + NOTICE | 3 + lib/automake.mk | 5 + lib/packets.h | 5 + lib/rstp-common.h | 884 ++++++++++++++++++ lib/rstp-state-machines.c | 2036 ++++++++++++++++++++++++++++++++++++++++++ lib/rstp-state-machines.h | 42 + lib/rstp.c | 1236 +++++++++++++++++++++++++ lib/rstp.h | 199 +++++ lib/stp.h | 5 - ofproto/ofproto-dpif-xlate.c | 158 +++- ofproto/ofproto-dpif-xlate.h | 5 +- ofproto/ofproto-dpif.c | 286 +++++- ofproto/ofproto-provider.h | 47 + ofproto/ofproto.c | 84 ++ ofproto/ofproto.h | 50 ++ tests/.gitignore | 1 + tests/automake.mk | 3 + tests/ovs-vsctl.at | 4 + tests/rstp.at | 149 ++++ tests/test-rstp.c | 684 ++++++++++++++ tests/testsuite.at | 1 + utilities/ovs-vsctl.8.in | 79 ++ vswitchd/bridge.c | 295 ++++++ vswitchd/vswitch.ovsschema | 15 +- vswitchd/vswitch.xml | 101 +++ 26 files changed, 6325 insertions(+), 53 deletions(-) create mode 100644 lib/rstp-common.h create mode 100644 lib/rstp-state-machines.c create mode 100644 lib/rstp-state-machines.h create mode 100644 lib/rstp.c create mode 100644 lib/rstp.h create mode 100644 tests/rstp.at create mode 100644 tests/test-rstp.c diff --git a/AUTHORS b/AUTHORS index 2da24d6d2..e3fe7ba07 100644 --- a/AUTHORS +++ b/AUTHORS @@ -95,6 +95,7 @@ Madhu Challa challa@noironetworks.com Mark Hamilton mhamilton@nicira.com Mark Maglana mmaglana@gmail.com Martin Casado casado@nicira.com +Martino Fornasa mf@fornasa.it Maryam Tahhan maryam.tahhan@intel.com Mehak Mahajan mmahajan@nicira.com Murphy McCauley murphy.mccauley@gmail.com diff --git a/NOTICE b/NOTICE index 7a3d660e6..a213190b3 100644 --- a/NOTICE +++ b/NOTICE @@ -22,3 +22,6 @@ and Werner Lemberg. m4/include_next.m4 and m4/absolute-header.m4 Copyright (C) 2006-2013 Free Software Foundation, Inc. + +Rapid Spanning Tree Protocol (RSTP) implementation +Copyright (c) 2011-2014 M3S, Srl - Italy diff --git a/lib/automake.mk b/lib/automake.mk index f371ee00d..b14a5108b 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -189,6 +189,11 @@ lib_libopenvswitch_la_SOURCES = \ lib/rconn.h \ lib/reconnect.c \ lib/reconnect.h \ + lib/rstp.c \ + lib/rstp.h \ + lib/rstp-common.h \ + lib/rstp-state-machines.c \ + lib/rstp-state-machines.h \ lib/sat-math.h \ lib/seq.c \ lib/seq.h \ diff --git a/lib/packets.h b/lib/packets.h index 5747c42cc..26c6ff1b4 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -282,6 +282,11 @@ struct llc_header { }); BUILD_ASSERT_DECL(LLC_HEADER_LEN == sizeof(struct llc_header)); +/* LLC field values used for STP frames. */ +#define STP_LLC_SSAP 0x42 +#define STP_LLC_DSAP 0x42 +#define STP_LLC_CNTL 0x03 + #define SNAP_ORG_ETHERNET "\0\0" /* The compiler adds a null byte, so sizeof(SNAP_ORG_ETHERNET) == 3. */ #define SNAP_HEADER_LEN 5 diff --git a/lib/rstp-common.h b/lib/rstp-common.h new file mode 100644 index 000000000..6bc04eb91 --- /dev/null +++ b/lib/rstp-common.h @@ -0,0 +1,884 @@ +/* + * Copyright (c) 2011-2014 M3S, Srl - Italy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Rapid Spanning Tree Protocol (IEEE 802.1D-2004) common header file. + * + * Authors: + * Martino Fornasa + * Daniele Venturino + * + * References to IEEE 802.1D-2004 standard are enclosed in square brackets. + * E.g. [17.3], [Table 17-1], etc. + * + */ + +#ifndef RSTP_COMMON_H +#define RSTP_COMMON_H 1 + +#include "rstp.h" +#include +#include +#include "list.h" +#include "ovs-atomic.h" +#include "packets.h" + +enum admin_port_state { + RSTP_ADMIN_BRIDGE_PORT_STATE_DISABLED = 0, + RSTP_ADMIN_BRIDGE_PORT_STATE_ENABLED = 1 +}; + +enum oper_p2p_mac_state { + RSTP_OPER_P2P_MAC_STATE_DISABLED = 0, + RSTP_OPER_P2P_MAC_STATE_ENABLED = 1 +}; + +/* State enumerations for state machines defined in rstp-state-machines.c */ +enum port_receive_state_machine { + PORT_RECEIVE_SM_INIT, + PORT_RECEIVE_SM_DISCARD_EXEC, + PORT_RECEIVE_SM_DISCARD, + PORT_RECEIVE_SM_RECEIVE_EXEC, + PORT_RECEIVE_SM_RECEIVE +}; +enum port_transmit_state_machine { + PORT_TRANSMIT_SM_INIT, + PORT_TRANSMIT_SM_TRANSMIT_INIT_EXEC, + PORT_TRANSMIT_SM_TRANSMIT_INIT, + PORT_TRANSMIT_SM_TRANSMIT_PERIODIC_EXEC, + PORT_TRANSMIT_SM_TRANSMIT_PERIODIC, + PORT_TRANSMIT_SM_IDLE_EXEC, + PORT_TRANSMIT_SM_IDLE, + PORT_TRANSMIT_SM_TRANSMIT_CONFIG_EXEC, + PORT_TRANSMIT_SM_TRANSMIT_CONFIG, + PORT_TRANSMIT_SM_TRANSMIT_TCN_EXEC, + PORT_TRANSMIT_SM_TRANSMIT_TCN, + PORT_TRANSMIT_SM_TRANSMIT_RSTP_EXEC, + PORT_TRANSMIT_SM_TRANSMIT_RSTP +}; +enum bridge_detection_state_machine { + BRIDGE_DETECTION_SM_INIT, + BRIDGE_DETECTION_SM_EDGE_EXEC, + BRIDGE_DETECTION_SM_EDGE, + BRIDGE_DETECTION_SM_NOT_EDGE_EXEC, + BRIDGE_DETECTION_SM_NOT_EDGE +}; +enum port_protocol_migration_state_machine { + PORT_PROTOCOL_MIGRATION_SM_INIT, + PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP_EXEC, + PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP, + PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP_EXEC, + PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP, + PORT_PROTOCOL_MIGRATION_SM_SENSING_EXEC, + PORT_PROTOCOL_MIGRATION_SM_SENSING +}; +enum port_information_state_machine { + PORT_INFORMATION_SM_INIT, + PORT_INFORMATION_SM_DISABLED_EXEC, + PORT_INFORMATION_SM_DISABLED, + PORT_INFORMATION_SM_AGED_EXEC, + PORT_INFORMATION_SM_AGED, + PORT_INFORMATION_SM_UPDATE_EXEC, + PORT_INFORMATION_SM_UPDATE, + PORT_INFORMATION_SM_CURRENT_EXEC, + PORT_INFORMATION_SM_CURRENT, + PORT_INFORMATION_SM_RECEIVE_EXEC, + PORT_INFORMATION_SM_RECEIVE, + PORT_INFORMATION_SM_OTHER_EXEC, + PORT_INFORMATION_SM_OTHER, + PORT_INFORMATION_SM_NOT_DESIGNATED_EXEC, + PORT_INFORMATION_SM_NOT_DESIGNATED, + PORT_INFORMATION_SM_INFERIOR_DESIGNATED_EXEC, + PORT_INFORMATION_SM_INFERIOR_DESIGNATED, + PORT_INFORMATION_SM_REPEATED_DESIGNATED_EXEC, + PORT_INFORMATION_SM_REPEATED_DESIGNATED, + PORT_INFORMATION_SM_SUPERIOR_DESIGNATED_EXEC, + PORT_INFORMATION_SM_SUPERIOR_DESIGNATED +}; +enum port_role_selection_state_machine { + PORT_ROLE_SELECTION_SM_INIT, + PORT_ROLE_SELECTION_SM_INIT_BRIDGE_EXEC, + PORT_ROLE_SELECTION_SM_INIT_BRIDGE, + PORT_ROLE_SELECTION_SM_ROLE_SELECTION_EXEC, + PORT_ROLE_SELECTION_SM_ROLE_SELECTION +}; +enum port_role_transition_state_machine { + PORT_ROLE_TRANSITION_SM_INIT, + PORT_ROLE_TRANSITION_SM_INIT_PORT_EXEC, + PORT_ROLE_TRANSITION_SM_DISABLE_PORT_EXEC, + PORT_ROLE_TRANSITION_SM_DISABLE_PORT, + PORT_ROLE_TRANSITION_SM_DISABLED_PORT_EXEC, + PORT_ROLE_TRANSITION_SM_DISABLED_PORT, + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC, + PORT_ROLE_TRANSITION_SM_ROOT_PORT, + PORT_ROLE_TRANSITION_SM_REROOT_EXEC, + PORT_ROLE_TRANSITION_SM_ROOT_AGREED_EXEC, + PORT_ROLE_TRANSITION_SM_ROOT_PROPOSED_EXEC, + PORT_ROLE_TRANSITION_SM_ROOT_FORWARD_EXEC, + PORT_ROLE_TRANSITION_SM_ROOT_LEARN_EXEC, + PORT_ROLE_TRANSITION_SM_REROOTED_EXEC, + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC, + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT, + PORT_ROLE_TRANSITION_SM_DESIGNATED_RETIRED_EXEC, + PORT_ROLE_TRANSITION_SM_DESIGNATED_SYNCED_EXEC, + PORT_ROLE_TRANSITION_SM_DESIGNATED_PROPOSE_EXEC, + PORT_ROLE_TRANSITION_SM_DESIGNATED_FORWARD_EXEC, + PORT_ROLE_TRANSITION_SM_DESIGNATED_LEARN_EXEC, + PORT_ROLE_TRANSITION_SM_DESIGNATED_DISCARD_EXEC, + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC, + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT, + PORT_ROLE_TRANSITION_SM_ALTERNATE_AGREED, + PORT_ROLE_TRANSITION_SM_ALTERNATE_PROPOSED_EXEC, + PORT_ROLE_TRANSITION_SM_BLOCK_PORT_EXEC, + PORT_ROLE_TRANSITION_SM_BLOCK_PORT, + PORT_ROLE_TRANSITION_SM_BACKUP_PORT_EXEC +}; +enum port_state_transition_state_machine { + PORT_STATE_TRANSITION_SM_INIT, + PORT_STATE_TRANSITION_SM_DISCARDING_EXEC, + PORT_STATE_TRANSITION_SM_DISCARDING, + PORT_STATE_TRANSITION_SM_LEARNING_EXEC, + PORT_STATE_TRANSITION_SM_LEARNING, + PORT_STATE_TRANSITION_SM_FORWARDING_EXEC, + PORT_STATE_TRANSITION_SM_FORWARDING +}; +enum topology_change_state_machine { + TOPOLOGY_CHANGE_SM_INIT, + TOPOLOGY_CHANGE_SM_INACTIVE_EXEC, + TOPOLOGY_CHANGE_SM_INACTIVE, + TOPOLOGY_CHANGE_SM_LEARNING_EXEC, + TOPOLOGY_CHANGE_SM_LEARNING, + TOPOLOGY_CHANGE_SM_DETECTED_EXEC, + TOPOLOGY_CHANGE_SM_ACTIVE_EXEC, + TOPOLOGY_CHANGE_SM_ACTIVE, + TOPOLOGY_CHANGE_SM_ACKNOWLEDGED_EXEC, + TOPOLOGY_CHANGE_SM_PROPAGATING_EXEC, + TOPOLOGY_CHANGE_SM_NOTIFIED_TC_EXEC, + TOPOLOGY_CHANGE_SM_NOTIFIED_TCN_EXEC, +}; + + +/* [17.18.4, 17.13, Table 17-1]. */ +struct rstp_times { + /* [17.13.5 - Bridge Forward Delay] The delay (expressed in seconds) used + * by STP Bridges (17.4) to transition Root and Designated Ports to + * Forwarding (Table 17-1). + * Default = 15.0 s. Values in range 4.0 - 30.0 + */ + uint16_t forward_delay; + + /* [17.13.6 - Bridge Hello Time] + * The interval between periodic transmissions of Configuration Messages + * by Designated Ports (Table 17-1). + * Default = 2.0 s. Fixed value + */ + uint16_t hello_time; + + /* [17.13.8 - Bridge Max Age] + * The maximum age of the information transmitted by the Bridge when it is + * the Root Bridge (Table 17-1). + * Default = 20.0 s. Values in range 6.0 - 40.0 */ + uint16_t max_age; + + uint16_t message_age; +}; + +/* Priority vector [17.6] */ +struct rstp_priority_vector { + rstp_identifier root_bridge_id; + uint32_t root_path_cost; + rstp_identifier designated_bridge_id; + uint16_t designated_port_id; + uint16_t bridge_port_id; +}; + +struct rstp_priority_vector4 { + rstp_identifier root_bridge_id; + uint32_t root_path_cost; + rstp_identifier designated_bridge_id; + uint16_t designated_port_id; +}; + +enum rstp_bpdu_type { + CONFIGURATION_BPDU = 0x0, + TOPOLOGY_CHANGE_NOTIFICATION_BPDU = 0x80, + RAPID_SPANNING_TREE_BPDU = 0x2 +} bpdu_type_t; + +enum rstp_bpdu_flag { + BPDU_FLAG_TOPCHANGE = 0x01, + BPDU_FLAG_PROPOSAL = 0x02, + BPDU_FLAG_LEARNING = 0x10, + BPDU_FLAG_FORWARDING = 0x20, + BPDU_FLAG_AGREEMENT = 0x40, + BPDU_FLAG_TOPCHANGEACK = 0x80 +} bpdu_flag; + +/* Rapid Spanning Tree BPDU [9.3.3] */ +OVS_PACKED( +struct rstp_bpdu { + ovs_be16 protocol_identifier; + uint8_t protocol_version_identifier; + uint8_t bpdu_type; + uint8_t flags; + ovs_be64 root_bridge_id; + ovs_be32 root_path_cost; + ovs_be64 designated_bridge_id; + ovs_be16 designated_port_id; + ovs_be16 message_age; + ovs_be16 max_age; + ovs_be16 hello_time; + ovs_be16 forward_delay; + uint8_t version1_length; + uint8_t padding[7]; +}); + +enum rstp_admin_point_to_point_mac_state { + RSTP_ADMIN_P2P_MAC_FORCE_TRUE, + RSTP_ADMIN_P2P_MAC_FORCE_FALSE, + RSTP_ADMIN_P2P_MAC_FORCE_AUTO +}; + +enum rstp_info_is { + INFO_IS_DISABLED, + INFO_IS_RECEIVED, + INFO_IS_AGED, + INFO_IS_MINE +}; + +enum rstp_rcvd_info { + SUPERIOR_DESIGNATED_INFO, + REPEATED_DESIGNATED_INFO, + INFERIOR_DESIGNATED_INFO, + INFERIOR_ROOT_ALTERNATE_INFO, + OTHER_INFO +}; + +struct rstp_port { + struct rstp *rstp; + struct list node; /* Node in rstp->ports list. */ + void *aux; + struct rstp_bpdu received_bpdu_buffer; + /************************************************************************* + * MAC status parameters + ************************************************************************/ + /* [6.4.2 - MAC_Operational] + * The value of this parameter is TRUE if [...] the MAC entity can be used + * to transmit and/or receive frames, and its use is permitted by + * management. + */ + bool mac_operational; + + /* [14.8.2.2] Administrative Bridge Port State */ + bool is_administrative_bridge_port; + + /* [6.4.3 - operPointToPointMAC] + * a) True. The MAC is connected to a point-to-point LAN; i.e., there is + * at most one other system attached to the LAN. + * b) False. The MAC is connected to a non-point-to-point LAN; i.e., + * there can be more than one other system attached to the LAN. + * + * If adminPointToPointMAC is set to ForceTrue, then operPointToPointMAC + * shall be set True. If adminPointToPointMAC is set to ForceFalse, then + * operPointToPointMAC shall be set False. + */ + bool oper_point_to_point_mac; + + /* [6.4.3 - adminPointToPointMAC] + * a) ForceTrue. The administrator requires the MAC to be treated as if it + * is connected to a point-to-point LAN, regardless of any indications + * to the contrary that are generated by the MAC entity. + * b) ForceFalse. The administrator requires the MAC to be treated as + * connected to a non-point-to-point LAN, regardless of any indications + * to the contrary that are generated by the MAC entity. + * c) Auto. The administrator requires the point-to-point status of the + * MAC to be determined in accordance with the specific MAC procedures + * defined in 6.5. + */ + enum rstp_admin_point_to_point_mac_state admin_point_to_point_mac; + + + /************************************************************************* + * [17.3 - RSTP performance parameters] Set by management actions on the + * bridge + *************************************************************************/ + + /* [17.13.1 - Admin Edge Port] + * The AdminEdgePort parameter for the Port (14.8.2). + */ + bool admin_edge; + + /* [17.13.3 - AutoEdge] + * The AutoEdgePort parameter for the Port (14.8.2). + */ + bool auto_edge; + + + /************************************************************************* + * The following variables are set by management actions on the bridge + ************************************************************************/ + + /* Port number and priority + * >=1 (max 12 bits [9.2.7]) + */ + uint16_t port_number; + + /* Port priority + * Range: 0-240 in steps of 16 (table 17-2) + */ + uint8_t priority; + + /* [17.13.11 - PortPathCost] + * The Port's contribution, when it is the Root Port, to the Root Path Cost + * (17.3.1, 17.5, 17.6) for the Bridge. + */ + uint32_t port_path_cost; + + /************************************************************************* + * The following variables are defined in [17.17 - State machine timers] + ************************************************************************/ + /* [17.17.1 - edgeDelayWhile] + * The Edge Delay timer. The time remaining, in the absence of a received + * BPDU, before this port is identified as an operEdgePort. + */ + uint16_t edge_delay_while; + + /* [17.17.2 - fdWhile] + * The Forward Delay timer. Used to delay Port State transitions until + * other Bridges have received spanning tree information. + */ + uint16_t fd_while; + + /* [17.17.3 - helloWhen] + * The Hello timer. Used to ensure that at least one BPDU is transmitted by + * a Designated Port in each HelloTime period. + */ + uint16_t hello_when; + + /* [17.17.4 - mdelayWhile] + * The Migration Delay timer. Used by the Port Protocol Migration state + * machine to allow time for another RSTP Bridge on the same LAN to + * synchronize its migration state with this Port before the receipt of a + * BPDU can cause this Port to change the BPDU types it transmits. + * Initialized to MigrateTime (17.13.9). + */ + uint16_t mdelay_while; + + /* [17.17.5 - rbWhile] + * The Recent Backup timer. Maintained at its initial value, twice + * HelloTime, while the Port is a Backup Port. + */ + uint16_t rb_while; + + /* [17.17.6 - rcvdInfoWhile] + * The Received Info timer. The time remaining before the spanning tree + * information received by this Port [portPriority (17.19.21) and portTimes + * (17.19.22)] is aged out if not refreshed by the receipt of a further + * Configuration Message. + */ + uint16_t rcvd_info_while; + + /* [17.17.7 - rrWhile] + * The Recent Root timer. + */ + uint16_t rr_while; + + /* [17.17.8 - tcWhile] + * The Topology Change timer. TCN Messages are sent while this timer is + * running. + */ + uint16_t tc_while; + + + /************************************************************************* + * The following variables are defined in [17.19 - Per-Port variables] + ************************************************************************/ + + /* [17.19.1 - ageingTime] + * Filtering database entries for this Port are aged out after ageingTime + * has elapsed since they were first created or refreshed by the Learning + * Process. + * The value of this parameter is normally Ageing Time (7.9.2, Table 7-5), + * and is changed to FwdDelay (17.20.6) for a period of FwdDelay after + * fdbFlush (17.19.7) is set by the topology change state machine if + * stpVersion (17.19.7) is TRUE. + */ + uint32_t ageing_time; + + /* [17.19.2 - agree] + * Set if synced is set for all other Ports. An RST BPDU with the Agreement + * flag set is transmitted and proposed is reset when agree is first set, + * and when proposed is set. + * Initialized by Port Information state machine. + */ + bool agree; + + /* [17.19.3 - agreed] + * Set when an RST BPDU is received with a Port Role of Root, Alternate, or + * Backup Port, the Agreement flag set, and a message priority the same or + * worse than the port priority. When agreed is set, the Designated Port + * knows that its neighbouring Bridge has confirmed that it can proceed to + * the Forwarding state without further delay. + * Initialized by Port Information state machine. + */ + bool agreed; + + /* [17.19.4 - designatedPriority] + * The first four components of the Port's designated priority vector + * value, as defined in 17.6. The fifth component of the designated + * priority vector value is portId (17.19.19). + * (Fifth component of the structure must not be used) + */ + struct rstp_priority_vector designated_priority_vector; + + /* [17.19.5 - designatedTimes] + * The designatedTimes variable comprises the set of timer parameter values + * (Message Age, Max Age, Forward Delay, and Hello Time) that used to + * update Port Times when updtInfo is set. Updated by the updtRolesTree() + * procedure (17.21.25). + */ + struct rstp_times designated_times; + + /* [17.19.6 - disputed] */ + bool disputed; + + /* [17.19.7 - fdbFlush] + * A boolean. Set by the topology change state machine to instruct the + * filtering database to remove all entries for this Port, immediately if + * rstpVersion (17.20.11) is TRUE, or by rapid ageing (17.19.1) if + * stpVersion (17.20.12) is TRUE. Reset by the filtering database once the + * entries are + * removed if rstpVersion is TRUE, and immediately if stpVersion is TRUE. + */ + uint8_t fdb_flush; + + /* [17.19.8 - forward] + * Initialized by Port State Transition state machine. + */ + bool forward; + + /* [17.19.9 - forwarding] + * Initialized by Port State Transition state machine. + */ + bool forwarding; + + /* [17.19.10 - infoIs] + * A variable that takes the values Mine, Aged, Received, or Disabled, to + * indicate the origin/state of the Port's Spanning Tree information + * (portInfo) held for the Port, as follows: + * a) If infoIs is Received, the port has received current (not aged out) + * information from the Designated Bridge for the attached LAN (a + * point-to-point bridge link being a special case of a LAN). + * b) If infoIs is Mine, information for the port has been derived from + * the Root Port for the Bridge (with the addition of root port cost + * information). This includes the possibility that the Root Port is + * "Port 0," i.e., the bridge is the Root Bridge for the Bridged Local + * Area Network. + * c) If infoIs is Aged, information from the Root Bridge has been aged + * out. Just as for "reselect" (see 17.19.34), the state machine does + * not formally allow the "Aged" state to persist. However, if there is + * a delay in recomputing the new root port, correct processing of a + * received BPDU is specified. + * d) Finally if the port is disabled, infoIs is Disabled. + */ + enum rstp_info_is info_is; + + /* [17.19.11 - learn] + * Initialized by Port State Transition state machine. + */ + bool learn; + + /* [17.19.12 - learning] + * Initialized by Port State Transition state machine. + */ + bool learning; + + /* [17.19.13 - mcheck] + * A boolean. May be set by management to force the Port Protocol Migration + * state machine to transmit RST BPDUs for a MigrateTime (17.13.9) period, + * to test whether all STP Bridges (17.4) on the attached LAN have been + * removed and the Port can continue to transmit RSTP BPDUs. Setting mcheck + * has no effect if stpVersion (17.20.12) is TRUE, i.e., the Bridge is + * operating in STP Compatibility mode. + */ + bool mcheck; + + /* [17.19.14 - msgPriority] + * The first four components of the message priority vector conveyed in a + * received BPDU, as defined in 17.6. + * (Fifth component of the structure must not be used). + */ + struct rstp_priority_vector msg_priority; + + /* [17.19.15 - msgTimes] + * The msgTimes variable comprises the timer parameter values (Message Age, + * Max Age, Forward Delay, and Hello Time) conveyed in a received BPDU. + */ + struct rstp_times msg_times; + + /* [17.19.16 - newInfo] + * A boolean. Set if a BPDU is to be transmitted. Reset by the Port + * Transmit state machine. + */ + bool new_info; + + /* [17.19.17 - operEdge] + * A boolean. The value of the operEdgePort parameter, as determined by the + * operation of the Bridge Detection state machine (17.25). + */ + bool oper_edge; + + /* [17.19.18 - portEnabled] + * A boolean. Set if the Bridge's MAC Relay Entity and Spanning Tree + * Protocol Entity can use the MAC Service provided by the Port's MAC + * entity to transmit and receive frames to and from the attached LAN, + * i.e., portEnabled is TRUE if and only if: + * a) MAC_Operational (6.4.2) is TRUE; and + * b) Administrative Bridge Port State (14.8.2.2) for the Port is + * Enabled; and + * c) AuthControlledPortStatus is Authorized [if the port is a network + * access port (IEEE Std 802.1X)]. + */ + bool port_enabled; + + /* [17.19.19 - portId] + * The Port Identifier. This variable forms the fifth component of the port + * priority and designated priority vectors defined in 17.6. + */ + uint16_t port_id; + + /* [17.19.21 - portPriority] + * The first four components of the Port's port priority vector value, as + * defined in 17.6. + * (Fifth component of the structure must not be used) + */ + struct rstp_priority_vector port_priority; + + /* [17.19.22 - portTimes] + * The portTimes variable comprises the Port's timer parameter values + * (Message Age, Max Age, Forward Delay, and Hello Time). These timer + * values are used in BPDUs transmitted from the Port. + */ + struct rstp_times port_times; + + /* [17.19.23 - proposed] + * Set when an RST BPDU with a Designated Port role and the Proposal flag + * set is received. If agree is not set, proposed causes sync to be set for + * all other Ports.of the Bridge. + */ + bool proposed; + + /* [17.19.24 - proposing] + * Set by a Designated Port that is not Forwarding, and conveyed to the + * Root Port or Alternate Port of a neighboring Bridge in the Proposal flag + * of an RST BPDU (9.3.3). + */ + bool proposing; + + /* [17.19.25 - rcvdBPDU] + * A boolean. Set by system dependent processes, this variable notifies the + * Port Receive state machine (17.23) when a valid (9.3.4) Configuration, + * TCN, or RST BPDU (9.3.1, 9.3.2, 9.3.3) is received on the Port. Reset + * by the Port Receive state machine. + */ + bool rcvd_bpdu; + + /* [17.19.26 - rcvdInfo] + * Set to the result of the rcvInfo() procedure (17.21.8). + */ + enum rstp_rcvd_info rcvd_info; + + /* [17.19.27 - rcvdMsg] */ + bool rcvd_msg; + + /* [17.19.28 - rcvdRSTP] */ + bool rcvd_rstp; + + /* [17.19.29 - rcvdSTP] */ + bool rcvd_stp; + + /* [17.19.30 - rcvdTc] */ + bool rcvd_tc; + + /* [17.19.31 - rcvdTcAck] */ + bool rcvd_tc_ack; + + /* [17.19.32 - rcvdTcn] */ + bool rcvd_tcn; + + /* [17.19.33 - reRoot] */ + bool re_root; + + /* [17.19.34 - reselect] */ + bool reselect; + + /* [17.19.35 - role] + * The assigned Port Role (17.7). + */ + enum rstp_port_role role; + + /* [17.19.36 - selected] + * A boolean. See 17.28, 17.21.16. + */ + bool selected; + + /* [17.19.37 - selectedRole] + * The newly computed role for the Port (17.7, 17.28, 17.21.25, 17.19.35). + */ + enum rstp_port_role selected_role; + + /* [17.19.38 - sendRSTP] + * A boolean. See 17.24, 17.26. + */ + bool send_rstp; + + /* [17.19.39 - sync] + * A boolean. See 17.10. + */ + bool sync; + + /* [17.19.40 - synced] + * A boolean. See 17.10. + */ + bool synced; + + /* [17.19.41 - tcAck] + * A boolean. Set if a Configuration Message with a topology change + * acknowledge flag set is to be transmitted. + */ + bool tc_ack; + + /* [17.19.42 - tcProp] + * A boolean. Set by the Topology Change state machine of any other Port, + * to indicate that a topology change should be propagated through this + * Port. + */ + bool tc_prop; + + /* [17.19.43 - tick] + * A boolean. See 17.22. + */ + bool tick; + + /* [17.19.44 - txCount] + * A counter. Incremented by the Port Transmission (17.26) state machine on + * every BPDU transmission, and decremented used by the Port Timers state + * machine (17.22) once a second. Transmissions are delayed if txCount + * reaches TxHoldCount (17.13.12). + */ + uint16_t tx_count; + + /* [17.19.45 - updtInfo] + * A boolean. Set by the Port Role Selection state machine (17.28, + * 17.21.25) to tell the Port Information state machine that it should copy + * designatedPriority to portPriority and designatedTimes to portTimes. + */ + bool updt_info; + + /* Counter for RSTP received frames - for rstpd */ + uint32_t rx_rstp_bpdu_cnt; + + /* Counter for bad RSTP received frames */ + uint32_t error_count; + + /* [14.8.2.1.3] Outputs + * a) Uptime count in seconds of the time elapsed since the Port was last + * reset or initialized. + */ + uint32_t uptime; + + enum rstp_state rstp_state; + bool state_changed; + + /* Per-port state machines state */ + enum port_receive_state_machine port_receive_sm_state; + enum port_protocol_migration_state_machine port_protocol_migration_sm_state; + enum bridge_detection_state_machine bridge_detection_sm_state; + enum port_transmit_state_machine port_transmit_sm_state; + enum port_information_state_machine port_information_sm_state; + enum port_role_transition_state_machine port_role_transition_sm_state; + enum port_state_transition_state_machine port_state_transition_sm_state; + enum topology_change_state_machine topology_change_sm_state; +}; + +struct rstp { + struct list node; /* Node in rstp instances list */ + char *name; /* Bridge name. */ + + /* Changes in last SM execution. */ + bool changes; + + /* Per-bridge state machines state */ + enum port_role_selection_state_machine port_role_selection_sm_state; + + /* Bridge MAC address + * (stored in the least significant 48 bits of rstp_identifier). + */ + rstp_identifier address; /* [7.12.5] */ + + /* Bridge priority */ + uint16_t priority; /* Valid values: 0-61440 in steps of 4096 */ + + /************************************************************************* + * [17.3 - RSTP performance parameters] + ************************************************************************/ + + /* [17.13] + * The Spanning Tree Protocol Entity shall be reinitialized, as specified + * by the assertion of BEGIN (17.18.1) in the state machine specification, + * if the following parameters are modified: + * a) Force Protocol Version (17.13.4) + * + * The spanning tree priority vectors and Port Role assignments for a + * Bridge shall be recomputed, as specified by the operation of the Port + * Role Selection state machine (17.28) by clearing selected (17.19.36) and + * setting reselect (17.19.34) for any Port or Ports for which the + * following parameters are modified: + * b) Bridge Identifier Priority (17.13.7) + * c) Port Identifier Priority (17.13.10) + * d) Port Path Cost (17.13.11) + * + * If the Transmit Hold Count is modified the value of txCount (17.19.44) + * for all Ports shall be set to zero. + * + * The RSTP specification permits changes in other performance parameters + * without exceptional actions. + */ + + + /* [17.13.2 - Ageing Time] + * The Ageing Time parameter for the Bridge (7.9.2, Table 7-5). + */ + uint32_t ageing_time; + + /* [17.13.4 - Force Protocol Version] + * The Force Protocol Version parameter for the Bridge (17.4, 14.8.1). + * This can take the value 0 (STP Compatibility mode) or 2 (the default, + * normal operation). + */ + enum rstp_force_protocol_version force_protocol_version; + + /* [17.13.5 - Bridge Forward Delay] + * The delay used by STP Bridges (17.4) to transition Root and Designated + * Ports to Forwarding (Table 17-1). + */ + uint16_t bridge_forward_delay; + + /* [17.13.6 - Bridge Hello Time] + * The interval between periodic transmissions of Configuration Messages + * by Designated Ports (Table 17-1). + */ + uint16_t bridge_hello_time; + + /* [17.13.8 - Bridge Max Age] + * The maximum age of the information transmitted by the Bridge when it is + * the Root Bridge (Table 17-1). + */ + uint16_t bridge_max_age; + + /* [17.13.9 - Migrate Time] + * The initial value of the mdelayWhile and edgeDelayWhile timers (17.17.4, + * 17.17.1), fixed for all RSTP implementations conforming to this + * specification (Table 17-1). + */ + uint16_t migrate_time; + + /* [17.13.12 - Transmit Hold Count] + * The Transmit Hold Count (Table 17-1) used by the Port Transmit state + * machine to limit transmission rate. + */ + uint16_t transmit_hold_count; + + + /************************************************************************* + * The following variables are defined in [17.18 - Per-Bridge variables] + ************************************************************************/ + + /* [17.18.1 - BEGIN] + * A Boolean controlled by the system initialization (17.16). If TRUE + * causes all state machines, including per Port state machines, to + * continuously execute their initial state. + */ + bool begin; + + /* [17.18.2 BridgeIdentifier] + * The unique Bridge Identifier assigned to this Bridge, comprising two + * components: the Bridge Identifier Priority, which may be modified by + * management (see 9.2.5 and 14.8.1.2) and is the more significant when + * Bridge Identifiers are compared, and a component derived from the Bridge + * Address (7.12.5), which guarantees uniqueness of the Bridge Identifiers + * of different Bridges. + */ + rstp_identifier bridge_identifier; + + /* [17.8.3 BridgePriority] + * The bridge priority vector, as defined in 17.6. The first (RootBridgeID) + * and third (DesignatedBridgeID) components are both equal to the value + * of the Bridge Identifier (17.18.2). The other components are zero. + */ + struct rstp_priority_vector bridge_priority; + + /* [17.18.4 - BridgeTimes] + * BridgeTimes comprises four components: the current values of Bridge + * Forward Delay, Bridge Hello Time, Bridge Max Age (17.13, Table 17-1), + * and a Message Age of zero. + */ + struct rstp_times bridge_times; + + /* [17.18.6 - rootPriority] + * The first four components of the Bridge's root priority vector, as + * defined in 17.6. + */ + struct rstp_priority_vector root_priority; + + /* [17.18.5 - rootPortId] + * The Port Identifier of the Root Port. This is the fifth component of + * the root priority vector, as defined in 17.6. + */ + uint16_t root_port_id; + + /* [17.18.7 - rootTimes] + * The rootTimes variable comprises the Bridge's operational timer + * parameter values (Message Age, Max Age, Forward Delay, and Hello Time), + * derived from the values stored in portTimes (17.19.22) for the Root Port + * or from BridgeTimes (17.18.4). + */ + struct rstp_times root_times; + + /* 17.20 State machine conditions and parameters */ + + /* [17.20.11] rstpVersion + * TRUE if Force Protocol Version (17.13.4) is greater than or equal to 2. + */ + bool rstp_version; + + /* [17.20.12] stpVersion + * TRUE if Force Protocol Version (17.13.4) is less than 2. + */ + bool stp_version; + + /* Ports */ + struct list ports; + uint16_t ports_count; + + struct ovs_refcount ref_cnt; + + /* Interface to client. */ + void (*send_bpdu)(struct ofpbuf *bpdu, int port_no, void *aux); + void *aux; +}; + +#endif /* rstp-common.h */ diff --git a/lib/rstp-state-machines.c b/lib/rstp-state-machines.c new file mode 100644 index 000000000..4aea49de2 --- /dev/null +++ b/lib/rstp-state-machines.c @@ -0,0 +1,2036 @@ +/* + * Copyright (c) 2011-2014 M3S, Srl - Italy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Rapid Spanning Tree Protocol (IEEE 802.1D-2004) state machines + * implementation. + * + * Authors: + * Martino Fornasa + * Daniele Venturino + * + * References to IEEE 802.1D-2004 standard are enclosed in square brackets. + * E.g. [17.3], [Table 17-1], etc. + * + */ + +#include +#include "rstp.h" +#include "rstp-state-machines.h" +#include +#include +#include +#include +#include +#include "byte-order.h" +#include "connectivity.h" +#include "ofpbuf.h" +#include "packets.h" +#include "seq.h" +#include "unixctl.h" +#include "util.h" +#include "vlog.h" + +VLOG_DEFINE_THIS_MODULE(rstp_sm); + +#define ROLE_FLAG_MASK 0xC +#define ROLE_FLAG_SHIFT 2 + +enum port_flag { + PORT_UNKN = 0, + PORT_ALT_BACK = 1, + PORT_ROOT = 2, + PORT_DES = 3 +}; + +enum bpdu_size { + CONFIGURATION_BPDU_SIZE = 35, + TOPOLOGY_CHANGE_NOTIFICATION_BPDU_SIZE = 4, + RAPID_SPANNING_TREE_BPDU_SIZE = 36 +}; + +enum vector_comparison { + INFERIOR = 0, + SUPERIOR = 1, + SAME = 2 +}; + +static void decrement_timer(uint16_t *); +static void rstp_send_bpdu(struct rstp_port *, const void *, size_t); +static int validate_received_bpdu(struct rstp_port *, const void *, size_t); +static ovs_be16 time_encode(uint8_t); +static uint8_t time_decode(ovs_be16); +static enum vector_comparison +compare_rstp_priority_vector(struct rstp_priority_vector *, + struct rstp_priority_vector *); +static bool rstp_times_equal(struct rstp_times *, struct rstp_times *); + +/* Per-Bridge State Machine */ +static int port_role_selection_sm(struct rstp *); +/* Per-Port State Machines */ +static int port_receive_sm(struct rstp_port *); +static int port_protocol_migration_sm(struct rstp_port *); +static int bridge_detection_sm(struct rstp_port *); +static int port_transmit_sm(struct rstp_port *); +static int port_information_sm(struct rstp_port *); +static int port_role_transition_sm(struct rstp_port *); +static int port_state_transition_sm(struct rstp_port *); +static int topology_change_sm(struct rstp_port *); +/* port_timers_sm() not defined as a state machine */ + +void +process_received_bpdu(struct rstp_port *p, const void *bpdu, size_t bpdu_size) +{ + struct rstp *rstp = p->rstp; + + if (!p->port_enabled) + return; + if (p->rcvd_bpdu) + return; + + if (validate_received_bpdu(p, bpdu, bpdu_size) == 0) { + p->rcvd_bpdu = true; + p->rx_rstp_bpdu_cnt++; + + memcpy(&p->received_bpdu_buffer, bpdu, sizeof(struct rstp_bpdu)); + + rstp->changes = true; + move_rstp(rstp); + } else { + VLOG_DBG("Bad BPDU received"); + p->error_count++; + } +} + +static int +validate_received_bpdu(struct rstp_port *p, const void *bpdu, size_t bpdu_size) +{ + /* Validation of received BPDU, see [9.3.4]. */ + const struct rstp_bpdu *temp; + + temp = bpdu; + if (bpdu_size < TOPOLOGY_CHANGE_NOTIFICATION_BPDU_SIZE || + ntohs(temp->protocol_identifier) != 0) { + return -1; + } else { + if (temp->bpdu_type == CONFIGURATION_BPDU && + bpdu_size >= CONFIGURATION_BPDU_SIZE && + (time_decode(temp->message_age) < time_decode(temp->max_age))) + { + if ((ntohll(temp->designated_bridge_id) != + p->rstp->bridge_identifier) || + ((ntohll(temp->designated_bridge_id) == + p->rstp->bridge_identifier) && + (ntohs(temp->designated_port_id) != p->port_id))) { + return 0; + } + else { + return -1; + } + } else if (temp->bpdu_type == TOPOLOGY_CHANGE_NOTIFICATION_BPDU) { + return 0; + } else if (temp->bpdu_type == RAPID_SPANNING_TREE_BPDU && + bpdu_size >= RAPID_SPANNING_TREE_BPDU_SIZE) { + return 0; + } + else { + return -1; + } + } +} + +/* + * move_rstp() + * This method is invoked to move the State Machines. The SMs move only if the + * boolean 'changes' is true, meaning that something changed and the SMs need + * to work to process this change. + * The boolean 'changes' is set every time a SM modifies its state, a BPDU is + * received, a timer expires or port down event is detected. If a parameter is + * set by management, then 'changes' is set. + */ +#define MAX_RSTP_ITERATIONS 1000 /* safeguard */ +int +move_rstp(struct rstp *rstp) +{ + struct rstp_port *p; + int num_iterations; + num_iterations = 0; + + while (rstp->changes == true && num_iterations < MAX_RSTP_ITERATIONS) { + VLOG_DBG("%s: move_rstp()", rstp->name); + rstp->changes = false; + port_role_selection_sm(rstp); + if (rstp->ports_count > 0) { + LIST_FOR_EACH (p, node, &rstp->ports) { + if (p->rstp_state != RSTP_DISABLED) { + port_receive_sm(p); + bridge_detection_sm(p); + port_information_sm(p); + port_role_transition_sm(p); + port_state_transition_sm(p); + topology_change_sm(p); + port_transmit_sm(p); + port_protocol_migration_sm(p); + } + } + } + num_iterations++; + seq_change(connectivity_seq_get()); + } + if (num_iterations >= MAX_RSTP_ITERATIONS) { + VLOG_ERR("%s: move_rstp() reached the iteration safeguard limit!", + rstp->name); + } + return 0; +} + +void decrease_rstp_port_timers(struct rstp *r) +{ + struct rstp_port *p; + + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + decrement_timer(&p->hello_when); + decrement_timer(&p->tc_while); + decrement_timer(&p->fd_while); + decrement_timer(&p->rcvd_info_while); + decrement_timer(&p->rr_while); + decrement_timer(&p->rb_while); + decrement_timer(&p->mdelay_while); + decrement_timer(&p->edge_delay_while); + decrement_timer(&p->tx_count); + p->uptime+=1; + } + } + r->changes = true; + move_rstp(r); +} + +static void +decrement_timer(uint16_t *timer) +{ + if (*timer != 0) { + *timer -= 1; + } +} + +/* Bridge State Machine. */ +/* [17.28] Port Role Selection state machine. */ + +static void +updt_role_disabled_tree(struct rstp *r) +{ + struct rstp_port *p; + + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + p->selected_role = ROLE_DISABLED; + } + } +} + +static void +clear_reselect_tree(struct rstp *r) +{ + struct rstp_port *p; + + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + p->reselect = false; + } + } +} + +void +updt_roles_tree(struct rstp *r) +{ + struct rstp_port *p; + int vsel; + struct rstp_priority_vector best_vector, candidate_vector; + + vsel = -1; + best_vector = r->bridge_priority; + /* Letter c1) */ + r->root_times = r->bridge_times; + /* Letters a) b) c) */ + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + uint32_t old_root_path_cost; + uint32_t root_path_cost; + if (p->info_is != INFO_IS_RECEIVED) { + continue; + } + /* [17.6] */ + candidate_vector = p->port_priority; + candidate_vector.bridge_port_id = p->port_id; + old_root_path_cost = candidate_vector.root_path_cost; + root_path_cost = old_root_path_cost + p->port_path_cost; + candidate_vector.root_path_cost = root_path_cost; + + if ((candidate_vector.designated_bridge_id & 0xffffffffffffULL) == + (r->bridge_priority.designated_bridge_id & 0xffffffffffffULL)) + { + break; + } + if (compare_rstp_priority_vector(&candidate_vector, &best_vector) + == SUPERIOR) { + best_vector = candidate_vector; + r->root_times = p->port_times; + r->root_times.message_age++; + vsel = p->port_number; + } + } + } + r->root_priority = best_vector; + r->root_port_id = best_vector.bridge_port_id; + VLOG_DBG("%s: new Root is "RSTP_ID_FMT"", r->name, + RSTP_ID_ARGS(r->root_priority.root_bridge_id)); + /* Letters d) e) */ + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + p->designated_priority_vector.root_bridge_id = + r->root_priority.root_bridge_id; + p->designated_priority_vector.root_path_cost = + r->root_priority.root_path_cost; + p->designated_priority_vector.designated_bridge_id = + r->bridge_identifier; + p->designated_priority_vector.designated_port_id = + p->port_id; + p->designated_times = r->root_times; + p->designated_times.hello_time = r->bridge_times.hello_time; + } + } + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + switch (p->info_is) { + case INFO_IS_DISABLED: + p->selected_role = ROLE_DISABLED; + break; + case INFO_IS_AGED: + p->updt_info = true; + p->selected_role = ROLE_DESIGNATED; + break; + case INFO_IS_MINE: + p->selected_role = ROLE_DESIGNATED; + if ((compare_rstp_priority_vector(&p->port_priority, + &p->designated_priority_vector) != SAME) || + !rstp_times_equal(&p->designated_times, &r->root_times)) { + p->updt_info = true; + } + break; + case INFO_IS_RECEIVED: + if (vsel == p->port_number) { /* Letter i) */ + p->selected_role = ROLE_ROOT; + p->updt_info = false; + } else if (compare_rstp_priority_vector( + &p->designated_priority_vector, &p->port_priority) + == INFERIOR) { + if (p->port_priority.designated_bridge_id != + r->bridge_identifier) { + p->selected_role = ROLE_ALTERNATE; + p->updt_info = false; + } else { + p->selected_role = ROLE_BACKUP; + p->updt_info = false; + } + } else { + p->selected_role = ROLE_DESIGNATED; + p->updt_info = true; + } + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + } + } + seq_change(connectivity_seq_get()); +} + +static void +set_selected_tree(struct rstp *r) +{ + struct rstp_port *p; + + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + if (p->reselect) { + return; + } + } + LIST_FOR_EACH (p, node, &r->ports) { + p->selected = true; + } + } +} + +static int +port_role_selection_sm(struct rstp *r) +{ + enum port_role_selection_state_machine old_state; + struct rstp_port *p; + + old_state = r->port_role_selection_sm_state; + + switch (r->port_role_selection_sm_state) { + case PORT_ROLE_SELECTION_SM_INIT: + if (r->begin) + r->port_role_selection_sm_state = + PORT_ROLE_SELECTION_SM_INIT_BRIDGE_EXEC; + break; + case PORT_ROLE_SELECTION_SM_INIT_BRIDGE_EXEC: + updt_role_disabled_tree(r); + r->port_role_selection_sm_state = PORT_ROLE_SELECTION_SM_INIT_BRIDGE; + /* no break */ + case PORT_ROLE_SELECTION_SM_INIT_BRIDGE: + r->port_role_selection_sm_state = + PORT_ROLE_SELECTION_SM_ROLE_SELECTION_EXEC; + break; + case PORT_ROLE_SELECTION_SM_ROLE_SELECTION_EXEC: + clear_reselect_tree(r); + updt_roles_tree(r); + set_selected_tree(r); + r->port_role_selection_sm_state = + PORT_ROLE_SELECTION_SM_ROLE_SELECTION; + /* no break */ + case PORT_ROLE_SELECTION_SM_ROLE_SELECTION: + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + if (p->reselect) { + r->port_role_selection_sm_state = + PORT_ROLE_SELECTION_SM_ROLE_SELECTION_EXEC; + break; + } + } + } + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != r->port_role_selection_sm_state) { + r->changes = true; + VLOG_DBG("Port_role_selection_sm %d -> %d", old_state, + r->port_role_selection_sm_state); + } + return 0; +} + +/* Port State Machines */ + +/* [17.23 - Port receive state machine] */ + +static void +updt_bpdu_version(struct rstp_port *p) /* [17.21.22] */ +{ + switch (p->received_bpdu_buffer.bpdu_type) { + case CONFIGURATION_BPDU: + case TOPOLOGY_CHANGE_NOTIFICATION_BPDU: + p->rcvd_rstp = false; + p->rcvd_stp = true; + break; + case RAPID_SPANNING_TREE_BPDU: + p->rcvd_rstp = true; + p->rcvd_stp = false; + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } +} + +static int +port_receive_sm(struct rstp_port *p) +{ + enum port_receive_state_machine old_state; + struct rstp *r; + + old_state = p->port_receive_sm_state; + r = p->rstp; + + switch (p->port_receive_sm_state) { + case PORT_RECEIVE_SM_INIT: + if (r->begin || ((p->rcvd_bpdu || (p->edge_delay_while != + r->migrate_time)) && !p->port_enabled)) { + p->port_receive_sm_state = PORT_RECEIVE_SM_DISCARD_EXEC; + } + break; + case PORT_RECEIVE_SM_DISCARD_EXEC: + p->rcvd_bpdu = p->rcvd_rstp = p->rcvd_stp = false; + p->rcvd_msg = false; + p->edge_delay_while = r->migrate_time; + p->port_receive_sm_state = PORT_RECEIVE_SM_DISCARD; + /* no break */ + case PORT_RECEIVE_SM_DISCARD: + if (p->rcvd_bpdu && p->port_enabled) { + p->port_receive_sm_state = PORT_RECEIVE_SM_RECEIVE_EXEC; + } + break; + case PORT_RECEIVE_SM_RECEIVE_EXEC: + updt_bpdu_version(p); + p->oper_edge = p->rcvd_bpdu = false; + p->rcvd_msg = true; + p->edge_delay_while = r->migrate_time; + p->port_receive_sm_state = PORT_RECEIVE_SM_RECEIVE; + /* no break */ + case PORT_RECEIVE_SM_RECEIVE: + if (p->rcvd_bpdu && p->port_enabled && !p->rcvd_msg) { + p->port_receive_sm_state = PORT_RECEIVE_SM_RECEIVE_EXEC; + } + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->port_receive_sm_state) { + r->changes = true; + VLOG_DBG("%s, port %u: Port_receive_sm %d -> %d", p->rstp->name, + p->port_number, old_state, p->port_receive_sm_state); + } + return 0; +} + +/* [17.24 - Port Protocol Migration state machine] */ +static int +port_protocol_migration_sm(struct rstp_port *p) +{ + enum port_protocol_migration_state_machine old_state; + struct rstp *r; + + old_state = p->port_protocol_migration_sm_state; + r = p->rstp; + + switch (p->port_protocol_migration_sm_state) { + case PORT_PROTOCOL_MIGRATION_SM_INIT: + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP_EXEC; + /* no break */ + case PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP_EXEC: + p->mcheck = false; + p->send_rstp = r->rstp_version; + p->mdelay_while = r->migrate_time; + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP; + /* no break */ + case PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP: + if (p->mdelay_while == 0) { + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_SENSING_EXEC; + } + else if ((p->mdelay_while != r->migrate_time) && !p->port_enabled) { + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP_EXEC; + } + break; + case PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP_EXEC: + p->send_rstp = false; + p->mdelay_while = r->migrate_time; + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP; + /* no break */ + case PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP: + if ((p->mdelay_while == 0) || (!p->port_enabled) || p->mcheck) { + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_SENSING_EXEC; + } + break; + case PORT_PROTOCOL_MIGRATION_SM_SENSING_EXEC: + p->rcvd_rstp = false; + p->rcvd_stp = false; + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_SENSING; + /* no break */ + case PORT_PROTOCOL_MIGRATION_SM_SENSING: + if (!p->port_enabled || p->mcheck || ((r->rstp_version) && + !p->send_rstp && p->rcvd_rstp)) { + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP_EXEC; + } + else if (p->send_rstp && p->rcvd_stp) { + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP_EXEC; + } + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->port_protocol_migration_sm_state) { + r->changes = true; + VLOG_DBG("%s, port %u: port_protocol_migration_sm %d -> %d", + p->rstp->name, p->port_number, old_state, + p->port_protocol_migration_sm_state); + } + + return 0; +} + +/* [17.25 - Bridge Detection state machine] */ +static int +bridge_detection_sm(struct rstp_port *p) +{ + enum bridge_detection_state_machine old_state; + struct rstp *r; + + old_state = p->bridge_detection_sm_state; + r = p->rstp; + + switch (p->bridge_detection_sm_state) { + case BRIDGE_DETECTION_SM_INIT: + if (r->begin && p->admin_edge) { + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_EDGE_EXEC; + } else if (r->begin && !p->admin_edge) { + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_NOT_EDGE_EXEC; + } + break; + case BRIDGE_DETECTION_SM_EDGE_EXEC: + p->oper_edge = true; + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_EDGE; + /* no break */ + case BRIDGE_DETECTION_SM_EDGE: + if ((!p->port_enabled && !p->admin_edge) || !p->oper_edge) { + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_NOT_EDGE_EXEC; + } + break; + case BRIDGE_DETECTION_SM_NOT_EDGE_EXEC: + p->oper_edge = false; + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_NOT_EDGE; + /* no break */ + case BRIDGE_DETECTION_SM_NOT_EDGE: + if ((!p->port_enabled && p->admin_edge) || ((p->edge_delay_while == 0) + && p->auto_edge && p->send_rstp && p->proposing)) { + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_EDGE_EXEC; + } + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->bridge_detection_sm_state) { + r->changes = true; + VLOG_DBG("%s, port %u: bridge_detection_sm %d -> %d", p->rstp->name, + p->port_number, old_state, p->bridge_detection_sm_state); + } + return 0; +} + +/* [17.26 - Port Transmit state machine] */ +static void +rstp_send_bpdu(struct rstp_port *p, const void *bpdu, size_t bpdu_size) +{ + struct eth_header *eth; + struct llc_header *llc; + struct ofpbuf *pkt; + + /* Skeleton. */ + pkt = ofpbuf_new(ETH_HEADER_LEN + LLC_HEADER_LEN + bpdu_size); + eth = ofpbuf_put_zeros(pkt, sizeof *eth); + llc = ofpbuf_put_zeros(pkt, sizeof *llc); + ofpbuf_set_frame(pkt, eth); + ofpbuf_set_l3(pkt, ofpbuf_put(pkt, bpdu, bpdu_size)); + + /* 802.2 header. */ + memcpy(eth->eth_dst, eth_addr_stp, ETH_ADDR_LEN); + /* p->rstp->send_bpdu() must fill in source address. */ + eth->eth_type = htons(ofpbuf_size(pkt) - ETH_HEADER_LEN); + + /* LLC header. */ + llc->llc_dsap = STP_LLC_DSAP; + llc->llc_ssap = STP_LLC_SSAP; + llc->llc_cntl = STP_LLC_CNTL; + p->rstp->send_bpdu(pkt, rstp_port_number(p), p->rstp->aux); +} + +static void +record_agreement(struct rstp_port *p) +{ + struct rstp *r; + + r = p->rstp; + if (r->rstp_version && p->oper_point_to_point_mac && + ((p->received_bpdu_buffer.flags & BPDU_FLAG_AGREEMENT))) { + p->agreed = true; + p->proposing = false; + } else { + p->agreed = false; + } +} + +static void +set_tc_flags(struct rstp_port *p) +{ + /* Sets rcvd_tc and/or rcvd_tc_ack if the Topology Change and/or Topology + * Change Acknowledgment flags, respectively, are set in a ConfigBPDU or + * RST BPDU. + */ + if (p->received_bpdu_buffer.bpdu_type == CONFIGURATION_BPDU || + p->received_bpdu_buffer.bpdu_type == RAPID_SPANNING_TREE_BPDU) { + if ((p->received_bpdu_buffer.flags & BPDU_FLAG_TOPCHANGE) != 0) { + p->rcvd_tc = true; + } + if ((p->received_bpdu_buffer.flags & BPDU_FLAG_TOPCHANGEACK) != 0) { + p->rcvd_tc_ack = true; + } + } + /* Sets rcvd_tcn true if the BPDU is a TCN BPDU. */ + if (p->received_bpdu_buffer.bpdu_type == + TOPOLOGY_CHANGE_NOTIFICATION_BPDU) { + p->rcvd_tcn = true; + } +} + +static void +record_dispute(struct rstp_port *p) +{ + if ((p->received_bpdu_buffer.flags & BPDU_FLAG_LEARNING) != 0) { + p->agreed = true; + p->proposing = false; + } +} + +static void +record_proposal(struct rstp_port *p) +{ + enum port_flag role = + ((p->received_bpdu_buffer.flags) & ROLE_FLAG_MASK) >> ROLE_FLAG_SHIFT; + if ((role == PORT_DES) && + ((p->received_bpdu_buffer.flags & BPDU_FLAG_PROPOSAL) != 0)) { + p->proposed = true; + } +} + +static void +record_priority(struct rstp_port *p) +{ + p->port_priority.root_bridge_id = p->msg_priority.root_bridge_id; + p->port_priority.root_path_cost = p->msg_priority.root_path_cost; + p->port_priority.designated_bridge_id = + p->msg_priority.designated_bridge_id; + p->port_priority.designated_port_id = p->msg_priority.designated_port_id; +} + +static void +record_times(struct rstp_port *p) +{ + p->port_times = p->msg_times; + if (p->msg_times.hello_time == 0) { + p->port_times.hello_time = 1; + } +} + +static void +updt_rcvd_info_while(struct rstp_port *p) +{ + /* [17.21.23] + * The value assigned to rcvdInfoWhile is the three times the Hello Time, + * if Message Age, incremented by 1 second and rounded to the nearest whole + * second, does not exceed Max Age, and is zero otherwise. + */ + if (p->port_times.message_age < p->port_times.max_age) { + p->rcvd_info_while = p->port_times.hello_time * 3; + } else { + p->rcvd_info_while = 0; + } +} + +/* Times are internally held in seconds, while the protocol uses 1/256 seconds. + * time_encode() is used to convert time values sent in bpdus, while + * time_decode() is used to convert time values received in bpdus. + */ +static ovs_be16 +time_encode(uint8_t value) +{ + return htons(value * 256); +} + +static uint8_t +time_decode(ovs_be16 encoded) +{ + return ntohs(encoded) / 256; +} + +/* [17.21.19] */ +static void +tx_config(struct rstp_port *p) +{ + struct rstp_bpdu bpdu; + + bpdu.protocol_identifier = htons(0); + bpdu.protocol_version_identifier = 0; + bpdu.bpdu_type = CONFIGURATION_BPDU; + bpdu.root_bridge_id = htonll(p->designated_priority_vector.root_bridge_id); + bpdu.root_path_cost = htonl(p->designated_priority_vector.root_path_cost); + bpdu.designated_bridge_id = + htonll(p->designated_priority_vector.designated_bridge_id); + bpdu.designated_port_id = + htons(p->designated_priority_vector.designated_port_id); + bpdu.message_age = time_encode(p->designated_times.message_age); + bpdu.max_age = time_encode(p->designated_times.max_age); + bpdu.hello_time = time_encode(p->designated_times.hello_time); + bpdu.forward_delay = time_encode(p->designated_times.forward_delay); + bpdu.flags = 0; + if (p->tc_while != 0) { + bpdu.flags |= BPDU_FLAG_TOPCHANGE; + } + if (p->tc_ack != 0) { + bpdu.flags |= BPDU_FLAG_TOPCHANGEACK; + } + rstp_send_bpdu(p, &bpdu, sizeof(struct rstp_bpdu)); +} + +/* [17.21.20] */ +static void +tx_rstp(struct rstp_port *p) +{ + struct rstp_bpdu bpdu; + + bpdu.protocol_identifier = htons(0); + bpdu.protocol_version_identifier = 2; + bpdu.bpdu_type = RAPID_SPANNING_TREE_BPDU; + bpdu.root_bridge_id = htonll(p->designated_priority_vector.root_bridge_id); + bpdu.root_path_cost = htonl(p->designated_priority_vector.root_path_cost); + bpdu.designated_bridge_id = + htonll(p->designated_priority_vector.designated_bridge_id); + bpdu.designated_port_id = + htons(p->designated_priority_vector.designated_port_id); + bpdu.message_age = time_encode(p->designated_times.message_age); + bpdu.max_age = time_encode(p->designated_times.max_age); + bpdu.hello_time = time_encode(p->designated_times.hello_time); + bpdu.forward_delay = time_encode(p->designated_times.forward_delay); + bpdu.flags = 0; + switch (p->role) { + case ROLE_ROOT: + bpdu.flags = PORT_ROOT << ROLE_FLAG_SHIFT; + break; + case ROLE_DESIGNATED: + bpdu.flags = PORT_DES << ROLE_FLAG_SHIFT; + break; + case ROLE_ALTERNATE: + case ROLE_BACKUP: + bpdu.flags = PORT_ALT_BACK << ROLE_FLAG_SHIFT; + break; + case ROLE_DISABLED: + /* should not happen! */ + VLOG_ERR("%s transmitting bpdu in disabled role on port " + ""RSTP_PORT_ID_FMT"", p->rstp->name, p->port_id); + OVS_NOT_REACHED(); + break; + } + if (p->agree) { + bpdu.flags |= BPDU_FLAG_AGREEMENT; + } + if (p->proposing) { + bpdu.flags |= BPDU_FLAG_PROPOSAL; + } + if (p->tc_while != 0) { + bpdu.flags |= BPDU_FLAG_TOPCHANGE; + } + if (p->learning) { + bpdu.flags |= BPDU_FLAG_LEARNING; + } + if (p->forwarding) { + bpdu.flags |= BPDU_FLAG_FORWARDING; + } + bpdu.version1_length = 0; + rstp_send_bpdu(p, &bpdu, sizeof(struct rstp_bpdu)); +} + +/* [17.21.21] */ +static void +tx_tcn(struct rstp_port *p) +{ + struct rstp_bpdu bpdu; + + memset(&bpdu, 0, sizeof(struct rstp_bpdu)); + + bpdu.protocol_identifier = htons(0); + bpdu.protocol_version_identifier = 0; + bpdu.bpdu_type = TOPOLOGY_CHANGE_NOTIFICATION_BPDU; + rstp_send_bpdu(p, &bpdu, sizeof(struct rstp_bpdu)); +} + +static int +port_transmit_sm(struct rstp_port *p) +{ + enum port_transmit_state_machine old_state; + struct rstp *r; + + old_state = p->port_transmit_sm_state; + r = p->rstp; + + switch (p->port_transmit_sm_state) { + case PORT_TRANSMIT_SM_INIT: + if (r->begin) { + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_INIT_EXEC; + } + break; + case PORT_TRANSMIT_SM_TRANSMIT_INIT_EXEC: + p->new_info = true; + p->tx_count = 0; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_INIT; + /* no break */ + case PORT_TRANSMIT_SM_TRANSMIT_INIT: + p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC; + break; + case PORT_TRANSMIT_SM_TRANSMIT_PERIODIC_EXEC: + p->new_info = p->new_info || (p->role == ROLE_DESIGNATED || + (p->role == ROLE_ROOT && p->tc_while != 0)); + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_PERIODIC; + /* no break */ + case PORT_TRANSMIT_SM_TRANSMIT_PERIODIC: + p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC; + break; + case PORT_TRANSMIT_SM_IDLE_EXEC: + p->hello_when = r->bridge_hello_time; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE; + /* no break */ + case PORT_TRANSMIT_SM_IDLE: + if (p->role == ROLE_DISABLED) { + break; + } + else if (p->send_rstp && p->new_info && + (p->tx_count < r->transmit_hold_count) && (p->hello_when != 0) + && p->selected && !p->updt_info) { + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_RSTP_EXEC; + } + else if (p->hello_when == 0 && p->selected && !p->updt_info) { + p->port_transmit_sm_state = + PORT_TRANSMIT_SM_TRANSMIT_PERIODIC_EXEC; + } + else if (!p->send_rstp && p->new_info && (p->role == ROLE_ROOT) && + (p->tx_count < r->transmit_hold_count) && (p->hello_when != 0) + && p->selected && !p->updt_info) { + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_TCN_EXEC; + } + else if (!p->send_rstp && p->new_info && (p->role == ROLE_DESIGNATED) + && (p->tx_count < r->transmit_hold_count) && + (p->hello_when != 0) && p->selected && !p->updt_info) { + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_CONFIG_EXEC; + } + break; + case PORT_TRANSMIT_SM_TRANSMIT_CONFIG_EXEC: + p->new_info = false; + tx_config(p); + p->tx_count += 1; + p->tc_ack = false; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_CONFIG; + /* no break */ + case PORT_TRANSMIT_SM_TRANSMIT_CONFIG: + p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC; + break; + case PORT_TRANSMIT_SM_TRANSMIT_TCN_EXEC: + p->new_info = false; + tx_tcn(p); + p->tx_count += 1; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_TCN; + /* no break */ + case PORT_TRANSMIT_SM_TRANSMIT_TCN: + p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC; + break; + case PORT_TRANSMIT_SM_TRANSMIT_RSTP_EXEC: + p->new_info = false; + tx_rstp(p); + p->tx_count += 1; + p->tc_ack = false; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_RSTP; + /* no break */ + case PORT_TRANSMIT_SM_TRANSMIT_RSTP: + p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC; + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->port_transmit_sm_state) { + r->changes = true; + VLOG_DBG("%s, port %u: port_transmit_sm %d -> %d", p->rstp->name, + p->port_number, old_state, p->port_transmit_sm_state); + } + return 0; +} + +/* [17.27 Port Information state machine] */ +#define RECEIVED 0 +#define MINE 1 + +static int +rcv_info(struct rstp_port *p) +{ + enum vector_comparison cp; + bool ct; + enum port_flag role; + + p->msg_priority.root_bridge_id = + ntohll(p->received_bpdu_buffer.root_bridge_id); + p->msg_priority.root_path_cost = + ntohl(p->received_bpdu_buffer.root_path_cost); + p->msg_priority.designated_bridge_id = + ntohll(p->received_bpdu_buffer.designated_bridge_id); + p->msg_priority.designated_port_id = + ntohs(p->received_bpdu_buffer.designated_port_id); + + p->msg_times.forward_delay = + time_decode(p->received_bpdu_buffer.forward_delay); + p->msg_times.hello_time = time_decode(p->received_bpdu_buffer.hello_time); + p->msg_times.max_age = time_decode(p->received_bpdu_buffer.max_age); + p->msg_times.message_age = + time_decode(p->received_bpdu_buffer.message_age); + + cp = compare_rstp_priority_vector(&p->msg_priority, &p->port_priority); + ct = rstp_times_equal(&p->port_times, &p->msg_times); + role = + ((p->received_bpdu_buffer.flags) & ROLE_FLAG_MASK) >> ROLE_FLAG_SHIFT; + + /* Returns SuperiorDesignatedInfo if: + * a) The received message conveys a Designated Port Role, and + * 1) The message priority is superior (17.6) to the Port.s port priority + * vector, or + * 2) The message priority vector is the same as the Port.s port priority + * vector, and any of the received timer parameter values (msg_times. + * 17.19.15) differ from those already held for the Port (port_times + * 17.19.22). + * NOTE: Configuration BPDU explicitly conveys a Designated Port Role. + */ + if ((role == PORT_DES || + p->received_bpdu_buffer.bpdu_type == CONFIGURATION_BPDU) && + ((cp == SUPERIOR) || ((cp == SAME) && ct == false))) { + return SUPERIOR_DESIGNATED_INFO; + } + + /* Returns RepeatedDesignatedInfo if: + * b) The received message conveys Designated Port Role, and a message + * priority vector and timer parameters that are the same as the Port's + * port priority vector or timer values. + */ + else if ((role == PORT_DES) && (cp == SAME) && (ct == true)) { + return REPEATED_DESIGNATED_INFO; + } + + /* Returns InferiorDesignatedInfo if: + * c) The received message conveys a Designated Port Role, and a message + * priority vector that is worse than the Port's port priority vector. + */ + else if ((role == PORT_DES) && (cp == INFERIOR)) { + return INFERIOR_DESIGNATED_INFO; + } + + /* Returns InferiorRootAlternateInfo if: + * d) The received message conveys a Root Port, Alternate Port, or Backup + * Port Role and a message priority that is the same as or worse than + * the port priority vector. + */ + else if ((role == PORT_ROOT || role == PORT_ALT_BACK) && + (cp == INFERIOR || cp == SAME)) { + return INFERIOR_ROOT_ALTERNATE_INFO; + } + + /* Otherwise, returns OtherInfo. */ + else { + return OTHER_INFO; + } +} + +static int +better_or_same_info(struct rstp_port *p, int new_info_is) +{ + /* >= SUPERIOR means that the vector is better or the same. */ + return ((new_info_is == RECEIVED && p->info_is == INFO_IS_RECEIVED && + compare_rstp_priority_vector(&p->msg_priority, + &p->port_priority) >= SUPERIOR) || + (new_info_is == MINE && p->info_is == INFO_IS_MINE && + compare_rstp_priority_vector(&p->designated_priority_vector, + &p->port_priority) >= SUPERIOR)); +} + +static int +port_information_sm(struct rstp_port *p) +{ + enum port_information_state_machine old_state; + struct rstp *r; + + old_state = p->port_information_sm_state; + r = p->rstp; + + if (!p->port_enabled && (p->info_is != INFO_IS_DISABLED)) { + p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC; + } + switch (p->port_information_sm_state) { + case PORT_INFORMATION_SM_INIT: + if (r->begin) { + p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC; + } + break; + case PORT_INFORMATION_SM_DISABLED_EXEC: + p->rcvd_msg = false; + p->proposing = p->proposed = p->agree = p->agreed = false; + p->rcvd_info_while = 0; + p->info_is = INFO_IS_DISABLED; + p->reselect = true; + p->selected = false; + p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED; + /* no break */ + case PORT_INFORMATION_SM_DISABLED: + if (p->port_enabled) { + p->port_information_sm_state = PORT_INFORMATION_SM_AGED_EXEC; + } + else if (p->rcvd_msg) { + p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC; + } + break; + case PORT_INFORMATION_SM_AGED_EXEC: + p->info_is = INFO_IS_AGED; + p->reselect = true; + p->selected = false; + p->port_information_sm_state = PORT_INFORMATION_SM_AGED; + /* no break */ + case PORT_INFORMATION_SM_AGED: + if (p->selected && p->updt_info) { + p->port_information_sm_state = PORT_INFORMATION_SM_UPDATE_EXEC; + } + break; + case PORT_INFORMATION_SM_UPDATE_EXEC: + p->proposing = p->proposed = false; + /* MINE is not specified in Standard 802.1D-2004. */ + p->agreed = p->agreed && better_or_same_info(p, MINE); + p->synced = p->synced && p->agreed; + p->port_priority.root_bridge_id = + p->designated_priority_vector.root_bridge_id; + p->port_priority.root_path_cost = + p->designated_priority_vector.root_path_cost; + p->port_priority.designated_bridge_id = + p->designated_priority_vector.designated_bridge_id; + p->port_priority.designated_port_id = + p->designated_priority_vector.designated_port_id; + p->port_times = p->designated_times; + p->updt_info = false; + p->info_is = INFO_IS_MINE; + p->new_info = true; + p->port_information_sm_state = PORT_INFORMATION_SM_UPDATE; + /* no break */ + case PORT_INFORMATION_SM_UPDATE: + p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC; + break; + case PORT_INFORMATION_SM_CURRENT_EXEC: + p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT; + /* no break */ + case PORT_INFORMATION_SM_CURRENT: + if (p->rcvd_msg && !p->updt_info) { + p->port_information_sm_state = PORT_INFORMATION_SM_RECEIVE_EXEC; + } else if (p->selected && p->updt_info) { + p->port_information_sm_state = PORT_INFORMATION_SM_UPDATE_EXEC; + } else if ((p->info_is == INFO_IS_RECEIVED) && + (p->rcvd_info_while == 0) && !p->updt_info && + !p->rcvd_msg) { + p->port_information_sm_state = PORT_INFORMATION_SM_AGED_EXEC; + } + break; + case PORT_INFORMATION_SM_RECEIVE_EXEC: + p->rcvd_info = rcv_info(p); + p->port_information_sm_state = PORT_INFORMATION_SM_RECEIVE; + /* no break */ + case PORT_INFORMATION_SM_RECEIVE: + switch (p->rcvd_info) { + case SUPERIOR_DESIGNATED_INFO: + p->port_information_sm_state = + PORT_INFORMATION_SM_SUPERIOR_DESIGNATED_EXEC; + break; + case REPEATED_DESIGNATED_INFO: + p->port_information_sm_state = + PORT_INFORMATION_SM_REPEATED_DESIGNATED_EXEC; + break; + case INFERIOR_DESIGNATED_INFO: + p->port_information_sm_state = + PORT_INFORMATION_SM_INFERIOR_DESIGNATED_EXEC; + break; + case INFERIOR_ROOT_ALTERNATE_INFO: + p->port_information_sm_state = + PORT_INFORMATION_SM_NOT_DESIGNATED_EXEC; + break; + case OTHER_INFO: + p->port_information_sm_state = PORT_INFORMATION_SM_OTHER_EXEC; + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + break; + case PORT_INFORMATION_SM_OTHER_EXEC: + p->rcvd_msg = false; + p->port_information_sm_state = PORT_INFORMATION_SM_OTHER; + /* no break */ + case PORT_INFORMATION_SM_OTHER: + p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC; + break; + case PORT_INFORMATION_SM_NOT_DESIGNATED_EXEC: + record_agreement(p); + set_tc_flags(p); + p->rcvd_msg = false; + p->port_information_sm_state = PORT_INFORMATION_SM_NOT_DESIGNATED; + /* no break */ + case PORT_INFORMATION_SM_NOT_DESIGNATED: + p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC; + break; + case PORT_INFORMATION_SM_INFERIOR_DESIGNATED_EXEC: + record_dispute(p); + p->rcvd_msg = false; + p->port_information_sm_state = PORT_INFORMATION_SM_INFERIOR_DESIGNATED; + /* no break */ + case PORT_INFORMATION_SM_INFERIOR_DESIGNATED: + p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC; + break; + case PORT_INFORMATION_SM_REPEATED_DESIGNATED_EXEC: + record_proposal(p); + set_tc_flags(p); + updt_rcvd_info_while(p); + p->rcvd_msg = false; + p->port_information_sm_state = PORT_INFORMATION_SM_REPEATED_DESIGNATED; + /* no break */ + case PORT_INFORMATION_SM_REPEATED_DESIGNATED: + p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC; + break; + case PORT_INFORMATION_SM_SUPERIOR_DESIGNATED_EXEC: + p->agreed = p->proposing = false; + record_proposal(p); + set_tc_flags(p); + /* RECEIVED is not specified in Standard 802.1D-2004. */ + p->agree = p->agree && better_or_same_info(p, RECEIVED); + record_priority(p); + record_times(p); + updt_rcvd_info_while(p); + p->info_is = INFO_IS_RECEIVED; + p->reselect = true; + p->selected = false; + p->rcvd_msg = false; + p->port_information_sm_state = PORT_INFORMATION_SM_SUPERIOR_DESIGNATED; + /* no break */ + case PORT_INFORMATION_SM_SUPERIOR_DESIGNATED: + p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC; + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->port_information_sm_state) { + r->changes = true; + VLOG_DBG("Port_information_sm %d -> %d", old_state, + p->port_information_sm_state); + } + return 0; +} + +/* [17.29 Port Role Transitions state machine] */ + +static void +set_re_root_tree(struct rstp_port *p) +{ + struct rstp *r; + struct rstp_port *p1; + + r = p->rstp; + if (r->ports_count > 0) { + LIST_FOR_EACH (p1, node, &r->ports) { + p1->re_root = true; + } + } +} + +static void +set_sync_tree(struct rstp_port *p) +{ + struct rstp *r; + struct rstp_port *p1; + + r = p->rstp; + if (r->ports_count > 0) { + LIST_FOR_EACH (p1, node, &r->ports) { + p1->sync = true; + } + } +} + +static int +hello_time(struct rstp_port *p) +{ + return p->designated_times.hello_time; +} + +static int +fwd_delay(struct rstp_port *p) +{ + return p->designated_times.forward_delay; +} + +static int +forward_delay(struct rstp_port *p) +{ + if (p->send_rstp) { + return hello_time(p); + } else { + return fwd_delay(p); + } +} + +static int +edge_delay(struct rstp_port *p) +{ + struct rstp *r; + + r = p->rstp; + if (p->oper_point_to_point_mac == 1) { + return r->migrate_time; + } else { + return p->designated_times.max_age; + } +} + +static int +check_selected_role_change(struct rstp_port *p, int current_role_state) +{ + if (p->selected && !p->updt_info && (p->role != p->selected_role) && + (p->selected_role != current_role_state)) { + VLOG_DBG("%s, port %u: case: current = %s role = %s selected = %d", + p->rstp->name, p->port_number, + rstp_port_role_name(current_role_state), + rstp_port_role_name(p->role), p->selected_role); + switch (p->selected_role) { + case ROLE_ROOT: + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + return true; + case ROLE_DESIGNATED: + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC; + return true; + case ROLE_ALTERNATE: + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_BLOCK_PORT_EXEC; + return true; + case ROLE_BACKUP: + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_BLOCK_PORT_EXEC; + return true; + case ROLE_DISABLED: + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DISABLE_PORT_EXEC; + return true; + } + } + return false; +} + +static int +re_rooted(struct rstp_port *p) +{ + struct rstp *r; + struct rstp_port *p1; + + r = p->rstp; + if (r->ports_count > 0) { + LIST_FOR_EACH (p1, node, &r->ports) { + if ((p1 != p) && (p1->rr_while != 0)) { + return false; + } + } + } + return true; +} + +static int +all_synced(struct rstp *r) +{ + struct rstp_port *p; + + if (r->ports_count > 0) { + LIST_FOR_EACH (p, node, &r->ports) { + if (!(p->selected && p->role == p->selected_role && + (p->role == ROLE_ROOT || p->synced == true))) { + return false; + } + } + } + return true; +} + +static int +port_role_transition_sm(struct rstp_port *p) +{ + enum port_role_transition_state_machine old_state; + struct rstp *r; + enum rstp_port_role last_role; + + old_state = p->port_role_transition_sm_state; + r = p->rstp; + last_role = p->role; + + switch (p->port_role_transition_sm_state) { + case PORT_ROLE_TRANSITION_SM_INIT: + if (r->begin) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_INIT_PORT_EXEC; + } + break; + case PORT_ROLE_TRANSITION_SM_INIT_PORT_EXEC: + p->role = ROLE_DISABLED; + p->learn = p->forward = false; + p->synced = false; + p->sync = p->re_root = true; + p->rr_while = p->designated_times.forward_delay; + p->fd_while = p->designated_times.max_age; + p->rb_while = 0; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DISABLE_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_DISABLE_PORT_EXEC: + p->role = p->selected_role; + p->learn = p->forward = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DISABLE_PORT; + /* no break */ + case PORT_ROLE_TRANSITION_SM_DISABLE_PORT: + if (check_selected_role_change(p, ROLE_DISABLED)) { + break; + } + else if (p->selected && !p->updt_info && !p->learning && + !p->forwarding) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DISABLED_PORT_EXEC; + } + break; + case PORT_ROLE_TRANSITION_SM_DISABLED_PORT_EXEC: + p->fd_while = p->designated_times.max_age; + p->synced = true; + p->rr_while = 0; + p->sync = p->re_root = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DISABLED_PORT; + /* no break */ + case PORT_ROLE_TRANSITION_SM_DISABLED_PORT: + if (check_selected_role_change(p, ROLE_DISABLED)) { + break; + } + else if (p->selected && !p->updt_info && + ((p->fd_while != p->designated_times.max_age) || p->sync || + p->re_root || !p->synced)) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DISABLED_PORT_EXEC; + } + break; + case PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC: + p->role = ROLE_ROOT; + p->rr_while = p->designated_times.forward_delay; + p->port_role_transition_sm_state = PORT_ROLE_TRANSITION_SM_ROOT_PORT; + /* no break */ + case PORT_ROLE_TRANSITION_SM_ROOT_PORT: + if (check_selected_role_change(p, ROLE_ROOT)) { + break; + } + else if (p->selected && !p->updt_info) { + if (p->rr_while != p->designated_times.forward_delay) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + break; + } + else if (p->re_root && p->forward) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_REROOTED_EXEC; + break; + } + else if (((p->fd_while == 0) || ((re_rooted(p) && + (p->rb_while == 0)) && + (r->rstp_version))) && !p->learn) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_LEARN_EXEC; + break; + } + else if (((p->fd_while == 0) || ((re_rooted(p) && + (p->rb_while == 0)) && + (r->rstp_version))) && p->learn && !p->forward) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_FORWARD_EXEC; + break; + } + else if (p->proposed && !p->agree) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PROPOSED_EXEC; + break; + } + else if ((all_synced(r) && !p->agree) || + (p->proposed && p->agree)) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_AGREED_EXEC; + break; + } + else if (!p->forward && !p->re_root) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_REROOT_EXEC; + break; + } + } + break; + case PORT_ROLE_TRANSITION_SM_REROOT_EXEC: + set_re_root_tree(p); + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_ROOT_AGREED_EXEC: + p->proposed = p->sync = false; + p->agree = p->new_info = true; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_ROOT_PROPOSED_EXEC: + set_sync_tree(p); + p->proposed = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_ROOT_FORWARD_EXEC: + p->fd_while = 0; + p->forward = true; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_ROOT_LEARN_EXEC: + p->fd_while = forward_delay(p); + p->learn = true; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_REROOTED_EXEC: + p->re_root = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ROOT_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC: + p->role = ROLE_DESIGNATED; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT; + /* no break */ + case PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT: + if (check_selected_role_change(p, ROLE_DESIGNATED)) { + break; + } + else if (p->selected && !p->updt_info) { + if (((p->sync && !p->synced) || (p->re_root && + (p->rr_while != 0)) || p->disputed) && + !p->oper_edge && (p->learn || p->forward)) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_DISCARD_EXEC; + } + else if (((p->fd_while == 0)|| p->agreed || p->oper_edge) && + ((p->rr_while == 0) || !p->re_root) && + !p->sync && !p->learn) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_LEARN_EXEC; + } + else if (((p->fd_while == 0) || p->agreed || p->oper_edge) && + ((p->rr_while == 0) || !p->re_root) && + !p->sync && (p->learn && !p->forward)) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_FORWARD_EXEC; + } + else if (!p->forward && !p->agreed && !p->proposing && + !p->oper_edge) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PROPOSE_EXEC; + } + else if ((!p->learning && !p->forwarding && !p->synced) || + (p->agreed && !p->synced) || (p->oper_edge && !p->synced) || + (p->sync && p->synced)) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_SYNCED_EXEC; + } + else if ((p->rr_while == 0) && p->re_root) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_RETIRED_EXEC; + } + } + break; + case PORT_ROLE_TRANSITION_SM_DESIGNATED_RETIRED_EXEC: + p->re_root = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_DESIGNATED_SYNCED_EXEC: + p->rr_while = 0; + p->synced = true; + p->sync = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_DESIGNATED_PROPOSE_EXEC: + p->proposing = true; + p->edge_delay_while = edge_delay(p); + p->new_info = true; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_DESIGNATED_FORWARD_EXEC: + p->forward = true; + p->fd_while = 0; + p->agreed = p->send_rstp; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_DESIGNATED_LEARN_EXEC: + p->learn = true; + p->fd_while = forward_delay(p); + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_DESIGNATED_DISCARD_EXEC: + p->learn = p->forward = p->disputed = false; + p->fd_while = forward_delay(p); + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC: + p->fd_while = p->designated_times.forward_delay; + p->synced = true; + p->rr_while = 0; + p->sync = p->re_root = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT; + /* no break */ + case PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT: + if (check_selected_role_change(p, ROLE_ALTERNATE)) { + break; + } + else if (p->selected && !p->updt_info) { + if ((p->rb_while != (2 * p->designated_times.hello_time)) && + (p->role == ROLE_BACKUP)) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_BACKUP_PORT_EXEC; + } + else if ((p->fd_while != forward_delay(p)) || p->sync || + p->re_root || !p->synced) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC; + } + else if (p->proposed && !p->agree) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_PROPOSED_EXEC; + } + else if (( all_synced(r) && !p->agree) || + (p->proposed && p->agree)) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_AGREED; + } + } + break; + case PORT_ROLE_TRANSITION_SM_ALTERNATE_AGREED: + p->proposed = false; + p->agree = true; + p->new_info = true; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_ALTERNATE_PROPOSED_EXEC: + set_sync_tree(p); + p->proposed = false; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC; + break; + case PORT_ROLE_TRANSITION_SM_BLOCK_PORT_EXEC: + p->role = p->selected_role; + p->learn = p->forward = false; + p->port_role_transition_sm_state = PORT_ROLE_TRANSITION_SM_BLOCK_PORT; + /* no break */ + case PORT_ROLE_TRANSITION_SM_BLOCK_PORT: + if (check_selected_role_change(p, ROLE_ALTERNATE)) { + break; + } + else if (p->selected && !p->updt_info && !p->learning && + !p->forwarding) { + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC; + } + break; + case PORT_ROLE_TRANSITION_SM_BACKUP_PORT_EXEC: + p->rb_while = 2 * p->designated_times.hello_time; + p->port_role_transition_sm_state = + PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC; + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->port_role_transition_sm_state) { + r->changes = true; + VLOG_DBG("%s, port %u: Port_role_transition_sm %d -> %d", + p->rstp->name, p->port_number, old_state, + p->port_role_transition_sm_state); + } + if (last_role != p->role) { + last_role = p->role; + VLOG_DBG("%s, port %u, port role ["RSTP_PORT_ID_FMT"] = %s", + p->rstp->name, p->port_number, p->port_id, + rstp_port_role_name(p->role)); + } + return 0; +} + +/* [17.30 - Port state transition state machine] */ + +static void +enable_learning(struct rstp_port *p) +{ + /* [17.21.6 enableLearning()] An implementation dependent procedure that + * causes the Learning Process (7.8) to start learning from frames received + * on the Port. The procedure does not complete until learning has been + * enabled. + */ + rstp_port_set_state(p, RSTP_LEARNING); +} + +static void +enable_forwarding(struct rstp_port *p) +{ + /* [17.21.5 enableForwarding()] An implementation dependent procedure that + * causes the Forwarding Process (7.7) to start forwarding frames through + * the Port. The procedure does not complete until forwarding has been + * enabled. + */ + rstp_port_set_state(p, RSTP_FORWARDING); +} + +static void +disable_learning(struct rstp_port *p) +{ + /* [17.21.4 - disableLearning()] An implementation dependent procedure that + * causes the Learning Process (7.8) to stop learning from the source + * address of frames received on the Port. The procedure does not complete + * until learning has stopped. + */ + rstp_port_set_state(p, RSTP_DISCARDING); +} + +static void +disable_forwarding(struct rstp_port *p) +{ + /* [17.21.3 - disableForwarding()] An implementation dependent procedure + * that causes the Forwarding Process (7.7) to stop forwarding frames + * through the Port. The procedure does not complete until forwarding has + * stopped. + */ + rstp_port_set_state(p, RSTP_DISCARDING); +} + +static int +port_state_transition_sm(struct rstp_port *p) +{ + enum port_state_transition_state_machine old_state; + struct rstp *r; + + old_state = p->port_state_transition_sm_state; + r = p->rstp; + + switch (p->port_state_transition_sm_state) { + case PORT_STATE_TRANSITION_SM_INIT: + if (r->begin) { + p->port_state_transition_sm_state = + PORT_STATE_TRANSITION_SM_DISCARDING_EXEC; + } + break; + case PORT_STATE_TRANSITION_SM_DISCARDING_EXEC: + disable_learning(p); + p->learning = false; + disable_forwarding(p); + p->forwarding = false; + p->port_state_transition_sm_state = + PORT_STATE_TRANSITION_SM_DISCARDING; + /* no break */ + case PORT_STATE_TRANSITION_SM_DISCARDING: + if (p->learn) { + p->port_state_transition_sm_state = + PORT_STATE_TRANSITION_SM_LEARNING_EXEC; + } + break; + case PORT_STATE_TRANSITION_SM_LEARNING_EXEC: + enable_learning(p); + p->learning = true; + p->port_state_transition_sm_state = PORT_STATE_TRANSITION_SM_LEARNING; + /* no break */ + case PORT_STATE_TRANSITION_SM_LEARNING: + if (!p->learn) { + p->port_state_transition_sm_state = + PORT_STATE_TRANSITION_SM_DISCARDING_EXEC; + } + else if (p->forward) { + p->port_state_transition_sm_state = + PORT_STATE_TRANSITION_SM_FORWARDING_EXEC; + } + break; + case PORT_STATE_TRANSITION_SM_FORWARDING_EXEC: + enable_forwarding(p); + p->forwarding = true; + p->port_state_transition_sm_state = + PORT_STATE_TRANSITION_SM_FORWARDING; + /* no break */ + case PORT_STATE_TRANSITION_SM_FORWARDING: + if (!p->forward) { + p->port_state_transition_sm_state = + PORT_STATE_TRANSITION_SM_DISCARDING_EXEC; + } + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->port_state_transition_sm_state) { + r->changes = true; + VLOG_DBG("%s, port %u: Port_state_transition_sm %d -> %d", + p->rstp->name, p->port_number, old_state, + p->port_state_transition_sm_state); + } + return 0; +} + +/* [17.31 - Topology Change state machine] */ + +static void +new_tc_while(struct rstp_port *p) +{ + struct rstp *r; + + r = p->rstp; + if (p->tc_while == 0 && p->send_rstp == true) { + p->tc_while = r->bridge_hello_time + 1; + p->new_info = true; + } + else if (p->tc_while == 0 && p->send_rstp == false) { + p->tc_while = r->bridge_max_age + r->bridge_forward_delay; + } +} + +/* [17.21.18 setTcPropTree()] + * Sets tcprop for all Ports except the Port that called the procedure. + */ +static void +set_tc_prop_tree(struct rstp_port *p) +{ + struct rstp *r; + struct rstp_port *p1; + + r = p->rstp; + if (r->ports_count > 0) { + LIST_FOR_EACH (p1, node, &r->ports) { + /* Set tc_prop on every port, except the one calling this + * function. + */ + if (p1->port_number != p->port_number) { + p1->tc_prop = true; + } + } + } +} + +static void +set_tc_prop_bridge(struct rstp_port *p) /* not specified in 802.1D-2004. */ +{ + set_tc_prop_tree(p); /* see 802.1w-2001. */ +} + +static int +topology_change_sm(struct rstp_port *p) +{ + enum topology_change_state_machine old_state; + struct rstp *r; + + old_state = p->topology_change_sm_state; + r = p->rstp; + + switch (p->topology_change_sm_state) { + case TOPOLOGY_CHANGE_SM_INIT: + if (r->begin) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_INACTIVE_EXEC; + } + break; + case TOPOLOGY_CHANGE_SM_INACTIVE_EXEC: + p->fdb_flush = true; + p->tc_while = 0; + p->tc_ack = false; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_INACTIVE; + /* no break */ + case TOPOLOGY_CHANGE_SM_INACTIVE: + if (p->learn && !p->fdb_flush) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_LEARNING_EXEC; + } + break; + case TOPOLOGY_CHANGE_SM_LEARNING_EXEC: + p->rcvd_tc = p->rcvd_tcn = p->rcvd_tc_ack = false; + p->tc_prop = p->rcvd_tc_ack = false; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_LEARNING; + /* no break */ + case TOPOLOGY_CHANGE_SM_LEARNING: + if ((p->role != ROLE_ROOT) && (p->role != ROLE_DESIGNATED) && + !(p->learn || p->learning) && !(p->rcvd_tc || p->rcvd_tcn || + p->rcvd_tc_ack || p->tc_prop)) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_INACTIVE_EXEC; + } + else if (p->rcvd_tc || p->rcvd_tcn || p->rcvd_tc_ack || p->tc_prop) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_LEARNING_EXEC; + } + else if (((p->role == ROLE_ROOT) || (p->role == ROLE_DESIGNATED)) && + p->forward && !p->oper_edge) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_DETECTED_EXEC; + } + break; + case TOPOLOGY_CHANGE_SM_DETECTED_EXEC: + new_tc_while(p); + set_tc_prop_tree(p); + p->new_info = true; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE_EXEC; + /* no break */ + case TOPOLOGY_CHANGE_SM_ACTIVE_EXEC: + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE; + /* no break */ + case TOPOLOGY_CHANGE_SM_ACTIVE: + if (((p->role != ROLE_ROOT) && (p->role != ROLE_DESIGNATED)) || + p->oper_edge) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_LEARNING_EXEC; + } + else if (p->rcvd_tcn) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_NOTIFIED_TCN_EXEC; + } + else if (p->rcvd_tc) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_NOTIFIED_TC_EXEC; + } + else if (p->tc_prop && !p->oper_edge) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_PROPAGATING_EXEC; + } + else if (p->rcvd_tc_ack) { + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACKNOWLEDGED_EXEC; + } + break; + case TOPOLOGY_CHANGE_SM_ACKNOWLEDGED_EXEC: + p->tc_while = 0; + p->rcvd_tc_ack = false; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE; + break; + case TOPOLOGY_CHANGE_SM_PROPAGATING_EXEC: + new_tc_while(p); + p->fdb_flush = true; + p->tc_prop = false; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE; + break; + case TOPOLOGY_CHANGE_SM_NOTIFIED_TC_EXEC: + p->rcvd_tcn = p->rcvd_tc = false; + if (p->role == ROLE_DESIGNATED) { + p->tc_ack = true; + } + set_tc_prop_bridge(p); + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE; + break; + case TOPOLOGY_CHANGE_SM_NOTIFIED_TCN_EXEC: + new_tc_while(p); + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE; + break; + default: + OVS_NOT_REACHED(); + /* no break */ + } + if (old_state != p->topology_change_sm_state) { + r->changes = true; + VLOG_DBG("Topology_change_sm %d -> %d",old_state, + p->topology_change_sm_state); + } + return 0; +} + +/**************************************************************************** + * [17.6] Priority vector calculation helper functions + ****************************************************************************/ + +/* [17.6] + * This message priority vector is superior to the port priority vector and + * will replace it if, and only if, the message priority vector is better + * than the port priority vector, or the message has been transmitted from the + * same Designated Bridge and Designated Port as the port priority vector, + * i.e.,if the following is true: + * ((RD < RootBridgeID)) || + * ((RD == RootBridgeID) && (RPCD < RootPathCost)) || + * ((RD == RootBridgeID) && (RPCD == RootPathCost) && + * (D < designated_bridge_id)) || + * ((RD == RootBridgeID) && (RPCD == RootPathCost) && + * (D == designated_bridge_id) && (PD < designated_port_id)) || + * ((D == designated_bridge_id.BridgeAddress) && + * (PD == designated_port_id.PortNumber)) + */ + +/* compare_rstp_priority_vector() compares two struct rstp_priority_vector and + * returns a value indicating if the first rstp_priority_vector is superior, + * same or inferior to the second one. + */ +static enum vector_comparison +compare_rstp_priority_vector(struct rstp_priority_vector *v1, + struct rstp_priority_vector *v2) +{ + VLOG_DBG("v1: "RSTP_ID_FMT", %u, "RSTP_ID_FMT", %d", + RSTP_ID_ARGS(v1->root_bridge_id), v1->root_path_cost, + RSTP_ID_ARGS(v1->designated_bridge_id), v1->designated_port_id); + VLOG_DBG("v2: "RSTP_ID_FMT", %u, "RSTP_ID_FMT", %d", + RSTP_ID_ARGS(v2->root_bridge_id), v2->root_path_cost, + RSTP_ID_ARGS(v2->designated_bridge_id), v2->designated_port_id); + + if ((v1->root_bridge_id < v2->root_bridge_id) || + ((v1->root_bridge_id == v2->root_bridge_id) && + (v1->root_path_cost < v2->root_path_cost)) || + ((v1->root_bridge_id == v2->root_bridge_id) && + (v1->root_path_cost == v2->root_path_cost) && + (v1->designated_bridge_id < v2->designated_bridge_id)) || + ((v1->root_bridge_id == v2->root_bridge_id) && + (v1->root_path_cost == v2->root_path_cost) && + (v1->designated_bridge_id == v2->designated_bridge_id) && + (v1->designated_port_id < v2->designated_port_id))) { + VLOG_DBG("superior_absolute"); + return SUPERIOR; + } + else if (((v1->root_bridge_id > v2->root_bridge_id) || + ((v1->root_bridge_id == v2->root_bridge_id) && + (v1->root_path_cost > v2->root_path_cost)) || + ((v1->root_bridge_id == v2->root_bridge_id) && + (v1->root_path_cost == v2->root_path_cost) && + (v1->designated_bridge_id > v2->designated_bridge_id)) || + ((v1->root_bridge_id == v2->root_bridge_id) && + (v1->root_path_cost == v2->root_path_cost) && + (v1->designated_bridge_id == v2->designated_bridge_id) && + (v1->designated_port_id > v2->designated_port_id))) && + (v1->designated_bridge_id == v2->designated_bridge_id) && + (v1->designated_port_id == v2->designated_port_id)) { + VLOG_DBG("superior_same_des"); + return SUPERIOR; + } + else if ((v1->root_bridge_id == v2->root_bridge_id) && + (v1->root_path_cost == v2->root_path_cost) && + (v1->designated_bridge_id == v2->designated_bridge_id) && + (v1->designated_port_id == v2->designated_port_id)) { + VLOG_DBG("same"); + return SAME; + } + else { + VLOG_DBG("inferior"); + return INFERIOR; + } +} + +static bool +rstp_times_equal(struct rstp_times *t1, struct rstp_times *t2) { + return ((t1->forward_delay == t2->forward_delay) && + (t1->hello_time == t2->hello_time) && + (t1->max_age == t2->max_age) && + (t1->message_age == t2->message_age)); +} diff --git a/lib/rstp-state-machines.h b/lib/rstp-state-machines.h new file mode 100644 index 000000000..7b9f8bc31 --- /dev/null +++ b/lib/rstp-state-machines.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2011-2014 M3S, Srl - Italy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Rapid Spanning Tree Protocol (IEEE 802.1D-2004) state machines + * implementation (header file). + * + * Authors: + * Martino Fornasa + * Daniele Venturino + * + * References to IEEE 802.1D-2004 standard are enclosed in square brackets. + * E.g. [17.3], [Table 17-1], etc. + * + */ + +#ifndef RSTP_STATE_MACHINES_H +#define RSTP_STATE_MACHINES_H 1 + +#include "rstp-common.h" + +/* Methods called by the Forwarding Layer, through functions of rstp.h. */ +int move_rstp(struct rstp *); +void decrease_rstp_port_timers(struct rstp *); +void process_received_bpdu(struct rstp_port *, const void *, size_t); + +void updt_roles_tree(struct rstp *); + +#endif /* rstp-state-machines.h */ diff --git a/lib/rstp.c b/lib/rstp.c new file mode 100644 index 000000000..b0ad613c3 --- /dev/null +++ b/lib/rstp.c @@ -0,0 +1,1236 @@ +/* + * Copyright (c) 2011-2014 M3S, Srl - Italy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Rapid Spanning Tree Protocol (IEEE 802.1D-2004) public interface. + * + * Authors: + * Martino Fornasa + * Daniele Venturino + * + * References to IEEE 802.1D-2004 standard are enclosed in square brackets. + * E.g. [17.3], [Table 17-1], etc. + * + */ + +#include +#include "rstp.h" +#include "rstp-common.h" +#include "rstp-state-machines.h" +#include +#include +#include +#include +#include +#include "byte-order.h" +#include "connectivity.h" +#include "ofpbuf.h" +#include "ofproto/ofproto.h" +#include "packets.h" +#include "seq.h" +#include "unixctl.h" +#include "util.h" +#include "vlog.h" + +VLOG_DEFINE_THIS_MODULE(rstp); + +static struct ovs_mutex mutex; +static struct list all_rstps__ = LIST_INITIALIZER(&all_rstps__); +static struct list *const all_rstps OVS_GUARDED_BY(mutex) = &all_rstps__; + +/* Internal use only */ +static void set_port_id__(struct rstp_port *); +static void update_port_enabled__(struct rstp_port *); +static void set_bridge_priority__(struct rstp *); +static void reinitialize_rstp__(struct rstp *); +static bool is_port_number_taken__(struct rstp *, int, struct rstp_port *); +static uint16_t rstp_first_free_number__(struct rstp *, struct rstp_port *); +static void rstp_initialize_port(struct rstp_port *p); + +const char * +rstp_state_name(enum rstp_state state) +{ + switch (state) { + case RSTP_DISABLED: + return "Disabled"; + case RSTP_LEARNING: + return "Learning"; + case RSTP_FORWARDING: + return "Forwarding"; + case RSTP_DISCARDING: + return "Discarding"; + default: + return "Unknown"; + } +} + +const char * +rstp_port_role_name(enum rstp_port_role role) +{ + switch (role) { + case ROLE_ROOT: + return "Root"; + case ROLE_DESIGNATED: + return "Designated"; + case ROLE_ALTERNATE: + return "Alternate"; + case ROLE_BACKUP: + return "Backup"; + case ROLE_DISABLED: + return "Disabled"; + default: + return "Unknown"; + } +} + +struct rstp * +rstp_ref(struct rstp *rstp_) +{ + struct rstp *rstp; + + rstp = rstp_; + if (rstp) { + ovs_refcount_ref(&rstp->ref_cnt); + } + return rstp; +} + +/* Frees RSTP struct */ +void +rstp_unref(struct rstp *rstp) +{ + struct rstp_port *p; + + if (rstp && ovs_refcount_unref(&rstp->ref_cnt) == 1) { + ovs_mutex_lock(&mutex); + if (rstp->ports_count > 0) { + LIST_FOR_EACH (p, node, &rstp->ports) { + rstp_delete_port(p); + } + } + list_remove(&rstp->node); + ovs_mutex_unlock(&mutex); + free(rstp->name); + free(rstp); + } +} + +/* Returns the port number. */ +int +rstp_port_number(const struct rstp_port *p) +{ + int number; + + ovs_mutex_lock(&mutex); + number = p->port_number; + ovs_mutex_unlock(&mutex); + return number; +} + +static void rstp_unixctl_tcn(struct unixctl_conn *, int argc, + const char *argv[], void *aux); + +/* Decrements the State Machines' timers. */ +void +rstp_tick_timers(struct rstp *rstp) +{ + ovs_mutex_lock(&mutex); + decrease_rstp_port_timers(rstp); + ovs_mutex_unlock(&mutex); +} + +/* Processes an incoming BPDU. */ +void +rstp_received_bpdu(struct rstp_port *p, const void *bpdu, size_t bpdu_size) +{ + ovs_mutex_lock(&mutex); + process_received_bpdu(p, bpdu, bpdu_size); + ovs_mutex_unlock(&mutex); +} + +void +rstp_init(void) +{ + unixctl_command_register("rstp/tcn", "[bridge]", 0, 1, rstp_unixctl_tcn, + NULL); +} + +/* Creates and returns a new RSTP instance that initially has no ports. */ +struct rstp * +rstp_create(const char *name, rstp_identifier bridge_address, + void (*send_bpdu)(struct ofpbuf *bpdu, int port_no, void *aux), + void *aux) +{ + static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; + struct rstp *rstp; + + VLOG_DBG("Creating RSTP instance"); + if (ovsthread_once_start(&once)) { + ovs_mutex_init_recursive(&mutex); + ovsthread_once_done(&once); + } + + rstp = xzalloc(sizeof *rstp); + rstp->name = xstrdup(name); + /* Set bridge address. */ + rstp_set_bridge_address(rstp, bridge_address); + /* Set default parameters values. */ + rstp_set_bridge_priority(rstp, RSTP_DEFAULT_PRIORITY); + rstp_set_bridge_ageing_time(rstp, RSTP_DEFAULT_AGEING_TIME); + rstp_set_bridge_force_protocol_version(rstp, FPV_DEFAULT); + rstp_set_bridge_forward_delay(rstp, RSTP_DEFAULT_BRIDGE_FORWARD_DELAY); + rstp_set_bridge_hello_time(rstp); + rstp_set_bridge_max_age(rstp, RSTP_DEFAULT_BRIDGE_MAX_AGE); + rstp_set_bridge_migrate_time(rstp); + rstp_set_bridge_transmit_hold_count(rstp, + RSTP_DEFAULT_TRANSMIT_HOLD_COUNT); + rstp_set_bridge_times(rstp, RSTP_DEFAULT_BRIDGE_FORWARD_DELAY, + RSTP_BRIDGE_HELLO_TIME, RSTP_DEFAULT_BRIDGE_MAX_AGE, + 0); + rstp->send_bpdu = send_bpdu; + rstp->aux = aux; + rstp->changes = false; + rstp->begin = true; + + ovs_mutex_lock(&mutex); + /* Initialize the ports list. */ + list_init(&rstp->ports); + ovs_refcount_init(&rstp->ref_cnt); + list_push_back(all_rstps, &rstp->node); + ovs_mutex_unlock(&mutex); + VLOG_DBG("RSTP instance creation done"); + return rstp; +} + +/* Called by rstp_set_bridge_address() and rstp_set_bridge_priority(), + * it updates the bridge priority vector according to the values passed by + * those setters. + */ +static void +set_bridge_priority__(struct rstp *rstp) +{ + rstp->bridge_priority.root_bridge_id = rstp->bridge_identifier; + rstp->bridge_priority.designated_bridge_id = rstp->bridge_identifier; + VLOG_DBG("%s: new bridge identifier: "RSTP_ID_FMT"", rstp->name, + RSTP_ID_ARGS(rstp->bridge_identifier)); +} + +/* Sets the bridge address. */ +void +rstp_set_bridge_address(struct rstp *rstp, rstp_identifier bridge_address) +{ + struct rstp_port *p; + + VLOG_DBG("%s: set bridge address to: "RSTP_ID_FMT"", rstp->name, + RSTP_ID_ARGS(bridge_address)); + ovs_mutex_lock(&mutex); + rstp->address = bridge_address; + rstp->bridge_identifier = bridge_address; + set_bridge_priority__(rstp); + + /* [17.13] When the bridge address changes, recalculates all priority + * vectors. + */ + if (rstp->ports_count > 0) { + LIST_FOR_EACH (p, node, &rstp->ports) { + p->selected = 0; + p->reselect = 1; + } + } + rstp->changes = true; + updt_roles_tree(rstp); + ovs_mutex_unlock(&mutex); +} + +const char * +rstp_get_name(const struct rstp *rstp) +{ + char *name; + + ovs_mutex_lock(&mutex); + name = rstp->name; + ovs_mutex_unlock(&mutex); + return name; +} + +rstp_identifier +rstp_get_bridge_id(const struct rstp *rstp) +{ + rstp_identifier bridge_id; + + ovs_mutex_lock(&mutex); + bridge_id = rstp->bridge_identifier; + ovs_mutex_unlock(&mutex); + return bridge_id; +} + +/* Sets the bridge priority. */ +void +rstp_set_bridge_priority(struct rstp *rstp, int new_priority) +{ + struct rstp_port *p; + + if (new_priority >= RSTP_MIN_PRIORITY && + new_priority <= RSTP_MAX_PRIORITY) { + VLOG_DBG("%s: set bridge priority to %d", rstp->name, + (new_priority / 4096) * 4096); + ovs_mutex_lock(&mutex); + rstp->priority = (new_priority / 4096) * 4096; + rstp->bridge_identifier &= 0xffffffffffffULL; + rstp->bridge_identifier |= + (uint64_t) ((new_priority / 4096) * 4096) << 48; + set_bridge_priority__(rstp); + + /* [17.13] */ + if (rstp->ports_count > 0){ + LIST_FOR_EACH (p, node, &rstp->ports) { + p->selected = 0; + p->reselect = 1; + } + } + rstp->changes = true; + updt_roles_tree(rstp); + ovs_mutex_unlock(&mutex); + } +} + +/* Sets the bridge ageing time. */ +void +rstp_set_bridge_ageing_time(struct rstp *rstp, int new_ageing_time) +{ + if (new_ageing_time >= RSTP_MIN_AGEING_TIME && + new_ageing_time <= RSTP_MAX_AGEING_TIME) { + VLOG_DBG("%s: set ageing time to %d", rstp->name, new_ageing_time); + ovs_mutex_lock(&mutex); + rstp->ageing_time = new_ageing_time; + ovs_mutex_unlock(&mutex); + } +} + +/* Reinitializes RSTP when switching from RSTP mode to STP mode + * or vice versa. + */ +static void +reinitialize_rstp__(struct rstp *rstp) +{ + struct rstp temp; + struct rstp_port *p, temp_port; + static struct list ports; + + /* Copy rstp in temp */ + temp = *rstp; + ports = rstp->ports; + /* stop and clear rstp */ + memset(rstp, 0, sizeof(struct rstp)); + + /* Initialize rstp. */ + rstp->name = temp.name; + /* Set bridge address. */ + rstp_set_bridge_address(rstp, temp.address); + /* Set default parameters values. */ + rstp_set_bridge_priority(rstp, RSTP_DEFAULT_PRIORITY); + rstp_set_bridge_ageing_time(rstp, RSTP_DEFAULT_AGEING_TIME); + rstp_set_bridge_forward_delay(rstp, RSTP_DEFAULT_BRIDGE_FORWARD_DELAY); + rstp_set_bridge_hello_time(rstp); + rstp_set_bridge_max_age(rstp, RSTP_DEFAULT_BRIDGE_MAX_AGE); + rstp_set_bridge_migrate_time(rstp); + rstp_set_bridge_transmit_hold_count(rstp, + RSTP_DEFAULT_TRANSMIT_HOLD_COUNT); + rstp_set_bridge_times(rstp, RSTP_DEFAULT_BRIDGE_FORWARD_DELAY, + RSTP_BRIDGE_HELLO_TIME, RSTP_DEFAULT_BRIDGE_MAX_AGE, + 0); + + rstp->send_bpdu = temp.send_bpdu; + rstp->aux = temp.aux; + rstp->node = temp.node; + rstp->changes = false; + rstp->begin = true; + rstp->ports = ports; + rstp->ports_count = temp.ports_count; + if (rstp->ports_count > 0){ + LIST_FOR_EACH (p, node, &rstp->ports) { + temp_port = *p; + memset(p, 0, sizeof(struct rstp_port)); + p->rstp = rstp; + p->node = temp_port.node; + p->aux = temp_port.aux; + p->port_number = temp_port.port_number; + p->port_priority = temp_port.port_priority; + p->port_id = temp_port.port_id; + p->rstp_state = RSTP_DISCARDING; + + rstp_port_set_administrative_bridge_port(p, + RSTP_ADMIN_BRIDGE_PORT_STATE_ENABLED); + rstp_port_set_oper_point_to_point_mac(p, 1); + rstp_port_set_path_cost(p, RSTP_DEFAULT_PORT_PATH_COST); + rstp_port_set_auto_edge(p, true); + /* Initialize state machines. */ + p->port_receive_sm_state = PORT_RECEIVE_SM_INIT; + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_INIT; + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_INIT; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_INIT; + p->port_information_sm_state = PORT_INFORMATION_SM_INIT; + p->port_role_transition_sm_state = PORT_ROLE_TRANSITION_SM_INIT; + p->port_state_transition_sm_state = PORT_STATE_TRANSITION_SM_INIT; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_INIT; + p->uptime = 0; + } + } + rstp->ref_cnt = temp.ref_cnt; +} + +/* Sets the force protocol version parameter. */ +void +rstp_set_bridge_force_protocol_version(struct rstp *rstp, + enum rstp_force_protocol_version new_force_protocol_version) +{ + if (new_force_protocol_version != rstp->force_protocol_version && + (new_force_protocol_version == FPV_STP_COMPATIBILITY || + new_force_protocol_version == FPV_DEFAULT)) { + VLOG_DBG("%s: set bridge Force Protocol Version to %d", rstp->name, + new_force_protocol_version); + ovs_mutex_lock(&mutex); + /* [17.13] The Spanning Tree Protocol Entity shall be reinitialized, + * as specified by the assertion of BEGIN (17.18.1) in the state + * machine specification. + */ + reinitialize_rstp__(rstp); + rstp->force_protocol_version = new_force_protocol_version; + if (rstp->force_protocol_version < 2) { + rstp->stp_version = true; + rstp->rstp_version = false; + } else { + rstp->stp_version = false; + rstp->rstp_version = true; + } + rstp->changes = true; + move_rstp(rstp); + ovs_mutex_unlock(&mutex); + } +} + +/* Sets the bridge Hello Time parameter. */ +void +rstp_set_bridge_hello_time(struct rstp *rstp) +{ + VLOG_DBG("%s: set RSTP Hello Time to %d", rstp->name, + RSTP_BRIDGE_HELLO_TIME); + /* 2 is the only acceptable value. */ + ovs_mutex_lock(&mutex); + rstp->bridge_hello_time = RSTP_BRIDGE_HELLO_TIME; + ovs_mutex_unlock(&mutex); +} + +/* Sets the bridge max age parameter. */ +void +rstp_set_bridge_max_age(struct rstp *rstp, int new_max_age) +{ + if (new_max_age >= RSTP_MIN_BRIDGE_MAX_AGE && + new_max_age <= RSTP_MAX_BRIDGE_MAX_AGE) { + /* [17.13] */ + if ((2*(rstp->bridge_forward_delay - 1) >= new_max_age) && + (new_max_age >= 2*rstp->bridge_hello_time)) { + VLOG_DBG("%s: set RSTP bridge Max Age to %d", rstp->name, + new_max_age); + ovs_mutex_lock(&mutex); + rstp->bridge_max_age = new_max_age; + rstp->bridge_times.max_age = new_max_age; + ovs_mutex_unlock(&mutex); + } + } +} + +/* Sets the bridge forward delay parameter. */ +void +rstp_set_bridge_forward_delay(struct rstp *rstp, int new_forward_delay) +{ + if (new_forward_delay >= RSTP_MIN_BRIDGE_FORWARD_DELAY && + new_forward_delay <= RSTP_MAX_BRIDGE_FORWARD_DELAY) { + if (2 * (new_forward_delay - 1) >= rstp->bridge_max_age) { + VLOG_DBG("%s: set RSTP Forward Delay to %d", rstp->name, + new_forward_delay); + ovs_mutex_lock(&mutex); + rstp->bridge_forward_delay = new_forward_delay; + rstp->bridge_times.forward_delay = new_forward_delay; + ovs_mutex_unlock(&mutex); + } + } +} + +/* Sets the bridge transmit hold count parameter. */ +void +rstp_set_bridge_transmit_hold_count(struct rstp *rstp, + int new_transmit_hold_count) +{ + struct rstp_port *p; + + if (new_transmit_hold_count >= RSTP_MIN_TRANSMIT_HOLD_COUNT && + new_transmit_hold_count <= RSTP_MAX_TRANSMIT_HOLD_COUNT) { + VLOG_DBG("%s: set RSTP Transmit Hold Count to %d", rstp->name, + new_transmit_hold_count); + /* Resetting txCount on all ports [17.13]. */ + ovs_mutex_lock(&mutex); + rstp->transmit_hold_count = new_transmit_hold_count; + if (rstp->ports_count > 0){ + LIST_FOR_EACH (p, node, &rstp->ports) { + p->tx_count=0; + } + } + ovs_mutex_unlock(&mutex); + } +} + +/* Sets the bridge migrate time parameter. */ +void +rstp_set_bridge_migrate_time(struct rstp *rstp) +{ + VLOG_DBG("%s: set RSTP Migrate Time to %d", rstp->name, + RSTP_MIGRATE_TIME); + /* 3 is the only acceptable value */ + ovs_mutex_lock(&mutex); + rstp->migrate_time = RSTP_MIGRATE_TIME; + ovs_mutex_unlock(&mutex); +} + +/* Sets the bridge times. */ +void +rstp_set_bridge_times(struct rstp *rstp, int new_forward_delay, + int new_hello_time, int new_max_age, + int new_message_age) +{ + VLOG_DBG("%s: set RSTP times to (%d, %d, %d, %d)", rstp->name, + new_forward_delay, new_hello_time, new_max_age, new_message_age); + if (new_forward_delay >= RSTP_MIN_BRIDGE_FORWARD_DELAY && + new_forward_delay <= RSTP_MAX_BRIDGE_FORWARD_DELAY) + rstp->bridge_times.forward_delay = new_forward_delay; + if (new_hello_time == RSTP_BRIDGE_HELLO_TIME) + rstp->bridge_times.hello_time = new_hello_time; + if (new_max_age >= RSTP_MIN_BRIDGE_MAX_AGE && + new_max_age <= RSTP_MAX_BRIDGE_MAX_AGE) + rstp->bridge_times.max_age = new_max_age; + rstp->bridge_times.message_age = new_message_age; +} + +/* Sets the port id, it is called by rstp_port_set_port_number() or + * rstp_port_set_priority(). + */ +static void +set_port_id__(struct rstp_port *p) +{ + struct rstp *rstp; + + rstp = p->rstp; + /* [9.2.7] Port identifier. */ + p->port_id = p->port_number | (p->priority << 8); + VLOG_DBG("%s: new RSTP port id "RSTP_PORT_ID_FMT"", rstp->name, + p->port_id); +} + +/* Sets the port priority. */ +void +rstp_port_set_priority(struct rstp_port *rstp_port, int new_port_priority) +{ + struct rstp *rstp; + + rstp = rstp_port->rstp; + if (new_port_priority >= RSTP_MIN_PORT_PRIORITY && + new_port_priority <= RSTP_MAX_PORT_PRIORITY) { + VLOG_DBG("%s, port %u: set RSTP port priority to %d", rstp->name, + rstp_port->port_number, new_port_priority); + ovs_mutex_lock(&mutex); + new_port_priority -= new_port_priority % RSTP_STEP_PORT_PRIORITY; + rstp_port->priority = new_port_priority; + set_port_id__(rstp_port); + rstp_port->selected = 0; + rstp_port->reselect = 1; + ovs_mutex_unlock(&mutex); + } +} + +/* Checks if a port number is already taken by an active port. */ +static bool +is_port_number_taken__(struct rstp *rstp, int n, struct rstp_port *rstp_port) +{ + struct rstp_port *p; + + if (rstp->ports_count > 0){ + LIST_FOR_EACH (p, node, &rstp->ports) { + if (p->port_number == n && rstp_port != rstp_get_port(rstp, n)) { + VLOG_DBG("%s: port number %d not available", rstp->name, n); + return true; + } + } + } + VLOG_DBG("%s: port number %d is available", rstp->name, n); + return false; +} + +static uint16_t +rstp_first_free_number__(struct rstp *rstp, struct rstp_port *rstp_port) { + int free_number; + + free_number = 1; + ovs_mutex_lock(&mutex); + while (free_number <= RSTP_MAX_PORTS) { + if (!is_port_number_taken__(rstp, free_number, rstp_port)) { + ovs_mutex_unlock(&mutex); + return free_number; + } + free_number++; + } + ovs_mutex_unlock(&mutex); + VLOG_DBG("%s, No free port number available.", rstp->name); + return 0; +} + +/* Sets the port number. */ +void +rstp_port_set_port_number(struct rstp_port *rstp_port, + uint16_t new_port_number) +{ + struct rstp *rstp; + + rstp = rstp_port->rstp; + ovs_mutex_lock(&mutex); + /* If new_port_number is inside bounds and available, use it. + * If new_port_number is 0 or it is already taken, use the first free + * available port number. + */ + if ((new_port_number >= 1 && new_port_number <= RSTP_MAX_PORTS) && + (!is_port_number_taken__(rstp_port->rstp, new_port_number, rstp_port))) + { + rstp_port->port_number = new_port_number; + } + else if (new_port_number == 0 || + is_port_number_taken__(rstp_port->rstp, new_port_number, + rstp_port)) { + rstp_port->port_number = rstp_first_free_number__(rstp, rstp_port); + } + + set_port_id__(rstp_port); + /* [17.13] is not clear. I suppose that a port number change + * should trigger reselection like a port priority change. + */ + rstp_port->selected = 0; + rstp_port->reselect = 1; + ovs_mutex_unlock(&mutex); + VLOG_DBG("%s: set new RSTP port number %d", rstp->name, + rstp_port->port_number); +} + +/* Converts the link speed to a port path cost [Table 17-3]. */ +uint32_t +rstp_convert_speed_to_cost(unsigned int speed) +{ + uint32_t value; + + value = speed >= 10000000 ? 2 /* 10 Tb/s. */ + : speed >= 1000000 ? 20 /* 1 Tb/s. */ + : speed >= 100000 ? 200 /* 100 Gb/s. */ + : speed >= 10000 ? 2000 /* 10 Gb/s. */ + : speed >= 1000 ? 20000 /* 1 Gb/s. */ + : speed >= 100 ? 200000 /* 100 Mb/s. */ + : speed >= 10 ? 2000000 /* 10 Mb/s. */ + : speed >= 1 ? 20000000 /* 1 Mb/s. */ + : RSTP_DEFAULT_PORT_PATH_COST; /* 100 Mb/s. */ + + return value; +} + +/* Sets the port path cost. */ +void +rstp_port_set_path_cost(struct rstp_port *rstp_port, + uint32_t new_port_path_cost) +{ + struct rstp *rstp; + + rstp = rstp_port->rstp; + if (new_port_path_cost >= RSTP_MIN_PORT_PATH_COST && + new_port_path_cost <= RSTP_MAX_PORT_PATH_COST) { + VLOG_DBG("%s, port %u, set RSTP port path cost to %d", rstp->name, + rstp_port->port_number, new_port_path_cost); + ovs_mutex_lock(&mutex); + rstp_port->port_path_cost = new_port_path_cost; + rstp_port->selected = 0; + rstp_port->reselect = 1; + ovs_mutex_unlock(&mutex); + } +} + +/* Gets the root path cost. */ +uint32_t +rstp_get_root_path_cost(const struct rstp *rstp) +{ + uint32_t cost; + + ovs_mutex_lock(&mutex); + cost = rstp->root_priority.root_path_cost; + ovs_mutex_unlock(&mutex); + return cost; +} + +/* Returns true if something has happened to 'rstp' which necessitates + * flushing the client's MAC learning table. + */ +bool +rstp_check_and_reset_fdb_flush(struct rstp *rstp) +{ + bool needs_flush; + struct rstp_port *p; + + needs_flush = false; + + ovs_mutex_lock(&mutex); + if (rstp->ports_count > 0){ + LIST_FOR_EACH (p, node, &rstp->ports) { + if (p->fdb_flush) { + needs_flush = true; + /* fdb_flush should be reset by the filtering database + * once the entries are removed if rstp_version is TRUE, and + * immediately if stp_version is TRUE.*/ + p->fdb_flush = false; + } + } + } + ovs_mutex_unlock(&mutex); + return needs_flush; +} + +/* Finds a port whose state has changed. If successful, stores the port whose + * state changed in '*portp' and returns true. If no port has changed, stores + * NULL in '*portp' and returns false. */ +bool +rstp_get_changed_port(struct rstp *rstp, struct rstp_port **portp) +{ + struct rstp_port *p; + bool changed; + + changed = false; + + ovs_mutex_lock(&mutex); + if (rstp->ports_count > 0){ + LIST_FOR_EACH (p, node, &rstp->ports) { + if (p->state_changed) { + p->state_changed = false; + *portp = p; + changed = true; + ovs_mutex_unlock(&mutex); + return changed; + } + } + } + *portp = NULL; + ovs_mutex_unlock(&mutex); + return changed; +} + +/* Returns the port in 'rstp' with number 'port_number'. */ +struct rstp_port * +rstp_get_port(struct rstp *rstp, int port_number) +{ + struct rstp_port *port; + + ovs_mutex_lock(&mutex); + if (rstp->ports_count > 0){ + LIST_FOR_EACH (port, node, &rstp->ports) { + if (port->port_number == port_number) { + ovs_mutex_unlock(&mutex); + return port; + } + } + } + ovs_mutex_unlock(&mutex); + return NULL; +} + +/* Updates the port_enabled parameter. */ +static void +update_port_enabled__(struct rstp_port *p) +{ + if (p->mac_operational && p->is_administrative_bridge_port == + RSTP_ADMIN_BRIDGE_PORT_STATE_ENABLED) { + p->port_enabled = true; + } else { + p->port_enabled = false; + } +} + +/* Sets the port MAC_Operational parameter [6.4.2]. */ +void +rstp_port_set_mac_operational(struct rstp_port *p, bool new_mac_operational) +{ + struct rstp *rstp; + + ovs_mutex_lock(&mutex); + rstp = p->rstp; + p->mac_operational = new_mac_operational; + update_port_enabled__(p); + rstp->changes = true; + move_rstp(rstp); + ovs_mutex_unlock(&mutex); +} + +/* Gets the port MAC_Operational parameter [6.4.2]. */ +bool +rstp_port_get_mac_operational(struct rstp_port *p) +{ + bool value; + + ovs_mutex_lock(&mutex); + value = p->mac_operational; + ovs_mutex_unlock(&mutex); + return value; +} + +/* Sets the port Administrative Bridge Port parameter. */ +void +rstp_port_set_administrative_bridge_port(struct rstp_port *p, + uint8_t new_admin_port_state) +{ + if (new_admin_port_state == RSTP_ADMIN_BRIDGE_PORT_STATE_DISABLED || + new_admin_port_state == RSTP_ADMIN_BRIDGE_PORT_STATE_ENABLED) { + p->is_administrative_bridge_port = new_admin_port_state; + update_port_enabled__(p); + } +} + +/* Sets the port oper_point_to_point_mac parameter. */ +void +rstp_port_set_oper_point_to_point_mac(struct rstp_port *p, + uint8_t new_oper_p2p_mac) +{ + if (new_oper_p2p_mac == RSTP_OPER_P2P_MAC_STATE_DISABLED || + new_oper_p2p_mac == RSTP_OPER_P2P_MAC_STATE_ENABLED) { + p->oper_point_to_point_mac = new_oper_p2p_mac; + update_port_enabled__(p); + } +} + +/* Initializes a port with the defaults values for its parameters. */ +static void +rstp_initialize_port(struct rstp_port *p) +OVS_REQUIRES(mutex) +{ + struct rstp *rstp; + + rstp = p->rstp; + rstp_port_set_administrative_bridge_port(p, + RSTP_ADMIN_BRIDGE_PORT_STATE_ENABLED); + rstp_port_set_oper_point_to_point_mac(p, 1); + rstp_port_set_priority(p, RSTP_DEFAULT_PORT_PRIORITY); + rstp_port_set_port_number(p, 0); + rstp_port_set_path_cost(p, RSTP_DEFAULT_PORT_PATH_COST); + rstp_port_set_auto_edge(p, true); + + p->port_receive_sm_state = PORT_RECEIVE_SM_INIT; + p->port_protocol_migration_sm_state = PORT_PROTOCOL_MIGRATION_SM_INIT; + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_INIT; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_INIT; + p->port_information_sm_state = PORT_INFORMATION_SM_INIT; + p->port_role_transition_sm_state = PORT_ROLE_TRANSITION_SM_INIT; + p->port_state_transition_sm_state = PORT_STATE_TRANSITION_SM_INIT; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_INIT; + p->aux = NULL; + p->uptime = 0; + + VLOG_DBG("%s: RSTP port "RSTP_PORT_ID_FMT" initialized.", rstp->name, + p->port_id); +} + +/* Reinitialization function used in tests. */ +void +reinitialize_port(struct rstp_port *p) +{ + struct rstp_port temp_port; + struct rstp *rstp; + + rstp = p->rstp; + temp_port = *p; + memset(p, 0, sizeof(struct rstp_port)); + p->rstp = rstp; + p->node = temp_port.node; + p->aux = temp_port.aux; + p->port_number = temp_port.port_number; + p->port_priority = temp_port.port_priority; + p->port_id = temp_port.port_id; + p->rstp_state = RSTP_DISCARDING; + + rstp_port_set_administrative_bridge_port(p, + RSTP_ADMIN_BRIDGE_PORT_STATE_ENABLED); + rstp_port_set_oper_point_to_point_mac(p, 1); + rstp_port_set_path_cost(p, RSTP_DEFAULT_PORT_PATH_COST); + rstp_port_set_auto_edge(p, true); + /* Initialize state machines. */ + p->port_receive_sm_state = PORT_RECEIVE_SM_INIT; + p->port_protocol_migration_sm_state = + PORT_PROTOCOL_MIGRATION_SM_INIT; + p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_INIT; + p->port_transmit_sm_state = PORT_TRANSMIT_SM_INIT; + p->port_information_sm_state = PORT_INFORMATION_SM_INIT; + p->port_role_transition_sm_state = PORT_ROLE_TRANSITION_SM_INIT; + p->port_state_transition_sm_state = PORT_STATE_TRANSITION_SM_INIT; + p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_INIT; + p->uptime = 0; + + VLOG_DBG("%s: RSTP port "RSTP_PORT_ID_FMT" reinitialized.", rstp->name, + p->port_id); +} + +/* Sets the port state. */ +void +rstp_port_set_state(struct rstp_port *p, enum rstp_state state) +OVS_REQUIRES(mutex) +{ + struct rstp *rstp; + + rstp = p->rstp; + VLOG_DBG("%s, port %u: set RSTP port state %s -> %s", rstp->name, + p->port_number, + rstp_state_name(p->rstp_state), rstp_state_name(state)); + + if (state != p->rstp_state && !p->state_changed) { + p->state_changed = true; + seq_change(connectivity_seq_get()); + } + p->rstp_state = state; +} + +/* Adds a RSTP port. */ +struct rstp_port * +rstp_add_port(struct rstp *rstp) { + struct rstp_port *p = xzalloc(sizeof *p); + + ovs_mutex_lock(&mutex); + p->rstp = rstp; + rstp_initialize_port(p); + rstp_port_set_state(p, RSTP_DISCARDING); + list_push_back(&rstp->ports, &p->node); + rstp->ports_count++; + rstp->changes = true; + move_rstp(rstp); + ovs_mutex_unlock(&mutex); + VLOG_DBG("%s: added port "RSTP_PORT_ID_FMT"", rstp->name, p->port_id); + return p; +} + +/* Deletes a RSTP port. */ +void +rstp_delete_port(struct rstp_port *p) { + struct rstp *rstp; + + ovs_mutex_lock(&mutex); + rstp = p->rstp; + rstp_port_set_state(p, RSTP_DISABLED); + list_remove(&p->node); + rstp->ports_count--; + VLOG_DBG("%s: removed port "RSTP_PORT_ID_FMT"", rstp->name, p->port_id); + free(p); + ovs_mutex_unlock(&mutex); +} + +/* Sets the port Admin Edge parameter. */ +void +rstp_port_set_admin_edge(struct rstp_port *rstp_port, bool new_admin_edge) +{ + struct rstp *rstp; + + rstp = rstp_port->rstp; + if (rstp_port->admin_edge != new_admin_edge) { + VLOG_DBG("%s, port %u: set RSTP Admin Edge to %d", rstp->name, + rstp_port->port_number, new_admin_edge); + ovs_mutex_lock(&mutex); + rstp_port->admin_edge = new_admin_edge; + ovs_mutex_unlock(&mutex); + } +} + +/* Sets the port Auto Edge parameter. */ +void +rstp_port_set_auto_edge(struct rstp_port *rstp_port, bool new_auto_edge) +{ + struct rstp *rstp; + + rstp = rstp_port->rstp; + if (rstp_port->auto_edge != new_auto_edge) { + VLOG_DBG("%s, port %u: set RSTP Auto Edge to %d", rstp->name, + rstp_port->port_number, new_auto_edge); + ovs_mutex_lock(&mutex); + rstp_port->auto_edge = new_auto_edge; + ovs_mutex_unlock(&mutex); + } +} + +/* Sets the port mcheck parameter. + * [17.19.13] May be set by management to force the Port Protocol Migration + * state machine to transmit RST BPDUs for a MigrateTime (17.13.9) period, to + * test whether all STP Bridges (17.4) on the attached LAN have been removed + * and the Port can continue to transmit RSTP BPDUs. Setting mcheck has no + * effect if stpVersion (17.20.12) is TRUE, i.e., the Bridge is operating in + * STP Compatibility. mode. + */ +void +rstp_port_set_mcheck(struct rstp_port *rstp_port, bool new_mcheck) +{ + struct rstp *rstp; + + ovs_mutex_lock(&mutex); + rstp = rstp_port->rstp; + if (new_mcheck == true && rstp_port->rstp->force_protocol_version >= 2) { + rstp_port->mcheck = true; + } + ovs_mutex_unlock(&mutex); + VLOG_DBG("%s, port %u: set RSTP mcheck to %d", rstp->name, + rstp_port->port_number, new_mcheck); +} + +/* Returns the designated bridge id. */ +rstp_identifier +rstp_get_designated_id(const struct rstp *rstp) +{ + rstp_identifier designated_id; + + ovs_mutex_lock(&mutex); + designated_id = rstp->root_priority.designated_bridge_id; + ovs_mutex_unlock(&mutex); + return designated_id; +} + +/* Returns the root bridge id. */ +rstp_identifier +rstp_get_root_id(const struct rstp *rstp) +{ + rstp_identifier root_id; + + ovs_mutex_lock(&mutex); + root_id = rstp->root_priority.root_bridge_id; + ovs_mutex_unlock(&mutex); + return root_id; +} + +/* Returns the designated port id. */ +uint16_t +rstp_get_designated_port_id(const struct rstp *rstp) +{ + uint16_t designated_port_id; + + ovs_mutex_lock(&mutex); + designated_port_id = rstp->root_priority.designated_port_id; + ovs_mutex_unlock(&mutex); + return designated_port_id; +} + +/* Return the bridge port id. */ +uint16_t +rstp_get_bridge_port_id(const struct rstp *rstp) +{ + uint16_t bridge_port_id; + + ovs_mutex_lock(&mutex); + bridge_port_id = rstp->root_priority.bridge_port_id; + ovs_mutex_unlock(&mutex); + return bridge_port_id; +} + +/* Returns true if the bridge believes to the be root of the spanning tree, + * false otherwise. + */ +bool +rstp_is_root_bridge(const struct rstp *rstp) +{ + bool is_root; + + ovs_mutex_lock(&mutex); + is_root = rstp->bridge_identifier == + rstp->root_priority.designated_bridge_id; + ovs_mutex_unlock(&mutex); + return is_root; +} + +/* Returns the bridge ID of the bridge currently believed to be the root. */ +rstp_identifier +rstp_get_designated_root(const struct rstp *rstp) +{ + rstp_identifier designated_root; + + ovs_mutex_lock(&mutex); + designated_root = rstp->root_priority.designated_bridge_id; + ovs_mutex_unlock(&mutex); + return designated_root; +} + +/* Returns the port connecting 'rstp' to the root bridge, or a null pointer if + * there is no such port. + */ +struct rstp_port * +rstp_get_root_port(struct rstp *rstp) +{ + struct rstp_port *p; + + ovs_mutex_lock(&mutex); + if (rstp->ports_count > 0){ + LIST_FOR_EACH (p, node, &rstp->ports) { + if (p->port_id == rstp->root_port_id) { + ovs_mutex_unlock(&mutex); + return p; + } + } + } + ovs_mutex_unlock(&mutex); + return NULL; +} + +/* Returns the port ID for 'p'. */ +uint16_t +rstp_port_get_id(const struct rstp_port *p) +{ + uint16_t port_id; + + ovs_mutex_lock(&mutex); + port_id = p->port_id; + ovs_mutex_unlock(&mutex); + return port_id; +} + +/* Returns the state of port 'p'. */ +enum rstp_state +rstp_port_get_state(const struct rstp_port *p) +{ + enum rstp_state state; + + ovs_mutex_lock(&mutex); + state = p->rstp_state; + ovs_mutex_unlock(&mutex); + return state; +} + +/* Returns the role of port 'p'. */ +enum rstp_port_role +rstp_port_get_role(const struct rstp_port *p) +{ + enum rstp_port_role role; + + ovs_mutex_lock(&mutex); + role = p->role; + ovs_mutex_unlock(&mutex); + return role; +} + +/* Retrieves BPDU transmit and receive counts for 'p'. */ +void +rstp_port_get_counts(const struct rstp_port *p, + int *tx_count, int *rx_count, int *error_count, int *uptime) +{ + ovs_mutex_lock(&mutex); + *tx_count = p->tx_count; + *rx_count = p->rx_rstp_bpdu_cnt; + *error_count = p->error_count; + *uptime = p->uptime; + ovs_mutex_unlock(&mutex); +} + +void +rstp_port_set_aux(struct rstp_port *p, void *aux) +{ + ovs_mutex_lock(&mutex); + p->aux = aux; + ovs_mutex_unlock(&mutex); +} + +void * +rstp_port_get_aux(struct rstp_port *p) +{ + void *aux; + + ovs_mutex_lock(&mutex); + aux = p->aux; + ovs_mutex_unlock(&mutex); + return aux; +} + +/* Returns true if 'state' is one in which BPDU packets should be received + * and transmitted on a port, false otherwise. + */ + bool + rstp_should_manage_bpdu(enum rstp_state state) + { + return (state == RSTP_DISCARDING || state == RSTP_LEARNING || + state == RSTP_FORWARDING); + } + +/* Returns true if 'state' is one in which packets received on a port should + * be forwarded, false otherwise. + * + * Returns true if 'state' is RSTP_DISABLED, since presumably in that case the + * port should still work, just not have RSTP applied to it. + */ +bool +rstp_forward_in_state(enum rstp_state state) +{ + return (state == RSTP_DISABLED || state == RSTP_FORWARDING); +} + +/* Returns true if 'state' is one in which MAC learning should be done on + * packets received on a port, false otherwise. + * + * Returns true if 'state' is RSTP_DISABLED, since presumably in that case the + * port should still work, just not have RSTP applied to it. */ +bool +rstp_learn_in_state(enum rstp_state state) +{ + return (state == RSTP_DISABLED || state == RSTP_LEARNING || + state == RSTP_FORWARDING); +} + +/* Unixctl. */ +static struct rstp * +rstp_find(const char *name) OVS_REQUIRES(mutex) +{ + struct rstp *rstp; + + LIST_FOR_EACH (rstp, node, all_rstps) { + if (!strcmp(rstp->name, name)) { + return rstp; + } + } + return NULL; +} + +static void +rstp_unixctl_tcn(struct unixctl_conn *conn, int argc, + const char *argv[], void *aux OVS_UNUSED) +{ + ovs_mutex_lock(&mutex); + if (argc > 1) { + struct rstp *rstp = rstp_find(argv[1]); + if (!rstp) { + unixctl_command_reply_error(conn, "No such RSTP object"); + goto out; + } + rstp->changes = true; + move_rstp(rstp); + } else { + struct rstp *rstp; + LIST_FOR_EACH (rstp, node, all_rstps) { + rstp->changes = true; + move_rstp(rstp); + } + } + unixctl_command_reply(conn, "OK"); + +out: + ovs_mutex_unlock(&mutex); +} diff --git a/lib/rstp.h b/lib/rstp.h new file mode 100644 index 000000000..916521a46 --- /dev/null +++ b/lib/rstp.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2011-2014 M3S, Srl - Italy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Rapid Spanning Tree Protocol (IEEE 802.1D-2004) public interface (header + * file). + * + * Authors: + * Martino Fornasa + * Daniele Venturino + * + * References to IEEE 802.1D-2004 standard are enclosed in square brackets. + * E.g. [17.3], [Table 17-1], etc. + * + */ + +#ifndef RSTP_H +#define RSTP_H 1 + +#include +#include +#include "compiler.h" +#include "util.h" + +#define RSTP_MAX_PORTS 4095 + +struct ofpbuf; + +/* Bridge priority defaults [Table 17-2] */ +#define RSTP_MIN_PRIORITY 0 +#define RSTP_MAX_PRIORITY 61440 +#define RSTP_PRIORITY_STEP 4096 +#define RSTP_DEFAULT_PRIORITY 32768 + +/* Port priority defaults [Table 17-2] */ +#define RSTP_MIN_PORT_PRIORITY 0 +#define RSTP_MAX_PORT_PRIORITY 240 +#define RSTP_STEP_PORT_PRIORITY 16 +#define RSTP_DEFAULT_PORT_PRIORITY 128 + +/* Performance parameters defaults. [Table 7-5] and [Table 17-1] + * These values are expressed in seconds. + */ +#define RSTP_DEFAULT_AGEING_TIME 300 +#define RSTP_MIN_AGEING_TIME 10 +#define RSTP_MAX_AGEING_TIME 1000000 + +#define RSTP_DEFAULT_BRIDGE_MAX_AGE 20 +#define RSTP_MIN_BRIDGE_MAX_AGE 6 +#define RSTP_MAX_BRIDGE_MAX_AGE 40 + +#define RSTP_DEFAULT_BRIDGE_FORWARD_DELAY 15 +#define RSTP_MIN_BRIDGE_FORWARD_DELAY 4 +#define RSTP_MAX_BRIDGE_FORWARD_DELAY 30 + +#define RSTP_DEFAULT_TRANSMIT_HOLD_COUNT 6 +#define RSTP_MIN_TRANSMIT_HOLD_COUNT 1 +#define RSTP_MAX_TRANSMIT_HOLD_COUNT 10 + +#define RSTP_BRIDGE_HELLO_TIME 2 /* Value is fixed [Table 17-1] */ + +#define RSTP_MIGRATE_TIME 3 /* Value is fixed [Table 17-1] */ + +/* Port path cost [Table 17-3] */ +#define RSTP_MIN_PORT_PATH_COST 1 +#define RSTP_MAX_PORT_PATH_COST 200000000 +#define RSTP_DEFAULT_PORT_PATH_COST 200000 + +/* RSTP Bridge identifier [9.2.5]. Top four most significant bits are a + * priority value. The next most significant twelve bits are a locally + * assigned system ID extension. Bottom 48 bits are MAC address of bridge. + */ +typedef uint64_t rstp_identifier; + +#define RSTP_ID_FMT "%01"PRIx8".%03"PRIx16".%012"PRIx64 +#define RSTP_ID_ARGS(rstp_id) \ + (uint8_t)((rstp_id) >> 60), \ + (uint16_t)(((rstp_id) & 0x0fff000000000000ULL) >> 48), \ + (uint64_t)((rstp_id) & 0xffffffffffffULL) + +#define RSTP_PORT_ID_FMT "%04"PRIx16 + +enum rstp_state { + RSTP_DISABLED, + RSTP_LEARNING, + RSTP_FORWARDING, + RSTP_DISCARDING +}; + +/* Force Protocol Version [17.13.4] */ +enum rstp_force_protocol_version { + FPV_STP_COMPATIBILITY = 0, + FPV_DEFAULT = 2 +}; + +enum rstp_port_role { + ROLE_ROOT, + ROLE_DESIGNATED, + ROLE_ALTERNATE, + ROLE_BACKUP, + ROLE_DISABLED +}; + +struct rstp; +struct rstp_port; +struct ofproto_rstp_settings; + +const char *rstp_state_name(enum rstp_state); +bool rstp_forward_in_state(enum rstp_state); +bool rstp_learn_in_state(enum rstp_state); +bool rstp_should_manage_bpdu(enum rstp_state state); +const char *rstp_port_role_name(enum rstp_port_role); + +void rstp_init(void); + +struct rstp * rstp_create(const char *, rstp_identifier bridge_id, + void (*send_bpdu)(struct ofpbuf *, int port_no, void *), + void *); +struct rstp *rstp_ref(struct rstp *); +void rstp_unref(struct rstp *); + +/* Functions used outside RSTP, to call functions defined in + rstp-state-machines.h */ +void rstp_tick_timers(struct rstp *); +void rstp_received_bpdu(struct rstp_port *, const void *, size_t); + +bool rstp_check_and_reset_fdb_flush(struct rstp *); +bool rstp_get_changed_port(struct rstp *, struct rstp_port **); +void rstp_port_set_mac_operational(struct rstp_port *, + bool new_mac_operational); +bool rstp_port_get_mac_operational(struct rstp_port *); + +/* Bridge setters */ +void rstp_set_bridge_address(struct rstp *, rstp_identifier bridge_address); +void rstp_set_bridge_priority(struct rstp *, int new_priority); +void rstp_set_bridge_ageing_time(struct rstp *, int new_ageing_time); +void rstp_set_bridge_force_protocol_version(struct rstp *, + enum rstp_force_protocol_version new_force_protocol_version); +void rstp_set_bridge_hello_time(struct rstp *); +void rstp_set_bridge_max_age(struct rstp *, int new_max_age); +void rstp_set_bridge_forward_delay(struct rstp *, int new_forward_delay); +void rstp_set_bridge_transmit_hold_count(struct rstp *, + int new_transmit_hold_count); +void rstp_set_bridge_migrate_time(struct rstp *); +void rstp_set_bridge_times(struct rstp *, int new_forward_delay, + int new_hello_time, int new_max_age, + int new_message_age); + +struct rstp_port * rstp_add_port(struct rstp *); +void reinitialize_port(struct rstp_port *p); +void rstp_delete_port(struct rstp_port *); +/* Port setters */ +void rstp_port_set_priority(struct rstp_port *, int new_port_priority); +void rstp_port_set_port_number(struct rstp_port *, uint16_t new_port_number); +uint32_t rstp_convert_speed_to_cost(unsigned int speed); +void rstp_port_set_path_cost(struct rstp_port *, uint32_t new_port_path_cost); +void rstp_port_set_admin_edge(struct rstp_port *, bool new_admin_edge); +void rstp_port_set_auto_edge(struct rstp_port *, bool new_auto_edge); +void rstp_port_set_state(struct rstp_port *, enum rstp_state new_state); +void rstp_port_set_aux(struct rstp_port *, void *aux); +void rstp_port_set_administrative_bridge_port(struct rstp_port *, uint8_t); +void rstp_port_set_oper_point_to_point_mac(struct rstp_port *, uint8_t); +void rstp_port_set_mcheck(struct rstp_port *, bool new_mcheck); + +/* Bridge getters */ +const char * rstp_get_name(const struct rstp *); +rstp_identifier rstp_get_root_id(const struct rstp *); +rstp_identifier rstp_get_bridge_id(const struct rstp *); +rstp_identifier rstp_get_designated_id(const struct rstp *); +uint32_t rstp_get_root_path_cost(const struct rstp *); +uint16_t rstp_get_designated_port_id(const struct rstp *); +uint16_t rstp_get_bridge_port_id(const struct rstp *); +struct rstp_port * rstp_get_root_port(struct rstp *); +rstp_identifier rstp_get_designated_root(const struct rstp *); +bool rstp_is_root_bridge(const struct rstp *); + +/* Port getters */ +int rstp_port_number(const struct rstp_port *); +struct rstp_port *rstp_get_port(struct rstp *, int port_no); +uint16_t rstp_port_get_id(const struct rstp_port *); +enum rstp_state rstp_port_get_state(const struct rstp_port *); +enum rstp_port_role rstp_port_get_role(const struct rstp_port *); +void rstp_port_get_counts(const struct rstp_port *, int *tx_count, + int *rx_count, int *error_count, int *uptime); +void * rstp_port_get_aux(struct rstp_port *); +#endif /* rstp.h */ diff --git a/lib/stp.h b/lib/stp.h index cdc24d4ac..3d13bbaea 100644 --- a/lib/stp.h +++ b/lib/stp.h @@ -27,11 +27,6 @@ struct ofpbuf; -/* LLC field values used for STP frames. */ -#define STP_LLC_SSAP 0x42 -#define STP_LLC_DSAP 0x42 -#define STP_LLC_CNTL 0x03 - /* Bridge and port priorities that should be used by default. */ #define STP_DEFAULT_BRIDGE_PRIORITY 32768 #define STP_DEFAULT_PORT_PRIORITY 128 diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 6e33a2757..01db80480 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -86,6 +86,7 @@ struct xbridge { struct dpif_ipfix *ipfix; /* Ipfix handle, or null. */ struct netflow *netflow; /* Netflow handle, or null. */ struct stp *stp; /* STP or null if disabled. */ + struct rstp *rstp; /* RSTP or null if disabled. */ /* Special rules installed by ofproto-dpif. */ struct rule_dpif *miss_rule; @@ -149,6 +150,7 @@ struct xport { enum ofputil_port_config config; /* OpenFlow port configuration. */ enum ofputil_port_state state; /* OpenFlow port state. */ int stp_port_no; /* STP port number or -1 if not in use. */ + int rstp_port_no; /* RSTP port number or -1 if not in use. */ struct hmap skb_priorities; /* Map of 'skb_priority_to_dscp's. */ @@ -353,17 +355,15 @@ static struct xc_entry *xlate_cache_add_entry(struct xlate_cache *xc, static void xlate_xbridge_init(struct xlate_cfg *, struct xbridge *); static void xlate_xbundle_init(struct xlate_cfg *, struct xbundle *); static void xlate_xport_init(struct xlate_cfg *, struct xport *); -static void xlate_xbridge_set(struct xbridge *xbridge, - struct dpif *dpif, +static void xlate_xbridge_set(struct xbridge *, struct dpif *, struct rule_dpif *miss_rule, struct rule_dpif *no_packet_in_rule, - const struct mac_learning *ml, struct stp *stp, - const struct mcast_snooping *ms, - const struct mbridge *mbridge, - const struct dpif_sflow *sflow, - const struct dpif_ipfix *ipfix, - const struct netflow *netflow, - enum ofp_config_flags frag, + const struct mac_learning *, struct stp *, + struct rstp *, const struct mcast_snooping *, + const struct mbridge *, + const struct dpif_sflow *, + const struct dpif_ipfix *, + const struct netflow *, enum ofp_config_flags, bool forward_bpdu, bool has_in_band, bool enable_recirc, bool variable_length_userdata, @@ -376,6 +376,7 @@ static void xlate_xbundle_set(struct xbundle *xbundle, static void xlate_xport_set(struct xport *xport, odp_port_t odp_port, const struct netdev *netdev, const struct cfm *cfm, const struct bfd *bfd, int stp_port_no, + int rstp_port_no, enum ofputil_port_config config, enum ofputil_port_state state, bool is_tunnel, bool may_enable); @@ -423,7 +424,7 @@ xlate_xbridge_set(struct xbridge *xbridge, struct rule_dpif *miss_rule, struct rule_dpif *no_packet_in_rule, const struct mac_learning *ml, struct stp *stp, - const struct mcast_snooping *ms, + struct rstp *rstp, const struct mcast_snooping *ms, const struct mbridge *mbridge, const struct dpif_sflow *sflow, const struct dpif_ipfix *ipfix, @@ -463,6 +464,11 @@ xlate_xbridge_set(struct xbridge *xbridge, xbridge->stp = stp_ref(stp); } + if (xbridge->rstp != rstp) { + rstp_unref(xbridge->rstp); + xbridge->rstp = rstp_ref(rstp); + } + if (xbridge->netflow != netflow) { netflow_unref(xbridge->netflow); xbridge->netflow = netflow_ref(netflow); @@ -508,13 +514,14 @@ xlate_xbundle_set(struct xbundle *xbundle, static void xlate_xport_set(struct xport *xport, odp_port_t odp_port, const struct netdev *netdev, const struct cfm *cfm, - const struct bfd *bfd, int stp_port_no, + const struct bfd *bfd, int stp_port_no, int rstp_port_no, enum ofputil_port_config config, enum ofputil_port_state state, bool is_tunnel, bool may_enable) { xport->config = config; xport->state = state; xport->stp_port_no = stp_port_no; + xport->rstp_port_no = rstp_port_no; xport->is_tunnel = is_tunnel; xport->may_enable = may_enable; xport->odp_port = odp_port; @@ -548,10 +555,11 @@ xlate_xbridge_copy(struct xbridge *xbridge) xlate_xbridge_set(new_xbridge, xbridge->dpif, xbridge->miss_rule, xbridge->no_packet_in_rule, xbridge->ml, xbridge->stp, - xbridge->ms, xbridge->mbridge, xbridge->sflow, - xbridge->ipfix, xbridge->netflow, xbridge->frag, - xbridge->forward_bpdu, xbridge->has_in_band, - xbridge->enable_recirc, xbridge->variable_length_userdata, + xbridge->rstp, xbridge->ms, xbridge->mbridge, + xbridge->sflow, xbridge->ipfix, xbridge->netflow, + xbridge->frag, xbridge->forward_bpdu, + xbridge->has_in_band, xbridge->enable_recirc, + xbridge->variable_length_userdata, xbridge->max_mpls_depth); LIST_FOR_EACH (xbundle, list_node, &xbridge->xbundles) { xlate_xbundle_copy(new_xbridge, xbundle); @@ -596,8 +604,9 @@ xlate_xport_copy(struct xbridge *xbridge, struct xbundle *xbundle, xlate_xport_init(new_xcfg, new_xport); xlate_xport_set(new_xport, xport->odp_port, xport->netdev, xport->cfm, - xport->bfd, xport->stp_port_no, xport->config, xport->state, - xport->is_tunnel, xport->may_enable); + xport->bfd, xport->stp_port_no, xport->rstp_port_no, + xport->config, xport->state, xport->is_tunnel, + xport->may_enable); if (xport->peer) { struct xport *peer = xport_lookup(new_xcfg, xport->peer->ofport); @@ -698,15 +707,13 @@ xlate_ofproto_set(struct ofproto_dpif *ofproto, const char *name, struct dpif *dpif, struct rule_dpif *miss_rule, struct rule_dpif *no_packet_in_rule, const struct mac_learning *ml, struct stp *stp, - const struct mcast_snooping *ms, + struct rstp *rstp, const struct mcast_snooping *ms, const struct mbridge *mbridge, const struct dpif_sflow *sflow, const struct dpif_ipfix *ipfix, const struct netflow *netflow, enum ofp_config_flags frag, - bool forward_bpdu, bool has_in_band, - bool enable_recirc, - bool variable_length_userdata, - size_t max_mpls_depth) + bool forward_bpdu, bool has_in_band, bool enable_recirc, + bool variable_length_userdata, size_t max_mpls_depth) { struct xbridge *xbridge; @@ -724,9 +731,9 @@ xlate_ofproto_set(struct ofproto_dpif *ofproto, const char *name, xbridge->name = xstrdup(name); xlate_xbridge_set(xbridge, dpif, miss_rule, no_packet_in_rule, ml, stp, - ms, mbridge, sflow, ipfix, netflow, frag, forward_bpdu, - has_in_band, enable_recirc, variable_length_userdata, - max_mpls_depth); + rstp, ms, mbridge, sflow, ipfix, netflow, frag, + forward_bpdu, has_in_band, enable_recirc, + variable_length_userdata, max_mpls_depth); } static void @@ -754,6 +761,7 @@ xlate_xbridge_remove(struct xlate_cfg *xcfg, struct xbridge *xbridge) dpif_sflow_unref(xbridge->sflow); dpif_ipfix_unref(xbridge->ipfix); stp_unref(xbridge->stp); + rstp_unref(xbridge->rstp); hmap_destroy(&xbridge->xports); free(xbridge->name); free(xbridge); @@ -835,7 +843,7 @@ xlate_ofport_set(struct ofproto_dpif *ofproto, struct ofbundle *ofbundle, struct ofport_dpif *ofport, ofp_port_t ofp_port, odp_port_t odp_port, const struct netdev *netdev, const struct cfm *cfm, const struct bfd *bfd, - struct ofport_dpif *peer, int stp_port_no, + struct ofport_dpif *peer, int stp_port_no, int rstp_port_no, const struct ofproto_port_queue *qdscp_list, size_t n_qdscp, enum ofputil_port_config config, enum ofputil_port_state state, bool is_tunnel, @@ -858,8 +866,8 @@ xlate_ofport_set(struct ofproto_dpif *ofproto, struct ofbundle *ofbundle, ovs_assert(xport->ofp_port == ofp_port); - xlate_xport_set(xport, odp_port, netdev, cfm, bfd, stp_port_no, config, - state, is_tunnel, may_enable); + xlate_xport_set(xport, odp_port, netdev, cfm, bfd, stp_port_no, + rstp_port_no, config, state, is_tunnel, may_enable); if (xport->peer) { xport->peer->peer = NULL; @@ -1148,6 +1156,58 @@ stp_process_packet(const struct xport *xport, const struct ofpbuf *packet) } } +static struct rstp_port * +xport_get_rstp_port(const struct xport *xport) +{ + return xport->xbridge->rstp && xport->rstp_port_no != -1 + ? rstp_get_port(xport->xbridge->rstp, xport->rstp_port_no) + : NULL; +} + +static bool +xport_rstp_learn_state(const struct xport *xport) +{ + struct rstp_port *rp = xport_get_rstp_port(xport); + return !rp || rstp_learn_in_state(rstp_port_get_state(rp)); +} + +static bool +xport_rstp_forward_state(const struct xport *xport) +{ + struct rstp_port *rp = xport_get_rstp_port(xport); + return !rp || rstp_forward_in_state(rstp_port_get_state(rp)); +} + +static bool +xport_rstp_should_manage_bpdu(const struct xport *xport) +{ + struct rstp_port *rp = xport_get_rstp_port(xport); + return rp && rstp_should_manage_bpdu(rstp_port_get_state(rp)); +} + +static void +rstp_process_packet(const struct xport *xport, const struct ofpbuf *packet) +{ + struct rstp_port *rp = xport_get_rstp_port(xport); + struct ofpbuf payload = *packet; + struct eth_header *eth = ofpbuf_data(&payload); + + /* Sink packets on ports that have RSTP disabled when the bridge has + * RSTP enabled. */ + if (!rp || rstp_port_get_state(rp) == RSTP_DISABLED) { + return; + } + + /* Trim off padding on payload. */ + if (ofpbuf_size(&payload) > ntohs(eth->eth_type) + ETH_HEADER_LEN) { + ofpbuf_set_size(&payload, ntohs(eth->eth_type) + ETH_HEADER_LEN); + } + + if (ofpbuf_try_pull(&payload, ETH_HEADER_LEN + LLC_HEADER_LEN)) { + rstp_received_bpdu(rp, ofpbuf_data(&payload), ofpbuf_size(&payload)); + } +} + static struct xport * get_ofp_port(const struct xbridge *xbridge, ofp_port_t ofp_port) { @@ -2382,9 +2442,11 @@ process_special(struct xlate_ctx *ctx, const struct flow *flow, lacp_process_packet(xport->xbundle->lacp, xport->ofport, packet); } return SLOW_LACP; - } else if (xbridge->stp && stp_should_process_flow(flow, wc)) { + } else if ((xbridge->stp || xbridge->rstp) && + stp_should_process_flow(flow, wc)) { if (packet) { - stp_process_packet(xport, packet); + xbridge->stp ? stp_process_packet(xport, packet) : + rstp_process_packet(xport, packet); } return SLOW_STP; } else { @@ -2417,14 +2479,26 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port, return; } else if (check_stp) { if (is_stp(&ctx->base_flow)) { - if (!xport_stp_should_forward_bpdu(xport)) { - xlate_report(ctx, "STP not in listening state, " - "skipping bpdu output"); + if (!xport_stp_should_forward_bpdu(xport) && + !xport_rstp_should_manage_bpdu(xport)) { + if (ctx->xbridge->stp != NULL) { + xlate_report(ctx, "STP not in listening state, " + "skipping bpdu output"); + } else if (ctx->xbridge->rstp != NULL) { + xlate_report(ctx, "RSTP not managing BPDU in this state, " + "skipping bpdu output"); + } return; } - } else if (!xport_stp_forward_state(xport)) { - xlate_report(ctx, "STP not in forwarding state, " - "skipping output"); + } else if (!xport_stp_forward_state(xport) || + !xport_rstp_forward_state(xport)) { + if (ctx->xbridge->stp != NULL) { + xlate_report(ctx, "STP not in forwarding state, " + "skipping output"); + } else if (ctx->xbridge->rstp != NULL) { + xlate_report(ctx, "RSTP not in forwarding state, " + "skipping output"); + } return; } } @@ -2450,11 +2524,11 @@ compose_output_action__(struct xlate_ctx *ctx, ofp_port_t ofp_port, if (special) { ctx->xout->slow |= special; } else if (may_receive(peer, ctx)) { - if (xport_stp_forward_state(peer)) { + if (xport_stp_forward_state(peer) && xport_rstp_forward_state(peer)) { xlate_table_action(ctx, flow->in_port.ofp_port, 0, true, true); } else { - /* Forwarding is disabled by STP. Let OFPP_NORMAL and the - * learning action look at the packet, then drop it. */ + /* Forwarding is disabled by STP and RSTP. Let OFPP_NORMAL and + * the learning action look at the packet, then drop it. */ struct flow old_base_flow = ctx->base_flow; size_t old_size = ofpbuf_size(ctx->xout->odp_actions); mirror_mask_t old_mirrors = ctx->xout->mirrors; @@ -3428,7 +3502,8 @@ may_receive(const struct xport *xport, struct xlate_ctx *ctx) * disabled. If just learning is enabled, we need to have * OFPP_NORMAL and the learning action have a look at the packet * before we can drop it. */ - if (!xport_stp_forward_state(xport) && !xport_stp_learn_state(xport)) { + if ((!xport_stp_forward_state(xport) && !xport_stp_learn_state(xport)) || + (!xport_rstp_forward_state(xport) && !xport_rstp_learn_state(xport))) { return false; } @@ -4150,7 +4225,8 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout) /* We've let OFPP_NORMAL and the learning action look at the * packet, so drop it now if forwarding is disabled. */ - if (in_port && !xport_stp_forward_state(in_port)) { + if (in_port && (!xport_stp_forward_state(in_port) || + !xport_rstp_forward_state(in_port))) { ofpbuf_set_size(ctx.xout->odp_actions, sample_actions_len); } } diff --git a/ofproto/ofproto-dpif-xlate.h b/ofproto/ofproto-dpif-xlate.h index b0dfc188a..1ba5a065a 100644 --- a/ofproto/ofproto-dpif-xlate.h +++ b/ofproto/ofproto-dpif-xlate.h @@ -147,7 +147,7 @@ void xlate_ofproto_set(struct ofproto_dpif *, const char *name, struct dpif *, struct rule_dpif *miss_rule, struct rule_dpif *no_packet_in_rule, const struct mac_learning *, struct stp *, - const struct mcast_snooping *, + struct rstp *, const struct mcast_snooping *, const struct mbridge *, const struct dpif_sflow *, const struct dpif_ipfix *, const struct netflow *, enum ofp_config_flags, bool forward_bpdu, @@ -167,7 +167,8 @@ void xlate_ofport_set(struct ofproto_dpif *, struct ofbundle *, struct ofport_dpif *, ofp_port_t, odp_port_t, const struct netdev *, const struct cfm *, const struct bfd *, struct ofport_dpif *peer, - int stp_port_no, const struct ofproto_port_queue *qdscp, + int stp_port_no, int rstp_port_no, + const struct ofproto_port_queue *qdscp, size_t n_qdscp, enum ofputil_port_config, enum ofputil_port_state, bool is_tunnel, bool may_enable); diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index a635ca6ed..bff0ff4ba 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -145,6 +145,10 @@ static void stp_wait(struct ofproto_dpif *ofproto); static int set_stp_port(struct ofport *, const struct ofproto_port_stp_settings *); +static void rstp_run(struct ofproto_dpif *ofproto); +static void set_rstp_port(struct ofport *, + const struct ofproto_port_rstp_settings *); + struct ofport_dpif { struct hmap_node odp_port_node; /* In dpif_backer's "odp_to_ofport_map". */ struct ofport up; @@ -165,6 +169,10 @@ struct ofport_dpif { enum stp_state stp_state; /* Always STP_DISABLED if STP not in use. */ long long int stp_state_entered; + /* Rapid Spanning Tree. */ + struct rstp_port *rstp_port; /* Rapid Spanning Tree Protocol, if any. */ + enum rstp_state rstp_state; /* Always RSTP_DISABLED if RSTP not in use. */ + /* Queue to DSCP mapping. */ struct ofproto_port_queue *qdscp; size_t n_qdscp; @@ -224,6 +232,7 @@ static void ofport_update_peer(struct ofport_dpif *); enum revalidate_reason { REV_RECONFIGURE = 1, /* Switch configuration changed. */ REV_STP, /* Spanning tree protocol port status change. */ + REV_RSTP, /* RSTP port status change. */ REV_BOND, /* Bonding changed. */ REV_PORT_TOGGLED, /* Port enabled or disabled by CFM, LACP, ...*/ REV_FLOW_TABLE, /* Flow table changed. */ @@ -232,6 +241,7 @@ enum revalidate_reason { }; COVERAGE_DEFINE(rev_reconfigure); COVERAGE_DEFINE(rev_stp); +COVERAGE_DEFINE(rev_rstp); COVERAGE_DEFINE(rev_bond); COVERAGE_DEFINE(rev_port_toggled); COVERAGE_DEFINE(rev_flow_table); @@ -302,6 +312,10 @@ struct ofproto_dpif { struct stp *stp; long long int stp_last_tick; + /* Rapid Spanning Tree. */ + struct rstp *rstp; + long long int rstp_last_tick; + /* VLAN splinters. */ struct ovs_mutex vsp_mutex; struct hmap realdev_vid_map OVS_GUARDED; /* (realdev,vid) -> vlandev. */ @@ -575,6 +589,7 @@ type_run(const char *type) switch (backer->need_revalidate) { case REV_RECONFIGURE: COVERAGE_INC(rev_reconfigure); break; case REV_STP: COVERAGE_INC(rev_stp); break; + case REV_RSTP: COVERAGE_INC(rev_rstp); break; case REV_BOND: COVERAGE_INC(rev_bond); break; case REV_PORT_TOGGLED: COVERAGE_INC(rev_port_toggled); break; case REV_FLOW_TABLE: COVERAGE_INC(rev_flow_table); break; @@ -595,8 +610,8 @@ type_run(const char *type) xlate_ofproto_set(ofproto, ofproto->up.name, ofproto->backer->dpif, ofproto->miss_rule, ofproto->no_packet_in_rule, ofproto->ml, - ofproto->stp, ofproto->ms, ofproto->mbridge, - ofproto->sflow, ofproto->ipfix, + ofproto->stp, ofproto->rstp, ofproto->ms, + ofproto->mbridge, ofproto->sflow, ofproto->ipfix, ofproto->netflow, ofproto->up.frag_handling, ofproto->up.forward_bpdu, connmgr_has_in_band(ofproto->up.connmgr), @@ -616,11 +631,14 @@ type_run(const char *type) int stp_port = ofport->stp_port ? stp_port_no(ofport->stp_port) : -1; + int rstp_port = ofport->rstp_port + ? rstp_port_number(ofport->rstp_port) + : -1; xlate_ofport_set(ofproto, ofport->bundle, ofport, ofport->up.ofp_port, ofport->odp_port, ofport->up.netdev, ofport->cfm, ofport->bfd, ofport->peer, stp_port, - ofport->qdscp, ofport->n_qdscp, + rstp_port, ofport->qdscp, ofport->n_qdscp, ofport->up.pp.config, ofport->up.pp.state, ofport->is_tunnel, ofport->may_enable); } @@ -1129,6 +1147,7 @@ construct(struct ofproto *ofproto_) ofproto->sflow = NULL; ofproto->ipfix = NULL; ofproto->stp = NULL; + ofproto->rstp = NULL; ofproto->dump_seq = 0; hmap_init(&ofproto->bundles); ofproto->ml = mac_learning_create(MAC_ENTRY_DEFAULT_IDLE_TIME); @@ -1393,6 +1412,7 @@ run(struct ofproto *ofproto_) } stp_run(ofproto); + rstp_run(ofproto); ovs_rwlock_wrlock(&ofproto->ml->rwlock); if (mac_learning_run(ofproto->ml)) { ofproto->backer->need_revalidate = REV_MAC_LEARNING; @@ -1551,6 +1571,8 @@ port_construct(struct ofport *port_) port->may_enable = true; port->stp_port = NULL; port->stp_state = STP_DISABLED; + port->rstp_port = NULL; + port->rstp_state = RSTP_DISABLED; port->is_tunnel = false; port->peer = NULL; port->qdscp = NULL; @@ -1661,6 +1683,9 @@ port_destruct(struct ofport *port_) if (port->stp_port) { stp_port_disable(port->stp_port); } + if (port->rstp_port) { + rstp_delete_port(port->rstp_port); + } if (ofproto->sflow) { dpif_sflow_del_port(ofproto->sflow, port->odp_port); } @@ -1883,6 +1908,31 @@ get_bfd_status(struct ofport *ofport_, struct smap *smap) /* Spanning Tree. */ +static void +rstp_send_bpdu_cb(struct ofpbuf *pkt, int port_num, void *ofproto_) +{ + struct ofproto_dpif *ofproto = ofproto_; + struct rstp_port *rp = rstp_get_port(ofproto->rstp, port_num); + struct ofport_dpif *ofport; + + ofport = rstp_port_get_aux(rp); + if (!ofport) { + VLOG_WARN_RL(&rl, "%s: cannot send BPDU on unknown port %d", + ofproto->up.name, port_num); + } else { + struct eth_header *eth = ofpbuf_l2(pkt); + + netdev_get_etheraddr(ofport->up.netdev, eth->eth_src); + if (eth_addr_is_zero(eth->eth_src)) { + VLOG_WARN_RL(&rl, "%s: cannot send BPDU on port %d " + "with unknown MAC", ofproto->up.name, port_num); + } else { + ofproto_dpif_send_packet(ofport, pkt); + } + } + ofpbuf_delete(pkt); +} + static void send_bpdu_cb(struct ofpbuf *pkt, int port_num, void *ofproto_) { @@ -1908,6 +1958,138 @@ send_bpdu_cb(struct ofpbuf *pkt, int port_num, void *ofproto_) ofpbuf_delete(pkt); } +/* Configure RSTP on 'ofproto_' using the settings defined in 's'. */ +static void +set_rstp(struct ofproto *ofproto_, const struct ofproto_rstp_settings *s) +{ + struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_); + + /* Only revalidate flows if the configuration changed. */ + if (!s != !ofproto->rstp) { + ofproto->backer->need_revalidate = REV_RECONFIGURE; + } + + if (s) { + if (!ofproto->rstp) { + ofproto->rstp = rstp_create(ofproto_->name, s->address, + rstp_send_bpdu_cb, ofproto); + ofproto->rstp_last_tick = time_msec(); + } + rstp_set_bridge_address(ofproto->rstp, s->address); + rstp_set_bridge_priority(ofproto->rstp, s->priority); + rstp_set_bridge_ageing_time(ofproto->rstp, s->ageing_time); + rstp_set_bridge_force_protocol_version(ofproto->rstp, + s->force_protocol_version); + rstp_set_bridge_max_age(ofproto->rstp, s->bridge_max_age); + rstp_set_bridge_forward_delay(ofproto->rstp, s->bridge_forward_delay); + rstp_set_bridge_transmit_hold_count(ofproto->rstp, + s->transmit_hold_count); + } else { + struct ofport *ofport; + HMAP_FOR_EACH (ofport, hmap_node, &ofproto->up.ports) { + set_rstp_port(ofport, NULL); + } + rstp_unref(ofproto->rstp); + ofproto->rstp = NULL; + } +} + +static void +get_rstp_status(struct ofproto *ofproto_, struct ofproto_rstp_status *s) +{ + struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_); + + if (ofproto->rstp) { + s->enabled = true; + s->root_id = rstp_get_root_id(ofproto->rstp); + s->bridge_id = rstp_get_bridge_id(ofproto->rstp); + s->designated_id = rstp_get_designated_id(ofproto->rstp); + s->root_path_cost = rstp_get_root_path_cost(ofproto->rstp); + s->designated_port_id = rstp_get_designated_port_id(ofproto->rstp); + s->bridge_port_id = rstp_get_bridge_port_id(ofproto->rstp); + } else { + s->enabled = false; + } +} + +static void +update_rstp_port_state(struct ofport_dpif *ofport) +{ + struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto); + enum rstp_state state; + + /* Figure out new state. */ + state = ofport->rstp_port ? rstp_port_get_state(ofport->rstp_port) + : RSTP_DISABLED; + + /* Update state. */ + if (ofport->rstp_state != state) { + enum ofputil_port_state of_state; + bool fwd_change; + + VLOG_DBG_RL(&rl, "port %s: RSTP state changed from %s to %s", + netdev_get_name(ofport->up.netdev), + rstp_state_name(ofport->rstp_state), + rstp_state_name(state)); + if (rstp_learn_in_state(ofport->rstp_state) + != rstp_learn_in_state(state)) { + /* xxx Learning action flows should also be flushed. */ + ovs_rwlock_wrlock(&ofproto->ml->rwlock); + mac_learning_flush(ofproto->ml); + ovs_rwlock_unlock(&ofproto->ml->rwlock); + } + fwd_change = rstp_forward_in_state(ofport->rstp_state) + != rstp_forward_in_state(state); + + ofproto->backer->need_revalidate = REV_RSTP; + ofport->rstp_state = state; + + if (fwd_change && ofport->bundle) { + bundle_update(ofport->bundle); + } + + /* Update the RSTP state bits in the OpenFlow port description. */ + of_state = ofport->up.pp.state & ~OFPUTIL_PS_STP_MASK; + of_state |= (state == RSTP_LEARNING ? OFPUTIL_PS_STP_LEARN + : state == RSTP_FORWARDING ? OFPUTIL_PS_STP_FORWARD + : state == RSTP_DISCARDING ? OFPUTIL_PS_STP_LISTEN + : 0); + ofproto_port_set_state(&ofport->up, of_state); + } +} + +static void +rstp_run(struct ofproto_dpif *ofproto) +{ + if (ofproto->rstp) { + long long int now = time_msec(); + long long int elapsed = now - ofproto->rstp_last_tick; + struct rstp_port *rp; + /* Every second, decrease the values of the timers. */ + if (elapsed >= 1000) { + rstp_tick_timers(ofproto->rstp); + ofproto->rstp_last_tick = now; + } + while (rstp_get_changed_port(ofproto->rstp, &rp)) { + struct ofport_dpif *ofport = rstp_port_get_aux(rp); + if (ofport) { + update_rstp_port_state(ofport); + } + } + /* FIXME: This check should be done on-event (i.e., when setting + * p->fdb_flush) and not periodically. + */ + if (rstp_check_and_reset_fdb_flush(ofproto->rstp)) { + ovs_rwlock_wrlock(&ofproto->ml->rwlock); + /* FIXME: RSTP should be able to flush the entries pertaining to a + * single port, not the whole table. + */ + mac_learning_flush(ofproto->ml); + ovs_rwlock_unlock(&ofproto->ml->rwlock); + } + } +} + /* Configures STP on 'ofproto_' using the settings defined in 's'. */ static int set_stp(struct ofproto *ofproto_, const struct ofproto_stp_settings *s) @@ -2126,6 +2308,94 @@ stp_wait(struct ofproto_dpif *ofproto) poll_timer_wait(1000); } } + +/* Configures RSTP on 'ofport_' using the settings defined in 's'. The + * caller is responsible for assigning RSTP port numbers and ensuring + * there are no duplicates. */ +static void +set_rstp_port(struct ofport *ofport_, + const struct ofproto_port_rstp_settings *s) +{ + struct ofport_dpif *ofport = ofport_dpif_cast(ofport_); + struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto); + struct rstp_port *rp = ofport->rstp_port; + int stp_port; + + if (!s || !s->enable) { + if (rp) { + ofport->rstp_port = NULL; + rstp_delete_port(rp); + update_rstp_port_state(ofport); + } + return; + } else if (rp && rstp_port_number(rp) != s->port_num + && ofport == rstp_port_get_aux(rp)) { + /* The port-id changed, so disable the old one if it's not + * already in use by another port. */ + if (s->port_num != 0) { + xlate_txn_start(); + stp_port = ofport->stp_port ? stp_port_no(ofport->stp_port) : -1; + xlate_ofport_set(ofproto, ofport->bundle, ofport, + ofport->up.ofp_port, ofport->odp_port, + ofport->up.netdev, ofport->cfm, + ofport->bfd, ofport->peer, stp_port, + s->port_num, + ofport->qdscp, ofport->n_qdscp, + ofport->up.pp.config, ofport->up.pp.state, + ofport->is_tunnel, ofport->may_enable); + xlate_txn_commit(); + } + + rstp_port_set_aux(rp, ofport); + rstp_port_set_priority(rp, s->priority); + rstp_port_set_port_number(rp, s->port_num); + rstp_port_set_path_cost(rp, s->path_cost); + rstp_port_set_admin_edge(rp, s->admin_edge_port); + rstp_port_set_auto_edge(rp, s->auto_edge); + rstp_port_set_mcheck(rp, s->mcheck); + + update_rstp_port_state(ofport); + + return; + } + rp = ofport->rstp_port = rstp_get_port(ofproto->rstp, s->port_num); + /* Enable RSTP on port */ + if (!rp) { + rp = ofport->rstp_port = rstp_add_port(ofproto->rstp); + } + /* Setters */ + rstp_port_set_aux(rp, ofport); + rstp_port_set_priority(rp, s->priority); + rstp_port_set_port_number(rp, s->port_num); + rstp_port_set_path_cost(rp, s->path_cost); + rstp_port_set_admin_edge(rp, s->admin_edge_port); + rstp_port_set_auto_edge(rp, s->auto_edge); + rstp_port_set_mcheck(rp, s->mcheck); + + update_rstp_port_state(ofport); +} + +static void +get_rstp_port_status(struct ofport *ofport_, + struct ofproto_port_rstp_status *s) +{ + struct ofport_dpif *ofport = ofport_dpif_cast(ofport_); + struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto); + struct rstp_port *rp = ofport->rstp_port; + + if (!ofproto->rstp || !rp) { + s->enabled = false; + return; + } + + s->enabled = true; + s->port_id = rstp_port_get_id(rp); + s->state = rstp_port_get_state(rp); + s->role = rstp_port_get_role(rp); + rstp_port_get_counts(rp, &s->tx_count, &s->rx_count, + &s->error_count, &s->uptime); +} + static int set_queues(struct ofport *ofport_, const struct ofproto_port_queue *qdscp, @@ -2863,6 +3133,12 @@ port_run(struct ofport_dpif *ofport) } ofport->may_enable = enable; + + if (ofport->rstp_port) { + if (rstp_port_get_mac_operational(ofport->rstp_port) != enable) { + rstp_port_set_mac_operational(ofport->rstp_port, enable); + } + } } static int @@ -5128,6 +5404,10 @@ const struct ofproto_class ofproto_dpif_class = { set_stp_port, get_stp_port_status, get_stp_port_stats, + set_rstp, + get_rstp_status, + set_rstp_port, + get_rstp_port_status, set_queues, bundle_set, bundle_remove, diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h index d490679b2..de354ec42 100644 --- a/ofproto/ofproto-provider.h +++ b/ofproto/ofproto-provider.h @@ -1417,6 +1417,53 @@ struct ofproto_class { int (*get_stp_port_stats)(struct ofport *ofport, struct ofproto_port_stp_stats *s); + /* Configures Rapid Spanning Tree Protocol (RSTP) on 'ofproto' using the + * settings defined in 's'. + * + * If 's' is nonnull, configures RSTP according to its members. + * + * If 's' is null, removes any RSTP configuration from 'ofproto'. + * + * EOPNOTSUPP as a return value indicates that this ofproto_class does not + * support RSTP, as does a null pointer. */ + void (*set_rstp)(struct ofproto *ofproto, + const struct ofproto_rstp_settings *s); + + /* Retrieves state of Rapid Spanning Tree Protocol (RSTP) on 'ofproto'. + * + * Stores RSTP state for 'ofproto' in 's'. If the 'enabled' member + * is false, the other member values are not meaningful. + * + * EOPNOTSUPP as a return value indicates that this ofproto_class does not + * support RSTP, as does a null pointer. */ + void (*get_rstp_status)(struct ofproto *ofproto, + struct ofproto_rstp_status *s); + + /* Configures Rapid Spanning Tree Protocol (RSTP) on 'ofport' using the + * settings defined in 's'. + * + * If 's' is nonnull, configures RSTP according to its members. The + * caller is responsible for assigning RSTP port numbers (using the + * 'port_num' member in the range of 1 through 255, inclusive) and + * ensuring there are no duplicates. + * + * If 's' is null, removes any RSTP configuration from 'ofport'. + * + * EOPNOTSUPP as a return value indicates that this ofproto_class does not + * support STP, as does a null pointer. */ + void (*set_rstp_port)(struct ofport *ofport, + const struct ofproto_port_rstp_settings *s); + + /* Retrieves Rapid Spanning Tree Protocol (RSTP) port status of 'ofport'. + * + * Stores RSTP state for 'ofport' in 's'. If the 'enabled' member is + * false, the other member values are not meaningful. + * + * EOPNOTSUPP as a return value indicates that this ofproto_class does not + * support RSTP, as does a null pointer. */ + void (*get_rstp_port_status)(struct ofport *ofport, + struct ofproto_port_rstp_status *s); + /* Registers meta-data associated with the 'n_qdscp' Qualities of Service * 'queues' attached to 'ofport'. This data is not intended to be * sufficient to implement QoS. Instead, providers may use this diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index 3e80b8726..7b1d47825 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -911,6 +911,87 @@ ofproto_port_get_stp_stats(struct ofproto *ofproto, ofp_port_t ofp_port, ? ofproto->ofproto_class->get_stp_port_stats(ofport, s) : EOPNOTSUPP); } + +/* Rapid Spanning Tree Protocol (RSTP) configuration. */ + +/* Configures RSTP on 'ofproto' using the settings defined in 's'. If + * 's' is NULL, disables RSTP. + * + * Returns 0 if successful, otherwise a positive errno value. */ +int +ofproto_set_rstp(struct ofproto *ofproto, + const struct ofproto_rstp_settings *s) +{ + if (!ofproto->ofproto_class->set_rstp) { + return EOPNOTSUPP; + } + ofproto->ofproto_class->set_rstp(ofproto, s); + return 0; +} + +/* Retrieves RSTP status of 'ofproto' and stores it in 's'. If the + * 'enabled' member of 's' is false, then the other members are not + * meaningful. + * + * Returns 0 if successful, otherwise a positive errno value. */ +int +ofproto_get_rstp_status(struct ofproto *ofproto, + struct ofproto_rstp_status *s) +{ + if (!ofproto->ofproto_class->get_rstp_status) { + return EOPNOTSUPP; + } + ofproto->ofproto_class->get_rstp_status(ofproto, s); + return 0; +} + +/* Configures RSTP on 'ofp_port' of 'ofproto' using the settings defined + * in 's'. The caller is responsible for assigning RSTP port numbers + * (using the 'port_num' member in the range of 1 through 255, inclusive) + * and ensuring there are no duplicates. If the 's' is NULL, then RSTP + * is disabled on the port. + * + * Returns 0 if successful, otherwise a positive errno value.*/ +int +ofproto_port_set_rstp(struct ofproto *ofproto, ofp_port_t ofp_port, + const struct ofproto_port_rstp_settings *s) +{ + struct ofport *ofport = ofproto_get_port(ofproto, ofp_port); + if (!ofport) { + VLOG_WARN("%s: cannot configure RSTP on nonexistent port %"PRIu16, + ofproto->name, ofp_port); + return ENODEV; + } + + if (!ofproto->ofproto_class->set_rstp_port) { + return EOPNOTSUPP; + } + ofproto->ofproto_class->set_rstp_port(ofport, s); + return 0; +} + +/* Retrieves RSTP port status of 'ofp_port' on 'ofproto' and stores it in + * 's'. If the 'enabled' member in 's' is false, then the other members + * are not meaningful. + * + * Returns 0 if successful, otherwise a positive errno value.*/ +int +ofproto_port_get_rstp_status(struct ofproto *ofproto, ofp_port_t ofp_port, + struct ofproto_port_rstp_status *s) +{ + struct ofport *ofport = ofproto_get_port(ofproto, ofp_port); + if (!ofport) { + VLOG_WARN_RL(&rl, "%s: cannot get RSTP status on nonexistent " + "port %"PRIu16, ofproto->name, ofp_port); + return ENODEV; + } + + if (!ofproto->ofproto_class->get_rstp_port_status) { + return EOPNOTSUPP; + } + ofproto->ofproto_class->get_rstp_port_status(ofport, s); + return 0; +} /* Queue DSCP configuration. */ @@ -2191,6 +2272,9 @@ ofproto_port_unregister(struct ofproto *ofproto, ofp_port_t ofp_port) if (port->ofproto->ofproto_class->set_stp_port) { port->ofproto->ofproto_class->set_stp_port(port, NULL); } + if (port->ofproto->ofproto_class->set_rstp_port) { + port->ofproto->ofproto_class->set_rstp_port(port, NULL); + } if (port->ofproto->ofproto_class->set_cfm) { port->ofproto->ofproto_class->set_cfm(port, NULL); } diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h index c51af1684..1b8709a5f 100644 --- a/ofproto/ofproto.h +++ b/ofproto/ofproto.h @@ -27,6 +27,7 @@ #include "flow.h" #include "meta-flow.h" #include "netflow.h" +#include "rstp.h" #include "smap.h" #include "sset.h" #include "stp.h" @@ -83,6 +84,47 @@ struct ofproto_ipfix_flow_exporter_options { uint32_t cache_max_flows; }; +struct ofproto_rstp_status { + bool enabled; /* If false, ignore other members. */ + rstp_identifier root_id; + rstp_identifier bridge_id; + rstp_identifier designated_id; + uint32_t root_path_cost; + uint16_t designated_port_id; + uint16_t bridge_port_id; +}; + +struct ofproto_rstp_settings { + rstp_identifier address; + uint16_t priority; + uint32_t ageing_time; + enum rstp_force_protocol_version force_protocol_version; + uint16_t bridge_forward_delay; + uint16_t bridge_max_age; + uint16_t transmit_hold_count; +}; + +struct ofproto_port_rstp_status { + bool enabled; /* If false, ignore other members. */ + uint16_t port_id; + enum rstp_port_role role; + enum rstp_state state; + int tx_count; /* Number of BPDUs transmitted. */ + int rx_count; /* Number of valid BPDUs received. */ + int error_count; /* Number of bad BPDUs received. */ + int uptime; +}; + +struct ofproto_port_rstp_settings { + bool enable; + uint16_t port_num; /* In the range 1-4095, inclusive. */ + uint8_t priority; + uint32_t path_cost; + bool admin_edge_port; + bool auto_edge; + bool mcheck; +}; + struct ofproto_stp_settings { stp_identifier system_id; uint16_t priority; @@ -271,6 +313,9 @@ bool ofproto_get_flow_restore_wait(void); int ofproto_set_stp(struct ofproto *, const struct ofproto_stp_settings *); int ofproto_get_stp_status(struct ofproto *, struct ofproto_stp_status *); +int ofproto_set_rstp(struct ofproto *, const struct ofproto_rstp_settings *); +int ofproto_get_rstp_status(struct ofproto *, struct ofproto_rstp_status *); + /* Configuration of ports. */ void ofproto_port_unregister(struct ofproto *, ofp_port_t ofp_port); @@ -292,6 +337,11 @@ int ofproto_port_get_stp_stats(struct ofproto *, ofp_port_t ofp_port, int ofproto_port_set_queues(struct ofproto *, ofp_port_t ofp_port, const struct ofproto_port_queue *, size_t n_queues); +int ofproto_port_get_rstp_status(struct ofproto *, ofp_port_t ofp_port, + struct ofproto_port_rstp_status *); + +int ofproto_port_set_rstp(struct ofproto *, ofp_port_t ofp_port, + const struct ofproto_port_rstp_settings *); /* The behaviour of the port regarding VLAN handling */ enum port_vlan_mode { diff --git a/tests/.gitignore b/tests/.gitignore index 7c980619f..908c50ecc 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -31,6 +31,7 @@ /test-packets /test-random /test-reconnect +/test-rstp /test-sflow /test-sha1 /test-stp diff --git a/tests/automake.mk b/tests/automake.mk index 470c1b989..9d32be9b2 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -65,6 +65,7 @@ TESTSUITE_AT = \ tests/ovs-monitor-ipsec.at \ tests/ovs-xapi-sync.at \ tests/stp.at \ + tests/rstp.at \ tests/interface-reconfigure.at \ tests/vlog.at \ tests/vtep-ctl.at @@ -123,6 +124,7 @@ valgrind_wrappers = \ tests/valgrind/test-packets \ tests/valgrind/test-random \ tests/valgrind/test-reconnect \ + tests/valgrind/test-rstp \ tests/valgrind/test-sha1 \ tests/valgrind/test-stp \ tests/valgrind/test-type-props \ @@ -232,6 +234,7 @@ tests_ovstest_SOURCES = \ tests/test-packets.c \ tests/test-random.c \ tests/test-reconnect.c \ + tests/test-rstp.c \ tests/test-sflow.c \ tests/test-sha1.c \ tests/test-stp.c \ diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at index 4ea47ef24..864c3823c 100644 --- a/tests/ovs-vsctl.at +++ b/tests/ovs-vsctl.at @@ -658,6 +658,8 @@ netflow : [] other_config : {} ports : [] protocols : [] +rstp_enable : false +rstp_status : {} sflow : [] status : {} stp_enable : false @@ -1154,6 +1156,8 @@ netflow : [] other_config : {} ports : [] protocols : [] +rstp_enable : false +rstp_status : {} sflow : [] status : {} stp_enable : false diff --git a/tests/rstp.at b/tests/rstp.at new file mode 100644 index 000000000..08183329e --- /dev/null +++ b/tests/rstp.at @@ -0,0 +1,149 @@ +AT_BANNER([Rapid Spanning Tree Protocol unit tests]) + +AT_SETUP([RSTP Single bridge]) +AT_KEYWORDS([RSTP]) +AT_DATA([test-rstp-num1], +[bridge 0 0x111 = a b +run 1000 +check 0 = root +]) +AT_CHECK([ovstest test-rstp test-rstp-num1], [0], []) +AT_CLEANUP + +AT_SETUP([RSTP Link failure]) +AT_KEYWORDS([RSTP]) +AT_DATA([test-rstp-num2], +[bridge 0 0x111 = a b +bridge 1 0x222 = a c +bridge 2 0x333 = b c +run 1000 +check 0 = root +check 1 = F:200000 F +check 2 = F:200000 Di +# Link b goes down +bridge 2 = X c +run 1000 +check 1 = F:200000 F +check 2 = D F:400000 +]) +AT_CHECK([ovstest test-rstp test-rstp-num2], [0], []) +AT_CLEANUP + +AT_SETUP([RSTP Double link Failure]) +AT_KEYWORDS([RSTP]) +AT_DATA([test-rstp-num3], +[bridge 0 0x111 = a b +bridge 1 0x222 = a c d +bridge 2 0x333 = b c e +bridge 3 0x444 = d f +bridge 4 0x555 = e f +run 1000 +check 0 = root +check 1 = F:200000 F F +check 2 = F:200000 Di F +check 3 = F:400000 F +check 4 = F:400000 Di +# Link b goes down +bridge 2 = X c e +run 1000 +check 0 = root +check 1 = F:200000 F F +check 2 = D F:400000 F +check 3 = F:400000 F +check 4 = F:600000 Di +# Link e goes down +bridge 4 = X f +run 1000 +check 0 = root +check 1 = F:200000 F F +check 2 = D F:400000 F +check 3 = F:400000 F +check 4 = D F:600000 +# Link f cost changes +bridge 4 = X f:100000 +run 1000 +check 4 = D F:500000 +# Bridge 4 becomes root and +bridge 4 ^ 31000 +run 1000 +check 4 = root +]) +AT_CHECK([ovstest test-rstp test-rstp-num3], [0], []) +AT_CLEANUP + +AT_SETUP([RSTP example from IEEE 802.1D-2004 figures 17.4 and 17.5]) +AT_KEYWORDS([RSTP]) +AT_DATA([test-rstp-ieee802.1d-2004-fig17.4], +[bridge 0 0x111 = a b e c +bridge 1 0x222 = a b d f +bridge 2 0x333 = c d l j h g +bridge 3 0x444 = e f n m k i +bridge 4 0x555 = g i 0 0 +bridge 5 0x666 = h k 0 0 +bridge 6 0x777 = j m 0 0 +bridge 7 0x888 = l n 0 0 +run 1000 +check 0 = root +check 1 = F:200000 Di F F +check 2 = F:200000 Di F F F F +check 3 = F:200000 Di F F F F +check 4 = F:400000 Di F F +check 5 = F:400000 Di F F +check 6 = F:400000 Di F F +check 7 = F:400000 Di F F + +# Now connect two ports of bridge 7 to the same LAN. +bridge 7 = l n o o +# Same results except for bridge 7: +run 1000 +check 0 = root +check 1 = F:200000 Di F F +check 2 = F:200000 Di F F F F +check 3 = F:200000 Di F F F F +check 4 = F:400000 Di F F +check 5 = F:400000 Di F F +check 6 = F:400000 Di F F +check 7 = F:400000 Di F Di +]) +AT_CHECK([ovstest test-rstp test-rstp-ieee802.1d-2004-fig17.4], [0], []) +AT_CLEANUP + +AT_SETUP([RSTP example from IEEE 802.1D-2004 figure 17.6]) +AT_KEYWORDS([RSTP]) +AT_DATA([test-rstp-ieee802.1d-2004-fig17.6], +[bridge 0 0x111 = a b l +bridge 1 0x222 = b c d +bridge 2 0x333 = d e f +bridge 3 0x444 = f g h +bridge 4 0x555 = j h i +bridge 5 0x666 = l j k +run 1000 +check 0 = root +check 1 = F:200000 F F +check 2 = F:400000 F F +check 3 = F:600000 F Di +check 4 = F:400000 F F +check 5 = F:200000 F F +]) +AT_CHECK([ovstest test-rstp test-rstp-ieee802.1d-2004-fig17.6], [0], []) +AT_CLEANUP + +AT_SETUP([RSTP example from IEEE 802.1D-2004 figure 17.7]) +AT_KEYWORDS([RSTP]) +AT_DATA([test-rstp-ieee802.1d-2004-fig17.7], +[bridge 0 0x000 = b +bridge 1 0x111 = a b d f h g e c +bridge 2 0x222 = g h j l n m k i +run 1000 +check 0 = root +check 1 = F F:200000 F F F F F F +check 2 = Di F:400000 F F F F F F +# Link g priority increment +bridge 1 = a b d f h g^112 e c +run 1000 +check 0 = root +check 1 = F F:200000 F F F F F F +check 2 = F:400000 Di F F F F F F +]) +AT_CHECK([ovstest test-rstp test-rstp-ieee802.1d-2004-fig17.7], [0], []) +AT_CLEANUP diff --git a/tests/test-rstp.c b/tests/test-rstp.c new file mode 100644 index 000000000..ad71f044e --- /dev/null +++ b/tests/test-rstp.c @@ -0,0 +1,684 @@ +#include + +#include "rstp.h" +#include +#include +#include +#include +#include +#include +#include "ofpbuf.h" +#include "ovstest.h" +#include "packets.h" +#include "vlog.h" + +#define MAX_PORTS 10 + +struct bpdu { + int port_no; + void *data; + size_t size; +}; + +struct bridge { + struct test_case *tc; + int id; + bool reached; + + struct rstp *rstp; + + struct lan *ports[RSTP_MAX_PORTS]; + int n_ports; + int n_active_ports; + +#define RXQ_SIZE 16 + struct bpdu rxq[RXQ_SIZE]; + int rxq_head, rxq_tail; +}; + +struct lan_conn { + struct bridge *bridge; + int port_no; +}; + +struct lan { + struct test_case *tc; + const char *name; + bool reached; + struct lan_conn conns[16]; + int n_conns; +}; + +struct test_case { + struct bridge *bridges[16]; + int n_bridges; + struct lan *lans[26]; + int n_lans; +}; + +static const char *file_name; +static int line_number; +static char line[128]; +static char *pos, *token; +static int n_warnings; + +static struct test_case * +new_test_case(void) +{ + struct test_case *tc = xmalloc(sizeof *tc); + tc->n_bridges = 0; + tc->n_lans = 0; + return tc; +} + +static void +send_bpdu(struct ofpbuf *pkt, int port_no, void *b_) +{ + struct bridge *b = b_; + struct lan *lan; + + assert(port_no < b->n_ports); + lan = b->ports[port_no]; + if (lan) { + const void *data = ofpbuf_l3(pkt); + size_t size = (char *) ofpbuf_tail(pkt) - (char *) data; + int i; + + for (i = 0; i < lan->n_conns; i++) { + struct lan_conn *conn = &lan->conns[i]; + if (conn->bridge != b || conn->port_no != port_no) { + struct bridge *dst = conn->bridge; + struct bpdu *bpdu = &dst->rxq[dst->rxq_head++ % RXQ_SIZE]; + assert(dst->rxq_head - dst->rxq_tail <= RXQ_SIZE); + bpdu->data = xmemdup(data, size); + bpdu->size = size; + bpdu->port_no = conn->port_no; + } + } + } + ofpbuf_delete(pkt); +} + +static struct bridge * +new_bridge(struct test_case *tc, int id) +{ + struct bridge *b = xmalloc(sizeof *b); + char name[16]; + struct rstp_port *p; + int i; + + b->tc = tc; + b->id = id; + snprintf(name, sizeof name, "rstp%x", id); + b->rstp = rstp_create(name, id, send_bpdu, b); + for (i = 1; i < MAX_PORTS; i++) { + p = rstp_add_port(b->rstp); + rstp_port_set_aux(p, b); + rstp_port_set_state(p, RSTP_DISABLED); + rstp_port_set_mac_operational(p, true); + } + + assert(tc->n_bridges < ARRAY_SIZE(tc->bridges)); + b->n_ports = 1; + b->n_active_ports = 1; + b->rxq_head = b->rxq_tail = 0; + tc->bridges[tc->n_bridges++] = b; + return b; +} + +static struct lan * +new_lan(struct test_case *tc, const char *name) +{ + struct lan *lan = xmalloc(sizeof *lan); + lan->tc = tc; + lan->name = xstrdup(name); + lan->n_conns = 0; + assert(tc->n_lans < ARRAY_SIZE(tc->lans)); + tc->lans[tc->n_lans++] = lan; + return lan; +} + +static void +reconnect_port(struct bridge *b, int port_no, struct lan *new_lan) +{ + struct lan *old_lan; + int j; + + assert(port_no < b->n_ports); + old_lan = b->ports[port_no]; + if (old_lan == new_lan) { + return; + } + + /* Disconnect from old_lan. */ + if (old_lan) { + for (j = 0; j < old_lan->n_conns; j++) { + struct lan_conn *c = &old_lan->conns[j]; + if (c->bridge == b && c->port_no == port_no) { + memmove(c, c + 1, sizeof *c * (old_lan->n_conns - j - 1)); + old_lan->n_conns--; + break; + } + } + } + + /* Connect to new_lan. */ + b->ports[port_no] = new_lan; + if (new_lan) { + int conn_no = new_lan->n_conns++; + assert(conn_no < ARRAY_SIZE(new_lan->conns)); + new_lan->conns[conn_no].bridge = b; + new_lan->conns[conn_no].port_no = port_no; + } +} + +static void +new_port(struct bridge *b, struct lan *lan, uint32_t path_cost) +{ + int port_no = b->n_ports++; + struct rstp_port *p = rstp_get_port(b->rstp, port_no); + + assert(port_no < ARRAY_SIZE(b->ports)); + b->ports[port_no] = NULL; + /* Enable port. */ + reinitialize_port(p); + rstp_port_set_path_cost(p, path_cost); + rstp_port_set_state(p, RSTP_DISCARDING); + rstp_port_set_mac_operational(p, true); + reconnect_port(b, port_no, lan); +} + +static void +dump(struct test_case *tc) +{ + int i; + + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + struct rstp *rstp = b->rstp; + int j; + + printf("%s:", rstp_get_name(rstp)); + if (rstp_is_root_bridge(rstp)) { + printf(" root"); + } + printf("\n"); + for (j = 0; j < b->n_ports; j++) { + struct rstp_port *p = rstp_get_port(rstp, j); + enum rstp_state state = rstp_port_get_state(p); + + printf("\tport %d", j); + if (b->ports[j]) { + printf(" (lan %s)", b->ports[j]->name); + } else { + printf(" (disconnected)"); + } + printf(": %s", rstp_state_name(state)); + if (p == rstp_get_root_port(rstp)) { + printf(" (root port, root_path_cost=%u)", + rstp_get_root_path_cost(rstp)); + } + printf("\n"); + } + } +} + +static void dump_lan_tree(struct test_case *, struct lan *, int level); + +static void +dump_bridge_tree(struct test_case *tc, struct bridge *b, int level) +{ + int i; + + if (b->reached) { + return; + } + b->reached = true; + for (i = 0; i < level; i++) { + printf("\t"); + } + printf("%s\n", rstp_get_name(b->rstp)); + for (i = 0; i < b->n_ports; i++) { + struct lan *lan = b->ports[i]; + struct rstp_port *p = rstp_get_port(b->rstp, i); + if (rstp_port_get_state(p) == RSTP_FORWARDING && lan) { + dump_lan_tree(tc, lan, level + 1); + } + } +} + +static void +dump_lan_tree(struct test_case *tc, struct lan *lan, int level) +{ + int i; + + if (lan->reached) { + return; + } + lan->reached = true; + for (i = 0; i < level; i++) { + printf("\t"); + } + printf("%s\n", lan->name); + for (i = 0; i < lan->n_conns; i++) { + struct bridge *b = lan->conns[i].bridge; + dump_bridge_tree(tc, b, level + 1); + } +} + +static void +tree(struct test_case *tc) +{ + int i; + + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + b->reached = false; + } + for (i = 0; i < tc->n_lans; i++) { + struct lan *lan = tc->lans[i]; + lan->reached = false; + } + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + struct rstp *rstp = b->rstp; + if (rstp_is_root_bridge(rstp)) { + dump_bridge_tree(tc, b, 0); + } + } +} + +static void +simulate(struct test_case *tc, int granularity) +{ + int time, i, round_trips; + for (time = 0; time < 1000 * 180; time += granularity) { + + for (i = 0; i < tc->n_bridges; i++) { + rstp_tick_timers(tc->bridges[i]->rstp); + } + for (round_trips = 0; round_trips < granularity; round_trips++) { + bool any = false; + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *b = tc->bridges[i]; + for (; b->rxq_tail != b->rxq_head; b->rxq_tail++) { + struct bpdu *bpdu = &b->rxq[b->rxq_tail % RXQ_SIZE]; + rstp_received_bpdu(rstp_get_port(b->rstp, bpdu->port_no), + bpdu->data, bpdu->size); + free(bpdu->data); + any = true; + } + } + if (!any) { + break; + } + } + } +} + +static void +err(const char *message, ...) + PRINTF_FORMAT(1, 2) + NO_RETURN; + +static void +err(const char *message, ...) +{ + va_list args; + + fprintf(stderr, "%s:%d:%"PRIdPTR": ", file_name, line_number, pos - line); + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + putc('\n', stderr); + + exit(EXIT_FAILURE); +} + +static void +warn(const char *message, ...) + PRINTF_FORMAT(1, 2); + +static void +warn(const char *message, ...) +{ + va_list args; + + fprintf(stderr, "%s:%d: ", file_name, line_number); + va_start(args, message); + vfprintf(stderr, message, args); + va_end(args); + putc('\n', stderr); + + n_warnings++; +} + +static bool +get_token(void) +{ + char *start; + + while (isspace((unsigned char) *pos)) { + pos++; + } + if (*pos == '\0') { + free(token); + token = NULL; + return false; + } + + start = pos; + if (isalpha((unsigned char) *pos)) { + while (isalpha((unsigned char) *++pos)) { + continue; + } + } else if (isdigit((unsigned char) *pos)) { + if (*pos == '0' && (pos[1] == 'x' || pos[1] == 'X')) { + pos += 2; + while (isxdigit((unsigned char) *pos)) { + pos++; + } + } else { + while (isdigit((unsigned char) *++pos)) { + continue; + } + } + } else { + pos++; + } + + free(token); + token = xmemdup0(start, pos - start); + return true; +} + +static bool +get_int(int *intp) +{ + char *save_pos = pos; + if (token && isdigit((unsigned char) *token)) { + *intp = strtol(token, NULL, 0); + get_token(); + return true; + } else { + pos = save_pos; + return false; + } +} + +static bool +match(const char *want) +{ + if (token && !strcmp(want, token)) { + get_token(); + return true; + } else { + return false; + } +} + +static int +must_get_int(void) +{ + int x; + if (!get_int(&x)) { + err("expected integer"); + } + return x; +} + +static void +must_match(const char *want) +{ + if (!match(want)) { + err("expected \"%s\"", want); + } +} + +static void +test_rstp_main(int argc, char *argv[]) +{ + struct test_case *tc; + FILE *input_file; + int i; + + vlog_set_pattern(VLF_CONSOLE, "%c|%p|%m"); + vlog_set_levels(NULL, VLF_SYSLOG, VLL_OFF); + + if (argc != 2) { + ovs_fatal(0, "usage: test-rstp INPUT.RSTP\n"); + } + file_name = argv[1]; + + input_file = fopen(file_name, "r"); + if (!input_file) { + ovs_fatal(errno, "error opening \"%s\"", file_name); + } + + tc = new_test_case(); + for (i = 0; i < 26; i++) { + char name[2]; + name[0] = 'a' + i; + name[1] = '\0'; + new_lan(tc, name); + } + + for (line_number = 1; fgets(line, sizeof line, input_file); + line_number++) + { + char *newline, *hash; + + newline = strchr(line, '\n'); + if (newline) { + *newline = '\0'; + } + hash = strchr(line, '#'); + if (hash) { + *hash = '\0'; + } + + pos = line; + if (!get_token()) { + continue; + } + if (match("bridge")) { + struct bridge *bridge; + int bridge_no, port_no; + + bridge_no = must_get_int(); + if (bridge_no < tc->n_bridges) { + bridge = tc->bridges[bridge_no]; + } else if (bridge_no == tc->n_bridges) { + bridge = new_bridge(tc, must_get_int()); + } else { + err("bridges must be numbered consecutively from 0"); + } + if (match("^")) { + rstp_set_bridge_priority(bridge->rstp, must_get_int()); + } + if (match("=")) { + for (port_no = 1; port_no < MAX_PORTS; port_no++) { + struct rstp_port *p = rstp_get_port(bridge->rstp, port_no); + if (!token || match("X")) { + /* Disable port. */ + reinitialize_port(p); + rstp_port_set_state(p, RSTP_DISABLED); + rstp_port_set_mac_operational(p, false); + } else if (match("_")) { + /* Nothing to do. */ + } else { + struct lan *lan; + uint32_t path_cost; + + if (!strcmp(token, "0")) { + lan = NULL; + } else if (strlen(token) == 1 + && islower((unsigned char)*token)) { + lan = tc->lans[*token - 'a']; + } else { + err("%s is not a valid LAN name " + "(0 or a lowercase letter)", token); + } + get_token(); + + path_cost = match(":") ? must_get_int() : + RSTP_DEFAULT_PORT_PATH_COST; + if (port_no < bridge->n_ports) { + /* Enable port. */ + reinitialize_port(p); + rstp_port_set_path_cost(p, path_cost); + rstp_port_set_state(p, RSTP_DISCARDING); + rstp_port_set_mac_operational(p, true); + reconnect_port(bridge, port_no, lan); + } else if (port_no == bridge->n_ports) { + new_port(bridge, lan, path_cost); + bridge->n_active_ports++; + } else { + err("ports must be numbered consecutively"); + } + if (match("^")) { + rstp_port_set_priority(p, must_get_int()); + } + } + } + } + } else if (match("run")) { + simulate(tc, must_get_int()); + } else if (match("dump")) { + dump(tc); + } else if (match("tree")) { + tree(tc); + } else if (match("check")) { + struct bridge *b; + struct rstp *rstp; + int bridge_no, port_no; + uint32_t cost_value; + + bridge_no = must_get_int(); + if (bridge_no >= tc->n_bridges) { + err("no bridge numbered %d", bridge_no); + } + b = tc->bridges[bridge_no]; + rstp = b->rstp; + + must_match("="); + + if (match("rootid")) { + uint64_t rootid; + must_match(":"); + rootid = must_get_int(); + if (match("^")) { + rootid |= (uint64_t) must_get_int() << 48; + } else { + rootid |= UINT64_C(0x8000) << 48; + } + if (rstp_get_designated_root(rstp) != rootid) { + warn("%s: root "RSTP_ID_FMT", not %"PRIx64, + rstp_get_name(rstp), + RSTP_ID_ARGS(rstp_get_designated_root(rstp)), + rootid); + } + } + cost_value = rstp_get_root_path_cost(rstp); + if (match("root")) { + if (cost_value != 0) { + warn("%s: root path cost of root is %d instead of 0 \n", + rstp_get_name(rstp), cost_value); + } + if (!rstp_is_root_bridge(rstp)) { + warn("%s: root is "RSTP_ID_FMT", not "RSTP_ID_FMT"", + rstp_get_name(rstp), + RSTP_ID_ARGS(rstp_get_designated_root(rstp)), + RSTP_ID_ARGS(rstp_get_bridge_id(rstp))); + } + for (port_no = 1; port_no < b->n_active_ports; port_no++) { + struct rstp_port *p = rstp_get_port(rstp, port_no); + enum rstp_state state = rstp_port_get_state(p); + if (state != RSTP_DISABLED && state != RSTP_FORWARDING) { + warn("%s: root port %d in state %s", + rstp_get_name(b->rstp), port_no, + rstp_state_name(state)); + } + } + } else { + for (port_no = 1; port_no < b->n_active_ports; port_no++) { + struct rstp_port *p = rstp_get_port(rstp, port_no); + enum rstp_state state; + if (token == NULL || match("D")) { + state = RSTP_DISABLED; + } else if (match("Di")) { + state = RSTP_DISCARDING; + } else if (match("Le")) { + state = RSTP_LEARNING; + } else if (match("F")) { + state = RSTP_FORWARDING; + } else if (match("_")) { + continue; + } else { + err("unknown port state %s", token); + } + if (rstp_port_get_state(p) != state) { + warn("%s port %d: state is %s but should be %s", + rstp_get_name(rstp), port_no, + rstp_state_name(rstp_port_get_state(p)), + rstp_state_name(state)); + } + if (state == RSTP_FORWARDING) { + struct rstp_port *root_port = rstp_get_root_port(rstp); + if (match(":")) { + int root_path_cost = must_get_int(); + if (p != root_port) { + warn("%s: port %d is not the root port", + rstp_get_name(rstp), port_no); + if (!root_port) { + warn("%s: (there is no root port)", + rstp_get_name(rstp)); + } else { + warn("%s: (port %d is the root port)", + rstp_get_name(rstp), + rstp_port_number(root_port)); + } + } else if (cost_value != root_path_cost) { + warn("%s: root path cost is %d, should be %d", + rstp_get_name(rstp), + cost_value, + root_path_cost); + } + } else if (p == root_port) { + warn("%s: port %d is the root port but " + "not expected to be", + rstp_get_name(rstp), port_no); + } + } + } + } + if (n_warnings) { + printf("failing because of %d warnings\n", n_warnings); + exit(EXIT_FAILURE); + } + } + if (get_token()) { + printf("failing because of errors\n"); + err("trailing garbage on line"); + } + } + free(token); + + for (i = 0; i < tc->n_lans; i++) { + struct lan *lan = tc->lans[i]; + free(CONST_CAST(char *, lan->name)); + free(lan); + } + for (i = 0; i < tc->n_bridges; i++) { + struct bridge *bridge = tc->bridges[i]; + int j; + for (j = 1; j < MAX_PORTS; j++) { + rstp_delete_port(rstp_get_port(bridge->rstp, j)); + } + rstp_unref(bridge->rstp); + free(bridge); + } + free(tc); +} + +OVSTEST_REGISTER("test-rstp", test_rstp_main); diff --git a/tests/testsuite.at b/tests/testsuite.at index 74e5c6dfa..871227798 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -157,5 +157,6 @@ m4_include([tests/ovs-monitor-ipsec.at]) m4_include([tests/ovs-xapi-sync.at]) m4_include([tests/interface-reconfigure.at]) m4_include([tests/stp.at]) +m4_include([tests/rstp.at]) m4_include([tests/vlog.at]) m4_include([tests/vtep-ctl.at]) diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in index a68b0bcd7..8cf13ae3b 100644 --- a/utilities/ovs-vsctl.8.in +++ b/utilities/ovs-vsctl.8.in @@ -1002,6 +1002,85 @@ Deconfigure multicasting snooping from above: .IP .B "ovs\-vsctl set Bridge br0 mcast_snooping_enable=false" .PP +.SS "802.1D-2004 Rapid Spanning Tree Protocol (RSTP)" +.PP +Configure bridge \fBbr0\fR to participate in an 802.1D-2004 Rapid Spanning Tree: +.IP +.B "ovs\-vsctl set Bridge br0 rstp_enable=true" +.PP +Set the bridge address of \fBbr0\fR to 00:aa:aa:aa:aa:aa : +.IP +.B "ovs\-vsctl set Bridge br0 other_config:rstp-address=00:aa:aa:aa:aa:aa" +.PP +Set the bridge priority of \fBbr0\fR to 0x7000. The value must be specified in +decimal notation and should be a multiple of 4096 (if not, it is rounded down to +the nearest multiple of 4096). The default priority value is 0x800 (32768). +.IP +.B "ovs\-vsctl set Bridge br0 other_config:rstp-priority=28672" +.PP +Set the bridge ageing time of \fBbr0\fR to 1000 s. The ageing time value should be +between 10 s and 1000000 s. The default value is 300 s. +.IP +.B "ovs\-vsctl set Bridge br0 other_config:rstp-ageing-time=1000" +.PP +Set the bridge force protocol version of \fBbr0\fR to 0. The force protocol version +has two acceptable values: 0 (STP compatibility mode) and 2 (normal operation). +.IP +.B "ovs\-vsctl set Bridge br0 other_config:rstp-force-protocol-version=0" +.PP +Set the bridge max age of \fBbr0\fR to 10 s. The max age value should be between 6 s +and 40 s. The default value is 20 s. +.IP +.B "ovs\-vsctl set Bridge br0 other_config:rstp-max-age=10" +.PP +Set the bridge forward delay of \fBbr0\fR to 15 s. +This value should be between 4 s and 30 s. The default value is 15 s. +.IP +.B "ovs\-vsctl set Bridge br0 other_config:rstp-forward-delay=15" +.PP +Set the bridge transmit hold count of \fBbr0\fR to 7 s. This value should be between +1 s and 10 s. The default value is 6 s. +.IP +.B "ovs\-vsctl set Bridge br0 other_config:rstp-transmit-hold-count=7" +.PP +Enable RSTP on the Port \fBeth0\fR. +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-enable=true" +.PP +Disable RSTP on the Port \fBeth0\fR. +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-enable=false" +.PP +Set the priority of port \fBeth0\fR to 20. The value must be specified in +decimal notation and should be a multiple of 16 (if not, it is rounded down to the +nearest multiple of 16). The default priority value is 0x80 (128). +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-port-priority=32" +.PP +Set the port number of port \fBeth0\fR to 3: +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-port-num=3" +.PP +Set the path cost of port \fBeth0\fR to 150: +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-path-cost=150" +.PP +Set the admin edge value of port \fBeth0\fR: +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-port-admin-edge=true" +.PP +Set the auto edge value of port \fBeth0\fR: +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-port-auto-edge=true" +.PP +Set the mcheck value of port \fBeth0\fR: +.IP +.B "ovs\-vsctl set Port eth0 other_config:rstp-port-mcheck=true" +.PP +Deconfigure RSTP from above: +.IP +.B "ovs\-vsctl set Bridge br0 rstp_enable=false" +.PP .SS "OpenFlow Version" .PP Configure bridge \fBbr0\fR to support OpenFlow versions 1.0, 1.2, and diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index 7631cd432..ba2eb79c3 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -229,6 +229,7 @@ static void bridge_configure_mcast_snooping(struct bridge *); static void bridge_configure_sflow(struct bridge *, int *sflow_bridge_number); static void bridge_configure_ipfix(struct bridge *); static void bridge_configure_stp(struct bridge *); +static void bridge_configure_rstp(struct bridge *); static void bridge_configure_tables(struct bridge *); static void bridge_configure_dp_desc(struct bridge *); static void bridge_configure_remotes(struct bridge *, @@ -379,9 +380,14 @@ bridge_init(const char *remote) ovsdb_idl_omit_alert(idl, &ovsrec_bridge_col_datapath_id); ovsdb_idl_omit_alert(idl, &ovsrec_bridge_col_status); + ovsdb_idl_omit_alert(idl, &ovsrec_bridge_col_rstp_status); + ovsdb_idl_omit_alert(idl, &ovsrec_bridge_col_stp_enable); + ovsdb_idl_omit_alert(idl, &ovsrec_bridge_col_rstp_enable); ovsdb_idl_omit(idl, &ovsrec_bridge_col_external_ids); ovsdb_idl_omit_alert(idl, &ovsrec_port_col_status); + ovsdb_idl_omit_alert(idl, &ovsrec_port_col_rstp_status); + ovsdb_idl_omit_alert(idl, &ovsrec_port_col_rstp_statistics); ovsdb_idl_omit_alert(idl, &ovsrec_port_col_statistics); ovsdb_idl_omit(idl, &ovsrec_port_col_external_ids); @@ -444,6 +450,7 @@ bridge_init(const char *remote) cfm_init(); ovs_numa_init(); stp_init(); + rstp_init(); } void @@ -627,6 +634,7 @@ bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg) bridge_configure_sflow(br, &sflow_bridge_number); bridge_configure_ipfix(br); bridge_configure_stp(br); + bridge_configure_rstp(br); bridge_configure_tables(br); bridge_configure_dp_desc(br); } @@ -1302,12 +1310,107 @@ port_configure_stp(const struct ofproto *ofproto, struct port *port, } } +static void +port_configure_rstp(const struct ofproto *ofproto, struct port *port, + struct ofproto_port_rstp_settings *port_s, int *port_num_counter) +{ + const char *config_str; + struct iface *iface; + + if (!smap_get_bool(&port->cfg->other_config, "rstp-enable", true)) { + port_s->enable = false; + return; + } else { + port_s->enable = true; + } + + /* RSTP over bonds is not supported. */ + if (!list_is_singleton(&port->ifaces)) { + VLOG_ERR("port %s: cannot enable RSTP on bonds, disabling", + port->name); + port_s->enable = false; + return; + } + + iface = CONTAINER_OF(list_front(&port->ifaces), struct iface, port_elem); + + /* Internal ports shouldn't participate in spanning tree, so + * skip them. */ + if (!strcmp(iface->type, "internal")) { + VLOG_DBG("port %s: disable RSTP on internal ports", port->name); + port_s->enable = false; + return; + } + + /* RSTP on mirror output ports is not supported. */ + if (ofproto_is_mirror_output_bundle(ofproto, port)) { + VLOG_DBG("port %s: disable RSTP on mirror ports", port->name); + port_s->enable = false; + return; + } + + config_str = smap_get(&port->cfg->other_config, "rstp-port-num"); + if (config_str) { + unsigned long int port_num = strtoul(config_str, NULL, 0); + if (port_num < 1 || port_num > RSTP_MAX_PORTS) { + VLOG_ERR("port %s: invalid rstp-port-num", port->name); + port_s->enable = false; + return; + } + port_s->port_num = port_num; + } + else { + if (*port_num_counter >= RSTP_MAX_PORTS) { + VLOG_ERR("port %s: too many RSTP ports, disabling", port->name); + port_s->enable = false; + return; + } + /* If rstp-port-num is not specified, use 0. rstp_port_set_port_number + * will look for the first free one. + */ + port_s->port_num = 0; + } + + config_str = smap_get(&port->cfg->other_config, "rstp-path-cost"); + if (config_str) { + port_s->path_cost = strtoul(config_str, NULL, 10); + } else { + enum netdev_features current; + unsigned int mbps; + + netdev_get_features(iface->netdev, ¤t, NULL, NULL, NULL); + mbps = netdev_features_to_bps(current, 100 * 1000 * 1000) / 1000000; + port_s->path_cost = rstp_convert_speed_to_cost(mbps); + } + + config_str = smap_get(&port->cfg->other_config, "rstp-port-priority"); + if (config_str) { + port_s->priority = strtoul(config_str, NULL, 0); + } else { + port_s->priority = RSTP_DEFAULT_PORT_PRIORITY; + } + + port_s->admin_edge_port = smap_get_bool(&port->cfg->other_config, + "rstp-port-admin-edge", false); + port_s->auto_edge = smap_get_bool(&port->cfg->other_config, + "rstp-port-auto-edge", true); + port_s->mcheck = smap_get_bool(&port->cfg->other_config, + "rstp-port-mcheck", false); +} + /* Set spanning tree configuration on 'br'. */ static void bridge_configure_stp(struct bridge *br) { + struct ofproto_rstp_status rstp_status; + ofproto_get_rstp_status(br->ofproto, &rstp_status); if (!br->cfg->stp_enable) { ofproto_set_stp(br->ofproto, NULL); + } else if (rstp_status.enabled) { + /* Do not activate STP if RSTP is enabled. */ + VLOG_ERR("STP cannot be enabled if RSTP is running."); + ofproto_set_stp(br->ofproto, NULL); + ovsrec_bridge_set_stp_enable(br->cfg, false); } else { struct ofproto_stp_settings br_s; const char *config_str; @@ -1397,6 +1500,112 @@ bridge_configure_stp(struct bridge *br) } } +static void +bridge_configure_rstp(struct bridge *br) +{ + struct ofproto_stp_status stp_status; + ofproto_get_stp_status(br->ofproto, &stp_status); + if (!br->cfg->rstp_enable) { + ofproto_set_rstp(br->ofproto, NULL); + } else if (stp_status.enabled) { + /* Do not activate RSTP if STP is enabled. */ + VLOG_ERR("RSTP cannot be enabled if STP is running."); + ofproto_set_rstp(br->ofproto, NULL); + ovsrec_bridge_set_rstp_enable(br->cfg, false); + } else { + struct ofproto_rstp_settings br_s; + const char *config_str; + struct port *port; + int port_num_counter; + + config_str = smap_get(&br->cfg->other_config, "rstp-address"); + if (config_str) { + uint8_t ea[ETH_ADDR_LEN]; + + if (eth_addr_from_string(config_str, ea)) { + br_s.address = eth_addr_to_uint64(ea); + } + else { + br_s.address = eth_addr_to_uint64(br->ea); + VLOG_ERR("bridge %s: invalid rstp-address, defaulting " + "to "ETH_ADDR_FMT, br->name, ETH_ADDR_ARGS(br->ea)); + } + } + else { + br_s.address = eth_addr_to_uint64(br->ea); + } + + config_str = smap_get(&br->cfg->other_config, "rstp-priority"); + if (config_str) { + br_s.priority = strtoul(config_str, NULL, 0); + } else { + br_s.priority = RSTP_DEFAULT_PRIORITY; + } + + config_str = smap_get(&br->cfg->other_config, "rstp-ageing-time"); + if (config_str) { + br_s.ageing_time = strtoul(config_str, NULL, 0); + } else { + br_s.ageing_time = RSTP_DEFAULT_AGEING_TIME; + } + + config_str = smap_get(&br->cfg->other_config, + "rstp-force-protocol-version"); + if (config_str) { + br_s.force_protocol_version = strtoul(config_str, NULL, 0); + } else { + br_s.force_protocol_version = FPV_DEFAULT; + } + + config_str = smap_get(&br->cfg->other_config, "rstp-max-age"); + if (config_str) { + br_s.bridge_max_age = strtoul(config_str, NULL, 10); + } else { + br_s.bridge_max_age = RSTP_DEFAULT_BRIDGE_MAX_AGE; + } + + config_str = smap_get(&br->cfg->other_config, "rstp-forward-delay"); + if (config_str) { + br_s.bridge_forward_delay = strtoul(config_str, NULL, 10); + } else { + br_s.bridge_forward_delay = RSTP_DEFAULT_BRIDGE_FORWARD_DELAY; + } + + config_str = smap_get(&br->cfg->other_config, + "rstp-transmit-hold-count"); + if (config_str) { + br_s.transmit_hold_count = strtoul(config_str, NULL, 10); + } else { + br_s.transmit_hold_count = RSTP_DEFAULT_TRANSMIT_HOLD_COUNT; + } + + /* Configure RSTP on the bridge. */ + if (ofproto_set_rstp(br->ofproto, &br_s)) { + VLOG_ERR("bridge %s: could not enable RSTP", br->name); + return; + } + + port_num_counter = 0; + HMAP_FOR_EACH (port, hmap_node, &br->ports) { + struct ofproto_port_rstp_settings port_s; + struct iface *iface; + + port_configure_rstp(br->ofproto, port, &port_s, + &port_num_counter); + + /* As bonds are not supported, just apply configuration to + * all interfaces. */ + LIST_FOR_EACH (iface, port_elem, &port->ifaces) { + if (ofproto_port_set_rstp(br->ofproto, iface->ofp_port, + &port_s)) { + VLOG_ERR("port %s: could not enable RSTP", port->name); + continue; + } + } + } + } +} + static bool bridge_has_bond_fake_iface(const struct bridge *br, const char *name) { @@ -2208,6 +2417,90 @@ port_refresh_stp_stats(struct port *port) ARRAY_SIZE(int_values)); } +static void +br_refresh_rstp_status(struct bridge *br) +{ + struct smap smap = SMAP_INITIALIZER(&smap); + struct ofproto *ofproto = br->ofproto; + struct ofproto_rstp_status status; + + if (ofproto_get_rstp_status(ofproto, &status)) { + return; + } + if (!status.enabled) { + ovsrec_bridge_set_rstp_status(br->cfg, NULL); + return; + } + smap_add_format(&smap, "rstp_bridge_id", RSTP_ID_FMT, + RSTP_ID_ARGS(status.bridge_id)); + smap_add_format(&smap, "rstp_root_path_cost", "%d", + status.root_path_cost); + smap_add_format(&smap, "rstp_root_id", RSTP_ID_FMT, + RSTP_ID_ARGS(status.root_id)); + smap_add_format(&smap, "rstp_designated_id", RSTP_ID_FMT, + RSTP_ID_ARGS(status.designated_id)); + smap_add_format(&smap, "rstp_designated_port_id", RSTP_PORT_ID_FMT, + status.designated_port_id); + smap_add_format(&smap, "rstp_bridge_port_id", RSTP_PORT_ID_FMT, + status.bridge_port_id); + ovsrec_bridge_set_rstp_status(br->cfg, &smap); + smap_destroy(&smap); +} + +static void +port_refresh_rstp_status(struct port *port) +{ + struct ofproto *ofproto = port->bridge->ofproto; + struct iface *iface; + struct ofproto_port_rstp_status status; + char *keys[3]; + int64_t int_values[3]; + struct smap smap; + + if (port_is_synthetic(port)) { + return; + } + + /* RSTP doesn't currently support bonds. */ + if (!list_is_singleton(&port->ifaces)) { + ovsrec_port_set_rstp_status(port->cfg, NULL); + return; + } + + iface = CONTAINER_OF(list_front(&port->ifaces), struct iface, port_elem); + if (ofproto_port_get_rstp_status(ofproto, iface->ofp_port, &status)) { + return; + } + + if (!status.enabled) { + ovsrec_port_set_rstp_status(port->cfg, NULL); + ovsrec_port_set_rstp_statistics(port->cfg, NULL, NULL, 0); + return; + } + /* Set Status column. */ + smap_init(&smap); + + smap_add_format(&smap, "rstp_port_id", RSTP_PORT_ID_FMT, + status.port_id); + smap_add_format(&smap, "rstp_port_role", "%s", + rstp_port_role_name(status.role)); + smap_add_format(&smap, "rstp_port_state", "%s", + rstp_state_name(status.state)); + + ovsrec_port_set_rstp_status(port->cfg, &smap); + smap_destroy(&smap); + + /* Set Statistics column. */ + keys[0] = "rstp_tx_count"; + int_values[0] = status.tx_count; + keys[1] = "rstp_rx_count"; + int_values[1] = status.rx_count; + keys[2] = "rstp_uptime"; + int_values[2] = status.uptime; + ovsrec_port_set_rstp_statistics(port->cfg, keys, int_values, + ARRAY_SIZE(int_values)); +} + static bool enable_system_stats(const struct ovsrec_open_vswitch *cfg) { @@ -2494,10 +2787,12 @@ bridge_run(void) struct port *port; br_refresh_stp_status(br); + br_refresh_rstp_status(br); HMAP_FOR_EACH (port, hmap_node, &br->ports) { struct iface *iface; port_refresh_stp_status(port); + port_refresh_rstp_status(port); LIST_FOR_EACH (iface, port_elem, &port->ifaces) { iface_refresh_netdev_status(iface); iface_refresh_ofproto_status(iface); diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema index bf86f2095..3d78f1df4 100644 --- a/vswitchd/vswitch.ovsschema +++ b/vswitchd/vswitch.ovsschema @@ -1,6 +1,6 @@ {"name": "Open_vSwitch", - "version": "7.8.0", - "cksum": "4147598271 20869", + "version": "7.9.0", + "cksum": "2301439325 21345", "tables": { "Open_vSwitch": { "columns": { @@ -54,6 +54,8 @@ "ephemeral": true}, "stp_enable": { "type": "boolean"}, + "rstp_enable": { + "type": "boolean"}, "mcast_snooping_enable": { "type": "boolean"}, "ports": { @@ -96,6 +98,9 @@ "status": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}, + "rstp_status": { + "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}, + "ephemeral": true}, "other_config": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}, "external_ids": { @@ -162,6 +167,12 @@ "status": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}, "ephemeral": true}, + "rstp_status": { + "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}, + "ephemeral": true}, + "rstp_statistics": { + "type": {"key": "string", "value": "integer", "min": 0, "max": "unlimited"}, + "ephemeral": true}, "statistics": { "type": {"key": "string", "value": "integer", "min": 0, "max": "unlimited"}, "ephemeral": true}, diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml index 42e944bc7..bcddc53d7 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -692,6 +692,107 @@ + + In IEEE Std 802.1D, 1998 Edition, and prior editions of this standard, + Clause 8 specified the spanning tree algorithm and protocol (STP).9 STP + has now been superseded by the Rapid Spanning Tree Protocol (RSTP) + specified in Clause 17 of the IEEE Std 802.1D, 2004 Edition. + The IEEE 802.1D-2004 Rapid Spanning Tree Algorithm Protocol configures + full, simple, and symmetric connectivity throughout a Bridged Local Area + Network that comprises individual LANs interconnected by Bridges. + Like STP, RSTP is a network protocol that ensures loop-free topologies. + It allows redundant links to be included in the network to provide + automatic backup paths if the active links fails. + + + Enable Rapid Spanning Tree on the bridge. By default, RSTP is disabled + on bridges. Bond, internal, and mirror ports are not supported + and will not participate in the spanning tree. + + + + The bridge's RSTP address (the lower 48 bits of the bridge-id) + in the form + xx:xx:xx:xx:xx:xx. + By default, the address is the MAC address of the bridge. + + + + The bridge's relative priority value for determining the root + bridge (the upper 16 bits of the bridge-id). A bridge with the + lowest bridge-id is elected the root. By default, the priority + is 0x8000 (32768). This value needs to be a multiple of 4096, otherwise + it's rounded to the nearest inferior one. + + + + The Ageing Time parameter for the Bridge. The default value + is 300. + + + + The Force Protocol Version parameter for the Bridge. This + can take the value 0 (.STP Compatibility. mode) or 2 + (the default, normal operation). + + + + The maximum age of the information transmitted by the Bridge + when it is the Root Bridge. The default value is 20. + + + + The delay used by STP Bridges to transition Root and Designated + Ports to Forwarding. The default value is 15. + + + + The Transmit Hold Count used by the Port Transmit state machine + to limit transmission rate. The default value is 6. + + + + The RSTP enable parameter of the Port. + + + + The port's relative priority value for determining the root + port (the upper 8 bits of the port-id). A port with the lowest + port-id is elected the root. + By default, the port priority is 0x80 (128). This value needs + to be a multiple of 16, otherwise it's rounded to the nearest + inferior one. + + + + The port's relative id for determining the root port + (the lower 8 bits of the port-id). A port with the lowest + port-id is elected the root. + + + + The port path cost. The Port.s contribution, when it is + the Root Port, to the Root Path Cost for the Bridge. + + + + The admin edge port parameter for the Port. + + + + The auto edge port parameter for the Port. + + + The mcheck port parameter for the Port. + + + Name of datapath provider. The kernel datapath has -- cgit v1.2.1