Skip to content

Commit 88518a8

Browse files
committed
tls: Avoid holding a large buffer across await (#1879)
The TLS detection code holds a ~512B buffer when peeking on a socket to parse an SNI value from a client connection. This is done to avoid a heap allocation--this optimization is probably premature. Per a conversation with @carllerche, holding data across an `await` is a painful anti-pattern that can have severe performance impacts, since the generated `Future` ends up holding all of this data (on the stack). Instead, our default behavior should be to use a heap-allocated data structure for any data that crosses an async `await`. This change eliminates the stack-allocated buffer in our TLS server in favor of a heap-allocated buffer. Signed-off-by: Oliver Gould <[email protected]>
1 parent 090d82d commit 88518a8

File tree

1 file changed

+12
-11
lines changed

1 file changed

+12
-11
lines changed

linkerd/tls/src/server.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,13 @@ pub struct DetectTls<T, P, L, N> {
9595
inner: N,
9696
}
9797

98-
// The initial peek buffer is statically allocated on the stack and is fairly small; but it is
99-
// large enough to hold the ~300B ClientHello sent by proxies.
98+
// The initial peek buffer is fairly small so that we can avoid allocating more
99+
// data then we need; but it is large enough to hold the ~300B ClientHello sent
100+
// by proxies.
100101
const PEEK_CAPACITY: usize = 512;
101102

102-
// A larger fallback buffer is allocated onto the heap if the initial peek buffer is
103-
// insufficient. This is the same value used in HTTP detection.
103+
// A larger fallback buffer is allocated onto the heap if the initial peek
104+
// buffer is insufficient. This is the same value used in HTTP detection.
104105
const BUFFER_CAPACITY: usize = 8192;
105106

106107
impl<P, L, N> NewDetectTls<P, L, N> {
@@ -219,18 +220,18 @@ async fn detect_sni<I>(mut io: I) -> io::Result<(Option<ServerId>, DetectIo<I>)>
219220
where
220221
I: io::Peek + io::AsyncRead + io::AsyncWrite + Send + Sync + Unpin,
221222
{
222-
// First, try to use MSG_PEEK to read the SNI from the TLS ClientHello.
223-
// Because peeked data does not need to be retained, we use a static
224-
// buffer to prevent needless heap allocation.
223+
// First, try to use MSG_PEEK to read the SNI from the TLS ClientHello. We
224+
// use a heap-allocated buffer to avoid creating a large `Future` (since we
225+
// need to hold the buffer across an await).
225226
//
226-
// Anecdotally, the ClientHello sent by Linkerd proxies is <300B. So a
227-
// ~500B byte buffer is more than enough.
228-
let mut buf = [0u8; PEEK_CAPACITY];
227+
// Anecdotally, the ClientHello sent by Linkerd proxies is <300B. So a ~500B
228+
// byte buffer is more than enough.
229+
let mut buf = BytesMut::with_capacity(PEEK_CAPACITY);
229230
let sz = io.peek(&mut buf).await?;
230231
debug!(sz, "Peeked bytes from TCP stream");
231232
// Peek may return 0 bytes if the socket is not peekable.
232233
if sz > 0 {
233-
match client_hello::parse_sni(&buf) {
234+
match client_hello::parse_sni(buf.as_ref()) {
234235
Ok(sni) => {
235236
return Ok((sni, EitherIo::Left(io)));
236237
}

0 commit comments

Comments
 (0)