Skip to content

Commit 2c0af44

Browse files
author
Aritra Basu
committed
vpplink: add VPP patch to preserve interface self LL receive DPO
Nodes can lose the local IPv6 link-local receive route (dpo-receive) from the ip6-ll FIB, which breaks all link-local control traffic (DHCPv6, NDP) to the interface's own address until the link-local /128 is manually re-added. Root cause in VPP: - ip6_link installs the interface LL /128 as local (FIB_ROUTE_PATH_LOCAL) in the ip6-ll table. - ip_neighbor and teib can also update/delete LL /128 entries as non-local adjacency paths (FIB_ROUTE_PATH_FLAG_NONE). - All such operations share FIB_SOURCE_IP6_ND in the same LL FIB. - A self-LL entry (same peer IP as interface's own LL on same sw_if) can overwrite local receive semantics; later age-out/ delete operations can remove the /128 entirely. The patch detects self-link-local in ip_neighbor/teib, skips non-local LL add/remove for self entries, and restores local LL route as safeguard. Signed-off-by: Aritra Basu <aritrbas@cisco.com>
1 parent ea03352 commit 2c0af44

File tree

2 files changed

+190
-1
lines changed

2 files changed

+190
-1
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Aritra Basu <aritrbas@cisco.com>
3+
Date: Fri, 20 Feb 2026 00:05:17 -0500
4+
Subject: [PATCH] ip-neighbor: preserve interface LL receive DPO for self
5+
link-local
6+
7+
When Linux and VPP share the same physical NIC and MAC address (e.g.
8+
CalicoVPP), both derive the same fe80:: link-local address. Linux
9+
NDP traffic with that source LL is visible to VPP, which learns it
10+
as a neighbor on the same interface.
11+
12+
ip_neighbor_adj_fib_add() programs LL /128 entries under
13+
FIB_SOURCE_IP6_ND with FIB_ROUTE_PATH_FLAG_NONE (adjacency). Since
14+
ip6_link also installs the interface's own LL /128 under the same FIB
15+
source with FIB_ROUTE_PATH_LOCAL (receive DPO), the neighbor update
16+
overwrites the receive DPO with an adjacency. When the transient
17+
neighbor ages out, ip_neighbor_adj_fib_remove() deletes the entry
18+
entirely, leaving no local receive DPO.
19+
20+
This breaks all link-local control traffic (DHCPv6, NDP) to the
21+
interface's own address.
22+
23+
Fixed by detecting self-link-local entries in ip_neighbor and teib:
24+
- On add: re-install the local receive route instead of adjacency
25+
- On remove: re-install the local receive route instead of delete
26+
Non-self LL ip_neighbor and teib behavior are unchanged.
27+
28+
Type: fix
29+
30+
Change-Id: I44afcc1c67bbb5cf0ac0390783781b0de4caa30a
31+
Signed-off-by: Aritra Basu <aritrbas@cisco.com>
32+
---
33+
src/vnet/ip-neighbor/ip_neighbor.c | 48 +++++++++++++++++++++++++++++
34+
src/vnet/teib/teib.c | 49 ++++++++++++++++++++++++++++++
35+
2 files changed, 97 insertions(+)
36+
37+
diff --git a/src/vnet/ip-neighbor/ip_neighbor.c b/src/vnet/ip-neighbor/ip_neighbor.c
38+
index 4b778d5ff..0b4924ac7 100644
39+
--- a/src/vnet/ip-neighbor/ip_neighbor.c
40+
+++ b/src/vnet/ip-neighbor/ip_neighbor.c
41+
@@ -310,6 +310,33 @@ ip_af_type_pfx_len (ip_address_family_t type)
42+
return (type == AF_IP4 ? 32 : 128);
43+
}
44+
45+
+static bool
46+
+ip_neighbor_is_self_link_local (const ip_neighbor_t *ipn)
47+
+{
48+
+ const ip6_address_t *ll;
49+
+
50+
+ if (AF_IP6 != ip_neighbor_get_af (ipn))
51+
+ return (false);
52+
+
53+
+ if (!ip6_address_is_link_local_unicast (&ip_addr_v6 (&ipn->ipn_key->ipnk_ip)))
54+
+ return (false);
55+
+
56+
+ ll = ip6_get_link_local_address (ipn->ipn_key->ipnk_sw_if_index);
57+
+
58+
+ return (NULL != ll && ip6_address_is_equal (ll, &ip_addr_v6 (&ipn->ipn_key->ipnk_ip)));
59+
+}
60+
+
61+
+static void
62+
+ip_neighbor_restore_self_link_local (const ip_neighbor_t *ipn)
63+
+{
64+
+ ip6_ll_prefix_t pfx = {
65+
+ .ilp_addr = ip_addr_v6 (&ipn->ipn_key->ipnk_ip),
66+
+ .ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index,
67+
+ };
68+
+
69+
+ ip6_ll_table_entry_update (&pfx, FIB_ROUTE_PATH_LOCAL);
70+
+}
71+
+
72+
static void
73+
ip_neighbor_adj_fib_add (ip_neighbor_t * ipn, u32 fib_index)
74+
{
75+
@@ -321,6 +348,17 @@ ip_neighbor_adj_fib_add (ip_neighbor_t * ipn, u32 fib_index)
76+
ip6_address_is_link_local_unicast (&ip_addr_v6
77+
(&ipn->ipn_key->ipnk_ip)))
78+
{
79+
+ /*
80+
+ * Do not overwrite the interface's own LL receive DPO with a
81+
+ * neighbor entry that can replace the local route in the LL FIB.
82+
+ * Re-install local as a safeguard for already-corrupted state.
83+
+ */
84+
+ if (ip_neighbor_is_self_link_local (ipn))
85+
+ {
86+
+ ip_neighbor_restore_self_link_local (ipn);
87+
+ return;
88+
+ }
89+
+
90+
ip6_ll_prefix_t pfx = {
91+
.ilp_addr = ip_addr_v6 (&ipn->ipn_key->ipnk_ip),
92+
.ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index,
93+
@@ -372,6 +410,16 @@ ip_neighbor_adj_fib_remove (ip_neighbor_t * ipn, u32 fib_index)
94+
ip6_address_is_link_local_unicast (&ip_addr_v6
95+
(&ipn->ipn_key->ipnk_ip)))
96+
{
97+
+ /*
98+
+ * Re-install local receive route instead of deleting it
99+
+ * to avoid leaving the interface without a receive route.
100+
+ */
101+
+ if (ip_neighbor_is_self_link_local (ipn))
102+
+ {
103+
+ ip_neighbor_restore_self_link_local (ipn);
104+
+ return;
105+
+ }
106+
+
107+
ip6_ll_prefix_t pfx = {
108+
.ilp_addr = ip_addr_v6 (&ipn->ipn_key->ipnk_ip),
109+
.ilp_sw_if_index = ipn->ipn_key->ipnk_sw_if_index,
110+
diff --git a/src/vnet/teib/teib.c b/src/vnet/teib/teib.c
111+
index 6fa3167b8..b92590899 100644
112+
--- a/src/vnet/teib/teib.c
113+
+++ b/src/vnet/teib/teib.c
114+
@@ -8,6 +8,7 @@
115+
#include <vnet/fib/fib_table.h>
116+
#include <vnet/adj/adj_midchain.h>
117+
#include <vnet/ip/ip6_ll_table.h>
118+
+#include <vnet/ip/ip6_link.h>
119+
120+
typedef struct teib_key_t_
121+
{
122+
@@ -126,9 +127,47 @@ teib_entry_find_46 (u32 sw_if_index,
123+
return (teib_entry_find (sw_if_index, &ip));
124+
}
125+
126+
+static bool
127+
+teib_is_self_link_local (const ip_address_t *ip, u32 sw_if_index)
128+
+{
129+
+ const ip6_address_t *ll;
130+
+
131+
+ if (AF_IP6 != ip_addr_version (ip))
132+
+ return (false);
133+
+
134+
+ if (!ip6_address_is_link_local_unicast (&ip_addr_v6 (ip)))
135+
+ return (false);
136+
+
137+
+ ll = ip6_get_link_local_address (sw_if_index);
138+
+
139+
+ return (NULL != ll && ip6_address_is_equal (ll, &ip_addr_v6 (ip)));
140+
+}
141+
+
142+
+static void
143+
+teib_restore_self_link_local (const ip_address_t *ip, u32 sw_if_index)
144+
+{
145+
+ ip6_ll_prefix_t pfx = {
146+
+ .ilp_addr = ip_addr_v6 (ip),
147+
+ .ilp_sw_if_index = sw_if_index,
148+
+ };
149+
+
150+
+ ip6_ll_table_entry_update (&pfx, FIB_ROUTE_PATH_LOCAL);
151+
+}
152+
+
153+
static void
154+
teib_adj_fib_add (const ip_address_t *ip, u32 sw_if_index, u32 peer_fib_index)
155+
{
156+
+ /*
157+
+ * Do not overwrite the interface's own LL receive DPO with a
158+
+ * teib entry that can replace the local route in the LL FIB.
159+
+ * Re-install local as a safeguard for already-corrupted state.
160+
+ */
161+
+ if (teib_is_self_link_local (ip, sw_if_index))
162+
+ {
163+
+ teib_restore_self_link_local (ip, sw_if_index);
164+
+ return;
165+
+ }
166+
+
167+
if (AF_IP6 == ip_addr_version (ip) &&
168+
ip6_address_is_link_local_unicast (&ip_addr_v6 (ip)))
169+
{
170+
@@ -156,6 +195,16 @@ teib_adj_fib_add (const ip_address_t *ip, u32 sw_if_index, u32 peer_fib_index)
171+
static void
172+
teib_adj_fib_remove (ip_address_t *ip, u32 sw_if_index, u32 peer_fib_index)
173+
{
174+
+ /*
175+
+ * Re-install local receive route instead of deleting it
176+
+ * to avoid leaving the interface without a receive route.
177+
+ */
178+
+ if (teib_is_self_link_local (ip, sw_if_index))
179+
+ {
180+
+ teib_restore_self_link_local (ip, sw_if_index);
181+
+ return;
182+
+ }
183+
+
184+
if (AF_IP6 == ip_addr_version (ip) &&
185+
ip6_address_is_link_local_unicast (&ip_addr_v6 (ip)))
186+
{
187+
--
188+
2.43.0
189+

vpplink/generated/vpp_clone_current.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,4 @@ git_cherry_pick refs/changes/03/44903/1 # 44903: vxlan: reset next_dpo on delete
135135
git_apply_private 0001-pbl-Port-based-balancer.patch
136136
git_apply_private 0002-cnat-WIP-no-k8s-maglev-from-pods.patch
137137
git_apply_private 0003-acl-acl-plugin-custom-policies.patch
138-
138+
git_apply_private 0004-ip-neighbor-preserve-interface-ll-receive-dpo.patch

0 commit comments

Comments
 (0)