@@ -1161,6 +1161,83 @@ 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 ( socket : & PySSLSocket , vm : & VirtualMachine ) -> SslResult < PyObjectRef > {
1182+ // Peek at what is available without consuming it.
1183+ let peeked_obj = match socket. sock_peek ( SSL3_RT_MAX_PLAIN_LENGTH , vm) {
1184+ Ok ( d) => d,
1185+ Err ( e) => {
1186+ if is_blocking_io_error ( & e, vm) {
1187+ return Err ( SslError :: WantRead ) ;
1188+ }
1189+ return Err ( SslError :: Py ( e) ) ;
1190+ }
1191+ } ;
1192+
1193+ let peeked = ArgBytesLike :: try_from_object ( vm, peeked_obj)
1194+ . map_err ( |_| SslError :: Syscall ( "Expected bytes-like object from peek" . to_string ( ) ) ) ?;
1195+ let peeked_bytes = peeked. borrow_buf ( ) ;
1196+
1197+ if peeked_bytes. is_empty ( ) {
1198+ return Err ( SslError :: WantRead ) ;
1199+ }
1200+
1201+ if peeked_bytes. len ( ) < TLS_RECORD_HEADER_SIZE {
1202+ // Not enough data for a TLS record header yet.
1203+ // Read all available bytes so rustls can buffer the partial header;
1204+ // this avoids busy-waiting because the kernel buffer is now empty
1205+ // and select() will only wake us when new data arrives.
1206+ return socket. sock_recv ( peeked_bytes. len ( ) , vm) . map_err ( |e| {
1207+ if is_blocking_io_error ( & e, vm) {
1208+ SslError :: WantRead
1209+ } else {
1210+ SslError :: Py ( e)
1211+ }
1212+ } ) ;
1213+ }
1214+
1215+ // Parse the TLS record length from the header.
1216+ let record_body_len = u16:: from_be_bytes ( [ peeked_bytes[ 3 ] , peeked_bytes[ 4 ] ] ) as usize ;
1217+ let total_record_size = TLS_RECORD_HEADER_SIZE + record_body_len;
1218+
1219+ let recv_size = if peeked_bytes. len ( ) >= total_record_size {
1220+ // Complete record available — consume exactly one record.
1221+ total_record_size
1222+ } else {
1223+ // Incomplete record — consume everything so the kernel buffer is
1224+ // drained and select() will block until more data arrives.
1225+ peeked_bytes. len ( )
1226+ } ;
1227+
1228+ // Must drop the borrow before calling sock_recv (which re-enters Python).
1229+ drop ( peeked_bytes) ;
1230+ drop ( peeked) ;
1231+
1232+ socket. sock_recv ( recv_size, vm) . map_err ( |e| {
1233+ if is_blocking_io_error ( & e, vm) {
1234+ SslError :: WantRead
1235+ } else {
1236+ SslError :: Py ( e)
1237+ }
1238+ } )
1239+ }
1240+
11641241fn handshake_read_data (
11651242 conn : & mut TlsConnection ,
11661243 socket : & PySSLSocket ,
@@ -1189,23 +1266,27 @@ fn handshake_read_data(
11891266 }
11901267 }
11911268
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 ) ) ;
1269+ let data_obj = if !is_bio {
1270+ // In socket mode, read one TLS record at a time to avoid consuming
1271+ // application data that may arrive alongside the final handshake
1272+ // record. This matches OpenSSL's default (no read-ahead) behaviour
1273+ // and keeps remaining data in the kernel buffer where select() can
1274+ // detect it.
1275+ handshake_recv_one_record ( socket, vm) ?
1276+ } else {
1277+ match socket. sock_recv ( SSL3_RT_MAX_PLAIN_LENGTH , vm) {
1278+ Ok ( d) => d,
1279+ Err ( e) => {
1280+ if is_blocking_io_error ( & e, vm) {
1281+ return Err ( SslError :: WantRead ) ;
1282+ }
1283+ if !conn. wants_write ( ) {
1284+ if e. fast_isinstance ( vm. ctx . exceptions . timeout_error ) {
1285+ return Ok ( ( false , false ) ) ;
1286+ }
12061287 }
1288+ return Err ( SslError :: Py ( e) ) ;
12071289 }
1208- return Err ( SslError :: Py ( e) ) ;
12091290 }
12101291 } ;
12111292
0 commit comments