mirror of
https://github.com/soypat/lneto.git
synced 2026-08-18 21:54:01 +00:00
More IPv6 support and race condition fixes (#123)
* changes in preparation of wireguard impl * suggestions by @MDr164
This commit is contained in:
@@ -35,8 +35,8 @@ go run ./examples/gen/gen-binary-bench # generate table
|
||||
|
||||
| Program | Extra Protocols | Packet capture printing | amd64 Go | WASM Go | amd64 TinyGo | WASM TinyGo | Pico TinyGo |
|
||||
|---|:---:|:---:|---|---|---|---|---|
|
||||
| [Lneto MWE](./examples/min-working-example/) | DNS,NTP,DHCP | ✅ | 3.8MB | 4.3MB | 1.6MB | 1.2MB | 185kB |
|
||||
| [Gvisor MWE w/ go-net](./examples/_import_examples/gvisor-mwe/) | None | ❌ | 6.6MB | 7.4MB | DNC | DNC | DNC |
|
||||
| [Lneto MWE](./examples/min-working-example/) | DNS,NTP,DHCP | ✅ | 3.9MB | 4.4MB | 1.6MB | 1.2MB | 189kB |
|
||||
| [Gvisor MWE w/ go-net](./examples/_import_examples/gvisor-mwe/) | None | ❌ | 6.6MB | 7.5MB | DNC | DNC | DNC |
|
||||
## `xcurl` example
|
||||
You may try lneto out on linux with the [xcurl example](./examples/xcurl/) which gets an HTTP page by doing all the low-level networking part using absolutely no standard library.
|
||||
|
||||
|
||||
+10
@@ -103,6 +103,16 @@ func (conn *Conn) State() State {
|
||||
return conn.h.State()
|
||||
}
|
||||
|
||||
// AwaitingSynSend reports whether the connection has been opened actively but has
|
||||
// not yet emitted its SYN. It locks the connection so it is safe to poll from a
|
||||
// goroutine other than the one driving the stack (unlike reaching through
|
||||
// [Conn.InternalHandler]).
|
||||
func (conn *Conn) AwaitingSynSend() bool {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
return conn.h.AwaitingSynSend()
|
||||
}
|
||||
|
||||
// BufferedInput returns the number of bytes in the socket's receive(input) buffer
|
||||
// and available to read via a [Conn.Read] call.
|
||||
func (conn *Conn) BufferedInput() int {
|
||||
|
||||
+59
-15
@@ -364,18 +364,18 @@ func (s *StackAsync) Addr4() [4]byte {
|
||||
}
|
||||
|
||||
func (s *StackAsync) SetAddr6(addr [16]byte) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.ipv6enabled {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.stack6.SetAddr6(addr)
|
||||
}
|
||||
return lneto.ErrUnsupported
|
||||
}
|
||||
|
||||
func (s *StackAsync) Addr6() [16]byte {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.ipv6enabled {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.stack6.Addr6()
|
||||
}
|
||||
return [16]byte{}
|
||||
@@ -412,6 +412,13 @@ func (s *StackAsync) GatewayHardwareAddr() [6]byte {
|
||||
return s.link.Gateway6()
|
||||
}
|
||||
|
||||
func (s *StackAsync) IsIPv6Enabled() bool {
|
||||
s.mu.Lock()
|
||||
enabled := s.ipv6enabled
|
||||
s.mu.Unlock()
|
||||
return enabled
|
||||
}
|
||||
|
||||
// EnableICMP registers an ICMP handler to the stack when enabled is true.
|
||||
// If enabled=false the currently registered ICMP handler is unregistered and state reset.
|
||||
func (s *StackAsync) EnableICMP(enabled bool) (err error) {
|
||||
@@ -437,25 +444,32 @@ func (s *StackAsync) EnableICMP(enabled bool) (err error) {
|
||||
func (s *StackAsync) DialUDP(conn *udp.Conn, localPort uint16, addrp netip.AddrPort) (err error) {
|
||||
addr := addrp.Addr()
|
||||
if addr.Is4() {
|
||||
err = s.DialUDP4(conn, localPort, addrp.Addr().As4(), addrp.Port())
|
||||
return s.DialUDP4(conn, localPort, addrp.Addr().As4(), addrp.Port())
|
||||
} else if s.ipv6enabled && addr.Is6() {
|
||||
err = s.stack6.DialUDP6(conn, localPort, addr.As16(), addrp.Port())
|
||||
} else {
|
||||
err = lneto.ErrInvalidAddr
|
||||
// stack6 is guarded by s.mu (the single stack lock), just like the IPv4
|
||||
// path locks inside DialUDP4. Hold it here so the port-handler mutation is
|
||||
// serialized against the Ingress/Egress demux.
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.stack6.DialUDP6(conn, localPort, addr.As16(), addrp.Port())
|
||||
}
|
||||
return err
|
||||
return lneto.ErrInvalidAddr
|
||||
}
|
||||
|
||||
func (s *StackAsync) DialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrPort) (err error) {
|
||||
addr := addrp.Addr()
|
||||
if addr.Is4() {
|
||||
err = s.DialTCP4(conn, localPort, addrp.Addr().As4(), addrp.Port())
|
||||
return s.DialTCP4(conn, localPort, addrp.Addr().As4(), addrp.Port())
|
||||
} else if s.ipv6enabled && addr.Is6() {
|
||||
err = s.stack6.DialTCP6(conn, localPort, addr.As16(), addrp.Port(), tcp.Value(s.Prand32()))
|
||||
} else {
|
||||
err = lneto.ErrInvalidAddr
|
||||
// stack6 is guarded by s.mu (the single stack lock), just like the IPv4
|
||||
// path locks inside DialTCP4. Hold it here so the port-handler mutation is
|
||||
// serialized against the Ingress/Egress demux. Use the unlocked prand32
|
||||
// since we already hold s.mu (Prand32 would deadlock).
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.stack6.DialTCP6(conn, localPort, addr.As16(), addrp.Port(), tcp.Value(s.prand32()))
|
||||
}
|
||||
return err
|
||||
return lneto.ErrInvalidAddr
|
||||
}
|
||||
|
||||
func (s *StackAsync) DialUDP4(conn *udp.Conn, localPort uint16, raddr [4]byte, rport uint16) (err error) {
|
||||
@@ -540,14 +554,44 @@ func (s *StackAsync) RegisterListenerUDP(pktconn *udp.PacketConn) (err error) {
|
||||
return s.udps.RegisterMACFiltered(pktconn, nil)
|
||||
}
|
||||
|
||||
func (s *StackAsync) RegisterListenerTCP6(listener *tcp.Listener) (err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.ipv6enabled {
|
||||
return lneto.ErrUnsupported
|
||||
}
|
||||
return s.stack6.RegisterListenerTCP6(listener)
|
||||
}
|
||||
|
||||
func (s *StackAsync) RegisterListenerUDP6(pktconn *udp.PacketConn) (err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.ipv6enabled {
|
||||
return lneto.ErrUnsupported
|
||||
}
|
||||
return s.stack6.RegisterListenerUDP6(pktconn)
|
||||
}
|
||||
|
||||
var errNoDNSServer = errors.New("no DNS server- did DHCP complete? You can set a predetermined DNS server in Stack configuration")
|
||||
|
||||
var errDNSv6Transport = errors.New("DNS query over IPv6 transport not supported; configure an IPv4 DNS server")
|
||||
|
||||
func (s *StackAsync) StartLookupIP(host string) error {
|
||||
return s.StartLookupIPType(host, dns.TypeA)
|
||||
}
|
||||
|
||||
// StartLookupIPType begins resolving host for the given record type (e.g. dns.TypeA
|
||||
// or dns.TypeAAAA). The DNS query is always carried over IPv4 to the configured DNS
|
||||
// server; resolving over an IPv6 DNS transport is not yet supported.
|
||||
func (s *StackAsync) StartLookupIPType(host string, qtype dns.Type) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.dnssv.IsValid() {
|
||||
return errNoDNSServer
|
||||
}
|
||||
if !s.dnssv.Is4() {
|
||||
return errDNSv6Transport
|
||||
}
|
||||
name, err := dns.NewName(host)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -561,7 +605,7 @@ func (s *StackAsync) StartLookupIP(host string) error {
|
||||
Questions: []dns.Question{
|
||||
{
|
||||
Name: name,
|
||||
Type: dns.TypeA,
|
||||
Type: qtype,
|
||||
Class: dns.ClassINET,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/soypat/lneto"
|
||||
"github.com/soypat/lneto/dhcp/dhcpv4"
|
||||
"github.com/soypat/lneto/dns"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
)
|
||||
|
||||
@@ -147,7 +148,13 @@ func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D
|
||||
}
|
||||
|
||||
func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []netip.Addr, err error) {
|
||||
err = s.async.StartLookupIP(host)
|
||||
return s.DoLookupIPType(host, timeout, dns.TypeA)
|
||||
}
|
||||
|
||||
// DoLookupIPType resolves host for the given record type (dns.TypeA or dns.TypeAAAA),
|
||||
// blocking until a response arrives or the timeout elapses.
|
||||
func (s StackBlocking) DoLookupIPType(host string, timeout time.Duration, qtype dns.Type) (addrs []netip.Addr, err error) {
|
||||
err = s.async.StartLookupIPType(host, qtype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -180,7 +187,7 @@ func (s StackBlocking) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.A
|
||||
state := conn.State()
|
||||
if state == tcp.StateEstablished {
|
||||
return nil
|
||||
} else if state == tcp.StateSynSent || state == tcp.StateSynRcvd || conn.InternalHandler().AwaitingSynSend() {
|
||||
} else if state == tcp.StateSynSent || state == tcp.StateSynRcvd || conn.AwaitingSynSend() {
|
||||
if err = s.checkDeadline(deadline); err != nil {
|
||||
conn.Abort()
|
||||
return err
|
||||
|
||||
+36
-17
@@ -47,6 +47,10 @@ type StackGo struct {
|
||||
func (s StackGo) Socket(ctx context.Context, network string, family, sotype int, laddr, raddr net.Addr) (c any, err error) {
|
||||
switch family {
|
||||
case syscall.AF_INET:
|
||||
case syscall.AF_INET6:
|
||||
if !s.blk.async.IsIPv6Enabled() {
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
default:
|
||||
return nil, lneto.ErrUnsupported
|
||||
}
|
||||
@@ -67,31 +71,38 @@ func (s StackGo) Socket(ctx context.Context, network string, family, sotype int,
|
||||
}
|
||||
|
||||
func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype int, laddr, raddr netip.AddrPort) (c any, err error) {
|
||||
var isV6 bool
|
||||
switch family {
|
||||
case syscall.AF_INET:
|
||||
case syscall.AF_INET6:
|
||||
if !s.blk.async.IsIPv6Enabled() {
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
isV6 = true
|
||||
default:
|
||||
return nil, lneto.ErrUnsupported
|
||||
}
|
||||
// A dial targets a specified (non-unspecified) remote; a listen does not.
|
||||
isDial := raddr.IsValid() && !raddr.Addr().IsUnspecified()
|
||||
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))
|
||||
// Auto-assign an ephemeral port for both outbound dials and for listeners
|
||||
// that did not request a fixed port.
|
||||
laddr = netip.AddrPortFrom(laddr.Addr(), uint16(49152+s.blk.async.Prand32()%16384))
|
||||
}
|
||||
if laddr.Addr().IsUnspecified() {
|
||||
// Fill in the stack's configured address for the requested family.
|
||||
if isV6 {
|
||||
laddr = netip.AddrPortFrom(netip.AddrFrom16(s.blk.async.Addr6()), laddr.Port())
|
||||
} else {
|
||||
return nil, lneto.ErrZeroSource
|
||||
laddr = netip.AddrPortFrom(netip.AddrFrom4(s.blk.async.ip4.Addr4()), laddr.Port())
|
||||
}
|
||||
}
|
||||
if laddr.Addr() == netip.IPv4Unspecified() {
|
||||
// Specify address.
|
||||
laddr = netip.AddrPortFrom(netip.AddrFrom4(s.blk.async.ip4.Addr4()), laddr.Port())
|
||||
} else if laddr.Addr().Is6() {
|
||||
return nil, lneto.ErrUnsupported
|
||||
}
|
||||
switch network {
|
||||
case "udp", "udp4":
|
||||
case "udp", "udp4", "udp6":
|
||||
if sotype != sockDGRAM {
|
||||
return nil, lneto.ErrUnsupported
|
||||
}
|
||||
if !raddr.IsValid() || raddr.Addr() == netip.IPv4Unspecified() {
|
||||
if !isDial {
|
||||
// LISTEN UDP: no fixed remote → PacketConn.
|
||||
var pc udppktconn
|
||||
err = pc.c.Configure(udp.PacketConnConfig{
|
||||
@@ -109,7 +120,11 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
||||
return nil, err
|
||||
}
|
||||
pc.laddr = net.UDPAddr{IP: laddr.Addr().AsSlice(), Port: int(laddr.Port())}
|
||||
err = s.blk.async.RegisterListenerUDP(&pc.c)
|
||||
if isV6 {
|
||||
err = s.blk.async.RegisterListenerUDP6(&pc.c)
|
||||
} else {
|
||||
err = s.blk.async.RegisterListenerUDP(&pc.c)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -138,12 +153,12 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
||||
raddr: udpaddr(raddr),
|
||||
}
|
||||
return uc, nil
|
||||
case "tcp", "tcp4":
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if sotype != sockSTREAM {
|
||||
return nil, lneto.ErrUnsupported
|
||||
}
|
||||
|
||||
if raddr.IsValid() && raddr.Addr() != netip.IPv4Unspecified() {
|
||||
if isDial {
|
||||
var conn tcp.Conn
|
||||
// DIAL TCP: active connection a.k.a TCP Client branch.
|
||||
err = conn.Configure(tcp.ConnConfig{
|
||||
@@ -171,7 +186,7 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
||||
localAddr: net.TCPAddrFromAddrPort(laddr),
|
||||
}
|
||||
return tc, nil
|
||||
} else if state == tcp.StateSynSent || state == tcp.StateSynRcvd || conn.InternalHandler().AwaitingSynSend() {
|
||||
} else if state == tcp.StateSynSent || state == tcp.StateSynRcvd || conn.AwaitingSynSend() {
|
||||
if err = ctx.Err(); err != nil {
|
||||
conn.Abort()
|
||||
return nil, err
|
||||
@@ -195,7 +210,11 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.blk.async.RegisterListenerTCP(&l.l)
|
||||
if isV6 {
|
||||
err = s.blk.async.RegisterListenerTCP6(&l.l)
|
||||
} else {
|
||||
err = s.blk.async.RegisterListenerTCP(&l.l)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ type Stack6 interface {
|
||||
Register6(node lneto.StackNode) error
|
||||
DialUDP6(conn *udp.Conn, localPort uint16, raddr [16]byte, rport uint16) error
|
||||
DialTCP6(conn *tcp.Conn, localPort uint16, raddr [16]byte, rport uint16, iss tcp.Value) error
|
||||
RegisterListenerTCP6(listener *tcp.Listener) error
|
||||
RegisterListenerUDP6(pktconn *udp.PacketConn) error
|
||||
IngressIPv6(ipframe []byte) error
|
||||
EgressIPv6(ipframe []byte) (int, error)
|
||||
IPv6Stack() lneto.StackNode
|
||||
@@ -119,6 +121,18 @@ func (s *stack6) EnableICMP6(enabled bool) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// RegisterListenerTCP6 registers a passive TCP listener on the IPv6 stack so it
|
||||
// receives inbound IPv6 segments. Mirrors StackAsync.RegisterListenerTCP for IPv4.
|
||||
func (s *stack6) RegisterListenerTCP6(listener *tcp.Listener) error {
|
||||
return s.tcps6.RegisterMACFiltered(listener, nil)
|
||||
}
|
||||
|
||||
// RegisterListenerUDP6 registers a UDP packet connection on the IPv6 stack so it
|
||||
// receives inbound IPv6 datagrams. Mirrors StackAsync.RegisterListenerUDP for IPv4.
|
||||
func (s *stack6) RegisterListenerUDP6(pktconn *udp.PacketConn) error {
|
||||
return s.udps6.RegisterMACFiltered(pktconn, nil)
|
||||
}
|
||||
|
||||
func (s *stack6) IngressIPv6(ipFrame []byte) error {
|
||||
return s.ip6.Demux(ipFrame, 0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user