@@ -12,9 +12,11 @@ use std::time::Duration;
1212use rustls:: server:: AcceptedAlert ;
1313use rustls:: { ServerConfig , ServerConnection } ;
1414use tokio:: io:: { AsyncBufRead , AsyncRead , AsyncWrite , ReadBuf } ;
15+ use tokio:: time:: Instant ;
1516
1617use crate :: common:: {
17- HandshakeFuture , IoSession , MidHandshake , Stream , SyncReadAdapter , SyncWriteAdapter , TlsState ,
18+ HandshakeFuture , IoSession , MidHandshake , Stream , SyncReadAdapter , SyncWriteAdapter , Timeout ,
19+ TlsState ,
1820} ;
1921
2022/// A wrapper around a `rustls::ServerConfig`, providing an async `accept` method.
@@ -39,9 +41,11 @@ impl TlsAcceptor {
3941 /// `None` disables the handshake timeout.
4042 ///
4143 /// The timeout applies to handshakes started via [`TlsAcceptor::accept`] and
42- /// [`TlsAcceptor::accept_with`]. It does not apply to handshakes resumed via
43- /// [`LazyConfigAcceptor`] / [`StartHandshake::into_stream`], which proceed
44- /// without a timeout.
44+ /// [`TlsAcceptor::accept_with`]. For the lazy-acceptor flow, set the timeout
45+ /// on the [`LazyConfigAcceptor`] itself via
46+ /// [`LazyConfigAcceptor::with_handshake_timeout`]. The deadline established
47+ /// there is inherited by the [`Accept`] returned from
48+ /// [`StartHandshake::into_stream`], so a single timeout covers both phases.
4549 pub fn with_handshake_timeout ( mut self , timeout : Option < Duration > ) -> Self {
4650 self . handshake_timeout = timeout;
4751 self
@@ -97,6 +101,7 @@ pub struct LazyConfigAcceptor<IO> {
97101 acceptor : rustls:: server:: Acceptor ,
98102 io : Option < IO > ,
99103 alert : Option < ( rustls:: Error , AcceptedAlert ) > ,
104+ timeout : Option < Timeout > ,
100105}
101106
102107impl < IO > LazyConfigAcceptor < IO >
@@ -109,9 +114,30 @@ where
109114 acceptor,
110115 io : Some ( io) ,
111116 alert : None ,
117+ timeout : None ,
112118 }
113119 }
114120
121+ /// Set the maximum amount of time to allow for the TLS handshake.
122+ ///
123+ /// `None` disables the handshake timeout.
124+ ///
125+ /// The deadline is fixed to `Instant::now() + duration` when this builder
126+ /// is called and spans both phases of the lazy-acceptor flow:
127+ ///
128+ /// 1. The ClientHello phase driven by this `LazyConfigAcceptor`.
129+ /// 2. The post-ClientHello phase driven by the [`Accept`] returned from
130+ /// [`StartHandshake::into_stream`].
131+ ///
132+ /// Both phases race against the same wall-clock deadline, so a single
133+ /// timeout covers the full handshake. To set a timeout on handshakes
134+ /// driven via [`TlsAcceptor::accept`] instead, use
135+ /// [`TlsAcceptor::with_handshake_timeout`].
136+ pub fn with_handshake_timeout ( mut self , timeout : Option < Duration > ) -> Self {
137+ self . timeout = timeout. map ( Timeout :: from_duration) ;
138+ self
139+ }
140+
115141 /// Takes back the client connection. Will return `None` if called more than once or if the
116142 /// connection has been accepted.
117143 ///
@@ -181,7 +207,7 @@ where
181207 match alert. write ( & mut SyncWriteAdapter { io, cx } ) {
182208 Err ( e) if e. kind ( ) == io:: ErrorKind :: WouldBlock => {
183209 this. alert = Some ( ( err, alert) ) ;
184- return Poll :: Pending ;
210+ return poll_pending_or_timeout ( & mut this . timeout , cx ) ;
185211 }
186212 Ok ( 0 ) | Err ( _) => {
187213 return Poll :: Ready ( Err ( io:: Error :: new ( io:: ErrorKind :: InvalidData , err) ) ) ;
@@ -197,14 +223,20 @@ where
197223 match this. acceptor . read_tls ( & mut reader) {
198224 Ok ( 0 ) => return Err ( io:: ErrorKind :: UnexpectedEof . into ( ) ) . into ( ) ,
199225 Ok ( _) => { }
200- Err ( e) if e. kind ( ) == io:: ErrorKind :: WouldBlock => return Poll :: Pending ,
226+ Err ( e) if e. kind ( ) == io:: ErrorKind :: WouldBlock => {
227+ return poll_pending_or_timeout ( & mut this. timeout , cx) ;
228+ }
201229 Err ( e) => return Err ( e) . into ( ) ,
202230 }
203231
204232 match this. acceptor . accept ( ) {
205233 Ok ( Some ( accepted) ) => {
206234 let io = this. io . take ( ) . unwrap ( ) ;
207- return Poll :: Ready ( Ok ( StartHandshake { accepted, io } ) ) ;
235+ return Poll :: Ready ( Ok ( StartHandshake {
236+ accepted,
237+ io,
238+ deadline : this. timeout . as_ref ( ) . map ( Timeout :: deadline) ,
239+ } ) ) ;
208240 }
209241 Ok ( None ) => { }
210242 Err ( ( err, alert) ) => {
@@ -215,6 +247,25 @@ where
215247 }
216248}
217249
250+ /// Returns `Poll::Pending`, unless the timeout has elapsed, in which case a `TimedOut`
251+ /// error is returned. With no timeout configured, always returns `Poll::Pending`.
252+ fn poll_pending_or_timeout < T > (
253+ timeout : & mut Option < Timeout > ,
254+ cx : & mut Context < ' _ > ,
255+ ) -> Poll < Result < T , io:: Error > > {
256+ let timeout = match timeout {
257+ Some ( timeout) => timeout,
258+ None => return Poll :: Pending ,
259+ } ;
260+ if timeout. poll_deadline ( cx) . is_pending ( ) {
261+ return Poll :: Pending ;
262+ }
263+ Poll :: Ready ( Err ( io:: Error :: new (
264+ io:: ErrorKind :: TimedOut ,
265+ "TLS handshake timed out" ,
266+ ) ) )
267+ }
268+
218269/// An incoming connection received through [`LazyConfigAcceptor`].
219270///
220271/// This contains the generic `IO` asynchronous transport,
@@ -226,6 +277,10 @@ where
226277pub struct StartHandshake < IO > {
227278 pub accepted : rustls:: server:: Accepted ,
228279 pub io : IO ,
280+ // Handshake deadline inherited from the LazyConfigAcceptor, if any.
281+ // Propagated into the Accept returned by into_stream so the timeout
282+ // spans both phases of the handshake.
283+ pub ( crate ) deadline : Option < Instant > ,
229284}
230285
231286impl < IO > StartHandshake < IO >
@@ -237,6 +292,7 @@ where
237292 Self {
238293 accepted,
239294 io : transport,
295+ deadline : None ,
240296 }
241297 }
242298
@@ -255,28 +311,28 @@ where
255311 let mut conn = match self . accepted . into_connection ( config) {
256312 Ok ( conn) => conn,
257313 Err ( ( error, alert) ) => {
258- return Accept ( HandshakeFuture :: new (
314+ return Accept ( HandshakeFuture :: from_deadline (
259315 MidHandshake :: SendAlert {
260316 io : self . io ,
261317 alert,
262318 // TODO(eliza): should this really return an `io::Error`?
263319 // Probably not...
264320 error : io:: Error :: new ( io:: ErrorKind :: InvalidData , error) ,
265321 } ,
266- None ,
322+ self . deadline ,
267323 ) ) ;
268324 }
269325 } ;
270326 f ( & mut conn) ;
271327
272- Accept ( HandshakeFuture :: new (
328+ Accept ( HandshakeFuture :: from_deadline (
273329 MidHandshake :: Handshaking ( TlsStream {
274330 session : conn,
275331 io : self . io ,
276332 state : TlsState :: Stream ,
277333 need_flush : false ,
278334 } ) ,
279- None ,
335+ self . deadline ,
280336 ) )
281337 }
282338}
0 commit comments