@@ -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::
@@ -17,6 +17,10 @@ Post-v3.6.0
as read-only and trigger assertion failure when application attempts
to modify the database data through this transaction.
+ - 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
--------------------
@@ -627,37 +627,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();
@@ -665,6 +649,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));
@@ -690,59 +675,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");
}
}
@@ -750,15 +745,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 = 0;
+
+ 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 && 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 (!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 (!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);
}
@@ -946,7 +991,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,
@@ -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.
@@ -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
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 <dchumak@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, 137 insertions(+), 70 deletions(-)