Skip to content

Commit d3772fc

Browse files
authored
Fix | Increase routing attempt to 10 in netcore for LoginNoFailover and adding routing support to LoginWithFailover (#2873)
1 parent cab5422 commit d3772fc

File tree

6 files changed

+175
-63
lines changed

6 files changed

+175
-63
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

+98-17
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
109109
// CONNECTION AND STATE VARIABLES
110110
private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
111111
private TdsParser _parser;
112+
113+
// Connection re-route limit
114+
internal const int _maxNumberOfRedirectRoute = 10;
115+
112116
private SqlLoginAck _loginAck;
113117
private SqlCredential _credential;
114118
private FederatedAuthenticationFeatureExtensionData _fedAuthFeatureExtensionData;
@@ -130,7 +134,7 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
130134
// The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken.
131135
SqlFedAuthToken _fedAuthToken = null;
132136
internal byte[] _accessTokenInBytes;
133-
internal readonly Func<SqlAuthenticationParameters, CancellationToken,Task<SqlAuthenticationToken>> _accessTokenCallback;
137+
internal readonly Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticationToken>> _accessTokenCallback;
134138

135139
private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper;
136140

@@ -1363,6 +1367,12 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword,
13631367
// The GLOBALTRANSACTIONS, DATACLASSIFICATION, TCE, and UTF8 support features are implicitly requested
13641368
requestedFeatures |= TdsEnums.FeatureExtension.GlobalTransactions | TdsEnums.FeatureExtension.DataClassification | TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.UTF8Support;
13651369

1370+
// The AzureSQLSupport feature is implicitly set for ReadOnly login
1371+
if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly)
1372+
{
1373+
requestedFeatures |= TdsEnums.FeatureExtension.AzureSQLSupport;
1374+
}
1375+
13661376
// The SQLDNSCaching feature is implicitly set
13671377
requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching;
13681378
requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport;
@@ -1440,6 +1450,23 @@ private void OpenLoginEnlist(TimeoutTimer timeout,
14401450
credential,
14411451
timeout);
14421452
}
1453+
1454+
if (!IsAzureSQLConnection)
1455+
{
1456+
// If not a connection to Azure SQL, Readonly with FailoverPartner is not supported
1457+
if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly)
1458+
{
1459+
if (!string.IsNullOrEmpty(ConnectionOptions.FailoverPartner))
1460+
{
1461+
throw SQL.ROR_FailoverNotSupportedConnString();
1462+
}
1463+
1464+
if (ServerProvidedFailOverPartner != null)
1465+
{
1466+
throw SQL.ROR_FailoverNotSupportedServer(this);
1467+
}
1468+
}
1469+
}
14431470
_timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
14441471
}
14451472
catch (Exception e)
@@ -1556,9 +1583,9 @@ private void LoginNoFailover(ServerInfo serverInfo,
15561583
if (RoutingInfo != null)
15571584
{
15581585
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.LoginNoFailover> Routed to {0}", serverInfo.ExtendedServerName);
1559-
if (routingAttempts > 0)
1586+
if (routingAttempts > _maxNumberOfRedirectRoute)
15601587
{
1561-
throw SQL.ROR_RecursiveRoutingNotSupported(this);
1588+
throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute);
15621589
}
15631590

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

17591788
_parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
17601789
Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}");
@@ -1787,15 +1816,44 @@ TimeoutTimer timeout
17871816
intervalTimer,
17881817
withFailover: true
17891818
);
1790-
1791-
if (RoutingInfo != null)
1819+
int routingAttemps = 0;
1820+
while (RoutingInfo != null)
17921821
{
1793-
// We are in login with failover scenation and server sent routing information
1794-
// If it is read-only routing - we did not supply AppIntent=RO (it should be checked before)
1795-
// If it is something else, not known yet (future server) - this client is not designed to support this.
1796-
// In any case, server should not have sent the routing info.
1822+
if (routingAttemps > _maxNumberOfRedirectRoute)
1823+
{
1824+
throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute);
1825+
}
1826+
routingAttemps++;
1827+
17971828
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.LoginWithFailover> Routed to {0}", RoutingInfo.ServerName);
1798-
throw SQL.ROR_UnexpectedRoutingInfo(this);
1829+
1830+
if(_parser != null)
1831+
{
1832+
_parser.Disconnect();
1833+
}
1834+
1835+
_parser = new TdsParser(ConnectionOptions.MARS, connectionOptions.Asynchronous);
1836+
1837+
Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}");
1838+
1839+
currentServerInfo = new ServerInfo(ConnectionOptions, RoutingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN);
1840+
_timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
1841+
_originalClientConnectionId = _clientConnectionId;
1842+
_routingDestination = currentServerInfo.UserServerName;
1843+
1844+
// restore properties that could be changed by the environemnt tokens
1845+
_currentPacketSize = connectionOptions.PacketSize;
1846+
_currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
1847+
CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog;
1848+
_currentFailoverPartner = null;
1849+
_instanceName = string.Empty;
1850+
1851+
AttemptOneLogin(
1852+
currentServerInfo,
1853+
newPassword,
1854+
newSecurePassword,
1855+
intervalTimer,
1856+
withFailover: true);
17991857
}
18001858
break; // leave the while loop -- we've successfully connected
18011859
}
@@ -1812,7 +1870,7 @@ TimeoutTimer timeout
18121870
throw; // Caller will call LoginFailure()
18131871
}
18141872

