A route can describe its next hop with a separate nexthop object (see
'ip nexthop') that it refers to through a nexthop id carried in the
RTA_NH_ID attribute, instead of encoding the next hop inline.  Routing
daemons commonly install routes this way, as it lets many routes share
one next hop and be redirected at once.

Parse RTA_NH_ID and report it in 'struct route_data'.  The kernel reports
the next hop inline as well, so such a route is parsed as any other and
the id is extra information, which users of the library that keep track
of the kernel nexthop table themselves can follow.

Nothing in the tree resolves the id.  ovs-router in particular relies on
the inline next hop, which the kernel leaves out when the
net.ipv4.nexthop_compat_mode sysctl is turned off, so OVS still needs
that sysctl at its default.  A route that then carries none of RTA_OIF,
RTA_GATEWAY, RTA_VIA and RTA_MULTIPATH is no longer dropped as
unparseable though: it is reported with an empty nexthop list next to a
non-zero id, which tells a user of the library that it has to resolve the
id itself.

Assisted-by: Claude Opus 5, Cursor
Signed-off-by: Han Zhou <[email protected]>
---
v2: Addressed Ilya's comments.
v3: Addressed more comments from Ilya. Also rebased.

 lib/route-table.c            | 19 ++++++++++---
 lib/route-table.h            |  3 +++
 tests/system-route.at        | 52 ++++++++++++++++++++++++++++++++++++
 tests/test-lib-route-table.c |  5 ++--
 4 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/lib/route-table.c b/lib/route-table.c
index 2a13a5cc7d93..ac6f8e74e50f 100644
--- a/lib/route-table.c
+++ b/lib/route-table.c
@@ -45,6 +45,7 @@
  * in case we're building with old headers. (We can't test for it with #ifdef
  * because it's an enum.) */
 #define RTA_MARK 16 /* Linux 2.6.36 */
+#define RTA_NH_ID 30 /* Linux 5.3 */
 #define FRA_SUPPRESS_PREFIXLEN 14 /* Linux 3.12 */
 #define FRA_TABLE 15 /* Linux 2.6.19 */
 #define FRA_PROTOCOL 21 /* Linux 4.17 */
