mirror of
https://github.com/soypat/lneto.git
synced 2026-09-10 08:39:30 +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:
+1
-1
@@ -164,7 +164,7 @@ func (conn *Conn) Read(b []byte) (int, error) {
|
||||
conn.mu.Unlock()
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
n, err := conn.h.Read(b)
|
||||
n, err := conn.h.ReadNext(b)
|
||||
conn.mu.Unlock()
|
||||
if n > 0 {
|
||||
return n, err
|
||||
|
||||
+3
-3
@@ -141,10 +141,10 @@ func (h *Handler) Write(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Read dequeues the next received datagram into b. If b is smaller than the
|
||||
// ReadNext dequeues the next received datagram into b. If b is smaller than the
|
||||
// datagram, the remaining bytes are discarded (SOCK_DGRAM semantics).
|
||||
// Returns 0, nil if no datagrams are available.
|
||||
func (h *Handler) Read(b []byte) (int, error) {
|
||||
func (h *Handler) ReadNext(b []byte) (int, error) {
|
||||
if len(h.rxDgrams) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
@@ -190,7 +190,7 @@ func (h *Handler) Abort() {
|
||||
}
|
||||
|
||||
// BufferedInputNext returns the size of the next datagram to read. A call
|
||||
// to [Handler.Read] will read up to this amount of bytes.
|
||||
// to [Handler.ReadNext] will read up to this amount of bytes.
|
||||
func (h *Handler) BufferedInputNext() int {
|
||||
if len(h.rxDgrams) == 0 {
|
||||
return 0
|
||||
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
package udp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
_ lneto.StackNode = (*muxHandler)(nil)
|
||||
_ lneto.StackNode = (*MuxHandlerMIMO)(nil)
|
||||
_ lneto.StackNode = (*MuxHandlerSIMO)(nil)
|
||||
)
|
||||
|
||||
// MuxHandlerSIMO is a single-input multiple-output UDP mux handler.
|
||||
// It binds to one local UDP port and multiplexes transmit/receive using that port.
|
||||
type MuxHandlerSIMO struct {
|
||||
muxHandler
|
||||
localPort uint16
|
||||
}
|
||||
|
||||
// MuxHandlerMIMO is a multi-input multi-output UDP mux handler.
|
||||
// It supports sending and receiving on multiple local UDP ports through shared state.
|
||||
type MuxHandlerMIMO struct {
|
||||
muxHandler
|
||||
}
|
||||
|
||||
// Configure initializes the SIMO handler for a single local source port.
|
||||
// The provided localPort becomes the only permitted receive port.
|
||||
func (ms *MuxHandlerSIMO) Configure(localPort uint16, cfg MuxConfig) (err error) {
|
||||
if localPort == 0 {
|
||||
return lneto.ErrZeroSource
|
||||
}
|
||||
err = ms.muxHandler.Configure(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ms.localPort = localPort
|
||||
ms.muxHandler.FilterAddLocalPort(localPort, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalPort returns the configured local UDP port for this SIMO handler.
|
||||
func (ms *MuxHandlerSIMO) LocalPort() uint16 { return ms.localPort }
|
||||
|
||||
// WriteTo queues a UDP payload for transmission from the handler's local port.
|
||||
func (ms *MuxHandlerSIMO) WriteTo(buf []byte, raddr netip.AddrPort) error {
|
||||
return ms.muxHandler.WriteTo(buf, ms.localPort, raddr)
|
||||
}
|
||||
|
||||
// ReadNext returns the next received datagram for this handler's local port.
|
||||
// If the datagram is for a different port it is discarded.
|
||||
func (ms *MuxHandlerSIMO) ReadNext(buf []byte) (n int, completeRead bool, raddr netip.AddrPort) {
|
||||
var lport uint16
|
||||
n, completeRead, lport, raddr = ms.muxHandler.ReadNext(buf)
|
||||
if lport != ms.localPort { // Can happen if user fiddles with filters.
|
||||
return 0, false, raddr
|
||||
}
|
||||
return n, completeRead, raddr
|
||||
}
|
||||
|
||||
// MuxConfig configures receive/transmit buffers and queue sizes for a UDP mux handler.
|
||||
type MuxConfig struct {
|
||||
// Configure receive buffer. If not set will use previously set buffer in [MuxHandler.Configure].
|
||||
RxBuf []byte
|
||||
// Configure transmit buffer. If not set will use previously set buffer in [MuxHandler.Configure].
|
||||
TxBuf []byte
|
||||
RxQueueSize int
|
||||
TxQueueSize int
|
||||
}
|
||||
|
||||
// muxHandler
|
||||
type muxHandler struct {
|
||||
connid uint64
|
||||
// filterLPorts stores rx port ranges over which Handler can receive data.
|
||||
// If not set will not filter UDP data.
|
||||
filterLPorts []struct {
|
||||
startPort uint16
|
||||
nports uint16 // must be at least 1 to be valid.
|
||||
}
|
||||
// filterRAddrs. If not set will receive any data.
|
||||
// filterRAddrs []struct {
|
||||
// addr netip.Prefix
|
||||
// }
|
||||
rxRing internal.Ring
|
||||
rxDgrams []struct {
|
||||
length uint16
|
||||
lport uint16
|
||||
rport uint16
|
||||
raddr netip.Addr
|
||||
}
|
||||
txRing internal.Ring
|
||||
txDgrams []struct {
|
||||
length uint16
|
||||
lport uint16
|
||||
rport uint16
|
||||
raddr netip.Addr
|
||||
}
|
||||
closeCalled bool
|
||||
}
|
||||
|
||||
// Configure initializes the handler with the given buffer and queue configuration.
|
||||
// Increments the connection ID, invalidating any prior stack registration.
|
||||
func (mh *muxHandler) Configure(cfg MuxConfig) error {
|
||||
if cfg.RxBuf == nil {
|
||||
cfg.RxBuf = mh.rxRing.Buf
|
||||
}
|
||||
if cfg.TxBuf == nil {
|
||||
cfg.TxBuf = mh.txRing.Buf
|
||||
}
|
||||
if len(cfg.RxBuf) < sizeHeader || len(cfg.TxBuf) < sizeHeader || cfg.RxQueueSize <= 0 || cfg.TxQueueSize <= 0 {
|
||||
return lneto.ErrInvalidConfig
|
||||
}
|
||||
mh.Abort()
|
||||
mh.rxRing = internal.Ring{Buf: cfg.RxBuf}
|
||||
mh.txRing = internal.Ring{Buf: cfg.TxBuf}
|
||||
internal.SliceReuse(&mh.rxDgrams, cfg.RxQueueSize)
|
||||
internal.SliceReuse(&mh.txDgrams, cfg.TxQueueSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Protocol implements [lneto.StackNode].
|
||||
func (mh *muxHandler) Protocol() uint64 { return uint64(lneto.IPProtoUDP) }
|
||||
|
||||
// ConnectionID implements [lneto.StackNode].
|
||||
func (mh *muxHandler) ConnectionID() *uint64 { return &mh.connid }
|
||||
|
||||
// LocalPort implements [lneto.StackNode] but not applicable to mux. Mux is a multi Rx/Tx port abstraction.
|
||||
func (mh *muxHandler) LocalPort() uint16 { return 0 }
|
||||
|
||||
func (mh *muxHandler) FilterResetLocalPorts() {
|
||||
mh.filterLPorts = mh.filterLPorts[:0]
|
||||
}
|
||||
|
||||
func (mh *muxHandler) FilterAddLocalPort(startPort, nports uint16) {
|
||||
if int(startPort)+int(nports) > math.MaxUint16 {
|
||||
panic("port overflow")
|
||||
}
|
||||
f := internal.SliceReclaim(&mh.filterLPorts)
|
||||
f.startPort = startPort
|
||||
f.nports = nports
|
||||
}
|
||||
|
||||
func (mh *muxHandler) FilterLocalPort(lport uint16) (filtered bool) {
|
||||
filtered = len(mh.filterLPorts) > 0
|
||||
for i := range mh.filterLPorts {
|
||||
maxPort := mh.filterLPorts[i].startPort + mh.filterLPorts[i].nports
|
||||
if lport >= mh.filterLPorts[i].startPort && lport < maxPort {
|
||||
filtered = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// Recv parses a UDP frame from buf, validates the ports and length fields,
|
||||
// and enqueues the payload into the rx ring buffer. Returns [lneto.ErrMismatch]
|
||||
// if source/destination ports don't match the configured ports.
|
||||
func (mh *muxHandler) Demux(carrierData []byte, frameOffset int) error {
|
||||
if mh.closeCalled {
|
||||
return net.ErrClosed
|
||||
}
|
||||
ufrm, err := NewFrame(carrierData[frameOffset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Rx port filter.
|
||||
lport := ufrm.DestinationPort()
|
||||
if mh.FilterLocalPort(lport) {
|
||||
return lneto.ErrMismatch
|
||||
}
|
||||
|
||||
// Header size validation.
|
||||
// No CRC validation at this level.
|
||||
ul := ufrm.Length()
|
||||
if ul < sizeHeader {
|
||||
return lneto.ErrInvalidLengthField
|
||||
} else if int(ul) > len(ufrm.RawData()) {
|
||||
return lneto.ErrTruncatedFrame
|
||||
}
|
||||
|
||||
free := cap(mh.rxDgrams) - len(mh.rxDgrams)
|
||||
if free == 0 {
|
||||
return lneto.ErrExhausted
|
||||
}
|
||||
|
||||
payload := ufrm.Payload()
|
||||
_, err = mh.rxRing.Write(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dgram := internal.SliceReclaim(&mh.rxDgrams)
|
||||
dgram.length = uint16(len(payload))
|
||||
dgram.lport = lport
|
||||
dgram.rport = ufrm.SourcePort()
|
||||
if frameOffset >= 20 {
|
||||
src, _, _, _, _ := internal.GetIPAddr(carrierData)
|
||||
dgram.raddr, _ = netip.AddrFromSlice(src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mh *muxHandler) Encapsulate(carrierData []byte, ipOffset, frameOffset int) (int, error) {
|
||||
if mh.closeCalled {
|
||||
return 0, net.ErrClosed
|
||||
} else if len(mh.txDgrams) == 0 {
|
||||
return 0, nil // No data to send.
|
||||
}
|
||||
buf := carrierData[frameOffset:]
|
||||
ufrm, err := NewFrame(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
dgram := internal.SliceDequeueFront(&mh.txDgrams)
|
||||
avail := len(buf) - 8
|
||||
if avail < int(dgram.length) {
|
||||
// TODO(soypat): If packet is too long we discard it entirely. Maybe we prefer sending incomplete data? How do other stacks deal with this?
|
||||
mh.txRing.ReadDiscard(int(dgram.length))
|
||||
return 0, lneto.ErrShortBuffer
|
||||
}
|
||||
|
||||
n, err := mh.txRing.Read(buf[8 : 8+dgram.length])
|
||||
if err != nil || n != int(dgram.length) {
|
||||
panic(fmt.Sprintf("udp send handler failure %d %s", n, err))
|
||||
}
|
||||
ufrm.SetSourcePort(dgram.lport)
|
||||
ufrm.SetDestinationPort(dgram.rport)
|
||||
ufrm.SetLength(8 + dgram.length)
|
||||
if ipOffset >= 0 && dgram.raddr.IsValid() {
|
||||
// Address write. Version check.
|
||||
var addroffset int
|
||||
switch carrierData[ipOffset] >> 4 {
|
||||
case 4:
|
||||
if !dgram.raddr.Is4() {
|
||||
return 0, lneto.ErrUnsupported
|
||||
}
|
||||
addroffset = ipOffset + 16
|
||||
|
||||
case 6:
|
||||
if !dgram.raddr.Is6() {
|
||||
return 0, lneto.ErrUnsupported
|
||||
}
|
||||
addroffset = ipOffset + 24
|
||||
default:
|
||||
return 0, lneto.ErrUnsupported
|
||||
}
|
||||
dgram.raddr.AppendBinary(carrierData[addroffset:addroffset])
|
||||
}
|
||||
return int(8 + dgram.length), nil
|
||||
}
|
||||
|
||||
func (mh *muxHandler) WriteTo(buf []byte, lport uint16, raddr netip.AddrPort) error {
|
||||
if mh.closeCalled {
|
||||
return net.ErrClosed
|
||||
} else if raddr.Port() == 0 || !raddr.IsValid() {
|
||||
return lneto.ErrZeroDestination
|
||||
} else if lport == 0 {
|
||||
return lneto.ErrZeroSource
|
||||
}
|
||||
avail := cap(mh.txDgrams) - len(mh.txDgrams)
|
||||
if avail == 0 {
|
||||
return lneto.ErrExhausted
|
||||
} else if mh.txRing.Free() < len(buf) {
|
||||
return lneto.ErrBufferFull
|
||||
}
|
||||
n, err := mh.txRing.Write(buf)
|
||||
if err != nil || n != len(buf) {
|
||||
return lneto.ErrBug
|
||||
}
|
||||
dgram := internal.SliceReclaim(&mh.txDgrams)
|
||||
dgram.length = uint16(len(buf))
|
||||
dgram.raddr = raddr.Addr()
|
||||
dgram.lport = lport
|
||||
dgram.rport = raddr.Port()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadNext dequeues the next received datagram into b. If b is smaller than the
|
||||
// datagram, the remaining bytes are discarded (SOCK_DGRAM semantics).
|
||||
// The port the datagram was destined to and address it was received from are returned.
|
||||
// If bytes are discarded completeRead=false.
|
||||
func (mh *muxHandler) ReadNext(buf []byte) (n int, completeRead bool, lport uint16, raddr netip.AddrPort) {
|
||||
if len(mh.rxDgrams) == 0 {
|
||||
return 0, false, 0, raddr
|
||||
}
|
||||
dgram := internal.SliceDequeueFront(&mh.rxDgrams)
|
||||
dlen := int(dgram.length)
|
||||
maxRead := min(dlen, len(buf))
|
||||
n, _ = mh.rxRing.Read(buf[:maxRead])
|
||||
if n < dlen {
|
||||
mh.rxRing.ReadDiscard(dlen - n)
|
||||
}
|
||||
return n, n == dlen, dgram.lport, netip.AddrPortFrom(dgram.raddr, dgram.rport)
|
||||
}
|
||||
|
||||
// BufferedInputNext returns the size of the next datagram to read. A call
|
||||
// to [Handler.ReadNext] will read up to this amount of bytes.
|
||||
func (mh *muxHandler) BufferedInputNext() uint16 {
|
||||
if len(mh.rxDgrams) > 0 {
|
||||
return mh.rxDgrams[0].length
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// BufferedInput returns the number of unread bytes in the receive buffer.
|
||||
func (h *muxHandler) BufferedInput() int {
|
||||
return h.rxRing.Buffered()
|
||||
}
|
||||
|
||||
// BufferedUnsent returns the number of written but unsent bytes in the transmit buffer.
|
||||
func (h *muxHandler) BufferedOutput() int {
|
||||
return h.txRing.Buffered()
|
||||
}
|
||||
|
||||
// SizeInput returns the total size of the receive ring buffer.
|
||||
func (h *muxHandler) SizeInput() int {
|
||||
return h.rxRing.Size()
|
||||
}
|
||||
|
||||
// SizeOutput returns the total size of the transmit ring buffer.
|
||||
func (h *muxHandler) SizeOutput() int {
|
||||
return h.txRing.Size()
|
||||
}
|
||||
|
||||
// FreeOutput returns the number of free bytes in the transmit buffer.
|
||||
// This tells the user how many bytes can be written with Write method before write failing.
|
||||
func (h *muxHandler) FreeOutput() int {
|
||||
return h.txRing.Free()
|
||||
}
|
||||
|
||||
// FreeInput returns the number of free bytes in the receive buffer.
|
||||
func (h *muxHandler) FreeInput() int {
|
||||
return h.rxRing.Free()
|
||||
}
|
||||
|
||||
func (mh *muxHandler) IsOpen() bool {
|
||||
return cap(mh.rxDgrams) > 0 && !mh.closeCalled
|
||||
}
|
||||
|
||||
func (mh *muxHandler) Close() {
|
||||
mh.closeCalled = true
|
||||
}
|
||||
|
||||
func (mh *muxHandler) Abort() {
|
||||
*mh = muxHandler{
|
||||
connid: mh.connid + 1,
|
||||
filterLPorts: mh.filterLPorts[:0],
|
||||
rxRing: mh.rxRing,
|
||||
rxDgrams: mh.rxDgrams[:0],
|
||||
txRing: mh.txRing,
|
||||
txDgrams: mh.txDgrams[:0],
|
||||
}
|
||||
mh.rxRing.Reset()
|
||||
mh.txRing.Reset()
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user