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 | SqlLocalDB instance pipename issue with Encryption #1433

Merged
merged 3 commits into from
Jan 14, 2022
Merged
Show file tree
Hide file tree
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 @@ -22,14 +22,13 @@ private static IntPtr UserInstanceDLLHandle
if (s_userInstanceDLLHandle == IntPtr.Zero)
{
SNINativeMethodWrapper.SNIQueryInfo(SNINativeMethodWrapper.QTypes.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle);
if(s_userInstanceDLLHandle != IntPtr.Zero)
if (s_userInstanceDLLHandle != IntPtr.Zero)
{
SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained");
}
else
{
SNINativeMethodWrapper.SNI_Error sniError;
SNINativeMethodWrapper.SNIGetLastError(out sniError);
SNINativeMethodWrapper.SNIGetLastError(out SNINativeMethodWrapper.SNI_Error sniError);
throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: (int)sniError.sniError);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,32 @@ namespace Microsoft.Data
{
internal static partial class LocalDBAPI
{
private const string const_localDbPrefix = @"(localdb)\";
private const string LocalDbPrefix = @"(localdb)\";
private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#";


[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen);

// check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
// localDB can also have a format of np:\\.\pipe\LOCALDB#<some number>\tsql\query
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
{
if (serverName == null)
return null;
string instanceName = null;
serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
return null;
string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
if (instanceName.Length == 0)
return null;
else
return instanceName;
bool isLocalDb = serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase) || serverName.StartsWith(LocalDbPrefix_NP, StringComparison.OrdinalIgnoreCase);
if (isLocalDb)
{
if (serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase))
{
instanceName = serverName.Substring(LocalDbPrefix.Length).Trim();
}
else
{
instanceName = serverName.TrimEnd();
}
}
return instanceName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,7 @@ internal SNIError GetLastError()
private static string GetLocalDBDataSource(string fullServerName, out bool error)
{
string localDBConnectionString = null;
bool isBadLocalDBDataSource;
string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out isBadLocalDBDataSource);
string localDBInstance = DataSource.GetLocalDBInstance(fullServerName, out bool isBadLocalDBDataSource);

if (isBadLocalDBDataSource)
{
Expand Down Expand Up @@ -381,6 +380,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 @@ -482,11 +482,9 @@ private void PopulateProtocol()
internal static string GetLocalDBInstance(string dataSource, out bool error)
{
string instanceName = null;

// ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue
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))
{
Expand All @@ -507,6 +505,11 @@ 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 @@ -19,8 +19,9 @@ namespace Microsoft.Data

internal static class LocalDBAPI
{
const string const_localDbPrefix = @"(localdb)\";
const string const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";
private const string LocalDbPrefix = @"(localdb)\";
private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#";
const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST";

static PermissionSet _fullTrust = null;
static bool _partialTrustFlagChecked = false;
Expand All @@ -30,16 +31,21 @@ internal static class LocalDBAPI
// check if name is in format (localdb)\<InstanceName - not empty> and return instance name if it is
internal static string GetLocalDbInstanceNameFromServerName(string serverName)
{
if (serverName == null)
return null;
string instanceName = null;
serverName = serverName.TrimStart(); // it can start with spaces if specified in quotes
if (!serverName.StartsWith(const_localDbPrefix, StringComparison.OrdinalIgnoreCase))
return null;
string instanceName = serverName.Substring(const_localDbPrefix.Length).Trim();
if (instanceName.Length == 0)
return null;
else
return instanceName;
bool isLocalDb = serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase) || serverName.StartsWith(LocalDbPrefix_NP, StringComparison.OrdinalIgnoreCase);
if (isLocalDb)
{
if (serverName.StartsWith(LocalDbPrefix, StringComparison.OrdinalIgnoreCase))
{
instanceName = serverName.Substring(LocalDbPrefix.Length).Trim();
}
else
{
instanceName = serverName.TrimEnd();
}
}
return instanceName;
}


Expand Down Expand Up @@ -261,7 +267,7 @@ internal static void DemandLocalDBPermissions()
{
if (!_partialTrustFlagChecked)
{
object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(const_partialTrustFlagKey);
object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(Const_partialTrustFlagKey);
if (partialTrustFlagValue != null && partialTrustFlagValue is bool)
{
_partialTrustAllowed = (bool)partialTrustFlagValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// 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 System;
using System.Diagnostics;
using System.Threading;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
Expand All @@ -13,14 +15,18 @@ public static class LocalDBTest
private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}";
private static readonly string[] s_sharedLocalDbInstances = new string[] { @$"server=(localdb)\.\{DataTestUtility.LocalDbSharedInstanceName}", @$"server=(localdb)\." };
private static readonly string s_badConnectionString = $@"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;";
private static readonly string s_commandPrompt = "cmd.exe";
private static readonly string s_sqlLocalDbInfo = @$"/c SqlLocalDb info {DataTestUtility.LocalDbAppName}";
private static readonly string s_startLocalDbCommand = @$"/c SqlLocalDb start {DataTestUtility.LocalDbAppName}";
private static readonly string s_localDbNamedPipeConnectionString = @$"server={GetLocalDbNamedPipe()}";

static string LocalDbName = DataTestUtility.LocalDbAppName;
#region LocalDbTests
[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void SqlLocalDbConnectionTest()
{
ConnectionTest(s_localDbConnectionString);
ConnectionTest(s_localDbNamedPipeConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
Expand All @@ -30,13 +36,15 @@ public static void LocalDBEncryptionNotSupportedTest()
// Encryption is not supported by SQL Local DB.
// But connection should succeed as encryption is disabled by driver.
ConnectionWithEncryptionTest(s_localDbConnectionString);
ConnectionWithEncryptionTest(s_localDbNamedPipeConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
[ConditionalFact(nameof(IsLocalDBEnvironmentSet))]
public static void LocalDBMarsTest()
{
ConnectionWithMarsTest(s_localDbConnectionString);
ConnectionWithMarsTest(s_localDbNamedPipeConnectionString);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP
Expand Down Expand Up @@ -123,5 +131,39 @@ private static void OpenConnection(string connString)
var result = command.ExecuteScalar();
Assert.NotNull(result);
}

private static string GetLocalDbNamedPipe()
{
string state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "state");
while (state.Equals("stopped", StringComparison.InvariantCultureIgnoreCase))
{
state = ExecuteLocalDBCommandProcess(s_commandPrompt, s_startLocalDbCommand, "state");
Thread.Sleep(2000);
}
return ExecuteLocalDBCommandProcess(s_commandPrompt, s_sqlLocalDbInfo, "pipeName");
}

private static string ExecuteLocalDBCommandProcess(string filename, string arguments, string infoType)
{
ProcessStartInfo sInfo = new()
{
FileName = filename,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
string[] lines = Process.Start(sInfo).StandardOutput.ReadToEnd().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
if (infoType.Equals("state"))
{
return lines[5].Split(':')[1].Trim();
}
else if (infoType.Equals("pipeName"))
{
return lines[7].Split(new string[] { "Instance pipe name:" }, StringSplitOptions.None)[1].Trim();
}
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"AzureKeyVaultClientSecret": "",
"SupportsIntegratedSecurity": true,
"LocalDbAppName": "",
"LocalDbSharedInstanceName":"",
"SupportsFileStream": false,
"FileStreamDirectory": "",
"UseManagedSNIOnWindows": false,
Expand Down