diff --git a/NEWS b/NEWS
index 7a2b185..4576408 100644
--- a/NEWS
+++ b/NEWS
@@ -28,7 +28,8 @@ Post-v2.7.0
        abbreviated to 4 hex digits.
      * "ovn-sbctl lflow-list" can now print OpenFlow flows that correspond
        to logical flows.
-   - Add the command 'ovs-appctl stp/show' (see ovs-vswitchd(8)).
+   - Add the command 'ovs-appctl stp/show' and 'ovs-appctl rstp/show'
+     (see ovs-vswitchd(8)).
    - OpenFlow:
      * All features required by OpenFlow 1.4 are now implemented, so
        ovs-vswitchd now enables OpenFlow 1.4 by default (in addition to
diff --git a/lib/rstp.c b/lib/rstp.c
index 48df7ad..095e01f 100644
--- a/lib/rstp.c
+++ b/lib/rstp.c
@@ -120,6 +120,10 @@ static void rstp_port_set_mcheck__(struct rstp_port *, bool mcheck)
     OVS_REQUIRES(rstp_mutex);
 static void reinitialize_port__(struct rstp_port *p)
     OVS_REQUIRES(rstp_mutex);
+static void rstp_unixctl_tcn(struct unixctl_conn *, int argc,
+                             const char *argv[], void *aux);
+static void rstp_unixctl_show(struct unixctl_conn *, int argc,
+                              const char *argv[], void *aux);
 
 const char *
 rstp_state_name(enum rstp_state state)
@@ -205,9 +209,6 @@ rstp_port_get_number(const struct rstp_port *p)
     return number;
 }
 
-static void rstp_unixctl_tcn(struct unixctl_conn *, int argc,
-                             const char *argv[], void *aux);
-
 /* Decrements the State Machines' timers. */
 void
 rstp_tick_timers(struct rstp *rstp)
@@ -240,6 +241,8 @@ rstp_init(void)
 
         unixctl_command_register("rstp/tcn", "[bridge]", 0, 1, rstp_unixctl_tcn,
                                  NULL);
+        unixctl_command_register("rstp/show", "[bridge]", 0, 1,
+                                 rstp_unixctl_show, NULL);
         ovsthread_once_done(&once);
     }
 }
@@ -1367,7 +1370,7 @@ rstp_get_designated_root(const struct rstp *rstp)
  * there is no such port.
  */
 struct rstp_port *
