-
Notifications
You must be signed in to change notification settings - Fork 322
Feature | Ignore enhanced routing tokens when feature disabled by server #3971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1308,7 +1308,7 @@ internal void OnError(SqlException exception, bool breakConnection, Action<Actio | |
| // @TODO: This class should not do low-level parsing of data from the server. | ||
| internal void OnFeatureExtAck(int featureId, byte[] data) | ||
| { | ||
| if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) | ||
| if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING && featureId != TdsEnums.FEATUREEXT_ENHANCEDROUTINGSUPPORT) | ||
| { | ||
| return; | ||
| } | ||
|
|
@@ -3288,6 +3288,17 @@ private void LoginNoFailover( | |
|
|
||
| if (RoutingInfo != null) | ||
| { | ||
| // Check if we received enhanced routing info, but not the ack for the feature. | ||
| // In this case, we should ignore the routing info and connect to the current server. | ||
| if (!string.IsNullOrEmpty(RoutingInfo.DatabaseName) && !IsEnhancedRoutingSupportEnabled) | ||
| { | ||
| SqlClientEventSource.Log.TryTraceEvent( | ||
| $"SqlInternalConnectionTds.LoginNoFailover | " + | ||
| $"Ignoring enhanced routing info because the server did not acknowledge the feature."); | ||
| RoutingInfo = null; | ||
| break; | ||
| } | ||
|
|
||
| SqlClientEventSource.Log.TryTraceEvent( | ||
| $"SqlInternalConnectionTds.LoginNoFailover | " + | ||
| $"Routed to {serverInfo.ExtendedServerName}"); | ||
|
|
@@ -3589,6 +3600,17 @@ private void LoginWithFailover( | |
| int routingAttempts = 0; | ||
| while (RoutingInfo != null) | ||
| { | ||
| // Check if we received enhanced routing info, but not the ack for the feature. | ||
| // In this case, we should ignore the routing info and connect to the current server. | ||
| if (!string.IsNullOrEmpty(RoutingInfo.DatabaseName) && !IsEnhancedRoutingSupportEnabled) | ||
| { | ||
| SqlClientEventSource.Log.TryTraceEvent( | ||
| $"SqlInternalConnectionTds.LoginWithFailover | " + | ||
| $"Ignoring enhanced routing info because the server did not acknowledge the feature."); | ||
| RoutingInfo = null; | ||
| continue; | ||
| } | ||
|
Comment on lines
+3603
to
+3612
|
||
|
|
||
| if (routingAttempts > MaxNumberOfRedirectRoute) | ||
| { | ||
| throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,185 +17,111 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests; | |
| [Collection("SimulatedServerTests")] | ||
| public class ConnectionEnhancedRoutingTests | ||
| { | ||
| [Fact] | ||
| public void RoutedConnection() | ||
| /// <summary> | ||
| /// Tests that a connection is routed to the target server when enhanced routing is enabled. | ||
| /// Uses Theory to test both sync and async code paths. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(false)] | ||
| [InlineData(true)] | ||
| public async Task RoutedConnection(bool useAsync) | ||
| { | ||
| // Arrange | ||
| using TdsServer server = new(new()); | ||
| server.Start(); | ||
| using TestRoutingServers servers = new(FeatureExtensionBehavior.Enabled); | ||
|
|
||
| string routingDatabaseName = Guid.NewGuid().ToString(); | ||
| bool clientProvidedCorrectDatabase = false; | ||
| server.OnLogin7Validated = loginToken => | ||
| servers.TargetServer.OnLogin7Validated = loginToken => | ||
| { | ||
| clientProvidedCorrectDatabase = routingDatabaseName == loginToken.Database; | ||
| clientProvidedCorrectDatabase = servers.RoutingDatabaseName == loginToken.Database; | ||
| }; | ||
|
|
||
| RoutingTdsServer router = new( | ||
| new RoutingTdsServerArguments() | ||
| { | ||
| RoutingTCPHost = "localhost", | ||
| RoutingTCPPort = (ushort)server.EndPoint.Port, | ||
| RoutingDatabaseName = routingDatabaseName, | ||
| RequireReadOnly = false | ||
| }); | ||
| router.Start(); | ||
| router.EnhancedRoutingBehavior = FeatureExtensionBehavior.Enabled; | ||
|
|
||
| string connectionString = (new SqlConnectionStringBuilder() | ||
| { | ||
| DataSource = $"localhost,{router.EndPoint.Port}", | ||
| Encrypt = false, | ||
| ConnectTimeout = 10000 | ||
| }).ConnectionString; | ||
|
|
||
| // Act | ||
| using SqlConnection connection = new(connectionString); | ||
| connection.Open(); | ||
|
|
||
| // Assert | ||
| Assert.Equal(ConnectionState.Open, connection.State); | ||
| Assert.Equal($"localhost,{server.EndPoint.Port}", ((SqlConnectionInternal)connection.InnerConnection).RoutingDestination); | ||
| Assert.Equal(routingDatabaseName, connection.Database); | ||
| Assert.True(clientProvidedCorrectDatabase); | ||
|
|
||
| Assert.Equal(1, router.PreLoginCount); | ||
| Assert.Equal(1, server.PreLoginCount); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task RoutedAsyncConnection() | ||
| { | ||
| // Arrange | ||
| using TdsServer server = new(new()); | ||
| server.Start(); | ||
|
|
||
| string routingDatabaseName = Guid.NewGuid().ToString(); | ||
| bool clientProvidedCorrectDatabase = false; | ||
| server.OnLogin7Validated = loginToken => | ||
| using SqlConnection connection = new(servers.ConnectionString); | ||
| if (useAsync) | ||
| { | ||
| clientProvidedCorrectDatabase = routingDatabaseName == loginToken.Database; | ||
| }; | ||
|
|
||
| RoutingTdsServer router = new( | ||
| new RoutingTdsServerArguments() | ||
| { | ||
| RoutingTCPHost = "localhost", | ||
| RoutingTCPPort = (ushort)server.EndPoint.Port, | ||
| RoutingDatabaseName = routingDatabaseName, | ||
| RequireReadOnly = false | ||
| }); | ||
| router.Start(); | ||
| router.EnhancedRoutingBehavior = FeatureExtensionBehavior.Enabled; | ||
|
|
||
| string connectionString = (new SqlConnectionStringBuilder() | ||
| await connection.OpenAsync(); | ||
| } | ||
| else | ||
| { | ||
| DataSource = $"localhost,{router.EndPoint.Port}", | ||
| Encrypt = false, | ||
| ConnectTimeout = 10000 | ||
| }).ConnectionString; | ||
|
|
||
| // Act | ||
| using SqlConnection connection = new(connectionString); | ||
| await connection.OpenAsync(); | ||
| connection.Open(); | ||
| } | ||
|
|
||
| // Assert | ||
| Assert.Equal(ConnectionState.Open, connection.State); | ||
| Assert.Equal($"localhost,{server.EndPoint.Port}", ((SqlConnectionInternal)connection.InnerConnection).RoutingDestination); | ||
| Assert.Equal(routingDatabaseName, connection.Database); | ||
| Assert.Equal($"localhost,{servers.TargetServer.EndPoint.Port}", ((SqlConnectionInternal)connection.InnerConnection).RoutingDestination); | ||
| Assert.Equal(servers.RoutingDatabaseName, connection.Database); | ||
| Assert.True(clientProvidedCorrectDatabase); | ||
|
|
||
| Assert.Equal(1, router.PreLoginCount); | ||
| Assert.Equal(1, server.PreLoginCount); | ||
| Assert.Equal(1, servers.Router.PreLoginCount); | ||
| Assert.Equal(1, servers.TargetServer.PreLoginCount); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ServerIgnoresEnhancedRoutingRequest() | ||
| /// <summary> | ||
| /// Tests that a connection is NOT routed when the server does not acknowledge the enhanced routing feature | ||
| /// or has it disabled. Covers both DoNotAcknowledge and Disabled behaviors. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(FeatureExtensionBehavior.DoNotAcknowledge)] | ||
| [InlineData(FeatureExtensionBehavior.Disabled)] | ||
| public void ServerDoesNotRoute(FeatureExtensionBehavior behavior) | ||
| { | ||
| // Arrange | ||
| using TdsServer server = new(new()); | ||
| server.Start(); | ||
|
|
||
| string routingDatabaseName = Guid.NewGuid().ToString(); | ||
| bool clientProvidedCorrectDatabase = false; | ||
| server.OnLogin7Validated = loginToken => | ||
| { | ||
| clientProvidedCorrectDatabase = null == loginToken.Database; | ||
| }; | ||
|
|
||
| RoutingTdsServer router = new( | ||
| new RoutingTdsServerArguments() | ||
| { | ||
| RoutingTCPHost = "localhost", | ||
| RoutingTCPPort = (ushort)server.EndPoint.Port, | ||
| RequireReadOnly = false | ||
| }); | ||
| router.Start(); | ||
| router.EnhancedRoutingBehavior = FeatureExtensionBehavior.DoNotAcknowledge; | ||
|
|
||
| string connectionString = (new SqlConnectionStringBuilder() | ||
| { | ||
| DataSource = $"localhost,{router.EndPoint.Port}", | ||
| Encrypt = false, | ||
| ConnectTimeout = 10000 | ||
| }).ConnectionString; | ||
| using TestRoutingServers servers = new(behavior); | ||
|
|
||
| // Act | ||
| using SqlConnection connection = new(connectionString); | ||
| using SqlConnection connection = new(servers.ConnectionString); | ||
| connection.Open(); | ||
|
|
||
|
Comment on lines
+63
to
74
|
||
| // Assert | ||
| Assert.Equal(ConnectionState.Open, connection.State); | ||
| Assert.Equal($"localhost,{server.EndPoint.Port}", ((SqlConnectionInternal)connection.InnerConnection).RoutingDestination); | ||
| Assert.Null(((SqlConnectionInternal)connection.InnerConnection).RoutingDestination); | ||
| Assert.Equal("master", connection.Database); | ||
| Assert.True(clientProvidedCorrectDatabase); | ||
|
|
||
| Assert.Equal(1, router.PreLoginCount); | ||
| Assert.Equal(1, server.PreLoginCount); | ||
| Assert.Equal(1, servers.Router.PreLoginCount); | ||
| Assert.Equal(0, servers.TargetServer.PreLoginCount); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ServerRejectsEnhancedRoutingRequest() | ||
| /// <summary> | ||
| /// Helper class that encapsulates the setup of a routing TDS server and target TDS server | ||
| /// for enhanced routing tests. | ||
| /// </summary> | ||
| private sealed class TestRoutingServers : IDisposable | ||
| { | ||
| // Arrange | ||
| using TdsServer server = new(new()); | ||
| server.Start(); | ||
| public TdsServer TargetServer { get; } | ||
| public RoutingTdsServer Router { get; } | ||
| public string RoutingDatabaseName { get; } | ||
| public string ConnectionString { get; } | ||
|
|
||
| string routingDatabaseName = Guid.NewGuid().ToString(); | ||
| bool clientProvidedCorrectDatabase = false; | ||
| server.OnLogin7Validated = loginToken => | ||
| public TestRoutingServers(FeatureExtensionBehavior enhancedRoutingBehavior) | ||
| { | ||
| clientProvidedCorrectDatabase = null == loginToken.Database; | ||
| }; | ||
|
|
||
| RoutingTdsServer router = new( | ||
| new RoutingTdsServerArguments() | ||
| RoutingDatabaseName = Guid.NewGuid().ToString(); | ||
|
|
||
| TargetServer = new TdsServer(new()); | ||
| TargetServer.Start(); | ||
|
|
||
| Router = new RoutingTdsServer( | ||
| new RoutingTdsServerArguments() | ||
| { | ||
| RoutingTCPHost = "localhost", | ||
| RoutingTCPPort = (ushort)TargetServer.EndPoint.Port, | ||
| RoutingDatabaseName = RoutingDatabaseName, | ||
| RequireReadOnly = false | ||
| }); | ||
| Router.Start(); | ||
| Router.EnhancedRoutingBehavior = enhancedRoutingBehavior; | ||
|
|
||
| ConnectionString = new SqlConnectionStringBuilder() | ||
| { | ||
| RoutingTCPHost = "localhost", | ||
| RoutingTCPPort = (ushort)server.EndPoint.Port, | ||
| RequireReadOnly = false | ||
| }); | ||
| router.Start(); | ||
| router.EnhancedRoutingBehavior = FeatureExtensionBehavior.Disabled; | ||
|
|
||
| string connectionString = (new SqlConnectionStringBuilder() | ||
| { | ||
| DataSource = $"localhost,{router.EndPoint.Port}", | ||
| Encrypt = false, | ||
| ConnectTimeout = 10000 | ||
| }).ConnectionString; | ||
|
|
||
| // Act | ||
| using SqlConnection connection = new(connectionString); | ||
| connection.Open(); | ||
|
|
||
| // Assert | ||
| Assert.Equal(ConnectionState.Open, connection.State); | ||
| Assert.Equal($"localhost,{server.EndPoint.Port}", ((SqlConnectionInternal)connection.InnerConnection).RoutingDestination); | ||
| Assert.Equal("master", connection.Database); | ||
| Assert.True(clientProvidedCorrectDatabase); | ||
| DataSource = $"localhost,{Router.EndPoint.Port}", | ||
| Encrypt = false, | ||
| ConnectTimeout = 10000 | ||
| }.ConnectionString; | ||
| } | ||
|
|
||
| Assert.Equal(1, router.PreLoginCount); | ||
| Assert.Equal(1, server.PreLoginCount); | ||
| public void Dispose() | ||
| { | ||
| Router?.Dispose(); | ||
| TargetServer?.Dispose(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsEnhancedRoutingSupportEnabledis never reset between login attempts. In a multi-hop redirect scenario, the flag could remaintruefrom a previous server that acknowledged enhanced routing, causing the client to incorrectly honor enhanced routing info from a subsequent server that did not acknowledge (or disabled) the feature. Consider resettingIsEnhancedRoutingSupportEnabled(and any related per-server feature-negotiation state) at the start of eachAttemptOneLogin/ login attempt so the decision to honor enhanced routing tokens is based only on the current server’s ACK.