Skip to content

Commit

Permalink
Fix | Increase routing attempt to 10 in netcore for LoginNoFailover a…
Browse files Browse the repository at this point in the history
…nd adding routing support to LoginWithFailover (#2873)
  • Loading branch information
JRahnama authored Oct 5, 2024
1 parent cab5422 commit d3772fc
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
// CONNECTION AND STATE VARIABLES
private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
private TdsParser _parser;

// Connection re-route limit
internal const int _maxNumberOfRedirectRoute = 10;

private SqlLoginAck _loginAck;
private SqlCredential _credential;
private FederatedAuthenticationFeatureExtensionData _fedAuthFeatureExtensionData;
Expand All @@ -130,7 +134,7 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
// The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken.
SqlFedAuthToken _fedAuthToken = null;
internal byte[] _accessTokenInBytes;
internal readonly Func<SqlAuthenticationParameters, CancellationToken,Task<SqlAuthenticationToken>> _accessTokenCallback;
internal readonly Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticationToken>> _accessTokenCallback;

private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper;

Expand Down Expand Up @@ -1363,6 +1367,12 @@ 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 AzureSQLSupport feature is implicitly set for ReadOnly login
if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly)
{
requestedFeatures |= TdsEnums.FeatureExtension.AzureSQLSupport;
}

// The SQLDNSCaching feature is implicitly set
requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching;
requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport;
Expand Down Expand Up @@ -1440,6 +1450,23 @@ private void OpenLoginEnlist(TimeoutTimer timeout,
credential,
timeout);
}

if (!IsAzureSQLConnection)
{
// If not a connection to Azure SQL, Readonly with FailoverPartner is not supported
if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly)
{
if (!string.IsNullOrEmpty(ConnectionOptions.FailoverPartner))
{
throw SQL.ROR_FailoverNotSupportedConnString();
}

if (ServerProvidedFailOverPartner != null)
{
throw SQL.ROR_FailoverNotSupportedServer(this);
}
}
}
_timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
}
catch (Exception e)
Expand Down Expand Up @@ -1556,9 +1583,9 @@ private void LoginNoFailover(ServerInfo serverInfo,
if (RoutingInfo != null)
{
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.LoginNoFailover> Routed to {0}", serverInfo.ExtendedServerName);
if (routingAttempts > 0)
if (routingAttempts > _maxNumberOfRedirectRoute)
{
throw SQL.ROR_RecursiveRoutingNotSupported(this);
throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute);
}

if (timeout.IsExpired)
Expand Down Expand Up @@ -1754,7 +1781,9 @@ TimeoutTimer timeout
// Re-allocate parser each time to make sure state is known
// RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
if (_parser != null)
{
_parser.Disconnect();
}

_parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}");
Expand Down Expand Up @@ -1787,15 +1816,44 @@ TimeoutTimer timeout
intervalTimer,
withFailover: true
);

if (RoutingInfo != null)
int routingAttemps = 0;
while (RoutingInfo != null)
{
// We are in login with failover scenation and server sent routing information
// If it is read-only routing - we did not supply AppIntent=RO (it should be checked before)
// If it is something else, not known yet (future server) - this client is not designed to support this.
// In any case, server should not have sent the routing info.
if (routingAttemps > _maxNumberOfRedirectRoute)
{
throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute);
}
routingAttemps++;

SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.LoginWithFailover> Routed to {0}", RoutingInfo.ServerName);
throw SQL.ROR_UnexpectedRoutingInfo(this);

if(_parser != null)
{
_parser.Disconnect();
}

_parser = new TdsParser(ConnectionOptions.MARS, connectionOptions.Asynchronous);

Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}");

currentServerInfo = new ServerInfo(ConnectionOptions, RoutingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN);
_timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
_originalClientConnectionId = _clientConnectionId;
_routingDestination = currentServerInfo.UserServerName;

// restore properties that could be changed by the environemnt tokens
_currentPacketSize = connectionOptions.PacketSize;
_currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog;
_currentFailoverPartner = null;
_instanceName = string.Empty;

AttemptOneLogin(
currentServerInfo,
newPassword,
newSecurePassword,
intervalTimer,
withFailover: true);
}
break; // leave the while loop -- we've successfully connected
}
Expand All @@ -1812,7 +1870,7 @@ TimeoutTimer timeout
throw; // Caller will call LoginFailure()
}

