Truc Nguyen created KAFKA-21341:
-----------------------------------
Summary: SslTransportLayer does not report a disconnect when the
peer sends close_notify without a TCP FIN
Key: KAFKA-21341
URL: https://issues.apache.org/jira/browse/KAFKA-21341
Project: Kafka
Issue Type: Bug
Components: clients, network
Affects Versions: 4.2.2, 4.3.1, 4.2.1, 4.1.2, 4.1.1, 4.0.2, 4.3.0, 4.2.0,
4.1.0, 4.0.1, 4.0.0, 3.9.2, 3.9.1, 3.9.0, 3.8.1, 3.8.0, 3.7.2, 3.7.1, 3.7.0,
3.6.2, 3.6.1, 3.6.0, 3.6.3, 3.7.3, 3.8.2, 3.9.3, 4.0.3, 4.1.3, 4.2.3, 4.3.2,
4.3.3, 4.4.0, 4.4.1, 4.5.0
Reporter: Truc Nguyen
Assignee: Truc Nguyen
When a TLS close_notify arrives in the same socket read as application data,
SslTransportLayer.read() returns the data and leaves the close to be handled by
a later read. However, updateBytesBuffered() clears hasBytesBuffered once the
buffers are empty, so the Selector only reads the channel again if the socket
becomes readable, which requires a TCP FIN.
RFC 8446 section 6.1 allows a peer to send close_notify and keep the TCP
connection open: close_notify closes only the sender's write side, and the
standard leaves the transport lifecycle to the application. A TLS-terminating
proxy that half-closes its TLS side (for example, Envoy's tcp_proxy) does
exactly this. The client then never notices the disconnect, and an in-flight
request waits for request.timeout.ms before it fails.
*Reproduction*
A TLS peer completes the handshake, then writes part (or all) of a response
followed by close_notify in a single write, and keeps the TCP connection open.
The client Selector never reports the connection as disconnected. If the full
response was sent, the client receives it but still does not see the disconnect.
*Expected behaviour*
Kafka already treats close_notify as end-of-stream when it arrives on its own:
the CLOSED unwrap makes read() throw EOFException and the channel is closed. It
should do the same when close_notify arrives together with data. This matches
RFC 8446 section 6.1, which requires a TLS implementation to receive
close_notify before indicating end-of-data to the application, and does not
require the receiver to keep its own write side open. Kafka has no half-close
semantics: once the peer has sent close_notify it can send no more responses,
so reporting the disconnect at once is the only useful behaviour. On close,
SslTransportLayer still sends its own close_notify.
*Proposed fix*
* After a CLOSED unwrap, keep the channel in the Selector's buffered-read set
(hasBytesBuffered = true) so the next poll() reads it again without waiting for
OP_READ.
* At the start of read(), if the engine's inbound side is done and no decrypted
data is left, throw EOFException so the channel is closed as a disconnect.
* In read(ByteBuffer[], int, int), return the bytes already read if a later
read throws EOFException, instead of losing them.
A channel that is muted (for example while the broker handles a request that
arrived with the close_notify) stays open until it is unmuted, so the response
to that request is still sent before the disconnect is reported.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)