Skip to content

wg-over-tailscale: gateway sendmmsg EMSGSIZE black-holes transfers over ~16KiB, with no MTU lever #790

Description

@matejaarqitech

Problem

With a gateway reached over Tailscale, every transfer through the tunnel runs at roughly 0.6–1.5 KB/s and any response larger than about 16KiB never completes. Small requests succeed, so the deployment looks healthy until something tries to move real data.

The gateway logs this continuously while traffic flows:

ERROR: [wg] peer(...) - Failed to send data packets: write udp 0.0.0.0:51820: sendmmsg: message too long

EMSGSIZE here is WireGuard forming datagrams larger than the Tailscale path can carry: the Tailscale interface is MTU 1280, while wg's tun defaults to 1420. Every full-size packet is dropped at the socket, and nothing adapts.

Measurements

Same URL, same client, with and without clawpatrol run. Both hosts are unbrokered (no endpoint declared, no credential injected), so neither the MITM path nor policy evaluation is involved:

request bytes direct through gateway
a JSON API response 6,868 0.26s (26.7 KB/s) 6.60s (1.0 KB/s)
a raw file over HTTPS 3,807 0.23s (16.7 KB/s) 5.91s (0.6 KB/s)

A size sweep stalls at a fixed ceiling rather than degrading gradually. Three different objects stopped at ~15.59KB, and one JSON response stopped at exactly 16,384 bytes:

object bytes received outcome
7.7KB thumbnail 7,731 completed in 4.9s
20KB thumbnail 20,449 stalled at 20s timeout
larger thumbnails (x2) 15,587 / 15,594 stalled
1.3MB file 15,593 stalled
JSON listing 16,384 stalled

Why the error rate is misleading

The sendmmsg errors arrive at a flat ~3.5/second regardless of what the traffic is doing — 36 errors across five small API calls versus 68 across twenty seconds of bulk download. That flatness reads as harmless background noise, which is how this went unnoticed through several rounds of functional testing. It is flat because it is paced by TCP retransmission backoff, not by transfer volume.

This is the same size-dependent black hole class described in #787, but on the IPv4 full-tunnel path with a WireGuard-over-Tailscale transport, and with the drop occurring on the gateway's own send path rather than on an unhandled ICMP signal.

What we tried

  • MTU = 1200 in the client's wg.conf [Interface] — no effect. Per-process mode uses a userspace netstack rather than bringing up the kernel interface via wg-quick, so the file's MTU line appears not to be consulted.
  • Searching the config schema for an MTU attribute: mtu is rejected in gateway, gateway.wireguard, defaults and endpoint blocks, and does not appear among the binary's HCL tags.
  • Searching for an environment override: no CLAWPATROL_* variable exposes an MTU.
  • Capping stored response bodies (limits.body_storage), in case audit persistence was the bottleneck — no change, which is what ruled that out.

So there appears to be no way to configure around this from the deployment side.

Suggested directions

Any one of these would unblock it:

  1. A configurable WireGuard MTU in the gateway.wireguard block, pushed to clients through the config channel so both ends agree.
  2. Automatically clamping the tun MTU when the transport is tsnet/Tailscale, where 1280 is the known path MTU.
  3. Honoring wg.conf's MTU in per-process mode, so operators at least have a client-side lever.

Happy to test a patch or collect more data — the reproduction is reliable and takes seconds.

Environment

  • Claw Patrol 0.5.8
  • Gateway: Linux x86_64, kernel 6.19, systemd unit, userspace WireGuard on udp/51820, reached over Tailscale
  • Client: Linux, per-process mode (clawpatrol run), joined without --whole-machine
  • Tailscale interface MTU 1280 on both ends; direct (non-relayed) path between them

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions