Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[WinHTTP] Certificate caching on WinHttpHandler to eliminate extra call to Custom Certificate Validation #111791

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

liveans
Copy link
Member

@liveans liveans commented Jan 24, 2025

This PR goal is improving performance around CustomCertificateValidationCallback.

Copy link
Contributor

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

@liveans
Copy link
Member Author

liveans commented Jan 24, 2025

/azp run runtime-libraries-coreclr outerloop-windows

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Comment on lines 321 to 322
AddressFamily.InterNetwork => new IPAddress(BinaryPrimitives.ReadUInt32LittleEndian(RemoteAddressSpan.Slice(4))),
AddressFamily.InterNetworkV6 => new IPAddress(RemoteAddressSpan.Slice(8, 16).ToArray()),
Copy link
Member

@antonfirsov antonfirsov Jan 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is running per-request, can we try to avoid the IPAddress allocations (eg. by using a custom struct as a dictionary key), or if we think they are insignificant, run some benchmarks to prove it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really think it's worth it? Usage of IPAddress simplifies the written code.

Copy link
Member

@antonfirsov antonfirsov Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usage of IPAddress simplifies the written code

I'm not sure. Since the addresses are only used as keys in platform-specific logic, we don't even need to parse them or have any logic distinguishing InterNetwork and InterNetworkV6. It's enough to copy the relevant bytes of the span into a struct containing a buffer big enough for both IPv4 and IPv6.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've discussed this in our offline chat, do you still believe we need to change this into some other structure @antonfirsov ?

Copy link
Member

@rokonec rokonec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very nice for Prove of concept, but there are few things which needs to be considered before we decide on feasibility of this approach:

  • decide if unbounded cache can be an memory leak issue (I have hard time to imagine use where it would)
  • consider different cache key to handle virtual servers infra

@liveans
Copy link
Member Author

liveans commented Feb 17, 2025

/azp run runtime-libraries-coreclr outerloop-windows

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@liveans liveans marked this pull request as ready for review February 17, 2025 19:22
@MihaZupan MihaZupan requested a review from Copilot February 17, 2025 20:45
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

@@ -56,6 +62,14 @@ private static void RequestCallback(
{
switch (internetStatus)
{
case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
if (CertificateCachingAppContextSwitchEnabled)
{
Copy link
Preview

Copilot AI Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the null-forgiving operator '!' assumes that Marshal.PtrToStringUni(statusInformation) is never null. Consider adding an explicit null check to prevent a potential exception if the pointer is unexpectedly null.

Suggested change
{
{
if (statusInformation == IntPtr.Zero)
{
throw new InvalidOperationException("statusInformation pointer is null.");
}

Copilot is powered by AI, so mistakes are possible. Review output carefully before use.

Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
ipAddress = addressFamily switch
{
AddressFamily.InterNetwork => new IPAddress(BinaryPrimitives.ReadUInt32LittleEndian(remoteAddressSpan.Slice(4))),
AddressFamily.InterNetworkV6 => new IPAddress(remoteAddressSpan.Slice(8, 16).ToArray()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit.
I think we should also try to get ScopeID for IPv6 to be 100% correct. LLA are rare but possible.

@liveans
Copy link
Member Author

liveans commented Feb 18, 2025

/azp run runtime-libraries-coreclr outerloop-windows

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@liveans
Copy link
Member Author

liveans commented Feb 18, 2025

/azp run runtime-libraries-coreclr outerloop-windows

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@liveans
Copy link
Member Author

liveans commented Feb 20, 2025

/azp run runtime-libraries-coreclr outerloop-windows

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we get validation that this saves enough perf to justify the added complexity, this looks reasonable to me.

Comment on lines +330 to +331
AddressFamily.InterNetwork => new IPAddress(BinaryPrimitives.ReadUInt32LittleEndian(remoteAddressSpan.Slice(4))),
AddressFamily.InterNetworkV6 => new IPAddress(remoteAddressSpan.Slice(8, 16).ToArray()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth leaving a link to the relevant docs here for where these offset numbers are coming from.

Comment on lines +38 to +41
public override bool Equals(object? obj)
{
throw new Exception("Unreachable");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

case Interop.WinHttp.WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
if (WinHttpHandler.CertificateCachingAppContextSwitchEnabled)
{
IPAddress connectedToIPAddress = IPAddress.Parse(Marshal.PtrToStringUni(statusInformation)!);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use IPAddress.Parse(ReadOnlySpan<char>) here? Moreover: are we confident this will work well for both IPv4 and IPv6?

@liveans liveans marked this pull request as draft February 24, 2025 22:00
@karelz karelz added this to the 10.0.0 milestone Feb 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants