Add xcurl port and debug flag (#129)

* feat: add xcurl port and debug flag

Update the docs and add new flags to xcurl to aid debugging under an OS
like Linux.

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>

* drop debugtcp flag from xcurl again

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>

---------

Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
This commit is contained in:
Marvin Drees
2026-06-22 23:32:50 +02:00
committed by GitHub
parent 28addf329a
commit a0a5336108
2 changed files with 41 additions and 2 deletions
+14
View File
@@ -49,6 +49,7 @@ You may try lneto out on linux with the [xcurl example](./examples/xcurl/) which
- HTTP over TCP/IPv4/Ethernet connection using
- NTP time check (optional)
- Print packet captures using lneto's [internet/pcap](./internet/pcap) package
- Optional fixed TCP source port with `-port`, useful when debugging raw-socket interactions with the host OS
See Developing section below for more information.
@@ -171,6 +172,19 @@ sudo ethtool -K <network-interface> gro off
Depending on the interface and driver, other offloading features may also need to be disabled.
When running `xcurl` directly on a normal Linux interface (for example `-i wlan0` or `-i eth0`), lneto shares the host interface IP and MAC address. The Linux TCP stack also sees incoming packets for that IP. If xcurl's TCP handshake succeeds but the HTTP request is followed by a TCP RST from the server, the host kernel may have sent its own outbound RST for xcurl's raw TCP flow because no kernel socket owns that source port.
For a short validation test, use a fixed xcurl source port and temporarily drop kernel-generated outbound RSTs for that port:
```sh
go build -o examples/xcurl/xcurl ./examples/xcurl
sudo iptables -I OUTPUT -p tcp --sport 2300 --tcp-flags RST RST -j DROP
sudo ./examples/xcurl/xcurl -i <network-interface> -port 2300 -host example.com
sudo iptables -D OUTPUT -p tcp --sport 2300 --tcp-flags RST RST -j DROP
```
This is a debugging workaround, not the preferred operating mode. Prefer `-ihttp`, a TAP interface, a network namespace, or an otherwise isolated interface/IP when testing xcurl without host TCP stack interference.
- [`examples/httptap`](./examples/httptap) (linux only, root privilidges required) Program opens a TAP interface and assigns an IP address to it and exposes the interface via a HTTP interface. This program is run with root privilidges to facilitate debugging of lneto since no root privilidges are required to interact with the HTTP interface exposed.
- `POST http://127.0.0.1:7070/send`: Receives a POST with request body containing JSON string of data to send over TAP interface. Response contains only status code.
- `GET http://127.0.0.1:7070/recv`: Receives a GET request. Response contains a JSON string of oldest unread TAP interface packet. If string is empty then there is no more data to read.
+27 -2
View File
@@ -50,6 +50,7 @@ func run() (err error) {
flagUseHTTP = false
flagHostToResolve = ""
flagRequestedIP = ""
flagLocalTCPPort = 0
flagDoNTP = false
flagNoPcap = false
flagPprof = false
@@ -58,6 +59,7 @@ func run() (err error) {
flag.BoolVar(&flagUseHTTP, "ihttp", flagUseHTTP, "Use HTTP tap interface.")
flag.StringVar(&flagHostToResolve, "host", flagHostToResolve, "Hostname to resolve via DNS.")
flag.StringVar(&flagRequestedIP, "addr", flagRequestedIP, "IP address to request via DHCP.")
flag.IntVar(&flagLocalTCPPort, "port", flagLocalTCPPort, "Local TCP source port to use. Zero chooses a pseudo-random port.")
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.")
@@ -79,6 +81,21 @@ func run() (err error) {
flag.Usage()
return err
}
reqAddr := [4]byte{192, 168, 1, 96}
requestedAddrSet := false
if flagRequestedIP != "" {
addr, err := netip.ParseAddr(flagRequestedIP)
if err != nil || !addr.Is4() {
flag.Usage()
return fmt.Errorf("invalid requested IPv4 address %q", flagRequestedIP)
}
reqAddr = addr.As4()
requestedAddrSet = true
}
if flagLocalTCPPort < 0 || flagLocalTCPPort > math.MaxUint16 {
flag.Usage()
return fmt.Errorf("invalid local TCP port %d", flagLocalTCPPort)
}
fmt.Println("softrand", softRand)
var iface ltesto.Interface
if flagUseHTTP {
@@ -223,11 +240,14 @@ func run() (err error) {
dhcpRetries = 2
)
timeDHCP := timer("DHCP request completed")
results, err := rstack.DoDHCPv4([4]byte{192, 168, 1, 96}, dhcpTimeout, dhcpRetries)
results, err := rstack.DoDHCPv4(reqAddr, dhcpTimeout, dhcpRetries)
if err != nil {
return fmt.Errorf("DHCP failed: %w", err)
}
timeDHCP()
if requestedAddrSet && results.AssignedAddr4 != reqAddr {
return fmt.Errorf("DHCP assigned %s, not requested %s", netip.AddrFrom4(results.AssignedAddr4), netip.AddrFrom4(reqAddr))
}
err = stack.AssimilateDHCPResults(results)
if err != nil {
@@ -302,7 +322,12 @@ func run() (err error) {
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, tcpDialTimeout, internetRetries)
localPort := uint16(flagLocalTCPPort)
if localPort == 0 {
localPort = uint16(softRand&0xefff) + 1024
}
fmt.Printf("TCP target %s local-port=%d\n", target, localPort)
err = rstack.DoDialTCP(&conn, localPort, target, tcpDialTimeout, internetRetries)
if err != nil {
return fmt.Errorf("TCP failed: %w", err)
}