1use std::{cell::RefCell, io::ErrorKind, net::SocketAddr, rc::Rc};
34
35use mio::{Token, net::TcpStream};
36use rusty_ulid::Ulid;
37use sozu_command::{config::MAX_LOOP_ITERATIONS, logging::ansi_palette};
38
39use crate::{
40 L7Proxy, ProxySession, Readiness, SessionMetrics, StateResult,
41 metrics::names,
42 protocol::{SessionResult, SessionState, mux::parser::CLIENT_PREFACE_MAGIC},
43 sozu_command::ready::Ready,
44 timer::TimeoutContainer,
45};
46
47pub const H2C_PREFACE_LEN: usize = CLIENT_PREFACE_MAGIC.len();
50
51macro_rules! log_context {
56 ($self:expr) => {{
57 let (open, reset, grey, gray, white) = ansi_palette();
58 format!(
59 "{open}HTTP-H2C{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}readiness{reset}={white}{readiness}{reset})\t >>>",
60 open = open,
61 reset = reset,
62 grey = grey,
63 gray = gray,
64 white = white,
65 frontend = $self.frontend_token.0,
66 readiness = $self.frontend_readiness,
67 )
68 }};
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub enum PrefaceVerdict {
74 H2,
76 Http1,
78 NeedMore,
80}
81
82pub fn classify_preface(peeked: &[u8]) -> PrefaceVerdict {
89 let seen = peeked.len().min(H2C_PREFACE_LEN);
90 let verdict = if peeked[..seen] != CLIENT_PREFACE_MAGIC[..seen] {
91 PrefaceVerdict::Http1
92 } else if seen == H2C_PREFACE_LEN {
93 PrefaceVerdict::H2
94 } else {
95 PrefaceVerdict::NeedMore
96 };
97 debug_assert_eq!(
99 verdict == PrefaceVerdict::H2,
100 seen == H2C_PREFACE_LEN && peeked[..seen] == CLIENT_PREFACE_MAGIC[..],
101 "H2 iff all {H2C_PREFACE_LEN} preface bytes were seen and matched"
102 );
103 debug_assert!(
105 verdict != PrefaceVerdict::NeedMore
106 || (seen < H2C_PREFACE_LEN && peeked[..seen] == CLIENT_PREFACE_MAGIC[..seen]),
107 "NeedMore only for a strict, matching prefix of the preface"
108 );
109 verdict
110}
111
112pub struct H2cPrefaceSniff {
116 pub container_frontend_timeout: TimeoutContainer,
117 pub frontend: TcpStream,
118 pub frontend_readiness: Readiness,
119 pub frontend_token: Token,
120 pub session_address: Option<SocketAddr>,
123 pub public_address: SocketAddr,
125 pub request_id: Ulid,
127 pub verdict: Option<PrefaceVerdict>,
129}
130
131impl H2cPrefaceSniff {
132 pub fn new(
136 container_frontend_timeout: TimeoutContainer,
137 frontend: TcpStream,
138 frontend_token: Token,
139 request_id: Ulid,
140 session_address: Option<SocketAddr>,
141 public_address: SocketAddr,
142 event: Ready,
143 ) -> Self {
144 H2cPrefaceSniff {
145 container_frontend_timeout,
146 frontend,
147 frontend_readiness: Readiness {
148 interest: Ready::READABLE | Ready::HUP | Ready::ERROR,
149 event,
150 },
151 frontend_token,
152 session_address,
153 public_address,
154 request_id,
155 verdict: None,
156 }
157 }
158
159 pub fn front_socket(&self) -> &TcpStream {
160 &self.frontend
161 }
162
163 fn readable(&mut self) -> SessionResult {
166 debug_assert!(
167 self.verdict.is_none(),
168 "the sniff must not peek again after it decided"
169 );
170 let mut window = [0u8; H2C_PREFACE_LEN];
171 match self.frontend.peek(&mut window) {
172 Ok(0) => {
173 trace!(
176 "{} socket closed with 0 bytes, closing session",
177 log_context!(self)
178 );
179 SessionResult::Close
180 }
181 Ok(peeked) => {
182 debug_assert!(
183 peeked <= H2C_PREFACE_LEN,
184 "peek cannot return more than the window it was given"
185 );
186 match classify_preface(&window[..peeked]) {
187 PrefaceVerdict::NeedMore => {
188 trace!(
189 "{} {} of {} preface bytes so far, waiting for more",
190 log_context!(self),
191 peeked,
192 H2C_PREFACE_LEN
193 );
194 self.frontend_readiness.event.remove(Ready::READABLE);
197 SessionResult::Continue
198 }
199 verdict => {
200 debug!(
201 "{} connection preface verdict: {:?}",
202 log_context!(self),
203 verdict
204 );
205 if verdict == PrefaceVerdict::H2 {
206 incr!(names::http::H2C_PRIOR_KNOWLEDGE);
207 }
208 self.verdict = Some(verdict);
209 SessionResult::Upgrade
210 }
211 }
212 }
213 Err(e) if e.kind() == ErrorKind::WouldBlock => {
214 self.frontend_readiness.event.remove(Ready::READABLE);
215 SessionResult::Continue
216 }
217 Err(e) if e.kind() == ErrorKind::Interrupted => SessionResult::Continue,
220 Err(e) => {
221 debug!(
222 "{} front socket peek error, closing the connection: {:?}",
223 log_context!(self),
224 e
225 );
226 self.frontend_readiness.reset();
227 SessionResult::Close
228 }
229 }
230 }
231}
232
233impl SessionState for H2cPrefaceSniff {
234 fn ready(
235 &mut self,
236 _session: Rc<RefCell<dyn ProxySession>>,
237 _proxy: Rc<RefCell<dyn L7Proxy>>,
238 _metrics: &mut SessionMetrics,
239 ) -> SessionResult {
240 let mut counter = 0;
241
242 while counter < MAX_LOOP_ITERATIONS {
243 let frontend_interest = self.frontend_readiness.filter_interest();
244
245 trace!("{} {:?}", log_context!(self), self.frontend_readiness);
246
247 if self.frontend_readiness.event.is_hup() && !frontend_interest.is_readable() {
254 return SessionResult::Close;
255 }
256
257 if frontend_interest.is_empty() {
258 break;
259 }
260
261 if frontend_interest.is_readable() {
262 let session_result = self.readable();
263 if session_result != SessionResult::Continue {
264 debug_assert_eq!(
266 session_result == SessionResult::Upgrade,
267 self.verdict.is_some(),
268 "the sniff upgrades exactly when it has decided"
269 );
270 return session_result;
271 }
272 }
273
274 if frontend_interest.is_error() {
275 error!("{} front error, disconnecting", log_context!(self));
276 self.frontend_readiness.interest = Ready::EMPTY;
277 return SessionResult::Close;
278 }
279
280 counter += 1;
281 }
282
283 if counter >= MAX_LOOP_ITERATIONS {
284 error!(
285 "{} handling session went through {} iterations, there's a probable infinite loop bug, closing the connection",
286 log_context!(self),
287 MAX_LOOP_ITERATIONS
288 );
289 incr!(names::http::INFINITE_LOOP_ERROR);
290 self.print_state("");
291 return SessionResult::Close;
292 }
293
294 debug_assert!(
295 self.verdict.is_none(),
296 "an undecided sniff is the only one that keeps waiting"
297 );
298 SessionResult::Continue
299 }
300
301 fn update_readiness(&mut self, token: Token, events: Ready) {
302 if self.frontend_token == token {
303 self.frontend_readiness.event |= events;
304 }
305 }
306
307 fn timeout(&mut self, token: Token, _metrics: &mut SessionMetrics) -> StateResult {
308 if self.frontend_token == token {
309 debug!(
310 "{} request timeout before the connection preface was decided",
311 log_context!(self)
312 );
313 self.container_frontend_timeout.triggered();
314 return StateResult::CloseSession;
315 }
316
317 error!(
318 "{} got timeout for an invalid token: {:?}",
319 log_context!(self),
320 token
321 );
322 StateResult::CloseSession
323 }
324
325 fn cancel_timeouts(&mut self) {
326 self.container_frontend_timeout.cancel();
327 }
328
329 fn print_state(&self, context: &str) {
330 error!(
331 "{} {} Session(H2cPrefaceSniff)\n\tFrontend:\n\t\ttoken: {:?}\treadiness: {:?}\tverdict: {:?}",
332 log_context!(self),
333 context,
334 self.frontend_token,
335 self.frontend_readiness,
336 self.verdict
337 );
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use std::{
344 io::Write,
345 net::{SocketAddr, TcpStream as StdTcpStream},
346 time::{Duration, Instant},
347 };
348
349 use mio::net::TcpListener;
350
351 use super::*;
352
353 #[test]
354 fn full_preface_is_h2() {
355 assert_eq!(
356 classify_preface(&CLIENT_PREFACE_MAGIC[..]),
357 PrefaceVerdict::H2
358 );
359 }
360
361 #[test]
362 fn preface_followed_by_settings_is_h2() {
363 let mut wire = CLIENT_PREFACE_MAGIC.to_vec();
364 wire.extend_from_slice(&[0, 0, 0, 4, 0, 0, 0, 0, 0]);
366 assert_eq!(classify_preface(&wire), PrefaceVerdict::H2);
367 }
368
369 #[test]
370 fn every_strict_prefix_needs_more() {
371 for len in 0..H2C_PREFACE_LEN {
372 assert_eq!(
373 classify_preface(&CLIENT_PREFACE_MAGIC[..len]),
374 PrefaceVerdict::NeedMore,
375 "a {len}-byte prefix of the preface must wait for more"
376 );
377 }
378 }
379
380 #[test]
381 fn a_divergence_at_any_byte_is_http1() {
382 for at in 0..H2C_PREFACE_LEN {
383 let mut wire = CLIENT_PREFACE_MAGIC.to_vec();
384 wire[at] ^= 0x20;
385 for len in (at + 1)..=H2C_PREFACE_LEN {
387 assert_eq!(
388 classify_preface(&wire[..len]),
389 PrefaceVerdict::Http1,
390 "a divergence at byte {at} must decide HTTP/1.x after {len} bytes"
391 );
392 }
393 }
394 }
395
396 #[test]
397 fn http1_requests_are_http1() {
398 for request in [
399 &b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"[..],
400 b"POST /upload HTTP/1.1\r\n",
401 b"PUT",
402 b"PRI * HTTP/1.1\r\n\r\n",
403 b"PRI / HTTP/2.0\r\n\r\nSM\r\n\r\n",
404 ] {
405 assert_eq!(
406 classify_preface(request),
407 PrefaceVerdict::Http1,
408 "{:?} must be served as HTTP/1.x",
409 String::from_utf8_lossy(request)
410 );
411 }
412 }
413
414 fn connected_pair() -> (TcpStream, StdTcpStream) {
416 let listener = TcpListener::bind("127.0.0.1:0".parse::<SocketAddr>().unwrap())
417 .expect("bind loopback listener");
418 let client = StdTcpStream::connect(listener.local_addr().unwrap())
419 .expect("connect to loopback listener");
420 let deadline = Instant::now() + Duration::from_secs(5);
421 loop {
422 match listener.accept() {
423 Ok((server, _)) => return (server, client),
424 Err(e) if e.kind() == ErrorKind::WouldBlock && Instant::now() < deadline => {
425 std::thread::sleep(Duration::from_millis(5));
426 }
427 Err(e) => panic!("accept failed: {e}"),
428 }
429 }
430 }
431
432 fn sniff_over(frontend: TcpStream) -> H2cPrefaceSniff {
433 H2cPrefaceSniff::new(
434 TimeoutContainer::new_empty(Duration::from_secs(10)),
435 frontend,
436 Token(1),
437 Ulid::generate(),
438 None,
439 "127.0.0.1:80".parse().unwrap(),
440 Ready::READABLE,
441 )
442 }
443
444 fn readable_after(sniff: &mut H2cPrefaceSniff, want: usize) -> SessionResult {
446 let deadline = Instant::now() + Duration::from_secs(5);
447 let mut probe = [0u8; H2C_PREFACE_LEN];
448 while sniff.frontend.peek(&mut probe).unwrap_or(0) < want {
449 assert!(Instant::now() < deadline, "bytes never arrived");
450 std::thread::sleep(Duration::from_millis(5));
451 }
452 sniff.frontend_readiness.event.insert(Ready::READABLE);
453 sniff.readable()
454 }
455
456 #[test]
459 fn a_split_preface_waits_then_decides_h2_without_consuming() {
460 let (frontend, mut client) = connected_pair();
461 let mut sniff = sniff_over(frontend);
462
463 client.write_all(&CLIENT_PREFACE_MAGIC[..10]).unwrap();
464 assert_eq!(readable_after(&mut sniff, 10), SessionResult::Continue);
465 assert_eq!(sniff.verdict, None);
466 assert!(
467 !sniff.frontend_readiness.event.is_readable(),
468 "a strict prefix must park READABLE until the next edge"
469 );
470
471 client.write_all(&CLIENT_PREFACE_MAGIC[10..]).unwrap();
472 assert_eq!(
473 readable_after(&mut sniff, H2C_PREFACE_LEN),
474 SessionResult::Upgrade
475 );
476 assert_eq!(sniff.verdict, Some(PrefaceVerdict::H2));
477
478 let mut left = [0u8; H2C_PREFACE_LEN];
479 assert_eq!(
480 sniff.frontend.peek(&mut left).unwrap(),
481 H2C_PREFACE_LEN,
482 "the sniff must leave every preface byte in the socket"
483 );
484 assert_eq!(&left, CLIENT_PREFACE_MAGIC);
485 }
486
487 #[test]
488 fn an_http1_request_decides_http1_without_consuming() {
489 let (frontend, mut client) = connected_pair();
490 let mut sniff = sniff_over(frontend);
491
492 let request = b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
493 client.write_all(request).unwrap();
494 assert_eq!(readable_after(&mut sniff, 1), SessionResult::Upgrade);
495 assert_eq!(sniff.verdict, Some(PrefaceVerdict::Http1));
496
497 let mut left = vec![0u8; request.len()];
498 let deadline = Instant::now() + Duration::from_secs(5);
499 while sniff.frontend.peek(&mut left).unwrap() < request.len() {
500 assert!(Instant::now() < deadline, "request bytes never arrived");
501 std::thread::sleep(Duration::from_millis(5));
502 }
503 assert_eq!(&left[..], &request[..]);
504 }
505
506 #[test]
507 fn a_close_before_any_byte_closes_the_session() {
508 let (frontend, client) = connected_pair();
509 let mut sniff = sniff_over(frontend);
510 drop(client);
511
512 let deadline = Instant::now() + Duration::from_secs(5);
513 let result = loop {
514 sniff.frontend_readiness.event.insert(Ready::READABLE);
515 match sniff.readable() {
516 SessionResult::Continue if Instant::now() < deadline => {
517 std::thread::sleep(Duration::from_millis(5));
518 }
519 other => break other,
520 }
521 };
522 assert_eq!(result, SessionResult::Close);
523 assert_eq!(sniff.verdict, None);
524 }
525}