diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 8ca4487b3a..ca2d41612a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -172,7 +172,7 @@
Microsoft\Data\SqlClient\RowsCopiedEventHandler.cs
-
+
Microsoft\Data\SqlClient\SqlSequentialTextReader.cs
@@ -475,6 +475,9 @@
Microsoft\Data\SqlClient\TdsParameterSetter.cs
+
+ Microsoft\Data\SqlClient\TdsParserStaticMethods.cs
+
Microsoft\Data\SqlClient\TdsRecordBufferSetter.cs
@@ -648,10 +651,12 @@
-
+
+ Microsoft\Data\Common\AdapterUtil.Windows.cs
+
Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs
@@ -664,6 +669,9 @@
+
+ Microsoft\Data\Common\AdapterUtil.Unix.cs
+
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
index dcbc62ef0d..92ef9b6043 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs
@@ -1853,11 +1853,36 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup,
string host = serverInfo.UserServerName;
string protocol = serverInfo.UserProtocol;
- //TODO: fix local host enforcement with datadirectory and failover
- if (options.EnforceLocalHost)
- {
- // verify LocalHost for |DataDirectory| usage
- SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
+ if (aliasLookup)
+ { // We skip this for UserInstances...
+ // Perform registry lookup to see if host is an alias. It will appropriately set host and protocol, if an Alias.
+ // Check if it was already resolved, during CR reconnection _currentSessionData values will be copied from
+ // _reconnectSessonData of the previous connection
+ if (_currentSessionData != null && !string.IsNullOrEmpty(host))
+ {
+ Tuple hostPortPair;
+ if (_currentSessionData._resolvedAliases.TryGetValue(host, out hostPortPair))
+ {
+ host = hostPortPair.Item1;
+ protocol = hostPortPair.Item2;
+ }
+ else
+ {
+ TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
+ _currentSessionData._resolvedAliases.Add(serverInfo.UserServerName, new Tuple(host, protocol));
+ }
+ }
+ else
+ {
+ TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
+ }
+
+ //TODO: fix local host enforcement with datadirectory and failover
+ if (options.EnforceLocalHost)
+ {
+ // verify LocalHost for |DataDirectory| usage
+ SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
+ }
}
serverInfo.SetDerivedNames(protocol, host);
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
index b1e683badc..5d4ff87aa6 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -95,6 +95,9 @@
Microsoft\Data\Common\AdapterUtil.cs
+
+ Microsoft\Data\Common\AdapterUtil.Windows.cs
+
Microsoft\Data\Common\DbConnectionStringCommon.cs
@@ -539,6 +542,9 @@
Microsoft\Data\SqlClient\TdsParameterSetter.cs
+
+ Microsoft\Data\SqlClient\TdsParserStaticMethods.cs
+
Microsoft\Data\SqlClient\TdsRecordBufferSetter.cs
@@ -634,7 +640,6 @@
-
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs
new file mode 100644
index 0000000000..8b84feecfc
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Unix.cs
@@ -0,0 +1,24 @@
+// 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.
+
+namespace Microsoft.Data.Common
+{
+ ///
+ /// The class ADP defines the exceptions that are specific to the Adapters.
+ /// The class contains functions that take the proper informational variables and then construct
+ /// the appropriate exception with an error string obtained from the resource framework.
+ /// The exception is then returned to the caller, so that the caller may then throw from its
+ /// location so that the catcher of the exception will have the appropriate call stack.
+ /// This class is used so that there will be compile time checking of error messages.
+ /// The resource Framework.txt will ensure proper string text based on the appropriate locale.
+ ///
+ internal static partial class ADP
+ {
+ internal static object LocalMachineRegistryValue(string subkey, string queryvalue)
+ {
+ // No registry in non-Windows environments
+ return null;
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Windows.cs
new file mode 100644
index 0000000000..c9d0f8d91a
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.Windows.cs
@@ -0,0 +1,49 @@
+// 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.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Security;
+using System.Security.Permissions;
+using Microsoft.Win32;
+
+namespace Microsoft.Data.Common
+{
+ ///
+ /// The class ADP defines the exceptions that are specific to the Adapters.
+ /// The class contains functions that take the proper informational variables and then construct
+ /// the appropriate exception with an error string obtained from the resource framework.
+ /// The exception is then returned to the caller, so that the caller may then throw from its
+ /// location so that the catcher of the exception will have the appropriate call stack.
+ /// This class is used so that there will be compile time checking of error messages.
+ /// The resource Framework.txt will ensure proper string text based on the appropriate locale.
+ ///
+ internal static partial class ADP
+ {
+ [ResourceExposure(ResourceScope.Machine)]
+ [ResourceConsumption(ResourceScope.Machine)]
+ internal static object LocalMachineRegistryValue(string subkey, string queryvalue)
+ { // MDAC 77697
+ (new RegistryPermission(RegistryPermissionAccess.Read, "HKEY_LOCAL_MACHINE\\" + subkey)).Assert(); // MDAC 62028
+ try
+ {
+ using (RegistryKey key = Registry.LocalMachine.OpenSubKey(subkey, false))
+ {
+ return key?.GetValue(queryvalue);
+ }
+ }
+ catch (SecurityException e)
+ {
+ // Even though we assert permission - it's possible there are
+ // ACL's on registry that cause SecurityException to be thrown.
+ ADP.TraceExceptionWithoutRethrow(e);
+ return null;
+ }
+ finally
+ {
+ RegistryPermission.RevertAssert();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
index d7f6ce7cf0..236bbe3902 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs
@@ -42,7 +42,7 @@ namespace Microsoft.Data.Common
/// This class is used so that there will be compile time checking of error messages.
/// The resource Framework.txt will ensure proper string text based on the appropriate locale.
///
- internal static class ADP
+ internal static partial class ADP
{
// NOTE: Initializing a Task in SQL CLR requires the "UNSAFE" permission set (http://msdn.microsoft.com/en-us/library/ms172338.aspx)
// Therefore we are lazily initializing these Tasks to avoid forcing customers to use the "UNSAFE" set when they are actually using no Async features
@@ -1437,31 +1437,6 @@ internal static string GetComputerNameDnsFullyQualified()
return value;
}
- [ResourceExposure(ResourceScope.Machine)]
- [ResourceConsumption(ResourceScope.Machine)]
- internal static object LocalMachineRegistryValue(string subkey, string queryvalue)
- { // MDAC 77697
- (new RegistryPermission(RegistryPermissionAccess.Read, "HKEY_LOCAL_MACHINE\\" + subkey)).Assert(); // MDAC 62028
- try
- {
- using (RegistryKey key = Registry.LocalMachine.OpenSubKey(subkey, false))
- {
- return key?.GetValue(queryvalue);
- }
- }
- catch (SecurityException e)
- {
- // Even though we assert permission - it's possible there are
- // ACL's on registry that cause SecurityException to be thrown.
- ADP.TraceExceptionWithoutRethrow(e);
- return null;
- }
- finally
- {
- RegistryPermission.RevertAssert();
- }
- }
-
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static IntPtr IntPtrOffset(IntPtr pbase, int offset)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
index f1a488c4b6..aac7c04913 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
@@ -1102,20 +1102,6 @@ internal PoolBlockingPeriod ConvertValueToPoolBlockingPeriod()
}
}
-#if NETFRAMEWORK
- protected internal override PermissionSet CreatePermissionSet()
- {
- PermissionSet permissionSet = new(PermissionState.None);
- permissionSet.AddPermission(new SqlClientPermission(this));
- return permissionSet;
- }
-
- internal bool ConvertValueToEncrypt()
- {
- bool defaultEncryptValue = !Parsetable.ContainsKey(KEY.Authentication) ? DEFAULT.Encrypt : true;
- return ConvertValueToBoolean(KEY.Encrypt, defaultEncryptValue);
- }
-
static internal Hashtable NetlibMapping()
{
const int NetLibCount = 8;
@@ -1166,6 +1152,21 @@ internal static class NETLIB
}
private static Hashtable s_netlibMapping;
+
+#if NETFRAMEWORK
+ protected internal override PermissionSet CreatePermissionSet()
+ {
+ PermissionSet permissionSet = new(PermissionState.None);
+ permissionSet.AddPermission(new SqlClientPermission(this));
+ return permissionSet;
+ }
+
+ internal bool ConvertValueToEncrypt()
+ {
+ bool defaultEncryptValue = !Parsetable.ContainsKey(KEY.Authentication) ? DEFAULT.Encrypt : true;
+ return ConvertValueToBoolean(KEY.Encrypt, defaultEncryptValue);
+ }
+
private readonly bool _connectionReset;
private readonly bool _contextConnection;
private readonly bool _transparentNetworkIPResolution;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
similarity index 67%
rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
index 7be7f61bb4..93aac3fdea 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs
@@ -3,10 +3,10 @@
// See the LICENSE file in the project root for more information.
using System;
-using Microsoft.Data.Common;
using System.Globalization;
+using System.Runtime.InteropServices;
using System.Runtime.Versioning;
-using System.Security.Permissions;
+using Microsoft.Data.Common;
namespace Microsoft.Data.SqlClient
{
@@ -88,12 +88,19 @@ static internal void AliasRegistryLookup(ref string host, ref string protocol)
}
}
- // Encrypt password to be sent to SQL Server
+ // Obfuscate password to be sent to SQL Server
+ // Blurb from the TDS spec at https://msdn.microsoft.com/en-us/library/dd304523.aspx
+ // "Before submitting a password from the client to the server, for every byte in the password buffer
+ // starting with the position pointed to by IbPassword, the client SHOULD first swap the four high bits
+ // with the four low bits and then do a bit-XOR with 0xA5 (10100101). After reading a submitted password,
+ // for every byte in the password buffer starting with the position pointed to by IbPassword, the server SHOULD
+ // first do a bit-XOR with 0xA5 (10100101) and then swap the four high bits with the four low bits."
+ // The password exchange during Login phase happens over a secure channel i.e. SSL/TLS
// Note: The same logic is used in SNIPacketSetData (SniManagedWrapper) to encrypt passwords stored in SecureString
// If this logic changed, SNIPacketSetData needs to be changed as well
internal static byte[] ObfuscatePassword(string password)
{
- byte[] bEnc = new byte[password.Length << 1];
+ byte[] bObfuscated = new byte[password.Length << 1];
int s;
byte bLo;
byte bHi;
@@ -103,62 +110,92 @@ internal static byte[] ObfuscatePassword(string password)
s = (int)password[i];
bLo = (byte)(s & 0xff);
bHi = (byte)((s >> 8) & 0xff);
- bEnc[i << 1] = (byte)((((bLo & 0x0f) << 4) | (bLo >> 4)) ^ 0xa5);
- bEnc[(i << 1) + 1] = (byte)((((bHi & 0x0f) << 4) | (bHi >> 4)) ^ 0xa5);
+ bObfuscated[i << 1] = (byte)((((bLo & 0x0f) << 4) | (bLo >> 4)) ^ 0xa5);
+ bObfuscated[(i << 1) + 1] = (byte)((((bHi & 0x0f) << 4) | (bHi >> 4)) ^ 0xa5);
}
- return bEnc;
+ return bObfuscated;
}
- [ResourceExposure(ResourceScope.None)] // SxS: we use this method for TDS login only
- [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
- static internal int GetCurrentProcessIdForTdsLoginOnly()
+ internal static byte[] ObfuscatePassword(byte[] password)
{
- return Common.SafeNativeMethods.GetCurrentProcessId();
+ byte bLo;
+ byte bHi;
+
+ for (int i = 0; i < password.Length; i++)
+ {
+ bLo = (byte)(password[i] & 0x0f);
+ bHi = (byte)(password[i] & 0xf0);
+ password[i] = (byte)(((bHi >> 4) | (bLo << 4)) ^ 0xa5);
+ }
+ return password;
+ }
+
+ private const int NoProcessId = -1;
+ private static int s_currentProcessId = NoProcessId;
+ internal static int GetCurrentProcessIdForTdsLoginOnly()
+ {
+ if (s_currentProcessId == NoProcessId)
+ {
+ // Pick up the process Id from the current process instead of randomly generating it.
+ // This would be helpful while tracing application related issues.
+ int processId;
+ using (System.Diagnostics.Process p = System.Diagnostics.Process.GetCurrentProcess())
+ {
+ processId = p.Id;
+ }
+ System.Threading.Volatile.Write(ref s_currentProcessId, processId);
+ }
+ return s_currentProcessId;
}
- [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)]
- [ResourceExposure(ResourceScope.None)] // SxS: we use this method for TDS login only
- [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
- static internal Int32 GetCurrentThreadIdForTdsLoginOnly()
+ internal static int GetCurrentThreadIdForTdsLoginOnly()
{
-#pragma warning disable 618
- return AppDomain.GetCurrentThreadId(); // don't need this to be support fibres;
-#pragma warning restore 618
+ return Environment.CurrentManagedThreadId;
}
+ private static byte[] s_nicAddress = null;
[ResourceExposure(ResourceScope.None)] // SxS: we use MAC address for TDS login only
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly()
{
- // NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key
- // has a value that is not zero, then we cannot use the NetworkAddress key and must
- // instead generate a random one. I do not fully understand why, this is simply what
- // the native providers do. As for generation, I use a random number generator, which
- // means that different processes on the same machine will have different NIC address
- // values on the server. It is not ideal, but native does not have the same value for
- // different processes either.
-
- const string key = "NetworkAddress";
- const string localKey = "NetworkAddressLocal";
- const string folder = "SOFTWARE\\Description\\Microsoft\\Rpc\\UuidTemporaryData";
-
- int result = 0;
- byte[] nicAddress = null;
-
- object temp = ADP.LocalMachineRegistryValue(folder, localKey);
- if (temp is int)
+ if (s_nicAddress != null)
{
- result = (int)temp;
+ return s_nicAddress;
}
- if (result <= 0)
+ byte[] nicAddress = null;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- temp = ADP.LocalMachineRegistryValue(folder, key);
- if (temp is byte[])
+ // NIC address is stored in NetworkAddress key. However, if NetworkAddressLocal key
+ // has a value that is not zero, then we cannot use the NetworkAddress key and must
+ // instead generate a random one. I do not fully understand why, this is simply what
+ // the native providers do. As for generation, I use a random number generator, which
+ // means that different processes on the same machine will have different NIC address
+ // values on the server. It is not ideal, but native does not have the same value for
+ // different processes either.
+
+ const string key = "NetworkAddress";
+ const string localKey = "NetworkAddressLocal";
+ const string folder = "SOFTWARE\\Description\\Microsoft\\Rpc\\UuidTemporaryData";
+
+ int result = 0;
+
+ object temp = ADP.LocalMachineRegistryValue(folder, localKey);
+ if (temp is int)
{
- nicAddress = (byte[])temp;
+ result = (int)temp;
+ }
+
+ if (result <= 0)
+ {
+ temp = ADP.LocalMachineRegistryValue(folder, key);
+ if (temp is byte[])
+ {
+ nicAddress = (byte[])temp;
+ }
}
}
@@ -169,7 +206,9 @@ static internal byte[] GetNetworkPhysicalAddressForTdsLoginOnly()
random.NextBytes(nicAddress);
}
- return nicAddress;
+ System.Threading.Interlocked.CompareExchange(ref s_nicAddress, nicAddress, null);
+
+ return s_nicAddress;
}
// translates remaining time in stateObj (from user specified timeout) to timeout value for SNI
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
index b6d2f73532..9e5b10cdd7 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
@@ -15,8 +15,8 @@
using System.Security;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Identity.Client;
using Microsoft.Data.SqlClient.TestUtilities;
+using Microsoft.Identity.Client;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -296,6 +296,11 @@ public static bool AreConnStringsSetup()
return !string.IsNullOrEmpty(NPConnectionString) && !string.IsNullOrEmpty(TCPConnectionString);
}
+ public static bool IsTCPConnStringSetup()
+ {
+ return !string.IsNullOrEmpty(TCPConnectionString);
+ }
+
// Synapse: Always Encrypted is not supported with Azure Synapse.
// Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/17858869-support-always-encrypted-in-sql-data-warehouse
public static bool AreConnStringSetupForAE()
@@ -828,6 +833,40 @@ public static string RetrieveValueFromConnStr(string connStr, string[] keywords)
return res;
}
+ public static bool ParseDataSource(string dataSource, out string hostname, out int port, out string instanceName)
+ {
+ hostname = string.Empty;
+ port = -1;
+ instanceName = string.Empty;
+
+ if (dataSource.Contains(",") && dataSource.Contains("\\"))
+ return false;
+
+ if (dataSource.Contains(":"))
+ {
+ dataSource = dataSource.Substring(dataSource.IndexOf(":") + 1);
+ }
+
+ if (dataSource.Contains(","))
+ {
+ if (!Int32.TryParse(dataSource.Substring(dataSource.LastIndexOf(",") + 1), out port))
+ {
+ return false;
+ }
+ dataSource = dataSource.Substring(0, dataSource.IndexOf(",") - 1);
+ }
+
+ if (dataSource.Contains("\\"))
+ {
+ instanceName = dataSource.Substring(dataSource.LastIndexOf("\\") + 1);
+ dataSource = dataSource.Substring(0, dataSource.LastIndexOf("\\"));
+ }
+
+ hostname = dataSource;
+
+ return hostname.Length > 0 && hostname.IndexOfAny(new char[] { '\\', ':', ',' }) == -1;
+ }
+
public class AKVEventListener : BaseEventListener
{
public override string Name => AKVEventSourceName;
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
index 41a52f48ac..32115b96d1 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs
@@ -5,7 +5,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
using System.Threading;
+using Microsoft.Win32;
using Xunit;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
@@ -368,5 +371,76 @@ public static void ConnectionOpenDisableRetry()
duration = timer.Elapsed;
Assert.True(duration.Seconds > 5, $"Connection Open() with retries took less time than expected. Expect > 5 sec with transient fault handling. Took {duration.Seconds} sec."); // sqlConnection.Open();
}
+
+ private const string ConnectToPath = "SOFTWARE\\Microsoft\\MSSQLServer\\Client\\ConnectTo";
+ private static bool CanCreateAliases()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
+ !DataTestUtility.IsTCPConnStringSetup())
+ {
+ return false;
+ }
+
+ using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
+ {
+ WindowsPrincipal principal = new(identity);
+ if (!principal.IsInRole(WindowsBuiltInRole.Administrator))
+ {
+ return false;
+ }
+ }
+
+ using RegistryKey key = Registry.LocalMachine.OpenSubKey(ConnectToPath, true);
+ if (key == null)
+ {
+ // Registry not writable
+ return false;
+ }
+
+ SqlConnectionStringBuilder b = new(DataTestUtility.TCPConnectionString);
+ if (!DataTestUtility.ParseDataSource(b.DataSource, out string hostname, out int port, out string instanceName) ||
+ !string.IsNullOrEmpty(instanceName))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ [PlatformSpecific(TestPlatforms.Windows)]
+ [ConditionalFact(nameof(CanCreateAliases))]
+ public static void ConnectionAliasTest()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ throw new Exception("Alias test only valid on Windows");
+ }
+
+ if (!CanCreateAliases())
+ {
+ throw new Exception("Unable to create aliases in this environment. Windows + Admin + non-instance data source required.");
+ }
+
+ SqlConnectionStringBuilder b = new(DataTestUtility.TCPConnectionString);
+ if (!DataTestUtility.ParseDataSource(b.DataSource, out string hostname, out int port, out string instanceName) ||
+ !string.IsNullOrEmpty(instanceName))
+ {
+ // Only works with connection strings that parse successfully and don't include an instance name
+ throw new Exception("Unable to create aliases in this configuration. Parsable data source without instance required.");
+ }
+
+ b.DataSource = "TESTALIAS-" + Guid.NewGuid().ToString().Replace("-", "");
+ using RegistryKey key = Registry.LocalMachine.OpenSubKey(ConnectToPath, true);
+ key.SetValue(b.DataSource, "DBMSSOCN," + hostname + "," + (port == -1 ? 1433 : port));
+ try
+ {
+ using SqlConnection sqlConnection = new(b.ConnectionString);
+ sqlConnection.Open();
+ }
+ finally
+ {
+ key.DeleteValue(b.DataSource);
+ }
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs
index 8079530796..1c1869f7f1 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs
@@ -47,7 +47,7 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove
builder.MultiSubnetFailover = useMultiSubnetFailover;
builder.IPAddressPreference = ipPreference;
- Assert.True(ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName), "Invalid data source.");
+ Assert.True(DataTestUtility.ParseDataSource(builder.DataSource, out string hostname, out _, out string instanceName));
if (IsBrowserAlive(hostname) && IsValidInstance(hostname, instanceName))
{
@@ -69,40 +69,6 @@ public static void ConnectManagedWithInstanceNameTest(bool useMultiSubnetFailove
}
}
- private static bool ParseDataSource(string dataSource, out string hostname, out int port, out string instanceName)
- {
- hostname = string.Empty;
- port = -1;
- instanceName = string.Empty;
-
- if (dataSource.Contains(",") && dataSource.Contains("\\"))
- return false;
-
- if (dataSource.Contains(":"))
- {
- dataSource = dataSource.Substring(dataSource.IndexOf(":") + 1);
- }
-
- if (dataSource.Contains(","))
- {
- if (!int.TryParse(dataSource.Substring(dataSource.LastIndexOf(",") + 1), out port))
- {
- return false;
- }
- dataSource = dataSource.Substring(0, dataSource.IndexOf(",") - 1);
- }
-
- if (dataSource.Contains("\\"))
- {
- instanceName = dataSource.Substring(dataSource.LastIndexOf("\\") + 1);
- dataSource = dataSource.Substring(0, dataSource.LastIndexOf("\\"));
- }
-
- hostname = dataSource;
-
- return hostname.Length > 0 && hostname.IndexOfAny(new char[] { '\\', ':', ',' }) == -1;
- }
-
private static bool IsBrowserAlive(string browserHostname)
{
const byte ClntUcastEx = 0x03;