{"record":{"id":"edf33d4b1ea4d114","repo":"fatedier/frp","slug":"read-message-length-error-w","errorCode":null,"errorMessage":"read message length error: %w","messagePattern":"read message length error: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/vnet/message.go","lineNumber":36,"sourceCode":"\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n)\n\n// Maximum message size\nconst (\n\tmaxMessageSize = 1024 * 1024 // 1MB\n)\n\n// Format: [length(4 bytes)][data(length bytes)]\n\n// ReadMessage reads a framed message from the reader\nfunc ReadMessage(r io.Reader) ([]byte, error) {\n\t// Read length (4 bytes)\n\tvar length uint32\n\terr := binary.Read(r, binary.LittleEndian, &length)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read message length error: %w\", err)\n\t}\n\n\t// Check length to prevent DoS\n\tif length == 0 {\n\t\treturn nil, fmt.Errorf(\"message length is 0\")\n\t}\n\tif length > maxMessageSize {\n\t\treturn nil, fmt.Errorf(\"message too large: %d > %d\", length, maxMessageSize)\n\t}\n\n\t// Read message data\n\tdata := make([]byte, length)\n\t_, err = io.ReadFull(r, data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read message data error: %w\", err)\n\t}\n\n\treturn data, nil","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/vnet/message.go#L18-L54","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Inspect the wrapped error: io.ErrUnexpectedEOF or reset points to network drop; plain io.EOF means clean peer close — just reconnect","Reconnect the frpc vnet session (frp retries automatically; restart frpc if it does not)","Keep the underlying connection alive (vnet rides frp's control connection — check frp keepalive/TCP settings) and check frps logs at the same timestamp","If both sides log framing errors simultaneously, suspect version mismatch between frpc and frps — align versions"],"exampleFix":"// before\nfunc readLoop(r io.Reader) {\n    for {\n        data, err := vnet.ReadMessage(r)\n        if err != nil { log.Printf(\"fatal: %v\", err); os.Exit(1) }\n    }\n}\n\n// after\nfor {\n    data, err := vnet.ReadMessage(r)\n    if err != nil {\n        if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { return } // peer gone\n        log.Printf(\"read message: %v\", err)\n        return\n    }\n    handle(data)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"data, err := vnet.ReadMessage(r)\nif err != nil {\n    var wrErr error = err\n    if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, net.ErrClosed) {\n        return // peer closed: reconnect at a higher level\n    }\n    return fmt.Errorf(\"vnet stream failed: %w\", wrErr)\n}","preventionTips":["Always check the wrapped error (%w) before deciding retry vs teardown","Keep frpc/frps versions in lockstep to avoid framing drift","Investigate simultaneous errors on both ends for the same connection"],"tags":["vnet","network","protocol","io"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}