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

  1. 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').
  2. If the cause is 'packet too big', cap or fragment the payload before Send.
  3. Otherwise treat the connection as dead: discard this derp.Client and let derphttp.Client (or your own logic) reconnect with a fresh dial.
  4. 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

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


AI-assisted analysis of tailscale/tailscale@a7769cbc33 (2026-08-18). Data as JSON: /api/errors/67e992e3d990b1e0. Report an issue: GitHub.