mirror of
https://github.com/soypat/lneto.git
synced 2026-08-20 06:29:03 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a391811a1 |
@@ -43,7 +43,7 @@ See Developing section below for more information.
|
|||||||
lneto is currently being used primarily by embedded developers and those who need a lighter alternative to gVisor in terms of memory usage and binary size.
|
lneto is currently being used primarily by embedded developers and those who need a lighter alternative to gVisor in terms of memory usage and binary size.
|
||||||
|
|
||||||
- [**soypat/cyw43439**](https://github.com/soypat/cyw43439): Enabling internet access on [Raspberry Pi Pico W](https://www.raspberrypi.com/products/raspberry-pi-pico/). See [`examples`](https://github.com/soypat/cyw43439/tree/main/examples).
|
- [**soypat/cyw43439**](https://github.com/soypat/cyw43439): Enabling internet access on [Raspberry Pi Pico W](https://www.raspberrypi.com/products/raspberry-pi-pico/). See [`examples`](https://github.com/soypat/cyw43439/tree/main/examples).
|
||||||
- [**tinygo-org/espradio**](https://github.com/tinygo-org/espradio): Enabling internet access on [Espressif's ESP32s](https://www.espressif.com/en/products/socs/esp32)
|
- [**tinygo-org/espradio**](https://github.com/tinygo-rog/espradio): Enabling internet access on [Espressif's ESP32s](https://www.espressif.com/en/products/socs/esp32)
|
||||||
- [**TamaGo**](https://github.com/usbarmory/tamago): Enabling networking on Go baremetal projects. See [go-net project](https://github.com/usbarmory/go-net).
|
- [**TamaGo**](https://github.com/usbarmory/tamago): Enabling networking on Go baremetal projects. See [go-net project](https://github.com/usbarmory/go-net).
|
||||||
- [**netbird.io**](https://netbird.io/)(planned): Secure remote P2P access.
|
- [**netbird.io**](https://netbird.io/)(planned): Secure remote P2P access.
|
||||||
- [**soypat/lan8720**](https://github.com/soypat/lan8720): Enabling internet access with 100M ethernet PHY devices.
|
- [**soypat/lan8720**](https://github.com/soypat/lan8720): Enabling internet access with 100M ethernet PHY devices.
|
||||||
@@ -218,4 +218,4 @@ The document has moved
|
|||||||
<A HREF="http://www.google.com/">here</A>.
|
<A HREF="http://www.google.com/">here</A>.
|
||||||
</BODY></HTML>
|
</BODY></HTML>
|
||||||
success
|
success
|
||||||
```
|
```
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
package lneto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Flag return values for a BackoffStrategy.
|
|
||||||
const (
|
|
||||||
BackoffFlagGosched = time.Duration(-1)
|
|
||||||
BackoffFlagNop = time.Duration(-2)
|
|
||||||
)
|
|
||||||
|
|
||||||
// BackoffStrategy is the abstraction of a backoff strategy for retrying an operation.
|
|
||||||
// It returns the amount of time to sleep for or a flag value:
|
|
||||||
// - Returns [BackoffFlagNop]: Signal no yielding function should be called.
|
|
||||||
// Useful for when the BackoffStrategy implements its own yield.
|
|
||||||
// - Returns [BackoffFlagGosched]: Signal [runtime.Gosched] should be called.
|
|
||||||
//
|
|
||||||
// Despite the name(changes welcome) consecutiveBackoffs starts at 0 and
|
|
||||||
// increments by 1 every time the operation is retried.
|
|
||||||
// See internal/backoff.go for a implementation example.
|
|
||||||
type BackoffStrategy func(consecutiveBackoffs uint) (sleepOrFlag time.Duration)
|
|
||||||
|
|
||||||
// Do applies the backoff strategy by calling backoff(consecutiveBackoffs)
|
|
||||||
// and then the corresponding yield function for the returned value. See [BackoffStrategy].
|
|
||||||
func (backoff BackoffStrategy) Do(consecutiveBackoffs uint) {
|
|
||||||
sleep := backoff(consecutiveBackoffs)
|
|
||||||
switch sleep {
|
|
||||||
case BackoffFlagNop:
|
|
||||||
// No yield. Yield implemented by backoff.
|
|
||||||
case BackoffFlagGosched:
|
|
||||||
runtime.Gosched()
|
|
||||||
default:
|
|
||||||
time.Sleep(sleep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -37,12 +37,12 @@ type StackNode interface {
|
|||||||
// SetFlagPending(flagPending func(numPendingEncapsulations int))
|
// SetFlagPending(flagPending func(numPendingEncapsulations int))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:generate stringer -type=IPProto,errGeneric -linecomment -output stringers.go .
|
||||||
|
|
||||||
// IPProto represents the IP protocol number.
|
// IPProto represents the IP protocol number.
|
||||||
type IPProto uint8
|
type IPProto uint8
|
||||||
|
|
||||||
// IP protocol numbers.
|
// IP protocol numbers.
|
||||||
//
|
|
||||||
//go:generate stringer -type=IPProto,errGeneric -linecomment -output stringers.go .
|
|
||||||
const (
|
const (
|
||||||
IPProtoHopByHop IPProto = 0 // IPv6 Hop-by-Hop Option [RFC8200]
|
IPProtoHopByHop IPProto = 0 // IPv6 Hop-by-Hop Option [RFC8200]
|
||||||
IPProtoICMP IPProto = 1 // ICMP [RFC792]
|
IPProtoICMP IPProto = 1 // ICMP [RFC792]
|
||||||
|
|||||||
+2
-13
@@ -5,24 +5,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// MaxMTU defines the maximum payload of a standard ethernet frame. Does NOT include ethernet header, FCS and VLAN tag.
|
|
||||||
// Ethernet frames can be larger but this is out of the 802.3 standard and going into jumbo frame territory.
|
|
||||||
MaxMTU = 1500
|
|
||||||
// MaxFrameLength (1522) defines the maximum length of a standard ethernet frame including headers, FCS and VLAN if present.
|
|
||||||
MaxFrameLength = MaxMTU + MaxOverheadSize
|
|
||||||
// MaxOverheadSize is the maximum overhead a packet can incur
|
// MaxOverheadSize is the maximum overhead a packet can incur
|
||||||
// from transmitting an ethernet frame. Includes:
|
// from transmitting an ethernet frame. Includes:
|
||||||
// - 14 bytes of Ethernet header containing MAC addresses and ethernet type, always present
|
// - 14 bytes of Ethernet header containing MAC addresses and ethernet type, always present
|
||||||
// - 4 bytes of VLAN tag, if present. See 802.1Q.
|
// - 4 bytes of VLAN tag, if present.
|
||||||
// - 4 bytes of the 32 bit trailing CRC, if required by PHY.
|
// - 4 bytes of the 32 bit trailing CRC, if required by PHY.
|
||||||
MaxOverheadSize = sizeHeaderNoVLAN + fcsOverhead + vlanOverhead
|
MaxOverheadSize = 14 + 4 + 4
|
||||||
vlanOverhead = 4
|
|
||||||
fcsOverhead = 4
|
|
||||||
sizeHeaderNoVLAN = 14
|
sizeHeaderNoVLAN = 14
|
||||||
// MinimumFrameLength as defined by IEEE 802.3 standards. Includes ethernet header.
|
|
||||||
MinimumFrameLength = 64
|
|
||||||
// Does not include VLAN/FCS for more conservative minimum MTU.
|
|
||||||
MinimumMTU = MinimumFrameLength - sizeHeaderNoVLAN
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppendAddr appends the text representation of the hardware address to the destination buffer.
|
// AppendAddr appends the text representation of the hardware address to the destination buffer.
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ func run() error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Create blocking + Berkeley stack
|
// Create blocking + Berkeley stack
|
||||||
blocking := stack.StackBlocking(stackBackoff)
|
blocking := stack.StackBlocking(5 * time.Millisecond)
|
||||||
berkeley := blocking.StackGo(xnet.StackGoConfig{
|
berkeley := blocking.StackGo(xnet.StackGoConfig{
|
||||||
ListenerPoolConfig: xnet.TCPPoolConfig{
|
ListenerPoolConfig: xnet.TCPPoolConfig{
|
||||||
PoolSize: uint16(flagPoolSize),
|
PoolSize: uint16(flagPoolSize),
|
||||||
@@ -212,7 +212,7 @@ func run() error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Perform DHCP to get address.
|
// Perform DHCP to get address.
|
||||||
rstack := stack.StackRetrying(stackBackoff)
|
rstack := stack.StackRetrying(5 * time.Millisecond)
|
||||||
const dhcpTimeout = 6 * time.Second
|
const dhcpTimeout = 6 * time.Second
|
||||||
const dhcpRetries = 2
|
const dhcpRetries = 2
|
||||||
results, err := rstack.DoDHCPv4([4]byte{192, 168, 1, 96}, dhcpTimeout, dhcpRetries)
|
results, err := rstack.DoDHCPv4([4]byte{192, 168, 1, 96}, dhcpTimeout, dhcpRetries)
|
||||||
@@ -399,10 +399,3 @@ func mockClient(stack *xnet.StackAsync, port uint16, subnet netip.Prefix) {
|
|||||||
fmt.Println("mockclient: received response:\n", string(page))
|
fmt.Println("mockclient: received response:\n", string(page))
|
||||||
mockConn.Close()
|
mockConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackBackoff(consecutiveBackoffs uint) time.Duration {
|
|
||||||
if consecutiveBackoffs < 10 {
|
|
||||||
return time.Millisecond
|
|
||||||
}
|
|
||||||
return 10 * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ func run() (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rstack := stack.StackRetrying(stackBackoff)
|
rstack := stack.StackRetrying(5 * time.Millisecond)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dhcpTimeout = 6 * time.Second
|
dhcpTimeout = 6 * time.Second
|
||||||
@@ -354,10 +354,3 @@ func tryPoll(iface ltesto.Interface, poll time.Duration) (dataMayBeReady bool, _
|
|||||||
dataMayBeReady = true
|
dataMayBeReady = true
|
||||||
return dataMayBeReady, nil
|
return dataMayBeReady, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackBackoff(consecutiveBackoffs uint) time.Duration {
|
|
||||||
if consecutiveBackoffs < 10 {
|
|
||||||
return time.Millisecond
|
|
||||||
}
|
|
||||||
return 10 * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func run(ctx context.Context, stack *xnet.StackAsync) error {
|
|||||||
// Other option is to instead use async API which leads to more verbose
|
// Other option is to instead use async API which leads to more verbose
|
||||||
// and more stateful code.
|
// and more stateful code.
|
||||||
go stackLoop(ctx, stack)
|
go stackLoop(ctx, stack)
|
||||||
rstack := stack.StackRetrying(stackBackoff)
|
rstack := stack.StackRetrying(pollTime)
|
||||||
results, err := rstack.DoDHCPv4([4]byte{}, protoTimeout, protoRetries)
|
results, err := rstack.DoDHCPv4([4]byte{}, protoTimeout, protoRetries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("doing DHCP: %w", err)
|
return fmt.Errorf("doing DHCP: %w", err)
|
||||||
@@ -93,7 +93,7 @@ func run(ctx context.Context, stack *xnet.StackAsync) error {
|
|||||||
return fmt.Errorf("resolving router MAC: %w", err)
|
return fmt.Errorf("resolving router MAC: %w", err)
|
||||||
}
|
}
|
||||||
stack.SetGateway6(gateway)
|
stack.SetGateway6(gateway)
|
||||||
berkstack := stack.StackBlocking(stackBackoff).StackGo(xnet.StackGoConfig{
|
berkstack := stack.StackBlocking(pollTime).StackGo(xnet.StackGoConfig{
|
||||||
ListenerPoolConfig: xnet.TCPPoolConfig{
|
ListenerPoolConfig: xnet.TCPPoolConfig{
|
||||||
PoolSize: tcpConnPoolSize,
|
PoolSize: tcpConnPoolSize,
|
||||||
QueueSize: tcpPacketQueueSize,
|
QueueSize: tcpPacketQueueSize,
|
||||||
@@ -171,10 +171,3 @@ func must(err error) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackBackoff(consecutiveBackoffs uint) time.Duration {
|
|
||||||
if consecutiveBackoffs < 10 {
|
|
||||||
return time.Millisecond
|
|
||||||
}
|
|
||||||
return 10 * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func run() (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rstack := stack.StackRetrying(stackBackoff)
|
rstack := stack.StackRetrying(5 * time.Millisecond)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dhcpTimeout = 6 * time.Second
|
dhcpTimeout = 6 * time.Second
|
||||||
@@ -382,10 +382,3 @@ func tryPoll(iface ltesto.Interface, poll time.Duration) (dataMayBeReady bool, _
|
|||||||
dataMayBeReady = true
|
dataMayBeReady = true
|
||||||
return dataMayBeReady, nil
|
return dataMayBeReady, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackBackoff(consecutiveBackoffs uint) time.Duration {
|
|
||||||
if consecutiveBackoffs < 10 {
|
|
||||||
return time.Millisecond
|
|
||||||
}
|
|
||||||
return 10 * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|||||||
+53
-30
@@ -1,39 +1,62 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import "time"
|
||||||
"time"
|
|
||||||
|
type BackoffFlags uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
BackoffHasPriority BackoffFlags = 1 << iota
|
||||||
|
BackoffCriticalPath
|
||||||
|
BackoffTCPConn
|
||||||
)
|
)
|
||||||
|
|
||||||
// BackoffConnRW implements exponential backoff suitable for TCP connection
|
const backoffMinWait = time.Microsecond
|
||||||
// read/write polling. It starts at 1us and caps at 5ms, doubling on each consecutive backoff.
|
|
||||||
func BackoffConnRW(consecutiveBackoffs uint) {
|
func backoffMaxWait(priority BackoffFlags) time.Duration {
|
||||||
const (
|
switch {
|
||||||
minWait = uint32(time.Microsecond)
|
case priority&BackoffCriticalPath != 0:
|
||||||
maxWait = 5 * uint32(time.Millisecond)
|
return 1 * time.Millisecond
|
||||||
maxShift = 22
|
case priority&BackoffTCPConn != 0:
|
||||||
_overflowCheck = minWait << maxShift
|
return 5 * time.Millisecond
|
||||||
)
|
default:
|
||||||
wait := minWait << min(consecutiveBackoffs, maxShift)
|
return time.Second >> (priority & BackoffHasPriority)
|
||||||
if wait > maxWait {
|
|
||||||
wait = maxWait
|
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(wait))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackoffStackProto implements exponential backoff suitable for stack-level
|
func NewBackoff(priority BackoffFlags) Backoff {
|
||||||
// protocol processing polling. It starts at 1us and caps at 100ms, doubling on each consecutive backoff.
|
return Backoff{
|
||||||
func BackoffStackProto(consecutiveBackoffs uint) {
|
wait: uint32(backoffMinWait),
|
||||||
const (
|
maxWait: uint32(backoffMaxWait(priority)),
|
||||||
minWait = uint32(time.Microsecond)
|
startWait: uint32(backoffMinWait),
|
||||||
maxWait = 100 * uint32(time.Millisecond)
|
}
|
||||||
|
}
|
||||||
// Statically calculated numbers below.
|
|
||||||
maxShift = 22
|
// A Backoff with a non-zero MaxWait is ready for use.
|
||||||
_overflowCheck = minWait << maxShift
|
type Backoff struct {
|
||||||
)
|
// wait defines the amount of time that Miss will wait on next call.
|
||||||
wait := minWait << min(consecutiveBackoffs, maxShift)
|
wait uint32
|
||||||
if wait > maxWait {
|
// Maximum allowable value for Wait.
|
||||||
wait = maxWait
|
maxWait uint32
|
||||||
|
// startWait is the intial Wait value, as well as the value that Wait takes after a call to Hit.
|
||||||
|
startWait uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hit sets eb.Wait to the StartWait value.
|
||||||
|
func (eb *Backoff) Hit() {
|
||||||
|
if eb.maxWait == 0 {
|
||||||
|
panic("MaxWait cannot be zero")
|
||||||
|
}
|
||||||
|
eb.wait = eb.startWait
|
||||||
|
}
|
||||||
|
|
||||||
|
// Miss sleeps for eb.Wait and increases eb.Wait exponentially.
|
||||||
|
func (eb *Backoff) Miss() {
|
||||||
|
if eb.maxWait == 0 {
|
||||||
|
panic("MaxWait cannot be zero")
|
||||||
|
}
|
||||||
|
time.Sleep(time.Duration(eb.wait))
|
||||||
|
eb.wait *= 2
|
||||||
|
if eb.wait > eb.maxWait {
|
||||||
|
eb.wait = eb.maxWait
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(wait))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,6 +271,20 @@ func (r *Ring) addOff(a, b int) int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Ring) string() string {
|
func (r *Ring) string() string {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
r2 := *r
|
r2 := *r
|
||||||
|
|||||||
@@ -159,8 +159,6 @@ func (h *handlers) encapsulateNode(node *node, buf []byte, offsetIP, offsetThisF
|
|||||||
err = nil // CLOSE error handled gracefully by deleting node.
|
err = nil // CLOSE error handled gracefully by deleting node.
|
||||||
node = nil // Node is destroyed in tryHandleError and invalidated.
|
node = nil // Node is destroyed in tryHandleError and invalidated.
|
||||||
}
|
}
|
||||||
// TODO(soypat): We have fuzz tests in place, maybe we can start returning the error up the chain to catch invalid settings at application level so that users don't have to have logs in place to understand invalid config/buffer size.
|
|
||||||
// Encapsulate should only fail with error on programmer errors.
|
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
return n, err
|
return n, err
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func makeHttpPayload(body string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCap(t *testing.T) {
|
func TestCap(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const httpBody = "{200,ok}"
|
const httpBody = "{200,ok}"
|
||||||
var buf [mtu]byte
|
var buf [mtu]byte
|
||||||
var gen ltesto.PacketGen
|
var gen ltesto.PacketGen
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (ls *StackEthernet) Reset6(mac, gateway [6]byte, mtu, maxNodes int) error {
|
|||||||
// It validates the configuration parameters and resets internal state.
|
// It validates the configuration parameters and resets internal state.
|
||||||
// The connection ID is incremented on each call to invalidate existing connections.
|
// The connection ID is incremented on each call to invalidate existing connections.
|
||||||
func (ls *StackEthernet) Configure(cfg StackEthernetConfig) error {
|
func (ls *StackEthernet) Configure(cfg StackEthernetConfig) error {
|
||||||
if cfg.MTU > ethernet.MaxMTU || cfg.MTU < ethernet.MinimumMTU {
|
if cfg.MTU > (math.MaxUint16-ethernet.MaxOverheadSize) || cfg.MTU < 256 {
|
||||||
return lneto.ErrInvalidConfig
|
return lneto.ErrInvalidConfig
|
||||||
} else if cfg.MaxNodes <= 0 {
|
} else if cfg.MaxNodes <= 0 {
|
||||||
return lneto.ErrInvalidConfig
|
return lneto.ErrInvalidConfig
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ func (sb *StackIP) Demux(carrierData []byte, offset int) error {
|
|||||||
|
|
||||||
func (sb *StackIP) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
|
func (sb *StackIP) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) (int, error) {
|
||||||
frame := carrierData[offsetToFrame:]
|
frame := carrierData[offsetToFrame:]
|
||||||
if len(frame) < ipv4.MinimumMTU {
|
if len(frame) < 256 {
|
||||||
return 0, io.ErrShortBuffer
|
return 0, io.ErrShortBuffer
|
||||||
}
|
}
|
||||||
ifrm, _ := ipv4.NewFrame(frame)
|
ifrm, _ := ipv4.NewFrame(frame)
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package ipv4
|
|||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RFC791 defines the minimum MTU for an IPv4 packet as 68, meaning a payload of 48 bytes when no IPv4 options included.
|
|
||||||
MinimumMTU = 68
|
|
||||||
sizeHeader = 20
|
sizeHeader = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+55
-81
@@ -2,11 +2,11 @@ package tcp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -29,7 +29,6 @@ type Conn struct {
|
|||||||
h Handler
|
h Handler
|
||||||
remoteAddr []byte
|
remoteAddr []byte
|
||||||
|
|
||||||
_backoff lneto.BackoffStrategy
|
|
||||||
rdead time.Time
|
rdead time.Time
|
||||||
wdead time.Time
|
wdead time.Time
|
||||||
abortErr error
|
abortErr error
|
||||||
@@ -55,10 +54,7 @@ type ConnConfig struct {
|
|||||||
RxBuf []byte
|
RxBuf []byte
|
||||||
TxBuf []byte
|
TxBuf []byte
|
||||||
TxPacketQueueSize int
|
TxPacketQueueSize int
|
||||||
// RWBackoff sets the backoff policy for backoff when data unavailable on Read or buffer full on Write.
|
Logger *slog.Logger
|
||||||
// If not set a default backoff strategy will be used. See [internal.BackoffConnRW].
|
|
||||||
RWBackoff lneto.BackoffStrategy
|
|
||||||
Logger *slog.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) Configure(config ConnConfig) (err error) {
|
func (conn *Conn) Configure(config ConnConfig) (err error) {
|
||||||
@@ -68,7 +64,6 @@ func (conn *Conn) Configure(config ConnConfig) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn._backoff = config.RWBackoff
|
|
||||||
conn.logger.log = config.Logger
|
conn.logger.log = config.Logger
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -211,35 +206,29 @@ func (conn *Conn) Write(b []byte) (int, error) {
|
|||||||
} else if plen == 0 {
|
} else if plen == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
backoff := internal.NewBackoff(internal.BackoffTCPConn)
|
||||||
n := 0
|
n := 0
|
||||||
var backoffs uint
|
for {
|
||||||
for len(b) > 0 {
|
|
||||||
if err := conn.checkPipe(connid, &conn.wdead); err != nil {
|
if err := conn.checkPipe(connid, &conn.wdead); err != nil {
|
||||||
return n, err
|
return 0, err
|
||||||
}
|
}
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
towrite := min(conn.h.FreeOutput(), len(b))
|
|
||||||
var ngot int
|
var ngot int
|
||||||
if towrite > 0 {
|
ngot, err = conn.h.Write(b)
|
||||||
ngot, err = conn.h.Write(b[:towrite])
|
conn.mu.Unlock()
|
||||||
conn.mu.Unlock()
|
n += ngot
|
||||||
if err != nil && err != internal.ErrRingBufferFull {
|
b = b[ngot:]
|
||||||
break
|
if (err != nil && err != internal.ErrRingBufferFull) || n == plen {
|
||||||
} else if ngot != towrite {
|
break
|
||||||
panic("unreachable")
|
} else if ngot > 0 {
|
||||||
}
|
backoff.Hit()
|
||||||
n += ngot
|
runtime.Gosched() // Do a little yield since we won't have data for sure otherwise.
|
||||||
b = b[ngot:]
|
|
||||||
backoffs = 0
|
|
||||||
} else {
|
} else {
|
||||||
// No data can be written.
|
backoff.Miss()
|
||||||
conn.mu.Unlock()
|
}
|
||||||
conn.trace("TCPConn.Write:insuf-buf", slog.Int("missing", plen-n), slog.Uint64("lport", uint64(lport)), slog.Uint64("rport", uint64(rport)))
|
conn.trace("TCPConn.Write:insuf-buf", slog.Int("missing", plen-n), slog.Uint64("lport", uint64(lport)), slog.Uint64("rport", uint64(rport)))
|
||||||
if conn.deadlineExceeded(&conn.wdead) {
|
if conn.deadlineExceeded(&conn.wdead) {
|
||||||
return n, errDeadlineExceeded
|
return n, errDeadlineExceeded
|
||||||
}
|
|
||||||
conn.backoff(backoffs)
|
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return n, err
|
return n, err
|
||||||
@@ -250,13 +239,17 @@ func (conn *Conn) Flush() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var backoffs uint
|
if conn.deadlineExceeded(&conn.wdead) {
|
||||||
|
return errDeadlineExceeded
|
||||||
|
} else if conn.BufferedUnsent() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
backoff := internal.NewBackoff(internal.BackoffTCPConn)
|
||||||
for conn.BufferedUnsent() != 0 {
|
for conn.BufferedUnsent() != 0 {
|
||||||
if err := conn.checkPipe(connid, &conn.wdead); err != nil {
|
if err := conn.checkPipe(connid, &conn.wdead); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn.backoff(backoffs)
|
backoff.Miss()
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -265,48 +258,37 @@ func (conn *Conn) Flush() error {
|
|||||||
// Read will block until data is available or connection closes.
|
// Read will block until data is available or connection closes.
|
||||||
// Returns io.EOF when the remote has closed the connection and all buffered data has been read.
|
// Returns io.EOF when the remote has closed the connection and all buffered data has been read.
|
||||||
func (conn *Conn) Read(b []byte) (int, error) {
|
func (conn *Conn) Read(b []byte) (int, error) {
|
||||||
conn.mu.Lock()
|
connid, err := conn.lockPipeConnID()
|
||||||
connID := conn.h.connid
|
if err != nil {
|
||||||
lport := conn.h.localPort
|
if conn.BufferedInput() > 0 {
|
||||||
rport := conn.h.remotePort
|
return conn.handlerRead(b) // Ensure remaining buffered data is read.
|
||||||
conn.mu.Unlock()
|
|
||||||
conn.trace("TCPConn.Read:start", slog.Uint64("lport", uint64(lport)), slog.Uint64("rport", uint64(rport)))
|
|
||||||
var backoffs uint
|
|
||||||
n := 0
|
|
||||||
for len(b) > 0 {
|
|
||||||
conn.mu.Lock()
|
|
||||||
if connID != conn.h.connid {
|
|
||||||
conn.mu.Unlock()
|
|
||||||
return n, net.ErrClosed
|
|
||||||
}
|
|
||||||
avail := conn.h.BufferedInput()
|
|
||||||
if avail > 0 {
|
|
||||||
// Read branch.
|
|
||||||
ngot, err := conn.h.Read(b)
|
|
||||||
conn.mu.Unlock()
|
|
||||||
n += ngot
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
b = b[ngot:]
|
|
||||||
} else if n > 0 {
|
|
||||||
conn.mu.Unlock()
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
state := conn.h.State()
|
|
||||||
conn.mu.Unlock()
|
|
||||||
if state.IsClosed() {
|
|
||||||
return n, net.ErrClosed
|
|
||||||
} else if !state.RxDataOpen() {
|
|
||||||
return n, io.EOF
|
|
||||||
} else if conn.deadlineExceeded(&conn.rdead) {
|
|
||||||
return n, errDeadlineExceeded
|
|
||||||
}
|
|
||||||
conn.backoff(backoffs)
|
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
return n, nil
|
lport := conn.LocalPort()
|
||||||
|
rport := conn.RemotePort()
|
||||||
|
conn.trace("TCPConn.Read:start", slog.Uint64("lport", uint64(lport)), slog.Uint64("rport", uint64(rport)))
|
||||||
|
backoff := internal.NewBackoff(internal.BackoffTCPConn)
|
||||||
|
for conn.BufferedInput() == 0 {
|
||||||
|
state := conn.State()
|
||||||
|
if !state.RxDataOpen() {
|
||||||
|
// No use waiting for data, jump to read and return corresponding error from there.
|
||||||
|
break
|
||||||
|
} else if err := conn.checkPipe(connid, &conn.rdead); err != nil {
|
||||||
|
if conn.BufferedInput() > 0 {
|
||||||
|
return conn.handlerRead(b) // Ensure remaining buffered data is read.
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
backoff.Miss()
|
||||||
|
}
|
||||||
|
return conn.handlerRead(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) handlerRead(b []byte) (int, error) {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) lockPipeConnID() (uint64, error) {
|
func (conn *Conn) lockPipeConnID() (uint64, error) {
|
||||||
@@ -464,11 +446,3 @@ func (conn *Conn) deadlineExceeded(deadline *time.Time) bool {
|
|||||||
func (conn *Conn) ConnectionID() *uint64 {
|
func (conn *Conn) ConnectionID() *uint64 {
|
||||||
return conn.h.ConnectionID()
|
return conn.h.ConnectionID()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) backoff(consecutiveBackoffs uint) {
|
|
||||||
if conn._backoff != nil {
|
|
||||||
conn._backoff.Do(consecutiveBackoffs)
|
|
||||||
} else {
|
|
||||||
internal.BackoffConnRW(consecutiveBackoffs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,34 @@ import (
|
|||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CongestionControl decides how the TCP sender reacts to congestion.
|
||||||
|
//
|
||||||
|
// The implementation may inspect the current handler state and the most recent
|
||||||
|
// segment that caused this control decision. The isTx flag reports whether the
|
||||||
|
// segment being observed was transmitted by this endpoint (true) or received
|
||||||
|
// from the peer (false), which lets the algorithm distinguish between outbound
|
||||||
|
// send-side events and inbound acknowledgment/loss signals.
|
||||||
|
//
|
||||||
|
// The returned retransmit slice contains indices into the handler's transmit
|
||||||
|
// buffer for packets that should be resent now. The indices must refer to the
|
||||||
|
// sender's current retransmission queue / ring buffer ordering as understood by
|
||||||
|
// the Handler. The retransmit slice is owned by the CongestionControl implementation
|
||||||
|
// and is only valid until the next ControlCall.
|
||||||
|
//
|
||||||
|
// haltSendingNewPackets tells the stack whether it should temporarily stop
|
||||||
|
// sending brand-new packets while this congestion event is being handled.
|
||||||
|
// When true, only retransmissions should be sent until the congestion-control
|
||||||
|
// implementation later allows new traffic again.
|
||||||
|
//
|
||||||
|
// Implementations are expected to maintain any needed internal state, such as
|
||||||
|
// congestion window, slow-start threshold, or pacing state.
|
||||||
|
type CongestionControl interface {
|
||||||
|
// Control is called when the stack needs to apply congestion-control policy,
|
||||||
|
// typically after loss has been detected or retransmission logic has been
|
||||||
|
// triggered.
|
||||||
|
Control(h *Handler, segment Segment, isTx bool) (retransmit []int, haltSendingNewPackets bool)
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate stringer -type=State,OptionKind -linecomment -output stringers.go .
|
//go:generate stringer -type=State,OptionKind -linecomment -output stringers.go .
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
+8
-10
@@ -5,12 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/soypat/lneto/ethernet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandler(t *testing.T) {
|
func TestHandler(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const maxpackets = 3
|
const maxpackets = 3
|
||||||
rng := rand.New(rand.NewSource(0))
|
rng := rand.New(rand.NewSource(0))
|
||||||
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
||||||
@@ -149,7 +147,7 @@ func establish(t *testing.T, client, server *Handler, packetBuf []byte) {
|
|||||||
// buffer for its SYN (advertising MSS=100), and the server should not send
|
// buffer for its SYN (advertising MSS=100), and the server should not send
|
||||||
// segments with more than 100 bytes of payload.
|
// segments with more than 100 bytes of payload.
|
||||||
func TestHandler_MSSHonored(t *testing.T) {
|
func TestHandler_MSSHonored(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
rng := rand.New(rand.NewSource(0))
|
rng := rand.New(rand.NewSource(0))
|
||||||
client, server := newHandler(t, mtu, 3), newHandler(t, mtu, 3)
|
client, server := newHandler(t, mtu, 3), newHandler(t, mtu, 3)
|
||||||
setupClientServer(t, rng, client, server)
|
setupClientServer(t, rng, client, server)
|
||||||
@@ -358,7 +356,7 @@ func TestTxBufferFreedOnACK(t *testing.T) {
|
|||||||
// stuck at Window=0 indefinitely after the app frees buffer space via Read().
|
// stuck at Window=0 indefinitely after the app frees buffer space via Read().
|
||||||
func TestWindowUpdateAfterRead(t *testing.T) {
|
func TestWindowUpdateAfterRead(t *testing.T) {
|
||||||
const rxBufSize = 256
|
const rxBufSize = 256
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const maxpackets = 4
|
const maxpackets = 4
|
||||||
rng := rand.New(rand.NewSource(99))
|
rng := rand.New(rand.NewSource(99))
|
||||||
|
|
||||||
@@ -461,7 +459,7 @@ func TestWindowUpdateAfterRead(t *testing.T) {
|
|||||||
// half the buffer do NOT trigger a window update (Silly Window Syndrome avoidance).
|
// half the buffer do NOT trigger a window update (Silly Window Syndrome avoidance).
|
||||||
func TestWindowUpdateSWSAvoidance(t *testing.T) {
|
func TestWindowUpdateSWSAvoidance(t *testing.T) {
|
||||||
const rxBufSize = 256
|
const rxBufSize = 256
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const maxpackets = 4
|
const maxpackets = 4
|
||||||
rng := rand.New(rand.NewSource(77))
|
rng := rand.New(rand.NewSource(77))
|
||||||
|
|
||||||
@@ -568,7 +566,7 @@ func TestWindowUpdateSWSAvoidance(t *testing.T) {
|
|||||||
// 6. Handler.Send() called again → same thing → AddPacket panics because
|
// 6. Handler.Send() called again → same thing → AddPacket panics because
|
||||||
// off=0 but lastPkt.end=0 != bufsize
|
// off=0 but lastPkt.end=0 != bufsize
|
||||||
func TestWriteAfterRemoteFIN(t *testing.T) {
|
func TestWriteAfterRemoteFIN(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const maxpackets = 3
|
const maxpackets = 3
|
||||||
rng := rand.New(rand.NewSource(11))
|
rng := rand.New(rand.NewSource(11))
|
||||||
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
||||||
@@ -637,7 +635,7 @@ func TestWriteAfterRemoteFIN(t *testing.T) {
|
|||||||
// This is a regression test for a bug where RST segments in non-synchronized
|
// This is a regression test for a bug where RST segments in non-synchronized
|
||||||
// states were blocked by errRequireSequential, causing connection pool leaks.
|
// states were blocked by errRequireSequential, causing connection pool leaks.
|
||||||
func TestRSTinSynReceived(t *testing.T) {
|
func TestRSTinSynReceived(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const maxpackets = 3
|
const maxpackets = 3
|
||||||
rng := rand.New(rand.NewSource(2))
|
rng := rand.New(rand.NewSource(2))
|
||||||
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
||||||
@@ -719,7 +717,7 @@ func TestRSTinSynReceived(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// The bug was that reset() cleared bufRx when state became CLOSED.
|
// The bug was that reset() cleared bufRx when state became CLOSED.
|
||||||
func TestBufferNotClearedOnPassiveClose(t *testing.T) {
|
func TestBufferNotClearedOnPassiveClose(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const maxpackets = 3
|
const maxpackets = 3
|
||||||
rng := rand.New(rand.NewSource(1))
|
rng := rand.New(rand.NewSource(1))
|
||||||
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
||||||
@@ -883,7 +881,7 @@ func TestBufferNotClearedOnPassiveClose(t *testing.T) {
|
|||||||
// 5. Handler.Send() called again → AddPacket panics because off=0 but
|
// 5. Handler.Send() called again → AddPacket panics because off=0 but
|
||||||
// lastPkt.end=0 != bufsize
|
// lastPkt.end=0 != bufsize
|
||||||
func TestChallengeACKWithBufferedData(t *testing.T) {
|
func TestChallengeACKWithBufferedData(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const maxpackets = 3
|
const maxpackets = 3
|
||||||
rng := rand.New(rand.NewSource(42))
|
rng := rand.New(rand.NewSource(42))
|
||||||
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
client, server := newHandler(t, mtu, maxpackets), newHandler(t, mtu, maxpackets)
|
||||||
|
|||||||
+14
-47
@@ -22,9 +22,8 @@ type Conn struct {
|
|||||||
|
|
||||||
remoteAddr []byte
|
remoteAddr []byte
|
||||||
|
|
||||||
_backoff lneto.BackoffStrategy
|
rdead time.Time
|
||||||
rdead time.Time
|
wdead time.Time
|
||||||
wdead time.Time
|
|
||||||
|
|
||||||
ipID uint16
|
ipID uint16
|
||||||
}
|
}
|
||||||
@@ -39,10 +38,6 @@ type ConnConfig struct {
|
|||||||
RxQueueSize int
|
RxQueueSize int
|
||||||
// TxQueueSize is the maximum number of outgoing datagrams that can be queued.
|
// TxQueueSize is the maximum number of outgoing datagrams that can be queued.
|
||||||
TxQueueSize int
|
TxQueueSize int
|
||||||
// RWBackoff sets the backoff policy for backoff when data unavailable on Read or buffer full on Write.
|
|
||||||
// This field is ineffective on configuring a [Handler].
|
|
||||||
// If not set a default backoff strategy will be used. See [internal.BackoffConnRW].
|
|
||||||
RWBackoff lneto.BackoffStrategy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure initializes the connection with the given buffer and queue configuration.
|
// Configure initializes the connection with the given buffer and queue configuration.
|
||||||
@@ -55,7 +50,6 @@ func (conn *Conn) Configure(cfg ConnConfig) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn._backoff = cfg.RWBackoff
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,14 +120,13 @@ func (conn *Conn) Write(b []byte) (int, error) {
|
|||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
connID, err := conn.lockPipeConnID()
|
backoff := internal.NewBackoff(internal.BackoffTCPConn)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var backoffs uint
|
|
||||||
for {
|
for {
|
||||||
|
if conn.deadlineExceeded(&conn.wdead) {
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
if conn.h.closeCalled || connID != conn.h.connid {
|
if conn.h.closeCalled {
|
||||||
conn.mu.Unlock()
|
conn.mu.Unlock()
|
||||||
return 0, net.ErrClosed
|
return 0, net.ErrClosed
|
||||||
}
|
}
|
||||||
@@ -142,25 +135,20 @@ func (conn *Conn) Write(b []byte) (int, error) {
|
|||||||
if n > 0 {
|
if n > 0 {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
if conn.deadlineExceeded(&conn.wdead) {
|
backoff.Miss()
|
||||||
return 0, os.ErrDeadlineExceeded
|
|
||||||
}
|
|
||||||
conn.backoff(backoffs)
|
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read dequeues a single datagram. If the buffer is smaller than the datagram,
|
// Read dequeues a single datagram. If the buffer is smaller than the datagram,
|
||||||
// the remaining bytes are discarded (SOCK_DGRAM semantics).
|
// the remaining bytes are discarded (SOCK_DGRAM semantics).
|
||||||
func (conn *Conn) Read(b []byte) (int, error) {
|
func (conn *Conn) Read(b []byte) (int, error) {
|
||||||
connID, err := conn.lockPipeConnID()
|
backoff := internal.NewBackoff(internal.BackoffTCPConn)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
var backoffs uint
|
|
||||||
for {
|
for {
|
||||||
|
if conn.deadlineExceeded(&conn.rdead) {
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
if conn.h.closeCalled && conn.h.BufferedInput() == 0 || connID != conn.h.connid {
|
if conn.h.closeCalled && conn.h.BufferedInput() == 0 {
|
||||||
conn.mu.Unlock()
|
conn.mu.Unlock()
|
||||||
return 0, net.ErrClosed
|
return 0, net.ErrClosed
|
||||||
}
|
}
|
||||||
@@ -169,11 +157,7 @@ func (conn *Conn) Read(b []byte) (int, error) {
|
|||||||
if n > 0 {
|
if n > 0 {
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
if conn.deadlineExceeded(&conn.rdead) {
|
backoff.Miss()
|
||||||
return 0, os.ErrDeadlineExceeded
|
|
||||||
}
|
|
||||||
conn.backoff(backoffs)
|
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,20 +284,3 @@ func (conn *Conn) FreeInput() int {
|
|||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
return conn.h.FreeInput()
|
return conn.h.FreeInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) backoff(consecutiveBackoffs uint) {
|
|
||||||
if conn._backoff != nil {
|
|
||||||
conn._backoff.Do(consecutiveBackoffs)
|
|
||||||
} else {
|
|
||||||
internal.BackoffConnRW(consecutiveBackoffs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *Conn) lockPipeConnID() (uint64, error) {
|
|
||||||
conn.mu.Lock()
|
|
||||||
defer conn.mu.Unlock()
|
|
||||||
if conn.h.closeCalled && len(conn.h.rxDgrams) == 0 {
|
|
||||||
return 0, net.ErrClosed
|
|
||||||
}
|
|
||||||
return conn.h.connid, nil
|
|
||||||
}
|
|
||||||
|
|||||||
+32
-62
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
"github.com/soypat/lneto/dhcpv4"
|
"github.com/soypat/lneto/dhcpv4"
|
||||||
"github.com/soypat/lneto/internal"
|
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,16 +19,22 @@ var (
|
|||||||
errDeadlineExceed = errors.New("cywnet: deadline exceeded")
|
errDeadlineExceed = errors.New("cywnet: deadline exceeded")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *StackAsync) StackBlocking(stackProtoBackoff lneto.BackoffStrategy) StackBlocking {
|
func (s *StackAsync) StackBlocking(loopSleep time.Duration) StackBlocking {
|
||||||
|
if loopSleep < 0 {
|
||||||
|
panic("invalid sleep")
|
||||||
|
} else if loopSleep > 3*time.Second {
|
||||||
|
// loopSleep should be a very small amount of time for stack to remain responsive.
|
||||||
|
panic("StackBlocking sleep too large")
|
||||||
|
}
|
||||||
return StackBlocking{
|
return StackBlocking{
|
||||||
async: s,
|
async: s,
|
||||||
_backoff: stackProtoBackoff,
|
loopSleep: loopSleep,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type StackBlocking struct {
|
type StackBlocking struct {
|
||||||
async *StackAsync
|
async *StackAsync
|
||||||
_backoff lneto.BackoffStrategy
|
loopSleep time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPResults, error) {
|
func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPResults, error) {
|
||||||
@@ -37,31 +42,20 @@ func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPRe
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var backoffs uint
|
sleep := s.loopSleep
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
requested := false
|
requested := false
|
||||||
var lastState dhcpv4.ClientState
|
|
||||||
for i := 0; i < maxIter; i++ {
|
for i := 0; i < maxIter; i++ {
|
||||||
s.async.mu.Lock()
|
|
||||||
state := s.async.dhcp.State()
|
state := s.async.dhcp.State()
|
||||||
s.async.mu.Unlock()
|
requested = requested || state > dhcpv4.StateInit
|
||||||
if state == lastState {
|
if requested && state == dhcpv4.StateInit {
|
||||||
if err = s.checkDeadline(deadline); err != nil {
|
return nil, errors.New("DHCP NACK")
|
||||||
return nil, err
|
} else if state == dhcpv4.StateBound {
|
||||||
}
|
break // DHCP done succesfully.
|
||||||
s.backoff(backoffs)
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
backoffs++
|
return nil, err
|
||||||
} else {
|
|
||||||
// State change indicates something happened.
|
|
||||||
backoffs = 0
|
|
||||||
lastState = 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.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
}
|
}
|
||||||
return s.async.ResultDHCP()
|
return s.async.ResultDHCP()
|
||||||
}
|
}
|
||||||
@@ -71,30 +65,24 @@ func (s StackBlocking) DoPing(hostAddr netip.Addr, timeout time.Duration) (round
|
|||||||
return 0, lneto.ErrInvalidAddr
|
return 0, lneto.ErrInvalidAddr
|
||||||
}
|
}
|
||||||
var buf [16]byte
|
var buf [16]byte
|
||||||
s.async.mu.Lock()
|
|
||||||
s.async.prandRead(buf[:])
|
s.async.prandRead(buf[:])
|
||||||
key, err := s.async.icmp.PingStart(hostAddr.As4(), buf[:], 56) // size=56 so ICMP size is 64, like linux.
|
key, err := s.async.icmp.PingStart(hostAddr.As4(), buf[:], 56) // size=56 so ICMP size is 64, like linux.
|
||||||
s.async.mu.Unlock()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
var backoffs uint
|
sleep := timeout / maxIter
|
||||||
for i := 0; i < maxIter; i++ {
|
for i := 0; i < maxIter; i++ {
|
||||||
s.async.mu.Lock()
|
time.Sleep(sleep)
|
||||||
|
elapsed := time.Since(start)
|
||||||
completed, exists := s.async.icmp.PingPop(key)
|
completed, exists := s.async.icmp.PingPop(key)
|
||||||
s.async.mu.Unlock()
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return 0, net.ErrClosed // lneto.ErrAborted
|
return 0, net.ErrClosed // lneto.ErrAborted
|
||||||
}
|
} else if completed {
|
||||||
elapsed := time.Since(start)
|
|
||||||
if completed {
|
|
||||||
return elapsed, nil
|
return elapsed, nil
|
||||||
} else if elapsed > timeout {
|
} else if elapsed > timeout {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.backoff(backoffs)
|
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
return 0, errDeadlineExceed
|
return 0, errDeadlineExceed
|
||||||
}
|
}
|
||||||
@@ -104,10 +92,9 @@ func (s StackBlocking) DoNTP(hostAddr netip.Addr, timeout time.Duration) (offset
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
sleep := s.loopSleep
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var done bool
|
var done bool
|
||||||
var backoffs uint
|
|
||||||
for i := 0; i < maxIter; i++ {
|
for i := 0; i < maxIter; i++ {
|
||||||
offset, done = s.async.ResultNTPOffset()
|
offset, done = s.async.ResultNTPOffset()
|
||||||
if done {
|
if done {
|
||||||
@@ -115,8 +102,7 @@ func (s StackBlocking) DoNTP(hostAddr netip.Addr, timeout time.Duration) (offset
|
|||||||
} else if err = s.checkDeadline(deadline); err != nil {
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
s.backoff(backoffs)
|
time.Sleep(sleep)
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
return -1, errDeadlineExceed
|
return -1, errDeadlineExceed
|
||||||
}
|
}
|
||||||
@@ -126,7 +112,7 @@ func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return hw, err
|
return hw, err
|
||||||
}
|
}
|
||||||
var backoffs uint
|
sleep := s.loopSleep
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
for i := 0; i < maxIter; i++ {
|
for i := 0; i < maxIter; i++ {
|
||||||
hw, err = s.async.ResultResolveHardwareAddress6(addr)
|
hw, err = s.async.ResultResolveHardwareAddress6(addr)
|
||||||
@@ -135,8 +121,7 @@ func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D
|
|||||||
} else if err = s.checkDeadline(deadline); err != nil {
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
s.backoff(backoffs)
|
time.Sleep(sleep)
|
||||||
backoffs++
|
|
||||||
err = errDeadlineExceed // Ensure that if iterations done error is returned.
|
err = errDeadlineExceed // Ensure that if iterations done error is returned.
|
||||||
}
|
}
|
||||||
ip4 := addr.As4()
|
ip4 := addr.As4()
|
||||||
@@ -149,9 +134,8 @@ func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []n
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
sleep := s.loopSleep
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var backoffs uint
|
|
||||||
for i := 0; i < maxIter; i++ {
|
for i := 0; i < maxIter; i++ {
|
||||||
addrs, completed, err := s.async.ResultLookupIP(host)
|
addrs, completed, err := s.async.ResultLookupIP(host)
|
||||||
if completed {
|
if completed {
|
||||||
@@ -159,8 +143,7 @@ func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []n
|
|||||||
} else if err = s.checkDeadline(deadline); err != nil {
|
} else if err = s.checkDeadline(deadline); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.backoff(backoffs)
|
time.Sleep(sleep)
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
return nil, errDeadlineExceed
|
return nil, errDeadlineExceed
|
||||||
}
|
}
|
||||||
@@ -172,8 +155,8 @@ func (s StackBlocking) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.A
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
sleep := s.loopSleep
|
||||||
deadline := time.Now().Add(timeout)
|
deadline := time.Now().Add(timeout)
|
||||||
var backoffs uint
|
|
||||||
for i := 0; i < maxIter; i++ {
|
for i := 0; i < maxIter; i++ {
|
||||||
state := conn.State()
|
state := conn.State()
|
||||||
if state == tcp.StateEstablished {
|
if state == tcp.StateEstablished {
|
||||||
@@ -183,13 +166,12 @@ func (s StackBlocking) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.A
|
|||||||
conn.Abort()
|
conn.Abort()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
time.Sleep(sleep)
|
||||||
} else {
|
} else {
|
||||||
// Unexpected state, abort and terminate connection.
|
// Unexpected state, abort and terminate connection.
|
||||||
conn.Abort()
|
conn.Abort()
|
||||||
return errTCPFailedToConnect
|
return errTCPFailedToConnect
|
||||||
}
|
}
|
||||||
s.backoff(backoffs)
|
|
||||||
backoffs++
|
|
||||||
}
|
}
|
||||||
return errDeadlineExceed
|
return errDeadlineExceed
|
||||||
}
|
}
|
||||||
@@ -200,15 +182,3 @@ func (s StackBlocking) checkDeadline(deadline time.Time) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s StackBlocking) backoff(consecutiveBackoffs uint) {
|
|
||||||
backoff(s._backoff, consecutiveBackoffs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func backoff(bo lneto.BackoffStrategy, consecutiveBackoffs uint) {
|
|
||||||
if bo != nil {
|
|
||||||
bo.Do(consecutiveBackoffs)
|
|
||||||
} else {
|
|
||||||
internal.BackoffStackProto(consecutiveBackoffs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+7
-11
@@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
@@ -21,8 +22,8 @@ type StackGoConfig struct {
|
|||||||
ListenerPoolConfig TCPPoolConfig
|
ListenerPoolConfig TCPPoolConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StackAsync) StackGo(stackProtoBackoff lneto.BackoffStrategy, cfg StackGoConfig) StackGo {
|
func (s *StackAsync) StackGo(loopSleep time.Duration, cfg StackGoConfig) StackGo {
|
||||||
return s.StackBlocking(stackProtoBackoff).StackGo(cfg)
|
return s.StackBlocking(loopSleep).StackGo(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s StackBlocking) StackGo(cfg StackGoConfig) StackGo {
|
func (s StackBlocking) StackGo(cfg StackGoConfig) StackGo {
|
||||||
@@ -130,10 +131,8 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var backoffs uint
|
|
||||||
for {
|
for {
|
||||||
s.blk.backoff(backoffs)
|
time.Sleep(s.blk.loopSleep)
|
||||||
backoffs++
|
|
||||||
state := conn.State()
|
state := conn.State()
|
||||||
if state == tcp.StateEstablished {
|
if state == tcp.StateEstablished {
|
||||||
tc := tcpconn{
|
tc := tcpconn{
|
||||||
@@ -160,7 +159,7 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
|||||||
}
|
}
|
||||||
var l tcplistener
|
var l tcplistener
|
||||||
l.localAddr = net.TCPAddrFromAddrPort(laddr)
|
l.localAddr = net.TCPAddrFromAddrPort(laddr)
|
||||||
l.sleep = s.blk._backoff
|
l.sleep = s.blk.loopSleep
|
||||||
err = l.l.Reset(laddr.Port(), pool)
|
err = l.l.Reset(laddr.Port(), pool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -178,7 +177,7 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
|||||||
type tcplistener struct {
|
type tcplistener struct {
|
||||||
l tcp.Listener
|
l tcp.Listener
|
||||||
closed bool
|
closed bool
|
||||||
sleep lneto.BackoffStrategy
|
sleep time.Duration
|
||||||
localAddr net.Addr
|
localAddr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,15 +191,12 @@ func (l *tcplistener) Accept() (net.Conn, error) {
|
|||||||
if l.closed {
|
if l.closed {
|
||||||
return nil, net.ErrClosed
|
return nil, net.ErrClosed
|
||||||
}
|
}
|
||||||
var backoffs uint
|
|
||||||
for {
|
for {
|
||||||
n := l.l.NumberOfReadyToAccept()
|
n := l.l.NumberOfReadyToAccept()
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
backoff(l.sleep, backoffs)
|
time.Sleep(l.sleep)
|
||||||
backoffs++
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
backoffs = 0
|
|
||||||
c, _, err := l.l.TryAccept()
|
c, _, err := l.l.TryAccept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/soypat/lneto"
|
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *StackAsync) StackRetrying(stackProtoBackoff lneto.BackoffStrategy) StackRetrying {
|
func (s *StackAsync) StackRetrying(loopSleep time.Duration) StackRetrying {
|
||||||
return StackRetrying{
|
return StackRetrying{
|
||||||
block: s.StackBlocking(stackProtoBackoff),
|
block: s.StackBlocking(loopSleep),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-9
@@ -53,9 +53,6 @@ type TCPPoolConfig struct {
|
|||||||
ClosingTimeout time.Duration
|
ClosingTimeout time.Duration
|
||||||
// NewUserData is used to create user data used for each individual TCP connection and returned on GetTCP.
|
// NewUserData is used to create user data used for each individual TCP connection and returned on GetTCP.
|
||||||
NewUserData func() any
|
NewUserData func() any
|
||||||
// NewBackoff returns the backoff to use for every newly configured TCP connection.
|
|
||||||
// This should always return a static(non-method) function unless you know what you are doing.
|
|
||||||
NewBackoff func() lneto.BackoffStrategy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
|
func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
|
||||||
@@ -79,16 +76,12 @@ func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
|
|||||||
for i := range pool.conns {
|
for i := range pool.conns {
|
||||||
bufoff := i * allocPerConn
|
bufoff := i * allocPerConn
|
||||||
txOff := bufoff + cfg.RxBufSize
|
txOff := bufoff + cfg.RxBufSize
|
||||||
conncfg := tcp.ConnConfig{
|
err := pool.conns[i].Configure(tcp.ConnConfig{
|
||||||
RxBuf: bufSpace[bufoff:txOff],
|
RxBuf: bufSpace[bufoff:txOff],
|
||||||
TxBuf: bufSpace[txOff : txOff+cfg.TxBufSize],
|
TxBuf: bufSpace[txOff : txOff+cfg.TxBufSize],
|
||||||
TxPacketQueueSize: cfg.QueueSize,
|
TxPacketQueueSize: cfg.QueueSize,
|
||||||
Logger: cfg.ConnLogger,
|
Logger: cfg.ConnLogger,
|
||||||
}
|
})
|
||||||
if cfg.NewBackoff != nil {
|
|
||||||
conncfg.RWBackoff = cfg.NewBackoff()
|
|
||||||
}
|
|
||||||
err := pool.conns[i].Configure(conncfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/soypat/lneto/ethernet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestARPLocal(t *testing.T) {
|
func TestARPLocal(t *testing.T) {
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const seed = 1
|
const seed = 1
|
||||||
s1, s2, c1, c2 := newTCPStacks(t, seed, mtu)
|
s1, s2, c1, c2 := newTCPStacks(t, seed, mtu)
|
||||||
routerHw := [6]byte{1, 2, 3, 4, 5, 6}
|
routerHw := [6]byte{1, 2, 3, 4, 5, 6}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func BenchmarkARPExchange(b *testing.B) {
|
func BenchmarkARPExchange(b *testing.B) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const frameSize = ethernet.MaxFrameLength
|
const frameSize = MTU + ethernet.MaxOverheadSize
|
||||||
c1, c2 := new(StackAsync), new(StackAsync)
|
c1, c2 := new(StackAsync), new(StackAsync)
|
||||||
queryAddr := netip.AddrFrom4([4]byte{192, 168, 1, 2})
|
queryAddr := netip.AddrFrom4([4]byte{192, 168, 1, 2})
|
||||||
|
|
||||||
@@ -76,8 +76,8 @@ func BenchmarkARPExchange(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkTCPHandshake(b *testing.B) {
|
func BenchmarkTCPHandshake(b *testing.B) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const frameSize = ethernet.MaxFrameLength
|
const frameSize = MTU + ethernet.MaxOverheadSize
|
||||||
const svPort = 8080
|
const svPort = 8080
|
||||||
client, sv := new(StackAsync), new(StackAsync)
|
client, sv := new(StackAsync), new(StackAsync)
|
||||||
clconn, svconn := new(tcp.Conn), new(tcp.Conn)
|
clconn, svconn := new(tcp.Conn), new(tcp.Conn)
|
||||||
|
|||||||
@@ -4,19 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/soypat/lneto"
|
|
||||||
"github.com/soypat/lneto/ethernet"
|
"github.com/soypat/lneto/ethernet"
|
||||||
"github.com/soypat/lneto/internal"
|
|
||||||
"github.com/soypat/lneto/ipv4"
|
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -147,8 +142,8 @@ func TestTCPListener_ConcurrentEcho(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func kernelLoop(ctx context.Context, server *StackAsync, clients []StackAsync) {
|
func kernelLoop(ctx context.Context, server *StackAsync, clients []StackAsync) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const carrierDataSize = ethernet.MaxFrameLength
|
const carrierDataSize = MTU + ethernet.MaxOverheadSize
|
||||||
buf := make([]byte, carrierDataSize)
|
buf := make([]byte, carrierDataSize)
|
||||||
rng := rand.New(rand.NewSource(1)) // Seed 1 for deterministic but randomized order
|
rng := rand.New(rand.NewSource(1)) // Seed 1 for deterministic but randomized order
|
||||||
order := make([]int, len(clients))
|
order := make([]int, len(clients))
|
||||||
@@ -290,148 +285,3 @@ func runClient(t *testing.T, id int, stack *StackAsync, conn *tcp.Conn,
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCloseTransmitsPending(t *testing.T) {
|
|
||||||
const mtu = ipv4.MinimumMTU
|
|
||||||
const tcpbufsize = mtu * 2
|
|
||||||
const tcpDataPerPkt = mtu - 14 - 20 - 20 // Ethernet=14, IPv4=20, TCP=20
|
|
||||||
const expectPkts = 2*tcpbufsize/tcpDataPerPkt + 1
|
|
||||||
const queueSize = 5
|
|
||||||
const port1, port2 = 10, 20
|
|
||||||
tst := testerFrom(t, mtu)
|
|
||||||
tst.buf = tst.buf[:mtu+14]
|
|
||||||
s1, s2, c1, c2 := newTCPStacks(t, 0x1337_c0de, mtu)
|
|
||||||
t.Run("sync", func(t *testing.T) {
|
|
||||||
// testCloseTransmitsPending(tst, s1, s2, c1, c2, queueSize, tcpbufsize, tcpbufsize, tcpbufsize)
|
|
||||||
})
|
|
||||||
t.Run("async", func(t *testing.T) {
|
|
||||||
testCloseTransmitsPending(tst, s1, s2, c1, c2, queueSize, tcpbufsize, tcpbufsize, 2*tcpbufsize)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCloseTransmitsPending(tst *tester, s1, s2 *StackAsync, c1, c2 *tcp.Conn, queueSize, tx1Buf, rx2Buf, datalen int) {
|
|
||||||
t := tst.t
|
|
||||||
buf := tst.buf
|
|
||||||
defer func() {
|
|
||||||
c1.Abort()
|
|
||||||
c2.Abort()
|
|
||||||
// Ensure they are unregistered.
|
|
||||||
s1.EgressIP(buf)
|
|
||||||
s2.EgressIP(buf)
|
|
||||||
}()
|
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
||||||
Level: slog.LevelDebug - 99,
|
|
||||||
}))
|
|
||||||
err := c1.Configure(tcp.ConnConfig{
|
|
||||||
RxBuf: nil,
|
|
||||||
TxBuf: make([]byte, tx1Buf),
|
|
||||||
TxPacketQueueSize: queueSize,
|
|
||||||
RWBackoff: backoffGosched,
|
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = c2.InternalHandler().SetBuffers(nil, make([]byte, rx2Buf), queueSize)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
const (
|
|
||||||
port1, port2 = 10, 20
|
|
||||||
)
|
|
||||||
tst.TestTCPSetupAndEstablish(s1, s2, c1, c2, port1, port2)
|
|
||||||
if c1.FreeOutput() != tx1Buf {
|
|
||||||
t.Fatalf("want %d free bytes, got %d", tx1Buf, c1.FreeOutput())
|
|
||||||
}
|
|
||||||
data := make([]byte, datalen)
|
|
||||||
for i := range datalen {
|
|
||||||
data[i] = byte(i)
|
|
||||||
}
|
|
||||||
deadline := time.Now().Add(3600 * time.Second)
|
|
||||||
err = c1.SetDeadline(deadline)
|
|
||||||
err2 := c2.SetDeadline(deadline)
|
|
||||||
if err != nil || err2 != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
async := datalen > tx1Buf
|
|
||||||
if async {
|
|
||||||
// Since data does not fit in TCP Tx buffer the test must be run asynchronously.
|
|
||||||
c1.InternalHandler().SetLoggers(logger, logger)
|
|
||||||
// c1.InternalHandler().SetLoggers(nil, nil)
|
|
||||||
go func() {
|
|
||||||
n, err := c1.Write(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("async write", err)
|
|
||||||
} else if n != len(data) {
|
|
||||||
t.Error("io.Writer faulty implementation")
|
|
||||||
}
|
|
||||||
err = c1.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("async close", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
n, err := c1.Write(data)
|
|
||||||
if err != nil || n != len(data) {
|
|
||||||
t.Fatal(err, n)
|
|
||||||
}
|
|
||||||
err = c1.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exchanges := -1
|
|
||||||
exchanging := 1
|
|
||||||
tcpData := 0
|
|
||||||
totalRead := 0
|
|
||||||
for exchanging > 0 || c1.State().TxDataOpen() {
|
|
||||||
exchanges++
|
|
||||||
exchanging = exchangeEthernetOnce(t, s1, s2, buf)
|
|
||||||
frm, ok := getTCPFrame(buf[:exchanging])
|
|
||||||
if ok {
|
|
||||||
n := len(frm.Payload())
|
|
||||||
tcpData += n
|
|
||||||
if async && tcpData > 0 {
|
|
||||||
ngot, err := c2.Read(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else if ngot != n {
|
|
||||||
t.Errorf("want %d data read c1->c1, got %d", n, ngot)
|
|
||||||
} else if !internal.BytesEqual(buf[:n], data[totalRead:totalRead+n]) {
|
|
||||||
t.Errorf("exch%d data rx mismatch, want:\n%q\ngot:\n%q\n", exchanges, data[totalRead:totalRead+n], buf[:n])
|
|
||||||
}
|
|
||||||
totalRead += ngot
|
|
||||||
runtime.Gosched() // Yield to let c1 write via goroutine.
|
|
||||||
acks := exchangeEthernetOnce(t, s2, s1, buf) // Send ACK s1's way.
|
|
||||||
if acks == 0 {
|
|
||||||
t.Error("no data sent back to s1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c1.BufferedUnsent() != 0 {
|
|
||||||
t.Errorf("done %s: want no data left unsent got %d/%d", c1.State(), c1.BufferedUnsent(), len(data))
|
|
||||||
}
|
|
||||||
if tcpData != datalen {
|
|
||||||
t.Errorf("done %s: want %d bytes sent, got %d", c1.State(), len(data), tcpData)
|
|
||||||
}
|
|
||||||
if t.Failed() {
|
|
||||||
t.Logf("test params: txsz1=%d rxsz2=%d queuesize=%d data(sent/had)=%d/%d", tx1Buf, rx2Buf, queueSize, tcpData, datalen)
|
|
||||||
}
|
|
||||||
if totalRead < datalen {
|
|
||||||
n, err := c2.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
} else if !internal.BytesEqual(buf[:n], data[totalRead:]) {
|
|
||||||
t.Errorf("expected last bytes equal: want:\n%q\ngot:\n%q\n", data[totalRead:], buf[:n])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func backoffGosched(consecutiveBackoffs uint) (sleep time.Duration) {
|
|
||||||
return lneto.BackoffFlagGosched
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/soypat/lneto/ethernet"
|
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTCPConn_SetDeadline_Established(t *testing.T) {
|
func TestTCPConn_SetDeadline_Established(t *testing.T) {
|
||||||
const seed = 9999
|
const seed = 9999
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const svPort = 8080
|
const svPort = 8080
|
||||||
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
tst := testerFrom(t, MTU)
|
tst := testerFrom(t, MTU)
|
||||||
@@ -38,7 +37,7 @@ func TestTCPConn_SetDeadline_Established(t *testing.T) {
|
|||||||
|
|
||||||
func TestTCPConn_ReadDeadlineExceeded(t *testing.T) {
|
func TestTCPConn_ReadDeadlineExceeded(t *testing.T) {
|
||||||
const seed = 10001
|
const seed = 10001
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const svPort = 8080
|
const svPort = 8080
|
||||||
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
tst := testerFrom(t, MTU)
|
tst := testerFrom(t, MTU)
|
||||||
@@ -64,7 +63,7 @@ func TestTCPConn_ReadDeadlineExceeded(t *testing.T) {
|
|||||||
|
|
||||||
func TestTCPConn_WriteDeadlineExceeded(t *testing.T) {
|
func TestTCPConn_WriteDeadlineExceeded(t *testing.T) {
|
||||||
const seed = 10002
|
const seed = 10002
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const svPort = 8080
|
const svPort = 8080
|
||||||
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
tst := testerFrom(t, MTU)
|
tst := testerFrom(t, MTU)
|
||||||
@@ -89,7 +88,7 @@ func TestTCPConn_WriteDeadlineExceeded(t *testing.T) {
|
|||||||
|
|
||||||
func TestTCPConn_FlushEmptyNoop(t *testing.T) {
|
func TestTCPConn_FlushEmptyNoop(t *testing.T) {
|
||||||
const seed = 10003
|
const seed = 10003
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const svPort = 8080
|
const svPort = 8080
|
||||||
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
tst := testerFrom(t, MTU)
|
tst := testerFrom(t, MTU)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
func TestDNS_QueryReceivesAnswer(t *testing.T) {
|
func TestDNS_QueryReceivesAnswer(t *testing.T) {
|
||||||
const seed = 9876
|
const seed = 9876
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
|
|
||||||
// Create client stack with DNS server configured.
|
// Create client stack with DNS server configured.
|
||||||
client := new(StackAsync)
|
client := new(StackAsync)
|
||||||
@@ -47,7 +47,7 @@ func TestDNS_QueryReceivesAnswer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Client sends DNS query.
|
// Client sends DNS query.
|
||||||
const carrierDataSize = ethernet.MaxFrameLength
|
const carrierDataSize = MTU + ethernet.MaxOverheadSize
|
||||||
var buf [carrierDataSize]byte
|
var buf [carrierDataSize]byte
|
||||||
n, err := client.EgressEthernet(buf[:])
|
n, err := client.EgressEthernet(buf[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func FuzzStackPacketHTTP(f *testing.F) {
|
func FuzzStackPacketHTTP(f *testing.F) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const seed = 1
|
const seed = 1
|
||||||
var buf [ethernet.MaxFrameLength]byte
|
var buf [MTU + ethernet.MaxOverheadSize]byte
|
||||||
s1, s2, c1, c2 := newTCPStacks(f, seed, MTU)
|
s1, s2, c1, c2 := newTCPStacks(f, seed, MTU)
|
||||||
var hdr httpraw.Header
|
var hdr httpraw.Header
|
||||||
err := s1.ListenTCP(c1, 80)
|
err := s1.ListenTCP(c1, 80)
|
||||||
@@ -78,7 +78,7 @@ func FuzzStackPacketHTTP(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.Fuzz(func(t *testing.T, pktnum int, a []byte) {
|
f.Fuzz(func(t *testing.T, pktnum int, a []byte) {
|
||||||
var buf [ethernet.MaxFrameLength]byte
|
var buf [MTU + ethernet.MaxOverheadSize]byte
|
||||||
s1, s2, c1, c2 := newTCPStacks(t, seed, MTU)
|
s1, s2, c1, c2 := newTCPStacks(t, seed, MTU)
|
||||||
err = s1.EnableICMP(true)
|
err = s1.EnableICMP(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -268,8 +268,8 @@ func testStackSeeded(t *testing.T, seed1, seed2 int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mtu = ethernet.MaxMTU
|
const mtu = 1500
|
||||||
const mfl = mtu + ethernet.MaxOverheadSize // frame length includes ethernet header
|
const mfl = mtu + 14 // frame length includes ethernet header
|
||||||
var buf [mfl]byte
|
var buf [mfl]byte
|
||||||
var s1, s2 StackAsync
|
var s1, s2 StackAsync
|
||||||
v1, v2 := byte(seed1), byte(seed2)
|
v1, v2 := byte(seed1), byte(seed2)
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ func TestStackAsync_ICMPEcho(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
echoSent := exchangeEthernetOnce(t, sender, receiver, buf)
|
echoSent := exchangeEthernetOnce(t, sender, receiver, buf)
|
||||||
if echoSent == 0 {
|
if !echoSent {
|
||||||
t.Error("ECHO not sent")
|
t.Error("ECHO not sent")
|
||||||
}
|
}
|
||||||
echoReplySent := exchangeEthernetOnce(t, receiver, sender, buf)
|
echoReplySent := exchangeEthernetOnce(t, receiver, sender, buf)
|
||||||
if echoReplySent == 0 {
|
if !echoReplySent {
|
||||||
t.Error("ECHOREPLY not sent")
|
t.Error("ECHOREPLY not sent")
|
||||||
}
|
}
|
||||||
n, err = sender.EgressEthernet(buf)
|
n, err = sender.EgressEthernet(buf)
|
||||||
@@ -70,19 +70,19 @@ func TestStackAsync_ICMPEcho(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// exchangeEthernetOnce sends one Ethernet frame from src to dst if available.
|
// exchangeEthernetOnce sends one Ethernet frame from src to dst if available.
|
||||||
func exchangeEthernetOnce(t *testing.T, src, dst *StackAsync, buf []byte) int {
|
func exchangeEthernetOnce(t *testing.T, src, dst *StackAsync, buf []byte) bool {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
n, err := src.EgressEthernet(buf)
|
n, err := src.EgressEthernet(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return 0
|
return false
|
||||||
}
|
}
|
||||||
if err := dst.IngressEthernet(buf[:n]); err != nil {
|
if err := dst.IngressEthernet(buf[:n]); err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return n
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// newICMPStacks creates two test stacks with distinct static addresses and hardware addresses.
|
// newICMPStacks creates two test stacks with distinct static addresses and hardware addresses.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
func TestStackAsyncListener_SingleConnection(t *testing.T) {
|
func TestStackAsyncListener_SingleConnection(t *testing.T) {
|
||||||
const seed int64 = 1234
|
const seed int64 = 1234
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const carrierSize = MTU + ethernet.MaxOverheadSize
|
const carrierSize = MTU + ethernet.MaxOverheadSize
|
||||||
const svPort = 80
|
const svPort = 80
|
||||||
const clPort = 1337
|
const clPort = 1337
|
||||||
@@ -122,7 +122,7 @@ func TestStackAsyncListener_SingleConnection(t *testing.T) {
|
|||||||
|
|
||||||
func TestStackAsyncListener_MultiSequentialConn(t *testing.T) {
|
func TestStackAsyncListener_MultiSequentialConn(t *testing.T) {
|
||||||
const seed int64 = 1234
|
const seed int64 = 1234
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const carrierSize = MTU + ethernet.MaxOverheadSize
|
const carrierSize = MTU + ethernet.MaxOverheadSize
|
||||||
const svPort = 80
|
const svPort = 80
|
||||||
const clPort = 1337
|
const clPort = 1337
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMDNS_QueryResponse(t *testing.T) {
|
func TestMDNS_QueryResponse(t *testing.T) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
svcName, err := dns.NewName("My Web._http._tcp.local")
|
svcName, err := dns.NewName("My Web._http._tcp.local")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -196,7 +196,7 @@ func TestMDNS_QueryResponse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMDNS_SRVThroughStack(t *testing.T) {
|
func TestMDNS_SRVThroughStack(t *testing.T) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
svcName, err := dns.NewName("My Web._http._tcp.local")
|
svcName, err := dns.NewName("My Web._http._tcp.local")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -275,7 +275,7 @@ func newMDNSStack(t *testing.T, hostname string, seed int64,
|
|||||||
mdnsCfg mdns.ClientConfig,
|
mdnsCfg mdns.ClientConfig,
|
||||||
) (*StackAsync, *mdns.Client) {
|
) (*StackAsync, *mdns.Client) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
stack := new(StackAsync)
|
stack := new(StackAsync)
|
||||||
err := stack.Reset(StackConfig{
|
err := stack.Reset(StackConfig{
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
@@ -335,7 +335,7 @@ func mdnsQueryRespond(t *testing.T, querier, responder *StackAsync, buf []byte)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMDNS_RealWorldQueries(t *testing.T) {
|
func TestMDNS_RealWorldQueries(t *testing.T) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
|
|
||||||
responderMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01}
|
responderMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01}
|
||||||
querierMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02}
|
querierMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02}
|
||||||
|
|||||||
+7
-35
@@ -9,7 +9,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/soypat/lneto"
|
|
||||||
"github.com/soypat/lneto/arp"
|
"github.com/soypat/lneto/arp"
|
||||||
"github.com/soypat/lneto/ethernet"
|
"github.com/soypat/lneto/ethernet"
|
||||||
"github.com/soypat/lneto/internal"
|
"github.com/soypat/lneto/internal"
|
||||||
@@ -30,7 +29,7 @@ const (
|
|||||||
|
|
||||||
func TestTCPConn_ReadBlocksUntilDataAvailable(t *testing.T) {
|
func TestTCPConn_ReadBlocksUntilDataAvailable(t *testing.T) {
|
||||||
const seed = 5678
|
const seed = 5678
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const svPort = 8080
|
const svPort = 8080
|
||||||
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
tst := testerFrom(t, MTU)
|
tst := testerFrom(t, MTU)
|
||||||
@@ -152,7 +151,7 @@ func TestStackAsyncTCP_multipacket(t *testing.T) {
|
|||||||
|
|
||||||
func TestStackAsyncTCP_singlepacket(t *testing.T) {
|
func TestStackAsyncTCP_singlepacket(t *testing.T) {
|
||||||
const seed = 1234
|
const seed = 1234
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const svPort = 80
|
const svPort = 80
|
||||||
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
tst := testerFrom(t, MTU)
|
tst := testerFrom(t, MTU)
|
||||||
@@ -284,19 +283,8 @@ func (tst *tester) TestTCPHandshake(stack1, stack2 *StackAsync) {
|
|||||||
noExchange(0),
|
noExchange(0),
|
||||||
noExchange(1),
|
noExchange(1),
|
||||||
}
|
}
|
||||||
var got [len(exch)]struct {
|
for _, wants := range exch {
|
||||||
seg tcp.Segment
|
tst.TCPExchange(wants, stack1, stack2)
|
||||||
}
|
|
||||||
for i, wants := range exch {
|
|
||||||
haveFailed := tst.t.Failed()
|
|
||||||
got[i].seg = tst.TCPExchange(wants, stack1, stack2)
|
|
||||||
if haveFailed != tst.t.Failed() {
|
|
||||||
tst.t.Logf("print out sent segments (%d):\n", i+1)
|
|
||||||
for k := range i + 1 {
|
|
||||||
str := tcp.StringExchange(got[k].seg, 255, 255, exch[k].SourceIdx == 0) // states unknown.
|
|
||||||
tst.t.Log(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,7 +734,7 @@ func (tst *tester) getARPOperation() arp.Operation {
|
|||||||
// The bug was that reset() cleared bufRx when state became CLOSED.
|
// The bug was that reset() cleared bufRx when state became CLOSED.
|
||||||
func TestTCPConn_BufferNotClearedOnPassiveClose(t *testing.T) {
|
func TestTCPConn_BufferNotClearedOnPassiveClose(t *testing.T) {
|
||||||
const seed = 9999
|
const seed = 9999
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const svPort = 8080
|
const svPort = 8080
|
||||||
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
client, sv, clconn, svconn := newTCPStacks(t, seed, MTU)
|
||||||
tst := testerFrom(t, MTU)
|
tst := testerFrom(t, MTU)
|
||||||
@@ -927,8 +915,8 @@ func TestTCPConn_BufferNotClearedOnPassiveClose(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStackAsync_ICMPEchoChecksum(t *testing.T) {
|
func TestStackAsync_ICMPEchoChecksum(t *testing.T) {
|
||||||
const MTU = ethernet.MaxMTU
|
const MTU = 1500
|
||||||
const MaxFrameLength = MTU + ethernet.MaxOverheadSize // Ethernet header+FCS+VLAN.
|
const MaxFrameLength = MTU + 14 + 4 // Ethernet header+FCS.
|
||||||
stackAddr := netip.AddrFrom4([4]byte{192, 168, 1, 99})
|
stackAddr := netip.AddrFrom4([4]byte{192, 168, 1, 99})
|
||||||
stackMAC := [6]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
|
stackMAC := [6]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
|
||||||
routerAddr := [4]byte{192, 168, 1, 1}
|
routerAddr := [4]byte{192, 168, 1, 1}
|
||||||
@@ -1029,19 +1017,3 @@ const (
|
|||||||
protoIPv4 = "IPv4"
|
protoIPv4 = "IPv4"
|
||||||
protoTCP = "TCP"
|
protoTCP = "TCP"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getTCPFrame(etherFrame []byte) (tcp.Frame, bool) {
|
|
||||||
efrm, err := ethernet.NewFrame(etherFrame)
|
|
||||||
if err != nil || efrm.EtherTypeOrSize() != ethernet.TypeIPv4 {
|
|
||||||
return tcp.Frame{}, false
|
|
||||||
}
|
|
||||||
ifrm, err := ipv4.NewFrame(efrm.Payload())
|
|
||||||
if err != nil || ifrm.Protocol() != lneto.IPProtoTCP {
|
|
||||||
return tcp.Frame{}, false
|
|
||||||
}
|
|
||||||
tfrm, err := tcp.NewFrame(ifrm.Payload())
|
|
||||||
if err != nil {
|
|
||||||
return tcp.Frame{}, false
|
|
||||||
}
|
|
||||||
return tfrm, true
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user