Skip to content

Commit a93ff40

Browse files
Align SqlException Numbers across platforms (#3461)
* - Align SqlException Numbers across platforms - Better capture error scenarios in TCP managed SNI. - Fix logging bug in SqlClientEventSource. - Change nativeError from uint to int * - Removed duplicate SniErrorDetails object and aligned field names. * - Added localized string for the new connection timed out exception. * - Fixed tests sensitive to OS newlines. --------- Co-authored-by: Paul Medynski <31868385+paulmedynski@users.noreply.github.com>
1 parent 2bb2c43 commit a93ff40

File tree

15 files changed

+146
-47
lines changed

15 files changed

+146
-47
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,7 +1507,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
15071507
string providerRid = string.Format("SNI_PN{0}", details.Provider);
15081508
string providerName = StringsHelper.GetResourceString(providerRid);
15091509
Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'");
1510-
uint win32ErrorCode = details.NativeError;
1510+
int win32ErrorCode = details.NativeError;
15111511

15121512
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.TdsParser.ProcessSNIError |ERR|ADV > SNI Native Error Code = {0}", win32ErrorCode);
15131513
if (details.SniErrorNumber == 0)
@@ -1557,7 +1557,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
15571557
// If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
15581558
if (details.SniErrorNumber == SniErrors.LocalDBErrorCode)
15591559
{
1560-
errorMessage += LocalDbApi.GetLocalDbMessage((int)details.NativeError);
1560+
errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError);
15611561
win32ErrorCode = 0;
15621562
}
15631563
SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.TdsParser.ProcessSNIError |ERR|ADV > Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage);
@@ -1567,9 +1567,9 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
15671567
sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage);
15681568

15691569
SqlClientEventSource.Log.TryAdvancedTraceErrorEvent("<sc.TdsParser.ProcessSNIError |ERR|ADV > SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}",
1570-
(int)details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server);
1570+
details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server);
15711571

1572-
return new SqlError(infoNumber: (int)details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server,
1572+
return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server,
15731573
errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception);
15741574
}
15751575
}

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,6 @@ namespace Microsoft.Data.SqlClient
1010
{
1111
internal sealed partial class TdsParser
1212
{
13-
internal struct SNIErrorDetails
14-
{
15-
public string errorMessage;
16-
public uint nativeError;
17-
public uint sniErrorNumber;
18-
public int provider;
19-
public uint lineNumber;
20-
public string function;
21-
public Exception exception;
22-
}
23-
2413
internal static void FillGuidBytes(Guid guid, Span<byte> buffer) => guid.TryWriteBytes(buffer);
2514

2615
internal static void FillDoubleBytes(double value, Span<byte> buffer) => BinaryPrimitives.TryWriteInt64LittleEndian(buffer, BitConverter.DoubleToInt64Bits(value));

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1588,7 +1588,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
15881588
string providerRid = string.Format("SNI_PN{0}", (int)details.Provider);
15891589
string providerName = StringsHelper.GetString(providerRid);
15901590
Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'");
1591-
uint win32ErrorCode = details.NativeError;
1591+
int win32ErrorCode = details.NativeError;
15921592

15931593
if (details.SniErrorNumber == 0)
15941594
{
@@ -1626,14 +1626,14 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj)
16261626
// If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code
16271627
if (details.SniErrorNumber == SniErrors.LocalDBErrorCode)
16281628
{
1629-
errorMessage += LocalDbApi.GetLocalDbMessage((int)details.NativeError);
1629+
errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError);
16301630
win32ErrorCode = 0;
16311631
}
16321632
}
16331633
errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})",
16341634
sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage);
16351635

1636-
return new SqlError((int)details.NativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
1636+
return new SqlError(details.NativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS,
16371637
_server, errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode);
16381638
}
16391639

