Skip to content
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

Fix | Increase routing attempt to 10 in netcore for LoginNoFailover and adding routing support to LoginWithFailover #2873

Merged
merged 4 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
#if DEBUG
Expand Down Expand Up @@ -1442,6 +1452,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 @@ -1558,9 +1585,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 @@ -1756,7 +1783,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 @@ -1789,15 +1818,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 @@ -1814,7 +1872,7 @@ TimeoutTimer timeout
throw; // Caller will call LoginFailure()
}

if (IsConnectionDoomed)
if (!ADP.IsAzureSqlServerEndpoint(connectionOptions.DataSource) && IsConnectionDoomed)
{
throw;
}
Expand Down Expand Up @@ -2745,23 +2803,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 @@ -2774,12 +2842,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 @@ -2824,7 +2905,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 @@ -8375,6 +8378,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 @@ -8747,6 +8768,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
Loading