Skip to content

Handle early disconnects before SSL handshake #596

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

Merged
merged 4 commits into from
Jul 11, 2023

Conversation

pvlugter
Copy link
Contributor

Refs #595

Handle the inbound connection closing before SSL negotiation and always complete the SSL handshake future.

At first, only added the channel inactive lifecycle event in SSLSessionHandlerAdapter. But with the channel pipeline being updated after connect, found that the inbound could already be closed before the handler was added (could check if it's active) but the channel could also be unregistered and outside the event loop and then the handler added event would be pending and never called. So reworked the pipeline changes so that the SSL adapter is added on channel init, so all lifecycle events are received. Pass the handshake Mono via a channel attribute — not sure if there's a better way, and the SSL adapter may already have swapped itself for the underlying SSL handler.

Simple integration test for checking that disconnects are completed exceptionally, simulating what we've seen with Google Cloud SQL. Before these changes, this test will hang. No test specifically for the races between the disconnects and the handler lifecycle events, but have tested these changes manually under load.

Comment on lines -147 to 151
connection.addHandlerFirst(new EnsureSubscribersCompleteChannelHandler(this.requestSink));
connection.addHandlerLast(new EnsureSubscribersCompleteChannelHandler(this.requestSink));
connection.addHandlerLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE - 5, 1, 4, -4, 0));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Keeps the same handler order. The SSL adapter is already added first.

Comment on lines 401 to 405
InternalLogger logger = InternalLoggerFactory.getInstance(ReactorNettyClient.class);
if (logger.isTraceEnabled()) {
pipeline.addFirst(LoggingHandler.class.getSimpleName(),
new LoggingHandler(ReactorNettyClient.class, LogLevel.TRACE));
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Trace logging handler also added on channel init, so it logs all lifecycle events.

if (this.sslConfig.getSslMode().requireSsl()) {
PostgresqlSslException e =
new PostgresqlSslException("Server support for SSL connection is disabled, but client was configured with SSL mode " + this.sslConfig.getSslMode());
completeHandshakeExceptionally(e);
} else {
completeHandshake();
ctx.channel().pipeline().remove(this);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noticed this path as well, which could be reached if the server is ssl=off and the client is sslmode=prefer. Probably not tested currently, but the adapter would still be in the pipeline and fail subsequent reads.

@mp911de mp911de added this to the 1.0.2.RELEASE milestone Jun 15, 2023
@mp911de mp911de added the type: bug A general bug label Jun 15, 2023
@mp911de mp911de linked an issue Jun 15, 2023 that may be closed by this pull request
Copy link
Collaborator

@mp911de mp911de left a comment

Choose a reason for hiding this comment

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

Thanks a lot. Closing the potential gaps that can leave a connection lingering makes sense. I left a few comments to provide guidance toward some changes.

Mono.from(SSLRequest.INSTANCE.encode(this.alloc)).subscribe(ctx::writeAndFlush);
ctx.fireChannelActive();
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can resort to calling super.channelActive().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, wasn't sure what would be preferred. Updated in d0bbd9d.

// If we receive channel inactive before removing this handler, then the inbound has closed early.
PostgresqlSslException e = new PostgresqlSslException("Connection closed during SSL negotiation");
completeHandshakeExceptionally(e);
ctx.fireChannelInactive();
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's call super.channelInactive()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

@@ -40,8 +40,17 @@ final class SSLSessionHandlerAdapter extends AbstractPostgresSSLHandlerAdapter {
}

@Override
public void handlerAdded(ChannelHandlerContext ctx) {
public void channelActive(ChannelHandlerContext ctx) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we require the same changes in SSLTunnelHandlerAdapter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

SSLTunnelHandlerAdapter immediately swaps itself for the underlying SslHandler, which already handles the disconnects, so shouldn't need any changes. Added a similar integration test for tunnel mode: 6de187d

return tcpClient.connect().flatMap(it -> {

ChannelPipeline pipeline = it.channel().pipeline();
return tcpClient.doOnChannelInit((observer, channel, remoteAddress) -> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Using doOnChannelInit does make sense to avoid the gap of having a closed channel.


return Mono.empty();
private static Mono<Void> getSslHandshake(Channel channel) {
Mono<Void> sslHandshake = channel.attr(SSL_HANDSHAKE_KEY).getAndSet(null);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of an attribute we could walk the handler pipeline and obtain the handshake mono from the AbstractPostgresSSLHandlerAdapter object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in original comment, since the handshake mono is under connect.flatMap and the SSL adapter swaps itself for the underlying SslHandler, the SSL adapter can already be removed from the pipeline at this point. Definitely for SSLTunnelHandlerAdapter, which swaps on handler added. Assuming it's possible for SSLSessionHandlerAdapter as well, based on timing. Or am I missing something? Maybe if the SSL tunnel adapter doesn't swap for the SSL handler until after connection, and it's clear that the adapters will still be in the pipeline.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is an indication that the idea of removing the SSL handler isn't ideal. The SSLSessionHandlerAdapter performs one-time-only ops by sending the SSL request upon connect and reading the result afterwards. If we introduce state to the handler to remember that we've sent the request and that we've consumed its result, then we could skip the respective activity in channelActive and channelRead and keep the handler within the pipeline.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, not removing the handlers is the alternative. So if leaving them in the pipeline, as noop handlers after the negotiation, is preferred, then I'll update to that.

Copy link
Contributor Author

@pvlugter pvlugter Jun 26, 2023

Choose a reason for hiding this comment

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

Updated in 0ba6b07 d40226f

@pvlugter pvlugter force-pushed the gh-595-ssl-handler branch from 0ba6b07 to ba6b2f4 Compare June 26, 2023 22:37
@pvlugter pvlugter force-pushed the gh-595-ssl-handler branch from ba6b2f4 to d40226f Compare June 26, 2023 22:41
@pvlugter
Copy link
Contributor Author

pvlugter commented Jul 8, 2023

@mp911de do you have time for another review here? Would be great to have this fix in a release to use.

@mp911de mp911de merged commit 70afe5f into pgjdbc:main Jul 11, 2023
mp911de added a commit that referenced this pull request Jul 11, 2023
[#595][#596]

Signed-off-by: Mark Paluch <[email protected]>
mp911de pushed a commit that referenced this pull request Jul 11, 2023
mp911de added a commit that referenced this pull request Jul 11, 2023
[#595][#596]

Signed-off-by: Mark Paluch <[email protected]>
@mp911de
Copy link
Collaborator

mp911de commented Jul 11, 2023

Thank you for your contribution. That's merged, polished, and backported now.

@pvlugter pvlugter deleted the gh-595-ssl-handler branch July 11, 2023 22:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Creating connections can hang during server downtime
2 participants