changes to TCP stack to enable xnet functionality

This commit is contained in:
soypat
2025-08-16 15:17:46 -03:00
parent c2995383da
commit 9cf4e35d2e
10 changed files with 124 additions and 19 deletions
+2 -2
View File
@@ -249,7 +249,7 @@ func (stack *Stack) OpenTCPListener(port uint16) (*internet.NodeTCPListener, err
func (stack *Stack) OpenPassiveTCP(port uint16, iss tcp.Value) (*tcp.Conn, error) { func (stack *Stack) OpenPassiveTCP(port uint16, iss tcp.Value) (*tcp.Conn, error) {
mtu := stack.ethernet.MTU() mtu := stack.ethernet.MTU()
conn := new(tcp.Conn) conn := new(tcp.Conn)
err := conn.Configure(&tcp.ConnConfig{ err := conn.Configure(tcp.ConnConfig{
RxBuf: make([]byte, mtu), RxBuf: make([]byte, mtu),
TxBuf: make([]byte, mtu), TxBuf: make([]byte, mtu),
TxPacketQueueSize: 3, TxPacketQueueSize: 3,
@@ -296,7 +296,7 @@ func (naiveTCPPool) GetTCP() (*tcp.Conn, tcp.Value) {
rand.Read(buf[:]) rand.Read(buf[:])
randVal := binary.LittleEndian.Uint32(buf[:]) randVal := binary.LittleEndian.Uint32(buf[:])
var conn tcp.Conn var conn tcp.Conn
err := conn.Configure(&tcp.ConnConfig{ err := conn.Configure(tcp.ConnConfig{
RxBuf: make([]byte, 1024), RxBuf: make([]byte, 1024),
TxBuf: make([]byte, 1024), TxBuf: make([]byte, 1024),
TxPacketQueueSize: 3, TxPacketQueueSize: 3,
+1 -1
View File
@@ -182,7 +182,7 @@ func NewEthernetTCPStack(ourMAC, gwMAC [6]byte, ip netip.AddrPort, mtu uint16, s
lport: 0, lport: 0,
}) })
var conn tcp.Conn var conn tcp.Conn
err = conn.Configure(&tcp.ConnConfig{ err = conn.Configure(tcp.ConnConfig{
RxBuf: make([]byte, mtu), RxBuf: make([]byte, mtu),
TxBuf: make([]byte, mtu), TxBuf: make([]byte, mtu),
TxPacketQueueSize: 3, TxPacketQueueSize: 3,
+1
View File
@@ -32,6 +32,7 @@ func run() error {
flagInterface = "tap0" flagInterface = "tap0"
) )
flag.StringVar(&flagInterface, "i", flagInterface, "Interface to select. tap* creates a tap interface. Any other name will create a bridge to the name of the interface i.e: 'enp7s0', 'wlp8s0', 'lo'") flag.StringVar(&flagInterface, "i", flagInterface, "Interface to select. tap* creates a tap interface. Any other name will create a bridge to the name of the interface i.e: 'enp7s0', 'wlp8s0', 'lo'")
flag.Parse()
var ( var (
flagNet = "192.168.10.1/24" flagNet = "192.168.10.1/24"
flagiface = "tap0" flagiface = "tap0"
+50 -1
View File
@@ -12,8 +12,10 @@ import (
"time" "time"
"github.com/soypat/lneto/dns" "github.com/soypat/lneto/dns"
"github.com/soypat/lneto/http/httpraw"
"github.com/soypat/lneto/internal" "github.com/soypat/lneto/internal"
"github.com/soypat/lneto/internal/ltesto" "github.com/soypat/lneto/internal/ltesto"
"github.com/soypat/lneto/internet/pcap"
"github.com/soypat/lneto/x/xnet" "github.com/soypat/lneto/x/xnet"
) )
@@ -35,9 +37,11 @@ func run() (err error) {
flagHostToResolve = "" flagHostToResolve = ""
flagRequestedIP = "" flagRequestedIP = ""
flagDoNTP = false flagDoNTP = false
flagHTTPGet = false
) )
flag.BoolVar(&flagHTTPGet, "httpget", flagHTTPGet, "Do an HTTP GET request ")
flag.StringVar(&flagInterface, "i", flagInterface, "Interface to use. Either tap* or the name of an existing interface to bridge to.") flag.StringVar(&flagInterface, "i", flagInterface, "Interface to use. Either tap* or the name of an existing interface to bridge to.")
flag.BoolVar(&flagUseHTTP, "http", flagUseHTTP, "Use HTTP tap interface.") flag.BoolVar(&flagUseHTTP, "ihttp", flagUseHTTP, "Use HTTP tap interface.")
flag.StringVar(&flagHostToResolve, "host", flagHostToResolve, "Hostname to resolve via DNS.") flag.StringVar(&flagHostToResolve, "host", flagHostToResolve, "Hostname to resolve via DNS.")
flag.StringVar(&flagRequestedIP, "addr", flagRequestedIP, "IP address to request via DHCP.") flag.StringVar(&flagRequestedIP, "addr", flagRequestedIP, "IP address to request via DHCP.")
flag.BoolVar(&flagDoNTP, "ntp", flagDoNTP, "Do NTP round and print result time") flag.BoolVar(&flagDoNTP, "ntp", flagDoNTP, "Do NTP round and print result time")
@@ -89,6 +93,9 @@ func run() (err error) {
RandSeed: softRand, RandSeed: softRand,
HardwareAddress: brHW, HardwareAddress: brHW,
MTU: uint16(mtu), MTU: uint16(mtu),
MaxTCPConns: 1,
TCPBufferSizeTx: 2048,
TCPBufferSizeRx: 2048,
}) })
if err != nil { if err != nil {
return err return err
@@ -97,12 +104,17 @@ func run() (err error) {
go func() { go func() {
lastAction := time.Now() lastAction := time.Now()
buf := make([]byte, mtu) buf := make([]byte, mtu)
var cap pcap.PacketBreakdown
for { for {
clear(buf) clear(buf)
nwrite, err := stack.Encapsulate(buf[:], 0) nwrite, err := stack.Encapsulate(buf[:], 0)
if err != nil { if err != nil {
fmt.Println("ERR:ENCAPSULATE", err) fmt.Println("ERR:ENCAPSULATE", err)
} else if nwrite > 0 { } else if nwrite > 0 {
frames, err := cap.CaptureEthernet(nil, buf[:nwrite], 0)
if len(frames) > 0 {
fmt.Println("OUT", frames)
}
n, err := iface.Write(buf[:nwrite]) n, err := iface.Write(buf[:nwrite])
if err != nil { if err != nil {
log.Fatal("groutine encapsulate:", err) log.Fatal("groutine encapsulate:", err)
@@ -179,6 +191,43 @@ func run() (err error) {
return fmt.Errorf("DNS of host %q failed: %w", flagHostToResolve, err) return fmt.Errorf("DNS of host %q failed: %w", flagHostToResolve, err)
} }
fmt.Printf("DNS resolution of %q complete and resolved to %v\n", flagHostToResolve, addrs) fmt.Printf("DNS resolution of %q complete and resolved to %v\n", flagHostToResolve, addrs)
if flagHTTPGet {
var hdr httpraw.Header
hdr.SetMethod("GET")
hdr.SetRequestURI("/")
hdr.SetProtocol("HTTP/1.1")
hdr.Set("Host", flagHostToResolve)
hdr.Set("User-Agent", "lneto")
hdr.Set("Accept-Language", "en-US,en;q=0.5")
req, err := hdr.AppendRequest(nil)
if err != nil {
return err
}
target := netip.AddrPortFrom(addrs[0], 80)
conn, err := rstack.DoDialTCP(uint16(softRand&0xefff)+1024, target, internetTimeout, internetRetries)
if err != nil {
return fmt.Errorf("TCP failed: %w", err)
}
conn.SetDeadline(time.Now().Add(internetTimeout))
_, err = conn.Write(req)
if err != nil {
return err
}
rxbuf := make([]byte, 2048)
var page []byte
for {
var n int
n, err = conn.Read(rxbuf)
page = append(page, rxbuf[:n]...)
if err != nil {
break
}
}
if len(page) == 0 {
return err
}
os.Stdout.Write(page)
}
return nil return nil
} }
+9
View File
@@ -93,15 +93,24 @@ func (ps *StackPorts) Demux(b []byte, offset int) (err error) {
func (ps *StackPorts) Register(h StackNode) error { func (ps *StackPorts) Register(h StackNode) error {
port := h.LocalPort() port := h.LocalPort()
proto := h.Protocol() proto := h.Protocol()
if port <= 0 { if port <= 0 {
return errZeroPort return errZeroPort
} else if proto != uint64(ps.protocol) { } else if proto != uint64(ps.protocol) {
return errInvalidProto return errInvalidProto
} }
var cid uint64
cidPtr := h.ConnectionID()
if cidPtr != nil {
cid = *cidPtr
}
return registerNode(&ps.handlers, node{ return registerNode(&ps.handlers, node{
demux: h.Demux, demux: h.Demux,
encapsulate: h.Encapsulate, encapsulate: h.Encapsulate,
port: port, port: port,
currConnID: cid,
connID: cidPtr,
proto: uint16(proto),
}) })
} }
+2 -2
View File
@@ -94,7 +94,7 @@ func setupClientServer(t *testing.T, rng *rand.Rand, client, server *StackIP, co
server.Reset(svip.Addr(), maxNodes) server.Reset(svip.Addr(), maxNodes)
client.Reset(clip.Addr(), maxNodes) client.Reset(clip.Addr(), maxNodes)
err := connServer.Configure(&tcp.ConnConfig{ err := connServer.Configure(tcp.ConnConfig{
RxBuf: make([]byte, bufsize), RxBuf: make([]byte, bufsize),
TxBuf: make([]byte, bufsize), TxBuf: make([]byte, bufsize),
TxPacketQueueSize: 3, TxPacketQueueSize: 3,
@@ -103,7 +103,7 @@ func setupClientServer(t *testing.T, rng *rand.Rand, client, server *StackIP, co
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = connClient.Configure(&tcp.ConnConfig{ err = connClient.Configure(tcp.ConnConfig{
RxBuf: make([]byte, bufsize), RxBuf: make([]byte, bufsize),
TxBuf: make([]byte, bufsize), TxBuf: make([]byte, bufsize),
TxPacketQueueSize: 3, TxPacketQueueSize: 3,
+9 -1
View File
@@ -44,7 +44,7 @@ type ConnConfig struct {
Logger *slog.Logger Logger *slog.Logger
} }
func (conn *Conn) Configure(config *ConnConfig) (err error) { func (conn *Conn) Configure(config ConnConfig) (err error) {
err = conn.h.SetBuffers(config.TxBuf, config.RxBuf, config.TxPacketQueueSize) err = conn.h.SetBuffers(config.TxBuf, config.RxBuf, config.TxPacketQueueSize)
if err != nil { if err != nil {
return err return err
@@ -109,9 +109,17 @@ func (conn *Conn) Abort() {
h: conn.h, h: conn.h,
remoteAddr: conn.remoteAddr[:0], remoteAddr: conn.remoteAddr[:0],
logger: conn.logger, logger: conn.logger,
ipID: conn.ipID,
} }
} }
// InternalHandler returns the internal [Handler] instance. The Handler contains lower level implementation logic for a TCP connection.
// Typical users should not be using this method unless implementing a stack which manages several TCP connections and thus need
// access to low level internals for careful memory management.
func (conn *Conn) InternalHandler() *Handler {
return &conn.h
}
// Write writes argument data to the TCPConns's output buffer which is queued to be sent. // Write writes argument data to the TCPConns's output buffer which is queued to be sent.
func (conn *Conn) Write(b []byte) (int, error) { func (conn *Conn) Write(b []byte) (int, error) {
err := conn.checkPipeOpen() err := conn.checkPipeOpen()
+11 -5
View File
@@ -53,16 +53,17 @@ func (h *Handler) ConnectionID() *uint64 {
func (h *Handler) State() State { return h.scb.State() } func (h *Handler) State() State { return h.scb.State() }
// SetBuffers sets the internal buffers used to receive and transmit bytes asynchronously via [Handler.Write] and [Handler.Read] calls. // SetBuffers sets the internal buffers used to receive and transmit bytes asynchronously via [Handler.Write] and [Handler.Read] calls.
// If the argument buffer is nil then the respective currently set buffer will be reused.
func (h *Handler) SetBuffers(txbuf, rxbuf []byte, packets int) error { func (h *Handler) SetBuffers(txbuf, rxbuf []byte, packets int) error {
if h.bufRx.Buf == nil && (len(rxbuf) < minBufferSize || len(txbuf) < minBufferSize) {
return errors.New("tcp: short buffer")
}
if !h.scb.State().IsClosed() { if !h.scb.State().IsClosed() {
return errors.New("tcp.Handler must be closed before setting buffers") return errors.New("tcp.Handler must be closed before setting buffers")
} }
if rxbuf != nil { if rxbuf != nil {
h.bufRx.Buf = rxbuf h.bufRx.Buf = rxbuf
} }
if len(h.bufRx.Buf) < minBufferSize {
return errors.New("short rx buffer")
}
h.scb.SetRecvWindow(Size(h.bufRx.Size())) h.scb.SetRecvWindow(Size(h.bufRx.Size()))
h.bufRx.Reset() h.bufRx.Reset()
return h.bufTx.ResetOrReuse(txbuf, packets, 0) return h.bufTx.ResetOrReuse(txbuf, packets, 0)
@@ -270,11 +271,16 @@ func (h *Handler) Send(b []byte) (int, error) {
return int(offset)*4 + int(segment.DATALEN), nil return int(offset)*4 + int(segment.DATALEN), nil
} }
// Free returns the amount of space free in the transmit buffer. A call to [Handler.Write] with a larger buffer will fail. // FreeTx returns the amount of space free in the transmit buffer. A call to [Handler.Write] with a larger buffer will fail.
func (h *Handler) Free() int { func (h *Handler) FreeTx() int {
return h.bufTx.Free() return h.bufTx.Free()
} }
// SizeRx returns the size of the TCP receive ring buffer.
func (h *Handler) SizeRx() int {
return h.bufRx.Size()
}
// Write implements [io.Writer] by copying b to a internal buffer to be sent over the network on the next // Write implements [io.Writer] by copying b to a internal buffer to be sent over the network on the next
// [Handler.Send] call that can send data to remote peer. Use [Handler.Free] to know the maximum length the argument slice can be before erroring. // [Handler.Send] call that can send data to remote peer. Use [Handler.Free] to know the maximum length the argument slice can be before erroring.
func (h *Handler) Write(b []byte) (int, error) { func (h *Handler) Write(b []byte) (int, error) {
+34 -1
View File
@@ -2,6 +2,7 @@ package xnet
import ( import (
"errors" "errors"
"io"
"net/netip" "net/netip"
"sync" "sync"
"time" "time"
@@ -16,6 +17,10 @@ import (
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
) )
const (
minTCPBuffer = 256
)
type StackAsync struct { type StackAsync struct {
mu sync.Mutex mu sync.Mutex
hostname string hostname string
@@ -52,6 +57,8 @@ type StackConfig struct {
NTPServer netip.Addr NTPServer netip.Addr
Hostname string Hostname string
MaxTCPConns int MaxTCPConns int
TCPBufferSizeTx int // Size of transmit buffer for TCP connections.
TCPBufferSizeRx int // Size of receive buffer for TCP connections.
RandSeed int64 RandSeed int64
HardwareAddress [6]byte HardwareAddress [6]byte
MTU uint16 MTU uint16
@@ -112,9 +119,35 @@ func (s *StackAsync) Reset(cfg StackConfig) error {
// Enable TCP if connections present. // Enable TCP if connections present.
if cfg.MaxTCPConns > 0 { if cfg.MaxTCPConns > 0 {
if cfg.TCPBufferSizeRx < minTCPBuffer || cfg.TCPBufferSizeTx < minTCPBuffer {
return io.ErrShortBuffer
}
if cap(s.tcpconns) < cfg.MaxTCPConns { if cap(s.tcpconns) < cfg.MaxTCPConns {
s.tcpconns = make([]tcp.Conn, 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) err = s.tcps.ResetTCP(cfg.MaxTCPConns)
if err != nil { if err != nil {
return err return err
@@ -213,7 +246,7 @@ func (s *StackAsync) DialTCP(localPort uint16, addrp netip.AddrPort) (conn *tcp.
} }
for i := range s.tcpconns { for i := range s.tcpconns {
maybeFreeConn := &s.tcpconns[i] maybeFreeConn := &s.tcpconns[i]
state := conn.State() state := maybeFreeConn.State()
if state.IsClosed() { if state.IsClosed() {
conn = maybeFreeConn conn = maybeFreeConn
break // Can be used! break // Can be used!
+5 -6
View File
@@ -122,21 +122,20 @@ func (s StackBlocking) DoDialTCP(localPort uint16, addrp netip.AddrPort, timeout
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
for i := 0; i < maxIter; i++ { for i := 0; i < maxIter; i++ {
state := conn.State() state := conn.State()
switch state { if state == tcp.StateEstablished {
case tcp.StateEstablished: return conn, nil
break } else if state == tcp.StateSynSent || state == tcp.StateSynRcvd || conn.InternalHandler().AwaitingSynSend() {
case tcp.StateSynSent, tcp.StateSynRcvd:
if err = s.checkDeadline(deadline); err != nil { if err = s.checkDeadline(deadline); err != nil {
return nil, err return nil, err
} }
time.Sleep(sleep) time.Sleep(sleep)
default: } else {
// Unexpected state, abort and terminate connection. // Unexpected state, abort and terminate connection.
conn.Abort() conn.Abort()
return nil, errTCPFailedToConnect return nil, errTCPFailedToConnect
} }
} }
return conn, nil return conn, errDeadlineExceed
} }
func (s StackBlocking) checkDeadline(deadline time.Time) error { func (s StackBlocking) checkDeadline(deadline time.Time) error {