When we get a forwarded packet, we put its source MAC into the MAC cache, overriding any existing entry. But we could have a flow which says to forward packets destined to that MAC based on the old entry. Such a flow could be prolonged indefinitely by attempts to send to the MAC. This is obviously bad, so do InvalidateRoutes in such cases.
115 lines
2.3 KiB
Go
115 lines
2.3 KiB
Go
package router
|
|
|
|
import (
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type MacCacheEntry struct {
|
|
lastSeen time.Time
|
|
peer *Peer
|
|
}
|
|
|
|
type MacCache struct {
|
|
sync.RWMutex
|
|
table map[uint64]*MacCacheEntry
|
|
maxAge time.Duration
|
|
expiryTimer *time.Timer
|
|
onExpiry func(net.HardwareAddr, *Peer)
|
|
}
|
|
|
|
func NewMacCache(maxAge time.Duration, onExpiry func(net.HardwareAddr, *Peer)) *MacCache {
|
|
cache := &MacCache{
|
|
table: make(map[uint64]*MacCacheEntry),
|
|
maxAge: maxAge,
|
|
onExpiry: onExpiry}
|
|
cache.setExpiryTimer()
|
|
return cache
|
|
}
|
|
|
|
func (cache *MacCache) add(mac net.HardwareAddr, peer *Peer, force bool) (bool, *Peer) {
|
|
key := macint(mac)
|
|
now := time.Now()
|
|
|
|
cache.RLock()
|
|
entry, found := cache.table[key]
|
|
if found && entry.peer == peer && now.Before(entry.lastSeen.Add(cache.maxAge/10)) {
|
|
cache.RUnlock()
|
|
return false, nil
|
|
}
|
|
cache.RUnlock()
|
|
|
|
cache.Lock()
|
|
defer cache.Unlock()
|
|
|
|
entry, found = cache.table[key]
|
|
if !found {
|
|
cache.table[key] = &MacCacheEntry{lastSeen: now, peer: peer}
|
|
return true, nil
|
|
}
|
|
|
|
if entry.peer != peer {
|
|
if !force {
|
|
return false, entry.peer
|
|
}
|
|
|
|
entry.peer = peer
|
|
}
|
|
|
|
if now.After(entry.lastSeen.Add(cache.maxAge / 10)) {
|
|
entry.lastSeen = now
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (cache *MacCache) Add(mac net.HardwareAddr, peer *Peer) (bool, *Peer) {
|
|
return cache.add(mac, peer, false)
|
|
}
|
|
|
|
func (cache *MacCache) AddForced(mac net.HardwareAddr, peer *Peer) (bool, *Peer) {
|
|
return cache.add(mac, peer, true)
|
|
}
|
|
|
|
func (cache *MacCache) Lookup(mac net.HardwareAddr) *Peer {
|
|
key := macint(mac)
|
|
cache.RLock()
|
|
defer cache.RUnlock()
|
|
entry, found := cache.table[key]
|
|
if !found {
|
|
return nil
|
|
}
|
|
return entry.peer
|
|
}
|
|
|
|
func (cache *MacCache) Delete(peer *Peer) bool {
|
|
found := false
|
|
cache.Lock()
|
|
defer cache.Unlock()
|
|
for key, entry := range cache.table {
|
|
if entry.peer == peer {
|
|
delete(cache.table, key)
|
|
found = true
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
func (cache *MacCache) setExpiryTimer() {
|
|
cache.expiryTimer = time.AfterFunc(cache.maxAge/10, func() { cache.expire() })
|
|
}
|
|
|
|
func (cache *MacCache) expire() {
|
|
now := time.Now()
|
|
cache.Lock()
|
|
defer cache.Unlock()
|
|
for key, entry := range cache.table {
|
|
if now.After(entry.lastSeen.Add(cache.maxAge)) {
|
|
delete(cache.table, key)
|
|
cache.onExpiry(intmac(key), entry.peer)
|
|
}
|
|
}
|
|
cache.setExpiryTimer()
|
|
}
|