On 8/20/25 4:34 PM, Dima Chumak via dev wrote:
> 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 <[email protected]>
> ---
>  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
>  --------------------
> -

Keep the line.  We normally have 2 lines between major versions.

> +   - 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));

Side note: I wonder why we're dumping priority here.  It's an internal number
that doesn't make much sense for the user.  It is also confusing as it is not
the priority that kernel reports.  We should remove it, IMO, but it's a separate
change.

> @@ -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)) {

This looks strange.  Why are we dumping all standard tables if we're asked
to dump a specific one?  We should only dump all if no specific table was
requested.

> +            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)) {

Ditto.

> +            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

This line is strangely short in comparison to other ones.

> +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

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

Reply via email to