Skip to content

Commit ead806e

Browse files
authored
Add DNS Caching Phase 1 (#594)
This PR adds support for the upcoming DNS Caching feature on the server-side. There are two phases for this feature and this is the first phase support. When using TCP protocol, the SqlClient driver will record the DNS information such as IP address and port number in an in-memory cache if the server sends back the corresponding TDS token for this caching. The DNS information will stay in the cache during the application lifetime unless the server tells the driver to clean the cache. In phase 1, the IP and port number are from the DNS resolution during pre-login handshake. If the DNS server fails to resolve the server name, the driver will connect with its cached IP and port if they existed. Otherwise, the connection will fail. Add support for both .NET Framework and .NET Core. This change requires the corresponding change in native SNI. The caching behavior is only for TCP connections. Need to fail the DNS Server to test this behavior.
1 parent 033541f commit ead806e

33 files changed

+1200
-73
lines changed

doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,23 @@ GO
10551055
]]></format>
10561056
</remarks>
10571057
</RetrieveStatistics>
1058+
<RetrieveInternalInfo>
1059+
<summary>Returns a name value pair collection of internal properties at the point in time the method is called.</summary>
1060+
<returns>Returns a reference of type <see cref="T:System.Collections.Generic.IDictionary" /> of (string, object) items.</returns>
1061+
<remarks>
1062+
<format type="text/markdown"><![CDATA[
1063+
1064+
## Remarks
1065+
When this method is called, the values retrieved are those at the current point in time. If you continue using the connection, the values are incorrect. You need to re-execute the method to obtain the most current values.
1066+
1067+
|Supported internal properties|Type|Information provided|Return value|
1068+
|-----------------------------|---------|----------------------------|------------|
1069+
|`SQLDNSCachingSupportedState`|string|To indicate the IsSupported flag sent by the server for DNS Caching|"true", "false", "innerConnection is null!"|
1070+
|`SQLDNSCachingSupportedStateBeforeRedirect`|string|To indicate the IsSupported flag sent by the server for DNS Caching before redirection.|"true", "false", "innerConnection is null!"|
1071+
1072+
]]></format>
1073+
</remarks>
1074+
</RetrieveInternalInfo>
10581075
<ServerVersion>
10591076
<summary>Gets a string that contains the version of the instance of SQL Server to which the client is connected.</summary>
10601077
<value>The version of the instance of SQL Server.</value>

