From 0138a9fdb2c1c56bae0695141fe557065ce9b674 Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Fri, 20 Sep 2019 19:14:36 -0700 Subject: [PATCH] Expose HttpSys RequestInfo extensibility (#14119) --- ...ft.AspNetCore.Server.HttpSys.netcoreapp.cs | 4 ++ src/Servers/HttpSys/src/FeatureContext.cs | 5 ++- .../HttpSys/src/IHttpSysRequestInfoFeature.cs | 25 +++++++++++ .../HttpSys/src/RequestProcessing/Request.cs | 15 +++++++ .../HttpSys/src/StandardFeatureCollection.cs | 1 + .../test/FunctionalTests/HttpsTests.cs | 43 +++++++++++++++++++ ...Core.Server.HttpSys.FunctionalTests.csproj | 3 +- .../RequestProcessing/NativeRequestContext.cs | 40 +++++++++++++++++ 8 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 src/Servers/HttpSys/src/IHttpSysRequestInfoFeature.cs 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 9112dfaca9..826cf629da 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); + } } }