diff --git a/eng/Version.Details.props b/eng/Version.Details.props
index 38a30def8f02..34e606099902 100644
--- a/eng/Version.Details.props
+++ b/eng/Version.Details.props
@@ -104,15 +104,15 @@ This file should be imported by eng/Versions.props
4.13.0-3.24613.7
4.13.0-3.24613.7
- 9.10.0-preview.1.25462.1
- 9.10.0-preview.1.25462.1
- 9.10.0-preview.1.25462.1
+ 9.10.0-preview.1.25468.3
+ 9.10.0-preview.1.25468.3
+ 9.10.0-preview.1.25468.3
- 1.0.0-prerelease.25458.1
- 1.0.0-prerelease.25458.1
- 1.0.0-prerelease.25458.1
- 1.0.0-prerelease.25458.1
- 1.0.0-prerelease.25458.1
+ 1.0.0-prerelease.25467.1
+ 1.0.0-prerelease.25467.1
+ 1.0.0-prerelease.25467.1
+ 1.0.0-prerelease.25467.1
+ 1.0.0-prerelease.25467.1
17.12.36
17.12.36
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index d2f6a5f99199..298dcd5d1569 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -390,37 +390,37 @@
https://github.com/dotnet/dotnet
2dea164f01d307c409cfe0d0ee5cb8a0691e3c94
-
+
https://github.com/dotnet/extensions
- d299e16f15234f9808b18fef50bf7770113fb4b2
+ 53ef1158f9f42632e111d6873a8cd72b803b4ae6
-
+
https://github.com/dotnet/extensions
- d299e16f15234f9808b18fef50bf7770113fb4b2
+ 53ef1158f9f42632e111d6873a8cd72b803b4ae6
-
+
https://github.com/dotnet/extensions
- d299e16f15234f9808b18fef50bf7770113fb4b2
+ 53ef1158f9f42632e111d6873a8cd72b803b4ae6
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- 373ed5bf1a64c212e655063e58967eb62f9187ef
+ 59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- 373ed5bf1a64c212e655063e58967eb62f9187ef
+ 59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- 373ed5bf1a64c212e655063e58967eb62f9187ef
+ 59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- 373ed5bf1a64c212e655063e58967eb62f9187ef
+ 59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
- 373ed5bf1a64c212e655063e58967eb62f9187ef
+ 59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1
diff --git a/src/Components/Web/src/Virtualization/Virtualize.cs b/src/Components/Web/src/Virtualization/Virtualize.cs
index 6ce14ee0952d..8e2d84f2f11b 100644
--- a/src/Components/Web/src/Virtualization/Virtualize.cs
+++ b/src/Components/Web/src/Virtualization/Virtualize.cs
@@ -362,6 +362,10 @@ private void CalcualteItemDistribution(
_ => MaxItemCount
};
+ // Count the OverscanCount as used capacity, so we don't end up in a situation where
+ // the user has set a very low MaxItemCount and we end up in an infinite loading loop.
+ maxItemCount += OverscanCount * 2;
+
itemsInSpacer = Math.Max(0, (int)Math.Floor(spacerSize / _itemSize) - OverscanCount);
visibleItemCapacity = (int)Math.Ceiling(containerSize / _itemSize) + 2 * OverscanCount;
unusedItemCapacity = Math.Max(0, visibleItemCapacity - maxItemCount);
diff --git a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs
index 565dc0190fcd..4fa21ef3fe11 100644
--- a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs
+++ b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs
@@ -291,14 +291,14 @@ public void CanLimitMaxItemsRendered(bool useAppContext)
// we only render 10 items due to the MaxItemCount setting
var scrollArea = Browser.Exists(By.Id("virtualize-scroll-area"));
var getItems = () => scrollArea.FindElements(By.ClassName("my-item"));
- Browser.Equal(10, () => getItems().Count);
+ Browser.Equal(16, () => getItems().Count);
Browser.Equal("Id: 0; Name: Thing 0", () => getItems().First().Text);
// Scrolling still works and loads new data, though there's no guarantee about
// exactly how many items will show up at any one time
Browser.ExecuteJavaScript("document.getElementById('virtualize-scroll-area').scrollTop = 300;");
Browser.NotEqual("Id: 0; Name: Thing 0", () => getItems().First().Text);
- Browser.True(() => getItems().Count > 3 && getItems().Count <= 10);
+ Browser.True(() => getItems().Count > 3 && getItems().Count <= 16);
}
[Fact]
@@ -573,6 +573,101 @@ public void EmptyContentRendered_Async()
int GetPlaceholderCount() => Browser.FindElements(By.Id("async-placeholder")).Count;
}
+ [Fact]
+ public void CanElevateEffectiveMaxItemCount_WhenOverscanExceedsMax()
+ {
+ Browser.MountTestComponent();
+ var container = Browser.Exists(By.Id("virtualize-large-overscan"));
+ // Ensure we have an initial contiguous batch and the elevated effective max has kicked in (>= OverscanCount)
+ var indices = GetVisibleItemIndices();
+ Browser.True(() => indices.Count >= 200);
+
+ // Give focus so PageDown works
+ container.Click();
+
+ var js = (IJavaScriptExecutor)Browser;
+ var lastMaxIndex = -1;
+ var lastScrollTop = -1L;
+
+ // Check if we've reached (or effectively reached) the bottom
+ var scrollHeight = (long)js.ExecuteScript("return arguments[0].scrollHeight", container);
+ var clientHeight = (long)js.ExecuteScript("return arguments[0].clientHeight", container);
+ var scrollTop = (long)js.ExecuteScript("return arguments[0].scrollTop", container);
+ while (scrollTop + clientHeight < scrollHeight)
+ {
+ // Validate contiguity on the current page
+ Browser.True(() => IsCurrentViewContiguous(indices));
+
+ // Track progress in indices
+ var currentMax = indices.Max();
+ Assert.True(currentMax >= lastMaxIndex, $"Unexpected backward movement: previous max {lastMaxIndex}, current max {currentMax}.");
+ lastMaxIndex = currentMax;
+
+ // Send PageDown
+ container.SendKeys(Keys.PageDown);
+
+ // Wait for scrollTop to change (progress) to avoid infinite loop
+ var prevScrollTop = scrollTop;
+ Browser.True(() =>
+ {
+ var st = (long)js.ExecuteScript("return arguments[0].scrollTop", container);
+ if (st > prevScrollTop)
+ {
+ lastScrollTop = st;
+ return true;
+ }
+ return false;
+ });
+ scrollHeight = (long)js.ExecuteScript("return arguments[0].scrollHeight", container);
+ clientHeight = (long)js.ExecuteScript("return arguments[0].clientHeight", container);
+ scrollTop = (long)js.ExecuteScript("return arguments[0].scrollTop", container);
+ }
+
+ // Final contiguous assertion at bottom
+ Browser.True(() => IsCurrentViewContiguous());
+
+ // Helper: check visible items contiguous with no holes
+ bool IsCurrentViewContiguous(List existingIndices = null)
+ {
+ var indices = existingIndices ?? GetVisibleItemIndices();
+ if (indices.Count == 0)
+ {
+ return false;
+ }
+
+ if (indices[^1] - indices[0] != indices.Count - 1)
+ {
+ return false;
+ }
+ for (var i = 1; i < indices.Count; i++)
+ {
+ if (indices[i] - indices[i - 1] != 1)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ List GetVisibleItemIndices()
+ {
+ var elements = container.FindElements(By.CssSelector(".large-overscan-item"));
+ var list = new List(elements.Count);
+ foreach (var el in elements)
+ {
+ var text = el.Text;
+ if (text.StartsWith("Item ", StringComparison.Ordinal))
+ {
+ if (int.TryParse(text.AsSpan(5), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+ {
+ list.Add(value);
+ }
+ }
+ }
+ return list;
+ }
+ }
+
private string[] GetPeopleNames(IWebElement container)
{
var peopleElements = container.FindElements(By.CssSelector(".person span"));
diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor
index a3bc250f0634..d7df87adbeed 100644
--- a/src/Components/test/testassets/BasicTestApp/Index.razor
+++ b/src/Components/test/testassets/BasicTestApp/Index.razor
@@ -119,6 +119,7 @@
+
diff --git a/src/Components/test/testassets/BasicTestApp/VirtualizationLargeOverscan.razor b/src/Components/test/testassets/BasicTestApp/VirtualizationLargeOverscan.razor
new file mode 100644
index 000000000000..3beb29cf87c2
--- /dev/null
+++ b/src/Components/test/testassets/BasicTestApp/VirtualizationLargeOverscan.razor
@@ -0,0 +1,12 @@
+@* Test component to validate behavior when OverscanCount greatly exceeds MaxItemCount. *@
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+
+
+
+@code {
+ private IList _items = Enumerable.Range(0, 5000).ToList();
+}
diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
index ed61268897ef..1b9e03dc5706 100644
--- a/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
+++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
@@ -30,6 +31,7 @@
{
var connectionFeature = context.Features.GetRequiredFeature();
var httpSysPropFeature = context.Features.GetRequiredFeature();
+ var tlsHandshakeFeature = context.Features.GetRequiredFeature();
// first time invocation to find out required size
var success = httpSysPropFeature.TryGetTlsClientHello(Array.Empty(), out var bytesReturned);
@@ -41,7 +43,14 @@
success = httpSysPropFeature.TryGetTlsClientHello(bytes, out _);
Debug.Assert(success);
- await context.Response.WriteAsync($"[Response] connectionId={connectionFeature.ConnectionId}; tlsClientHello.length={bytesReturned}; tlsclienthello start={string.Join(' ', bytes.AsSpan(0, 30).ToArray())}");
+ await context.Response.WriteAsync(
+ $"""
+ connectionId = {connectionFeature.ConnectionId};
+ negotiated cipher suite = {tlsHandshakeFeature.NegotiatedCipherSuite};
+ tlsClientHello.length = {bytesReturned};
+ tlsclienthello start = {string.Join(' ', bytes.AsSpan(0, 30).ToArray())}
+ """);
+
await next(context);
});
diff --git a/src/Servers/HttpSys/src/LoggerEventIds.cs b/src/Servers/HttpSys/src/LoggerEventIds.cs
index e6d745f506be..a9f2c969c6bc 100644
--- a/src/Servers/HttpSys/src/LoggerEventIds.cs
+++ b/src/Servers/HttpSys/src/LoggerEventIds.cs
@@ -60,4 +60,5 @@ internal static class LoggerEventIds
public const int AcceptObserveExpectationMismatch = 53;
public const int RequestParsingError = 54;
public const int TlsListenerError = 55;
+ public const int QueryTlsCipherSuiteError = 56;
}
diff --git a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
index f5dfbc96a6cd..fbada3843642 100644
--- a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
+++ b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
@@ -70,6 +70,7 @@ internal static unsafe uint HttpSetRequestProperty(SafeHandle requestQueueHandle
internal static bool SupportsReset { get; }
internal static bool SupportsDelegation { get; }
internal static bool SupportsClientHello { get; }
+ internal static bool SupportsQueryTlsCipherInfo { get; }
internal static bool Supported { get; }
static unsafe HttpApi()
@@ -86,6 +87,7 @@ static unsafe HttpApi()
SupportsTrailers = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureResponseTrailers);
SupportsDelegation = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureDelegateEx);
SupportsClientHello = IsFeatureSupported((HTTP_FEATURE_ID)11 /* HTTP_FEATURE_ID.HttpFeatureCacheTlsClientHello */) && HttpGetRequestPropertySupported;
+ SupportsQueryTlsCipherInfo = IsFeatureSupported((HTTP_FEATURE_ID)15 /* HTTP_FEATURE_ID.HttpFeatureQueryCipherInfo */) && HttpGetRequestPropertySupported;
}
}
diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs
index 8e4babf7ca21..3d99fe8e718b 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs
@@ -3,6 +3,7 @@
using System.Globalization;
using System.Net;
+using System.Net.Security;
using System.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
@@ -334,6 +335,8 @@ private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint
public SslProtocols Protocol { get; private set; }
+ public TlsCipherSuite? NegotiatedCipherSuite { get; private set; }
+
[Obsolete(Obsoletions.RuntimeTlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.RuntimeTlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.RuntimeSharedUrlFormat)]
public CipherAlgorithmType CipherAlgorithm { get; private set; }
@@ -356,6 +359,8 @@ private void GetTlsHandshakeResults()
{
var handshake = RequestContext.GetTlsHandshake();
Protocol = (SslProtocols)handshake.Protocol;
+
+ NegotiatedCipherSuite = RequestContext.GetTlsCipherSuite();
#pragma warning disable SYSLIB0058 // Type or member is obsolete
CipherAlgorithm = (CipherAlgorithmType)handshake.CipherType;
CipherStrength = (int)handshake.CipherStrength;
diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs
index 1c80f92febc2..a66e44d1484c 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.FeatureCollection.cs
@@ -5,6 +5,7 @@
using System.Globalization;
using System.IO.Pipelines;
using System.Net;
+using System.Net.Security;
using System.Security.Authentication;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
@@ -593,6 +594,8 @@ bool IHttpBodyControlFeature.AllowSynchronousIO
SslProtocols ITlsHandshakeFeature.Protocol => Request.Protocol;
+ TlsCipherSuite? ITlsHandshakeFeature.NegotiatedCipherSuite => Request.NegotiatedCipherSuite;
+
#pragma warning disable SYSLIB0058 // Type or member is obsolete
CipherAlgorithmType ITlsHandshakeFeature.CipherAlgorithm => Request.CipherAlgorithm;
diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs
index d7766698bc41..0fce6896778b 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs
@@ -23,5 +23,8 @@ private static partial class Log
[LoggerMessage(LoggerEventIds.RequestParsingError, LogLevel.Debug, "Failed to invoke QueryTlsClientHello; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "TlsClientHelloRetrieveError")]
public static partial void TlsClientHelloRetrieveError(ILogger logger, ulong requestId, uint win32Error);
+
+ [LoggerMessage(LoggerEventIds.QueryTlsCipherSuiteError, LogLevel.Debug, "Failed to invoke QueryTlsCipherSuite; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "QueryTlsCipherSuiteError")]
+ public static partial void QueryTlsCipherSuiteError(ILogger logger, ulong requestId, uint win32Error);
}
}
diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
index eba7d33ff3b8..5aefb9df1cf3 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.AspNetCore.Http;
@@ -219,6 +220,45 @@ internal void ForceCancelRequest()
}
}
+ ///
+ /// Gets TLS cipher suite used for the request, if supported by the OS and http.sys.
+ ///
+ ///
+ /// null, if query of TlsCipherSuite is not supported or the query failed.
+ /// TlsCipherSuite value, if query is successful.
+ ///
+ internal unsafe TlsCipherSuite? GetTlsCipherSuite()
+ {
+ if (!HttpApi.SupportsQueryTlsCipherInfo)
+ {
+ return default;
+ }
+
+ var requestId = PinsReleased ? Request.RequestId : RequestId;
+
+ SecPkgContext_CipherInfo cipherInfo = default;
+
+ var statusCode = HttpApi.HttpGetRequestProperty(
+ requestQueueHandle: Server.RequestQueue.Handle,
+ requestId,
+ propertyId: (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */,
+ qualifier: null,
+ qualifierSize: 0,
+ output: &cipherInfo,
+ outputSize: (uint)sizeof(SecPkgContext_CipherInfo),
+ bytesReturned: IntPtr.Zero,
+ overlapped: IntPtr.Zero);
+
+ if (statusCode is ErrorCodes.ERROR_SUCCESS)
+ {
+ return checked((TlsCipherSuite)cipherInfo.dwCipherSuite);
+ }
+
+ // OS supports querying TlsCipherSuite, but request failed.
+ Log.QueryTlsCipherSuiteError(Logger, requestId, statusCode);
+ return null;
+ }
+
///
/// Attempts to get the client hello message bytes from the http.sys.
/// If successful writes the bytes into , and shows how many bytes were written in .
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp
index 8112de09b6e6..b0d5a8b2266b 100644
--- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp
+++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp
@@ -133,6 +133,23 @@ std::wstring Environment::GetDllDirectoryValue()
return expandedStr;
}
+ProcessorArchitecture Environment::GetCurrentProcessArchitecture()
+{
+ // Use compile-time detection - we know which architectures we support
+ // and this is the most reliable and efficient approach. IsWow64Process2
+ // doesn't show the correct architecture when running under x64 emulation
+ // on ARM64.
+#if defined(_M_ARM64)
+ return ProcessorArchitecture::ARM64;
+#elif defined(_M_AMD64)
+ return ProcessorArchitecture::AMD64;
+#elif defined(_M_IX86)
+ return ProcessorArchitecture::x86;
+#else
+ static_assert(false, "Unknown target architecture");
+#endif
+}
+
bool Environment::IsRunning64BitProcess()
{
// Check the bitness of the currently running process
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h
index 9e3e1b1bf772..a9e6e85d9ecc 100644
--- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h
+++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h
@@ -5,6 +5,7 @@
#include
#include
+#include "ProcessorArchitecture.h"
class Environment
{
@@ -23,6 +24,8 @@ class Environment
static
bool IsRunning64BitProcess();
static
+ ProcessorArchitecture GetCurrentProcessArchitecture();
+ static
HRESULT CopyToDirectory(const std::wstring& source, const std::filesystem::path& destination, bool cleanDest, const std::filesystem::path& directoryToIgnore, int& copiedFileCount);
static
bool CheckUpToDate(const std::wstring& source, const std::filesystem::path& destination, const std::wstring& extension, const std::filesystem::path& directoryToIgnore);
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp
index 8233d68a115e..8fc74b47c993 100644
--- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp
+++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp
@@ -411,7 +411,6 @@ HostFxrResolver::InvokeWhereToFindDotnet()
HandleWrapper hThread;
CComBSTR pwzDotnetName = nullptr;
DWORD dwFilePointer = 0;
- BOOL fIsCurrentProcess64Bit = FALSE;
DWORD dwExitCode = 0;
STRU struDotnetSubstring;
STRU struDotnetLocationsString;
@@ -426,6 +425,7 @@ HostFxrResolver::InvokeWhereToFindDotnet()
securityAttributes.bInheritHandle = TRUE;
LOG_INFO(L"Invoking where.exe to find dotnet.exe");
+ auto currentProcessArch = Environment::GetCurrentProcessArchitecture();
// Create a read/write pipe that will be used for reading the result of where.exe
FINISHED_LAST_ERROR_IF(!CreatePipe(&hStdOutReadPipe, &hStdOutWritePipe, &securityAttributes, 0));
@@ -499,13 +499,9 @@ HostFxrResolver::InvokeWhereToFindDotnet()
}
FINISHED_IF_FAILED(struDotnetLocationsString.CopyA(pzFileContents, dwNumBytesRead));
-
LOG_INFOF(L"where.exe invocation returned: '%ls'", struDotnetLocationsString.QueryStr());
- fIsCurrentProcess64Bit = Environment::IsRunning64BitProcess();
-
- LOG_INFOF(L"Current process bitness type detected as isX64=%d", fIsCurrentProcess64Bit);
-
+ // Look for a dotnet.exe that matches the current process architecture
while (TRUE)
{
index = struDotnetLocationsString.IndexOf(L"\r\n", prevIndex);
@@ -518,28 +514,38 @@ HostFxrResolver::InvokeWhereToFindDotnet()
// \r\n is two wchars, so add 2 here.
prevIndex = index + 2;
- LOG_INFOF(L"Processing entry '%ls'", struDotnetSubstring.QueryStr());
-
- if (fIsCurrentProcess64Bit == IsX64(struDotnetSubstring.QueryStr()))
+ ProcessorArchitecture dotnetArch = GetFileProcessorArchitecture(struDotnetSubstring.QueryStr());
+ if (dotnetArch == currentProcessArch)
{
- // The bitness of dotnet matched with the current worker process bitness.
+ LOG_INFOF(L"Found dotnet.exe matching current process architecture (%ls) '%ls'",
+ ProcessorArchitectureToString(dotnetArch),
+ struDotnetSubstring.QueryStr());
+
return std::make_optional(struDotnetSubstring.QueryStr());
}
+ else
+ {
+ LOG_INFOF(L"Skipping dotnet.exe with non-matching architecture %ls (need %ls). '%ls'",
+ ProcessorArchitectureToString(dotnetArch),
+ ProcessorArchitectureToString(currentProcessArch),
+ struDotnetSubstring.QueryStr());
+ }
}
Finished:
return result;
}
-BOOL HostFxrResolver::IsX64(const WCHAR* dotnetPath)
+// Reads the PE header of the binary to determine its architecture.
+ProcessorArchitecture HostFxrResolver::GetFileProcessorArchitecture(const WCHAR* binaryPath)
{
// Errors while reading from the file shouldn't throw unless
// file.exception(bits) is set
- std::ifstream file(dotnetPath, std::ios::binary);
+ std::ifstream file(binaryPath, std::ios::binary);
if (!file.is_open())
{
- LOG_TRACEF(L"Failed to open file %ls", dotnetPath);
- return false;
+ LOG_TRACEF(L"Failed to open file %ls", binaryPath);
+ return ProcessorArchitecture::Unknown;
}
// Read the DOS header
@@ -547,8 +553,8 @@ BOOL HostFxrResolver::IsX64(const WCHAR* dotnetPath)
file.read(reinterpret_cast(&dosHeader), sizeof(dosHeader));
if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) // 'MZ'
{
- LOG_TRACEF(L"%ls is not a valid executable file (missing MZ header).", dotnetPath);
- return false;
+ LOG_TRACEF(L"%ls is not a valid executable file (missing MZ header).", binaryPath);
+ return ProcessorArchitecture::Unknown;
}
// Seek to the PE header
@@ -559,32 +565,30 @@ BOOL HostFxrResolver::IsX64(const WCHAR* dotnetPath)
file.read(reinterpret_cast(&peSignature), sizeof(peSignature));
if (peSignature != IMAGE_NT_SIGNATURE) // 'PE\0\0'
{
- LOG_TRACEF(L"%ls is not a valid PE file (missing PE header).", dotnetPath);
- return false;
+ LOG_TRACEF(L"%ls is not a valid PE file (missing PE header).", binaryPath);
+ return ProcessorArchitecture::Unknown;
}
// Read the file header
IMAGE_FILE_HEADER fileHeader{};
file.read(reinterpret_cast(&fileHeader), sizeof(fileHeader));
- // Read the optional header magic field
- WORD magic{};
- file.read(reinterpret_cast(&magic), sizeof(magic));
-
- // Determine the architecture based on the magic value
- if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
+ // Determine the architecture based on the machine type
+ switch (fileHeader.Machine)
{
- LOG_INFOF(L"%ls is 32-bit", dotnetPath);
- return false;
- }
- else if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
- {
- LOG_INFOF(L"%ls is 64-bit", dotnetPath);
- return true;
+ case IMAGE_FILE_MACHINE_I386:
+ LOG_INFOF(L"%ls is x86 (32-bit)", binaryPath);
+ return ProcessorArchitecture::x86;
+ case IMAGE_FILE_MACHINE_AMD64:
+ LOG_INFOF(L"%ls is AMD64 (x64)", binaryPath);
+ return ProcessorArchitecture::AMD64;
+ case IMAGE_FILE_MACHINE_ARM64:
+ LOG_INFOF(L"%ls is ARM64", binaryPath);
+ return ProcessorArchitecture::ARM64;
+ default:
+ LOG_INFOF(L"%ls has unknown architecture (machine type: 0x%X)", binaryPath, fileHeader.Machine);
+ return ProcessorArchitecture::Unknown;
}
-
- LOG_INFOF(L"%ls is unknown architecture %i", dotnetPath, fileHeader.Machine);
- return false;
}
std::optional
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h
index 08ec650aec54..9065e2aecd2b 100644
--- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h
+++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h
@@ -8,8 +8,8 @@
#include
#include
#include
-
#include "ErrorContext.h"
+#include "ProcessorArchitecture.h"
#define READ_BUFFER_SIZE 4096
@@ -74,7 +74,7 @@ class HostFxrResolver
const std::filesystem::path & requestedPath
);
- static BOOL IsX64(const WCHAR* dotnetPath);
+ static ProcessorArchitecture GetFileProcessorArchitecture(const WCHAR* binaryPath);
struct LocalFreeDeleter
{
diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ProcessorArchitecture.h b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ProcessorArchitecture.h
new file mode 100644
index 000000000000..195feddcae7b
--- /dev/null
+++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ProcessorArchitecture.h
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+#pragma once
+
+enum class ProcessorArchitecture
+{
+ Unknown,
+ x86,
+ AMD64,
+ ARM64
+};
+
+inline const wchar_t* ProcessorArchitectureToString(ProcessorArchitecture arch)
+{
+ switch (arch)
+ {
+ case ProcessorArchitecture::x86:
+ return L"x86";
+ case ProcessorArchitecture::AMD64:
+ return L"AMD64";
+ case ProcessorArchitecture::ARM64:
+ return L"ARM64";
+ case ProcessorArchitecture::Unknown:
+ default:
+ return L"Unknown";
+ }
+}
\ No newline at end of file
diff --git a/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs b/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs
index 0ff3b86369c6..e3559fa5b1e0 100644
--- a/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs
+++ b/src/Servers/IIS/IIS/samples/NativeIISSample/Startup.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
@@ -52,6 +53,19 @@ public void Configure(IApplicationBuilder app)
await context.Response.WriteAsync("ClientCert: " + context.Connection.ClientCertificate + Environment.NewLine);
await context.Response.WriteAsync(Environment.NewLine);
+ var handshakeFeature = context.Features.Get();
+ if (handshakeFeature is not null)
+ {
+ await context.Response.WriteAsync(Environment.NewLine);
+ await context.Response.WriteAsync("TLS Information:" + Environment.NewLine);
+ await context.Response.WriteAsync($"Protocol: {handshakeFeature.Protocol}" + Environment.NewLine);
+
+ if (handshakeFeature.NegotiatedCipherSuite.HasValue)
+ {
+ await context.Response.WriteAsync($"Cipher Suite: {handshakeFeature.NegotiatedCipherSuite.Value}" + Environment.NewLine);
+ }
+ }
+
await context.Response.WriteAsync("User: " + context.User.Identity.Name + Environment.NewLine);
if (_authSchemeProvider != null)
{
diff --git a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
index 3ddc9315cf66..4a1417ed1b52 100644
--- a/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
+++ b/src/Servers/IIS/IIS/src/Core/IISHttpContext.cs
@@ -402,6 +402,8 @@ private void GetTlsHandshakeResults()
{
var handshake = GetTlsHandshake();
Protocol = (SslProtocols)handshake.Protocol;
+
+ NegotiatedCipherSuite = GetTlsCipherSuite();
#pragma warning disable SYSLIB0058 // Type or member is obsolete
CipherAlgorithm = (CipherAlgorithmType)handshake.CipherType;
CipherStrength = (int)handshake.CipherStrength;
@@ -415,6 +417,28 @@ private void GetTlsHandshakeResults()
SniHostName = sni.Hostname.ToString();
}
+ private unsafe TlsCipherSuite? GetTlsCipherSuite()
+ {
+ SecPkgContext_CipherInfo cipherInfo = default;
+
+ var statusCode = NativeMethods.HttpQueryRequestProperty(
+ RequestId,
+ (HTTP_REQUEST_PROPERTY)14 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsCipherInfo */,
+ qualifier: null,
+ qualifierSize: 0,
+ output: &cipherInfo,
+ outputSize: (uint)sizeof(SecPkgContext_CipherInfo),
+ bytesReturned: null,
+ overlapped: IntPtr.Zero);
+
+ if (statusCode == NativeMethods.HR_OK)
+ {
+ return checked((TlsCipherSuite)cipherInfo.dwCipherSuite);
+ }
+
+ return default;
+ }
+
private unsafe HTTP_REQUEST_PROPERTY_SNI GetClientSni()
{
var buffer = new byte[HttpApiTypes.SniPropertySizeInBytes];
diff --git a/src/Shared/HttpSys/NativeInterop/SecPkgContext_CipherInfo.cs b/src/Shared/HttpSys/NativeInterop/SecPkgContext_CipherInfo.cs
new file mode 100644
index 000000000000..1bba1c1afcef
--- /dev/null
+++ b/src/Shared/HttpSys/NativeInterop/SecPkgContext_CipherInfo.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.InteropServices;
+
+namespace Microsoft.AspNetCore.HttpSys.Internal;
+
+// From Schannel.h
+[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+internal unsafe struct SecPkgContext_CipherInfo
+{
+ private const int SZ_ALG_MAX_SIZE = 64;
+
+ private readonly int dwVersion;
+ private readonly int dwProtocol;
+ public readonly int dwCipherSuite;
+ private readonly int dwBaseCipherSuite;
+ private fixed char szCipherSuite[SZ_ALG_MAX_SIZE];
+ private fixed char szCipher[SZ_ALG_MAX_SIZE];
+ private readonly int dwCipherLen;
+ private readonly int dwCipherBlockLen; // in bytes
+ private fixed char szHash[SZ_ALG_MAX_SIZE];
+ private readonly int dwHashLen;
+ private fixed char szExchange[SZ_ALG_MAX_SIZE];
+ private readonly int dwMinExchangeLen;
+ private readonly int dwMaxExchangeLen;
+ private fixed char szCertificate[SZ_ALG_MAX_SIZE];
+ private readonly int dwKeyType;
+}