github.com/polevpn/netstack@v1.10.9/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/polevpn/netstack/sleep" 23 "github.com/polevpn/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 }