Files
Patricio Whittingslow 8efd810610 more API touch ups
2026-05-14 13:15:16 -03:00

198 lines
5.0 KiB
Go

package icmpv6
import (
"github.com/soypat/lneto"
"github.com/soypat/lneto/internal"
)
var _ lneto.StackNode = (*Client)(nil)
const (
keyHashCompletedBit = 1 << 31
keyHashSentBit = 1 << 30
keyHashBits = (1 << 30) - 1
)
type ClientConfig struct {
// Echo (ping) fields.
ResponseQueueBuffer []byte
ResponseQueueLimit int
HashSeed uint32
ID uint16
// NDP fields; NDP is disabled when NDPCache == 0.
OurAddr [16]byte
OurMAC [6]byte
NDPCache int
}
type Client struct {
connid uint64
magic uint32
_seq uint16
id uint16
outgoingEcho []struct {
pattern []byte
key uint32
size uint16
raddr [16]byte
}
// responseLengths stores the length of responses received.
// together they should add up to the written length of responseRing.
incomingEcho []struct {
length uint16
id uint16
seq uint16
raddr [16]byte
}
responseRing internal.Ring
// NDP address resolution fields.
ndpCache ndpCache
onresolve func(mac [6]byte, addr [16]byte)
ourMAC [6]byte
ourIP [16]byte
}
func (client *Client) Configure(cfg ClientConfig) error {
echoOK := cfg.HashSeed != 0 && len(cfg.ResponseQueueBuffer) >= 16 && cfg.ResponseQueueLimit > 0
ndpOK := cfg.NDPCache > 0
if !echoOK && !ndpOK {
return lneto.ErrInvalidConfig
}
client.connid++
if echoOK {
internal.SliceReuse(&client.outgoingEcho, cfg.ResponseQueueLimit)
internal.SliceReuse(&client.incomingEcho, cfg.ResponseQueueLimit)
client.responseRing = internal.Ring{Buf: cfg.ResponseQueueBuffer}
client.magic = cfg.HashSeed
client.id = cfg.ID
}
if ndpOK {
client.ourIP = cfg.OurAddr
client.ourMAC = cfg.OurMAC
client.ndpCache.reset(cfg.NDPCache)
}
return nil
}
func (client *Client) SetAddr6(addr [16]byte) {
client.ourIP = addr
client.Reset()
}
func (client *Client) Protocol() uint64 { return uint64(lneto.IPProtoIPv6ICMP) }
func (client *Client) LocalPort() uint16 { return 0 }
func (client *Client) ConnectionID() *uint64 { return &client.connid }
func (client *Client) Abort() {
client.Reset()
client.connid++
}
func (client *Client) Reset() {
client.incomingEcho = client.incomingEcho[:0]
client.outgoingEcho = client.outgoingEcho[:0]
client.responseRing.Reset()
client.ndpCache.reset(0)
}
func (client *Client) Demux(carrierData []byte, frameOffset int) error {
rawdata := carrierData[frameOffset:]
ifrm, err := NewFrame(rawdata)
if err != nil {
return err
}
tp := ifrm.Type()
ipEnabled := frameOffset >= 40
var crc lneto.CRC791
if ipEnabled {
crc.WriteEven(carrierData[8:40]) // IPv6 src(16B)+dst(16B) pseudo-header
crc.AddUint32(uint32(len(rawdata)))
crc.AddUint32(uint32(lneto.IPProtoIPv6ICMP))
}
if crc.PayloadSum16(rawdata) != 0 {
return lneto.ErrBadCRC
}
switch tp {
case TypeEchoRequest, TypeEchoReply:
return client.demuxEcho(carrierData, frameOffset)
case TypeNeighborSolicitation, TypeNeighborAdvertisement:
return client.demuxNDP(carrierData, frameOffset)
default:
return lneto.ErrPacketDrop
}
}
func (client *Client) Encapsulate(carrierData []byte, ipOffset, frameOffset int) (int, error) {
n, dst, err := client.encapsEcho(carrierData, frameOffset)
if n == 0 && err == nil {
n, dst, err = client.encapsNDP(carrierData, frameOffset)
}
if n == 0 || err != nil {
return n, err
}
ifrm, _ := NewFrame(carrierData[frameOffset : frameOffset+n])
ifrm.SetCRC(0)
if ipOffset >= 0 {
if err = internal.SetIPAddrs(carrierData[ipOffset:], 0, nil, dst[:]); err != nil {
return 0, err
}
var crc lneto.CRC791
crc.WriteEven(carrierData[ipOffset+8 : ipOffset+40])
crc.AddUint32(uint32(n))
crc.AddUint32(uint32(lneto.IPProtoIPv6ICMP))
ifrm.SetCRC(crc.PayloadSum16(carrierData[frameOffset : frameOffset+n]))
} else {
var crc lneto.CRC791
ifrm.SetCRC(crc.PayloadSum16(carrierData[frameOffset : frameOffset+n]))
}
return n, nil
}
// NDP public API — mirrors NDPHandler but on the unified Client.
func (client *Client) SetNDPResolveCallback(cb func(mac [6]byte, addr [16]byte)) {
client.onresolve = cb
}
func (client *Client) NDPStartQuery(addr [16]byte, triggerCallback bool) error {
if addr == ([16]byte{}) {
return lneto.ErrZeroDestination
}
e := client.ndpCache.acquireNext()
e.use([6]byte{}, addr, ndpFlagIncomplete|ndpFlagIncompletePendingQuery|ndpFlagPriority)
if triggerCallback {
e.flags |= ndpFlagResolveTriggersCallback
}
return nil
}
func (client *Client) NDPCacheLookup(addr [16]byte) ([6]byte, error) {
e := client.ndpCache.Lookup(addr)
if e == nil {
return [6]byte{}, errNDPQueryNotFound
} else if e.flags.hasAny(ndpFlagIncomplete) {
return [6]byte{}, errNDPQueryPending
}
return e.mac, nil
}
func (client *Client) NDPCacheSeed(addr [16]byte, mac [6]byte) error {
if addr == ([16]byte{}) {
return lneto.ErrZeroDestination
}
e := client.ndpCache.acquireNext()
e.use(mac, addr, 0)
return nil
}
func (client *Client) NDPCacheRemove(addr [16]byte) error {
e := client.ndpCache.Lookup(addr)
if e == nil {
return errNDPQueryNotFound
}
e.destroy()
return nil
}