Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aritra Basu <aritrbas@cisco.com>
Date: Fri, 20 Feb 2026 00:05:17 -0500
Subject: [PATCH] ip-neighbor: preserve interface LL receive DPO for self
link-local

When Linux and VPP share the same physical NIC and MAC address (e.g.
CalicoVPP), both derive the same fe80:: link-local address. Linux
NDP traffic with that source LL is visible to VPP, which learns it
as a neighbor on the same interface.

ip_neighbor_adj_fib_add() programs LL /128 entries under
FIB_SOURCE_IP6_ND with FIB_ROUTE_PATH_FLAG_NONE (adjacency). Since
ip6_link also installs the interface's own LL /128 under the same FIB
source with FIB_ROUTE_PATH_LOCAL (receive DPO), the neighbor update
overwrites the receive DPO with an adjacency. When the transient
neighbor ages out, ip_neighbor_adj_fib_remove() deletes the entry
entirely, leaving no local receive DPO.

This breaks all link-local control traffic (DHCPv6, NDP) to the
interface's own address.

Fixed by detecting self-link-local entries in ip_neighbor and teib:
- On add: re-install the local receive route instead of adjacency
- On remove: re-install the local receive route instead of delete
Non-self LL ip_neighbor and teib behavior are unchanged.

Type: fix

Change-Id: I44afcc1c67bbb5cf0ac0390783781b0de4caa30a
Signed-off-by: Aritra Basu <aritrbas@cisco.com>
---
src/vnet/ip-neighbor/ip_neighbor.c | 48 +++++++++++++++++++++++++++++
src/vnet/teib/teib.c | 49 ++++++++++++++++++++++++++++++
2 files changed, 97 insertions(+)

diff --git a/src/vnet/ip-neighbor/ip_neighbor.c b/src/vnet/ip-neighbor/ip_neighbor.c
index 4b778d5ff..0b4924ac7 100644
--- a/src/vnet/ip-neighbor/ip_neighbor.c
+++ b/src/vnet/ip-neighbor/ip_neighbor.c
@@ -310,6 +310,33 @@ ip_af_type_pfx_len (ip_address_family_t type)
return (type == AF_IP4 ? 32 : 128);
}

