Skip to content

Commit f368547

Browse files
Merged PR 4064: [3.1.4] Fix | LocalDb and managed SNI (#2129)
Ports [#2129](#2129)
1 parent 6d98ca0 commit f368547

File tree

8 files changed

+149
-45
lines changed

8 files changed

+149
-45
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ private string GetConnectionString(string localDbInstance)
5151
{
5252
StringBuilder localDBConnectionString = new StringBuilder(MAX_LOCAL_DB_CONNECTION_STRING_SIZE + 1);
5353
int sizeOfbuffer = localDBConnectionString.Capacity;
54-
localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer);
55-
return localDBConnectionString.ToString();
54+
int result = localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer);
55+
if (result != TdsEnums.SNI_SUCCESS)
56+
{
57+
SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBErrorCode, Strings.SNI_ERROR_50);
58+
SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Unsuccessful 'LocalDBStartInstance' method call with {0} result to start '{1}' localDb instance", args0: result, args1: localDbInstance);
59+
localDBConnectionString = null;
60+
}
61+
return localDBConnectionString?.ToString();
5662
}
5763

5864
internal enum LocalDBErrorState

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@ namespace Microsoft.Data.SqlClient.SNI
2020
/// </summary>
2121
internal enum SNIProviders
2222
{
23-
HTTP_PROV, // HTTP Provider
24-
NP_PROV, // Named Pipes Provider
25-
SESSION_PROV, // Session Provider
26-
SIGN_PROV, // Sign Provider
27-
SM_PROV, // Shared Memory Provider
28-
SMUX_PROV, // SMUX Provider
29-
SSL_PROV, // SSL Provider
30-
TCP_PROV, // TCP Provider
31-
MAX_PROVS, // Number of providers
32-
INVALID_PROV // SQL Network Interfaces
23+
HTTP_PROV = 0, // HTTP Provider
24+
NP_PROV = 1, // Named Pipes Provider
25+
SESSION_PROV = 2, // Session Provider
26+
SIGN_PROV = 3, // Sign Provider
27+
SM_PROV = 4, // Shared Memory Provider
28+
SMUX_PROV = 5, // SMUX Provider
29+
SSL_PROV = 6, // SSL Provider
30+
TCP_PROV = 7, // TCP Provider
31+
VIA_PROV = 8, // Virtual Interface Architecture Provider
32+
CTAIP_PROV = 9,
33+
MAX_PROVS = 10, // Number of providers
34+
INVALID_PROV = 11 // SQL Network Interfaces
3335
}
3436

3537
/// <summary>
@@ -110,6 +112,7 @@ internal class SNICommon
110112
internal const int ConnTimeoutError = 11;
111113
internal const int ConnNotUsableError = 19;
112114
internal const int InvalidConnStringError = 25;
115+
internal const int ErrorLocatingServerInstance = 26;
113116
internal const int HandshakeFailureError = 31;
114117
internal const int InternalExceptionError = 35;
115118
internal const int ConnOpenFailedError = 40;

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ private string GetLocalDBDataSource(string fullServerName, out bool error)
507507
Debug.Assert(!string.IsNullOrWhiteSpace(localDBInstance), "Local DB Instance name cannot be empty.");
508508
localDBConnectionString = LocalDB.GetLocalDBConnectionString(localDBInstance);
509509