src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,18 @@ public SqlConnection(string connectionString, Microsoft.Data.SqlClient.SqlCreden
555555
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/ClientConnectionId/*'/>
556556
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
557557
public System.Guid ClientConnectionId { get { throw null; } }
558+
559+
///
560+
/// for internal test only
561+
///
562+
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
563+
internal string SQLDNSCachingSupportedState { get { throw null; } }
564+
///
565+
/// for internal test only
566+
///
567+
[System.ComponentModel.DesignerSerializationVisibilityAttribute(0)]
568+
internal string SQLDNSCachingSupportedStateBeforeRedirect { get { throw null; } }
569+
558570
object System.ICloneable.Clone() { throw null; }
559571
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/ConnectionString/*'/>
560572
[System.ComponentModel.DefaultValueAttribute("")]
@@ -639,6 +651,9 @@ public void Open(SqlConnectionOverrides overrides) { }
639651
public void ResetStatistics() { }
640652
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/RetrieveStatistics/*'/>
641653
public System.Collections.IDictionary RetrieveStatistics() { throw null; }
654+
655+
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml' path='docs/members[@name="SqlConnection"]/RetrieveInternalInfo/*'/>
656+
public System.Collections.Generic.IDictionary<string, object> RetrieveInternalInfo() { throw null; }
642657
}
643658
/// <include file='../../../../doc/snippets/Microsoft.Data.SqlClient/SqlConnectionOverrides.xml' path='docs/members[@name="SqlConnectionOverrides"]/SqlConnectionOverrides/*' />
644659
public enum SqlConnectionOverrides

src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.Data.SqlClient;
77
using System;
88
using System.Runtime.InteropServices;
9+
using System.Text;
910

1011
namespace Microsoft.Data.SqlClient
1112
{
@@ -20,6 +21,8 @@ internal static partial class SNINativeMethodWrapper
2021
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
2122
internal delegate void SqlAsyncCallbackDelegate(IntPtr m_ConsKey, IntPtr pPacket, uint dwError);
2223

24+
internal const int SniIP6AddrStringBufferLength = 48; // from SNI layer
25+
2326
internal static int SniMaxComposedSpnLength
2427
{
2528
get
@@ -162,6 +165,20 @@ private unsafe struct SNI_CLIENT_CONSUMER_INFO
162165
public TransparentNetworkResolutionMode transparentNetworkResolution;
163166
public int totalTimeout;
164167
public bool isAzureSqlServerEndpoint;
168+
public SNI_DNSCache_Info DNSCacheInfo;
169+
}
170+
171+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
172+
internal struct SNI_DNSCache_Info
173+
{
174+
[MarshalAs(UnmanagedType.LPWStr)]
175+
public string wszCachedFQDN;
176+
[MarshalAs(UnmanagedType.LPWStr)]
177+
public string wszCachedTcpIPv4;
178+
[MarshalAs(UnmanagedType.LPWStr)]
179+
public string wszCachedTcpIPv6;
180+
[MarshalAs(UnmanagedType.LPWStr)]
181+
public string wszCachedTcpPort;
165182
}
166183

167184
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
@@ -236,6 +253,15 @@ internal struct SNI_Error
236253
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
237254
private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out Guid pbQInfo);
238255

256+
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
257+
private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ushort portNum);
258+
259+
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
260+
private static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen);
261+
262+
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
263+
private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ProviderEnum provNum);
264+
239265
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
240266
private static extern uint SNIInitialize([In] IntPtr pmo);
241267

@@ -248,7 +274,8 @@ private static extern uint SNIOpenWrapper(
248274
[MarshalAs(UnmanagedType.LPWStr)] string szConnect,
249275
[In] SNIHandle pConn,
250276
out IntPtr ppConn,
251-
[MarshalAs(UnmanagedType.Bool)] bool fSync);
277+
[MarshalAs(UnmanagedType.Bool)] bool fSync,
278+
[In] ref SNI_DNSCache_Info pDNSCachedInfo);
252279

253280
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl)]
254281
private static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IOType IOType);
@@ -283,22 +310,53 @@ internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId)
283310
{
284311
return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_CONNID, out connId);
285312
}
313+
314+
internal static uint SniGetProviderNumber(SNIHandle pConn, ref ProviderEnum provNum)
315+
{
316+
return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PROVIDERNUM, out provNum);
317+
}
318+
319+
internal static uint SniGetConnectionPort(SNIHandle pConn, ref ushort portNum)
320+
{
321+
return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PEERPORT, out portNum);
322+
}
323+
324+
internal static uint SniGetConnectionIPString(SNIHandle pConn, ref string connIPStr)
325+
{
326+
UInt32 ret;
327+
uint connIPLen = 0;
328+
329+
int bufferSize = SniIP6AddrStringBufferLength;
330+
StringBuilder addrBuffer = new StringBuilder(bufferSize);
331+
332+
ret = SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out connIPLen);
333+
334+
connIPStr = addrBuffer.ToString(0, Convert.ToInt32(connIPLen));
335+
336+
return ret;
337+
}
286338

287339
internal static uint SNIInitialize()
288340
{
289341
return SNIInitialize(IntPtr.Zero);
290342
}
291343

292-
internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync)
344+
internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SQLDNSInfo cachedDNSInfo)
293345
{
294346
// initialize consumer info for MARS
295347
Sni_Consumer_Info native_consumerInfo = new Sni_Consumer_Info();
296348
MarshalConsumerInfo(consumerInfo, ref native_consumerInfo);
297349

298-
return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync);
350+
SNI_DNSCache_Info native_cachedDNSInfo = new SNI_DNSCache_Info();
351+
native_cachedDNSInfo.wszCachedFQDN = cachedDNSInfo?.FQDN;
352+
native_cachedDNSInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4;
353+
native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
354+
native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port;
355+
356+
return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ref native_cachedDNSInfo);
299357
}
300358

301-
internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string constring, ref IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, bool fSync, int timeout, bool fParallel)
359+
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)
302360
{
303361
fixed (byte* pin_instanceName = &instanceName[0])
304362
{
@@ -321,6 +379,11 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons
321379
clientConsumerInfo.totalTimeout = SniOpenTimeOut;
322380
clientConsumerInfo.isAzureSqlServerEndpoint = ADP.IsAzureSqlServerEndpoint(constring);
323381

382+
clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN;
383+
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4;
384+
clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6;
385+
clientConsumerInfo.DNSCacheInfo.wszCachedTcpPort = cachedDNSInfo?.Port;
386+
324387
if (spnBuffer != null)
325388
{
326389
fixed (byte* pin_spnBuffer = &spnBuffer[0])

src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@
147147
<Compile Include="..\..\src\Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs">
148148
<Link>Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs</Link>
149149
</Compile>
150+
<Compile Include="..\..\src\Microsoft\Data\SqlClient\SQLFallbackDNSCache.cs">
151+
<Link>Microsoft\Data\SqlClient\SQLFallbackDNSCache.cs</Link>
152+
</Compile>
150153
</ItemGroup>
151154
<ItemGroup Condition="'$(TargetGroup)' == 'netstandard' OR '$(TargetGroup)' == 'netcoreapp' OR '$(IsUAPAssembly)' == 'true'">
152155
<Compile Include="Microsoft.Data.SqlClient.TypeForwards.cs" />

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,10 @@ public uint WritePacket(SNIHandle handle, SNIPacket packet, bool sync)
263263
/// <param name="async">Asynchronous connection</param>
264264
/// <param name="parallel">Attempt parallel connects</param>
265265
/// <param name="isIntegratedSecurity"></param>
266+
/// <param name="cachedFQDN">Used for DNS Cache</param>
267+
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
266268
/// <returns>SNI handle</returns>
267-
public SNIHandle CreateConnectionHandle(object callbackObject, string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[] spnBuffer, bool flushCache, bool async, bool parallel, bool isIntegratedSecurity)
269+
public SNIHandle CreateConnectionHandle(object callbackObject, 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)
268270
{
269271
instanceName = new byte[1];
270272

@@ -291,7 +293,7 @@ public SNIHandle CreateConnectionHandle(object callbackObject, string fullServer
291293
case DataSource.Protocol.Admin:
292294
case DataSource.Protocol.None: // default to using tcp if no protocol is provided
293295
case DataSource.Protocol.TCP:
294-
sniHandle = CreateTcpHandle(details, timerExpire, callbackObject, parallel);
296+
sniHandle = CreateTcpHandle(details, timerExpire, callbackObject, parallel, cachedFQDN, ref pendingDNSInfo);
295297
break;
296298
case DataSource.Protocol.NP:
297299
sniHandle = CreateNpHandle(details, timerExpire, callbackObject, parallel);
@@ -373,8 +375,10 @@ private static byte[] GetSqlServerSPN(string hostNameOrAddress, string portOrIns
373375
/// <param name="timerExpire">Timer expiration</param>
374376
/// <param name="callbackObject">Asynchronous I/O callback object</param>
375377
/// <param name="parallel">Should MultiSubnetFailover be used</param>
378+
/// <param name="cachedFQDN">Key for DNS Cache</param>
379+
/// <param name="pendingDNSInfo">Used for DNS Cache</param>
376380
/// <returns>SNITCPHandle</returns>
377-
private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, object callbackObject, bool parallel)
381+
private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, object callbackObject, bool parallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo)
378382
{
379383
// TCP Format:
380384
// tcp:<host name>\<instance name>
@@ -412,7 +416,7 @@ private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, objec
412416
port = isAdminConnection ? DefaultSqlServerDacPort : DefaultSqlServerPort;
413417
}
414418

415-
return new SNITCPHandle(hostName, port, timerExpire, callbackObject, parallel);
419+
return new SNITCPHandle(hostName, port, timerExpire, callbackObject, parallel, cachedFQDN, ref pendingDNSInfo);
416420
}
417421

418422

0 commit comments

Comments
 (0)