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 1309b646e7..cf53e8ecff 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 @@ -434,6 +434,8 @@ internal void Connect(ServerInfo serverInfo, false, true, fParallel, + TransparentNetworkResolutionState.DisabledMode, + -1, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, @@ -532,6 +534,8 @@ internal void Connect(ServerInfo serverInfo, true, true, fParallel, + TransparentNetworkResolutionState.DisabledMode, + -1, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject, diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 5eb4464b54..86409c3b3b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -21,7 +21,7 @@ internal abstract partial class TdsParserStateObject // Constructors // ////////////////// - internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) + protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) { // Construct a MARS session Debug.Assert(parser != null, "no parser?"); @@ -57,40 +57,17 @@ internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalCon // General methods // ///////////////////// - internal abstract void CreatePhysicalSNIHandle( - string serverName, - TimeoutTimer timeout, - out byte[] instanceName, - out ResolvedServerSpn resolvedSpn, - bool flushCache, - bool async, - bool fParallel, - SqlConnectionIPAddressPreference iPAddressPreference, - string cachedFQDN, - ref SQLDNSInfo pendingDNSInfo, - string serverSPN, - bool isIntegratedSecurity = false, - bool tlsFirst = false, - string hostNameInCertificate = "", - string serverCertificateFilename = ""); - - internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo); - - protected abstract void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async); - - protected abstract void FreeGcHandle(int remaining, bool release); - internal abstract uint EnableSsl(ref uint info, bool tlsFirst, string serverCertificateFilename); - internal abstract void Dispose(); - internal abstract uint CheckConnection(); internal int DecrementPendingCallbacks(bool release) { int remaining = Interlocked.Decrement(ref _pendingCallbacks); SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.DecrementPendingCallbacks | ADV | State Object Id {0}, after decrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); + FreeGcHandle(remaining, release); + // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it Debug.Assert((remaining == -1 && SessionHandle.IsNull) || (0 <= remaining && remaining < 3), $"_pendingCallbacks values is invalid after decrementing: {remaining}"); 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 2b173fb087..0ebc36c06a 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 @@ -85,6 +85,8 @@ internal override void CreatePhysicalSNIHandle( bool flushCache, bool async, bool parallel, + TransparentNetworkResolutionState transparentNetworkResolutionState, + int totalTimeout, SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, 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 9a9ae68105..bf7c4aebb7 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 @@ -52,14 +52,12 @@ internal TdsParserStateObjectNative(TdsParser parser, TdsParserStateObject physi { } - public TdsParserStateObjectNative(TdsParser parser) + internal TdsParserStateObjectNative(TdsParser parser) : base(parser) { } - //////////////// - // Properties // - //////////////// + #region Properties internal SNIHandle Handle => _sessionHandle; @@ -71,6 +69,8 @@ public TdsParserStateObjectNative(TdsParser parser) internal override Guid? SessionId => default; + #endregion + protected override void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async) { Debug.Assert(physicalConnection is TdsParserStateObjectNative, "Expected a stateObject of type " + this.GetType()); @@ -83,6 +83,9 @@ protected override void CreateSessionHandle(TdsParserStateObject physicalConnect _sessionHandle = new SNIHandle(myInfo, nativeSNIObject.Handle, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); } + // Retrieve the IP and port number from native SNI for TCP protocol. The IP information is stored temporarily in the + // pendingSQLDNSObject but not in the DNS Cache at this point. We only add items to the DNS Cache after we receive the + // IsSupported flag as true in the feature ext ack from server. internal override void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo) { uint result; @@ -155,11 +158,13 @@ internal override void CreatePhysicalSNIHandle( string serverName, TimeoutTimer timeout, out byte[] instanceName, - out Microsoft.Data.SqlClient.ManagedSni.ResolvedServerSpn resolvedSpn, + out ManagedSni.ResolvedServerSpn resolvedSpn, bool flushCache, bool async, bool fParallel, - SqlConnectionIPAddressPreference ipPreference, + TransparentNetworkResolutionState transparentNetworkResolutionState, + int totalTimeout, + SqlConnectionIPAddressPreference iPAddressPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, string serverSPN, @@ -188,7 +193,7 @@ internal override void CreatePhysicalSNIHandle( bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo); _sessionHandle = new SNIHandle(myInfo, serverName, ref serverSPN, timeout.MillisecondsRemainingInt, out instanceName, - flushCache, !async, fParallel, ipPreference, cachedDNSInfo, hostNameInCertificate); + flushCache, !async, fParallel, iPAddressPreference, cachedDNSInfo, hostNameInCertificate); resolvedSpn = new(serverSPN.TrimEnd()); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index b3231b0cee..746925de3c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -450,6 +450,9 @@ Microsoft\Data\SqlClient\LocalAppContextSwitches.cs + + Microsoft\Data\SqlClient\ManagedSni\ResolvedServerSpn.cs + Microsoft\Data\SqlClient\NoneAttestationEnclaveProvider.cs @@ -924,7 +927,6 @@ - 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 756f2f044a..d65b0db83e 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 @@ -381,8 +381,11 @@ internal void Connect(ServerInfo serverInfo, ThrowExceptionAndWarning(_physicalStateObj); Debug.Fail("SNI returned status != success, but no error thrown?"); } - - string serverSpn = null; + else + { + SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | Connection Object Id {0}, Authentication Mode: {1}", _connHandler.ObjectID, + authType == SqlAuthenticationMethod.NotSpecified ? SqlAuthenticationMethod.SqlPassword.ToString() : authType.ToString()); + } //Create LocalDB instance if necessary if (connHandler.ConnectionOptions.LocalDBInstance != null) @@ -401,17 +404,6 @@ internal void Connect(ServerInfo serverInfo, { _authenticationProvider = Connection._sspiContextProvider ?? _physicalStateObj.CreateSspiContextProvider(); - if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) - { - serverSpn = serverInfo.ServerSPN; - SqlClientEventSource.Log.TryTraceEvent(" Server SPN `{0}` from the connection string is used.", serverInfo.ServerSPN); - } - else - { - // Empty signifies to interop layer that SPN needs to be generated - serverSpn = string.Empty; - } - SqlClientEventSource.Log.TryTraceEvent(" SSPI or Active Directory Authentication Library for SQL Server based integrated authentication"); } else @@ -495,7 +487,7 @@ internal void Connect(ServerInfo serverInfo, serverInfo.ExtendedServerName, timeout, out instanceName, - ref serverSpn, + out var resolvedServerSpn, false, true, fParallel, @@ -503,7 +495,12 @@ internal void Connect(ServerInfo serverInfo, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, - hostNameInCertificate); + ref _connHandler.pendingSQLDNSObject, + serverInfo.ServerSPN, + integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated, + isTlsFirst, + hostNameInCertificate, + serverCertificateFilename); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -544,7 +541,7 @@ internal void Connect(ServerInfo serverInfo, Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); // for DNS Caching phase 1 - AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache); + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); if (!ClientOSEncryptionSupport) { @@ -587,15 +584,20 @@ internal void Connect(ServerInfo serverInfo, serverInfo.ExtendedServerName, timeout, out instanceName, - ref serverSpn, + out resolvedServerSpn, true, true, fParallel, transparentNetworkResolutionState, totalTimeout, _connHandler.ConnectionOptions.IPAddressPreference, - serverInfo.ResolvedServerName, - hostNameInCertificate); + FQDNforDNSCache, + ref _connHandler.pendingSQLDNSObject, + serverInfo.ServerSPN, + integratedSecurity, + isTlsFirst, + hostNameInCertificate, + serverCertificateFilename); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -609,7 +611,7 @@ internal void Connect(ServerInfo serverInfo, SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); // for DNS Caching phase 1 - AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache); + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache, ref _connHandler.pendingSQLDNSObject); SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); status = ConsumePreLoginHandshake( @@ -631,7 +633,10 @@ internal void Connect(ServerInfo serverInfo, } SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake successful"); - _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this, serverSpn); + if (_authenticationProvider is { }) + { + _authenticationProvider.Initialize(serverInfo, _physicalStateObj, this, resolvedServerSpn.Primary, resolvedServerSpn.Secondary); + } if (_fMARS && marsCapable) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.netfx.cs deleted file mode 100644 index d7957767fa..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.netfx.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System; -using System.Net; -using System.Threading; -using Interop.Windows.Sni; - -namespace Microsoft.Data.SqlClient -{ - internal sealed partial class TdsParser - { - // Retrieve the IP and port number from native SNI for TCP protocol. The IP information is stored temporarily in the - // pendingSQLDNSObject but not in the DNS Cache at this point. We only add items to the DNS Cache after we receive the - // IsSupported flag as true in the feature ext ack from server. - internal void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey) - { - uint result; - ushort portFromSNI = 0; - string IPStringFromSNI = string.Empty; - IPAddress IPFromSNI; - isTcpProtocol = false; - Provider providerNumber = Provider.INVALID_PROV; - - if (string.IsNullOrEmpty(userProtocol)) - { - - result = SniNativeWrapper.SniGetProviderNumber(_physicalStateObj.Handle, ref providerNumber); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetProviderNumber"); - isTcpProtocol = (providerNumber == Provider.TCP_PROV); - } - else if (userProtocol == TdsEnums.TCP) - { - isTcpProtocol = true; - } - - // serverInfo.UserProtocol could be empty - if (isTcpProtocol) - { - result = SniNativeWrapper.SniGetConnectionPort(_physicalStateObj.Handle, ref portFromSNI); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionPort"); - - - result = SniNativeWrapper.SniGetConnectionIpString(_physicalStateObj.Handle, ref IPStringFromSNI); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionIPString"); - - _connHandler.pendingSQLDNSObject = new SQLDNSInfo(DNSCacheKey, null, null, portFromSNI.ToString()); - - if (IPAddress.TryParse(IPStringFromSNI, out IPFromSNI)) - { - if (System.Net.Sockets.AddressFamily.InterNetwork == IPFromSNI.AddressFamily) - { - _connHandler.pendingSQLDNSObject.AddrIPv4 = IPStringFromSNI; - } - else if (System.Net.Sockets.AddressFamily.InterNetworkV6 == IPFromSNI.AddressFamily) - { - _connHandler.pendingSQLDNSObject.AddrIPv6 = IPStringFromSNI; - } - } - } - else - { - _connHandler.pendingSQLDNSObject = null; - } - } - - // @TODO: Remove. - internal bool RunReliably(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) - { - return Run(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj); - // @TODO: CER Exception Handling was removed here (see GH#3581) - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 4181012274..276eabce8c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -26,9 +26,6 @@ internal partial class TdsParserStateObject protected SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn - // Async variables - private GCHandle _gcHandle; // keeps this object alive until we're closed. - // Used for blanking out password in trace. internal int _tracePasswordOffset = 0; internal int _tracePasswordLength = 0; @@ -39,7 +36,7 @@ internal partial class TdsParserStateObject // Constructors // ////////////////// - protected TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bool async) + protected TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalConnection, bool async) { // Construct a MARS session Debug.Assert(parser != null, "no parser?"); @@ -56,12 +53,8 @@ protected TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, b // Determine packet size based on physical connection buffer lengths. SetPacketSize(_parser._physicalStateObj._outBuff.Length); - ConsumerInfo myInfo = CreateConsumerInfo(async); - SQLDNSInfo cachedDNSInfo; - - SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCache, out cachedDNSInfo); + CreateSessionHandle(physicalConnection, async); - _sessionHandle = new SNIHandle(myInfo, physicalConnection, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); if (IsFailedHandle()) { AddError(parser.ProcessSNIError(this)); @@ -90,50 +83,6 @@ internal SNIHandle Handle // General methods // ///////////////////// - private ConsumerInfo CreateConsumerInfo(bool async) - { - ConsumerInfo myInfo = new ConsumerInfo(); - - Debug.Assert(_outBuff.Length == _inBuff.Length, "Unexpected unequal buffers."); - - myInfo.defaultBufferSize = _outBuff.Length; // Obtain packet size from outBuff size. - - if (async) - { - myInfo.readDelegate = SNILoadHandle.SingletonInstance.ReadAsyncCallbackDispatcher; - myInfo.writeDelegate = SNILoadHandle.SingletonInstance.WriteAsyncCallbackDispatcher; - _gcHandle = GCHandle.Alloc(this, GCHandleType.Normal); - myInfo.key = (IntPtr)_gcHandle; - } - return myInfo; - } - - internal void CreatePhysicalSNIHandle( - string serverName, - TimeoutTimer timeout, - out byte[] instanceName, - ref string spn, - bool flushCache, - bool async, - bool fParallel, - TransparentNetworkResolutionState transparentNetworkResolutionState, - int totalTimeout, - SqlConnectionIPAddressPreference ipPreference, - string cachedFQDN, - string hostNameInCertificate = "") - { - ConsumerInfo myInfo = CreateConsumerInfo(async); - - // serverName : serverInfo.ExtendedServerName - // may not use this serverName as key - - _ = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out SQLDNSInfo cachedDNSInfo); - - _sessionHandle = new SNIHandle(myInfo, serverName, ref spn, timeout.MillisecondsRemainingInt, - out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, - ipPreference, cachedDNSInfo, hostNameInCertificate); - } - internal uint CheckConnection() => SniNativeWrapper.SniCheckConnection(Handle); internal int DecrementPendingCallbacks(bool release) @@ -141,11 +90,7 @@ internal int DecrementPendingCallbacks(bool release) int remaining = Interlocked.Decrement(ref _pendingCallbacks); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after decrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks); - if ((0 == remaining || release) && _gcHandle.IsAllocated) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, FREEING HANDLE!", ObjectID); - _gcHandle.Free(); - } + FreeGcHandle(remaining, release); // NOTE: TdsParserSessionPool may call DecrementPendingCallbacks on a TdsParserStateObject which is already disposed // This is not dangerous (since the stateObj is no longer in use), but we need to add a workaround in the assert for it @@ -153,43 +98,6 @@ internal int DecrementPendingCallbacks(bool release) return remaining; } - internal void Dispose() - { - - SafeHandle packetHandle = _sniPacket; - SafeHandle sessionHandle = _sessionHandle; - SafeHandle asyncAttnPacket = _sniAsyncAttnPacket; - _sniPacket = null; - _sessionHandle = null; - _sniAsyncAttnPacket = null; - - DisposeCounters(); - - if (sessionHandle != null || packetHandle != null) - { - // Comment CloseMARSSession - // UNDONE - if there are pending reads or writes on logical connections, we need to block - // here for the callbacks!!! This only applies to async. Should be fixed by async fixes for - // AD unload/exit. - - if (packetHandle != null) - { - packetHandle.Dispose(); - } - if (asyncAttnPacket != null) - { - asyncAttnPacket.Dispose(); - } - if (sessionHandle != null) - { - sessionHandle.Dispose(); - DecrementPendingCallbacks(true); // Will dispose of GC handle. - } - } - - DisposePacketCache(); - } - /// /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) /// NOTE: This is not safe to do on a connection that is currently in use diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index 72c5356f14..9ffd372742 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -5,11 +5,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Net; using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Threading.Tasks; using Interop.Windows.Sni; using Microsoft.Data.Common; +using Microsoft.Data.ProviderBase; namespace Microsoft.Data.SqlClient { @@ -17,21 +21,21 @@ internal class TdsParserStateObjectNative : TdsParserStateObject { private readonly WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used + private GCHandle _gcHandle; // keeps this object alive until we're closed. + private readonly Dictionary _pendingWritePackets = new Dictionary(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback) internal TdsParserStateObjectNative(TdsParser parser, TdsParserStateObject physicalConnection, bool async) - : base(parser, physicalConnection.Handle, async) + : base(parser, physicalConnection, async) { } - public TdsParserStateObjectNative(TdsParser parser) + internal TdsParserStateObjectNative(TdsParser parser) : base(parser) { } - //////////////// - // Properties // - //////////////// + #region Properties internal override uint Status => _sessionHandle != null ? _sessionHandle.Status : TdsEnums.SNI_UNINITIALIZED; @@ -41,6 +45,136 @@ public TdsParserStateObjectNative(TdsParser parser) internal override Guid? SessionId => default; + #endregion + + protected override void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async) + { + Debug.Assert(physicalConnection is TdsParserStateObjectNative, "Expected a stateObject of type " + this.GetType()); + TdsParserStateObjectNative nativeSNIObject = physicalConnection as TdsParserStateObjectNative; + ConsumerInfo myInfo = CreateConsumerInfo(async); + + SQLDNSInfo cachedDNSInfo; + bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCache, out cachedDNSInfo); + + _sessionHandle = new SNIHandle(myInfo, nativeSNIObject.Handle, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); + } + + // Retrieve the IP and port number from native SNI for TCP protocol. The IP information is stored temporarily in the + // pendingSQLDNSObject but not in the DNS Cache at this point. We only add items to the DNS Cache after we receive the + // IsSupported flag as true in the feature ext ack from server. + internal override void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo) + { + uint result; + ushort portFromSNI = 0; + string IPStringFromSNI = string.Empty; + IPAddress IPFromSNI; + _parser.isTcpProtocol = false; + Provider providerNumber = Provider.INVALID_PROV; + + if (string.IsNullOrEmpty(userProtocol)) + { + + result = SniNativeWrapper.SniGetProviderNumber(Handle, ref providerNumber); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetProviderNumber"); + _parser.isTcpProtocol = (providerNumber == Provider.TCP_PROV); + } + else if (userProtocol == TdsEnums.TCP) + { + _parser.isTcpProtocol = true; + } + + // serverInfo.UserProtocol could be empty + if (_parser.isTcpProtocol) + { + result = SniNativeWrapper.SniGetConnectionPort(Handle, ref portFromSNI); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionPort"); + + result = SniNativeWrapper.SniGetConnectionIpString(Handle, ref IPStringFromSNI); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionIPString"); + + pendingDNSInfo = new SQLDNSInfo(DNSCacheKey, null, null, portFromSNI.ToString()); + + if (IPAddress.TryParse(IPStringFromSNI, out IPFromSNI)) + { + if (System.Net.Sockets.AddressFamily.InterNetwork == IPFromSNI.AddressFamily) + { + pendingDNSInfo.AddrIPv4 = IPStringFromSNI; + } + else if (System.Net.Sockets.AddressFamily.InterNetworkV6 == IPFromSNI.AddressFamily) + { + pendingDNSInfo.AddrIPv6 = IPStringFromSNI; + } + } + } + else + { + pendingDNSInfo = null; + } + } + + private ConsumerInfo CreateConsumerInfo(bool async) + { + ConsumerInfo myInfo = new ConsumerInfo(); + + Debug.Assert(_outBuff.Length == _inBuff.Length, "Unexpected unequal buffers."); + + myInfo.defaultBufferSize = _outBuff.Length; // Obtain packet size from outBuff size. + + if (async) + { + myInfo.readDelegate = SNILoadHandle.SingletonInstance.ReadAsyncCallbackDispatcher; + myInfo.writeDelegate = SNILoadHandle.SingletonInstance.WriteAsyncCallbackDispatcher; + _gcHandle = GCHandle.Alloc(this, GCHandleType.Normal); + myInfo.key = (IntPtr)_gcHandle; + } + return myInfo; + } + + internal override void CreatePhysicalSNIHandle( + string serverName, + TimeoutTimer timeout, + out byte[] instanceName, + out ManagedSni.ResolvedServerSpn resolvedSpn, + bool flushCache, + bool async, + bool fParallel, + TransparentNetworkResolutionState transparentNetworkResolutionState, + int totalTimeout, + SqlConnectionIPAddressPreference iPAddressPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + string serverSPN, + bool isIntegratedSecurity = false, + bool tlsFirst = false, + string hostNameInCertificate = "", + string serverCertificateFilename = "") + { + if (isIntegratedSecurity) + { + if (!string.IsNullOrEmpty(serverSPN)) + { + SqlClientEventSource.Log.TryTraceEvent(" Server SPN `{0}` from the connection string is used.", serverSPN); + } + else + { + // Empty signifies to interop layer that SPN needs to be generated + serverSPN = string.Empty; + } + } + + ConsumerInfo myInfo = CreateConsumerInfo(async); + + // serverName : serverInfo.ExtendedServerName + // may not use this serverName as key + + _ = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out SQLDNSInfo cachedDNSInfo); + + _sessionHandle = new SNIHandle(myInfo, serverName, ref serverSPN, timeout.MillisecondsRemainingInt, + out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, + iPAddressPreference, cachedDNSInfo, hostNameInCertificate); + resolvedSpn = new(serverSPN.TrimEnd()); + } + protected override uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize) { Debug.Assert(packet.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer"); @@ -75,6 +209,52 @@ protected override void RemovePacketFromPendingList(PacketHandle ptr) } } + internal override void Dispose() + { + SafeHandle packetHandle = _sniPacket; + SafeHandle sessionHandle = _sessionHandle; + SafeHandle asyncAttnPacket = _sniAsyncAttnPacket; + _sniPacket = null; + _sessionHandle = null; + _sniAsyncAttnPacket = null; + + DisposeCounters(); + + if (sessionHandle != null || packetHandle != null) + { + // Comment CloseMARSSession + // UNDONE - if there are pending reads or writes on logical connections, we need to block + // here for the callbacks!!! This only applies to async. Should be fixed by async fixes for + // AD unload/exit. + + if (packetHandle != null) + { + packetHandle.Dispose(); + } + if (asyncAttnPacket != null) + { + asyncAttnPacket.Dispose(); + } + if (sessionHandle != null) + { + sessionHandle.Dispose(); + DecrementPendingCallbacks(true); // Will dispose of GC handle. + } + } + + DisposePacketCache(); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + protected override void FreeGcHandle(int remaining, bool release) + { + if ((0 == remaining || release) && _gcHandle.IsAllocated) + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, FREEING HANDLE!", ObjectID); + _gcHandle.Free(); + } + } + internal override bool IsFailedHandle() => _sessionHandle.Status != TdsEnums.SNI_SUCCESS; internal override bool IsPacketEmpty(PacketHandle readPacket) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index 649087f32b..cdcd02efea 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -1377,11 +1377,8 @@ private void RunParserReliably(BulkCopySimpleResultSet bulkCopyHandler = null) internalConnection.ThreadHasParserLockForClose = true; try { - #if NETFRAMEWORK - _parser.RunReliably(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); - #else - _parser.Run(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); - #endif + // @TODO: CER Exception Handling was removed here (see GH#3581) + _parser.Run(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); } finally { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs index 5113f08c92..04bd173f0c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSessionPool.cs @@ -215,24 +215,6 @@ internal string TraceString() _cachedCount, _cache.Count); } - -#if NETFRAMEWORK - internal void BestEffortCleanup() - { - for (int i = 0; i < _cache.Count; i++) - { - TdsParserStateObject session = _cache[i]; - if (session != null) - { - SNIHandle sessionHandle = session.Handle; - if (sessionHandle != null) - { - sessionHandle.Dispose(); - } - } - } - } -#endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index 473a6ffc00..343eaeca5a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -13,6 +13,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ManagedSni; + #if NETFRAMEWORK using System.Runtime.ConstrainedExecution; #endif @@ -506,12 +509,39 @@ internal long TimeoutTime protected abstract PacketHandle EmptyReadPacket { get; } + protected abstract void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async); + + internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo); + + internal abstract void CreatePhysicalSNIHandle( + string serverName, + TimeoutTimer timeout, + out byte[] instanceName, + out ResolvedServerSpn resolvedSpn, + bool flushCache, + bool async, + bool fParallel, + TransparentNetworkResolutionState transparentNetworkResolutionState, + int totalTimeout, + SqlConnectionIPAddressPreference iPAddressPreference, + string cachedFQDN, + ref SQLDNSInfo pendingDNSInfo, + string serverSPN, + bool isIntegratedSecurity = false, + bool tlsFirst = false, + string hostNameInCertificate = "", + string serverCertificateFilename = ""); + internal abstract PacketHandle GetResetWritePacket(int dataSize); protected abstract uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize); protected abstract bool CheckPacket(PacketHandle packet, TaskCompletionSource source); + internal abstract void Dispose(); + + protected abstract void FreeGcHandle(int remaining, bool release); + internal abstract bool IsFailedHandle(); internal abstract bool IsPacketEmpty(PacketHandle readPacket);