@@ -468,6 +469,7 @@ route_table_parse__(struct ofpbuf *buf, size_t ofs,
         [RTA_PRIORITY] = { .type = NL_A_U32, .optional = true },
         [RTA_VIA] = { .type = NL_A_RTA_VIA, .optional = true },
         [RTA_MULTIPATH] = { .type = NL_A_NESTED, .optional = true },
+        [RTA_NH_ID] = { .type = NL_A_U32, .optional = true },
     };
 
     static const struct nl_policy policy6[] = {
@@ -480,6 +482,7 @@ route_table_parse__(struct ofpbuf *buf, size_t ofs,
         [RTA_PRIORITY] = { .type = NL_A_U32, .optional = true },
         [RTA_VIA] = { .type = NL_A_RTA_VIA, .optional = true },
         [RTA_MULTIPATH] = { .type = NL_A_NESTED, .optional = true },
+        [RTA_NH_ID] = { .type = NL_A_U32, .optional = true },
     };
 
     struct nlattr *attrs[ARRAY_SIZE(policy)];
@@ -585,6 +588,9 @@ route_table_parse__(struct ofpbuf *buf, size_t ofs,
         if (attrs[RTA_PRIORITY]) {
             change->rd.rta_priority = nl_attr_get_u32(attrs[RTA_PRIORITY]);
         }
+        if (attrs[RTA_NH_ID]) {
+            change->rd.rta_nhid = nl_attr_get_u32(attrs[RTA_NH_ID]);
+        }
         if (attrs[RTA_VIA]) {
             const struct rtvia *rtvia = nl_attr_get(attrs[RTA_VIA]);
             ovs_be32 addr;
@@ -673,9 +679,16 @@ route_table_parse__(struct ofpbuf *buf, size_t ofs,
         if (route_type_needs_nexthop(rtm->rtm_type)
             && !attrs[RTA_OIF] && !attrs[RTA_GATEWAY]
             && !attrs[RTA_VIA] && !attrs[RTA_MULTIPATH]) {
-            VLOG_DBG_RL(&rl, "route message needs an RTA_OIF, RTA_GATEWAY, "
-                             "RTA_VIA or RTA_MULTIPATH attribute");
-            goto error_out;
+            if (!attrs[RTA_NH_ID]) {
+                VLOG_DBG_RL(&rl, "route message needs an RTA_OIF, "
+                                 "RTA_GATEWAY, RTA_VIA, RTA_MULTIPATH or "
+                                 "RTA_NH_ID attribute");
+                goto error_out;
+            }
+
+            /* The next hop is in the nexthop object named by RTA_NH_ID, so
+             * the entry added at the start of parsing was never filled in. */
+            route_data_destroy_nexthops__(&change->rd);
         }
         /* Add any additional RTA attribute processing before RTA_MULTIPATH. */
 
diff --git a/lib/route-table.h b/lib/route-table.h
index b49fbb14ebe0..45a9a012480a 100644
--- a/lib/route-table.h
+++ b/lib/route-table.h
@@ -141,6 +141,9 @@ struct route_data {
     uint32_t rta_mark;           /* 0 if missing. */
     uint32_t rta_table_id;       /* 0 if missing. */
     uint32_t rta_priority;       /* 0 if missing. */
+    uint32_t rta_nhid;           /* Nexthop object id, 0 if missing.  Resolved
+                                    by the user; 'nexthops' is empty unless the
+                                    kernel also reported it inline. */
 };
 
 struct rule_data {
diff --git a/tests/system-route.at b/tests/system-route.at
index a074c51f9fd0..60cc2888e769 100644
--- a/tests/system-route.at
+++ b/tests/system-route.at
@@ -333,6 +333,58 @@ AT_CHECK([ovstest test-lib-route-table-dump | \
 
 AT_CLEANUP
 
+AT_SETUP([route-table - route with nexthop id])
+AT_KEYWORDS([route])
+
+dnl Skip if the running kernel / iproute2 does not support nexthop objects.
+AT_SKIP_IF([! ip nexthop show >/dev/null 2>&1])
+
+dnl Create a veth pair with both ends up.  A nexthop object can only reference
+dnl a device that has carrier.
+on_exit 'ip link del p1-route'
+AT_CHECK([ip link add p1-route type veth peer name ovs-p1-route])
+AT_CHECK([ip link set p1-route up])
+AT_CHECK([ip link set ovs-p1-route up])
+
+dnl Add ip addresses.
+AT_CHECK([ip addr add 10.0.0.17/24 dev p1-route], [0], [stdout])
+AT_CHECK([ip -6 addr add fc00:db8:cafe::17/64 dev p1-route], [0], [stdout])
+
+dnl Create nexthop objects and a nexthop group referencing two of them.
+on_exit 'ip nexthop del id 4200'
+AT_CHECK([ip nexthop add id 4200 via 10.0.0.18 dev p1-route])
+on_exit 'ip nexthop del id 4201'
+AT_CHECK([ip nexthop add id 4201 via 10.0.0.19 dev p1-route])
+on_exit 'ip nexthop del id 4210'
+AT_CHECK([ip nexthop add id 4210 group 4200/4201])
+on_exit 'ip nexthop del id 4202'
+AT_CHECK([ip nexthop add id 4202 via fc00:db8:cafe::18 dev p1-route])
+
+dnl Add routes that reference their next hop through a nexthop id.
+on_exit 'ip route flush table 42'
+AT_CHECK([ip route add 192.168.10.12/32 nhid 4200 table 42])
+AT_CHECK([ip route add 192.168.10.13/32 nhid 4210 table 42])
+dnl Until duplicate address detection has finished iproute2 would give us an
+dnl "Invalid source address" error, so wait a while to succeed.
+OVS_WAIT_UNTIL([ip -6 route add fc00:db8:beef::12/128 nhid 4202 table 42])
+
+dnl The nexthop id must be reported, for a single nexthop object as well as
+dnl for a group of them.
+AT_CHECK([ovstest test-lib-route-table-dump | \
+          awk '/^192.168.10.12/{print$1" "$2" "$3" "$19" "$20}'], [0], [dnl
+192.168.10.12/32 relevant: 1 rta_nhid: 4200
+])
+AT_CHECK([ovstest test-lib-route-table-dump | \
+          awk '/^192.168.10.13/{print$1" "$2" "$3" "$19" "$20}'], [0], [dnl
+192.168.10.13/32 relevant: 1 rta_nhid: 4210
+])
+AT_CHECK([ovstest test-lib-route-table-dump | \
+          awk '/^fc00:db8:beef::12/{print$1" "$2" "$3" "$19" "$20}'], [0], [dnl
+fc00:db8:beef::12/128 relevant: 1 rta_nhid: 4202
+])
+
+AT_CLEANUP
+
 dnl Checks that OVS ignores unsupported routing rules.
 AT_SETUP([ovs-route - unsupported rules])
 AT_KEYWORDS([route])
diff --git a/tests/test-lib-route-table.c b/tests/test-lib-route-table.c
index f99f056c8ddc..d6fe20cb180e 100644
--- a/tests/test-lib-route-table.c
+++ b/tests/test-lib-route-table.c
@@ -86,11 +86,12 @@ test_lib_route_table_handle_msg(const struct 
route_table_msg *change,
 
     printf("%s/%u relevant: %d nlmsg_type: %d rtm_protocol: %s (%u) "
            "rtn_local: %d rta_prefsrc: %s rta_mark: %"PRIu32" "
-           "rta_table_id: %s rta_priority: %"PRIu32"\n",
+           "rta_table_id: %s rta_priority: %"PRIu32" rta_nhid: %"PRIu32"\n",
            ds_cstr(&rta_dst), rd->rtm_dst_len, change->relevant,
            change->nlmsg_type, rt_prot_name(rd->rtm_protocol),
            rd->rtm_protocol, rd->rtn_local, ds_cstr(&rta_prefsrc),
-           rd->rta_mark, rt_table_name(rd->rta_table_id), rd->rta_priority);
+           rd->rta_mark, rt_table_name(rd->rta_table_id), rd->rta_priority,
+           rd->rta_nhid);
 
     LIST_FOR_EACH (rdnh, nexthop_node, &rd->nexthops) {
         ds_clear(&nexthop_addr);
-- 
2.38.1

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

Reply via email to