From 325941057766e20bfde2052d99b4b54b32856249 Mon Sep 17 00:00:00 2001 From: Srinivas Aji Date: Fri, 30 Nov 2007 01:28:09 +0530 Subject: [PATCH] Add in new RSTP implementation to replace RSTPLIB. We have a new implementation of RSTP, which is meant to implement the Spanning Tree Protocol in IEEE 802.1D-2004. This implementation is written as a library consisting of one .c file and one .h file. It is written from scratch directly from the 802.1D-2004 document. This commit adds the files rstp.h and rstp.c to the directory, in preparation for modifying other code to use this instead of rstplib. Signed-off-by: Srinivas Aji --- rstp.c | 3071 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ rstp.h | 226 +++++ 2 files changed, 3297 insertions(+) create mode 100644 rstp.c create mode 100644 rstp.h diff --git a/rstp.c b/rstp.c new file mode 100644 index 0000000..7191778 --- /dev/null +++ b/rstp.c @@ -0,0 +1,3071 @@ +/***************************************************************************** + Copyright (c) 2007 EMC Corporation. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + The full GNU General Public License is included in this distribution in the + file called LICENSE. + + Authors: Srinivas Aji + +******************************************************************************/ + +/********************************************************************* + This is an implementation of the Rapid Spanning Tree Protocol + based on the 802.1D-2004 standard. + Section references in the code refer to those parts of the standard. +*********************************************************************/ + +#include "rstp.h" +#include + +/* Macros to be used in the later parts */ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define FALSE 0 +#define TRUE 1 + + +/* memcmp wrapper, validating size equality of _a and _b */ +#define CMP(_a, _op, _b) \ +(memcmp(&(_a), &(_b), \ + __builtin_choose_expr(sizeof(_a) == sizeof(_b), \ + sizeof(_a), \ + (void)0) \ +) _op 0) + +#define ZERO(_a) memset(&(_a), 0, sizeof(_a)) + +/* memcmp wrapper, validating size equality of _a and _b */ +#define CPY(_a, _b) \ +(memcpy(&(_a), &(_b), \ + __builtin_choose_expr(sizeof(_a) == sizeof(_b), \ + sizeof(_a), \ + (void)0) \ +)) + +#define BRIDGE_ARGS_PROTO Bridge *BRIDGE +#define PORT_ARGS_PROTO Bridge *BRIDGE, Port *PORT + +#define BRIDGE_ARGS BRIDGE +#define PORT_ARGS BRIDGE, PORT +#define FPORT_ARGS BRIDGE, FPORT + +/* Define BRIDGE and PORT as global pointers to a struct type which holds + a user_ref of value NULL, and no other fields. + That way log messages can use BRIDGE and PORT always. +*/ + +struct _dummy_user_ref_struct +{ + void *user_ref; +} _user_ref_var = { .user_ref = NULL}, + *BRIDGE = &_user_ref_var, *PORT = &_user_ref_var; + +/* Logging macros */ +#define DEBUG(fmt...) \ + STP_OUT_logmsg(BRIDGE->user_ref, PORT->user_ref, STP_LOG_LEVEL_DEBUG, fmt) +#define ERROR(fmt...) \ + STP_OUT_logmsg(BRIDGE->user_ref, PORT->user_ref, STP_LOG_LEVEL_ERROR, fmt) +#define ABORT(fmt...) \ + ({ ERROR(fmt); *(int *)0 = 1; }) + +/***************** Macros for state machine descriptions ********************/ + +#define _CAT2(x, y) x ## _ ## y + +#define _CAT(x, y) _CAT2(x, y) + +#define __STR(x) #x + +#define _STR(x) __STR(x) + +#define STN(_state) _CAT(STM_NAME, _state) + +#define STM_LABEL(_state) _CAT(STM_NAME, label_ ## _state) + +#define STM_FUNC(_name) _CAT(_name, func) + +/* BEGIN is state 0 in all state machines */ +#define STATES(_args...) enum _CAT(STM_NAME, states) { STN(BEGIN), _args } + +/* Transition definition */ +#define TR(_cond, _new_state) \ +({ \ + if (_cond) { \ + new_state = STN(_new_state); \ + DEBUG("%s: Transition to state %s\n", _STR(STM_NAME), #_new_state); \ + goto STM_LABEL(_new_state); \ + } \ +}) + +/* Global conditions: Transition conditions that don't depend on current state. + In our state machine, they get checked after those that are specific to the + current state + new_state is the state we last transitioned to, or 0 if no transition + was made. +*/ +#define GC(_transitions...) \ + global_conditions: \ + _transitions \ + return new_state + +/* Transitions from BEGIN state */ +#define BG(_transitions...) \ + case STN(BEGIN): \ + _transitions \ + ABORT("%s: No where to go from BEGIN\n", _STR(STM_NAME)); \ + return -1 + +/* State definition */ +#define ST(_state, _actions, _transitions) \ + STM_LABEL(_state): \ + _actions \ + if (single_step) \ + return new_state; \ + case STN(_state): \ + _transitions \ + goto global_conditions + +#define STM_FUNC_DECL(_type, _args...) \ + int _type(int state, int single_step, _args) + +/* This just barely works. _args expansion includes commas */ +//int STM_FUNC(STM_NAME)(int state, int single_step, _args) + +#define STM(_args, _contents...) \ +static STM_FUNC_DECL(STM_FUNC(STM_NAME), _args) \ +{ \ + int new_state = 0; \ + switch (state) { \ + _contents \ + default: \ + ABORT("%s: Got unknown state %d\n", __func__, state); \ + return -1; \ + } \ +} + +#define UCT 1 + +#define STM_RUN(_state, _transitioned, _func, _args...) \ +({ \ + int new_state = _func(*_state, 0/*no single step*/, _args); \ + if (new_state > 0) { \ + *_state = new_state; \ + *_transitioned = TRUE; \ + } \ +}) + +#define STM_BEGIN(_state, _func, _args...) \ +({ \ + *_state = _func(0/*BEGIN*/, 1/*single step*/, _args); \ +}) + +/************ End of Macros for state machine descriptions ***********/ + +//Types: + +typedef unsigned char uchar; + +typedef unsigned int uint; + +typedef unsigned short uint16; + +typedef uint bool; + +typedef struct _BridgeId { + uchar bridge_identifier_priority[2]; + uchar bridge_address[6]; +} __attribute__((packed)) BridgeId; + +typedef struct { + uchar cost[4]; +} PathCost; + +static inline uint path_cost_to_uint(PathCost x) +{ + return + (x.cost[0] << 24) + (x.cost[1] << 16) + (x.cost[2] << 8) + (x.cost[3]); + +} + + +static inline PathCost path_cost_add(PathCost x, uint y) +{ + uint z = y + + (x.cost[0] << 24) + (x.cost[1] << 16) + (x.cost[2] << 8) + (x.cost[3]); + + PathCost r = { + .cost = { + (z >> 24), (z >> 16) & 0xff, (z >> 8) & 0xff, z & 0xff + } + }; + + return r; +} + +typedef struct _PortId { + uchar port_id[2]; /* 4 high bits of priority and 12 for Id */ +} __attribute__((packed)) PortId; + +static inline uint port_id_to_uint(PortId p) +{ + return (p.port_id[0] << 8) + p.port_id[1]; +} + +typedef struct _PriorityVector +{ + BridgeId root_bridge_id; + PathCost root_path_cost; + BridgeId designated_bridge_id; + PortId designated_port_id; + PortId bridge_port_id; +} __attribute__((packed)) PriorityVector; + +/* First 4 components of priority vector */ +typedef struct _PriorityVector4 +{ + BridgeId root_bridge_id; + PathCost root_path_cost; + BridgeId designated_bridge_id; + PortId designated_port_id; +} __attribute__((packed)) PriorityVector4; + + +typedef struct _Times +{ + uint forward_delay; + uint hello_time; + uint max_age; + uint message_age; +} Times; + +typedef enum _RcvdInfo { + SuperiorDesignatedInfo, + RepeatedDesignatedInfo, + InferiorDesignatedInfo, + InferiorRootAlternateInfo, + OtherInfo +} RcvdInfo; + + +typedef enum _InfoType { + Mine, + Aged, + Received, + Disabled +} InfoType; + +// We accomodate the port role encoding in BPDUs as per 9.2.9 +// into this type, defining an extra value of AltBackupPort valid +// only in a BPDU. +typedef enum _Role { + UnknownPort = 0, + AltBackupPort = 1, + RootPort = 2, + DesignatedPort = 3, + AlternatePort, + BackupPort, + DisabledPort +} Role; + +typedef enum _BPDUType { + BPDUTypeConfig = 0, + BPDUTypeRST = 2, + BPDUTypeTCN = 128 +} BPDUType; + +/* Defining fields directly in struct _Port */ +#if 0 +typedef struct _ParsedBPDU +{ + uint version; + BPDUType bpdu_type; + + /* flags */ + bool bpdu_tcFlag; // bit 1 of flags + bool bpdu_proposalFlag; // bit 2 of flags + Role bpdu_role; // bits 3 & 4 of flags + bool bpdu_learningFlag; // bit 5 of flags + bool bpdu_forwardingFlag; // bit 6 of flags + bool bpdu_agreementFlag; // bit 7 of flags + bool bpdu_tcAckFlag; // bit 8 of flags + + PriorityVector4 bpdu_priority; + Times bpdu_times; + +} ParsedBPDU; +#endif + +#define STP_PROTOCOL_ID 0 + +typedef struct _RawBPDU +{ + uint16 protocol_id; + uchar version; + uchar type; + /* TCN has only upto this */ + uchar TCN_END[0]; + uchar flags; +#define BPDU_FLAG_TC 0x1 +#define BPDU_FLAG_PROPOSAL 0x2 +#define BPDU_FLAG_ROLE_MASK 0xc +#define BPDU_FLAG_ROLE(role) ((role << 2) & BPDU_FLAG_ROLE_MASK) +#define BPDU_FLAG_ROLE_GET(flags) ((flags & BPDU_FLAG_ROLE_MASK)>>2) +#define BPDU_FLAG_LEARNING 0x10 +#define BPDU_FLAG_FORWARDING 0x20 +#define BPDU_FLAG_AGREEMENT 0x40 +#define BPDU_FLAG_TC_ACK 0x80 + + PriorityVector4 priority; + + uchar message_age[2]; + uchar max_age[2]; + uchar hello_time[2]; + uchar forward_delay[2]; + /* End of Config BPDU */ + uchar CONFIG_END[0]; + + uchar version1_len; + /* End of RST BPDU */ + uchar RST_END[0]; + +} __attribute__((packed)) RawBPDU; + + + +/* Values for adminPointToPointMAC */ +typedef enum _AdminP2P { + P2PForceFalse = STP_ADMIN_P2P_FORCE_FALSE, + P2PForceTrue = STP_ADMIN_P2P_FORCE_TRUE, + P2PAuto = STP_ADMIN_P2P_AUTO +} AdminP2P; + + +/* Number of per bridge and per port state machines. */ +#define NUM_BRIDGE_STATE_MACHINES 1 +#define NUM_PORT_STATE_MACHINES 9 + +/* + Framework: + + Wherever this is a bridge context, we assume the variable BRIDGE points + to it. And wherever there is a port context, we assume PORT points to it. + + We define all the fields in the Bridge and Port structures as starting + with and underscore and have + #define bridgevar BRIDGE->_bridgevar + and + #define portvar PORT->_portvar + + This way we keep the expressions in the state machines fairly close + to the way they appear in the standard's figures. + +*/ + +typedef STP_Port Port; +typedef STP_Bridge Bridge; + +/* Per Bridge: */ + +struct STP_Bridge_ +{ + + /* Administratively set variables */ + + // 17.13.2 + // - Not defining - Never used. + // This is meant to control FDB, we don't deal with that here. + /*int _AgeingTime;*/ + /*#define AgeingTime BRIDGE->_AgeingTime*/ + + // 17.13.4 + // This is item a) in the list in the introduction in 17.13 + // As per that, we need to reinitialize state machines by asserting BEGIN + // when changing this + uint _ForceProtocolVersion; +#define ForceProtocolVersion BRIDGE->_ForceProtocolVersion + + // 17.13.5 + // - Not defining - this is part of BridgeTimes below + /*int BridgeForwardDelay;*/ +#define BridgeForwardDelay BridgeTimes.forward_delay + + // 17.13.6 + // - Not defining - this is part of BridgeTimes below + /*int BridgeHelloTime;*/ +#define BridgeHelloTime BridgeTimes.hello_time + + // 17.13.7 BridgeIdentifierPriority + // This is item b) in the list in the introduction in 17.13 + // As per that, we need to clear selected and set reselect when changing this + // Let us do it for all ports since this is a bridge variable. + // - Not defining this variable, since it is part of BridgeIdentifier below + /*int BridgeIdentifierPriority;*/ +#define BridgeIdentifierPriority BridgeIdentifier.bridge_identifier_priority + + // 17.13.8 + // - Not defining - this is part of BridgeTimes below + /*int BridgeMaxAge;*/ +#define BridgeMaxAge BridgeTimes.max_age + + // 17.13.9 + uint _MigrateTime; +#define MigrateTime BRIDGE->_MigrateTime + + // 17.13.12 + uint _TransmitHoldCount; + +#define TransmitHoldCount BRIDGE->_TransmitHoldCount + + /* Other (not administrative) */ + + // 17.18.1 + // Condition that initializes all state machines. + // But machines will spin if it continues to be asserted, + // So we make it state that we put all machines in instead of asserting this + /*bool BEGIN;*/ + + // 17.18.1 + // The high 16 bits are BridgeIdentifierPriority, noted above (17.13.7) + // We need to change the rest of BridgeIdentifier if the bridge MAC address + // changes. Standard doesn't expect this. Let us reinitialize state machines + // when this happens + BridgeId _BridgeIdentifier; /* Includes priority */ +#define BridgeIdentifier BRIDGE->_BridgeIdentifier + + // 17.18.3, = B:0:B:0:0 - where B is Bridge Identifier + // - Not defining it . + // - We will compute it from BridgeIdentifier in updtRolesTree() + /*PriorityVector _BridgePriority;*/ + /*#define BridgePriority BRIDGE->_BridgePriority*/ + + // 17.18.4 + // This holds BridgeForwardDelay, BridgeHelloTime, BridgeMaxAge + // in .forward_delay, .hello_time, .max_age + // .message_age is 0 + Times _BridgeTimes; +#define BridgeTimes BRIDGE->_BridgeTimes + + // 17.18.5 + // 5th component of root priority vector + PortId _rootPortId; +#define rootPortId BRIDGE->_rootPortId + + // 17.18.6 + // first 4 components of root priority vector + PriorityVector4 _rootPriority; +#define rootPriority BRIDGE->_rootPriority + + // 17.18.7 + /* operational times got from portTimes for root port, or from BridgeTimes */ + Times _rootTimes; +#define rootTimes BRIDGE->_rootTimes + + + + uint time_since_tc; + uint tc_count; + uint tcWhile_count; /* How many port tcWhile's are nonzero */ + + /* Our stuff - not from standard */ + + Port *port_list; + void *user_ref; + bool stp_on; + int state[NUM_BRIDGE_STATE_MACHINES]; +}; + + +/* Per port */ + +struct STP_Port_ +{ + + /* Administrative variables: */ + + // 17.13.2 + bool _AdminEdgePort; +#define AdminEdgePort PORT->_AdminEdgePort + + // 17.13.3 + bool _AutoEdgePort; +#define AutoEdgePort PORT->_AutoEdgePort + + // 17.13.10 + // This is item c) in the introduction to 17.13 + // As per that, we need to clear selected and set reselect for this port + // when changing this + // - Not defining this, it is part of portId below + /*int PortIdentifierPriority;*/ + // Not defining macro either, it is just 4 high bits in portId.port_id[0] + /*#define PortIdentifierPriority portId.portIdPriority*/ + + // 17.13.11 + // This is item d) in the introduction to 17.13 + // As per that, we need to clear selected and set reselect for this port + // when changing this + // PortPathCost defined below ref 17.19.20, is set to this if this is + // non-zero, else it is auto-set based on link speed. + uint _AdminPortPathCost; +#define AdminPortPathCost PORT->_AdminPortPathCost + + // 6.4.2 + // MAC Enabled: Controlled by ifconfig up/down + // We will sense and act on it + // We set port enabled in that case + + // 6.4.3 + AdminP2P _adminPointToPointMAC; +#define adminPointToPointMAC PORT->_adminPointToPointMAC + + bool _operPointToPointMAC; +#define operPointToPointMAC PORT->_operPointToPointMAC + + /* Timers: */ + + // 17.17.1 + uint _edgeDelayWhile; +#define edgeDelayWhile PORT->_edgeDelayWhile + + // 17.17.2 + uint _fdWhile; +#define fdWhile PORT->_fdWhile + + // 17.17.3 + uint _helloWhen; +#define helloWhen PORT->_helloWhen + + // 17.17.4 + uint _mdelayWhile; +#define mdelayWhile PORT->_mdelayWhile + + // 17.17.5 + uint _rbWhile; +#define rbWhile PORT->_rbWhile + + // 17.17.6 + uint _rcvdInfoWhile; +#define rcvdInfoWhile PORT->_rcvdInfoWhile + + // 17.17.7 + uint _rrWhile; +#define rrWhile PORT->_rrWhile + + // 17.17.8 + uint _tcWhile; +#define tcWhile PORT->_tcWhile + + // 17.19.1 + // - Not defining - Never used + // This is supposed to control FDB ageing on a per port basis. + // We don't control FDB ageing here. + /*int _ageingTime;*/ + /*#define ageingTime PORT->_ageingTime*/ + + // 17.19.2 + bool _agree; +#define agree PORT->_agree + + // 17.19.3 + bool _agreed; +#define agreed PORT->_agreed + + // 17.19.4 + // First 4 components of designated priority vector + // 5th component is portId + PriorityVector4 _designatedPriority; +#define designatedPriority PORT->_designatedPriority + + // 17.19.5 + Times _designatedTimes; +#define designatedTimes PORT->_designatedTimes + + // 17.19.6 + bool _disputed; +#define disputed PORT->_disputed + + // 17.19.8 + /* Set by topology change state machine to instruct filtering database to + remove all entries for this Port, immediately if rstpVersion is TRUE + or by rapid ageing if stpVersion is TRUE. Reset by filtering database once + entries are removed if rstpVersion is TRUE and immediately if + stpVersion is TRUE + */ + bool _fdbFlush; +#define fdbFlush PORT->_fdbFlush + + // 17.19.8 + bool _forward; +#define forward PORT->_forward + + // 17.19.9 + // Setting this will involve and OUT action + bool _forwarding; +#define forwarding PORT->_forwarding + + // 17.19.10 + // Indicates origin/state of portInfo held for the port + InfoType _infoIs; +#define infoIs PORT->_infoIs + + // 17.19.11 + bool _learn; +#define learn PORT->_learn + + // 17.19.12 + bool _learning; +#define learning PORT->_learning + + // 17.19.13 + bool _mcheck; +#define mcheck PORT->_mcheck + + // 17.19.14 + // First 4 components of priority vector from received BPDU + PriorityVector4 _msgPriority; +#define msgPriority PORT->_msgPriority + + // 17.19.15 + // Times from received BPDU + Times _msgTimes; +#define msgTimes PORT->_msgTimes + + // 17.19.16 + /* Set if a BPDU is to be transmitted, + reset by Port Transmit state machine */ + bool _newInfo; +#define newInfo PORT->_newInfo + + // 17.19.17 + bool _operEdge; +#define operEdge PORT->_operEdge + + // 17.19.18 + bool _portEnabled; +#define portEnabled PORT->_portEnabled + + // 17.19.19 + PortId _portId; +#define portId PORT->_portId + + // 17.19.20 + uint _PortPathCost; +#define PortPathCost PORT->_PortPathCost + + // 17.19.21 + // First 4 components of port's priority vector + PriorityVector4 _portPriority; +#define portPriority PORT->_portPriority + + // 17.19.22 + /* Ports timer parameter values */ + Times _portTimes; +#define portTimes PORT->_portTimes + + // 17.19.23 + bool _proposed; +#define proposed PORT->_proposed + + // 17.19.24 + bool _proposing; +#define proposing PORT->_proposing + + // 17.19.25 + /* Set by system when Config, TCN, or RST BPDU is received + - IN action sets this */ + /* CORR: rcvdBPDU + But all other references in the doc use rcvdBpdu, so using that. */ + bool _rcvdBpdu; +#define rcvdBpdu PORT->_rcvdBpdu + + // 17.19.26 + /* Result of rcvInfo() procedure */ + RcvdInfo _rcvdInfo; +#define rcvdInfo PORT->_rcvdInfo + + // 17.19.27 + bool _rcvdMsg; +#define rcvdMsg PORT->_rcvdMsg + + // 17.19.28 + bool _rcvdRSTP; +#define rcvdRSTP PORT->_rcvdRSTP + + // 17.19.29 + bool _rcvdSTP; +#define rcvdSTP PORT->_rcvdSTP + + // 17.19.30 + bool _rcvdTc; +#define rcvdTc PORT->_rcvdTc + + // 17.19.31 + bool _rcvdTcAck; +#define rcvdTcAck PORT->_rcvdTcAck + + // 17.19.32 + bool _rcvdTcn; +#define rcvdTcn PORT->_rcvdTcn + + // 17.19.33 + bool _reRoot; +#define reRoot PORT->_reRoot + + // 17.19.34 + bool _reselect; +#define reselect PORT->_reselect + + // 17.19.35 + Role _role; +#define role PORT->_role + + // 17.19.36 + bool _selected; +#define selected PORT->_selected + + // 17.19.37 + Role _selectedRole; +#define selectedRole PORT->_selectedRole + + // 17.19.38 + bool _sendRSTP; +#define sendRSTP PORT->_sendRSTP + + // 17.19.39 + bool _sync; +#define sync PORT->_sync + + // 17.19.40 + bool _synced; +#define synced PORT->_synced + + // 17.19.41 + bool _tcAck; +#define tcAck PORT->_tcAck + + // 17.19.42 + bool _tcProp; +#define tcProp PORT->_tcProp + + // 17.19.43 + bool _tick; +#define tick PORT->_tick + + // 17.19.44 + uint _txCount; +#define txCount PORT->_txCount + + // 17.19.45 + bool _updtInfo; +#define updtInfo PORT->_updtInfo + + // Not in 17.19, but is used. When we receive a BPDU, + // fill in the received BPDU into this. + // We just define the fields directly + + uint _bpduVersion; +#define bpduVersion PORT->_bpduVersion + BPDUType _bpduType; +#define bpduType PORT->_bpduType + /* flags */ + bool _bpduTcFlag; // bit 1 of flags +#define bpduTcFlag PORT->_bpduTcFlag + bool _bpduProposalFlag; // bit 2 of flags +#define bpduProposalFlag PORT->_bpduProposalFlag + Role _bpduRole; // bits 3 & 4 of flags +#define bpduRole PORT->_bpduRole + bool _bpduLearningFlag; // bit 5 of flags +#define bpduLearningFlag PORT->_bpduLearningFlag + bool _bpduForwardingFlag; // bit 6 of flags +#define bpduForwardingFlag PORT->_bpduForwardingFlag + bool _bpduAgreementFlag; // bit 7 of flags +#define bpduAgreementFlag PORT->_bpduAgreementFlag + bool _bpduTcAckFlag; // bit 8 of flags +#define bpduTcAckFlag PORT->_bpduTcAckFlag + PriorityVector4 _bpduPriority; +#define bpduPriority PORT->_bpduPriority + Times _bpduTimes; +#define bpduTimes PORT->_bpduTimes + + + + + /* Our stuff - not from standard */ + + Port *port_next; + Bridge *bridge; + void *user_ref; + + uint port_state_flags; + uint speed; + bool duplex; + int state[NUM_PORT_STATE_MACHINES]; + +}; + +/* Macros for iterating over all ports */ + +/* Check whether expr is true for all ports */ +/* PORT is not defined by this but is possible defined where this is used. + FPORT iterates over all ports, so we need to use the FPORT->_field + (with underscore) for for port variables in the expression here */ +#define ForAllPort(expr) \ +({ \ + Port *FPORT; \ + bool res = TRUE; \ + for (FPORT = BRIDGE->port_list; FPORT != NULL; FPORT = FPORT->port_next) \ + if (!(expr)) { \ + res = FALSE; \ + break; \ + } \ + res; \ +}) + +/* Execute statements for all ports */ +/* PORT is not defined by this but is possible defined where this is used. + FPORT iterates over all ports, so we need to use the FPORT->_field + (with underscore) for for port variables in statements here */ +#define ForAllPortDo(statements...) \ +({ \ + Port *FPORT; \ + for (FPORT = BRIDGE->port_list; FPORT != NULL; FPORT = FPORT->port_next) \ + { statements } \ +}) + +// 17.20 ----- conditions and paramaters + +// 17.20.1 +#define AdminEdge AdminEdgePort + +// 17.20.2 +#define AutoEdge AutoEdgePort + +// 17.20.3 +#define allSynced ForAllPort(FPORT->_synced || FPORT->_role == RootPort) + +// 17.20.4 +#define EdgeDelay (operPointToPointMAC?MigrateTime:MaxAge) + +// 17.20.5 +#define forwardDelay (sendRSTP?HelloTime:FwdDelay) + +// 17.20.6 +#define FwdDelay designatedTimes.forward_delay + +// 17.20.7 +#define HelloTime designatedTimes.hello_time + +// 17.20.8 +#define MaxAge designatedTimes.max_age + +// 17.20.9 +// #define MigrateTime MigrateTime + +// 17.20.10 +#define reRooted ForAllPort(FPORT == PORT || FPORT->_rrWhile == 0) + +// 17.20.11 +#define rstpVersion (ForceProtocolVersion >= 2) + +// 17.20.12 +#define stpVersion (ForceProtocolVersion < 2) + +// 17.20.13 +#define TxHoldCount TransmitHoldCount + +// 17.21 ---------- Procedures ------------------------ + +// 17.21.1 +// Definition has an argument newInfoIs, but the uses don't seem to have it. +// So we define it without that, assuming newInfoIs == infoIs +static bool betterorsameInfo(PORT_ARGS_PROTO/*, InfoType newInfoIs*/) +{ + return + (/*newInfoIs == Received &&*/ infoIs == Received && + CMP(msgPriority, <=, portPriority)) || + (/*newInfoIs == Mine &&*/ infoIs == Mine && + CMP(designatedPriority, <=, portPriority)); +} + +// 17.21.2 +static void clearReselectTree(BRIDGE_ARGS_PROTO) +{ + ForAllPortDo( + FPORT->_reselect = FALSE; + ); +} + +// 17.21.3 +static void disableForwarding(PORT_ARGS_PROTO) +{ + /* Cause forwarding process to stop forwarding frames through this port. */ + /* Complete before returning */ + /* OUT */ + PORT->port_state_flags &= ~STP_PORT_STATE_FLAG_FORWARDING; + STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags); +} + +// 17.21.4 +static void disableLearning(PORT_ARGS_PROTO) +{ + /* Cause port to stop learning process */ + /* Complete before returning */ + /* OUT */ + PORT->port_state_flags &= ~STP_PORT_STATE_FLAG_LEARNING; + STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags); +} + +// 17.21.5 +static void enableForwarding(PORT_ARGS_PROTO) +{ + /* Cause port to start forwarding. */ + /* Complete before returning */ + /* OUT */ + PORT->port_state_flags |= STP_PORT_STATE_FLAG_FORWARDING; + STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags); +} + +// 17.21.6 +static void enableLearning(PORT_ARGS_PROTO) +{ + /* Cause port to start learning. */ + /* Complete before returning */ + /* OUT */ + PORT->port_state_flags |= STP_PORT_STATE_FLAG_LEARNING; + STP_OUT_port_set_state(PORT->user_ref, PORT->port_state_flags); +} + +/**** Topology change tracking and reporting ****/ + +// change is +1 if a tcWhile became non-zero, -1 if a tcWhile became 0. +static inline void update_tcWhile_count(BRIDGE_ARGS_PROTO, int change) +{ + if (change == 0) + return; + + if (BRIDGE->tcWhile_count == 0) { + /* Becoming nonzero from zero */ + BRIDGE->tc_count++; + BRIDGE->time_since_tc = 0; + } + + BRIDGE->tcWhile_count+= change; +} + +#define set_tcWhile(_val) \ +({ uint _oldval = tcWhile; tcWhile = _val; \ + update_tcWhile_count(BRIDGE_ARGS, (tcWhile?1:0) - (_oldval?1:0)); }) + + +// 17.21.7 +static void newTcWhile(PORT_ARGS_PROTO) +{ + if (tcWhile == 0) { + if (sendRSTP /* CORR: sendRstp */) { + // Standard wants tcWhile rounded up + // We rounded HelloTime up when we received it. + //tcWhile = HelloTime + 1; + set_tcWhile(HelloTime + 1); + + newInfo = TRUE; + } + else { + //tcWhile = rootTimes.max_age + rootTimes.forward_delay; + set_tcWhile(rootTimes.max_age + rootTimes.forward_delay); + } + } +} + +// 17.21.8 +static RcvdInfo rcvInfo(PORT_ARGS_PROTO) +{ + + if (bpduType == BPDUTypeTCN) { + // DIFF: + // 802.1D-2004 doesn't say explicitly what we should do in this case + // 802.1w-2001 says return OtherInfo, but just doing that won't ever + // set rcvdTcn, or cause any effect + // 802.1Q-2005 (MST, based on 802.1D-2004) says set rcvdTcn here + // Going by that, we set rcvdTcn and return OtherInfo + + rcvdTcn = TRUE; + return OtherInfo; + } + + /* Note: config BPDU conveys Designated Port Role */ + if (bpduType == BPDUTypeConfig) + bpduRole = DesignatedPort; + + msgPriority = bpduPriority; + msgTimes = bpduTimes; + + if (bpduRole == DesignatedPort) { + // a) + if (CMP(msgPriority, <, portPriority) // 1) + || (CMP(msgPriority, ==, portPriority) // 2) + && CMP(msgTimes, !=, portTimes))) + return SuperiorDesignatedInfo; + + // b) + if (CMP(msgPriority, ==, portPriority) && CMP(msgTimes, ==, portTimes)) + return RepeatedDesignatedInfo; + + // c) + if (CMP(msgPriority, >, portPriority)) + return InferiorDesignatedInfo; + } + + if ((bpduRole == RootPort || bpduRole == AltBackupPort) && + CMP(msgPriority, >=, portPriority)) + return InferiorRootAlternateInfo; + + return OtherInfo; + +} + +// 17.21.9 +static void recordAgreement(PORT_ARGS_PROTO) +{ + if (rstpVersion && operPointToPointMAC && bpduAgreementFlag) { + agreed = TRUE; + proposing = FALSE; + } + else + agreed = FALSE; +} + +// 17.21.10 +// No one sets disputed in 802.1D-2004, but see below. +static void recordDispute(PORT_ARGS_PROTO) +{ + if (// bpduType == BPDUTypeRST && -- Implied since only RST has this flag + bpduLearningFlag) { + // DIFF: Going by 802.1Q-2005, and the remark at the end of + // 17.29.3 Designated Port states, about setting disputed, + // it looks like what we need is + + disputed = TRUE; + agreed = FALSE; + + // and not, as written in 17.21.10, + // agreed = TRUE; + // proposing = FALSE; + } +} + +// 17.21.11 +static void recordProposal(PORT_ARGS_PROTO) +{ + if (bpduRole == DesignatedPort && bpduProposalFlag) + proposed = TRUE; +} + +// 17.21.12 +static void recordPriority(PORT_ARGS_PROTO) +{ + portPriority = msgPriority; +} + +#define MIN_COMPAT_HELLO_TIME 1 + +// 17.21.13 +static void recordTimes(PORT_ARGS_PROTO) +{ + portTimes = msgTimes; + if (portTimes.hello_time < MIN_COMPAT_HELLO_TIME) + portTimes.hello_time = MIN_COMPAT_HELLO_TIME; +} + +/* Per bridge */ +// 17.21.14 +static void setSyncTree(BRIDGE_ARGS_PROTO) +{ + ForAllPortDo( + FPORT->_sync = TRUE; + ); +} + +// 17.21.15 +static void setReRootTree(BRIDGE_ARGS_PROTO) +{ + ForAllPortDo( + FPORT->_reRoot = TRUE; + ); + +} + +// 17.21.16 +static void setSelectedTree(BRIDGE_ARGS_PROTO) +{ + if (ForAllPort(FPORT->_reselect == FALSE)) + ForAllPortDo( + FPORT->_selected = TRUE; + ); +} + +// 17.21.17 +static void setTcFlags(PORT_ARGS_PROTO) +{ + if (bpduType == BPDUTypeTCN) + rcvdTcn = TRUE; + else { // Only other types are Config and RST + //if (bpduType == BPDUTypeConfig || bpduType == BPDUTypeRST) { + if (bpduTcFlag) + rcvdTc = TRUE; + if (bpduTcAckFlag) + rcvdTcAck = TRUE; + } +} + +// 17.21.18 +static void setTcPropTree(PORT_ARGS_PROTO) +{ + ForAllPortDo( + if (FPORT != PORT) FPORT->_tcProp = TRUE; + ); +} + +/***** txConfig(), txRstp() and txTcn() *****/ + +/* To decide how much of the RawBPDU to actually transmit */ +#define offsetof(_TYPE, _MEMBER) __builtin_offsetof (_TYPE, _MEMBER) +//#define offsetof(_TYPE, _MEMBER) ((int)&((_TYPE *)0)->_MEMBER) + +/* To fill the times values in the BPDU in txConfig() and txRstp() */ + +static void set_bpdu_time(uchar time[2], int t) +{ + time[0] = t; + time[1] = 0; +} + +static void set_bpdu_times(RawBPDU *b, Times *t) +{ + set_bpdu_time(b->message_age, t->message_age); + set_bpdu_time(b->max_age, t->max_age); + set_bpdu_time(b->hello_time, t->hello_time); + set_bpdu_time(b->forward_delay, t->forward_delay); +} + +// 17.21.19 +static void txConfig(PORT_ARGS_PROTO) +{ + RawBPDU b; + + b.protocol_id = STP_PROTOCOL_ID; + b.version = 0; + b.type = BPDUTypeConfig; + + b.priority = designatedPriority; + + int flags = 0; + flags |= (tcWhile != 0) ? BPDU_FLAG_TC : 0; + flags |= (tcAck) ? BPDU_FLAG_TC_ACK : 0; + b.flags = flags; + + set_bpdu_times(&b, &designatedTimes); + + DEBUG("Sending Config BPDU\n"); + STP_OUT_tx_bpdu(PORT->user_ref, &b, offsetof(RawBPDU, CONFIG_END)); +} + +// 17.21.20 +static void txRstp(PORT_ARGS_PROTO) +{ + RawBPDU b; + + b.protocol_id = STP_PROTOCOL_ID; + b.version = 2; + b.type = BPDUTypeRST; + + b.priority = designatedPriority; + + int flags = 0; + + int r = role; + if (r == AlternatePort || r == BackupPort) + r = AltBackupPort; + + flags |= BPDU_FLAG_ROLE(r); + + flags |= agree ? BPDU_FLAG_AGREEMENT : 0; + flags |= proposing ? BPDU_FLAG_PROPOSAL : 0; + flags |= (tcWhile != 0) ? BPDU_FLAG_TC : 0; + // tcAckFlag = 0; + flags |= learning ? BPDU_FLAG_LEARNING : 0; + flags |= forwarding ? BPDU_FLAG_FORWARDING : 0; + + b.flags = flags; + + set_bpdu_times(&b, &designatedTimes); + + b.version1_len = 0; + + DEBUG("Sending RST BPDU\n"); + STP_OUT_tx_bpdu(PORT->user_ref, &b, offsetof(RawBPDU, RST_END)); +} + +// 17.21.21 +static void txTcn(PORT_ARGS_PROTO) +{ + RawBPDU b; + + b.protocol_id = STP_PROTOCOL_ID; + b.version = 0; + b.type = BPDUTypeTCN; + + DEBUG("Sending TCN BPDU\n"); + STP_OUT_tx_bpdu(PORT->user_ref, &b, offsetof(RawBPDU, TCN_END)); +} + +// 17.21.22 +static void updtBPDUVersion(PORT_ARGS_PROTO) +{ + if ((bpduVersion == 0 || bpduVersion == 1) + && (bpduType == BPDUTypeTCN || bpduType == BPDUTypeConfig)) + rcvdSTP = TRUE; + if (bpduType == BPDUTypeRST) + rcvdRSTP = TRUE; +} + +// 17.21.23 +static void updtRcvdInfoWhile(PORT_ARGS_PROTO) +{ + if (portTimes.message_age + 1 <= portTimes.max_age) + rcvdInfoWhile = 3*portTimes.hello_time; + else + rcvdInfoWhile = 0; +} + +// 17.21.24 +static void updtRoleDisabledTree(BRIDGE_ARGS_PROTO) +{ + ForAllPortDo( + FPORT->_selectedRole = DisabledPort; + ); +} + +// 17.21.25 +static void updtRolesTree(BRIDGE_ARGS_PROTO) +{ + // Computes Spanning Tree Priority vectors (17.5, 17.6) and timer values + /* + Sets: + bridge's rootTimes, + designatedPriority for each port, designatedTimes for each Port + For each port: selectedRole, possibly updtInfo + */ + + // a), b) + PriorityVector4 root_priority; + PortId root_port_id; + Port *root_port = NULL; + + /* Initialize root_priority to computed BridgePriority = B:0:B:0:0 */ + ZERO(root_priority); + ZERO(root_port_id); + root_priority.root_bridge_id = BridgeIdentifier; + root_priority.designated_bridge_id = BridgeIdentifier; + + ForAllPortDo(Port *PORT = FPORT; // So we can use port var names directly + if (infoIs == Received && + CMP(portPriority.designated_bridge_id, !=, BridgeIdentifier) + ) { + // a) - root path priority vector + PriorityVector4 root_path_priority = portPriority; + root_path_priority.root_path_cost = + path_cost_add(root_path_priority.root_path_cost, + PortPathCost); + if (CMP(root_path_priority, <, root_priority) + || (CMP(root_path_priority, ==, root_priority) + && CMP(portId, <, root_port_id))) { + root_priority = root_path_priority; + root_port_id = portId; + root_port = PORT; + } + } + ); + // Now root_priority is the minimum of BridgePriority and + // of all the root path priorities where designated bridge id is not our ID + // b) + rootPriority = root_priority; + rootPortId = root_port_id; + + // c) + if (root_port != NULL) { + rootTimes = root_port->_portTimes; + rootTimes.message_age += 1; /* Rounded to whole second */ + } + else + rootTimes = BridgeTimes; + + + ForAllPortDo(Port *PORT = FPORT; + // d) + designatedPriority.root_bridge_id = + root_priority.root_bridge_id; + designatedPriority.root_path_cost = + root_priority.root_path_cost; + designatedPriority.designated_bridge_id = BridgeIdentifier; + designatedPriority.designated_port_id = portId; + + // e) + designatedTimes = rootTimes; + designatedTimes.hello_time = BridgeTimes.hello_time; + ); + + + ForAllPortDo(Port *PORT = FPORT; + switch (infoIs) { + case Disabled: // f) + selectedRole = DisabledPort; + break; + case Aged: // g) + updtInfo = TRUE; + selectedRole = DesignatedPort; + break; + case Mine: // h) + selectedRole = DesignatedPort; + if (CMP(designatedPriority, !=, portPriority) || + // Maybe below should be root_port->_portTimes instead + // of designatedTimes, going literally by h)? + // designatedTimes same as that that except for hello_time + CMP(designatedTimes, !=, portTimes)) + updtInfo = TRUE; + break; + + case Received: + if (PORT == root_port) { // i) + selectedRole = RootPort; + updtInfo = FALSE; + } + // In j), k), l) standard says designated priority is (or + // is not) _higher_ than the port priority + // Seems like it should mean _better_, numerically + // higher is worse. We assume it means better, i.e. + // numerically lower + else if (CMP(designatedPriority, >=, portPriority)) { + if (CMP(portPriority.designated_bridge_id, + !=, BridgeIdentifier)) { // j) + selectedRole = AlternatePort; + } + else { // k) + /* portPriority.designated_bridge_id is our bridge */ + selectedRole = BackupPort; + } + updtInfo = FALSE; // for j) and k) + } + else { // l) + /* Not root port and designatedPriority < portPriority */ + selectedRole = DesignatedPort; + updtInfo = TRUE; + } + break; + default: + ERROR("Unknown value for infoIs: %d\n", infoIs); + } + DEBUG("Selected Role Set to: %d\n", selectedRole); + ); +} + + + +/* Other procedures we need */ + +/* Call this whenever we set fdbFlush to TRUE. This will flush the + fdb data on the specfied port. */ +static void do_fdbFlush(PORT_ARGS_PROTO) +{ + if (rstpVersion) { + STP_OUT_port_fdb_flush(PORT->user_ref); + fdbFlush = FALSE; /* done */ + } + else { + /* We need to reduce ageing time to FwdDelay for time FwdDelay */ + /* Let us just flush them right away. + Bridge doesn't let us set ageing by port anyway */ + STP_OUT_port_fdb_flush(PORT->user_ref); + fdbFlush = FALSE; + } +} + +/**************************** STATE MACHINES *******************************/ + +/*--------------- State machine diagrams ------------------------*/ + +// 17.22 - Fig. 17-13 + +#define STM_NAME PortTimers + +STATES(STN(ONE_SECOND), STN(TICK)); + +#define dec(x) if (x) x = x - 1 + +STM(PORT_ARGS_PROTO, + + GC(); + + BG( + TR(UCT, ONE_SECOND); + ); + + ST(ONE_SECOND, + tick = FALSE; + , + TR(tick == TRUE, TICK); + ); + + ST(TICK, + dec(helloWhen); + //dec(tcWhile); + if (tcWhile) { + set_tcWhile(tcWhile - 1); + } + dec(fdWhile); + dec(rcvdInfoWhile); + dec(rrWhile); + dec(rbWhile); + dec(mdelayWhile); + dec(edgeDelayWhile); + dec(txCount); + , + TR(UCT, ONE_SECOND); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.23 - Fig. 17-14 + +#define STM_NAME PortReceive + +STATES(STN(DISCARD), STN(RECEIVE)); + +STM(PORT_ARGS_PROTO, + + GC( + TR((rcvdBpdu || (edgeDelayWhile != MigrateTime)) && !portEnabled, + DISCARD); + ); + + BG( + TR(UCT, DISCARD); + ); + + ST(DISCARD, + rcvdBpdu = rcvdRSTP = rcvdSTP = FALSE; + rcvdMsg = FALSE; + edgeDelayWhile = MigrateTime; + , + TR(rcvdBpdu && portEnabled, RECEIVE); + ); + + ST(RECEIVE, + updtBPDUVersion(PORT_ARGS); + operEdge = rcvdBpdu = FALSE; + rcvdMsg = TRUE; + edgeDelayWhile = MigrateTime; + , + TR(rcvdBpdu && portEnabled && !rcvdMsg, RECEIVE); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.24 - Fig. 17-15 + +#define STM_NAME PortProtocolMigration + +STATES(STN(CHECKING_RSTP), STN(SELECTING_STP), STN(SENSING)); + +STM(PORT_ARGS_PROTO, + + GC(); + + BG( + TR(UCT, CHECKING_RSTP); + ); + + ST(CHECKING_RSTP, + mcheck = FALSE; + sendRSTP = (rstpVersion); + mdelayWhile = MigrateTime; + , + TR(mdelayWhile == 0, SENSING); + TR((mdelayWhile != MigrateTime) && !portEnabled, CHECKING_RSTP); + ); + + ST(SELECTING_STP, + sendRSTP = FALSE; + mdelayWhile = MigrateTime; + , + TR(mdelayWhile == 0 || !portEnabled || mcheck, SENSING); + ); + + ST(SENSING, + rcvdRSTP = rcvdSTP = FALSE; + , + TR(!portEnabled || mcheck || ((rstpVersion) && !sendRSTP && rcvdRSTP), + CHECKING_RSTP); + TR(sendRSTP && rcvdSTP, SELECTING_STP); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.25 - Fig. 17-16 + +#define STM_NAME BridgeDetection + +STATES(STN(EDGE), STN(NOT_EDGE)); + +STM(PORT_ARGS_PROTO, + + GC(); + + BG( + TR(AdminEdge, EDGE); + TR(!AdminEdge, NOT_EDGE); + ); + + ST(EDGE, + operEdge = TRUE; + , + TR((!portEnabled && !AdminEdge) || !operEdge, NOT_EDGE); + ); + + ST(NOT_EDGE, + operEdge = FALSE; + , + TR((!portEnabled && AdminEdge) || + ((edgeDelayWhile == 0) && AutoEdge + && sendRSTP /* CORR: sendRstp */ && proposing), + EDGE); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.26 - Fig. 17-17 + +#define xc selected && !updtInfo +#define xc2 newInfo && (txCount < TxHoldCount) && (helloWhen != 0) + +#define STM_NAME PortTransmit + +STATES(STN(TRANSMIT_INIT), STN(TRANSMIT_PERIODIC), STN(TRANSMIT_CONFIG), + STN(TRANSMIT_TCN), STN(TRANSMIT_RSTP), STN(IDLE)); + +STM(PORT_ARGS_PROTO, + + GC(); + + BG( + TR(UCT, TRANSMIT_INIT); + ); + + ST(TRANSMIT_INIT, + newInfo = TRUE; + txCount = 0; + , + TR(UCT, IDLE); + ); + + ST(TRANSMIT_PERIODIC, + newInfo = newInfo || (role == DesignatedPort || + (role == RootPort && (tcWhile != 0))); + , + TR(UCT, IDLE); + ); + + ST(TRANSMIT_CONFIG, + newInfo = FALSE; + txConfig(PORT_ARGS); + txCount += 1; + tcAck = FALSE; + , + TR(UCT, IDLE); + ); + + ST(TRANSMIT_TCN, + newInfo = FALSE; + txTcn(PORT_ARGS); + txCount += 1; + , + TR(UCT, IDLE); + ); + + ST(TRANSMIT_RSTP, + newInfo = FALSE; + txRstp(PORT_ARGS); + txCount += 1; + tcAck = FALSE; + , + TR(UCT, IDLE); + ); + + ST(IDLE, + helloWhen = HelloTime; + , + TR(helloWhen == 0 && xc, TRANSMIT_PERIODIC); + TR(sendRSTP && xc2 && xc, TRANSMIT_RSTP); + TR(!sendRSTP && role == RootPort && xc2 && xc, TRANSMIT_TCN); + TR(!sendRSTP && role == DesignatedPort && xc2 && xc, TRANSMIT_CONFIG); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.27 - Fig. 17-18 + +#define STM_NAME PortInformation + +STATES(STN(DISABLED), STN(AGED), STN(UPDATE), + STN(SUPERIOR_DESIGNATED), STN(REPEATED_DESIGNATED), + STN(INFERIOR_DESIGNATED), STN(NOT_DESIGNATED), + STN(OTHER), STN(CURRENT), STN(RECEIVE) + ); + +STM(PORT_ARGS_PROTO, + + GC( + TR(!portEnabled && (infoIs != Disabled), DISABLED); + ); + + BG( + TR(UCT, DISABLED); + ); + + ST(DISABLED, + rcvdMsg = FALSE; + proposing = proposed = agree = agreed = FALSE; + rcvdInfoWhile = 0; + infoIs = Disabled; reselect = TRUE; selected = FALSE; + , + TR(portEnabled, AGED); + TR(rcvdMsg, DISABLED); + ); + + ST(AGED, + infoIs = Aged; + reselect = TRUE; selected = FALSE; + , + TR(selected && updtInfo, UPDATE); + ); + + ST(UPDATE, + proposing = proposed = FALSE; + agreed = agreed && betterorsameInfo(PORT_ARGS); + synced = synced && agreed; + portPriority = designatedPriority; + portTimes = designatedTimes; + updtInfo = FALSE; infoIs = Mine; newInfo = TRUE; + , + TR(UCT, CURRENT); + ); + + ST(SUPERIOR_DESIGNATED, + agreed = proposing = FALSE; + recordProposal(PORT_ARGS); + setTcFlags(PORT_ARGS); + agreed = agreed && betterorsameInfo(PORT_ARGS); + recordPriority(PORT_ARGS); + recordTimes(PORT_ARGS); + updtRcvdInfoWhile(PORT_ARGS); + infoIs = Received; reselect = TRUE; selected = FALSE; + rcvdMsg = FALSE; + , + TR(UCT, CURRENT); + ); + + ST(REPEATED_DESIGNATED, + recordProposal(PORT_ARGS); + setTcFlags(PORT_ARGS); + updtRcvdInfoWhile(PORT_ARGS); + rcvdMsg = FALSE; + , + TR(UCT, CURRENT); + ); + + ST(INFERIOR_DESIGNATED, + recordDispute(PORT_ARGS); + rcvdMsg = FALSE; + , + TR(UCT, CURRENT); + ); + + ST(NOT_DESIGNATED, + recordAgreement(PORT_ARGS); + setTcFlags(PORT_ARGS); + rcvdMsg = FALSE; + , + TR(UCT, CURRENT); + ); + + ST(OTHER, + rcvdMsg = FALSE; + , + TR(UCT, CURRENT); + ); + + ST(CURRENT, + , + TR(selected && updtInfo, UPDATE); + TR((infoIs == Received) && (rcvdInfoWhile == 0) && + !updtInfo && !rcvdMsg, + AGED); + TR(rcvdMsg && !updtInfo, RECEIVE); + ); + + ST(RECEIVE, + rcvdInfo = rcvInfo(PORT_ARGS); + , + TR(rcvdInfo == SuperiorDesignatedInfo, SUPERIOR_DESIGNATED); + TR(rcvdInfo == RepeatedDesignatedInfo, REPEATED_DESIGNATED); + TR(rcvdInfo == InferiorDesignatedInfo, INFERIOR_DESIGNATED); + TR(rcvdInfo == InferiorRootAlternateInfo, NOT_DESIGNATED); + TR(rcvdInfo == OtherInfo, OTHER); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.28 - Fig. 17-19 + +#define STM_NAME PortRoleSelection + +STATES(STN(INIT_BRIDGE), STN(ROLE_SELECTION)); + +STM(BRIDGE_ARGS_PROTO, + + GC(); + + BG( + TR(UCT, INIT_BRIDGE); + ); + + ST(INIT_BRIDGE, + updtRoleDisabledTree(BRIDGE_ARGS); + , + TR(UCT, ROLE_SELECTION); + ); + + ST(ROLE_SELECTION, + clearReselectTree(BRIDGE_ARGS); + updtRolesTree(BRIDGE_ARGS); + setSelectedTree(BRIDGE_ARGS); + , + TR(!ForAllPort(!FPORT->_reselect), ROLE_SELECTION); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.29 +// 17.29.1 - Fig. 17-20 Disabled Port role transitions +// 17.29.2 - Fig. 17-21 Root Port role transitions +// 17.29.3 - Fig. 17-22 Designated Port role transitions +// 17.29.4 - Fig. 17-23 Alternate and Backup Port role transitions + +#define STM_NAME PortRoleTransitions + +STATES(/* Disabled Port role transitions */ + STN(INIT_PORT), STN(DISABLE_PORT), STN(DISABLED_PORT), + /* Root Port role transitions */ + STN(ROOT_PROPOSED), STN(ROOT_AGREED), STN(ROOT_SYNCED), + STN(REROOT), STN(REROOTED), + STN(ROOT_LEARN), STN(ROOT_FORWARD), STN(ROOT_PORT), + /* Designated port role transitions */ + STN(DESIGNATED_PROPOSE), STN(DESIGNATED_SYNCED), + STN(DESIGNATED_RETIRED), STN(DESIGNATED_DISCARD), + STN(DESIGNATED_LEARN), STN(DESIGNATED_FORWARD), STN(DESIGNATED_PORT), + /* Alternate and Backup Port role transitions */ + STN(BLOCK_PORT), STN(ALTERNATE_PROPOSED), STN(ALTERNATE_AGREED), + STN(BACKUP_PORT), STN(ALTERNATE_PORT) + ); + +STM(PORT_ARGS_PROTO, + + GC( + // Let us put role != selectedRole first in the && of conditions + // Since this will be false in the stable state, so condition check + // will fail quicker. + + /* Disabled Port role transitions */ + TR((role != selectedRole) && (selectedRole == DisabledPort) && xc, + DISABLE_PORT); + /* Root Port role transitions */ + TR((role != selectedRole) && (selectedRole == RootPort) && xc, + ROOT_PORT); + /* Designated port role transitions */ + TR((role != selectedRole) && (selectedRole == DesignatedPort) && xc, + DESIGNATED_PORT); + /* Alternate and Backup Port role transitions */ + TR((role !=selectedRole) && ((selectedRole == AlternatePort) + || (selectedRole == BackupPort)) && xc, + BLOCK_PORT); + ); + + + /* Disabled Port role transitions */ + + BG( + TR(UCT, INIT_PORT); + ); + + ST(INIT_PORT, + role = DisabledPort; + learn = forward = FALSE; + synced = FALSE; + sync = reRoot = TRUE; + rrWhile = FwdDelay; + fdWhile= MaxAge; + rbWhile = 0; + , + TR(UCT, DISABLE_PORT); + ); + + ST(DISABLE_PORT, + role = selectedRole; + learn = forward = FALSE; + , + TR(!learning && !forwarding && xc, DISABLED_PORT); + ); + + ST(DISABLED_PORT, + fdWhile = MaxAge; + synced = TRUE; + rrWhile = 0; + sync = reRoot = FALSE; + , + TR((fdWhile != MaxAge || sync || reRoot || !synced) && xc, + DISABLED_PORT); + ); + + /* Root Port role transitions */ + + ST(ROOT_PROPOSED, + setSyncTree(BRIDGE_ARGS); + proposed = FALSE; + , + TR(UCT, ROOT_PORT); + ); + + ST(ROOT_AGREED, + proposed = sync = FALSE; + agree = TRUE; + newInfo = TRUE; + , + TR(UCT, ROOT_PORT); + ); + + // This state is there in the 802.1Q-2005 state machine + ST(ROOT_SYNCED, + synced = TRUE; + sync = FALSE; + , + TR(UCT, ROOT_PORT); + ); + + ST(REROOT, + setReRootTree(BRIDGE_ARGS); + , + TR(UCT, ROOT_PORT); + ); + + ST(REROOTED, + reRoot = FALSE; + , + TR(UCT, ROOT_PORT); + ); + + ST(ROOT_LEARN, + fdWhile = forwardDelay; + learn = TRUE; + , + TR(UCT, ROOT_PORT); + ); + + ST(ROOT_FORWARD, + fdWhile = 0; + forward = TRUE; + , + TR(UCT, ROOT_PORT); + ); + + ST(ROOT_PORT, + role = RootPort; + rrWhile = FwdDelay; + , + TR(proposed && !agree && xc, ROOT_PROPOSED); + TR(((allSynced && !agree) || (proposed && agree)) && xc, ROOT_AGREED); + TR(((agreed && !synced) || (sync && synced)) && xc, ROOT_SYNCED); + TR(!forward && !reRoot && xc, REROOT); + TR(reRoot && forward && xc, REROOTED); + TR((fdWhile == 0 || + ((reRooted && (rbWhile == 0)) && (rstpVersion))) && !learn && xc, + ROOT_LEARN); + TR((fdWhile == 0 || ((reRooted && (rbWhile == 0)) && (rstpVersion))) + && learn && !forward && xc, + ROOT_FORWARD); + TR(rrWhile != FwdDelay && xc, ROOT_PORT); + ); + + + /* Designated port role transitions */ + + ST(DESIGNATED_PROPOSE, + proposing = TRUE; + edgeDelayWhile = EdgeDelay; + newInfo = TRUE; + , + TR(UCT, DESIGNATED_PORT); + ); + + ST(DESIGNATED_SYNCED, + rrWhile = 0; + synced = TRUE; + sync = FALSE; + , + TR(UCT, DESIGNATED_PORT); + ); + + ST(DESIGNATED_RETIRED, + reRoot = FALSE; + , + TR(UCT, DESIGNATED_PORT); + ); + + ST(DESIGNATED_DISCARD, + learn = forward = disputed = FALSE; + fdWhile = forwardDelay; + , + TR(UCT, DESIGNATED_PORT); + ); + + ST(DESIGNATED_LEARN, + learn = TRUE; + fdWhile = forwardDelay; + , + TR(UCT, DESIGNATED_PORT); + ); + + ST(DESIGNATED_FORWARD, + forward = TRUE; + fdWhile = 0; + agreed = sendRSTP /* CORR: sendRstp */; + , + TR(UCT, DESIGNATED_PORT); + ); + + ST(DESIGNATED_PORT, + role = DesignatedPort; + , + TR(!forward && !agreed && !proposing && !operEdge && xc, + DESIGNATED_PROPOSE); + TR(((!learning && !forwarding && !synced) + || (agreed && !synced) || (operEdge && !synced) + || (sync && synced)) && xc, + DESIGNATED_SYNCED); + TR(rrWhile == 0 && reRoot && xc, DESIGNATED_RETIRED); + TR(((sync && !synced) || (reRoot && (rrWhile != 0)) || disputed) + && !operEdge && (learn || forward) && xc, + DESIGNATED_DISCARD); + TR(((fdWhile == 0) || agreed || operEdge) + && ((rrWhile ==0) || !reRoot) && !sync && !learn && xc, + DESIGNATED_LEARN); + TR(((fdWhile == 0) || agreed || operEdge) + && ((rrWhile ==0) || !reRoot) && !sync && (learn && !forward) && xc, + DESIGNATED_FORWARD); + ); + + + /* Alternate and Backup Port role transitions */ + + ST(BLOCK_PORT, + role = selectedRole; + learn = forward = FALSE; + , + TR(!learning && !forwarding && xc, ALTERNATE_PORT); + ); + + ST(ALTERNATE_PROPOSED, + setSyncTree(BRIDGE_ARGS); + proposed = FALSE; + , + TR(UCT, ALTERNATE_PORT); + ); + + ST(ALTERNATE_AGREED, + proposed = FALSE; + agree = TRUE; + newInfo = TRUE; + , + TR(UCT, ALTERNATE_PORT); + ); + + ST(BACKUP_PORT, + rbWhile = 2*HelloTime; + , + TR(UCT, ALTERNATE_PORT); + ); + + ST(ALTERNATE_PORT, + // DIFF: std says FwdDelay, but that leads to infinite loop here + // when sendRSTP is set, since FwdDelay != forwardDelay then + // 802.1Q-2005 says forwardDelay here + fdWhile = forwardDelay; + synced = TRUE; + rrWhile = 0; + sync = reRoot = FALSE; + , + TR(proposed && !agree && xc, + ALTERNATE_PROPOSED); + TR(((allSynced && !agree) || (proposed && agree)) && xc, + ALTERNATE_AGREED); + TR((rbWhile != 2*HelloTime) && (role == BackupPort) && xc, + BACKUP_PORT); + TR(((fdWhile != forwardDelay) || sync || reRoot || !synced) && xc, + ALTERNATE_PORT); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.30 - Fig. 17-24 + +#define STM_NAME PortStateTransition + +STATES(STN(DISCARDING), STN(LEARNING), STN(FORWARDING)); + +STM(PORT_ARGS_PROTO, + + GC(); + + BG( + TR(UCT, DISCARDING); + ); + + ST(DISCARDING, + disableLearning(PORT_ARGS); + learning = FALSE; + disableForwarding(PORT_ARGS); + forwarding = FALSE; + , + TR(learn, LEARNING); + ); + + ST(LEARNING, + enableLearning(PORT_ARGS); + learning = TRUE; + , + TR(!learn, DISCARDING); + TR(forward, FORWARDING); + ); + + ST(FORWARDING, + enableForwarding(PORT_ARGS); + forwarding = TRUE; + , + TR(!forward, DISCARDING); + ); + + ); + +#undef STM_NAME + +/*------------------------------------------------------------*/ + +// 17.31 - Fig. 17-25 + +#define STM_NAME TopologyChange + +STATES(STN(INACTIVE), STN(LEARNING), STN(DETECTED), STN(ACKNOWLEDGED), + STN(PROPAGATING), STN(NOTIFIED_TC), STN(NOTIFIED_TCN), STN(ACTIVE)); + +STM(PORT_ARGS_PROTO, + + GC(); + + BG( + TR(UCT, INACTIVE); + ); + + ST(INACTIVE, + fdbFlush = TRUE; + do_fdbFlush(PORT_ARGS); /* We add this to trigger the fdb flush */ + //tcWhile = 0; + set_tcWhile(0); + tcAck = FALSE; + , + TR(learn && !fdbFlush, LEARNING); + ); + + ST(LEARNING, + rcvdTc = rcvdTcn = rcvdTcAck = FALSE; + rcvdTc = tcProp = FALSE; + , + TR(((role == RootPort) || (role == DesignatedPort)) + && forward && !operEdge, + DETECTED); + + TR((role != RootPort) && + (role != DesignatedPort) && + !(learn || learning) && + !(rcvdTc || rcvdTcn || rcvdTcAck || tcProp), + INACTIVE); + + TR(rcvdTc || + rcvdTcn || + rcvdTcAck || + tcProp, + LEARNING); + ); + + ST(DETECTED, + newTcWhile(PORT_ARGS); + setTcPropTree(PORT_ARGS); + newInfo = TRUE; + , + TR(UCT, ACTIVE); + ); + + ST(ACKNOWLEDGED, + //tcWhile = 0; + set_tcWhile(0); + rcvdTcAck = FALSE; + , + TR(UCT, ACTIVE); + ); + + ST(PROPAGATING, + newTcWhile(PORT_ARGS); + fdbFlush = TRUE; + do_fdbFlush(PORT_ARGS); /* We add this to trigger the fdb flush */ + tcProp = FALSE; + , + TR(UCT, ACTIVE); + ); + + ST(NOTIFIED_TC, + rcvdTcn = rcvdTc = FALSE; + if (role == DesignatedPort) tcAck = TRUE; + /* + Fig 17-25 has setTcPropBridge(); + Must mean setTcPropTree(). + The 802.1w-2001 standard has setTcPropBridge() defined to be the + same thing as setTcPropTree() in 802.1D-2004, and also has + setTcPropBridge() in both DETECTED and NOTIFIED_TC states. + 802.1D-2004 has setTcPropTree() in DETECTED state, but + setTcPropBridge() in NOTIFIED_TC state. + */ + setTcPropTree(PORT_ARGS); /* CORR: setTcPropBridge() */ + , + TR(UCT, ACTIVE); + ); + + ST(NOTIFIED_TCN, + newTcWhile(PORT_ARGS); + , + TR(UCT, NOTIFIED_TC); + ); + + ST(ACTIVE, + , + TR(((role != RootPort) && (role != DesignatedPort) ) || operEdge, + LEARNING); + TR(rcvdTcAck, ACKNOWLEDGED); + TR(tcProp && !operEdge, PROPAGATING); + TR(rcvdTc, NOTIFIED_TC); + TR(rcvdTcn, NOTIFIED_TCN); + ); + + ); + +#undef STM_NAME + + +/***** Running the state machines *****/ + +static STM_FUNC_DECL((*bridge_stms[NUM_BRIDGE_STATE_MACHINES]), + BRIDGE_ARGS_PROTO) + = { + STM_FUNC(PortRoleSelection), + }; + +static STM_FUNC_DECL((*port_stms[NUM_PORT_STATE_MACHINES]), PORT_ARGS_PROTO) + = { + STM_FUNC(PortTimers), + STM_FUNC(PortReceive), + STM_FUNC(PortProtocolMigration), + STM_FUNC(BridgeDetection), + STM_FUNC(PortTransmit), + STM_FUNC(PortInformation), + STM_FUNC(PortRoleTransitions), + STM_FUNC(PortStateTransition), + STM_FUNC(TopologyChange), + }; + +static void state_machines_run(BRIDGE_ARGS_PROTO) +{ + if (!BRIDGE->stp_on) + return; + + int i; + bool transitioned; + do { + transitioned = FALSE; + for (i = 0; i < NUM_BRIDGE_STATE_MACHINES; i++) + STM_RUN(&BRIDGE->state[i], &transitioned, bridge_stms[i], BRIDGE_ARGS); + + ForAllPortDo( + for (i = 0; i < NUM_PORT_STATE_MACHINES; i++) + STM_RUN(&FPORT->state[i], &transitioned, + port_stms[i], FPORT_ARGS); + ); + } while (transitioned); +} + +static void state_machines_begin(BRIDGE_ARGS_PROTO) +{ + if (!BRIDGE->stp_on) + return; + + int i; + for (i = 0; i < NUM_BRIDGE_STATE_MACHINES; i++) + STM_BEGIN(&BRIDGE->state[i], bridge_stms[i], BRIDGE_ARGS); + + ForAllPortDo( + for (i = 0; i < NUM_PORT_STATE_MACHINES; i++) + STM_BEGIN(&FPORT->state[i], port_stms[i], FPORT_ARGS); + ); + + /* Initialized. Now run them. */ + state_machines_run(BRIDGE_ARGS); +} + +void STP_IN_set_bridge_enable(Bridge *BRIDGE, unsigned int enabled) +{ + uint on = enabled?1:0; + if (BRIDGE->stp_on == on) + return; + + BRIDGE->stp_on = on; + + if (on) + state_machines_begin(BRIDGE_ARGS); + else { + + } +} + + +/* + PortPathCost and operPointToPointMAC can change through configuration + as well as link notifications (when configured to auto). + Common functions to handle these. +*/ + +// 17.14 - Table 17-3, also NOTE 3 +static uint compute_pcost(int speed) +{ + /* speed is in MB/s*/ + if (speed > 0) + return speed < 20000000 ? 20000000/speed : 1; + else + return 200000000; +} + +/* Returns TRUE if PostPathCost changed */ +static bool check_port_path_cost(PORT_ARGS_PROTO) +{ + int pcost; + if (AdminPortPathCost == 0) { + pcost = compute_pcost(PORT->speed); + } + else { + pcost = AdminPortPathCost; + } + + if (PortPathCost != pcost) { + PortPathCost = pcost; + selected = FALSE; reselect = TRUE; // 17.13 + return TRUE; + } + + return FALSE; +} + +/* Returns TRUE if operPointToPointMAC changed */ +static bool check_port_p2p(PORT_ARGS_PROTO) +{ + bool p2p; + if (adminPointToPointMAC == P2PAuto) { + p2p = !!PORT->duplex; + } + else { + p2p = (adminPointToPointMAC == P2PForceTrue); + } + + if (operPointToPointMAC != p2p) { + operPointToPointMAC = p2p; + return TRUE; + } + + return FALSE; +} + +/******************** Event notifications ******************************/ + +void STP_IN_one_second(BRIDGE_ARGS_PROTO) +{ + if (BRIDGE->tcWhile_count == 0) + BRIDGE->time_since_tc++; + + ForAllPortDo( + FPORT->_tick = TRUE; + ); + state_machines_run(BRIDGE_ARGS); +} + +void STP_IN_set_port_enable(Port *PORT, unsigned int enabled, + unsigned int speed, unsigned int duplex) +{ + Bridge *BRIDGE = PORT->bridge; + + bool changed = FALSE; + + if (enabled) { + if (!portEnabled) { + portEnabled = TRUE; + changed = TRUE; + } + + if (PORT->speed != speed) { + PORT->speed = speed; + if (check_port_path_cost(PORT_ARGS)) + changed = TRUE; + } + + if (PORT->duplex != duplex) { + PORT->duplex = duplex; + if (check_port_p2p(PORT_ARGS)) + changed = TRUE; + } + } + else { + if (portEnabled) { + portEnabled = FALSE; + changed = TRUE; + } + } + if (changed) + state_machines_run(BRIDGE_ARGS); +} + +/* Rounding up. What should we really be doing with fractional times? */ +static uint get_bpdu_time(const uchar time[2]) +{ + uint t = (time[0] << 8) + time[1]; + return (t + 0xff) >> 8; +} + + +void STP_IN_rx_bpdu(Port *PORT, const void *base, unsigned int len) +{ + Bridge *BRIDGE = PORT->bridge; + const RawBPDU *p = base; + + if (!BRIDGE->stp_on) + return; // No action - else we might fail the next test + + if (rcvdBpdu) { + ABORT("Port hasn't processed old BPDU\n"); + return; + } + + // 9.3.4 - Validate + if (len < 4) + return; + if (p->protocol_id != STP_PROTOCOL_ID) + return; + + DEBUG("Received BPDU of type %d\n", p->type); + + if (// 9.3.4 a) + (p->type == BPDUTypeConfig && + len >= 35 && + CMP(p->message_age, <, p->max_age) && + !(CMP(p->priority.designated_bridge_id.bridge_address, ==, + BridgeIdentifier.bridge_address) && + (port_id_to_uint(p->priority.designated_port_id) & 0xfff) == + (port_id_to_uint(portId) & 0xfff) + )) + || + // 9.3.4 b) + (p->type == BPDUTypeTCN) + || + // 9.3.4 c) + (p->type == BPDUTypeRST && len >= 36) + ) { + /* BPDU has been validated */ + } + else { + DEBUG("BPDU validation failed\n"); + return; + } + + // 9.3.4 d) + bpduVersion = p->version <= 2 ? p->version : 2; + + bpduType = p->type; + + bpduTcFlag = + (p->type != BPDUTypeTCN) ? ((p->flags & BPDU_FLAG_TC) != 0) : 0; + bpduProposalFlag = + (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_PROPOSAL) != 0) : 0; + bpduRole = + (p->type == BPDUTypeRST) ? BPDU_FLAG_ROLE_GET(p->flags) : 0; + bpduLearningFlag = + (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_LEARNING) != 0) : 0; + bpduForwardingFlag = + (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_FORWARDING) != 0) : 0; + bpduAgreementFlag = + (p->type == BPDUTypeRST) ? ((p->flags & BPDU_FLAG_AGREEMENT) != 0) : 0; + bpduTcAckFlag = + (p->type != BPDUTypeTCN) ? ((p->flags & BPDU_FLAG_TC_ACK) != 0) : 0; + + if (p->type == BPDUTypeTCN) { + ZERO(bpduPriority); + ZERO(bpduTimes); + + } + else { + bpduPriority = p->priority; + + bpduTimes.message_age = get_bpdu_time(p->message_age); + bpduTimes.max_age = get_bpdu_time(p->max_age); + bpduTimes.forward_delay = get_bpdu_time(p->forward_delay); + bpduTimes.hello_time = get_bpdu_time(p->hello_time); + } + + rcvdBpdu = TRUE; + + state_machines_run(BRIDGE_ARGS); + +} + +/************************** Configuration *************************/ + +// 17.14 +static int check_times(BRIDGE_ARGS_PROTO, int max_age, int fwd_delay) +{ + if (2*(fwd_delay - 1) < max_age) { + ERROR("Configured BridgeTimes doesn't meet " + "2 * (Bridge Foward Delay - 1) >= Bridge Max Age\n"); + return -1; + } + + // This condition never fails, with hello_time == 2 && max_age >= 6. + // So no need to check. + //if (max_age < 2*(BridgeHelloTime + 1)) { + // ERROR("Doesn't meet Bridge Max Age >= 2 * (Bridge Hello Time + 1) "); + // return -1; + //} + + return 0; +} + +// 14.8.1.2 +int STP_IN_set_bridge_config(Bridge *BRIDGE, const STP_BridgeConfig *cfg) +{ + int r = 0; + bool changed = FALSE, init = FALSE; + /* First, validation */ + + if (cfg->set_bridge_protocol_version) { + if (cfg->bridge_protocol_version != 0 && cfg->bridge_protocol_version != 2) { + ERROR("Protocol version must be 0 or 2\n"); + r = -1; + } + } + + /* No validation of bridge_address. It can be any 6 bytes */ + + if (cfg->set_bridge_priority) { + // 17.14 - Table 17-2 + if (cfg->bridge_priority & ~0xf000) { + ERROR("Bridge Priority restricted to 0-61440 in steps of 4096\n"); + r = -1; + } + } + + Times new_times = BridgeTimes; + bool set_times = FALSE; + + if (cfg->set_bridge_hello_time) { + // 17.14 - Table 17-1 + if (cfg->bridge_hello_time != BridgeHelloTime) { + ERROR("Hello Time cannot be changed from 2s in RSTP\n"); + r = -1; + } + new_times.hello_time = cfg->bridge_hello_time; + set_times = TRUE; + } + + if (cfg->set_bridge_max_age) { + // 17.14 - Table 17-1 + if (cfg->bridge_max_age < 6 || cfg->bridge_max_age > 40) { + ERROR("Bridge Max Age must be between 6 and 40\n"); + r = -1; + } + new_times.max_age = cfg->bridge_max_age; + set_times = TRUE; + } + + if (cfg->set_bridge_forward_delay) { + // 17.14 - Table 17-1 + if (cfg->bridge_forward_delay < 4 || cfg->bridge_forward_delay > 30) { + ERROR("Bridge Forward Delay must be between 4 and 30\n"); + r = -1; + } + new_times.forward_delay = cfg->bridge_forward_delay; + set_times = TRUE; + } + + if (check_times(BRIDGE_ARGS, new_times.max_age, new_times.forward_delay)) + r = -1; + + if (cfg->set_bridge_tx_hold_count) { + // 17.14 - Table 17-1 + if (cfg->bridge_tx_hold_count < 1 || cfg->bridge_tx_hold_count > 10) { + ERROR("Transmit Hold Count must be between 1 and 10\n"); + r = -1; + } + } + + /* If not valid, return error */ + if (r) + return r; + + /* Set things */ + if (cfg->set_bridge_protocol_version && + ForceProtocolVersion != cfg->bridge_protocol_version) { + + ForceProtocolVersion = cfg->bridge_protocol_version; + + changed = TRUE; + // 17.13 a) + init = TRUE; + } + + if (cfg->set_bridge_address && + CMP(BridgeIdentifier.bridge_address, !=, cfg->bridge_address)) { + + CPY(BridgeIdentifier.bridge_address, cfg->bridge_address); + + changed = TRUE; + init = TRUE; + } + + if (cfg->set_bridge_priority && + ((BridgeIdentifierPriority[0] & 0xf0) << 8) != cfg->bridge_priority) { + + BridgeIdentifierPriority[0] &= ~0xf0; + BridgeIdentifierPriority[0] |= cfg->bridge_priority >> 8; + + changed = TRUE; + /* 802.1Q-2005, 12.8.1.3.4 c) (Management, setting bridge protocol params) + says this should be done for any management change, so we do this below. + + // 17.13 b) + ForAllPortDo( + FPORT->_selected = FALSE; + FPORT->_reselect = TRUE; + ); + */ + } + + if (set_times && + CMP(new_times, !=, BridgeTimes)) { + + BridgeTimes = new_times; + + changed = TRUE; + } + + if (cfg->set_bridge_tx_hold_count && + TxHoldCount != cfg->bridge_tx_hold_count) { + + TxHoldCount = cfg->bridge_tx_hold_count; + + changed = TRUE; + // 17.13 introduction + ForAllPortDo( + FPORT->_txCount = 0; + ); + } + + if (changed && BRIDGE->stp_on) { + // Do this for any change. + // Seems like 802.1Q-2005, 12.8.1.3.4 c) wants this. + /* + Otherwise we fail UNH rstp.op_D test 3.2 since when administratively + setting BridgeForwardDelay, etc, the values don't propagate from + rootTimes to designatedTimes immediately without this change. + + */ + ForAllPortDo( + FPORT->_selected = FALSE; + FPORT->_reselect = TRUE; + ); + + if (init) + state_machines_begin(BRIDGE_ARGS); + else + state_machines_run(BRIDGE_ARGS); + } + + return 0; +} + +// 14.8.2.3 +int STP_IN_set_port_config(Port *PORT, const STP_PortConfig *cfg) +{ + Bridge *BRIDGE = PORT->bridge; + + int r = 0; + bool changed = FALSE; + /* First, validation */ + + if (cfg->set_port_priority) { + // 17.14 - Table 17-2 + if (cfg->port_priority & ~0xf0) { + ERROR("Port Priority restricted to 0-240 in steps of 16\n"); + r = -1; + } + } + + if (cfg->set_port_pathcost) { + // 17.14 - Note 3 and Table 17-3 + if (cfg->port_pathcost > 200000000) { + ERROR("Port path cost restricted to 1-200,000,000 or 0 for auto\n"); + r = -1; + } + } + + if (cfg->set_port_admin_edge) { + if (cfg->port_admin_edge != 0 && cfg->port_admin_edge != 1) { + ERROR("Admin Edge must be 0 or 1\n"); + r = -1; + } + } + + if (cfg->set_port_auto_edge) { + if (cfg->port_auto_edge != 0 && cfg->port_auto_edge != 1) { + ERROR("Auto Edge must be 0 or 1\n"); + r = -1; + } + } + + if (cfg->set_port_admin_p2p) { + if (cfg->port_admin_p2p < 0 || cfg->port_admin_p2p > 2) { + ERROR("Admin P2P must be " + "0 (force false), 1 (force true), or 2 (auto)\n"); + r = -1; + } + } + + /* If not valid, return error */ + if (r) + return r; + + /* Set things */ + if (cfg->set_port_priority && + (portId.port_id[0] & 0xf0) != cfg->port_priority) { + + portId.port_id[0] &= ~0xf0; + portId.port_id[0] |= cfg->port_priority; + + changed = TRUE; + // 17.13 c) + selected = FALSE; reselect = TRUE; + } + + if (cfg->set_port_pathcost && + AdminPortPathCost != cfg->port_pathcost) { + + AdminPortPathCost = cfg->port_pathcost; + + if (check_port_path_cost(PORT_ARGS)) { + /* PortPathCost was changed */ + changed = TRUE; + // 17.13 d) - Done by check_port_path_cost() + // selected = FALSE; reselect = TRUE; + } + } + + if (cfg->set_port_admin_edge && + AdminEdge != cfg->port_admin_edge) { + + AdminEdge = cfg->port_admin_edge; + + changed = TRUE; + } + + if (cfg->set_port_auto_edge && + AutoEdge != cfg->port_auto_edge) { + + AutoEdge = cfg->port_auto_edge; + + changed = TRUE; + } + + if (cfg->set_port_admin_p2p && + adminPointToPointMAC != cfg->port_admin_p2p) { + + adminPointToPointMAC = cfg->port_admin_p2p; + + if (check_port_p2p(PORT_ARGS)) { + /* operPointToPointMAC was changed */ + changed = TRUE; + } + } + + if (changed) + state_machines_run(BRIDGE_ARGS); + + return 0; +} + +/* To make this #define work */ +typedef STP_BridgeConfig STP_bridgeConfig; +typedef STP_PortConfig STP_portConfig; + +#define SET_CFG(_type, _arg, _field, _value) \ +({ \ + STP_ ## _type ## Config cfg; \ + ZERO(cfg); \ + cfg._type ## _ ## _field = _value; \ + cfg. set_ ## _type ## _ ## _field = 1; \ + STP_IN_set_ ## _type ## _config(_arg, &cfg); \ +}) + +/* Single field Bridge config functions */ + +int STP_IN_set_protocol_version(Bridge *BRIDGE, unsigned int version) +{ + return SET_CFG(bridge, BRIDGE, protocol_version, version); +} + +int STP_IN_set_bridge_address(Bridge *BRIDGE, const STP_MacAddress *addr) +{ + return SET_CFG(bridge, BRIDGE, address, *addr); +} + +int STP_IN_set_bridge_priority(Bridge *BRIDGE, unsigned int priority) +{ + return SET_CFG(bridge, BRIDGE, priority, priority); +} + +int STP_IN_set_bridge_hello_time(Bridge *BRIDGE, unsigned int hello_time) +{ + return SET_CFG(bridge, BRIDGE, hello_time, hello_time); +} + +int STP_IN_set_bridge_max_age(Bridge *BRIDGE, unsigned int max_age) +{ + return SET_CFG(bridge, BRIDGE, max_age, max_age); +} + + +int STP_IN_set_bridge_forward_delay(Bridge *BRIDGE, unsigned int fwd_delay) +{ + return SET_CFG(bridge, BRIDGE, forward_delay, fwd_delay); +} + + +int STP_IN_set_tx_hold_count(Bridge *BRIDGE, unsigned int count) +{ + return SET_CFG(bridge, BRIDGE, tx_hold_count, count); +} + + +/* Single field Port config functions */ + +int STP_IN_set_port_priority(Port *PORT, unsigned int priority) +{ + return SET_CFG(port, PORT, priority, priority); +} + +int STP_IN_set_port_pathcost(Port *PORT, unsigned int pcost) +{ + return SET_CFG(port, PORT, pathcost, pcost); +} + +/* edge is 0 or 1 */ +int STP_IN_set_port_admin_edge(Port *PORT, unsigned int edge) +{ + return SET_CFG(port, PORT, admin_edge, edge); +} + +int STP_IN_set_port_auto_edge(Port *PORT, unsigned int edge) +{ + return SET_CFG(port, PORT, auto_edge, edge); +} + +int STP_IN_set_port_admin_p2p(Port *PORT, unsigned int p2p) +{ + return SET_CFG(port, PORT, admin_p2p, p2p); +} + +/* Force migration check */ +// 14.8.2.4 +int STP_IN_port_mcheck(Port *PORT) +{ + Bridge *BRIDGE = PORT->bridge; + + if (!BRIDGE->stp_on) { + ERROR("Bridge is down\n"); + return -1; + } + + if (rstpVersion) { + mcheck = TRUE; + + state_machines_run(BRIDGE_ARGS); + } + + return 0; +} + + +/********************* Status ************************/ + +// 14.8.1.1 +void STP_IN_get_bridge_status(Bridge *BRIDGE, STP_BridgeStatus *status) +{ + CPY(status->bridge_id, BridgeIdentifier); + + status->time_since_topology_change = BRIDGE->time_since_tc; + status->topology_change_count = BRIDGE->tc_count; + status->topology_change = BRIDGE->tcWhile_count?1:0; + + CPY(status->designated_root, rootPriority.root_bridge_id); + + status->root_path_cost = path_cost_to_uint(rootPriority.root_path_cost); + + CPY(status->designated_bridge, rootPriority.designated_bridge_id); + + status->root_port = + ((rootPortId.port_id[0] & 0x0f) << 8) + rootPortId.port_id[1]; + + status->max_age = rootTimes.max_age; + status->hello_time = rootTimes.hello_time; + status->forward_delay = rootTimes.forward_delay; + + status->bridge_max_age = BridgeTimes.max_age; + status->bridge_hello_time = BridgeTimes.hello_time; + status->bridge_forward_delay = BridgeTimes.forward_delay; + + status->tx_hold_count = TxHoldCount; + + status->protocol_version = ForceProtocolVersion; + + status->enabled = BRIDGE->stp_on; +} + +// 14.8.2.1 +void STP_IN_get_port_status(Port *PORT, STP_PortStatus *status) +{ + status->uptime = -1; // XXX: Don't know + + status->state = PORT->port_state_flags; + if (status->state & STP_PORT_STATE_FLAG_FORWARDING) + status->state &= ~STP_PORT_STATE_FLAG_LEARNING; + + status->id = port_id_to_uint(portId); + + // admin_path_cost is not in 14.18.2.1 but it is the one piece of config + // information missing there, so it is useful to have it. + status->admin_path_cost = AdminPortPathCost; + status->path_cost = PortPathCost; + + CPY(status->designated_root, designatedPriority.root_bridge_id); + status->designated_cost = + path_cost_to_uint(designatedPriority.root_path_cost); + CPY(status->designated_bridge, designatedPriority.designated_bridge_id); + status->designated_port = + port_id_to_uint(designatedPriority.designated_port_id); + + status->tc_ack = tcAck; + status->admin_edge_port = AdminEdge; + status->oper_edge_port = operEdge; + status->auto_edge_port = AutoEdge; + + status->enabled = portEnabled; + + status->admin_p2p = adminPointToPointMAC; + status->oper_p2p = operPointToPointMAC; +} + + +/**************** Creating and deleting ********************/ + +/* user_ref is a pointer that the STP part uses to call the system part + Bridge is created as disabled. You need to enable it. + */ +Bridge *STP_IN_bridge_create(void *user_ref) +{ + Bridge *b = STP_OUT_mem_zalloc(sizeof(Bridge)); + + if (!b) { + ERROR("Couldn't allocate memory for bridge\n"); + return NULL; + } + + b->user_ref = user_ref; + + Bridge *BRIDGE = b; + + /* Default configuration values */ + BridgeIdentifierPriority[0] = 0x80; // Default bridge priority = 32768 + MigrateTime = 3; + BridgeHelloTime = 2; + BridgeMaxAge = 20; + BridgeForwardDelay = 15; + TxHoldCount = 6; + ForceProtocolVersion = 2; + + return BRIDGE; +} + +/* All ports will be deleted, don't reference them */ +void STP_IN_bridge_delete(Bridge *BRIDGE) +{ + /* Disable ports - for what it is worth */ + ForAllPortDo( + FPORT->_portEnabled = FALSE; + ); + + state_machines_run(BRIDGE_ARGS); + + /* Delete all ports */ + while (BRIDGE->port_list) + STP_IN_port_delete(BRIDGE->port_list); + + STP_OUT_mem_free(BRIDGE); +} + +/* user_ref is a pointer that the STP part uses to call the system part + Port is created as disabled. You need to enable it. +*/ +Port *STP_IN_port_create(Bridge *BRIDGE, + unsigned int port_number, void *user_ref) +{ + if (port_number == 0 || (port_number & ~0x0fff)) { + ERROR("Port id should be in the range 1 - 1023\n"); + return NULL; + } + + Port *p = STP_OUT_mem_zalloc(sizeof(Port)); + + if (!p) { + ERROR("Couldn't allocate memory for port\n"); + return NULL; + } + + p->user_ref = user_ref; + p->bridge = BRIDGE; + + p->port_next = BRIDGE->port_list; + BRIDGE->port_list = p; + + Port *PORT = p; + + portId.port_id[0] = 0x80 | (port_number >> 8); // Default port priority = 128 + portId.port_id[1] = port_number & 0xff; + + // Don't need zero assignments since we zalloc'ed + + //AdminPathCost = 0; + + adminPointToPointMAC = P2PAuto; + + if (BRIDGE->stp_on) { + /* Try and initialize some things */ + int i; + for (i = 0; i < NUM_PORT_STATE_MACHINES; i++) + STM_BEGIN(&PORT->state[i], port_stms[i], PORT_ARGS); + + // selected = FALSE + reselect = TRUE; + } + + return PORT; +} + +void STP_IN_port_delete(Port *PORT) +{ + Bridge *BRIDGE = PORT->bridge; + + /* Disable */ + if (portEnabled) { + portEnabled = FALSE; + state_machines_run(BRIDGE_ARGS); + } + + /* Find position in list */ + Port **prev = &BRIDGE->port_list; + while (*prev != PORT && *prev != NULL) + prev = &(*prev)->port_next; + + if (*prev == NULL) + ABORT("port not in its bridge's list\n"); + + /* Remove from list */ + *prev = PORT->port_next; + + STP_OUT_mem_free(PORT); +} + diff --git a/rstp.h b/rstp.h new file mode 100644 index 0000000..94c208a --- /dev/null +++ b/rstp.h @@ -0,0 +1,226 @@ +#ifndef _RSTP_H +#define _RSTP_H + +/********************************************************************* + This is an implementation of the Rapid Spanning Tree Protocol + based on the 802.1D-2004 standard. +*********************************************************************/ + +struct STP_Bridge_; +typedef struct STP_Bridge_ STP_Bridge; +struct STP_Port_; +typedef struct STP_Port_ STP_Port; + +typedef struct STP_macaddr +{ + unsigned char addr[6]; +} STP_MacAddress; + + +/****** Creating and deleting ******/ + +/* user_ref is a pointer that the STP part uses to call the system part + Bridge is created as disabled. You need to enable it. + */ +STP_Bridge *STP_IN_bridge_create(void *user_ref); + +/* All ports will be deleted, don't reference them */ +void STP_IN_bridge_delete(STP_Bridge *); + +/* user_ref is a pointer that the STP part uses to call the system part + Port is created as disabled. You need to enable it. +*/ +STP_Port *STP_IN_port_create(STP_Bridge *, + unsigned int port_number, void *user_ref); + +void STP_IN_port_delete(STP_Port *); + +/****** enable/disable ******/ + +/* When disabled, we can set config without any immediate effect. + State machine is reinitialized when we enable. +*/ +void STP_IN_set_bridge_enable(STP_Bridge *, unsigned int enabled); + +/****** Configuration ******/ + +typedef struct STP_BridgeConfig_ +{ + int set_bridge_protocol_version; + unsigned int bridge_protocol_version; + + /* Setting bridge address dynamically is not part of the standard + It is convenient for us to interface with Linux bridges where this + may happen. We reinit the state machine when this is changed. + */ + unsigned int set_bridge_address; + STP_MacAddress bridge_address; + + unsigned int set_bridge_priority; + unsigned int bridge_priority; + + unsigned int set_bridge_hello_time; + unsigned int bridge_hello_time; + + unsigned int set_bridge_max_age; + unsigned int bridge_max_age; + + unsigned int set_bridge_forward_delay; + unsigned int bridge_forward_delay; + + unsigned int set_bridge_tx_hold_count; + unsigned int bridge_tx_hold_count; +} STP_BridgeConfig; + +int STP_IN_set_bridge_config(STP_Bridge *, const STP_BridgeConfig *); + +/* Alternate forms to set single fields */ + +/* 0 for STP, 2 for RSTP */ +int STP_IN_set_protocol_version(STP_Bridge *, unsigned int version); + +/* This reinitializes the bridge if bridge is enabled */ +int STP_IN_set_bridge_address(STP_Bridge *, const STP_MacAddress *); + +int STP_IN_set_bridge_priority(STP_Bridge *, unsigned int priority); + +/* Just for compatibility interfacing. Always fails */ +int STP_IN_set_bridge_hello_time(STP_Bridge *, unsigned int hello_time); + +int STP_IN_set_bridge_max_age(STP_Bridge *, unsigned int max_age); + +int STP_IN_set_bridge_forward_delay(STP_Bridge *, unsigned int fwd_delay); + +int STP_IN_set_tx_hold_count(STP_Bridge *, unsigned int count); + + +typedef struct STP_PortConfig_ +{ + unsigned int set_port_priority; + unsigned int port_priority; + + unsigned int set_port_pathcost; + unsigned int port_pathcost; + + unsigned int set_port_admin_edge; + unsigned int port_admin_edge; + + unsigned int set_port_auto_edge; + unsigned int port_auto_edge; + + unsigned int set_port_admin_p2p; + unsigned int port_admin_p2p; +#define STP_ADMIN_P2P_FORCE_FALSE 0 +#define STP_ADMIN_P2P_FORCE_TRUE 1 +#define STP_ADMIN_P2P_AUTO 2 +} STP_PortConfig; + +int STP_IN_set_port_config(STP_Port *, const STP_PortConfig *); + +/* Alternate forms to set single fields */ + +int STP_IN_set_port_priority(STP_Port *, unsigned int priority); + +int STP_IN_set_port_pathcost(STP_Port *, unsigned int pcost); + +/* edge is 0 or 1 - boolean */ +int STP_IN_set_port_admin_edge(STP_Port *, unsigned int edge); + +int STP_IN_set_port_auto_edge(STP_Port *, unsigned int edge); + +/* See STP_PortConfig struct for allowed values of p2p */ +int STP_IN_set_port_admin_p2p(STP_Port *, unsigned int p2p); + + +/* Force migration check */ +int STP_IN_port_mcheck(STP_Port *); + +/****** Notifications ******/ + +/* speed and duplex matter only if enabled */ +void STP_IN_set_port_enable(STP_Port *, unsigned int enabled, + unsigned int speed, unsigned int duplex); + +/* No MAC or LLC header included */ +void STP_IN_rx_bpdu(STP_Port *, const void *base, unsigned int len); + +/* Should be called every second, triggers timer operation. */ +void STP_IN_one_second(STP_Bridge *); + +/****** Status ******/ + +typedef struct STP_BridgeStatus_ +{ + unsigned char bridge_id[8]; + unsigned int time_since_topology_change; + unsigned int topology_change_count; + unsigned int topology_change; /* boolean */ + unsigned char designated_root[8]; + unsigned int root_path_cost; + /* Not in standard, but part of rootPriority */ + unsigned char designated_bridge[8]; + unsigned int root_port; + unsigned int max_age; + unsigned int hello_time; + unsigned int forward_delay; + unsigned int bridge_max_age; + unsigned int bridge_hello_time; + unsigned int bridge_forward_delay; + unsigned int tx_hold_count; + unsigned int protocol_version; + unsigned int enabled; /* Whether we are running state machines */ +} STP_BridgeStatus; + +void STP_IN_get_bridge_status(STP_Bridge *, STP_BridgeStatus *); + +typedef struct STP_PortStatus_ +{ + unsigned int uptime; + unsigned int state; +#define STP_PORT_STATE_DISCARDING 0 +#define STP_PORT_STATE_LEARNING 1 +#define STP_PORT_STATE_FORWARDING 2 + unsigned int id; + unsigned int admin_path_cost; + unsigned int path_cost; + unsigned char designated_root[8]; + unsigned int designated_cost; + unsigned char designated_bridge[8]; + unsigned int designated_port; + unsigned int tc_ack; /* booleans */ + unsigned int admin_edge_port; + unsigned int oper_edge_port; + unsigned int auto_edge_port; + unsigned int enabled; /* Is this MAC Enabled or MAC operational? + It is false both if ifconfig down + and if the link status is down. + */ + unsigned int admin_p2p; + unsigned int oper_p2p; +} STP_PortStatus; + +void STP_IN_get_port_status(STP_Port *, STP_PortStatus *); + +/****** Procedures to be provided by user ******/ + +/* No MAC or LLC header included */ +void STP_OUT_tx_bpdu(void *port_user_ref, void *base, unsigned int len); + +#define STP_PORT_STATE_FLAG_LEARNING 1 +#define STP_PORT_STATE_FLAG_FORWARDING 2 +void STP_OUT_port_set_state(void *user_ref, unsigned int flags); + +void STP_OUT_port_fdb_flush(void *user_ref); + +#define STP_LOG_LEVEL_ERROR 1 +#define STP_LOG_LEVEL_DEBUG 10 + +void STP_OUT_logmsg(void *br_user_ref, void *port_user_ref, + int level, char *fmt, ...); + + +void *STP_OUT_mem_zalloc(unsigned int size); + +void STP_OUT_mem_free(void *); + +#endif -- 2.39.2