|
| 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 | + |
0 commit comments