tailscale/tailscale · error

unsupported gVisor GSOType: %v

Error message

unsupported gVisor GSOType: %v

What it means

net/tstun converts gVisor netstack GSO metadata into wireguard-go tun.GSOOptions when moving packets toward the TUN device. Only GSONone, GSOTCPv4, and GSOTCPv6 have mappings; any other gVisor GSOType value falls to the default branch and yields this error. It signals a version skew between the vendored gVisor/netstack and wireguard-go tun packages (e.g. a new GSO type added upstream), not a runtime network condition.

Source

Thrown at net/tstun/wrap.go:966

	if !buildfeatures.HasNetstack {
		panic("unreachable")
	}
	options := tun.GSOOptions{
		CsumStart:  gso.L3HdrLen,
		CsumOffset: gso.CsumOffset,
		GSOSize:    gso.MSS,
		NeedsCsum:  gso.NeedsCsum,
	}
	switch gso.Type {
	case netstack_GSONone:
		options.GSOType = tun.GSONone
		return options, nil
	case netstack_GSOTCPv4:
		options.GSOType = tun.GSOTCPv4
	case netstack_GSOTCPv6:
		options.GSOType = tun.GSOTCPv6
	default:
		return tun.GSOOptions{}, fmt.Errorf("unsupported gVisor GSOType: %v", gso.Type)
	}
	// options.HdrLen is both layer 3 and 4 together, whereas gVisor only
	// gives us layer 3 length. We have to gather TCP header length
	// ourselves.
	if len(pkt) < int(gso.L3HdrLen)+minTCPHeaderSize {
		return tun.GSOOptions{}, errors.New("gVisor GSOTCP packet length too short")
	}
	tcphLen := uint16(pkt[int(gso.L3HdrLen)+12] >> 4 * 4)
	options.HdrLen = gso.L3HdrLen + tcphLen
	return options, nil
}

// invertGSOChecksum inverts the transport layer checksum in pkt if gVisor
// handed us a segment with a partial checksum. A partial checksum is not a
// ones' complement of the sum, and incremental checksum updating is not yet
// partial checksum aware. This may be called twice for a single packet,
// both before and after partial checksum updates where later checksum
// offloading still expects a partial checksum.

View on GitHub (pinned to 5201273aec)

Solutions

  1. Pin gvisor, netstack, and wireguard-go/wintun to the version pair the Tailscale release you track uses (check its go.mod)
  2. If you intentionally added a GSO type in netstack, add the corresponding case mapping it to a tun.GSO* constant here
  3. As a diagnostic, disable GSO on the affected path to confirm the version skew
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard is version alignment, checked at build time: keep gvisor,
// wireguard-go and tailscale from the same release.
// go.mod check example:
//   require github.com/google/gvisor v0.0.0-<hash pinned by tailscale release>

Try / catch

options, err := convertGSO(gso, pkt)
if err != nil {
	if strings.Contains(err.Error(), "unsupported gVisor GSOType") {
		// version skew between netstack and tun — drop the packet and
		// alert; do not crash the data path
		log.Printf("gso conversion failed (version skew?): %v", err)
		return tun.GSOOptions{}, nil // or drop packet upstream
	}
	return tun.GSOOptions{}, err
}

Prevention

When it happens

Trigger: A packet arrives with gso.Type outside netstack_GSONone/GSOTCPv4/GSOTCPv6 — almost always after upgrading github.com/google/gvisor (or wireguard-go/wintun) independently of the versions Tailscale pins.

Common situations: go.mod upgrades of gvisor or wireguard-go in a fork/vendor tree; custom netstack builds that emit experimental UDP GSO types; cross-version mixing of tailscale components.

Related errors


AI-assisted analysis of tailscale/tailscale@5201273aec (2026-08-18). Data as JSON: /api/errors/3e4049b741281f70. Report an issue: GitHub.