Repository navigation
ShadowLAN 3.1.0
A latency and bufferbloat release. Games now hear from each other as fast as the network allows, and bulk transfers still run at
full link speed, matching a plain TCP connection over the same path.
The wire protocol is unchanged (PVER 3 / UVER 3): 3.0.0 and 3.1.0 hooks and relays work together. Deploy the new server.py on
your relay to get the relay-side improvements.
Highlights
-
Low latency on every way a game waits for data. Before, the hook checked for tunneled data on a timer, which added delay
depending on how the game read its sockets:- TCP with a blocking
recv: about 10 ms - TCP or UDP with
select,pollorWSAPoll: about 25 ms (51 ms for blocking UDP receives on Windows)
Now a game wakes up the moment data arrives: about 0.1 ms round trip on Linux and about 0.5 ms on Windows (measured under Wine),
not counting the network. - TCP with a blocking
-
No more busy-spinning. Games waiting on a tunneled TCP connection with
poll/selectused to spin a CPU core at 60–99%.
They now sleep until data arrives. -
Much less bufferbloat during bulk transfers. On a 20 Mbit/s link with 40 ms round trip, a message sent on the same connection
as a running download used to wait about 4 seconds. Now it waits about 240 ms, which is less than a plain kernel TCP connection on
the same path (about 555 ms). Throughput is unchanged. -
UDP-over-TCP stays responsive under load. When game traffic exceeds the link, stale datagrams are dropped instead of queuing
without limit. Latency stays around 200 ms instead of growing past several seconds.
Fixes
- Linux: blocking UDP receives no longer hang. A plain blocking
recvfromon a tunneled UDP socket could block forever. - Startup deadlock when several threads bind at once. Two threads binding sockets at the same time could leave every later
bind()in the process hanging. - Data at process exit. A game that sent data and exited immediately could lose the tail of it. Two causes are fixed: the
hook's exit flush could silently skip a stream, and the relay closed a departing machine's open connections before their last bytes
arrived. - End of stream is reported to
poll/select. When the other side closes a TCP connection, the game'spoll/selectnow
reports it as readable, as a real kernel does. - New hosted UDP sessions get their first reply immediately instead of after up to 100 ms.
- wclient relay connections now have Nagle's algorithm disabled, avoiding delays on small writes.
Bandwidth
Bulk throughput matches a direct kernel TCP connection over the same links:
| Link | 3.0.0 | 3.1.0 | Plain kernel TCP |
|---|---|---|---|
| 20 Mbit/s, 40 ms RTT | 19.1 Mbit/s | 19.1 Mbit/s | 19.0 Mbit/s |
| 200 Mbit/s, 100 ms RTT | 187 Mbit/s | 186 Mbit/s | 181 Mbit/s |
Tunnel connections now let the operating system size their buffers automatically. Before, 3.0.0 requested fixed 4 MB buffers, which
turned that off. On a typical VPS the kernel cuts such a request down to about 425 KB, which limited throughput on long, fast
paths.
Under the hood
- Every waiting call (
recv,recvfrom,connect,send,poll,ppoll,select,pselect,WSAPoll) is woken by the hook
as soon as data arrives, instead of rechecking on a timer. - Unsent data is kept small (
TCP_NOTSENT_LOWATon Linux and the relay). The app-side send queue per connection is 256 KB, down
from 4 MB. A single larger send is still accepted in parts, as a real socket does. - Each control message goes out in one TCP segment instead of three.
Upgrading
- Relay: replace
server.pyand restart. Existing 3.0.0 hooks keep working. - Players: replace the hook files from the package for your platform. A 3.1.0 hook works with a 3.0.0 relay too, but you only
get the full improvement with both upgraded.
Assets
shadowlan-v3.1.0-windows-amd64.zip:lan_hook64.dll,lan_hook32.dll,injector.exe, relay and client scripts, docsshadowlan-v3.1.0-linux-amd64.tar.gz:lan_hook.so, relay and client scripts, docs