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 lengthView on GitHub (pinned to 6c8a8d0a97)
Solutions
- Confirm frpc and frps run the same frp version (frpc -v / frps -v), upgrade both together
- Make sure nothing else terminates/inspects the connection (L7 proxies, health checkers) — vnet needs a byte-clean stream
- If it recurs with matching versions, capture the stream and check what precedes the zero length — earlier frames are likely being mis-read
- 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
- Never continue reading a stream after a framing violation
- Keep vnet-related listeners off public interfaces
- Align frpc/frps versions; framing is version-coupled
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
- message too large: %d > %d
- auth.oidc.clientID is required; auth.oidc.tokenEndpointURL i
- invalid protocol
- IPv4 zone is forbidden
- zone is not valid UTF-8
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/2a03995e5d98b046.
Report an issue: GitHub.