Files
lneto/x/xnet/stack-go.go
T
Pat Whittingslow 0382410f02 begin adding udp conn (#69)
* begin adding udp conn

* udp tests passing

* bugfixes for udp Send,Abort,Encapsulate methods

* document UDP methods
2026-04-10 00:34:37 -03:00

252 lines
5.5 KiB
Go

package xnet
import (
"context"
"net"
"net/netip"
"syscall"
"time"
"github.com/soypat/lneto"
"github.com/soypat/lneto/tcp"
"github.com/soypat/lneto/udp"
)
// Socket types
const (
sockSTREAM = 0x1
sockDGRAM = 0x2
)
type StackGoConfig struct {
ListenerPoolConfig TCPPoolConfig
}
func (s *StackAsync) StackGo(loopSleep time.Duration, cfg StackGoConfig) StackGo {
return s.StackBlocking(loopSleep).StackGo(cfg)
}
func (s StackBlocking) StackGo(cfg StackGoConfig) StackGo {
sg := StackGo{
blk: s,
plcfg: cfg.ListenerPoolConfig,
}
return sg
}
type StackGo struct {
blk StackBlocking
plcfg TCPPoolConfig
}
func (s StackGo) Socket(ctx context.Context, network string, family, sotype int, laddr, raddr net.Addr) (c interface{}, err error) {
switch family {
case syscall.AF_INET:
default:
return nil, lneto.ErrUnsupported
}
var local, remote netip.AddrPort
if laddr != nil {
local, err = netip.ParseAddrPort(laddr.String())
if err != nil {
return nil, err
}
}
if raddr != nil {
remote, err = netip.ParseAddrPort(raddr.String())
if err != nil {
return nil, err
}
}
return s.SocketNetip(ctx, network, family, sotype, local, remote)
}
func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype int, laddr, raddr netip.AddrPort) (c interface{}, err error) {
switch family {
case syscall.AF_INET:
default:
return nil, lneto.ErrUnsupported
}
if laddr.Port() == 0 {
if raddr.IsValid() && raddr.Addr() != netip.IPv4Unspecified() {
// Outbound (dial) connection: auto-assign ephemeral port.
laddr = netip.AddrPortFrom(laddr.Addr(), uint16(49152+s.blk.async.Prand32()%16384))
} else {
return nil, lneto.ErrZeroSource
}
}
if laddr.Addr() == netip.IPv4Unspecified() {
// Specify address.
laddr = netip.AddrPortFrom(s.blk.async.ip.Addr(), laddr.Port())
} else if laddr.Addr().Is6() {
return nil, lneto.ErrUnsupported
}
switch network {
case "udp", "udp4":
if sotype != sockDGRAM {
return nil, lneto.ErrUnsupported
}
if !raddr.IsValid() || raddr.Addr() == netip.IPv4Unspecified() {
return nil, lneto.ErrZeroDestination
}
var conn udp.Conn
err = conn.Configure(udp.ConnConfig{
TxBuf: make([]byte, s.plcfg.TxBufSize),
RxBuf: make([]byte, s.plcfg.RxBufSize),
TxQueueSize: s.plcfg.QueueSize,
RxQueueSize: s.plcfg.QueueSize,
})
if err != nil {
return nil, err
}
raddr4 := raddr.Addr().As4()
err = conn.Open(laddr.Port(), raddr.Port(), raddr4[:])
if err != nil {
return nil, err
}
err = s.blk.async.RegisterUDP(&conn, raddr4[:], raddr.Port())
if err != nil {
return nil, err
}
uc := udpconn{
Conn: &conn,
localAddr: net.UDPAddrFromAddrPort(laddr),
raddr: net.UDPAddrFromAddrPort(raddr),
}
return uc, nil
case "tcp", "tcp4":
if sotype != sockSTREAM {
return nil, lneto.ErrUnsupported
}
if raddr.IsValid() && raddr.Addr() != netip.IPv4Unspecified() {
var conn tcp.Conn
// DIAL TCP: active connection a.k.a TCP Client branch.
err = conn.Configure(tcp.ConnConfig{
TxBuf: make([]byte, s.plcfg.TxBufSize),
RxBuf: make([]byte, s.plcfg.RxBufSize),
TxPacketQueueSize: s.plcfg.QueueSize,
})
if err != nil {
return nil, err
}
err = s.blk.async.DialTCP(&conn, laddr.Port(), raddr)
if err != nil {
return nil, err
}
for {
time.Sleep(s.blk.loopSleep)
state := conn.State()
if state == tcp.StateEstablished {
tc := tcpconn{
Conn: &conn,
localAddr: net.TCPAddrFromAddrPort(laddr),
}
return tc, nil
} else if state == tcp.StateSynSent || state == tcp.StateSynRcvd || conn.InternalHandler().AwaitingSynSend() {
if err = ctx.Err(); err != nil {
conn.Abort()
return nil, err
}
} else {
// Unexpected state, abort and terminate connection.
conn.Abort()
return errTCPFailedToConnect, nil
}
}
} else {
// LISTEN TCP: passive connection. fulfills net.Listener interface.
pool, err := NewTCPPool(s.plcfg)
if err != nil {
return nil, err
}
var l tcplistener
l.localAddr = net.TCPAddrFromAddrPort(laddr)
l.sleep = s.blk.loopSleep
err = l.l.Reset(laddr.Port(), pool)
if err != nil {
return nil, err
}
err = s.blk.async.RegisterListener(&l.l)
if err != nil {
return nil, err
}
return &l, nil
}
}
return nil, lneto.ErrUnsupported
}
type tcplistener struct {
l tcp.Listener
closed bool
sleep time.Duration
localAddr net.Addr
}
var _ net.Listener = (*tcplistener)(nil)
func (l *tcplistener) Addr() net.Addr {
return l.localAddr
}
func (l *tcplistener) Accept() (net.Conn, error) {
if l.closed {
return nil, net.ErrClosed
}
for {
n := l.l.NumberOfReadyToAccept()
if n == 0 {
time.Sleep(l.sleep)
continue
}
c, _, err := l.l.TryAccept()
if err != nil {
return nil, err
}
cc := tcpconn{
Conn: c,
localAddr: l.localAddr,
}
return cc, nil
}
}
func (l *tcplistener) Close() error {
if l.closed {
return net.ErrClosed
}
err := l.l.Close()
l.closed = true
return err
}
type tcpconn struct {
*tcp.Conn
localAddr net.Addr
}
var _ net.Conn = tcpconn{}
func (c tcpconn) LocalAddr() net.Addr {
return c.localAddr
}
func (c tcpconn) RemoteAddr() net.Addr {
return &net.TCPAddr{
IP: c.Conn.RemoteAddr(),
Port: int(c.Conn.RemotePort()),
}
}
type udpconn struct {
*udp.Conn
localAddr net.Addr
raddr net.Addr
}
var _ net.Conn = udpconn{}
func (c udpconn) LocalAddr() net.Addr { return c.localAddr }
func (c udpconn) RemoteAddr() net.Addr { return c.raddr }