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
- Pin gvisor, netstack, and wireguard-go/wintun to the version pair the Tailscale release you track uses (check its go.mod)
- If you intentionally added a GSO type in netstack, add the corresponding case mapping it to a tun.GSO* constant here
- 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
- Never bump gvisor or wireguard-go independently in a Tailscale fork; take them from the same release tag
- Run the integration/test suite's throughput tests after any dep bump — GSO skew shows up immediately
- If experimenting with new GSO types, add the mapping case in the same change
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.