Introduce an optional parameter to `ovs-appctl ovs/route/show` for
printing non-default routing tables:

  ovs-appctl ovs/route/show [table=all|id]

Default usage is unchanged:

  ovs-appctl ovs/route/show
  Route Table:
  Cached: ::1/128 dev lo SRC ::1
  Cached: 127.0.0.0/8 dev lo SRC 127.0.0.1 local
  Cached: 10.7.7.0/24 dev br-phy1 SRC 10.7.7.17
  Cached: 0.0.0.0/0 dev eth1 GW 10.0.0.1 SRC 10.0.0.2

New usage with a specific table displays only the routes from that
table:

  ovs-appctl ovs/route/show table=10
  Route Table #10:
  Cached: 10.7.7.0/24 dev br-phy0 SRC 10.7.7.7

Special table 'all' displays all of the routes, the ones which are
coming from a non-default table have additional field 'table' displayed:

  ovs-appctl ovs/route/show table=all
  Cached: 10.7.7.0/24 dev br-phy1 SRC 10.7.7.17 table 20
  Cached: 10.7.7.0/24 dev br-phy0 SRC 10.7.7.7 table 10
  Cached: ::1/128 dev lo SRC ::1
  Cached: 127.0.0.0/8 dev lo SRC 127.0.0.1 local
  Cached: 10.7.7.0/24 dev br-phy1 SRC 10.7.7.17
  Cached: 0.0.0.0/0 dev eth1 GW 10.0.0.1 SRC 10.0.0.2

Signed-off-by: Dima Chumak <dchu...@nvidia.com>
---
 Documentation/howto/userspace-tunneling.rst |   2 +-
 NEWS                                        |   4 +-
 lib/ovs-router.c                            | 177 ++++++++++++--------
 ofproto/ofproto-tnl-unixctl.man             |   9 +-
 tests/ovs-router.at                         |  15 ++
 5 files changed, 136 insertions(+), 71 deletions(-)

diff --git a/Documentation/howto/userspace-tunneling.rst 
b/Documentation/howto/userspace-tunneling.rst
index 31d82fd5e57a..1dd34cd2f5e4 100644
--- a/Documentation/howto/userspace-tunneling.rst
+++ b/Documentation/howto/userspace-tunneling.rst
@@ -205,7 +205,7 @@ To add route::
 
 To see all routes configured::
 
-    $ ovs-appctl ovs/route/show
+    $ ovs-appctl ovs/route/show [table=ID|all]
 
 To delete route::
 
diff --git a/NEWS b/NEWS
index 1dbe0ef31093..d0db019df4c5 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,8 @@
 Post-v3.6.0
 --------------------
-
+   - ovs-appctl:
+     * 'ovs/route/show': added new option, table=[ID|all], to list routes from
+       a specific OVS table or all routes from all tables.
 
 v3.6.0 - 18 Aug 2025
 --------------------
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index b6003a40a624..91f331847c99 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -626,37 +626,21 @@ ovs_router_del(struct unixctl_conn *conn, int argc 
OVS_UNUSED,
 }
 
 static void
-ovs_router_show_json(struct json **routes)
+ovs_router_show_json(struct json *json_routes, const struct classifier *cls,
+                     uint32_t table)
 {
-    struct json **json_entries = NULL;
+    struct ds ds = DS_EMPTY_INITIALIZER;
     struct ovs_router_entry *rt;
-    struct classifier *cls_main;
-    struct ds ds;
-    int n_rules;
-    int i = 0;
 
-    cls_main = cls_find(CLS_MAIN);
-    if (!cls_main) {
-        goto out;
-    }
-
-    n_rules = classifier_count(cls_main);
-    if (!n_rules) {
-        goto out;
+    if (!cls) {
+        return;
     }
 
-    json_entries = xmalloc(n_rules * sizeof *json_entries);
-    ds_init(&ds);
-
-    CLS_FOR_EACH (rt, cr, cls_main) {
+    CLS_FOR_EACH (rt, cr, cls) {
         bool user = rt->priority != rt->plen && !rt->local;
         uint8_t plen = rt->plen;
         struct json *json, *nh;
 
-        if (i >= n_rules) {
-            break;
-        }
-
         json = json_object_create();
         nh = json_object_create();
 
@@ -664,6 +648,7 @@ ovs_router_show_json(struct json **routes)
             plen -= 96;
         }
 
+        json_object_put(json, "table", json_integer_create(table));
         json_object_put(json, "user", json_boolean_create(user));
         json_object_put(json, "local", json_boolean_create(rt->local));
         json_object_put(json, "priority", json_integer_create(rt->priority));
@@ -689,59 +674,69 @@ ovs_router_show_json(struct json **routes)
         }
 
         json_object_put(json, "nexthops", json_array_create_1(nh));
-        json_entries[i++] = json;
+        json_array_add(json_routes, json);
     }
 
     ds_destroy(&ds);
+}
 
-out:
-    *routes = json_array_create(json_entries, i);
+static bool
+is_standard_table(uint32_t table_id)
+{
+    return table_id == CLS_DEFAULT
+           || table_id == CLS_MAIN
+           || table_id == CLS_LOCAL;
 }
 
 static void
