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
- Reject/close the connection on this error — it is not retryable on the same stream
- Ensure frpc and frps versions match exactly; framing changed across releases
- Keep individual vnet messages under 1MB; chunk large payloads at the application layer
- 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
- Chunk large payloads below 1MB per message
- Treat oversized-length as corruption: close the connection
- Port-scan exposure turns framing guards into log noise — restrict access
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
- message length is 0
- read message length error: %w
- auth.oidc.clientID is required; auth.oidc.tokenEndpointURL i
- unexpected frame type %d, want %d
- invalid protocol
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/64e28cc8c9f34dbc.
Report an issue: GitHub.