fatedier/frp · error

message length is 0

Error message

message length is 0

What it means

Protocol validation error in vnet's ReadMessage: the 4-byte length prefix was read successfully but its value is 0. A zero-length frame is invalid by design (it would allocate nothing and make EOF/parse behaviour ambiguous), so it is rejected as a DoS/robustness guard. Receiving it means the stream is not speaking the expected [4-byte len][data] framing at that byte offset.

Source

Thrown at pkg/vnet/message.go:41

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

// WriteMessage writes a framed message to the writer
func WriteMessage(w io.Writer, data []byte) error {
	// Get data length

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Confirm frpc and frps run the same frp version (frpc -v / frps -v), upgrade both together
  2. Make sure nothing else terminates/inspects the connection (L7 proxies, health checkers) — vnet needs a byte-clean stream
  3. If it recurs with matching versions, capture the stream and check what precedes the zero length — earlier frames are likely being mis-read
  4. Disconnect and let frp re-establish the vnet session; persistent occurrences indicate a real framing bug — report with logs

Example fix

// before: treat any ReadMessage failure identically, keep the conn
data, err := vnet.ReadMessage(r)
if err != nil { log.Println(err); continue }

// after: framing violations are unrecoverable — drop the connection
data, err := vnet.ReadMessage(r)
if err != nil {
    log.Printf("vnet framing error, closing conn: %v", err)
    conn.Close()
    return
}
Defensive patterns

Strategy: try-catch

Validate before calling

// only your own framed peers should reach a vnet stream; firewall the port
// and authenticate before exchanging framed messages

Try / catch

_, err := vnet.ReadMessage(r)
if err != nil {
    // 'message length is 0' means stream desync or junk input: close, never continue
    conn.Close()
    return
}

Prevention

When it happens

Trigger: A peer (or anything sharing the connection) sends a 4-zero-byte sequence; the reader's stream is desynchronized because a previous frame was partially consumed or extra bytes were injected; connecting a non-vnet client to a vnet port so arbitrary bytes are interpreted as a length header.

Common situations: Version skew between frpc and frps changing message framing; a load balancer or proxy injecting a PROXY/health-check line into the stream; leftover buffered bytes after an earlier framing error; probing/scanning the vnet port with tools like nc.

Related errors


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