mirror of
https://github.com/soypat/lneto.git
synced 2026-08-18 21:54:01 +00:00
add x/xnet experimental package
This commit is contained in:
@@ -21,6 +21,7 @@ vendor/
|
|||||||
**__debug_bin*
|
**__debug_bin*
|
||||||
# `__debug_bin` Debug binary generated in VSCode when using the built-in debugger.
|
# `__debug_bin` Debug binary generated in VSCode when using the built-in debugger.
|
||||||
*bin
|
*bin
|
||||||
|
/xnet
|
||||||
/bridge
|
/bridge
|
||||||
# IDE
|
# IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ If you can use the `net` package, use it. Need something faster and less-heap al
|
|||||||
- [`lneto/dns`](./dns): DNS protocol implementation and low level logic.
|
- [`lneto/dns`](./dns): DNS protocol implementation and low level logic.
|
||||||
- [`lneto/ntp`](./ntp): NTP implementation and low level logic. Includes NTP time primitives manipulation and conversion to Go native types.
|
- [`lneto/ntp`](./ntp): NTP implementation and low level logic. Includes NTP time primitives manipulation and conversion to Go native types.
|
||||||
- [`lneto/internal`](./internal): Lightweight and flexible ring buffer implementation and debugging primitives.
|
- [`lneto/internal`](./internal): Lightweight and flexible ring buffer implementation and debugging primitives.
|
||||||
|
- [`lneto/x`](./x): Experimental packages.
|
||||||
|
- [`lneto/x/xnet`](./x/xnet/): `net` package like abstractions of stack implementations for ease of reuse. Still in testing phase and likely subject to breaking API change.
|
||||||
|
|
||||||
### Abstractions
|
### Abstractions
|
||||||
The following interface is implemented by networking stack nodes and the stack themselves.
|
The following interface is implemented by networking stack nodes and the stack themselves.
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package lneto
|
||||||
|
|
||||||
|
// type ErrorPacketDrop struct {
|
||||||
|
// Message string
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var genericErrPacketDrop = &ErrorPacketDrop{Message: "lneto: packet dropped"}
|
||||||
|
|
||||||
|
// // ErrGenericPacketDrop returns the generic packet drop error. It performs no allocations.
|
||||||
|
// func ErrGenericPacketDrop() error {
|
||||||
|
// return genericErrPacketDrop
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (err *ErrorPacketDrop) Error() string {
|
||||||
|
// return err.Message
|
||||||
|
// }
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto/dns"
|
||||||
|
"github.com/soypat/lneto/internal"
|
||||||
|
"github.com/soypat/lneto/internal/ltesto"
|
||||||
|
"github.com/soypat/lneto/x/xnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
var softRand = time.Now().Unix()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("success")
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() (err error) {
|
||||||
|
var (
|
||||||
|
flagInterface = "tap0"
|
||||||
|
flagUseHTTP = false
|
||||||
|
flagHostToResolve = ""
|
||||||
|
flagRequestedIP = ""
|
||||||
|
flagDoNTP = false
|
||||||
|
)
|
||||||
|
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.StringVar(&flagHostToResolve, "host", flagHostToResolve, "Hostname to resolve via DNS.")
|
||||||
|
flag.StringVar(&flagRequestedIP, "addr", flagRequestedIP, "IP address to request via DHCP.")
|
||||||
|
flag.BoolVar(&flagDoNTP, "ntp", flagDoNTP, "Do NTP round and print result time")
|
||||||
|
flag.Parse()
|
||||||
|
fmt.Println("softrand", softRand)
|
||||||
|
_, err = dns.NewName(flagHostToResolve)
|
||||||
|
if err != nil {
|
||||||
|
flag.Usage()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var iface ltesto.Interface
|
||||||
|
if flagUseHTTP {
|
||||||
|
iface = ltesto.NewHTTPTapClient("http://127.0.0.1:7070")
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(flagInterface, "tap") {
|
||||||
|
tap, err := internal.NewTap(flagInterface, netip.MustParsePrefix("192.168.1.1/24"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iface = tap
|
||||||
|
} else {
|
||||||
|
bridge, err := internal.NewBridge(flagInterface)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iface = bridge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer iface.Close()
|
||||||
|
|
||||||
|
nicHW, err := iface.HardwareAddress6()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
brHW := nicHW
|
||||||
|
mtu, err := iface.MTU()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nicAddr, err := iface.IPMask()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println("NIC hardware address:", net.HardwareAddr(nicHW[:]).String(), "bridgeHW:", net.HardwareAddr(brHW[:]).String(), "mtu:", mtu, "addr:", nicAddr.String())
|
||||||
|
var stack xnet.StackAsync
|
||||||
|
err = stack.Reset(xnet.StackConfig{
|
||||||
|
Hostname: "xnet-test",
|
||||||
|
RandSeed: softRand,
|
||||||
|
HardwareAddress: brHW,
|
||||||
|
MTU: uint16(mtu),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Loop goroutine.
|
||||||
|
go func() {
|
||||||
|
lastAction := time.Now()
|
||||||
|
buf := make([]byte, mtu)
|
||||||
|
for {
|
||||||
|
clear(buf)
|
||||||
|
nwrite, err := stack.Encapsulate(buf[:], 0)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("ERR:ENCAPSULATE", err)
|
||||||
|
} else if nwrite > 0 {
|
||||||
|
n, err := iface.Write(buf[:nwrite])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("groutine encapsulate:", err)
|
||||||
|
} else if n != nwrite {
|
||||||
|
log.Fatalf("mismatch written bytes %d!=%d", nwrite, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(buf)
|
||||||
|
nread, err := iface.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("groutine read:", err)
|
||||||
|
} else if nread > 0 {
|
||||||
|
err = stack.Demux(buf[:nread], 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("groutine demux:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nread == 0 && nwrite == 0 && time.Since(lastAction) > 4*time.Second {
|
||||||
|
time.Sleep(5 * time.Millisecond)
|
||||||
|
} else {
|
||||||
|
lastAction = time.Now()
|
||||||
|
runtime.Gosched()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
rstack := stack.StackRetrying()
|
||||||
|
|
||||||
|
const (
|
||||||
|
dhcpTimeout = 6 * time.Second
|
||||||
|
dhcpRetries = 2
|
||||||
|
)
|
||||||
|
results, err := rstack.DoDHCPv4([4]byte{192, 168, 1, 96}, dhcpTimeout, dhcpRetries)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("DHCP failed: %w", err)
|
||||||
|
}
|
||||||
|
err = stack.AssimilateDHCPResults(results)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("assimilating DHCP results: %w", err)
|
||||||
|
}
|
||||||
|
const (
|
||||||
|
arpTimeout = 2 * time.Second
|
||||||
|
arpRetries = 2
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
internetTimeout = 3 * time.Second
|
||||||
|
internetRetries = 2
|
||||||
|
)
|
||||||
|
routerHw, err := rstack.DoResolveHardwareAddress6(results.Router, arpTimeout, arpRetries)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ARP resolution of router failed: %w", err)
|
||||||
|
}
|
||||||
|
stack.SetGateway6(routerHw)
|
||||||
|
if flagDoNTP {
|
||||||
|
const ntpHost = "pool.ntp.org"
|
||||||
|
addrs, err := rstack.DoLookupIP(ntpHost, internetTimeout, internetRetries)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("NTP address lookup of %q failed: %w", ntpHost, err)
|
||||||
|
}
|
||||||
|
offset, err := rstack.DoNTP(addrs[0], internetTimeout, internetRetries)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("NTP address lookup of %q failed: %w", ntpHost, err)
|
||||||
|
}
|
||||||
|
relative := "behind"
|
||||||
|
if offset < 0 {
|
||||||
|
relative = "ahead"
|
||||||
|
}
|
||||||
|
fmt.Println("NTP completed. You are", offset.Abs().String(), relative, "of the NTP server")
|
||||||
|
}
|
||||||
|
addrs, err := rstack.DoLookupIP(flagHostToResolve, internetTimeout, internetRetries)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clear(buf []byte) {
|
||||||
|
for i := range buf {
|
||||||
|
buf[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,456 @@
|
|||||||
|
package xnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto"
|
||||||
|
"github.com/soypat/lneto/arp"
|
||||||
|
"github.com/soypat/lneto/dhcpv4"
|
||||||
|
"github.com/soypat/lneto/dns"
|
||||||
|
"github.com/soypat/lneto/ethernet"
|
||||||
|
"github.com/soypat/lneto/internet"
|
||||||
|
"github.com/soypat/lneto/ntp"
|
||||||
|
"github.com/soypat/lneto/tcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StackAsync struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
hostname string
|
||||||
|
clientID string
|
||||||
|
link internet.StackEthernet
|
||||||
|
ip internet.StackIP
|
||||||
|
arp arp.Handler
|
||||||
|
udps internet.StackPorts
|
||||||
|
tcps internet.StackPorts
|
||||||
|
tcpconns []tcp.Conn
|
||||||
|
|
||||||
|
dhcpUDP internet.StackUDPPort
|
||||||
|
dhcp dhcpv4.Client
|
||||||
|
dhcpResults DHCPResults
|
||||||
|
|
||||||
|
dnsUDP internet.StackUDPPort
|
||||||
|
dns dns.Client
|
||||||
|
ednsopt dns.Resource
|
||||||
|
lookup dns.Message
|
||||||
|
dnssv netip.Addr
|
||||||
|
|
||||||
|
ntpUDP internet.StackUDPPort
|
||||||
|
ntp ntp.Client
|
||||||
|
|
||||||
|
sysprec int8 // NTP system precision.
|
||||||
|
|
||||||
|
prng uint32
|
||||||
|
lastrecv uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type StackConfig struct {
|
||||||
|
StaticAddress netip.Addr
|
||||||
|
DNSServer netip.Addr
|
||||||
|
NTPServer netip.Addr
|
||||||
|
Hostname string
|
||||||
|
MaxTCPConns int
|
||||||
|
RandSeed int64
|
||||||
|
HardwareAddress [6]byte
|
||||||
|
MTU uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) Hostname() string {
|
||||||
|
return s.hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) Demux(carrierData []byte, etherOff int) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.lastrecv = uint16(len(carrierData))
|
||||||
|
return s.link.Demux(carrierData, etherOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) Encapsulate(carrierData []byte, etherOff int) (int, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.link.Encapsulate(carrierData, etherOff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) Reset(cfg StackConfig) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
mac := cfg.HardwareAddress
|
||||||
|
mtu := cfg.MTU
|
||||||
|
addr := cfg.StaticAddress
|
||||||
|
s.prng = uint32(cfg.RandSeed)
|
||||||
|
if s.prng == 0 {
|
||||||
|
return errors.New("zero random seed")
|
||||||
|
}
|
||||||
|
s.hostname = cfg.Hostname
|
||||||
|
if !addr.IsValid() {
|
||||||
|
addr = netip.AddrFrom4([4]byte{}) // If static not set DHCP will be performed and address will be zero.
|
||||||
|
} else if addr.Is6() {
|
||||||
|
return errors.New("IPv6 unsupported as of yet")
|
||||||
|
}
|
||||||
|
const linkNodes = 2 // ARP and IP nodes
|
||||||
|
err := s.link.Reset6(mac, ethernet.BroadcastAddr(), int(mtu), linkNodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
const ipNodes = 2 // UDP, TCP ports.
|
||||||
|
err = s.ip.Reset(addr, ipNodes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.resetARP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
const udpMaintenanceConns = 3 // DHCP, DNS, NTP.
|
||||||
|
err = s.udps.ResetUDP(udpMaintenanceConns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable TCP if connections present.
|
||||||
|
if cfg.MaxTCPConns > 0 {
|
||||||
|
if cap(s.tcpconns) < cfg.MaxTCPConns {
|
||||||
|
s.tcpconns = make([]tcp.Conn, cfg.MaxTCPConns)
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
err = s.link.Register(&s.arp) // ARP.
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.link.Register(&s.ip) // IPv4 | IPv6
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.ip.Register(&s.udps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var timebuf [32]time.Time
|
||||||
|
s.sysprec = ntp.CalculateSystemPrecision(time.Now, timebuf[:])
|
||||||
|
if s.clientID == "" {
|
||||||
|
s.clientID = "lneto-" + s.hostname
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalidIPAddr = errors.New("invaldi IP address")
|
||||||
|
|
||||||
|
func (s *StackAsync) resetARP() error {
|
||||||
|
mac := s.link.HardwareAddr6()
|
||||||
|
addr := s.ip.Addr()
|
||||||
|
if !addr.IsValid() {
|
||||||
|
return errInvalidIPAddr
|
||||||
|
}
|
||||||
|
proto := ethernet.TypeIPv4
|
||||||
|
if addr.Is6() {
|
||||||
|
proto = ethernet.TypeIPv6
|
||||||
|
}
|
||||||
|
return s.arp.Reset(arp.HandlerConfig{
|
||||||
|
HardwareAddr: mac[:],
|
||||||
|
ProtocolAddr: addr.AsSlice(),
|
||||||
|
MaxQueries: 3,
|
||||||
|
MaxPending: 3,
|
||||||
|
HardwareType: 1,
|
||||||
|
ProtocolType: proto,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prand32 generates a pseudo random 32-bit unsigned integer from the internal state and advances the seed.
|
||||||
|
func (s *StackAsync) Prand32() uint32 {
|
||||||
|
/* Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs" */
|
||||||
|
seed := s.prng
|
||||||
|
seed ^= seed << 13
|
||||||
|
seed ^= seed >> 17
|
||||||
|
seed ^= seed << 5
|
||||||
|
s.prng = seed
|
||||||
|
return seed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) SetIPAddr(addr netip.Addr) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
err := s.ip.SetAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.resetARP()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) SetHardwareAddress(hw [6]byte) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.link.SetHardwareAddr6(hw)
|
||||||
|
return s.resetARP()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) SetGateway6(gwhw [6]byte) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.link.SetGateway6(gwhw)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoTCP = errors.New("no TCP initialized")
|
||||||
|
errNoTCPConnsAvail = errors.New("all allocated TCP connections busy")
|
||||||
|
)
|
||||||
|
|
||||||
|
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 := conn.State()
|
||||||
|
if state.IsClosed() {
|
||||||
|
conn = maybeFreeConn
|
||||||
|
break // Can be used!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
return nil, errNoTCPConnsAvail
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
err = s.tcps.Register(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Abort()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errNoDNSServer = errors.New("no DNS server- did DHCP complete? You can set a predetermined DNS server in Stack configuration")
|
||||||
|
|
||||||
|
func (s *StackAsync) StartLookupIP(host string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if !s.dnssv.IsValid() {
|
||||||
|
return errNoDNSServer
|
||||||
|
}
|
||||||
|
name, err := dns.NewName(host)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ednsopt.SetEDNS0(uint16(s.link.MTU())-100, 0, 0, nil)
|
||||||
|
rand := s.Prand32()
|
||||||
|
err = s.dns.StartResolve(uint16(rand>>1)+1024, uint16(rand), dns.ResolveConfig{
|
||||||
|
Questions: []dns.Question{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
Type: dns.TypeA,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Additional: []dns.Resource{
|
||||||
|
s.ednsopt,
|
||||||
|
},
|
||||||
|
EnableRecursion: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dns4 := s.dnssv.As4()
|
||||||
|
s.dnsUDP.SetStackNode(&s.dns, dns4[:], dns.ServerPort)
|
||||||
|
err = s.udps.Register(&s.dnsUDP)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var errDNSNotDone = errors.New("DNS not done")
|
||||||
|
|
||||||
|
func (s *StackAsync) ResultLookupIP(host string) ([]netip.Addr, bool, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
done, err := s.dns.MessageCopyTo(&s.lookup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, done, err
|
||||||
|
} else if !done {
|
||||||
|
return nil, done, errDNSNotDone
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrs []netip.Addr
|
||||||
|
ans := s.lookup.Answers
|
||||||
|
for i := range ans {
|
||||||
|
data := ans[i].RawData()
|
||||||
|
if len(data) == 4 {
|
||||||
|
addrs = append(addrs, netip.AddrFrom4([4]byte(data)))
|
||||||
|
} else if len(data) == 16 {
|
||||||
|
addrs = append(addrs, netip.AddrFrom16([16]byte(data)))
|
||||||
|
} else {
|
||||||
|
return addrs, done, errors.New("bogus IP")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addrs, done, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) StartDHCPv4Request(request [4]byte) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.dhcp.Reset()
|
||||||
|
xid := s.Prand32()
|
||||||
|
err := s.dhcp.BeginRequest(xid, dhcpv4.RequestConfig{
|
||||||
|
RequestedAddr: request,
|
||||||
|
ClientHardwareAddr: s.link.HardwareAddr6(),
|
||||||
|
Hostname: s.hostname,
|
||||||
|
ClientID: s.clientID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.dhcpUDP.SetStackNode(&s.dhcp, nil, dhcpv4.DefaultServerPort)
|
||||||
|
err = s.udps.Register(&s.dhcpUDP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) StartNTP(addr netip.Addr) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
s.ntp.Reset(s.sysprec, time.Now)
|
||||||
|
|
||||||
|
addr4 := addr.As4()
|
||||||
|
s.ntpUDP.SetStackNode(&s.ntp, addr4[:], ntp.ServerPort)
|
||||||
|
err := s.udps.Register(&s.ntpUDP)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultNTPOffset returns the result of the NTP protocol such that the following code returns the corrected time.
|
||||||
|
// If the bool is false then the NTP has not yet completed.
|
||||||
|
//
|
||||||
|
// nowCorrected := time.Now().Add(resultNTP)
|
||||||
|
func (s *StackAsync) ResultNTPOffset() (time.Duration, bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.ntp.Offset(), s.ntp.IsDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) StartResolveHardwareAddress6(ip netip.Addr) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if !ip.Is4() {
|
||||||
|
return errors.New("unsupported or invalid IP address")
|
||||||
|
}
|
||||||
|
addr := ip.As4()
|
||||||
|
return s.arp.StartQuery(addr[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultResolveHardwareAddress6
|
||||||
|
func (s *StackAsync) ResultResolveHardwareAddress6(ip netip.Addr) (hw [6]byte, err error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if !ip.Is4() {
|
||||||
|
return hw, errors.New("unsupported or invalid IP address")
|
||||||
|
}
|
||||||
|
addr := ip.As4()
|
||||||
|
hwslice, err := s.arp.QueryResult(addr[:])
|
||||||
|
if err != nil {
|
||||||
|
return hw, err
|
||||||
|
} else if len(hwslice) != 6 {
|
||||||
|
panic("unreachable slice hw length")
|
||||||
|
}
|
||||||
|
return [6]byte(hwslice), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DHCPResults struct {
|
||||||
|
DNSServers []netip.Addr
|
||||||
|
Router netip.Addr
|
||||||
|
AssignedAddr netip.Addr
|
||||||
|
ServerAddr netip.Addr
|
||||||
|
BroadcastAddr netip.Addr
|
||||||
|
Gateway netip.Addr
|
||||||
|
Subnet netip.Prefix
|
||||||
|
TRebind uint32 // [seconds]
|
||||||
|
TRenewal uint32
|
||||||
|
TLease uint32 // IP lease time [seconds].
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) ResultDHCP() (*DHCPResults, error) {
|
||||||
|
err := s.populateDHCPResults()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &s.dhcpResults, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssimilateDHCPResults sets the stack's following parameters:
|
||||||
|
// - IPv4 address.
|
||||||
|
// - DNS server.
|
||||||
|
func (stack *StackAsync) AssimilateDHCPResults(results *DHCPResults) error {
|
||||||
|
stack.mu.Lock()
|
||||||
|
defer stack.mu.Unlock()
|
||||||
|
if results.AssignedAddr.IsValid() {
|
||||||
|
err := stack.ip.SetAddr(results.AssignedAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(results.DNSServers) > 0 {
|
||||||
|
if !results.DNSServers[0].IsValid() || !results.DNSServers[0].Is4() {
|
||||||
|
return errors.New("bad DNS server address, IPv6 or invalid")
|
||||||
|
}
|
||||||
|
stack.dnssv = results.DNSServers[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) populateDHCPResults() error {
|
||||||
|
if !s.dhcp.State().HasIP() {
|
||||||
|
return errors.New("DHCP not completed")
|
||||||
|
}
|
||||||
|
router4, ok := s.dhcp.RouterAddr()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("no DHCP router address")
|
||||||
|
}
|
||||||
|
assigned4, ok := s.dhcp.AssignedAddr()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("no DHCP assigned address")
|
||||||
|
}
|
||||||
|
router := netip.AddrFrom4(router4)
|
||||||
|
s.dhcpResults = DHCPResults{
|
||||||
|
Router: router,
|
||||||
|
Subnet: netip.PrefixFrom(router, int(s.dhcp.SubnetCIDRBits())),
|
||||||
|
AssignedAddr: netip.AddrFrom4(assigned4),
|
||||||
|
ServerAddr: addr4(s.dhcp.ServerAddr()),
|
||||||
|
BroadcastAddr: addr4(s.dhcp.BroadcastAddr()),
|
||||||
|
Gateway: addr4(s.dhcp.GatewayAddr()),
|
||||||
|
TRebind: s.dhcp.RebindingSeconds(),
|
||||||
|
TRenewal: s.dhcp.RenewalSeconds(),
|
||||||
|
TLease: s.dhcp.IPLeaseSeconds(),
|
||||||
|
DNSServers: s.dhcpResults.DNSServers[:0], // reuse field capacity.
|
||||||
|
}
|
||||||
|
s.dhcpResults.DNSServers = s.dhcp.AppendDNSServers(s.dhcpResults.DNSServers)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addr4(addr [4]byte, ok bool) netip.Addr {
|
||||||
|
if !ok {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
return netip.AddrFrom4(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(b []byte) uint16 {
|
||||||
|
var csum lneto.CRC791
|
||||||
|
csum.Write(b)
|
||||||
|
return csum.Sum16()
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package xnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto/dhcpv4"
|
||||||
|
"github.com/soypat/lneto/tcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxIter = 1000
|
||||||
|
maxTimeout = time.Minute
|
||||||
|
maxSleep = maxTimeout / maxIter
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errDeadlineExceed = errors.New("cywnet: deadline exceeded")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *StackAsync) StackBlocking() StackBlocking {
|
||||||
|
return StackBlocking{
|
||||||
|
async: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StackBlocking struct {
|
||||||
|
async *StackAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPResults, error) {
|
||||||
|
err := s.async.StartDHCPv4Request(reqAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sleep := timeout/maxIter + 1
|
||||||
|
deadline := time.Now().Add(timeout)
|
||||||
|
requested := false
|
||||||
|
for i := 0; i < maxIter; i++ {
|
||||||
|
state := s.async.dhcp.State()
|
||||||
|
requested = requested || state > dhcpv4.StateInit
|
||||||
|
if requested && state == dhcpv4.StateInit {
|
||||||
|
return nil, errors.New("DHCP NACK")
|
||||||
|
} else if state == dhcpv4.StateBound {
|
||||||
|
break // DHCP done succesfully.
|
||||||
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
|
}
|
||||||
|
return s.async.ResultDHCP()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackBlocking) DoNTP(hostAddr netip.Addr, timeout time.Duration) (offset time.Duration, err error) {
|
||||||
|
err = s.async.StartNTP(hostAddr)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
sleep := timeout/maxIter + 1
|
||||||
|
deadline := time.Now().Add(timeout)
|
||||||
|
var done bool
|
||||||
|
for i := 0; i < maxIter; i++ {
|
||||||
|
offset, done = s.async.ResultNTPOffset()
|
||||||
|
if done {
|
||||||
|
return offset, nil
|
||||||
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
|
}
|
||||||
|
return -1, errDeadlineExceed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.Duration) (hw [6]byte, err error) {
|
||||||
|
err = s.async.StartResolveHardwareAddress6(addr)
|
||||||
|
if err != nil {
|
||||||
|
return hw, err
|
||||||
|
}
|
||||||
|
sleep := timeout/maxIter + 1
|
||||||
|
deadline := time.Now().Add(timeout)
|
||||||
|
for i := 0; i < maxIter; i++ {
|
||||||
|
hw, err = s.async.ResultResolveHardwareAddress6(addr)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
|
return hw, err
|
||||||
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
|
err = errDeadlineExceed // Ensure that if iterations done error is returned.
|
||||||
|
}
|
||||||
|
return hw, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []netip.Addr, err error) {
|
||||||
|
err = s.async.StartLookupIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sleep := timeout/maxIter + 1
|
||||||
|
deadline := time.Now().Add(timeout)
|
||||||
|
for i := 0; i < maxIter; i++ {
|
||||||
|
addrs, completed, err := s.async.ResultLookupIP(host)
|
||||||
|
if completed {
|
||||||
|
return addrs, err
|
||||||
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
|
}
|
||||||
|
return nil, errDeadlineExceed
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sleep := timeout/maxIter + 1
|
||||||
|
deadline := time.Now().Add(timeout)
|
||||||
|
for i := 0; i < maxIter; i++ {
|
||||||
|
state := conn.State()
|
||||||
|
switch state {
|
||||||
|
case tcp.StateEstablished:
|
||||||
|
break
|
||||||
|
case tcp.StateSynSent, tcp.StateSynRcvd:
|
||||||
|
if err = s.checkDeadline(deadline); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
|
default:
|
||||||
|
// Unexpected state, abort and terminate connection.
|
||||||
|
conn.Abort()
|
||||||
|
return nil, errTCPFailedToConnect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackBlocking) checkDeadline(deadline time.Time) error {
|
||||||
|
if time.Since(deadline) > 0 {
|
||||||
|
return errDeadlineExceed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package xnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto/tcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *StackAsync) StackRetrying() StackRetrying {
|
||||||
|
return StackRetrying{
|
||||||
|
block: s.StackBlocking(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errRetriesExceeded = errors.New("cywnet: retries exceeded")
|
||||||
|
)
|
||||||
|
|
||||||
|
type StackRetrying struct {
|
||||||
|
block StackBlocking
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackRetrying) DoDHCPv4(reqAddr [4]byte, timeout time.Duration, retries int) (results *DHCPResults, err error) {
|
||||||
|
expectEnd := time.Now().Add(timeout * time.Duration(retries))
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
println("Retrying DHCP")
|
||||||
|
}
|
||||||
|
results, err = s.block.DoDHCPv4(reqAddr, timeout)
|
||||||
|
if err == nil {
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().Before(expectEnd) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, errRetriesExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackRetrying) DoNTP(ntpHost netip.Addr, timeout time.Duration, retries int) (offset time.Duration, err error) {
|
||||||
|
expectEnd := time.Now().Add(timeout * time.Duration(retries))
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
println("Retrying DHCP")
|
||||||
|
}
|
||||||
|
offset, err = s.block.DoNTP(ntpHost, timeout)
|
||||||
|
if err == nil {
|
||||||
|
return offset, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().Before(expectEnd) {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return -1, errRetriesExceeded
|
||||||
|
}
|
||||||
|
func (s StackRetrying) DoLookupIP(host string, timeout time.Duration, retries int) (addrs []netip.Addr, err error) {
|
||||||
|
expectEnd := time.Now().Add(timeout * time.Duration(retries))
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
addrs, err = s.block.DoLookupIP(host, timeout)
|
||||||
|
if err == nil {
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().Before(expectEnd) {
|
||||||
|
return addrs, err
|
||||||
|
}
|
||||||
|
return nil, errRetriesExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackRetrying) DoResolveHardwareAddress6(addr netip.Addr, timeout time.Duration, retries int) (hw [6]byte, err error) {
|
||||||
|
expectEnd := time.Now().Add(timeout * time.Duration(retries))
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
hw, err = s.block.DoResolveHardwareAddress6(addr, timeout)
|
||||||
|
if err == nil {
|
||||||
|
return hw, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().Before(expectEnd) {
|
||||||
|
return hw, err
|
||||||
|
}
|
||||||
|
return hw, errRetriesExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StackRetrying) DoDialTCP(localPort uint16, addrp netip.AddrPort, timeout time.Duration, retries int) (conn *tcp.Conn, err error) {
|
||||||
|
expectEnd := time.Now().Add(timeout * time.Duration(retries))
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
conn, err = s.block.DoDialTCP(localPort, addrp, timeout)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().Before(expectEnd) {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
return nil, errRetriesExceeded
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user