tailscale/tailscale · error

couldn't check system's UDP GRO forwarding configuration, fa

Error message

couldn't check system's UDP GRO forwarding configuration, failed to init ethtool: %v

What it means

CheckUDPGROForwarding probes NIC offload features through the safchain/ethtool netlink library. Its first step opens an ethtool handle; if that fails, the kernel or sandbox does not expose the ethtool generic-netlink family and the check cannot run at all. Tailscale surfaces this on Linux subnet routers/exit nodes as a health warning.

Source

Thrown at net/netkernelconf/netkernelconf_linux.go:33

	rxWantFeature      = "rx-udp-gro-forwarding"
	rxDoNotWantFeature = "rx-gro-list"
	txFeature          = "tx-udp-segmentation"
)

// CheckUDPGROForwarding checks if the machine is optimally configured to
// forward UDP packets between the default route and Tailscale TUN interfaces.
// It returns a non-nil warn in the case that the configuration is suboptimal.
// It returns a non-nil err in the case that an error is encountered while
// performing the check.
func CheckUDPGROForwarding(tunInterface, defaultRouteInterface string) (warn, err error) {
	const kbLink = "\nSee https://tailscale.com/s/ethtool-config-udp-gro"
	errWithPrefix := func(format string, a ...any) error {
		const errPrefix = "couldn't check system's UDP GRO forwarding configuration, "
		return fmt.Errorf(errPrefix+format, a...)
	}
	e, err := ethtool.NewEthtool()
	if err != nil {
		return nil, errWithPrefix("failed to init ethtool: %v", err)
	}
	defer e.Close()
	tunFeatures, err := e.Features(tunInterface)
	if err != nil {
		return nil, errWithPrefix("failed to retrieve TUN device features: %v", err)
	}
	if !tunFeatures[txFeature] {
		// if txFeature is disabled/nonexistent on the TUN then UDP GRO
		// forwarding doesn't matter, we won't be taking advantage of it.
		return nil, nil
	}
	defaultFeatures, err := e.Features(defaultRouteInterface)
	if err != nil {
		return nil, errWithPrefix("failed to retrieve default route interface features: %v", err)
	}
	defaultHasRxWant, ok := defaultFeatures[rxWantFeature]
	if !ok {
		// unlikely the feature is nonexistent with txFeature in the TUN driver

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Allow AF_NETLINK sockets in the container's seccomp policy or run with CAP_NET_ADMIN on a host kernel
  2. Run the check on the host network namespace instead of a restricted sandbox
  3. Upgrade to a kernel with ethtool netlink support
  4. If the environment cannot support it, accept the warning and baseline UDP throughput
Defensive patterns

Strategy: try-catch

Validate before calling

// Feature-detect ethtool availability before relying on the check
if _, err := net.InterfaceByName(tunInterface); err != nil {
 return fmt.Errorf("interface %s missing; skip GRO check", tunInterface)
}
warn, err := netkernelconf.CheckUDPGROForwarding(tunInterface, defaultIf)

Try / catch

warn, err := netkernelconf.CheckUDPGROForwarding(tun, def)
if err != nil {
 if strings.Contains(err.Error(), "failed to init ethtool") {
 // environment limitation (sandbox/kernel): log once, not fatal
 log.Printf("GRO check unavailable in this environment: %v", err)
 } else {
 return err
 }
}
if warn != nil { log.Printf("%v", warn) }

Prevention

When it happens

Trigger: Running tailscaled (or calling this function) under gVisor/runsc, a container whose seccomp profile blocks AF_NETLINK sockets, very old kernels without ethtool netlink support, or stripped-down VMs.

Common situations: Kubernetes pods with custom seccomp/apparmor profiles, GKE sandboxed pods, minimal appliance kernels.

Related errors


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/08802c064e5de44e. Report an issue: GitHub.