Files
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

230 lines
6.1 KiB
Go

package udp
import (
"net/netip"
"testing"
"github.com/soypat/lneto"
"github.com/soypat/lneto/internal"
)
func newTestSIMO(t *testing.T, localPort uint16) *MuxHandlerSIMO {
t.Helper()
var ms MuxHandlerSIMO
err := ms.Configure(localPort, MuxConfig{
RxBuf: make([]byte, 256),
TxBuf: make([]byte, 256),
RxQueueSize: 4,
TxQueueSize: 4,
})
if err != nil {
t.Fatal(err)
}
return &ms
}
func TestMuxSIMO_Configure_ZeroPort(t *testing.T) {
var ms MuxHandlerSIMO
err := ms.Configure(0, MuxConfig{
RxBuf: make([]byte, 256),
TxBuf: make([]byte, 256),
RxQueueSize: 4,
TxQueueSize: 4,
})
if err != lneto.ErrZeroSource {
t.Fatalf("want ErrZeroSource, got %v", err)
}
}
func TestMuxSIMO_LocalPort(t *testing.T) {
ms := newTestSIMO(t, 1234)
if ms.LocalPort() != 1234 {
t.Fatalf("want 1234, got %d", ms.LocalPort())
}
}
func TestMuxSIMO_WriteToEncapsulateRoundtrip(t *testing.T) {
const localPort = 1234
raddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 8080)
ms := newTestSIMO(t, localPort)
payload := []byte("hello mux")
if err := ms.WriteTo(payload, raddr); err != nil {
t.Fatal(err)
}
var buf [128]byte
n, err := ms.Encapsulate(buf[:], -1, 0)
if err != nil {
t.Fatal(err)
}
if want := 8 + len(payload); n != want {
t.Fatalf("encapsulated %d bytes, want %d", n, want)
}
ufrm, err := NewFrame(buf[:n])
if err != nil {
t.Fatal(err)
}
if ufrm.SourcePort() != localPort {
t.Fatalf("src port %d, want %d", ufrm.SourcePort(), localPort)
}
if ufrm.DestinationPort() != raddr.Port() {
t.Fatalf("dst port %d, want %d", ufrm.DestinationPort(), raddr.Port())
}
if !internal.BytesEqual(ufrm.Payload(), payload) {
t.Fatal("payload mismatch")
}
// No more pending.
n, err = ms.Encapsulate(buf[:], -1, 0)
if err != nil || n != 0 {
t.Fatalf("expected empty encapsulate, got n=%d err=%v", n, err)
}
}
func TestMuxSIMO_DemuxReadNextRoundtrip(t *testing.T) {
const localPort = 1234
const remotePort = 8080
ms := newTestSIMO(t, localPort)
payload := []byte("incoming")
frame := makeUDPFrame(remotePort, localPort, payload)
if err := ms.Demux(frame, 0); err != nil {
t.Fatal(err)
}
var buf [64]byte
n, completeRead, raddr := ms.ReadNext(buf[:])
if n != len(payload) {
t.Fatalf("read %d bytes, want %d", n, len(payload))
}
if !completeRead {
t.Fatal("want completeRead=true")
}
if !internal.BytesEqual(buf[:n], payload) {
t.Fatal("payload mismatch")
}
if raddr.IsValid() {
t.Fatal("want zero raddr when no IP carrier (frameOffset=0 < 20)")
}
}
func TestMuxSIMO_DemuxFiltersMismatch(t *testing.T) {
ms := newTestSIMO(t, 1234)
frame := makeUDPFrame(8080, 9999, []byte("wrong port"))
err := ms.Demux(frame, 0)
if err != lneto.ErrMismatch {
t.Fatalf("want ErrMismatch, got %v", err)
}
}
func TestMuxSIMO_ReadNextTruncates(t *testing.T) {
const localPort = 1234
ms := newTestSIMO(t, localPort)
payload := []byte("toolongpayload")
if err := ms.Demux(makeUDPFrame(8080, localPort, payload), 0); err != nil {
t.Fatal(err)
}
var small [4]byte
n, completeRead, _ := ms.ReadNext(small[:])
if n != 4 {
t.Fatalf("read %d, want 4", n)
}
if completeRead {
t.Fatal("want completeRead=false on truncation")
}
if !internal.BytesEqual(small[:], payload[:4]) {
t.Fatal("truncated data mismatch")
}
// Next datagram should work cleanly after truncation.
payload2 := []byte("ok")
if err := ms.Demux(makeUDPFrame(8080, localPort, payload2), 0); err != nil {
t.Fatal(err)
}
var buf [64]byte
n, completeRead, _ = ms.ReadNext(buf[:])
if !completeRead || !internal.BytesEqual(buf[:n], payload2) {
t.Fatal("post-truncation read mismatch")
}
}
func TestMuxSIMO_MultipleDatagrams(t *testing.T) {
const localPort = 5000
ms := newTestSIMO(t, localPort)
type send struct {
payload string
raddr netip.AddrPort
}
sends := []send{
{"alpha", netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 0, 0, 1}), 100)},
{"beta", netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 0, 0, 2}), 200)},
{"gamma", netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 0, 0, 3}), 300)},
}
for _, s := range sends {
if err := ms.WriteTo([]byte(s.payload), s.raddr); err != nil {
t.Fatal(err)
}
}
var buf [128]byte
for _, s := range sends {
n, err := ms.Encapsulate(buf[:], -1, 0)
if err != nil {
t.Fatal(err)
}
ufrm, err := NewFrame(buf[:n])
if err != nil {
t.Fatal(err)
}
if got := string(ufrm.Payload()); got != s.payload {
t.Fatalf("payload %q, want %q", got, s.payload)
}
if ufrm.DestinationPort() != s.raddr.Port() {
t.Fatalf("dst port %d, want %d", ufrm.DestinationPort(), s.raddr.Port())
}
}
}
func TestMuxSIMO_TxQueueExhausted(t *testing.T) {
ms := newTestSIMO(t, 1234) // queue size 4
raddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 9000)
for i := range 4 {
if err := ms.WriteTo([]byte{byte(i)}, raddr); err != nil {
t.Fatalf("WriteTo %d: %v", i, err)
}
}
if err := ms.WriteTo([]byte{0xff}, raddr); err != lneto.ErrExhausted {
t.Fatalf("want ErrExhausted, got %v", err)
}
}
func TestMuxSIMO_RxQueueExhausted(t *testing.T) {
const localPort = 1234
ms := newTestSIMO(t, localPort) // queue size 4
for i := range 4 {
frame := makeUDPFrame(8080, localPort, []byte{byte(i)})
if err := ms.Demux(frame, 0); err != nil {
t.Fatalf("Demux %d: %v", i, err)
}
}
frame := makeUDPFrame(8080, localPort, []byte{0xff})
if err := ms.Demux(frame, 0); err != lneto.ErrExhausted {
t.Fatalf("want ErrExhausted, got %v", err)
}
}
func TestMuxSIMO_ClosedBehavior(t *testing.T) {
ms := newTestSIMO(t, 1234)
ms.Close()
raddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 9000)
if err := ms.WriteTo([]byte("data"), raddr); err == nil {
t.Fatal("expected error writing to closed handler")
}
frame := makeUDPFrame(8080, 1234, []byte("data"))
if err := ms.Demux(frame, 0); err == nil {
t.Fatal("expected error demuxing to closed handler")
}
}
func TestMuxSIMO_WriteToInvalidRaddr(t *testing.T) {
ms := newTestSIMO(t, 1234)
zeroPort := netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 0)
if err := ms.WriteTo([]byte("data"), zeroPort); err != lneto.ErrZeroDestination {
t.Fatalf("want ErrZeroDestination, got %v", err)
}
}