src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal struct SniError
1212
internal Provider provider;
1313
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261)]
1414
internal string errorMessage;
15-
internal uint nativeError;
15+
internal int nativeError;
1616
internal uint sniError;
1717
[MarshalAs(UnmanagedType.LPWStr)]
1818
internal string fileName;

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ internal static IPAddress[] GetDnsIpAddresses(string serverName)
189189
/// <param name="sniError">SNI error code</param>
190190
/// <param name="errorMessage">Error message</param>
191191
/// <returns></returns>
192-
internal static uint ReportSNIError(SniProviders provider, uint nativeError, uint sniError, string errorMessage)
192+
internal static uint ReportSNIError(SniProviders provider, int nativeError, uint sniError, string errorMessage)
193193
{
194194
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, native Error = {1}, SNI Error = {2}, Error Message = {3}", args0: provider, args1: nativeError, args2: sniError, args3: errorMessage);
195195
return ReportSNIError(new SniError(provider, nativeError, sniError, errorMessage));
@@ -203,7 +203,7 @@ internal static uint ReportSNIError(SniProviders provider, uint nativeError, uin
203203
/// <param name="sniException">SNI Exception</param>
204204
/// <param name="nativeErrorCode">Native SNI error code</param>
205205
/// <returns></returns>
206-
internal static uint ReportSNIError(SniProviders provider, uint sniError, Exception sniException, uint nativeErrorCode = 0)
206+
internal static uint ReportSNIError(SniProviders provider, uint sniError, Exception sniException, int nativeErrorCode = 0)
207207
{
208208
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, SNI Error = {1}, Exception = {2}", args0: provider, args1: sniError, args2: sniException?.Message);
209209
return ReportSNIError(new SniError(provider, sniError, sniException, nativeErrorCode));

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#if NET
66

77
using System;
8+
using System.ComponentModel;
9+
using System.Net.Sockets;
810

911
namespace Microsoft.Data.SqlClient.ManagedSni
1012
{
@@ -14,17 +16,18 @@ namespace Microsoft.Data.SqlClient.ManagedSni
1416
internal class SniError
1517
{
1618
// Error numbers from native SNI implementation
17-
internal const uint CertificateValidationErrorCode = 2148074277;
19+
// This is signed int representation of the error code 0x80090325
20+
internal const int CertificateValidationErrorCode = -2146893019;
1821

1922
public readonly SniProviders provider;
2023
public readonly string errorMessage;
21-
public readonly uint nativeError;
24+
public readonly int nativeError;
2225
public readonly uint sniError;
2326
public readonly string function;
2427
public readonly uint lineNumber;
2528
public readonly Exception exception;
2629

27-
public SniError(SniProviders provider, uint nativeError, uint sniErrorCode, string errorMessage)
30+
public SniError(SniProviders provider, int nativeError, uint sniErrorCode, string errorMessage)
2831
{
2932
lineNumber = 0;
3033
function = string.Empty;
@@ -35,12 +38,25 @@ public SniError(SniProviders provider, uint nativeError, uint sniErrorCode, stri
3538
exception = null;
3639
}
3740

38-
public SniError(SniProviders provider, uint sniErrorCode, Exception sniException, uint nativeErrorCode = 0)
41+
public SniError(SniProviders provider, uint sniErrorCode, Exception sniException, int nativeErrorCode = 0)
3942
{
4043
lineNumber = 0;
4144
function = string.Empty;
4245
this.provider = provider;
4346
nativeError = nativeErrorCode;
47+
if (nativeErrorCode == 0)
48+
{
49+
if (sniException is SocketException socketException)
50+
{
51+
// SocketErrorCode values are cross-plat consistent in .NET (matching native Windows error codes)
52+
// underlying type of SocketErrorCode is int
53+
nativeError = (int)socketException.SocketErrorCode;
54+
}
55+
else if (sniException is Win32Exception win32Exception)
56+
{
57+
nativeError = win32Exception.NativeErrorCode; // Replicates native SNI behavior
58+
}
59+
}
4460
sniError = sniErrorCode;
4561
errorMessage = string.Empty;
4662
exception = sniException;

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public override uint Receive(out SniPacket packet, int timeout)
203203
packet = null;
204204
var e = new Win32Exception();
205205
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, Packet length found 0, Win32 exception raised: {1}", args0: _connectionId, args1: e?.Message);
206-
return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message);
206+
return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message);
207207
}
208208
}
209209
catch (ObjectDisposedException ode)
@@ -413,7 +413,7 @@ private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniExceptio
413413
return SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.InternalExceptionError, sniException);
414414
}
415415

416-
private uint ReportErrorAndReleasePacket(SniPacket packet, uint nativeError, uint sniError, string errorMessage)
416+
private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage)
417417
{
418418
if (packet != null)
419419
{

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
375375

376376
IEnumerable<IPAddress> ipAddresses = GetHostAddressesSortedByPreference(serverName, ipPreference);
377377

378+
SocketException lastSocketException = null;
379+
378380
foreach (IPAddress ipAddress in ipAddresses)
379381
{
380382
bool isSocketSelected = false;
@@ -426,7 +428,9 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
426428
{
427429
if (timeout.IsExpired)
428430
{
429-
return null;
431+
throw new Win32Exception(
432+
TdsEnums.SNI_WAIT_TIMEOUT,
433+
StringsHelper.GetString(Strings.SQL_ConnectTimeout));
430434
}
431435

432436
int socketSelectTimeout =
@@ -442,10 +446,24 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
442446

443447
Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout);
444448
// nothing selected means timeout
449+
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO,
450+
"Socket.Select results: checkReadLst.Count: {0}, checkWriteLst.Count: {1}, checkErrorLst.Count: {2}",
451+
checkReadLst.Count, checkWriteLst.Count, checkErrorLst.Count);
445452
} while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0);
446453

447454
// workaround: false positive socket.Connected on linux: https://github.com/dotnet/runtime/issues/55538
448455
isConnected = socket.Connected && checkErrorLst.Count == 0;
456+
if (!isConnected)
457+
{
458+
// Retrieve the socket error code
459+
int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error);
460+
SocketError socketError = (SocketError)socketErrorCode;
461+
462+
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR,
463+
"Socket connection failed. SocketError: {0} ({1})", socketError, socketErrorCode);
464+
465+
lastSocketException = new SocketException(socketErrorCode);
466+
}
449467
}
450468

