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

[release/7.0] Close MsQuic after checking for QUIC support to free resources (#75163, #75441) #75521

Merged
merged 4 commits into from
Sep 23, 2022
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.Quic;
Expand All @@ -19,6 +20,9 @@ internal sealed unsafe partial class MsQuicApi

private static readonly Version MsQuicVersion = new Version(2, 1);
Copy link
Member

Choose a reason for hiding this comment

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

I got confused by this naming. This isn't the version of msquic that's being used, but rather than minimum supported version, yes? It should probably be named accordingly, e.g. MinimumSupportedMsQuicVersion.


private static readonly delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int> MsQuicOpenVersion;
private static readonly delegate* unmanaged[Cdecl]<QUIC_API_TABLE*, void> MsQuicClose;

public MsQuicSafeHandle Registration { get; }

public QUIC_API_TABLE* ApiTable { get; }
Expand Down Expand Up @@ -47,7 +51,8 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)
}
}

internal static MsQuicApi Api { get; } = null!;
private static readonly Lazy<MsQuicApi> _api = new Lazy<MsQuicApi>(AllocateMsQuicApi);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: s_api

internal static MsQuicApi Api => _api.Value;

internal static bool IsQuicSupported { get; }

Expand All @@ -56,92 +61,110 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)
internal static bool Tls13ServerMayBeDisabled { get; }
internal static bool Tls13ClientMayBeDisabled { get; }

#pragma warning disable CA1810 // Initialize all static fields in 'MsQuicApi' when those fields are declared and remove the explicit static constructor
static MsQuicApi()
{
IntPtr msQuicHandle;
if (!NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle) &&
if (!NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out IntPtr msQuicHandle) &&
!NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle))
{
// MsQuic library not loaded
return;
}

MsQuicOpenVersion = (delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int>)NativeLibrary.GetExport(msQuicHandle, nameof(MsQuicOpenVersion));
Copy link
Member

Choose a reason for hiding this comment

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

Old version had TryGetExport for these methods, but what will happen now if the export fails? Will it throw? Will it return null?

Copy link
Member

Choose a reason for hiding this comment

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

NativeLibrary.GetExport will throw an exception when the export is not found in the library.

From #75441 (comment) : The failures to get exports from the MsQuic library should be fatal. If we fail to get the export, it means that there is something very wrong.

MsQuicClose = (delegate* unmanaged[Cdecl]<QUIC_API_TABLE*, void>)NativeLibrary.GetExport(msQuicHandle, nameof(MsQuicClose));

if (!TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out _))
{
// Too low version of the library (likely pre-2.0)
return;
}

try
{
if (!NativeLibrary.TryGetExport(msQuicHandle, "MsQuicOpenVersion", out IntPtr msQuicOpenVersionAddress))
// Check version
int arraySize = 4;
Copy link
Member

Choose a reason for hiding this comment

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

const int ArraySize = 4;

uint* libVersion = stackalloc uint[arraySize];
uint size = (uint)arraySize * sizeof(uint);
if (StatusFailed(apiTable->GetParam(null, QUIC_PARAM_GLOBAL_LIBRARY_VERSION, &size, libVersion)))
{
return;
}

QUIC_API_TABLE* apiTable = null;
delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int> msQuicOpenVersion = (delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int>)msQuicOpenVersionAddress;
if (StatusFailed(msQuicOpenVersion((uint)MsQuicVersion.Major, &apiTable)))
var version = new Version((int)libVersion[0], (int)libVersion[1], (int)libVersion[2], (int)libVersion[3]);
if (version < MsQuicVersion)
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(null, $"Incompatible MsQuic library version '{version}', expecting '{MsQuicVersion}'");
}
return;
}

