diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 7f40bdf9c9..ecb0510f3a 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -451,6 +451,10 @@ stages: testResultsFiles: '*.xml' searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)/Flaky' artifacts: + - name: Windows_Test_Dumps + path: artifacts/dumps/ + publishOnError: true + includeForks: false - name: Windows_Test_Logs path: artifacts/log/ publishOnError: true @@ -475,6 +479,10 @@ stages: - script: ./src/ProjectTemplates/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.test.binlog" displayName: Test Templates artifacts: + - name: Windows_Test_Templates_Dumps + path: artifacts/dumps/ + publishOnError: true + includeForks: false - name: Windows_Test_Templates_Logs path: artifacts/log/ publishOnError: true diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml index 5a829c68cf..a136f3871f 100644 --- a/.azure/pipelines/jobs/default-build.yml +++ b/.azure/pipelines/jobs/default-build.yml @@ -129,7 +129,7 @@ jobs: - ${{ if and(eq(parameters.agentOs, 'Windows'), eq(parameters.isTestingJob, true)) }}: - powershell: ./eng/scripts/InstallProcDump.ps1 displayName: Install ProcDump - - powershell: ./eng/scripts/StartDumpCollectionForHangingBuilds.ps1 $(ProcDumpPath)procdump.exe artifacts/log/ (Get-Date).AddMinutes(160) dotnet + - powershell: ./eng/scripts/StartDumpCollectionForHangingBuilds.ps1 $(ProcDumpPath)procdump.exe artifacts/dumps/ (Get-Date).AddMinutes(160) dotnet displayName: Start background dump collection - ${{ if eq(parameters.installNodeJs, 'true') }}: - task: NodeTool@0 @@ -171,7 +171,7 @@ jobs: - ${{ parameters.afterBuild }} - ${{ if and(eq(parameters.agentOs, 'Windows'), eq(parameters.isTestingJob, true)) }}: - - powershell: ./eng/scripts/FinishDumpCollectionForHangingBuilds.ps1 artifacts/log/ + - powershell: ./eng/scripts/FinishDumpCollectionForHangingBuilds.ps1 artifacts/dumps/ displayName: Finish background dump collection continueOnError: true condition: always() diff --git a/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs b/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs index bccbdc2248..e339f777fa 100644 --- a/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs +++ b/src/Servers/HttpSys/ref/Microsoft.AspNetCore.Server.HttpSys.netcoreapp.cs @@ -57,6 +57,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys public Microsoft.AspNetCore.Server.HttpSys.TimeoutManager Timeouts { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public Microsoft.AspNetCore.Server.HttpSys.UrlPrefixCollection UrlPrefixes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } } + public partial interface IHttpSysRequestInfoFeature + { + System.Collections.Generic.IReadOnlyDictionary> RequestInfo { get; } + } public sealed partial class TimeoutManager { internal TimeoutManager() { } diff --git a/src/Servers/HttpSys/src/FeatureContext.cs b/src/Servers/HttpSys/src/FeatureContext.cs index 0d97b4bbd4..a689230ab8 100644 --- a/src/Servers/HttpSys/src/FeatureContext.cs +++ b/src/Servers/HttpSys/src/FeatureContext.cs @@ -34,7 +34,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys IHttpUpgradeFeature, IHttpRequestIdentifierFeature, IHttpMaxRequestBodySizeFeature, - IHttpBodyControlFeature + IHttpBodyControlFeature, + IHttpSysRequestInfoFeature { private RequestContext _requestContext; private IFeatureCollection _features; @@ -546,6 +547,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys int ITlsHandshakeFeature.KeyExchangeStrength => Request.KeyExchangeStrength; + IReadOnlyDictionary> IHttpSysRequestInfoFeature.RequestInfo => Request.RequestInfo; + internal async Task OnResponseStart() { if (_responseStarted) diff --git a/src/Servers/HttpSys/src/IHttpSysRequestInfoFeature.cs b/src/Servers/HttpSys/src/IHttpSysRequestInfoFeature.cs new file mode 100644 index 0000000000..21914fffd0 --- /dev/null +++ b/src/Servers/HttpSys/src/IHttpSysRequestInfoFeature.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +// Note: This will also be useful for IIS in-proc. +// Plan: Have Microsoft.AspNetCore.Server.IIS take a dependency on Microsoft.AspNetCore.Server.HttpSys and implement this interface. +namespace Microsoft.AspNetCore.Server.HttpSys +{ + /// + /// This exposes the Http.Sys HTTP_REQUEST_INFO extensibility point as opaque data for the caller to interperate. + /// https://docs.microsoft.com/en-us/windows/win32/api/http/ns-http-http_request_v2 + /// https://docs.microsoft.com/en-us/windows/win32/api/http/ns-http-http_request_info + /// + public interface IHttpSysRequestInfoFeature + { + /// + /// A collection of the HTTP_REQUEST_INFO for the current request. The integer represents the identifying + /// HTTP_REQUEST_INFO_TYPE enum value. The Memory is opaque bytes that need to be interperted in the format + /// specified by the enum value. + /// + public IReadOnlyDictionary> RequestInfo { get; } + } +} diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs index 3f2fe439ba..3d7d9a4088 100644 --- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs +++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; @@ -31,6 +32,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys private AspNetCore.HttpSys.Internal.SocketAddress _localEndPoint; private AspNetCore.HttpSys.Internal.SocketAddress _remoteEndPoint; + private IReadOnlyDictionary> _requestInfo; + private bool _isDisposed = false; internal Request(RequestContext requestContext, NativeRequestContext nativeRequestContext) @@ -252,6 +255,18 @@ namespace Microsoft.AspNetCore.Server.HttpSys public int KeyExchangeStrength { get; private set; } + public IReadOnlyDictionary> RequestInfo + { + get + { + if (_requestInfo == null) + { + _requestInfo = _nativeRequestContext.GetRequestInfo(); + } + return _requestInfo; + } + } + private void GetTlsHandshakeResults() { var handshake = _nativeRequestContext.GetTlsHandshake(); diff --git a/src/Servers/HttpSys/src/StandardFeatureCollection.cs b/src/Servers/HttpSys/src/StandardFeatureCollection.cs index 9fb0d75b87..e63155c823 100644 --- a/src/Servers/HttpSys/src/StandardFeatureCollection.cs +++ b/src/Servers/HttpSys/src/StandardFeatureCollection.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys { typeof(RequestContext), ctx => ctx.RequestContext }, { typeof(IHttpMaxRequestBodySizeFeature), _identityFunc }, { typeof(IHttpBodyControlFeature), _identityFunc }, + { typeof(IHttpSysRequestInfoFeature), _identityFunc }, }; private readonly FeatureContext _featureContext; diff --git a/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs b/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs index 09d6f2a084..6bbaeb13cb 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/HttpsTests.cs @@ -3,7 +3,9 @@ using System; using System.IO; +using System.Linq; using System.Net.Http; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -12,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.HttpSys.Internal; using Microsoft.AspNetCore.Testing; using Xunit; @@ -160,6 +163,46 @@ namespace Microsoft.AspNetCore.Server.HttpSys } } + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win2008R2)] + public async Task Https_ITlsHandshakeFeature_MatchesIHttpSysExtensionInfoFeature() + { + using (Utilities.CreateDynamicHttpsServer(out var address, async httpContext => + { + try + { + var tlsFeature = httpContext.Features.Get(); + var requestInfoFeature = httpContext.Features.Get(); + Assert.NotNull(tlsFeature); + Assert.NotNull(requestInfoFeature); + Assert.True(requestInfoFeature.RequestInfo.Count > 0); + var tlsInfo = requestInfoFeature.RequestInfo[(int)HttpApiTypes.HTTP_REQUEST_INFO_TYPE.HttpRequestInfoTypeSslProtocol]; + HttpApiTypes.HTTP_SSL_PROTOCOL_INFO tlsCopy; + unsafe + { + using var handle = tlsInfo.Pin(); + tlsCopy = Marshal.PtrToStructure((IntPtr)handle.Pointer); + } + + // Assert.Equal(tlsFeature.Protocol, tlsCopy.Protocol); // These don't directly match because the native and managed enums use different values. + Assert.Equal(tlsFeature.CipherAlgorithm, tlsCopy.CipherType); + Assert.Equal(tlsFeature.CipherStrength, (int)tlsCopy.CipherStrength); + Assert.Equal(tlsFeature.HashAlgorithm, tlsCopy.HashType); + Assert.Equal(tlsFeature.HashStrength, (int)tlsCopy.HashStrength); + Assert.Equal(tlsFeature.KeyExchangeAlgorithm, tlsCopy.KeyExchangeType); + Assert.Equal(tlsFeature.KeyExchangeStrength, (int)tlsCopy.KeyExchangeStrength); + } + catch (Exception ex) + { + await httpContext.Response.WriteAsync(ex.ToString()); + } + })) + { + string response = await SendRequestAsync(address); + Assert.Equal(string.Empty, response); + } + } + private async Task SendRequestAsync(string uri, X509Certificate cert = null) { diff --git a/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj b/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj index 36a36cdc08..281d455f5e 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj +++ b/src/Servers/HttpSys/test/FunctionalTests/Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj @@ -1,8 +1,9 @@ - + $(DefaultNetCoreTargetFramework) HttpSys.FunctionalTests + true diff --git a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs index c55364f561..2c4dcf1ce1 100644 --- a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs +++ b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Net.Sockets; using System.Runtime.InteropServices; @@ -478,5 +480,43 @@ namespace Microsoft.AspNetCore.HttpSys.Internal } return dataRead; } + + internal IReadOnlyDictionary> GetRequestInfo() + { + if (_permanentlyPinned) + { + return GetRequestInfo((IntPtr)_nativeRequest, (HttpApiTypes.HTTP_REQUEST_V2*)_nativeRequest); + } + else + { + fixed (byte* pMemoryBlob = _backingBuffer) + { + var request = (HttpApiTypes.HTTP_REQUEST_V2*)(pMemoryBlob + _bufferAlignment); + return GetRequestInfo(_originalBufferAddress, request); + } + } + } + + private IReadOnlyDictionary> GetRequestInfo(IntPtr baseAddress, HttpApiTypes.HTTP_REQUEST_V2* nativeRequest) + { + var count = nativeRequest->RequestInfoCount; + if (count == 0) + { + return ImmutableDictionary>.Empty; + } + + var info = new Dictionary>(count); + + for (var i = 0; i < count; i++) + { + var requestInfo = nativeRequest->pRequestInfo[i]; + var offset = (long)requestInfo.pInfo - (long)baseAddress; + info.Add( + (int)requestInfo.InfoType, + new ReadOnlyMemory(_backingBuffer, (int)offset, (int)requestInfo.InfoLength)); + } + + return new ReadOnlyDictionary>(info); + } } }