@@ -1161,6 +1161,87 @@ fn handshake_write_loop(
11611161///
11621162/// Waits for and reads TLS records from the peer, handling SNI callback setup.
11631163/// Returns (made_progress, is_first_sni_read).
1164+ /// TLS record header size (content_type + version + length).
1165+ const TLS_RECORD_HEADER_SIZE : usize = 5 ;
1166+
1167+ /// Determine how many bytes to read from the socket during a TLS handshake.
1168+ ///
1169+ /// OpenSSL reads one TLS record at a time (no read-ahead by default).
1170+ /// Rustls, however, consumes all available TCP data when fed via read_tls().
1171+ /// If application data arrives simultaneously with the final handshake record,
1172+ /// the eager read drains the TCP buffer, leaving the app data in rustls's
1173+ /// internal buffer where select() cannot see it. This causes asyncore-based
1174+ /// servers (which rely on select() for readability) to miss the data and the
1175+ /// peer times out.
1176+ ///
1177+ /// Fix: peek at the TCP buffer to find the first complete TLS record boundary
1178+ /// and recv() only that many bytes. Any remaining data (including application
1179+ /// data that piggybacked on the same TCP segment) stays in the kernel buffer
1180+ /// and remains visible to select().
1181+ fn handshake_recv_one_record (
1182+ socket : & PySSLSocket ,
1183+ vm : & VirtualMachine ,
1184+ ) -> SslResult < PyObjectRef > {
1185+ // Peek at what is available without consuming it.
1186+ let peeked_obj = match socket. sock_peek ( SSL3_RT_MAX_PLAIN_LENGTH , vm) {
1187+ Ok ( d) => d,
1188+ Err ( e) => {
1189+ if is_blocking_io_error ( & e, vm) {
1190+ return Err ( SslError :: WantRead ) ;
1191+ }
1192+ return Err ( SslError :: Py ( e) ) ;
1193+ }
1194+ } ;
1195+
1196+ let peeked = ArgBytesLike :: try_from_object ( vm, peeked_obj)
1197+ . map_err ( |_| SslError :: Syscall ( "Expected bytes-like object from peek" . to_string ( ) ) ) ?;
1198+ let peeked_bytes = peeked. borrow_buf ( ) ;
1199+
1200+ if peeked_bytes. is_empty ( ) {
1201+ return Err ( SslError :: WantRead ) ;
1202+ }
1203+
1204+ if peeked_bytes. len ( ) < TLS_RECORD_HEADER_SIZE {
1205+ // Not enough data for a TLS record header yet.
1206+ // Read all available bytes so rustls can buffer the partial header;
1207+ // this avoids busy-waiting because the kernel buffer is now empty
1208+ // and select() will only wake us when new data arrives.
1209+ return socket. sock_recv ( peeked_bytes. len ( ) , vm) . map_err ( |e| {
1210+ if is_blocking_io_error ( & e, vm) {
1211+ SslError :: WantRead
1212+ } else {
1213+ SslError :: Py ( e)
1214+ }
1215+ } ) ;
1216+ }
1217+
1218+ // Parse the TLS record length from the header.
1219+ let record_body_len =
1220+ u16:: from_be_bytes ( [ peeked_bytes[ 3 ] , peeked_bytes[ 4 ] ] ) as usize ;
1221+ let total_record_size = TLS_RECORD_HEADER_SIZE + record_body_len;
1222+
1223+ let recv_size = if peeked_bytes. len ( ) >= total_record_size {
1224+ // Complete record available — consume exactly one record.
1225+ total_record_size
1226+ } else {
1227+ // Incomplete record — consume everything so the kernel buffer is
1228+ // drained and select() will block until more data arrives.
1229+ peeked_bytes. len ( )
1230+ } ;
1231+
1232+ // Must drop the borrow before calling sock_recv (which re-enters Python).
1233+ drop ( peeked_bytes) ;
1234+ drop ( peeked) ;
1235+
1236+ socket. sock_recv ( recv_size, vm) . map_err ( |e| {
1237+ if is_blocking_io_error ( & e, vm) {
1238+ SslError :: WantRead
1239+ } else {
1240+ SslError :: Py ( e)
1241+ }
1242+ } )
1243+ }
1244+
11641245fn handshake_read_data (
11651246 conn : & mut TlsConnection ,
11661247 socket : & PySSLSocket ,
@@ -1189,23 +1270,27 @@ fn handshake_read_data(
11891270 }
11901271 }
11911272
1192- let data_obj = match socket. sock_recv ( SSL3_RT_MAX_PLAIN_LENGTH , vm) {
1193- Ok ( d) => d,
1194- Err ( e) => {
1195- if is_blocking_io_error ( & e, vm) {
1196- return Err ( SslError :: WantRead ) ;
1197- }
1198- // In socket mode, if recv times out and we're only waiting for read
1199- // (no wants_write), we might be waiting for optional NewSessionTicket in TLS 1.3
1200- // Consider the handshake complete
1201- if !is_bio && !conn. wants_write ( ) {
1202- // Check if it's a timeout exception
1203- if e. fast_isinstance ( vm. ctx . exceptions . timeout_error ) {
1204- // Timeout waiting for optional data - handshake is complete
1205- return Ok ( ( false , false ) ) ;
1273+ let data_obj = if !is_bio {
1274+ // In socket mode, read one TLS record at a time to avoid consuming
1275+ // application data that may arrive alongside the final handshake
1276+ // record. This matches OpenSSL's default (no read-ahead) behaviour
1277+ // and keeps remaining data in the kernel buffer where select() can
1278+ // detect it.
1279+ handshake_recv_one_record ( socket, vm) ?
1280+ } else {
1281+ match socket. sock_recv ( SSL3_RT_MAX_PLAIN_LENGTH , vm) {
1282+ Ok ( d) => d,
1283+ Err ( e) => {
1284+ if is_blocking_io_error ( & e, vm) {
1285+ return Err ( SslError :: WantRead ) ;
1286+ }
1287+ if !conn. wants_write ( ) {
1288+ if e. fast_isinstance ( vm. ctx . exceptions . timeout_error ) {
1289+ return Ok ( ( false , false ) ) ;
1290+ }
12061291 }
1292+ return Err ( SslError :: Py ( e) ) ;
12071293 }
1208- return Err ( SslError :: Py ( e) ) ;
12091294 }
12101295 } ;
12111296
0 commit comments