You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jira@kafka.apache.org by "Truc Nguyen (Jira)" <ji...@apache.org> on 2026/10/23 09:39:00 UTC

[jira] [Created] (KAFKA-21341) SslTransportLayer does not report a disconnect when the peer sends close_notify without a TCP FIN

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)