mirror of
https://github.com/soypat/lneto.git
synced 2026-09-09 00:09:08 +00:00
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
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
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) 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
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package icmpv6
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/soypat/lneto/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
testHashSeed = 0xdeadbeef
|
||||
)
|
||||
|
||||
func TestClients(t *testing.T) {
|
||||
const sizebuffer = 64
|
||||
const queuesize = 2
|
||||
var sender, responder Client
|
||||
err := sender.Configure(ClientConfig{
|
||||
ResponseQueueBuffer: make([]byte, sizebuffer),
|
||||
ResponseQueueLimit: queuesize,
|
||||
HashSeed: testHashSeed,
|
||||
ID: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = responder.Configure(ClientConfig{
|
||||
ResponseQueueBuffer: make([]byte, sizebuffer),
|
||||
ResponseQueueLimit: queuesize,
|
||||
HashSeed: testHashSeed,
|
||||
ID: 2,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pattern := []byte("ab12")
|
||||
size := 8
|
||||
var buf [64]byte
|
||||
key1 := testSingleExchange(t, &sender, &responder, buf[:], pattern, uint16(size))
|
||||
completed, ok := sender.PingPop(key1)
|
||||
if !completed || !ok {
|
||||
t.Fatal("ping did not complete or not exist")
|
||||
}
|
||||
n, err := sender.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if n > 0 {
|
||||
t.Error("sender: expected no more data to be sent")
|
||||
}
|
||||
n, err = responder.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if n > 0 {
|
||||
t.Error("responder: expected no more data to be sent")
|
||||
}
|
||||
}
|
||||
|
||||
func testSingleExchange(t *testing.T, sender, responder *Client, buf []byte, pattern []byte, size uint16) (senderKey uint32) {
|
||||
const frameOff = 0
|
||||
const ipOff = -1
|
||||
n, err := responder.Encapsulate(buf, ipOff, frameOff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n > 0 {
|
||||
t.Fatal("expected no data pending to be sent from responder")
|
||||
}
|
||||
senderKey, n = testSendEcho(t, sender, buf, pattern, size)
|
||||
completed, ok := sender.PingPeek(senderKey)
|
||||
if !ok {
|
||||
t.Error("ping key not exist")
|
||||
} else if completed {
|
||||
t.Error("ping completed before response")
|
||||
}
|
||||
ifrm, _ := NewFrame(buf[frameOff : frameOff+n])
|
||||
efrm := FrameEcho{Frame: ifrm}
|
||||
id, seq := efrm.Identifier(), efrm.SequenceNumber()
|
||||
err1 := responder.Demux(buf[:frameOff+n], frameOff)
|
||||
if err1 != nil {
|
||||
t.Error("responder demux during single", err1)
|
||||
}
|
||||
n, err = responder.Encapsulate(buf, ipOff, frameOff)
|
||||
if err != nil {
|
||||
t.Error("responder encaps during single", err)
|
||||
return
|
||||
} else if n == 0 && err1 == nil {
|
||||
t.Error("responder wrote no data")
|
||||
return
|
||||
}
|
||||
ifrm, err = NewFrame(buf[frameOff : frameOff+n])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ifrm.Type() != TypeEchoReply {
|
||||
t.Fatalf("expected echo reply %d", ifrm.Type())
|
||||
}
|
||||
efrm = FrameEcho{Frame: ifrm}
|
||||
if efrm.Identifier() != id {
|
||||
t.Error("mismatched identifier want/got:", id, efrm.Identifier())
|
||||
}
|
||||
if efrm.SequenceNumber() != seq {
|
||||
t.Error("mismatched sequence number want/got:", seq, efrm.SequenceNumber())
|
||||
}
|
||||
data := efrm.Data()
|
||||
testPatternMatch(t, data, pattern, int(size))
|
||||
err = sender.Demux(buf[:frameOff+n], frameOff)
|
||||
if err != nil {
|
||||
t.Error("sender demuxed response", err)
|
||||
}
|
||||
completed, ok = sender.PingPeek(senderKey)
|
||||
if !completed {
|
||||
t.Error("expected ping to have completed")
|
||||
}
|
||||
if !ok {
|
||||
t.Error("ping key not exist after completion")
|
||||
}
|
||||
if completed2, ok2 := sender.PingPeek(senderKey); completed != completed2 || ok != ok2 {
|
||||
t.Error("change in status after peek")
|
||||
}
|
||||
n, err = sender.Encapsulate(buf, ipOff, frameOff)
|
||||
if err != nil {
|
||||
t.Error("error after done")
|
||||
}
|
||||
if n > 0 {
|
||||
t.Error("expected no data to be sent after ping completion", n)
|
||||
}
|
||||
return senderKey
|
||||
}
|
||||
|
||||
func testSendEcho(t *testing.T, sender *Client, buf []byte, pattern []byte, size uint16) (key uint32, n int) {
|
||||
t.Helper()
|
||||
const frameOff = 0
|
||||
const ipOff = -1
|
||||
n, err := sender.Encapsulate(buf, ipOff, frameOff)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n > 0 {
|
||||
t.Fatal("expected no data pending to send on testSendEcho start")
|
||||
}
|
||||
key, err = sender.PingStart([16]byte{1}, pattern, size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err = sender.Encapsulate(buf[:], ipOff, frameOff)
|
||||
if err != nil {
|
||||
t.Errorf("sender encapsulate: %v", err)
|
||||
}
|
||||
ifrm, err := NewFrame(buf[:n])
|
||||
if err != nil {
|
||||
t.Fatal(err) // only fails in short frame case.
|
||||
}
|
||||
if ifrm.Type() != TypeEchoRequest {
|
||||
t.Errorf("not echo request type on send: %d", ifrm.Type())
|
||||
}
|
||||
efrm := FrameEcho{Frame: ifrm}
|
||||
data := efrm.Data()
|
||||
testPatternMatch(t, data, pattern, int(size))
|
||||
return key, n
|
||||
}
|
||||
|
||||
func testPatternMatch(t *testing.T, data []byte, pattern []byte, size int) {
|
||||
t.Helper()
|
||||
if len(data) != size {
|
||||
t.Errorf("pattern size mismatch, want %d, got %d", size, len(data))
|
||||
}
|
||||
for i := 0; i < size; i += len(pattern) {
|
||||
got := data[i:min(len(data), i+len(pattern))]
|
||||
want := pattern[:len(got)]
|
||||
if !internal.BytesEqual(got, want) {
|
||||
t.Errorf("pattern data mismatch at %d, got %s, want %s", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package icmpv6
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=Type,CodeDestinationUnreachable,CodeParameterProblem -linecomment -output stringers.go
|
||||
|
||||
const (
|
||||
sizeHeader = 8
|
||||
sizeNDPBase = sizeHeader + 16 // 24: ICMPv6 header + 16-byte target address
|
||||
sizeNDPOption = 8 // 1 type + 1 len + 6 MAC (Ethernet link-layer option, RFC 4861 §4.6.1)
|
||||
sizeNDP = sizeNDPBase + sizeNDPOption
|
||||
)
|
||||
|
||||
type Type uint8
|
||||
|
||||
const (
|
||||
TypeDestinationUnreachable Type = 1 // destination unreachable
|
||||
TypePacketTooBig Type = 2 // packet too big
|
||||
TypeTimeExceeded Type = 3 // time exceeded
|
||||
TypeParameterProblem Type = 4 // parameter problem
|
||||
|
||||
TypeEchoRequest Type = 128 // echo request
|
||||
TypeEchoReply Type = 129 // echo reply
|
||||
|
||||
TypeRouterSolicitation Type = 133 // router solicitation
|
||||
TypeRouterAdvertisement Type = 134 // router advertisement
|
||||
TypeNeighborSolicitation Type = 135 // neighbor solicitation
|
||||
TypeNeighborAdvertisement Type = 136 // neighbor advertisement
|
||||
TypeRedirectMessage Type = 137 // redirect message
|
||||
)
|
||||
|
||||
type CodeTimeExceeded uint8
|
||||
|
||||
const (
|
||||
CodeHopLimitExceeded CodeTimeExceeded = iota // hop limit exceeded in transit
|
||||
CodeFragmentReassembly // fragment reassembly time exceeded
|
||||
)
|
||||
|
||||
type CodeDestinationUnreachable uint8
|
||||
|
||||
const (
|
||||
CodeNoRoute CodeDestinationUnreachable = iota // no route to destination
|
||||
CodeAdminProhibited // communication administratively prohibited
|
||||
CodeBeyondScope // beyond scope of source address
|
||||
CodeAddressUnreachable // address unreachable
|
||||
CodePortUnreachable // port unreachable
|
||||
CodeIngressEgressPolicy // source address failed ingress/egress policy
|
||||
CodeRejectRoute // reject route to destination
|
||||
)
|
||||
|
||||
type CodeParameterProblem uint8
|
||||
|
||||
const (
|
||||
CodeErroneousHeaderField CodeParameterProblem = iota // erroneous header field encountered
|
||||
CodeUnrecognizedNextHeader // unrecognized next header type encountered
|
||||
CodeUnrecognizedIPv6Option // unrecognized IPv6 option encountered
|
||||
)
|
||||
|
||||
func NewFrame(buf []byte) (Frame, error) {
|
||||
if len(buf) < sizeHeader {
|
||||
return Frame{}, lneto.ErrTruncatedFrame
|
||||
}
|
||||
return Frame{buf: buf}, nil
|
||||
}
|
||||
|
||||
type Frame struct {
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func (frm Frame) Type() Type { return Type(frm.buf[0]) }
|
||||
|
||||
func (frm Frame) SetType(t Type) { frm.buf[0] = uint8(t) }
|
||||
|
||||
func (frm Frame) Code() uint8 { return frm.buf[1] }
|
||||
|
||||
func (frm Frame) SetCode(code uint8) { frm.buf[1] = code }
|
||||
|
||||
// CRC returns the checksum field of the frame.
|
||||
func (frm Frame) CRC() uint16 {
|
||||
return binary.BigEndian.Uint16(frm.buf[2:4])
|
||||
}
|
||||
|
||||
// SetCRC sets the checksum field of the frame.
|
||||
func (frm Frame) SetCRC(crc uint16) {
|
||||
binary.BigEndian.PutUint16(frm.buf[2:4], crc)
|
||||
}
|
||||
|
||||
func (frm Frame) payload() []byte {
|
||||
return frm.buf[4:]
|
||||
}
|
||||
|
||||
type FrameDestinationUnreachable struct {
|
||||
Frame
|
||||
}
|
||||
|
||||
func (frm FrameDestinationUnreachable) Code() CodeDestinationUnreachable {
|
||||
return CodeDestinationUnreachable(frm.Frame.Code())
|
||||
}
|
||||
|
||||
func (frm FrameDestinationUnreachable) SetCode(code CodeDestinationUnreachable) {
|
||||
frm.Frame.SetCode(uint8(code))
|
||||
}
|
||||
|
||||
type FramePacketTooBig struct {
|
||||
Frame
|
||||
}
|
||||
|
||||
func (frm FramePacketTooBig) MTU() uint32 {
|
||||
return binary.BigEndian.Uint32(frm.buf[4:8])
|
||||
}
|
||||
|
||||
func (frm FramePacketTooBig) SetMTU(mtu uint32) {
|
||||
binary.BigEndian.PutUint32(frm.buf[4:8], mtu)
|
||||
}
|
||||
|
||||
type FrameParameterProblem struct {
|
||||
Frame
|
||||
}
|
||||
|
||||
func (frm FrameParameterProblem) Code() CodeParameterProblem {
|
||||
return CodeParameterProblem(frm.Frame.Code())
|
||||
}
|
||||
|
||||
func (frm FrameParameterProblem) SetCode(code CodeParameterProblem) {
|
||||
frm.Frame.SetCode(uint8(code))
|
||||
}
|
||||
|
||||
// Pointer identifies the octet offset within the invoking packet where the error was detected.
|
||||
func (frm FrameParameterProblem) Pointer() uint32 {
|
||||
return binary.BigEndian.Uint32(frm.buf[4:8])
|
||||
}
|
||||
|
||||
func (frm FrameParameterProblem) SetPointer(ptr uint32) {
|
||||
binary.BigEndian.PutUint32(frm.buf[4:8], ptr)
|
||||
}
|
||||
|
||||
type FrameEcho struct {
|
||||
Frame
|
||||
}
|
||||
|
||||
func (frm FrameEcho) Identifier() uint16 {
|
||||
return binary.BigEndian.Uint16(frm.buf[4:6])
|
||||
}
|
||||
|
||||
func (frm FrameEcho) SetIdentifier(id uint16) {
|
||||
binary.BigEndian.PutUint16(frm.buf[4:6], id)
|
||||
}
|
||||
|
||||
func (frm FrameEcho) SequenceNumber() uint16 {
|
||||
return binary.BigEndian.Uint16(frm.buf[6:8])
|
||||
}
|
||||
|
||||
func (frm FrameEcho) SetSequenceNumber(seq uint16) {
|
||||
binary.BigEndian.PutUint16(frm.buf[6:8], seq)
|
||||
}
|
||||
|
||||
func (frm FrameEcho) Data() []byte {
|
||||
return frm.buf[8:]
|
||||
}
|
||||
|
||||
func (frm FrameEcho) RawData() []byte {
|
||||
return frm.buf
|
||||
}
|
||||
|
||||
// FrameNeighborSolicitation accesses a Neighbor Solicitation message (RFC 4861 §4.3).
|
||||
// Layout after ICMPv6 base header: Reserved(4B) | TargetAddr(16B) | Options.
|
||||
type FrameNeighborSolicitation struct {
|
||||
Frame
|
||||
}
|
||||
|
||||
// TargetAddr returns the IPv6 address being queried.
|
||||
func (frm FrameNeighborSolicitation) TargetAddr() *[16]byte {
|
||||
return (*[16]byte)(frm.buf[8:24])
|
||||
}
|
||||
|
||||
// Options returns the bytes following the fixed header for parsing NDP options.
|
||||
func (frm FrameNeighborSolicitation) Options() []byte {
|
||||
return frm.buf[24:]
|
||||
}
|
||||
|
||||
// FrameNeighborAdvertisement accesses a Neighbor Advertisement message (RFC 4861 §4.4).
|
||||
// Layout after ICMPv6 base header: R|S|O|Reserved(4B) | TargetAddr(16B) | Options.
|
||||
type FrameNeighborAdvertisement struct {
|
||||
Frame
|
||||
}
|
||||
|
||||
// Flags returns the R (router), S (solicited), O (override) flag bits.
|
||||
func (frm FrameNeighborAdvertisement) Flags() (router, solicited, override bool) {
|
||||
b := frm.buf[4]
|
||||
return b&0x80 != 0, b&0x40 != 0, b&0x20 != 0
|
||||
}
|
||||
|
||||
// SetFlags sets the R, S, O flags and zeroes the reserved bits.
|
||||
func (frm FrameNeighborAdvertisement) SetFlags(router, solicited, override bool) {
|
||||
var b byte
|
||||
if router {
|
||||
b |= 0x80
|
||||
}
|
||||
if solicited {
|
||||
b |= 0x40
|
||||
}
|
||||
if override {
|
||||
b |= 0x20
|
||||
}
|
||||
frm.buf[4] = b
|
||||
frm.buf[5] = 0
|
||||
frm.buf[6] = 0
|
||||
frm.buf[7] = 0
|
||||
}
|
||||
|
||||
// TargetAddr returns the IPv6 address being announced.
|
||||
func (frm FrameNeighborAdvertisement) TargetAddr() *[16]byte {
|
||||
return (*[16]byte)(frm.buf[8:24])
|
||||
}
|
||||
|
||||
// Options returns the bytes following the fixed header for parsing NDP options.
|
||||
func (frm FrameNeighborAdvertisement) Options() []byte {
|
||||
return frm.buf[24:]
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package icmpv6
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClientNDP(t *testing.T) {
|
||||
addr1 := [16]byte{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||
addr2 := [16]byte{0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}
|
||||
mac1 := [6]byte{0xde, 0xad, 0xbe, 0xef, 0x00, 0x01}
|
||||
mac2 := [6]byte{0xc0, 0xff, 0xee, 0xc0, 0xff, 0x02}
|
||||
|
||||
var h1, h2 Client
|
||||
if err := h1.Configure(ClientConfig{OurAddr: addr1, OurMAC: mac1, NDPCache: 2}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := h2.Configure(ClientConfig{OurAddr: addr2, OurMAC: mac2, NDPCache: 2}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf [64]byte
|
||||
|
||||
// No pending work: both clients should be silent.
|
||||
n, err := h1.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil || n > 0 {
|
||||
t.Fatal("expected no data before query:", err, n)
|
||||
}
|
||||
n, err = h2.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil || n > 0 {
|
||||
t.Fatal("expected no data before query:", err, n)
|
||||
}
|
||||
|
||||
// h1 queries h2's MAC.
|
||||
if err = h1.NDPStartQuery(addr2, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// h1 sends a Neighbor Solicitation.
|
||||
n, err = h1.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
t.Fatal("h1 encapsulate NS:", err)
|
||||
} else if n == 0 {
|
||||
t.Fatal("expected NS to be written")
|
||||
}
|
||||
validateNDP(t, buf[:n], TypeNeighborSolicitation)
|
||||
|
||||
// Verify NS target address is addr2.
|
||||
ifrm, _ := NewFrame(buf[:n])
|
||||
nsfrm := FrameNeighborSolicitation{Frame: ifrm}
|
||||
if *nsfrm.TargetAddr() != addr2 {
|
||||
t.Errorf("NS target addr mismatch: want %x, got %x", addr2, *nsfrm.TargetAddr())
|
||||
}
|
||||
|
||||
// h2 receives the NS.
|
||||
if err = h2.Demux(buf[:n], 0); err != nil {
|
||||
t.Fatal("h2 demux NS:", err)
|
||||
}
|
||||
|
||||
// h2 sends a Neighbor Advertisement.
|
||||
n, err = h2.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil {
|
||||
t.Fatal("h2 encapsulate NA:", err)
|
||||
} else if n == 0 {
|
||||
t.Fatal("expected NA to be written")
|
||||
}
|
||||
validateNDP(t, buf[:n], TypeNeighborAdvertisement)
|
||||
|
||||
// Verify NA target address is addr2 (h2's own address).
|
||||
ifrm, _ = NewFrame(buf[:n])
|
||||
nafrm := FrameNeighborAdvertisement{Frame: ifrm}
|
||||
if *nafrm.TargetAddr() != addr2 {
|
||||
t.Errorf("NA target addr mismatch: want %x, got %x", addr2, *nafrm.TargetAddr())
|
||||
}
|
||||
_, solicited, _ := nafrm.Flags()
|
||||
if !solicited {
|
||||
t.Error("expected solicited flag set in NA")
|
||||
}
|
||||
|
||||
// Double-tap: h2 should have nothing left to send.
|
||||
n2, err := h2.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil || n2 > 0 {
|
||||
t.Fatal("double tap: expected no more data from h2:", err, n2)
|
||||
}
|
||||
|
||||
// h1 receives the NA and resolves h2's MAC.
|
||||
if err = h1.Demux(buf[:n], 0); err != nil {
|
||||
t.Fatal("h1 demux NA:", err)
|
||||
}
|
||||
mac, err := h1.NDPCacheLookup(addr2)
|
||||
if err != nil {
|
||||
t.Fatal("cache lookup after resolution:", err)
|
||||
}
|
||||
if !bytes.Equal(mac[:], mac2[:]) {
|
||||
t.Errorf("resolved MAC mismatch: want %x, got %x", mac2, mac)
|
||||
}
|
||||
|
||||
// h1 should have nothing left to send.
|
||||
n, err = h1.Encapsulate(buf[:], -1, 0)
|
||||
if err != nil || n > 0 {
|
||||
t.Fatal("expected no data after completion:", err, n)
|
||||
}
|
||||
}
|
||||
|
||||
func validateNDP(t *testing.T, buf []byte, wantType Type) {
|
||||
t.Helper()
|
||||
if len(buf) < sizeNDP {
|
||||
t.Errorf("NDP frame too short: %d < %d", len(buf), sizeNDP)
|
||||
return
|
||||
}
|
||||
ifrm, err := NewFrame(buf)
|
||||
if err != nil {
|
||||
t.Error("NewFrame:", err)
|
||||
return
|
||||
}
|
||||
if ifrm.Type() != wantType {
|
||||
t.Errorf("type mismatch: want %s, got %s", wantType, ifrm.Type())
|
||||
}
|
||||
if ifrm.Code() != 0 {
|
||||
t.Errorf("code must be zero, got %d", ifrm.Code())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Code generated by "stringer -type=Type,CodeDestinationUnreachable,CodeParameterProblem -linecomment -output stringers.go"; DO NOT EDIT.
|
||||
|
||||
package icmpv6
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TypeDestinationUnreachable-1]
|
||||
_ = x[TypePacketTooBig-2]
|
||||
_ = x[TypeTimeExceeded-3]
|
||||
_ = x[TypeParameterProblem-4]
|
||||
_ = x[TypeEchoRequest-128]
|
||||
_ = x[TypeEchoReply-129]
|
||||
_ = x[TypeRouterSolicitation-133]
|
||||
_ = x[TypeRouterAdvertisement-134]
|
||||
_ = x[TypeNeighborSolicitation-135]
|
||||
_ = x[TypeNeighborAdvertisement-136]
|
||||
_ = x[TypeRedirectMessage-137]
|
||||
}
|
||||
|
||||
const (
|
||||
_Type_name_0 = "destination unreachablepacket too bigtime exceededparameter problem"
|
||||
_Type_name_1 = "echo requestecho reply"
|
||||
_Type_name_2 = "router solicitationrouter advertisementneighbor solicitationneighbor advertisementredirect message"
|
||||
)
|
||||
|
||||
var (
|
||||
_Type_index_0 = [...]uint8{0, 23, 37, 50, 67}
|
||||
_Type_index_1 = [...]uint8{0, 12, 22}
|
||||
_Type_index_2 = [...]uint8{0, 19, 39, 60, 82, 98}
|
||||
)
|
||||
|
||||
func (i Type) String() string {
|
||||
switch {
|
||||
case 1 <= i && i <= 4:
|
||||
i -= 1
|
||||
return _Type_name_0[_Type_index_0[i]:_Type_index_0[i+1]]
|
||||
case 128 <= i && i <= 129:
|
||||
i -= 128
|
||||
return _Type_name_1[_Type_index_1[i]:_Type_index_1[i+1]]
|
||||
case 133 <= i && i <= 137:
|
||||
i -= 133
|
||||
return _Type_name_2[_Type_index_2[i]:_Type_index_2[i+1]]
|
||||
default:
|
||||
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[CodeNoRoute-0]
|
||||
_ = x[CodeAdminProhibited-1]
|
||||
_ = x[CodeBeyondScope-2]
|
||||
_ = x[CodeAddressUnreachable-3]
|
||||
_ = x[CodePortUnreachable-4]
|
||||
_ = x[CodeIngressEgressPolicy-5]
|
||||
_ = x[CodeRejectRoute-6]
|
||||
}
|
||||
|
||||
const _CodeDestinationUnreachable_name = "no route to destinationcommunication administratively prohibitedbeyond scope of source addressaddress unreachableport unreachablesource address failed ingress/egress policyreject route to destination"
|
||||
|
||||
var _CodeDestinationUnreachable_index = [...]uint8{0, 23, 64, 94, 113, 129, 172, 199}
|
||||
|
||||
func (i CodeDestinationUnreachable) String() string {
|
||||
if i >= CodeDestinationUnreachable(len(_CodeDestinationUnreachable_index)-1) {
|
||||
return "CodeDestinationUnreachable(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CodeDestinationUnreachable_name[_CodeDestinationUnreachable_index[i]:_CodeDestinationUnreachable_index[i+1]]
|
||||
}
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[CodeErroneousHeaderField-0]
|
||||
_ = x[CodeUnrecognizedNextHeader-1]
|
||||
_ = x[CodeUnrecognizedIPv6Option-2]
|
||||
}
|
||||
|
||||
const _CodeParameterProblem_name = "erroneous header field encounteredunrecognized next header type encounteredunrecognized IPv6 option encountered"
|
||||
|
||||
var _CodeParameterProblem_index = [...]uint8{0, 34, 75, 111}
|
||||
|
||||
func (i CodeParameterProblem) String() string {
|
||||
if i >= CodeParameterProblem(len(_CodeParameterProblem_index)-1) {
|
||||
return "CodeParameterProblem(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _CodeParameterProblem_name[_CodeParameterProblem_index[i]:_CodeParameterProblem_index[i+1]]
|
||||
}
|
||||
Reference in New Issue
Block a user