fatedier/frp · error

missing v2 crypto negotiation

Error message

missing v2 crypto negotiation

What it means

When a connection negotiates wire protocol v2, newControlReadWriter requires an AEAD crypto context established during negotiation (cryptoContext). If wireProtocol is v2 but cryptoContext is nil, the server rejects creating the control read-writer with this invariant error: v2's encryption layer was never set up for this connection.

Source

Thrown at server/service.go:610

	return acceptedConn, nil
}

func writeWithDeadline(conn net.Conn, timeout time.Duration, writeFn func() error) error {
	_ = conn.SetWriteDeadline(time.Now().Add(timeout))
	defer func() {
		_ = conn.SetWriteDeadline(time.Time{})
	}()
	return writeFn()
}

func (ac *acceptedConnection) messageConnFor(rw io.ReadWriter) *msg.Conn {
	return msg.NewConn(ac.conn, msg.NewReadWriter(rw, ac.wireProtocol))
}

func (ac *acceptedConnection) newControlReadWriter(rw io.ReadWriter, key []byte) (io.ReadWriter, error) {
	if ac.wireProtocol == wire.ProtocolV2 {
		if ac.cryptoContext == nil {
			return nil, fmt.Errorf("missing v2 crypto negotiation")
		}
		return netpkg.NewAEADCryptoReadWriter(
			rw,
			key,
			netpkg.AEADCryptoRoleServer,
			ac.cryptoContext.Algorithm,
			ac.cryptoContext.TranscriptHash,
		)
	}
	return netpkg.NewCryptoReadWriter(rw, key)
}

func (ac *acceptedConnection) readFirstV2Msg(conn net.Conn, wireConn *wire.Conn) (msg.Message, error) {
	frame, err := wireConn.ReadFrame()
	if err != nil {
		return nil, fmt.Errorf("read v2 frame: %w", err)
	}
	if frame.Type == wire.FrameTypeClientHello {

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Upgrade frpc and frps to the same version so the v2 negotiation flow matches on both ends
  2. Capture which side aborts the negotiation (server logs around the login) and inspect the wrapped handshake
  3. As a stopgap, configure the client to use the v1 wire protocol until versions align
Defensive patterns

Strategy: try-catch

Try / catch

rw, err := ac.newControlReadWriter(rw, key)
if err != nil && strings.Contains(err.Error(), "missing v2 crypto negotiation") {
    // drop the connection; do not fall back to plaintext. Negotiation must be redone.
    return err
}

Prevention

When it happens

Trigger: A v2 client whose crypto negotiation (hello/auth exchange carrying algorithm and transcript hash) never completed before the control connection is finalized; version skew where one side enables v2 crypto but the other skips negotiation; internal code paths creating a control connection without running the negotiation step.

Common situations: Mixed frpc/frps versions during a rolling upgrade of the v2 protocol; middleboxes truncating the handshake; custom clients implementing v2 incompletely.

Related errors


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