diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index fe98596d61..67d7a955d0 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -4,7 +4,7 @@ This document provides all the necessary details to build the driver and run tes ## Visual Studio Pre-Requisites -This project should be ideally built with Visual Studio 2017+ for the best compatibility. Use either of the two environments with their required set of compoenents as mentioned below: +This project should be built with Visual Studio 2019+ for the best compatibility. The required set of components are provided in the below file: - **Visual Studio 2019** with imported components: [VS19Components](/tools/vsconfig/VS19Components.vsconfig) Once the environment is setup properly, execute the desired set of commands below from the _root_ folder to perform the respective operations: @@ -106,6 +106,7 @@ Manual Tests require the below setup to run: |------|--------|-------------------| |TCPConnectionString | Connection String for a TCP enabled SQL Server instance. | `Server={servername};Database={Database_Name};Trusted_Connection=True;`
OR `Data Source={servername};Initial Catalog={Database_Name};Integrated Security=True;`| |NPConnectionString | Connection String for a Named Pipes enabled SQL Server instance.| `Server=\\{servername}\pipe\sql\query;Database={Database_Name};Trusted_Connection=True;`
OR
`Data Source=np:{servername};Initial Catalog={Database_Name};Integrated Security=True;`| +|TCPConnectionStringHGSVBS | (Optional) Connection String for a TCP enabled SQL Server with Host Guardian Service (HGS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = HGS; Enclave Attestation Url = {AttestationURL};`| |AADAuthorityURL | (Optional) Identifies the OAuth2 authority resource for `Server` specified in `AADPasswordConnectionString` | `https://login.windows.net/`, where `` is the tenant ID of the Azure Active Directory (Azure AD) tenant | |AADPasswordConnectionString | (Optional) Connection String for testing Azure Active Directory Password Authentication. | `Data Source={server.database.windows.net}; Initial Catalog={Azure_DB_Name};Authentication=Active Directory Password; User ID={AAD_User}; Password={AAD_User_Password};`| |AADSecurePrincipalId | (Optional) The Application Id of a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Application ID} | @@ -204,9 +205,9 @@ Tests can be built and run with custom Target Frameworks. See the below examples ``` ```bash -> msbuild /t:BuildTestsNetCore /p:TargetNetCoreVersion=netcoreapp3.0 +> msbuild /t:BuildTestsNetCore /p:TargetNetCoreVersion=netcoreapp3.1 # Build the tests for custom TargetFramework (.NET Core) -# Applicable values: netcoreapp2.1 | netcoreapp2.2 | netcoreapp3.0 +# Applicable values: netcoreapp2.1 | netcoreapp2.2 | netcoreapp3.1 | netcoreapp5.0 ``` ### Running Tests: @@ -216,9 +217,9 @@ Tests can be built and run with custom Target Frameworks. See the below examples # Use above property to run Functional Tests with custom TargetFramework (.NET Framework) # Applicable values: net46 (Default) | net461 | net462 | net47 | net471 net472 | net48 -> dotnet test /p:TargetNetCoreVersion=netcoreapp3.0 ... +> dotnet test /p:TargetNetCoreVersion=netcoreapp3.1 ... # Use above property to run Functional Tests with custom TargetFramework (.NET Core) -# Applicable values: netcoreapp2.1 | netcoreapp2.2 | netcoreapp3.0 +# Applicable values: netcoreapp2.1 | netcoreapp2.2 | netcoreapp3.1 | netcoreapp5.0 ``` ## Using Managed SNI on Windows @@ -256,7 +257,7 @@ There may be times where connection cannot be made to SQL Server, we found below - Clear Docker images to create clean image from time-to-time, and clear docker cache if needed by running `docker system prune` in Command Prompt. -- If you face `sni.dll not found` errors when debugging, try updating below properties in netcore\Microsoft.Data.SqlClient.csproj file and try again: +- If you face `Microsoft.Data.SqlClient.SNI.dll not found` errors when debugging, try updating the below properties in the netcore\Microsoft.Data.SqlClient.csproj file and try again: ```xml Unix false diff --git a/CHANGELOG.md b/CHANGELOG.md index c9dc431b85..409e175eed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -## [Preview Release 2.0.0-preview4.20142.4 ] - 2020-05-21 +## [Stable Release 2.0.0] - 2020-06-16 + +### Added +- Added internal driver support to provide resiliency to DNS failures [#594](https://github.com/dotnet/SqlClient/pull/594) +- Added support for `Active Directory Integrated`, `Active Directory Interactive` and `Active Directory Service Principal` authentication mode for .NET Core and .NET Standard [#560](https://github.com/dotnet/SqlClient/pull/560) +- Added support for `Active Directory Service Principal` authentication mode for .NET Framework [#560](https://github.com/dotnet/SqlClient/pull/560) +- Added support for optional `ORDER` hints in `SqlBulkCopy` for improved performance [#540](https://github.com/dotnet/SqlClient/pull/540) + +### Fixed +- Fixed `SqlSequentialStream` multipacket read stalling issue in .NET Core [#603](https://github.com/dotnet/SqlClient/pull/603) +- Fixed code page issue for Kazakh collation in SQL Server [#584](https://github.com/dotnet/SqlClient/pull/584) +- Fixed stalled application issues when end of stream is reached [#577](https://github.com/dotnet/SqlClient/pull/577) +- Fixed driver behavior to not throw exception for invalid configuration file [#573](https://github.com/dotnet/SqlClient/pull/573) +- Fixed Object null reference issue when failover partner is set [#588](https://github.com/dotnet/SqlClient/pull/588) +- Fixed `applicationintent` connection string property issue [#585](https://github.com/dotnet/SqlClient/pull/585) + +### Changes +- Raise warning message when insecure TLS protocols are in use [#591](https://github.com/dotnet/SqlClient/pull/591) + +### Breaking Changes +- Modified enclave provider interface `SqlColumnEncryptionEnclaveProvider` to be internal [#602](https://github.com/dotnet/SqlClient/pull/602) - _This change is not likely to impact customer applications since secure enclaves is a relatively new feature and they would have had to implement their own enclave provider, which is not a trivial task_. +- Updated `SqlClientMetaDataCollectionNames` exposed constants by removing non-existing constants and adding new to the metadata collection [#580](https://github.com/dotnet/SqlClient/pull/580) + + +## [Preview Release 2.0.0-preview4.20142.4] - 2020-05-21 ### Added - Microsoft.Data.SqlClient (.NET Core and .NET Standard) on Windows is now dependent on **Microsoft.Data.SqlClient.SNI.runtime**, replacing the previous dependency on **runtime.native.System.Data.SqlClient.SNI** [#570](https://github.com/dotnet/SqlClient/pull/570) diff --git a/README.md b/README.md index 87db72ab54..4bf96a5f71 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ The Microsoft.Data.SqlClient NuGet package is available on [NuGet.org](https://w ## SNI Package References -For the .NET Framework driver on Windows, a package reference to [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/) loads `x64` and `x86` native `SNI.dll` libraries into the client's build directories. +For the .NET Framework driver on Windows, a package reference to [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/) loads native `Microsoft.Data.SqlClient.SNI.x64.dll` and `Microsoft.Data.SqlClient.SNI.x86.dll` libraries into the client's build directories. -For the .NET Core driver on Windows, a package reference to [runtime.native.System.Data.SqlClient.sni](https://www.nuget.org/packages/runtime.native.System.Data.SqlClient.sni/) loads `arm64`, `x64` and `x86` native `SNI.dll` libraries into the client's build directories. +For the .NET Core driver on Windows, a package reference to [Microsoft.Data.SqlClient.SNI.runtime](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI.runtime/) loads `arm`, `arm64`, `x64` and `x86` native `Microsoft.Data.SqlClient.SNI.dll` libraries into the client's build directories. ## Helpful Links @@ -48,9 +48,10 @@ We thank you for your continuous support in improving the SqlClient library! - Wraith ([@Wraith2](https://github.com/Wraith2)) - Erik Ejlskov Jensen ([@ErikEJ](https://github.com/ErikEJ)) +- Simon Cropp ([@SimonCropp](https://github.com/SimonCropp)) - Stefán Jökull Sigurðarson ([@stebet](https://github.com/stebet)) -- Stephen Toub ([@stephentoub](https://github.com/stephentoub)) - Shay Rojansky ([@roji](https://github.com/roji)) +- Stephen Toub ([@stephentoub](https://github.com/stephentoub)) - Rasmus Melchior Jacobsen ([@rmja](https://github.com/rmja)) - Phillip Haydon ([@phillip-haydon](https://github.com/phillip-haydon)) - Robin Sue ([@Suchiman](https://github.com/Suchiman)) diff --git a/doc/samples/SqlBulkCopyOptions_Default.cs b/doc/samples/SqlBulkCopyOptions_Default.cs index 884cb51bfd..10d3283f75 100644 --- a/doc/samples/SqlBulkCopyOptions_Default.cs +++ b/doc/samples/SqlBulkCopyOptions_Default.cs @@ -72,6 +72,10 @@ static void Main() } catch (Exception ex) { + // Print the number of rows processed using the + // RowsCopied property. + Console.WriteLine("{0} rows were processed.", + bulkCopy.RowsCopied); Console.WriteLine(ex.Message); } finally @@ -82,6 +86,8 @@ static void Main() // Perform a final count on the destination // table to see how many rows were added. + // Note that for this scenario, the value will + // not be equal to the RowsCopied property. long countEnd = System.Convert.ToInt32( commandRowCount.ExecuteScalar()); Console.WriteLine("Ending row count = {0}", countEnd); @@ -92,8 +98,8 @@ static void Main() } private static string GetConnectionString() - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. + // To avoid storing the sourceConnection string in your code, + // you can retrieve it from a configuration file. { return "Data Source=(local); " + " Integrated Security=true;" + diff --git a/doc/samples/SqlBulkCopyOptions_UseInternalTransaction.cs b/doc/samples/SqlBulkCopyOptions_UseInternalTransaction.cs index 7dc5c321b6..ee8d1446f1 100644 --- a/doc/samples/SqlBulkCopyOptions_UseInternalTransaction.cs +++ b/doc/samples/SqlBulkCopyOptions_UseInternalTransaction.cs @@ -78,6 +78,10 @@ static void Main() } catch (Exception ex) { + // Print the number of rows processed using the + // RowsCopied property. + Console.WriteLine("{0} rows were processed.", + bulkCopy.RowsCopied); Console.WriteLine(ex.Message); } finally @@ -88,6 +92,8 @@ static void Main() // Perform a final count on the destination // table to see how many rows were added. + // Note that for this scenario, the value will + // not be equal to the RowsCopied property. long countEnd = System.Convert.ToInt32( commandRowCount.ExecuteScalar()); Console.WriteLine("Ending row count = {0}", countEnd); @@ -98,8 +104,8 @@ static void Main() } private static string GetConnectionString() - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. + // To avoid storing the sourceConnection string in your code, + // you can retrieve it from a configuration file. { return "Data Source=(local); " + " Integrated Security=true;" + diff --git a/doc/samples/SqlBulkCopy_ExternalTransaction.cs b/doc/samples/SqlBulkCopy_ExternalTransaction.cs index 2d0cf663f3..92a95faf19 100644 --- a/doc/samples/SqlBulkCopy_ExternalTransaction.cs +++ b/doc/samples/SqlBulkCopy_ExternalTransaction.cs @@ -81,6 +81,10 @@ static void Main() } catch (Exception ex) { + // Print the number of rows processed using the + // RowsCopied property. + Console.WriteLine("{0} rows were processed.", + bulkCopy.RowsCopied); Console.WriteLine(ex.Message); transaction.Rollback(); } @@ -104,8 +108,8 @@ static void Main() } private static string GetConnectionString() - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. + // To avoid storing the sourceConnection string in your code, + // you can retrieve it from a configuration file. { return "Data Source=(local); " + " Integrated Security=true;" + diff --git a/doc/samples/SqlBulkCopy_WriteToServer.cs b/doc/samples/SqlBulkCopy_WriteToServer.cs index 6c701dc2f5..107d36d65b 100644 --- a/doc/samples/SqlBulkCopy_WriteToServer.cs +++ b/doc/samples/SqlBulkCopy_WriteToServer.cs @@ -54,6 +54,10 @@ static void Main() { // Write from the source to the destination. bulkCopy.WriteToServer(reader); + // Print the number of rows processed using the + // RowsCopied property. + Console.WriteLine("{0} rows were processed.", + bulkCopy.RowsCopied); } catch (Exception ex) { @@ -81,8 +85,8 @@ static void Main() } private static string GetConnectionString() - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. + // To avoid storing the sourceConnection string in your code, + // you can retrieve it from a configuration file. { return "Data Source=(local); " + " Integrated Security=true;" + diff --git a/doc/samples/SqlClientEventSource.cs b/doc/samples/SqlClientEventSource.cs new file mode 100644 index 0000000000..40acc00c90 --- /dev/null +++ b/doc/samples/SqlClientEventSource.cs @@ -0,0 +1,60 @@ +// +using System; +using System.Diagnostics.Tracing; +using Microsoft.Data.SqlClient; + +// This listener class will listen for events from the SqlClientEventSource class. +// SqlClientEventSource is an implementation of the EventSource class which gives +// it the ability to create events. +public class SqlClientListener : EventListener +{ + protected override void OnEventSourceCreated(EventSource eventSource) + { + // Only enable events from SqlClientEventSource. + if (eventSource.Name.Equals("Microsoft.Data.SqlClient.EventSource")) + { + // Use EventKeyWord 2 to capture basic application flow events. + // See the above table for all available keywords. + EnableEvents(eventSource, EventLevel.Informational, (EventKeywords)2); + } + } + + // This callback runs whenever an event is written by SqlClientEventSource. + // Event data is accessed through the EventWrittenEventArgs parameter. + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + // Print event data. + Console.WriteLine(eventData.Payload[0]); + } +} + +class Program +{ + public static void Main() + { + // Create a new event listener. + using (SqlClientListener listener = new SqlClientListener()) + { + string connectionString = "Data Source=localhost; " + + "Initial Catalog=AdventureWorks; Integrated Security=true"; + + // Open a connection to the AdventureWorks database. + using (SqlConnection connection = new SqlConnection(connectionString)) + { + connection.Open(); + + string sql = "SELECT * FROM Sales.Currency"; + SqlCommand command = new SqlCommand(sql, connection); + + // Perform a data operation on the server. + SqlDataReader reader = command.ExecuteReader(); + while (reader.Read()) + { + // Read the data. + } + reader.Close(); + } + } + } +} +// diff --git a/doc/samples/SqlDataReader_DataDiscoveryAndClassification.cs b/doc/samples/SqlDataReader_DataDiscoveryAndClassification.cs new file mode 100644 index 0000000000..d9dfcee124 --- /dev/null +++ b/doc/samples/SqlDataReader_DataDiscoveryAndClassification.cs @@ -0,0 +1,159 @@ +// +using System; +using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient.DataClassification; + +class Program +{ + // Name of the temporary table created for this sample program. + static string tableName = "SQLCLIENT_DATA_DISCOVERY_CLASSIFICATION"; + + public static void Main() + { + // To avoid storing the connection string in your code, you can retrieve it from a configuration file. + string connectionString = "Data Source=localhost; Integrated Security=true; Initial Catalog=AdventureWorks;"; + + // Open a connection to the AdventureWorks database. + using (SqlConnection connection = new SqlConnection(connectionString)) + { + connection.Open(); + + try + { + // Check if the target SQL Server supports Data Discovery and Classification. + if (DataClassificationSupported(connection)) + { + // Create the temporary table and retrieve its Data Discovery and Classification information. + CreateTable(connection); + RunTests(connection); + } + } + finally + { + // Drop the temporary table. + DropTable(connection); + } + } + } + + /// + /// Verifies if SQL Data Discovery and Classification feature is available on the target server. + /// + /// The SqlConnection to work with. + /// True if the target SQL Server supports the feature and false otherwise. + public static bool DataClassificationSupported(SqlConnection connection) + { + try + { + SqlCommand command = new SqlCommand(null, connection); + command.CommandText = "SELECT * FROM SYS.SENSITIVITY_CLASSIFICATIONS"; + command.ExecuteNonQuery(); + } + catch (SqlException e) + { + // Error 208: Object Not Found + if (e.Errors != null && e.Errors[0].Number == 208) + { + Console.WriteLine("This feature is not supported on the target SQL Server."); + return false; + } + } + return true; + } + + /// + /// Creates a temporary table for this sample program and sets tags for Sensitivity Classification. + /// + /// The SqlConnection to work with. + private static void CreateTable(SqlConnection connection) + { + SqlCommand command = new SqlCommand(null, connection); + + // Creates table for storing Supplier data. + command.CommandText = $"CREATE TABLE {tableName} (" + + "[Id] [int] IDENTITY(1,1) NOT NULL," + + "[CompanyName] [nvarchar](40) NOT NULL," + + "[ContactName] [nvarchar](50) NULL," + + "[ContactTitle] [nvarchar](40) NULL," + + "[City] [nvarchar](40) NULL," + + "[CountryName] [nvarchar](40) NULL," + + "[Phone] [nvarchar](30) MASKED WITH (FUNCTION = 'default()') NULL," + + "[Fax] [nvarchar](30) MASKED WITH (FUNCTION = 'default()') NULL)"; + command.ExecuteNonQuery(); + + // Set Sensitivity Classification tags for table columns. + command.CommandText = $"ADD SENSITIVITY CLASSIFICATION TO {tableName}" + + ".CompanyName WITH (LABEL='PII', LABEL_ID='L1', INFORMATION_TYPE='Company Name', INFORMATION_TYPE_ID='COMPANY')"; + command.ExecuteNonQuery(); + + command.CommandText = $"ADD SENSITIVITY CLASSIFICATION TO {tableName}" + + ".ContactName WITH (LABEL='PII', LABEL_ID='L1', INFORMATION_TYPE='Person Name', INFORMATION_TYPE_ID='NAME')"; + command.ExecuteNonQuery(); + + command.CommandText = $"ADD SENSITIVITY CLASSIFICATION TO {tableName}" + + ".Phone WITH (LABEL='PII', LABEL_ID='L1', INFORMATION_TYPE='Contact Information', INFORMATION_TYPE_ID='CONTACT')"; + command.ExecuteNonQuery(); + + command.CommandText = $"ADD SENSITIVITY CLASSIFICATION TO {tableName}" + + ".Fax WITH (LABEL='PII', LABEL_ID='L1', INFORMATION_TYPE='Contact Information', INFORMATION_TYPE_ID='CONTACT')"; + command.ExecuteNonQuery(); + } + + /// + /// Run query to fetch result set from target table. + /// + /// The SqlConnection to work with. + private static void RunTests(SqlConnection connection) + { + SqlCommand command = new SqlCommand(null, connection); + command.CommandText = $"SELECT * FROM {tableName}"; + using (SqlDataReader reader = command.ExecuteReader()) + { + PrintSensitivityClassification(reader); + } + } + + /// + /// Prints Sensitivity Classification data as received in the result set. + /// + /// The SqlDataReader to work with. + private static void PrintSensitivityClassification(SqlDataReader reader) + { + if (reader.SensitivityClassification != null) + { + for (int columnPos = 0; columnPos < reader.SensitivityClassification.ColumnSensitivities.Count; columnPos++) + { + foreach (SensitivityProperty sp in reader.SensitivityClassification.ColumnSensitivities[columnPos].SensitivityProperties) + { + if (sp.Label != null) + { + Console.WriteLine($"Labels received for Column : {columnPos}"); + Console.WriteLine($"Label ID: {sp.Label.Id}"); + Console.WriteLine($"Label Name: {sp.Label.Name}"); + Console.WriteLine(); + } + + if (sp.InformationType != null) + { + Console.WriteLine($"Information Types received for Column : {columnPos}"); + Console.WriteLine($"Information Type ID: {sp.InformationType.Id}"); + Console.WriteLine($"Information Type: {sp.InformationType.Name}"); + Console.WriteLine(); + } + } + } + } + } + + /// + /// Deletes the table created for this sample program. + /// + /// The SqlConnection to work with. + private static void DropTable(SqlConnection connection) + { + SqlCommand command = new SqlCommand(null, connection); + command.CommandText = $"DROP TABLE {tableName}"; + command.ExecuteNonQuery(); + } +} +// diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index e2980f9d37..978aaa00db 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -1055,6 +1055,23 @@ GO ]]> + + Returns a name value pair collection of internal properties at the point in time the method is called. + Returns a reference of type of (string, object) items. + + + + Gets a string that contains the version of the instance of SQL Server to which the client is connected. The version of the instance of SQL Server. diff --git a/release-notes/2.0/2.0.0.md b/release-notes/2.0/2.0.0.md new file mode 100644 index 0000000000..fe6625910b --- /dev/null +++ b/release-notes/2.0/2.0.0.md @@ -0,0 +1,275 @@ +# Release Notes + +## Microsoft.Data.SqlClient 2.0.0 released 16 June 2020 + +This update brings the below changes over the previous preview release: + +### Added +- Added internal driver support to provide resiliency to DNS failures [#594](https://github.com/dotnet/SqlClient/pull/594) +- Added support for `Active Directory Integrated`, `Active Directory Interactive` and `Active Directory Service Principal` authentication mode for .NET Core and .NET Standard [#560](https://github.com/dotnet/SqlClient/pull/560) +- Added support for `Active Directory Service Principal` authentication mode for .NET Framework [#560](https://github.com/dotnet/SqlClient/pull/560) +- Added support for optional `ORDER` hints in `SqlBulkCopy` for improved performance [#540](https://github.com/dotnet/SqlClient/pull/540) + +### Fixed +- Fixed `SqlSequentialStream` multipacket read stalling issue in .NET Core [#603](https://github.com/dotnet/SqlClient/pull/603) +- Fixed code page issue for Kazakh collation in SQL Server [#584](https://github.com/dotnet/SqlClient/pull/584) +- Fixed stalled application issues when end of stream is reached [#577](https://github.com/dotnet/SqlClient/pull/577) +- Fixed driver behavior to not throw exception for invalid configuration file [#573](https://github.com/dotnet/SqlClient/pull/573) +- Fixed Object null reference issue when failover partner is set [#588](https://github.com/dotnet/SqlClient/pull/588) +- Fixed `applicationintent` connection string property issue [#585](https://github.com/dotnet/SqlClient/pull/585) + +### Changes +- Raise warning message when insecure TLS protocols are in use [#591](https://github.com/dotnet/SqlClient/pull/591) + +### Breaking Changes +- Modified enclave provider interface `SqlColumnEncryptionEnclaveProvider` to be internal along with related classes `SqlEnclaveAttestationParameters` and `SqlEnclaveSession` [#602](https://github.com/dotnet/SqlClient/pull/602) - _This change is not likely to impact customer applications since secure enclaves is a relatively new feature and they would have had to implement their own enclave provider, which is not a trivial task_. +- Updated `SqlClientMetaDataCollectionNames` exposed constants by removing non-existing constants and adding new to the metadata collection [#580](https://github.com/dotnet/SqlClient/pull/580) + +## Summary of changes in 2.0 + +All changes in Microsoft.Data.SqlClient v2.0 over v1.1: + +### New Additions +- Added support to allow large UDT buffer size (_upto_ `Int.MaxValue`) as supported by SQL Server starting TDS 7.3 [#340](https://github.com/dotnet/SqlClient/pull/340) +- Added support for capturing EventSource traces in .NET Framework, .NET Core, and .NET Standard applications [#399](https://github.com/dotnet/SqlClient/pull/399) [#461](https://github.com/dotnet/SqlClient/pull/461) [#479](https://github.com/dotnet/SqlClient/pull/479) [#483](https://github.com/dotnet/SqlClient/pull/483) [#484](https://github.com/dotnet/SqlClient/pull/484) [Read More](#eventSource-tracing-support) +- Added support for Cross-platform TCP Keep Alive applicable to .NET Core 3.1+ applications [#395](https://github.com/dotnet/SqlClient/pull/395) +- Added support for enabling Managed networking implementation on Windows applicable to .NET Core and .NET Standard applications [#477](https://github.com/dotnet/SqlClient/pull/477) [Read More](#enable-managed-networking-on-windows) +- Added `RowsCopied` property in `SqlBulkCopy` to expose count of copied rows [#409](https://github.com/dotnet/SqlClient/pull/409) [Read More](#sqlbulkcopy.rowscopied-property) +- Added "NeutralResourcesLanguage" attribute for .NET Framework assembly [#433](https://github.com/dotnet/SqlClient/pull/433) +- Added caching for invariant culture check result [#376](https://github.com/dotnet/SqlClient/pull/376) +- Added cached `SqlReferenceCollection.FindLiveReaderContext` objects [#380](https://github.com/dotnet/SqlClient/pull/380) +- Allow passing username with Active Directory Interactive Authentication [#492](https://github.com/dotnet/SqlClient/pull/492) [Read More](#username-support-for-active-directory-interactive-mode) +- Allow large UDT buffers for .NET Framework [#456](https://github.com/dotnet/SqlClient/pull/456) +- Added "Transaction Id" and "Client Version" in Diagnostic Source traces [#515](https://github.com/dotnet/SqlClient/pull/515) +- Added new `SqlConnectionOverrides` APIs to perform `SqlConnection.Open()` with fail fast option [#463](https://github.com/dotnet/SqlClient/pull/463) [Read More](#connection-open-overrides) +- Microsoft.Data.SqlClient (.NET Core and .NET Standard) on Windows is now dependent on **Microsoft.Data.SqlClient.SNI.runtime**, replacing the previous dependency on **runtime.native.System.Data.SqlClient.SNI** [#570](https://github.com/dotnet/SqlClient/pull/570) [Read More](#sni-dependency-changes) +- The new **Microsoft.Data.SqlClient.SNI.runtime** dependency adds support for the *ARM* platform along with the already supported platforms *ARM64*, *x64* and *x86* on Windows [#570](https://github.com/dotnet/SqlClient/pull/570) [Read More](#sni-dependency-changes) +- Improved driver performance by introducing managed packet recycling [#389](https://github.com/dotnet/SqlClient/pull/389) +- Added internal driver support to provide resiliency to DNS failures [#594](https://github.com/dotnet/SqlClient/pull/594) +- Added support for `Active Directory Integrated`, `Active Directory Interactive` and `Active Directory Service Principal` authentication mode for .NET Core and .NET Standard [#560](https://github.com/dotnet/SqlClient/pull/560) - [Read more](#additional-active-directory-authentication-modes) +- Added support for `Active Directory Service Principal` authentication mode for .NET Framework [#560](https://github.com/dotnet/SqlClient/pull/560) +- Added support for optional `ORDER` hints in `SqlBulkCopy` for improved performance [#540](https://github.com/dotnet/SqlClient/pull/540) [Read More](#order-hints-for-sqlbulkcopy) + +### Bug Fixes +- Fixed issues with `SqlCommandSet` not working with Byte Array parameters [#360](https://github.com/dotnet/SqlClient/pull/360) +- Fixed Statement command cancellation in Managed SNI [#248](https://github.com/dotnet/SqlClient/pull/248) - Ported [dotnet/corefx#38271](https://github.com/dotnet/corefx/pull/38271) +- Fixed zero connection timeout issue in Managed SNI [#332](https://github.com/dotnet/SqlClient/pull/332) +- Fixed "DataType" metadata information for TinyInt datatype to be `System.Byte` [#338](https://github.com/dotnet/SqlClient/pull/338) +- Fixed driver behavior to use `CancellationTokenResource` only for non-infinite timeout and cleanup after usage [#339](https://github.com/dotnet/SqlClient/pull/339) +- Fixed `ConnectionTime` and `ClientConnectionId` reported by `SqlStatistics` when connection is closed [#341](https://github.com/dotnet/SqlClient/pull/341) +- Fixed deadlock issues by reverting async changes to `SNIPacket` [#349](https://github.com/dotnet/SqlClient/pull/349) +- Fixed Access Token behavior in connection pool to perform string comparison [#443](https://github.com/dotnet/SqlClient/pull/443) +- Fixed concurrent connection speed issues when connecting with Azure Active Directory Authentication modes in .NET Core [#466](https://github.com/dotnet/SqlClient/pull/466) +- Fixed issues with `Password` persistence in Connection String [#453](https://github.com/dotnet/SqlClient/pull/453) +- Addressed MARS TDS Header errors by reverting changes to make `SqlDataReader.ReadAsync()` non-blocking [#547](https://github.com/dotnet/SqlClient/pull/547) +- Fixed driver behavior to not perform enlistment of pooled connection in aborted transaction [#543](https://github.com/dotnet/SqlClient/pull/543) +- Fixed wrong application domain selected when starting `SqlDependencyListener` [#410](https://github.com/dotnet/SqlClient/pull/410) +- Added missing refs for `RowCopied` property in `SqlBulkCopy` [#508](https://github.com/dotnet/SqlClient/pull/508) +- Fixed `SqlBulkCopy` to work with database columns containing metadata about data classification [#568](https://github.com/dotnet/SqlClient/pull/568) +- Fixed unsafe cast in `SqlException` for `SerializationEntry.Value` +- Fixed null reference exceptions in `SqlDelegatedTransaction` methods [#563](https://github.com/dotnet/SqlClient/pull/563) +- Fixed `SqlSequentialStream` multipacket read stalling issue in .NET Core [#603](https://github.com/dotnet/SqlClient/pull/603) +- Fixed code page issue for Kazakh collation in SQL Server [#584](https://github.com/dotnet/SqlClient/pull/584) +- Fixed stalled application issues when end of stream is reached [#577](https://github.com/dotnet/SqlClient/pull/577) +- Fixed driver behavior to not throw exception for invalid configuration file [#573](https://github.com/dotnet/SqlClient/pull/573) +- Fixed Object null reference issue when failover partner is set [#588](https://github.com/dotnet/SqlClient/pull/588) +- Fixed `applicationintent` connection string property issue [#585](https://github.com/dotnet/SqlClient/pull/585) + +### Improvements and Changes +- Improved performance of Managed SNI by removing double fetch of domain name [#366](https://github.com/dotnet/SqlClient/pull/366) +- Improved performance of Async Method Allocations in Managed SNI [#328](https://github.com/dotnet/SqlClient/pull/328) +- Improved performance of Managed SNI by enhancing utilization of resources [#173](https://github.com/dotnet/SqlClient/pull/173) - Ported [dotnet/corefx#35363](https://github.com/dotnet/corefx/pull/35363) and [dotnet/corefx#40732](https://github.com/dotnet/corefx/pull/40732) +- Improved performance of Managed SNI RPC Parameter Usage [#209](https://github.com/dotnet/SqlClient/pull/209) - Ported [dotnet/corefx#34049](https://github.com/dotnet/corefx/pull/34049) +- Changed enclave key map to be lazy initialized [#372](https://github.com/dotnet/SqlClient/pull/372) +- Changed `Recieve()` and `ReceiveAsync()` implementation to receive null packets on failure [#350](https://github.com/dotnet/SqlClient/pull/350) +- Changed `EnclaveProviderBase` caching implementation to support Async Scenarios _(Introduces breaking changes)_ [#346](https://github.com/dotnet/SqlClient/pull/346) +- Updated all driver assemblies to be CLS Compliant [#396](https://github.com/dotnet/SqlClient/pull/396) +- Updated Bulk Copy error messages to also include Column, Row and non-encrypted Data information [#437](https://github.com/dotnet/SqlClient/pull/437) +- Updated error messages for "Always Encrypted - Secure Enclaves" to handle 'Attestation Protocol' and fixed typos [#421](https://github.com/dotnet/SqlClient/pull/421) [#397](https://github.com/dotnet/SqlClient/pull/397) +- Removed sync over async in `SNINpHandle.EnableSsl` [#474](https://github.com/dotnet/SqlClient/pull/474) +- Changed non-generic `ArrayList` to `List` in `SqlBulkCopy` [#457](https://github.com/dotnet/SqlClient/pull/457) +- Multiple performance improvements [#377](https://github.com/dotnet/SqlClient/pull/377) [#378](https://github.com/dotnet/SqlClient/pull/378) [#379](https://github.com/dotnet/SqlClient/pull/379) +- Improved performance by removing unwanted method calls in Event Source tracing [#506](https://github.com/dotnet/SqlClient/pull/506) +- Removed Diagnostic Source and Configuration Manager dependencies from .NET Standard implementation [#535](https://github.com/dotnet/SqlClient/pull/535) +- Removed redundant calls to `DbConnectionPoolKey.GetType()` [#512](https://github.com/dotnet/SqlClient/pull/512) +- Standardized connection string properties for enhanced user experience [#534](https://github.com/dotnet/SqlClient/pull/534) [Read More](#new-connection-string-property-synonyms) +- Improved performance by reducing eventsource tracing related to allocations from TVP write methods [#557](https://github.com/dotnet/SqlClient/pull/557) [#564](https://github.com/dotnet/SqlClient/pull/564) + +### Breaking Changes +- The driver will now perform Server Certificate validation when TLS encryption is enforced by the target Server, which is the default for Azure connections [#391](https://github.com/dotnet/SqlClient/pull/391) +- `SqlDataReader.GetSchemaTable()` now returns empty `DataTable` instead of returning `null` [#419](https://github.com/dotnet/SqlClient/pull/419) +- Updated driver to perform decimal scale rounding to match SQL Server behavior [#470](https://github.com/dotnet/SqlClient/pull/470) [Read More](#enable-decimal-truncation-behavior-conditionally) +- For .NET Framework applications consuming **Microsoft.Data.SqlClient**, the `SNI.dll` files previously downloaded to the `bin\x64` and `bin\x86` folders are now named `Microsoft.Data.SqlClient.SNI.x64.dll` and `Microsoft.Data.SqlClient.SNI.x86.dll` and will be downloaded to the `bin` directory, to support auto-loading in the application process [#570](https://github.com/dotnet/SqlClient/pull/570). _This change is not going to impact client applications unless a direct reference has been made to `SNI.dll` or the x86 and x64 folders._ +- Modified enclave provider interface `SqlColumnEncryptionEnclaveProvider` to be internal along with related classes `SqlEnclaveAttestationParameters` and `SqlEnclaveSession` [#602](https://github.com/dotnet/SqlClient/pull/602) - _This change is not likely to impact customer applications since secure enclaves is a relatively new feature and they would have had to implement their own enclave provider, which is not a trivial task_. +- Updated `SqlClientMetaDataCollectionNames` exposed constants by removing non-existing constants and adding new to the metadata collection [#580](https://github.com/dotnet/SqlClient/pull/580) + + +### Additional Active Directory authentication modes +This release brings parity of Active Directory authentication modes supported for .NET Framework and .NET Core applications. With the 2.0 stable release, the following authentication modes are supported for **Microsoft.Data.SqlClient**: + +|Authentication mechanism | .NET Framework 4.6+ | .NET Core 2.1+ | .NET Standard 2.0+| +|------|--------|--------|--------| +|Active Directory Password | Yes | Yes | Yes | +|Active Directory Integrated | Yes | **Yes**1 | **Yes**1 | +|Active Directory Interactive | Yes | **Yes**1 | **Yes**1 | +|**Active Directory Service Principal**1 | **Yes**1 | **Yes**1 | **Yes**1 | + +_1 New authentication mode starting with Microsoft.Data.SqlClient v2.0_ + +#### Active Directory Service Principal +This authentication mode uses Active Directory Service Principal to connect to an Azure SQL Database using the client ID and secret of a service principal identity. Service principal authentication involves setting up an App registration with a secret, granting permissions to the App in the Azure SQL Database instance, then connecting with a connection string like the following: + +`Server=tcp:.database.windows.net;Database=;Authentication=Active Directory Service Principal;User Id=;Password=;` + +### EventSource tracing support +This release introduces support for capturing EventSource trace logs for debugging applications. In order to capture these traces, client applications must listen to events from SqlClient's EventSource implementation: + +"Microsoft.Data.SqlClient.EventSource" + +Supported Event Keywords are: + +| Keyword Name | Value | Description | +| ------------ | ----- | ----------- | +| ExecutionTrace | 1 | Turns on capturing Start/Stop events before and after command execution. | +| Trace | 2 | Turns on capturing basic application flow trace events. | +| Scope | 4 | Turns on capturing enter and exit events | +| NotificationTrace | 8 | Turns on capturing `SqlNotification` trace events | +| NotificationScope | 16 | Turns on capturing `SqlNotification` scope enter and exit events | +| PoolerTrace | 32 | Turns on capturing connection pooling flow trace events. | +| PoolerScope | 64 | Turns on capturing connection pooling scope trace events. | +| AdvancedTrace | 128 | Turns on capturing advanced flow trace events. | +| AdvancedTraceBin | 256 | Turns on capturing advanced flow trace events with additional information. | +| CorrelationTrace | 512 | Turns on capturing correlation flow trace events. | +| StateDump | 1024 | Turns on capturing full state dump of `SqlConnection` | +| SNITrace | 2048 | Turns on capturing flow trace events from Managed Networking implementation (only applicable in .NET Core) | +| SNIScope | 4096 | Turns on capturing scope events from Managed Networking implementation (only applicable in .NET Core) | +||| + + +### Enable managed networking on Windows +This release introduces a new AppContext switch, "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", that enables the use of Managed SNI on Windows for testing and debugging purposes. This switch will toggle the driver's behavior to use Managed SNI in .NET Core 2.1+ and .NET Standard 2.0+ projects on Windows. Using the managed SNI implementation eliminates the dependency on the native Microsoft.Data.SqlClient.SNI binaries for a fully managed stack. + +To set the switch from app startup, specify: + +```cs +AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); +``` +> [NOTE] **Known differences when compared to Native SNI.dll**: Managed SNI does not support non-domain Windows Authentication. + + +### Enable decimal truncation behavior conditionally +Starting with v2.0.0-preview3, the decimal data scale will be rounded by the driver by default as is done by SQL Server. +For backwards compatibility, you can set the [AppContext](https://docs.microsoft.com/en-us/dotnet/api/system.appcontext?view=netframework-4.8) switch "Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal" to "true". + +To set the switch at application startup, specify: + +```cs +AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal", true); +``` + +### New Connection string property synonyms + +The below connection properties can be interchangeably used with the new synonyms to avoid spacing confusion and for an enhanced user experience. + +|Existing connection string property|New Synonym| +|-----------------------------------|-----------| +| ApplicationIntent | Application Intent | +| ConnectRetryCount | Connect Retry Count | +| ConnectRetryInterval | Connect Retry Interval | +| PoolBlockingPeriod | Pool Blocking Period | +| MultipleActiveResultSets | Multiple Active Result Sets | +| MultiSubnetFailover | Multiple Subnet Failover | +| TransparentNetworkIPResolution | Transparent Network IP Resolution | +| TrustServerCertificate | Trust Server Certificate | + +> [Note] This is not a breaking change. Old properties will continue to be supported for backwards compatibility. + + +#### SqlBulkCopy.RowsCopied property + +This property provides read-only access to the number of rows processed in the ongoing bulk copy operation. Note that this value is not necessarily equal to the number of rows added to the destination table. + + +#### Connection Open Overrides + +The default behavior of `SqlConnection.Open()` can be overridden to disable the ten second delay and automatic connection retries triggered by transient errors. + +```csharp +using SqlConnection sqlConnection = new SqlConnection("Data Source=(local);Integrated Security=true;Initial Catalog=AdventureWorks;"); +sqlConnection.Open(SqlConnectionOverrides.OpenWithoutRetry); +``` + +#### Username support for Active Directory Interactive mode + +A username can now be specified in the connection string when using Azure Active Directory Interactive authentication mode for both .NET Framework and .NET Core targeted applications. + +Set a username using the **User ID** or **UID** connection string property: + +``` +"Server=; Database=; Authentication=Active Directory Interactive; User Id=;" +``` + +#### Order hints for SqlBulkCopy + +Bulk copy operations offer significant performance advantages over other methods for loading data into a SQL Server table. Performance can be further enhanced by using order hints. Specifying order hints for your bulk copy operations can lower the insertion time of sorted data into tables with clustered indexes. + +By default, the bulk insert operation assumes the incoming data is unordered. SQL Server forces an intermediate sort of this data before bulk loading it. If you know that your incoming data is already sorted, you can use order hints to tell the bulk copy operation about the sort order of any destination columns that are part of a clustered index. + + +#### SNI dependency changes + +Microsoft.Data.SqlClient (.NET Core and .NET Standard) on Windows is now dependent on **Microsoft.Data.SqlClient.SNI.runtime**, replacing the previous dependency on **runtime.native.System.Data.SqlClient.SNI**. The new dependency adds support for the `ARM` platform along with the already supported platforms `ARM64`, `x64` and `x86` on Windows. + + +## Target Platform Support + +- .NET Framework 4.6+ (Windows x86, Windows x64) +- .NET Core 2.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 2.0.0 +- Microsoft.Identity.Client 4.14.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 5.6.0 +- Microsoft.IdentityModel.JsonWebTokens 5.6.0 + +#### .NET Core 2.1 + +- Microsoft.Data.SqlClient.SNI.runtime 2.0.0 +- Microsoft.Win32.Registry 4.7.0 +- System.Security.Principal.Windows 4.7.0 +- System.Text.Encoding.CodePages 4.7.0 +- System.Diagnostics.DiagnosticSource 4.7.0 +- System.Configuration.ConfigurationManager 4.7.0 +- System.Runtime.Caching 4.7.0 +- Microsoft.Identity.Client 4.14.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 5.6.0 +- Microsoft.IdentityModel.JsonWebTokens 5.6.0 + +#### .NET Core 3.1 + +- Microsoft.Data.SqlClient.SNI.runtime 2.0.0 +- Microsoft.Win32.Registry 4.7.0 +- System.Security.Principal.Windows 4.7.0 +- System.Text.Encoding.CodePages 4.7.0 +- System.Diagnostics.DiagnosticSource 4.7.0 +- System.Configuration.ConfigurationManager 4.7.0 +- System.Runtime.Caching 4.7.0 +- Microsoft.Identity.Client 4.14.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 5.6.0 +- Microsoft.IdentityModel.JsonWebTokens 5.6.0 + +#### .NET Standard + +- Microsoft.Data.SqlClient.SNI.runtime 2.0.0 +- Microsoft.Win32.Registry 4.7.0 +- System.Buffers 4.5.1 +- System.Memory 4.5.4 +- System.Security.Principal.Windows 4.7.0 +- System.Text.Encoding.CodePages 4.7.0 +- Microsoft.Identity.Client 4.14.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 5.6.0 +- Microsoft.IdentityModel.JsonWebTokens 5.6.0 diff --git a/release-notes/2.0/2.0.md b/release-notes/2.0/2.0.md index a50abbaaf4..57ab9b4f80 100644 --- a/release-notes/2.0/2.0.md +++ b/release-notes/2.0/2.0.md @@ -1,5 +1,11 @@ # Microsoft.Data.SqlClient 2.0 Releases +The following Microsoft.Data.SqlClient 2.0 stable releases have been shipped: + +| Release Date | Version | Notes | +| :-- | :-- | :--: | +| 2020/06/16 | 2.0.0 | [release notes](2.0.0.md) | + The following Microsoft.Data.SqlClient 2.0 preview releases have been shipped: | Release Date | Version | Notes | diff --git a/release-notes/2.0/README.md b/release-notes/2.0/README.md index a50abbaaf4..57ab9b4f80 100644 --- a/release-notes/2.0/README.md +++ b/release-notes/2.0/README.md @@ -1,5 +1,11 @@ # Microsoft.Data.SqlClient 2.0 Releases +The following Microsoft.Data.SqlClient 2.0 stable releases have been shipped: + +| Release Date | Version | Notes | +| :-- | :-- | :--: | +| 2020/06/16 | 2.0.0 | [release notes](2.0.0.md) | + The following Microsoft.Data.SqlClient 2.0 preview releases have been shipped: | Release Date | Version | Notes | diff --git a/release-notes/README.md b/release-notes/README.md index 829f2a2822..1986a536cb 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -1,6 +1,6 @@ # Microsoft.Data.SqlClient Release Notes -The latest stable release is [Microsoft.Data.SqlClient 1.1](1.1). +The latest stable release is [Microsoft.Data.SqlClient 2.0](2.0). ## Release Information diff --git a/roadmap.md b/roadmap.md index 61d1e5adc9..a9c8f5308d 100644 --- a/roadmap.md +++ b/roadmap.md @@ -13,7 +13,8 @@ The Microsoft.Data.SqlClient roadmap communicates project priorities for evolvin |---------------------------|--------------|---------------| | Microsoft.Data.SqlClient v1.0 (servicing) | As needed (see also [1.0 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/1.0)) | Closed | | Microsoft.Data.SqlClient v1.1 (servicing) | As needed (see also [1.1 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/1.1)) | Closed | -| Microsoft.Data.SqlClient v2.0 | GA (General Availability) estimated for May 2020 | [SqlClient 2.0.0](https://github.com/dotnet/SqlClient/projects/5) | +| Microsoft.Data.SqlClient v2.0 (servicing) | As needed (see also [2.0 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/2.0)) | Closed | +| Microsoft.Data.SqlClient v2.1 | GA (General Availability) estimated for November 2020 | [SqlClient 2.1.0](https://github.com/dotnet/SqlClient/projects/6) | > Note: Dates are calendar year (as opposed to fiscal year). diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs index 2a8787f487..d509a52ee6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs @@ -191,42 +191,6 @@ public abstract partial class SqlColumnEncryptionKeyStoreProvider /// public virtual bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { throw null; } } - /// - public abstract partial class SqlColumnEncryptionEnclaveProvider - { - /// - protected SqlColumnEncryptionEnclaveProvider() { } - /// - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); - /// - public abstract Microsoft.Data.SqlClient.SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); - /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); - /// - public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, Microsoft.Data.SqlClient.SqlEnclaveSession enclaveSession); - } - /// - public partial class SqlEnclaveAttestationParameters - { - /// - public SqlEnclaveAttestationParameters(int protocol, byte[] input, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey) { } - /// - public System.Security.Cryptography.ECDiffieHellmanCng ClientDiffieHellmanKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public int Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public byte[] GetInput() { throw null; } - } - /// - public partial class SqlEnclaveSession - { - /// - public SqlEnclaveSession(byte[] sessionKey, long sessionId) { } - /// - public long SessionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public byte[] GetSessionKey() { throw null; } - } } namespace Microsoft.Data.SqlTypes 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 679f5d32fc..bb52f2dbb6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -575,6 +575,18 @@ public sealed partial class SqlConnection : System.Data.Common.DbConnection, Sys /// [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] public System.Guid ClientConnectionId { get { throw null; } } + + /// + /// for internal test only + /// + [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] + internal string SQLDNSCachingSupportedState { get { throw null; } } + /// + /// for internal test only + /// + [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] + internal string SQLDNSCachingSupportedStateBeforeRedirect { get { throw null; } } + object System.ICloneable.Clone() { throw null; } /// [System.ComponentModel.DefaultValueAttribute("")] @@ -659,6 +671,9 @@ public sealed partial class SqlConnection : System.Data.Common.DbConnection, Sys public void ResetStatistics() { } /// public System.Collections.IDictionary RetrieveStatistics() { throw null; } + + /// + public System.Collections.Generic.IDictionary RetrieveInternalInfo() { throw null; } } /// public enum SqlConnectionOverrides 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 a4eed7751b..2566f441f0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs @@ -6,6 +6,7 @@ using Microsoft.Data.SqlClient; using System; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.Data.SqlClient { @@ -20,6 +21,8 @@ internal static partial class SNINativeMethodWrapper [UnmanagedFunctionPointer(CallingConvention.StdCall)] internal delegate void SqlAsyncCallbackDelegate(IntPtr m_ConsKey, IntPtr pPacket, uint dwError); + internal const int SniIP6AddrStringBufferLength = 48; // from SNI layer + internal static int SniMaxComposedSpnLength { get @@ -162,6 +165,20 @@ private unsafe struct SNI_CLIENT_CONSUMER_INFO public TransparentNetworkResolutionMode transparentNetworkResolution; public int totalTimeout; public bool isAzureSqlServerEndpoint; + public SNI_DNSCache_Info DNSCacheInfo; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct SNI_DNSCache_Info + { + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedFQDN; + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedTcpIPv4; + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedTcpIPv6; + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedTcpPort; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] @@ -236,6 +253,15 @@ internal struct SNI_Error [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out Guid pbQInfo); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ushort portNum); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ProviderEnum provNum); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] private static extern uint SNIInitialize([In] IntPtr pmo); @@ -248,7 +274,8 @@ internal struct SNI_Error [MarshalAs(UnmanagedType.LPWStr)] string szConnect, [In] SNIHandle pConn, out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync); + [MarshalAs(UnmanagedType.Bool)] bool fSync, + [In] ref SNI_DNSCache_Info pDNSCachedInfo); [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IOType IOType); @@ -283,22 +310,53 @@ internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId) { return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_CONNID, out connId); } + + internal static uint SniGetProviderNumber(SNIHandle pConn, ref ProviderEnum provNum) + { + return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PROVIDERNUM, out provNum); + } + + internal static uint SniGetConnectionPort(SNIHandle pConn, ref ushort portNum) + { + return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PEERPORT, out portNum); + } + + internal static uint SniGetConnectionIPString(SNIHandle pConn, ref string connIPStr) + { + UInt32 ret; + uint connIPLen = 0; + + int bufferSize = SniIP6AddrStringBufferLength; + StringBuilder addrBuffer = new StringBuilder(bufferSize); + + ret = SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out connIPLen); + + connIPStr = addrBuffer.ToString(0, Convert.ToInt32(connIPLen)); + + return ret; + } internal static uint SNIInitialize() { return SNIInitialize(IntPtr.Zero); } - internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync) + internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SQLDNSInfo cachedDNSInfo) { // initialize consumer info for MARS Sni_Consumer_Info native_consumerInfo = new Sni_Consumer_Info(); MarshalConsumerInfo(consumerInfo, ref native_consumerInfo); - return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync); + SNI_DNSCache_Info native_cachedDNSInfo = new SNI_DNSCache_Info(); + native_cachedDNSInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; + native_cachedDNSInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; + native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; + native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port; + + return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, 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) + 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) { fixed (byte* pin_instanceName = &instanceName[0]) { @@ -321,6 +379,11 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons clientConsumerInfo.totalTimeout = SniOpenTimeOut; clientConsumerInfo.isAzureSqlServerEndpoint = ADP.IsAzureSqlServerEndpoint(constring); + clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpPort = cachedDNSInfo?.Port; + if (spnBuffer != null) { fixed (byte* pin_spnBuffer = &spnBuffer[0]) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index c31298fb12..4328cfeebc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -147,6 +147,9 @@ Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs + + Microsoft\Data\SqlClient\SQLFallbackDNSCache.cs + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs index a915e59bfd..4a461b0532 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.NetCoreApp.cs @@ -62,16 +62,16 @@ internal class AzureAttestationEnclaveProvider : EnclaveProviderBase private static readonly MemoryCache OpenIdConnectConfigurationCache = new MemoryCache("OpenIdConnectConfigurationCache"); #endregion - #region Public methods + #region Internal methods // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { GetEnclaveSessionHelper(servername, attestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) + internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; @@ -81,7 +81,7 @@ public override SqlEnclaveAttestationParameters GetAttestationParameters(string } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) + internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; @@ -126,7 +126,7 @@ public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellma } // When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. - public override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + internal override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) { InvalidateEnclaveSessionHelper(serverName, enclaveAttestationUrl, enclaveSessionToInvalidate); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclavePackage.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclavePackage.cs index f0a3641cee..d559d943dc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclavePackage.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclavePackage.cs @@ -10,8 +10,8 @@ namespace Microsoft.Data.SqlClient internal class EnclavePackage { - public SqlEnclaveSession EnclaveSession { get; } - public byte[] EnclavePackageBytes { get; } + internal SqlEnclaveSession EnclaveSession { get; } + internal byte[] EnclavePackageBytes { get; } /// /// Constructor diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveSessionCache.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveSessionCache.NetCoreApp.cs index b4f591ae9b..1445918d37 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveSessionCache.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveSessionCache.NetCoreApp.cs @@ -22,7 +22,7 @@ internal class EnclaveSessionCache private static int enclaveCacheTimeOutInHours = 8; // Retrieves a SqlEnclaveSession from the cache - public SqlEnclaveSession GetEnclaveSession(string servername, string attestationUrl, out long counter) + internal SqlEnclaveSession GetEnclaveSession(string servername, string attestationUrl, out long counter) { string cacheKey = GenerateCacheKey(servername, attestationUrl); SqlEnclaveSession enclaveSession = enclaveMemoryCache[cacheKey] as SqlEnclaveSession; @@ -31,7 +31,7 @@ public SqlEnclaveSession GetEnclaveSession(string servername, string attestation } // Invalidates a SqlEnclaveSession entry in the cache - public void InvalidateSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + internal void InvalidateSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) { string cacheKey = GenerateCacheKey(serverName, enclaveAttestationUrl); @@ -52,7 +52,7 @@ public void InvalidateSession(string serverName, string enclaveAttestationUrl, S } // Creates a new SqlEnclaveSession and adds it to the cache - public SqlEnclaveSession CreateSession(string attestationUrl, string serverName, byte[] sharedSecret, long sessionId, out long counter) + internal SqlEnclaveSession CreateSession(string attestationUrl, string serverName, byte[] sharedSecret, long sessionId, out long counter) { string cacheKey = GenerateCacheKey(serverName, attestationUrl); SqlEnclaveSession enclaveSession = null; 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 dd26939d29..eaeedf8f20 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 @@ -263,8 +263,10 @@ public uint WritePacket(SNIHandle handle, SNIPacket packet, bool sync) /// Asynchronous connection /// Attempt parallel connects /// + /// Used for DNS Cache + /// Used for DNS Cache /// SNI handle - 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) + 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) { instanceName = new byte[1]; @@ -291,7 +293,7 @@ public SNIHandle CreateConnectionHandle(object callbackObject, string fullServer 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, callbackObject, parallel); + sniHandle = CreateTcpHandle(details, timerExpire, callbackObject, parallel, cachedFQDN, ref pendingDNSInfo); break; case DataSource.Protocol.NP: sniHandle = CreateNpHandle(details, timerExpire, callbackObject, parallel); @@ -373,8 +375,10 @@ private static byte[] GetSqlServerSPN(string hostNameOrAddress, string portOrIns /// Timer expiration /// Asynchronous I/O callback object /// Should MultiSubnetFailover be used + /// Key for DNS Cache + /// Used for DNS Cache /// SNITCPHandle - private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, object callbackObject, bool parallel) + private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, object callbackObject, bool parallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { // TCP Format: // tcp:\ @@ -412,7 +416,7 @@ private SNITCPHandle CreateTcpHandle(DataSource details, long timerExpire, objec port = isAdminConnection ? DefaultSqlServerDacPort : DefaultSqlServerPort; } - return new SNITCPHandle(hostName, port, timerExpire, callbackObject, parallel); + return new SNITCPHandle(hostName, port, timerExpire, callbackObject, parallel, 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 e83e63882a..9099427e7f 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,12 +116,17 @@ public override int ProtocolVersion /// Connection timer expiration /// Callback object /// Parallel executions - public SNITCPHandle(string serverName, int port, long timerExpire, object callbackObject, bool parallel) + /// Key for DNS Cache + /// Used for DNS Cache + public SNITCPHandle(string serverName, int port, long timerExpire, object callbackObject, bool parallel, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { _callbackObject = callbackObject; _targetServer = serverName; _sendSync = new object(); + SQLDNSInfo cachedDNSInfo; + bool hasCachedDNSInfo = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo); + try { TimeSpan ts = default(TimeSpan); @@ -135,33 +140,71 @@ public SNITCPHandle(string serverName, int port, long timerExpire, object callba ts = ts.Ticks < 0 ? TimeSpan.FromTicks(0) : ts; } - Task connectTask; - if (parallel) - { - Task serverAddrTask = Dns.GetHostAddressesAsync(serverName); - serverAddrTask.Wait(ts); - IPAddress[] serverAddresses = serverAddrTask.Result; + bool reportError = true; - if (serverAddresses.Length > MaxParallelIpAddresses) + // 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. + try + { + if (parallel) { - // Fail if above 64 to match legacy behavior - ReportTcpSNIError(0, SNICommon.MultiSubnetFailoverWithMoreThan64IPs, string.Empty); - return; + _socket = TryConnectParallel(serverName, port, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); } - - connectTask = ParallelConnectAsync(serverAddresses, port); - - if (!(isInfiniteTimeOut ? connectTask.Wait(-1) : connectTask.Wait(ts))) + else { - ReportTcpSNIError(0, SNICommon.ConnOpenFailedError, string.Empty); - return; + _socket = Connect(serverName, port, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo); } - - _socket = connectTask.Result; } - else + catch (Exception ex) { - _socket = Connect(serverName, port, ts, isInfiniteTimeOut); + // Retry with cached IP address + if (ex is SocketException || ex is ArgumentException || ex is AggregateException) + { + if (hasCachedDNSInfo == false) + { + throw; + } + else + { + int portRetry = String.IsNullOrEmpty(cachedDNSInfo.Port) ? port : Int32.Parse(cachedDNSInfo.Port); + + try + { + if (parallel) + { + _socket = TryConnectParallel(cachedDNSInfo.AddrIPv4, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); + } + else + { + _socket = Connect(cachedDNSInfo.AddrIPv4, portRetry, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo); + } + } + catch(Exception exRetry) + { + if (exRetry is SocketException || exRetry is ArgumentNullException + || exRetry is ArgumentException || exRetry is ArgumentOutOfRangeException || exRetry is AggregateException) + { + if (parallel) + { + _socket = TryConnectParallel(cachedDNSInfo.AddrIPv6, portRetry, ts, isInfiniteTimeOut, ref reportError, cachedFQDN, ref pendingDNSInfo); + } + else + { + _socket = Connect(cachedDNSInfo.AddrIPv6, portRetry, ts, isInfiniteTimeOut, cachedFQDN, ref pendingDNSInfo); + } + } + else + { + throw; + } + } + } + } + else + { + throw; + } } if (_socket == null || !_socket.Connected) @@ -171,7 +214,11 @@ public SNITCPHandle(string serverName, int port, long timerExpire, object callba _socket.Dispose(); _socket = null; } - ReportTcpSNIError(0, SNICommon.ConnOpenFailedError, string.Empty); + + if (reportError) + { + ReportTcpSNIError(0, SNICommon.ConnOpenFailedError, string.Empty); + } return; } @@ -196,9 +243,70 @@ public SNITCPHandle(string serverName, int port, long timerExpire, object callba _status = TdsEnums.SNI_SUCCESS; } - private static Socket Connect(string serverName, int port, TimeSpan timeout, bool isInfiniteTimeout) + // Connect to server with hostName and port in parellel mode. + // 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 Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool isInfiniteTimeOut, ref bool callerReportError, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) + { + Socket availableSocket = null; + Task connectTask; + + Task serverAddrTask = Dns.GetHostAddressesAsync(hostName); + serverAddrTask.Wait(ts); + IPAddress[] serverAddresses = serverAddrTask.Result; + + if (serverAddresses.Length > MaxParallelIpAddresses) + { + // Fail if above 64 to match legacy behavior + callerReportError = false; + ReportTcpSNIError(0, SNICommon.MultiSubnetFailoverWithMoreThan64IPs, string.Empty); + return availableSocket; + } + + string IPv4String = null; + string IPv6String = null; + + foreach (IPAddress ipAddress in serverAddresses) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + { + IPv4String = ipAddress.ToString(); + } + else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) + { + IPv6String = ipAddress.ToString(); + } + } + + if (IPv4String != null || IPv6String != null) + { + pendingDNSInfo = new SQLDNSInfo(cachedFQDN, IPv4String, IPv6String, port.ToString()); + } + + connectTask = ParallelConnectAsync(serverAddresses, port); + + if (!(isInfiniteTimeOut ? connectTask.Wait(-1) : connectTask.Wait(ts))) + { + callerReportError = false; + ReportTcpSNIError(0, SNICommon.ConnOpenFailedError, string.Empty); + return availableSocket; + } + + availableSocket = connectTask.Result; + return availableSocket; + + } + + // 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) { IPAddress[] ipAddresses = Dns.GetHostAddresses(serverName); + + string IPv4String = null; + string IPv6String = null; + IPAddress serverIPv4 = null; IPAddress serverIPv6 = null; foreach (IPAddress ipAddress in ipAddresses) @@ -206,15 +314,22 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo if (ipAddress.AddressFamily == AddressFamily.InterNetwork) { serverIPv4 = ipAddress; + IPv4String = ipAddress.ToString(); } else if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6) { serverIPv6 = ipAddress; + IPv6String = ipAddress.ToString(); } } ipAddresses = new IPAddress[] { serverIPv4, serverIPv6 }; Socket[] sockets = new Socket[2]; + if (IPv4String != null || IPv6String != null) + { + pendingDNSInfo = new SQLDNSInfo(cachedFQDN, IPv4String, IPv6String, port.ToString()); + } + CancellationTokenSource cts = null; void Cancel() diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs index e008b58edc..ccf4fe889c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.NetCoreApp.cs @@ -9,7 +9,7 @@ namespace Microsoft.Data.SqlClient /// /// The base class that defines the interface for enclave providers for Always Encrypted. An enclave is a protected region of memory inside SQL Server, used for computations on encrypted columns. An enclave provider encapsulates the client-side implementation details of the enclave attestation protocol as well as the logic for creating and caching enclave sessions. /// - public abstract partial class SqlColumnEncryptionEnclaveProvider + internal abstract partial class SqlColumnEncryptionEnclaveProvider { /// Performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. /// The information the provider uses to attest the enclave and generate a symmetric key for the session. The format of this information is specific to the enclave attestation protocol. @@ -20,7 +20,7 @@ public abstract partial class SqlColumnEncryptionEnclaveProvider /// The length of the extra data needed for attestating the enclave. /// The requested enclave session or null if the provider does not implement session caching. /// A counter that the enclave provider is expected to increment each time SqlClient retrieves the session from the cache. The purpose of this field is to prevent replay attacks. - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, + internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); } -} \ No newline at end of file +} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index 0e9af83456..79ba91de7b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -5,15 +5,15 @@ namespace Microsoft.Data.SqlClient { /// - public abstract partial class SqlColumnEncryptionEnclaveProvider + internal abstract partial class SqlColumnEncryptionEnclaveProvider { /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); + internal abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// - public abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); + internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// - public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSession); + internal abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSession); } } 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 4f2a24bd0d..757cc1ce96 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 @@ -562,6 +562,52 @@ public override string Database } } + /// + /// To indicate the IsSupported flag sent by the server for DNS Caching. This property is for internal testing only. + /// + internal string SQLDNSCachingSupportedState + { + get + { + SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); + string result; + + if (null != innerConnection) + { + result = innerConnection.IsSQLDNSCachingSupported ? "true": "false"; + } + else + { + result = "innerConnection is null!"; + } + + return result; + } + } + + /// + /// To indicate the IsSupported flag sent by the server for DNS Caching before redirection. This property is for internal testing only. + /// + internal string SQLDNSCachingSupportedStateBeforeRedirect + { + get + { + SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); + string result; + + if (null != innerConnection) + { + result = innerConnection.IsDNSCachingBeforeRedirectSupported ? "true": "false"; + } + else + { + result = "innerConnection is null!"; + } + + return result; + } + } + /// public override string DataSource { @@ -1988,6 +2034,17 @@ private void UpdateStatistics() Statistics.UpdateStatistics(); } + /// + public IDictionary RetrieveInternalInfo() + { + IDictionary internalDictionary = new Dictionary(); + + internalDictionary.Add("SQLDNSCachingSupportedState", SQLDNSCachingSupportedState); + internalDictionary.Add("SQLDNSCachingSupportedStateBeforeRedirect", SQLDNSCachingSupportedStateBeforeRedirect); + + return internalDictionary; + } + /// object ICloneable.Clone() => new SqlConnection(this); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs index 10c624e97a..4896d8e9cb 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -4517,14 +4517,23 @@ private Task GetBytesAsyncReadDataStage(GetBytesAsyncCallContext context, b SetTimeout(_defaultTimeoutMilliseconds); // Try to read without any continuations (all the data may already be in the stateObj's buffer) - if (!TryGetBytesInternalSequential(context.columnIndex, context.buffer, context.index, context.length, out bytesRead)) + bool filledBuffer = context._reader.TryGetBytesInternalSequential( + context.columnIndex, + context.buffer, + context.index + context.totalBytesRead, + context.length - context.totalBytesRead, + out bytesRead + ); + context.totalBytesRead += bytesRead; + Debug.Assert(context.totalBytesRead <= context.length, "Read more bytes than required"); + + if (!filledBuffer) { // This will be the 'state' for the callback - int totalBytesRead = bytesRead; - if (!isContinuation) { // This is the first async operation which is happening - setup the _currentTask and timeout + Debug.Assert(context._source==null, "context._source should not be non-null when trying to change to async"); source = new TaskCompletionSource(); Task original = Interlocked.CompareExchange(ref _currentTask, source.Task, null); if (original != null) @@ -4532,7 +4541,7 @@ private Task GetBytesAsyncReadDataStage(GetBytesAsyncCallContext context, b source.SetException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); return source.Task; } - + context._source = source; // Check if cancellation due to close is requested (this needs to be done after setting _currentTask) if (_cancelAsyncOnCloseToken.IsCancellationRequested) { @@ -4561,7 +4570,7 @@ private Task GetBytesAsyncReadDataStage(GetBytesAsyncCallContext context, b } else { - Debug.Assert(context._source != null, "context.source should not be null when continuing"); + Debug.Assert(context._source != null, "context._source should not be null when continuing"); // setup for cleanup/completing retryTask.ContinueWith( continuationAction: AAsyncCallContext.s_completeCallback, diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs index 72eee7d62b..1f59daf511 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs @@ -7,17 +7,17 @@ namespace Microsoft.Data.SqlClient { /// - public partial class SqlEnclaveAttestationParameters + internal partial class SqlEnclaveAttestationParameters { private static readonly string _clientDiffieHellmanKeyName = "ClientDiffieHellmanKey"; private static readonly string _inputName = "input"; private static readonly string _className = "EnclaveAttestationParameters"; /// - public ECDiffieHellmanCng ClientDiffieHellmanKey { get; } + internal ECDiffieHellmanCng ClientDiffieHellmanKey { get; } /// - public SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellmanCng clientDiffieHellmanKey) + internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellmanCng clientDiffieHellmanKey) { _input = input ?? throw SQL.NullArgumentInConstructorInternal(_inputName, _className); Protocol = protocol; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs index 58de866956..25f2737c70 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs @@ -5,15 +5,15 @@ namespace Microsoft.Data.SqlClient { /// - public partial class SqlEnclaveAttestationParameters + internal partial class SqlEnclaveAttestationParameters { private readonly byte[] _input = null; /// - public int Protocol { get; } + internal int Protocol { get; } /// - public byte[] GetInput() + internal byte[] GetInput() { return Clone(_input); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs index ac3e70b6e6..4328123100 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs @@ -5,7 +5,7 @@ namespace Microsoft.Data.SqlClient { /// - public class SqlEnclaveSession + internal class SqlEnclaveSession { private static readonly string _sessionKeyName = "SessionKey"; @@ -14,10 +14,10 @@ public class SqlEnclaveSession private readonly byte[] _sessionKey; /// - public long SessionId { get; } + internal long SessionId { get; } /// - public byte[] GetSessionKey() + internal byte[] GetSessionKey() { return Clone(_sessionKey); } @@ -41,7 +41,7 @@ private byte[] Clone(byte[] arrayToClone) } /// - public SqlEnclaveSession(byte[] sessionKey, long sessionId/*, long counter*/) + internal SqlEnclaveSession(byte[] sessionKey, long sessionId/*, long counter*/) { if (null == sessionKey) { 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 3369fa35f3..51928f9b74 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 @@ -128,6 +128,61 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper; private readonly SqlAuthenticationProviderManager _sqlAuthenticationProviderManager; + internal bool _cleanSQLDNSCaching = false; + + private bool _serverSupportsDNSCaching = false; + + /// + /// Get or set if SQLDNSCaching is supported by the server. + /// + internal bool IsSQLDNSCachingSupported + { + get + { + return _serverSupportsDNSCaching; + } + set + { + _serverSupportsDNSCaching = value; + } + } + + private bool _SQLDNSRetryEnabled = false; + + /// + /// Get or set if we need retrying with IP received from FeatureExtAck. + /// + internal bool IsSQLDNSRetryEnabled + { + get + { + return _SQLDNSRetryEnabled; + } + set + { + _SQLDNSRetryEnabled = value; + } + } + + private bool _DNSCachingBeforeRedirect = false; + + /// + /// Get or set if the control ring send redirect token and feature ext ack with true for DNSCaching + /// + internal bool IsDNSCachingBeforeRedirectSupported + { + get + { + return _DNSCachingBeforeRedirect; + } + set + { + _DNSCachingBeforeRedirect = value; + } + } + + internal SQLDNSInfo pendingSQLDNSObject = null; + // TCE flags internal byte _tceVersionSupported; @@ -1249,6 +1304,9 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, // The GLOBALTRANSACTIONS, DATACLASSIFICATION, TCE, and UTF8 support features are implicitly requested requestedFeatures |= TdsEnums.FeatureExtension.GlobalTransactions | TdsEnums.FeatureExtension.DataClassification | TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.UTF8Support; + // The SQLDNSCaching feature is implicitly set + requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData); } @@ -2379,8 +2437,11 @@ internal void OnFeatureExtAck(int featureId, byte[] data) { if (RoutingInfo != null) { - return; + if (TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId) { + return; + } } + switch (featureId) { case TdsEnums.FEATUREEXT_SRECOVERY: @@ -2567,6 +2628,40 @@ internal void OnFeatureExtAck(int featureId, byte[] data) _parser.DataClassificationVersion = (enabled == 0) ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED : supportedDataClassificationVersion; break; } + + case TdsEnums.FEATUREEXT_SQLDNSCACHING: + { + SqlClientEventSource.Log.AdvancedTraceEvent(" {0}, Received feature extension acknowledgement for SQLDNSCACHING", ObjectID); + + if (data.Length < 1) + { + SqlClientEventSource.Log.TraceEvent(" {0}, Unknown token for SQLDNSCACHING", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + if (1 == data[0]) { + IsSQLDNSCachingSupported = true; + _cleanSQLDNSCaching = false; + + if (RoutingInfo != null) + { + IsDNSCachingBeforeRedirectSupported = true; + } + } + else { + // we receive the IsSupported whose value is 0 + IsSQLDNSCachingSupported = false; + _cleanSQLDNSCaching = true; + } + + // need to add more steps for phase 2 + // get IPv4 + IPv6 + Port number + // not put them in the DNS cache at this point but need to store them somewhere + // generate pendingSQLDNSObject and turn on IsSQLDNSRetryEnabled flag + + break; + } + default: { // Unknown feature ack @@ -2701,4 +2796,3 @@ internal void SetDerivedNames(string protocol, string serverName) } } } - 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 8b5a19abd7..13e0327818 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 @@ -214,6 +214,7 @@ public enum EnvChangeType : byte public const byte FEATUREEXT_AZURESQLSUPPORT = 0x08; public const byte FEATUREEXT_DATACLASSIFICATION = 0x09; public const byte FEATUREEXT_UTF8SUPPORT = 0x0A; + public const byte FEATUREEXT_SQLDNSCACHING = 0x0B; [Flags] public enum FeatureExtension : uint @@ -226,6 +227,7 @@ public enum FeatureExtension : uint AzureSQLSupport = 1 << (TdsEnums.FEATUREEXT_AZURESQLSUPPORT - 1), DataClassification = 1 << (TdsEnums.FEATUREEXT_DATACLASSIFICATION - 1), UTF8Support = 1 << (TdsEnums.FEATUREEXT_UTF8SUPPORT - 1), + SQLDNSCaching = 1 << (TdsEnums.FEATUREEXT_SQLDNSCACHING - 1) } public const uint UTF8_IN_TDSCOLLATION = 0x4000000; 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 c487f9ab41..2667c3aef3 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 @@ -163,6 +163,9 @@ internal sealed partial class TdsParser /// internal string EnclaveType { get; set; } + internal bool isTcpProtocol { get; set; } + internal string FQDNforDNSCahce { get; set; } + /// /// Get if data classification is enabled by the server. /// @@ -362,6 +365,9 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) _connHandler = connHandler; _loginWithFailover = withFailover; + // Clean up IsSQLDNSCachingSupported flag from previous status + _connHandler.IsSQLDNSCachingSupported = false; + uint sniStatus = TdsParserStateObjectFactory.Singleton.SNIStatus; if (sniStatus != TdsEnums.SNI_SUCCESS) @@ -417,9 +423,19 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) bool fParallel = _connHandler.ConnectionOptions.MultiSubnetFailover; + FQDNforDNSCahce = serverInfo.ResolvedServerName; + + int commaPos = FQDNforDNSCahce.IndexOf(","); + if (commaPos != -1) + { + FQDNforDNSCahce = FQDNforDNSCahce.Substring(0, commaPos); + } + + _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, integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated); + out instanceName, ref _sniSpnBuffer, false, true, fParallel, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -458,6 +474,13 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) uint result = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); + + if (null == _connHandler.pendingSQLDNSObject) + { + // for DNS Caching phase 1 + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject); + } + SqlClientEventSource.Log.TraceEvent(" Sending prelogin handshake", "SEC"); SendPreLoginHandshake(instanceName, encrypt); @@ -476,7 +499,7 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) // 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, integratedSecurity); + _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref _sniSpnBuffer, true, true, fParallel, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject, integratedSecurity); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -490,6 +513,12 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); SqlClientEventSource.Log.TraceEvent(" Sending prelogin handshake", "SEC"); + if (null == _connHandler.pendingSQLDNSObject) + { + // for DNS Caching phase 1 + _physicalStateObj.AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce, ref _connHandler.pendingSQLDNSObject); + } + SendPreLoginHandshake(instanceName, encrypt); status = ConsumePreLoginHandshake(encrypt, trustServerCert, integratedSecurity, out marsCapable, out _connHandler._fedAuthRequired); @@ -3103,6 +3132,20 @@ private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) } } while (featureId != TdsEnums.FEATUREEXT_TERMINATOR); + // Write to DNS Cache or clean up DNS Cache for TCP protocol + bool ret = false; + if (_connHandler._cleanSQLDNSCaching) + { + ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCahce); + } + + if ( _connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null + && !SQLFallbackDNSCache.Instance.IsDuplicate(_connHandler.pendingSQLDNSObject)) + { + ret = SQLFallbackDNSCache.Instance.AddDNSInfo(_connHandler.pendingSQLDNSObject); + _connHandler.pendingSQLDNSObject = null; + } + // Check if column encryption was on and feature wasn't acknowledged and we aren't going to be routed to another server. if (Connection.RoutingInfo == null && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled @@ -7825,6 +7868,20 @@ internal int WriteUTF8SupportFeatureRequest(bool write /* if false just calculat return len; } + internal int WriteSQLDNSCachingFeatureRequest(bool write /* if false just calculates the length */) + { + int len = 5; // 1byte = featureID, 4bytes = featureData length + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_SQLDNSCACHING); + WriteInt(0, _physicalStateObj); // we don't send any data + } + + return len; + } + internal void TdsLogin(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, FederatedAuthenticationFeatureExtensionData? fedAuthFeatureExtensionData) { _physicalStateObj.SetTimeoutSeconds(rec.timeout); @@ -7982,6 +8039,11 @@ internal void TdsLogin(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures length += WriteUTF8SupportFeatureRequest(false); } + if ((requestedFeatures & TdsEnums.FeatureExtension.SQLDNSCaching) != 0) + { + length += WriteSQLDNSCachingFeatureRequest(false); + } + length++; // for terminator } @@ -8244,6 +8306,11 @@ internal void TdsLogin(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures WriteUTF8SupportFeatureRequest(true); } + if ((requestedFeatures & TdsEnums.FeatureExtension.SQLDNSCaching) != 0) + { + WriteSQLDNSCachingFeatureRequest(true); + } + _physicalStateObj.WriteByte(0xFF); // terminator } } 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 0aa77e5cae..921d72a385 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 @@ -143,7 +143,8 @@ internal sealed class SNIHandle : SafeHandle out byte[] instanceName, bool flushCache, bool fSync, - bool fParallel) + bool fParallel, + SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true) { try @@ -158,18 +159,18 @@ internal sealed class SNIHandle : SafeHandle } _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle, - spnBuffer, instanceName, flushCache, fSync, timeout, fParallel); + spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, cachedDNSInfo); } } // constructs SNI Handle for MARS session - internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent) : base(IntPtr.Zero, true) + internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent, SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true) { try { } finally { - _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync); + _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, 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 6888ad0453..44eb698f48 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 @@ -762,7 +762,9 @@ 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, 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, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo, bool isIntegratedSecurity = false); + + internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo); internal abstract uint SniGetConnectionId(ref Guid clientConnectionId); 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 6e25589986..cc2430bf24 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 @@ -49,9 +49,9 @@ internal SNIMarsHandle CreateMarsSession(object callbackObject, bool async) protected override uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize) => SNIProxy.Singleton.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, bool isIntegratedSecurity) + 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) { - _sessionHandle = SNIProxy.Singleton.CreateConnectionHandle(this, serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity); + _sessionHandle = SNIProxy.Singleton.CreateConnectionHandle(this, serverName, ignoreSniOpenTimeout, timerExpire, out instanceName, ref spnBuffer, flushCache, async, parallel, isIntegratedSecurity, cachedFQDN, ref pendingDNSInfo); if (_sessionHandle == null) { _parser.ProcessSNIError(this); @@ -63,6 +63,12 @@ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSni } } + // The assignment will be happened right after we resolve DNS in managed SNI layer + internal override void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo) + { + // No-op + } + internal void ReadAsyncCallback(SNIPacket packet, uint error) { ReadAsyncCallback(IntPtr.Zero, PacketHandle.FromManagedPacket(packet), error); 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 a38b5524df..a358992399 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 @@ -9,6 +9,7 @@ using System.Security.Authentication; using System.Threading.Tasks; using Microsoft.Data.Common; +using System.Net; namespace Microsoft.Data.SqlClient { @@ -61,7 +62,62 @@ protected override void CreateSessionHandle(TdsParserStateObject physicalConnect Debug.Assert(physicalConnection is TdsParserStateObjectNative, "Expected a stateObject of type " + this.GetType()); TdsParserStateObjectNative nativeSNIObject = physicalConnection as TdsParserStateObjectNative; SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); - _sessionHandle = new SNIHandle(myInfo, nativeSNIObject.Handle); + + SQLDNSInfo cachedDNSInfo; + bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCahce, out cachedDNSInfo); + + _sessionHandle = new SNIHandle(myInfo, nativeSNIObject.Handle, cachedDNSInfo); + } + + 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; + SNINativeMethodWrapper.ProviderEnum providerNumber = SNINativeMethodWrapper.ProviderEnum.INVALID_PROV; + + if (string.IsNullOrEmpty(userProtocol)) + { + + result = SNINativeMethodWrapper.SniGetProviderNumber(Handle, ref providerNumber); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetProviderNumber"); + _parser.isTcpProtocol = (providerNumber == SNINativeMethodWrapper.ProviderEnum.TCP_PROV); + } + else if (userProtocol == TdsEnums.TCP) + { + _parser.isTcpProtocol = true; + } + + // serverInfo.UserProtocol could be empty + if (_parser.isTcpProtocol) + { + result = SNINativeMethodWrapper.SniGetConnectionPort(Handle, ref portFromSNI); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionPort"); + + + result = SNINativeMethodWrapper.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 SNINativeMethodWrapper.ConsumerInfo CreateConsumerInfo(bool async) @@ -82,7 +138,7 @@ 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, bool isIntegratedSecurity) + 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) { // We assume that the loadSSPILibrary has been called already. now allocate proper length of buffer spnBuffer = null; @@ -113,7 +169,10 @@ internal override void CreatePhysicalSNIHandle(string serverName, bool ignoreSni } } - _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), out instanceName, flushCache, !async, fParallel); + 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, cachedDNSInfo); } protected override uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs index b768c32bae..207c5ef11a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.NetCoreApp.cs @@ -82,17 +82,17 @@ internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : Enclave #endregion - #region Public methods + #region Internal methods // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { GetEnclaveSessionHelper(servername, attestationUrl, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) + internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; @@ -101,7 +101,7 @@ public override SqlEnclaveAttestationParameters GetAttestationParameters(string } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) + internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; @@ -141,7 +141,7 @@ public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellma } // When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. - public override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + internal override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) { InvalidateEnclaveSessionHelper(serverName, enclaveAttestationUrl, enclaveSessionToInvalidate); } 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 4081ed7707..a574b75859 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -477,20 +477,6 @@ public partial class SqlColumnEncryptionCspProvider : Microsoft.Data.SqlClient.S /// public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) { throw null; } } - /// - public abstract partial class SqlColumnEncryptionEnclaveProvider - { - /// - protected SqlColumnEncryptionEnclaveProvider() { } - /// - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter); - /// - public abstract Microsoft.Data.SqlClient.SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); - /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out Microsoft.Data.SqlClient.SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); - /// - public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, Microsoft.Data.SqlClient.SqlEnclaveSession enclaveSession); - } /// public abstract partial class SqlColumnEncryptionKeyStoreProvider { @@ -847,6 +833,9 @@ public sealed partial class SqlConnection : System.Data.Common.DbConnection, Sys public void ResetStatistics() { } /// public System.Collections.IDictionary RetrieveStatistics() { throw null; } + + /// + public System.Collections.Generic.IDictionary RetrieveInternalInfo() { throw null; } } /// public enum SqlConnectionColumnEncryptionSetting @@ -1333,28 +1322,6 @@ public sealed partial class SqlDependency [System.Security.Permissions.HostProtectionAttribute(System.Security.Permissions.SecurityAction.LinkDemand, ExternalThreading = true)] public static bool Stop(string connectionString, string queue) { throw null; } } - /// - public partial class SqlEnclaveAttestationParameters - { - /// - public SqlEnclaveAttestationParameters(int protocol, byte[] input, System.Security.Cryptography.ECDiffieHellmanCng clientDiffieHellmanKey) { } - /// - public System.Security.Cryptography.ECDiffieHellmanCng ClientDiffieHellmanKey { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public int Protocol { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public byte[] GetInput() { throw null; } - } - /// - public partial class SqlEnclaveSession - { - /// - public SqlEnclaveSession(byte[] sessionKey, long sessionId) { } - /// - public long SessionId { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - /// - public byte[] GetSessionKey() { throw null; } - } /// public sealed partial class SqlError { 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 858fadfdb4..f90f36784d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -201,6 +201,9 @@ Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs + + Microsoft\Data\SqlClient\SQLFallbackDNSCache.cs + 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 edfb5e960f..0cddc32dc1 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 @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using System.Text; using static Microsoft.Data.SqlClient.SNINativeMethodWrapper; namespace Microsoft.Data.SqlClient @@ -78,6 +79,15 @@ internal static class SNINativeManagedWrapperX64 [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, ref IntPtr pbQInfo); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ushort portNum); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + internal static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ProviderEnum provNum); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIInitialize")] internal static extern uint SNIInitialize([In] IntPtr pmo); @@ -90,7 +100,8 @@ internal static class SNINativeManagedWrapperX64 [MarshalAs(UnmanagedType.LPWStr)] string szConnect, [In] SNIHandle pConn, out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync); + [MarshalAs(UnmanagedType.Bool)] bool fSync, + [In] ref SNI_DNSCache_Info pDNSCachedInfo); [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IOType IOType); 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 89c9af997b..398ecc4872 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 @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using System.Text; using static Microsoft.Data.SqlClient.SNINativeMethodWrapper; namespace Microsoft.Data.SqlClient @@ -78,6 +79,15 @@ internal static class SNINativeManagedWrapperX86 [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, ref IntPtr pbQInfo); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ushort portNum); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + internal static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen); + + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] + internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ProviderEnum provNum); + [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIInitialize")] internal static extern uint SNIInitialize([In] IntPtr pmo); @@ -90,7 +100,8 @@ internal static class SNINativeManagedWrapperX86 [MarshalAs(UnmanagedType.LPWStr)] string szConnect, [In] SNIHandle pConn, out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync); + [MarshalAs(UnmanagedType.Bool)] bool fSync, + [In] ref SNI_DNSCache_Info pDNSCachedInfo); [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IOType IOType); 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 fefdeea4b7..66efa587b6 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 @@ -13,6 +13,7 @@ using System.Threading; using Microsoft.Data.Common; using Microsoft.Data.SqlClient; +using System.Text; namespace Microsoft.Data.SqlClient { @@ -50,6 +51,7 @@ internal static class SNINativeMethodWrapper internal const int LocalDBInvalidSqlUserInstanceDllPath = 55; internal const int LocalDBFailedToLoadDll = 56; internal const int LocalDBBadRuntime = 57; + internal const int SniIP6AddrStringBufferLength = 48; // from SNI layer internal static int SniMaxComposedSpnLength { @@ -352,6 +354,20 @@ internal unsafe struct SNI_CLIENT_CONSUMER_INFO public TransparentNetworkResolutionMode transparentNetworkResolution; public int totalTimeout; public bool isAzureSqlServerEndpoint; + public SNI_DNSCache_Info DNSCacheInfo; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct SNI_DNSCache_Info + { + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedFQDN; + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedTcpIPv4; + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedTcpIPv6; + [MarshalAs(UnmanagedType.LPWStr)] + public string wszCachedTcpPort; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] @@ -547,6 +563,27 @@ private static uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapp SNINativeManagedWrapperX86.SNIGetInfoWrapper(pConn, QType, ref pbQInfo); } + private static uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ushort portNum) + { + return s_is64bitProcess ? + SNINativeManagedWrapperX64.SNIGetInfoWrapper(pConn, QType, out portNum) : + SNINativeManagedWrapperX86.SNIGetInfoWrapper(pConn, QType, out portNum); + } + + private static uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen) + { + return s_is64bitProcess ? + SNINativeManagedWrapperX64.SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLen) : + SNINativeManagedWrapperX86.SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLen); + } + + private static uint SNIGetInfoWrapper([In] SNIHandle pConn, SNINativeMethodWrapper.QTypes QType, out ProviderEnum provNum) + { + return s_is64bitProcess ? + SNINativeManagedWrapperX64.SNIGetInfoWrapper(pConn, QType, out provNum) : + SNINativeManagedWrapperX86.SNIGetInfoWrapper(pConn, QType, out provNum); + } + private static uint SNIInitialize([In] IntPtr pmo) { return s_is64bitProcess ? @@ -566,11 +603,12 @@ private static uint SNIOpenSyncExWrapper(ref SNI_CLIENT_CONSUMER_INFO pClientCon [MarshalAs(UnmanagedType.LPWStr)] string szConnect, [In] SNIHandle pConn, out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync) + [MarshalAs(UnmanagedType.Bool)] bool fSync, + [In] ref SNI_DNSCache_Info pDNSCachedInfo) { return s_is64bitProcess ? - SNINativeManagedWrapperX64.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync) : - SNINativeManagedWrapperX86.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync); + SNINativeManagedWrapperX64.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ref pDNSCachedInfo) : + SNINativeManagedWrapperX86.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ref pDNSCachedInfo); } private static IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IOType IOType) @@ -687,22 +725,55 @@ internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId) { return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_CONNID, out connId); } + + internal static uint SniGetProviderNumber(SNIHandle pConn, ref ProviderEnum provNum) + { + return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PROVIDERNUM, out provNum); + } + + internal static uint SniGetConnectionPort(SNIHandle pConn, ref ushort portNum) + { + return SNIGetInfoWrapper(pConn, QTypes.SNI_QUERY_CONN_PEERPORT, out portNum); + } + + internal static uint SniGetConnectionIPString(SNIHandle pConn, ref string connIPStr) + { + UInt32 ret; + uint ERROR_SUCCESS = 0; + uint connIPLen = 0; + + int bufferSize = SniIP6AddrStringBufferLength; + StringBuilder addrBuffer = new StringBuilder(bufferSize); + + ret = SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out connIPLen); + Debug.Assert(ret == ERROR_SUCCESS, "SNIGetPeerAddrStrWrapper fail"); + + connIPStr = addrBuffer.ToString(0, Convert.ToInt32(connIPLen)); + + return ret; + } internal static uint SNIInitialize() { return SNIInitialize(IntPtr.Zero); } - internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync) + internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SQLDNSInfo cachedDNSInfo) { // initialize consumer info for MARS Sni_Consumer_Info native_consumerInfo = new Sni_Consumer_Info(); MarshalConsumerInfo(consumerInfo, ref native_consumerInfo); - return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync); + SNI_DNSCache_Info native_cachedDNSInfo = new SNI_DNSCache_Info(); + native_cachedDNSInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; + native_cachedDNSInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; + native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; + native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port; + + return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, 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) + 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) { fixed (byte* pin_instanceName = &instanceName[0]) { @@ -737,6 +808,11 @@ internal static unsafe uint SNIOpenSyncEx(ConsumerInfo consumerInfo, string cons }; clientConsumerInfo.totalTimeout = totalTimeout; + clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpPort = cachedDNSInfo?.Port; + if (spnBuffer != null) { fixed (byte* pin_spnBuffer = &spnBuffer[0]) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs index c993ed3800..644d336232 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -11,6 +11,7 @@ namespace Microsoft.Data.ProviderBase using System.Diagnostics; using System.Globalization; using System.IO; + using System.Xml; using Microsoft.Data.Common; internal class DbMetaDataFactory @@ -549,7 +550,9 @@ private void LoadDataSetFromXml(Stream XmlStream) { _metaDataCollectionsDataSet = new DataSet(); _metaDataCollectionsDataSet.Locale = System.Globalization.CultureInfo.InvariantCulture; - _metaDataCollectionsDataSet.ReadXml(XmlStream); + // Reading from a stream has security implications. Create an XmlReader and do not allow the + // XmlReader to open any external resources by setting the XmlResolver property to null. + _metaDataCollectionsDataSet.ReadXml(XmlReader.Create(XmlStream, new XmlReaderSettings() { XmlResolver = null })); } virtual protected DataTable PrepareCollection(String collectionName, String[] restrictions, DbConnection connection) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index 81159b2182..dea4db7680 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -62,16 +62,16 @@ internal class AzureAttestationEnclaveProvider : EnclaveProviderBase private static readonly MemoryCache OpenIdConnectConfigurationCache = new MemoryCache("OpenIdConnectConfigurationCache"); #endregion - #region Public methods + #region Internal methods // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { GetEnclaveSessionHelper(servername, attestationUrl, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) + internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; @@ -81,7 +81,7 @@ public override SqlEnclaveAttestationParameters GetAttestationParameters(string } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) + internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; @@ -126,7 +126,7 @@ public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellma } // When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. - public override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + internal override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) { InvalidateEnclaveSessionHelper(serverName, enclaveAttestationUrl, enclaveSessionToInvalidate); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs index 24903f1eae..4201b3545a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.cs @@ -417,8 +417,8 @@ internal class RetryableEnclaveQueryExecutionException : Exception internal class EnclavePackage { - public SqlEnclaveSession EnclaveSession { get; } - public byte[] EnclavePackageBytes { get; } + internal SqlEnclaveSession EnclaveSession { get; } + internal byte[] EnclavePackageBytes { get; } /// /// Constructor diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs index 75e4bcd617..5cf0590d06 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs @@ -22,7 +22,7 @@ internal class EnclaveSessionCache private static int enclaveCacheTimeOutInHours = 8; // Retrieves a SqlEnclaveSession from the cache - public SqlEnclaveSession GetEnclaveSession(string servername, string attestationUrl, out long counter) + internal SqlEnclaveSession GetEnclaveSession(string servername, string attestationUrl, out long counter) { string cacheKey = GenerateCacheKey(servername, attestationUrl); SqlEnclaveSession enclaveSession = enclaveMemoryCache[cacheKey] as SqlEnclaveSession; @@ -31,7 +31,7 @@ public SqlEnclaveSession GetEnclaveSession(string servername, string attestation } // Invalidates a SqlEnclaveSession entry in the cache - public void InvalidateSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + internal void InvalidateSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) { string cacheKey = GenerateCacheKey(serverName, enclaveAttestationUrl); @@ -52,7 +52,7 @@ public void InvalidateSession(string serverName, string enclaveAttestationUrl, S } // Creates a new SqlEnclaveSession and adds it to the cache - public SqlEnclaveSession CreateSession(string attestationUrl, string serverName, byte[] sharedSecret, long sessionId, out long counter) + internal SqlEnclaveSession CreateSession(string attestationUrl, string serverName, byte[] sharedSecret, long sessionId, out long counter) { string cacheKey = GenerateCacheKey(serverName, attestationUrl); SqlEnclaveSession enclaveSession = null; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs index 811816f1e6..cf9af48632 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SimulatorEnclaveProvider.cs @@ -22,14 +22,14 @@ internal class SimulatorEnclaveProvider : EnclaveProviderBase // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { GetEnclaveSessionHelper(servername, attestationUrl, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. // The information SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) + internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(384); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; @@ -39,7 +39,7 @@ public override SqlEnclaveAttestationParameters GetAttestationParameters(string } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) + internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { ////for simulator: enclave does not send public key, and sends an empty attestation info //// The only non-trivial content it sends is the session setup info (DH pubkey of enclave) @@ -108,7 +108,7 @@ public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellma /// The name of the SQL Server instance containing the enclave. /// The endpoint of an attestation service, SqlClient contacts to attest the enclave. /// The session to be invalidated. - public override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + internal override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) { InvalidateEnclaveSessionHelper(serverName, enclaveAttestationUrl, enclaveSessionToInvalidate); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index b5915c578c..d29e7d1643 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -7,19 +7,19 @@ namespace Microsoft.Data.SqlClient { /// - public abstract class SqlColumnEncryptionEnclaveProvider + internal abstract class SqlColumnEncryptionEnclaveProvider { /// - public abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); + internal abstract void GetEnclaveSession(string serverName, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength); /// - public abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); + internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// - public abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); + internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); /// - public abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSession); + internal abstract void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSession); } } 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 3440ea14f6..d4dcf356bf 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 @@ -739,6 +739,54 @@ override public string Database } } + /// + /// To indicate the IsSupported flag sent by the server for DNS Caching. This property is for internal testing only. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + internal string SQLDNSCachingSupportedState + { + get + { + SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); + string result; + + if (null != innerConnection) + { + result = innerConnection.IsSQLDNSCachingSupported ? "true": "false"; + } + else + { + result = "innerConnection is null!"; + } + + return result; + } + } + + /// + /// To indicate the IsSupported flag sent by the server for DNS Caching before redirection. This property is for internal testing only. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + internal string SQLDNSCachingSupportedStateBeforeRedirect + { + get + { + SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); + string result; + + if (null != innerConnection) + { + result = innerConnection.IsDNSCachingBeforeRedirectSupported ? "true": "false"; + } + else + { + result = "innerConnection is null!"; + } + + return result; + } + } + /// [ Browsable(true), @@ -2693,6 +2741,17 @@ private void UpdateStatistics() Statistics.UpdateStatistics(); } + /// + public IDictionary RetrieveInternalInfo() + { + IDictionary internalDictionary = new Dictionary(); + + internalDictionary.Add("SQLDNSCachingSupportedState", SQLDNSCachingSupportedState); + internalDictionary.Add("SQLDNSCachingSupportedStateBeforeRedirect", SQLDNSCachingSupportedStateBeforeRedirect); + + return internalDictionary; + } + // // UDT SUPPORT // diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependency.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependency.cs index be77a748b9..89fab12b93 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -9,6 +9,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.Remoting; +using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Versioning; using System.Security.Permissions; @@ -240,7 +241,29 @@ private static void InvokeCallback(object eventContextPair) // END EventContextPair private class. // ---------------------------------------- + // ---------------------------------------- + // Private class for restricting allowed types from deserialization. + // ---------------------------------------- + private class SqlDependencyProcessDispatcherSerializationBinder : SerializationBinder + { + public override Type BindToType(string assemblyName, string typeName) + { + // Deserializing an unexpected type can inject objects with malicious side effects. + // If the type is unexpected, throw an exception to stop deserialization. + if (typeName == nameof(SqlDependencyProcessDispatcher)) + { + return typeof(SqlDependencyProcessDispatcher); + } + else + { + throw new ArgumentException("Unexpected type", nameof(typeName)); + } + } + } + // ---------------------------------------- + // END SqlDependencyProcessDispatcherSerializationBinder private class. + // ---------------------------------------- // ---------------- // Instance members @@ -631,6 +654,8 @@ private static void GetSerializedObject(ObjRef objRef, BinaryFormatter formatter [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.SerializationFormatter)] private static SqlDependencyProcessDispatcher GetDeserializedObject(BinaryFormatter formatter, MemoryStream stream) { + // Use a custom SerializationBinder to restrict deserialized types to SqlDependencyProcessDispatcher. + formatter.Binder = new SqlDependencyProcessDispatcherSerializationBinder(); object result = formatter.Deserialize(stream); Debug.Assert(result.GetType() == typeof(SqlDependencyProcessDispatcher), "Unexpected type stored in native!"); return (SqlDependencyProcessDispatcher)result; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs index 42783f430b..6566ee1c72 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs @@ -1266,8 +1266,8 @@ internal static SqlNotification ProcessMessage(SqlXml xmlMessage) return null; } - // Create a new XmlTextReader on the Message node value. - using (XmlTextReader xmlMessageReader = new XmlTextReader(xmlReader.Value, XmlNodeType.Element, null)) + // Create a new XmlTextReader on the Message node value. Prohibit DTD processing when dealing with untrusted sources. + using (XmlTextReader xmlMessageReader = new XmlTextReader(xmlReader.Value, XmlNodeType.Element, null) { DtdProcessing = DtdProcessing.Prohibit }) { // Proceed to the Text Node. if (!xmlMessageReader.Read()) diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs index 224c191bd9..3422180d8e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs @@ -8,7 +8,7 @@ namespace Microsoft.Data.SqlClient { /// - public class SqlEnclaveAttestationParameters + internal class SqlEnclaveAttestationParameters { private static readonly string _clientDiffieHellmanKeyName = "ClientDiffieHellmanKey"; @@ -18,14 +18,14 @@ public class SqlEnclaveAttestationParameters private readonly byte[] _input; /// - public int Protocol { get; } + internal int Protocol { get; } /// - public ECDiffieHellmanCng ClientDiffieHellmanKey { get; } + internal ECDiffieHellmanCng ClientDiffieHellmanKey { get; } /// - public byte[] GetInput() + internal byte[] GetInput() { return Clone(_input); } @@ -54,7 +54,7 @@ private byte[] Clone(byte[] arrayToClone) } /// - public SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellmanCng clientDiffieHellmanKey) + internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellmanCng clientDiffieHellmanKey) { if (null == clientDiffieHellmanKey) { throw SQL.NullArgumentInConstructorInternal(_clientDiffieHellmanKeyName, _className); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs index ac74fa99ea..032f9d006d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveSession.cs @@ -6,7 +6,7 @@ namespace Microsoft.Data.SqlClient { /// - public class SqlEnclaveSession + internal class SqlEnclaveSession { private static readonly string _sessionKeyName = "SessionKey"; @@ -15,10 +15,10 @@ public class SqlEnclaveSession private readonly byte[] _sessionKey; /// - public long SessionId { get; } + internal long SessionId { get; } /// - public byte[] GetSessionKey() + internal byte[] GetSessionKey() { return Clone(_sessionKey); } @@ -42,7 +42,7 @@ private byte[] Clone(byte[] arrayToClone) } /// - public SqlEnclaveSession(byte[] sessionKey, long sessionId/*, long counter*/) + internal SqlEnclaveSession(byte[] sessionKey, long sessionId/*, long counter*/) { if (null == sessionKey) { throw SQL.NullArgumentInConstructorInternal(_sessionKeyName, _className); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 1ba6c09ed5..335ccbd4a1 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -142,6 +142,61 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa ClientCertificateRetrievalCallback _clientCallback; SqlClientOriginalNetworkAddressInfo _originalNetworkAddressInfo; + internal bool _cleanSQLDNSCaching = false; + + private bool _serverSupportsDNSCaching = false; + + /// + /// Get or set if SQLDNSCaching FeatureExtAck is supported by the server. + /// + internal bool IsSQLDNSCachingSupported + { + get + { + return _serverSupportsDNSCaching; + } + set + { + _serverSupportsDNSCaching = value; + } + } + + private bool _SQLDNSRetryEnabled = false; + + /// + /// Get or set if we need retrying with IP received from FeatureExtAck. + /// + internal bool IsSQLDNSRetryEnabled + { + get + { + return _SQLDNSRetryEnabled; + } + set + { + _SQLDNSRetryEnabled = value; + } + } + + private bool DNSCachingBeforeRedirect = false; + + /// + /// Get or set if the control ring send redirect token and SQLDNSCaching FeatureExtAck with true + /// + internal bool IsDNSCachingBeforeRedirectSupported + { + get + { + return DNSCachingBeforeRedirect; + } + set + { + DNSCachingBeforeRedirect = value; + } + } + + internal SQLDNSInfo pendingSQLDNSObject = null; + // TCE flags internal byte _tceVersionSupported; @@ -1531,6 +1586,9 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.AzureSQLSupport; } + // The SQLDNSCaching feature is implicitly set + requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, _originalNetworkAddressInfo); } @@ -2819,8 +2877,11 @@ internal void OnFeatureExtAck(int featureId, byte[] data) { if (_routingInfo != null) { - return; + if (TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId) { + return; + } } + switch (featureId) { case TdsEnums.FEATUREEXT_SRECOVERY: @@ -3034,6 +3095,40 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } + case TdsEnums.FEATUREEXT_SQLDNSCACHING: + { + SqlClientEventSource.Log.AdvancedTraceEvent(" {0}, Received feature extension acknowledgement for SQLDNSCACHING", ObjectID); + + if (data.Length < 1) + { + SqlClientEventSource.Log.TraceEvent(" {0}, Unknown token for SQLDNSCACHING", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + if (1 == data[0]) { + IsSQLDNSCachingSupported = true; + _cleanSQLDNSCaching = false; + + if (_routingInfo != null) + { + IsDNSCachingBeforeRedirectSupported = true; + } + } + else { + // we receive the IsSupported whose value is 0 + IsSQLDNSCachingSupported = false; + _cleanSQLDNSCaching = true; + } + + // need to add more steps for phrase 2 + // get IPv4 + IPv6 + Port number + // not put them in the DNS cache at this point but need to store them somewhere + + // generate pendingSQLDNSObject and turn on IsSQLDNSRetryEnabled flag + + break; + } + default: { // Unknown feature ack @@ -3168,4 +3263,3 @@ internal void SetDerivedNames(string protocol, string serverName) } } } - 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 d85f7ff84c..e256a1e895 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 @@ -206,6 +206,7 @@ internal static class TdsEnums public const byte FEATUREEXT_AZURESQLSUPPORT = 0x08; public const byte FEATUREEXT_DATACLASSIFICATION = 0x09; public const byte FEATUREEXT_UTF8SUPPORT = 0x0A; + public const byte FEATUREEXT_SQLDNSCACHING = 0x0B; [Flags] public enum FeatureExtension : uint @@ -217,7 +218,8 @@ public enum FeatureExtension : uint GlobalTransactions = 1 << (TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS - 1), AzureSQLSupport = 1 << (TdsEnums.FEATUREEXT_AZURESQLSUPPORT - 1), DataClassification = 1 << (TdsEnums.FEATUREEXT_DATACLASSIFICATION - 1), - UTF8Support = 1 << (TdsEnums.FEATUREEXT_UTF8SUPPORT - 1), + UTF8Support = 1 << (TdsEnums.FEATUREEXT_UTF8SUPPORT - 1), + SQLDNSCaching = 1 << (TdsEnums.FEATUREEXT_SQLDNSCACHING - 1) } public const uint UTF8_IN_TDSCOLLATION = 0x4000000; 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 1298983119..3a9b37eb85 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 @@ -17,6 +17,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; +using System.Net; using Microsoft.Data.Common; using Microsoft.Data.Sql; using Microsoft.Data.SqlClient.DataClassification; @@ -283,6 +284,10 @@ internal bool IsColumnEncryptionSupported /// internal string EnclaveType { get; set; } + internal bool isTcpProtocol { get; set; } + + internal string FQDNforDNSCahce { get; set; } + /// /// Get if data classification is enabled by the server. /// @@ -499,6 +504,9 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) _connHandler = connHandler; _loginWithFailover = withFailover; + // Clean up IsSQLDNSCachingSupported flag from previous status + _connHandler.IsSQLDNSCachingSupported = false; + UInt32 sniStatus = SNILoadHandle.SingletonInstance.SNIStatus; if (sniStatus != TdsEnums.SNI_SUCCESS) { @@ -570,8 +578,16 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) int totalTimeout = _connHandler.ConnectionOptions.ConnectTimeout; + FQDNforDNSCahce = serverInfo.ResolvedServerName; + + int commaPos = FQDNforDNSCahce.IndexOf(","); + if (commaPos != -1) + { + FQDNforDNSCahce = FQDNforDNSCahce.Substring(0, commaPos); + } + _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, - out instanceName, _sniSpnBuffer, false, true, fParallel, transparentNetworkResolutionState, totalTimeout); + out instanceName, _sniSpnBuffer, false, true, fParallel, transparentNetworkResolutionState, totalTimeout, FQDNforDNSCahce); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -610,6 +626,9 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) UInt32 result = SNINativeMethodWrapper.SniGetConnectionId(_physicalStateObj.Handle, ref _connHandler._clientConnectionId); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); + + // for DNS Caching phase 1 + AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce); // UNDONE - send "" for instance now, need to fix later SqlClientEventSource.Log.TraceEvent(" Sending prelogin handshake", "SEC"); @@ -631,7 +650,7 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) // 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); + _physicalStateObj.CreatePhysicalSNIHandle(serverInfo.ExtendedServerName, ignoreSniOpenTimeout, timerExpire, out instanceName, _sniSpnBuffer, true, true, fParallel, transparentNetworkResolutionState, totalTimeout, serverInfo.ResolvedServerName); if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { @@ -645,6 +664,9 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); SqlClientEventSource.Log.TraceEvent(" Sending prelogin handshake", "SEC"); + // for DNS Caching phase 1 + AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCahce); + SendPreLoginHandshake(instanceName, encrypt, !string.IsNullOrEmpty(certificate), useOriginalAddressInfo); status = ConsumePreLoginHandshake(authType, encrypt, trustServerCert, integratedSecurity, serverCallback, clientCallback, out marsCapable, out _connHandler._fedAuthRequired); @@ -672,6 +694,60 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) return; } + // 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) + { + UInt32 result; + ushort portFromSNI = 0; + string IPStringFromSNI = string.Empty; + IPAddress IPFromSNI; + isTcpProtocol = false; + SNINativeMethodWrapper.ProviderEnum providerNumber = SNINativeMethodWrapper.ProviderEnum.INVALID_PROV; + + if (string.IsNullOrEmpty(userProtocol)) + { + + result = SNINativeMethodWrapper.SniGetProviderNumber(_physicalStateObj.Handle, ref providerNumber); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetProviderNumber"); + isTcpProtocol = (providerNumber == SNINativeMethodWrapper.ProviderEnum.TCP_PROV); + } + else if (userProtocol == TdsEnums.TCP) + { + isTcpProtocol = true; + } + + // serverInfo.UserProtocol could be empty + if (isTcpProtocol) + { + result = SNINativeMethodWrapper.SniGetConnectionPort(_physicalStateObj.Handle, ref portFromSNI); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionPort"); + + + result = SNINativeMethodWrapper.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; + } + } + internal void RemoveEncryption() { Debug.Assert((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN, "Invalid encryption option state"); @@ -3478,6 +3554,20 @@ private bool TryProcessFeatureExtAck(TdsParserStateObject stateObj) } } while (featureId != TdsEnums.FEATUREEXT_TERMINATOR); + // Write to DNS Cache or clean up DNS Cache for TCP protocol + bool ret = false; + if (_connHandler._cleanSQLDNSCaching) + { + ret = SQLFallbackDNSCache.Instance.DeleteDNSInfo(FQDNforDNSCahce); + } + + if ( _connHandler.IsSQLDNSCachingSupported && _connHandler.pendingSQLDNSObject != null + && !SQLFallbackDNSCache.Instance.IsDuplicate(_connHandler.pendingSQLDNSObject)) + { + ret = SQLFallbackDNSCache.Instance.AddDNSInfo(_connHandler.pendingSQLDNSObject); + _connHandler.pendingSQLDNSObject = null; + } + // Check if column encryption was on and feature wasn't acknowledged and we aren't going to be routed to another server. if (this.Connection.RoutingInfo == null && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled @@ -8559,6 +8649,20 @@ internal int WriteUTF8SupportFeatureRequest(bool write /* if false just calculat return len; } + internal int WriteSQLDNSCachingFeatureRequest(bool write /* if false just calculates the length */) + { + int len = 5; // 1byte = featureID, 4bytes = featureData length + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_SQLDNSCACHING); + WriteInt(0, _physicalStateObj); // we don't send any data + } + + return len; + } + internal void TdsLogin(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, @@ -8746,6 +8850,12 @@ internal int WriteUTF8SupportFeatureRequest(bool write /* if false just calculat { length += WriteUTF8SupportFeatureRequest(false); } + + if ((requestedFeatures & TdsEnums.FeatureExtension.SQLDNSCaching) != 0) + { + length += WriteSQLDNSCachingFeatureRequest(false); + } + length++; // for terminator } } @@ -9017,6 +9127,12 @@ internal int WriteUTF8SupportFeatureRequest(bool write /* if false just calculat { WriteUTF8SupportFeatureRequest(true); } + + if ((requestedFeatures & TdsEnums.FeatureExtension.SQLDNSCaching) != 0) + { + WriteSQLDNSCachingFeatureRequest(true); + } + _physicalStateObj.WriteByte(0xFF); // terminator } } // try 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 e351d36d61..30e874995c 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 @@ -149,7 +149,8 @@ internal sealed class SNIHandle : SafeHandle bool fSync, bool fParallel, TransparentNetworkResolutionState transparentNetworkResolutionState, - int totalTimeout) + int totalTimeout, + SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true) { @@ -171,19 +172,19 @@ internal sealed class SNIHandle : SafeHandle int transparentNetworkResolutionStateNo = (int)transparentNetworkResolutionState; _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle, spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, transparentNetworkResolutionStateNo, totalTimeout, - ADP.IsAzureSqlServerEndpoint(serverName)); + ADP.IsAzureSqlServerEndpoint(serverName), cachedDNSInfo); } } // constructs SNI Handle for MARS session - internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent) : base(IntPtr.Zero, true) + internal SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, SNIHandle parent, SQLDNSInfo cachedDNSInfo) : base(IntPtr.Zero, true) { RuntimeHelpers.PrepareConstrainedRegions(); try { } finally { - _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync); + _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, 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 63f0f7d68b..09305c3cf4 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 @@ -294,7 +294,11 @@ internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bo SetPacketSize(_parser._physicalStateObj._outBuff.Length); SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); - _sessionHandle = new SNIHandle(myInfo, physicalConnection); + + SQLDNSInfo cachedDNSInfo; + bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCahce, out cachedDNSInfo); + + _sessionHandle = new SNIHandle(myInfo, physicalConnection, cachedDNSInfo); if (_sessionHandle.Status != TdsEnums.SNI_SUCCESS) { AddError(parser.ProcessSNIError(this)); @@ -820,7 +824,7 @@ 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) + 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) { SNINativeMethodWrapper.ConsumerInfo myInfo = CreateConsumerInfo(async); @@ -842,7 +846,13 @@ internal void CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeo timeout = 0; } } - _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, ignoreSniOpenTimeout, checked((int)timeout), out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout); + + // serverName : serverInfo.ExtendedServerName + // may not use this serverName as key + 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); } internal bool Deactivate() diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index f7b256c6ea..73da211f7e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -82,18 +82,18 @@ internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : Enclave #endregion - #region Public methods + #region Internal methods // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - public override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) + internal override void GetEnclaveSession(string servername, string attestationUrl, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) { GetEnclaveSessionHelper(servername, attestationUrl, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); } // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. // The information SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - public override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) + internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; @@ -102,7 +102,7 @@ public override SqlEnclaveAttestationParameters GetAttestationParameters(string } // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) + internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, string attestationUrl, string servername, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) { sqlEnclaveSession = null; counter = 0; @@ -142,7 +142,7 @@ public override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellma } // When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. - public override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) + internal override void InvalidateEnclaveSession(string serverName, string enclaveAttestationUrl, SqlEnclaveSession enclaveSessionToInvalidate) { InvalidateEnclaveSessionHelper(serverName, enclaveAttestationUrl, enclaveSessionToInvalidate); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx index a25204efdf..fc0c7a2f5b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx @@ -2809,10 +2809,10 @@ Zuordnungen müssen entweder vollständig auf dem Namen oder der Ordinalzahl basieren. - Der angegebene Wert "{0}" vom Typ "{1}" aus der Datenquelle kann für Spalte "{3} [{4}]", Zeile "{5}" nicht in den Typ "{2}" konvertiert werden. + Der angegebene Wert{0} vom Typ "{1}" aus der Datenquelle kann für Spalte {3} [{4}], Zeile {5} nicht in den Typ "{2}" konvertiert werden. - Der angegebene Wert "{0}" vom Typ "{1}" aus der Datenquelle kann für Spalte "{3} [{4}]" nicht in den Typ "{2}" konvertiert werden. + Der angegebene Wert{0} vom Typ "{1}" aus der Datenquelle kann für Spalte {3} [{4}] nicht in den Typ "{2}" konvertiert werden. Die angegebene ColumnMapping stimmt mit keiner Spalte in der Quelle oder dem Ziel überein. @@ -4531,33 +4531,33 @@ Die UDT-Größe muss kleiner als {1} sein. Größe: {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + Sicherheitswarnung: Das ausgehandelte Protokoll "{0}" ist ein unsicheres Protokoll und wird nur aus Gründen der Abwärtskompatibilität unterstützt. Die empfohlene Protokollversion ist TLS 1.2 und höher. - The specified value is not valid in the '{0}' enumeration. + Der angegebene Wert ist in der Enumeration {0} ungültig. - The given column order hint is not valid. + Der angegebene Hinweis zur Spaltenreihenfolge ist ungültig. - The column '{0}' was specified more than once. + Die Spalte "{0}" wurde mehrfach angegeben. - The sorted column '{0}' is not valid in the destination table. + Die sortierte Spalte "{0}" ist in der Zieltabelle nicht gültig. - A column order hint cannot have an unspecified sort order. + Im Hinweis zur Spaltenreihenfolge muss die Sortierreihenfolge angegeben sein. - Unsupported authentication specified in this context: {0} + In diesem Kontext wurde eine nicht unterstützte Authentifizierung angegeben: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Timeout bei der interaktiven Active Directory-Authentifizierung. Der Benutzer hat zu lange nicht auf die Authentifizierungsanforderung reagiert. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + "Authentication=Active Directory Interactive" kann nicht verwendet werden, wenn die Eigenschaft "Credential" festgelegt wurde. - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + Die Eigenschaft "Credential" kann nicht festgelegt werden, wenn in der Verbindungszeichenfolge "Authentication=Active Directory Interactive" angegeben wurde. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx index 06cc7767db..8b2d79f760 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx @@ -4531,33 +4531,33 @@ El tamaño de UDT debe ser menor que {1}; tamaño: {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + Advertencia de seguridad: el elemento {0} negociado es un protocolo inseguro y solo se admite por compatibilidad con versiones anteriores. La versión recomendada del protocolo es TLS 1.2 y versiones posteriores. - The specified value is not valid in the '{0}' enumeration. + El valor especificado no es válido en la enumeración '{0}'. - The given column order hint is not valid. + La sugerencia de orden de columna especificada no es válida. - The column '{0}' was specified more than once. + Una columna "{0}" se ha especificado más de una vez. - The sorted column '{0}' is not valid in the destination table. + La columna ordenada "{0}" no se encuentra en la tabla de destino. - A column order hint cannot have an unspecified sort order. + Una sugerencia de orden de columnas no puede tener un criterio de ordenación no especificado. - Unsupported authentication specified in this context: {0} + Se ha especificado una autenticación no admitida en este contexto: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Se ha agotado el tiempo de espera de la autenticación interactiva de Active Directory. El usuario tardó demasiado en responder a la solicitud de autenticación. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + No se puede usar "Authentication=Active Directory Interactive" si se ha establecido la propiedad Credential. - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + No se puede establecer la propiedad Credential si se ha especificado "Authentication=Active Directory Interactive" en la cadena de conexión. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx index bd22c8feeb..4602d257cf 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx @@ -4531,33 +4531,33 @@ La taille UDT doit être inférieure à {1}, taille : {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + Avertissement de sécurité : Le {0} négocié est un protocole non sécurisé qui est pris en charge uniquement à des fins de compatibilité descendante. La version de protocole recommandée est TLS 1.2 et ultérieur. - The specified value is not valid in the '{0}' enumeration. + La valeur spécifiée n'est pas valide dans l'énumération '{0}'. - The given column order hint is not valid. + L'indicateur d'ordre de colonnes donné n'est pas valide. - The column '{0}' was specified more than once. + La colonne « {0} » a été spécifiée plusieurs fois. - The sorted column '{0}' is not valid in the destination table. + La colonne « {0} » triée n'est pas valide dans la table de destination. - A column order hint cannot have an unspecified sort order. + Un indicateur d'ordre de tri de colonne ne peut pas avoir un ordre de tri non spécifié. - Unsupported authentication specified in this context: {0} + Authentification non prise en charge spécifiée dans ce contexte : {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + L'authentification interactive Active Directory a expiré. L'utilisateur a mis trop de temps à répondre à la demande d'authentification. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + Impossible d'utiliser « Authentication=Active Directory Interactive », si la propriété Credential a été définie. - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + Impossible de définir la propriété Credential si « Authentication=Active Directory Interactive » a été spécifié dans la chaîne de connexion. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx index 12c60c0209..f1ee09e1a8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx @@ -4531,33 +4531,33 @@ Le dimensioni del tipo definito dall'utente (UDT) devono essere inferiori a {1}, dimensioni: {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + Avviso di sicurezza: Il protocollo negoziato {0} è un protocollo non sicuro ed è supportato solo per la compatibilità con le versioni precedenti. La versione consigliata del protocollo è TLS 1.2 e versioni successive. - The specified value is not valid in the '{0}' enumeration. + Valore specificato non valido nell'enumerazione '{0}'. - The given column order hint is not valid. + L'hint per l'ordine colonne specificato non è valido. - The column '{0}' was specified more than once. + La colonna '{0}' è stata specificata più di una volta. - The sorted column '{0}' is not valid in the destination table. + La colonna ordinata '{0}' non è valida nella tabella di destinazione. - A column order hint cannot have an unspecified sort order. + Un hint per l'ordine colonne non può avere un ordinamento non specificato. - Unsupported authentication specified in this context: {0} + Autenticazione non supportata specificata in questo contesto: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Timeout dell'autenticazione di Active Directory interattivo. L'utente ha impiegato troppo tempo per rispondere alla richiesta di autenticazione. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + Non è possibile usare 'Authentication=Active Directory Interactive' se è stata impostata la proprietà Credential. - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + Non è possibile impostare la proprietà Credential se nella stringa di connessione è stato specificato 'Authentication=Active Directory Interactive'. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx index 544bcdb26c..2f64ad57a8 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx @@ -4531,33 +4531,33 @@ UDT のサイズは {1} より小さくなければなりません。サイズ: {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + セキュリティ警告: ネゴシエートされた {0} はセキュリティで保護されていないプロトコルであり、下位互換性でのみサポートされます。推奨されるプロトコル バージョンは、TLS 1.2 以降です。 - The specified value is not valid in the '{0}' enumeration. + 指定された値は '{0}' 列挙で有効ではありません。 - The given column order hint is not valid. + 指定された列の順序のヒントが無効です。 - The column '{0}' was specified more than once. + 列 '{0}' が 2 回以上指定されました。 - The sorted column '{0}' is not valid in the destination table. + 並べ替えられた列 '{0}' は対象のテーブルで無効です。 - A column order hint cannot have an unspecified sort order. + 列の順序のヒントに未指定の並べ替え順序を含めることができません。 - Unsupported authentication specified in this context: {0} + このコンテキストでサポートされていない認証が指定されています: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Active Directory 対話型認証がタイムアウトになりました。ユーザーが認証要求に応答するのに時間がかかりすぎました。 - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + Credential プロパティが設定されている場合は、'Authentication=Active Directory Interactive' を使用できません。 - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + 接続文字列で 'Authentication=Active Directory Interactive' が指定されている場合は、Credential プロパティを設定できません。 \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx index 5da87b350d..7f99ec373a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx @@ -4531,33 +4531,33 @@ UDT 크기는 {1}보다 작아야 합니다. 크기: {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + 보안 경고: 협상된 {0}은(는) 안전하지 않은 프로토콜이며 이전 버전과의 호환성을 위해서만 지원됩니다. 권장 프로토콜 버전은 TLS 1.2 이상입니다. - The specified value is not valid in the '{0}' enumeration. + 지정한 값은 '{0}' 열거형에 사용할 수 없습니다. - The given column order hint is not valid. + 지정된 열 순서 힌트가 잘못되었습니다. - The column '{0}' was specified more than once. + '{0}' 열이 두 번 이상 지정되었습니다. - The sorted column '{0}' is not valid in the destination table. + 정렬된 열 '{0}'은(는) 대상 테이블에서 유효하지 않습니다. - A column order hint cannot have an unspecified sort order. + 열 순서 힌트에는 지정되지 않은 정렬 순서를 사용할 수 없습니다. - Unsupported authentication specified in this context: {0} + 이 컨텍스트에 지원되지 않는 인증이 지정됨: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Active Directory Interactive 인증 시간이 초과되었습니다. 사용자가 인증 요청에 응답하는 데 시간이 너무 오래 걸렸습니다. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + Credential 속성이 설정된 경우 'Authentication=Active Directory Interactive'를 사용할 수 없습니다. - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + 'Authentication=Active Directory Interactive'가 연결 문자열에 지정된 경우 Credential 속성을 설정할 수 없습니다. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx index beeb608a90..e74b3b9f40 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx @@ -4531,33 +4531,33 @@ O tamanho do UDT precisa ser menor que {1}. Tamanho: {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + Aviso de Segurança: o {0} negociado é um protocolo não seguro com suporte somente para compatibilidade com versões anteriores. A versão recomendada do protocolo é TLS 1.2 e posterior. - The specified value is not valid in the '{0}' enumeration. + O valor especificado não é válido na enumeração '{0}'. - The given column order hint is not valid. + A dica de ordem de coluna fornecida não é válida. - The column '{0}' was specified more than once. + Uma coluna '{0}' foi especificada mais de uma vez. - The sorted column '{0}' is not valid in the destination table. + A coluna classificada '{0}' não é válida na tabela de destino. - A column order hint cannot have an unspecified sort order. + Uma dica de ordem de coluna não pode ter uma ordem de classificação não especificada. - Unsupported authentication specified in this context: {0} + Autenticação sem suporte especificada neste contexto: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + A autenticação Interativa do Active Directory atingiu o tempo limite. O usuário demorou muito tempo para responder à solicitação de autenticação. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + Não é possível usar 'Authentication=Active Directory Interactive' quando a propriedade Credential está configurada. - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + Não é possível definir a propriedade Credential quando 'Authentication=Active Directory Interactive' é especificado na cadeia de conexão. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx index 335adb907c..f9c5b26848 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx @@ -4531,33 +4531,33 @@ Размер пользовательского типа должен быть меньше {1}, текущий размер: {0}. - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + Предупреждение системы безопасности: согласованный {0} является незащищенным протоколом и поддерживается только в целях обратной совместимости. Рекомендуемая версия протокола — TLS 1.2 или более поздняя. - The specified value is not valid in the '{0}' enumeration. + Указанное значение недействительно в перечислении '{0}'. - The given column order hint is not valid. + Задано недопустимое указание порядка столбцов. - The column '{0}' was specified more than once. + Столбец "{0}" был указан несколько раз. - The sorted column '{0}' is not valid in the destination table. + Отсортированный столбец "{0}" недопустим в конечной таблице. - A column order hint cannot have an unspecified sort order. + Указание порядка столбцов не может иметь неопределенный порядок сортировки. - Unsupported authentication specified in this context: {0} + Указан неподдерживаемый в этом контексте режим проверки подлинности: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Истекло время ожидания интерактивной проверки подлинности Active Directory. Пользователь слишком долго не отвечал на запрос проверки подлинности. - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + Невозможно использовать Authentication=Active Directory Interactive, если задано свойство Credential. - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + Не удается задать свойство Credential, если в строке подключения указано Authentication=Active Directory Interactive. \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx index 7fe1f2eaf0..94379c7791 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx @@ -4531,33 +4531,33 @@ UDT 大小必须小于 {1},大小为 {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + 安全警告:协商的 {0} 是非安全协议,只有在为了实现向后兼容性才受支持。建议的协议版本为 TLS 1.2 及更高版本。 - The specified value is not valid in the '{0}' enumeration. + 指定的值在“{0}”枚举中无效。 - The given column order hint is not valid. + 给定的列排序顺序提示无效。 - The column '{0}' was specified more than once. + 列“{0}”被指定了多次。 - The sorted column '{0}' is not valid in the destination table. + 已排序的列“{0}”在目标表中无效。 - A column order hint cannot have an unspecified sort order. + 列排序顺序提示不得包含未指定的排序顺序。 - Unsupported authentication specified in this context: {0} + 在此上下文中指定的身份验证不受支持: {0} - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Active Directory 交互式身份验证超时。用户响应身份验证请求的时间过长。 - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + 如果设置了 Credential 属性,则无法使用 "Authentication=Active Directory Interactive"。 - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + 如果在连接字符串中指定了 "Authentication=Active Directory Interactive",则无法设置 Credential 属性。 \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx index 67e644c8a4..20a91a411b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx @@ -4531,33 +4531,33 @@ UDT 大小必須小於 {1},大小: {0} - Security Warning: The negotiated {0} is an insecure protocol and is supported for backward compatibility only. The recommended protocol version is TLS 1.2 and later. + 安全性警告: 交涉的 {0} 是不安全的通訊協定,僅支援回溯相容性。建議的通訊協定版本為 TLS 1.2 及更新版本。 - The specified value is not valid in the '{0}' enumeration. + 指定值在 '{0}' 列舉中是無效的。 - The given column order hint is not valid. + 指定的資料行順序提示無效。 - The column '{0}' was specified more than once. + 多次指定資料行 '{0}'。 - The sorted column '{0}' is not valid in the destination table. + 目的地資料表中的排序資料行 '{0}' 無效。 - A column order hint cannot have an unspecified sort order. + 資料行順序提示不能有未指定的排序次序。 - Unsupported authentication specified in this context: {0} + 在此內容 {0} 中指定了不支援的驗證 - Active Directory Interactive authentication timed out. The user took too long to respond to the authentication request. + Active Directory 互動式驗證逾時。使用者回應驗證要求所花的時間過長。 - Cannot use 'Authentication=Active Directory Interactive', if the Credential property has been set. + 如果已設定 Credential 屬性,就無法使用 'Authentication=Active Directory Interactive'。 - Cannot set the Credential property if 'Authentication=Active Directory Interactive' has been specified in the connection string. + 如果連接字串中已指定 'Authentication=Active Directory Interactive',則無法設定 Credential 屬性。 \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs new file mode 100644 index 0000000000..e18b61cee4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SQLFallbackDNSCache.cs @@ -0,0 +1,86 @@ +// 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.Collections.Concurrent; + +namespace Microsoft.Data.SqlClient +{ + internal 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. + private ConcurrentDictionary DNSInfoCache; + + // singleton instance + public static SQLFallbackDNSCache Instance { get { return _SQLFallbackDNSCache; } } + + private SQLFallbackDNSCache() + { + int level = 4 * Environment.ProcessorCount; + DNSInfoCache = new ConcurrentDictionary(concurrencyLevel: level, + capacity: initialCapacity, + comparer: StringComparer.OrdinalIgnoreCase); + } + + internal bool AddDNSInfo(SQLDNSInfo item) + { + if (null != item) + { + if (DNSInfoCache.ContainsKey(item.FQDN)) + { + + DeleteDNSInfo(item.FQDN); + } + + return DNSInfoCache.TryAdd(item.FQDN, item); + } + + return false; + } + + internal bool DeleteDNSInfo(string FQDN) + { + SQLDNSInfo value; + return DNSInfoCache.TryRemove(FQDN, out value); + } + + internal bool GetDNSInfo(string FQDN, out SQLDNSInfo result) + { + return DNSInfoCache.TryGetValue(FQDN, out result); + } + + internal bool IsDuplicate(SQLDNSInfo newItem) + { + if (null != newItem) + { + SQLDNSInfo oldItem; + if (GetDNSInfo(newItem.FQDN, out oldItem)) + { + return (newItem.AddrIPv4 == oldItem.AddrIPv4 && + newItem.AddrIPv6 == oldItem.AddrIPv6 && + newItem.Port == oldItem.Port); + } + } + + return false; + } + } + + internal class SQLDNSInfo + { + public string FQDN { get; set; } + public string AddrIPv4 { get; set; } + public string AddrIPv6 { get; set; } + public string Port { get; set; } + + internal SQLDNSInfo(string FQDN, string ipv4, string ipv6, string port) + { + this.FQDN = FQDN; + AddrIPv4 = ipv4; + AddrIPv6 = ipv6; + Port = port; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index 244e008f96..f2870aae1e 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -4,12 +4,19 @@ using System; using System.Data; +using System.Collections.Generic; using Xunit; namespace Microsoft.Data.SqlClient.Tests { public partial class SqlConnectionTest { + private static readonly string[] s_retrieveInternalInfoKeys = + { + "SQLDNSCachingSupportedState", + "SQLDNSCachingSupportedStateBeforeRedirect" + }; + [Fact] public void Constructor1() { @@ -1212,5 +1219,47 @@ public void ServerVersion_Connection_Closed() Assert.NotNull(ex.Message); } } + + [Fact] + public void RetrieveInternalInfo_Success() + { + SqlConnection cn = new SqlConnection(); + IDictionary d = cn.RetrieveInternalInfo(); + + Assert.NotNull(d); + } + + [Fact] + public void RetrieveInternalInfo_ExpectedKeysInDictionary_Success() + { + SqlConnection cn = new SqlConnection(); + IDictionary d = cn.RetrieveInternalInfo(); + + Assert.NotEmpty(d); + Assert.Equal(s_retrieveInternalInfoKeys.Length, d.Count); + + Assert.NotEmpty(d.Keys); + Assert.Equal(s_retrieveInternalInfoKeys.Length, d.Keys.Count); + + Assert.NotEmpty(d.Values); + Assert.Equal(s_retrieveInternalInfoKeys.Length, d.Values.Count); + + foreach(string key in s_retrieveInternalInfoKeys) + { + Assert.True(d.ContainsKey(key)); + + d.TryGetValue(key, out object value); + Assert.NotNull(value); + Assert.IsType(value); + } + } + + [Fact] + public void RetrieveInternalInfo_UnexpectedKeysInDictionary_Success() + { + SqlConnection cn = new SqlConnection(); + IDictionary d = cn.RetrieveInternalInfo(); + Assert.False(d.ContainsKey("Foo")); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 6d0af535e4..1e8fa398c6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -43,6 +43,12 @@ public static class DataTestUtility public static readonly bool SupportsFileStream = false; public static readonly bool UseManagedSNIOnWindows = false; + public static readonly string DNSCachingConnString = null; + public static readonly string DNSCachingServerCR = null; // this is for the control ring + public static readonly string DNSCachingServerTR = null; // this is for the tenant ring + public static readonly bool IsDNSCachingSupportedCR = false; // this is for the control ring + public static readonly bool IsDNSCachingSupportedTR = false; // this is for the tenant ring + public const string UdtTestDbName = "UdtTestDb"; public const string AKVKeyName = "TestSqlClientAzureKeyVaultProvider"; private const string ManagedNetworkingAppContextSwitch = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; @@ -75,6 +81,11 @@ private class Config public bool SupportsLocalDb = false; public bool SupportsFileStream = false; public bool UseManagedSNIOnWindows = false; + public string DNSCachingConnString = null; + public string DNSCachingServerCR = null; // this is for the control ring + public string DNSCachingServerTR = null; // this is for the tenant ring + public bool IsDNSCachingSupportedCR = false; // this is for the control ring + public bool IsDNSCachingSupportedTR = false; // this is for the tenant ring } static DataTestUtility() @@ -100,6 +111,12 @@ static DataTestUtility() TracingEnabled = c.TracingEnabled; UseManagedSNIOnWindows = c.UseManagedSNIOnWindows; + DNSCachingConnString = c.DNSCachingConnString; + DNSCachingServerCR = c.DNSCachingServerCR; + DNSCachingServerTR = c.DNSCachingServerTR; + IsDNSCachingSupportedCR = c.IsDNSCachingSupportedCR; + IsDNSCachingSupportedTR = c.IsDNSCachingSupportedTR; + if (TracingEnabled) { TraceListener = new DataTestUtility.TraceEventListener(); @@ -264,6 +281,9 @@ public static bool IsSupportedDataClassification() } return true; } + + public static bool IsDNSCachingSetup() => !string.IsNullOrEmpty(DNSCachingConnString); + public static bool IsUdtTestDatabasePresent() => IsDatabasePresent(UdtTestDbName); public static bool AreConnStringsSetup() 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 d0152fca05..34bd1e6ead 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 @@ -195,6 +195,7 @@ + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs new file mode 100644 index 0000000000..33460acb8d --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DNSCachingTest/DNSCachingTest.cs @@ -0,0 +1,79 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + + public class DNSCachingTest + { + public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection)); + public static Type SQLFallbackDNSCacheType = systemData.GetType("Microsoft.Data.SqlClient.SQLFallbackDNSCache"); + public static Type SQLDNSInfoType = systemData.GetType("Microsoft.Data.SqlClient.SQLDNSInfo"); + public static MethodInfo SQLFallbackDNSCacheGetDNSInfo = SQLFallbackDNSCacheType.GetMethod("GetDNSInfo", BindingFlags.Instance | BindingFlags.NonPublic); + + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsDNSCachingSetup))] + public void DNSCachingIsSupportedFlag() + { + string expectedDNSCachingSupportedCR = DataTestUtility.IsDNSCachingSupportedCR ? "true" : "false"; + string expectedDNSCachingSupportedTR = DataTestUtility.IsDNSCachingSupportedTR ? "true" : "false"; + + using(SqlConnection connection = new SqlConnection(DataTestUtility.DNSCachingConnString)) + { + connection.Open(); + + IDictionary dictionary = connection.RetrieveInternalInfo(); + bool ret = dictionary.TryGetValue("SQLDNSCachingSupportedState", out object val); + ret = dictionary.TryGetValue("SQLDNSCachingSupportedStateBeforeRedirect", out object valBeforeRedirect); + string isSupportedStateTR = (string)val; + string isSupportedStateCR = (string)valBeforeRedirect; + Assert.Equal(expectedDNSCachingSupportedCR, isSupportedStateCR); + Assert.Equal(expectedDNSCachingSupportedTR, isSupportedStateTR); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsDNSCachingSetup))] + public void DNSCachingGetDNSInfo() + { + using(SqlConnection connection = new SqlConnection(DataTestUtility.DNSCachingConnString)) + { + connection.Open(); + } + + var SQLFallbackDNSCacheInstance = SQLFallbackDNSCacheType.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public).GetValue(null); + + var serverList = new List>(); + serverList.Add(new KeyValuePair(DataTestUtility.DNSCachingServerCR, DataTestUtility.IsDNSCachingSupportedCR)); + serverList.Add(new KeyValuePair(DataTestUtility.DNSCachingServerTR, DataTestUtility.IsDNSCachingSupportedTR)); + + foreach(var server in serverList) + { + object[] parameters; + bool ret; + + if (!string.IsNullOrEmpty(server.Key)) + { + parameters = new object[] { server.Key, null }; + ret = (bool)SQLFallbackDNSCacheGetDNSInfo.Invoke(SQLFallbackDNSCacheInstance, parameters); + + if (server.Value) + { + Assert.NotNull(parameters[1]); + Assert.Equal(server.Key, (string)SQLDNSInfoType.GetProperty("FQDN").GetValue(parameters[1])); + } + else + { + Assert.Null(parameters[1]); + } + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs index 0872e457d8..1eab7e65c3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -37,6 +37,97 @@ public static void RunAllTestsForSingleServer_TCP() RunAllTestsForSingleServer(DataTestUtility.TCPConnectionString); } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task AsyncMultiPacketStreamRead() + { + int packetSize = 514; // force small packet size so we can quickly check multi packet reads + + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); + builder.PacketSize = 514; + string connectionString = builder.ToString(); + + byte[] inputData = null; + byte[] outputData = null; + string tableName = DataTestUtility.GetUniqueNameForSqlServer("data"); + + using (SqlConnection connection = new SqlConnection(connectionString)) + { + await connection.OpenAsync(); + + try + { + inputData = CreateBinaryTable(connection, tableName, packetSize); + + using (SqlCommand command = new SqlCommand($"SELECT foo FROM {tableName}", connection)) + using (SqlDataReader reader = await command.ExecuteReaderAsync(System.Data.CommandBehavior.SequentialAccess)) + { + await reader.ReadAsync(); + + using (Stream stream = reader.GetStream(0)) + using (CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60))) + using (MemoryStream memory = new MemoryStream(16 * 1024)) + { + await stream.CopyToAsync(memory, 37, cancellationTokenSource.Token); // prime number sized buffer to cause many cross packet partial reads + outputData = memory.ToArray(); + } + } + } + finally + { + DataTestUtility.DropTable(connection, tableName); + } + } + + Assert.NotNull(outputData); + int sharedLength = Math.Min(inputData.Length, outputData.Length); + if (sharedLength < outputData.Length) + { + Assert.False(true, $"output is longer than input, input={inputData.Length} bytes, output={outputData.Length} bytes"); + } + if (sharedLength < inputData.Length) + { + Assert.False(true, $"input is longer than output, input={inputData.Length} bytes, output={outputData.Length} bytes"); + } + for (int index = 0; index < sharedLength; index++) + { + if (inputData[index] != outputData[index]) // avoid formatting the output string unless there is a difference + { + Assert.True(false, $"input and output differ at index {index}, input={inputData[index]}, output={outputData[index]}"); + } + } + + } + + private static byte[] CreateBinaryTable(SqlConnection connection, string tableName, int packetSize) + { + byte[] pattern = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }; + byte[] data = new byte[packetSize * 10]; + int position = 0; + while (position < data.Length) + { + int copyCount = Math.Min(pattern.Length, data.Length - position); + Array.Copy(pattern, 0, data, position, copyCount); + position += copyCount; + } + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = $@" +IF OBJECT_ID('dbo.{tableName}', 'U') IS NOT NULL +DROP TABLE {tableName}; +CREATE TABLE {tableName} (id INT, foo VARBINARY(MAX)) +"; + cmd.ExecuteNonQuery(); + + cmd.CommandText = $"INSERT INTO {tableName} (id, foo) VALUES (@id, @foo)"; + cmd.Parameters.AddWithValue("id", 1); + cmd.Parameters.AddWithValue("foo", data); + cmd.ExecuteNonQuery(); + } + + return data; + } + private static void RunAllTestsForSingleServer(string connectionString, bool usingNamePipes = false) { RowBuffer(connectionString); @@ -1811,7 +1902,7 @@ private static void TestXEventsStreaming(string connectionString) SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess); for (int i = 0; i < streamXeventCount && reader.Read(); i++) { - Int32 colType = reader.GetInt32(0); + int colType = reader.GetInt32(0); int cb = (int)reader.GetBytes(1, 0, null, 0, 0); byte[] bytes = new byte[cb]; diff --git a/tools/props/Versions.props b/tools/props/Versions.props index e10ec44a73..16c550dfd6 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -2,14 +2,17 @@ 1.0.0.0 - $(AssemblyFileVersion) + + + + 2.0.20168.4 1.0.0-beta.18578.2 - 2.0.0-dev + 2.1.0-dev $(NugetPackageVersion) - 2.0.0-preview1.20141.10 + 2.0.0 4.3.1 4.3.0 @@ -24,7 +27,7 @@ 4.7.0 - 2.0.0-preview1.20141.10 + 2.0.0 4.7.0 4.7.0 4.7.0 diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index f6be6bab6b..3e09d83333 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -27,13 +27,13 @@ When using NuGet 3.x this package requires at least version 3.4. sqlclient microsoft.data.sqlclient - + - + @@ -45,7 +45,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -57,7 +57,7 @@ When using NuGet 3.x this package requires at least version 3.4. - +