fatedier/frp · error

unexpected frame type %d, want %d

Error message

unexpected frame type %d, want %d

What it means

Returned by Conn.ReadJSONFrame when the frame just read has a Type different from the expected frameType argument. ReadJSONFrame is a convenience that reads one frame and asserts its type, so an out-of-order or unexpected message produces this error rather than a silent misparse.

Source

Thrown at pkg/proto/wire/wire.go:115

	header := make([]byte, 8)
	binary.BigEndian.PutUint16(header[0:2], f.Type)
	binary.BigEndian.PutUint16(header[2:4], f.Flags)
	binary.BigEndian.PutUint32(header[4:8], uint32(len(f.Payload)))
	if _, err := c.rw.Write(header); err != nil {
		return err
	}
	_, err := c.rw.Write(f.Payload)
	return err
}

func (c *Conn) ReadJSONFrame(frameType uint16, out any) error {
	f, err := c.ReadFrame()
	if err != nil {
		return err
	}
	if f.Type != frameType {
		return fmt.Errorf("unexpected frame type %d, want %d", f.Type, frameType)
	}
	return c.UnmarshalFrame(f, out)
}

func (c *Conn) UnmarshalFrame(f *Frame, out any) error {
	return json.Unmarshal(f.Payload, out)
}

func NewJSONFrame(frameType uint16, in any) (*Frame, error) {
	payload, err := json.Marshal(in)
	if err != nil {
		return nil, err
	}
	return &Frame{
		Type:    frameType,
		Payload: payload,
	}, nil
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Read frames generically with ReadFrame and dispatch on f.Type, handling error frames, instead of hard-expecting one type.
  2. Ensure exactly one goroutine owns the read side of the connection.
  3. After any prior read/write error, treat the connection as dead — do not keep expecting the next typed frame.

Example fix

// before
var hello wire.ServerHello
conn.ReadJSONFrame(wire.FrameTypeServerHello, &hello) // fails if server sent error frame

// after
f, err := conn.ReadFrame()
if err != nil { return err }
switch f.Type {
case wire.FrameTypeServerHello:
    return conn.UnmarshalFrame(f, &hello)
case wire.FrameTypeError:
    return parseAndReturnError(f)
}
Defensive patterns

Strategy: try-catch

Try / catch

f, err := conn.ReadFrame()
if err != nil { return err }
if f.Type != expectedType {
    if f.Type == frameTypeError {
        return decodeRemoteError(f) // server sent an error frame instead
    }
    return fmt.Errorf("unexpected frame type %d, want %d", f.Type, expectedType)
}

Prevention

When it happens

Trigger: Calling ReadJSONFrame(FrameTypeServerHello, ...) but the server sent an error frame first; protocol state machine getting out of order (two readers, or reading after an error); peer sending a frame type from a newer protocol.

Common situations: Server replies with an error/close frame where the client expects the next handshake frame; concurrent goroutines reading from the same Conn; version skew introducing new frame types.

Related errors


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