-ovs_router_show_text(struct ds *ds)
+ovs_router_show_text(struct ds *ds, const struct classifier *cls,
+                     uint32_t table, bool show_header)
 {
-    struct classifier *std_cls[3];
     struct ovs_router_entry *rt;
 
-    std_cls[0] = cls_find(CLS_LOCAL);
-    std_cls[1] = cls_find(CLS_MAIN);
-    std_cls[2] = cls_find(CLS_DEFAULT);
+    if (show_header) {
+        if (is_standard_table(table)) {
+            ds_put_format(ds, "Route Table:\n");
+        } else {
+            ds_put_format(ds, "Route Table #%"PRIu32":\n", table);
+        }
+    }
 
-    ds_put_format(ds, "Route Table:\n");
-    for (int i = 0; i < ARRAY_SIZE(std_cls); i++) {
-        if (!std_cls[i]) {
-            continue;
+    if (!cls) {
+        return;
+    }
+
+    CLS_FOR_EACH (rt, cr, cls) {
+        uint8_t plen;
+        if (rt->priority == rt->plen || rt->local) {
+            ds_put_format(ds, "Cached: ");
+        } else {
+            ds_put_format(ds, "User: ");
+        }
+        ipv6_format_mapped(&rt->nw_addr, ds);
+        plen = rt->plen;
+        if (IN6_IS_ADDR_V4MAPPED(&rt->nw_addr)) {
+            plen -= 96;
+        }
+        ds_put_format(ds, "/%"PRIu8, plen);
+        if (rt->mark) {
+            ds_put_format(ds, " MARK %"PRIu32, rt->mark);
         }
-        CLS_FOR_EACH (rt, cr, std_cls[i]) {
-            uint8_t plen;
-            if (rt->priority == rt->plen || rt->local) {
-                ds_put_format(ds, "Cached: ");
-            } else {
-                ds_put_format(ds, "User: ");
-            }
-            ipv6_format_mapped(&rt->nw_addr, ds);
-            plen = rt->plen;
-            if (IN6_IS_ADDR_V4MAPPED(&rt->nw_addr)) {
-                plen -= 96;
-            }
-            ds_put_format(ds, "/%"PRIu8, plen);
-            if (rt->mark) {
-                ds_put_format(ds, " MARK %"PRIu32, rt->mark);
-            }
 
-            ds_put_format(ds, " dev %s", rt->output_netdev);
-            if (ipv6_addr_is_set(&rt->gw)) {
-                ds_put_format(ds, " GW ");
-                ipv6_format_mapped(&rt->gw, ds);
-            }
-            ds_put_format(ds, " SRC ");
-            ipv6_format_mapped(&rt->src_addr, ds);
-            if (rt->local) {
-                ds_put_format(ds, " local");
-            }
-            ds_put_format(ds, "\n");
+        ds_put_format(ds, " dev %s", rt->output_netdev);
+        if (ipv6_addr_is_set(&rt->gw)) {
+            ds_put_format(ds, " GW ");
+            ipv6_format_mapped(&rt->gw, ds);
         }
+        ds_put_format(ds, " SRC ");
+        ipv6_format_mapped(&rt->src_addr, ds);
+        if (rt->local) {
+            ds_put_format(ds, " local");
+        }
+        if (!is_standard_table(table) && !show_header) {
+            ds_put_format(ds, " table %"PRIu32, table);
+        }
+        ds_put_format(ds, "\n");
     }
 }
 
@@ -749,15 +744,65 @@ static void
 ovs_router_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
                const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
 {
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct classifier *cls = NULL;
+    uint32_t table = CLS_MAIN;
+
+    if (argc > 1) {
+        if (!strcmp(argv[1], "table=all")) {
+            table = CLS_ALL;
+        } else if (!ovs_scan(argv[1], "table=%"SCNu32, &table)) {
+            unixctl_command_reply_error(conn, "Invalid table format");
+            return;
+        }
+    }
+
+    if (table != CLS_ALL) {
+        cls = cls_find(table);
+        if (!cls) {
+            ds_put_format(&ds, "Table '%s' not found", argv[1]);
+            unixctl_command_reply_error(conn, ds_cstr_ro(&ds));
+            ds_destroy(&ds);
+            return;
+        }
+    }
+
     if (unixctl_command_get_output_format(conn) == UNIXCTL_OUTPUT_FMT_JSON) {
-        struct json *routes;
+        struct json *routes = NULL;
+
+        routes = json_array_create_empty();
+
+        if (table == CLS_ALL) {
+            struct clsmap_node *node;
+
+            CMAP_FOR_EACH (node, cmap_node, &clsmap) {
+                ovs_router_show_json(routes, &node->cls, node->table);
+            }
+            ovs_router_show_json(routes, cls_find(CLS_MAIN), CLS_MAIN);
+        } else if (is_standard_table(table)) {
+            ovs_router_show_json(routes, cls_find(CLS_LOCAL), CLS_LOCAL);
+            ovs_router_show_json(routes, cls_find(CLS_MAIN), CLS_MAIN);
+            ovs_router_show_json(routes, cls_find(CLS_DEFAULT), CLS_DEFAULT);
+        } else {
+            ovs_router_show_json(routes, cls, table);
+        }
 
-        ovs_router_show_json(&routes);
         unixctl_command_reply_json(conn, routes);
     } else {
-        struct ds ds = DS_EMPTY_INITIALIZER;
+        if (table == CLS_ALL) {
+            struct clsmap_node *node;
 
-        ovs_router_show_text(&ds);
+            CMAP_FOR_EACH (node, cmap_node, &clsmap) {
+                ovs_router_show_text(&ds, &node->cls, node->table, false);
+            }
+        } else if (is_standard_table(table)) {
+            ovs_router_show_text(&ds, cls_find(CLS_LOCAL), CLS_LOCAL, true);
+            ovs_router_show_text(&ds, cls_find(CLS_MAIN), CLS_MAIN, false);
+            ovs_router_show_text(&ds, cls_find(CLS_DEFAULT), CLS_DEFAULT,
+                                 false);
+        } else {
+            ovs_router_show_text(&ds, cls, table, true);
+        }
         unixctl_command_reply(conn, ds_cstr(&ds));
         ds_destroy(&ds);
     }
@@ -930,7 +975,7 @@ ovs_router_init(void)
                                  "ip/plen dev [gw] "
                                  "[pkt_mark=mark] [src=src_ip]",
                                  2, 5, ovs_router_add, NULL);
-        unixctl_command_register("ovs/route/show", "", 0, 0,
+        unixctl_command_register("ovs/route/show", "[table=all|id]", 0, 1,
                                  ovs_router_show, NULL);
         unixctl_command_register("ovs/route/del", "ip/plen "
                                  "[pkt_mark=mark]", 1, 2, ovs_router_del,
diff --git a/ofproto/ofproto-tnl-unixctl.man b/ofproto/ofproto-tnl-unixctl.man
index 245d2030bdc4..657f1d077d70 100644
--- a/ofproto/ofproto-tnl-unixctl.man
+++ b/ofproto/ofproto-tnl-unixctl.man
@@ -7,9 +7,12 @@ Adds \fIip\fR/\fIplen\fR route to vswitchd routing table. 
\fIoutput_bridge\fR
 needs to be OVS bridge name.  This command is useful if OVS cached
 routes does not look right.
 .
-.IP "\fBovs/route/show\fR"
-Print all routes in OVS routing table, This includes routes cached
-from system routing table and user configured routes.
+.IP "\fBovs/route/show\fR [\fBtable\fR=\fBall\fR|\fIid\fR]"
+Print routes in OVS routing table. This includes routes cached
+from system routing table and user configured routes. By default, the contents
+of all the default tables (local, main, default) is displayed, unless requested
+otherwise with \fItable\fR parameter. In this case the contents of a specific
+table ID or of all routing tables is printed.
 .
 .IP "\fBovs/route/del\fR \fIip\fR/\fIplen\fR [\fBpkt_mark\fR=\fImark\fR]"
 Delete ip/plen route from OVS routing table.
diff --git a/tests/ovs-router.at b/tests/ovs-router.at
index b86d0a1cd37c..4e5e26688b14 100644
--- a/tests/ovs-router.at
+++ b/tests/ovs-router.at
@@ -30,6 +30,17 @@ User: 2.2.2.3/32 MARK 1 dev br0 SRC 2.2.2.2
 ])
 AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], [0], [dnl
 [[
+  {
+    "dst": "2.2.2.2",
+    "local": true,
+    "nexthops": [
+      {
+        "dev": "br0"}],
+    "prefix": 32,
+    "prefsrc": "2.2.2.2",
+    "priority": 192,
+    "table": 255,
+    "user": false},
   {
     "dst": "2.2.2.3",
     "local": false,
@@ -40,6 +51,7 @@ AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], 
[0], [dnl
     "prefix": 32,
     "prefsrc": "2.2.2.2",
     "priority": 160,
+    "table": 254,
     "user": true},
   {
     "dst": "2.2.2.0",
@@ -50,6 +62,7 @@ AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], 
[0], [dnl
     "prefix": 24,
     "prefsrc": "2.2.2.2",
     "priority": 120,
+    "table": 254,
     "user": false},
   {
     "dst": "1.1.1.0",
@@ -61,6 +74,7 @@ AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], 
[0], [dnl
     "prefix": 24,
     "prefsrc": "2.2.2.2",
     "priority": 152,
+    "table": 254,
     "user": true},
   {
     "dst": "1.1.2.0",
@@ -73,6 +87,7 @@ AT_CHECK([ovs-appctl --format=json --pretty ovs/route/show], 
[0], [dnl
     "prefix": 24,
     "prefsrc": "2.2.2.2",
     "priority": 152,
+    "table": 254,
     "user": true}]]
 ])
 OVS_VSWITCHD_STOP
-- 
2.50.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to