fatedier/frp · error
frame payload length %d exceeds limit %d
Error message
frame payload length %d exceeds limit %d
What it means
Returned by Conn.ReadFrame when the header's declared payload length exceeds the connection's maxFramePayloadSize (DefaultMaxFramePayloadSize = 64 KiB). This is a DoS guard: it refuses to allocate a buffer based on an attacker-controlled length field before reading the payload.
Source
Thrown at pkg/proto/wire/wire.go:76
rw: rw,
maxFramePayloadSize: DefaultMaxFramePayloadSize,
}
}
func (c *Conn) ReadFrame() (*Frame, error) {
header := make([]byte, 8)
if _, err := io.ReadFull(c.rw, header); err != nil {
return nil, err
}
frameType := binary.BigEndian.Uint16(header[0:2])
flags := binary.BigEndian.Uint16(header[2:4])
length := binary.BigEndian.Uint32(header[4:8])
if flags != 0 {
return nil, fmt.Errorf("unsupported frame flags: %d", flags)
}
if length > c.maxFramePayloadSize {
return nil, fmt.Errorf("frame payload length %d exceeds limit %d", length, c.maxFramePayloadSize)
}
payload := make([]byte, length)
if _, err := io.ReadFull(c.rw, payload); err != nil {
return nil, err
}
return &Frame{
Type: frameType,
Flags: flags,
Payload: payload,
}, nil
}
func (c *Conn) WriteFrame(f *Frame) error {
if f.Flags != 0 {
return fmt.Errorf("unsupported frame flags: %d", f.Flags)
}
if len(f.Payload) > int(c.maxFramePayloadSize) {View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Keep framed JSON messages under 64 KiB — split large payloads (e.g. paginate status responses) instead of raising the cap.
- If both ends are yours and you truly need bigger frames, construct the Conn with a larger maxFramePayloadSize on both sides (NewConn sets the default; keep them symmetric).
- If occasional, suspect stream desynchronization — audit all writers to the connection for interleaved writes.
Example fix
// before
frame, _ := wire.NewJSONFrame(wire.FrameTypeX, hugeStatusObject) // payload > 64KiB
conn.WriteFrame(frame)
// after
// chunk or shrink the payload, e.g. per-proxy status requests
for name, statuses := range allStatus { sendFrame(conn, name, statuses) } Defensive patterns
Strategy: validation
Validate before calling
// sender side, before writing:
payload, err := json.Marshal(msg)
if err != nil { return err }
if len(payload) > int(wire.DefaultMaxFramePayloadSize) {
return fmt.Errorf("message %d bytes exceeds frame limit %d", len(payload), wire.DefaultMaxFramePayloadSize)
} Try / catch
if _, err := conn.ReadFrame(); err != nil {
if strings.Contains(err.Error(), "exceeds limit") {
// peer sent/claimed oversized frame: close conn (possible desync or DoS)
conn.Close()
}
return err
} Prevention
- Design messages to stay well under 64 KiB; paginate bulky data.
- If raising the limit, raise it symmetrically on both endpoints.
When it happens
Trigger: A peer declares length > 65536 in the 4-byte big-endian length field, either legitimately (trying to send a huge JSON message) or because the stream is desynchronized and the length bytes are actually payload data.
Common situations: A proxy status or config message whose JSON exceeds 64 KiB; framing bugs where a frame is written with the wrong length; malicious clients probing the server.
Related errors
- unsupported frame flags: %d
- unexpected frame type %d, want %d
- invalid transport.protocol, optional values are %v
- %s cannot be empty
- %s is too long: length %d exceeds maximum %d
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/0ae728c416e23a6e.
Report an issue: GitHub.