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 | Adding support for sharedInstances to managed SNI #1237

Merged
merged 14 commits into from
Sep 20, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace Microsoft.Data.SqlClient.SNI
{
Expand Down Expand Up @@ -142,7 +143,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode)
/// <param name="cachedFQDN">Used for DNS Cache</param>
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
/// <returns>SNI handle</returns>
internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer,
internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer,
bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
{
instanceName = new byte[1];
Expand Down Expand Up @@ -415,6 +416,7 @@ internal enum Protocol { TCP, NP, None, Admin };

private string _workingDataSource;
private string _dataSourceAfterTrimmingProtocol;

internal bool IsBadDataSource { get; private set; } = false;

internal bool IsSsrpRequired { get; private set; } = false;
Expand Down Expand Up @@ -472,31 +474,39 @@ private void PopulateProtocol()
}
}

// LocalDbInstance name always starts with (localdb)
// possible scenarios:
// (localdb)\<instance name>
// or (localdb)\. which goes to default localdb
// or (localdb)\.\<sharedInstance name>
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>
if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase))
{
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;
}
}

return instanceName;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ public static class DataTestUtility
public static readonly string AKVClientId = null;
public static readonly string AKVClientSecret = null;
public static readonly string LocalDbAppName = null;
public static readonly string LocalDbSharedInstanceName = null;
public static List<string> AEConnStrings = new List<string>();
public static List<string> AEConnStringsSetup = new List<string>();
public static List<string> LocalDbDataSources = new();
public static bool EnclaveEnabled { get; private set; } = false;
public static readonly bool TracingEnabled = false;
public static readonly bool SupportsIntegratedSecurity = false;
Expand Down Expand Up @@ -84,6 +86,7 @@ static DataTestUtility()
AADServicePrincipalId = c.AADServicePrincipalId;
AADServicePrincipalSecret = c.AADServicePrincipalSecret;
LocalDbAppName = c.LocalDbAppName;
LocalDbSharedInstanceName = c.LocalDbSharedInstanceName;
SupportsIntegratedSecurity = c.SupportsIntegratedSecurity;
SupportsFileStream = c.SupportsFileStream;
EnclaveEnabled = c.EnclaveEnabled;
Expand Down Expand Up @@ -150,6 +153,12 @@ static DataTestUtility()
AEConnStringsSetup.Add(TCPConnectionString);
}
}
if (!string.IsNullOrEmpty(LocalDbAppName))
{
LocalDbDataSources.Add(@$"server=(localdb)\{LocalDbAppName}");
LocalDbDataSources.Add(@$"server=(localdb)\.\{LocalDbSharedInstanceName}");
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
LocalDbDataSources.Add(@$"server=(localdb)\.");
}
}

public static IEnumerable<string> ConnectionStrings
Expand Down Expand Up @@ -443,7 +452,7 @@ public static void DropDatabase(SqlConnection sqlConnection, string dbName)
}
}

public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim());
public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim()) && !string.IsNullOrEmpty(LocalDbSharedInstanceName?.Trim());
JRahnama marked this conversation as resolved.
Show resolved Hide resolved

public static bool IsIntegratedSecuritySetup() => SupportsIntegratedSecurity;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
Expand All @@ -11,47 +11,55 @@ public static class LocalDBTest
private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled();

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void LocalDBConnectionTest()
[ConditionalTheory(nameof(IsLocalDBEnvironmentSet))]
[MemberData(nameof(LocalDbSourceProvider))]
public static void LocalDBConnectionTest(string connectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}");
builder.IntegratedSecurity = true;
builder.ConnectTimeout = 2;
SqlConnectionStringBuilder builder = new(connectionString)
{
IntegratedSecurity = true,
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
ConnectTimeout = 2,
Encrypt = false
};
OpenConnection(builder.ConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void LocalDBMarsTest()
[ConditionalTheory(nameof(IsLocalDBEnvironmentSet))]
[MemberData(nameof(LocalDbSourceProvider))]
public static void LocalDBMarsTest(string connectionString)
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}");
builder.IntegratedSecurity = true;
builder.MultipleActiveResultSets = true;
builder.ConnectTimeout = 2;
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString)
{
IntegratedSecurity = true,
JRahnama marked this conversation as resolved.
Show resolved Hide resolved
MultipleActiveResultSets = true,
ConnectTimeout = 2,
Encrypt = false
};
OpenConnection(builder.ConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void InvalidDBTest()
[ConditionalTheory(nameof(IsLocalDBEnvironmentSet))]
[MemberData(nameof(LocalDbSourceProvider))]
public static void InvalidDBTest(string connectionString)
{
using (var connection = new SqlConnection(@$"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;"))
{
DataTestUtility.AssertThrowsWrapper<SqlException>(() => connection.Open());
}
using var connection = new SqlConnection(connectionString);
DataTestUtility.AssertThrowsWrapper<SqlException>(() => connection.Open());
}

private static void OpenConnection(string connString)
{
using (SqlConnection connection = new SqlConnection(connString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection))
{
var result = command.ExecuteScalar();
Assert.NotNull(result);
}
}
using SqlConnection connection = new(connString);
connection.Open();
using SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection);
var result = command.ExecuteScalar();
Assert.NotNull(result);
}

public static IEnumerable<object[]> LocalDbSourceProvider()
{
return (IEnumerable<object[]>)DataTestUtility.LocalDbDataSources;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class Config
public string AzureKeyVaultClientId = null;
public string AzureKeyVaultClientSecret = null;
public string LocalDbAppName = null;
public string LocalDbSharedInstanceName = null;
DavoudEshtehari marked this conversation as resolved.
Show resolved Hide resolved
public bool EnclaveEnabled = false;
public bool TracingEnabled = false;
public bool SupportsIntegratedSecurity = false;
Expand Down