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 2 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
Expand Up @@ -339,9 +339,6 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo
string IPv4String = null;
string IPv6String = null;

Socket[] sockets = new Socket[2];
AddressFamily[] preferedIPFamilies = new AddressFamily[2];

if (ipPreference == SqlConnectionIPAddressPreference.IPv6First)
{
preferedIPFamilies[0] = AddressFamily.InterNetworkV6;
Expand All @@ -352,6 +349,14 @@ 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)
johnnypham marked this conversation as resolved.
Show resolved Hide resolved
{
return null;
}

Socket[] sockets = new Socket[ipAddresses.Length];
AddressFamily[] preferedIPFamilies = new AddressFamily[] { AddressFamily.InterNetwork, AddressFamily.InterNetworkV6 };

CancellationTokenSource cts = null;

Expand Down Expand Up @@ -383,13 +388,14 @@ 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 traveral once.
for (int i = 0; i < preferedIPFamilies.Length; ++i)
{

foreach (IPAddress ipAddress in ipAddresses)
{
try
Expand All @@ -401,18 +407,18 @@ void Cancel()
continue;
}

JRahnama marked this conversation as resolved.
Show resolved Hide resolved
sockets[i] = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
sockets[n] = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

// enable keep-alive on socket
SetKeepAliveValues(ref sockets[i]);
SetKeepAliveValues(ref sockets[n]);

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[n].Connect(ipAddress, port);
if (sockets[n] != null) // sockets[n] can be null if cancel callback is executed during connect()
{
if (sockets[i].Connected)
if (sockets[n].Connected)
{
availableSocket = sockets[i];
availableSocket = sockets[n];
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
IPv4String = ipAddress.ToString();
Expand All @@ -421,12 +427,13 @@ void Cancel()
{
IPv6String = ipAddress.ToString();
}

break;
}
else
{
sockets[i].Dispose();
sockets[i] = null;
sockets[n].Dispose();
sockets[n] = null;
}
}
n++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand All @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));

Expand All @@ -1343,5 +1343,65 @@ public void RetrieveInternalInfo_UnexpectedKeysInDictionary_Success()
IDictionary<string, object> 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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<IPAddress> ipAddresses = Dns.GetHostAddresses(connection.DataSource).ToList();
return ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetwork) &&
ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetworkV6);
}
}

/// <summary>
/// Generate a unique name to use in Sql Server;
/// some providers does not support names (Oracle supports up to 30).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<Link>Common\System\Collections\DictionaryExtensions.cs</Link>
</Compile>
<Compile Include="DataCommon\AADUtility.cs" />
<Compile Include="SQL\ConfigurableIpPreferenceTest\ConfigurableIpPreferenceTest.cs" />
<None Include="SQL\RetryLogic\Resources\CustomConfigurableRetryLogic.cs" />
<Compile Include="SQL\RetryLogic\RetryLogicConfigHelper.cs" />
<Compile Include="SQL\SqlBulkCopyTest\OrderHint.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}