-rstp_get_root_port(struct rstp *rstp)
+rstp_get_root_port(const struct rstp *rstp)
 {
     struct rstp_port *p;
 
@@ -1506,3 +1509,106 @@ rstp_unixctl_tcn(struct unixctl_conn *conn, int argc,
 out:
     ovs_mutex_unlock(&rstp_mutex);
 }
+
+static void
+rstp_bridge_id_details(struct ds *ds, const rstp_identifier bridge_id,
+                       const uint16_t hello_time, const uint16_t max_age,
+                       const uint16_t forward_delay)
+    OVS_REQUIRES(rstp_mutex)
+{
+    uint16_t priority = bridge_id >> 48;
+    ds_put_format(ds, "\tstp-priority\t%"PRIu16"\n", priority);
+
+    struct eth_addr mac;
+    const uint64_t mac_bits = (UINT64_C(1) << 48) - 1;
+    eth_addr_from_uint64(bridge_id & mac_bits, &mac);
+    ds_put_format(ds, "\tstp-system-id\t"ETH_ADDR_FMT"\n", ETH_ADDR_ARGS(mac));
+    ds_put_format(ds, "\tstp-hello-time\t%"PRIu16"s\n", hello_time);
+    ds_put_format(ds, "\tstp-max-age\t%"PRIu16"s\n", max_age);
+    ds_put_format(ds, "\tstp-fwd-delay\t%"PRIu16"s\n", forward_delay);
+}
+
+static void
+rstp_print_details(struct ds *ds, const struct rstp *rstp)
+    OVS_REQUIRES(rstp_mutex)
+{
+    ds_put_format(ds, "---- %s ----\n", rstp->name);
+    ds_put_cstr(ds, "Root ID:\n");
+
+    bool is_root = rstp_is_root_bridge(rstp);
+    struct rstp_port *p = rstp_get_root_port(rstp);
+
+    rstp_identifier bridge_id =
+        is_root ? rstp->bridge_identifier : rstp_get_root_id(rstp);
+    uint16_t hello_time =
+        is_root ? rstp->bridge_hello_time : p->designated_times.hello_time;
+    uint16_t max_age =
+        is_root ? rstp->bridge_max_age : p->designated_times.max_age;
+    uint16_t forward_delay =
+        is_root ? rstp->bridge_forward_delay : p->designated_times.forward_delay;
+
+    rstp_bridge_id_details(ds, bridge_id, hello_time, max_age, forward_delay);
+    if (is_root) {
+        ds_put_cstr(ds, "\tThis bridge is the root\n");
+    } else {
+        ds_put_format(ds, "\troot-port\t%s\n", p->port_name);
+        ds_put_format(ds, "\troot-path-cost\t%u\n",
+                      rstp_get_root_path_cost(rstp));
+    }
+    ds_put_cstr(ds, "\n");
+
+    ds_put_cstr(ds, "Bridge ID:\n");
+    rstp_bridge_id_details(ds, rstp->bridge_identifier,
+                           rstp->bridge_hello_time,
+                           rstp->bridge_max_age,
+                           rstp->bridge_forward_delay);
+    ds_put_cstr(ds, "\n");
+
+    ds_put_format(ds, "\t%-11.10s%-11.10s%-11.10s%-9.8s%-8.7s\n",
+                  "Interface", "Role", "State", "Cost", "Pri.Nbr");
+    ds_put_cstr(ds, "\t---------- ---------- ---------- -------- -------\n");
+    HMAP_FOR_EACH (p, node, &rstp->ports) {
+        if (p->rstp_state != RSTP_DISABLED) {
+            ds_put_format(ds, "\t%-11.10s",
+                          p->port_name ? p->port_name : "null");
+            ds_put_format(ds, "%-11.10s", rstp_port_role_name(p->role));
+            ds_put_format(ds, "%-11.10s", rstp_state_name(p->rstp_state));
+            ds_put_format(ds, "%-9d", p->port_path_cost);
+            ds_put_format(ds, "%d.%d\n", p->priority, p->port_number);
+        }
+    }
+
+    ds_put_cstr(ds, "\n");
+}
+
+static void
+rstp_unixctl_show(struct unixctl_conn *conn, int argc,
+                  const char *argv[], void *aux OVS_UNUSED)
+    OVS_EXCLUDED(rstp_mutex)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+
+    ovs_mutex_lock(&rstp_mutex);
+    if (argc > 1) {
+        struct rstp *rstp = rstp_find(argv[1]);
+
+        if (!rstp) {
+            unixctl_command_reply_error(conn, "No such RSTP object");
+            goto out;
+        }
+
+        rstp_print_details(&ds, rstp);
+    } else {
+        struct rstp *rstp;
+
+        LIST_FOR_EACH (rstp, node, all_rstps) {
+            rstp_print_details(&ds, rstp);
+        }
+    }
+
+    unixctl_command_reply(conn, ds_cstr(&ds));
+    ds_destroy(&ds);
+
+out:
+    ovs_mutex_unlock(&rstp_mutex);
+}
diff --git a/lib/rstp.h b/lib/rstp.h
index b6f9089..15b1f14 100644
--- a/lib/rstp.h
+++ b/lib/rstp.h
@@ -180,7 +180,7 @@ rstp_identifier rstp_get_designated_id(const struct rstp *);
 uint32_t rstp_get_root_path_cost(const struct rstp *);
 uint16_t rstp_get_designated_port_id(const struct rstp *);
 uint16_t rstp_get_bridge_port_id(const struct rstp *);
-struct rstp_port * rstp_get_root_port(struct rstp *);
+struct rstp_port * rstp_get_root_port(const struct rstp *);
 rstp_identifier rstp_get_designated_root(const struct rstp *);
 bool rstp_is_root_bridge(const struct rstp *);
 
diff --git a/vswitchd/ovs-vswitchd.8.in b/vswitchd/ovs-vswitchd.8.in
index be7bd66..9178737 100644
--- a/vswitchd/ovs-vswitchd.8.in
+++ b/vswitchd/ovs-vswitchd.8.in
@@ -141,12 +141,21 @@ interfaces if none is given) to be \fIstatus\fR.  \fIstatus\fR can be
 .IP "\fBstp/tcn\fR [\fIbridge\fR]"
 Forces a topology change event on \fIbridge\fR if it's running STP.  This
 may cause it to send Topology Change Notifications to its peers and flush
-its MAC table..  If no \fIbridge\fR is given, forces a topology change
+its MAC table.  If no \fIbridge\fR is given, forces a topology change
 event on all bridges.
 .IP "\fBstp/show\fR [\fIbridge\fR]"
 Displays detailed information about spanning tree on the \fIbridge\fR.  If
 \fIbridge\fR is not specified, then displays detailed information about all
 bridges with STP enabled.
+.IP "\fBrstp/tcn\fR [\fIbridge\fR]"
+Forces a topology change event on \fIbridge\fR if it's running RSTP.  This
+may cause it to send Topology Change Notifications to its peers and flush
+its MAC table.  If no \fIbridge\fR is given, forces a topology change
+event on all bridges.
+.IP "\fBrstp/show\fR [\fIbridge\fR]"
+Displays detailed information about rapid spanning tree on the \fIbridge\fR.
+If \fIbridge\fR is not specified, then displays detailed information about all
+bridges with RSTP enabled.
 .SS "BRIDGE COMMANDS"
 These commands manage bridges.
 .IP "\fBfdb/flush\fR [\fIbridge\fR]"
