Skip to content

Commit

Permalink
Don't unload MsQuic from the process (dotnet#75441)
Browse files Browse the repository at this point in the history
  • Loading branch information
rzikm committed Sep 13, 2022
1 parent 1147c86 commit 67b137f
Showing 1 changed file with 53 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ internal sealed unsafe partial class MsQuicApi

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

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 @@ -58,144 +61,112 @@ 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()
{
if (!TryLoadMsQuic(out IntPtr 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));
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 (!TryOpenMsQuic(msQuicHandle, out QUIC_API_TABLE* apiTable, out _))
// Check version
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;
}

try
var version = new Version((int)libVersion[0], (int)libVersion[1], (int)libVersion[2], (int)libVersion[3]);
if (version < MsQuicVersion)
{
// Check version
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)))
if (NetEventSource.Log.IsEnabled())
{
return;
}

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;
NetEventSource.Info(null, $"Incompatible MsQuic library version '{version}', expecting '{MsQuicVersion}'");
}
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;
// 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;

if (UsesSChannelBackend)
if (UsesSChannelBackend)
{
// Implies windows platform, check TLS1.3 availability
if (!IsWindowsVersionSupported())
{
// Implies windows platform, check TLS1.3 availability
if (!IsWindowsVersionSupported())
if (NetEventSource.Log.IsEnabled())
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(null, $"Current Windows version ({Environment.OSVersion}) is not supported by QUIC. Minimal supported version is {MinWindowsVersion}");
}

return;
NetEventSource.Info(null, $"Current Windows version ({Environment.OSVersion}) is not supported by QUIC. Minimal supported version is {MinWindowsVersion}");
}

Tls13ServerMayBeDisabled = IsTls13Disabled(isServer: true);
Tls13ClientMayBeDisabled = IsTls13Disabled(isServer: false);
return;
}

IsQuicSupported = true;
}
finally
{
// Gracefully close the API table to free resources. The API table will be allocated lazily again if needed
bool closed = TryCloseMsQuic(msQuicHandle, apiTable);
Debug.Assert(closed, "Failed to close MsQuic");
Tls13ServerMayBeDisabled = IsTls13Disabled(isServer: true);
Tls13ClientMayBeDisabled = IsTls13Disabled(isServer: false);
}

IsQuicSupported = true;
}
finally
{
// Unload the library, we will load it again when we actually use QUIC
NativeLibrary.Free(msQuicHandle);
// Gracefully close the API table to free resources. The API table will be allocated lazily again if needed
MsQuicClose(apiTable);
}
}
#pragma warning restore CA1810

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

int openStatus = MsQuic.QUIC_STATUS_INTERNAL_ERROR;

if (TryLoadMsQuic(out IntPtr msQuicHandle) &&
TryOpenMsQuic(msQuicHandle, out QUIC_API_TABLE* apiTable, out openStatus))
if (!TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out int openStatus))
{
return new MsQuicApi(apiTable);
throw ThrowHelper.GetExceptionForMsQuicStatus(openStatus);
}

ThrowHelper.ThrowIfMsQuicError(openStatus);

// this should unreachable as TryOpenMsQuic returns non-success status on failure
throw new Exception("Failed to create MsQuicApi instance");
return new MsQuicApi(apiTable);
}

private static bool TryLoadMsQuic(out IntPtr msQuicHandle) =>
NativeLibrary.TryLoad($"{Interop.Libraries.MsQuic}.{MsQuicVersion.Major}", typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle) ||
NativeLibrary.TryLoad(Interop.Libraries.MsQuic, typeof(MsQuicApi).Assembly, DllImportSearchPath.AssemblyDirectory, out msQuicHandle);

private static bool TryOpenMsQuic(IntPtr msQuicHandle, out QUIC_API_TABLE* apiTable, out int openStatus)
private static bool TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out int openStatus)
{
apiTable = null;
if (!NativeLibrary.TryGetExport(msQuicHandle, "MsQuicOpenVersion", out IntPtr msQuicOpenVersionAddress))
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(null, "Failed to get MsQuicOpenVersion export in msquic library.");
}

openStatus = MsQuic.QUIC_STATUS_NOT_FOUND;
return false;
}
Debug.Assert(MsQuicOpenVersion != null);

QUIC_API_TABLE* table = null;
delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int> msQuicOpenVersion = (delegate* unmanaged[Cdecl]<uint, QUIC_API_TABLE**, int>)msQuicOpenVersionAddress;
openStatus = msQuicOpenVersion((uint)MsQuicVersion.Major, &table);
openStatus = MsQuicOpenVersion((uint)MsQuicVersion.Major, &table);
if (StatusFailed(openStatus))
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(null, $"MsQuicOpenVersion returned {openStatus} status code.");
}

apiTable = null;
return false;
}

apiTable = table;
return true;
}

private static bool TryCloseMsQuic(IntPtr msQuicHandle, QUIC_API_TABLE* apiTable)
{
if (NativeLibrary.TryGetExport(msQuicHandle, "MsQuicClose", out IntPtr msQuicClose))
{
((delegate* unmanaged[Cdecl]<QUIC_API_TABLE*, void>)msQuicClose)(apiTable);
return true;
}

return false;
}

private static bool IsWindowsVersionSupported() => OperatingSystem.IsWindowsVersionAtLeast(MinWindowsVersion.Major,
MinWindowsVersion.Minor, MinWindowsVersion.Build, MinWindowsVersion.Revision);

Expand Down

0 comments on commit 67b137f

Please sign in to comment.