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

Fix | LocalDb and managed SNI #2129

Merged
merged 2 commits into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,14 @@ private string GetConnectionString(string localDbInstance)
{
StringBuilder localDBConnectionString = new StringBuilder(MAX_LOCAL_DB_CONNECTION_STRING_SIZE + 1);
int sizeOfbuffer = localDBConnectionString.Capacity;
localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer);
return localDBConnectionString.ToString();
int result = localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer);
David-Engel marked this conversation as resolved.
Show resolved Hide resolved
if (result != TdsEnums.SNI_SUCCESS)
{
SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBErrorCode, Strings.SNI_ERROR_50);
SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Unsuccessful 'LocalDBStartInstance' method call with {0} result to start '{1}' localDb instance", args0: result, args1: localDbInstance);
localDBConnectionString = null;
}
return localDBConnectionString?.ToString();
}

internal enum LocalDBErrorState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@ namespace Microsoft.Data.SqlClient.SNI
/// </summary>
internal enum SNIProviders
{
HTTP_PROV, // HTTP Provider
NP_PROV, // Named Pipes Provider
SESSION_PROV, // Session Provider
SIGN_PROV, // Sign Provider
SM_PROV, // Shared Memory Provider
SMUX_PROV, // SMUX Provider
SSL_PROV, // SSL Provider
TCP_PROV, // TCP Provider
MAX_PROVS, // Number of providers
INVALID_PROV // SQL Network Interfaces
HTTP_PROV = 0, // HTTP Provider
NP_PROV = 1, // Named Pipes Provider
SESSION_PROV = 2, // Session Provider
SIGN_PROV = 3, // Sign Provider
SM_PROV = 4, // Shared Memory Provider
SMUX_PROV = 5, // SMUX Provider
SSL_PROV = 6, // SSL Provider
TCP_PROV = 7, // TCP Provider
VIA_PROV = 8, // Virtual Interface Architecture Provider
CTAIP_PROV = 9,
MAX_PROVS = 10, // Number of providers
INVALID_PROV = 11 // SQL Network Interfaces
David-Engel marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode)
case DataSource.Protocol.TCP:
sniHandle = CreateTcpHandle(details, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo,
tlsFirst, hostNameInCertificate, serverCertificateFilename);
break;
break;
case DataSource.Protocol.NP:
sniHandle = CreateNpHandle(details, timerExpire, parallel, tlsFirst);
break;
Expand Down Expand Up @@ -390,7 +390,7 @@ private static string GetLocalDBDataSource(string fullServerName, out bool error
Debug.Assert(!string.IsNullOrWhiteSpace(localDBInstance), "Local DB Instance name cannot be empty.");
localDBConnectionString = LocalDB.GetLocalDBConnectionString(localDBInstance);

if (fullServerName == null)
if (fullServerName == null || string.IsNullOrEmpty(localDBConnectionString))
{
// The Last error is set in LocalDB.GetLocalDBConnectionString. We don't need to set Last here.
error = true;
Expand Down Expand Up @@ -520,7 +520,18 @@ internal static string GetLocalDBInstance(string dataSource, out bool error)
ReadOnlySpan<char> input = dataSource.AsSpan().TrimStart();
error = false;
// NetStandard 2.0 does not support passing a string to ReadOnlySpan<char>
if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
int index = input.IndexOf(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase);
if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
instanceName = input.Trim().ToString();
}
else if (index > 0)
{
SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.ErrorLocatingServerInstance, Strings.SNI_ERROR_26);
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIProxy), EventType.ERR, "Incompatible use of prefix with LocalDb: '{0}'", dataSource);
error = true;
}
else if (index == 0)
{
// When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index
// Such ad input = input[1..];
Expand All @@ -539,10 +550,6 @@ internal static string GetLocalDBInstance(string dataSource, out bool error)
error = true;
}
}
else if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
instanceName = input.Trim().ToString();
}

return instanceName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1514,20 +1514,17 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
}
else
{

if (TdsParserStateObjectFactory.UseManagedSNI)
{
// SNI error. Append additional error message info if available.
//
// SNI error. Append additional error message info if available and hasn't been included.
string sniLookupMessage = SQL.GetSNIErrorMessage((int)details.sniErrorNumber);
errorMessage = (errorMessage != string.Empty) ?
(sniLookupMessage + ": " + errorMessage) :
sniLookupMessage;
errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage))
? sniLookupMessage
: (sniLookupMessage + ": " + errorMessage);
}
else
{
// SNI error. Replace the entire message.
//
errorMessage = SQL.GetSNIErrorMessage((int)details.sniErrorNumber);

// If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
Expand All @@ -1536,6 +1533,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
errorMessage += LocalDBAPI.GetLocalDBMessage((int)details.nativeError);
win32ErrorCode = 0;
}
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.TdsParser.ProcessSNIError |ERR|ADV > Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage);
}
}
errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})",
Expand Down Expand Up @@ -12606,7 +12604,7 @@ internal bool TryReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsPar
return true; // No data
}

Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL),"Out of sync plp read request");
Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL), "Out of sync plp read request");

Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
charsLeft = len;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,12 @@ public static bool IsTargetReadyForAeWithKeyStore()

