github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/tcpip/stack/neighbor_cache.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package stack 16 17 import ( 18 "fmt" 19 20 "github.com/nicocha30/gvisor-ligolo/pkg/tcpip" 21 ) 22 23 // NeighborCacheSize is the size of the neighborCache. Exceeding this size will 24 // result in the least recently used entry being evicted. 25 const NeighborCacheSize = 512 // max entries per interface 26 27 // NeighborStats holds metrics for the neighbor table. 28 type NeighborStats struct { 29 // UnreachableEntryLookups counts the number of lookups performed on an 30 // entry in Unreachable state. 31 UnreachableEntryLookups *tcpip.StatCounter 32 } 33 34 // neighborCache maps IP addresses to link addresses. It uses the Least 35 // Recently Used (LRU) eviction strategy to implement a bounded cache for 36 // dynamically acquired entries. It contains the state machine and configuration 37 // for running Neighbor Unreachability Detection (NUD). 38 // 39 // There are two types of entries in the neighbor cache: 40 // 1. Dynamic entries are discovered automatically by neighbor discovery 41 // protocols (e.g. ARP, NDP). These protocols will attempt to reconfirm 42 // reachability with the device once the entry's state becomes Stale. 43 // 2. Static entries are explicitly added by a user and have no expiration. 44 // Their state is always Static. The amount of static entries stored in the 45 // cache is unbounded. 46 type neighborCache struct { 47 nic *nic 48 state *NUDState 49 linkRes LinkAddressResolver 50 51 mu struct { 52 neighborCacheRWMutex 53 54 cache map[tcpip.Address]*neighborEntry 55 dynamic struct { 56 lru neighborEntryList 57 58 // count tracks the amount of dynamic entries in the cache. This is 59 // needed since static entries do not count towards the LRU cache 60 // eviction strategy. 61 count uint16 62 } 63 } 64 } 65 66 // getOrCreateEntry retrieves a cache entry associated with addr. The 67 // returned entry is always refreshed in the cache (it is reachable via the 68 // map, and its place is bumped in LRU). 69 // 70 // If a matching entry exists in the cache, it is returned. If no matching 71 // entry exists and the cache is full, an existing entry is evicted via LRU, 72 // reset to state incomplete, and returned. If no matching entry exists and the 73 // cache is not full, a new entry with state incomplete is allocated and 74 // returned. 75 func (n *neighborCache) getOrCreateEntry(remoteAddr tcpip.Address) *neighborEntry { 76 n.mu.Lock() 77 defer n.mu.Unlock() 78 79 if entry, ok := n.mu.cache[remoteAddr]; ok { 80 entry.mu.RLock() 81 if entry.mu.neigh.State != Static { 82 n.mu.dynamic.lru.Remove(entry) 83 n.mu.dynamic.lru.PushFront(entry) 84 } 85 entry.mu.RUnlock() 86 return entry 87 } 88 89 // The entry that needs to be created must be dynamic since all static 90 // entries are directly added to the cache via addStaticEntry. 91 entry := newNeighborEntry(n, remoteAddr, n.state) 92 if n.mu.dynamic.count == NeighborCacheSize { 93 e := n.mu.dynamic.lru.Back() 94 e.mu.Lock() 95 96 delete(n.mu.cache, e.mu.neigh.Addr) 97 n.mu.dynamic.lru.Remove(e) 98 n.mu.dynamic.count-- 99 100 e.removeLocked() 101 e.mu.Unlock() 102 } 103 n.mu.cache[remoteAddr] = entry 104 n.mu.dynamic.lru.PushFront(entry) 105 n.mu.dynamic.count++ 106 return entry 107 } 108 109 // entry looks up neighbor information matching the remote address, and returns 110 // it if readily available. 111 // 112 // Returns ErrWouldBlock if the link address is not readily available, along 113 // with a notification channel for the caller to block on. Triggers address 114 // resolution asynchronously. 115 // 116 // If onResolve is provided, it will be called either immediately, if resolution 117 // is not required, or when address resolution is complete, with the resolved 118 // link address and whether resolution succeeded. After any callbacks have been 119 // called, the returned notification channel is closed. 120 // 121 // NB: if a callback is provided, it should not call into the neighbor cache. 122 // 123 // If specified, the local address must be an address local to the interface the 124 // neighbor cache belongs to. The local address is the source address of a 125 // packet prompting NUD/link address resolution. 126 func (n *neighborCache) entry(remoteAddr, localAddr tcpip.Address, onResolve func(LinkResolutionResult)) (*neighborEntry, <-chan struct{}, tcpip.Error) { 127 entry := n.getOrCreateEntry(remoteAddr) 128 entry.mu.Lock() 129 defer entry.mu.Unlock() 130 131 switch s := entry.mu.neigh.State; s { 132 case Stale: 133 entry.handlePacketQueuedLocked(localAddr) 134 fallthrough 135 case Reachable, Static, Delay, Probe: 136 // As per RFC 4861 section 7.3.3: 137 // "Neighbor Unreachability Detection operates in parallel with the sending 138 // of packets to a neighbor. While reasserting a neighbor's reachability, 139 // a node continues sending packets to that neighbor using the cached 140 // link-layer address." 141 if onResolve != nil { 142 onResolve(LinkResolutionResult{LinkAddress: entry.mu.neigh.LinkAddr, Err: nil}) 143 } 144 return entry, nil, nil 145 case Unknown, Incomplete, Unreachable: 146 if onResolve != nil { 147 entry.mu.onResolve = append(entry.mu.onResolve, onResolve) 148 } 149 if entry.mu.done == nil { 150 // Address resolution needs to be initiated. 151 entry.mu.done = make(chan struct{}) 152 } 153 entry.handlePacketQueuedLocked(localAddr) 154 return entry, entry.mu.done, &tcpip.ErrWouldBlock{} 155 default: 156 panic(fmt.Sprintf("Invalid cache entry state: %s", s)) 157 } 158 } 159 160 // entries returns all entries in the neighbor cache. 161 func (n *neighborCache) entries() []NeighborEntry { 162 n.mu.RLock() 163 defer n.mu.RUnlock() 164 165 entries := make([]NeighborEntry, 0, len(n.mu.cache)) 166 for _, entry := range n.mu.cache { 167 entry.mu.RLock() 168 entries = append(entries, entry.mu.neigh) 169 entry.mu.RUnlock() 170 } 171 return entries 172 } 173 174 // addStaticEntry adds a static entry to the neighbor cache, mapping an IP 175 // address to a link address. If a dynamic entry exists in the neighbor cache 176 // with the same address, it will be replaced with this static entry. If a 177 // static entry exists with the same address but different link address, it 178 // will be updated with the new link address. If a static entry exists with the 179 // same address and link address, nothing will happen. 180 func (n *neighborCache) addStaticEntry(addr tcpip.Address, linkAddr tcpip.LinkAddress) { 181 n.mu.Lock() 182 defer n.mu.Unlock() 183 184 if entry, ok := n.mu.cache[addr]; ok { 185 entry.mu.Lock() 186 if entry.mu.neigh.State != Static { 187 // Dynamic entry found with the same address. 188 n.mu.dynamic.lru.Remove(entry) 189 n.mu.dynamic.count-- 190 } else if entry.mu.neigh.LinkAddr == linkAddr { 191 // Static entry found with the same address and link address. 192 entry.mu.Unlock() 193 return 194 } else { 195 // Static entry found with the same address but different link address. 196 entry.mu.neigh.LinkAddr = linkAddr 197 entry.dispatchChangeEventLocked() 198 entry.mu.Unlock() 199 return 200 } 201 202 entry.removeLocked() 203 entry.mu.Unlock() 204 } 205 206 entry := newStaticNeighborEntry(n, addr, linkAddr, n.state) 207 n.mu.cache[addr] = entry 208 209 entry.mu.Lock() 210 defer entry.mu.Unlock() 211 entry.dispatchAddEventLocked() 212 } 213 214 // removeEntry removes a dynamic or static entry by address from the neighbor 215 // cache. Returns true if the entry was found and deleted. 216 func (n *neighborCache) removeEntry(addr tcpip.Address) bool { 217 n.mu.Lock() 218 defer n.mu.Unlock() 219 220 entry, ok := n.mu.cache[addr] 221 if !ok { 222 return false 223 } 224 225 entry.mu.Lock() 226 defer entry.mu.Unlock() 227 228 if entry.mu.neigh.State != Static { 229 n.mu.dynamic.lru.Remove(entry) 230 n.mu.dynamic.count-- 231 } 232 233 entry.removeLocked() 234 delete(n.mu.cache, entry.mu.neigh.Addr) 235 return true 236 } 237 238 // clear removes all dynamic and static entries from the neighbor cache. 239 func (n *neighborCache) clear() { 240 n.mu.Lock() 241 defer n.mu.Unlock() 242 243 for _, entry := range n.mu.cache { 244 entry.mu.Lock() 245 entry.removeLocked() 246 entry.mu.Unlock() 247 } 248 249 n.mu.dynamic.lru = neighborEntryList{} 250 n.mu.cache = make(map[tcpip.Address]*neighborEntry) 251 n.mu.dynamic.count = 0 252 } 253 254 // config returns the NUD configuration. 255 func (n *neighborCache) config() NUDConfigurations { 256 return n.state.Config() 257 } 258 259 // setConfig changes the NUD configuration. 260 // 261 // If config contains invalid NUD configuration values, it will be fixed to 262 // use default values for the erroneous values. 263 func (n *neighborCache) setConfig(config NUDConfigurations) { 264 config.resetInvalidFields() 265 n.state.SetConfig(config) 266 } 267 268 // handleProbe handles a neighbor probe as defined by RFC 4861 section 7.2.3. 269 // 270 // Validation of the probe is expected to be handled by the caller. 271 func (n *neighborCache) handleProbe(remoteAddr tcpip.Address, remoteLinkAddr tcpip.LinkAddress) { 272 entry := n.getOrCreateEntry(remoteAddr) 273 entry.mu.Lock() 274 entry.handleProbeLocked(remoteLinkAddr) 275 entry.mu.Unlock() 276 } 277 278 // handleConfirmation handles a neighbor confirmation as defined by 279 // RFC 4861 section 7.2.5. 280 // 281 // Validation of the confirmation is expected to be handled by the caller. 282 func (n *neighborCache) handleConfirmation(addr tcpip.Address, linkAddr tcpip.LinkAddress, flags ReachabilityConfirmationFlags) { 283 n.mu.RLock() 284 entry, ok := n.mu.cache[addr] 285 n.mu.RUnlock() 286 if ok { 287 entry.mu.Lock() 288 entry.handleConfirmationLocked(linkAddr, flags) 289 entry.mu.Unlock() 290 } else { 291 // The confirmation SHOULD be silently discarded if the recipient did not 292 // initiate any communication with the target. This is indicated if there is 293 // no matching entry for the remote address. 294 n.nic.stats.neighbor.droppedConfirmationForNoninitiatedNeighbor.Increment() 295 } 296 } 297 298 func (n *neighborCache) init(nic *nic, r LinkAddressResolver) { 299 *n = neighborCache{ 300 nic: nic, 301 state: NewNUDState(nic.stack.nudConfigs, nic.stack.clock, nic.stack.randomGenerator), 302 linkRes: r, 303 } 304 n.mu.Lock() 305 n.mu.cache = make(map[tcpip.Address]*neighborEntry, NeighborCacheSize) 306 n.mu.Unlock() 307 }