Previously fdb/show required exactly one bridge argument.  This
change allows specifying multiple bridge names in a single call,
reducing the number of unixctl round-trips for clients that need
to poll FDB entries across many bridges (e.g. EVPN agents managing
multiple VNIs).

For text output, each bridge's output is prefixed with a
"bridge <name>" header line when multiple bridges are specified.
Single-bridge output remains unchanged for backward compatibility.

For JSON output, multiple bridges produce an array of objects
with "bridge" and "entries" keys.  Single-bridge output remains
a flat array of FDB entries.

Signed-off-by: Takeru Hayasaka <[email protected]>
---
 NEWS                   |  5 +++-
 ofproto/ofproto-dpif.c | 57 ++++++++++++++++++++++++++++++++++--------
 tests/ofproto-dpif.at  | 32 ++++++++++++++++++++++++
 3 files changed, 82 insertions(+), 12 deletions(-)

diff --git a/NEWS b/NEWS
index 1a3044cbfb2f..88d3ac7dad97 100644
--- a/NEWS
+++ b/NEWS
@@ -3,7 +3,10 @@ Post-v3.7.0
    - Userspace datapath:
      * ARP/ND lookups for native tunnel are now rate limited. The holdout
        timer can be configured with 'tnl/neigh/retrans_time'.
-
+   - ovs-appctl:
+     * "fdb/show" now accepts multiple bridge names in a single call,
+       reducing the number of unixctl round-trips for clients that poll
+       FDB entries across many bridges.
 
 v3.7.0 - 16 Feb 2026
 --------------------
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index a02afe8ef335..ff3fe2cf9ee5 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -6242,25 +6242,60 @@ done_unlock:
 }
 
 static void
-ofproto_unixctl_fdb_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
-                          const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
+ofproto_unixctl_fdb_show(struct unixctl_conn *conn, int argc,
+                          const char *argv[], void *aux OVS_UNUSED)
 {
-    const struct ofproto_dpif *ofproto = ofproto_dpif_lookup_by_name(argv[1]);
+    bool multi = argc > 2;
 
-    if (!ofproto) {
-        unixctl_command_reply_error(conn, "no such bridge");
-        return;
+    /* Validate all bridge names up front. */
+    for (int i = 1; i < argc; i++) {
+        if (!ofproto_dpif_lookup_by_name(argv[i])) {
+            unixctl_command_reply_error(conn, "no such bridge");
+            return;
+        }
     }
 
     if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {
-        struct json *fdb_entries;
+        struct json **bridge_entries = xmalloc((argc - 1)
+                                               * sizeof *bridge_entries);
+
+        for (int i = 1; i < argc; i++) {
+            const struct ofproto_dpif *ofproto =
+                ofproto_dpif_lookup_by_name(argv[i]);
+            struct json *fdb_entries;
+
+            ofproto_unixctl_fdb_show_json(ofproto, &fdb_entries);
+            if (multi) {
+                struct json *obj = json_object_create();
 
-        ofproto_unixctl_fdb_show_json(ofproto, &fdb_entries);
-        unixctl_command_reply_json(conn, fdb_entries);
+                json_object_put_string(obj, "bridge", argv[i]);
+                json_object_put(obj, "entries", fdb_entries);
+                bridge_entries[i - 1] = obj;
+            } else {
+                bridge_entries[i - 1] = fdb_entries;
+            }
+        }
+
+        if (multi) {
+            unixctl_command_reply_json(
+                conn, json_array_create(bridge_entries, argc - 1));
+        } else {
+            unixctl_command_reply_json(conn, bridge_entries[0]);
+            free(bridge_entries);
+        }
     } else {
         struct ds ds = DS_EMPTY_INITIALIZER;
 
-        ofproto_unixctl_fdb_show_text(ofproto, &ds);
+        for (int i = 1; i < argc; i++) {
+            const struct ofproto_dpif *ofproto =
+                ofproto_dpif_lookup_by_name(argv[i]);
+
+            if (multi) {
+                ds_put_format(&ds, "bridge %s\n", argv[i]);
+            }
+            ofproto_unixctl_fdb_show_text(ofproto, &ds);
+        }
+
         unixctl_command_reply(conn, ds_cstr(&ds));
         ds_destroy(&ds);
     }
@@ -7131,7 +7166,7 @@ ofproto_unixctl_init(void)
                              ofproto_unixctl_fdb_delete, NULL);
     unixctl_command_register("fdb/flush", "[bridge]", 0, 1,
                              ofproto_unixctl_fdb_flush, NULL);
-    unixctl_command_register("fdb/show", "bridge", 1, 1,
+    unixctl_command_register("fdb/show", "bridge [bridge2] ...", 1, INT_MAX,
                              ofproto_unixctl_fdb_show, NULL);
     unixctl_command_register("fdb/stats-clear", "[bridge]", 0, 1,
                              ofproto_unixctl_fdb_stats_clear, NULL);
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 39e43d376849..3d8c5fa1e2f4 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -7639,6 +7639,38 @@ AT_CHECK_UNQUOTED([ovs-appctl fdb/show br1 | sed 
's/[[0-9]]\{1,\}$/?/'], [0], [d
     5     0  50:54:00:00:00:07    ?
 ])
 
+# Test fdb/show with multiple bridges.
+AT_CHECK_UNQUOTED([ovs-appctl fdb/show br0 br1 | sed '/^ 
/s/[[0-9]]\{1,\}$/?/'], [0], [dnl
+bridge br0
+ port  VLAN  MAC                Age
+    2     0  50:54:00:00:00:05    ?
+bridge br1
+ port  VLAN  MAC                Age
+    5     0  50:54:00:00:00:07    ?
+])
+
+dnl Check json output with multiple bridges.
+AT_CHECK([ovs-appctl --format json --pretty fdb/show br0 br1 \
+          | sed 's/"age": [[0-9]]*/"age": ?/g'], [0], [dnl
+[[
+  {
+    "bridge": "br0",
+    "entries": [
+      {
+        "age": ?,
+        "mac": "50:54:00:00:00:05",
+        "port": 2,
+        "vlan": 0}]},
+  {
+    "bridge": "br1",
+    "entries": [
+      {
+        "age": ?,
+        "mac": "50:54:00:00:00:07",
+        "port": 5,
+        "vlan": 0}]}]]
+])
+
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
-- 
2.43.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to