]> git.proxmox.com Git - mirror_ovs.git/blobdiff - lib/rstp-state-machines.c
stopwatch: Remove tabs from output.
[mirror_ovs.git] / lib / rstp-state-machines.c
index e2d6e76b45b96d9711ab5f97b26d573133f3cbe7..7bd1f80c41a6164f78d593784cafe6b95c0dc999 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014 M3S, Srl - Italy
+ * Copyright (c) 2011-2015 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.
@@ -21,6 +21,7 @@
  * Authors:
  *         Martino Fornasa <mf@fornasa.it>
  *         Daniele Venturino <daniele.venturino@m3s.it>
+ *         Carlo Andreotti <c.andreotti@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 <sys/types.h>
+#include <netinet/in.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 "openvswitch/ofpbuf.h"
+#include "dp-packet.h"
 #include "packets.h"
 #include "seq.h"
 #include "unixctl.h"
 #include "util.h"
-#include "vlog.h"
+#include "openvswitch/vlog.h"
 
 VLOG_DEFINE_THIS_MODULE(rstp_sm);
 
@@ -290,7 +292,14 @@ updt_roles_tree__(struct rstp *r)
     struct rstp_port *p;
     int vsel;
     struct rstp_priority_vector best_vector, candidate_vector;
+    enum rstp_port_role new_root_old_role = ROLE_DESIGNATED;
+    uint16_t old_root_port_number = 0;
+    uint16_t new_root_port_number = 0;
 
+    old_root_port_number = r->root_port_id & 0x00ff;
+    if (old_root_port_number) {
+        r->old_root_aux = rstp_get_port_aux__(r, old_root_port_number);
+    }
     vsel = -1;
     best_vector = r->bridge_priority;
     /* Letter c1) */
@@ -320,12 +329,26 @@ updt_roles_tree__(struct rstp *r)
             r->root_times = p->port_times;
             r->root_times.message_age++;
             vsel = p->port_number;
+            new_root_old_role = p->role;
         }
     }
     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));
+    new_root_port_number = r->root_port_id & 0x00ff;
+    if (new_root_port_number) {
+        r->new_root_aux = rstp_get_port_aux__(r, new_root_port_number);
+    }
+    /* Shift learned MAC addresses from an old Root Port to an existing
+     * Alternate Port. */
+    if (!r->root_changed
+        && new_root_old_role == ROLE_ALTERNATE
+        && new_root_port_number
+        && old_root_port_number
+        && new_root_port_number != old_root_port_number) {
+        r->root_changed = true;
+    }
     /* Letters d) e) */
     HMAP_FOR_EACH (p, node, &r->ports) {
         p->designated_priority_vector.root_bridge_id =
@@ -378,7 +401,7 @@ updt_roles_tree__(struct rstp *r)
             break;
         default:
             OVS_NOT_REACHED();
-            /* no break */
+            /* fall through */
         }
     }
     seq_change(connectivity_seq_get());
@@ -419,7 +442,7 @@ port_role_selection_sm(struct rstp *r)
     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 */
+        /* fall through */
     case PORT_ROLE_SELECTION_SM_INIT_BRIDGE:
         r->port_role_selection_sm_state =
             PORT_ROLE_SELECTION_SM_ROLE_SELECTION_EXEC;
@@ -430,7 +453,7 @@ port_role_selection_sm(struct rstp *r)
         set_selected_tree(r);
         r->port_role_selection_sm_state =
             PORT_ROLE_SELECTION_SM_ROLE_SELECTION;
