Skip to content
Merged
Changes from 2 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
Expand Up @@ -20,6 +20,8 @@ internal sealed unsafe partial class MsQuicApi

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

private static readonly IntPtr MsQuicHandle = TryLoadMsQuic(out IntPtr handle) ? handle : IntPtr.Zero;

public MsQuicSafeHandle Registration { get; }

public QUIC_API_TABLE* ApiTable { get; }
Expand Down Expand Up @@ -60,104 +62,93 @@ private MsQuicApi(QUIC_API_TABLE* apiTable)

static MsQuicApi()
{
if (!TryLoadMsQuic(out IntPtr msQuicHandle))
if (MsQuicHandle == IntPtr.Zero)
{
// MsQuic library not loaded
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (MsQuicHandle == IntPtr.Zero)
{
// MsQuic library not loaded
return;
}
if (!TryLoadMsQuic(out MsQuicHandle))
{
// MsQuic library not loaded
return;
}

and delete inline initialization of MsQuicHandle above

Copy link
Member

Choose a reason for hiding this comment

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

Instead of storing MsQuicHandle, you can consider storing the pointers to the two exports in statics instead. It would save a bit of redundant work on the re-initialization.


if (!TryOpenMsQuic(out QUIC_API_TABLE* apiTable, out _))
{
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
bool closed = TryCloseMsQuic(apiTable);
Debug.Assert(closed, "Failed to close MsQuic");
}
}

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)
{
Debug.Assert(MsQuicHandle != IntPtr.Zero);

apiTable = null;
if (!NativeLibrary.TryGetExport(msQuicHandle, "MsQuicOpenVersion", out IntPtr msQuicOpenVersionAddress))
if (!NativeLibrary.TryGetExport(MsQuicHandle, "MsQuicOpenVersion", out IntPtr msQuicOpenVersionAddress))
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be NativeLibrary.GetExport.

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.

{
if (NetEventSource.Log.IsEnabled())
{
Expand Down Expand Up @@ -185,9 +176,11 @@ private static bool TryOpenMsQuic(IntPtr msQuicHandle, out QUIC_API_TABLE* apiTa
return true;
}

private static bool TryCloseMsQuic(IntPtr msQuicHandle, QUIC_API_TABLE* apiTable)
private static bool TryCloseMsQuic(QUIC_API_TABLE* apiTable)
Copy link
Member

Choose a reason for hiding this comment

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

This can return void if you incorporate my other feedback

{
if (NativeLibrary.TryGetExport(msQuicHandle, "MsQuicClose", out IntPtr msQuicClose))
Debug.Assert(MsQuicHandle != IntPtr.Zero);

if (NativeLibrary.TryGetExport(MsQuicHandle, "MsQuicClose", out IntPtr msQuicClose))
{
((delegate* unmanaged[Cdecl]<QUIC_API_TABLE*, void>)msQuicClose)(apiTable);
return true;
Expand Down