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

  1. Keep framed JSON messages under 64 KiB — split large payloads (e.g. paginate status responses) instead of raising the cap.
  2. 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).
  3. 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

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


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/0ae728c416e23a6e. Report an issue: GitHub.