diff options
author | Katya Sokolova <esokolov@microsoft.com> | 2022-08-13 00:04:34 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-13 00:04:34 +0300 |
commit | 3824cb2931453d7818c6fff0762cc6f1c536a147 (patch) | |
tree | 52ab3ee2cf2cf218c3e20ca45183babcd84667dd | |
parent | 280c373c7b840bcf2226aac343e8a4e27144ff1d (diff) |
Handle web socket downgrade when HTTP/2 not enabled (#73843)
* Handle downgrade when HTTP/2 not enabled
* Update src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs
Co-authored-by: Natalia Kondratyeva <knatalia@microsoft.com>
* feedback
* Force 1.1 for non-secure web request if policy allows
Co-authored-by: Natalia Kondratyeva <knatalia@microsoft.com>
5 files changed, 77 insertions, 10 deletions
diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs index 04c5bef5d95..d2a6f430a9b 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs @@ -88,7 +88,14 @@ namespace System.Net.Http throw CancellationHelper.CreateOperationCanceledException(e, cancellationToken); } - throw new HttpRequestException(SR.net_http_ssl_connection_failed, e); + HttpRequestException ex = new HttpRequestException(SR.net_http_ssl_connection_failed, e); + if (request.IsExtendedConnectRequest) + { + // Extended connect request is negotiating strictly for ALPN = "h2" because HttpClient is unaware of a possible downgrade. + // At this point, SSL connection for HTTP / 2 failed, and the exception should indicate the reason for the external client / user. + ex.Data["HTTP2_ENABLED"] = false; + } + throw ex; } // Handle race condition if cancellation happens after SSL auth completes but before the registration is disposed 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 354b50b5966..e4afdd294fc 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 @@ -426,7 +426,13 @@ namespace System.Net.Http { Debug.Assert(desiredVersion == 2 || desiredVersion == 3); - throw new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion), inner); + HttpRequestException ex = new HttpRequestException(SR.Format(SR.net_http_requested_version_cannot_establish, request.Version, request.VersionPolicy, desiredVersion), inner); + if (request.IsExtendedConnectRequest && desiredVersion == 2) + { + ex.Data["HTTP2_ENABLED"] = false; + } + + throw ex; } private bool CheckExpirationOnGet(HttpConnectionBase connection) @@ -1122,12 +1128,7 @@ namespace System.Net.Http // Throw if fallback is not allowed by the version policy. if (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) { - HttpRequestException exception = new HttpRequestException(SR.Format(SR.net_http_requested_version_server_refused, request.Version, request.VersionPolicy), e); - if (request.IsExtendedConnectRequest) - { - exception.Data["HTTP2_ENABLED"] = false; - } - throw exception; + throw new HttpRequestException(SR.Format(SR.net_http_requested_version_server_refused, request.Version, request.VersionPolicy), e); } if (NetEventSource.Log.IsEnabled()) diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs index 29ddf98cafe..6bb365012ef 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs @@ -53,7 +53,8 @@ namespace System.Net.WebSockets bool disposeResponse = false; - bool tryDowngrade = false; + // force non-secure request to 1.1 whenever it is possible as HttpClient does + bool tryDowngrade = uri.Scheme == UriScheme.Ws && (options.HttpVersion == HttpVersion.Version11 || options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrLower); try { @@ -63,7 +64,7 @@ namespace System.Net.WebSockets { HttpRequestMessage request; if (!tryDowngrade && options.HttpVersion >= HttpVersion.Version20 - || (options.HttpVersion == HttpVersion.Version11 && options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrHigher)) + || (options.HttpVersion == HttpVersion.Version11 && options.HttpVersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && uri.Scheme == UriScheme.Wss)) { if (options.HttpVersion > HttpVersion.Version20 && options.HttpVersionPolicy != HttpVersionPolicy.RequestVersionOrLower) { diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs index 203040e4ffc..a32587bc862 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ClientWebSocketTestBase.cs @@ -26,6 +26,11 @@ namespace System.Net.WebSockets.Client.Tests new object[] { o[0], false }, new object[] { o[0], true } }).ToArray(); + public static readonly object[][] SecureEchoServersAndBoolean = new object[][] + { + new object[] { Test.Common.Configuration.WebSockets.SecureRemoteEchoServer, false }, + new object[] { Test.Common.Configuration.WebSockets.SecureRemoteEchoServer, true } + }; public const int TimeOutMilliseconds = 30000; public const int CloseDescriptionMaxLength = 123; diff --git a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs index aa14fd93ab2..1ec2f1ee39f 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/ConnectTest.Http2.cs @@ -96,6 +96,59 @@ namespace System.Net.WebSockets.Client.Tests ); } + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] + [Theory] + [MemberData(nameof(SecureEchoServersAndBoolean))] + [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] + public async Task ConnectAsync_Http11Server_DowngradeFail(Uri server, bool useHandler) + { + using (var cws = new ClientWebSocket()) + using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) + { + cws.Options.HttpVersion = HttpVersion.Version20; + cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionExact; + Task t; + if (useHandler) + { + var handler = new SocketsHttpHandler(); + t = cws.ConnectAsync(server, new HttpMessageInvoker(handler), cts.Token); + } + else + { + t = cws.ConnectAsync(server, cts.Token); + } + var ex = await Assert.ThrowsAnyAsync<WebSocketException>(() => t); + Assert.IsType<HttpRequestException>(ex.InnerException); + Assert.True(ex.InnerException.Data.Contains("HTTP2_ENABLED")); + Assert.Equal(WebSocketState.Closed, cws.State); + } + } + + [OuterLoop("Uses external servers", typeof(PlatformDetection), nameof(PlatformDetection.LocalEchoServerIsNotAvailable))] + [Theory] + [MemberData(nameof(EchoServersAndBoolean))] + [SkipOnPlatform(TestPlatforms.Browser, "System.Net.Sockets is not supported on this platform")] + public async Task ConnectAsync_Http11Server_DowngradeSuccess(Uri server, bool useHandler) + { + using (var cws = new ClientWebSocket()) + using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) + { + cws.Options.HttpVersion = HttpVersion.Version20; + cws.Options.HttpVersionPolicy = Http.HttpVersionPolicy.RequestVersionOrLower; + if (useHandler) + { + var handler = new SocketsHttpHandler(); + await cws.ConnectAsync(server, new HttpMessageInvoker(handler), cts.Token); + } + else + { + await cws.ConnectAsync(server, cts.Token); + } + Assert.Equal(WebSocketState.Open, cws.State); + } + } + + [Theory] [InlineData(false)] [InlineData(true)] |