-
Notifications
You must be signed in to change notification settings - Fork 184
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
Conversation
connection.addHandlerFirst(new EnsureSubscribersCompleteChannelHandler(this.requestSink)); | ||
connection.addHandlerLast(new EnsureSubscribersCompleteChannelHandler(this.requestSink)); | ||
connection.addHandlerLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE - 5, 1, 4, -4, 0)); |
There was a problem hiding this comment.
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.
InternalLogger logger = InternalLoggerFactory.getInstance(ReactorNettyClient.class); | ||
if (logger.isTraceEnabled()) { | ||
pipeline.addFirst(LoggingHandler.class.getSimpleName(), | ||
new LoggingHandler(ReactorNettyClient.class, LogLevel.TRACE)); | ||
} |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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()
.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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()
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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) -> { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0ba6b07
to
ba6b2f4
Compare
ba6b2f4
to
d40226f
Compare
@mp911de do you have time for another review here? Would be great to have this fix in a release to use. |
[#595][#596] Signed-off-by: Mark Paluch <[email protected]>
[#595][#596] Signed-off-by: Mark Paluch <[email protected]>
Thank you for your contribution. That's merged, polished, and backported now. |
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.