From 71555e5f2dc5746a131ef64a2bec5b3a935550ba Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Fri, 23 Jul 2021 16:13:53 +0200 Subject: [PATCH 1/2] Fix HTTP/3 ALPN --- .../Common/tests/System/Net/Http/Http3LoopbackServer.cs | 4 +--- .../src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs | 4 +--- .../System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs | 2 +- .../tests/StressTests/HttpStress/StressServer.cs | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs index 6e511915f047b3..8622a3774f99bd 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs @@ -34,9 +34,7 @@ public Http3LoopbackServer(QuicImplementationProvider quicImplementationProvider EnabledSslProtocols = options.SslProtocols, ApplicationProtocols = new List { - new SslApplicationProtocol("h3-31"), - new SslApplicationProtocol("h3-30"), - new SslApplicationProtocol("h3-29") + new SslApplicationProtocol("h3") }, ServerCertificate = _cert, ClientCertificateRequired = false diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs index d3e6a90c4305b5..235614997c8d26 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs @@ -21,9 +21,7 @@ namespace System.Net.Http internal sealed class Http3Connection : HttpConnectionBase { // TODO: once HTTP/3 is standardized, create APIs for this. - public static readonly SslApplicationProtocol Http3ApplicationProtocol29 = new SslApplicationProtocol("h3-29"); - public static readonly SslApplicationProtocol Http3ApplicationProtocol30 = new SslApplicationProtocol("h3-30"); - public static readonly SslApplicationProtocol Http3ApplicationProtocol31 = new SslApplicationProtocol("h3-31"); + public static readonly SslApplicationProtocol Http3ApplicationProtocol = new SslApplicationProtocol("h3"); private readonly HttpConnectionPool _pool; private readonly HttpAuthority? _origin; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index fc5149397a3ada..8f14d1fe66d453 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -304,7 +304,7 @@ private static List CreateHttp3ApplicationProtocols() if (IsHttp3Supported()) { // TODO: Once the HTTP/3 versions are part of SslApplicationProtocol, see https://github.com/dotnet/runtime/issues/1293, move this back to field initialization. - return new List() { Http3Connection.Http3ApplicationProtocol31, Http3Connection.Http3ApplicationProtocol30, Http3Connection.Http3ApplicationProtocol29 }; + return new List() { Http3Connection.Http3ApplicationProtocol }; } return null!; diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs index 32927f7b1ae124..e994e7ed15cb2e 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs @@ -127,7 +127,7 @@ void ConfigureListenOptions(ListenOptions listenOptions) { host = host.UseQuic(options => { - options.Alpn = "h3-29"; + options.Alpn = "h3"; options.IdleTimeout = TimeSpan.FromMinutes(1); }); } From e2361bef9f10a996dc7c565035f0c6e83472ecbf Mon Sep 17 00:00:00 2001 From: Natalia Kondratyeva Date: Tue, 27 Jul 2021 15:59:35 +0200 Subject: [PATCH 2/2] Add tests --- .../System/Net/Http/Http3LoopbackServer.cs | 10 +- .../HttpClientHandlerTest.Http3.cs | 123 ++++++++++++++++++ 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs index 8622a3774f99bd..ffd97b9219c2b2 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs @@ -34,7 +34,7 @@ public Http3LoopbackServer(QuicImplementationProvider quicImplementationProvider EnabledSslProtocols = options.SslProtocols, ApplicationProtocols = new List { - new SslApplicationProtocol("h3") + new SslApplicationProtocol(options.Alpn) }, ServerCertificate = _cert, ClientCertificateRequired = false @@ -120,13 +120,17 @@ private static Http3Options CreateOptions(GenericLoopbackOptions options) } public class Http3Options : GenericLoopbackOptions { - public int MaxUnidirectionalStreams {get; set; } + public int MaxUnidirectionalStreams { get; set; } + + public int MaxBidirectionalStreams { get; set; } + + public string Alpn { get; set; } - public int MaxBidirectionalStreams {get; set; } public Http3Options() { MaxUnidirectionalStreams = 100; MaxBidirectionalStreams = 100; + Alpn = "h3"; } } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index 22d0370ef10fc7..7c7908b55920e6 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -10,6 +10,7 @@ using System.Net.Quic; using System.Net.Security; using System.Net.Test.Common; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -631,6 +632,128 @@ private static async Task SendDataForever(Http3LoopbackStream stream) } } + [ConditionalFact(nameof(IsMsQuicSupported))] + public async Task Alpn_H3_Success() + { + // Mock doesn't use ALPN. + if (UseQuicImplementationProvider == QuicImplementationProviders.Mock) + { + return; + } + + var options = new Http3Options() { Alpn = "h3" }; + using Http3LoopbackServer server = CreateHttp3LoopbackServer(options); + + using var clientDone = new SemaphoreSlim(0); + using var serverDone = new SemaphoreSlim(0); + + Task serverTask = Task.Run(async () => + { + using Http3LoopbackConnection connection = (Http3LoopbackConnection)await server.EstablishGenericConnectionAsync(); + + SslApplicationProtocol negotiatedAlpn = ExtractMsQuicNegotiatedAlpn(connection); + Assert.Equal(new SslApplicationProtocol("h3"), negotiatedAlpn); + + using Http3LoopbackStream stream = await connection.AcceptRequestStreamAsync(); + await stream.HandleRequestAsync(); + + serverDone.Release(); + await clientDone.WaitAsync(); + }); + + Task clientTask = Task.Run(async () => + { + using HttpClient client = CreateHttpClient(); + + using HttpRequestMessage request = new() + { + Method = HttpMethod.Get, + RequestUri = server.Address, + Version = HttpVersion30, + VersionPolicy = HttpVersionPolicy.RequestVersionExact + }; + HttpResponseMessage response = await client.SendAsync(request).WaitAsync(TimeSpan.FromSeconds(10)); + + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpVersion.Version30, response.Version); + + clientDone.Release(); + await serverDone.WaitAsync(); + }); + + await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(200_000); + } + + [ConditionalFact(nameof(IsMsQuicSupported))] + public async Task Alpn_NonH3_NegotiationFailure() + { + // Mock doesn't use ALPN. + if (UseQuicImplementationProvider == QuicImplementationProviders.Mock) + { + return; + } + + var options = new Http3Options() { Alpn = "h3-29" }; // anything other than "h3" + using Http3LoopbackServer server = CreateHttp3LoopbackServer(options); + + using var clientDone = new SemaphoreSlim(0); + + Task serverTask = Task.Run(async () => + { + // ALPN handshake handled by transport, app level will not get any notification + await clientDone.WaitAsync(); + }); + + Task clientTask = Task.Run(async () => + { + using HttpClient client = CreateHttpClient(); + + using HttpRequestMessage request = new() + { + Method = HttpMethod.Get, + RequestUri = server.Address, + Version = HttpVersion30, + VersionPolicy = HttpVersionPolicy.RequestVersionExact + }; + + HttpRequestException ex = await Assert.ThrowsAsync(() => client.SendAsync(request).WaitAsync(TimeSpan.FromSeconds(10))); + Assert.Contains("ALPN_NEG_FAILURE", ex.Message); + + clientDone.Release(); + }); + + await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(200_000); + } + + private SslApplicationProtocol ExtractMsQuicNegotiatedAlpn(Http3LoopbackConnection loopbackConnection) + { + // TODO: rewrite after object structure change + // current structure: + // Http3LoopbackConnection -> private QuicConnection _connection + // QuicConnection -> private QuicConnectionProvider _provider (= MsQuicConnection) + // MsQuicConnection -> private SslApplicationProtocol _negotiatedAlpnProtocol + + FieldInfo quicConnectionField = loopbackConnection.GetType().GetField("_connection", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(quicConnectionField); + object quicConnection = quicConnectionField.GetValue(loopbackConnection); + Assert.NotNull(quicConnection); + Assert.Equal("QuicConnection", quicConnection.GetType().Name); + + FieldInfo msQuicConnectionField = quicConnection.GetType().GetField("_provider", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(msQuicConnectionField); + object msQuicConnection = msQuicConnectionField.GetValue(quicConnection); + Assert.NotNull(msQuicConnection); + Assert.Equal("MsQuicConnection", msQuicConnection.GetType().Name); + + FieldInfo alpnField = msQuicConnection.GetType().GetField("_negotiatedAlpnProtocol", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.NotNull(alpnField); + object alpn = alpnField.GetValue(msQuicConnection); + Assert.NotNull(alpn); + Assert.IsType(alpn); + + return (SslApplicationProtocol)alpn; + } + /// /// These are public interop test servers for various QUIC and HTTP/3 implementations, /// taken from https://github.com/quicwg/base-drafts/wiki/Implementations