510-
if (fullServerName == null)
510+
if (fullServerName == null || string.IsNullOrEmpty(localDBConnectionString))
511511
{
512512
// The Last error is set in LocalDB.GetLocalDBConnectionString. We don't need to set Last here.
513513
error = true;
@@ -531,6 +531,7 @@ internal class DataSource
531531
private const string Slash = @"/";
532532
private const string PipeToken = "pipe";
533533
private const string LocalDbHost = "(localdb)";
534+
private const string LocalDbHost_NP = @"np:\\.\pipe\LOCALDB#";
534535
private const string NamedPipeInstanceNameHeader = "mssql$";
535536
private const string DefaultPipeName = "sql\\query";
536537

@@ -626,25 +627,41 @@ private void PopulateProtocol()
626627
internal static string GetLocalDBInstance(string dataSource, out bool error)
627628
{
628629
string instanceName = null;
629-
630-
string workingDataSource = dataSource.ToLowerInvariant();
631-
632-
string[] tokensByBackSlash = workingDataSource.Split(BackSlashCharacter);
630+
// ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
631+
ReadOnlySpan<char> input = dataSource.AsSpan().TrimStart();
633632

634633
error = false;
635634

636635
// All LocalDb endpoints are of the format host\instancename where host is always (LocalDb) (case-insensitive)
637-
if (tokensByBackSlash.Length == 2 && LocalDbHost.Equals(tokensByBackSlash[0].TrimStart()))
636+
// NetStandard 2.0 does not support passing a string to ReadOnlySpan<char>
637+
int index = input.IndexOf(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase);
638+
if (input.StartsWith(LocalDbHost_NP.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
639+
{
640+
instanceName = input.Trim().ToString();
641+
}
642+
else if (index > 0)
643+
{
644+
SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.ErrorLocatingServerInstance, Strings.SNI_ERROR_26);
645+
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIProxy), EventType.ERR, "Incompatible use of prefix with LocalDb: '{0}'", dataSource);
646+
error = true;
647+
}
648+
else if (index == 0)
638649
{
639-
if (!string.IsNullOrWhiteSpace(tokensByBackSlash[1]))
650+
// When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index
651+
// Such ad input = input[1..];
652+
input = input.Slice(LocalDbHost.Length);
653+
if (!input.IsEmpty && input[0] == BackSlashCharacter)
640654
{
641-
instanceName = tokensByBackSlash[1].Trim();
655+
input = input.Slice(1);
656+
}
657+
if (!input.IsEmpty)
658+
{
659+
instanceName = input.Trim().ToString();
642660
}
643661
else
644662
{
645663
SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBNoInstanceName, Strings.SNI_ERROR_51);
646664
error = true;
647-
return null;
648665
}
649666
}
650667

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,20 +1419,17 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
14191419
}
14201420
else
14211421
{
1422-
14231422
if (TdsParserStateObjectFactory.UseManagedSNI)
14241423
{
1425-
// SNI error. Append additional error message info if available.
1426-
//
1424+
// SNI error. Append additional error message info if available and hasn't been included.
14271425
string sniLookupMessage = SQL.GetSNIErrorMessage((int)details.sniErrorNumber);
1428-
errorMessage = (errorMessage != string.Empty) ?
1429-
(sniLookupMessage + ": " + errorMessage) :
1430-
sniLookupMessage;
1426+
errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage))
1427+
? sniLookupMessage
1428+
: (sniLookupMessage + ": " + errorMessage);
14311429
}
14321430
else
14331431
{
14341432
// SNI error. Replace the entire message.
1435-
//
14361433
errorMessage = SQL.GetSNIErrorMessage((int)details.sniErrorNumber);
14371434

14381435
// If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
@@ -1441,6 +1438,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
14411438
errorMessage += LocalDBAPI.GetLocalDBMessage((int)details.nativeError);
14421439
win32ErrorCode = 0;
14431440
}
1441+
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.TdsParser.ProcessSNIError |ERR|ADV > Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage);
14441442
}
14451443
}
14461444
errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})",
@@ -12409,8 +12407,7 @@ internal bool TryReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsPar
1240912407
return true; // No data
1241012408
}
1241112409

12412-
Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL),
12413-
"Out of sync plp read request");
12410+
Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL), "Out of sync plp read request");
1241412411

1241512412
Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!");
1241612413
charsLeft = len;

src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs

