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

117 lines
3.1 KiB
Go

package icmpv6
import (
"errors"
"github.com/soypat/lneto"
)
const (
ndpOptSourceLinkAddr = 1 // RFC 4861 §4.6.1
ndpOptTargetLinkAddr = 2 // RFC 4861 §4.6.2
)
var (
errNDPQueryPending = errors.New("icmpv6: NDP query pending")
errNDPQueryNotFound = errors.New("icmpv6: NDP query not found")
)
func (client *Client) demuxNDP(carrierData []byte, frameOffset int) error {
rawdata := carrierData[frameOffset:]
if len(rawdata) < sizeNDPBase {
return lneto.ErrTruncatedFrame
}
ifrm, _ := NewFrame(rawdata) // already validated by Demux
tp := ifrm.Type()
targetAddr := (*[16]byte)(rawdata[8:24])
options := rawdata[24:]
ipEnabled := frameOffset >= 40
switch tp {
case TypeNeighborSolicitation:
if *targetAddr != client.ourIP {
return nil // Not for us.
}
mac, ok := parseLinkLayerOption(options, ndpOptSourceLinkAddr)
if !ok {
return lneto.ErrPacketDrop
}
var senderAddr [16]byte
if ipEnabled {
copy(senderAddr[:], carrierData[8:24]) // IPv6 source address
}
e := client.ndpCache.acquireNext()
e.use(mac, senderAddr, ndpFlagPendingResponse)
case TypeNeighborAdvertisement:
mac, ok := parseLinkLayerOption(options, ndpOptTargetLinkAddr)
if !ok {
return lneto.ErrPacketDrop
}
e := client.ndpCache.Lookup(*targetAddr)
if e == nil {
return nil // Unsolicited or already evicted.
}
e.mac = mac
e.flags &^= ndpFlagIncomplete | ndpFlagIncompletePendingQuery
if e.flags.hasAny(ndpFlagResolveTriggersCallback) && client.onresolve != nil {
client.onresolve(mac, *targetAddr)
}
}
return nil
}
func (client *Client) encapsNDP(carrierData []byte, frameOffset int) (n int, dst [16]byte, err error) {
buf := carrierData[frameOffset:]
if len(buf) < sizeNDP {
return 0, [16]byte{}, lneto.ErrShortBuffer
}
tp := TypeNeighborAdvertisement
e := client.ndpCache.getNextFlagged(ndpFlagPendingResponse) // Prioritize responses.
if e == nil {
e = client.ndpCache.getNextFlagged(ndpFlagIncompletePendingQuery)
if e == nil {
return 0, [16]byte{}, nil
}
e.flags &^= ndpFlagIncompletePendingQuery
tp = TypeNeighborSolicitation
} else {
e.flags &^= ndpFlagPendingResponse
}
n, err = e.put(buf, client.ourIP, client.ourMAC, tp)
if err != nil {
return 0, [16]byte{}, err
}
switch tp {
case TypeNeighborSolicitation:
dst = solicitedNodeMulticast(e.addr)
case TypeNeighborAdvertisement:
dst = e.addr
}
return n, dst, nil
}
// solicitedNodeMulticast returns the solicited-node multicast address for addr (RFC 4291 §2.7.1).
func solicitedNodeMulticast(addr [16]byte) [16]byte {
return [16]byte{
0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0xff,
addr[13], addr[14], addr[15],
}
}
// parseLinkLayerOption scans NDP options for the first option of optType
// and returns the embedded 6-byte Ethernet address.
func parseLinkLayerOption(options []byte, optType byte) ([6]byte, bool) {
for len(options) >= sizeNDPOption {
t := options[0]
l := int(options[1]) * 8
if l == 0 || l > len(options) {
break
}
if t == optType && l >= sizeNDPOption {
return [6]byte(options[2:8]), true
}
options = options[l:]
}
return [6]byte{}, false
}