Skip to content

Conversation

@saurabh500
Copy link
Contributor

@saurabh500 saurabh500 commented Jun 22, 2024

Adding Prelogin handler

There is a Handler called PreloginHandler, which is composed of two subhandlers depending on the Encryption option of the Connection String.

Encrypt = Strict signifies TDS8 and anything else means TDS7

For TDS8, the chain in prelogin looks like Tds8TlsHandler -> PreloginPacketHandler
Else PreloginPacketHandler -> Tds74TlsHandler.

The creation of SslStreams, and their ordering is done in the PreloginHandler itself.

Tds8TlsHandler and Tds74TlsHandlers, take care of TLS before or after prelogin exchange depending on the protocol.

The code in this PR hasn't changed anything from the existing code, except removing checks for unsupported SQL Server versions.

Tests have been added.

I also tested end to end (not checkedin) by creating the DataSourceParsingHandler>TransportCreationHandler > PreloginHandler in a chain.

To test TDS8, I used Azure SQL DB and for TDS74, I used a local server.

  1. DataSourceParsingHandler has been changed to look for ServerInfo instead of ConnectionOptions.
  2. TransportCreationHandler has been changed to add a GUID which is the connection Id.
  3. For Prelogin handler, I chose to use a different context object, this is to add some derived items and to specifically reference some prelogin related fields.

How to review

Start from the PreloginHandler class which is the handler entrypoint for Prelogin. All the Tds8 specific handling is in Tds8TlsHandler and Tds74 handling is in Tds74TlsHandler.
The transmission and reading Prelogin packet is available in PreloginPacketHandler

Tests

Unit tests have been added.

For sanity integration test I used the following in a Unit test to validate connectivity. However this snippet is not checked in

DataSourceParsingHandler dspHandler = new DataSourceParsingHandler();
TransportCreationHandler tcHandler = new TransportCreationHandler();
PreloginHandler plHandler = new PreloginHandler();
ConnectionHandlerContext chc = new ConnectionHandlerContext();
SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder();
csb.DataSource = "tcp:localhost,1444";
csb.Encrypt = SqlConnectionEncryptOption.Mandatory;

csb.TrustServerCertificate = true;

SqlConnectionString scs = new SqlConnectionString(csb.ConnectionString);
chc.ConnectionString = scs;
var serverInfo = new ServerInfo(scs);
serverInfo.SetDerivedNames(null, serverInfo.UserServerName);
chc.SeverInfo = serverInfo;
dspHandler.NextHandler = tcHandler;
tcHandler.NextHandler = plHandler;
dspHandler.Handle(chc, false, default).GetAwaiter().GetResult();

Copy link
Contributor

@edwardneal edwardneal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realise this is still a draft PR, but it's the first set of handlers which cover real-world TDS traffic so I thought it best to comment early. There's probably quite a bit of overlap with your own testing!

Comment on lines 48 to 52
SqlClientEventSource.Log.TryTraceEvent("<sc|{0}|{1}|{2}>{3}",
nameof(PreloginHandler),
nameof(AuthenticateClientInternal),
SqlClientLogger.LogLevel.Warning,
warningMessage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At present, logging is a little inconsistent between SqlClientEventSource and request.ConnectionContext.Logger. It'd be a good idea to try to try and unify these (perhaps making all new logs use request.ConnectionContext.Logger, which would leave an integration point for the OpenTelemetry integration.)

In the meantime, we can make a simple change to interpolate this string at compile time rather than force the runtime to parse a format string. The same applies to other instances.

Suggested change
SqlClientEventSource.Log.TryTraceEvent("<sc|{0}|{1}|{2}>{3}",
nameof(PreloginHandler),
nameof(AuthenticateClientInternal),
SqlClientLogger.LogLevel.Warning,
warningMessage);
SqlClientEventSource.Log.TryTraceEvent($"<sc|{nameof(PreloginHandler)}|{nameof(AuthenticateClientInternal)}|{SqlClientLogger.LogLevel.Warning}>{warningMessage}");

@saurabh500
Copy link
Contributor Author

@VladimirReshetnikov I have taken care of all the review comments.

Comment on lines +109 to +110
return (InternalEncryptionOption == EncryptionOptions.ON && !TrustServerCertificate)
|| (ConnectionContext.AccessTokenInBytes != null && !TrustServerCertificate);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (InternalEncryptionOption == EncryptionOptions.ON && !TrustServerCertificate)
|| (ConnectionContext.AccessTokenInBytes != null && !TrustServerCertificate);
return !TrustServerCertificate && (InternalEncryptionOption == EncryptionOptions.ON
|| ConnectionContext.AccessTokenInBytes);

Nit: readability suggestion.

@saurabh500 saurabh500 self-assigned this Jun 27, 2024
@saurabh500
Copy link
Contributor Author

@cheenamalhotra addressed all comments.


SqlClientEventSource.Log.TrySNITraceEvent(nameof(PreloginHandler), EventType.INFO, "Connection Id {0}, Certificate will be validated for Target Server name", args0: connectionId);

return SNICommon.ValidateSslServerCertificate(connectionId,
Copy link
Member

@cheenamalhotra cheenamalhotra Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not critical at the moment, but these dependencies on Managed SNI can make things complicated for both SqlClient and SqlClientX, so I would recommend adding a comment in such API docs that they are shared APIs, and should be handled carefully.

@saurabh500 saurabh500 merged commit 6379221 into dotnet:feat/sqlclientx Jun 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants