Files
lneto/ipv6/icmpv6/client_ping.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

194 lines
5.0 KiB
Go

package icmpv6
import (
"slices"
"github.com/soypat/lneto"
"github.com/soypat/lneto/internal"
)
func (client *Client) PingIncomingCapacity() int {
return cap(client.incomingEcho)
}
func (client *Client) PingStart(remoteAddr [16]byte, pattern []byte, size uint16) (key uint32, err error) {
if int(size) < len(pattern) || len(pattern) == 0 {
return 0, lneto.ErrInvalidConfig
} else if remoteAddr == [16]byte{} {
return 0, lneto.ErrZeroDestination
}
free := cap(client.outgoingEcho) - len(client.outgoingEcho)
if free == 0 {
return 0, lneto.ErrExhausted
}
key = client.magichash(pattern, int(size)) & keyHashBits
v := internal.SliceReclaim(&client.outgoingEcho)
v.key = key
v.size = size
v.pattern = append(v.pattern[:0], pattern...)
v.raddr = remoteAddr
return key, nil
}
func (client *Client) PingPeek(key uint32) (completed, ok bool) {
idx := client.pingidx(key)
if idx >= 0 {
return client.outgoingEcho[idx].key&keyHashCompletedBit != 0, true
}
return false, false
}
func (client *Client) PingPop(key uint32) (completed, ok bool) {
idx := client.pingidx(key)
if idx >= 0 {
completed := client.outgoingEcho[idx].key&keyHashCompletedBit != 0
client.outgoingEcho = slices.Delete(client.outgoingEcho, idx, idx+1)
return completed, true
}
return false, false
}
func (client *Client) pingidx(key uint32) int {
for i := range client.outgoingEcho {
if client.outgoingEcho[i].key&keyHashBits == key {
return i
}
}
return -1
}
func (client *Client) seq() uint16 {
client._seq++
return client._seq
}
func (client *Client) magichash(pattern []byte, size int) (hash uint32) {
hash = client.magic
i := 0
n := size / len(pattern)
for i < n {
for _, b := range pattern {
hash = hash*31 + uint32(b)
}
i++
}
n = size % len(pattern)
for i = 0; i < n; i++ {
hash = hash*31 + uint32(pattern[i])
}
return hash
}
// demuxEcho handles TypeEchoRequest and TypeEchoReply frames.
// CRC has already been verified by the caller (Client.Demux).
func (client *Client) demuxEcho(carrierData []byte, frameOffset int) error {
rawdata := carrierData[frameOffset:]
ifrm, _ := NewFrame(rawdata) // already validated by Demux
tp := ifrm.Type()
ipEnabled := frameOffset >= 40
var raddr [16]byte
if ipEnabled {
src, _, _, _, _ := internal.GetIPAddr(carrierData)
if len(src) == 16 {
raddr = [16]byte(src)
}
}
var err error
switch tp {
case TypeEchoRequest:
free := cap(client.incomingEcho) - len(client.incomingEcho)
if free == 0 {
return lneto.ErrExhausted
}
efrm := FrameEcho{Frame: ifrm}
data := efrm.Data()
if len(data) == 0 {
return lneto.ErrPacketDrop
}
n, werr := client.responseRing.Write(data)
if werr != nil {
err = werr
break
}
v := internal.SliceReclaim(&client.incomingEcho)
v.length = uint16(n)
v.id = efrm.Identifier()
v.seq = efrm.SequenceNumber()
v.raddr = raddr
case TypeEchoReply:
efrm := FrameEcho{Frame: ifrm}
data := efrm.Data()
if len(data) == 0 {
return lneto.ErrPacketDrop
}
hash := client.magichash(data, len(data)) & keyHashBits
idx := client.pingidx(hash)
if idx < 0 || (ipEnabled && client.outgoingEcho[idx].raddr != raddr) {
err = lneto.ErrPacketDrop
break
}
client.outgoingEcho[idx].key |= keyHashCompletedBit
default:
err = lneto.ErrPacketDrop
}
return err
}
// encapsEcho writes an echo reply or request frame into carrierData[frameOffset:].
// CRC and SetIPAddrs are handled by the caller (Client.Encapsulate).
func (client *Client) encapsEcho(carrierData []byte, frameOffset int) (n int, dst [16]byte, err error) {
if len(client.incomingEcho) == 0 && len(client.outgoingEcho) == 0 {
return 0, [16]byte{}, nil
}
ifrm, err := NewFrame(carrierData[frameOffset:])
if err != nil {
return 0, [16]byte{}, err
}
if len(client.incomingEcho) > 0 {
// Priority: send echo reply.
inc := client.incomingEcho[0]
efrm := FrameEcho{Frame: ifrm}
efrm.SetType(TypeEchoReply)
efrm.SetIdentifier(inc.id)
efrm.SetSequenceNumber(inc.seq)
dataLen := int(inc.length)
_, rerr := client.responseRing.Read(efrm.Data()[:dataLen])
if rerr != nil {
return 0, [16]byte{}, rerr
}
client.incomingEcho = slices.Delete(client.incomingEcho, 0, 1)
n = sizeHeader + dataLen
dst = inc.raddr
} else {
idx := 0
for idx < len(client.outgoingEcho) && client.outgoingEcho[idx].key&keyHashSentBit != 0 {
idx++
}
if idx >= len(client.outgoingEcho) {
return 0, [16]byte{}, nil // No pending packet to send.
}
out := &client.outgoingEcho[idx]
efrm := FrameEcho{Frame: ifrm}
efrm.SetType(TypeEchoRequest)
efrm.SetIdentifier(client.id)
efrm.SetSequenceNumber(client.seq())
pattern := out.pattern
data := efrm.Data()
size := int(out.size)
written := 0
for written+len(pattern) <= size && written+len(pattern) <= len(data) {
copy(data[written:], pattern)
written += len(pattern)
}
copy(data[written:written+size%len(pattern)], pattern)
n = sizeHeader + size
out.key |= keyHashSentBit
dst = out.raddr
}
ifrm.buf = carrierData[frameOffset : frameOffset+n]
ifrm.SetCode(0)
return n, dst, nil
}