Lines changed: 29 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,10 +1195,10 @@
11951195
<value>TCP Provider</value>
11961196
</data>
11971197
<data name="SNI_PN8" xml:space="preserve">
1198-
<value />
1198+
<value>VIA Provider</value>
11991199
</data>
12001200
<data name="SNI_PN9" xml:space="preserve">
1201-
<value>SQL Network Interfaces</value>
1201+
<value>CTAIP Provider</value>
12021202
</data>
12031203
<data name="AZURESQL_GenericEndpoint" xml:space="preserve">
12041204
<value>.database.windows.net</value>
@@ -1935,4 +1935,10 @@
19351935
<data name="AAD_Token_Retrieving_Timeout" xml:space="preserve">
19361936
<value>Connection timed out while retrieving an access token using '{0}' authentication method. Last error: {1}: {2}</value>
19371937
</data>
1938+
<data name="SNI_PN10" xml:space="preserve">
1939+
<value />
1940+
</data>
1941+
<data name="SNI_PN11" xml:space="preserve">
1942+
<value>SQL Network Interfaces</value>
1943+
</data>
19381944
</root>

src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,12 @@ public static bool IsAKVSetupAvailable()
323323

324324
public static bool IsNotUsingManagedSNIOnWindows() => !UseManagedSNIOnWindows;
325325

326-
public static bool IsUsingNativeSNI() => !IsUsingManagedSNI();
327-
326+
public static bool IsUsingNativeSNI() =>
327+
#if !NETFRAMEWORK
328+
DataTestUtility.IsNotUsingManagedSNIOnWindows();
329+
#else
330+
true;
331+
#endif
328332
// Synapse: UTF8 collations are not supported with Azure Synapse.
329333
// Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/40103791-utf-8-collations-should-be-supported-in-azure-syna
330334
public static bool IsUTF8Supported()

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests
99
public static class LocalDBTest
1010
{
1111
private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled();
12+
private static bool IsNativeSNI() => DataTestUtility.IsUsingNativeSNI();
13+
private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}";
1214

1315
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
1416
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
@@ -41,6 +43,57 @@ public static void InvalidDBTest()
4143
}
4244
}
4345

46+
#region Failures
47+
// ToDo: After adding shared memory support on managed SNI, the IsNativeSNI could be taken out
48+
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
49+
[ConditionalTheory(nameof(IsLocalDBEnvironmentSet), nameof(IsNativeSNI))]
50+
[InlineData("lpc:")]
51+
public static void SharedMemoryAndSqlLocalDbConnectionTest(string prefix)
52+
{
53+
SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString);
54+
stringBuilder.DataSource = prefix + stringBuilder.DataSource;
55+
SqlException ex = Assert.Throws<SqlException>(() => ConnectionTest(stringBuilder.ConnectionString));
56+
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);
57+
}
58+
59+
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
60+
[InlineData("tcp:")]
61+
[InlineData("np:")]
62+
[InlineData("undefinded:")]
63+
[ConditionalTheory(nameof(IsLocalDBEnvironmentSet)/*, nameof(IsNativeSNI)*/)]
64+
public static void PrefixAndSqlLocalDbConnectionTest(string prefix)
65+
{
66+
SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString);
67+
stringBuilder.DataSource = prefix + stringBuilder.DataSource;
68+
SqlException ex = Assert.Throws<SqlException>(() => ConnectionTest(stringBuilder.ConnectionString));
69+
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);
70+
}
71+
72+
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
73+
[ConditionalFact(nameof(IsLocalDBEnvironmentSet)/*, nameof(IsNativeSNI)*/)]
74+
public static void InvalidSqlLocalDbConnectionTest()
75+
{
76+
SqlConnectionStringBuilder stringBuilder = new(s_localDbConnectionString);
77+
stringBuilder.DataSource = stringBuilder.DataSource + "Invalid123";
78+
SqlException ex = Assert.Throws<SqlException>(() => ConnectionTest(stringBuilder.ConnectionString));
79+
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);
80+
if (IsNativeSNI())
81+
{
82+
Assert.Contains("The specified LocalDB instance does not exist.", ex.Message);
83+
}
84+
}
85+
#endregion
86+
87+
private static void ConnectionTest(string connectionString)
88+
{
89+
SqlConnectionStringBuilder builder = new(connectionString)
90+
{
91+
IntegratedSecurity = true,
92+
ConnectTimeout = 2
93+
};
94+
OpenConnection(builder.ConnectionString);
95+
}
96+
4497
private static void OpenConnection(string connString)
4598
{
4699
using (SqlConnection connection = new SqlConnection(connString))

0 commit comments

Comments
 (0)