]> git.proxmox.com Git - mirror_ovs.git/blobdiff - ofproto/ofproto-dpif.c
Rapid Spanning Tree Protocol (IEEE 802.1D).
[mirror_ovs.git] / ofproto / ofproto-dpif.c
index a635ca6ed4c1a88e1814a71a0140cfa376de9f8a..bff0ff4babb4d7adbc7e868ed5b51b990211fd6d 100644 (file)
@@ -145,6 +145,10 @@ static void stp_wait(struct ofproto_dpif *ofproto);
 static int set_stp_port(struct ofport *,
                         const struct ofproto_port_stp_settings *);
 
+static void rstp_run(struct ofproto_dpif *ofproto);
+static void set_rstp_port(struct ofport *,
+                         const struct ofproto_port_rstp_settings *);
+
 struct ofport_dpif {
     struct hmap_node odp_port_node; /* In dpif_backer's "odp_to_ofport_map". */
     struct ofport up;
@@ -165,6 +169,10 @@ struct ofport_dpif {
     enum stp_state stp_state;   /* Always STP_DISABLED if STP not in use. */
     long long int stp_state_entered;
 
+    /* Rapid Spanning Tree. */
+    struct rstp_port *rstp_port; /* Rapid Spanning Tree Protocol, if any. */
+    enum rstp_state rstp_state; /* Always RSTP_DISABLED if RSTP not in use. */
+
     /* Queue to DSCP mapping. */
     struct ofproto_port_queue *qdscp;
     size_t n_qdscp;
@@ -224,6 +232,7 @@ static void ofport_update_peer(struct ofport_dpif *);
 enum revalidate_reason {
     REV_RECONFIGURE = 1,       /* Switch configuration changed. */
     REV_STP,                   /* Spanning tree protocol port status change. */
+    REV_RSTP,                  /* RSTP port status change. */
     REV_BOND,                  /* Bonding changed. */
     REV_PORT_TOGGLED,          /* Port enabled or disabled by CFM, LACP, ...*/
     REV_FLOW_TABLE,            /* Flow table changed. */
@@ -232,6 +241,7 @@ enum revalidate_reason {
 };
 COVERAGE_DEFINE(rev_reconfigure);
 COVERAGE_DEFINE(rev_stp);
+COVERAGE_DEFINE(rev_rstp);
 COVERAGE_DEFINE(rev_bond);
 COVERAGE_DEFINE(rev_port_toggled);
 COVERAGE_DEFINE(rev_flow_table);
@@ -302,6 +312,10 @@ struct ofproto_dpif {
     struct stp *stp;
     long long int stp_last_tick;
 
+    /* Rapid Spanning Tree. */
+    struct rstp *rstp;
+    long long int rstp_last_tick;
+
     /* VLAN splinters. */
     struct ovs_mutex vsp_mutex;
     struct hmap realdev_vid_map OVS_GUARDED; /* (realdev,vid) -> vlandev. */
@@ -575,6 +589,7 @@ type_run(const char *type)
         switch (backer->need_revalidate) {
         case REV_RECONFIGURE:    COVERAGE_INC(rev_reconfigure);    break;
         case REV_STP:            COVERAGE_INC(rev_stp);            break;
+        case REV_RSTP:           COVERAGE_INC(rev_rstp);           break;
         case REV_BOND:           COVERAGE_INC(rev_bond);           break;
         case REV_PORT_TOGGLED:   COVERAGE_INC(rev_port_toggled);   break;
         case REV_FLOW_TABLE:     COVERAGE_INC(rev_flow_table);     break;
@@ -595,8 +610,8 @@ type_run(const char *type)
             xlate_ofproto_set(ofproto, ofproto->up.name,
                               ofproto->backer->dpif, ofproto->miss_rule,
                               ofproto->no_packet_in_rule, ofproto->ml,
-                              ofproto->stp, ofproto->ms, ofproto->mbridge,
-                              ofproto->sflow, ofproto->ipfix,
+                              ofproto->stp, ofproto->rstp, ofproto->ms,
+                              ofproto->mbridge, ofproto->sflow, ofproto->ipfix,
                               ofproto->netflow, ofproto->up.frag_handling,
                               ofproto->up.forward_bpdu,
                               connmgr_has_in_band(ofproto->up.connmgr),
@@ -616,11 +631,14 @@ type_run(const char *type)
                 int stp_port = ofport->stp_port
                     ? stp_port_no(ofport->stp_port)
                     : -1;
+                int rstp_port = ofport->rstp_port
+                    ? rstp_port_number(ofport->rstp_port)
+                    : -1;
                 xlate_ofport_set(ofproto, ofport->bundle, ofport,
                                  ofport->up.ofp_port, ofport->odp_port,
                                  ofport->up.netdev, ofport->cfm,
                                  ofport->bfd, ofport->peer, stp_port,
-                                 ofport->qdscp, ofport->n_qdscp,
+                                 rstp_port, ofport->qdscp, ofport->n_qdscp,
                                  ofport->up.pp.config, ofport->up.pp.state,
                                  ofport->is_tunnel, ofport->may_enable);
             }
@@ -1129,6 +1147,7 @@ construct(struct ofproto *ofproto_)
     ofproto->sflow = NULL;
     ofproto->ipfix = NULL;
     ofproto->stp = NULL;
+    ofproto->rstp = NULL;
     ofproto->dump_seq = 0;
     hmap_init(&ofproto->bundles);
     ofproto->ml = mac_learning_create(MAC_ENTRY_DEFAULT_IDLE_TIME);
@@ -1393,6 +1412,7 @@ run(struct ofproto *ofproto_)
     }
 
     stp_run(ofproto);
+    rstp_run(ofproto);
     ovs_rwlock_wrlock(&ofproto->ml->rwlock);
     if (mac_learning_run(ofproto->ml)) {
         ofproto->backer->need_revalidate = REV_MAC_LEARNING;
@@ -1551,6 +1571,8 @@ port_construct(struct ofport *port_)
     port->may_enable = true;
     port->stp_port = NULL;
     port->stp_state = STP_DISABLED;
+    port->rstp_port = NULL;
+    port->rstp_state = RSTP_DISABLED;
     port->is_tunnel = false;
     port->peer = NULL;
     port->qdscp = NULL;
@@ -1661,6 +1683,9 @@ port_destruct(struct ofport *port_)
     if (port->stp_port) {
         stp_port_disable(port->stp_port);
     }
+    if (port->rstp_port) {
+        rstp_delete_port(port->rstp_port);
+    }
     if (ofproto->sflow) {
         dpif_sflow_del_port(ofproto->sflow, port->odp_port);
     }
@@ -1883,6 +1908,31 @@ get_bfd_status(struct ofport *ofport_, struct smap *smap)
 \f
 /* Spanning Tree. */
 
+static void
+rstp_send_bpdu_cb(struct ofpbuf *pkt, int port_num, void *ofproto_)
+{
+    struct ofproto_dpif *ofproto = ofproto_;
+    struct rstp_port *rp = rstp_get_port(ofproto->rstp, port_num);
+    struct ofport_dpif *ofport;
+
+    ofport = rstp_port_get_aux(rp);
+    if (!ofport) {
+        VLOG_WARN_RL(&rl, "%s: cannot send BPDU on unknown port %d",
+                ofproto->up.name, port_num);
+    } else {
+        struct eth_header *eth = ofpbuf_l2(pkt);
+
+        netdev_get_etheraddr(ofport->up.netdev, eth->eth_src);
+        if (eth_addr_is_zero(eth->eth_src)) {
+            VLOG_WARN_RL(&rl, "%s: cannot send BPDU on port %d "
+                    "with unknown MAC", ofproto->up.name, port_num);
+        } else {
+            ofproto_dpif_send_packet(ofport, pkt);
+        }
+    }
+    ofpbuf_delete(pkt);
+}
+
 static void
 send_bpdu_cb(struct ofpbuf *pkt, int port_num, void *ofproto_)
 {
@@ -1908,6 +1958,138 @@ send_bpdu_cb(struct ofpbuf *pkt, int port_num, void *ofproto_)
     ofpbuf_delete(pkt);
 }
 
+/* Configure RSTP on 'ofproto_' using the settings defined in 's'. */
+static void
+set_rstp(struct ofproto *ofproto_, const struct ofproto_rstp_settings *s)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+    /* Only revalidate flows if the configuration changed. */
+    if (!s != !ofproto->rstp) {
+        ofproto->backer->need_revalidate = REV_RECONFIGURE;
+    }
+
+    if (s) {
+        if (!ofproto->rstp) {
+            ofproto->rstp = rstp_create(ofproto_->name, s->address,
+              rstp_send_bpdu_cb, ofproto);
+            ofproto->rstp_last_tick = time_msec();
+        }
+        rstp_set_bridge_address(ofproto->rstp, s->address);
+        rstp_set_bridge_priority(ofproto->rstp, s->priority);
+        rstp_set_bridge_ageing_time(ofproto->rstp, s->ageing_time);
+        rstp_set_bridge_force_protocol_version(ofproto->rstp,
+                                               s->force_protocol_version);
+        rstp_set_bridge_max_age(ofproto->rstp, s->bridge_max_age);
+        rstp_set_bridge_forward_delay(ofproto->rstp, s->bridge_forward_delay);
+        rstp_set_bridge_transmit_hold_count(ofproto->rstp,
+                                            s->transmit_hold_count);
+    } else {
+        struct ofport *ofport;
+        HMAP_FOR_EACH (ofport, hmap_node, &ofproto->up.ports) {
+            set_rstp_port(ofport, NULL);
+        }
+        rstp_unref(ofproto->rstp);
+        ofproto->rstp = NULL;
+    }
+}
+
+static void
+get_rstp_status(struct ofproto *ofproto_, struct ofproto_rstp_status *s)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
+
+    if (ofproto->rstp) {
+        s->enabled = true;
+        s->root_id = rstp_get_root_id(ofproto->rstp);
+        s->bridge_id = rstp_get_bridge_id(ofproto->rstp);
+        s->designated_id = rstp_get_designated_id(ofproto->rstp);
+        s->root_path_cost = rstp_get_root_path_cost(ofproto->rstp);
+        s->designated_port_id = rstp_get_designated_port_id(ofproto->rstp);
+        s->bridge_port_id = rstp_get_bridge_port_id(ofproto->rstp);
+    } else {
+        s->enabled = false;
+    }
+}
+
+static void
+update_rstp_port_state(struct ofport_dpif *ofport)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+    enum rstp_state state;
+
+    /* Figure out new state. */
+    state = ofport->rstp_port ? rstp_port_get_state(ofport->rstp_port)
+        : RSTP_DISABLED;
+
+    /* Update state. */
+    if (ofport->rstp_state != state) {
+        enum ofputil_port_state of_state;
+        bool fwd_change;
+
+        VLOG_DBG_RL(&rl, "port %s: RSTP state changed from %s to %s",
+                netdev_get_name(ofport->up.netdev),
+                rstp_state_name(ofport->rstp_state),
+                rstp_state_name(state));
+        if (rstp_learn_in_state(ofport->rstp_state)
+                != rstp_learn_in_state(state)) {
+            /* xxx Learning action flows should also be flushed. */
+            ovs_rwlock_wrlock(&ofproto->ml->rwlock);
+            mac_learning_flush(ofproto->ml);
+            ovs_rwlock_unlock(&ofproto->ml->rwlock);
+        }
+        fwd_change = rstp_forward_in_state(ofport->rstp_state)
+            != rstp_forward_in_state(state);
+
+        ofproto->backer->need_revalidate = REV_RSTP;
+        ofport->rstp_state = state;
+
+        if (fwd_change && ofport->bundle) {
+            bundle_update(ofport->bundle);
+        }
+
+        /* Update the RSTP state bits in the OpenFlow port description. */
+        of_state = ofport->up.pp.state & ~OFPUTIL_PS_STP_MASK;
+        of_state |= (state == RSTP_LEARNING ? OFPUTIL_PS_STP_LEARN
+                : state == RSTP_FORWARDING ? OFPUTIL_PS_STP_FORWARD
+                : state == RSTP_DISCARDING ?  OFPUTIL_PS_STP_LISTEN
+                : 0);
+        ofproto_port_set_state(&ofport->up, of_state);
+    }
+}
+
+static void
+rstp_run(struct ofproto_dpif *ofproto)
+{
+    if (ofproto->rstp) {
+        long long int now = time_msec();
+        long long int elapsed = now - ofproto->rstp_last_tick;
+        struct rstp_port *rp;
+        /* Every second, decrease the values of the timers. */
+        if (elapsed >= 1000) {
+            rstp_tick_timers(ofproto->rstp);
+            ofproto->rstp_last_tick = now;
+        }
+        while (rstp_get_changed_port(ofproto->rstp, &rp)) {
+            struct ofport_dpif *ofport = rstp_port_get_aux(rp);
+            if (ofport) {
+                update_rstp_port_state(ofport);
+            }
+        }
+        /* FIXME: This check should be done on-event (i.e., when setting
+         * p->fdb_flush) and not periodically.
+         */
+        if (rstp_check_and_reset_fdb_flush(ofproto->rstp)) {
+            ovs_rwlock_wrlock(&ofproto->ml->rwlock);
+            /* FIXME: RSTP should be able to flush the entries pertaining to a
+             * single port, not the whole table.
+             */
+            mac_learning_flush(ofproto->ml);
+            ovs_rwlock_unlock(&ofproto->ml->rwlock);
+        }
+    }
+}
+
 /* Configures STP on 'ofproto_' using the settings defined in 's'. */
 static int
 set_stp(struct ofproto *ofproto_, const struct ofproto_stp_settings *s)
@@ -2126,6 +2308,94 @@ stp_wait(struct ofproto_dpif *ofproto)
         poll_timer_wait(1000);
     }
 }
