Skip to content

Linux TUN GRO probe runs before link is up and returns EIO on OpenWrt #81

Description

@hanai

Summary

On OpenWrt 25.12.5 (mediatek/filogic, aarch64_cortex-a53), the Linux TUN GRO capability probe fails with EIO, causing sing-tun to disable both TCP and UDP GRO:

WARN inbound/tun[tun-in]: disabled TUN TCP & UDP GRO due to GRO probe error: batch write: write tun: input/output error

Normal connectivity still works, but sing-tun falls back without TUN TCP/UDP GRO, which may increase CPU usage or reduce throughput under load.

Environment

DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='25.12.5'
DISTRIB_REVISION='r33051-f5dae5ece4'
DISTRIB_TARGET='mediatek/filogic'
DISTRIB_ARCH='aarch64_cortex-a53'
DISTRIB_DESCRIPTION='OpenWrt 25.12.5 r33051-f5dae5ece4'
DISTRIB_TAINTS=''
sing-box version 1.14.0-alpha.43

Environment: go1.25.11 linux/arm64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,with_usbip,badlinkname,tfogo_checklinkname0,with_musl
Revision: f025e6f163a2f438c8dad2402fb723c255521ce5
CGO: enabled

The warning was observed with a TUN inbound and a Tailscale endpoint. No explicit stack value was configured.

TUN features reported by the kernel

ethtool -k tun0 reports the relevant offload features as enabled:

tx-checksumming: on
  tx-checksum-ip-generic: on
scatter-gather: on
tcp-segmentation-offload: on
  tx-tcp-segmentation: on
  tx-tcp6-segmentation: on
generic-segmentation-offload: on
generic-receive-offload: on
tx-udp-segmentation: on
rx-gro-list: on

This suggests that the kernel advertises support for the TUN offload path, but the actual probe write fails.

Suspected cause

The current Linux startup order appears to run the GRO probe before bringing the TUN link up:

  1. NativeTun.Start() calls probeTCPGRO() when vnetHdr is enabled.
  2. Only afterwards does it call t.start().
  3. t.start() is where netlink.LinkSetUp(tunLink) is performed.

Relevant code:

Tailscale also disables GRO only when the probe returns EINVAL, while sing-tun currently disables it for any error, including EIO.

On this OpenWrt system, writing the probe packet while the link is still down may be the reason for:

write tun: input/output error

This is a hypothesis based on the startup order and kernel feature output; it has not yet been confirmed with a patched build.

Expected behavior

If the kernel and TUN device support the required offloads, the probe should pass and TCP/UDP GRO should remain enabled.

If the probe is performed before the interface is ready, a transient readiness error should not necessarily be treated as proof that GRO is unsupported.

Possible fix

Run probeTCPGRO() only after the TUN interface has been brought up, similar to Tailscale's documented requirement.

Alternatively or additionally, consider distinguishing unsupported-feature errors such as EINVAL from transient or ordering-related errors such as EIO before permanently disabling TCP and UDP GRO for the process lifetime.

I can test a patch that moves the probe after LinkSetUp if helpful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions