1use std::{cell::RefCell, io::ErrorKind, net::SocketAddr, rc::Rc, time::Instant};
10
11use mio::{Token, net::TcpStream};
12use rustls::{Error as RustlsError, ServerConnection};
13use rusty_ulid::Ulid;
14use sozu_command::{
15 config::MAX_LOOP_ITERATIONS,
16 logging::{LogContext, ansi_palette},
17};
18
19use crate::metrics::names;
20use crate::{
21 Readiness, Ready, SessionMetrics, SessionResult, StateResult, protocol::SessionState,
22 timer::TimeoutContainer,
23};
24
25macro_rules! log_context {
33 ($self:expr) => {{
34 let (open, reset, grey, gray, white) = ansi_palette();
35 format!(
36 "{gray}{ctx}{reset}\t{open}RUSTLS{reset}\t{grey}Session{reset}({gray}sni_bytes{reset}={white}{sni_bytes:?}{reset}, {gray}alpn_bytes{reset}={white}{alpn_bytes:?}{reset}, {gray}version{reset}={white}{version:?}{reset}, {gray}source{reset}={white}{source:?}{reset}, {gray}frontend{reset}={white}{frontend}{reset}, {gray}readiness{reset}={white}{readiness}{reset})\t >>>",
37 open = open,
38 reset = reset,
39 grey = grey,
40 gray = gray,
41 white = white,
42 ctx = $self.log_context(),
43 sni_bytes = $self.session.server_name().map(str::len),
44 alpn_bytes = $self.session.alpn_protocol().map(|bytes| bytes.len()),
45 version = $self.session.protocol_version(),
46 source = $self
47 .peer_address
48 .map(|addr| addr.to_string())
49 .unwrap_or_else(|| "<none>".to_string()),
50 frontend = $self.frontend_token.0,
51 readiness = $self.frontend_readiness,
52 )
53 }};
54}
55
56pub enum TlsState {
57 Initial,
58 Handshake,
59 Established,
60 Error,
61}
62
63pub struct TlsHandshake {
64 pub container_frontend_timeout: TimeoutContainer,
65 pub frontend_readiness: Readiness,
66 frontend_token: Token,
67 pub peer_address: Option<SocketAddr>,
68 pub request_id: Ulid,
69 pub session: ServerConnection,
70 pub stream: TcpStream,
71 handshake_started_at: Option<Instant>,
76}
77
78impl TlsHandshake {
79 pub fn new(
86 container_frontend_timeout: TimeoutContainer,
87 session: ServerConnection,
88 stream: TcpStream,
89 frontend_token: Token,
90 request_id: Ulid,
91 peer_address: Option<SocketAddr>,
92 ) -> TlsHandshake {
93 TlsHandshake {
94 container_frontend_timeout,
95 frontend_readiness: Readiness {
96 interest: Ready::READABLE | Ready::HUP | Ready::ERROR,
97 event: Ready::EMPTY,
98 },
99 frontend_token,
100 peer_address,
101 request_id,
102 session,
103 stream,
104 handshake_started_at: None,
105 }
106 }
107
108 fn record_handshake_duration_ms(&mut self) -> Option<u128> {
114 let was_anchored = self.handshake_started_at.is_some();
115 let elapsed = self
116 .handshake_started_at
117 .take()
118 .map(|t| t.elapsed().as_millis());
119 debug_assert!(
123 self.handshake_started_at.is_none(),
124 "handshake anchor must be cleared after recording the duration"
125 );
126 debug_assert_eq!(
127 elapsed.is_some(),
128 was_anchored,
129 "a duration is returned iff the handshake had been anchored"
130 );
131 elapsed
132 }
133
134 pub fn readable(&mut self) -> SessionResult {
135 self.handshake_started_at.get_or_insert_with(Instant::now);
140 debug_assert!(
142 self.handshake_started_at.is_some(),
143 "handshake anchor must be set before driving TLS I/O"
144 );
145
146 let was_handshaking = self.session.is_handshaking();
150
151 let mut can_read = true;
152
153 loop {
154 let mut can_work = false;
155
156 if self.session.wants_read() && can_read {
157 can_work = true;
158
159 match self.session.read_tls(&mut self.stream) {
160 Ok(0) => {
161 error!("{} Connection closed during handshake", log_context!(self));
162 return SessionResult::Close;
163 }
164 Ok(_) => {}
165 Err(e) => match e.kind() {
166 ErrorKind::WouldBlock => {
167 self.frontend_readiness.event.remove(Ready::READABLE);
168 can_read = false
169 }
170 _ => {
171 error!(
172 "{} Could not perform handshake: {:?}",
173 log_context!(self),
174 e
175 );
176 return SessionResult::Close;
177 }
178 },
179 }
180
181 if let Err(e) = self.session.process_new_packets() {
182 self.log_handshake_error(&e);
183 return SessionResult::Close;
184 }
185 }
186
187 if !can_work {
188 break;
189 }
190 }
191
192 debug_assert!(
195 was_handshaking || !self.session.is_handshaking(),
196 "rustls handshake must not regress from finished back to handshaking"
197 );
198
199 if !self.session.wants_read() {
202 self.frontend_readiness.interest.remove(Ready::READABLE);
203 }
204 debug_assert!(
205 self.session.wants_read() || !self.frontend_readiness.interest.is_readable(),
206 "READABLE interest must be cleared once rustls stops wanting reads"
207 );
208
209 if self.session.wants_write() {
210 self.frontend_readiness.interest.insert(Ready::WRITABLE);
211 }
212
213 if self.session.is_handshaking() {
214 SessionResult::Continue
215 } else {
216 if self.session.wants_write() {
218 SessionResult::Continue
219 } else {
220 debug_assert!(
223 !self.session.is_handshaking() && !self.session.wants_write(),
224 "Upgrade requires a completed handshake with no pending output"
225 );
226 self.frontend_readiness.interest.insert(Ready::READABLE);
227 self.frontend_readiness.event.insert(Ready::READABLE);
228 self.frontend_readiness.interest.insert(Ready::WRITABLE);
229 if let Some(elapsed_ms) = self.record_handshake_duration_ms() {
230 time!(names::tls::HANDSHAKE_MS, elapsed_ms);
231 }
232 SessionResult::Upgrade
233 }
234 }
235 }
236
237 pub fn writable(&mut self) -> SessionResult {
238 self.handshake_started_at.get_or_insert_with(Instant::now);
240 debug_assert!(
241 self.handshake_started_at.is_some(),
242 "handshake anchor must be set before driving TLS I/O"
243 );
244
245 let was_handshaking = self.session.is_handshaking();
247
248 let mut can_write = true;
249
250 loop {
251 let mut can_work = false;
252
253 if self.session.wants_write() && can_write {
254 can_work = true;
255
256 match self.session.write_tls(&mut self.stream) {
257 Ok(_) => {}
258 Err(e) => match e.kind() {
259 ErrorKind::WouldBlock => {
260 self.frontend_readiness.event.remove(Ready::WRITABLE);
261 can_write = false
262 }
263 _ => {
264 error!(
265 "{} Could not perform handshake: {:?}",
266 log_context!(self),
267 e
268 );
269 return SessionResult::Close;
270 }
271 },
272 }
273
274 if let Err(e) = self.session.process_new_packets() {
275 self.log_handshake_error(&e);
276 return SessionResult::Close;
277 }
278 }
279
280 if !can_work {
281 break;
282 }
283 }
284
285 debug_assert!(
288 was_handshaking || !self.session.is_handshaking(),
289 "rustls handshake must not regress from finished back to handshaking"
290 );
291
292 if !self.session.wants_write() {
295 self.frontend_readiness.interest.remove(Ready::WRITABLE);
296 }
297 debug_assert!(
298 self.session.wants_write() || !self.frontend_readiness.interest.is_writable(),
299 "WRITABLE interest must be cleared once rustls stops wanting writes"
300 );
301
302 if self.session.wants_read() {
303 self.frontend_readiness.interest.insert(Ready::READABLE);
304 }
305
306 if self.session.is_handshaking() {
307 SessionResult::Continue
308 } else if self.session.wants_read() {
309 debug_assert!(
312 !self.session.is_handshaking(),
313 "Upgrade requires a completed handshake"
314 );
315 self.frontend_readiness.interest.insert(Ready::READABLE);
316 if let Some(elapsed_ms) = self.record_handshake_duration_ms() {
317 time!(names::tls::HANDSHAKE_MS, elapsed_ms);
318 }
319 SessionResult::Upgrade
320 } else {
321 debug_assert!(
322 !self.session.is_handshaking(),
323 "Upgrade requires a completed handshake"
324 );
325 self.frontend_readiness.interest.insert(Ready::WRITABLE);
326 self.frontend_readiness.interest.insert(Ready::READABLE);
327 if let Some(elapsed_ms) = self.record_handshake_duration_ms() {
328 time!(names::tls::HANDSHAKE_MS, elapsed_ms);
329 }
330 SessionResult::Upgrade
331 }
332 }
333
334 pub fn log_context(&self) -> LogContext<'_> {
335 LogContext {
336 session_id: self.request_id,
337 request_id: None,
338 cluster_id: None,
339 backend_id: None,
340 }
341 }
342
343 pub fn front_socket(&self) -> &TcpStream {
344 &self.stream
345 }
346
347 fn log_handshake_error(&self, err: &RustlsError) {
364 let reason = handshake_failure_reason(err);
365 debug_assert!(
369 reason.starts_with("tls.handshake.failed."),
370 "handshake failure metric {reason} escaped the tls.handshake.failed. namespace"
371 );
372 match err {
373 RustlsError::AlertReceived(_) => debug!(
374 "{} Could not perform handshake: {:?}",
375 log_context!(self),
376 err
377 ),
378 RustlsError::PeerIncompatible(_)
379 | RustlsError::PeerMisbehaved(_)
380 | RustlsError::InvalidMessage(_)
381 | RustlsError::InappropriateMessage { .. }
382 | RustlsError::InappropriateHandshakeMessage { .. }
383 | RustlsError::PeerSentOversizedRecord
384 | RustlsError::NoApplicationProtocol
385 | RustlsError::InvalidCertificate(_)
386 | RustlsError::DecryptError
387 | RustlsError::NoCertificatesPresented => warn!(
388 "{} Could not perform handshake: {:?}",
389 log_context!(self),
390 err
391 ),
392 _ => error!(
393 "{} Could not perform handshake: {:?}",
394 log_context!(self),
395 err
396 ),
397 }
398 count!(reason, 1);
399 }
400}
401
402fn handshake_failure_reason(err: &RustlsError) -> &'static str {
408 match err {
409 RustlsError::AlertReceived(_) => "tls.handshake.failed.alert_received",
410 RustlsError::PeerIncompatible(_) => "tls.handshake.failed.peer_incompatible",
411 RustlsError::PeerMisbehaved(_) => "tls.handshake.failed.peer_misbehaved",
412 RustlsError::InvalidMessage(_) => "tls.handshake.failed.invalid_message",
413 RustlsError::InappropriateMessage { .. } => "tls.handshake.failed.inappropriate_message",
414 RustlsError::InappropriateHandshakeMessage { .. } => {
415 "tls.handshake.failed.inappropriate_handshake_message"
416 }
417 RustlsError::PeerSentOversizedRecord => "tls.handshake.failed.oversized_record",
418 RustlsError::NoApplicationProtocol => "tls.handshake.failed.no_alpn",
419 RustlsError::InvalidCertificate(_) => "tls.handshake.failed.invalid_certificate",
420 RustlsError::DecryptError => "tls.handshake.failed.decrypt_error",
421 RustlsError::NoCertificatesPresented => "tls.handshake.failed.no_certificates_present",
422 _ => "tls.handshake.failed.other",
423 }
424}
425
426impl SessionState for TlsHandshake {
427 fn ready(
428 &mut self,
429 _session: Rc<RefCell<dyn crate::ProxySession>>,
430 _proxy: Rc<RefCell<dyn crate::L7Proxy>>,
431 _metrics: &mut SessionMetrics,
432 ) -> SessionResult {
433 let mut counter = 0;
434
435 if self.frontend_readiness.event.is_hup() {
436 return SessionResult::Close;
437 }
438
439 while counter < MAX_LOOP_ITERATIONS {
440 let frontend_interest = self.frontend_readiness.filter_interest();
441
442 trace!("{} Interest({:?})", log_context!(self), frontend_interest);
443 if frontend_interest.is_empty() {
444 break;
445 }
446
447 if frontend_interest.is_readable() {
448 let protocol_result = self.readable();
449 if protocol_result != SessionResult::Continue {
450 return protocol_result;
451 }
452 }
453
454 if frontend_interest.is_writable() {
455 let protocol_result = self.writable();
456 if protocol_result != SessionResult::Continue {
457 return protocol_result;
458 }
459 }
460
461 if frontend_interest.is_error() {
462 error!("{} Front socket error, disconnecting", log_context!(self));
463 self.frontend_readiness.interest = Ready::EMPTY;
464 return SessionResult::Close;
465 }
466
467 counter += 1;
468 }
469
470 if counter >= MAX_LOOP_ITERATIONS {
471 error!(
472 "{}\tHandling session went through {} iterations, there's a probable infinite loop bug, closing the connection",
473 log_context!(self),
474 MAX_LOOP_ITERATIONS
475 );
476
477 incr!(names::http::INFINITE_LOOP_ERROR);
478 self.print_state("HTTPS");
479
480 return SessionResult::Close;
481 }
482
483 SessionResult::Continue
484 }
485
486 fn update_readiness(&mut self, token: Token, events: Ready) {
487 if self.frontend_token == token {
488 self.frontend_readiness.event |= events;
489 }
490 }
491
492 fn timeout(&mut self, token: Token, _metrics: &mut SessionMetrics) -> StateResult {
493 if self.frontend_token == token {
495 self.container_frontend_timeout.triggered();
496 return StateResult::CloseSession;
497 }
498
499 error!(
500 "{}, Expect state: got timeout for an invalid token: {:?}",
501 log_context!(self),
502 token
503 );
504 StateResult::CloseSession
505 }
506
507 fn cancel_timeouts(&mut self) {
508 self.container_frontend_timeout.cancel();
509 }
510
511 fn print_state(&self, context: &str) {
512 error!(
513 "{} Session(Handshake)\n\tFrontend:\n\t\ttoken: {:?}\treadiness: {:?}",
514 context, self.frontend_token, self.frontend_readiness
515 );
516 }
517}
518
519#[cfg(test)]
523mod tests {
524 use std::collections::HashSet;
525
526 use rustls::{
527 AlertDescription, CertificateError, ContentType, Error as RustlsError, HandshakeType,
528 InvalidMessage, PeerIncompatible, PeerMisbehaved,
529 };
530
531 use super::handshake_failure_reason;
532
533 #[test]
539 fn handshake_failure_reason_maps_every_variant_to_unique_namespaced_key() {
540 let cases: &[(RustlsError, &str)] = &[
541 (
542 RustlsError::AlertReceived(AlertDescription::HandshakeFailure),
543 "tls.handshake.failed.alert_received",
544 ),
545 (
546 RustlsError::PeerIncompatible(PeerIncompatible::NoCipherSuitesInCommon),
547 "tls.handshake.failed.peer_incompatible",
548 ),
549 (
550 RustlsError::PeerMisbehaved(PeerMisbehaved::IllegalMiddleboxChangeCipherSpec),
551 "tls.handshake.failed.peer_misbehaved",
552 ),
553 (
554 RustlsError::InvalidMessage(InvalidMessage::InvalidContentType),
555 "tls.handshake.failed.invalid_message",
556 ),
557 (
558 RustlsError::InappropriateMessage {
559 expect_types: vec![ContentType::Handshake],
560 got_type: ContentType::ApplicationData,
561 },
562 "tls.handshake.failed.inappropriate_message",
563 ),
564 (
565 RustlsError::InappropriateHandshakeMessage {
566 expect_types: vec![HandshakeType::ClientHello],
567 got_type: HandshakeType::Finished,
568 },
569 "tls.handshake.failed.inappropriate_handshake_message",
570 ),
571 (
572 RustlsError::PeerSentOversizedRecord,
573 "tls.handshake.failed.oversized_record",
574 ),
575 (
576 RustlsError::NoApplicationProtocol,
577 "tls.handshake.failed.no_alpn",
578 ),
579 (
580 RustlsError::InvalidCertificate(CertificateError::Expired),
581 "tls.handshake.failed.invalid_certificate",
582 ),
583 (
584 RustlsError::DecryptError,
585 "tls.handshake.failed.decrypt_error",
586 ),
587 (
588 RustlsError::NoCertificatesPresented,
589 "tls.handshake.failed.no_certificates_present",
590 ),
591 (
593 RustlsError::General("test".to_owned()),
594 "tls.handshake.failed.other",
595 ),
596 (RustlsError::EncryptError, "tls.handshake.failed.other"),
597 (
598 RustlsError::FailedToGetCurrentTime,
599 "tls.handshake.failed.other",
600 ),
601 (
602 RustlsError::HandshakeNotComplete,
603 "tls.handshake.failed.other",
604 ),
605 ];
606
607 let mut seen = HashSet::new();
608 for (err, expected) in cases {
609 let got = handshake_failure_reason(err);
610 assert_eq!(got, *expected, "variant {err:?} → {got}, want {expected}");
611 assert!(
612 got.starts_with("tls.handshake.failed."),
613 "reason {got} missing tls.handshake.failed. namespace"
614 );
615 seen.insert(got);
616 }
617
618 assert_eq!(seen.len(), 12, "unexpected key set: {seen:?}");
620 }
621}