From 612f25964be0184a88fe7fab7d04e639888c99c2 Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Tue, 23 Mar 2021 18:01:59 -0700 Subject: [PATCH 01/24] Add IPAddressPreference net core --- .../SqlConnectionIPAddressPreference.xml | 21 +++ .../SqlConnectionStringBuilder.xml | 4 + .../netcore/ref/Microsoft.Data.SqlClient.cs | 16 +++ .../Data/Common/DbConnectionStringCommon.cs | 134 ++++++++++++++++++ .../Microsoft/Data/SqlClient/SqlConnection.cs | 12 ++ .../Data/SqlClient/SqlConnectionString.cs | 36 ++++- .../SqlClient/SqlConnectionStringBuilder.cs | 46 +++++- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 13 ++ 8 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml new file mode 100644 index 0000000000..bd8f42a8dd --- /dev/null +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml @@ -0,0 +1,21 @@ + + + + + Specifies a value for Attestation Protocol. + + + + Connects with IPv4 address first. If connection fails, try IPv6 address if provided. Use this as default value. + 0 + + + Connects with IPv6 address first. If connection fails, try IPv4 address if provided. + 1 + + + Connects with IP addresses in the order based on platform configuration. + 2 + + + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 46e9ee6877..0fdd9d3599 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -346,6 +346,10 @@ False To set the value to null, use . + + Set/Get 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 0b8047b7bc..e76366478e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -396,6 +396,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 { @@ -879,6 +891,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/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionStringCommon.cs index eb23fcdfef..f693f1ebff 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 @@ -398,6 +398,135 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st } } + #endregion + + #region <> + + /// + /// IP Address Preference. + /// + const string IPAddrPreference46 = "IPv4First"; + const string IPAddrPreference64 = "IPv6First"; + const string IPAddrPreferenceOS = "UsePlatformDefault"; + + /// + /// Convert a string value to the corresponding IPAddressPreference + /// + /// + /// + /// + internal static bool TryConvertToIPAddressPreference(string value, out SqlConnectionIPAddressPreference result) + { + if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference46)) + { + result = SqlConnectionIPAddressPreference.IPv4First; + return true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference64)) + { + result = SqlConnectionIPAddressPreference.IPv6First; + return true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreferenceOS)) + { + result = SqlConnectionIPAddressPreference.UsePlatformDefault; + return true; + } + else + { + result = DbConnectionStringDefaults.IPAddressPreference; + return false; + } + } + + internal static bool IsValidIPAddressPreference(SqlConnectionIPAddressPreference value) + { + Debug.Assert(Enum.GetNames(typeof(SqlConnectionIPAddressPreference)).Length == 3, "SqlConnectionIPAddressPreference enum has changed, update needed"); + return value == SqlConnectionIPAddressPreference.IPv4First + || value == SqlConnectionIPAddressPreference.IPv6First + || value == SqlConnectionIPAddressPreference.UsePlatformDefault; + } + + internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value) + { + Debug.Assert(IsValidIPAddressPreference(value), "value is not a valid IP address preference"); + + switch (value) + { + case SqlConnectionIPAddressPreference.UsePlatformDefault: + return IPAddrPreferenceOS; + case SqlConnectionIPAddressPreference.IPv6First: + return IPAddrPreference64; + default: + return IPAddrPreference46; + } + } + + internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) + { + if (null == value) + { + return DbConnectionStringDefaults.IPAddressPreference; // IPv4First + } + + string sValue = (value as string); + SqlConnectionIPAddressPreference result; + + if (null != sValue) + { + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToIPAddressPreference(sValue, out 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) + { + eValue = (SqlConnectionIPAddressPreference)value; + } + 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) @@ -728,6 +857,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 +895,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 +924,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/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index 0f73866cdc..0e7407ed30 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 @@ -353,6 +353,18 @@ internal SqlConnectionAttestationProtocol AttestationProtocol } } + /// + /// Get IP address preference + /// + internal SqlConnectionIPAddressPreference iPAddressPreference + { + get + { + SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; + return opt.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..1e381d9662 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 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 @@ -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 { get { return _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.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..ae835188a3 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 { return _ipAddressPreference; } + set + { + if (!DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value)) + { + throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)value); + } + + SetIPAddressPreferenceValue(value); + _ipAddressPreference = value; + } + } + /// public bool TrustServerCertificate { @@ -904,6 +928,16 @@ private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(str return DbConnectionStringBuilderUtil.ConvertToAttestationProtocol(keyword, value); } + /// + /// Convert to SqlConnectionIPAddressPreference + /// + /// + /// + private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) + { + return DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); + } + private object GetAt(Keywords index) { switch (index) @@ -980,6 +1014,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 +1163,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 +1202,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 +1351,3 @@ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContex } } } - 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 { From b5c42ff21d4a312f2fb9b49177ac829d17d96bab Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Thu, 25 Mar 2021 14:35:44 -0700 Subject: [PATCH 02/24] netcore SynonymCount update --- .../src/Microsoft/Data/SqlClient/SqlConnectionString.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 1e381d9662..38a0ea8e46 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 @@ -164,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; From 5ca9b4a8b25e8de9bbec54d247d47be9eddbdda0 Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Thu, 25 Mar 2021 17:41:18 -0700 Subject: [PATCH 03/24] Add keyword in netfx --- .../netcore/src/Resources/Strings.Designer.cs | 9 ++ .../netcore/src/Resources/Strings.resx | 3 + .../netfx/ref/Microsoft.Data.SqlClient.cs | 17 +++ .../Data/Common/DbConnectionStringCommon.cs | 133 ++++++++++++++++++ .../Microsoft/Data/SqlClient/SqlConnection.cs | 12 ++ .../Data/SqlClient/SqlConnectionString.cs | 40 +++++- .../SqlClient/SqlConnectionStringBuilder.cs | 50 ++++++- .../src/Microsoft/Data/SqlClient/TdsEnums.cs | 13 ++ .../netfx/src/Resources/Strings.Designer.cs | 9 ++ .../netfx/src/Resources/Strings.resx | 3 + 10 files changed, 286 insertions(+), 3 deletions(-) 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 a351132431..229d20d92d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -4496,6 +4496,15 @@ 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 { + get { + return 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 6854ef7fad..f423b8c34d 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 1ec87b7897..dc947ad571 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -884,6 +884,19 @@ public enum SqlConnectionAttestationProtocol HGS = 3 } + /// + public enum SqlConnectionIPAddressPreference + { + /// + IPv4First = 0, // default + + /// + IPv6First = 1, + + /// + UsePlatformDefault = 2 + } + /// public enum SqlConnectionOverrides { @@ -970,6 +983,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..91ea777129 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,134 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st #endregion + #region <> + + /// + /// IP Address Preference. + /// + const string IPAddrPreference46 = "IPv4First"; + const string IPAddrPreference64 = "IPv6First"; + const string IPAddrPreferenceOS = "UsePlatformDefault"; + + /// + /// Convert a string value to the corresponding IPAddressPreference + /// + /// + /// + /// + internal static bool TryConvertToIPAddressPreference(string value, out SqlConnectionIPAddressPreference result) + { + if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference46)) + { + result = SqlConnectionIPAddressPreference.IPv4First; + return true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference64)) + { + result = SqlConnectionIPAddressPreference.IPv6First; + return true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreferenceOS)) + { + result = SqlConnectionIPAddressPreference.UsePlatformDefault; + return true; + } + else + { + result = DbConnectionStringDefaults.IPAddressPreference; + return false; + } + } + + internal static bool IsValidIPAddressPreference(SqlConnectionIPAddressPreference value) + { + Debug.Assert(Enum.GetNames(typeof(SqlConnectionIPAddressPreference)).Length == 3, "SqlConnectionIPAddressPreference enum has changed, update needed"); + return value == SqlConnectionIPAddressPreference.IPv4First + || value == SqlConnectionIPAddressPreference.IPv6First + || value == SqlConnectionIPAddressPreference.UsePlatformDefault; + } + + internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value) + { + Debug.Assert(IsValidIPAddressPreference(value), "value is not a valid IP address preference"); + + switch (value) + { + case SqlConnectionIPAddressPreference.UsePlatformDefault: + return IPAddrPreferenceOS; + case SqlConnectionIPAddressPreference.IPv6First: + return IPAddrPreference64; + default: + return IPAddrPreference46; + } + } + + internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) + { + if (null == value) + { + return DbConnectionStringDefaults.IPAddressPreference; // IPv4First + } + + string sValue = (value as string); + SqlConnectionIPAddressPreference result; + + if (null != sValue) + { + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToIPAddressPreference(sValue, out 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) + { + eValue = (SqlConnectionIPAddressPreference)value; + } + 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 +1193,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 +1268,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 +1294,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/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index e93ff01edf..e0627d14b5 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 @@ -551,6 +551,18 @@ internal SqlConnectionAttestationProtocol AttestationProtocol } } + /// + /// Get IP address preference + /// + internal SqlConnectionIPAddressPreference iPAddressPreference + { + get + { + SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; + return opt.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..9f89beb31e 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 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 { get { return _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.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..5ae648152a 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 { return _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,16 @@ private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(str return DbConnectionStringBuilderUtil.ConvertToAttestationProtocol(keyword, value); } + /// + /// Convert to SqlConnectionIPAddressPreference + /// + /// + /// + private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) + { + return DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); + } + private object GetAt(Keywords index) { switch (index) @@ -1357,6 +1395,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 +1592,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 +1641,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 +1972,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..8afebecd15 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/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index af7390b2a9..3a6ff879ac 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -12095,6 +12095,15 @@ 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 { + get { + return 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 d1b907c6e9..363ed678a9 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. From 444ab9bf88dd07e0484a27acdb69da8ff2b93567 Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Tue, 30 Mar 2021 17:38:57 -0700 Subject: [PATCH 04/24] Add IP preference logic --- .../Interop/SNINativeMethodWrapper.Windows.cs | 10 +- .../Microsoft/Data/SqlClient/SNI/SNIProxy.cs | 15 +- .../Data/SqlClient/SNI/SNITcpHandle.cs | 128 +++++++++++------- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 7 +- .../Data/SqlClient/TdsParserSafeHandles.cs | 7 +- .../Data/SqlClient/TdsParserStateObject.cs | 3 +- .../SqlClient/TdsParserStateObjectManaged.cs | 6 +- .../SqlClient/TdsParserStateObjectNative.cs | 7 +- .../Interop/SNINativeManagedWrapperX64.cs | 1 + .../Interop/SNINativeManagedWrapperX86.cs | 1 + .../Data/Interop/SNINativeMethodWrapper.cs | 14 +- .../src/Microsoft/Data/SqlClient/TdsParser.cs | 5 +- .../Data/SqlClient/TdsParserSafeHandles.cs | 7 +- .../Data/SqlClient/TdsParserStateObject.cs | 8 +- 14 files changed, 139 insertions(+), 80 deletions(-) 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 e3b91c6ee5..07511959c8 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)] @@ -347,7 +349,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(); @@ -359,10 +361,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]) { @@ -385,6 +388,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/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 98ed3f222b..0a2dfbae3f 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); @@ -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 cachedIPA; + string cachedIPB; + + if (SqlConnectionIPAddressPreference.IPv6First == ipPreference) { + cachedIPA = cachedDNSInfo.AddrIPv6; + cachedIPB = cachedDNSInfo.AddrIPv4; + } else { + cachedIPA = cachedDNSInfo.AddrIPv4; + cachedIPB = cachedDNSInfo.AddrIPv6; + } + try { if (parallel) { - _socket = TryConnectParallel(cachedDNSInfo.AddrIPv4, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); + _socket = TryConnectParallel(cachedIPA, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); } else { - _socket = Connect(cachedDNSInfo.AddrIPv4, portRetry, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo); + _socket = Connect(cachedIPA, 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(cachedIPB, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); } else { - _socket = Connect(cachedDNSInfo.AddrIPv6, portRetry, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo); + _socket = Connect(cachedIPB, portRetry, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo); } } else @@ -320,34 +332,25 @@ 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) { IPAddress[] ipAddresses = Dns.GetHostAddresses(serverName); string IPv4String = null; - string IPv6String = null; + string IPv6String = null; + + Socket[] sockets = new Socket[2]; + AddressFamily[] preferedIPFamilies = new AddressFamily[2]; - IPAddress serverIPv4 = null; - IPAddress serverIPv6 = null; - foreach (IPAddress ipAddress in ipAddresses) + if (ipPreference == SqlConnectionIPAddressPreference.IPv6First) { - if (ipAddress.AddressFamily == AddressFamily.InterNetwork) - { - serverIPv4 = ipAddress; - IPv4String = ipAddress.ToString(); - } - else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) - { - serverIPv6 = ipAddress; - IPv6String = ipAddress.ToString(); - } + preferedIPFamilies[0] = AddressFamily.InterNetworkV6; + preferedIPFamilies[1] = AddressFamily.InterNetwork; } - ipAddresses = new IPAddress[] { serverIPv4, serverIPv6 }; - Socket[] sockets = new Socket[2]; - - if (IPv4String != null || IPv6String != null) + else { - pendingDNSInfo = new SQLDNSInfo(cachedFQDN, IPv4String, IPv6String, port.ToString()); + preferedIPFamilies[0] = AddressFamily.InterNetwork; + preferedIPFamilies[1] = AddressFamily.InterNetworkV6; } CancellationTokenSource cts = null; @@ -380,37 +383,64 @@ void Cancel() Socket availableSocket = null; try { - for (int i = 0; i < sockets.Length; ++i) + // 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 traveral once. + for (int i = 0; i < preferedIPFamilies.Length; ++i) { - try + + foreach (IPAddress ipAddress in ipAddresses) { - if (ipAddresses[i] != null) + try { - sockets[i] = new Socket(ipAddresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); - - // enable keep-alive on socket - SetKeepAliveValues(ref sockets[i]); - - SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connecting to IP address {0} and port {1}", args0: ipAddresses[i], args1: port); - sockets[i].Connect(ipAddresses[i], port); - if (sockets[i] != null) // sockets[i] can be null if cancel callback is executed during connect() + if (ipAddress != null) { - if (sockets[i].Connected) + if (ipAddress.AddressFamily != preferedIPFamilies[i] && ipPreference != SqlConnectionIPAddressPreference.UsePlatformDefault) { - availableSocket = sockets[i]; - break; + continue; } - else + + sockets[i] = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + + // enable keep-alive on socket + SetKeepAliveValues(ref sockets[i]); + + SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.INFO, "Connecting to IP address {0} and port {1}", args0: ipAddress, args1: port); + sockets[i].Connect(ipAddress, port); + if (sockets[i] != null) // sockets[i] can be null if cancel callback is executed during connect() { - sockets[i].Dispose(); - sockets[i] = null; + if (sockets[i].Connected) + { + availableSocket = sockets[i]; + if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + { + IPv4String = ipAddress.ToString(); + } + else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) + { + IPv6String = ipAddress.ToString(); + } + break; + } + else + { + 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); + } } - catch (Exception e) + + // If we have already got an valid Socket, we won't do the second traversal. + if (availableSocket != null || ipPreference == SqlConnectionIPAddressPreference.UsePlatformDefault) { - SqlClientEventSource.Log.TrySNITraceEvent(s_className, EventType.ERR, "THIS EXCEPTION IS BEING SWALLOWED: {0}", args0: e?.Message); + break; } } } @@ -419,6 +449,12 @@ void Cancel() cts?.Dispose(); } + // we only record the ip we can connect with successfully. + if (IPv4String != null || IPv6String != null) + { + pendingDNSInfo = new SQLDNSInfo(cachedFQDN, IPv4String, IPv6String, port.ToString()); + } + return availableSocket; } 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 5e2bff208c..4017b2466c 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 c2e41d4cb3..359e70845d 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 1b7deb2a0b..462f86ea38 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) @@ -138,7 +138,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][]; @@ -172,7 +173,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/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs index 3b2549e5de..9d48404492 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 fc1e90750c..205c08896f 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 a79e0e71e5..2442378e99 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/TdsParser.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs index 2a5f4ca1c9..a3cabd62c4 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 c91b3285e2..fa15080023 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() From ce608d372c57c7c533cbff0610dcfe8d27f1d71b Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Wed, 31 Mar 2021 17:50:04 -0700 Subject: [PATCH 05/24] Fix incorrect description --- .../SqlConnectionIPAddressPreference.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml index bd8f42a8dd..e698c22bf3 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml @@ -2,7 +2,7 @@ - Specifies a value for Attestation Protocol. + Specifies a value for IP address preference when doing TCP connection. From 673dc391963970b663459dfa56e478c6e15f883a Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Wed, 7 Apr 2021 19:24:37 -0700 Subject: [PATCH 06/24] Apply suggestions from code review Co-authored-by: David Engel --- .../SqlConnectionIPAddressPreference.xml | 15 +++++++++++---- .../SqlConnectionStringBuilder.xml | 9 ++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml index e698c22bf3..8ea3d16f78 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml @@ -2,19 +2,26 @@ - Specifies a value for IP address preference when doing TCP connection. + Specifies a value for IP address preference during a TCP connection. + + + - Connects with IPv4 address first. If connection fails, try IPv6 address if provided. Use this as default value. + Connects using IPv4 address(es) first. If the connection fails, try IPv6 address(es), if provided. This is the default value. 0 - Connects with IPv6 address first. If connection fails, try IPv4 address if provided. + Connect using IPv6 address(es) first. If the connection fails, try IPv4 address(es), if available. 1 - Connects with IP addresses in the order based on platform configuration. + 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 0fdd9d3599..619ea2d690 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -347,8 +347,15 @@ False To set the value to null, use . - Set/Get the value of IP address preference. + 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. From 1f27eb727fa9dcfb5002b6714299b0642ced84fc Mon Sep 17 00:00:00 2001 From: Karina Zhou Date: Wed, 7 Apr 2021 19:30:04 -0700 Subject: [PATCH 07/24] Update DNS cache descripption --- .../netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 0a2dfbae3f..7bf3063aef 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 @@ -148,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) From 37a0b77d99ff1697f97307563a53ebd15c2d7e84 Mon Sep 17 00:00:00 2001 From: Johnny Pham <23270162+johnnypham@users.noreply.github.com> Date: Mon, 19 Apr 2021 14:44:12 -0700 Subject: [PATCH 08/24] tests for ip address preference --- .../SqlConnectionStringBuilderTest.cs | 2 + .../FunctionalTests/SqlConnectionTest.cs | 70 +++++++++++++++++-- .../ManualTests/DataCommon/DataTestUtility.cs | 17 +++++ ....Data.SqlClient.ManualTesting.Tests.csproj | 1 + .../ConfigurableIpPreferenceTest.cs | 67 ++++++++++++++++++ 5 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 9796b297c2..4f8802d340 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -55,6 +55,8 @@ 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("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..be96a2f1f8 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,65 @@ 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=IPv6First"; + cn.ConnectionString = "IPAddressPreference=UsePlatformDefault"; + } + + [Fact] + public void ConnectionString_IPAddressPreference_Invalid() + { + SqlConnection cn = new SqlConnection(); + + // number + try + { + cn.ConnectionString = "IPAddressPreference=-1"; + } + 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); + } + + // symbols + try + { + cn.ConnectionString = "IPAddressPreference=!@#"; + } + 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); + } + + // invalid ip preference + try + { + cn.ConnectionString = "IPAddressPreference=ABC"; + } + 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 58dd79df04..51d522bf04 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; @@ -345,6 +348,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 def31ffc10..79179a01d6 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/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs new file mode 100644 index 0000000000..42146395ea --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -0,0 +1,67 @@ +// 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.Reflection; +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 + { + [ConditionalTheory(typeof(DataTestUtility), nameof(DoesHostAddressContainBothIPv4AndIPv6))] + [InlineData(";IPAddressPreference=IPv6First")] + [InlineData(";IPAddressPreference=IPv4First")] + public void ConfigurableIpPreferenceManagedSni(string ipPreference) + { + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); + TestConfigurableIpPreference(ipPreference); + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", false); + } + + + private void TestConfigurableIpPreference(string ipPreference) + { + using (SqlConnection connection = new SqlConnection(DNSCachingConnString + 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.Equal(connection.DataSource, GetPropertyValueFromCacheEntry(FQDNProperty, dnsCacheEntry)); + + if (ipPreference == ";IPAddressPreference=IPv4First") + { + Assert.NotNull(GetPropertyValueFromCacheEntry(AddrIPv4Property, dnsCacheEntry)); + Assert.Null(GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry)); + } + else if (ipPreference == ";IPAddressPreference=IPv6First") + { + Assert.NotNull(GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry)); + Assert.Null(GetPropertyValueFromCacheEntry(AddrIPv4Property, dnsCacheEntry)); + } + string x = "Aabqsaada"; + Console.WriteLine(x); + } + + object GetDnsCache() => + SQLFallbackDNSCacheType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public).GetValue(null); + + string GetPropertyValueFromCacheEntry(string property, object dnsCacheEntry) => + (string)SQLDNSInfoType.GetProperty(property).GetValue(dnsCacheEntry); + } + } +} From 0b6c0e48249a03e967dab05cbdf4ee56e4253fb6 Mon Sep 17 00:00:00 2001 From: Johnny Pham <23270162+johnnypham@users.noreply.github.com> Date: Mon, 19 Apr 2021 14:49:18 -0700 Subject: [PATCH 09/24] Update SNITcpHandle.cs --- .../Data/SqlClient/SNI/SNITcpHandle.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 f25d898c77..e7fc09d53d 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 @@ -339,6 +339,15 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo string IPv4String = null; string IPv6String = null; + // Returning null socket is handled by the caller function. + if(ipAddresses == null || ipAddresses.Length == 0) + { + return null; + } + + Socket[] sockets = new Socket[ipAddresses.Length]; + AddressFamily[] preferedIPFamilies = new AddressFamily[2]; + if (ipPreference == SqlConnectionIPAddressPreference.IPv6First) { preferedIPFamilies[0] = AddressFamily.InterNetworkV6; @@ -349,14 +358,6 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo preferedIPFamilies[0] = AddressFamily.InterNetwork; preferedIPFamilies[1] = AddressFamily.InterNetworkV6; } - // Returning null socket is handled by the caller function. - 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; From 8fa43b89c1d037319c2a2ce3231dd4d6e8ff8697 Mon Sep 17 00:00:00 2001 From: Johnny Pham <23270162+johnnypham@users.noreply.github.com> Date: Fri, 23 Apr 2021 12:43:21 -0700 Subject: [PATCH 10/24] Update ConfigurableIpPreferenceTest.cs --- .../ConfigurableIpPreferenceTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs index 42146395ea..447bb10986 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -53,8 +53,6 @@ private void TestConfigurableIpPreference(string ipPreference) Assert.NotNull(GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry)); Assert.Null(GetPropertyValueFromCacheEntry(AddrIPv4Property, dnsCacheEntry)); } - string x = "Aabqsaada"; - Console.WriteLine(x); } object GetDnsCache() => From dfeea182a287013477333d6b0587fe6d2601403a Mon Sep 17 00:00:00 2001 From: Johnny Pham <23270162+johnnypham@users.noreply.github.com> Date: Fri, 23 Apr 2021 12:47:55 -0700 Subject: [PATCH 11/24] Update src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs --- .../netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e7fc09d53d..1ea30f0ff6 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 @@ -340,7 +340,7 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo 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; } From b5a625bd69b2ed9b9cc26431a330ed425d8f410b Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Fri, 30 Apr 2021 12:10:11 -0700 Subject: [PATCH 12/24] Improvement --- .../Data/SqlClient/SNI/SNITcpHandle.cs | 26 +++++---- .../SqlClient/SqlInternalConnectionTds.cs | 13 ++--- .../Data/SqlClient/SQLFallbackDNSCache.cs | 4 +- .../SqlConnectionStringBuilderTest.cs | 1 + .../FunctionalTests/SqlConnectionTest.cs | 53 +++++++------------ .../ConfigurableIpPreferenceTest.cs | 15 +++--- .../config.default.json | 3 ++ 7 files changed, 55 insertions(+), 60 deletions(-) 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 1ea30f0ff6..a136ba2642 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 @@ -334,6 +334,8 @@ private Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool i // 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, 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; @@ -348,16 +350,17 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo Socket[] sockets = new Socket[ipAddresses.Length]; AddressFamily[] preferedIPFamilies = new AddressFamily[2]; - if (ipPreference == SqlConnectionIPAddressPreference.IPv6First) - { - preferedIPFamilies[0] = AddressFamily.InterNetworkV6; - preferedIPFamilies[1] = AddressFamily.InterNetwork; - } - else + if (ipPreference == SqlConnectionIPAddressPreference.IPv4First) { 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; @@ -394,7 +397,7 @@ void Cancel() // 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 traveral once. + // For UsePlatformDefault preference, we do traversal once. for (int i = 0; i < preferedIPFamilies.Length; ++i) { foreach (IPAddress ipAddress in ipAddresses) @@ -413,7 +416,10 @@ void Cancel() // 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[n] can be null if cancel callback is executed during connect() { @@ -443,10 +449,12 @@ void Cancel() 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 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; 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/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 4f8802d340..4a61e10edd 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -57,6 +57,7 @@ public partial class SqlConnectionStringBuilderTest [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 be96a2f1f8..b3afc51527 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -1349,49 +1349,32 @@ 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"; } - [Fact] - public void ConnectionString_IPAddressPreference_Invalid() + [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(); - - // number - try - { - cn.ConnectionString = "IPAddressPreference=-1"; - } - 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); - } - - // symbols - try - { - cn.ConnectionString = "IPAddressPreference=!@#"; - } - 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); - } - - // invalid ip preference try { - cn.ConnectionString = "IPAddressPreference=ABC"; + cn.ConnectionString = value; + Assert.True(false, $"It mustn't come to this line; Value '{value}' should be invalid."); } catch (ArgumentException ex) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs index 447bb10986..fa49119817 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -13,9 +13,12 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class ConfigurableIpPreferenceTest { + private const string CnnPrefIPv6 = ";IPAddressPreference=IPv6First"; + private const string CnnPrefIPv4 = ";IPAddressPreference=IPv4First"; + [ConditionalTheory(typeof(DataTestUtility), nameof(DoesHostAddressContainBothIPv4AndIPv6))] - [InlineData(";IPAddressPreference=IPv6First")] - [InlineData(";IPAddressPreference=IPv4First")] + [InlineData(CnnPrefIPv6)] + [InlineData(CnnPrefIPv4)] public void ConfigurableIpPreferenceManagedSni(string ipPreference) { AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); @@ -23,7 +26,6 @@ public void ConfigurableIpPreferenceManagedSni(string ipPreference) AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", false); } - private void TestConfigurableIpPreference(string ipPreference) { using (SqlConnection connection = new SqlConnection(DNSCachingConnString + ipPreference)) @@ -43,14 +45,15 @@ private void TestConfigurableIpPreference(string ipPreference) Assert.Equal(connection.DataSource, GetPropertyValueFromCacheEntry(FQDNProperty, dnsCacheEntry)); - if (ipPreference == ";IPAddressPreference=IPv4First") + if (ipPreference == CnnPrefIPv4) { Assert.NotNull(GetPropertyValueFromCacheEntry(AddrIPv4Property, dnsCacheEntry)); Assert.Null(GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry)); } - else if (ipPreference == ";IPAddressPreference=IPv6First") + else if (ipPreference == CnnPrefIPv6) { - Assert.NotNull(GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry)); + string ipv6 = GetPropertyValueFromCacheEntry(AddrIPv6Property, dnsCacheEntry); + Assert.NotNull(ipv6); Assert.Null(GetPropertyValueFromCacheEntry(AddrIPv4Property, dnsCacheEntry)); } } 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..e2ab462507 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,9 @@ "SupportsLocalDb": false, "SupportsFileStream": false, "UseManagedSNIOnWindows": false, + "DNSCachingConnString": "", + "DNSCachingServerCR": "", + "DNSCachingServerTR": "", "IsAzureSynapse": false, "EnclaveAzureDatabaseConnString": "", "UserManagedIdentityClientId": "" From 5642e741821f33b90a3a19cbe490ee51efb7aefd Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Fri, 30 Apr 2021 13:09:56 -0700 Subject: [PATCH 13/24] Add new configurations --- BUILDGUIDE.md | 7 +++++++ .../config.default.json | 2 ++ 2 files changed, 9 insertions(+) diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 5a7d0080e1..5a23ec1951 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -121,7 +121,14 @@ Manual Tests require the below setup to run: |SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| |SupportsFileStream | (Optional) Whether or not FileStream is enabled on SQL Server| `true` OR `false`| |UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`| + |DNSCachingConnString | (Optional) Connection String for an Azure SQL Server with dual stack IPv4 and IPv6 network support| `Data Source={server.database.windows.net}; Initial Catalog={Azure_DB_Name};User ID={AAD_User}; Password={AAD_User_Password};`| + |DNSCachingServerCR | ???| ???| + |DNSCachingServerTR | ???| ???| + |IsDNSCachingSupportedCR | ???| ???| + |IsDNSCachingSupportedTR | ???| ???| |IsAzureSynpase | (Optional) When set to 'true', test suite runs compatible tests for Azure Synapse/Parallel Data Warehouse. | `true` OR `false`| + |EnclaveAzureDatabaseConnString | ???| ???| + |UserManagedIdentityClientId | ???| ???| ### Commands to run Manual Tests: 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 e2ab462507..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 @@ -21,6 +21,8 @@ "DNSCachingConnString": "", "DNSCachingServerCR": "", "DNSCachingServerTR": "", + "IsDNSCachingSupportedCR": false, + "IsDNSCachingSupportedTR": false, "IsAzureSynapse": false, "EnclaveAzureDatabaseConnString": "", "UserManagedIdentityClientId": "" From e8b3ca4bbc86f3094226ad2c33d15d25c307c000 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Fri, 30 Apr 2021 15:45:57 -0700 Subject: [PATCH 14/24] Revert "Add new configurations" This reverts commit 5642e741821f33b90a3a19cbe490ee51efb7aefd. --- BUILDGUIDE.md | 7 ------- .../config.default.json | 2 -- 2 files changed, 9 deletions(-) diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 5a23ec1951..5a7d0080e1 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -121,14 +121,7 @@ Manual Tests require the below setup to run: |SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| |SupportsFileStream | (Optional) Whether or not FileStream is enabled on SQL Server| `true` OR `false`| |UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`| - |DNSCachingConnString | (Optional) Connection String for an Azure SQL Server with dual stack IPv4 and IPv6 network support| `Data Source={server.database.windows.net}; Initial Catalog={Azure_DB_Name};User ID={AAD_User}; Password={AAD_User_Password};`| - |DNSCachingServerCR | ???| ???| - |DNSCachingServerTR | ???| ???| - |IsDNSCachingSupportedCR | ???| ???| - |IsDNSCachingSupportedTR | ???| ???| |IsAzureSynpase | (Optional) When set to 'true', test suite runs compatible tests for Azure Synapse/Parallel Data Warehouse. | `true` OR `false`| - |EnclaveAzureDatabaseConnString | ???| ???| - |UserManagedIdentityClientId | ???| ???| ### Commands to run Manual Tests: 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 6c631187b5..e2ab462507 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 @@ -21,8 +21,6 @@ "DNSCachingConnString": "", "DNSCachingServerCR": "", "DNSCachingServerTR": "", - "IsDNSCachingSupportedCR": false, - "IsDNSCachingSupportedTR": false, "IsAzureSynapse": false, "EnclaveAzureDatabaseConnString": "", "UserManagedIdentityClientId": "" From ac69f38a69f2321bb3be55efd4ddf00446b4ad4c Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Fri, 30 Apr 2021 15:47:57 -0700 Subject: [PATCH 15/24] Update default config --- .../Microsoft.Data.SqlClient.TestUtilities/config.default.json | 2 ++ 1 file changed, 2 insertions(+) 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 e2ab462507..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 @@ -21,6 +21,8 @@ "DNSCachingConnString": "", "DNSCachingServerCR": "", "DNSCachingServerTR": "", + "IsDNSCachingSupportedCR": false, + "IsDNSCachingSupportedTR": false, "IsAzureSynapse": false, "EnclaveAzureDatabaseConnString": "", "UserManagedIdentityClientId": "" From 16935d4ce85747a558da5c8a52ec0f2a61abd0e2 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Wed, 5 May 2021 22:15:06 -0700 Subject: [PATCH 16/24] Add test --- .../Data/SqlClient/SNI/SNITcpHandle.cs | 6 +- .../SystemDataInternals/ConnectionHelper.cs | 23 ++++++ .../ConfigurableIpPreferenceTest.cs | 70 ++++++++++++++++++- .../SQL/DNSCachingTest/DNSCachingTest.cs | 1 - 4 files changed, 92 insertions(+), 8 deletions(-) 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 a136ba2642..3d5d6ecf85 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 @@ -392,16 +392,15 @@ 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) @@ -443,7 +442,6 @@ void Cancel() sockets[n] = null; } } - n++; } } catch (Exception e) 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 index fa49119817..5c2e1346f2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -3,7 +3,13 @@ // 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; @@ -16,19 +22,76 @@ public class ConfigurableIpPreferenceTest private const string CnnPrefIPv6 = ";IPAddressPreference=IPv6First"; private const string CnnPrefIPv4 = ";IPAddressPreference=IPv4First"; + static ConfigurableIpPreferenceTest() + { + AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString", true); + } + + 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); + } + + // IPv6 configuration went to an issue with Ubuntu 16 that is following up to get fixed by Azure support. + [PlatformSpecific(TestPlatforms.Windows)] + [ConditionalTheory(nameof(IsTCPConnectionStringSetup), nameof(IsValidDataSource))] + [InlineData(CnnPrefIPv6)] + [InlineData(CnnPrefIPv4)] + [InlineData(";IPAddressPreference=UsePlatformDefault")] + public void ConfigurableIpPreference(string ipPreference) + { + using (SqlConnection connection = new SqlConnection(TCPConnectionString + ipPreference)) + { + 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))] [InlineData(CnnPrefIPv6)] [InlineData(CnnPrefIPv4)] public void ConfigurableIpPreferenceManagedSni(string ipPreference) { AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); - TestConfigurableIpPreference(ipPreference); + TestCachedConfigurableIpPreference(ipPreference, DNSCachingConnString); AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", false); } - private void TestConfigurableIpPreference(string ipPreference) + private void TestCachedConfigurableIpPreference(string ipPreference, string cnnString) { - using (SqlConnection connection = new SqlConnection(DNSCachingConnString + ipPreference)) + using (SqlConnection connection = new SqlConnection(cnnString + ipPreference)) { // each successful connection updates the dns cache entry for the data source connection.Open(); @@ -43,6 +106,7 @@ private void TestConfigurableIpPreference(string ipPreference) const string AddrIPv6Property = "AddrIPv6"; const string FQDNProperty = "FQDN"; + Assert.NotNull(dnsCacheEntry); Assert.Equal(connection.DataSource, GetPropertyValueFromCacheEntry(FQDNProperty, dnsCacheEntry)); if (ipPreference == CnnPrefIPv4) 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; From 4a1d7f7066b729f7daaf6ea17cfc044edeac6b40 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Tue, 11 May 2021 11:00:04 -0700 Subject: [PATCH 17/24] Apply suggestions from code review Co-authored-by: Javad Co-authored-by: Cheena Malhotra --- .../Data/Common/DbConnectionStringCommon.cs | 12 +++++------- .../src/Microsoft/Data/SqlClient/SqlConnection.cs | 6 +----- 2 files changed, 6 insertions(+), 12 deletions(-) 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 f693f1ebff..f8ef4333d1 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 @@ -464,19 +464,17 @@ internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPrefere internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) { - if (null == value) + if (value is null) { return DbConnectionStringDefaults.IPAddressPreference; // IPv4First } string sValue = (value as string); - SqlConnectionIPAddressPreference result; - - if (null != sValue) + if (sValue is not null) { // try again after remove leading & trailing whitespaces. sValue = sValue.Trim(); - if (TryConvertToIPAddressPreference(sValue, out result)) + if (TryConvertToIPAddressPreference(sValue, out SqlConnectionIPAddressPreference result)) { return result; } @@ -489,9 +487,9 @@ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(st // the value is not string, try other options SqlConnectionIPAddressPreference eValue; - if (value is SqlConnectionIPAddressPreference) + if (value is SqlConnectionIPAddressPreference preference) { - eValue = (SqlConnectionIPAddressPreference)value; + eValue = preference; } else if (value.GetType().IsEnum) { 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 f229bba751..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 @@ -396,11 +396,7 @@ internal SqlConnectionAttestationProtocol AttestationProtocol /// internal SqlConnectionIPAddressPreference iPAddressPreference { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt.IPAddressPreference; - } + get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference; } // This method will be called once connection string is set or changed. From c71143c166027fb7dd77cb75b34e66cadfe489fd Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Tue, 11 May 2021 16:00:20 -0700 Subject: [PATCH 18/24] Address comments + improvement --- .../Data/Common/DbConnectionStringCommon.cs | 67 ++++++---------- .../Data/SqlClient/SNI/SNITcpHandle.cs | 20 ++--- .../Data/Common/DbConnectionStringCommon.cs | 77 +++++++------------ 3 files changed, 60 insertions(+), 104 deletions(-) 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 f8ef4333d1..4bff26d475 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; @@ -401,66 +402,46 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st #endregion #region <> - /// /// IP Address Preference. /// - const string IPAddrPreference46 = "IPv4First"; - const string IPAddrPreference64 = "IPv6First"; - const string IPAddrPreferenceOS = "UsePlatformDefault"; - + 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 + /// 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 (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference46)) - { - result = SqlConnectionIPAddressPreference.IPv4First; - return true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference64)) - { - result = SqlConnectionIPAddressPreference.IPv6First; - return true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreferenceOS)) - { - result = SqlConnectionIPAddressPreference.UsePlatformDefault; - return true; - } - else + 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) - { - Debug.Assert(Enum.GetNames(typeof(SqlConnectionIPAddressPreference)).Length == 3, "SqlConnectionIPAddressPreference enum has changed, update needed"); - return value == SqlConnectionIPAddressPreference.IPv4First + => value == SqlConnectionIPAddressPreference.IPv4First || value == SqlConnectionIPAddressPreference.IPv6First || value == SqlConnectionIPAddressPreference.UsePlatformDefault; - } internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value) - { - Debug.Assert(IsValidIPAddressPreference(value), "value is not a valid IP address preference"); - - switch (value) - { - case SqlConnectionIPAddressPreference.UsePlatformDefault: - return IPAddrPreferenceOS; - case SqlConnectionIPAddressPreference.IPv6First: - return IPAddrPreference64; - default: - return IPAddrPreference46; - } - } + => Enum.GetName(typeof(SqlConnectionIPAddressPreference), value); internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) { @@ -523,8 +504,6 @@ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(st } } } - - #endregion internal static bool IsValidApplicationIntentValue(ApplicationIntent value) 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 3d5d6ecf85..88a1125c66 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 @@ -176,26 +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 cachedIPA; - string cachedIPB; + string firstCachedIP; + string secondCachedIP; if (SqlConnectionIPAddressPreference.IPv6First == ipPreference) { - cachedIPA = cachedDNSInfo.AddrIPv6; - cachedIPB = cachedDNSInfo.AddrIPv4; + firstCachedIP = cachedDNSInfo.AddrIPv6; + secondCachedIP = cachedDNSInfo.AddrIPv4; } else { - cachedIPA = cachedDNSInfo.AddrIPv4; - cachedIPB = cachedDNSInfo.AddrIPv6; + firstCachedIP = cachedDNSInfo.AddrIPv4; + secondCachedIP = cachedDNSInfo.AddrIPv6; } try { if (parallel) { - _socket = TryConnectParallel(cachedIPA, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); + _socket = TryConnectParallel(firstCachedIP, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); } else { - _socket = Connect(cachedIPA, portRetry, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo); + _socket = Connect(firstCachedIP, portRetry, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo); } } catch (Exception exRetry) @@ -206,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(cachedIPB, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); + _socket = TryConnectParallel(secondCachedIP, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); } else { - _socket = Connect(cachedIPB, portRetry, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo); + _socket = Connect(secondCachedIP, portRetry, ts, isInfiniteTimeOut, ipPreference, cachedFQDN, ref pendingDNSInfo); } } else 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 91ea777129..a0369403a6 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 @@ -999,82 +999,60 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st #endregion #region <> - /// /// IP Address Preference. /// - const string IPAddrPreference46 = "IPv4First"; - const string IPAddrPreference64 = "IPv6First"; - const string IPAddrPreferenceOS = "UsePlatformDefault"; - + 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 + /// 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 (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference46)) - { - result = SqlConnectionIPAddressPreference.IPv4First; - return true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreference64)) - { - result = SqlConnectionIPAddressPreference.IPv6First; - return true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, IPAddrPreferenceOS)) - { - result = SqlConnectionIPAddressPreference.UsePlatformDefault; - return true; - } - else + 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) - { - Debug.Assert(Enum.GetNames(typeof(SqlConnectionIPAddressPreference)).Length == 3, "SqlConnectionIPAddressPreference enum has changed, update needed"); - return value == SqlConnectionIPAddressPreference.IPv4First + => value == SqlConnectionIPAddressPreference.IPv4First || value == SqlConnectionIPAddressPreference.IPv6First || value == SqlConnectionIPAddressPreference.UsePlatformDefault; - } internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value) - { - Debug.Assert(IsValidIPAddressPreference(value), "value is not a valid IP address preference"); - - switch (value) - { - case SqlConnectionIPAddressPreference.UsePlatformDefault: - return IPAddrPreferenceOS; - case SqlConnectionIPAddressPreference.IPv6First: - return IPAddrPreference64; - default: - return IPAddrPreference46; - } - } + => Enum.GetName(typeof(SqlConnectionIPAddressPreference), value); internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) { - if (null == value) + if (value is null) { return DbConnectionStringDefaults.IPAddressPreference; // IPv4First } string sValue = (value as string); - SqlConnectionIPAddressPreference result; - - if (null != sValue) + if (sValue is not null) { // try again after remove leading & trailing whitespaces. sValue = sValue.Trim(); - if (TryConvertToIPAddressPreference(sValue, out result)) + if (TryConvertToIPAddressPreference(sValue, out SqlConnectionIPAddressPreference result)) { return result; } @@ -1087,9 +1065,9 @@ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(st // the value is not string, try other options SqlConnectionIPAddressPreference eValue; - if (value is SqlConnectionIPAddressPreference) + if (value is SqlConnectionIPAddressPreference preference) { - eValue = (SqlConnectionIPAddressPreference)value; + eValue = preference; } else if (value.GetType().IsEnum) { @@ -1123,7 +1101,6 @@ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(st } } } - #endregion internal static bool IsValidCertificateValue(string value) From 8464adbf9fa92f5f36633f052b4d0f4e27db48ef Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Tue, 11 May 2021 16:43:37 -0700 Subject: [PATCH 19/24] Apply suggestions from code review Co-authored-by: Cheena Malhotra --- .../netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 ae92827aeb..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 @@ -593,11 +593,7 @@ internal SqlConnectionAttestationProtocol AttestationProtocol /// internal SqlConnectionIPAddressPreference iPAddressPreference { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt.IPAddressPreference; - } + get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference; } // Is this connection is a Context Connection? From ebbc42b5ca9510659a4d66e62c8500aad60cbb47 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Wed, 12 May 2021 18:20:29 -0700 Subject: [PATCH 20/24] Address comments --- .../src/Microsoft/Data/Common/DbConnectionStringCommon.cs | 3 +-- .../src/Microsoft/Data/SqlClient/SqlConnectionString.cs | 6 +++--- .../Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs | 6 ++---- .../src/Microsoft/Data/Common/DbConnectionStringCommon.cs | 3 +-- .../src/Microsoft/Data/SqlClient/SqlConnectionString.cs | 6 +++--- .../Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs | 6 ++---- .../ConfigurableIpPreferenceTest.cs | 2 -- 7 files changed, 12 insertions(+), 20 deletions(-) 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 4bff26d475..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 @@ -450,8 +450,7 @@ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(st return DbConnectionStringDefaults.IPAddressPreference; // IPv4First } - string sValue = (value as string); - if (sValue is not null) + if (value is string sValue) { // try again after remove leading & trailing whitespaces. sValue = sValue.Trim(); 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 38a0ea8e46..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,7 +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 IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; + internal static readonly SqlConnectionIPAddressPreference s_IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; } // SqlConnection ConnectionString Options @@ -565,7 +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 { get { return _ipAddressPreference; } } + internal SqlConnectionIPAddressPreference IPAddressPreference => _ipAddressPreference; internal bool PersistSecurityInfo { get { return _persistSecurityInfo; } } internal bool Pooling { get { return _pooling; } } internal bool Replication { get { return _replication; } } @@ -916,7 +916,7 @@ internal SqlConnectionIPAddressPreference ConvertValueToIPAddressPreference() { if (!TryGetParsetableValue(KEY.IPAddressPreference, out string value)) { - return DEFAULT.IPAddressPreference; + return DEFAULT.s_IPAddressPreference; } try 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 ae835188a3..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 @@ -530,7 +530,7 @@ public SqlConnectionAttestationProtocol AttestationProtocol /// public SqlConnectionIPAddressPreference IPAddressPreference { - get { return _ipAddressPreference; } + get => _ipAddressPreference; set { if (!DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value)) @@ -934,9 +934,7 @@ private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(str /// /// private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) - { - return DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); - } + => DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); private object GetAt(Keywords index) { 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 a0369403a6..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 @@ -1047,8 +1047,7 @@ internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(st return DbConnectionStringDefaults.IPAddressPreference; // IPv4First } - string sValue = (value as string); - if (sValue is not null) + if (value is string sValue) { // try again after remove leading & trailing whitespaces. sValue = sValue.Trim(); 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 9f89beb31e..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,7 +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 IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; + internal static readonly SqlConnectionIPAddressPreference s_IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; #if ADONET_CERT_AUTH internal const string Certificate = _emptyString; @@ -688,7 +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 { get { return _ipAddressPreference; } } + internal SqlConnectionIPAddressPreference IPAddressPreference => _ipAddressPreference; #if ADONET_CERT_AUTH internal string Certificate { get { return _certificate; } } internal bool UsesCertificate { get { return _authType == SqlClient.SqlAuthenticationMethod.SqlCertificate; } } @@ -1097,7 +1097,7 @@ internal SqlConnectionIPAddressPreference ConvertValueToIPAddressPreference() string valStr = value as string; if (valStr == null) { - return DEFAULT.IPAddressPreference; + return DEFAULT.s_IPAddressPreference; } try 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 5ae648152a..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 @@ -703,7 +703,7 @@ public SqlConnectionAttestationProtocol AttestationProtocol [RefreshPropertiesAttribute(RefreshProperties.All)] public SqlConnectionIPAddressPreference IPAddressPreference { - get { return _ipAddressPreference; } + get => _ipAddressPreference; set { if (!DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value)) @@ -1301,9 +1301,7 @@ private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(str /// /// private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) - { - return DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); - } + => DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); private object GetAt(Keywords index) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs index 5c2e1346f2..0d2e0a53e7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -48,8 +48,6 @@ private static bool IsValidDataSource() ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetworkV6); } - // IPv6 configuration went to an issue with Ubuntu 16 that is following up to get fixed by Azure support. - [PlatformSpecific(TestPlatforms.Windows)] [ConditionalTheory(nameof(IsTCPConnectionStringSetup), nameof(IsValidDataSource))] [InlineData(CnnPrefIPv6)] [InlineData(CnnPrefIPv4)] From 12ff426c889808268a408aabf4816fd6f7fe6626 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Wed, 12 May 2021 18:33:34 -0700 Subject: [PATCH 21/24] Apply suggestions from code review --- .../netcore/src/Resources/Strings.Designer.cs | 7 ++----- .../netfx/src/Resources/Strings.Designer.cs | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) 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 afb8528d6a..9318d7eb40 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Strings.Designer.cs @@ -4446,11 +4446,8 @@ internal static string TCE_DbConnectionString_AttestationProtocol { /// /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances. /// - internal static string TCE_DbConnectionString_IPAddressPreference { - get { - return ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture); - } - } + 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/netfx/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs index 214e50ce86..f5b5f44a4c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.Designer.cs @@ -12081,11 +12081,8 @@ internal static string TCE_DbConnectionString_AttestationProtocol { /// /// Looks up a localized string similar to Specifies an IP address preference when connecting to SQL instances. /// - internal static string TCE_DbConnectionString_IPAddressPreference { - get { - return ResourceManager.GetString("TCE_DbConnectionString_IPAddressPreference", resourceCulture); - } - } + 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.. From a2bc3859950d484bde8b8e9b7e5fb9e8fd78329f Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Thu, 13 May 2021 16:20:58 -0700 Subject: [PATCH 22/24] Address comments + modify CancelAsyncConnections test --- .../Data/SqlClient/SNI/SNITcpHandle.cs | 38 +++++++++---------- .../AsyncCancelledConnectionsTest.cs | 10 ++--- .../ConfigurableIpPreferenceTest.cs | 8 +--- 3 files changed, 24 insertions(+), 32 deletions(-) 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 88a1125c66..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 @@ -364,25 +364,6 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo CancellationTokenSource cts = null; - 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); - } - } - } - if (!isInfiniteTimeout) { cts = new CancellationTokenSource(timeout); @@ -471,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/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/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs index 0d2e0a53e7..70b943c04b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -77,15 +77,11 @@ public void ConfigurableIpPreference(string ipPreference) } // 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))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DoesHostAddressContainBothIPv4AndIPv6), nameof(IsUsingManagedSNI))] [InlineData(CnnPrefIPv6)] [InlineData(CnnPrefIPv4)] public void ConfigurableIpPreferenceManagedSni(string ipPreference) - { - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); - TestCachedConfigurableIpPreference(ipPreference, DNSCachingConnString); - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", false); - } + => TestCachedConfigurableIpPreference(ipPreference, DNSCachingConnString); private void TestCachedConfigurableIpPreference(string ipPreference, string cnnString) { From 0f27fdede4460486244d3ef1c170b32b5ec35d20 Mon Sep 17 00:00:00 2001 From: DavoudEshtehari <61173489+DavoudEshtehari@users.noreply.github.com> Date: Fri, 14 May 2021 12:31:30 -0700 Subject: [PATCH 23/24] Update doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml Co-authored-by: Cheena Malhotra --- .../SqlConnectionIPAddressPreference.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml index 8ea3d16f78..21cb5127b6 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml @@ -8,7 +8,7 @@ From 019fce082712cc46480bbd9c66a5472317d5c486 Mon Sep 17 00:00:00 2001 From: Davoud Eshtehari Date: Fri, 14 May 2021 16:21:14 -0700 Subject: [PATCH 24/24] Address comments + Disable TNIR in connection string --- .../SqlConnectionIPAddressPreference.xml | 14 +++++++++++++- .../netfx/ref/Microsoft.Data.SqlClient.cs | 2 +- .../netfx/src/Microsoft/Data/SqlClient/TdsEnums.cs | 2 +- .../ConfigurableIpPreferenceTest.cs | 11 +++++------ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml index 21cb5127b6..e713cb776b 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml @@ -1,6 +1,6 @@ - + Specifies a value for IP address preference during a TCP connection. @@ -9,6 +9,18 @@ ## Remarks If `Multi Subnet Failover` or "Transparent Network IP Resolution" is set to `true`, this setting has no effect. +]]> + + + + + Specifies a value for IP address preference during a TCP connection. + + + 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 bd7a46fa07..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,7 +888,7 @@ public enum SqlConnectionAttestationProtocol HGS = 3 } - /// + /// public enum SqlConnectionIPAddressPreference { /// 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 8afebecd15..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,7 +1076,7 @@ public enum SqlConnectionAttestationProtocol HGS = 3 } - /// + /// public enum SqlConnectionIPAddressPreference { /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs index 70b943c04b..8003660889 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -22,11 +22,6 @@ public class ConfigurableIpPreferenceTest private const string CnnPrefIPv6 = ";IPAddressPreference=IPv6First"; private const string CnnPrefIPv4 = ";IPAddressPreference=IPv4First"; - static ConfigurableIpPreferenceTest() - { - AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString", true); - } - private static bool IsTCPConnectionStringSetup() => !string.IsNullOrEmpty(TCPConnectionString); private static bool IsValidDataSource() { @@ -54,7 +49,11 @@ private static bool IsValidDataSource() [InlineData(";IPAddressPreference=UsePlatformDefault")] public void ConfigurableIpPreference(string ipPreference) { - using (SqlConnection connection = new SqlConnection(TCPConnectionString + 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);