try
{
int arraySize = 4;
uint* libVersion = stackalloc uint[arraySize];
uint size = (uint)arraySize * sizeof(uint);
if (StatusFailed(apiTable->GetParam(null, QUIC_PARAM_GLOBAL_LIBRARY_VERSION, &size, libVersion)))
{
return;
}
// Assume SChannel is being used on windows and query for the actual provider from the library if querying is supported
QUIC_TLS_PROVIDER provider = OperatingSystem.IsWindows() ? QUIC_TLS_PROVIDER.SCHANNEL : QUIC_TLS_PROVIDER.OPENSSL;
size = sizeof(QUIC_TLS_PROVIDER);
apiTable->GetParam(null, QUIC_PARAM_GLOBAL_TLS_PROVIDER, &size, &provider);
UsesSChannelBackend = provider == QUIC_TLS_PROVIDER.SCHANNEL;

var version = new Version((int)libVersion[0], (int)libVersion[1], (int)libVersion[2], (int)libVersion[3]);
if (version < MsQuicVersion)
if (UsesSChannelBackend)
{
// Implies windows platform, check TLS1.3 availability
if (!IsWindowsVersionSupported())
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(null, $"Incompatible MsQuic library version '{version}', expecting '{MsQuicVersion}'");
NetEventSource.Info(null, $"Current Windows version ({Environment.OSVersion}) is not supported by QUIC. Minimal supported version is {MinWindowsVersion}");
}

return;
}

// Assume SChannel is being used on windows and query for the actual provider from the library
QUIC_TLS_PROVIDER provider = OperatingSystem.IsWindows() ? QUIC_TLS_PROVIDER.SCHANNEL : QUIC_TLS_PROVIDER.OPENSSL;
size = sizeof(QUIC_TLS_PROVIDER);
apiTable->GetParam(null, QUIC_PARAM_GLOBAL_TLS_PROVIDER, &size, &provider);
UsesSChannelBackend = provider == QUIC_TLS_PROVIDER.SCHANNEL;
Tls13ServerMayBeDisabled = IsTls13Disabled(isServer: true);
Tls13ClientMayBeDisabled = IsTls13Disabled(isServer: false);
}

if (UsesSChannelBackend)
{
// Implies windows platform, check TLS1.3 availability
if (!IsWindowsVersionSupported())
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(null, $"Current Windows version ({Environment.OSVersion}) is not supported by QUIC. Minimal supported version is {MinWindowsVersion}");
}
IsQuicSupported = true;
}
finally
{
// Gracefully close the API table to free resources. The API table will be allocated lazily again if needed
MsQuicClose(apiTable);
}
}
#pragma warning restore CA1810

return;
}
private static MsQuicApi AllocateMsQuicApi()
{
Debug.Assert(IsQuicSupported);

Tls13ServerMayBeDisabled = IsTls13Disabled(isServer: true);
Tls13ClientMayBeDisabled = IsTls13Disabled(isServer: false);
}
if (!TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out int openStatus))
{
throw ThrowHelper.GetExceptionForMsQuicStatus(openStatus);
}

Api = new MsQuicApi(apiTable);
IsQuicSupported = true;
}
finally
{
if (!IsQuicSupported && NativeLibrary.TryGetExport(msQuicHandle, "MsQuicClose", out IntPtr msQuicClose))
{
// Gracefully close the API table
((delegate* unmanaged[Cdecl]<QUIC_API_TABLE*, void>)msQuicClose)(apiTable);
}
}
return new MsQuicApi(apiTable);
}

}
finally
private static bool TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out int openStatus)
{
Debug.Assert(MsQuicOpenVersion != null);

QUIC_API_TABLE* table = null;
openStatus = MsQuicOpenVersion((uint)MsQuicVersion.Major, &table);
if (StatusFailed(openStatus))
{
if (!IsQuicSupported)
if (NetEventSource.Log.IsEnabled())
{
NativeLibrary.Free(msQuicHandle);
NetEventSource.Info(null, $"MsQuicOpenVersion returned {openStatus} status code.");
}

apiTable = null;
return false;
}

apiTable = table;
return true;
}

private static bool IsWindowsVersionSupported() => OperatingSystem.IsWindowsVersionAtLeast(MinWindowsVersion.Major,
Expand Down