inet.af/netstack@v0.0.0-20220214151720-7585b01ddccf/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  	"inet.af/netstack/sync"
    21  	"inet.af/netstack/tcpip"
    22  )
    23  
    24  const neighborCacheSize = 512 // max entries per interface
    25  
    26  // NeighborStats holds metrics for the neighbor table.
    27  type NeighborStats struct {
    28  	// UnreachableEntryLookups counts the number of lookups performed on an
    29  	// entry in Unreachable state.
    30  	UnreachableEntryLookups *tcpip.StatCounter
    31  }
    32  
    33  // neighborCache maps IP addresses to link addresses. It uses the Least
    34  // Recently Used (LRU) eviction strategy to implement a bounded cache for
    35  // dynamically acquired entries. It contains the state machine and configuration
    36  // for running Neighbor Unreachability Detection (NUD).
    37  //
    38  // There are two types of entries in the neighbor cache:
    39  //  1. Dynamic entries are discovered automatically by neighbor discovery
    40  //     protocols (e.g. ARP, NDP). These protocols will attempt to reconfirm
    41  //     reachability with the device once the entry's state becomes Stale.
    42  //  2. Static entries are explicitly added by a user and have no expiration.
    43  //     Their state is always Static. The amount of static entries stored in the
    44  //     cache is unbounded.
    45  type neighborCache struct {
    46  	nic     *nic
    47  	state   *NUDState
    48  	linkRes LinkAddressResolver
    49  
    50  	mu struct {
    51  		sync.RWMutex
    52  
    53  		cache   map[tcpip.Address]*neighborEntry
    54  		dynamic struct {
    55  			lru neighborEntryList
    56  
    57  			// count tracks the amount of dynamic entries in the cache. This is
    58  			// needed since static entries do not count towards the LRU cache
    59  			// eviction strategy.
    60  			count uint16
    61  		}
    62  	}
    63  }
    64  
    65  // getOrCreateEntry retrieves a cache entry associated with addr. The
    66  // returned entry is always refreshed in the cache (it is reachable via the
    67  // map, and its place is bumped in LRU).
    68  //
    69  // If a matching entry exists in the cache, it is returned. If no matching
    70  // entry exists and the cache is full, an existing entry is evicted via LRU,
    71  // reset to state incomplete, and returned. If no matching entry exists and the
    72  // cache is not full, a new entry with state incomplete is allocated and
    73  // returned.
    74  func (n *neighborCache) getOrCreateEntry(remoteAddr tcpip.Address) *neighborEntry {
    75  	n.mu.Lock()
    76  	defer n.mu.Unlock()
    77  
    78  	if entry, ok := n.mu.cache[remoteAddr]; ok {
    79  		entry.mu.RLock()
    80  		if entry.mu.neigh.State != Static {
    81  			n.mu.dynamic.lru.Remove(entry)
    82  			n.mu.dynamic.lru.PushFront(entry)
    83  		}
    84  		entry.mu.RUnlock()
    85  		return entry
    86  	}
    87  
    88  	// The entry that needs to be created must be dynamic since all static
    89  	// entries are directly added to the cache via addStaticEntry.
    90  	entry := newNeighborEntry(n, remoteAddr, n.state)
    91  	if n.mu.dynamic.count == neighborCacheSize {
    92  		e := n.mu.dynamic.lru.Back()
    93  		e.mu.Lock()
    94  
    95  		delete(n.mu.cache, e.mu.neigh.Addr)
    96  		n.mu.dynamic.lru.Remove(e)
    97  		n.mu.dynamic.count--
    98  
    99  		e.removeLocked()
   100  		e.mu.Unlock()
   101  	}
   102  	n.mu.cache[remoteAddr] = entry
   103  	n.mu.dynamic.lru.PushFront(entry)
   104  	n.mu.dynamic.count++
   105  	return entry
   106  }
   107  
   108  // entry looks up neighbor information matching the remote address, and returns
   109  // it if readily available.
   110  //
   111  // Returns ErrWouldBlock if the link address is not readily available, along
   112  // with a notification channel for the caller to block on. Triggers address
   113  // resolution asynchronously.
   114  //
   115  // If onResolve is provided, it will be called either immediately, if resolution
   116  // is not required, or when address resolution is complete, with the resolved
   117  // link address and whether resolution succeeded. After any callbacks have been
   118  // called, the returned notification channel is closed.
   119  //
   120  // NB: if a callback is provided, it should not call into the neighbor cache.
   121  //
   122  // If specified, the local address must be an address local to the interface the
   123  // neighbor cache belongs to. The local address is the source address of a
   124  // packet prompting NUD/link address resolution.
   125  //
   126  // TODO(gvisor.dev/issue/5151): Don't return the neighbor entry.
   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.mu.neigh, 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.mu.neigh, 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  	n.mu.cache = make(map[tcpip.Address]*neighborEntry)
   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  	}
   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  }
   296  
   297  // handleUpperLevelConfirmation processes a confirmation of reachablity from
   298  // some protocol that operates at a layer above the IP/link layer.
   299  func (n *neighborCache) handleUpperLevelConfirmation(addr tcpip.Address) {
   300  	n.mu.RLock()
   301  	entry, ok := n.mu.cache[addr]
   302  	n.mu.RUnlock()
   303  	if ok {
   304  		entry.mu.Lock()
   305  		entry.handleUpperLevelConfirmationLocked()
   306  		entry.mu.Unlock()
   307  	}
   308  }
   309  
   310  func (n *neighborCache) init(nic *nic, r LinkAddressResolver) {
   311  	*n = neighborCache{
   312  		nic:     nic,
   313  		state:   NewNUDState(nic.stack.nudConfigs, nic.stack.clock, nic.stack.randomGenerator),
   314  		linkRes: r,
   315  	}
   316  	n.mu.Lock()
   317  	n.mu.cache = make(map[tcpip.Address]*neighborEntry, neighborCacheSize)
   318  	n.mu.Unlock()
   319  }