Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IP address preference support for TCP connection #1015

Merged
merged 29 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
612f259
Add IPAddressPreference net core
karinazhou Mar 24, 2021
2314a55
Merge branch 'main' of https://github.com/dotnet/SqlClient into IPAdd…
karinazhou Mar 24, 2021
b5c42ff
netcore SynonymCount update
karinazhou Mar 25, 2021
5ca9b4a
Add keyword in netfx
karinazhou Mar 26, 2021
444ab9b
Add IP preference logic
karinazhou Mar 31, 2021
ce608d3
Fix incorrect description
karinazhou Apr 1, 2021
673dc39
Apply suggestions from code review
karinazhou Apr 8, 2021
1f27eb7
Update DNS cache descripption
karinazhou Apr 8, 2021
5d42d26
Merge branch 'IPAddressPreference' of https://github.com/karinazhou/S…
karinazhou Apr 8, 2021
63d07bc
Merge remote-tracking branch 'upstream/main' into pr/1015
johnnypham Apr 16, 2021
37a0b77
tests for ip address preference
johnnypham Apr 19, 2021
0b6c0e4
Update SNITcpHandle.cs
johnnypham Apr 19, 2021
8fa43b8
Update ConfigurableIpPreferenceTest.cs
johnnypham Apr 23, 2021
dfeea18
Update src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlCli…
johnnypham Apr 23, 2021
b5a625b
Improvement
Apr 30, 2021
0cf2304
Merge remote-tracking branch 'UpStream/main' into IPAddressPreference
Apr 30, 2021
5642e74
Add new configurations
Apr 30, 2021
e8b3ca4
Revert "Add new configurations"
Apr 30, 2021
ac69f38
Update default config
Apr 30, 2021
16935d4
Add test
May 6, 2021
ee3dbd3
Merge remote-tracking branch 'UpStream/main' into IPAddressPreference
May 11, 2021
4a1d7f7
Apply suggestions from code review
DavoudEshtehari May 11, 2021
c71143c
Address comments
May 11, 2021
8464adb
Apply suggestions from code review
DavoudEshtehari May 11, 2021
ebbc42b
Address comments
May 13, 2021
12ff426
Apply suggestions from code review
DavoudEshtehari May 13, 2021
a2bc385
Address comments
May 13, 2021
0f27fde
Update doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPr…
DavoudEshtehari May 14, 2021
019fce0
Address comments
May 14, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<docs>
<members name="SqlConnectionIPAddressPreference">
<SqlConnectionIPAddressPreferenceNetfx>
<summary>
Specifies a value for IP address preference during a TCP connection.
</summary>
<remarks>
<format type="text/markdown"><![CDATA[

## Remarks
If `Multi Subnet Failover` or "Transparent Network IP Resolution" is set to `true`, this setting has no effect.
]]></format>
</remarks>
</SqlConnectionIPAddressPreferenceNetfx>
<SqlConnectionIPAddressPreference>
<summary>
Specifies a value for IP address preference during a TCP connection.
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
</summary>
karinazhou marked this conversation as resolved.
Show resolved Hide resolved
<remarks>
<format type="text/markdown"><![CDATA[

## Remarks
If `Multi Subnet Failover` is set to `true`, this setting has no effect.
DavoudEshtehari marked this conversation as resolved.
Show resolved Hide resolved
]]></format>
</remarks>
</SqlConnectionIPAddressPreference>
<IPv4First>
<summary>Connects using IPv4 address(es) first. If the connection fails, try IPv6 address(es), if provided. This is the default value.</summary>
<value>0</value>
</IPv4First>
<IPv6First>
<summary>Connect using IPv6 address(es) first. If the connection fails, try IPv4 address(es), if available.</summary>
<value>1</value>
</IPv6First>
<UsePlatformDefault>
<summary>Connects with IP addresses in the order the underlying platform or operating system provides them.</summary>
<value>2</value>
</UsePlatformDefault>
</members>
</docs>
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,17 @@ False
</remarks>
<exception cref="T:System.ArgumentNullException">To set the value to null, use <see cref="F:System.DBNull.Value" />.</exception>
</DataSource>
<IPAddressPreference>
<summary>Gets or sets the value of IP address preference.</summary>
<returns>Returns IP address preference.</returns>
karinazhou marked this conversation as resolved.
Show resolved Hide resolved
<remarks>
<format type="text/markdown"><![CDATA[

## Remarks
If `Multi Subnet Failover` is set to `true`, this setting has no effect.
]]></format>
</remarks>
</IPAddressPreference>
<EnclaveAttestationUrl>
<summary>Gets or sets the enclave attestation Url to be used with enclave based Always Encrypted.</summary>
<value>The enclave attestation Url.</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,18 @@ public enum SqlConnectionAttestationProtocol
HGS = 3
}
#endif
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml' path='docs/members[@name="SqlConnectionIPAddressPreference"]/SqlConnectionIPAddressPreference/*' />
public enum SqlConnectionIPAddressPreference
{
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml' path='docs/members[@name="SqlConnectionIPAddressPreference"]/IPv4First/*' />
IPv4First = 0, // default

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml' path='docs/members[@name="SqlConnectionIPAddressPreference"]/IPv6First/*' />
IPv6First = 1,

/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionIPAddressPreference.xml' path='docs/members[@name="SqlConnectionIPAddressPreference"]/UsePlatformDefault/*' />
UsePlatformDefault = 2
}
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCertificateStoreProvider.xml' path='docs/members[@name="SqlColumnEncryptionCertificateStoreProvider"]/SqlColumnEncryptionCertificateStoreProvider/*'/>
public partial class SqlColumnEncryptionCertificateStoreProvider : Microsoft.Data.SqlClient.SqlColumnEncryptionKeyStoreProvider
{
Expand Down Expand Up @@ -883,6 +895,10 @@ public SqlConnectionStringBuilder(string connectionString) { }
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public string EnclaveAttestationUrl { get { throw null; } set { } }
#endif
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/IPAddressPreference/*'/>
[System.ComponentModel.DisplayNameAttribute("IP Address Preference")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
public Microsoft.Data.SqlClient.SqlConnectionIPAddressPreference IPAddressPreference { get { throw null; } set { } }
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml' path='docs/members[@name="SqlConnectionStringBuilder"]/Encrypt/*'/>
[System.ComponentModel.DisplayNameAttribute("Encrypt")]
[System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -341,7 +343,7 @@ internal static uint SNIInitialize()
return SNIInitialize(IntPtr.Zero);
}

internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SQLDNSInfo cachedDNSInfo)
internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo)
{
// initialize consumer info for MARS
Sni_Consumer_Info native_consumerInfo = new Sni_Consumer_Info();
Expand All @@ -353,10 +355,11 @@ internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHan
native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port;

return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ref native_cachedDNSInfo);
return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ipPreference, ref native_cachedDNSInfo);
}

internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, bool fSync, int timeout, bool fParallel, SQLDNSInfo cachedDNSInfo)
internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache,
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
bool fSync, int timeout, bool fParallel, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo)
{
fixed (byte* pin_instanceName = &instanceName[0])
{
Expand All @@ -379,6 +382,7 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons
clientConsumerInfo.totalTimeout = SniOpenTimeOut;
clientConsumerInfo.isAzureSqlServerEndpoint = ADP.IsAzureSqlServerEndpoint(constring);

clientConsumerInfo.ipAddressPreference = ipPreference;
clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN;
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4;
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -400,6 +401,110 @@ internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(st

#endregion

#region <<IPAddressPreference Utility>>
/// <summary>
/// IP Address Preference.
/// </summary>
private readonly static Dictionary<string, SqlConnectionIPAddressPreference> s_preferenceNames = new(StringComparer.InvariantCultureIgnoreCase);

static DbConnectionStringBuilderUtil()
{
foreach (SqlConnectionIPAddressPreference item in Enum.GetValues(typeof(SqlConnectionIPAddressPreference)))
{
s_preferenceNames.Add(item.ToString(), item);
}
}

/// <summary>
/// Convert a string value to the corresponding IPAddressPreference.
/// </summary>
/// <param name="value">The string representation of the enumeration name to convert.</param>
/// <param name="result">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.</param>
/// <returns>`true` if the value parameter was converted successfully; otherwise, `false`.</returns>
internal static bool TryConvertToIPAddressPreference(string value, out SqlConnectionIPAddressPreference result)
{
if (!s_preferenceNames.TryGetValue(value, out result))
{
result = DbConnectionStringDefaults.IPAddressPreference;
return false;
}
return true;
}

/// <summary>
/// Verifies if the `value` is defined in the expected Enum.
/// </summary>
internal static bool IsValidIPAddressPreference(SqlConnectionIPAddressPreference value)
=> value == SqlConnectionIPAddressPreference.IPv4First
|| value == SqlConnectionIPAddressPreference.IPv6First
|| value == SqlConnectionIPAddressPreference.UsePlatformDefault;

internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value)
=> Enum.GetName(typeof(SqlConnectionIPAddressPreference), value);

internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value)
{
if (value is null)
{
return DbConnectionStringDefaults.IPAddressPreference; // IPv4First
}

if (value is string sValue)
{
// try again after remove leading & trailing whitespaces.
sValue = sValue.Trim();
if (TryConvertToIPAddressPreference(sValue, out SqlConnectionIPAddressPreference result))
{
return result;
}

// string values must be valid
throw ADP.InvalidConnectionOptionValue(keyword);
}
else
{
// the value is not string, try other options
SqlConnectionIPAddressPreference eValue;

if (value is SqlConnectionIPAddressPreference preference)
{
eValue = preference;
}
else if (value.GetType().IsEnum)
{
// explicitly block scenarios in which user tries to use wrong enum types, like:
// builder["SqlConnectionIPAddressPreference"] = EnvironmentVariableTarget.Process;
// workaround: explicitly cast non-SqlConnectionIPAddressPreference enums to int
throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), null);
}
else
{
try
{
// Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest
eValue = (SqlConnectionIPAddressPreference)Enum.ToObject(typeof(SqlConnectionIPAddressPreference), value);
}
catch (ArgumentException e)
{
// to be consistent with the messages we send in case of wrong type usage, replace
// the error with our exception, and keep the original one as inner one for troubleshooting
throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), e);
}
}

if (IsValidIPAddressPreference(eValue))
{
return eValue;
}
else
{
throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)eValue);
}
}
}
#endregion

internal static bool IsValidApplicationIntentValue(ApplicationIntent value)
{
Debug.Assert(Enum.GetNames(typeof(ApplicationIntent)).Length == 2, "ApplicationIntent enum has changed, update needed");
Expand Down Expand Up @@ -728,6 +833,7 @@ internal static partial class DbConnectionStringDefaults
internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled;
internal const string EnclaveAttestationUrl = _emptyString;
internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified;
internal const SqlConnectionIPAddressPreference IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First;
}


Expand Down Expand Up @@ -765,6 +871,7 @@ internal static partial class DbConnectionStringKeywords
internal const string ColumnEncryptionSetting = "Column Encryption Setting";
internal const string EnclaveAttestationUrl = "Enclave Attestation Url";
internal const string AttestationProtocol = "Attestation Protocol";
internal const string IPAddressPreference = "IP Address Preference";

// common keywords (OleDb, OracleClient, SqlClient)
internal const string DataSource = "Data Source";
Expand Down Expand Up @@ -793,6 +900,9 @@ internal static class DbConnectionStringSynonyms
//internal const string ApplicationName = APP;
internal const string APP = "app";

// internal const string IPAddressPreference = IPADDRESSPREFERENCE;
internal const string IPADDRESSPREFERENCE = "IPAddressPreference";

//internal const string ApplicationIntent = APPLICATIONINTENT;
internal const string APPLICATIONINTENT = "ApplicationIntent";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,12 @@ internal uint WritePacket(SNIHandle handle, SNIPacket packet, bool sync)
/// <param name="async">Asynchronous connection</param>
/// <param name="parallel">Attempt parallel connects</param>
/// <param name="isIntegratedSecurity"></param>
/// <param name="ipPreference">IP address preference</param>
/// <param name="cachedFQDN">Used for DNS Cache</param>
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
/// <returns>SNI handle</returns>
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];

Expand All @@ -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);
Expand Down Expand Up @@ -374,10 +376,11 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr
/// <param name="details">Data source</param>
/// <param name="timerExpire">Timer expiration</param>
/// <param name="parallel">Should MultiSubnetFailover be used</param>
/// <param name="ipPreference">IP address preference</param>
/// <param name="cachedFQDN">Key for DNS Cache</param>
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
/// <returns>SNITCPHandle</returns>
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:<host name>\<instance name>
Expand Down Expand Up @@ -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);
}


Expand Down
Loading