fatedier/frp · error

read message length error: %w

Error message

read message length error: %w

What it means

Framing-layer error from vnet's ReadMessage: it could not read the 4-byte little-endian length prefix that precedes every message on a vnet control/data stream. The %w wraps the underlying error — almost always io.EOF or a connection reset — meaning the peer went away or the stream is corrupted mid-frame.

Source

Thrown at pkg/vnet/message.go:36

	"encoding/binary"
	"fmt"
	"io"
)

// Maximum message size
const (
	maxMessageSize = 1024 * 1024 // 1MB
)

// 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

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Inspect the wrapped error: io.ErrUnexpectedEOF or reset points to network drop; plain io.EOF means clean peer close — just reconnect
  2. Reconnect the frpc vnet session (frp retries automatically; restart frpc if it does not)
  3. Keep the underlying connection alive (vnet rides frp's control connection — check frp keepalive/TCP settings) and check frps logs at the same timestamp
  4. If both sides log framing errors simultaneously, suspect version mismatch between frpc and frps — align versions

Example fix

// before
func readLoop(r io.Reader) {
    for {
        data, err := vnet.ReadMessage(r)
        if err != nil { log.Printf("fatal: %v", err); os.Exit(1) }
    }
}

// after
for {
    data, err := vnet.ReadMessage(r)
    if err != nil {
        if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { return } // peer gone
        log.Printf("read message: %v", err)
        return
    }
    handle(data)
}
Defensive patterns

Strategy: try-catch

Try / catch

data, err := vnet.ReadMessage(r)
if err != nil {
    var wrErr error = err
    if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, net.ErrClosed) {
        return // peer closed: reconnect at a higher level
    }
    return fmt.Errorf("vnet stream failed: %w", wrErr)
}

Prevention

When it happens

Trigger: The remote end (frps or frpc vnet component) closes the connection while ReadMessage is blocked reading the header; a network interruption resets the TCP connection; reading from a stream where a previous partial read left the cursor misaligned (protocol desync).

Common situations: frps restart or crash while vnet is active; NAT/firewall idle timeout killing the virtual-net connection; one side upgraded to a version with a different framing.

Related errors


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