slackhq/nebula · error

could not enable TCP SACK: %v

Error message

could not enable TCP SACK: %v

What it means

In netstack New (service/service.go), after creating the gVisor ipstack, TCP SACK is enabled via SetTransportProtocolOption; any tcpip error is fatal and reported as this message. SACK is required for the tunneled TCP stack to behave well, so the library fails fast.

Source

Thrown at service/service.go:80

		}
	}()

	ctx := control.Context()
	eg, ctx := errgroup.WithContext(ctx)
	s := Service{
		eg:      eg,
		control: control,
	}
	s.mu.listeners = map[uint16]*tcpListener{}

	s.ipstack = stack.New(stack.Options{
		NetworkProtocols:   []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
		TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
	})
	sackEnabledOpt := tcpip.TCPSACKEnabled(true) // TCP SACK is disabled by default
	tcpipErr := s.ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt)
	if tcpipErr != nil {
		return nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
	}
	linkEP := channel.New( /*size*/ 512 /*mtu*/, 1280, "")
	if tcpipProblem := s.ipstack.CreateNIC(nicID, linkEP); tcpipProblem != nil {
		return nil, fmt.Errorf("could not create netstack NIC: %v", tcpipProblem)
	}
	ipv4Subnet, _ := tcpip.NewSubnet(tcpip.AddrFrom4([4]byte{0x00, 0x00, 0x00, 0x00}), tcpip.MaskFrom(strings.Repeat("\x00", 4)))
	s.ipstack.SetRouteTable([]tcpip.Route{
		{
			Destination: ipv4Subnet,
			NIC:         nicID,
		},
	})

	ipNet := device.Networks()
	pa := tcpip.ProtocolAddress{
		AddressWithPrefix: tcpip.AddrFromSlice(ipNet[0].Addr().AsSlice()).WithPrefix(),
		Protocol:          ipv4.ProtocolNumber,
	}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Ensure tcp.NewProtocol is included in TransportProtocols when creating the stack
  2. Update or pin the gVisor dependency to a version compatible with this code
  3. Rebuild with the unmodified stack configuration from service/service.go
  4. Capture the wrapped tcpipErr (%v) to identify the exact tcpip error code

Example fix

// before
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6}
// after
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6}
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure protocols registered before New:
stacks := []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6}

Try / catch

svc, err := service.New(...)
if err != nil {
    if strings.Contains(err.Error(), "TCP SACK") {
        log.Fatalf("netstack TCP setup failed (check gVisor deps/protocol registration): %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: service.New is called (via doService, run, or newSimpleService) and the gVisor netstack rejects the TCPSACKEnabled option — practically only when the TCP protocol was not registered on the stack or a gVisor version/API incompatibility exists.

Common situations: Custom builds where tcp.NewProtocol was dropped from TransportProtocols; vendored gVisor fork with different option semantics; upstream gVisor API change breaking SetTransportProtocolOption.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/8b4ba36057a90da1. Report an issue: GitHub.