1815-
if (IsConnectionDoomed)
1873+
if (!ADP.IsAzureSqlServerEndpoint(connectionOptions.DataSource) && IsConnectionDoomed)
18161874
{
18171875
throw;
18181876
}
@@ -2743,23 +2801,33 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
27432801
}
27442802
break;
27452803
}
2746-
2747-
case TdsEnums.FEATUREEXT_UTF8SUPPORT:
2804+
case TdsEnums.FEATUREEXT_AZURESQLSUPPORT:
27482805
{
2749-
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for UTF8 support", ObjectID);
2806+
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for AzureSQLSupport", ObjectID);
2807+
27502808
if (data.Length < 1)
27512809
{
2752-
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown value for UTF8 support", ObjectID);
2753-
throw SQL.ParsingError();
2810+
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
2811+
}
2812+
2813+
IsAzureSQLConnection = true;
2814+
2815+
// Bit 0 for RO/FP support
2816+
if ((data[0] & 1) == 1 && SqlClientEventSource.Log.IsTraceEnabled())
2817+
{
2818+
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, FailoverPartner enabled with Readonly intent for AzureSQL DB", ObjectID);
2819+
27542820
}
27552821
break;
27562822
}
27572823
case TdsEnums.FEATUREEXT_DATACLASSIFICATION:
27582824
{
27592825
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for DATACLASSIFICATION", ObjectID);
2826+
27602827
if (data.Length < 1)
27612828
{
27622829
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown token for DATACLASSIFICATION", ObjectID);
2830+
27632831
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
27642832
}
27652833
byte supportedDataClassificationVersion = data[0];
@@ -2772,12 +2840,25 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
27722840
if (data.Length != 2)
27732841
{
27742842
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown token for DATACLASSIFICATION", ObjectID);
2843+
27752844
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
27762845
}
27772846
byte enabled = data[1];
27782847
_parser.DataClassificationVersion = (enabled == 0) ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED : supportedDataClassificationVersion;
27792848
break;
27802849
}
2850+
case TdsEnums.FEATUREEXT_UTF8SUPPORT:
2851+
{
2852+
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ADV> {0}, Received feature extension acknowledgement for UTF8 support", ObjectID);
2853+
2854+
if (data.Length < 1)
2855+
{
2856+
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown value for UTF8 support", ObjectID);
2857+
2858+
throw SQL.ParsingError();
2859+
}
2860+
break;
2861+
}
27812862

27822863
case TdsEnums.FEATUREEXT_SQLDNSCACHING:
27832864
{
@@ -2822,7 +2903,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
28222903
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Unknown token for JSONSUPPORT", ObjectID);
28232904
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
28242905
}
2825-
byte jsonSupportVersion = data[0];
2906+
byte jsonSupportVersion = data[0];
28262907
if (jsonSupportVersion == 0 || jsonSupportVersion > TdsEnums.MAX_SUPPORTED_JSON_VERSION)
28272908
{
28282909
SqlClientEventSource.Log.TryTraceEvent("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> {0}, Invalid version number for JSONSUPPORT", ObjectID);

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs

+26-1
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ internal static void Assert(string message)
157157
// size of Guid (e.g. _clientConnectionId, ActivityId.Id)
158158
private const int GUID_SIZE = 16;
159159

160+
// now data length is 1 byte
161+
// First bit is 1 indicating client support failover partner with readonly intent
162+
private static readonly byte[] s_featureExtDataAzureSQLSupportFeatureRequest = { 0x01 };
163+
160164
// NOTE: You must take the internal connection's _parserLock before modifying this
161165
internal bool _asyncWrite = false;
162166

@@ -550,7 +554,6 @@ internal void Connect(
550554

551555
// On Instance failure re-connect and flush SNI named instance cache.
552556
_physicalStateObj.SniContext = SniContext.Snix_Connect;
553-
554557
_physicalStateObj.CreatePhysicalSNIHandle(
555558
serverInfo.ExtendedServerName,
556559
timeout, out instanceName,
@@ -8392,6 +8395,24 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD
83928395
return len; // size of data written
83938396
}
83948397

8398+
internal int WriteAzureSQLSupportFeatureRequest(bool write /* if false just calculates the length */)
8399+
{
8400+
int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = featureData
8401+
8402+
if (write)
8403+
{
8404+
// Write Feature ID
8405+
_physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_AZURESQLSUPPORT);
8406+
8407+
// Feature Data length
8408+
WriteInt(s_featureExtDataAzureSQLSupportFeatureRequest.Length, _physicalStateObj);
8409+
8410+
_physicalStateObj.WriteByteArray(s_featureExtDataAzureSQLSupportFeatureRequest, s_featureExtDataAzureSQLSupportFeatureRequest.Length, 0);
8411+
}
8412+
8413+
return len;
8414+
}
8415+
83958416
internal int WriteDataClassificationFeatureRequest(bool write /* if false just calculates the length */)
83968417
{
83978418
int len = 6; // 1byte = featureID, 4bytes = featureData length, 1 bytes = Version
@@ -8764,6 +8785,10 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures,
87648785
{
87658786
length += WriteGlobalTransactionsFeatureRequest(write);
87668787
}
8788+
if ((requestedFeatures & TdsEnums.FeatureExtension.AzureSQLSupport) != 0)
8789+
{
8790+
length += WriteAzureSQLSupportFeatureRequest(write);
8791+
}
87678792
if ((requestedFeatures & TdsEnums.FeatureExtension.DataClassification) != 0)
87688793
{
87698794
length += WriteDataClassificationFeatureRequest(write);

0 commit comments

Comments
 (0)