451469
if (isConnected)
@@ -463,6 +481,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
463481
}
464482
pendingDNSInfo = new SQLDNSInfo(cachedFQDN, iPv4String, iPv6String, port.ToString());
465483
isSocketSelected = true;
484+
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO,
485+
"Connected to socket: {0}", socket.RemoteEndPoint);
466486
return socket;
467487
}
468488
}
@@ -471,6 +491,7 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
471491
SqlClientEventSource.Log.TryAdvancedTraceEvent(
472492
"{0}.{1}{2}THIS EXCEPTION IS BEING SWALLOWED: {3}",
473493
nameof(SniTcpHandle), nameof(Connect), EventType.ERR, e);
494+
lastSocketException = e;
474495
}
475496
finally
476497
{
@@ -479,6 +500,14 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout,
479500
}
480501
}
481502

503+
if (lastSocketException != null)
504+
{
505+
SqlClientEventSource.Log.TryAdvancedTraceEvent(
506+
"{0}.{1}{2}Last Socket Exception: {3}",
507+
nameof(SniTcpHandle), nameof(Connect), EventType.ERR, lastSocketException);
508+
throw lastSocketException;
509+
}
510+
482511
return null;
483512
}
484513
}
@@ -574,6 +603,20 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim
574603
Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout);
575604
// nothing selected means select timed out
576605
} while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0 && !timeout.IsExpired);
606+
foreach (Socket socket in checkErrorLst)
607+
{
608+
// Retrieve the socket error code
609+
int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error);
610+
SocketError socketError = (SocketError)socketErrorCode;
611+
612+
// Log any failed sockets
613+
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO,
614+
"Socket connection failed for {0}. SocketError: {1} ({2})",
615+
sockets[socket], socketError, socketErrorCode);
616+
617+
lastError = new SocketException(socketErrorCode);
618+
}
619+
577620
}
578621
catch (SocketException e)
579622
{
@@ -588,6 +631,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim
588631
{
589632
SqlClientEventSource.Log.TryAdvancedTraceEvent(
590633
"{0}.{1}{2}ParallelConnect timeout expired.", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.INFO);
634+
// We will throw below after cleanup
591635
break;
592636
}
593637

@@ -654,9 +698,21 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim
654698

655699
if (connectedSocket == null)
656700
{
701+
if (timeout.IsExpired)
702+
{
703+
throw new Win32Exception(
704+
TdsEnums.SNI_WAIT_TIMEOUT,
705+
StringsHelper.GetString(Strings.SQL_ConnectTimeout));
706+
}
707+
657708
SqlClientEventSource.Log.TryAdvancedTraceEvent(
658-
"{0}.{1}{2}No socket connections succeeded. Last error: {3}",
709+
"{0}.{1}{2} No socket connections succeeded. Last error: {3}",
659710
nameof(SniTcpHandle), nameof(ParallelConnect), EventType.ERR, lastError);
711+
712+
if (lastError != null)
713+
{
714+
throw lastError;
715+
}
660716
}
661717

662718
return connectedSocket;
@@ -861,7 +917,7 @@ public override uint Receive(out SniPacket packet, int timeoutInMilliseconds)
861917
packet = null;
862918
var e = new Win32Exception();
863919
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Win32 exception occurred: {1}", args0: _connectionId, args1: e?.Message);
864-
return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message);
920+
return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message);
865921
}
866922

867923
SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Data read from stream synchronously", args0: _connectionId);
@@ -992,13 +1048,13 @@ public override uint CheckConnection()
9921048
return TdsEnums.SNI_SUCCESS;
9931049
}
9941050

995-
private uint ReportTcpSNIError(Exception sniException, uint nativeErrorCode = 0)
1051+
private uint ReportTcpSNIError(Exception sniException, int nativeErrorCode = 0)
9961052
{
9971053
_status = TdsEnums.SNI_ERROR;
9981054
return SniCommon.ReportSNIError(SniProviders.TCP_PROV, SniCommon.InternalExceptionError, sniException, nativeErrorCode);
9991055
}
10001056

1001-
private uint ReportTcpSNIError(uint nativeError, uint sniError, string errorMessage)
1057+
private uint ReportTcpSNIError(int nativeError, uint sniError, string errorMessage)
10021058
{
10031059
_status = TdsEnums.SNI_ERROR;
10041060
return SniCommon.ReportSNIError(SniProviders.TCP_PROV, nativeError, sniError, errorMessage);
@@ -1013,7 +1069,7 @@ private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniExceptio
10131069
return ReportTcpSNIError(sniException);
10141070
}
10151071

1016-
private uint ReportErrorAndReleasePacket(SniPacket packet, uint nativeError, uint sniError, string errorMessage)
1072+
private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage)
10171073
{
10181074
if (packet != null)
10191075
{

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ internal long TryScopeEnterEvent(string className, [System.Runtime.CompilerServi
424424
{
425425
StringBuilder sb = new StringBuilder(className);
426426
sb.Append(".").Append(memberName).Append(" | INFO | SCOPE | Entering Scope {0}");
427-
return SNIScopeEnter(sb.ToString());
427+
return ScopeEnter(sb.ToString());
428428
}
429429
return 0;
430430
}

0 commit comments

Comments
 (0)