Skip to content

Commit 1d1dea5

Browse files
committed
feat(tls): Add tls handshake timeout support
Implement timeout controls in the TLS connection process to prevent the client from getting stuck due to the server becoming unresponsive while handling TLS. Refs: #2072
1 parent 5ad89bf commit 1d1dea5

File tree

5 files changed

+56
-8
lines changed

5 files changed

+56
-8
lines changed

tonic/src/transport/channel/service/tls.rs

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::fmt;
2-
use std::sync::Arc;
2+
use std::{sync::Arc, time::Duration};
33

44
use hyper_util::rt::TokioIo;
55
use tokio::io::{AsyncRead, AsyncWrite};
6+
use tokio::time;
67
use tokio_rustls::{
78
rustls::{
89
crypto,
@@ -23,6 +24,7 @@ pub(crate) struct TlsConnector {
2324
config: Arc<ClientConfig>,
2425
domain: Arc<ServerName<'static>>,
2526
assume_http2: bool,
27+
timeout: Option<Duration>,
2628
}
2729

2830
impl TlsConnector {
@@ -34,6 +36,7 @@ impl TlsConnector {
3436
assume_http2: bool,
3537
#[cfg(feature = "tls-native-roots")] with_native_roots: bool,
3638
#[cfg(feature = "tls-webpki-roots")] with_webpki_roots: bool,
39+
timeout: Option<Duration>,
3740
) -> Result<Self, crate::BoxError> {
3841
fn with_provider(
3942
provider: Arc<crypto::CryptoProvider>,
@@ -92,16 +95,22 @@ impl TlsConnector {
9295
config: Arc::new(config),
9396
domain: Arc::new(ServerName::try_from(domain)?.to_owned()),
9497
assume_http2,
98+
timeout,
9599
})
96100
}
97101

98102
pub(crate) async fn connect<I>(&self, io: I) -> Result<BoxedIo, crate::BoxError>
99103
where
100104
I: AsyncRead + AsyncWrite + Send + Unpin + 'static,
101105
{
102-
let io = RustlsConnector::from(self.config.clone())
103-
.connect(self.domain.as_ref().to_owned(), io)
104-
.await?;
106+
let conn_fut =
107+
RustlsConnector::from(self.config.clone()).connect(self.domain.as_ref().to_owned(), io);
108+
let io = match self.timeout {
109+
Some(timeout) => time::timeout(timeout, conn_fut)
110+
.await
111+
.map_err(|_| TlsError::HandshakeTimeout)?,
112+
None => conn_fut.await,
113+
}?;
105114

106115
// Generally we require ALPN to be negotiated, but if the user has
107116
// explicitly set `assume_http2` to true, we'll allow it to be missing.

tonic/src/transport/channel/tls.rs

+11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::transport::{
44
Error,
55
};
66
use http::Uri;
7+
use std::time::Duration;
78
use tokio_rustls::rustls::pki_types::TrustAnchor;
89

910
/// Configures TLS settings for endpoints.
@@ -18,6 +19,7 @@ pub struct ClientTlsConfig {
1819
with_native_roots: bool,
1920
#[cfg(feature = "tls-webpki-roots")]
2021
with_webpki_roots: bool,
22+
timeout: Option<Duration>,
2123
}
2224

2325
impl ClientTlsConfig {
@@ -112,6 +114,14 @@ impl ClientTlsConfig {
112114
config
113115
}
114116

117+
/// Sets the timeout for the TLS handshake.
118+
pub fn timeout(self, timeout: Duration) -> Self {
119+
ClientTlsConfig {
120+
timeout: Some(timeout),
121+
..self
122+
}
123+
}
124+
115125
pub(crate) fn into_tls_connector(self, uri: &Uri) -> Result<TlsConnector, crate::BoxError> {
116126
let domain = match &self.domain {
117127
Some(domain) => domain,
@@ -127,6 +137,7 @@ impl ClientTlsConfig {
127137
self.with_native_roots,
128138
#[cfg(feature = "tls-webpki-roots")]
129139
self.with_webpki_roots,
140+
self.timeout,
130141
)
131142
}
132143
}

tonic/src/transport/server/service/tls.rs

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
use std::{fmt, sync::Arc};
1+
use std::{fmt, sync::Arc, time::Duration};
22

33
use tokio::io::{AsyncRead, AsyncWrite};
4+
use tokio::time;
45
use tokio_rustls::{
56
rustls::{server::WebPkiClientVerifier, RootCertStore, ServerConfig},
67
server::TlsStream,
78
TlsAcceptor as RustlsAcceptor,
89
};
910

1011
use crate::transport::{
11-
service::tls::{convert_certificate_to_pki_types, convert_identity_to_pki_types, ALPN_H2},
12+
service::tls::{
13+
convert_certificate_to_pki_types, convert_identity_to_pki_types, TlsError, ALPN_H2,
14+
},
1215
Certificate, Identity,
1316
};
1417

1518
#[derive(Clone)]
1619
pub(crate) struct TlsAcceptor {
1720
inner: Arc<ServerConfig>,
21+
timeout: Option<Duration>,
1822
}
1923

2024
impl TlsAcceptor {
@@ -23,6 +27,7 @@ impl TlsAcceptor {
2327
client_ca_root: Option<&Certificate>,
2428
client_auth_optional: bool,
2529
ignore_client_order: bool,
30+
timeout: Option<Duration>,
2631
) -> Result<Self, crate::BoxError> {
2732
let builder = ServerConfig::builder();
2833

@@ -48,6 +53,7 @@ impl TlsAcceptor {
4853
config.alpn_protocols.push(ALPN_H2.into());
4954
Ok(Self {
5055
inner: Arc::new(config),
56+
timeout,
5157
})
5258
}
5359

@@ -56,7 +62,14 @@ impl TlsAcceptor {
5662
IO: AsyncRead + AsyncWrite + Unpin,
5763
{
5864
let acceptor = RustlsAcceptor::from(self.inner.clone());
59-
acceptor.accept(io).await.map_err(Into::into)
65+
let accept_fut = acceptor.accept(io);
66+
match self.timeout {
67+
Some(timeout) => time::timeout(timeout, accept_fut)
68+
.await
69+
.map_err(|_| TlsError::HandshakeTimeout)?,
70+
None => accept_fut.await,
71+
}
72+
.map_err(Into::into)
6073
}
6174
}
6275

tonic/src/transport/server/tls.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fmt;
1+
use std::{fmt, time::Duration};
22

33
use super::service::TlsAcceptor;
44
use crate::transport::tls::{Certificate, Identity};
@@ -10,6 +10,7 @@ pub struct ServerTlsConfig {
1010
client_ca_root: Option<Certificate>,
1111
client_auth_optional: bool,
1212
ignore_client_order: bool,
13+
timeout: Option<Duration>,
1314
}
1415

1516
impl fmt::Debug for ServerTlsConfig {
@@ -64,12 +65,24 @@ impl ServerTlsConfig {
6465
}
6566
}
6667

68+
/// Sets the timeout for the TLS handshake.
69+
///
70+
/// # Default
71+
/// By default, this option is set to `None`.
72+
pub fn timeout(self, timeout: Duration) -> Self {
73+
ServerTlsConfig {
74+
timeout: Some(timeout),
75+
..self
76+
}
77+
}
78+
6779
pub(crate) fn tls_acceptor(&self) -> Result<TlsAcceptor, crate::BoxError> {
6880
TlsAcceptor::new(
6981
self.identity.as_ref().unwrap(),
7082
self.client_ca_root.as_ref(),
7183
self.client_auth_optional,
7284
self.ignore_client_order,
85+
self.timeout,
7386
)
7487
}
7588
}

tonic/src/transport/service/tls.rs

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub(crate) enum TlsError {
1515
NativeCertsNotFound,
1616
CertificateParseError,
1717
PrivateKeyParseError,
18+
HandshakeTimeout,
1819
}
1920

2021
impl fmt::Display for TlsError {
@@ -29,6 +30,7 @@ impl fmt::Display for TlsError {
2930
f,
3031
"Error parsing TLS private key - no RSA or PKCS8-encoded keys found."
3132
),
33+
TlsError::HandshakeTimeout => write!(f, "TLS handshake timeout."),
3234
}
3335
}
3436
}

0 commit comments

Comments
 (0)