fix(quic-interop): offer both h3 + hq-interop ALPNs, dispatch by negotiated
The previous commit (e5bbf8509) switched ALPN per testcase based on
quic-interop-runner convention (h3 for http3/multiplexing, hq-interop
otherwise). That broke picoquic, which had been 4/4 green: picoquic-qns
strictly registers h3 ALPN and rejects hq-interop. Different servers
disagree on which ALPN they configure for the same testcase:
- quic-go-qns — strictly hq-interop for non-http3
- aioquic-qns — accepts either
- picoquic-qns — strictly h3 for all testcases
There's no per-testcase convention all peers honor. Right move is the
TLS-spec-supported one: offer BOTH h3 and hq-interop in the ClientHello,
let the server pick whichever matches its config, then dispatch the GET
client by `tls.negotiatedAlpn` after the handshake completes. http3 and
multiplexing still restrict to h3 only since they exercise H3 framing.
Brings picoquic back to fully green and should unblock quic-go for the
non-http3 testcases.
https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
This commit is contained in:
+36
-15
@@ -89,16 +89,20 @@ fun main() {
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
// ALPN per quic-interop-runner convention. Most testcases use
|
// ALPN selection. Different servers configure different ALPNs PER
|
||||||
// `hq-interop` (HTTP/0.9 over QUIC, no H3/QPACK). Only the `http3` and
|
// testcase, with no consistent convention:
|
||||||
// `multiplexing` testcases use `h3`. quic-go-qns enforces this strictly
|
// - quic-go-qns — strictly hq-interop for non-http3 tests
|
||||||
// (CRYPTO_ERROR 0x178 / TLS no_application_protocol on mismatch);
|
// - aioquic-qns — accepts either
|
||||||
// aioquic and picoquic accept either. Match the per-test convention
|
// - picoquic-qns — strictly h3 for ALL testcases
|
||||||
// so we work everywhere.
|
//
|
||||||
val alpn =
|
// Solution: offer BOTH `h3` and `hq-interop` in the ClientHello. TLS
|
||||||
|
// ALPN negotiation lets the server pick whichever matches its config.
|
||||||
|
// For testcases that REQUIRE H3 framing (http3, multiplexing) we
|
||||||
|
// restrict to h3 so any server that picks hq-interop fails fast.
|
||||||
|
val offeredAlpns =
|
||||||
when (testcase) {
|
when (testcase) {
|
||||||
"http3", "multiplexing" -> Alpn.H3
|
"http3", "multiplexing" -> listOf(Alpn.H3)
|
||||||
else -> Alpn.HQ_INTEROP
|
else -> listOf(Alpn.HQ_INTEROP, Alpn.H3)
|
||||||
}
|
}
|
||||||
|
|
||||||
val code =
|
val code =
|
||||||
@@ -133,7 +137,7 @@ fun main() {
|
|||||||
requests = requests,
|
requests = requests,
|
||||||
downloadsDir = downloadsDir,
|
downloadsDir = downloadsDir,
|
||||||
cipherSuites = cipherSuites,
|
cipherSuites = cipherSuites,
|
||||||
alpn = alpn,
|
offeredAlpns = offeredAlpns,
|
||||||
keyLogPath = keyLogPath,
|
keyLogPath = keyLogPath,
|
||||||
parallel = (testcase == "multiplexing"),
|
parallel = (testcase == "multiplexing"),
|
||||||
)
|
)
|
||||||
@@ -163,7 +167,7 @@ private fun runTransferTest(
|
|||||||
requests: String,
|
requests: String,
|
||||||
downloadsDir: File,
|
downloadsDir: File,
|
||||||
cipherSuites: IntArray?,
|
cipherSuites: IntArray?,
|
||||||
alpn: Alpn,
|
offeredAlpns: List<Alpn>,
|
||||||
keyLogPath: String?,
|
keyLogPath: String?,
|
||||||
parallel: Boolean,
|
parallel: Boolean,
|
||||||
): Int {
|
): Int {
|
||||||
@@ -199,7 +203,7 @@ private fun runTransferTest(
|
|||||||
serverName = host,
|
serverName = host,
|
||||||
config = QuicConnectionConfig(),
|
config = QuicConnectionConfig(),
|
||||||
tlsCertificateValidator = PermissiveCertificateValidator(),
|
tlsCertificateValidator = PermissiveCertificateValidator(),
|
||||||
alpnList = listOf(alpn.wireBytes),
|
alpnList = offeredAlpns.map { it.wireBytes },
|
||||||
cipherSuites =
|
cipherSuites =
|
||||||
cipherSuites
|
cipherSuites
|
||||||
?: intArrayOf(
|
?: intArrayOf(
|
||||||
@@ -221,10 +225,27 @@ private fun runTransferTest(
|
|||||||
return@runBlocking "handshake_failed"
|
return@runBlocking "handshake_failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatch the GET client by what the server actually picked.
|
||||||
|
// If the server didn't pick or picked something unrecognized,
|
||||||
|
// fall back to HQ-interop (simpler, more permissive).
|
||||||
|
val negotiated =
|
||||||
|
conn.tls.negotiatedAlpn
|
||||||
|
?.decodeToString()
|
||||||
|
.orEmpty()
|
||||||
val client: GetClient =
|
val client: GetClient =
|
||||||
when (alpn) {
|
when (negotiated) {
|
||||||
Alpn.H3 -> Http3GetClient(conn).also { it.init() }
|
"h3" -> {
|
||||||
Alpn.HQ_INTEROP -> HqInteropGetClient(conn)
|
Http3GetClient(conn).also { it.init() }
|
||||||
|
}
|
||||||
|
|
||||||
|
"hq-interop" -> {
|
||||||
|
HqInteropGetClient(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
System.err.println("unrecognized negotiated ALPN '$negotiated'; defaulting to hq-interop")
|
||||||
|
HqInteropGetClient(conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val authority = if (port == 443) host else "$host:$port"
|
val authority = if (port == 443) host else "$host:$port"
|
||||||
|
|||||||
Reference in New Issue
Block a user