diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml
new file mode 100644
index 0000000000..e713cb776b
--- /dev/null
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml
@@ -0,0 +1,40 @@
+
+
+
+
+ Specifies a value for IP address preference during a TCP connection.
+
+
+
+
+
+
+
+ Specifies a value for IP address preference during a TCP connection.
+
+
+
+
+
+
+ Connects using IPv4 address(es) first. If the connection fails, try IPv6 address(es), if provided. This is the default value.
+ 0
+
+
+ Connect using IPv6 address(es) first. If the connection fails, try IPv4 address(es), if available.
+ 1
+
+
+ Connects with IP addresses in the order the underlying platform or operating system provides them.
+ 2
+
+
+
diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml
index 46e9ee6877..619ea2d690 100644
--- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml
+++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml
@@ -346,6 +346,17 @@ False
To set the value to null, use .
+
+ Gets or sets the value of IP address preference.
+ Returns IP address preference.
+
+
+
+
Gets or sets the enclave attestation Url to be used with enclave based Always Encrypted.
The enclave attestation Url.
diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
index e630a03acb..17bc6c59c4 100644
--- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs
@@ -398,6 +398,18 @@ public enum SqlConnectionAttestationProtocol
HGS = 3
}
#endif
+ ///
+ public enum SqlConnectionIPAddressPreference
+ {
+ ///
+ IPv4First = 0, // default
+
+ ///
+ IPv6First = 1,
+
+ ///
+ UsePlatformDefault = 2
+ }
///
public partial class SqlColumnEncryptionCertificateStoreProvider : Microsoft.Data.SqlClient.SqlColumnEncryptionKeyStoreProvider
{
@@ -883,6 +895,10 @@ public SqlConnectionStringBuilder(string connectionString) { }
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public string EnclaveAttestationUrl { get { throw null; } set { } }
#endif
+ ///
+ [System.ComponentModel.DisplayNameAttribute("IP Address Preference")]
+ [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
+ public Microsoft.Data.SqlClient.SqlConnectionIPAddressPreference IPAddressPreference { get { throw null; } set { } }
///
[System.ComponentModel.DisplayNameAttribute("Encrypt")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
index 8201ec41aa..20159ca382 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs
@@ -165,6 +165,7 @@ private unsafe struct SNI_CLIENT_CONSUMER_INFO
public TransparentNetworkResolutionMode transparentNetworkResolution;
public int totalTimeout;
public bool isAzureSqlServerEndpoint;
+ public SqlConnectionIPAddressPreference ipAddressPreference;
public SNI_DNSCache_Info DNSCacheInfo;
}
@@ -275,6 +276,7 @@ private static extern uint SNIOpenWrapper(
[In] SNIHandle pConn,
out IntPtr ppConn,
[MarshalAs(UnmanagedType.Bool)] bool fSync,
+ SqlConnectionIPAddressPreference ipPreference,
[In] ref SNI_DNSCache_Info pDNSCachedInfo);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
@@ -341,7 +343,7 @@ internal static uint SNIInitialize()
return SNIInitialize(IntPtr.Zero);
}
- internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SQLDNSInfo cachedDNSInfo)
+ internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo)
{
// initialize consumer info for MARS
Sni_Consumer_Info native_consumerInfo = new Sni_Consumer_Info();
@@ -353,10 +355,11 @@ internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHan
native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port;
- return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ref native_cachedDNSInfo);
+ return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ipPreference, ref native_cachedDNSInfo);
}
- internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, bool fSync, int timeout, bool fParallel, SQLDNSInfo cachedDNSInfo)
+ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache,
+ bool fSync, int timeout, bool fParallel, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo)
{
fixed (byte* pin_instanceName = &instanceName[0])
{
@@ -379,6 +382,7 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons
clientConsumerInfo.totalTimeout = SniOpenTimeOut;
clientConsumerInfo.isAzureSqlServerEndpoint = ADP.IsAzureSqlServerEndpoint(constring);
+ clientConsumerInfo.ipAddressPreference = ipPreference;
clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN;
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4;
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
index eb23fcdfef..3c22c4ecd8 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
@@ -400,6 +401,110 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st
#endregion
+ #region <>
+ ///
+ /// IP Address Preference.
+ ///
+ private readonly static Dictionary s_preferenceNames = new(StringComparer.InvariantCultureIgnoreCase);
+
+ static DbConnectionStringBuilderUtil()
+ {
+ foreach (SqlConnectionIPAddressPreference item in Enum.GetValues(typeof(SqlConnectionIPAddressPreference)))
+ {
+ s_preferenceNames.Add(item.ToString(), item);
+ }
+ }
+
+ ///
+ /// Convert a string value to the corresponding IPAddressPreference.
+ ///
+ /// The string representation of the enumeration name to convert.
+ /// When this method returns, `result` contains an object of type `SqlConnectionIPAddressPreference` whose value is represented by `value` if the operation succeeds.
+ /// If the parse operation fails, `result` contains the default value of the `SqlConnectionIPAddressPreference` type.
+ /// `true` if the value parameter was converted successfully; otherwise, `false`.
+ internal static bool TryConvertToIPAddressPreference(string value, out SqlConnectionIPAddressPreference result)
+ {
+ if (!s_preferenceNames.TryGetValue(value, out result))
+ {
+ result = DbConnectionStringDefaults.IPAddressPreference;
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Verifies if the `value` is defined in the expected Enum.
+ ///
+ internal static bool IsValidIPAddressPreference(SqlConnectionIPAddressPreference value)
+ => value == SqlConnectionIPAddressPreference.IPv4First
+ || value == SqlConnectionIPAddressPreference.IPv6First
+ || value == SqlConnectionIPAddressPreference.UsePlatformDefault;
+
+ internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value)
+ => Enum.GetName(typeof(SqlConnectionIPAddressPreference), value);
+
+ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value)
+ {
+ if (value is null)
+ {
+ return DbConnectionStringDefaults.IPAddressPreference; // IPv4First
+ }
+
+ if (value is string sValue)
+ {
+ // try again after remove leading & trailing whitespaces.
+ sValue = sValue.Trim();
+ if (TryConvertToIPAddressPreference(sValue, out SqlConnectionIPAddressPreference result))
+ {
+ return result;
+ }
+
+ // string values must be valid
+ throw ADP.InvalidConnectionOptionValue(keyword);
+ }
+ else
+ {
+ // the value is not string, try other options
+ SqlConnectionIPAddressPreference eValue;
+
+ if (value is SqlConnectionIPAddressPreference preference)
+ {
+ eValue = preference;
+ }
+ else if (value.GetType().IsEnum)
+ {
+ // explicitly block scenarios in which user tries to use wrong enum types, like:
+ // builder["SqlConnectionIPAddressPreference"] = EnvironmentVariableTarget.Process;
+ // workaround: explicitly cast non-SqlConnectionIPAddressPreference enums to int
+ throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), null);
+ }
+ else
+ {
+ try
+ {
+ // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest
+ eValue = (SqlConnectionIPAddressPreference)Enum.ToObject(typeof(SqlConnectionIPAddressPreference), value);
+ }
+ catch (ArgumentException e)
+ {
+ // to be consistent with the messages we send in case of wrong type usage, replace
+ // the error with our exception, and keep the original one as inner one for troubleshooting
+ throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), e);
+ }
+ }
+
+ if (IsValidIPAddressPreference(eValue))
+ {
+ return eValue;
+ }
+ else
+ {
+ throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)eValue);
+ }
+ }
+ }
+ #endregion
+
internal static bool IsValidApplicationIntentValue(ApplicationIntent value)
{
Debug.Assert(Enum.GetNames(typeof(ApplicationIntent)).Length == 2, "ApplicationIntent enum has changed, update needed");
@@ -728,6 +833,7 @@ internal static partial class DbConnectionStringDefaults
internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = _emptyString;
internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
+ internal const SqlConnectionIPAddressPreference IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First;
}
@@ -765,6 +871,7 @@ internal static partial class DbConnectionStringKeywords
internal const string ColumnEncryptionSetting = "Column Encryption Setting";
internal const string EnclaveAttestationUrl = "Enclave Attestation Url";
internal const string AttestationProtocol = "Attestation Protocol";
+ internal const string IPAddressPreference = "IP Address Preference";
// common keywords (OleDb, OracleClient, SqlClient)
internal const string DataSource = "Data Source";
@@ -793,6 +900,9 @@ internal static class DbConnectionStringSynonyms
//internal const string ApplicationName = APP;
internal const string APP = "app";
+ // internal const string IPAddressPreference = IPADDRESSPREFERENCE;
+ internal const string IPADDRESSPREFERENCE = "IPAddressPreference";
+
//internal const string ApplicationIntent = APPLICATIONINTENT;
internal const string APPLICATIONINTENT = "ApplicationIntent";
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
index e05b7498f8..5823e7f44c 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs
@@ -254,10 +254,12 @@ internal uint WritePacket(SNIHandle handle, SNIPacket packet, bool sync)
/// Asynchronous connection
/// Attempt parallel connects
///
+ /// IP address preference
/// Used for DNS Cache
- /// Used for DNS Cache
+ /// Used for DNS Cache
/// SNI handle
- internal SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
+ internal 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];
@@ -284,7 +286,7 @@ internal SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniO
case DataSource.Protocol.Admin:
case DataSource.Protocol.None: // default to using tcp if no protocol is provided
case DataSource.Protocol.TCP:
- sniHandle = CreateTcpHandle(details, timerExpire, parallel, cachedFQDN, ref pendingDNSInfo);
+ sniHandle = CreateTcpHandle(details, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo);
break;
case DataSource.Protocol.NP:
sniHandle = CreateNpHandle(details, timerExpire, parallel);
@@ -374,10 +376,11 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr
/// Data source
/// Timer expiration
/// Should MultiSubnetFailover be used
+ /// IP address preference
/// Key for DNS Cache
- /// Used for DNS Cache
+ /// Used for DNS Cache
/// SNITCPHandle
- private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, bool parallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
+ private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, bool parallel, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
{
// TCP Format:
// tcp:\
@@ -415,7 +418,7 @@ private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, bool
port = isAdminConnection ? DefaultSqlServerDacPort : DefaultSqlServerPort;
}
- return new SNITCPHandle(hostName, port, timerExpire, parallel, cachedFQDN, ref pendingDNSInfo);
+ return new SNITCPHandle(hostName, port, timerExpire, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo);
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
index 4ca0631c51..d2a8341c0f 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs
@@ -116,9 +116,10 @@ public override int ProtocolVersion
/// TCP port number
/// Connection timer expiration
/// Parallel executions
+ /// IP address preference
/// Key for DNS Cache
- /// Used for DNS Cache
- public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
+ /// Used for DNS Cache
+ public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
{
long scopeID = SqlClientEventSource.Log.TrySNIScopeEnterEvent(s_className);
SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connection Id {0}, Setting server name = {1}", args0: _connectionId, args1: serverName);
@@ -147,8 +148,8 @@ public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel
SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connection Id {0}, Connecting to serverName {1} and port {2}", args0: _connectionId, args1: serverName, args2: port);
// We will always first try to connect with serverName as before and let the DNS server to resolve the serverName.
- // If the DSN resolution fails, we will try with IPs in the DNS cache if existed. We try with IPv4 first and followed by IPv6 if
- // IPv4 fails. The exceptions will be throw to upper level and be handled as before.
+ // If the DSN resolution fails, we will try with IPs in the DNS cache if existed. We try with cached IPs based on IPAddressPreference.
+ // The exceptions will be throw to upper level and be handled as before.
try
{
if (parallel)
@@ -157,7 +158,7 @@ public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel
}
else
{
- _socket = Connect(serverName, port, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo);
+ _socket = Connect(serverName, port, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo);
}
}
catch (Exception ex)
@@ -175,15 +176,26 @@ public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel
int portRetry = string.IsNullOrEmpty(cachedDNSInfo.Port) ? port : int.Parse(cachedDNSInfo.Port);
SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connection Id {0}, Retrying with cached DNS IP Address {1} and port {2}", args0: _connectionId, args1: cachedDNSInfo.AddrIPv4, args2: cachedDNSInfo.Port);
+ string firstCachedIP;
+ string secondCachedIP;
+
+ if (SqlConnectionIPAddressPreference.IPv6First == ipPreference) {
+ firstCachedIP = cachedDNSInfo.AddrIPv6;
+ secondCachedIP = cachedDNSInfo.AddrIPv4;
+ } else {
+ firstCachedIP = cachedDNSInfo.AddrIPv4;
+ secondCachedIP = cachedDNSInfo.AddrIPv6;
+ }
+
try
{
if (parallel)
{
- _socket = TryConnectParallel(cachedDNSInfo.AddrIPv4, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo);
+ _socket = TryConnectParallel(firstCachedIP, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo);
}
else
{
- _socket = Connect(cachedDNSInfo.AddrIPv4, portRetry, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo);
+ _socket = Connect(firstCachedIP, portRetry, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo);
}
}
catch (Exception exRetry)
@@ -194,11 +206,11 @@ public SNITCPHandle(string serverName, int port, long timerExpire, bool parallel
SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connection Id {0}, Retrying exception {1}", args0: _connectionId, args1: exRetry?.Message);
if (parallel)
{
- _socket = TryConnectParallel(cachedDNSInfo.AddrIPv6, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo);
+ _socket = TryConnectParallel(secondCachedIP, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo);
}
else
{
- _socket = Connect(cachedDNSInfo.AddrIPv6, portRetry, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo);
+ _socket = Connect(secondCachedIP, portRetry, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo);
}
}
else
@@ -320,42 +332,37 @@ private Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool i
// Connect to server with hostName and port.
// The IP information will be collected temporarily as the pendingDNSInfo but is not stored in the DNS cache at this point.
// Only write to the DNS cache when we receive IsSupported flag as true in the Feature Ext Ack from server.
- private static Socket Connect(string serverName, int port, TimeSpan timeout, bool isInfiniteTimeout, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
+ private static Socket Connect(string serverName, int port, TimeSpan timeout, bool isInfiniteTimeout, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
{
+ SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "IP preference : {0}", Enum.GetName(typeof(SqlConnectionIPAddressPreference), ipPreference));
+
IPAddress[] ipAddresses = Dns.GetHostAddresses(serverName);
string IPv4String = null;
- string IPv6String = null;
-
+ string IPv6String = null;
+
// Returning null socket is handled by the caller function.
- if(ipAddresses == null || ipAddresses.Length == 0)
+ if (ipAddresses == null || ipAddresses.Length == 0)
{
return null;
}
Socket[] sockets = new Socket[ipAddresses.Length];
- AddressFamily[] preferedIPFamilies = new AddressFamily[] { AddressFamily.InterNetwork, AddressFamily.InterNetworkV6 };
-
- CancellationTokenSource cts = null;
+ AddressFamily[] preferedIPFamilies = new AddressFamily[2];
- void Cancel()
+ if (ipPreference == SqlConnectionIPAddressPreference.IPv4First)
{
- for (int i = 0; i < sockets.Length; ++i)
- {
- try
- {
- if (sockets[i] != null && !sockets[i].Connected)
- {
- sockets[i].Dispose();
- sockets[i] = null;
- }
- }
- catch (Exception e)
- {
- SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.ERR, "THIS EXCEPTION IS BEING SWALLOWED: {0}", args0: e?.Message);
- }
- }
+ preferedIPFamilies[0] = AddressFamily.InterNetwork;
+ preferedIPFamilies[1] = AddressFamily.InterNetworkV6;
}
+ else if (ipPreference == SqlConnectionIPAddressPreference.IPv6First)
+ {
+ preferedIPFamilies[0] = AddressFamily.InterNetworkV6;
+ preferedIPFamilies[1] = AddressFamily.InterNetwork;
+ }
+ // else -> UsePlatformDefault
+
+ CancellationTokenSource cts = null;
if (!isInfiniteTimeout)
{
@@ -366,32 +373,39 @@ void Cancel()
Socket availableSocket = null;
try
{
- int n = 0; // Socket index
-
// We go through the IP list twice.
// In the first traversal, we only try to connect with the preferedIPFamilies[0].
// In the second traversal, we only try to connect with the preferedIPFamilies[1].
+ // For UsePlatformDefault preference, we do traversal once.
for (int i = 0; i < preferedIPFamilies.Length; ++i)
{
- foreach (IPAddress ipAddress in ipAddresses)
+ for (int n = 0; n < ipAddresses.Length; n++)
{
+ IPAddress ipAddress = ipAddresses[n];
try
{
- if (ipAddress != null && ipAddress.AddressFamily == preferedIPFamilies[i])
+ if (ipAddress != null)
{
+ if (ipAddress.AddressFamily != preferedIPFamilies[i] && ipPreference != SqlConnectionIPAddressPreference.UsePlatformDefault)
+ {
+ continue;
+ }
+
sockets[n] = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
// enable keep-alive on socket
SetKeepAliveValues(ref sockets[n]);
- SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connecting to IP address {0} and port {1}", args0: ipAddress, args1: port);
+ SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connecting to IP address {0} and port {1} using {2} address family.",
+ args0: ipAddress,
+ args1: port,
+ args2: ipAddress.AddressFamily);
sockets[n].Connect(ipAddress, port);
- if (sockets[n] != null) // sockets[i] can be null if cancel callback is executed during connect()
+ if (sockets[n] != null) // sockets[n] can be null if cancel callback is executed during connect()
{
if (sockets[n].Connected)
{
availableSocket = sockets[n];
-
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
IPv4String = ipAddress.ToString();
@@ -409,20 +423,21 @@ void Cancel()
sockets[n] = null;
}
}
- n++;
}
}
catch (Exception e)
{
SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.ERR, "THIS EXCEPTION IS BEING SWALLOWED: {0}", args0: e?.Message);
+ SqlClientEventSource.Log.TryAdvancedTraceEvent($"{s_className}.{System.Reflection.MethodBase.GetCurrentMethod().Name}{EventType.ERR}THIS EXCEPTION IS BEING SWALLOWED: {e}");
}
}
- // If we have already got an valid Socket, we won't do the second traversal.
- if (availableSocket != null)
+ // If we have already got a valid Socket, or the platform default was prefered
+ // we won't do the second traversal.
+ if (availableSocket != null || ipPreference == SqlConnectionIPAddressPreference.UsePlatformDefault)
{
break;
- }
+ }
}
}
finally
@@ -437,6 +452,25 @@ void Cancel()
}
return availableSocket;
+
+ void Cancel()
+ {
+ for (int i = 0; i < sockets.Length; ++i)
+ {
+ try
+ {
+ if (sockets[i] != null && !sockets[i].Connected)
+ {
+ sockets[i].Dispose();
+ sockets[i] = null;
+ }
+ }
+ catch (Exception e)
+ {
+ SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.ERR, "THIS EXCEPTION IS BEING SWALLOWED: {0}", args0: e?.Message);
+ }
+ }
+ }
}
private static Task ParallelConnectAsync(IPAddress[] serverAddresses, int port)
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
index 7cdd3b56d5..d89f841b48 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -391,6 +391,14 @@ internal SqlConnectionAttestationProtocol AttestationProtocol
}
}
+ ///
+ /// Get IP address preference
+ ///
+ internal SqlConnectionIPAddressPreference iPAddressPreference
+ {
+ get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference;
+ }
+
// This method will be called once connection string is set or changed.
private void CacheConnectionStringProperties()
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
index aa9023139c..d2d2cf7891 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
@@ -53,6 +53,7 @@ internal static partial class DEFAULT
internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = _emptyString;
internal static readonly SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
+ internal static readonly SqlConnectionIPAddressPreference s_IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First;
}
// SqlConnection ConnectionString Options
@@ -69,6 +70,7 @@ internal static class KEY
internal const string ColumnEncryptionSetting = "column encryption setting";
internal const string EnclaveAttestationUrl = "enclave attestation url";
internal const string AttestationProtocol = "attestation protocol";
+ internal const string IPAddressPreference = "ip address preference";
internal const string Command_Timeout = "command timeout";
internal const string Connect_Timeout = "connect timeout";
@@ -106,6 +108,8 @@ internal static class KEY
// Constant for the number of duplicate options in the connection string
private static class SYNONYM
{
+ // ip address preference
+ internal const string IPADDRESSPREFERENCE = "ipaddresspreference";
//application intent
internal const string APPLICATIONINTENT = "applicationintent";
// application name
@@ -160,9 +164,9 @@ private static class SYNONYM
}
#if NETCOREAPP
- internal const int SynonymCount = 25;
+ internal const int SynonymCount = 26;
#else
- internal const int SynonymCount = 24;
+ internal const int SynonymCount = 25;
#endif
internal const int DeprecatedSynonymCount = 3;
@@ -213,6 +217,7 @@ internal static class TRANSACTIONBINDING
private readonly SqlConnectionColumnEncryptionSetting _columnEncryptionSetting;
private readonly string _enclaveAttestationUrl;
private readonly SqlConnectionAttestationProtocol _attestationProtocol;
+ private readonly SqlConnectionIPAddressPreference _ipAddressPreference;
private readonly int _commandTimeout;
private readonly int _connectTimeout;
@@ -293,6 +298,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G
_columnEncryptionSetting = ConvertValueToColumnEncryptionSetting();
_enclaveAttestationUrl = ConvertValueToString(KEY.EnclaveAttestationUrl, DEFAULT.EnclaveAttestationUrl);
_attestationProtocol = ConvertValueToAttestationProtocol();
+ _ipAddressPreference = ConvertValueToIPAddressPreference();
// Temporary string - this value is stored internally as an enum.
string typeSystemVersionString = ConvertValueToString(KEY.Type_System_Version, null);
@@ -559,6 +565,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS
internal SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting { get { return _columnEncryptionSetting; } }
internal string EnclaveAttestationUrl { get { return _enclaveAttestationUrl; } }
internal SqlConnectionAttestationProtocol AttestationProtocol { get { return _attestationProtocol; } }
+ internal SqlConnectionIPAddressPreference IPAddressPreference => _ipAddressPreference;
internal bool PersistSecurityInfo { get { return _persistSecurityInfo; } }
internal bool Pooling { get { return _pooling; } }
internal bool Replication { get { return _replication; } }
@@ -687,6 +694,7 @@ internal static Dictionary GetParseSynonyms()
{ KEY.Connect_Retry_Count, KEY.Connect_Retry_Count },
{ KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval },
{ KEY.Authentication, KEY.Authentication },
+ { KEY.IPAddressPreference, KEY.IPAddressPreference },
{ SYNONYM.APP, KEY.Application_Name },
{ SYNONYM.APPLICATIONINTENT, KEY.ApplicationIntent },
@@ -717,7 +725,8 @@ internal static Dictionary GetParseSynonyms()
{ SYNONYM.TRUSTSERVERCERTIFICATE, KEY.TrustServerCertificate },
{ SYNONYM.UID, KEY.User_ID },
{ SYNONYM.User, KEY.User_ID },
- { SYNONYM.WSID, KEY.Workstation_Id }
+ { SYNONYM.WSID, KEY.Workstation_Id },
+ { SYNONYM.IPADDRESSPREFERENCE, KEY.IPAddressPreference }
};
Debug.Assert(synonyms.Count == count, "incorrect initial ParseSynonyms size");
Interlocked.CompareExchange(ref s_sqlClientSynonyms, synonyms, null);
@@ -898,5 +907,30 @@ internal SqlConnectionAttestationProtocol ConvertValueToAttestationProtocol()
throw ADP.InvalidConnectionOptionValue(KEY.AttestationProtocol, e);
}
}
+
+ ///
+ /// Convert the value to SqlConnectionIPAddressPreference
+ ///
+ ///
+ internal SqlConnectionIPAddressPreference ConvertValueToIPAddressPreference()
+ {
+ if (!TryGetParsetableValue(KEY.IPAddressPreference, out string value))
+ {
+ return DEFAULT.s_IPAddressPreference;
+ }
+
+ try
+ {
+ return DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(KEY.IPAddressPreference, value);
+ }
+ catch (FormatException e)
+ {
+ throw ADP.InvalidConnectionOptionValue(KEY.IPAddressPreference, e);
+ }
+ catch (OverflowException e)
+ {
+ throw ADP.InvalidConnectionOptionValue(KEY.IPAddressPreference, e);
+ }
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
index 0a7a06659a..9102b07a4a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
@@ -59,6 +59,7 @@ private enum Keywords
AttestationProtocol,
CommandTimeout,
+ IPAddressPreference,
// keep the count value last
KeywordsCount
@@ -107,6 +108,7 @@ private enum Keywords
private SqlConnectionColumnEncryptionSetting _columnEncryptionSetting = DbConnectionStringDefaults.ColumnEncryptionSetting;
private string _enclaveAttestationUrl = DbConnectionStringDefaults.EnclaveAttestationUrl;
private SqlConnectionAttestationProtocol _attestationProtocol = DbConnectionStringDefaults.AttestationProtocol;
+ private SqlConnectionIPAddressPreference _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference;
private static string[] CreateValidKeywords()
{
@@ -149,6 +151,7 @@ private static string[] CreateValidKeywords()
validKeywords[(int)Keywords.ColumnEncryptionSetting] = DbConnectionStringKeywords.ColumnEncryptionSetting;
validKeywords[(int)Keywords.EnclaveAttestationUrl] = DbConnectionStringKeywords.EnclaveAttestationUrl;
validKeywords[(int)Keywords.AttestationProtocol] = DbConnectionStringKeywords.AttestationProtocol;
+ validKeywords[(int)Keywords.IPAddressPreference] = DbConnectionStringKeywords.IPAddressPreference;
return validKeywords;
}
@@ -193,7 +196,9 @@ private static Dictionary CreateKeywordsDictionary()
hash.Add(DbConnectionStringKeywords.ColumnEncryptionSetting, Keywords.ColumnEncryptionSetting);
hash.Add(DbConnectionStringKeywords.EnclaveAttestationUrl, Keywords.EnclaveAttestationUrl);
hash.Add(DbConnectionStringKeywords.AttestationProtocol, Keywords.AttestationProtocol);
+ hash.Add(DbConnectionStringKeywords.IPAddressPreference, Keywords.IPAddressPreference);
+ hash.Add(DbConnectionStringSynonyms.IPADDRESSPREFERENCE, Keywords.IPAddressPreference);
hash.Add(DbConnectionStringSynonyms.APP, Keywords.ApplicationName);
hash.Add(DbConnectionStringSynonyms.APPLICATIONINTENT, Keywords.ApplicationIntent);
hash.Add(DbConnectionStringSynonyms.EXTENDEDPROPERTIES, Keywords.AttachDBFilename);
@@ -326,6 +331,9 @@ public override object this[string keyword]
case Keywords.AttestationProtocol:
AttestationProtocol = ConvertToAttestationProtocol(keyword, value);
break;
+ case Keywords.IPAddressPreference:
+ IPAddressPreference = ConvertToIPAddressPreference(keyword, value);
+ break;
#if NETCOREAPP
case Keywords.PoolBlockingPeriod: PoolBlockingPeriod = ConvertToPoolBlockingPeriod(keyword, value); break;
#endif
@@ -519,6 +527,22 @@ public SqlConnectionAttestationProtocol AttestationProtocol
}
}
+ ///
+ public SqlConnectionIPAddressPreference IPAddressPreference
+ {
+ get => _ipAddressPreference;
+ set
+ {
+ if (!DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value))
+ {
+ throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)value);
+ }
+
+ SetIPAddressPreferenceValue(value);
+ _ipAddressPreference = value;
+ }
+ }
+
///
public bool TrustServerCertificate
{
@@ -904,6 +928,14 @@ private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(str
return DbConnectionStringBuilderUtil.ConvertToAttestationProtocol(keyword, value);
}
+ ///
+ /// Convert to SqlConnectionIPAddressPreference
+ ///
+ ///
+ ///
+ private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value)
+ => DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value);
+
private object GetAt(Keywords index)
{
switch (index)
@@ -980,6 +1012,8 @@ private object GetAt(Keywords index)
return EnclaveAttestationUrl;
case Keywords.AttestationProtocol:
return AttestationProtocol;
+ case Keywords.IPAddressPreference:
+ return IPAddressPreference;
default:
Debug.Fail("unexpected keyword");
throw UnsupportedKeyword(s_validKeywords[(int)index]);
@@ -1127,6 +1161,9 @@ private void Reset(Keywords index)
case Keywords.AttestationProtocol:
_attestationProtocol = DbConnectionStringDefaults.AttestationProtocol;
break;
+ case Keywords.IPAddressPreference:
+ _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference;
+ break;
default:
Debug.Fail("unexpected keyword");
throw UnsupportedKeyword(s_validKeywords[(int)index]);
@@ -1163,6 +1200,12 @@ private void SetAttestationProtocolValue(SqlConnectionAttestationProtocol value)
base[DbConnectionStringKeywords.AttestationProtocol] = DbConnectionStringBuilderUtil.AttestationProtocolToString(value);
}
+ private void SetIPAddressPreferenceValue(SqlConnectionIPAddressPreference value)
+ {
+ Debug.Assert(DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value), "Invalid value for SqlConnectionIPAddressPreference");
+ base[DbConnectionStringKeywords.IPAddressPreference] = DbConnectionStringBuilderUtil.IPAddressPreferenceToString(value);
+ }
+
private void SetAuthenticationValue(SqlAuthenticationMethod value)
{
Debug.Assert(DbConnectionStringBuilderUtil.IsValidAuthenticationTypeValue(value), "Invalid value for AuthenticationType");
@@ -1306,4 +1349,3 @@ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContex
}
}
}
-
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 5dcda47e0f..3935ed01e4 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
@@ -21,7 +21,7 @@
namespace Microsoft.Data.SqlClient
{
- internal class SessionStateRecord
+ internal sealed class SessionStateRecord
{
internal bool _recoverable;
internal uint _version;
@@ -29,7 +29,7 @@ internal class SessionStateRecord
internal byte[] _data;
}
- internal class SessionData
+ internal sealed class SessionData
{
internal const int _maxNumberOfSessionStates = 256;
internal uint _tdsVersion;
@@ -101,7 +101,7 @@ public void AssertUnrecoverableStateCountIsCorrect()
}
}
- sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
+ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposable
{
// CONNECTION AND STATE VARIABLES
private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
@@ -2481,12 +2481,9 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo)
internal void OnFeatureExtAck(int featureId, byte[] data)
{
- if (RoutingInfo != null)
+ if (RoutingInfo != null && TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId)
{
- if (TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId)
- {
- return;
- }
+ return;
}
switch (featureId)
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs
index 416ec86fd0..9099f5882c 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsEnums.cs
@@ -1077,6 +1077,19 @@ public enum SqlConnectionAttestationProtocol
HGS = 3
}
+ ///
+ public enum SqlConnectionIPAddressPreference
+ {
+ ///
+ IPv4First = 0, // default
+
+ ///
+ IPv6First = 1,
+
+ ///
+ UsePlatformDefault = 2
+ }
+
///
public enum SqlConnectionColumnEncryptionSetting
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
index e0ebfdf669..9e26aa375f 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -412,8 +412,8 @@ internal void Connect(
_connHandler.pendingSQLDNSObject = null;
// AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server
- _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire,
- out instanceName, ref _sniSpnBuffer, false, true, fParallel, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated);
+ _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, false, true, fParallel,
+ _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated);
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
@@ -477,7 +477,8 @@ internal void Connect(
// On Instance failure re-connect and flush SNI named instance cache.
_physicalStateObj.SniContext = SniContext.Snix_Connect;
- _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, true, true, fParallel, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity);
+ _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, true, true, fParallel,
+ _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity);
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs
index 921d72a385..62411969ff 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs
@@ -144,6 +144,7 @@ internal SNIHandle(
bool flushCache,
bool fSync,
bool fParallel,
+ SqlConnectionIPAddressPreference ipPreference,
SQLDNSInfo cachedDNSInfo)
: base(IntPtr.Zero, true)
{
@@ -159,18 +160,18 @@ internal SNIHandle(
}
_status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle,
- spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, cachedDNSInfo);
+ spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, ipPreference, cachedDNSInfo);
}
}
// constructs SNI Handle for MARS session
- internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent, SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true)
+ internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true)
{
try
{ }
finally
{
- _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, cachedDNSInfo);
+ _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, ipPreference, cachedDNSInfo);
}
}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index f3299816ed..05bba67a5a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -792,7 +792,8 @@ private void ResetCancelAndProcessAttention()
}
}
- internal abstract void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity = false);
+ internal abstract void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel,
+ SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity = false);
internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo);
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
index 24b0c960d8..6bf08b0336 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs
@@ -50,9 +50,11 @@ internal SNIMarsHandle CreateMarsSession(object callbackObject, bool async)
protected override uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize)
=> SNIProxy.GetInstance().PacketGetData(packet.ManagedPacket, _inBuff, ref dataSize);
- internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool parallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity)
+ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool parallel,
+ SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity)
{
- _sessionHandle = SNIProxy.GetInstance().CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity, cachedFQDN, ref pendingDNSInfo);
+ _sessionHandle = SNIProxy.GetInstance().CreateConnectionHandle(serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity,
+ iPAddressPreference, cachedFQDN, ref pendingDNSInfo);
if (_sessionHandle == null)
{
_parser.ProcessSNIError(this);
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
index 34c7910dde..ecb6e0bb43 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs
@@ -66,7 +66,7 @@ protected override void CreateSessionHandle(TdsParserStateObject physicalConnect
SQLDNSInfo cachedDNSInfo;
bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCahce, out cachedDNSInfo);
- _sessionHandle = new SNIHandle(myInfo, nativeSNIObject.Handle, cachedDNSInfo);
+ _sessionHandle = new SNIHandle(myInfo, nativeSNIObject.Handle, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo);
}
internal override void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo)
@@ -137,7 +137,8 @@ private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async)
return myInfo;
}
- internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity)
+ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool fParallel,
+ SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity)
{
// We assume that the loadSSPILibrary has been called already. now allocate proper length of buffer
spnBuffer = new byte[1][];
@@ -171,7 +172,7 @@ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSni
SQLDNSInfo cachedDNSInfo;
bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo);
- _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer[0], ignoreSniOpenTimeout, checked((int)timeout), out instanceName, flushCache, !async, fParallel, cachedDNSInfo);
+ _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer[0], ignoreSniOpenTimeout, checked((int)timeout), out instanceName, flushCache, !async, fParallel, ipPreference, cachedDNSInfo);
}
protected override uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize)
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs
index 8a95a52351..9318d7eb40 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs
@@ -4442,6 +4442,12 @@ internal static string TCE_DbConnectionString_AttestationProtocol {
return ResourceManager.GetString("TCE_DbConnectionString_AttestationProtocol", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances.
+ ///
+ internal static string TCE_DbConnectionString_IPAddressPreference
+ => ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture);
///
/// Looks up a localized string similar to Decryption failed. The last 10 bytes of the encrypted column encryption key are: '{0}'. The first 10 bytes of ciphertext are: '{1}'..
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx
index 5dd36f38ac..335803c097 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.resx
@@ -1851,6 +1851,9 @@
Specifies an attestation protocol for its corresponding enclave attestation service.
+
+ Specifies an IP address preference when connecting to SQL instances.
+
The enclave type '{0}' returned from the server is not supported.
diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
index 23c88ff5b5..7e35b64c04 100644
--- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs
@@ -888,6 +888,19 @@ public enum SqlConnectionAttestationProtocol
HGS = 3
}
+ ///
+ public enum SqlConnectionIPAddressPreference
+ {
+ ///
+ IPv4First = 0, // default
+
+ ///
+ IPv6First = 1,
+
+ ///
+ UsePlatformDefault = 2
+ }
+
///
public enum SqlConnectionOverrides
{
@@ -974,6 +987,10 @@ public SqlConnectionStringBuilder(string connectionString) { }
[System.ComponentModel.DisplayNameAttribute("Attestation Protocol")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public Microsoft.Data.SqlClient.SqlConnectionAttestationProtocol AttestationProtocol { get { throw null; } set { } }
+ ///
+ [System.ComponentModel.DisplayNameAttribute("IP Address Preference")]
+ [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
+ public Microsoft.Data.SqlClient.SqlConnectionIPAddressPreference IPAddressPreference { get { throw null; } set { } }
///
[System.ComponentModel.DisplayNameAttribute("Encrypt")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
index 71ab0deea3..ec0bd3a558 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionStringCommon.cs
@@ -998,6 +998,110 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st
#endregion
+ #region <>
+ ///
+ /// IP Address Preference.
+ ///
+ private readonly static Dictionary s_preferenceNames = new(StringComparer.InvariantCultureIgnoreCase);
+
+ static DbConnectionStringBuilderUtil()
+ {
+ foreach (SqlConnectionIPAddressPreference item in Enum.GetValues(typeof(SqlConnectionIPAddressPreference)))
+ {
+ s_preferenceNames.Add(item.ToString(), item);
+ }
+ }
+
+ ///
+ /// Convert a string value to the corresponding IPAddressPreference.
+ ///
+ /// The string representation of the enumeration name to convert.
+ /// When this method returns, `result` contains an object of type `SqlConnectionIPAddressPreference` whose value is represented by `value` if the operation succeeds.
+ /// If the parse operation fails, `result` contains the default value of the `SqlConnectionIPAddressPreference` type.
+ /// `true` if the value parameter was converted successfully; otherwise, `false`.
+ internal static bool TryConvertToIPAddressPreference(string value, out SqlConnectionIPAddressPreference result)
+ {
+ if (!s_preferenceNames.TryGetValue(value, out result))
+ {
+ result = DbConnectionStringDefaults.IPAddressPreference;
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Verifies if the `value` is defined in the expected Enum.
+ ///
+ internal static bool IsValidIPAddressPreference(SqlConnectionIPAddressPreference value)
+ => value == SqlConnectionIPAddressPreference.IPv4First
+ || value == SqlConnectionIPAddressPreference.IPv6First
+ || value == SqlConnectionIPAddressPreference.UsePlatformDefault;
+
+ internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value)
+ => Enum.GetName(typeof(SqlConnectionIPAddressPreference), value);
+
+ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value)
+ {
+ if (value is null)
+ {
+ return DbConnectionStringDefaults.IPAddressPreference; // IPv4First
+ }
+
+ if (value is string sValue)
+ {
+ // try again after remove leading & trailing whitespaces.
+ sValue = sValue.Trim();
+ if (TryConvertToIPAddressPreference(sValue, out SqlConnectionIPAddressPreference result))
+ {
+ return result;
+ }
+
+ // string values must be valid
+ throw ADP.InvalidConnectionOptionValue(keyword);
+ }
+ else
+ {
+ // the value is not string, try other options
+ SqlConnectionIPAddressPreference eValue;
+
+ if (value is SqlConnectionIPAddressPreference preference)
+ {
+ eValue = preference;
+ }
+ else if (value.GetType().IsEnum)
+ {
+ // explicitly block scenarios in which user tries to use wrong enum types, like:
+ // builder["SqlConnectionIPAddressPreference"] = EnvironmentVariableTarget.Process;
+ // workaround: explicitly cast non-SqlConnectionIPAddressPreference enums to int
+ throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), null);
+ }
+ else
+ {
+ try
+ {
+ // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest
+ eValue = (SqlConnectionIPAddressPreference)Enum.ToObject(typeof(SqlConnectionIPAddressPreference), value);
+ }
+ catch (ArgumentException e)
+ {
+ // to be consistent with the messages we send in case of wrong type usage, replace
+ // the error with our exception, and keep the original one as inner one for troubleshooting
+ throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), e);
+ }
+ }
+
+ if (IsValidIPAddressPreference(eValue))
+ {
+ return eValue;
+ }
+ else
+ {
+ throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)eValue);
+ }
+ }
+ }
+ #endregion
+
internal static bool IsValidCertificateValue(string value)
{
return string.IsNullOrEmpty(value)
@@ -1065,6 +1169,7 @@ internal static class DbConnectionStringDefaults
internal static readonly SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = _emptyString;
internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
+ internal const SqlConnectionIPAddressPreference IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First;
internal const string Certificate = _emptyString;
internal const PoolBlockingPeriod PoolBlockingPeriod = SqlClient.PoolBlockingPeriod.Auto;
}
@@ -1139,6 +1244,7 @@ internal static class DbConnectionStringKeywords
internal const string ColumnEncryptionSetting = "Column Encryption Setting";
internal const string EnclaveAttestationUrl = "Enclave Attestation Url";
internal const string AttestationProtocol = "Attestation Protocol";
+ internal const string IPAddressPreference = "IP Address Preference";
internal const string PoolBlockingPeriod = "Pool Blocking Period";
// common keywords (OleDb, OracleClient, SqlClient)
@@ -1164,6 +1270,9 @@ internal static class DbConnectionStringSynonyms
//internal const string ApplicationName = APP;
internal const string APP = "app";
+ // internal const string IPAddressPreference = IPADDRESSPREFERENCE;
+ internal const string IPADDRESSPREFERENCE = "ipaddresspreference";
+
//internal const string ApplicationIntent = APPLICATIONINTENT;
internal const string APPLICATIONINTENT = "applicationintent";
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
index 0cddc32dc1..b28c736977 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs
@@ -101,6 +101,7 @@ internal static extern uint SNIOpenWrapper(
[In] SNIHandle pConn,
out IntPtr ppConn,
[MarshalAs(UnmanagedType.Bool)] bool fSync,
+ SqlConnectionIPAddressPreference ipPreference,
[In] ref SNI_DNSCache_Info pDNSCachedInfo);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
index 398ecc4872..2dc215ad36 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs
@@ -101,6 +101,7 @@ internal static extern uint SNIOpenWrapper(
[In] SNIHandle pConn,
out IntPtr ppConn,
[MarshalAs(UnmanagedType.Bool)] bool fSync,
+ SqlConnectionIPAddressPreference ipPreference,
[In] ref SNI_DNSCache_Info pDNSCachedInfo);
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
index 0ac874b8b6..19dd12587a 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs
@@ -354,6 +354,7 @@ internal unsafe struct SNI_CLIENT_CONSUMER_INFO
public TransparentNetworkResolutionMode transparentNetworkResolution;
public int totalTimeout;
public bool isAzureSqlServerEndpoint;
+ public SqlConnectionIPAddressPreference ipAddressPreference;
public SNI_DNSCache_Info DNSCacheInfo;
}
@@ -604,11 +605,12 @@ private static uint SNIOpenWrapper(
[In] SNIHandle pConn,
out IntPtr ppConn,
[MarshalAs(UnmanagedType.Bool)] bool fSync,
+ SqlConnectionIPAddressPreference ipPreference,
[In] ref SNI_DNSCache_Info pDNSCachedInfo)
{
return s_is64bitProcess ?
- SNINativeManagedWrapperX64.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ref pDNSCachedInfo) :
- SNINativeManagedWrapperX86.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ref pDNSCachedInfo);
+ SNINativeManagedWrapperX64.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ipPreference, ref pDNSCachedInfo) :
+ SNINativeManagedWrapperX86.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ipPreference, ref pDNSCachedInfo);
}
private static IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IOType IOType)
@@ -758,7 +760,7 @@ internal static uint SNIInitialize()
return SNIInitialize(IntPtr.Zero);
}
- internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SQLDNSInfo cachedDNSInfo)
+ internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo)
{
// initialize consumer info for MARS
Sni_Consumer_Info native_consumerInfo = new Sni_Consumer_Info();
@@ -770,10 +772,11 @@ internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHan
native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port;
- return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ref native_cachedDNSInfo);
+ return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ipPreference, ref native_cachedDNSInfo);
}
- internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, bool fSync, int timeout, bool fParallel, Int32 transparentNetworkResolutionStateNo, Int32 totalTimeout, Boolean isAzureSqlServerEndpoint, SQLDNSInfo cachedDNSInfo)
+ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, bool fSync, int timeout, bool fParallel,
+ Int32 transparentNetworkResolutionStateNo, Int32 totalTimeout, Boolean isAzureSqlServerEndpoint, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo)
{
fixed (byte* pin_instanceName = &instanceName[0])
{
@@ -808,6 +811,7 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons
};
clientConsumerInfo.totalTimeout = totalTimeout;
+ clientConsumerInfo.ipAddressPreference = ipPreference;
clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN;
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4;
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
index f734ced9f8..3c9a6a4a1a 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs
@@ -588,6 +588,14 @@ internal SqlConnectionAttestationProtocol AttestationProtocol
}
}
+ ///
+ /// Get IP address preference
+ ///
+ internal SqlConnectionIPAddressPreference iPAddressPreference
+ {
+ get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference;
+ }
+
// Is this connection is a Context Connection?
private bool UsesContextConnection(SqlConnectionString opt)
{
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
index 2b3ffa9d70..761ab74751 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionString.cs
@@ -58,6 +58,7 @@ internal static class DEFAULT
internal static readonly SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = _emptyString;
internal static readonly SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
+ internal static readonly SqlConnectionIPAddressPreference s_IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First;
#if ADONET_CERT_AUTH
internal const string Certificate = _emptyString;
@@ -76,6 +77,7 @@ internal static class KEY
internal const string ColumnEncryptionSetting = "column encryption setting";
internal const string EnclaveAttestationUrl = "enclave attestation url";
internal const string AttestationProtocol = "attestation protocol";
+ internal const string IPAddressPreference = "ip address preference";
internal const string Connect_Timeout = "connect timeout";
internal const string Command_Timeout = "command timeout";
internal const string Connection_Reset = "connection reset";
@@ -118,6 +120,8 @@ internal static class KEY
private static class SYNONYM
{
+ // ip address preference
+ internal const string IPADDRESSPREFERENCE = "ipaddresspreference";
// application intent
internal const string APPLICATIONINTENT = "applicationintent";
// application name
@@ -172,7 +176,7 @@ private static class SYNONYM
// make sure to update SynonymCount value below when adding or removing synonyms
}
- internal const int SynonymCount = 29;
+ internal const int SynonymCount = 30;
// the following are all inserted as keys into the _netlibMapping hash
internal static class NETLIB
@@ -239,6 +243,7 @@ internal static class TRANSACIONBINDING
private readonly SqlConnectionColumnEncryptionSetting _columnEncryptionSetting;
private readonly string _enclaveAttestationUrl;
private readonly SqlConnectionAttestationProtocol _attestationProtocol;
+ private readonly SqlConnectionIPAddressPreference _ipAddressPreference;
private readonly int _commandTimeout;
private readonly int _connectTimeout;
@@ -325,6 +330,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G
_columnEncryptionSetting = ConvertValueToColumnEncryptionSetting();
_enclaveAttestationUrl = ConvertValueToString(KEY.EnclaveAttestationUrl, DEFAULT.EnclaveAttestationUrl);
_attestationProtocol = ConvertValueToAttestationProtocol();
+ _ipAddressPreference = ConvertValueToIPAddressPreference();
#if ADONET_CERT_AUTH
_certificate = ConvertValueToString(KEY.Certificate, DEFAULT.Certificate);
@@ -682,6 +688,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS
internal SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting { get { return _columnEncryptionSetting; } }
internal string EnclaveAttestationUrl { get { return _enclaveAttestationUrl; } }
internal SqlConnectionAttestationProtocol AttestationProtocol { get { return _attestationProtocol; } }
+ internal SqlConnectionIPAddressPreference IPAddressPreference => _ipAddressPreference;
#if ADONET_CERT_AUTH
internal string Certificate { get { return _certificate; } }
internal bool UsesCertificate { get { return _authType == SqlClient.SqlAuthenticationMethod.SqlCertificate; } }
@@ -822,6 +829,7 @@ internal static Hashtable GetParseSynonyms()
hash.Add(KEY.Connect_Retry_Count, KEY.Connect_Retry_Count);
hash.Add(KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval);
hash.Add(KEY.Authentication, KEY.Authentication);
+ hash.Add(KEY.IPAddressPreference, KEY.IPAddressPreference);
#if ADONET_CERT_AUTH
hash.Add(KEY.Certificate, KEY.Certificate);
#endif
@@ -854,6 +862,7 @@ internal static Hashtable GetParseSynonyms()
hash.Add(SYNONYM.UID, KEY.User_ID);
hash.Add(SYNONYM.User, KEY.User_ID);
hash.Add(SYNONYM.WSID, KEY.Workstation_Id);
+ hash.Add(SYNONYM.IPADDRESSPREFERENCE, KEY.IPAddressPreference);
Debug.Assert(SqlConnectionStringBuilder.KeywordsCount + SynonymCount == hash.Count, "incorrect initial ParseSynonyms size");
_sqlClientSynonyms = hash;
}
@@ -1077,6 +1086,34 @@ internal SqlConnectionAttestationProtocol ConvertValueToAttestationProtocol()
}
}
+ ///
+ /// Convert the value to SqlConnectionIPAddressPreference
+ ///
+ ///
+ internal SqlConnectionIPAddressPreference ConvertValueToIPAddressPreference()
+ {
+ object value = base.Parsetable[KEY.IPAddressPreference];
+
+ string valStr = value as string;
+ if (valStr == null)
+ {
+ return DEFAULT.s_IPAddressPreference;
+ }
+
+ try
+ {
+ return DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(KEY.IPAddressPreference, valStr);
+ }
+ catch (FormatException e)
+ {
+ throw ADP.InvalidConnectionOptionValue(KEY.IPAddressPreference, e);
+ }
+ catch (OverflowException e)
+ {
+ throw ADP.InvalidConnectionOptionValue(KEY.IPAddressPreference, e);
+ }
+ }
+
internal bool ConvertValueToEncrypt()
{
// If the Authentication keyword is provided, default to Encrypt=true;
@@ -1087,4 +1124,3 @@ internal bool ConvertValueToEncrypt()
}
}
}
-
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
index 17d62e41bd..15590fe981 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs
@@ -65,6 +65,7 @@ private enum Keywords
AttestationProtocol,
CommandTimeout,
+ IPAddressPreference,
#if ADONET_CERT_AUTH
Certificate,
@@ -118,6 +119,7 @@ private enum Keywords
private SqlConnectionColumnEncryptionSetting _columnEncryptionSetting = DbConnectionStringDefaults.ColumnEncryptionSetting;
private string _enclaveAttestationUrl = DbConnectionStringDefaults.EnclaveAttestationUrl;
private SqlConnectionAttestationProtocol _attestationProtocol = DbConnectionStringDefaults.AttestationProtocol;
+ private SqlConnectionIPAddressPreference _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference;
private PoolBlockingPeriod _poolBlockingPeriod = DbConnectionStringDefaults.PoolBlockingPeriod;
#if ADONET_CERT_AUTH
@@ -168,6 +170,7 @@ static SqlConnectionStringBuilder()
validKeywords[(int)Keywords.ColumnEncryptionSetting] = DbConnectionStringKeywords.ColumnEncryptionSetting;
validKeywords[(int)Keywords.EnclaveAttestationUrl] = DbConnectionStringKeywords.EnclaveAttestationUrl;
validKeywords[(int)Keywords.AttestationProtocol] = DbConnectionStringKeywords.AttestationProtocol;
+ validKeywords[(int)Keywords.IPAddressPreference] = DbConnectionStringKeywords.IPAddressPreference;
#if ADONET_CERT_AUTH
validKeywords[(int)Keywords.Certificate] = DbConnectionStringKeywords.Certificate;
#endif
@@ -215,9 +218,11 @@ static SqlConnectionStringBuilder()
hash.Add(DbConnectionStringKeywords.ColumnEncryptionSetting, Keywords.ColumnEncryptionSetting);
hash.Add(DbConnectionStringKeywords.EnclaveAttestationUrl, Keywords.EnclaveAttestationUrl);
hash.Add(DbConnectionStringKeywords.AttestationProtocol, Keywords.AttestationProtocol);
+ hash.Add(DbConnectionStringKeywords.IPAddressPreference, Keywords.IPAddressPreference);
#if ADONET_CERT_AUTH
hash.Add(DbConnectionStringKeywords.Certificate, Keywords.Certificate);
#endif
+ hash.Add(DbConnectionStringSynonyms.IPADDRESSPREFERENCE, Keywords.IPAddressPreference);
hash.Add(DbConnectionStringSynonyms.APP, Keywords.ApplicationName);
hash.Add(DbConnectionStringSynonyms.APPLICATIONINTENT, Keywords.ApplicationIntent);
hash.Add(DbConnectionStringSynonyms.Async, Keywords.AsynchronousProcessing);
@@ -357,6 +362,9 @@ public override object this[string keyword]
case Keywords.AttestationProtocol:
AttestationProtocol = ConvertToAttestationProtocol(keyword, value);
break;
+ case Keywords.IPAddressPreference:
+ IPAddressPreference = ConvertToIPAddressPreference(keyword, value);
+ break;
#if ADONET_CERT_AUTH
case Keywords.Certificate:
Certificate = ConvertToString(value);
@@ -688,6 +696,26 @@ public SqlConnectionAttestationProtocol AttestationProtocol
}
}
+ ///
+ [DisplayName(DbConnectionStringKeywords.IPAddressPreference)]
+ [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Security)]
+ [ResDescriptionAttribute(StringsHelper.ResourceNames.TCE_DbConnectionString_IPAddressPreference)]
+ [RefreshPropertiesAttribute(RefreshProperties.All)]
+ public SqlConnectionIPAddressPreference IPAddressPreference
+ {
+ get => _ipAddressPreference;
+ set
+ {
+ if (!DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value))
+ {
+ throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)value);
+ }
+
+ SetIPAddressPreferenceValue(value);
+ _ipAddressPreference = value;
+ }
+ }
+
///
[DisplayName(DbConnectionStringKeywords.TrustServerCertificate)]
[ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Security)]
@@ -1267,6 +1295,14 @@ private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(str
return DbConnectionStringBuilderUtil.ConvertToAttestationProtocol(keyword, value);
}
+ ///
+ /// Convert to SqlConnectionIPAddressPreference
+ ///
+ ///
+ ///
+ private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value)
+ => DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value);
+
private object GetAt(Keywords index)
{
switch (index)
@@ -1357,6 +1393,8 @@ private object GetAt(Keywords index)
return EnclaveAttestationUrl;
case Keywords.AttestationProtocol:
return AttestationProtocol;
+ case Keywords.IPAddressPreference:
+ return IPAddressPreference;
#if ADONET_CERT_AUTH
case Keywords.Certificate: return Certificate;
#endif
@@ -1552,6 +1590,9 @@ private void Reset(Keywords index)
case Keywords.AttestationProtocol:
_attestationProtocol = DbConnectionStringDefaults.AttestationProtocol;
break;
+ case Keywords.IPAddressPreference:
+ _ipAddressPreference = DbConnectionStringDefaults.IPAddressPreference;
+ break;
default:
Debug.Fail("unexpected keyword");
throw ADP.KeywordNotSupported(_validKeywords[(int)index]);
@@ -1598,6 +1639,12 @@ private void SetAttestationProtocolValue(SqlConnectionAttestationProtocol value)
base[DbConnectionStringKeywords.AttestationProtocol] = DbConnectionStringBuilderUtil.AttestationProtocolToString(value);
}
+ private void SetIPAddressPreferenceValue(SqlConnectionIPAddressPreference value)
+ {
+ Debug.Assert(DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value), "Invalid value for SqlConnectionIPAddressPreference");
+ base[DbConnectionStringKeywords.IPAddressPreference] = DbConnectionStringBuilderUtil.IPAddressPreferenceToString(value);
+ }
+
///
public override bool ShouldSerialize(string keyword)
@@ -1923,4 +1970,3 @@ private System.ComponentModel.Design.Serialization.InstanceDescriptor ConvertToI
}
}
-
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs
index 5e422fef74..4ac0a23fa4 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs
@@ -1076,6 +1076,19 @@ public enum SqlConnectionAttestationProtocol
HGS = 3
}
+ ///
+ public enum SqlConnectionIPAddressPreference
+ {
+ ///
+ IPv4First = 0, // default
+
+ ///
+ IPv6First = 1,
+
+ ///
+ UsePlatformDefault = 2
+ }
+
///
public enum SqlAuthenticationMethod
{
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
index 6b4d322c1f..78015db428 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs
@@ -593,7 +593,7 @@ internal void Connect(ServerInfo serverInfo,
}
_physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire,
- out instanceName, _sniSpnBuffer, false, true, fParallel, transparentNetworkResolutionState, totalTimeout, FQDNforDNSCahce);
+ out instanceName, _sniSpnBuffer, false, true, fParallel, transparentNetworkResolutionState, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCahce);
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
@@ -656,7 +656,8 @@ internal void Connect(ServerInfo serverInfo,
// On Instance failure re-connect and flush SNI named instance cache.
_physicalStateObj.SniContext = SniContext.Snix_Connect;
- _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, _sniSpnBuffer, true, true, fParallel, transparentNetworkResolutionState, totalTimeout, serverInfo.ResolvedServerName);
+ _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire,
+ out instanceName, _sniSpnBuffer, true, true, fParallel, transparentNetworkResolutionState, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, serverInfo.ResolvedServerName);
if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status)
{
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs
index 30e874995c..b61ed1dd34 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.cs
@@ -150,6 +150,7 @@ internal SNIHandle(
bool fParallel,
TransparentNetworkResolutionState transparentNetworkResolutionState,
int totalTimeout,
+ SqlConnectionIPAddressPreference ipPreference,
SQLDNSInfo cachedDNSInfo)
: base(IntPtr.Zero, true)
{
@@ -172,19 +173,19 @@ internal SNIHandle(
int transparentNetworkResolutionStateNo = (int)transparentNetworkResolutionState;
_status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle,
spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, transparentNetworkResolutionStateNo, totalTimeout,
- ADP.IsAzureSqlServerEndpoint(serverName), cachedDNSInfo);
+ ADP.IsAzureSqlServerEndpoint(serverName), ipPreference, cachedDNSInfo);
}
}
// constructs SNI Handle for MARS session
- internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent, SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true)
+ internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true)
{
RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
{
- _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, cachedDNSInfo);
+ _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, ipPreference, cachedDNSInfo);
}
}
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
index ebd75aaefa..9453f6102a 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs
@@ -326,7 +326,7 @@ internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bo
SQLDNSInfo cachedDNSInfo;
bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCahce, out cachedDNSInfo);
- _sessionHandle = new SNIHandle(myInfo, physicalConnection, cachedDNSInfo);
+ _sessionHandle = new SNIHandle(myInfo, physicalConnection, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo);
if (_sessionHandle.Status != TdsEnums.SNI_SUCCESS)
{
AddError(parser.ProcessSNIError(this));
@@ -852,7 +852,8 @@ private SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async)
return myInfo;
}
- internal void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, byte[] spnBuffer, bool flushCache, bool async, bool fParallel, TransparentNetworkResolutionState transparentNetworkResolutionState, int totalTimeout, string cachedFQDN)
+ internal void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, byte[] spnBuffer, bool flushCache,
+ bool async, bool fParallel, TransparentNetworkResolutionState transparentNetworkResolutionState, int totalTimeout, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN)
{
SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async);
@@ -880,7 +881,8 @@ internal void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeo
SQLDNSInfo cachedDNSInfo;
bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo);
- _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, cachedDNSInfo);
+ _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout),
+ out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, ipPreference, cachedDNSInfo);
}
internal bool Deactivate()
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
index 925c38901d..f5b5f44a4c 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs
@@ -12077,6 +12077,12 @@ internal static string TCE_DbConnectionString_AttestationProtocol {
return ResourceManager.GetString("TCE_DbConnectionString_AttestationProtocol", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances.
+ ///
+ internal static string TCE_DbConnectionString_IPAddressPreference
+ => ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture);
///
/// Looks up a localized string similar to Default column encryption setting for all the commands on the connection..
diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
index 1ffcff4d0b..7ca22b2fe8 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.resx
@@ -4524,6 +4524,9 @@
Specifies an attestation protocol for its corresponding enclave attestation service.
+
+ Specifies an IP address preference when connecting to SQL instances.
+
The enclave type '{0}' returned from the server is not supported.
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs
index e18b61cee4..9d4136d01f 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs
@@ -7,7 +7,7 @@
namespace Microsoft.Data.SqlClient
{
- internal class SQLFallbackDNSCache
+ internal sealed class SQLFallbackDNSCache
{
private static readonly SQLFallbackDNSCache _SQLFallbackDNSCache = new SQLFallbackDNSCache();
private static readonly int initialCapacity = 101; // give some prime number here according to MSDN docs. It will be resized if reached capacity.
@@ -68,7 +68,7 @@ internal bool IsDuplicate(SQLDNSInfo newItem)
}
}
- internal class SQLDNSInfo
+ internal sealed class SQLDNSInfo
{
public string FQDN { get; set; }
public string AddrIPv4 { get; set; }
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs
index 9796b297c2..4a61e10edd 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs
@@ -55,6 +55,9 @@ public partial class SqlConnectionStringBuilderTest
[InlineData("Initial Catalog = Northwind; Failover Partner = randomserver.sys.local")]
[InlineData("Initial Catalog = tempdb")]
[InlineData("Integrated Security = true")]
+ [InlineData("IPAddressPreference = IPv4First")]
+ [InlineData("IPAddressPreference = IPv6First")]
+ [InlineData("IPAddressPreference = UsePlatformDefault")]
[InlineData("Trusted_Connection = false")]
[InlineData("Max Pool Size = 50")]
[InlineData("Min Pool Size = 20")]
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
index 9289f23768..b3afc51527 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs
@@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient.Tests
{
public partial class SqlConnectionTest
{
- private static readonly string[] s_retrieveInternalInfoKeys =
+ private static readonly string[] s_retrieveInternalInfoKeys =
{
"SQLDNSCachingSupportedState",
"SQLDNSCachingSupportedStateBeforeRedirect"
@@ -53,7 +53,7 @@ public void Constructor2()
Assert.Null(cn.Site);
Assert.Equal(ConnectionState.Closed, cn.State);
Assert.False(cn.StatisticsEnabled);
- Assert.True(string.Compare (Environment.MachineName, cn.WorkstationId, true) == 0);
+ Assert.True(string.Compare(Environment.MachineName, cn.WorkstationId, true) == 0);
cn = new SqlConnection((string)null);
Assert.Equal(string.Empty, cn.ConnectionString);
@@ -67,7 +67,7 @@ public void Constructor2()
Assert.Null(cn.Site);
Assert.Equal(ConnectionState.Closed, cn.State);
Assert.False(cn.StatisticsEnabled);
- Assert.True(string.Compare (Environment.MachineName, cn.WorkstationId, true) == 0);
+ Assert.True(string.Compare(Environment.MachineName, cn.WorkstationId, true) == 0);
}
[Fact]
@@ -107,7 +107,7 @@ public void Constructor2_ConnectionString_Invalid()
try
{
new SqlConnection("Packet Size=511");
- }
+ }
catch (ArgumentException ex)
{
// Invalid 'Packet Size'. The value must be an
@@ -1326,7 +1326,7 @@ public void RetrieveInternalInfo_ExpectedKeysInDictionary_Success()
Assert.NotEmpty(d.Values);
Assert.Equal(s_retrieveInternalInfoKeys.Length, d.Values.Count);
- foreach(string key in s_retrieveInternalInfoKeys)
+ foreach (string key in s_retrieveInternalInfoKeys)
{
Assert.True(d.ContainsKey(key));
@@ -1343,5 +1343,48 @@ public void RetrieveInternalInfo_UnexpectedKeysInDictionary_Success()
IDictionary d = cn.RetrieveInternalInfo();
Assert.False(d.ContainsKey("Foo"));
}
+
+ [Fact]
+ public void ConnectionString_IPAddressPreference()
+ {
+ SqlConnection cn = new SqlConnection();
+ cn.ConnectionString = "IPAddressPreference=IPv4First";
+ cn.ConnectionString = "IPAddressPreference=IPV4FIRST";
+ cn.ConnectionString = "IPAddressPreference=ipv4first";
+ cn.ConnectionString = "IPAddressPreference=iPv4FirSt";
+ cn.ConnectionString = "IPAddressPreference=IPv6First";
+ cn.ConnectionString = "IPAddressPreference=IPV6FIRST";
+ cn.ConnectionString = "IPAddressPreference=ipv6first";
+ cn.ConnectionString = "IPAddressPreference=iPv6FirST";
+ cn.ConnectionString = "IPAddressPreference=UsePlatformDefault";
+ cn.ConnectionString = "IPAddressPreference=USEPLATFORMDEFAULT";
+ cn.ConnectionString = "IPAddressPreference=useplatformdefault";
+ cn.ConnectionString = "IPAddressPreference=usePlAtFormdeFault";
+ }
+
+ [Theory]
+ [InlineData("IPAddressPreference=-1")]
+ [InlineData("IPAddressPreference=0")]
+ [InlineData("IPAddressPreference=!@#")]
+ [InlineData("IPAddressPreference=ABC")]
+ [InlineData("IPAddressPreference=ipv6")]
+ public void ConnectionString_IPAddressPreference_Invalid(string value)
+ {
+ SqlConnection cn = new SqlConnection();
+ try
+ {
+ cn.ConnectionString = value;
+ Assert.True(false, $"It mustn't come to this line; Value '{value}' should be invalid.");
+ }
+ catch (ArgumentException ex)
+ {
+ // Invalid value for key 'ip address preference'
+ Assert.Equal(typeof(ArgumentException), ex.GetType());
+ Assert.Null(ex.InnerException);
+ Assert.NotNull(ex.Message);
+ Assert.Contains("'ip address preference'", ex.Message);
+ Assert.Null(ex.ParamName);
+ }
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
index a97a16a05d..2aefb3fe36 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
@@ -9,6 +9,9 @@
using System.Diagnostics.Tracing;
using System.Globalization;
using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
using System.Security;
using System.Threading;
using System.Threading.Tasks;
@@ -343,6 +346,20 @@ public static bool IsTCPConnectionStringPasswordIncluded()
return RetrieveValueFromConnStr(TCPConnectionString, new string[] { "Password", "PWD" }) != string.Empty;
}
+ public static bool DoesHostAddressContainBothIPv4AndIPv6()
+ {
+ if (!IsDNSCachingSetup())
+ {
+ return false;
+ }
+ using (var connection = new SqlConnection(DNSCachingConnString))
+ {
+ List ipAddresses = Dns.GetHostAddresses(connection.DataSource).ToList();
+ return ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetwork) &&
+ ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetworkV6);
+ }
+ }
+
///
/// Generate a unique name to use in Sql Server;
/// some providers does not support names (Oracle supports up to 30).
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
index 0878e72f74..0a1946d7cc 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
@@ -70,6 +70,7 @@
Common\System\Collections\DictionaryExtensions.cs
+
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs
index 2e2a1ca022..e71d6d62f6 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AsyncTest/AsyncCancelledConnectionsTest.cs
@@ -28,18 +28,14 @@ public void CancelAsyncConnections()
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString);
builder.MultipleActiveResultSets = false;
- RunCancelAsyncConnections(builder, false);
- RunCancelAsyncConnections(builder, true);
+ RunCancelAsyncConnections(builder);
builder.MultipleActiveResultSets = true;
- RunCancelAsyncConnections(builder, false);
- RunCancelAsyncConnections(builder, true);
+ RunCancelAsyncConnections(builder);
}
- private void RunCancelAsyncConnections(SqlConnectionStringBuilder connectionStringBuilder, bool makeAsyncBlocking)
+ private void RunCancelAsyncConnections(SqlConnectionStringBuilder connectionStringBuilder)
{
SqlConnection.ClearAllPools();
- AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking", makeAsyncBlocking);
-
_watch = Stopwatch.StartNew();
_random = new Random(4); // chosen via fair dice role.
ParallelLoopResult results = new ParallelLoopResult();
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs
index 54561e1be9..32bac50d08 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionHelper.cs
@@ -17,6 +17,7 @@ internal static class ConnectionHelper
private static Type s_dbConnectionInternal = s_MicrosoftDotData.GetType("Microsoft.Data.ProviderBase.DbConnectionInternal");
private static Type s_tdsParser = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.TdsParser");
private static Type s_tdsParserStateObject = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.TdsParserStateObject");
+ private static Type s_SQLDNSInfo = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.SQLDNSInfo");
private static PropertyInfo s_sqlConnectionInternalConnection = s_sqlConnection.GetProperty("InnerConnection", BindingFlags.Instance | BindingFlags.NonPublic);
private static PropertyInfo s_dbConnectionInternalPool = s_dbConnectionInternal.GetProperty("Pool", BindingFlags.Instance | BindingFlags.NonPublic);
private static MethodInfo s_dbConnectionInternalIsConnectionAlive = s_dbConnectionInternal.GetMethod("IsConnectionAlive", BindingFlags.Instance | BindingFlags.NonPublic);
@@ -26,6 +27,11 @@ internal static class ConnectionHelper
private static FieldInfo s_tdsParserStateObjectProperty = s_tdsParser.GetField("_physicalStateObj", BindingFlags.Instance | BindingFlags.NonPublic);
private static FieldInfo s_enforceTimeoutDelayProperty = s_tdsParserStateObject.GetField("_enforceTimeoutDelay", BindingFlags.Instance | BindingFlags.NonPublic);
private static FieldInfo s_enforcedTimeoutDelayInMilliSeconds = s_tdsParserStateObject.GetField("_enforcedTimeoutDelayInMilliSeconds", BindingFlags.Instance | BindingFlags.NonPublic);
+ private static FieldInfo s_pendingSQLDNSObject = s_sqlInternalConnectionTds.GetField("pendingSQLDNSObject", BindingFlags.Instance | BindingFlags.NonPublic);
+ private static PropertyInfo s_pendingSQLDNS_FQDN = s_SQLDNSInfo.GetProperty("FQDN", BindingFlags.Instance | BindingFlags.Public);
+ private static PropertyInfo s_pendingSQLDNS_AddrIPv4 = s_SQLDNSInfo.GetProperty("AddrIPv4", BindingFlags.Instance | BindingFlags.Public);
+ private static PropertyInfo s_pendingSQLDNS_AddrIPv6 = s_SQLDNSInfo.GetProperty("AddrIPv6", BindingFlags.Instance | BindingFlags.Public);
+ private static PropertyInfo s_pendingSQLDNS_Port = s_SQLDNSInfo.GetProperty("Port", BindingFlags.Instance | BindingFlags.Public);
public static object GetConnectionPool(object internalConnection)
{
@@ -79,5 +85,22 @@ public static void SetEnforcedTimeout(this SqlConnection connection, bool enforc
s_enforceTimeoutDelayProperty.SetValue(stateObj, enforce);
s_enforcedTimeoutDelayInMilliSeconds.SetValue(stateObj, timeout);
}
+
+ ///
+ /// Resolve the established socket end point information for TCP protocol.
+ ///
+ /// Active connection to extract the requested data
+ /// FQDN, AddrIPv4, AddrIPv6, and Port in sequence
+ public static Tuple GetSQLDNSInfo(this SqlConnection connection)
+ {
+ object internalConnection = GetInternalConnection(connection);
+ VerifyObjectIsInternalConnection(internalConnection);
+ object pendingSQLDNSInfo = s_pendingSQLDNSObject.GetValue(internalConnection);
+ string fqdn = s_pendingSQLDNS_FQDN.GetValue(pendingSQLDNSInfo) as string;
+ string ipv4 = s_pendingSQLDNS_AddrIPv4.GetValue(pendingSQLDNSInfo) as string;
+ string ipv6 = s_pendingSQLDNS_AddrIPv6.GetValue(pendingSQLDNSInfo) as string;
+ string port = s_pendingSQLDNS_Port.GetValue(pendingSQLDNSInfo) as string;
+ return new Tuple(fqdn, ipv4, ipv6, port);
+ }
}
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs
new file mode 100644
index 0000000000..8003660889
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs
@@ -0,0 +1,125 @@
+// 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;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Reflection;
+using Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals;
+using Xunit;
+
+using static Microsoft.Data.SqlClient.ManualTesting.Tests.DataTestUtility;
+using static Microsoft.Data.SqlClient.ManualTesting.Tests.DNSCachingTest;
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class ConfigurableIpPreferenceTest
+ {
+ private const string CnnPrefIPv6 = ";IPAddressPreference=IPv6First";
+ private const string CnnPrefIPv4 = ";IPAddressPreference=IPv4First";
+
+ private static bool IsTCPConnectionStringSetup() => !string.IsNullOrEmpty(TCPConnectionString);
+ private static bool IsValidDataSource()
+ {
+ SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(TCPConnectionString);
+ int startIdx = builder.DataSource.IndexOf(':') + 1;
+ int endIdx = builder.DataSource.IndexOf(',');
+ string serverName;
+ if (endIdx == -1)
+ {
+ serverName = builder.DataSource.Substring(startIdx);
+ }
+ else
+ {
+ serverName = builder.DataSource.Substring(startIdx, endIdx - startIdx);
+ }
+
+ List ipAddresses = Dns.GetHostAddresses(serverName).ToList();
+ return ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetwork) &&
+ ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetworkV6);
+ }
+
+ [ConditionalTheory(nameof(IsTCPConnectionStringSetup), nameof(IsValidDataSource))]
+ [InlineData(CnnPrefIPv6)]
+ [InlineData(CnnPrefIPv4)]
+ [InlineData(";IPAddressPreference=UsePlatformDefault")]
+ public void ConfigurableIpPreference(string ipPreference)
+ {
+ using (SqlConnection connection = new SqlConnection(TCPConnectionString + ipPreference
+#if NETFRAMEWORK
+ + ";TransparentNetworkIPResolution=false" // doesn't support in .NET Core
+#endif
+ ))
+ {
+ connection.Open();
+ Assert.Equal(ConnectionState.Open, connection.State);
+ Tuple DNSInfo = connection.GetSQLDNSInfo();
+ if(ipPreference == CnnPrefIPv4)
+ {
+ Assert.NotNull(DNSInfo.Item2); //IPv4
+ Assert.Null(DNSInfo.Item3); //IPv6
+ }
+ else if(ipPreference == CnnPrefIPv6)
+ {
+ Assert.Null(DNSInfo.Item2);
+ Assert.NotNull(DNSInfo.Item3);
+ }
+ else
+ {
+ Assert.True((DNSInfo.Item2 != null && DNSInfo.Item3 == null) || (DNSInfo.Item2 == null && DNSInfo.Item3 != null));
+ }
+ }
+ }
+
+ // Azure SQL Server doesn't support dual-stack IPv4 and IPv6 that is going to be supported by end of 2021.
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DoesHostAddressContainBothIPv4AndIPv6), nameof(IsUsingManagedSNI))]
+ [InlineData(CnnPrefIPv6)]
+ [InlineData(CnnPrefIPv4)]
+ public void ConfigurableIpPreferenceManagedSni(string ipPreference)
+ => TestCachedConfigurableIpPreference(ipPreference, DNSCachingConnString);
+
+ private void TestCachedConfigurableIpPreference(string ipPreference, string cnnString)
+ {
+ using (SqlConnection connection = new SqlConnection(cnnString + ipPreference))
+ {
+ // each successful connection updates the dns cache entry for the data source
+ connection.Open();
+ var SQLFallbackDNSCacheInstance = GetDnsCache();
+
+ // get the dns cache entry with the given key. parameters[1] will be initialized as the entry
+ object[] parameters = new object[] { connection.DataSource, null };
+ SQLFallbackDNSCacheGetDNSInfo.Invoke(SQLFallbackDNSCacheInstance, parameters);
+ var dnsCacheEntry = parameters[1];
+
+ const string AddrIPv4Property = "AddrIPv4";
+ const string AddrIPv6Property = "AddrIPv6";
+ const string FQDNProperty = "FQDN";
+
+ Assert.NotNull(dnsCacheEntry);
+ Assert.Equal(connection.DataSource, GetPropertyValueFromCacheEntry(FQDNProperty, dnsCacheEntry));
+
+ if (ipPreference == CnnPrefIPv4)
+ {
+ Assert.NotNull(GetPropertyValueFromCacheEntry(AddrIPv4Property, dnsCacheEntry));
+ Assert.Null(GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry));
+ }
+ else if (ipPreference == CnnPrefIPv6)
+ {
+ string ipv6 = GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry);
+ Assert.NotNull(ipv6);
+ Assert.Null(GetPropertyValueFromCacheEntry(AddrIPv4Property, dnsCacheEntry));
+ }
+ }
+
+ object GetDnsCache() =>
+ SQLFallbackDNSCacheType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public).GetValue(null);
+
+ string GetPropertyValueFromCacheEntry(string property, object dnsCacheEntry) =>
+ (string)SQLDNSInfoType.GetProperty(property).GetValue(dnsCacheEntry);
+ }
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs
index 33460acb8d..a23efff073 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Reflection;
using Xunit;
diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json
index 4c725d8555..6c631187b5 100644
--- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json
+++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json
@@ -18,6 +18,11 @@
"SupportsLocalDb": false,
"SupportsFileStream": false,
"UseManagedSNIOnWindows": false,
+ "DNSCachingConnString": "",
+ "DNSCachingServerCR": "",
+ "DNSCachingServerTR": "",
+ "IsDNSCachingSupportedCR": false,
+ "IsDNSCachingSupportedTR": false,
"IsAzureSynapse": false,
"EnclaveAzureDatabaseConnString": "",
"UserManagedIdentityClientId": ""