fatedier/frp · error

message too large: %d > %d

Error message

message too large: %d > %d

What it means

DoS guard in vnet's ReadMessage: the declared frame length exceeds maxMessageSize (1 MiB). Any single vnet message must be at most 1MB, so a header claiming more is rejected before allocation, both to bound memory and because a huge length almost always indicates desync or a hostile/garbage stream rather than a real message.

Source

Thrown at pkg/vnet/message.go:44

)

// Format: [length(4 bytes)][data(length bytes)]

// ReadMessage reads a framed message from the reader
func ReadMessage(r io.Reader) ([]byte, error) {
	// Read length (4 bytes)
	var length uint32
	err := binary.Read(r, binary.LittleEndian, &length)
	if err != nil {
		return nil, fmt.Errorf("read message length error: %w", err)
	}

	// Check length to prevent DoS
	if length == 0 {
		return nil, fmt.Errorf("message length is 0")
	}
	if length > maxMessageSize {
		return nil, fmt.Errorf("message too large: %d > %d", length, maxMessageSize)
	}

	// Read message data
	data := make([]byte, length)
	_, err = io.ReadFull(r, data)
	if err != nil {
		return nil, fmt.Errorf("read message data error: %w", err)
	}

	return data, nil
}

// WriteMessage writes a framed message to the writer
func WriteMessage(w io.Writer, data []byte) error {
	// Get data length
	length := uint32(len(data))
	if length == 0 {
		return fmt.Errorf("message data length is 0")

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Reject/close the connection on this error — it is not retryable on the same stream
  2. Ensure frpc and frps versions match exactly; framing changed across releases
  3. Keep individual vnet messages under 1MB; chunk large payloads at the application layer
  4. Keep the vnet port private (bind to trusted interface / firewall it) so random traffic cannot hit the framing reader

Example fix

// before: log and continue reading the same (desynced) stream
if err != nil { log.Println(err); continue }

// after: oversized length invalidates the stream
data, err := vnet.ReadMessage(r)
if err != nil {
    return fmt.Errorf("closing corrupt vnet stream: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

// sender side: keep each framed message under the protocol max
const maxMessageSize = 1024 * 1024
func send(w io.Writer, payload []byte) error {
    if len(payload) > maxMessageSize {
        payload = chunk(payload) // application-level split
    }
    return vnet.WriteMessage(w, payload)
}

Try / catch

_, err := vnet.ReadMessage(r)
if err != nil && strings.Contains(err.Error(), "message too large") {
    conn.Close() // header untrustworthy: stream is desynced or hostile
    return
}

Prevention

When it happens

Trigger: Length prefix reads a value > 1048576 due to stream desync (extra/missing bytes shifted the header position); random bytes hitting the port; a legitimate peer trying to send a vnet payload larger than 1MB, which the protocol forbids.

Common situations: Port scanners or health checks sending junk to a vnet-related listener; version mismatch changing framing; downstream code attempting to tunnel a huge datagram through vnet in one message.

Related errors


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