tailscale/tailscale · error
failed to receive: %w
Error message
failed to receive: %w
What it means
In the TUN bandwidth probe, a goroutine loops on toc.Recv() to pull packets relayed back over the 'to' DERP connection and inject them into the TUN device. This error fires when Recv itself fails — the receiving DERP stream broke mid-transfer (the TUN-mode analogue of the pair probe's receive error).
Source
Thrown at prober/derp.go:1015
// This goroutine reads packets from the `toc` DERP client and writes them towards the TUN.
// It only reports errors to `recvErrC` channel.
wg.Add(1)
recvErrC := make(chan error, 1)
go func() {
defer wg.Done()
// Depending on platform, we need some space for headers at the front
// of TUN I/O op buffers. The below constant is more than enough space
// for any platform that this might run on.
tunWriteStartOffset := device.MessageTransportHeaderSize
buf := make([]byte, mtu+tunWriteStartOffset)
bufs := make([][]byte, 1)
fromDERPPubKey := fromc.SelfPublicKey()
for {
m, err := toc.Recv()
if err != nil {
recvErrC <- fmt.Errorf("failed to receive: %w", err)
return
}
switch v := m.(type) {
case derp.ReceivedPacket:
if v.Source != fromDERPPubKey {
recvErrC <- fmt.Errorf("got data packet from unexpected source, %v", v.Source)
return
}
pkt := v.Data
copy(buf[tunWriteStartOffset:], pkt)
bufs[0] = buf[:len(pkt)+tunWriteStartOffset]
if _, err := dev.Write(bufs, tunWriteStartOffset); err != nil {
recvErrC <- fmt.Errorf("failed to write to TUN device: %w", err)
return
}
case derp.KeepAliveMessage:
// Silently ignore.
default:View on GitHub (pinned to 5201273aec)
Solutions
- Inspect the wrapped error for EOF/ECONNRESET/TLS causes to distinguish a drop from a protocol failure.
- Re-run with a smaller transfer size: if small transfers pass, an intermediary idle timeout is truncating long streams.
- Check the 'to' derper's logs and connection limits for the failure window.
- For probes behind proxies, raise idle timeouts or exclude DERP traffic from the proxy.
Defensive patterns
Strategy: retry
Validate before calling
if err := toc.Connect(ctx); err != nil {
return fmt.Errorf("to client not connected: %w", err)
} Type guard
func isConnDropped(err error) bool {
return errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) ||
errors.Is(err, syscall.ECONNRESET)
} Try / catch
Classify the wrapped cause: EOF/ECONNRESET -> drop, recreate clients and retry once; TLS error -> investigate certs; persistent for long transfers only -> shrink size or raise intermediary idle timeouts instead of retrying.
Prevention
- Keep bandwidth transfer durations under intermediary idle timeouts
- Ensure derper and any LB in front of it permit long-lived connections
- Recreate DERP clients per probe run
- Monitor the 'to' node's connection-reset counters during sustained probing
When it happens
Trigger: toc.Recv() returns an error: the derper closed or reset the client connection, a TLS stream failure, a protocol error frame, or the client was closed locally by the probe teardown path.
Common situations: Receiving derper restarting during a long bandwidth transfer; LB/proxy idle or request timeouts cutting the long-lived HTTPS stream; server evicting the client; very large transfer sizes stretching the connection past intermediary limits.
Related errors
- derp.Recv: %w
- error receiving from %q: %w
- failed to dial address %q: %w
- invalid derp map json: %w
- upload failed: %w
AI-assisted analysis of tailscale/tailscale@5201273aec (2026-08-18).
Data as JSON: /api/errors/fdac74407d1caca8.
Report an issue: GitHub.