Go version
go1.26.0 windows/amd64
Output of go env in your module/workspace:
What did you do?
Used golang.org/x/net@v0.50.0 (or later) with an HTTP/2 server behind a connection multiplexer such as cmux, which sniffs the connection preface and optionally sends a bare SETTINGS frame before handing the net.Conn off to http2.Server.ServeConn.
What did you see happen?
Clients (Chrome, Edge, etc.) close the connection immediately with:
GOAWAY ErrCode=PROTOCOL_ERROR
Debug="spdy::SETTINGS_DEPRECATE_HTTP2_PRIORITIES value changed after first SETTINGS frame."
What did you expect to see?
http2.Server should not emit SETTINGS_NO_RFC7540_PRIORITIES=1 by default. Prior to v0.50.0, this setting was never sent, and the behavior change should require an explicit opt-in rather than being a side-effect of the default write schedulers (*randomWriteScheduler, *roundRobinWriteScheduler).
Root Cause
Two related changes landed in golang.org/x/net@v0.50.0 as part of #75500:
- A new helper method
writeSchedIgnoresRFC7540() was introduced. Notably, both *randomWriteScheduler and *roundRobinWriteScheduler — the two default schedulers most users run — return true:
https://github.com/golang/net/blob/ebddb99633e0fc35d135f62e9400678492c1d3be/http2/server.go#L660-L671
- When
writeSchedIgnoresRFC7540() returns true, the server now unconditionally appends SettingNoRFC7540Priorities = 1 to its initial SETTINGS frame:
https://github.com/golang/net/blob/ebddb99633e0fc35d135f62e9400678492c1d3be/http2/server.go#L961-L963
Failure Sequence
cmux reads the HTTP/2 client connection preface and sends a bare SETTINGS frame to the client (no SETTINGS_DEPRECATE_HTTP2_PRIORITIES, so value is implicitly 0).
- The connection is handed off to
http2.Server.ServeConn.
http2.Server sends its own SETTINGS frame, now including SETTINGS_DEPRECATE_HTTP2_PRIORITIES = 1.
- The client observes this value changing from
0 to 1 across two SETTINGS frames. RFC 9218 §9.1 prohibits this: a sender MUST NOT revert this setting once enabled.
- Client closes with GOAWAY / PROTOCOL_ERROR.
Discussion: Why DisableClientPriority Does Not Fix On-wire Regression
DisableClientPriority (planned for Go 1.27, see #75500) is designed to control the server's stream scheduling behavior. It disables client-driven prioritization and selects round-robin across all streams. However, this does not prevent the server from setting SettingNoRFC7540Priorities=1 in its initial SETTINGS frame, as roundRobinWriteScheduler still returns true for writeSchedIgnoresRFC7540().
Therefore, the on-wire protocol regression described above remains unaddressed. There is no current mechanism for applications using cmux-style intermediaries to suppress emission of SETTINGS_NO_RFC7540_PRIORITIES from Go's HTTP/2 server.
Possible Directions
-
Restrict writeSchedIgnoresRFC7540() to opt-in schedulers only — Only explicit adoption of the RFC 9218 priority scheduler would emit SETTINGS_NO_RFC7540_PRIORITIES=1. The default scheduling (including round-robin and random) should preserve pre-v0.50.0 on-wire behavior.
-
Add an explicit flag to control emission of SETTINGS_NO_RFC7540_PRIORITIES — e.g., a NoRFC7540Priorities bool field on http2.Server. This would decouple scheduling logic from frame signaling.
Either option would allow advanced use cases (like cmux) to avoid breaking HTTP/2 interoperability with some clients.
Happy to hear thoughts from the maintainers on which direction or solution is preferred.
Related: #75500
Go version
go1.26.0 windows/amd64
Output of
go envin your module/workspace:What did you do?
Used
golang.org/x/net@v0.50.0(or later) with an HTTP/2 server behind a connection multiplexer such as cmux, which sniffs the connection preface and optionally sends a bare SETTINGS frame before handing thenet.Connoff tohttp2.Server.ServeConn.What did you see happen?
Clients (Chrome, Edge, etc.) close the connection immediately with:
What did you expect to see?
http2.Servershould not emitSETTINGS_NO_RFC7540_PRIORITIES=1by default. Prior tov0.50.0, this setting was never sent, and the behavior change should require an explicit opt-in rather than being a side-effect of the default write schedulers (*randomWriteScheduler,*roundRobinWriteScheduler).Root Cause
Two related changes landed in
golang.org/x/net@v0.50.0as part of #75500:writeSchedIgnoresRFC7540()was introduced. Notably, both*randomWriteSchedulerand*roundRobinWriteScheduler— the two default schedulers most users run — returntrue:https://github.com/golang/net/blob/ebddb99633e0fc35d135f62e9400678492c1d3be/http2/server.go#L660-L671
writeSchedIgnoresRFC7540()returnstrue, the server now unconditionally appendsSettingNoRFC7540Priorities = 1to its initial SETTINGS frame:https://github.com/golang/net/blob/ebddb99633e0fc35d135f62e9400678492c1d3be/http2/server.go#L961-L963
Failure Sequence
cmuxreads the HTTP/2 client connection preface and sends a bare SETTINGS frame to the client (noSETTINGS_DEPRECATE_HTTP2_PRIORITIES, so value is implicitly 0).http2.Server.ServeConn.http2.Serversends its own SETTINGS frame, now includingSETTINGS_DEPRECATE_HTTP2_PRIORITIES = 1.0to1across two SETTINGS frames. RFC 9218 §9.1 prohibits this: a sender MUST NOT revert this setting once enabled.Discussion: Why
DisableClientPriorityDoes Not Fix On-wire RegressionDisableClientPriority(planned for Go 1.27, see #75500) is designed to control the server's stream scheduling behavior. It disables client-driven prioritization and selects round-robin across all streams. However, this does not prevent the server from settingSettingNoRFC7540Priorities=1in its initial SETTINGS frame, asroundRobinWriteSchedulerstill returnstrueforwriteSchedIgnoresRFC7540().Therefore, the on-wire protocol regression described above remains unaddressed. There is no current mechanism for applications using cmux-style intermediaries to suppress emission of
SETTINGS_NO_RFC7540_PRIORITIESfrom Go's HTTP/2 server.Possible Directions
Restrict
writeSchedIgnoresRFC7540()to opt-in schedulers only — Only explicit adoption of the RFC 9218 priority scheduler would emitSETTINGS_NO_RFC7540_PRIORITIES=1. The default scheduling (including round-robin and random) should preserve pre-v0.50.0 on-wire behavior.Add an explicit flag to control emission of
SETTINGS_NO_RFC7540_PRIORITIES— e.g., aNoRFC7540Priorities boolfield onhttp2.Server. This would decouple scheduling logic from frame signaling.Either option would allow advanced use cases (like cmux) to avoid breaking HTTP/2 interoperability with some clients.
Happy to hear thoughts from the maintainers on which direction or solution is preferred.
Related: #75500