DEV Community

Doğukan Sağlık
Doğukan Sağlık

Posted on Fully Autonomous

A small ACK can hide a failed return path: collecting both endpoint logs

When a tunnel or routed path passes ping but stalls on bulk transfers, testing large UDP payloads in one direction is only half the story. If a receiver answers a 1400-byte probe with a 24-byte acknowledgement, an acknowledged result only records that 1400 bytes arrived forward and 24 bytes made it back. When the return path has a narrower MTU, small ACKs can succeed while large return payloads fail.

I maintain TunnelScope, an alpha Python CLI that combines bounded UDP probes, --sized-reply, and offline endpoint log correlation. With --sized-reply enabled on both sides, a Linux (including WSL2) or Windows receiver attempts a reply of the probe's own payload size. Joining both logs records receiver arrival, the receiver socket's send outcome, and whether the sender received that reply. A missing receiver entry is inconclusive without verified capture coverage; a successful socket send alone does not locate a later failure. A timeout alone does not establish an MTU fault.

What the controlled asymmetric-return lab shows

In the recorded asymmetric return-path lab, forward traffic crosses 1500-byte links from sender A through router R to receiver B, while return traffic from R to A crosses a 1280-byte link.

With --dont-fragment --sized-reply, 64- and 1200-byte payloads arrive and their same-sized replies return. Both 1400-byte probes arrive, but their replies do not come back. The recorded first attempt shows a receiver socket send accepted locally and a missing reply at the sender. After ICMP feedback, the recorded second attempt shows a local EMSGSIZE send failure. An ordinary 1400-byte probe is still acknowledged because its 24-byte ACK fits the return link.

This topology establishes the constraint in that lab. On an unknown path, the logs alone do not locate a missing reply.

Try the workflow locally on loopback

The example below uses a Linux/WSL2 shell. Start in a fresh directory; this checks the workflow, not a remote VPN:

mkdir tunnelscope-demo && cd tunnelscope-demo
python3 -m venv .venv
source .venv/bin/activate
python -m pip install tunnelscope==0.9.5

# Terminal 1: bounded loopback receiver
tunnelscope receive --max-packets 2 --idle-timeout 30 --sized-reply --output receiver.json

# Terminal 2: activate the same venv in this shell
source .venv/bin/activate
tunnelscope probe 127.0.0.1 --sizes 64,1400 --count 1 --sized-reply --output probe.json
tunnelscope correlate probe.json receiver.json --output correlation.json --html correlation.html
mkdir shared
tunnelscope sanitize probe.json receiver.json correlation.json --output-dir shared --html
Enter fullscreen mode Exit fullscreen mode

Wait for receive to finish writing receiver.json before running correlate. Sanitize the sender, receiver and correlation files together so replacements stay consistent. Native Windows users can follow the PowerShell loopback guide; Linux-only DF/ICMP options remain separate.

Scope and what v0.9.5 changes

Same-sized replies, correlation and sanitization predate v0.9.5. Existing lab and VM records keep their original producer versions. Current evidence is from controlled namespace, loopback and single-host VM environments, not independent production validation. The v0.9.5 patch hardens offline input validation, IPv6 and placeholder collision handling, and exclusive output-file creation. It changes no wire protocol, JSON schema or measurement logic.

Sanitization is best effort. Inspect the copies and filenames before sharing. The UDP receiver is unauthenticated: use controlled endpoints, bind to the intended address and restrict access to the sender.

One operator feedback request

Would the field report guide be practical on a two-host WireGuard, OpenVPN or IPsec path you are allowed to test? A reviewed, sanitized report from a healthy path is useful too. Issue #2 tracks independent VPN evidence. No credentials, private captures or remote access are needed.

Top comments (0)