Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/9.0] Fix IIS outofprocess to remove WebSocket compression handshake #58931

Open
wants to merge 7 commits into
base: release/9.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,12 @@ public override string ToString()
{
return string.Format(
CultureInfo.InvariantCulture,
"[Variation] :: ServerType={0}, Runtime={1}, Arch={2}, BaseUrlHint={3}, Publish={4}",
"[Variation] :: ServerType={0}, Runtime={1}, Arch={2}, BaseUrlHint={3}, Publish={4}, HostingModel={5}",
ServerType,
RuntimeFlavor,
RuntimeArchitecture,
ApplicationBaseUriHint,
PublishApplicationBeforeDeployment);
PublishApplicationBeforeDeployment,
HostingModel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ FORWARDING_HANDLER::ExecuteRequestHandler()
if (cchHeader == 9 && _stricmp(pszWebSocketHeader, "websocket") == 0)
{
m_fWebSocketEnabled = TRUE;

// WinHttp does not support any extensions being returned by the server, so we remove the request header to avoid the server
// responding with any accepted extensions.
pRequest->DeleteHeader("Sec-WebSocket-Extensions");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
/// This type just maps collection names to available fixtures
/// </summary>
[CollectionDefinition(Name)]
public class IISTestSiteCollection : ICollectionFixture<IISTestSiteFixture>
public class IISTestSiteCollectionInProc : ICollectionFixture<IISTestSiteFixture>
{
public const string Name = nameof(IISTestSiteCollection);
public const string Name = nameof(IISTestSiteCollectionInProc);
}

[CollectionDefinition(Name)]
public class IISTestSiteCollectionOutOfProc : ICollectionFixture<IISTestSiteFixture>
{
public const string Name = nameof(IISTestSiteCollectionOutOfProc);
}

[CollectionDefinition(Name)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ internal IISTestSiteFixture(Action<IISDeploymentParameters> configure)
ApplicationPublisher = new PublishedApplicationPublisher(Helpers.GetInProcessTestSitesName()),
ServerType = DeployerSelector.ServerType
};

// Uncomment to add IIS debug logs to test output.
//DeploymentParameters.EnvironmentVariables.Add("ASPNETCORE_MODULE_DEBUG", "console");

// This queue does not have websockets enabled currently, adding the module will break all tests using this fixture.
if (!HelixHelper.GetTargetHelixQueue().ToLowerInvariant().Contains("windows.amd64.server2022"))
{
DeploymentParameters.EnableModule("WebSocketModule", "%IIS_BIN%/iiswsock.dll");
}
}

public HttpClient Client => DeploymentResult.HttpClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
#endif

[Collection(IISTestSiteCollection.Name)]
[Collection(IISTestSiteCollectionInProc.Name)]
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
public class RequestResponseTests
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Xunit.Abstractions;

#if !IIS_FUNCTIONALS
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;

#if IISEXPRESS_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
#elif NEWHANDLER_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewHandler.FunctionalTests;
#elif NEWSHIM_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
#endif
#else

namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
#endif

[Collection(IISTestSiteCollectionInProc.Name)]
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")]
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
public class WebSocketsInProcessTests : WebSocketsTests
{
public WebSocketsInProcessTests(IISTestSiteFixture fixture, ITestOutputHelper testOutput) : base(fixture, testOutput)
{
Fixture.DeploymentParameters.HostingModel = HostingModel.InProcess;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Xunit.Abstractions;

#if !IIS_FUNCTIONALS
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;

#if IISEXPRESS_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
#elif NEWHANDLER_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewHandler.FunctionalTests;
#elif NEWSHIM_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
#endif
#else

namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
#endif

[Collection(IISTestSiteCollectionOutOfProc.Name)]
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")]
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
public class WebSocketsOutOfProcessTests : WebSocketsTests
{
public WebSocketsOutOfProcessTests(IISTestSiteFixture fixture, ITestOutputHelper testOutput) : base(fixture, testOutput)
{
Fixture.DeploymentParameters.HostingModel = HostingModel.OutOfProcess;
}
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,79 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.InternalTesting;
using Xunit;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Xunit.Abstractions;

#if !IIS_FUNCTIONALS
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;

#if IISEXPRESS_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
#elif NEWHANDLER_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewHandler.FunctionalTests;
#elif NEWSHIM_FUNCTIONALS
namespace Microsoft.AspNetCore.Server.IIS.NewShim.FunctionalTests;
#endif
#else

namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests;
#endif

[Collection(IISTestSiteCollection.Name)]
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")]
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
public class WebSocketsTests
#if IISEXPRESS_FUNCTIONALS
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open")]
#else
// These queues do not have websockets enabled currently for full IIS
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;Windows.Amd64.Server2022.Open")]
#endif
public abstract class WebSocketsTests : FunctionalTestsBase
{
private readonly string _requestUri;
private readonly string _webSocketUri;
public IISTestSiteFixture Fixture { get; }

public WebSocketsTests(IISTestSiteFixture fixture)
public WebSocketsTests(IISTestSiteFixture fixture, ITestOutputHelper testOutput) : base(testOutput)
{
_requestUri = fixture.DeploymentResult.ApplicationBaseUri;
_webSocketUri = _requestUri.Replace("http:", "ws:");
Fixture = fixture;
}

[ConditionalFact]
public async Task RequestWithBody_NotUpgradable()
{
using var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(200) };
using var response = await client.PostAsync(_requestUri + "WebSocketNotUpgradable", new StringContent("Hello World"));
using var response = await client.PostAsync(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketNotUpgradable", new StringContent("Hello World"));
response.EnsureSuccessStatusCode();
}

[ConditionalFact]
public async Task RequestWithoutBody_Upgradable()
{
if (Fixture.DeploymentParameters.HostingModel == HostingModel.OutOfProcess)
{
// OutOfProcess doesn't support upgrade requests without the "Upgrade": "websocket" header.
return;
}

using var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(200) };
// POST with Content-Length: 0 counts as not having a body.
using var response = await client.PostAsync(_requestUri + "WebSocketUpgradable", new StringContent(""));
using var response = await client.PostAsync(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketUpgradable", new StringContent(""));
response.EnsureSuccessStatusCode();
}

[ConditionalFact]
public async Task OnStartedCalledForWebSocket()
{
var cws = new ClientWebSocket();
await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketLifetimeEvents"), default);
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
webSocketUri = webSocketUri.Replace("http:", "ws:");

using var cws = new ClientWebSocket();
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketLifetimeEvents"), default);

await ReceiveMessage(cws, "OnStarting");
await ReceiveMessage(cws, "Upgraded");
Expand All @@ -60,17 +82,23 @@ public async Task OnStartedCalledForWebSocket()
[ConditionalFact]
public async Task WebReadBeforeUpgrade()
{
var cws = new ClientWebSocket();
await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketReadBeforeUpgrade"), default);
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
webSocketUri = webSocketUri.Replace("http:", "ws:");

using var cws = new ClientWebSocket();
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketReadBeforeUpgrade"), default);

await ReceiveMessage(cws, "Yay");
}

[ConditionalFact]
public async Task CanSendAndReceieveData()
{
var cws = new ClientWebSocket();
await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketEcho"), default);
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
webSocketUri = webSocketUri.Replace("http:", "ws:");

using var cws = new ClientWebSocket();
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketEcho"), default);

for (int i = 0; i < 1000; i++)
{
Expand All @@ -80,10 +108,33 @@ public async Task CanSendAndReceieveData()
}
}

[ConditionalFact]
public async Task AttemptCompressionWorks()
{
var webSocketUri = Fixture.DeploymentResult.ApplicationBaseUri;
webSocketUri = webSocketUri.Replace("http:", "ws:");

using var cws = new ClientWebSocket();
cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions();
await cws.ConnectAsync(new Uri(webSocketUri + "WebSocketAllowCompression"), default);

// Compression doesn't work with OutOfProcess, let's make sure the websocket extensions aren't forwarded and the connection still works
var expected = Fixture.DeploymentParameters.HostingModel == HostingModel.InProcess
? "permessage-deflate; client_max_window_bits=15" : "None";
await ReceiveMessage(cws, expected);

for (int i = 0; i < 1000; i++)
{
var message = i.ToString(CultureInfo.InvariantCulture);
await SendMessage(cws, message);
await ReceiveMessage(cws, message);
}
}

[ConditionalFact]
public async Task Http1_0_Request_NotUpgradable()
{
Uri uri = new Uri(_requestUri + "WebSocketNotUpgradable");
Uri uri = new Uri(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketNotUpgradable");
using TcpClient client = new TcpClient();

await client.ConnectAsync(uri.Host, uri.Port);
Expand All @@ -103,7 +154,7 @@ public async Task Http1_0_Request_NotUpgradable()
[ConditionalFact]
public async Task Http1_0_Request_UpgradeErrors()
{
Uri uri = new Uri(_requestUri + "WebSocketUpgradeFails");
Uri uri = new Uri(Fixture.DeploymentResult.ApplicationBaseUri + "WebSocketUpgradeFails");
using TcpClient client = new TcpClient();

await client.ConnectAsync(uri.Host, uri.Port);
Expand Down Expand Up @@ -148,6 +199,7 @@ private async Task SendMessage(ClientWebSocket webSocket, string message)

private async Task ReceiveMessage(ClientWebSocket webSocket, string expectedMessage)
{
Debug.Assert(expectedMessage.Length > 0);
var received = new byte[expectedMessage.Length];

var offset = 0;
Expand All @@ -156,7 +208,7 @@ private async Task ReceiveMessage(ClientWebSocket webSocket, string expectedMess
{
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(received, offset, received.Length - offset), default);
offset += result.Count;
} while (!result.EndOfMessage);
} while (!result.EndOfMessage && result.CloseStatus is null && received.Length - offset > 0);

Assert.Equal(expectedMessage, Encoding.ASCII.GetString(received));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>


<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Server.IIS" />
<ProjectReference Include="..\testassets\IIS.Common.TestLib\IIS.Common.TestLib.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
<Reference Include="Microsoft.AspNetCore.HttpsPolicy" />
<Reference Include="Microsoft.AspNetCore.WebSockets" />
<Reference Include="Microsoft.AspNetCore.WebUtilities" />
<Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<Reference Include="Microsoft.Extensions.Configuration.Json" />
Expand Down
Loading
Loading