+static bool
+ip_neighbor_is_self_link_local (const ip_neighbor_t *ipn)
+{
+ const ip6_address_t *ll;
+
+ if (AF_IP6 != ip_neighbor_get_af (ipn))
+ return (false);
+
+ if (!ip6_address_is_link_local_unicast (&ip_addr_v6 (&ipn->ipn_key->ipnk_ip)))
+ return (false);
+
+ ll = ip6_get_link_local_address (ipn->ipn_key->ipnk_sw_if_index);
+
+ return (NULL != ll && ip6_address_is_equal (ll, &ip_addr_v6 (&ipn->ipn_key->ipnk_ip)));
+}
+
+static void
+ip_neighbor_restore_self_link_local (const ip_neighbor_t *ipn)
+{
+ ip6_ll_prefix_t pfx = {
+ .ilp_addr = ip_addr_v6 (&ipn->ipn_key->ipnk_ip),
+ .ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index,
+ };
+
+ ip6_ll_table_entry_update (&pfx, FIB_ROUTE_PATH_LOCAL);
+}
+
static void
ip_neighbor_adj_fib_add (ip_neighbor_t * ipn, u32 fib_index)
{
@@ -321,6 +348,17 @@ ip_neighbor_adj_fib_add (ip_neighbor_t * ipn, u32 fib_index)
ip6_address_is_link_local_unicast (&ip_addr_v6
(&ipn->ipn_key->ipnk_ip)))
{
+ /*
+ * Do not overwrite the interface's own LL receive DPO with a
+ * neighbor entry that can replace the local route in the LL FIB.
+ * Re-install local as a safeguard for already-corrupted state.
+ */
+ if (ip_neighbor_is_self_link_local (ipn))
+ {
+ ip_neighbor_restore_self_link_local (ipn);
+ return;
+ }
+
ip6_ll_prefix_t pfx = {
.ilp_addr = ip_addr_v6 (&ipn->ipn_key->ipnk_ip),
.ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index,
@@ -372,6 +410,16 @@ ip_neighbor_adj_fib_remove (ip_neighbor_t * ipn, u32 fib_index)
ip6_address_is_link_local_unicast (&ip_addr_v6
(&ipn->ipn_key->ipnk_ip)))
{
+ /*
+ * Re-install local receive route instead of deleting it
+ * to avoid leaving the interface without a receive route.
+ */
+ if (ip_neighbor_is_self_link_local (ipn))
+ {
+ ip_neighbor_restore_self_link_local (ipn);
+ return;
+ }
+
ip6_ll_prefix_t pfx = {
.ilp_addr = ip_addr_v6 (&ipn->ipn_key->ipnk_ip),
.ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index,
diff --git a/src/vnet/teib/teib.c b/src/vnet/teib/teib.c
index 6fa3167b8..b92590899 100644
--- a/src/vnet/teib/teib.c
+++ b/src/vnet/teib/teib.c
@@ -8,6 +8,7 @@
#include <vnet/fib/fib_table.h>
#include <vnet/adj/adj_midchain.h>
#include <vnet/ip/ip6_ll_table.h>
+#include <vnet/ip/ip6_link.h>

typedef struct teib_key_t_
{
@@ -126,9 +127,47 @@ teib_entry_find_46 (u32 sw_if_index,
return (teib_entry_find (sw_if_index, &ip));
}

+static bool
+teib_is_self_link_local (const ip_address_t *ip, u32 sw_if_index)
+{
+ const ip6_address_t *ll;
+
+ if (AF_IP6 != ip_addr_version (ip))
+ return (false);
+
+ if (!ip6_address_is_link_local_unicast (&ip_addr_v6 (ip)))
+ return (false);
+
+ ll = ip6_get_link_local_address (sw_if_index);
+
+ return (NULL != ll && ip6_address_is_equal (ll, &ip_addr_v6 (ip)));
+}
+
+static void
+teib_restore_self_link_local (const ip_address_t *ip, u32 sw_if_index)
+{
+ ip6_ll_prefix_t pfx = {
+ .ilp_addr = ip_addr_v6 (ip),
+ .ilp_sw_if_index = sw_if_index,
+ };
+
+ ip6_ll_table_entry_update (&pfx, FIB_ROUTE_PATH_LOCAL);
+}
+
static void
teib_adj_fib_add (const ip_address_t *ip, u32 sw_if_index, u32 peer_fib_index)
{
+ /*
+ * Do not overwrite the interface's own LL receive DPO with a
+ * teib entry that can replace the local route in the LL FIB.
+ * Re-install local as a safeguard for already-corrupted state.
+ */
+ if (teib_is_self_link_local (ip, sw_if_index))
+ {
+ teib_restore_self_link_local (ip, sw_if_index);
+ return;
+ }
+
if (AF_IP6 == ip_addr_version (ip) &&
ip6_address_is_link_local_unicast (&ip_addr_v6 (ip)))
{
@@ -156,6 +195,16 @@ teib_adj_fib_add (const ip_address_t *ip, u32 sw_if_index, u32 peer_fib_index)
static void
teib_adj_fib_remove (ip_address_t *ip, u32 sw_if_index, u32 peer_fib_index)
{
+ /*
+ * Re-install local receive route instead of deleting it
+ * to avoid leaving the interface without a receive route.
+ */
+ if (teib_is_self_link_local (ip, sw_if_index))
+ {
+ teib_restore_self_link_local (ip, sw_if_index);
+ return;
+ }
+
if (AF_IP6 == ip_addr_version (ip) &&
ip6_address_is_link_local_unicast (&ip_addr_v6 (ip)))
{
--
2.43.0

2 changes: 1 addition & 1 deletion vpplink/generated/vpp_clone_current.sh
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,4 @@ git_cherry_pick refs/changes/03/44903/1 # 44903: vxlan: reset next_dpo on delete
git_apply_private 0001-pbl-Port-based-balancer.patch
git_apply_private 0002-cnat-WIP-no-k8s-maglev-from-pods.patch
git_apply_private 0003-acl-acl-plugin-custom-policies.patch

git_apply_private 0004-ip-neighbor-preserve-interface-ll-receive-dpo.patch
Loading