FDB entries using a nexthop ID instead of a direct destination
address are not parsed. Add NDA_NH_ID support so they can be
used by the EVPN FDBs.

Assisted-by: Claude Opus 4.6, Claude Code
Signed-off-by: Ales Musil <[email protected]>
---
 controller/neighbor-exchange-netlink.c |  13 +++
 controller/neighbor-exchange-netlink.h |   1 +
 tests/ovn-macros.at                    |   6 +-
 tests/system-ovn-netlink.at            | 121 ++++++++++++++++++++-----
 tests/test-ovn-netlink.c               |  10 +-
 5 files changed, 121 insertions(+), 30 deletions(-)

diff --git a/controller/neighbor-exchange-netlink.c 
b/controller/neighbor-exchange-netlink.c
index 812c146f4..9eecf9f5b 100644
--- a/controller/neighbor-exchange-netlink.c
+++ b/controller/neighbor-exchange-netlink.c
@@ -266,12 +266,18 @@ ne_table_parse__(struct ofpbuf *buf, size_t ofs, const 
struct nlmsghdr *nlmsg,
         [NDA_DST] = { .type = NL_A_U32, .optional = true },
         [NDA_LLADDR] = { .type = NL_A_LL_ADDR, .optional = true },
         [NDA_PORT] = { .type = NL_A_U16, .optional = true },
+        /* NDA_NH_ID is only used with AF_BRIDGE messages.
+         * This entry aligns the policy array sizes. */
+        [NDA_NH_ID] = { .type = NL_A_U32, .optional = true },
     };
 
     static const struct nl_policy policy6[] = {
         [NDA_DST] = { .type = NL_A_IPV6, .optional = true },
         [NDA_LLADDR] = { .type = NL_A_LL_ADDR, .optional = true },
         [NDA_PORT] = { .type = NL_A_U16, .optional = true },
+        /* NDA_NH_ID is only used with AF_BRIDGE messages.
+         * This entry aligns the policy array sizes. */
+        [NDA_NH_ID] = { .type = NL_A_U32, .optional = true },
     };
 
     static const struct nl_policy policy_bridge[] = {
@@ -281,6 +287,7 @@ ne_table_parse__(struct ofpbuf *buf, size_t ofs, const 
struct nlmsghdr *nlmsg,
         [NDA_LLADDR] = { .type = NL_A_LL_ADDR, .optional = true },
         [NDA_PORT] = { .type = NL_A_U16, .optional = true },
         [NDA_VLAN] = { .type = NL_A_U16, .optional = true },
+        [NDA_NH_ID] = { .type = NL_A_U32, .optional = true },
     };
 
     BUILD_ASSERT(ARRAY_SIZE(policy) == ARRAY_SIZE(policy6));
@@ -345,6 +352,12 @@ ne_table_parse__(struct ofpbuf *buf, size_t ofs, const 
struct nlmsghdr *nlmsg,
         if (attrs[NDA_VLAN]) {
             change->nd.vlan = nl_attr_get_u16(attrs[NDA_VLAN]);
         }
+
+        /* NDA_NH_ID is only present in AF_BRIDGE messages in practice,
+         * but reading it unconditionally is harmless. */
+        if (attrs[NDA_NH_ID]) {
+            change->nd.nh_id = nl_attr_get_u32(attrs[NDA_NH_ID]);
+        }
     } else {
         VLOG_DBG_RL(&rl, "received unparseable rtnetlink neigh message");
         return 0;
diff --git a/controller/neighbor-exchange-netlink.h 
b/controller/neighbor-exchange-netlink.h
index 6d907938e..659fe72d8 100644
--- a/controller/neighbor-exchange-netlink.h
+++ b/controller/neighbor-exchange-netlink.h
@@ -40,6 +40,7 @@ struct ne_nl_received_neigh {
                              * from linux/neighbour.h. */
     uint8_t  type;          /* A value out of 'rtm_type' from linux/rtnetlink.h
                              * e.g., RTN_UNICAST, RTN_MULTICAST. */
+    uint32_t nh_id;         /* ID of nexthop group if present, 0 otherwise. */
 };
 
 /* A digested version of a neigh message sent down by the kernel to indicate
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index 39f03ba62..241453df9 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -1465,7 +1465,11 @@ ovn_strip_collector_set() {
 }
 
 netlink_if_index() {
-    ip -o link show dev $1 | awk -F: '{print $1}'
+    local ns_prefix=
+    if test -n "$2"; then
+        ns_prefix="ip netns exec $2"
+    fi
+    $ns_prefix ip -o link show dev $1 | awk -F: '{print $1}'
 }
 
 OVS_END_SHELL_HELPERS
diff --git a/tests/system-ovn-netlink.at b/tests/system-ovn-netlink.at
index 738800ea7..6bcad017f 100644
--- a/tests/system-ovn-netlink.at
+++ b/tests/system-ovn-netlink.at
@@ -24,9 +24,9 @@ check bridge fdb append 00:00:00:00:00:00 dev vxlan-test \
 if_index=$(netlink_if_index vxlan-test)
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     bridge $if_index 0 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:00 dst=42.42.42.3 port=0
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:00 dst=42.42.42.4 
port=4790
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:00 dst=42.42.42.3 port=0 
nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:00 dst=42.42.42.4 
port=4790 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0 nh_id=0
 ])
 AT_CLEANUP
 
@@ -52,8 +52,44 @@ check bridge fdb add 00:00:00:00:00:03 dev vxlan-test \
 if_index=$(netlink_if_index vxlan-test)
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     bridge $if_index 0 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:03 dst=42.42.42.3 port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:03 dst=42.42.42.3 port=0 
nh_id=0
+])
+
+dnl Test FDB entries with nexthop group ID (NDA_NH_ID).
+dnl Use a separate namespace to avoid nexthop ID conflicts.
+ADD_NAMESPACES(nhid)
+
+dnl Bridge setup.
+NS_EXEC([nhid], [ip link add br-nhid type bridge])
+NS_EXEC([nhid], [ip link set br-nhid address 00:00:00:00:00:01])
+NS_EXEC([nhid], [ip link set dev br-nhid up])
+
+dnl VXLAN setup.
+NS_EXEC([nhid], [ip link add vxlan-nhid type vxlan id 42 \
+    dstport 4789 local 42.42.42.2 nolearning])
+NS_EXEC([nhid], [ip link set vxlan-nhid master br-nhid])
+NS_EXEC([nhid], [ip link set vxlan-nhid address 00:00:00:00:00:02])
+NS_EXEC([nhid], [ip link set dev vxlan-nhid up])
+
+dnl Nexthop setup.
+NS_EXEC([nhid], [ip nexthop add id 1 via 192.168.1.1 fdb])
+NS_EXEC([nhid], [ip nexthop add id 10 group 1 fdb])
+
+dnl FDB entries.
+NS_EXEC([nhid], [bridge fdb add 00:00:00:00:00:03 dev vxlan-nhid \
+    dst 42.42.42.3 static extern_learn])
+NS_EXEC([nhid], [bridge fdb add 00:00:00:00:00:04 dev vxlan-nhid nhid 10])
+
+nhid_if_index=$(netlink_if_index vxlan-nhid nhid)
+
+dnl Verify that nh_id is parsed and reported.
+OVS_WAIT_FOR_OUTPUT_UNQUOTED(
+    [ip netns exec nhid ovstest test-ovn-netlink neighbor-sync \
+        bridge $nhid_if_index 0 | sort], [0], [dnl
+Neighbor ifindex=$nhid_if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0 
nh_id=0
+Neighbor ifindex=$nhid_if_index vlan=0 eth=00:00:00:00:00:03 dst=42.42.42.3 
port=0 nh_id=0
+Neighbor ifindex=$nhid_if_index vlan=0 eth=00:00:00:00:00:04 dst=:: port=0 
nh_id=10
 ])
 AT_CLEANUP
 
@@ -76,9 +112,9 @@ dnl the L2 multicast ones.
 if_index=$(netlink_if_index lo-test)
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     bridge $if_index 2 00:00:00:00:01:00 00:00:00:00:02:00 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0
-Neighbor ifindex=$if_index vlan=0 eth=01:00:5e:00:00:01 dst=:: port=0
-Neighbor ifindex=$if_index vlan=0 eth=33:33:00:00:00:01 dst=:: port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=01:00:5e:00:00:01 dst=:: port=0 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=33:33:00:00:00:01 dst=:: port=0 nh_id=0
 ])
 
 dnl Check that OVN installed its entries (these are always installed
@@ -98,9 +134,9 @@ check bridge fdb del 00:00:00:00:02:00 dev lo-test master 
static
 
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     bridge $if_index 2 00:00:00:00:01:00 00:00:00:00:02:00 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0
-Neighbor ifindex=$if_index vlan=0 eth=01:00:5e:00:00:01 dst=:: port=0
-Neighbor ifindex=$if_index vlan=0 eth=33:33:00:00:00:01 dst=:: port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=01:00:5e:00:00:01 dst=:: port=0 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=33:33:00:00:00:01 dst=:: port=0 nh_id=0
 ])
 
 OVS_WAIT_FOR_OUTPUT([bridge fdb show dev lo-test | grep static | sort], [0],
@@ -118,9 +154,9 @@ check bridge fdb add 00:00:00:00:04:00 dev lo-test master 
static
 
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     bridge $if_index 2 00:00:00:00:01:00 00:00:00:00:02:00 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0
-Neighbor ifindex=$if_index vlan=0 eth=01:00:5e:00:00:01 dst=:: port=0
-Neighbor ifindex=$if_index vlan=0 eth=33:33:00:00:00:01 dst=:: port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:00:02 dst=:: port=0 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=01:00:5e:00:00:01 dst=:: port=0 nh_id=0
+Neighbor ifindex=$if_index vlan=0 eth=33:33:00:00:00:01 dst=:: port=0 nh_id=0
 ])
 
 OVS_WAIT_FOR_OUTPUT([bridge fdb show dev lo-test | grep static | sort], [0],
@@ -160,7 +196,7 @@ dnl external ones.
 if_index=$(netlink_if_index br-test)
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     inet $if_index 1 00:00:00:00:20:00 20.20.20.20 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 port=0 
nh_id=0
 ])
 
 dnl Check that OVN installed its entries (these are always installed
@@ -172,7 +208,7 @@ dnl Let OVN inject some IPv6 neighbors too and make sure it 
learnt the
 dnl external ones.
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     inet6 $if_index 1 00:00:00:00:20:00 20::20 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10::10 port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10::10 port=0 
nh_id=0
 ])
 
 dnl Check that OVN installed its entries (these are always installed
@@ -186,11 +222,11 @@ check ip neigh del dev br-test 20.20.20.20
 check ip -6 neigh del dev br-test 20::20
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     inet $if_index 1 00:00:00:00:20:00 20.20.20.20 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 port=0 
nh_id=0
 ])
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     inet6 $if_index 1 00:00:00:00:20:00 20::20 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10::10 port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10::10 port=0 
nh_id=0
 ])
 
 OVN_NEIGH_EQUAL([br-test], [nud noarp], [20.20.20], [dnl
@@ -207,11 +243,11 @@ check ip -6 neigh add 20::40 \
 
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     inet $if_index 1 00:00:00:00:20:00 20.20.20.20 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 port=0 
nh_id=0
 ])
 OVS_WAIT_FOR_OUTPUT_UNQUOTED([ovstest test-ovn-netlink neighbor-sync \
     inet6 $if_index 1 00:00:00:00:20:00 20::20 | sort], [0], [dnl
-Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10::10 port=0
+Neighbor ifindex=$if_index vlan=0 eth=00:00:00:00:10:00 dst=10::10 port=0 
nh_id=0
 ])
 
 OVN_NEIGH_EQUAL([br-test], [nud noarp], [20.20.20], [dnl
@@ -241,13 +277,13 @@ lo_if_index=$(netlink_if_index lo-test)
 dnl Should notify if an entry is added to a bridge port monitored by OVN.
 AT_CHECK_UNQUOTED([ovstest test-ovn-netlink neighbor-table-notify \
     'bridge fdb add 00:00:00:00:00:05 dev lo-test'], [0], [dnl
-Add neighbor ifindex=$lo_if_index vlan=0 eth=00:00:00:00:00:05 dst=:: port=0
+Add neighbor ifindex=$lo_if_index vlan=0 eth=00:00:00:00:00:05 dst=:: port=0 
nh_id=0
 ])
 
 dnl Should notify if an entry is removed from a bridge port monitored by OVN.
 AT_CHECK_UNQUOTED([ovstest test-ovn-netlink neighbor-table-notify \
     'bridge fdb del 00:00:00:00:00:05 dev lo-test'], [0], [dnl
-Delete neighbor ifindex=$lo_if_index vlan=0 eth=00:00:00:00:00:05 dst=:: port=0
+Delete neighbor ifindex=$lo_if_index vlan=0 eth=00:00:00:00:00:05 dst=:: 
port=0 nh_id=0
 ])
 
 dnl Should NOT notify if a static entry is added to a bridge port
@@ -261,7 +297,7 @@ dnl OVN.
 AT_CHECK_UNQUOTED([ovstest test-ovn-netlink neighbor-table-notify \
     'ip neigh add 10.10.10.10 lladdr 00:00:00:00:10:00 \
         dev br-test extern_learn'], [0], [dnl
-Add neighbor ifindex=$br_if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 
port=0
+Add neighbor ifindex=$br_if_index vlan=0 eth=00:00:00:00:10:00 dst=10.10.10.10 
port=0 nh_id=0
 ])
 
 dnl Should notify if an entry is removed from a bridge that's monitored by
@@ -269,8 +305,8 @@ dnl OVN.
 AT_CHECK_UNQUOTED([ovstest test-ovn-netlink neighbor-table-notify \
     'ip neigh del 10.10.10.10 lladdr 00:00:00:00:10:00 \
         dev br-test' | sort], [0], [dnl
-Add neighbor ifindex=$br_if_index vlan=0 eth=00:00:00:00:00:00 dst=10.10.10.10 
port=0
-Delete neighbor ifindex=$br_if_index vlan=0 eth=00:00:00:00:00:00 
dst=10.10.10.10 port=0
+Add neighbor ifindex=$br_if_index vlan=0 eth=00:00:00:00:00:00 dst=10.10.10.10 
port=0 nh_id=0
+Delete neighbor ifindex=$br_if_index vlan=0 eth=00:00:00:00:00:00 
dst=10.10.10.10 port=0 nh_id=0
 ])
 
 dnl Should NOT notify if a noarp entry is added to a bridge port
@@ -279,6 +315,41 @@ AT_CHECK_UNQUOTED([ovstest test-ovn-netlink 
neighbor-table-notify \
     'ip neigh add 20.20.20.20 lladdr 00:00:00:00:20:00 \
         dev br-test nud noarp'], [0], [dnl
 ])
+
+dnl Test NDA_NH_ID in notifications.
+dnl Use a separate namespace to avoid nexthop ID conflicts.
+ADD_NAMESPACES(nhid)
+
+dnl Bridge setup.
+NS_EXEC([nhid], [ip link add br-nhid type bridge])
+NS_EXEC([nhid], [ip link set br-nhid address 00:00:00:00:00:01])
+NS_EXEC([nhid], [ip link set dev br-nhid up])
+
+dnl VXLAN setup.
+NS_EXEC([nhid], [ip link add vxlan-nhid type vxlan id 42 \
+    dstport 4789 local 42.42.42.2 nolearning])
+NS_EXEC([nhid], [ip link set vxlan-nhid master br-nhid])
+NS_EXEC([nhid], [ip link set dev vxlan-nhid up])
+
+dnl Nexthop setup.
+NS_EXEC([nhid], [ip nexthop add id 1 via 192.168.1.1 fdb])
+NS_EXEC([nhid], [ip nexthop add id 10 group 1 fdb])
+
+nhid_if_index=$(netlink_if_index vxlan-nhid nhid)
+
+dnl Should notify with nh_id when FDB entry with nhid is added.
+AT_CHECK_UNQUOTED(
+    [ip netns exec nhid ovstest test-ovn-netlink neighbor-table-notify \
+        'bridge fdb add 00:00:00:00:00:07 dev vxlan-nhid nhid 10'], [0], [dnl
+Add neighbor ifindex=$nhid_if_index vlan=0 eth=00:00:00:00:00:07 dst=:: port=0 
nh_id=10
+])
+
+dnl Should notify with nh_id when FDB entry with nhid is deleted.
+AT_CHECK_UNQUOTED(
+    [ip netns exec nhid ovstest test-ovn-netlink neighbor-table-notify \
+        'bridge fdb del 00:00:00:00:00:07 dev vxlan-nhid nhid 10'], [0], [dnl
+Delete neighbor ifindex=$nhid_if_index vlan=0 eth=00:00:00:00:00:07 dst=:: 
port=0 nh_id=10
+])
 AT_CLEANUP
 
 AT_SETUP([netlink - host-if-monitor])
diff --git a/tests/test-ovn-netlink.c b/tests/test-ovn-netlink.c
index 45880e3b4..bea269b5e 100644
--- a/tests/test-ovn-netlink.c
+++ b/tests/test-ovn-netlink.c
@@ -95,10 +95,11 @@ test_neighbor_sync(struct ovs_cmdl_context *ctx)
     VECTOR_FOR_EACH_PTR (&received_neighbors, ne) {
         char addr_s[INET6_ADDRSTRLEN + 1];
         printf("Neighbor ifindex=%"PRId32" vlan=%"PRIu16" "
-               "eth=" ETH_ADDR_FMT " dst=%s port=%"PRIu16"\n",
+               "eth=" ETH_ADDR_FMT " dst=%s port=%"PRIu16" "
+               "nh_id=%"PRIu32"\n",
                ne->if_index, ne->vlan, ETH_ADDR_ARGS(ne->lladdr),
                ipv6_string_mapped(addr_s, &ne->addr) ? addr_s : "(invalid)",
-               ne->port);
+               ne->port, ne->nh_id);
     }
 
 done:
@@ -142,13 +143,14 @@ test_neighbor_table_notify(struct ovs_cmdl_context *ctx)
     VECTOR_FOR_EACH_PTR (msgs, msg) {
         char addr_s[INET6_ADDRSTRLEN + 1];
         printf("%s neighbor ifindex=%"PRId32" vlan=%"PRIu16" "
-               "eth=" ETH_ADDR_FMT " dst=%s port=%"PRIu16"\n",
+               "eth=" ETH_ADDR_FMT " dst=%s port=%"PRIu16" "
+               "nh_id=%"PRIu32"\n",
                msg->nlmsg_type == RTM_NEWNEIGH ? "Add" : "Delete",
                msg->nd.if_index, msg->nd.vlan, ETH_ADDR_ARGS(msg->nd.lladdr),
                ipv6_string_mapped(addr_s, &msg->nd.addr)
                    ? addr_s
                    : "(invalid)",
-               msg->nd.port);
+               msg->nd.port, msg->nd.nh_id);
     }
 
     ovn_netlink_notifiers_destroy();
-- 
2.53.0

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

Reply via email to