diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml
index 107fcdef9ff3..5e4e2e533b16 100644
--- a/.azure/pipelines/ci.yml
+++ b/.azure/pipelines/ci.yml
@@ -102,14 +102,14 @@ variables:
- name: WindowsArm64InstallersLogArgs
value: /bl:artifacts/log/Release/Build.Installers.Arm64.binlog
- name: _InternalRuntimeDownloadArgs
- value: -RuntimeSourceFeed https://dotnetbuilds.blob.core.windows.net/internal
+ value: -RuntimeSourceFeed https://ci.dot.net/internal
-RuntimeSourceFeedKey $(dotnetbuilds-internal-container-read-token-base64)
/p:DotNetAssetRootAccessTokenSuffix='$(dotnetbuilds-internal-container-read-token-base64)'
# The code signing doesn't use the aspnet build scripts, so the msbuild parameters have to be passed directly. This
# is awkward but necessary because the eng/common/ build scripts don't add the msbuild properties automatically.
- name: _InternalRuntimeDownloadCodeSignArgs
value: $(_InternalRuntimeDownloadArgs)
- /p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal
+ /p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal
/p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64)
- group: DotNet-HelixApi-Access
- ${{ if notin(variables['Build.Reason'], 'PullRequest') }}:
diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml
index fbe60b3e7964..eee89df3733a 100644
--- a/.azure/pipelines/jobs/default-build.yml
+++ b/.azure/pipelines/jobs/default-build.yml
@@ -109,10 +109,10 @@ jobs:
vmImage: macOS-13
${{ if eq(parameters.agentOs, 'Linux') }}:
${{ if eq(parameters.useHostedUbuntu, true) }}:
- vmImage: ubuntu-20.04
+ vmImage: ubuntu-22.04
${{ if eq(parameters.useHostedUbuntu, false) }}:
name: $(DncEngPublicBuildPool)
- demands: ImageOverride -equals Build.Ubuntu.2004.Amd64.Open
+ demands: ImageOverride -equals Build.Ubuntu.2204.Amd64.Open
${{ if eq(parameters.agentOs, 'Windows') }}:
name: $(DncEngPublicBuildPool)
demands: ImageOverride -equals windows.vs2022preview.amd64.open
@@ -324,7 +324,7 @@ jobs:
os: macOS
${{ if eq(parameters.agentOs, 'Linux') }}:
name: $(DncEngInternalBuildPool)
- image: 1es-ubuntu-2004
+ image: 1es-ubuntu-2204
os: linux
${{ if eq(parameters.agentOs, 'Windows') }}:
name: $(DncEngInternalBuildPool)
diff --git a/AspNetCore.sln b/AspNetCore.sln
index 367d27911f8e..a16a5c63a6e9 100644
--- a/AspNetCore.sln
+++ b/AspNetCore.sln
@@ -1784,6 +1784,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotReferencedInWasmCodePack
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components.WasmRemoteAuthentication", "src\Components\test\testassets\Components.WasmRemoteAuthentication\Components.WasmRemoteAuthentication.csproj", "{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TlsFeaturesObserve", "src\Servers\HttpSys\samples\TlsFeaturesObserve\TlsFeaturesObserve.csproj", "{98C71EC8-1303-F55D-4032-E6728971770E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -10753,6 +10755,22 @@ Global
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x64.Build.0 = Release|Any CPU
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x86.ActiveCfg = Release|Any CPU
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13}.Release|x86.Build.0 = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|arm64.Build.0 = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|x64.Build.0 = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Debug|x86.Build.0 = Debug|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|arm64.ActiveCfg = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|arm64.Build.0 = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|x64.ActiveCfg = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|x64.Build.0 = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|x86.ActiveCfg = Release|Any CPU
+ {98C71EC8-1303-F55D-4032-E6728971770E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -11634,6 +11652,7 @@ Global
{F232B503-D412-45EE-8B31-EFD46B9FA302} = {AA5ABFBC-177C-421E-B743-005E0FD1248B}
{433F91E4-E39D-4EB0-B798-2998B3969A2C} = {6126DCE4-9692-4EE2-B240-C65743572995}
{8A021D6D-7935-4AB3-BB47-38D4FF9B0D13} = {6126DCE4-9692-4EE2-B240-C65743572995}
+ {98C71EC8-1303-F55D-4032-E6728971770E} = {49016328-4D32-46E4-A4D2-94686ED38EA2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
diff --git a/NuGet.config b/NuGet.config
index a972af091630..aa5c279af136 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -6,10 +6,10 @@
-
+
-
+
@@ -30,10 +30,10 @@
-
+
-
+
diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props
index af5cc98a0d47..f1b0e807fea1 100644
--- a/eng/Baseline.Designer.props
+++ b/eng/Baseline.Designer.props
@@ -2,117 +2,117 @@
$(MSBuildAllProjects);$(MSBuildThisFileFullPath)
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
+
-
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
@@ -120,138 +120,138 @@
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
-
-
-
+
+
+
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
-
+
+
@@ -259,7 +259,7 @@
- 8.0.13
+ 8.0.15
@@ -268,51 +268,51 @@
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
-
+
+
@@ -322,8 +322,8 @@
-
-
+
+
@@ -331,8 +331,8 @@
-
-
+
+
@@ -343,58 +343,58 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
@@ -403,7 +403,7 @@
- 8.0.13
+ 8.0.15
@@ -411,71 +411,71 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
-
-
+
+
- 8.0.13
+ 8.0.15
@@ -491,27 +491,27 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
@@ -520,23 +520,23 @@
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
@@ -545,54 +545,54 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
-
+
+
-
-
+
+
-
-
+
+
- 8.0.13
+ 8.0.15
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
@@ -600,83 +600,83 @@
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
-
-
-
+
+
+
+
- 8.0.13
+ 8.0.15
@@ -685,64 +685,64 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
@@ -764,7 +764,7 @@
- 8.0.13
+ 8.0.15
@@ -786,7 +786,7 @@
- 8.0.13
+ 8.0.15
@@ -802,23 +802,23 @@
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
@@ -826,24 +826,24 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
-
-
+
+
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
@@ -853,7 +853,7 @@
- 8.0.13
+ 8.0.15
@@ -862,73 +862,73 @@
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
-
+
-
+
-
+
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
@@ -957,11 +957,11 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
@@ -979,18 +979,18 @@
- 8.0.13
+ 8.0.15
- 8.0.13
+ 8.0.15
-
+
- 8.0.13
+ 8.0.15
diff --git a/eng/Baseline.xml b/eng/Baseline.xml
index 9efbb290ef60..9746528d4365 100644
--- a/eng/Baseline.xml
+++ b/eng/Baseline.xml
@@ -4,110 +4,110 @@ This file contains a list of all the packages and their versions which were rele
Update this list when preparing for a new patch.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 176de8a62280..bfa82b7e83bb 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -9,37 +9,37 @@
-->
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 0118cb6810a48869bf7494aabd86ef44da5940a3
+ c755c4a6f3df392ac86ce9985f27a8c3e851b3b3
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
@@ -121,9 +121,9 @@
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
5535e31a712343a63f5d7d796cd874e563e5ac14
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
@@ -185,13 +185,13 @@
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
5535e31a712343a63f5d7d796cd874e563e5ac14
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
-
+
https://github.com/dotnet/source-build-externals
- dc30cd1ec22f198d658e011c14525d4d65873991
+ 16bcad1c13be082bd52ce178896d1119a73081a9
@@ -223,9 +223,9 @@
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
81cabf2857a01351e5ab578947c7403a5b128ad1
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 05e0f2d2c881def48961d3b83fa11ae84df8e534
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
@@ -275,17 +275,17 @@
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
81cabf2857a01351e5ab578947c7403a5b128ad1
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
@@ -316,31 +316,31 @@
Win-x64 is used here because we have picked an arbitrary runtime identifier to flow the version of the latest NETCore.App runtime.
All Runtime.$rid packages should have the same version.
-->
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
https://github.com/dotnet/xdt
9a1c3e1b7f0c8763d4c96e593961a61a72679a7b
-
+
https://github.com/dotnet/source-build-reference-packages
- d73fc552386797322e84fa9b2ef5eaa5369de83c
+ 6ae07097c0f03eb59c8a581faaedcc3f2e4cc42c
@@ -368,34 +368,34 @@
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 50c4cb9fc31c47f03eac865d7bc518af173b74b7
+ efd5742bb5dd1677fbbbeb277bcfb5c9025548e5
https://github.com/dotnet/winforms
abda8e3bfa78319363526b5a5f86863ec979940e
-
+
https://github.com/dotnet/arcade
- a319ada170a54ee87c7a81e3309948e3d3ea7aca
+ c7a36e53f91e41943746f97a8c183549364c5092
-
+
https://github.com/dotnet/arcade
- a319ada170a54ee87c7a81e3309948e3d3ea7aca
+ c7a36e53f91e41943746f97a8c183549364c5092
-
+
https://github.com/dotnet/arcade
- a319ada170a54ee87c7a81e3309948e3d3ea7aca
+ c7a36e53f91e41943746f97a8c183549364c5092
-
+
https://github.com/dotnet/arcade
- a319ada170a54ee87c7a81e3309948e3d3ea7aca
+ c7a36e53f91e41943746f97a8c183549364c5092
-
+
https://github.com/dotnet/arcade
- a319ada170a54ee87c7a81e3309948e3d3ea7aca
+ c7a36e53f91e41943746f97a8c183549364c5092
https://github.com/dotnet/extensions
diff --git a/eng/Versions.props b/eng/Versions.props
index 5d05883df000..be43c831fee0 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -8,10 +8,10 @@
8
0
- 15
+ 16
- false
+ true
7.1.2
7.*
8.0.2
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15-servicing.25164.13
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16-servicing.25215.6
8.0.0
8.0.1
8.0.0
@@ -93,7 +93,7 @@
8.0.0
8.0.0
8.0.0
- 8.0.15-servicing.25164.13
+ 8.0.16-servicing.25215.6
8.0.1
8.0.1
8.0.1
@@ -109,7 +109,7 @@
8.0.0
8.0.2
8.0.0
- 8.0.15-servicing.25164.13
+ 8.0.16-servicing.25215.6
8.0.1
8.0.1
8.0.2
@@ -117,7 +117,7 @@
8.0.0-rtm.23520.14
8.0.0
8.0.1
- 8.0.2
+ 8.0.3
8.0.1
8.0.0
8.0.1
@@ -129,9 +129,9 @@
8.0.0
8.0.0
8.0.0
- 8.0.15-servicing.25164.13
+ 8.0.16-servicing.25215.6
- 8.0.15-servicing.25164.13
+ 8.0.16-servicing.25215.6
8.0.0
8.0.1
@@ -143,14 +143,14 @@
8.1.0-preview.23604.1
8.1.0-preview.23604.1
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15
- 8.0.15
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16
+ 8.0.16
4.8.0-7.24574.2
4.8.0-7.24574.2
@@ -162,13 +162,13 @@
6.2.4
6.2.4
- 8.0.0-beta.25111.4
- 8.0.0-beta.25111.4
- 8.0.0-beta.25111.4
+ 8.0.0-beta.25208.7
+ 8.0.0-beta.25208.7
+ 8.0.0-beta.25208.7
- 8.0.0-alpha.1.25104.1
+ 8.0.0-alpha.1.25202.2
- 8.0.0-alpha.1.25081.5
+ 8.0.0-alpha.1.25210.2
2.0.0-beta-23228-03
diff --git a/eng/common/cross/toolchain.cmake b/eng/common/cross/toolchain.cmake
index dafabdcaef00..f93dc440df0e 100644
--- a/eng/common/cross/toolchain.cmake
+++ b/eng/common/cross/toolchain.cmake
@@ -280,6 +280,8 @@ elseif(TARGET_ARCH_NAME MATCHES "^(arm64|x64)$")
add_toolchain_linker_flag("-Wl,--rpath-link=${CROSS_ROOTFS}/usr/lib64")
add_toolchain_linker_flag("-Wl,--rpath-link=${TIZEN_TOOLCHAIN_PATH}")
endif()
+elseif(TARGET_ARCH_NAME STREQUAL "s390x")
+ add_toolchain_linker_flag("--target=${TOOLCHAIN}")
elseif(TARGET_ARCH_NAME STREQUAL "x86")
if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl)
add_toolchain_linker_flag("--target=${TOOLCHAIN}")
@@ -327,6 +329,8 @@ if(TARGET_ARCH_NAME MATCHES "^(arm|armel)$")
if(TARGET_ARCH_NAME STREQUAL "armel")
add_compile_options(-mfloat-abi=softfp)
endif()
+elseif(TARGET_ARCH_NAME STREQUAL "s390x")
+ add_compile_options("--target=${TOOLCHAIN}")
elseif(TARGET_ARCH_NAME STREQUAL "x86")
if(EXISTS ${CROSS_ROOTFS}/usr/lib/gcc/i586-alpine-linux-musl)
add_compile_options(--target=${TOOLCHAIN})
diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1
index 3e5c1c74a1c5..a0c7d792a76f 100644
--- a/eng/common/generate-sbom-prep.ps1
+++ b/eng/common/generate-sbom-prep.ps1
@@ -4,18 +4,26 @@ Param(
. $PSScriptRoot\pipeline-logging-functions.ps1
+# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly
+# with their own overwriting ours. So we create it as a sub directory of the requested manifest path.
+$ArtifactName = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM"
+$SafeArtifactName = $ArtifactName -replace '["/:<>\\|?@*"() ]', '_'
+$SbomGenerationDir = Join-Path $ManifestDirPath $SafeArtifactName
+
+Write-Host "Artifact name before : $ArtifactName"
+Write-Host "Artifact name after : $SafeArtifactName"
+
Write-Host "Creating dir $ManifestDirPath"
+
# create directory for sbom manifest to be placed
-if (!(Test-Path -path $ManifestDirPath))
+if (!(Test-Path -path $SbomGenerationDir))
{
- New-Item -ItemType Directory -path $ManifestDirPath
- Write-Host "Successfully created directory $ManifestDirPath"
+ New-Item -ItemType Directory -path $SbomGenerationDir
+ Write-Host "Successfully created directory $SbomGenerationDir"
}
else{
Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder."
}
Write-Host "Updating artifact name"
-$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_'
-Write-Host "Artifact name $artifact_name"
-Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name"
+Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$SafeArtifactName"
diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh
index d5c76dc827b4..bbb4922151e6 100644
--- a/eng/common/generate-sbom-prep.sh
+++ b/eng/common/generate-sbom-prep.sh
@@ -14,19 +14,24 @@ done
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
. $scriptroot/pipeline-logging-functions.sh
+# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts.
+artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM"
+safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}"
+
manifest_dir=$1
-if [ ! -d "$manifest_dir" ] ; then
- mkdir -p "$manifest_dir"
- echo "Sbom directory created." $manifest_dir
+# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly
+# with their own overwriting ours. So we create it as a sub directory of the requested manifest path.
+sbom_generation_dir="$manifest_dir/$safe_artifact_name"
+
+if [ ! -d "$sbom_generation_dir" ] ; then
+ mkdir -p "$sbom_generation_dir"
+ echo "Sbom directory created." $sbom_generation_dir
else
Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder."
fi
-artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM"
echo "Artifact name before : "$artifact_name
-# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts.
-safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}"
echo "Artifact name after : "$safe_artifact_name
export ARTIFACT_NAME=$safe_artifact_name
echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name"
diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml
index 1f035fee73f4..98ccbd7a9c16 100644
--- a/eng/common/templates-official/job/job.yml
+++ b/eng/common/templates-official/job/job.yml
@@ -38,6 +38,7 @@ parameters:
enableSbom: true
PackageVersion: 7.0.0
BuildDropPath: '$(Build.SourcesDirectory)/artifacts'
+ ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom
jobs:
- job: ${{ parameters.name }}
@@ -261,4 +262,4 @@ jobs:
targetPath: '$(Build.SourcesDirectory)\eng\common\BuildConfiguration'
artifactName: 'BuildConfiguration'
displayName: 'Publish build retry configuration'
- continueOnError: true
\ No newline at end of file
+ continueOnError: true
diff --git a/eng/common/templates-official/steps/generate-sbom.yml b/eng/common/templates-official/steps/generate-sbom.yml
index 1bf43bf807af..daf0957b68d7 100644
--- a/eng/common/templates-official/steps/generate-sbom.yml
+++ b/eng/common/templates-official/steps/generate-sbom.yml
@@ -35,7 +35,7 @@ steps:
PackageName: ${{ parameters.packageName }}
BuildDropPath: ${{ parameters.buildDropPath }}
PackageVersion: ${{ parameters.packageVersion }}
- ManifestDirPath: ${{ parameters.manifestDirPath }}
+ ManifestDirPath: ${{ parameters.manifestDirPath }}/$(ARTIFACT_NAME)
${{ if ne(parameters.IgnoreDirectories, '') }}:
AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}'
diff --git a/eng/common/templates-official/steps/send-to-helix.yml b/eng/common/templates-official/steps/send-to-helix.yml
index 3eb7e2d5f840..22f2501307d4 100644
--- a/eng/common/templates-official/steps/send-to-helix.yml
+++ b/eng/common/templates-official/steps/send-to-helix.yml
@@ -8,6 +8,7 @@ parameters:
HelixConfiguration: '' # optional -- additional property attached to a job
HelixPreCommands: '' # optional -- commands to run before Helix work item execution
HelixPostCommands: '' # optional -- commands to run after Helix work item execution
+ HelixProjectArguments: '' # optional -- arguments passed to the build command for helixpublish.proj
WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects
WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects
WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects
@@ -24,12 +25,12 @@ parameters:
IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set
HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net )
Creator: '' # optional -- if the build is external, use this to specify who is sending the job
- DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO
+ DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO
condition: succeeded() # optional -- condition for step to execute; defaults to succeeded()
continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false
steps:
- - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"'
+ - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"'
displayName: ${{ parameters.DisplayNamePrefix }} (Windows)
env:
BuildConfig: $(_BuildConfig)
@@ -59,7 +60,7 @@ steps:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT'))
continueOnError: ${{ parameters.continueOnError }}
- - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog
+ - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog
displayName: ${{ parameters.DisplayNamePrefix }} (Unix)
env:
BuildConfig: $(_BuildConfig)
diff --git a/eng/common/templates-official/steps/source-build.yml b/eng/common/templates-official/steps/source-build.yml
index 829f17c34d11..503164fa7f22 100644
--- a/eng/common/templates-official/steps/source-build.yml
+++ b/eng/common/templates-official/steps/source-build.yml
@@ -44,7 +44,7 @@ steps:
# in the default public locations.
internalRuntimeDownloadArgs=
if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then
- internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)'
+ internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)'
fi
buildConfig=Release
diff --git a/eng/common/templates/steps/send-to-helix.yml b/eng/common/templates/steps/send-to-helix.yml
index 3eb7e2d5f840..22f2501307d4 100644
--- a/eng/common/templates/steps/send-to-helix.yml
+++ b/eng/common/templates/steps/send-to-helix.yml
@@ -8,6 +8,7 @@ parameters:
HelixConfiguration: '' # optional -- additional property attached to a job
HelixPreCommands: '' # optional -- commands to run before Helix work item execution
HelixPostCommands: '' # optional -- commands to run after Helix work item execution
+ HelixProjectArguments: '' # optional -- arguments passed to the build command for helixpublish.proj
WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects
WorkItemCommand: '' # optional -- a command to execute on the payload; requires WorkItemDirectory; incompatible with XUnitProjects
WorkItemTimeout: '' # optional -- a timeout in TimeSpan.Parse-ready value (e.g. 00:02:00) for the work item command; requires WorkItemDirectory; incompatible with XUnitProjects
@@ -24,12 +25,12 @@ parameters:
IsExternal: false # [DEPRECATED] -- doesn't do anything, jobs are external if HelixAccessToken is empty and Creator is set
HelixBaseUri: 'https://helix.dot.net/' # optional -- sets the Helix API base URI (allows targeting https://helix.int-dot.net )
Creator: '' # optional -- if the build is external, use this to specify who is sending the job
- DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO
+ DisplayNamePrefix: 'Run Tests' # optional -- rename the beginning of the displayName of the steps in AzDO
condition: succeeded() # optional -- condition for step to execute; defaults to succeeded()
continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false
steps:
- - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"'
+ - powershell: 'powershell "$env:BUILD_SOURCESDIRECTORY\eng\common\msbuild.ps1 $env:BUILD_SOURCESDIRECTORY\eng\common\helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$env:BUILD_SOURCESDIRECTORY\artifacts\log\$env:BuildConfig\SendToHelix.binlog"'
displayName: ${{ parameters.DisplayNamePrefix }} (Windows)
env:
BuildConfig: $(_BuildConfig)
@@ -59,7 +60,7 @@ steps:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
condition: and(${{ parameters.condition }}, eq(variables['Agent.Os'], 'Windows_NT'))
continueOnError: ${{ parameters.continueOnError }}
- - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog
+ - script: $BUILD_SOURCESDIRECTORY/eng/common/msbuild.sh $BUILD_SOURCESDIRECTORY/eng/common/helixpublish.proj ${{ parameters.HelixProjectArguments }} /restore /p:TreatWarningsAsErrors=false /t:Test /bl:$BUILD_SOURCESDIRECTORY/artifacts/log/$BuildConfig/SendToHelix.binlog
displayName: ${{ parameters.DisplayNamePrefix }} (Unix)
env:
BuildConfig: $(_BuildConfig)
diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1
index a00577ed17aa..82b2798ba307 100644
--- a/eng/common/tools.ps1
+++ b/eng/common/tools.ps1
@@ -42,7 +42,7 @@
[bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true }
# Enable repos to use a particular version of the on-line dotnet-install scripts.
-# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1
+# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1
[string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' }
# True to use global NuGet cache instead of restoring packages to repository-local directory.
@@ -263,7 +263,7 @@ function GetDotNetInstallScript([string] $dotnetRoot) {
if (!(Test-Path $installScript)) {
Create-Directory $dotnetRoot
$ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit
- $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1"
+ $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1"
Retry({
Write-Host "GET $uri"
diff --git a/eng/common/tools.sh b/eng/common/tools.sh
index b9b329ce37ff..68db15430230 100755
--- a/eng/common/tools.sh
+++ b/eng/common/tools.sh
@@ -54,7 +54,7 @@ warn_as_error=${warn_as_error:-true}
use_installed_dotnet_cli=${use_installed_dotnet_cli:-true}
# Enable repos to use a particular version of the on-line dotnet-install scripts.
-# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh
+# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh
dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'}
# True to use global NuGet cache instead of restoring packages to repository-local directory.
@@ -297,7 +297,7 @@ function with_retries {
function GetDotNetInstallScript {
local root=$1
local install_script="$root/dotnet-install.sh"
- local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh"
+ local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh"
if [[ ! -a "$install_script" ]]; then
mkdir -p "$root"
diff --git a/eng/helix/helix.proj b/eng/helix/helix.proj
index 99254551279f..9535e87a6042 100644
--- a/eng/helix/helix.proj
+++ b/eng/helix/helix.proj
@@ -57,13 +57,13 @@
runtime
-
-
$([System.Environment]::GetEnvironmentVariable('DotNetBuildsInternalReadSasToken'))
-
$([System.Environment]::GetEnvironmentVariable('DotNetBuildsInternalReadSasToken'))
diff --git a/eng/scripts/install-nginx-linux.sh b/eng/scripts/install-nginx-linux.sh
index bbfb79c48203..f075a899d1cf 100755
--- a/eng/scripts/install-nginx-linux.sh
+++ b/eng/scripts/install-nginx-linux.sh
@@ -6,7 +6,7 @@ scriptroot="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
reporoot="$(dirname "$(dirname "$scriptroot")")"
nginxinstall="$reporoot/.tools/nginx"
-curl -sSL http://nginx.org/download/nginx-1.14.2.tar.gz --retry 5 | tar zxfv - -C /tmp && cd /tmp/nginx-1.14.2/
+curl -sSL http://nginx.org/download/nginx-1.26.3.tar.gz --retry 5 | tar zxfv - -C /tmp && cd /tmp/nginx-1.26.3/
./configure --prefix=$nginxinstall --with-http_ssl_module --without-http_rewrite_module
make
make install
diff --git a/global.json b/global.json
index 9d3930333424..a7cd171707cd 100644
--- a/global.json
+++ b/global.json
@@ -1,9 +1,9 @@
{
"sdk": {
- "version": "8.0.113"
+ "version": "8.0.115"
},
"tools": {
- "dotnet": "8.0.113",
+ "dotnet": "8.0.115",
"runtimes": {
"dotnet/x86": [
"$(MicrosoftNETCoreBrowserDebugHostTransportVersion)"
@@ -25,8 +25,8 @@
},
"msbuild-sdks": {
"Yarn.MSBuild": "1.22.19",
- "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.25111.4",
- "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.25111.4"
+ "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.25208.7",
+ "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.25208.7"
},
"native-tools": {
"jdk": "latest"
diff --git a/src/Framework/App.Runtime/src/Microsoft.AspNetCore.App.Runtime.csproj b/src/Framework/App.Runtime/src/Microsoft.AspNetCore.App.Runtime.csproj
index fadf9de6547c..1f8527329959 100644
--- a/src/Framework/App.Runtime/src/Microsoft.AspNetCore.App.Runtime.csproj
+++ b/src/Framework/App.Runtime/src/Microsoft.AspNetCore.App.Runtime.csproj
@@ -560,7 +560,7 @@ This package is an internal implementation of the .NET Core SDK and is not meant
-
+
diff --git a/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs b/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs
index 22ab9e8be5f9..4f0e347ae937 100644
--- a/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs
+++ b/src/Identity/EntityFrameworkCore/test/EF.Test/UserStoreTest.cs
@@ -144,6 +144,9 @@ await Assert.ThrowsAsync("user",
await Assert.ThrowsAsync("user", async () => await store.GetTwoFactorEnabledAsync(null));
await Assert.ThrowsAsync("user",
async () => await store.SetTwoFactorEnabledAsync(null, true));
+ await Assert.ThrowsAsync("user", async () => await store.RedeemCodeAsync(user: null, code: "fake", default));
+ await Assert.ThrowsAsync("code", async () => await store.RedeemCodeAsync(new IdentityUser("fake"), code: null, default));
+ await Assert.ThrowsAsync("code", async () => await store.RedeemCodeAsync(new IdentityUser("fake"), code: "", default));
await Assert.ThrowsAsync("user", async () => await store.GetAccessFailedCountAsync(null));
await Assert.ThrowsAsync("user", async () => await store.GetLockoutEnabledAsync(null));
await Assert.ThrowsAsync("user", async () => await store.SetLockoutEnabledAsync(null, false));
diff --git a/src/Identity/Extensions.Stores/src/UserStoreBase.cs b/src/Identity/Extensions.Stores/src/UserStoreBase.cs
index c45dd197e4a2..804ebcbad7dc 100644
--- a/src/Identity/Extensions.Stores/src/UserStoreBase.cs
+++ b/src/Identity/Extensions.Stores/src/UserStoreBase.cs
@@ -969,7 +969,7 @@ public virtual async Task RedeemCodeAsync(TUser user, string code, Cancell
ThrowIfDisposed();
ArgumentNullThrowHelper.ThrowIfNull(user);
- ArgumentNullThrowHelper.ThrowIfNull(code);
+ ArgumentNullThrowHelper.ThrowIfNullOrEmpty(code);
var mergedCodes = await GetTokenAsync(user, InternalLoginProvider, RecoveryCodeTokenName, cancellationToken).ConfigureAwait(false) ?? "";
var splitCodes = mergedCodes.Split(';');
diff --git a/src/Installers/Windows/WindowsHostingBundle/Product.targets b/src/Installers/Windows/WindowsHostingBundle/Product.targets
index 3e805f35bb3a..c1dc097445d4 100644
--- a/src/Installers/Windows/WindowsHostingBundle/Product.targets
+++ b/src/Installers/Windows/WindowsHostingBundle/Product.targets
@@ -83,7 +83,7 @@
-->
-
+
diff --git a/src/Servers/HttpSys/HttpSysServer.slnf b/src/Servers/HttpSys/HttpSysServer.slnf
index 4e0193ae8f4d..2b9ed68e48f0 100644
--- a/src/Servers/HttpSys/HttpSysServer.slnf
+++ b/src/Servers/HttpSys/HttpSysServer.slnf
@@ -37,6 +37,7 @@
"src\\Servers\\HttpSys\\samples\\QueueSharing\\QueueSharing.csproj",
"src\\Servers\\HttpSys\\samples\\SelfHostServer\\SelfHostServer.csproj",
"src\\Servers\\HttpSys\\samples\\TestClient\\TestClient.csproj",
+ "src\\Servers\\HttpSys\\samples\\TlsFeaturesObserve\\TlsFeaturesObserve.csproj",
"src\\Servers\\HttpSys\\src\\Microsoft.AspNetCore.Server.HttpSys.csproj",
"src\\Servers\\HttpSys\\test\\FunctionalTests\\Microsoft.AspNetCore.Server.HttpSys.FunctionalTests.csproj",
"src\\Servers\\HttpSys\\test\\NonHelixTests\\Microsoft.AspNetCore.Server.HttpSys.NonHelixTests.csproj",
@@ -53,4 +54,4 @@
"src\\WebEncoders\\src\\Microsoft.Extensions.WebEncoders.csproj"
]
}
-}
\ No newline at end of file
+}
diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/HttpSysConfigurator.cs b/src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/HttpSysConfigurator.cs
new file mode 100644
index 000000000000..3865ecd59451
--- /dev/null
+++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/HttpSysConfigurator.cs
@@ -0,0 +1,123 @@
+// 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;
+using System.Runtime.InteropServices;
+
+namespace TlsFeaturesObserve.HttpSys;
+
+internal static class HttpSysConfigurator
+{
+ const uint HTTP_INITIALIZE_CONFIG = 0x00000002;
+ const uint ERROR_ALREADY_EXISTS = 183;
+
+ static readonly HTTPAPI_VERSION HttpApiVersion = new HTTPAPI_VERSION(1, 0);
+
+ internal static void ConfigureCacheTlsClientHello()
+ {
+ // Arbitrarily chosen port, but must match the port used in the web server. Via UrlPrefixes or launchsettings.
+ var ipPort = new IPEndPoint(new IPAddress([0, 0, 0, 0]), 6000);
+ var certThumbprint = "" /* your cert thumbprint here */;
+ var appId = Guid.NewGuid();
+ var sslCertStoreName = "My";
+
+ CallHttpApi(() => SetConfiguration(ipPort, certThumbprint, appId, sslCertStoreName));
+ }
+
+ static void SetConfiguration(IPEndPoint ipPort, string certThumbprint, Guid appId, string sslCertStoreName)
+ {
+ var sockAddrHandle = CreateSockaddrStructure(ipPort);
+ var pIpPort = sockAddrHandle.AddrOfPinnedObject();
+ var httpServiceConfigSslKey = new HTTP_SERVICE_CONFIG_SSL_KEY(pIpPort);
+
+ var hash = GetHash(certThumbprint);
+ var handleHash = GCHandle.Alloc(hash, GCHandleType.Pinned);
+ var configSslParam = new HTTP_SERVICE_CONFIG_SSL_PARAM
+ {
+ AppId = appId,
+ DefaultFlags = 0x00008000 /* HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO */,
+ DefaultRevocationFreshnessTime = 0,
+ DefaultRevocationUrlRetrievalTimeout = 15,
+ pSslCertStoreName = sslCertStoreName,
+ pSslHash = handleHash.AddrOfPinnedObject(),
+ SslHashLength = hash.Length,
+ pDefaultSslCtlIdentifier = null,
+ pDefaultSslCtlStoreName = sslCertStoreName
+ };
+
+ var configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET
+ {
+ ParamDesc = configSslParam,
+ KeyDesc = httpServiceConfigSslKey
+ };
+
+ var pInputConfigInfo = Marshal.AllocCoTaskMem(
+ Marshal.SizeOf(typeof(HTTP_SERVICE_CONFIG_SSL_SET)));
+ Marshal.StructureToPtr(configSslSet, pInputConfigInfo, false);
+
+ var status = HttpSetServiceConfiguration(nint.Zero,
+ HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
+ pInputConfigInfo,
+ Marshal.SizeOf(configSslSet),
+ nint.Zero);
+
+ if (status == ERROR_ALREADY_EXISTS || status == 0) // already present or success
+ {
+ Console.WriteLine($"HttpServiceConfiguration is correct");
+ }
+ else
+ {
+ Console.WriteLine("Failed to HttpSetServiceConfiguration: " + status);
+ }
+ }
+
+ static byte[] GetHash(string thumbprint)
+ {
+ var length = thumbprint.Length;
+ var bytes = new byte[length / 2];
+ for (var i = 0; i < length; i += 2)
+ {
+ bytes[i / 2] = Convert.ToByte(thumbprint.Substring(i, 2), 16);
+ }
+
+ return bytes;
+ }
+
+ static GCHandle CreateSockaddrStructure(IPEndPoint ipEndPoint)
+ {
+ var socketAddress = ipEndPoint.Serialize();
+
+ // use an array of bytes instead of the sockaddr structure
+ var sockAddrStructureBytes = new byte[socketAddress.Size];
+ var sockAddrHandle = GCHandle.Alloc(sockAddrStructureBytes, GCHandleType.Pinned);
+ for (var i = 0; i < socketAddress.Size; ++i)
+ {
+ sockAddrStructureBytes[i] = socketAddress[i];
+ }
+ return sockAddrHandle;
+ }
+
+ static void CallHttpApi(Action body)
+ {
+ const uint flags = HTTP_INITIALIZE_CONFIG;
+ var retVal = HttpInitialize(HttpApiVersion, flags, IntPtr.Zero);
+ body();
+ }
+
+// disabled warning since it is just a sample
+#pragma warning disable SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
+ [DllImport("httpapi.dll", SetLastError = true)]
+ private static extern uint HttpInitialize(
+ HTTPAPI_VERSION version,
+ uint flags,
+ IntPtr pReserved);
+
+ [DllImport("httpapi.dll", SetLastError = true)]
+ public static extern uint HttpSetServiceConfiguration(
+ nint serviceIntPtr,
+ HTTP_SERVICE_CONFIG_ID configId,
+ nint pConfigInformation,
+ int configInformationLength,
+ nint pOverlapped);
+#pragma warning restore SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
+}
diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/Native.cs b/src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/Native.cs
new file mode 100644
index 000000000000..b939163d2252
--- /dev/null
+++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/HttpSys/Native.cs
@@ -0,0 +1,97 @@
+// 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.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace TlsFeaturesObserve.HttpSys;
+
+// Http.Sys types from https://learn.microsoft.com/windows/win32/api/http/
+
+[StructLayout(LayoutKind.Sequential, Pack = 2)]
+public struct HTTPAPI_VERSION
+{
+ public ushort HttpApiMajorVersion;
+ public ushort HttpApiMinorVersion;
+
+ public HTTPAPI_VERSION(ushort majorVersion, ushort minorVersion)
+ {
+ HttpApiMajorVersion = majorVersion;
+ HttpApiMinorVersion = minorVersion;
+ }
+}
+
+public enum HTTP_SERVICE_CONFIG_ID
+{
+ HttpServiceConfigIPListenList = 0,
+ HttpServiceConfigSSLCertInfo,
+ HttpServiceConfigUrlAclInfo,
+ HttpServiceConfigMax
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct HTTP_SERVICE_CONFIG_SSL_SET
+{
+ public HTTP_SERVICE_CONFIG_SSL_KEY KeyDesc;
+ public HTTP_SERVICE_CONFIG_SSL_PARAM ParamDesc;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct HTTP_SERVICE_CONFIG_SSL_KEY
+{
+ public IntPtr pIpPort;
+
+ public HTTP_SERVICE_CONFIG_SSL_KEY(IntPtr pIpPort)
+ {
+ this.pIpPort = pIpPort;
+ }
+}
+
+[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+public struct HTTP_SERVICE_CONFIG_SSL_PARAM
+{
+ public int SslHashLength;
+ public IntPtr pSslHash;
+ public Guid AppId;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string pSslCertStoreName;
+ public CertCheckModes DefaultCertCheckMode;
+ public int DefaultRevocationFreshnessTime;
+ public int DefaultRevocationUrlRetrievalTimeout;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string pDefaultSslCtlIdentifier;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string pDefaultSslCtlStoreName;
+ public uint DefaultFlags; // HTTP_SERVICE_CONFIG_SSL_FLAG
+}
+
+[Flags]
+public enum CertCheckModes : uint
+{
+ ///
+ /// Enables the client certificate revocation check.
+ ///
+ None = 0,
+
+ ///
+ /// Client certificate is not to be verified for revocation.
+ ///
+ DoNotVerifyCertificateRevocation = 1,
+
+ ///
+ /// Only cached certificate is to be used the revocation check.
+ ///
+ VerifyRevocationWithCachedCertificateOnly = 2,
+
+ ///
+ /// The RevocationFreshnessTime setting is enabled.
+ ///
+ EnableRevocationFreshnessTime = 4,
+
+ ///
+ /// No usage check is to be performed.
+ ///
+ NoUsageCheck = 0x10000
+}
diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
new file mode 100644
index 000000000000..9551965ac398
--- /dev/null
+++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Server.HttpSys;
+using Microsoft.Extensions.Hosting;
+using TlsFeatureObserve;
+using TlsFeaturesObserve.HttpSys;
+
+HttpSysConfigurator.ConfigureCacheTlsClientHello();
+CreateHostBuilder(args).Build().Run();
+
+static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder.UseStartup()
+ .UseHttpSys(options =>
+ {
+ // If you want to use https locally: https://stackoverflow.com/a/51841893
+ options.UrlPrefixes.Add("https://*:6000"); // HTTPS
+
+ options.Authentication.Schemes = AuthenticationSchemes.None;
+ options.Authentication.AllowAnonymous = true;
+
+ var property = typeof(HttpSysOptions).GetProperty("TlsClientHelloBytesCallback", BindingFlags.NonPublic | BindingFlags.Instance);
+ var delegateType = property.PropertyType; // Get the exact delegate type
+
+ // Create a delegate of the correct type
+ var callbackDelegate = Delegate.CreateDelegate(delegateType, typeof(Holder).GetMethod(nameof(Holder.ProcessTlsClientHello), BindingFlags.Static | BindingFlags.Public));
+
+ property?.SetValue(options, callbackDelegate);
+ });
+ });
+
+public static class Holder
+{
+ public static void ProcessTlsClientHello(IFeatureCollection features, ReadOnlySpan tlsClientHelloBytes)
+ {
+ var httpConnectionFeature = features.Get();
+
+ var myTlsFeature = new MyTlsFeature(
+ connectionId: httpConnectionFeature.ConnectionId,
+ tlsClientHelloLength: tlsClientHelloBytes.Length);
+
+ features.Set(myTlsFeature);
+ }
+}
+
+public interface IMyTlsFeature
+{
+ string ConnectionId { get; }
+ int TlsClientHelloLength { get; }
+}
+
+public class MyTlsFeature : IMyTlsFeature
+{
+ public string ConnectionId { get; }
+ public int TlsClientHelloLength { get; }
+
+ public MyTlsFeature(string connectionId, int tlsClientHelloLength)
+ {
+ ConnectionId = connectionId;
+ TlsClientHelloLength = tlsClientHelloLength;
+ }
+}
diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/Properties/launchSettings.json b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Properties/launchSettings.json
new file mode 100644
index 000000000000..c9d6b5efcb3c
--- /dev/null
+++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "TlsFeaturesObserve": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:5000",
+ "nativeDebugging": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/Startup.cs b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Startup.cs
new file mode 100644
index 000000000000..8ba6d27aef98
--- /dev/null
+++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/Startup.cs
@@ -0,0 +1,28 @@
+// 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 Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Connections.Features;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Server.HttpSys;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace TlsFeatureObserve;
+
+public class Startup
+{
+ public void Configure(IApplicationBuilder app)
+ {
+ app.Run(async (HttpContext context) =>
+ {
+ context.Response.ContentType = "text/plain";
+
+ var tlsFeature = context.Features.Get();
+ await context.Response.WriteAsync("TlsClientHello data: " + $"connectionId={tlsFeature?.ConnectionId}; length={tlsFeature?.TlsClientHelloLength}");
+ });
+ }
+}
diff --git a/src/Servers/HttpSys/samples/TlsFeaturesObserve/TlsFeaturesObserve.csproj b/src/Servers/HttpSys/samples/TlsFeaturesObserve/TlsFeaturesObserve.csproj
new file mode 100644
index 000000000000..f65f8a98a72a
--- /dev/null
+++ b/src/Servers/HttpSys/samples/TlsFeaturesObserve/TlsFeaturesObserve.csproj
@@ -0,0 +1,14 @@
+
+
+
+ $(DefaultNetCoreTargetFramework)
+ Exe
+ true
+
+
+
+
+
+
+
+
diff --git a/src/Servers/HttpSys/src/HttpSysListener.cs b/src/Servers/HttpSys/src/HttpSysListener.cs
index 2b7924491d32..7fecff3c848d 100644
--- a/src/Servers/HttpSys/src/HttpSysListener.cs
+++ b/src/Servers/HttpSys/src/HttpSysListener.cs
@@ -5,6 +5,7 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpSys.Internal;
+using Microsoft.AspNetCore.Server.HttpSys.RequestProcessing;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Server.HttpSys;
@@ -37,6 +38,7 @@ internal sealed partial class HttpSysListener : IDisposable
private readonly UrlGroup _urlGroup;
private readonly RequestQueue _requestQueue;
private readonly DisconnectListener _disconnectListener;
+ private readonly TlsListener? _tlsListener;
private readonly object _internalLock;
@@ -69,12 +71,14 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
try
{
_serverSession = new ServerSession();
-
_requestQueue = new RequestQueue(options.RequestQueueName, options.RequestQueueMode, Logger);
-
_urlGroup = new UrlGroup(_serverSession, _requestQueue, Logger);
_disconnectListener = new DisconnectListener(_requestQueue, Logger);
+ if (options.TlsClientHelloBytesCallback is not null)
+ {
+ _tlsListener = new TlsListener(Logger, options.TlsClientHelloBytesCallback);
+ }
}
catch (Exception exception)
{
@@ -82,6 +86,7 @@ public HttpSysListener(HttpSysOptions options, ILoggerFactory loggerFactory)
_requestQueue?.Dispose();
_urlGroup?.Dispose();
_serverSession?.Dispose();
+ _tlsListener?.Dispose();
Log.HttpSysListenerCtorError(Logger, exception);
throw;
}
@@ -96,20 +101,10 @@ internal enum State
internal ILogger Logger { get; private set; }
- internal UrlGroup UrlGroup
- {
- get { return _urlGroup; }
- }
-
- internal RequestQueue RequestQueue
- {
- get { return _requestQueue; }
- }
-
- internal DisconnectListener DisconnectListener
- {
- get { return _disconnectListener; }
- }
+ internal UrlGroup UrlGroup => _urlGroup;
+ internal RequestQueue RequestQueue => _requestQueue;
+ internal TlsListener? TlsListener => _tlsListener;
+ internal DisconnectListener DisconnectListener => _disconnectListener;
public HttpSysOptions Options { get; }
@@ -262,6 +257,7 @@ private void DisposeInternal()
Debug.Assert(!_serverSession.Id.IsInvalid, "ServerSessionHandle is invalid in CloseV2Config");
_serverSession.Dispose();
+ _tlsListener?.Dispose();
}
///
diff --git a/src/Servers/HttpSys/src/HttpSysOptions.cs b/src/Servers/HttpSys/src/HttpSysOptions.cs
index 87fb1ba6d176..dfed2d7beced 100644
--- a/src/Servers/HttpSys/src/HttpSysOptions.cs
+++ b/src/Servers/HttpSys/src/HttpSysOptions.cs
@@ -242,10 +242,23 @@ public Http503VerbosityLevel Http503Verbosity
/// Configures request headers to use encoding.
///
///
- /// Defaults to `false`, in which case will be used. />.
+ /// Defaults to false, in which case will be used. />.
///
public bool UseLatin1RequestHeaders { get; set; }
+ ///
+ /// A callback to be invoked to get the TLS client hello bytes.
+ /// Null by default.
+ ///
+ ///
+ /// Works only if HTTP_SERVICE_CONFIG_SSL_FLAG_ENABLE_CACHE_CLIENT_HELLO flag is set on http.sys service configuration.
+ /// See
+ /// and
+ ///
+ internal TlsClientHelloCallback? TlsClientHelloBytesCallback { get; set; }
+
+ internal delegate void TlsClientHelloCallback(IFeatureCollection features, ReadOnlySpan clientHelloBytes);
+
// Not called when attaching to an existing queue.
internal void Apply(UrlGroup urlGroup, RequestQueue? requestQueue)
{
diff --git a/src/Servers/HttpSys/src/LoggerEventIds.cs b/src/Servers/HttpSys/src/LoggerEventIds.cs
index 5bc0b6b65ed6..e6d745f506be 100644
--- a/src/Servers/HttpSys/src/LoggerEventIds.cs
+++ b/src/Servers/HttpSys/src/LoggerEventIds.cs
@@ -59,4 +59,5 @@ internal static class LoggerEventIds
public const int AcceptCancelExpectationMismatch = 52;
public const int AcceptObserveExpectationMismatch = 53;
public const int RequestParsingError = 54;
+ public const int TlsListenerError = 55;
}
diff --git a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
index 575ebc259d25..efb42c87c5b5 100644
--- a/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
+++ b/src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
@@ -122,13 +122,30 @@ internal static HTTP_API_VERSION ApiVersion
}
internal static SafeLibraryHandle? HttpApiModule { get; private set; }
- internal static HttpGetRequestPropertyInvoker? HttpGetRequestProperty { get; private set; }
- internal static HttpSetRequestPropertyInvoker? HttpSetRequestProperty { get; private set; }
- [MemberNotNullWhen(true, nameof(HttpSetRequestProperty))]
+ private static HttpGetRequestPropertyInvoker? HttpGetRequestInvoker { get; set; }
+ private static HttpSetRequestPropertyInvoker? HttpSetRequestInvoker { get; set; }
+
+ internal static bool HttpGetRequestPropertySupported => HttpGetRequestInvoker is not null;
+ internal static bool HttpSetRequestPropertySupported => HttpSetRequestInvoker is not null;
+
+ internal static unsafe uint HttpGetRequestProperty(SafeHandle requestQueueHandle, ulong requestId, HTTP_REQUEST_PROPERTY propertyId,
+ void* qualifier, uint qualifierSize, void* output, uint outputSize, uint* bytesReturned, IntPtr overlapped)
+ {
+ return HttpGetRequestInvoker!(requestQueueHandle, requestId, propertyId, qualifier, qualifierSize, output, outputSize, bytesReturned, overlapped);
+ }
+
+ internal static unsafe uint HttpSetRequestProperty(SafeHandle requestQueueHandle, ulong requestId, HTTP_REQUEST_PROPERTY propertyId,
+ void* input, uint inputSize, IntPtr overlapped)
+ {
+ return HttpSetRequestInvoker!(requestQueueHandle, requestId, propertyId, input, inputSize, overlapped);
+ }
+
+ [MemberNotNullWhen(true, nameof(HttpSetRequestInvoker))]
internal static bool SupportsTrailers { get; private set; }
- [MemberNotNullWhen(true, nameof(HttpSetRequestProperty))]
+ [MemberNotNullWhen(true, nameof(HttpSetRequestInvoker))]
internal static bool SupportsReset { get; private set; }
internal static bool SupportsDelegation { get; private set; }
+ internal static bool SupportsClientHello { get; private set; }
static HttpApi()
{
@@ -147,11 +164,12 @@ private static void InitHttpApi(ushort majorVersion, ushort minorVersion)
if (supported)
{
HttpApiModule = SafeLibraryHandle.Open(HTTPAPI);
- HttpGetRequestProperty = HttpApiModule.GetProcAddress("HttpQueryRequestProperty", throwIfNotFound: false);
- HttpSetRequestProperty = HttpApiModule.GetProcAddress("HttpSetRequestProperty", throwIfNotFound: false);
- SupportsReset = HttpSetRequestProperty != null;
+ HttpGetRequestInvoker = HttpApiModule.GetProcAddress("HttpQueryRequestProperty", throwIfNotFound: false);
+ HttpSetRequestInvoker = HttpApiModule.GetProcAddress("HttpSetRequestProperty", throwIfNotFound: false);
+ SupportsReset = HttpSetRequestPropertySupported;
SupportsTrailers = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureResponseTrailers);
SupportsDelegation = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureDelegateEx);
+ SupportsClientHello = IsFeatureSupported((HTTP_FEATURE_ID)11 /* HTTP_FEATURE_ID.HttpFeatureCacheTlsClientHello */) && HttpGetRequestPropertySupported;
}
}
diff --git a/src/Servers/HttpSys/src/RequestProcessing/Request.cs b/src/Servers/HttpSys/src/RequestProcessing/Request.cs
index 6029f8269f53..9aa93adb508d 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/Request.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/Request.cs
@@ -9,10 +9,12 @@
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
+using static Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions;
namespace Microsoft.AspNetCore.Server.HttpSys;
@@ -362,6 +364,9 @@ private void GetTlsHandshakeResults()
SniHostName = sni.Hostname;
}
+ internal bool GetAndInvokeTlsClientHelloCallback(IFeatureCollection features, TlsClientHelloCallback tlsClientHelloBytesCallback)
+ => RequestContext.GetAndInvokeTlsClientHelloMessageBytesCallback(features, tlsClientHelloBytesCallback);
+
public X509Certificate2? ClientCertificate
{
get
diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs
index 41b1cf480d5a..d7766698bc41 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.Log.cs
@@ -20,5 +20,8 @@ private static partial class Log
[LoggerMessage(LoggerEventIds.RequestParsingError, LogLevel.Debug, "Failed to parse request.", EventName = "RequestParsingError")]
public static partial void RequestParsingError(ILogger logger, Exception exception);
+
+ [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);
}
}
diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
index e9f277b6a990..5c45db813880 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContext.cs
@@ -1,13 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Principal;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.HttpSys.Internal;
using Microsoft.Extensions.Logging;
+using static Microsoft.AspNetCore.HttpSys.Internal.HttpApiTypes;
+using static Microsoft.AspNetCore.HttpSys.Internal.UnsafeNclNativeMethods;
+using static Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions;
namespace Microsoft.AspNetCore.Server.HttpSys;
@@ -234,30 +239,118 @@ internal void ForceCancelRequest()
}
}
- internal unsafe HttpApiTypes.HTTP_REQUEST_PROPERTY_SNI GetClientSni()
+ ///
+ /// Attempts to get the client hello message bytes from HTTP.sys and calls the user provided callback.
+ /// If not successful, will return false.
+ ///
+ internal unsafe bool GetAndInvokeTlsClientHelloMessageBytesCallback(IFeatureCollection features, TlsClientHelloCallback tlsClientHelloBytesCallback)
{
- if (HttpApi.HttpGetRequestProperty != null)
+ if (!HttpApi.SupportsClientHello)
+ {
+ // not supported, so we just return and don't invoke the callback
+ return false;
+ }
+
+ uint bytesReturnedValue = 0;
+ uint* bytesReturned = &bytesReturnedValue;
+ uint statusCode;
+
+ var requestId = PinsReleased ? Request.RequestId : RequestId;
+
+ // we will try with some "random" buffer size
+ var buffer = ArrayPool.Shared.Rent(512);
+ try
{
- var buffer = new byte[HttpApiTypes.SniPropertySizeInBytes];
fixed (byte* pBuffer = buffer)
{
- var statusCode = HttpApi.HttpGetRequestProperty(
- Server.RequestQueue.Handle,
- RequestId,
- HttpApiTypes.HTTP_REQUEST_PROPERTY.HttpRequestPropertySni,
+ statusCode = HttpApi.HttpGetRequestProperty(
+ requestQueueHandle: Server.RequestQueue.Handle,
+ requestId,
+ propertyId: (HTTP_REQUEST_PROPERTY)11 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsClientHello */,
qualifier: null,
qualifierSize: 0,
- (void*)pBuffer,
- (uint)buffer.Length,
- bytesReturned: null,
- IntPtr.Zero);
+ output: pBuffer,
+ outputSize: (uint)buffer.Length,
+ bytesReturned: bytesReturned,
+ overlapped: IntPtr.Zero);
- if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
+ if (statusCode is ErrorCodes.ERROR_SUCCESS)
{
- return Marshal.PtrToStructure((IntPtr)pBuffer);
+ tlsClientHelloBytesCallback(features, buffer.AsSpan(0, (int)bytesReturnedValue));
+ return true;
}
}
}
+ finally
+ {
+ ArrayPool.Shared.Return(buffer, clearArray: true);
+ }
+
+ // if buffer supplied is too small, `bytesReturned` will have proper size
+ // so retry should succeed with the properly allocated buffer
+ if (statusCode is ErrorCodes.ERROR_MORE_DATA or ErrorCodes.ERROR_INSUFFICIENT_BUFFER)
+ {
+ try
+ {
+ var correctSize = (int)bytesReturnedValue;
+ buffer = ArrayPool.Shared.Rent(correctSize);
+
+ fixed (byte* pBuffer = buffer)
+ {
+ statusCode = HttpApi.HttpGetRequestProperty(
+ requestQueueHandle: Server.RequestQueue.Handle,
+ requestId,
+ propertyId: (HTTP_REQUEST_PROPERTY)11 /* HTTP_REQUEST_PROPERTY.HttpRequestPropertyTlsClientHello */,
+ qualifier: null,
+ qualifierSize: 0,
+ output: pBuffer,
+ outputSize: (uint)buffer.Length,
+ bytesReturned: bytesReturned,
+ overlapped: IntPtr.Zero);
+
+ if (statusCode is ErrorCodes.ERROR_SUCCESS)
+ {
+ tlsClientHelloBytesCallback(features, buffer.AsSpan(0, correctSize));
+ return true;
+ }
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer, clearArray: true);
+ }
+ }
+
+ Log.TlsClientHelloRetrieveError(Logger, requestId, statusCode);
+ return false;
+ }
+
+ internal unsafe HTTP_REQUEST_PROPERTY_SNI GetClientSni()
+ {
+ if (!HttpApi.HttpGetRequestPropertySupported)
+ {
+ return default;
+ }
+
+ var buffer = new byte[HttpApiTypes.SniPropertySizeInBytes];
+ fixed (byte* pBuffer = buffer)
+ {
+ var statusCode = HttpApi.HttpGetRequestProperty(
+ Server.RequestQueue.Handle,
+ RequestId,
+ HTTP_REQUEST_PROPERTY.HttpRequestPropertySni,
+ qualifier: null,
+ qualifierSize: 0,
+ pBuffer,
+ (uint)buffer.Length,
+ bytesReturned: null,
+ IntPtr.Zero);
+
+ if (statusCode == ErrorCodes.ERROR_SUCCESS)
+ {
+ return Marshal.PtrToStructure((IntPtr)pBuffer);
+ }
+ }
return default;
}
diff --git a/src/Servers/HttpSys/src/RequestProcessing/RequestContextOfT.cs b/src/Servers/HttpSys/src/RequestProcessing/RequestContextOfT.cs
index 2a1d06a06d26..399f1292d60d 100644
--- a/src/Servers/HttpSys/src/RequestProcessing/RequestContextOfT.cs
+++ b/src/Servers/HttpSys/src/RequestProcessing/RequestContextOfT.cs
@@ -48,6 +48,12 @@ public override async Task ExecuteAsync()
context = application.CreateContext(Features);
try
{
+ if (Server.Options.TlsClientHelloBytesCallback is not null && Server.TlsListener is not null
+ && Request.IsHttps)
+ {
+ Server.TlsListener.InvokeTlsClientHelloCallback(Request.RawConnectionId, Features, Request.GetAndInvokeTlsClientHelloCallback);
+ }
+
await application.ProcessRequestAsync(context);
await CompleteAsync();
}
diff --git a/src/Servers/HttpSys/src/RequestProcessing/TlsListener.Log.cs b/src/Servers/HttpSys/src/RequestProcessing/TlsListener.Log.cs
new file mode 100644
index 000000000000..20ffe5c74b6f
--- /dev/null
+++ b/src/Servers/HttpSys/src/RequestProcessing/TlsListener.Log.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.HttpSys.RequestProcessing;
+
+internal sealed partial class TlsListener : IDisposable
+{
+ private static partial class Log
+ {
+ [LoggerMessage(LoggerEventIds.TlsListenerError, LogLevel.Error, "Error during closed connection cleanup.", EventName = "TlsListenerCleanupClosedConnectionError")]
+ public static partial void CleanupClosedConnectionError(ILogger logger, Exception exception);
+ }
+}
diff --git a/src/Servers/HttpSys/src/RequestProcessing/TlsListener.cs b/src/Servers/HttpSys/src/RequestProcessing/TlsListener.cs
new file mode 100644
index 000000000000..731ecea05f6e
--- /dev/null
+++ b/src/Servers/HttpSys/src/RequestProcessing/TlsListener.cs
@@ -0,0 +1,144 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Concurrent;
+using System.Collections.ObjectModel;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+using static Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions;
+
+namespace Microsoft.AspNetCore.Server.HttpSys.RequestProcessing;
+
+internal sealed partial class TlsListener : IDisposable
+{
+ private readonly ConcurrentDictionary _connectionTimestamps = new();
+ private readonly TlsClientHelloCallback _tlsClientHelloBytesCallback;
+ private readonly ILogger _logger;
+
+ private readonly PeriodicTimer _cleanupTimer;
+ private readonly Task _cleanupTask;
+ private readonly TimeProvider _timeProvider;
+
+ private readonly TimeSpan ConnectionIdleTime = TimeSpan.FromMinutes(5);
+ private readonly TimeSpan CleanupDelay = TimeSpan.FromSeconds(10);
+ internal readonly int CacheSizeLimit = 1_000_000;
+
+ // Internal for testing purposes
+ internal ReadOnlyDictionary ConnectionTimeStamps => _connectionTimestamps.AsReadOnly();
+
+ internal TlsListener(ILogger logger, TlsClientHelloCallback tlsClientHelloBytesCallback, TimeProvider? timeProvider = null)
+ {
+ if (AppContext.GetData("Microsoft.AspNetCore.Server.HttpSys.TlsListener.CacheSizeLimit") is int limit)
+ {
+ CacheSizeLimit = limit;
+ }
+
+ if (AppContext.GetData("Microsoft.AspNetCore.Server.HttpSys.TlsListener.ConnectionIdleTime") is int idleTime)
+ {
+ ConnectionIdleTime = TimeSpan.FromSeconds(idleTime);
+ }
+
+ if (AppContext.GetData("Microsoft.AspNetCore.Server.HttpSys.TlsListener.CleanupDelay") is int cleanupDelay)
+ {
+ CleanupDelay = TimeSpan.FromSeconds(cleanupDelay);
+ }
+
+ _logger = logger;
+ _tlsClientHelloBytesCallback = tlsClientHelloBytesCallback;
+
+ _timeProvider = timeProvider ?? TimeProvider.System;
+ _cleanupTimer = new PeriodicTimer(CleanupDelay, _timeProvider);
+ _cleanupTask = CleanupLoopAsync();
+ }
+
+ // Method looks weird because we want it to be testable by not directly requiring a Request object
+ internal void InvokeTlsClientHelloCallback(ulong connectionId, IFeatureCollection features,
+ Func invokeTlsClientHelloCallback)
+ {
+ if (!_connectionTimestamps.TryAdd(connectionId, _timeProvider.GetUtcNow()))
+ {
+ // update TTL
+ _connectionTimestamps[connectionId] = _timeProvider.GetUtcNow();
+ return;
+ }
+
+ _ = invokeTlsClientHelloCallback(features, _tlsClientHelloBytesCallback);
+ }
+
+ internal async Task CleanupLoopAsync()
+ {
+ while (await _cleanupTimer.WaitForNextTickAsync())
+ {
+ try
+ {
+ var now = _timeProvider.GetUtcNow();
+
+ // Remove idle connections
+ foreach (var kvp in _connectionTimestamps)
+ {
+ if (now - kvp.Value >= ConnectionIdleTime)
+ {
+ _connectionTimestamps.TryRemove(kvp.Key, out _);
+ }
+ }
+
+ // Evict oldest items if above CacheSizeLimit
+ var currentCount = _connectionTimestamps.Count;
+ if (currentCount > CacheSizeLimit)
+ {
+ var excessCount = currentCount - CacheSizeLimit;
+
+ // Find the oldest items in a single pass
+ var oldestTimestamps = new SortedSet>(TimeComparer.Instance);
+
+ foreach (var kvp in _connectionTimestamps)
+ {
+ if (oldestTimestamps.Count < excessCount)
+ {
+ oldestTimestamps.Add(new KeyValuePair(kvp.Key, kvp.Value));
+ }
+ else if (kvp.Value < oldestTimestamps.Max.Value)
+ {
+ oldestTimestamps.Remove(oldestTimestamps.Max);
+ oldestTimestamps.Add(new KeyValuePair(kvp.Key, kvp.Value));
+ }
+ }
+
+ // Remove the oldest keys
+ foreach (var item in oldestTimestamps)
+ {
+ _connectionTimestamps.TryRemove(item.Key, out _);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.CleanupClosedConnectionError(_logger, ex);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _cleanupTimer.Dispose();
+ _cleanupTask.Wait();
+ }
+
+ private sealed class TimeComparer : IComparer>
+ {
+ public static TimeComparer Instance { get; } = new TimeComparer();
+
+ public int Compare(KeyValuePair x, KeyValuePair y)
+ {
+ // Compare timestamps first
+ int timestampComparison = x.Value.CompareTo(y.Value);
+ if (timestampComparison != 0)
+ {
+ return timestampComparison;
+ }
+
+ // Use the key as a tiebreaker to ensure uniqueness
+ return x.Key.CompareTo(y.Key);
+ }
+ }
+}
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 08276e6a23fd..56f300b89198 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
@@ -32,6 +32,7 @@
+
diff --git a/src/Servers/HttpSys/test/FunctionalTests/TlsListenerTests.cs b/src/Servers/HttpSys/test/FunctionalTests/TlsListenerTests.cs
new file mode 100644
index 000000000000..00c3ac024d32
--- /dev/null
+++ b/src/Servers/HttpSys/test/FunctionalTests/TlsListenerTests.cs
@@ -0,0 +1,141 @@
+// 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.Http.Features;
+using Microsoft.AspNetCore.Server.HttpSys.RequestProcessing;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Time.Testing;
+using Moq;
+using static Microsoft.AspNetCore.Server.HttpSys.HttpSysOptions;
+
+namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests;
+
+public class TlsListenerTests
+{
+ [Fact]
+ public void AddsAndUpdatesConnectionTimestamps()
+ {
+ // Arrange
+ var logger = Mock.Of();
+ var timeProvider = new FakeTimeProvider();
+ var callbackInvoked = false;
+ var tlsListener = new TlsListener(logger, (_, __) => { callbackInvoked = true; }, timeProvider);
+
+ var features = Mock.Of();
+
+ // Act
+ tlsListener.InvokeTlsClientHelloCallback(connectionId: 1UL, features,
+ invokeTlsClientHelloCallback: (f, cb) => { cb(f, ReadOnlySpan.Empty); return true; });
+
+ var originalTime = timeProvider.GetUtcNow();
+
+ // Assert
+ Assert.True(callbackInvoked);
+ Assert.Equal(originalTime, Assert.Single(tlsListener.ConnectionTimeStamps).Value);
+
+ timeProvider.Advance(TimeSpan.FromSeconds(1));
+ callbackInvoked = false;
+ // Update the timestamp
+ tlsListener.InvokeTlsClientHelloCallback(connectionId: 1UL, features,
+ invokeTlsClientHelloCallback: (f, cb) => { cb(f, ReadOnlySpan.Empty); return true; });
+
+ // Callback should not be invoked again and the timestamp should be updated
+ Assert.False(callbackInvoked);
+ Assert.Equal(timeProvider.GetUtcNow(), Assert.Single(tlsListener.ConnectionTimeStamps).Value);
+ Assert.NotEqual(originalTime, timeProvider.GetUtcNow());
+ }
+
+ [Fact]
+ public async Task RemovesIdleConnections()
+ {
+ // Arrange
+ var logger = Mock.Of();
+ var timeProvider = new FakeTimeProvider();
+ using var tlsListener = new TlsListener(logger, (_, __) => { }, timeProvider);
+
+ var features = Mock.Of();
+
+ bool InvokeCallback(IFeatureCollection f, TlsClientHelloCallback cb)
+ {
+ cb(f, ReadOnlySpan.Empty);
+ return true;
+ }
+
+ // Act
+ tlsListener.InvokeTlsClientHelloCallback(connectionId: 1UL, features, InvokeCallback);
+
+ // 1 less minute than the idle time cleanup
+ timeProvider.Advance(TimeSpan.FromMinutes(4));
+ Assert.Single(tlsListener.ConnectionTimeStamps);
+
+ tlsListener.InvokeTlsClientHelloCallback(connectionId: 2UL, features, InvokeCallback);
+ Assert.Equal(2, tlsListener.ConnectionTimeStamps.Count);
+
+ // With the previous 4 minutes, this should be 5 minutes and remove the first connection
+ timeProvider.Advance(TimeSpan.FromMinutes(1));
+
+ var timeout = TimeSpan.FromSeconds(5);
+ while (timeout > TimeSpan.Zero)
+ {
+ // Wait for the cleanup loop to run
+ if (tlsListener.ConnectionTimeStamps.Count == 1)
+ {
+ break;
+ }
+ timeout -= TimeSpan.FromMilliseconds(100);
+ await Task.Delay(100);
+ }
+
+ // Assert
+ Assert.Single(tlsListener.ConnectionTimeStamps);
+ Assert.Contains(2UL, tlsListener.ConnectionTimeStamps.Keys);
+ }
+
+ [Fact]
+ public async Task EvictsOldestConnectionsWhenExceedingCacheSizeLimit()
+ {
+ // Arrange
+ var logger = Mock.Of();
+ var timeProvider = new FakeTimeProvider();
+ var tlsListener = new TlsListener(logger, (_, __) => { }, timeProvider);
+ var features = Mock.Of();
+
+ ulong i = 0;
+ for (; i < (ulong)tlsListener.CacheSizeLimit; i++)
+ {
+ tlsListener.InvokeTlsClientHelloCallback(i, features, (f, cb) => { cb(f, ReadOnlySpan.Empty); return true; });
+ }
+
+ timeProvider.Advance(TimeSpan.FromSeconds(5));
+
+ for (; i < (ulong)tlsListener.CacheSizeLimit + 3; i++)
+ {
+ tlsListener.InvokeTlsClientHelloCallback(i, features, (f, cb) => { cb(f, ReadOnlySpan.Empty); return true; });
+ }
+
+ // 'touch' first connection to update its timestamp
+ tlsListener.InvokeTlsClientHelloCallback(0, features, (f, cb) => { cb(f, ReadOnlySpan.Empty); return true; });
+
+ // Make sure the cleanup loop has run to evict items since we're above the cache size limit
+ timeProvider.Advance(TimeSpan.FromMinutes(1));
+
+ var timeout = TimeSpan.FromSeconds(5);
+ while (timeout > TimeSpan.Zero)
+ {
+ // Wait for the cleanup loop to run
+ if (tlsListener.ConnectionTimeStamps.Count == tlsListener.CacheSizeLimit)
+ {
+ break;
+ }
+ timeout -= TimeSpan.FromMilliseconds(100);
+ await Task.Delay(100);
+ }
+
+ Assert.Equal(tlsListener.CacheSizeLimit, tlsListener.ConnectionTimeStamps.Count);
+ Assert.Contains(0UL, tlsListener.ConnectionTimeStamps.Keys);
+ // 3 newest connections should be present
+ Assert.Contains(i - 1, tlsListener.ConnectionTimeStamps.Keys);
+ Assert.Contains(i - 2, tlsListener.ConnectionTimeStamps.Keys);
+ Assert.Contains(i - 3, tlsListener.ConnectionTimeStamps.Keys);
+ }
+}
diff --git a/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs b/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs
index 7fed60b434b1..9fce59c69c36 100644
--- a/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs
+++ b/src/Shared/HttpSys/NativeInterop/UnsafeNativeMethods.cs
@@ -28,6 +28,7 @@ internal static class ErrorCodes
internal const uint ERROR_HANDLE_EOF = 38;
internal const uint ERROR_NOT_SUPPORTED = 50;
internal const uint ERROR_INVALID_PARAMETER = 87;
+ internal const uint ERROR_INSUFFICIENT_BUFFER = 122;
internal const uint ERROR_INVALID_NAME = 123;
internal const uint ERROR_ALREADY_EXISTS = 183;
internal const uint ERROR_MORE_DATA = 234;
diff --git a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs
index b58bad9e800e..ad4f63eaacd7 100644
--- a/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs
+++ b/src/Shared/HttpSys/RequestProcessing/NativeRequestContext.cs
@@ -31,9 +31,11 @@ internal unsafe class NativeRequestContext : IDisposable
private MemoryHandle _memoryHandle;
private readonly int _bufferAlignment;
private readonly bool _permanentlyPinned;
- private bool _disposed;
private IReadOnlyDictionary>? _requestInfo;
+ private bool _disposed;
+ private bool _pinsReleased;
+
[MemberNotNullWhen(false, nameof(_backingBuffer))]
private bool PermanentlyPinned => _permanentlyPinned;
@@ -168,6 +170,11 @@ internal uint Size
}
}
+ ///
+ /// Shows whether was already invoked on this native request context
+ ///
+ internal bool PinsReleased => _pinsReleased;
+
// ReleasePins() should be called exactly once. It must be called before Dispose() is called, which means it must be called
// before an object (Request) which closes the RequestContext on demand is returned to the application.
internal void ReleasePins()
@@ -177,6 +184,7 @@ internal void ReleasePins()
_memoryHandle.Dispose();
_memoryHandle = default;
_nativeRequest = null;
+ _pinsReleased = true;
}
public bool TryGetTimestamp(HttpSysRequestTimingType timestampType, out long timestamp)
diff --git a/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs b/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs
index fc1d5c847d74..e83e87423745 100644
--- a/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs
+++ b/src/Shared/ThrowHelpers/ArgumentNullThrowHelper.cs
@@ -30,6 +30,29 @@ public static void ThrowIfNull(
#endif
}
+ /// Throws an if is null or empty.
+ /// The argument to validate as non-null and non-empty.
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfNullOrEmpty(
+#if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
+ [NotNull]
+#endif
+ string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
+ {
+#if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK
+ if (argument is null)
+ {
+ Throw(paramName);
+ }
+ else if (argument.Length == 0)
+ {
+ throw new ArgumentException("Must not be null or empty", paramName);
+ }
+#else
+ ArgumentException.ThrowIfNullOrEmpty(argument, paramName);
+#endif
+ }
+
#if !NET7_0_OR_GREATER || NETSTANDARD || NETFRAMEWORK
#if INTERNAL_NULLABLE_ATTRIBUTES || NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
[DoesNotReturn]
diff --git a/src/SignalR/common/Shared/MessageBuffer.cs b/src/SignalR/common/Shared/MessageBuffer.cs
index 17b9ae170fe0..f08fff86aa40 100644
--- a/src/SignalR/common/Shared/MessageBuffer.cs
+++ b/src/SignalR/common/Shared/MessageBuffer.cs
@@ -121,15 +121,16 @@ private async Task RunTimer()
public ValueTask WriteAsync(SerializedHubMessage hubMessage, CancellationToken cancellationToken)
{
- return WriteAsyncCore(hubMessage.Message!, hubMessage.GetSerializedMessage(_protocol), cancellationToken);
+ // Default to HubInvocationMessage as that's the only type we use SerializedHubMessage for currently when Message is null. Should harden this in the future.
+ return WriteAsyncCore(hubMessage.Message?.GetType() ?? typeof(HubInvocationMessage), hubMessage.GetSerializedMessage(_protocol), cancellationToken);
}
public ValueTask WriteAsync(HubMessage hubMessage, CancellationToken cancellationToken)
{
- return WriteAsyncCore(hubMessage, _protocol.GetMessageBytes(hubMessage), cancellationToken);
+ return WriteAsyncCore(hubMessage.GetType(), _protocol.GetMessageBytes(hubMessage), cancellationToken);
}
- private async ValueTask WriteAsyncCore(HubMessage hubMessage, ReadOnlyMemory messageBytes, CancellationToken cancellationToken)
+ private async ValueTask WriteAsyncCore(Type hubMessageType, ReadOnlyMemory messageBytes, CancellationToken cancellationToken)
{
// TODO: Add backpressure based on message count
if (_bufferedByteCount > _bufferLimit)
@@ -158,7 +159,7 @@ private async ValueTask WriteAsyncCore(HubMessage hubMessage, ReadO
await _writeLock.WaitAsync(cancellationToken: default).ConfigureAwait(false);
try
{
- if (hubMessage is HubInvocationMessage invocationMessage)
+ if (typeof(HubInvocationMessage).IsAssignableFrom(hubMessageType))
{
_totalMessageCount++;
_bufferedByteCount += messageBytes.Length;
diff --git a/src/SignalR/server/Core/src/SerializedHubMessage.cs b/src/SignalR/server/Core/src/SerializedHubMessage.cs
index e355b0329128..9f4327a4cc58 100644
--- a/src/SignalR/server/Core/src/SerializedHubMessage.cs
+++ b/src/SignalR/server/Core/src/SerializedHubMessage.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.Diagnostics;
using Microsoft.AspNetCore.SignalR.Protocol;
namespace Microsoft.AspNetCore.SignalR;
@@ -40,6 +41,8 @@ public SerializedHubMessage(IReadOnlyList messages)
/// The hub message for the cache. This will be serialized with an in to get the message's serialized representation.
public SerializedHubMessage(HubMessage message)
{
+ // Type currently only used for invocation messages, we should probably refactor it to be explicit about that e.g. new property for message type?
+ Debug.Assert(message.GetType().IsAssignableTo(typeof(HubInvocationMessage)));
Message = message;
}
diff --git a/src/SignalR/server/SignalR/test/Internal/MessageBufferTests.cs b/src/SignalR/server/SignalR/test/Internal/MessageBufferTests.cs
index e6f6248a733b..ac2a7888e942 100644
--- a/src/SignalR/server/SignalR/test/Internal/MessageBufferTests.cs
+++ b/src/SignalR/server/SignalR/test/Internal/MessageBufferTests.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.IO.Pipelines;
+using System.Text.Json;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.SignalR.Internal;
@@ -169,6 +170,62 @@ public async Task UnAckedMessageResentOnReconnect()
Assert.False(messageBuffer.ShouldProcessMessage(CompletionMessage.WithResult("1", null)));
}
+ // Regression test for https://github.com/dotnet/aspnetcore/issues/55575
+ [Fact]
+ public async Task UnAckedSerializedMessageResentOnReconnect()
+ {
+ var protocol = new JsonHubProtocol();
+ var connection = new TestConnectionContext();
+ var pipes = DuplexPipe.CreateConnectionPair(new PipeOptions(), new PipeOptions());
+ connection.Transport = pipes.Transport;
+ using var messageBuffer = new MessageBuffer(connection, protocol, bufferLimit: 1000, NullLogger.Instance);
+
+ var invocationMessage = new SerializedHubMessage([new SerializedMessage(protocol.Name,
+ protocol.GetMessageBytes(new InvocationMessage("method1", [1])))]);
+ await messageBuffer.WriteAsync(invocationMessage, default);
+
+ var res = await pipes.Application.Input.ReadAsync();
+
+ var buffer = res.Buffer;
+ Assert.True(protocol.TryParseMessage(ref buffer, new TestBinder(), out var message));
+ var parsedMessage = Assert.IsType(message);
+ Assert.Equal("method1", parsedMessage.Target);
+ Assert.Equal(1, ((JsonElement)Assert.Single(parsedMessage.Arguments)).GetInt32());
+
+ pipes.Application.Input.AdvanceTo(buffer.Start);
+
+ DuplexPipe.UpdateConnectionPair(ref pipes, connection);
+ await messageBuffer.ResendAsync(pipes.Transport.Output);
+
+ Assert.True(messageBuffer.ShouldProcessMessage(PingMessage.Instance));
+ Assert.True(messageBuffer.ShouldProcessMessage(CompletionMessage.WithResult("1", null)));
+ Assert.True(messageBuffer.ShouldProcessMessage(new SequenceMessage(1)));
+
+ res = await pipes.Application.Input.ReadAsync();
+
+ buffer = res.Buffer;
+ Assert.True(protocol.TryParseMessage(ref buffer, new TestBinder(), out message));
+ var seqMessage = Assert.IsType(message);
+ Assert.Equal(1, seqMessage.SequenceId);
+
+ pipes.Application.Input.AdvanceTo(buffer.Start);
+
+ res = await pipes.Application.Input.ReadAsync();
+
+ buffer = res.Buffer;
+ Assert.True(protocol.TryParseMessage(ref buffer, new TestBinder(), out message));
+ parsedMessage = Assert.IsType(message);
+ Assert.Equal("method1", parsedMessage.Target);
+ Assert.Equal(1, ((JsonElement)Assert.Single(parsedMessage.Arguments)).GetInt32());
+
+ pipes.Application.Input.AdvanceTo(buffer.Start);
+
+ messageBuffer.ShouldProcessMessage(new SequenceMessage(1));
+
+ Assert.True(messageBuffer.ShouldProcessMessage(PingMessage.Instance));
+ Assert.False(messageBuffer.ShouldProcessMessage(CompletionMessage.WithResult("1", null)));
+ }
+
[Fact]
public async Task AckedMessageNotResentOnReconnect()
{
diff --git a/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs b/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs
index 74cf30df19a9..0beb6e2233e5 100644
--- a/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs
+++ b/src/SignalR/server/StackExchangeRedis/test/RedisEndToEnd.cs
@@ -1,17 +1,15 @@
// 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.Collections.Generic;
-using System.Threading.Tasks;
+using System.Net.WebSockets;
using Microsoft.AspNetCore.Http.Connections;
+using Microsoft.AspNetCore.Http.Connections.Client;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Protocol;
using Microsoft.AspNetCore.SignalR.Tests;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Xunit;
namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests;
@@ -211,7 +209,105 @@ public async Task CanSendAndReceiveUserMessagesUserNameWithPatternIsTreatedAsLit
}
}
- private static HubConnection CreateConnection(string url, HttpTransportType transportType, IHubProtocol protocol, ILoggerFactory loggerFactory, string userName = null)
+ [ConditionalTheory]
+ [SkipIfDockerNotPresent]
+ [InlineData("messagepack")]
+ [InlineData("json")]
+ public async Task StatefulReconnectPreservesMessageFromOtherServer(string protocolName)
+ {
+ using (StartVerifiableLog())
+ {
+ var protocol = HubProtocolHelpers.GetHubProtocol(protocolName);
+
+ ClientWebSocket innerWs = null;
+ WebSocketWrapper ws = null;
+ TaskCompletionSource reconnectTcs = null;
+ TaskCompletionSource startedReconnectTcs = null;
+
+ var connection = CreateConnection(_serverFixture.FirstServer.Url + "/stateful", HttpTransportType.WebSockets, protocol, LoggerFactory,
+ customizeConnection: builder =>
+ {
+ builder.WithStatefulReconnect();
+ builder.Services.Configure(o =>
+ {
+ // Replace the websocket creation for the first connection so we can make the client think there was an ungraceful closure
+ // Which will trigger the stateful reconnect flow
+ o.WebSocketFactory = async (context, token) =>
+ {
+ if (reconnectTcs is null)
+ {
+ reconnectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ startedReconnectTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ }
+ else
+ {
+ startedReconnectTcs.SetResult();
+ // We only want to wait on the reconnect, not the initial connection attempt
+ await reconnectTcs.Task.DefaultTimeout();
+ }
+
+ innerWs = new ClientWebSocket();
+ ws = new WebSocketWrapper(innerWs);
+ await innerWs.ConnectAsync(context.Uri, token);
+
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ while (innerWs.State == WebSocketState.Open)
+ {
+ var buffer = new byte[1024];
+ var res = await innerWs.ReceiveAsync(buffer, default);
+ ws.SetReceiveResult((res, buffer.AsMemory(0, res.Count)));
+ }
+ }
+ // Log but ignore receive errors, that likely just means the connection closed
+ catch (Exception ex)
+ {
+ Logger.LogInformation(ex, "Error while reading from inner websocket");
+ }
+ });
+
+ return ws;
+ };
+ });
+ });
+ var secondConnection = CreateConnection(_serverFixture.SecondServer.Url + "/stateful", HttpTransportType.WebSockets, protocol, LoggerFactory);
+
+ var tcs = new TaskCompletionSource();
+ connection.On("SendToAll", message => tcs.TrySetResult(message));
+
+ var tcs2 = new TaskCompletionSource();
+ secondConnection.On("SendToAll", message => tcs2.TrySetResult(message));
+
+ await connection.StartAsync().DefaultTimeout();
+ await secondConnection.StartAsync().DefaultTimeout();
+
+ // Close first connection before the second connection sends a message to all clients
+ await ws.CloseOutputAsync(WebSocketCloseStatus.InternalServerError, statusDescription: null, default);
+ await startedReconnectTcs.Task.DefaultTimeout();
+
+ // Send to all clients, since both clients are on different servers this means the backplane will be used
+ // And we want to test that messages are still preserved for stateful reconnect purposes when a client disconnects
+ // But is on a different server from the original message sender.
+ await secondConnection.SendAsync("SendToAll", "test message").DefaultTimeout();
+
+ // Check that second connection still receives the message
+ Assert.Equal("test message", await tcs2.Task.DefaultTimeout());
+ Assert.False(tcs.Task.IsCompleted);
+
+ // allow first connection to reconnect
+ reconnectTcs.SetResult();
+
+ // Check that first connection received the message once it reconnected
+ Assert.Equal("test message", await tcs.Task.DefaultTimeout());
+
+ await connection.DisposeAsync().DefaultTimeout();
+ }
+ }
+
+ private static HubConnection CreateConnection(string url, HttpTransportType transportType, IHubProtocol protocol, ILoggerFactory loggerFactory, string userName = null,
+ Action customizeConnection = null)
{
var hubConnectionBuilder = new HubConnectionBuilder()
.WithLoggerFactory(loggerFactory)
@@ -225,6 +321,8 @@ private static HubConnection CreateConnection(string url, HttpTransportType tran
hubConnectionBuilder.Services.AddSingleton(protocol);
+ customizeConnection?.Invoke(hubConnectionBuilder);
+
return hubConnectionBuilder.Build();
}
@@ -253,4 +351,67 @@ public static IEnumerable