+
+/* Configures RSTP on 'ofport_' using the settings defined in 's'.  The
+ * caller is responsible for assigning RSTP port numbers and ensuring
+ * there are no duplicates. */
+static void
+set_rstp_port(struct ofport *ofport_,
+        const struct ofproto_port_rstp_settings *s)
+{
+    struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+    struct rstp_port *rp = ofport->rstp_port;
+    int stp_port;
+
+    if (!s || !s->enable) {
+        if (rp) {
+            ofport->rstp_port = NULL;
+            rstp_delete_port(rp);
+            update_rstp_port_state(ofport);
+        }
+        return;
+    } else if (rp && rstp_port_number(rp) != s->port_num
+                  && ofport == rstp_port_get_aux(rp)) {
+        /* The port-id changed, so disable the old one if it's not
+         * already in use by another port. */
+        if (s->port_num != 0) {
+            xlate_txn_start();
+            stp_port = ofport->stp_port ? stp_port_no(ofport->stp_port) : -1;
+            xlate_ofport_set(ofproto, ofport->bundle, ofport,
+                    ofport->up.ofp_port, ofport->odp_port,
+                    ofport->up.netdev, ofport->cfm,
+                    ofport->bfd, ofport->peer, stp_port,
+                    s->port_num,
+                    ofport->qdscp, ofport->n_qdscp,
+                    ofport->up.pp.config, ofport->up.pp.state,
+                    ofport->is_tunnel, ofport->may_enable);
+            xlate_txn_commit();
+        }
+
+        rstp_port_set_aux(rp, ofport);
+        rstp_port_set_priority(rp, s->priority);
+        rstp_port_set_port_number(rp, s->port_num);
+        rstp_port_set_path_cost(rp, s->path_cost);
+        rstp_port_set_admin_edge(rp, s->admin_edge_port);
+        rstp_port_set_auto_edge(rp, s->auto_edge);
+        rstp_port_set_mcheck(rp, s->mcheck);
+
+        update_rstp_port_state(ofport);
+
+        return;
+    }
+    rp = ofport->rstp_port = rstp_get_port(ofproto->rstp, s->port_num);
+    /* Enable RSTP on port */
+    if (!rp) {
+        rp = ofport->rstp_port = rstp_add_port(ofproto->rstp);
+    }
+    /* Setters */
+    rstp_port_set_aux(rp, ofport);
+    rstp_port_set_priority(rp, s->priority);
+    rstp_port_set_port_number(rp, s->port_num);
+    rstp_port_set_path_cost(rp, s->path_cost);
+    rstp_port_set_admin_edge(rp, s->admin_edge_port);
+    rstp_port_set_auto_edge(rp, s->auto_edge);
+    rstp_port_set_mcheck(rp, s->mcheck);
+
+    update_rstp_port_state(ofport);
+}
+
+static void
+get_rstp_port_status(struct ofport *ofport_,
+        struct ofproto_port_rstp_status *s)
+{
+    struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
+    struct rstp_port *rp = ofport->rstp_port;
+
+    if (!ofproto->rstp || !rp) {
+        s->enabled = false;
+        return;
+    }
+
+    s->enabled = true;
+    s->port_id = rstp_port_get_id(rp);
+    s->state = rstp_port_get_state(rp);
+    s->role = rstp_port_get_role(rp);
+    rstp_port_get_counts(rp, &s->tx_count, &s->rx_count,
+                         &s->error_count, &s->uptime);
+}
+
 \f
 static int
 set_queues(struct ofport *ofport_, const struct ofproto_port_queue *qdscp,
@@ -2863,6 +3133,12 @@ port_run(struct ofport_dpif *ofport)
     }
 
     ofport->may_enable = enable;
+
+    if (ofport->rstp_port) {
+        if (rstp_port_get_mac_operational(ofport->rstp_port) != enable) {
+            rstp_port_set_mac_operational(ofport->rstp_port, enable);
+        }
+    }
 }
 
 static int
@@ -5128,6 +5404,10 @@ const struct ofproto_class ofproto_dpif_class = {
     set_stp_port,
     get_stp_port_status,
     get_stp_port_stats,
+    set_rstp,
+    get_rstp_status,
+    set_rstp_port,
+    get_rstp_port_status,
     set_queues,
     bundle_set,
     bundle_remove,