Add tcp.Listener (#15)

* begin reviewing TCP listener

* begin adding listener tests

* adding TestExchange implementation

* split out legacy and Step tests

* move listener to tcp package

* fix up tcp tests taking very long

* add xnet.TCPPool and work on TCPPool semantics

* xnet: TCPPool only accepts established connections

* xnet: getting to bottom of panic in xnet.Listener

* xnet: improve listener tests

* fix several data races in testing fixtures

* fix more synchronization things in listener test

* finalize tcplistener test
This commit is contained in:
Pat Whittingslow
2026-01-03 12:41:57 -03:00
committed by GitHub
parent 5731a378dd
commit 018f9258ac
19 changed files with 2288 additions and 849 deletions
+37 -25
View File
@@ -5,6 +5,7 @@ import (
"errors"
"math/rand"
"net/netip"
"sync"
"testing"
"github.com/soypat/lneto/arp"
@@ -14,6 +15,8 @@ import (
)
const (
logExchange = false
synack = tcp.FlagSYN | tcp.FlagACK
pshack = tcp.FlagPSH | tcp.FlagACK
finack = tcp.FlagFIN | tcp.FlagACK
@@ -143,12 +146,12 @@ func testerFrom(t *testing.T, mtu int) *tester {
}
type tester struct {
t *testing.T
cap pcap.PacketBreakdown
frmbuf []pcap.Frame
buf []byte
exch []tcpExpectExchange
lastSeg tcp.Segment
t *testing.T
cap pcap.PacketBreakdown
frmbuf []pcap.Frame
bufmu sync.Mutex
buf []byte
}
type tcpExpectExchange struct {
@@ -177,7 +180,7 @@ func (tst *tester) TestTCPSetupAndEstablish(svStack, clStack *StackAsync, svConn
func (tst *tester) TestTCPHandshake(stack1, stack2 *StackAsync) {
tst.t.Helper()
tst.exch = append(tst.exch[:0], []tcpExpectExchange{
exch := [...]tcpExpectExchange{
{
SourceIdx: 0,
WantFlags: tcp.FlagSYN,
@@ -194,8 +197,8 @@ func (tst *tester) TestTCPHandshake(stack1, stack2 *StackAsync) {
},
noExchange(0),
noExchange(1),
}...)
for _, wants := range tst.exch {
}
for _, wants := range exch {
tst.TCPExchange(wants, stack1, stack2)
}
}
@@ -217,7 +220,7 @@ func (tst *tester) TestTCPEstablishedSingleData(srcStack, dstStack *StackAsync,
t.Fatal(err)
}
nprev := dstConn.BufferedInput()
tst.exch = append(tst.exch[:0], []tcpExpectExchange{
exch := [...]tcpExpectExchange{
{
SourceIdx: 0,
WantFlags: pshack,
@@ -230,10 +233,12 @@ func (tst *tester) TestTCPEstablishedSingleData(srcStack, dstStack *StackAsync,
},
noExchange(0),
noExchange(1),
}...)
for _, wants := range tst.exch {
}
for _, wants := range exch {
tst.TCPExchange(wants, srcStack, dstStack)
}
tst.bufmu.Lock()
defer tst.bufmu.Unlock()
n, err := dstConn.Read(tst.buf)
if err != nil {
t.Errorf("reading back data %q on conn2: %s", sendData, err)
@@ -249,6 +254,7 @@ func (tst *tester) TestTCPEstablishedSingleData(srcStack, dstStack *StackAsync,
t.Errorf("expected to read back %q from conn, got %q", sendData, got)
}
}
setzero(tst.buf[:n])
}
@@ -263,7 +269,7 @@ func (tst *tester) TestTCPClose(stack1, stack2 *StackAsync, conn1, conn2 *tcp.Co
if err != nil {
t.Fatal(err)
}
tst.exch = append(tst.exch[:0], []tcpExpectExchange{
exch := [...]tcpExpectExchange{
{
SourceIdx: 0,
WantFlags: finack, // Closer sends FINACK
@@ -284,19 +290,22 @@ func (tst *tester) TestTCPClose(stack1, stack2 *StackAsync, conn1, conn2 *tcp.Co
},
noExchange(0),
noExchange(1),
}...)
t.Log(conn1.State().String(), conn2.State().String())
for i, exch := range tst.exch {
}
if logExchange {
t.Log(conn1.State().String(), conn2.State().String())
}
for i, exch := range exch {
failed := t.Failed()
tst.TCPExchange(exch, stack1, stack2)
seg := tst.TCPExchange(exch, stack1, stack2)
if !failed && t.Failed() {
t.Error(i, exch.SourceIdx, "close failure")
}
if exch.WantFlags == 0 {
continue
}
t.Log(i, tcp.StringExchange(tst.lastSeg, conn1.State(), conn2.State(), exch.SourceIdx != 0))
if logExchange {
t.Log(i, tcp.StringExchange(seg, conn1.State(), conn2.State(), exch.SourceIdx != 0))
}
}
state1 := conn1.State()
@@ -315,8 +324,9 @@ func (tst *tester) TestTCPClose(stack1, stack2 *StackAsync, conn1, conn2 *tcp.Co
}
}
func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAsync) {
tst.lastSeg = tcp.Segment{}
func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAsync) tcp.Segment {
tst.bufmu.Lock()
defer tst.bufmu.Unlock()
var src, dst *StackAsync
defer func(failed bool) {
if !failed && tst.t.Failed() {
@@ -328,7 +338,6 @@ func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAs
t.Helper()
buf := tst.buf[:cap(tst.buf)]
nodata := expect.WantFlags == 0
switch expect.SourceIdx {
case 0:
src, dst = stack1, stack2
@@ -337,17 +346,18 @@ func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAs
default:
panic("OOB")
}
n, err := src.Encapsulate(buf[:], -1, 0)
if err != nil {
t.Fatal(err)
} else if n == 0 {
if nodata {
return // No data sent and no data expected.
return tcp.Segment{} // No data sent and no data expected.
}
t.Error("zero bits sent")
} else if nodata && n > 0 {
t.Error("expected no data sent and got data")
return
return tcp.Segment{}
}
defer setzero(buf[:n])
@@ -379,7 +389,6 @@ func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAs
payload := tfrm.Payload()
seg := tfrm.Segment(len(payload))
tst.lastSeg = seg
if !bytes.Equal(payload, expect.WantData) {
t.Errorf("mismatched data sent, \nwant=%q\ngot=%q\n", expect.WantData, payload)
}
@@ -390,11 +399,14 @@ func (tst *tester) TCPExchange(expect tcpExpectExchange, stack1, stack2 *StackAs
if err != nil {
t.Fatal(err)
}
return seg
}
func (tst *tester) ARPExchangeOnly(querying, target *StackAsync) {
t := tst.t
t.Helper()
tst.bufmu.Lock()
defer tst.bufmu.Unlock()
buf := tst.buf[:cap(tst.buf)]
// === PHASE 1: ARP Request from querying stack ===