Repository navigation
Add connectionTimeout to bound the TCP connect and TLS handshake - #649
Merged
Merged
Conversation
socketTimeout bounds only the TCP connect: SecureSocket.connect's timeout covers RawSocket.connect, not the TLS handshake that follows. A peer that accepts the TCP connection but never answers the TLS client hello holds a connection attempt forever, with or without socketTimeout. MqttServerClient.connectionTimeout (milliseconds, null by default) bounds the TCP connect and, for a secure connection, the TLS handshake of each connection attempt on TCP and secure TCP connections. When it elapses the attempt fails with a NoConnectionException through the existing failed connect path. Without a socketTimeout the TCP connect is given the same timeout, so the runtime cancels a pending connect. The runtime cannot abort a TLS handshake in progress, so a socket whose handshake completes after the timeout is destroyed when it does and is never used. connectTimeoutPeriod and socketTimeout are unchanged, and with connectionTimeout unset the behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shamblett
added a commit
that referenced
this pull request
Oct 7, 2026
* feat: Add connectionTimeout to bound the TCP connect and TLS handshake (#649) socketTimeout bounds only the TCP connect: SecureSocket.connect's timeout covers RawSocket.connect, not the TLS handshake that follows. A peer that accepts the TCP connection but never answers the TLS client hello holds a connection attempt forever, with or without socketTimeout. MqttServerClient.connectionTimeout (milliseconds, null by default) bounds the TCP connect and, for a secure connection, the TLS handshake of each connection attempt on TCP and secure TCP connections. When it elapses the attempt fails with a NoConnectionException through the existing failed connect path. Without a socketTimeout the TCP connect is given the same timeout, so the runtime cancels a pending connect. The runtime cannot abort a TLS handshake in progress, so a socket whose handshake completes after the timeout is destroyed when it does and is never used. connectTimeoutPeriod and socketTimeout are unchanged, and with connectionTimeout unset the behaviour is unchanged. Co-authored-by: Sagar Jaiswal <20596140+sagar448@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * Formatting * DCM * Changelog --------- Co-authored-by: Sagar Jaiswal <jas_96@hotmail.co.uk> Co-authored-by: Sagar Jaiswal <20596140+sagar448@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Owner
|
Thanks for this, especially the tests, a lot of folks often forget these. Merged, package re-published at version 10.12.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A connection attempt on a secure (TLS) connection has no upper bound once the TCP
connection is made.
socketTimeoutis passed toSecureSocket.connect(timeout:), whichonly bounds
RawSocket.connect— the TLS handshake that follows has no timeout indart:io. So a peer that accepts the TCP connection but never answers the TLS clienthello (a misbehaving middlebox, a captive portal, a firewall doing DPI, a wedged broker)
holds
connect()forever, with or withoutsocketTimeout, and an application's ownreconnect loop stops with it. Setting
socketTimeoutalso setsconnectTimeoutPeriodto10 ms, which is too short for a remote broker's CONNACK, so it can't be used for this
anyway. #589 noted a connect bound "needs its own option to make it clear what it's for".
Reproduction
(Same with plain
SecureSocket.connect('127.0.0.1', port, timeout: Duration(seconds: 2)).)Change
A new
MqttServerClient.connectionTimeout(milliseconds,nullby default) bounds eachconnection attempt's TCP connect and, for a secure connection, its TLS handshake, on TCP
and secure TCP connections:
NoConnectionExceptionthrough the existingfailed-connect path (the same path as a refused connection or a failed handshake),
so existing error handling applies.
connectionStatus.stateisfaulted.dart:iocannot abort a TLS handshake in progress, so a socket whose handshakecompletes after the timeout is destroyed when it does and is never used (no CONNECT
is sent on it).
socketTimeoutset, the TCP connect is given the same timeout, so the runtimecancels a still-pending connect (e.g. a SYN black hole) instead of leaving it to the OS.
socketTimeoutandconnectTimeoutPeriodare unchanged. WithconnectionTimeoutunsetthe code path is unchanged.
MqttServerConnectionand used by the fourconnect/connectAutocall sites of the normal and secure connections. It is passed to them as a plain field,
so no constructor signatures change.
Tests
mqtt_client_connection_unsecure_test.dart: Connection Timeout - TCP connect nevercompletes (fails at the timeout, not after the CONNACK wait; the TCP connect is given
the timeout) and Connection Timeout - socket timeout unchanged.
mqtt_client_connection_secure_test.dart, new group Connection Timeout: a realloopback peer that never answers the TLS client hello (fails at the timeout); a peer
that completes the handshake only after the timeout (the late socket is closed and
receives no bytes); a normal TLS broker connects with the timeout set.
MockBroker Connection Keep Alive - Successful response, fails the same way without
this change:
test/pem/localhost.certhas no subjectAltName, which the macOS verifierrejects. It passes on Linux.
behind an ALPN-requiring TLS terminator): connects as before. A peer that stalls the
handshake, and a SYN black hole (
192.0.2.1), fail at the timeout.Backward compatibility
Additive and opt-in: one new nullable field, default
null= today's behaviour; nosignature changes;
socketTimeoutsemantics untouched. Websocket connections are notcovered (like
socketTimeout). Happy to extend it toMqttServerWsConnection/Ws2ifyou'd like. I added a CHANGELOG entry under a provisional
10.12.0heading. Feel free tofold it into your release commit or rename the option (e.g. a
Duration).🤖 Generated with Claude Code