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