github.com/google/netstack@v0.0.0-20191123085552-55fcc16cd0eb/tcpip/stack/linkaddrcache.go (about)

     1  // Copyright 2018 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  	"sync"
    20  	"time"
    21  
    22  	"github.com/google/netstack/sleep"
    23  	"github.com/google/netstack/tcpip"
    24  )
    25  
    26  const linkAddrCacheSize = 512 // max cache entries
    27  
    28  // linkAddrCache is a fixed-sized cache mapping IP addresses to link addresses.
    29  //
    30  // The entries are stored in a ring buffer, oldest entry replaced first.
    31  //
    32  // This struct is safe for concurrent use.
    33  type linkAddrCache struct {
    34  	// ageLimit is how long a cache entry is valid for.
    35  	ageLimit time.Duration
    36  
    37  	// resolutionTimeout is the amount of time to wait for a link request to
    38  	// resolve an address.
    39  	resolutionTimeout time.Duration
    40  
    41  	// resolutionAttempts is the number of times an address is attempted to be
    42  	// resolved before failing.
    43  	resolutionAttempts int
    44  
    45  	cache struct {
    46  		sync.Mutex
    47  		table map[tcpip.FullAddress]*linkAddrEntry
    48  		lru   linkAddrEntryList
    49  	}
    50  }
    51  
    52  // entryState controls the state of a single entry in the cache.
    53  type entryState int
    54  
    55  const (
    56  	// incomplete means that there is an outstanding request to resolve the
    57  	// address. This is the initial state.
    58  	incomplete entryState = iota
    59  	// ready means that the address has been resolved and can be used.
    60  	ready
    61  	// failed means that address resolution timed out and the address
    62  	// could not be resolved.
    63  	failed
    64  )
    65  
    66  // String implements Stringer.
    67  func (s entryState) String() string {
    68  	switch s {
    69  	case incomplete:
    70  		return "incomplete"
    71  	case ready:
    72  		return "ready"
    73  	case failed:
    74  		return "failed"
    75  	default:
    76  		return fmt.Sprintf("unknown(%d)", s)
    77  	}
    78  }
    79  
    80  // A linkAddrEntry is an entry in the linkAddrCache.
    81  // This struct is thread-compatible.
    82  type linkAddrEntry struct {
    83  	linkAddrEntryEntry
    84  
    85  	addr       tcpip.FullAddress
    86  	linkAddr   tcpip.LinkAddress
    87  	expiration time.Time
    88  	s          entryState
    89  
    90  	// wakers is a set of waiters for address resolution result. Anytime
    91  	// state transitions out of incomplete these waiters are notified.
    92  	wakers map[*sleep.Waker]struct{}
    93  
    94  	// done is used to allow callers to wait on address resolution. It is nil iff
    95  	// s is incomplete and resolution is not yet in progress.
    96  	done chan struct{}
    97  }
    98  
    99  // changeState sets the entry's state to ns, notifying any waiters.
   100  //
   101  // The entry's expiration is bumped up to the greater of itself and the passed
   102  // expiration; the zero value indicates immediate expiration, and is set
   103  // unconditionally - this is an implementation detail that allows for entries
   104  // to be reused.
   105  func (e *linkAddrEntry) changeState(ns entryState, expiration time.Time) {
   106  	// Notify whoever is waiting on address resolution when transitioning
   107  	// out of incomplete.
   108  	if e.s == incomplete && ns != incomplete {
   109  		for w := range e.wakers {
   110  			w.Assert()
   111  		}
   112  		e.wakers = nil
   113  		if ch := e.done; ch != nil {
   114  			close(ch)
   115  		}
   116  		e.done = nil
   117  	}
   118  
   119  	if expiration.IsZero() || expiration.After(e.expiration) {
   120  		e.expiration = expiration
   121  	}
   122  	e.s = ns
   123  }
   124  
   125  func (e *linkAddrEntry) removeWaker(w *sleep.Waker) {
   126  	delete(e.wakers, w)
   127  }
   128  
   129  // add adds a k -> v mapping to the cache.
   130  func (c *linkAddrCache) add(k tcpip.FullAddress, v tcpip.LinkAddress) {
   131  	// Calculate expiration time before acquiring the lock, since expiration is
   132  	// relative to the time when information was learned, rather than when it
   133  	// happened to be inserted into the cache.
   134  	expiration := time.Now().Add(c.ageLimit)
   135  
   136  	c.cache.Lock()
   137  	entry := c.getOrCreateEntryLocked(k)
   138  	entry.linkAddr = v
   139  
   140  	entry.changeState(ready, expiration)
   141  	c.cache.Unlock()
   142  }
   143  
   144  // getOrCreateEntryLocked retrieves a cache entry associated with k. The
   145  // returned entry is always refreshed in the cache (it is reachable via the
   146  // map, and its place is bumped in LRU).
   147  //
   148  // If a matching entry exists in the cache, it is returned. If no matching
   149  // entry exists and the cache is full, an existing entry is evicted via LRU,
   150  // reset to state incomplete, and returned. If no matching entry exists and the
   151  // cache is not full, a new entry with state incomplete is allocated and
   152  // returned.
   153  func (c *linkAddrCache) getOrCreateEntryLocked(k tcpip.FullAddress) *linkAddrEntry {
   154  	if entry, ok := c.cache.table[k]; ok {
   155  		c.cache.lru.Remove(entry)
   156  		c.cache.lru.PushFront(entry)
   157  		return entry
   158  	}
   159  	var entry *linkAddrEntry
   160  	if len(c.cache.table) == linkAddrCacheSize {
   161  		entry = c.cache.lru.Back()
   162  
   163  		delete(c.cache.table, entry.addr)
   164  		c.cache.lru.Remove(entry)
   165  
   166  		// Wake waiters and mark the soon-to-be-reused entry as expired. Note
   167  		// that the state passed doesn't matter when the zero time is passed.
   168  		entry.changeState(failed, time.Time{})
   169  	} else {
   170  		entry = new(linkAddrEntry)
   171  	}
   172  
   173  	*entry = linkAddrEntry{
   174  		addr: k,
   175  		s:    incomplete,
   176  	}
   177  	c.cache.table[k] = entry
   178  	c.cache.lru.PushFront(entry)
   179  	return entry
   180  }
   181  
   182  // get reports any known link address for k.
   183  func (c *linkAddrCache) get(k tcpip.FullAddress, linkRes LinkAddressResolver, localAddr tcpip.Address, linkEP LinkEndpoint, waker *sleep.Waker) (tcpip.LinkAddress, <-chan struct{}, *tcpip.Error) {
   184  	if linkRes != nil {
   185  		if addr, ok := linkRes.ResolveStaticAddress(k.Addr); ok {
   186  			return addr, nil, nil
   187  		}
   188  	}
   189  
   190  	c.cache.Lock()
   191  	defer c.cache.Unlock()
   192  	entry := c.getOrCreateEntryLocked(k)
   193  	switch s := entry.s; s {
   194  	case ready, failed:
   195  		if !time.Now().After(entry.expiration) {
   196  			// Not expired.
   197  			switch s {
   198  			case ready:
   199  				return entry.linkAddr, nil, nil
   200  			case failed:
   201  				return entry.linkAddr, nil, tcpip.ErrNoLinkAddress
   202  			default:
   203  				panic(fmt.Sprintf("invalid cache entry state: %s", s))
   204  			}
   205  		}
   206  
   207  		entry.changeState(incomplete, time.Time{})
   208  		fallthrough
   209  	case incomplete:
   210  		if waker != nil {
   211  			if entry.wakers == nil {
   212  				entry.wakers = make(map[*sleep.Waker]struct{})
   213  			}
   214  			entry.wakers[waker] = struct{}{}
   215  		}
   216  
   217  		if entry.done == nil {
   218  			// Address resolution needs to be initiated.
   219  			if linkRes == nil {
   220  				return entry.linkAddr, nil, tcpip.ErrNoLinkAddress
   221  			}
   222  
   223  			entry.done = make(chan struct{})
   224  			go c.startAddressResolution(k, linkRes, localAddr, linkEP, entry.done)
   225  		}
   226  
   227  		return entry.linkAddr, entry.done, tcpip.ErrWouldBlock
   228  	default:
   229  		panic(fmt.Sprintf("invalid cache entry state: %s", s))
   230  	}
   231  }
   232  
   233  // removeWaker removes a waker previously added through get().
   234  func (c *linkAddrCache) removeWaker(k tcpip.FullAddress, waker *sleep.Waker) {
   235  	c.cache.Lock()
   236  	defer c.cache.Unlock()
   237  
   238  	if entry, ok := c.cache.table[k]; ok {
   239  		entry.removeWaker(waker)
   240  	}
   241  }
   242  
   243  func (c *linkAddrCache) startAddressResolution(k tcpip.FullAddress, linkRes LinkAddressResolver, localAddr tcpip.Address, linkEP LinkEndpoint, done <-chan struct{}) {
   244  	for i := 0; ; i++ {
   245  		// Send link request, then wait for the timeout limit and check
   246  		// whether the request succeeded.
   247  		linkRes.LinkAddressRequest(k.Addr, localAddr, linkEP)
   248  
   249  		select {
   250  		case now := <-time.After(c.resolutionTimeout):
   251  			if stop := c.checkLinkRequest(now, k, i); stop {
   252  				return
   253  			}
   254  		case <-done:
   255  			return
   256  		}
   257  	}
   258  }
   259  
   260  // checkLinkRequest checks whether previous attempt to resolve address has succeeded
   261  // and mark the entry accordingly, e.g. ready, failed, etc. Return true if request
   262  // can stop, false if another request should be sent.
   263  func (c *linkAddrCache) checkLinkRequest(now time.Time, k tcpip.FullAddress, attempt int) bool {
   264  	c.cache.Lock()
   265  	defer c.cache.Unlock()
   266  	entry, ok := c.cache.table[k]
   267  	if !ok {
   268  		// Entry was evicted from the cache.
   269  		return true
   270  	}
   271  	switch s := entry.s; s {
   272  	case ready, failed:
   273  		// Entry was made ready by resolver or failed. Either way we're done.
   274  	case incomplete:
   275  		if attempt+1 < c.resolutionAttempts {
   276  			// No response yet, need to send another ARP request.
   277  			return false
   278  		}
   279  		// Max number of retries reached, mark entry as failed.
   280  		entry.changeState(failed, now.Add(c.ageLimit))
   281  	default:
   282  		panic(fmt.Sprintf("invalid cache entry state: %s", s))
   283  	}
   284  	return true
   285  }
   286  
   287  func newLinkAddrCache(ageLimit, resolutionTimeout time.Duration, resolutionAttempts int) *linkAddrCache {
   288  	c := &linkAddrCache{
   289  		ageLimit:           ageLimit,
   290  		resolutionTimeout:  resolutionTimeout,
   291  		resolutionAttempts: resolutionAttempts,
   292  	}
   293  	c.cache.table = make(map[tcpip.FullAddress]*linkAddrEntry, linkAddrCacheSize)
   294  	return c
   295  }