Fix slow exchanges on linux by adding polling and other improvements (#14)

* add prints to investigate slowness

* add loop sleep for blocking Stack

* add connection close http attribute and detect end of HTML page

* add timeout to bridge read

* examples/xnet: add ntp timestamp to prints and show output in readme

* add polling to http tap interface

* more digits in ntp format

* prevent crashes on non-8 timestamp
This commit is contained in:
Pat Whittingslow
2026-01-01 09:35:35 -03:00
committed by GitHub
parent 3cbef6fa92
commit f8166aea05
9 changed files with 297 additions and 36 deletions
+5
View File
@@ -15,6 +15,10 @@ vendor/
*.so
*.dylib
*.hex
# Profiling
*.pprof
# example binaries.
/tap
/stack
@@ -46,6 +50,7 @@ local
*.zip
*.gz
*.tar
*.txt
# If running a python script.
*/__pycache__/*
+76 -1
View File
@@ -100,5 +100,80 @@ Now run the application you wish to test without elevated privilidges. Stackbasi
go run ./examples/stackbasic
```
**Wireshark**: Using the provided method of interfacing mean's you'll always be able to easily reach the TAP interface on your machine over HTTP from any process, be it Python or Go. To visualize the packets over the interface we suggest using wireshark and selecting the `tap0` interface which will show all activity over the HTTP TAP interface created with [`./examples/tap`](./examples/tap/main.go).
### Wireshark and Packet Capture API
Using the provided method of interfacing mean's you'll always be able to easily reach the TAP interface on your machine over HTTP from any process, be it Python or Go. To visualize the packets over the interface we suggest using **Wireshark** and selecting the `tap0` interface which will show all activity over the HTTP TAP interface created with [`./examples/tap`](./examples/tap/main.go).
Alternatively there's the [`internet/pcap`](./internet/pcap) package that does the same thing as Wireshark but as a Go API. Here's the result of running xnet example with pcap logging:
```log
go run ./examples/xnet -httpget -host google.com -ihttp -ntp
softrand 1767229198
NIC hardware address: d8:5e:d3:43:03:eb bridgeHW: d8:5e:d3:43:03:eb mtu: 1500 addr: 192.168.1.53/24
OUT 328 [Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=255.255.255.255 | UDP [RFC768] len=8; (Source port)=68; (Destination port)=67 | DHCPv4 len=285; op=1; Flags=0x0000; (Client Address)=us; (Offered Address)=us; (Server Next Address)=255.255.255.255; (Relay Agent Address)=us; (Client Hardware Address)=d85e:d343:3eb::]
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=192.168.1.53 | ICMP [RFC792] len=64]
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=192.168.1.53 | ICMP [RFC792] len=64]
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=192.168.1.53 | ICMP [RFC792] len=64]
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=192.168.1.53 | ICMP [RFC792] len=64]
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=192.168.1.53 | ICMP [RFC792] len=64]
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=192.168.1.53 | ICMP [RFC792] len=64]
IN 60 [Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=e8:4d:74:9f:61:4a | ARP len=28; op=1; (Sender hardware address)=e8:4d:74:9f:61:4a; (Sender protocol address)=192.168.1.1; (Target hardware address)=00:00:00:00:00:00; (Target protocol address)=192.168.1.53]
IN 590 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x0000; source=192.168.1.1; destination=192.168.1.53 | UDP [RFC768] len=8; (Source port)=67; (Destination port)=68 | DHCPv4 len=273; op=2; Flags=0x0000; (Client Address)=us; (Offered Address)=192.168.1.53; (Server Next Address)=us; (Relay Agent Address)=us; (Client Hardware Address)=d85e:d343:3eb::]
OUT 326 [Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=255.255.255.255 | UDP [RFC768] len=8; (Source port)=68; (Destination port)=67 | DHCPv4 len=283; op=1; Flags=0x0000; (Client Address)=us; (Offered Address)=192.168.1.53; (Server Next Address)=us; (Relay Agent Address)=us; (Client Hardware Address)=d85e:d343:3eb::]
IN 590 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x0000; source=192.168.1.1; destination=192.168.1.53 | UDP [RFC768] len=8; (Source port)=67; (Destination port)=68 | DHCPv4 len=273; op=2; Flags=0x0000; (Client Address)=us; (Offered Address)=192.168.1.53; (Server Next Address)=us; (Relay Agent Address)=us; (Client Hardware Address)=d85e:d343:3eb::]
[119ms] DHCP request completed
2025/12/31 21:59:58 INFO dhcp-complete assignedIP=192.168.1.53 routerIP=192.168.1.1 DNS=[192.168.1.1] subnet=192.168.1.0/24
OUT 42 [Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=us | ARP len=28; op=1; (Sender hardware address)=us; (Sender protocol address)=us; (Target hardware address)=00:00:00:00:00:00; (Target protocol address)=192.168.1.1]
IN 60 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | ARP len=28; op=2; (Sender hardware address)=e8:4d:74:9f:61:4a; (Sender protocol address)=192.168.1.1; (Target hardware address)=us; (Target protocol address)=us]
[1.1s] Router ARP resolution
IN 60 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | ARP len=28; op=1; (Sender hardware address)=e8:4d:74:9f:61:4a; (Sender protocol address)=192.168.1.1; (Target hardware address)=00:00:00:00:00:00; (Target protocol address)=us]
OUT 42 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | ARP len=28; op=2; (Sender hardware address)=us; (Sender protocol address)=us; (Target hardware address)=e8:4d:74:9f:61:4a; (Target protocol address)=192.168.1.1]
OUT 83 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=192.168.1.1 | UDP [RFC768] len=8; (Source port)=57216; (Destination port)=53 | DNS len=41]
IN 147 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=us | UDP [RFC768] len=8; (Source port)=53; (Destination port)=57216 | DNS len=105]
[3.5s] NTP IP lookup
OUT 90 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=170.210.222.10 | UDP [RFC768] len=8; (Source port)=1023; (Destination port)=123 | NTP len=48; (Reference Time)=1900-01-01T00:00:00; (Origin Time)=1900-01-01T00:00:00; (Receive Time)=1900-01-01T00:00:00; (Transit Time)=1900-01-01T00:00:00]
IN 90 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x0000; source=170.210.222.10; destination=us | UDP [RFC768] len=8; (Source port)=123; (Destination port)=1023 | NTP len=48; (Reference Time)=2026-01-01T00:43:04; (Origin Time)=1900-01-01T00:00:00; (Receive Time)=2026-01-01T01:00:03; (Transit Time)=2026-01-01T01:00:03]
[786ms] NTP exchange
NTP completed. You are 11.493283ms ahead of the NTP server
OUT 81 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=192.168.1.1 | UDP [RFC768] len=8; (Source port)=56316; (Destination port)=53 | DNS len=39]
IN 97 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=us | UDP [RFC768] len=8; (Source port)=53; (Destination port)=56316 | DNS len=55]
[1s] resolve google.com
DNS resolution of "google.com" complete and resolved to [142.251.129.142]
[10µs] create HTTP GET request
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=us | ICMP [RFC792] len=64]
OUT 58 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=142.251.129.142 | TCP [RFC9293] len=24; (Source port)=51982; (Destination port)=80; flags=SYN]
IN 98 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=192.168.1.1; destination=us | ICMP [RFC792] len=64]
IN 60 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=142.251.129.142; destination=us | TCP [RFC9293] len=24; (Source port)=80; (Destination port)=51982; flags=SYN,ACK | payload? len=2]
OUT 54 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=142.251.129.142 | TCP [RFC9293] len=20; (Source port)=51982; (Destination port)=80; flags=ACK]
[646ms] TCP dial (handshake)
[5µs] send HTTP request
OUT 161 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=142.251.129.142 | TCP [RFC9293] len=20; (Source port)=51982; (Destination port)=80; flags=PSH,ACK | HTTP len=107]
IN 60 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x0000; source=142.251.129.142; destination=us | TCP [RFC9293] len=20; (Source port)=80; (Destination port)=51982; flags=ACK | payload? len=6]
IN 846 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x0000; source=142.251.129.142; destination=us | TCP [RFC9293] len=20; (Source port)=80; (Destination port)=51982; flags=PSH,ACK | HTTP len=792]
IN 60 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x0000; source=142.251.129.142; destination=us | TCP [RFC9293] len=20; (Source port)=80; (Destination port)=51982; flags=FIN,ACK | payload? len=6]
IN 804 [Ethernet len=14; destination=us; source=e8:4d:74:9f:61:4a | IPv4 len=20; (Type of Service)=0x00; flags=0x0000; source=142.251.129.142; destination=us | TCP [RFC9293] len=20; (Source port)=80; (Destination port)=51982; flags=ACK | HTTP len=750]
OUT 54 [Ethernet len=14; destination=e8:4d:74:9f:61:4a; source=us | IPv4 len=20; (Type of Service)=0x00; flags=0x4000; source=us; destination=142.251.129.142 | TCP [RFC9293] len=20; (Source port)=51982; (Destination port)=80; flags=ACK]
[2.9s] recv http request
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-v-ysoE0WjLlAMlo2ek5UrA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp
Date: Thu, 01 Jan 2026 01:00:08 GMT
Expires: Sat, 31 Jan 2026 01:00:08 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Connection: close
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
success
```
+91 -4
View File
@@ -13,6 +13,7 @@ import (
"net/netip"
"os"
"runtime"
"runtime/pprof"
"strings"
"time"
@@ -47,6 +48,7 @@ func run() (err error) {
flagDoNTP = false
flagHTTPGet = false
flagNoPcap = false
flagPprof = false
)
flag.BoolVar(&flagHTTPGet, "httpget", flagHTTPGet, "Do an HTTP GET request ")
flag.StringVar(&flagInterface, "i", flagInterface, "Interface to use. Either tap* or the name of an existing interface to bridge to.")
@@ -55,7 +57,16 @@ func run() (err error) {
flag.StringVar(&flagRequestedIP, "addr", flagRequestedIP, "IP address to request via DHCP.")
flag.BoolVar(&flagDoNTP, "ntp", flagDoNTP, "Do NTP round and print result time")
flag.BoolVar(&flagNoPcap, "nopcap", flagNoPcap, "Disable pcap logging.")
flag.BoolVar(&flagPprof, "pprof", flagPprof, "Enable CPU profiling.")
flag.Parse()
if flagPprof {
var b bytes.Buffer
pprof.StartCPUProfile(&b)
defer func() {
pprof.StopCPUProfile()
os.WriteFile("xnet.pprof", b.Bytes(), 0777)
}()
}
fmt.Println("softrand", softRand)
_, err = dns.NewName(flagHostToResolve)
if err != nil {
@@ -77,6 +88,10 @@ func run() (err error) {
if err != nil {
return err
}
err = bridge.SetReadTimeout(5 * time.Millisecond)
if err != nil {
return err
}
iface = bridge
}
}
@@ -108,6 +123,7 @@ func run() (err error) {
if err != nil {
return err
}
// Loop goroutine.
go func() {
lastAction := time.Now()
@@ -115,7 +131,7 @@ func run() (err error) {
var cap pcap.PacketBreakdown
var frames []pcap.Frame
pf := pcap.Formatter{
FilterClasses: []pcap.FieldClass{pcap.FieldClassFlags, pcap.FieldClassOperation, pcap.FieldClassDst, pcap.FieldClassSrc, pcap.FieldClassAddress},
FilterClasses: []pcap.FieldClass{pcap.FieldClassFlags, pcap.FieldClassOperation, pcap.FieldClassDst, pcap.FieldClassSrc, pcap.FieldClassAddress, pcap.FieldClassTimestamp},
}
var pfbuf []byte
logFrames := func(context string, pkt []byte) error {
@@ -158,6 +174,14 @@ func run() (err error) {
}
clear(buf[:nwrite])
// Poll before read if interface supports it, to avoid blocking indefinitely.
ready, err := tryPoll(iface, 5*time.Millisecond)
if err != nil {
log.Fatal("goroutine poll:", err)
}
if !ready {
continue
}
nread, err := iface.Read(buf)
if err != nil {
log.Fatal("groutine read:", err)
@@ -181,16 +205,18 @@ func run() (err error) {
}
}()
rstack := stack.StackRetrying()
rstack := stack.StackRetrying(5 * time.Millisecond)
const (
dhcpTimeout = 6 * time.Second
dhcpRetries = 2
)
timeDHCP := timer("DHCP request completed")
results, err := rstack.DoDHCPv4([4]byte{192, 168, 1, 96}, dhcpTimeout, dhcpRetries)
if err != nil {
return fmt.Errorf("DHCP failed: %w", err)
}
timeDHCP()
err = stack.AssimilateDHCPResults(results)
if err != nil {
return fmt.Errorf("assimilating DHCP results: %w", err)
@@ -204,31 +230,39 @@ func run() (err error) {
internetTimeout = 3 * time.Second
internetRetries = 2
)
timeResolveRouterHW := timer("Router ARP resolution")
routerHw, err := rstack.DoResolveHardwareAddress6(results.Router, arpTimeout, arpRetries)
if err != nil {
return fmt.Errorf("ARP resolution of router failed: %w", err)
}
timeResolveRouterHW()
stack.SetGateway6(routerHw)
if flagDoNTP {
timeLookupNTP := timer("NTP IP lookup")
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)
}
timeLookupNTP()
timeNTP := timer("NTP exchange")
offset, err := rstack.DoNTP(addrs[0], internetTimeout, internetRetries)
if err != nil {
return fmt.Errorf("NTP address lookup of %q failed: %w", ntpHost, err)
}
timeNTP()
relative := "behind"
if offset < 0 {
relative = "ahead"
}
fmt.Println("NTP completed. You are", offset.Abs().String(), relative, "of the NTP server")
}
timeResolveIP := timer("resolve " + flagHostToResolve)
addrs, err := rstack.DoLookupIP(flagHostToResolve, internetTimeout, internetRetries)
if err != nil {
return fmt.Errorf("DNS of host %q failed: %w", flagHostToResolve, err)
}
timeResolveIP()
fmt.Printf("DNS resolution of %q complete and resolved to %v\n", flagHostToResolve, addrs)
var conn tcp.Conn
conn.Configure(tcp.ConnConfig{
@@ -237,6 +271,7 @@ func run() (err error) {
TxPacketQueueSize: 3,
})
if flagHTTPGet {
timeHTTPCreate := timer("create HTTP GET request")
var hdr httpraw.Header
hdr.SetMethod("GET")
hdr.SetRequestURI("/")
@@ -244,22 +279,31 @@ func run() (err error) {
hdr.Set("Host", flagHostToResolve)
hdr.Set("User-Agent", "lneto")
hdr.Set("Accept-Language", "en-US,en;q=0.5")
hdr.Set("Connection", "close") // Encourage server to close connection after it finishes sending response.
req, err := hdr.AppendRequest(nil)
if err != nil {
return err
}
const tcpDebugTimeout = 60 * time.Minute
timeHTTPCreate()
timeTCPDial := timer("TCP dial (handshake)")
const tcpDialTimeout = 8 * time.Second // Was 60 * time.Minute causing 3.6s sleep per iteration!
target := netip.AddrPortFrom(addrs[0], 80)
err = rstack.DoDialTCP(&conn, uint16(softRand&0xefff)+1024, target, tcpDebugTimeout, internetRetries)
err = rstack.DoDialTCP(&conn, uint16(softRand&0xefff)+1024, target, tcpDialTimeout, internetRetries)
if err != nil {
return fmt.Errorf("TCP failed: %w", err)
}
timeTCPDial()
timeHTTPSend := timer("send HTTP request")
conn.SetDeadline(time.Now().Add(internetTimeout))
_, err = conn.Write(req)
timeHTTPSend()
timeHTTPRcv := timer("recv http request")
if err != nil {
return err
}
rxbuf := make([]byte, 2048)
var page []byte
for {
var n int
@@ -268,10 +312,16 @@ func run() (err error) {
if err != nil {
break
}
ptrimmed := bytes.TrimSpace(page)
if bytes.EqualFold(ptrimmed[len(ptrimmed)-7:], []byte("</html>")) {
// We've received the last part of the HTML. we're done.
break
}
}
if len(page) == 0 {
return err
}
timeHTTPRcv()
os.Stdout.Write(page)
}
return nil
@@ -282,3 +332,40 @@ func clear(buf []byte) {
buf[i] = 0
}
}
func timer(context string) func() {
start := time.Now()
return func() {
elapsed := time.Since(start)
fmt.Printf("[%s] %s\n", prettyDuration(elapsed), context)
}
}
func prettyDuration(d time.Duration) string {
switch {
case d < time.Microsecond:
// Print as is.
case d < time.Millisecond:
d = d.Round(time.Microsecond)
case d < time.Second:
d = d.Round(time.Millisecond)
case d < 10*time.Second:
d = d.Round(100 * time.Millisecond)
case d < 10*time.Minute:
d = d.Round(1000 * time.Millisecond)
case d < time.Hour:
d = d.Round(time.Minute)
}
return d.String()
}
func tryPoll(iface ltesto.Interface, poll time.Duration) (dataMayBeReady bool, _ error) {
if poller, ok := iface.(interface {
Poll(time.Duration) (bool, error)
}); ok {
ready, err := poller.Poll(poll)
return ready, err
}
dataMayBeReady = true
return dataMayBeReady, nil
}
+45 -14
View File
@@ -26,6 +26,16 @@ type Interface interface {
IPMask() (netip.Prefix, error)
}
type HTTPTapClient struct {
c http.Client
infoURL string
recvurl string
sendurl string
ip netip.Prefix
hwaddr [6]byte
buf []byte
}
var _ Interface = (*HTTPTapClient)(nil)
// NewHTTPTapClient returns a HTTPTapClient ready for use.
@@ -65,12 +75,7 @@ func (h *HTTPTapClient) ensureMTU() (err error) {
err = fmt.Errorf("unable to get MTU from server: %w", err)
}
}()
resp, err := h.c.Get(h.infoURL)
if err != nil {
return err
}
var info tapInfo
err = json.NewDecoder(resp.Body).Decode(&info)
info, err := h.info()
if err != nil {
return err
} else if info.MTU <= minMTU {
@@ -88,14 +93,14 @@ func (h *HTTPTapClient) ensureMTU() (err error) {
return nil
}
type HTTPTapClient struct {
c http.Client
infoURL string
recvurl string
sendurl string
ip netip.Prefix
hwaddr [6]byte
buf []byte
func (h *HTTPTapClient) info() (tapInfo, error) {
resp, err := h.c.Get(h.infoURL)
if err != nil {
return tapInfo{}, err
}
var info tapInfo
err = json.NewDecoder(resp.Body).Decode(&info)
return info, err
}
func (h *HTTPTapClient) ReadDiscard() (err error) {
@@ -109,6 +114,24 @@ func (h *HTTPTapClient) ReadDiscard() (err error) {
return err
}
func (h *HTTPTapClient) Poll(d time.Duration) (ready bool, err error) {
info, err := h.info()
if err != nil {
return false, err
} else if info.DataReady {
return true, nil
}
deadline := time.Now().Add(d)
for !info.DataReady && time.Until(deadline) > 0 {
time.Sleep(5 * time.Millisecond)
info, err = h.info()
if err != nil {
return false, err
}
}
return info.DataReady, err
}
func (h *HTTPTapClient) ReadBytes() (data []byte, err error) {
err = h.ensureMTU()
if err != nil {
@@ -177,6 +200,7 @@ type tapInfo struct {
MTU int
IPPrefix string
HardwareAddr string
DataReady bool
}
func (sv *HTTPTapServer) OnTransfer(cb func(channel int, pkt []byte)) {
@@ -255,10 +279,17 @@ func NewHTTPTapServer(iface Interface, minMTU, queueOut, queueIn int) (*HTTPTapS
hwstr := net.HardwareAddr(hw6[:]).String()
ipstr := netmask.String()
sv.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
var dataready bool = true
if poller, ok := taps.tap.(interface {
Poll(time.Duration) (bool, error)
}); ok {
dataready, err = poller.Poll(0)
}
info := tapInfo{
MTU: mtu,
IPPrefix: ipstr,
HardwareAddr: hwstr,
DataReady: dataready,
}
json.NewEncoder(w).Encode(info)
})
+44
View File
@@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"syscall"
"time"
"unsafe"
)
@@ -66,6 +67,22 @@ func (tap *Tap) Read(b []byte) (int, error) {
return syscall.Read(tap.fd, b)
}
// Poll waits up to timeout for the tap device to have data available for reading.
// Returns true if data is available, false if timeout was reached.
func (tap *Tap) Poll(timeout time.Duration) (bool, error) {
var readfds syscall.FdSet
readfds.Bits[tap.fd/64] |= 1 << (uint(tap.fd) % 64)
tv := syscall.Timeval{
Sec: int64(timeout / time.Second),
Usec: int64((timeout % time.Second) / time.Microsecond),
}
n, err := syscall.Select(tap.fd+1, &readfds, nil, nil, &tv)
if err != nil {
return false, err
}
return n > 0, nil
}
func (tap *Tap) Write(b []byte) (int, error) {
return syscall.Write(tap.fd, b)
}
@@ -247,6 +264,33 @@ func (br *Bridge) IPMask() (netip.Prefix, error) {
return getSocketMask(br.fd, br.name)
}
// SetReadTimeout sets the receive timeout for the bridge socket.
// This prevents Read from blocking indefinitely, allowing the caller
// to periodically call Encapsulate even when no packets arrive.
func (br *Bridge) SetReadTimeout(timeout time.Duration) error {
tv := syscall.Timeval{
Sec: int64(timeout / time.Second),
Usec: int64((timeout % time.Second) / time.Microsecond),
}
return syscall.SetsockoptTimeval(br.fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv)
}
// Poll waits up to timeout for the bridge socket to have data available for reading.
// Returns true if data is available, false if timeout was reached.
func (br *Bridge) Poll(timeout time.Duration) (bool, error) {
var readfds syscall.FdSet
readfds.Bits[br.fd/64] |= 1 << (uint(br.fd) % 64)
tv := syscall.Timeval{
Sec: int64(timeout / time.Second),
Usec: int64((timeout % time.Second) / time.Microsecond),
}
n, err := syscall.Select(br.fd+1, &readfds, nil, nil, &tv)
if err != nil {
return false, err
}
return n > 0, nil
}
func (br *Bridge) Addr() (netip.Addr, error) {
addrp, err := getSocketIP(br.fd, br.name)
if err != nil {
+5 -4
View File
@@ -671,6 +671,7 @@ const (
// FieldClassBinaryText represents long stretches of binary data such as BOOTP DHCPv4 field.
FieldClassBinaryText // binary-text
FieldClassOperation // op
FieldClassTimestamp // timestamp
)
const octet = 8
@@ -1058,25 +1059,25 @@ var baseNTPFields = [...]FrameField{
},
{
Name: "Reference Time",
Class: FieldClassText,
Class: FieldClassTimestamp,
FrameBitOffset: 16 * octet,
BitLength: 8 * octet,
},
{
Name: "Origin Time",
Class: FieldClassText,
Class: FieldClassTimestamp,
FrameBitOffset: 24 * octet,
BitLength: 8 * octet,
},
{
Name: "Receive Time",
Class: FieldClassText,
Class: FieldClassTimestamp,
FrameBitOffset: 32 * octet,
BitLength: 8 * octet,
},
{
Name: "Transit Time",
Class: FieldClassText,
Class: FieldClassTimestamp,
FrameBitOffset: 40 * octet,
BitLength: 8 * octet,
},
+12
View File
@@ -1,15 +1,19 @@
package pcap
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"net/netip"
"slices"
"strconv"
"strings"
_ "time"
"github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/ntp"
"github.com/soypat/lneto/tcp"
)
@@ -106,6 +110,14 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
switch field.Class {
default:
fallthrough
case FieldClassTimestamp:
// inspired by [time.RFC3339]
const littlerfc3339 = "2006-01-02T15:04:05.9999"
if len(f.buf) != 8 {
return dst, errors.New("only timestamp8 supported")
}
ts := ntp.TimestampFromUint64(binary.BigEndian.Uint64(f.buf))
dst = ts.Time().AppendFormat(dst, littlerfc3339)
case FieldClassChecksum, FieldClassID, FieldClassFlags, FieldClassOptions:
// Binary data to be printed as hexadecimal.
dst = append(dst, "0x"...)
+17 -11
View File
@@ -10,23 +10,29 @@ import (
)
const (
maxIter = 1000
maxTimeout = time.Minute
maxSleep = maxTimeout / maxIter
maxIter = 1000
)
var (
errDeadlineExceed = errors.New("cywnet: deadline exceeded")
)
func (s *StackAsync) StackBlocking() 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{
async: s,
async: s,
loopSleep: loopSleep,
}
}
type StackBlocking struct {
async *StackAsync
async *StackAsync
loopSleep time.Duration
}
func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPResults, error) {
@@ -34,7 +40,7 @@ func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPRe
if err != nil {
return nil, err
}
sleep := timeout/maxIter + 1
sleep := s.loopSleep
deadline := time.Now().Add(timeout)
requested := false
for i := 0; i < maxIter; i++ {
@@ -57,7 +63,7 @@ func (s StackBlocking) DoNTP(hostAddr netip.Addr, timeout time.Duration) (offset
if err != nil {
return -1, err
}
sleep := timeout/maxIter + 1
sleep := s.loopSleep
deadline := time.Now().Add(timeout)
var done bool
for i := 0; i < maxIter; i++ {
@@ -77,7 +83,7 @@ func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D
if err != nil {
return hw, err
}
sleep := timeout/maxIter + 1
sleep := s.loopSleep
deadline := time.Now().Add(timeout)
for i := 0; i < maxIter; i++ {
hw, err = s.async.ResultResolveHardwareAddress6(addr)
@@ -97,7 +103,7 @@ func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []n
if err != nil {
return nil, err
}
sleep := timeout/maxIter + 1
sleep := s.loopSleep
deadline := time.Now().Add(timeout)
for i := 0; i < maxIter; i++ {
addrs, completed, err := s.async.ResultLookupIP(host)
@@ -118,7 +124,7 @@ func (s StackBlocking) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.A
if err != nil {
return err
}
sleep := timeout/maxIter + 1
sleep := s.loopSleep
deadline := time.Now().Add(timeout)
for i := 0; i < maxIter; i++ {
state := conn.State()
+2 -2
View File
@@ -8,9 +8,9 @@ import (
"github.com/soypat/lneto/tcp"
)
func (s *StackAsync) StackRetrying() StackRetrying {
func (s *StackAsync) StackRetrying(loopSleep time.Duration) StackRetrying {
return StackRetrying{
block: s.StackBlocking(),
block: s.StackBlocking(loopSleep),
}
}