mirror of
https://github.com/soypat/lneto.git
synced 2026-08-23 16:09:07 +00:00
passive MAC learning and ARP cache revamp (#96)
* begin working on arp cache * fix little things * rework arp cache priority * fix test * keep working on ARP * push changes before thinking about ip prefix issue * add passive peer MAC setting * patch egress mac with correct ethernet CRCs * consolidate subnet learning in subnetTable type * add tests and fix subnet table bug
This commit is contained in:
+167
@@ -0,0 +1,167 @@
|
||||
package arp
|
||||
|
||||
import (
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/internal"
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
entries []entry
|
||||
}
|
||||
|
||||
// entry is designed for compactness. size=class=24 bytes, same as a slice header on x86.
|
||||
type entry struct {
|
||||
addr [16]byte
|
||||
mac [6]byte
|
||||
age uint8
|
||||
flags eflags
|
||||
}
|
||||
|
||||
func (e *entry) use(mac [6]byte, proto []byte, flags eflags) {
|
||||
e.flags = eflagInUse | flags
|
||||
if copy(e.addr[:], proto) == 16 {
|
||||
e.flags |= eflagIPv6
|
||||
}
|
||||
e.age = 0
|
||||
e.mac = mac
|
||||
}
|
||||
|
||||
func (e *entry) destroy() { *e = entry{} }
|
||||
|
||||
func (e *entry) put(frame, ourAddr []byte, ourMAC [6]byte, op Operation) (int, error) {
|
||||
if len(ourMAC) != 6 {
|
||||
return 0, lneto.ErrInvalidAddr
|
||||
}
|
||||
f, err := NewFrame(frame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.SetHardware(1, 6)
|
||||
f.SetOperation(op)
|
||||
var n int
|
||||
if e.flags&eflagIPv6 != 0 {
|
||||
if len(ourAddr) != 16 {
|
||||
return 0, lneto.ErrInvalidAddr
|
||||
} else if len(frame) < sizeHeaderv6 {
|
||||
return 0, lneto.ErrShortBuffer
|
||||
}
|
||||
f.SetProtocol(ethernet.TypeIPv6, 16)
|
||||
hw, addr := f.Sender16()
|
||||
*hw = ourMAC
|
||||
copy(addr[:], ourAddr)
|
||||
hw, addr = f.Target16()
|
||||
copy(hw[:], e.mac[:])
|
||||
copy(addr[:], e.addr[:])
|
||||
n = sizeHeaderv6
|
||||
} else {
|
||||
if len(ourAddr) != 4 {
|
||||
return 0, lneto.ErrInvalidAddr
|
||||
}
|
||||
f.SetProtocol(ethernet.TypeIPv4, 4)
|
||||
hw, addr := f.Sender4()
|
||||
*hw = ourMAC
|
||||
copy(addr[:], ourAddr)
|
||||
hw, addr = f.Target4()
|
||||
copy(hw[:], e.mac[:])
|
||||
copy(addr[:], e.addr[:])
|
||||
n = sizeHeaderv4
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (flags eflags) hasAny(bits eflags) bool { return flags&bits != 0 }
|
||||
|
||||
type eflags uint8
|
||||
|
||||
// unset eflagInUse to signal the entry can be acquired for a new query.
|
||||
const (
|
||||
// eflagInUse set when in use. Discarded/unused entries have this bit unset.
|
||||
eflagInUse eflags = 1 << iota
|
||||
// set for IPv6 addressed entries.
|
||||
eflagIPv6
|
||||
// network device queried our address and we must respond to it.
|
||||
// Both MAC and IP are valid in this case.
|
||||
eflagPendingResponse
|
||||
// user asked to query this address and query has yet to be answered. May or may not be sent.
|
||||
eflagIncomplete
|
||||
// user asked to query this address and the query has not been sent out yet.
|
||||
// The MAC address is invalid in this case.
|
||||
eflagIncompletePendingQuery
|
||||
// eflagPriority set for prioritized cache entries. These entries are discarded last.
|
||||
// i.e: set for user created queries, unset for external incoming network queries.
|
||||
eflagPriority
|
||||
// trigger callback, you know the drill.
|
||||
eflagResolveTriggersCallback
|
||||
)
|
||||
|
||||
func (c *cache) age() {
|
||||
for i := range c.entries {
|
||||
if c.entries[i].flags&eflagInUse != 0 && c.entries[i].age < 255 {
|
||||
c.entries[i].age++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cache) reset(size int) {
|
||||
internal.SliceReuse(&c.entries, size)
|
||||
c.entries = c.entries[:cap(c.entries)] // maximize queries given allocation.
|
||||
}
|
||||
|
||||
func (c *cache) getNextFlagged(entryHasFlags eflags) *entry {
|
||||
for i := range c.entries {
|
||||
flags := c.entries[i].flags
|
||||
if flags&eflagInUse != 0 && flags.hasAny(entryHasFlags) {
|
||||
return &c.entries[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cache) clearFlags(entryHasFlags, clrTheseFlagsIfMatch eflags) {
|
||||
for i := range c.entries {
|
||||
// Can clear flags on unused too, simpler.
|
||||
if c.entries[i].flags&entryHasFlags != 0 {
|
||||
c.entries[i].flags &^= clrTheseFlagsIfMatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cache) Lookup(addr []byte) *entry {
|
||||
n := len(addr)
|
||||
for i := range c.entries {
|
||||
if c.entries[i].flags&eflagInUse != 0 && internal.BytesEqual(c.entries[i].addr[:n], addr) {
|
||||
return &c.entries[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// acquireNext gets next available entry for use. If all are in use evicts
|
||||
// the oldest passive entry (learned from incoming requests) before touching
|
||||
// active user queries or pending responses.
|
||||
func (c *cache) acquireNext() *entry {
|
||||
const priorityFlags = eflagPendingResponse | eflagIncomplete | eflagPriority
|
||||
oldest, oldestPassive := 0, -1
|
||||
for i := range c.entries {
|
||||
if c.entries[i].flags&eflagInUse == 0 {
|
||||
oldest = i
|
||||
break
|
||||
}
|
||||
if !c.entries[i].flags.hasAny(priorityFlags) {
|
||||
if oldestPassive < 0 || c.entries[i].age > c.entries[oldestPassive].age {
|
||||
oldestPassive = i
|
||||
}
|
||||
}
|
||||
if c.entries[i].age > c.entries[oldest].age {
|
||||
oldest = i
|
||||
}
|
||||
}
|
||||
if oldestPassive >= 0 && c.entries[oldest].flags&eflagInUse != 0 {
|
||||
oldest = oldestPassive
|
||||
}
|
||||
c.age()
|
||||
e := &c.entries[oldest]
|
||||
e.destroy()
|
||||
return e
|
||||
}
|
||||
Reference in New Issue
Block a user