tailscale/tailscale · error
derp.Send: %w
Error message
derp.Send: %w
What it means
This is a wrapper, not a root cause: every error returned from derp.Client.send is prefixed with "derp.Send:" by the deferred wrap in the send method. The real failure (oversized packet, frame header write error, bufio write/flush error, 5s write timeout) is in the wrapped error chain — inspect it with errors.Unwrap or %v printing.
Source
Thrown at derp/derp_client.go:262
buf := make([]byte, 0, KeyLen+len(msgbox))
buf = c.publicKey.AppendTo(buf)
buf = append(buf, msgbox...)
return WriteFrame(c.bw, FrameClientInfo, buf)
}
// ServerPublicKey returns the server's public key.
func (c *Client) ServerPublicKey() key.NodePublic { return c.serverKey }
// Send sends a packet to the Tailscale node identified by dstKey.
//
// It is an error if the packet is larger than 64KB.
func (c *Client) Send(dstKey key.NodePublic, pkt []byte) error { return c.send(dstKey, pkt) }
func (c *Client) send(dstKey key.NodePublic, pkt []byte) (ret error) {
defer func() {
if ret != nil {
ret = fmt.Errorf("derp.Send: %w", ret)
}
}()
if len(pkt) > MaxPacketSize {
return fmt.Errorf("packet too big: %d", len(pkt))
}
c.wmu.Lock()
defer c.wmu.Unlock()
if c.rate != nil {
pktLen := FrameHeaderLen + key.NodePublicRawLen + len(pkt)
if !c.rate.AllowN(c.clock.Now(), pktLen) {
return nil // drop
}
}
if err := WriteFrameHeader(c.bw, FrameSendPacket, uint32(key.NodePublicRawLen+len(pkt))); err != nil {
return err
}View on GitHub (pinned to a7769cbc33)
Solutions
- Print the full chain: the wrapped error after 'derp.Send:' names the actual fault (e.g. 'packet too big', 'write tcp ...: broken pipe', 'write timeout').
- If the cause is 'packet too big', cap or fragment the payload before Send.
- Otherwise treat the connection as dead: discard this derp.Client and let derphttp.Client (or your own logic) reconnect with a fresh dial.
- Check server-side logs for why it stopped reading (rate limits, shutdown, mesh issues).
Defensive patterns
Strategy: try-catch
Try / catch
if err := client.Send(dst, pkt); err != nil {
// Any 'derp.Send:' error means the write path failed; the connection
// must be re-established, not retried.
logf("send failed: %v", err)
client.Close()
client = mustReconnect() // fresh derp.Client via new dial
} Prevention
- Treat any error returned by Send as fatal for that derp.Client and reconnect.
- Check len(pkt) against derp.MaxPacketSize before Send to rule out the most common local cause.
- Keep a single owner goroutine for writes (send holds wmu) — never call Send from many goroutines with retries racing the reconnect.
When it happens
Trigger: Client.Send(dstKey, pkt) when len(pkt) > MaxPacketSize (65536), when the underlying TCP/TLS connection is broken (write returns EPIPE/EOF), when bufio Flush fails, or when the 5-second write deadline timer fires (writeTimeoutFired).
Common situations: Sending after the server closed the connection or after a Recv error already killed the client; NAT/firewall dropping the long-lived connection; oversized application datagrams; DERP server restart mid-session.
Related errors
- derp.NotePreferred: %v
- sending packet %w
- error sending via %q: %w
- %s connect to %v: %v
- bad frame type 0x%X, want 0x%X
AI-assisted analysis of tailscale/tailscale@a7769cbc33 (2026-08-18).
Data as JSON: /api/errors/67e992e3d990b1e0.
Report an issue: GitHub.