gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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  	"gvisor.dev/gvisor/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  	clear(n.mu.cache)
   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.insecureRNG),
   302  		linkRes: r,
   303  	}
   304  	n.mu.Lock()
   305  	n.mu.cache = make(map[tcpip.Address]*neighborEntry, NeighborCacheSize)
   306  	n.mu.Unlock()
   307  }