{"record":{"id":"67e992e3d990b1e0","repo":"tailscale/tailscale","slug":"derp-send-w","errorCode":null,"errorMessage":"derp.Send: %w","messagePattern":"derp\\.Send: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"derp/derp_client.go","lineNumber":262,"sourceCode":"\n\tbuf := make([]byte, 0, KeyLen+len(msgbox))\n\tbuf = c.publicKey.AppendTo(buf)\n\tbuf = append(buf, msgbox...)\n\treturn WriteFrame(c.bw, FrameClientInfo, buf)\n}\n\n// ServerPublicKey returns the server's public key.\nfunc (c *Client) ServerPublicKey() key.NodePublic { return c.serverKey }\n\n// Send sends a packet to the Tailscale node identified by dstKey.\n//\n// It is an error if the packet is larger than 64KB.\nfunc (c *Client) Send(dstKey key.NodePublic, pkt []byte) error { return c.send(dstKey, pkt) }\n\nfunc (c *Client) send(dstKey key.NodePublic, pkt []byte) (ret error) {\n\tdefer func() {\n\t\tif ret != nil {\n\t\t\tret = fmt.Errorf(\"derp.Send: %w\", ret)\n\t\t}\n\t}()\n\n\tif len(pkt) > MaxPacketSize {\n\t\treturn fmt.Errorf(\"packet too big: %d\", len(pkt))\n\t}\n\n\tc.wmu.Lock()\n\tdefer c.wmu.Unlock()\n\tif c.rate != nil {\n\t\tpktLen := FrameHeaderLen + key.NodePublicRawLen + len(pkt)\n\t\tif !c.rate.AllowN(c.clock.Now(), pktLen) {\n\t\t\treturn nil // drop\n\t\t}\n\t}\n\tif err := WriteFrameHeader(c.bw, FrameSendPacket, uint32(key.NodePublicRawLen+len(pkt))); err != nil {\n\t\treturn err\n\t}","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/tailscale/tailscale/blob/a7769cbc33a3eba62bb16fc803b97077c2969d16/derp/derp_client.go#L244-L280","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := client.Send(dst, pkt); err != nil {\n    // Any 'derp.Send:' error means the write path failed; the connection\n    // must be re-established, not retried.\n    logf(\"send failed: %v\", err)\n    client.Close()\n    client = mustReconnect() // fresh derp.Client via new dial\n}","preventionTips":["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."],"tags":["derp","send","network","go","error-wrapper"],"backgroundTag":"network-write-error","analyzedSha":"a7769cbc33a3eba62bb16fc803b97077c2969d16","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}