Skip to content

Commit

Permalink
Merged PR 4064: [3.1.4] Fix | LocalDb and managed SNI (dotnet#2129)
Browse files Browse the repository at this point in the history
  • Loading branch information
DavoudEshtehari committed Oct 17, 2023
1 parent 6d98ca0 commit f368547
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,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);
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 @@ -20,16 +20,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
}

/// <summary>
Expand Down Expand Up @@ -110,6 +112,7 @@ internal class SNICommon
internal const int ConnTimeoutError = 11;
internal const int ConnNotUsableError = 19;
internal const int InvalidConnStringError = 25;
internal const int ErrorLocatingServerInstance = 26;
internal const int HandshakeFailureError = 31;
internal const int InternalExceptionError = 35;
internal const int ConnOpenFailedError = 40;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ private 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 All @@ -531,6 +531,7 @@ internal class DataSource
private const string Slash = @"/";
private const string PipeToken = "pipe";
private const string LocalDbHost = "(localdb)";
private const string LocalDbHost_NP = @"np:\\.\pipe\LOCALDB#";
private const string NamedPipeInstanceNameHeader = "mssql$";
private const string DefaultPipeName = "sql\\query";

Expand Down Expand Up @@ -626,25 +627,41 @@ private void PopulateProtocol()
internal static string GetLocalDBInstance(string dataSource, out bool error)
{
string instanceName = null;

string workingDataSource = dataSource.ToLowerInvariant();

string[] tokensByBackSlash = workingDataSource.Split(BackSlashCharacter);
// ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
ReadOnlySpan<char> input = dataSource.AsSpan().TrimStart();

error = false;

// All LocalDb endpoints are of the format host\instancename where host is always (LocalDb) (case-insensitive)
if (tokensByBackSlash.Length == 2 && LocalDbHost.Equals(tokensByBackSlash[0].TrimStart()))
// NetStandard 2.0 does not support passing a string to ReadOnlySpan<char>
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)
{
if (!string.IsNullOrWhiteSpace(tokensByBackSlash[1]))
// When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index
// Such ad input = input[1..];
input = input.Slice(LocalDbHost.Length);
if (!input.IsEmpty && input[0] == BackSlashCharacter)
{
instanceName = tokensByBackSlash[1].Trim();
input = input.Slice(1);
}
if (!input.IsEmpty)
{
instanceName = input.Trim().ToString();
}
else
{
SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBNoInstanceName, Strings.SNI_ERROR_51);
error = true;
return null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1419,20 +1419,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 @@ -1441,6 +1438,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 @@ -12409,8 +12407,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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1195,10 +1195,10 @@
<value>TCP Provider</value>
</data>
<data name="SNI_PN8" xml:space="preserve">
<value />
<value>VIA Provider</value>
</data>
<data name="SNI_PN9" xml:space="preserve">
<value>SQL Network Interfaces</value>
<value>CTAIP Provider</value>
</data>
<data name="AZURESQL_GenericEndpoint" xml:space="preserve">
<value>.database.windows.net</value>
Expand Down Expand Up @@ -1935,4 +1935,10 @@
<data name="AAD_Token_Retrieving_Timeout" xml:space="preserve">
<value>Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2}</value>
</data>
<data name="SNI_PN10" xml:space="preserve">
<value />
</data>
<data name="SNI_PN11" xml:space="preserve">
<value>SQL Network Interfaces</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,12 @@ public static bool IsAKVSetupAvailable()

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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
public static class LocalDBTest
{
private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled();
private static bool IsNativeSNI() => DataTestUtility.IsUsingNativeSNI();
private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}";

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
Expand Down Expand Up @@ -41,6 +43,57 @@ public static void InvalidDBTest()
}
}

#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);
}
}
#endregion

private static void ConnectionTest(string connectionString)
{
SqlConnectionStringBuilder builder = new(connectionString)
{
IntegratedSecurity = true,
ConnectTimeout = 2
};
OpenConnection(builder.ConnectionString);
}

private static void OpenConnection(string connString)
{
using (SqlConnection connection = new SqlConnection(connString))
Expand Down

0 comments on commit f368547

Please sign in to comment.