public static bool IsNotUsingManagedSNIOnWindows() => !UseManagedSNIOnWindows;

public static bool IsUsingNativeSNI() => !IsUsingManagedSNI();

public static bool IsUsingNativeSNI() =>
#if !NETFRAMEWORK
DataTestUtility.IsNotUsingManagedSNIOnWindows();
#else
true;
#endif
// Synapse: UTF8 collations are not supported with Azure Synapse.
// Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/40103791-utf-8-collations-should-be-supported-in-azure-syna
public static bool IsUTF8Supported()
Expand Down Expand Up @@ -885,7 +889,7 @@ public static bool ParseDataSource(string dataSource, out string hostname, out i

if (dataSource.Contains(","))
{
if (!Int32.TryParse(dataSource.Substring(dataSource.LastIndexOf(",",StringComparison.Ordinal) + 1), out port))
if (!Int32.TryParse(dataSource.Substring(dataSource.LastIndexOf(",", StringComparison.Ordinal) + 1), out port))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ private enum InfoType
state
}
private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled();
private static bool IsNativeSNI() => DataTestUtility.IsUsingNativeSNI();
private static bool IsLocalDbSharedInstanceSet() => DataTestUtility.IsLocalDbSharedInstanceSetup();
private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}";
private static readonly string[] s_sharedLocalDbInstances = new string[] { @$"server=(localdb)\.\{DataTestUtility.LocalDbSharedInstanceName}", @$"server=(localdb)\." };
Expand Down Expand Up @@ -123,6 +124,47 @@ public static void LocalDBNamepipeMarsTest()

#endregion

#region Failures
// ToDo: After adding shared memory support on managed SNI, the IsNativeSNI could be taken out
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalTheory(nameof(IsLocalDBEnvironmentSet), nameof(IsNativeSNI))]
[InlineData("lpc:")]
public static void SharedMemoryAndSqlLocalDbConnectionTest(string prefix)
{
SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString);
stringBuilder.DataSource = prefix + stringBuilder.DataSource;
SqlException ex = Assert.Throws<SqlException>(() => ConnectionTest(stringBuilder.ConnectionString));
Assert.Contains("A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 41 - Cannot open a Shared Memory connection to a remote SQL server)", ex.Message);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[InlineData("tcp:")]
[InlineData("np:")]
[InlineData("undefinded:")]
[ConditionalTheory(nameof(IsLocalDBEnvironmentSet)/*, nameof(IsNativeSNI)*/)]
public static void PrefixAndSqlLocalDbConnectionTest(string prefix)
{
SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString);
stringBuilder.DataSource = prefix + stringBuilder.DataSource;
SqlException ex = Assert.Throws<SqlException>(() => ConnectionTest(stringBuilder.ConnectionString));
Assert.Contains("A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)", ex.Message);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet)/*, nameof(IsNativeSNI)*/)]
public static void InvalidSqlLocalDbConnectionTest()
{
SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString);
stringBuilder.DataSource = stringBuilder.DataSource + "Invalid123";
SqlException ex = Assert.Throws<SqlException>(() => ConnectionTest(stringBuilder.ConnectionString));
Assert.Contains("A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 50 - Local Database Runtime error occurred.", ex.Message);
if (IsNativeSNI())
{
Assert.Contains("The specified LocalDB instance does not exist.", ex.Message);
}
Comment on lines +161 to +164
Copy link
Member Author

Choose a reason for hiding this comment

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

Additional information comes from the native method call in TdsParser by LocalDBAPI.GetLocalDBMessage().

}
#endregion

private static void ConnectionWithMarsTest(string connectionString)
{
SqlConnectionStringBuilder builder = new(connectionString)
Expand Down Expand Up @@ -178,13 +220,13 @@ private static void RestartLocalDB()
{
string state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, InfoType.state);
int count = 5;
while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase) && count>0)
while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase) && count > 0)
{
count--;
state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_startLocalDbCommand, InfoType.state);
Thread.Sleep(2000);
}
if(state == null || state != "Running")
if (state == null || state != "Running")
{
throw new LocalDBNotStartedException();
}
Expand Down