From 86fc8fdaf5e7716024c872edf8f80d7df836899e Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Fri, 17 Oct 2025 23:36:48 -0300 Subject: [PATCH] refactor stack TCP stuff; begin adding TCP test --- x/xnet/stack-async.go | 86 +++++++++++----------------------------- x/xnet/stack-blocking.go | 15 +++---- x/xnet/stack-retrying.go | 10 ++--- x/xnet/xnet_test.go | 67 +++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 75 deletions(-) create mode 100644 x/xnet/xnet_test.go diff --git a/x/xnet/stack-async.go b/x/xnet/stack-async.go index a520fd3..946abd8 100644 --- a/x/xnet/stack-async.go +++ b/x/xnet/stack-async.go @@ -2,7 +2,6 @@ package xnet import ( "errors" - "io" "net/netip" "sync" "time" @@ -30,7 +29,6 @@ type StackAsync struct { arp arp.Handler udps internet.StackPorts tcps internet.StackPorts - tcpconns []tcp.Conn dhcpUDP internet.StackUDPPort dhcp dhcpv4.Client @@ -59,8 +57,6 @@ type StackConfig struct { NTPServer netip.Addr Hostname string MaxTCPConns int - TCPBufferSizeTx int // Size of transmit buffer for TCP connections. - TCPBufferSizeRx int // Size of receive buffer for TCP connections. RandSeed int64 HardwareAddress [6]byte MTU uint16 @@ -127,44 +123,14 @@ func (s *StackAsync) Reset(cfg StackConfig) error { } // Enable TCP if connections present. - if cfg.MaxTCPConns > 0 { - if cfg.TCPBufferSizeRx < minTCPBuffer || cfg.TCPBufferSizeTx < minTCPBuffer { - return io.ErrShortBuffer - } - if cap(s.tcpconns) < cfg.MaxTCPConns { - s.tcpconns = make([]tcp.Conn, cfg.MaxTCPConns) - } - s.tcpconns = s.tcpconns[:cfg.MaxTCPConns] - for i := range s.tcpconns { - c := &s.tcpconns[i] - c.Abort() // Abort forcibly closes connection to allow buffer setting and reduce risk of race conditions. - h := c.InternalHandler() - sizetx := h.FreeTx() - sizerx := h.SizeRx() - // If size is sufficient then buffers will remain nil and memory will be reused. - var rxbuf, txbuf []byte - if sizetx < cfg.TCPBufferSizeTx || sizerx < cfg.TCPBufferSizeRx { - buf := make([]byte, cfg.TCPBufferSizeTx+cfg.TCPBufferSizeRx) - rxbuf = buf[:cfg.TCPBufferSizeRx] - txbuf = buf[cfg.TCPBufferSizeRx : cfg.TCPBufferSizeRx+cfg.TCPBufferSizeTx] - } - err = c.Configure(tcp.ConnConfig{ - RxBuf: rxbuf, - TxBuf: txbuf, - TxPacketQueueSize: 4, - }) - if err != nil { - return err - } - } - err = s.tcps.ResetTCP(cfg.MaxTCPConns) - if err != nil { - return err - } - err = s.ip.Register(&s.tcps) - if err != nil { - return err - } + + err = s.tcps.ResetTCP(cfg.MaxTCPConns) + if err != nil { + return err + } + err = s.ip.Register(&s.tcps) + if err != nil { + return err } // Now setup stacks. @@ -231,6 +197,12 @@ func (s *StackAsync) SetIPAddr(addr netip.Addr) error { return s.resetARP() } +func (s *StackAsync) Addr() netip.Addr { + s.mu.Lock() + defer s.mu.Unlock() + return s.ip.Addr() +} + func (s *StackAsync) SetHardwareAddress(hw [6]byte) error { s.mu.Lock() defer s.mu.Unlock() @@ -244,38 +216,26 @@ func (s *StackAsync) SetGateway6(gwhw [6]byte) { s.link.SetGateway6(gwhw) } -var ( - errNoTCP = errors.New("no TCP initialized") - errNoTCPConnsAvail = errors.New("all allocated TCP connections busy") -) +func (s *StackAsync) start() { -func (s *StackAsync) DialTCP(localPort uint16, addrp netip.AddrPort) (conn *tcp.Conn, err error) { - if len(s.tcpconns) == 0 { - return nil, errNoTCP - } - for i := range s.tcpconns { - maybeFreeConn := &s.tcpconns[i] - state := maybeFreeConn.State() - if state.IsClosed() { - conn = maybeFreeConn - break // Can be used! - } - } - if conn == nil { - return nil, errNoTCPConnsAvail +} + +func (s *StackAsync) DialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrPort) (err error) { + if !conn.State().IsClosed() { + return errors.New("conn not closed") } conn.Abort() // Conn is closed, safe to abort. err = conn.OpenActive(localPort, addrp, tcp.Value(s.Prand32())) if err != nil { conn.Abort() - return nil, err + return err } err = s.tcps.Register(conn) if err != nil { conn.Abort() - return nil, err + return err } - return conn, nil + return nil } var errNoDNSServer = errors.New("no DNS server- did DHCP complete? You can set a predetermined DNS server in Stack configuration") diff --git a/x/xnet/stack-blocking.go b/x/xnet/stack-blocking.go index b31f44b..0afee0a 100644 --- a/x/xnet/stack-blocking.go +++ b/x/xnet/stack-blocking.go @@ -113,29 +113,30 @@ func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []n var errTCPFailedToConnect = errors.New("tcp failed to connect") -func (s StackBlocking) DoDialTCP(localPort uint16, addrp netip.AddrPort, timeout time.Duration) (conn *tcp.Conn, err error) { - conn, err = s.async.DialTCP(localPort, addrp) +func (s StackBlocking) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrPort, timeout time.Duration) (err error) { + err = s.async.DialTCP(conn, localPort, addrp) if err != nil { - return nil, err + return err } sleep := timeout/maxIter + 1 deadline := time.Now().Add(timeout) for i := 0; i < maxIter; i++ { state := conn.State() if state == tcp.StateEstablished { - return conn, nil + return nil } else if state == tcp.StateSynSent || state == tcp.StateSynRcvd || conn.InternalHandler().AwaitingSynSend() { if err = s.checkDeadline(deadline); err != nil { - return nil, err + conn.Abort() + return err } time.Sleep(sleep) } else { // Unexpected state, abort and terminate connection. conn.Abort() - return nil, errTCPFailedToConnect + return errTCPFailedToConnect } } - return conn, errDeadlineExceed + return errDeadlineExceed } func (s StackBlocking) checkDeadline(deadline time.Time) error { diff --git a/x/xnet/stack-retrying.go b/x/xnet/stack-retrying.go index 4866a61..52e17e7 100644 --- a/x/xnet/stack-retrying.go +++ b/x/xnet/stack-retrying.go @@ -83,16 +83,16 @@ func (s StackRetrying) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D return hw, errRetriesExceeded } -func (s StackRetrying) DoDialTCP(localPort uint16, addrp netip.AddrPort, timeout time.Duration, retries int) (conn *tcp.Conn, err error) { +func (s StackRetrying) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrPort, timeout time.Duration, retries int) (err error) { expectEnd := time.Now().Add(timeout * time.Duration(retries)) for i := 0; i < retries; i++ { - conn, err = s.block.DoDialTCP(localPort, addrp, timeout) + err = s.block.DoDialTCP(conn, localPort, addrp, timeout) if err == nil { - return conn, nil + return nil } } if time.Now().Before(expectEnd) { - return conn, err + return err } - return nil, errRetriesExceeded + return errRetriesExceeded } diff --git a/x/xnet/xnet_test.go b/x/xnet/xnet_test.go new file mode 100644 index 0000000..84b9efa --- /dev/null +++ b/x/xnet/xnet_test.go @@ -0,0 +1,67 @@ +package xnet + +import ( + "net/netip" + "testing" + + "github.com/soypat/lneto/tcp" +) + +func TestABC(t *testing.T) { + const seed = 1234 + const MTU = 1500 + var mac = [6]byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x01} + var client StackAsync + err := client.Reset(StackConfig{ + StaticAddress: netip.MustParseAddr("10.0.0.1"), + MaxTCPConns: 1, + MTU: MTU, + HardwareAddress: mac, + Hostname: "client", + RandSeed: seed, + }) + if err != nil { + t.Fatal(err) + } + + var macsv = [6]byte{0x02, 0x00, 0x00, 0x00, 0x00, 0x01} + var sv StackAsync + err = sv.Reset(StackConfig{ + StaticAddress: netip.MustParseAddr("10.0.0.2"), + MaxTCPConns: 1, + MTU: MTU, + HardwareAddress: macsv, + Hostname: "server", + RandSeed: seed, + }) + if err != nil { + t.Fatal(err) + } + // IMG_1084.MOV + + const svPort = 80 + var clconn tcp.Conn + err = clconn.Configure(tcp.ConnConfig{ + RxBuf: make([]byte, MTU), + TxBuf: make([]byte, MTU), + TxPacketQueueSize: 4, + }) + if err != nil { + t.Fatal(err) + } + err = client.DialTCP(&clconn, 1337, netip.AddrPortFrom(sv.Addr(), svPort)) + if err != nil { + t.Fatal(err) + } + + var svconn tcp.Conn + err = svconn.Configure(tcp.ConnConfig{ + RxBuf: make([]byte, MTU), + TxBuf: make([]byte, MTU), + TxPacketQueueSize: 4, + }) + if err != nil { + t.Fatal(err) + } + // clconn.OpenListen() +}