if (IsConnectionDoomed)
if (!ADP.IsAzureSqlServerEndpoint(connectionOptions.DataSource) && IsConnectionDoomed)
{
throw;
}
Expand Down Expand Up @@ -2743,23 +2801,33 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
}
break;
}

case TdsEnums.FEATUREEXT_UTF8SUPPORT:
case TdsEnums.FEATUREEXT_AZURESQLSUPPORT:
{
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for UTF8 support", ObjectID);
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for AzureSQLSupport", ObjectID);

if (data.Length < 1)
{
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown value for UTF8 support", ObjectID);
throw SQL.ParsingError();
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
}

IsAzureSQLConnection = true;

// Bit 0 for RO/FP support
if ((data[0] & 1) == 1 && SqlClientEventSource.Log.IsTraceEnabled())
{
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, FailoverPartner enabled with Readonly intent for AzureSQL DB", ObjectID);

}
break;
}
case TdsEnums.FEATUREEXT_DATACLASSIFICATION:
{
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for DATACLASSIFICATION", ObjectID);

if (data.Length < 1)
{
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown token for DATACLASSIFICATION", ObjectID);

throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
}
byte supportedDataClassificationVersion = data[0];
Expand All @@ -2772,12 +2840,25 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
if (data.Length != 2)
{
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown token for DATACLASSIFICATION", ObjectID);

throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
}
byte enabled = data[1];
_parser.DataClassificationVersion = (enabled == 0) ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED : supportedDataClassificationVersion;
break;
}
case TdsEnums.FEATUREEXT_UTF8SUPPORT:
{
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for UTF8 support", ObjectID);

if (data.Length < 1)
{
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown value for UTF8 support", ObjectID);

throw SQL.ParsingError();
}
break;
}

case TdsEnums.FEATUREEXT_SQLDNSCACHING:
{
Expand Down Expand Up @@ -2822,7 +2903,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown token for JSONSUPPORT", ObjectID);
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
}
byte jsonSupportVersion = data[0];
byte jsonSupportVersion = data[0];
if (jsonSupportVersion == 0 || jsonSupportVersion > TdsEnums.MAX_SUPPORTED_JSON_VERSION)
{
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Invalid version number for JSONSUPPORT", ObjectID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ internal static void Assert(string message)
// size of Guid (e.g. _clientConnectionId, ActivityId.Id)
private const int GUID_SIZE = 16;

// now data length is 1 byte
// First bit is 1 indicating client support failover partner with readonly intent
private static readonly byte[] s_featureExtDataAzureSQLSupportFeatureRequest = { 0x01 };

// NOTE: You must take the internal connection's _parserLock before modifying this
internal bool _asyncWrite = false;

Expand Down Expand Up @@ -550,7 +554,6 @@ internal void Connect(

// On Instance failure re-connect and flush SNI named instance cache.
_physicalStateObj.SniContext = SniContext.Snix_Connect;

_physicalStateObj.CreatePhysicalSNIHandle(
serverInfo.ExtendedServerName,
timeout, out instanceName,
Expand Down Expand Up @@ -8392,6 +8395,24 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD
return len; // size of data written
}

internal int WriteAzureSQLSupportFeatureRequest(bool write /* if false just calculates the length */)
{
int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = featureData

if (write)
{
// Write Feature ID
_physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_AZURESQLSUPPORT);

// Feature Data length
WriteInt(s_featureExtDataAzureSQLSupportFeatureRequest.Length, _physicalStateObj);

_physicalStateObj.WriteByteArray(s_featureExtDataAzureSQLSupportFeatureRequest, s_featureExtDataAzureSQLSupportFeatureRequest.Length, 0);
}

return len;
}

internal int WriteDataClassificationFeatureRequest(bool write /* if false just calculates the length */)
{
int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version
Expand Down Expand Up @@ -8764,6 +8785,10 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures,
{
length += WriteGlobalTransactionsFeatureRequest(write);
}
if ((requestedFeatures & TdsEnums.FeatureExtension.AzureSQLSupport) != 0)
{
length += WriteAzureSQLSupportFeatureRequest(write);
}
if ((requestedFeatures & TdsEnums.FeatureExtension.DataClassification) != 0)
{
length += WriteDataClassificationFeatureRequest(write);
Expand Down
Loading

0 comments on commit d3772fc

Please sign in to comment.