Files
lneto/ipv6/icmpv6/ndp_cache.go
T
Pat Whittingslow 7d5830d7ab V2 Netbird integration - UDP MIMO/SIMO, ICMPv6, DHCPv6 implementations (#106)
* begin adding udp.MuxHandler

* add udp MuxHandlerSIMO/MIMO

* add tcp rx shutdown

* icmpv6 client

* icmpv6 Client shared NDP/Echo preparation

* icmpv6 client ndp/echo split

* icmpv6 client ndp/echo split done

* icmpv6 adjustments

* add dhcpv6 stubs

* dhcpv4 preliminary revision

* add dns.NextLabel

* dns label name tweaks

* dns begin work on TCP client

* add dnstcp package

* apply gofmt changes

* add udp mux tests

* clean up, remove StackBig for now

* remove dnstcp so as to merged confident parts and we continue dnstcp work elsewhere
2026-05-10 12:16:30 -03:00

137 lines
3.6 KiB
Go

package icmpv6
import (
"github.com/soypat/lneto"
"github.com/soypat/lneto/internal"
)
type ndpCache struct {
entries []ndpEntry
}
// ndpEntry maps an IPv6 address to a MAC. Designed for compactness: 24 bytes.
type ndpEntry struct {
addr [16]byte
mac [6]byte
age uint8
flags ndpFlags
}
type ndpFlags uint8
// unset ndpFlagInUse to signal the entry can be acquired for a new query.
const (
ndpFlagInUse ndpFlags = 1 << iota
ndpFlagPendingResponse // received a NS for our addr; must send NA
ndpFlagIncomplete // query in flight; MAC not yet resolved
ndpFlagIncompletePendingQuery // NS not yet transmitted
ndpFlagPriority // user query; evicted last
ndpFlagResolveTriggersCallback // call onresolve when MAC is learned
)
func (f ndpFlags) hasAny(bits ndpFlags) bool { return f&bits != 0 }
func (e *ndpEntry) use(mac [6]byte, addr [16]byte, flags ndpFlags) {
e.flags = ndpFlagInUse | flags
e.addr = addr
e.age = 0
e.mac = mac
}
func (e *ndpEntry) destroy() { *e = ndpEntry{} }
func (e *ndpEntry) put(buf []byte, ourAddr [16]byte, ourMAC [6]byte, tp Type) (int, error) {
if len(buf) < sizeNDP {
return 0, lneto.ErrShortBuffer
}
buf[0] = uint8(tp)
buf[1] = 0
buf[2], buf[3] = 0, 0 // checksum zeroed; caller computes it
switch tp {
case TypeNeighborSolicitation:
buf[4], buf[5], buf[6], buf[7] = 0, 0, 0, 0
copy(buf[8:24], e.addr[:]) // target = address being queried
buf[24] = ndpOptSourceLinkAddr
buf[25] = 1 // length in units of 8 bytes
copy(buf[26:32], ourMAC[:])
case TypeNeighborAdvertisement:
buf[4] = 0x60 // S=1 (solicited), O=1 (override)
buf[5], buf[6], buf[7] = 0, 0, 0
copy(buf[8:24], ourAddr[:]) // target = our address being announced
buf[24] = ndpOptTargetLinkAddr
buf[25] = 1
copy(buf[26:32], ourMAC[:])
default:
return 0, lneto.ErrUnsupported
}
return sizeNDP, nil
}
func (c *ndpCache) ageEntries() {
for i := range c.entries {
if c.entries[i].flags&ndpFlagInUse != 0 && c.entries[i].age < 255 {
c.entries[i].age++
}
}
}
func (c *ndpCache) reset(maxLimit int) {
internal.SliceReuse(&c.entries, maxLimit)
c.entries = c.entries[:cap(c.entries)]
}
func (c *ndpCache) getNextFlagged(flags ndpFlags) *ndpEntry {
for i := range c.entries {
f := c.entries[i].flags
if f&ndpFlagInUse != 0 && f.hasAny(flags) {
return &c.entries[i]
}
}
return nil
}
func (c *ndpCache) clearFlags(entryHasFlags, clrTheseFlagsIfMatch ndpFlags) {
for i := range c.entries {
if c.entries[i].flags&entryHasFlags != 0 {
c.entries[i].flags &^= clrTheseFlagsIfMatch
}
}
}
func (c *ndpCache) Lookup(addr [16]byte) *ndpEntry {
for i := range c.entries {
if c.entries[i].flags&ndpFlagInUse != 0 && c.entries[i].addr == addr {
return &c.entries[i]
}
}
return nil
}
// acquireNext returns the next available entry, evicting the oldest passive
// (passively-learned) entry before touching active user queries or pending responses.
func (c *ndpCache) acquireNext() *ndpEntry {
const priorityFlags = ndpFlagPendingResponse | ndpFlagIncomplete | ndpFlagPriority
oldest, oldestPassive := 0, -1
for i := range c.entries {
if c.entries[i].flags&ndpFlagInUse == 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&ndpFlagInUse != 0 {
oldest = oldestPassive
}
c.ageEntries()
e := &c.entries[oldest]
e.destroy()
return e
}