On 10/27/25 1:42 PM, Dima Chumak via dev wrote:
> Introduce an optional parameter to `ovs-appctl ovs/route/show` for
> adding/deleting user routes in non-default routing tables.
>
> A table with ID doesn't have to exist before adding a route, OVS will
> create a new table if it is missing and will add a user route there.
> Though, to effect route lookup such table has to be referenced by a
> routing rule.
>
> Signed-off-by: Dima Chumak <[email protected]>
> ---
> Documentation/howto/userspace-tunneling.rst | 5 +-
> NEWS | 2 +
> lib/ovs-router.c | 62 ++++++++++++++++-----
> ofproto/ofproto-tnl-unixctl.man | 14 +++--
> 4 files changed, 64 insertions(+), 19 deletions(-)
>
> diff --git a/Documentation/howto/userspace-tunneling.rst
> b/Documentation/howto/userspace-tunneling.rst
> index c7c6464520a6..8116e81e5a7d 100644
> --- a/Documentation/howto/userspace-tunneling.rst
> +++ b/Documentation/howto/userspace-tunneling.rst
> @@ -201,7 +201,8 @@ Tunnel routing table
>
> To add route::
>
> - $ ovs-appctl ovs/route/add <IP address>/<prefix length>
> <output-bridge-name> <gw>
> + $ ovs-appctl ovs/route/add <IP address>/<prefix length>
> + <output-bridge-name> <gw> [table=ID]
>
> To see all routes configured::
>
> @@ -213,7 +214,7 @@ To see all router rules configured::
>
> To delete route::
>
> - $ ovs-appctl ovs/route/del <IP address>/<prefix length>
> + $ ovs-appctl ovs/route/del <IP address>/<prefix length> [table=ID]
>
> To look up and display the route for a destination::
>
> diff --git a/NEWS b/NEWS
> index 8ce1a08da6b7..d183d6caad5a 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -21,6 +21,8 @@ Post-v3.6.0
> * 'ovs/route/show': added new option, table=[ID|all], to list routes
> from
> a specific OVS table or all routes from all tables.
> * Added a new sub-command, ovs/route/rule/show, to list OVS router
> rules.
> + * 'ovs/route/add' and 'ovs/route/del': added new option, table=ID, to
> + add/delete a route from a specific OVS table.
nit: 'OVS table' may be a little ambiguous as there are OpenFlow tables
and maybe some other tables. It's clear that we're talking about routing
tables, so it may be better to omit the 'OVS' here.
>
>
> v3.6.0 - 18 Aug 2025
> diff --git a/lib/ovs-router.c b/lib/ovs-router.c
> index d67a098ba48f..a6486c48b7e2 100644
> --- a/lib/ovs-router.c
> +++ b/lib/ovs-router.c
> @@ -461,7 +461,7 @@ rt_entry_delete__(const struct cls_rule *cr, struct
> classifier *cls)
> }
>
> static bool
> -rt_entry_delete(uint32_t mark, uint8_t priority,
> +rt_entry_delete(struct classifier *cls, uint32_t mark, uint8_t priority,
> const struct in6_addr *ip6_dst, uint8_t plen)
> {
> struct classifier *cls_main = cls_find(CLS_MAIN);
> @@ -479,10 +479,10 @@ rt_entry_delete(uint32_t mark, uint8_t priority,
> cls_rule_init(&rule, &match, priority);
>
> /* Find the exact rule. */
> - cr = classifier_find_rule_exactly(cls_main, &rule, OVS_VERSION_MAX);
> + cr = classifier_find_rule_exactly(cls, &rule, OVS_VERSION_MAX);
> if (cr) {
> ovs_mutex_lock(&mutex);
> - rt_entry_delete__(cr, cls_main);
> + rt_entry_delete__(cr, cls);
> ovs_mutex_unlock(&mutex);
>
> res = true;
> @@ -521,6 +521,7 @@ ovs_router_add(struct unixctl_conn *conn, int argc,
> struct in6_addr src6 = in6addr_any;
> struct in6_addr gw6 = in6addr_any;
> char src6_s[IPV6_SCAN_LEN + 1];
> + uint32_t table = CLS_MAIN;
> struct in6_addr ip6;
> uint32_t mark = 0;
> unsigned int plen;
> @@ -545,7 +546,7 @@ ovs_router_add(struct unixctl_conn *conn, int argc,
>
> /* Parse optional parameters. */
> for (i = 3; i < argc; i++) {
> - if (ovs_scan(argv[i], "pkt_mark=%"SCNi32, &mark)) {
> + if (ovs_scan(argv[i], "pkt_mark=%"SCNu32, &mark)) {
> continue;
> }
>
> @@ -566,6 +567,13 @@ ovs_router_add(struct unixctl_conn *conn, int argc,
> }
> }
>
> + if (ovs_scan(argv[i], "table=%"SCNu32, &table)) {
> + continue;
> + } else if (ovs_scan(argv[i], "table=")) {
> + unixctl_command_reply_error(conn, "Invalid table format");
> + return;
> + }
> +
> unixctl_command_reply_error(conn,
> "Invalid pkt_mark, IP gateway or
> src_ip");
> return;
> @@ -578,7 +586,7 @@ ovs_router_add(struct unixctl_conn *conn, int argc,
> in6_addr_set_mapped_ipv4(&src6, src);
> }
>
> - err = ovs_router_insert__(CLS_MAIN, mark, plen + 32, true, &ip6, plen,
> + err = ovs_router_insert__(table, mark, plen + 32, true, &ip6, plen,
> argv[2], &gw6, &src6);
> if (err) {
> unixctl_command_reply_error(conn, "Error while inserting route.");
> @@ -591,10 +599,13 @@ static void
> ovs_router_del(struct unixctl_conn *conn, int argc OVS_UNUSED,
> const char *argv[], void *aux OVS_UNUSED)
> {
> + struct classifier *cls = cls_find(CLS_MAIN);
> struct in6_addr ip6;
> uint32_t mark = 0;
> unsigned int plen;
> + uint32_t table;
> ovs_be32 ip;
> + int i;
>
> if (scan_ipv4_route(argv[1], &ip, &plen)) {
> in6_addr_set_mapped_ipv4(&ip6, ip);
> @@ -603,14 +614,39 @@ ovs_router_del(struct unixctl_conn *conn, int argc
> OVS_UNUSED,
> unixctl_command_reply_error(conn, "Invalid parameters");
> return;
> }
> - if (argc > 2) {
> - if (!ovs_scan(argv[2], "pkt_mark=%"SCNi32, &mark)) {
> - unixctl_command_reply_error(conn, "Invalid pkt_mark");
> +
> + /* Parse optional parameters. */
> + for (i = 2; i < argc; i++) {
> + if (ovs_scan(argv[i], "pkt_mark=%"SCNu32, &mark)) {
> + continue;
> + }
> +
> + if (ovs_scan(argv[i], "table=%"SCNu32, &table)) {
> + cls = cls_find(table);
> + if (!cls) {
> + struct ds ds = DS_EMPTY_INITIALIZER;
> +
> + ds_put_format(&ds, "Table %s not found", argv[i]);
> + unixctl_command_reply_error(conn, ds_cstr_ro(&ds));
> + ds_destroy(&ds);
> + return;
> + }
> + continue;
> + } else if (ovs_scan(argv[i], "table=")) {
> + unixctl_command_reply_error(conn, "Invalid table format");
> return;
> }
> +
> + unixctl_command_reply_error(conn, "Invalid pkt_mark or table");
> + return;
> + }
> +
> + if (!cls) {
> + unixctl_command_reply_error(conn, "Table not found");
> + return;
> }
>
> - if (rt_entry_delete(mark, plen + 32, &ip6, plen)) {
> + if (rt_entry_delete(cls, mark, plen + 32, &ip6, plen)) {
> unixctl_command_reply(conn, "OK");
> seq_change(tnl_conf_seq);
> } else {
> @@ -1074,13 +1110,13 @@ ovs_router_init(void)
> fatal_signal_add_hook(ovs_router_flush_handler, NULL, NULL, true);
> unixctl_command_register("ovs/route/add",
> "ip/plen dev [gw] "
> - "[pkt_mark=mark] [src=src_ip]",
> - 2, 5, ovs_router_add, NULL);
> + "[pkt_mark=mark] [src=src_ip] [table=id]",
> + 2, 6, ovs_router_add, NULL);
> 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,
> - NULL);
> + "[pkt_mark=mark] [table=id]", 1, 3,
> + ovs_router_del, NULL);
> unixctl_command_register("ovs/route/lookup", "ip_addr "
> "[pkt_mark=mark]", 1, 2,
> ovs_router_lookup_cmd, NULL);
> diff --git a/ofproto/ofproto-tnl-unixctl.man b/ofproto/ofproto-tnl-unixctl.man
> index b62063dcdae0..9cab99e6ffa7 100644
> --- a/ofproto/ofproto-tnl-unixctl.man
> +++ b/ofproto/ofproto-tnl-unixctl.man
> @@ -2,10 +2,13 @@
> These commands query and modify OVS tunnel components.
> .
> .IP "\fBovs/route/add\fR \fIip\fR/\fIplen\fR \fIoutput_bridge\fR \
> -[\fIgw\fR] [\fBpkt_mark\fR=\fImark\fR] [\fBsrc\fR=\fIsrc_ip\fR]"
> +[\fIgw\fR] [\fBpkt_mark\fR=\fImark\fR] [\fBsrc\fR=\fIsrc_ip\fR] \
> +[\fBtable\fR=\fIid\fR]"
> 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.
> +routes does not look right. A non-standard table ID can be specified, in
> which
> +case the route is added to that routing table. The table is created if it
> +doesn't exist.
> .
> .IP "\fBovs/route/show\fR [\fBtable\fR=\fBall\fR|\fIid\fR]"
> Print routes in OVS routing table. This includes routes cached from
> @@ -14,8 +17,11 @@ 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.
> +.IP "\fBovs/route/del\fR \fIip\fR/\fIplen\fR [\fBpkt_mark\fR=\fImark\fR] \
> +[\fBtable\fR=\fIid\fR]"
> +Delete ip/plen route from OVS routing table. The standard routing table is
> used
> +by default, or a specific custom table when ID is provided via \fItable\fR
> +parameter.
> .
> .IP "\fBovs/route/rule/show\fR"
> Print routing rules in OVS. This includes routing rules cached from the
> system
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev