{"record":{"id":"e8cbb2e9661c7ec4","repo":"grpc/grpc-go","slug":"negotiated-unknown-next-protocol-q","errorCode":null,"errorMessage":"negotiated unknown next_protocol %q","messagePattern":"negotiated unknown next_protocol %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/alts/internal/conn/record.go","lineNumber":147,"sourceCode":"\t// nextFrame stores the next frame (in protected buffer) info.\n\tnextFrame []byte\n\t// overhead is the calculated overhead of each frame.\n\toverhead  int\n\tconstPool constBufferPool // stored as a field to avoid heap allocations.\n}\n\n// NewConn creates a new secure channel instance given the other party role and\n// handshaking result.\nfunc NewConn(c net.Conn, side core.Side, recordProtocol string, key []byte, protected []byte) (net.Conn, error) {\n\treturn NewConnWithMaxFrameSize(c, side, recordProtocol, key, protected, 0)\n}\n\n// NewConnWithMaxFrameSize creates a new secure channel instance given the\n// other party role, handshaking result, and negotiated maximum frame size.\nfunc NewConnWithMaxFrameSize(c net.Conn, side core.Side, recordProtocol string, key []byte, protected []byte, negotiatedMaxFrameSize int) (net.Conn, error) {\n\tnewCrypto := protocols[recordProtocol]\n\tif newCrypto == nil {\n\t\treturn nil, fmt.Errorf(\"negotiated unknown next_protocol %q\", recordProtocol)\n\t}\n\tcrypto, err := newCrypto(side, key)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"protocol %q: %v\", recordProtocol, err)\n\t}\n\toverhead := MsgLenFieldSize + msgTypeFieldSize + crypto.EncryptionOverhead()\n\n\t// Clamp maxRecordLen to be at least altsRecordDefaultLength.\n\tmaxRecordLen := max(altsRecordDefaultLength, negotiatedMaxFrameSize)\n\tpayloadLengthLimit := maxRecordLen - overhead\n\t// We pre-allocate protected to be of size 32KB during initialization.\n\t// We increase the size of the buffer by the required amount if it can't\n\t// hold a complete encrypted record.\n\tprotectedHandle := readBufPool.Get(max(altsReadBufferInitialSize, len(protected)))\n\tprotectedBuf := *protectedHandle\n\t// Copy additional data from hanshaker service.\n\tcopy(protectedBuf, protected)\n\tprotectedBuf = protectedBuf[:len(protected)]","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/credentials/alts/internal/conn/record.go#L129-L165","documentation":"Returned by conn.NewConnWithMaxFrameSize when the recordProtocol string negotiated by the ALTS handshake is not found in the 'protocols' map (i.e. no ALTSRecordFunc is registered for it). The only protocol registered by the handshaker is 'ALTSRP_GCM_AES128_REKEY'; any other name triggers this.","triggerScenarios":"The ALTS handshaker service returns a HandshakerResult.RecordProtocol whose value is not 'ALTSRP_GCM_AES128_REKEY', and the secure connection cannot be constructed. Reached via doHandshake -> conn.NewConnWithMaxFrameSize during a client or server ALTS handshake on GCP.","commonSituations":"A version mismatch between the grpc-go client/server and the GCP metadata-server handshaker service that negotiates a newer record protocol (e.g. a future AES-256 variant) not yet supported by this grpc-go build; a test mock handshaker returning a made-up protocol name; a custom ALTSRecordFunc registration that was skipped (e.g. blank import missing).","solutions":["Upgrade grpc-go to a version that registers the negotiated record protocol.","If running a custom handshaker service, ensure it only negotiates 'ALTSRP_GCM_AES128_REKEY' for clients/servers on this grpc-go version.","If you intentionally added a new protocol, make sure its ALTSRecordFunc is registered via conn.RegisterProtocol before the handshake runs (e.g. via a blank import)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Treat as a fatal ALTS handshake failure; surface to the caller and retry the RPC.\nif _, _, err := chs.ClientHandshake(ctx); err != nil {\n    if strings.Contains(err.Error(), \"negotiated unknown next_protocol\") {\n        return fmt.Errorf(\"ALTS peer negotiated an unsupported record protocol; upgrade grpc-go: %w\", err)\n    }\n    return err\n}","preventionTips":["Keep grpc-go updated so the negotiated record protocol is always supported.","If introducing a new ALTS record protocol, register its ALTSRecordFunc via a blank import before handshaking.","In test harnesses, ensure mock handshakers only return registered protocols."],"tags":["grpc","alts","negotiation","handshake","version-mismatch"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}