-        /* no break */
+        /* fall through */
     case PORT_ROLE_SELECTION_SM_ROLE_SELECTION:
         HMAP_FOR_EACH (p, node, &r->ports) {
             if (p->reselect) {
@@ -442,7 +465,7 @@ port_role_selection_sm(struct rstp *r)
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != r->port_role_selection_sm_state) {
         r->changes = true;
@@ -472,7 +495,7 @@ updt_bpdu_version(struct rstp_port *p)  /* [17.21.22] */
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
 }
 
@@ -498,9 +521,13 @@ port_receive_sm(struct rstp_port *p)
         p->rcvd_msg = false;
         p->edge_delay_while = r->migrate_time;
         p->port_receive_sm_state = PORT_RECEIVE_SM_DISCARD;
-        /* no break */
+        /* fall through */
     case PORT_RECEIVE_SM_DISCARD:
-        if (p->rcvd_bpdu && p->port_enabled) {
+        if ((p->rcvd_bpdu || (p->edge_delay_while != r->migrate_time))
+            && !p->port_enabled) {
+            /* Global transition. */
+            p->port_receive_sm_state = PORT_RECEIVE_SM_DISCARD_EXEC;
+        } else if (p->rcvd_bpdu && p->port_enabled) {
             p->port_receive_sm_state = PORT_RECEIVE_SM_RECEIVE_EXEC;
         }
         break;
@@ -510,15 +537,19 @@ port_receive_sm(struct rstp_port *p)
         p->rcvd_msg = true;
         p->edge_delay_while = r->migrate_time;
         p->port_receive_sm_state = PORT_RECEIVE_SM_RECEIVE;
-        /* no break */
+        /* fall through */
     case PORT_RECEIVE_SM_RECEIVE:
-        if (p->rcvd_bpdu && p->port_enabled && !p->rcvd_msg) {
+        if ((p->rcvd_bpdu || (p->edge_delay_while != r->migrate_time))
+            && !p->port_enabled) {
+            /* Global transition. */
+            p->port_receive_sm_state = PORT_RECEIVE_SM_DISCARD_EXEC;
+        } else 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 */
+        /* fall through */
     }
     if (old_state != p->port_receive_sm_state) {
         r->changes = true;
@@ -543,14 +574,14 @@ port_protocol_migration_sm(struct rstp_port *p)
     case PORT_PROTOCOL_MIGRATION_SM_INIT:
         p->port_protocol_migration_sm_state =
             PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP_EXEC;
-        /* no break */
+        /* fall through */
     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 */
+        /* fall through */
     case PORT_PROTOCOL_MIGRATION_SM_CHECKING_RSTP:
         if (p->mdelay_while == 0) {
             p->port_protocol_migration_sm_state =
@@ -565,7 +596,7 @@ port_protocol_migration_sm(struct rstp_port *p)
         p->mdelay_while = r->migrate_time;
         p->port_protocol_migration_sm_state =
             PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP;
-        /* no break */
+        /* fall through */
     case PORT_PROTOCOL_MIGRATION_SM_SELECTING_STP:
         if ((p->mdelay_while == 0) || (!p->port_enabled) || p->mcheck) {
             p->port_protocol_migration_sm_state =
@@ -577,7 +608,7 @@ port_protocol_migration_sm(struct rstp_port *p)
         p->rcvd_stp = false;
         p->port_protocol_migration_sm_state =
             PORT_PROTOCOL_MIGRATION_SM_SENSING;
-        /* no break */
+        /* fall through */
     case PORT_PROTOCOL_MIGRATION_SM_SENSING:
         if (!p->port_enabled || p->mcheck || ((r->rstp_version) &&
                                               !p->send_rstp && p->rcvd_rstp)) {
@@ -590,7 +621,7 @@ port_protocol_migration_sm(struct rstp_port *p)
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != p->port_protocol_migration_sm_state) {
         r->changes = true;
@@ -624,7 +655,7 @@ bridge_detection_sm(struct rstp_port *p)
     case BRIDGE_DETECTION_SM_EDGE_EXEC:
         p->oper_edge = true;
         p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_EDGE;
-        /* no break */
+        /* fall through */
     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;
@@ -633,7 +664,7 @@ bridge_detection_sm(struct rstp_port *p)
     case BRIDGE_DETECTION_SM_NOT_EDGE_EXEC:
         p->oper_edge = false;
         p->bridge_detection_sm_state = BRIDGE_DETECTION_SM_NOT_EDGE;
-        /* no break */
+        /* fall through */
     case BRIDGE_DETECTION_SM_NOT_EDGE:
         if ((!p->port_enabled && p->admin_edge)
             || ((p->edge_delay_while == 0) && p->auto_edge && p->send_rstp
@@ -643,7 +674,7 @@ bridge_detection_sm(struct rstp_port *p)
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != p->bridge_detection_sm_state) {
         r->changes = true;
@@ -660,19 +691,19 @@ 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;
+    struct dp_packet *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));
+    pkt = dp_packet_new(ETH_HEADER_LEN + LLC_HEADER_LEN + bpdu_size);
+    eth = dp_packet_put_zeros(pkt, sizeof *eth);
+    llc = dp_packet_put_zeros(pkt, sizeof *llc);
+    dp_packet_reset_offsets(pkt);
+    dp_packet_set_l3(pkt, dp_packet_put(pkt, bpdu, bpdu_size));
 
     /* 802.2 header. */
-    memcpy(eth->eth_dst, eth_addr_stp, ETH_ADDR_LEN);
+    eth->eth_dst = eth_addr_stp;
     /* p->rstp->send_bpdu() must fill in source address. */
-    eth->eth_type = htons(ofpbuf_size(pkt) - ETH_HEADER_LEN);
+    eth->eth_type = htons(dp_packet_size(pkt) - ETH_HEADER_LEN);
 
     /* LLC header. */
     llc->llc_dsap = STP_LLC_DSAP;
@@ -726,8 +757,11 @@ record_dispute(struct rstp_port *p)
     OVS_REQUIRES(rstp_mutex)
 {
     if ((p->received_bpdu_buffer.flags & BPDU_FLAG_LEARNING) != 0) {
-        p->agreed = true;
-        p->proposing = false;
+        /* 802.1D-2004 says to set the agreed flag and to clear the proposing
+         * flag. 802.1q-2008 instead says to set the disputed variable and to
+         * clear the agreed variable. */
+        p->disputed = true;
+        p->agreed = false;
     }
 }
 
@@ -804,6 +838,7 @@ tx_config(struct rstp_port *p)
 {
     struct rstp_bpdu bpdu;
 
+    memset(&bpdu, 0, sizeof bpdu);
     bpdu.protocol_identifier = htons(0);
     bpdu.protocol_version_identifier = 0;
     bpdu.bpdu_type = CONFIGURATION_BPDU;
@@ -834,6 +869,7 @@ tx_rstp(struct rstp_port *p)
 {
     struct rstp_bpdu bpdu;
 
+    memset(&bpdu, 0, sizeof bpdu);
     bpdu.protocol_identifier = htons(0);
     bpdu.protocol_version_identifier = 2;
     bpdu.bpdu_type = RAPID_SPANNING_TREE_BPDU;
@@ -920,7 +956,7 @@ port_transmit_sm(struct rstp_port *p)
         p->new_info = true;
         p->tx_count = 0;
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_INIT;
-        /* no break */
+        /* fall through */
     case PORT_TRANSMIT_SM_TRANSMIT_INIT:
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC;
         break;
@@ -928,14 +964,14 @@ port_transmit_sm(struct rstp_port *p)
         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 */
+        /* fall through */
     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 */
+        /* fall through */
     case PORT_TRANSMIT_SM_IDLE:
         if (p->role == ROLE_DISABLED) {
             VLOG_DBG("%s, port %u: port_transmit_sm ROLE == DISABLED.",
@@ -964,7 +1000,7 @@ port_transmit_sm(struct rstp_port *p)
         p->tx_count += 1;
         p->tc_ack = false;
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_CONFIG;
-        /* no break */
+        /* fall through */
     case PORT_TRANSMIT_SM_TRANSMIT_CONFIG:
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC;
         break;
@@ -973,7 +1009,7 @@ port_transmit_sm(struct rstp_port *p)
         tx_tcn(p);
         p->tx_count += 1;
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_TCN;
-        /* no break */
+        /* fall through */
     case PORT_TRANSMIT_SM_TRANSMIT_TCN:
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC;
         break;
@@ -983,13 +1019,13 @@ port_transmit_sm(struct rstp_port *p)
         p->tx_count += 1;
         p->tc_ack = false;
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_TRANSMIT_RSTP;
-        /* no break */
+        /* fall through */
     case PORT_TRANSMIT_SM_TRANSMIT_RSTP:
         p->port_transmit_sm_state = PORT_TRANSMIT_SM_IDLE_EXEC;
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != p->port_transmit_sm_state) {
         r->changes = true;
@@ -1029,8 +1065,21 @@ rcv_info(struct rstp_port *p)
 
     cp = compare_rstp_priority_vectors(&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;
+    /* Configuration BPDU conveys a Designated Port Role. */
+    if (p->received_bpdu_buffer.bpdu_type == CONFIGURATION_BPDU) {
+        role = PORT_DES;
+    } else {
+        role =
+            (p->received_bpdu_buffer.flags & ROLE_FLAG_MASK) >> ROLE_FLAG_SHIFT;
+    }
+
+    /* 802.1D-2004 does not report this behaviour.
+     * 802.1Q-2008 says set rcvdTcn. */
+    if (p->received_bpdu_buffer.bpdu_type ==
+            TOPOLOGY_CHANGE_NOTIFICATION_BPDU) {
+        p->rcvd_tcn = true;
+        return OTHER_INFO;
+    }
 
     /* Returns SuperiorDesignatedInfo if:
      * a) The received message conveys a Designated Port Role, and
@@ -1042,9 +1091,7 @@ rcv_info(struct rstp_port *p)
      *     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))) {
+    if (role == PORT_DES && (cp == SUPERIOR || (cp == SAME && ct == false))) {
         return SUPERIOR_DESIGNATED_INFO;
 
         /* Returns RepeatedDesignatedInfo if:
@@ -1094,16 +1141,15 @@ port_information_sm(struct rstp_port *p)
 {
     enum port_information_state_machine old_state;
     struct rstp *r;
+    struct rstp_port *p1;
 
     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) {
+        if (r->begin
+            || (!p->port_enabled && p->info_is != INFO_IS_DISABLED)) {
             p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
         }
         break;
@@ -1115,9 +1161,12 @@ port_information_sm(struct rstp_port *p)
         p->reselect = true;
         p->selected = false;
         p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED;
-        /* no break */
+        /* fall through */
     case PORT_INFORMATION_SM_DISABLED:
-        if (p->port_enabled) {
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else 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;
@@ -1128,9 +1177,12 @@ port_information_sm(struct rstp_port *p)
         p->reselect = true;
         p->selected = false;
         p->port_information_sm_state = PORT_INFORMATION_SM_AGED;
-        /* no break */
+        /* fall through */
     case PORT_INFORMATION_SM_AGED:
-        if (p->selected && p->updt_info) {
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else if (p->selected && p->updt_info) {
             p->port_information_sm_state = PORT_INFORMATION_SM_UPDATE_EXEC;
         }
         break;
@@ -1152,15 +1204,23 @@ port_information_sm(struct rstp_port *p)
         p->info_is = INFO_IS_MINE;
         p->new_info = true;
         p->port_information_sm_state = PORT_INFORMATION_SM_UPDATE;
-        /* no break */
+        /* fall through */
     case PORT_INFORMATION_SM_UPDATE:
-        p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else {
+            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 */
+        /* fall through */
     case PORT_INFORMATION_SM_CURRENT:
-        if (p->rcvd_msg && !p->updt_info) {
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else 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;
@@ -1173,66 +1233,107 @@ port_information_sm(struct rstp_port *p)
     case PORT_INFORMATION_SM_RECEIVE_EXEC:
         p->rcvd_info = rcv_info(p);
         p->port_information_sm_state = PORT_INFORMATION_SM_RECEIVE;
-        /* no break */
+        /* fall through */
     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 */
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else {
+            switch (p->rcvd_info) {
+            case SUPERIOR_DESIGNATED_INFO:
+                /* 802.1q-2008 has a checkBPDUConsistency() function, called on
+                 * a BPDU reception.  checkBPDUConsistency() clears the agreed
+                 * variable if the received message priority vector is superior
+                 * to the port priority vector, the BPDU is an ST BPDU or an
+                 * RST BPDU, its port role is Designated and its Learning flag
+                 * is set. */
+                if (p->received_bpdu_buffer.flags & BPDU_FLAG_LEARNING) {
+                    HMAP_FOR_EACH (p1, node, &r->ports) {
+                        if (p1->port_number != p->port_number) {
+                            p1->agreed = false;
+                        }
+                    }
+                }
+                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();
+                /* fall through */
+            }
         }
         break;
     case PORT_INFORMATION_SM_OTHER_EXEC:
         p->rcvd_msg = false;
         p->port_information_sm_state = PORT_INFORMATION_SM_OTHER;
-        /* no break */
+        /* fall through */
     case PORT_INFORMATION_SM_OTHER:
-        p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else {
+            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 */
+        /* fall through */
     case PORT_INFORMATION_SM_NOT_DESIGNATED:
-        p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else {
+            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 */
+        /* fall through */
     case PORT_INFORMATION_SM_INFERIOR_DESIGNATED:
-        p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else {
+            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);
+        /* This record_agreement() is missing in 802.1D-2004, but it's present
+         * in 802.1q-2008. */
+        record_agreement(p);
         updt_rcvd_info_while(p);
         p->rcvd_msg = false;
         p->port_information_sm_state = PORT_INFORMATION_SM_REPEATED_DESIGNATED;
-        /* no break */
+        /* fall through */
     case PORT_INFORMATION_SM_REPEATED_DESIGNATED:
-        p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else {
+            p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        }
         break;
     case PORT_INFORMATION_SM_SUPERIOR_DESIGNATED_EXEC:
         p->agreed = p->proposing = false;
@@ -1240,6 +1341,10 @@ port_information_sm(struct rstp_port *p)
         set_tc_flags(p);
         /* RECEIVED is not specified in Standard 802.1D-2004. */
         p->agree = p->agree && better_or_same_info(p, RECEIVED);
+        /* This record_agreement() and the synced assignment are  missing in
+         * 802.1D-2004, but they're present in 802.1q-2008. */
+        record_agreement(p);
+        p->synced = p->synced && p->agreed;
         record_priority(p);
         record_times(p);
         updt_rcvd_info_while(p);
@@ -1248,13 +1353,18 @@ port_information_sm(struct rstp_port *p)
         p->selected = false;
         p->rcvd_msg = false;
         p->port_information_sm_state = PORT_INFORMATION_SM_SUPERIOR_DESIGNATED;
-        /* no break */
+        /* fall through */
     case PORT_INFORMATION_SM_SUPERIOR_DESIGNATED:
-        p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        if (!p->port_enabled && p->info_is != INFO_IS_DISABLED) {
+            /* Global transition. */
+            p->port_information_sm_state = PORT_INFORMATION_SM_DISABLED_EXEC;
+        } else {
+            p->port_information_sm_state = PORT_INFORMATION_SM_CURRENT_EXEC;
+        }
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != p->port_information_sm_state) {
         r->changes = true;
@@ -1433,10 +1543,10 @@ port_role_transition_sm(struct rstp_port *p)
         p->learn = p->forward = false;
         p->port_role_transition_sm_state =
             PORT_ROLE_TRANSITION_SM_DISABLE_PORT;
-        /* no break */
+        /* fall through */
     case PORT_ROLE_TRANSITION_SM_DISABLE_PORT:
         if (check_selected_role_change(p, ROLE_DISABLED)) {
-            break;
+            /* Global transition. */
         } else if (p->selected && !p->updt_info && !p->learning
                    && !p->forwarding) {
             p->port_role_transition_sm_state =
@@ -1450,10 +1560,10 @@ port_role_transition_sm(struct rstp_port *p)
         p->sync = p->re_root = false;
         p->port_role_transition_sm_state =
             PORT_ROLE_TRANSITION_SM_DISABLED_PORT;
-        /* no break */
+        /* fall through */
     case PORT_ROLE_TRANSITION_SM_DISABLED_PORT:
         if (check_selected_role_change(p, ROLE_DISABLED)) {
-            break;
+            /* Global transition. */
         } else if (p->selected && !p->updt_info
                    && (p->fd_while != p->designated_times.max_age || p->sync
                        || p->re_root || !p->synced)) {
@@ -1465,10 +1575,10 @@ port_role_transition_sm(struct rstp_port *p)
         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 */
+        /* fall through */
     case PORT_ROLE_TRANSITION_SM_ROOT_PORT:
         if (check_selected_role_change(p, ROLE_ROOT)) {
-            break;
+            /* Global transition. */
         } else if (p->selected && !p->updt_info) {
             if (p->rr_while != p->designated_times.forward_delay) {
                 p->port_role_transition_sm_state =
@@ -1505,49 +1615,73 @@ port_role_transition_sm(struct rstp_port *p)
                 break;
             }
         }
-    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;
+        if (check_selected_role_change(p, ROLE_ROOT)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_ROOT)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_ROOT)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_ROOT)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_ROOT)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_ROOT)) {
+            /* Global transition. */
+        } else {
+            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 */
+        /* fall through */
     case PORT_ROLE_TRANSITION_SM_DESIGNATED_PORT:
         if (check_selected_role_change(p, ROLE_DESIGNATED)) {
-            break;
+            /* Global transition. */
         } else if (p->selected && !p->updt_info) {
             if (((p->sync && !p->synced)
                  || (p->re_root && p->rr_while != 0) || p->disputed)
@@ -1582,41 +1716,65 @@ port_role_transition_sm(struct rstp_port *p)
         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;
+        if (check_selected_role_change(p, ROLE_DESIGNATED)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_DESIGNATED)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_DESIGNATED)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_DESIGNATED)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_DESIGNATED)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_DESIGNATED)) {
+            /* Global transition. */
+        } else {
+            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;
@@ -1625,10 +1783,10 @@ port_role_transition_sm(struct rstp_port *p)
         p->sync = p->re_root = false;
         p->port_role_transition_sm_state =
             PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT;
-        /* no break */
+        /* fall through */
     case PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT:
         if (check_selected_role_change(p, ROLE_ALTERNATE)) {
-            break;
+            /* Global transition. */
         } else if (p->selected && !p->updt_info) {
             if (p->rb_while != 2 * p->designated_times.hello_time
                 && p->role == ROLE_BACKUP) {
@@ -1652,23 +1810,31 @@ port_role_transition_sm(struct rstp_port *p)
         p->proposed = false;
         p->agree = true;
         p->new_info = true;
-        p->port_role_transition_sm_state =
-            PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC;
+        if (check_selected_role_change(p, ROLE_ALTERNATE)) {
+            /* Global transition. */
+        } else {
+            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;
+        if (check_selected_role_change(p, ROLE_ALTERNATE)) {
+            /* Global transition. */
+        } else {
+            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 */
+        /* fall through */
     case PORT_ROLE_TRANSITION_SM_BLOCK_PORT:
         if (check_selected_role_change(p, ROLE_ALTERNATE)) {
-            break;
+            /* Global transition. */
         } else if (p->selected && !p->updt_info && !p->learning &&
                    !p->forwarding) {
             p->port_role_transition_sm_state =
@@ -1677,12 +1843,16 @@ port_role_transition_sm(struct rstp_port *p)
         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;
+        if (check_selected_role_change(p, ROLE_ALTERNATE)) {
+            /* Global transition. */
+        } else {
+            p->port_role_transition_sm_state =
+                PORT_ROLE_TRANSITION_SM_ALTERNATE_PORT_EXEC;
+        }
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != p->port_role_transition_sm_state) {
         r->changes = true;
@@ -1691,7 +1861,6 @@ port_role_transition_sm(struct rstp_port *p)
                  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));
@@ -1773,7 +1942,7 @@ port_state_transition_sm(struct rstp_port *p)
         p->forwarding = false;
         p->port_state_transition_sm_state =
             PORT_STATE_TRANSITION_SM_DISCARDING;
-        /* no break */
+        /* fall through */
     case PORT_STATE_TRANSITION_SM_DISCARDING:
         if (p->learn) {
             p->port_state_transition_sm_state =
@@ -1784,7 +1953,7 @@ port_state_transition_sm(struct rstp_port *p)
         enable_learning(p);
         p->learning = true;
         p->port_state_transition_sm_state = PORT_STATE_TRANSITION_SM_LEARNING;
-        /* no break */
+        /* fall through */
     case PORT_STATE_TRANSITION_SM_LEARNING:
         if (!p->learn) {
             p->port_state_transition_sm_state =
@@ -1799,7 +1968,7 @@ port_state_transition_sm(struct rstp_port *p)
         p->forwarding = true;
         p->port_state_transition_sm_state =
             PORT_STATE_TRANSITION_SM_FORWARDING;
-        /* no break */
+        /* fall through */
     case PORT_STATE_TRANSITION_SM_FORWARDING:
         if (!p->forward) {
             p->port_state_transition_sm_state =
@@ -1808,7 +1977,7 @@ port_state_transition_sm(struct rstp_port *p)
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != p->port_state_transition_sm_state) {
         r->changes = true;
@@ -1884,7 +2053,7 @@ topology_change_sm(struct rstp_port *p)
         p->tc_while = 0;
         p->tc_ack = false;
         p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_INACTIVE;
-        /* no break */
+        /* fall through */
     case TOPOLOGY_CHANGE_SM_INACTIVE:
         if (p->learn && !p->fdb_flush) {
             p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_LEARNING_EXEC;
@@ -1894,7 +2063,7 @@ topology_change_sm(struct rstp_port *p)
         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 */
+        /* fall through */
     case TOPOLOGY_CHANGE_SM_LEARNING:
         if (p->role != ROLE_ROOT && p->role != ROLE_DESIGNATED &&
             !(p->learn || p->learning) && !(p->rcvd_tc || p->rcvd_tcn ||
@@ -1912,10 +2081,10 @@ topology_change_sm(struct rstp_port *p)
         set_tc_prop_tree(p);
         p->new_info = true;
         p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE_EXEC;
-        /* no break */
+        /* fall through */
     case TOPOLOGY_CHANGE_SM_ACTIVE_EXEC:
         p->topology_change_sm_state = TOPOLOGY_CHANGE_SM_ACTIVE;
-        /* no break */
+        /* fall through */
     case TOPOLOGY_CHANGE_SM_ACTIVE:
         if ((p->role != ROLE_ROOT && p->role != ROLE_DESIGNATED)
             || p->oper_edge) {
@@ -1955,7 +2124,7 @@ topology_change_sm(struct rstp_port *p)
         break;
     default:
         OVS_NOT_REACHED();
-        /* no break */
+        /* fall through */
     }
     if (old_state != p->topology_change_sm_state) {
         r->changes = true;