summaryrefslogtreecommitdiff
path: root/lib/rstp-state-machines.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rstp-state-machines.c')
-rw-r--r--lib/rstp-state-machines.c2036
1 files changed, 2036 insertions, 0 deletions
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 <mf@fornasa.it>
+ * Daniele Venturino <daniele.venturino@m3s.it>
+ *
+ * References to IEEE 802.1D-2004 standard are enclosed in square brackets.
+ * E.g. [17.3], [Table 17-1], etc.
+ *
+ */
+
+#include <config.h>
+#include "rstp.h"
+#include "rstp-state-machines.h"
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#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));
+}