Skip to content

Commit e7c2afd

Browse files
authored
feat(http/detect)!: error when the socket is closed (#3721)
* refactor(http): consolidate HTTP protocol detection Linkerd's HTTP protocol detection logic is spread across a few crates: the linkerd-detect crate is generic over the actual protocol detection logic, and the linkerd-proxy-http crate provides an implementation. There are no other implemetations of the Detect interface. This leads to gnarly type signatures in the form `Result<Option<http::Variant>, DetectTimeoutError>`: simultaneously verbose and not particularly informative (what does the None case mean exactly). This commit introduces a new crate, `linkerd-http-detect`, consolidating this logic and removes the prior implementations. The admin, inbound, and outbound stacks are updated to use these new types. This work is done in anticipation of introducing metrics that report HTTP detection behavior. There are no functional changes. * feat(http/detect)!: error when the socket is closed When a proxy does protocol detection, the initial read may indicate that the connection was closed by the client with no data being written to the socket. In such a case, the proxy continues to process the connection as if may be proxied, but we expect this to fail immediately. This can lead to unexpected proxy behavior: for example, inbound proxies may report policy denials. To address this, this change surfaces an error (as if the read call failed). This could, theoretically, impact some bizarre clients that initiate half-open connections. These corner cases can use explicit opaque policies to bypass detection.
1 parent 606b51b commit e7c2afd

File tree

3 files changed

+9
-9
lines changed

3 files changed

+9
-9
lines changed

linkerd/app/admin/src/stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ impl Config {
166166
}
167167
// If the connection failed HTTP detection, check if we detected TLS for
168168
// another target. This might indicate that the client is confused/stale.
169-
http::Detection::Empty | http::Detection::NotHttp => match tcp.tls {
169+
http::Detection::NotHttp => match tcp.tls {
170170
tls::ConditionalServerTls::Some(tls::ServerTls::Passthru { sni }) => {
171171
Err(UnexpectedSni(sni, tcp.client).into())
172172
}

linkerd/app/inbound/src/detect.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,7 @@ impl Inbound<svc::ArcNewTcp<Http, io::BoxedIo>> {
109109
|(detected, Detect { tls, .. })| -> Result<_, Infallible> {
110110
match detected {
111111
http::Detection::Http(http) => Ok(svc::Either::A(Http { http, tls })),
112-
http::Detection::Empty | http::Detection::NotHttp => {
113-
Ok(svc::Either::B(tls))
114-
}
112+
http::Detection::NotHttp => Ok(svc::Either::B(tls)),
115113
// When HTTP detection fails, forward the connection to the application as
116114
// an opaque TCP stream.
117115
http::Detection::ReadTimeout(timeout) => {

linkerd/http/detect/src/lib.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ pub struct DetectParams {
1818

1919
#[derive(Debug, Clone)]
2020
pub enum Detection {
21-
Empty,
2221
NotHttp,
2322
Http(Variant),
2423
ReadTimeout(time::Duration),
@@ -154,15 +153,18 @@ async fn detect<I: io::AsyncRead + Send + Unpin + 'static>(
154153
) -> io::Result<Detection> {
155154
debug_assert!(buf.capacity() > 0, "buffer must have capacity");
156155

157-
trace!(capacity = READ_CAPACITY, timeout = ?read_timeout, "Reading");
156+
trace!(capacity = buf.capacity(), timeout = ?read_timeout, "Reading");
158157
let sz = match time::timeout(read_timeout, io.read_buf(buf)).await {
159158
Ok(res) => res?,
160159
Err(_) => return Ok(Detection::ReadTimeout(read_timeout)),
161160
};
162161

163162
trace!(sz, "Read");
164163
if sz == 0 {
165-
return Ok(Detection::Empty);
164+
return Err(io::Error::new(
165+
io::ErrorKind::UnexpectedEof,
166+
"socket closed before protocol detection",
167+
));
166168
}
167169

168170
// HTTP/2 checking is faster because it's a simple string match. If we
@@ -300,8 +302,8 @@ mod tests {
300302
};
301303
let mut buf = BytesMut::with_capacity(1024);
302304
let mut io = io::Builder::new().build();
303-
let kind = detect(params, &mut io, &mut buf).await.unwrap();
304-
assert!(matches!(kind, Detection::Empty), "{kind:?}");
305+
let err = detect(params, &mut io, &mut buf).await.unwrap_err();
306+
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof, "{err:?}");
305307
assert_eq!(&buf[..], b"");
306308
}
307309
}

0 commit comments

Comments
 (0)