ktls_core/context.rs
1//! Kernel TLS connection context.
2
3use std::io;
4use std::os::fd::{AsFd, AsRawFd};
5
6use bitfield_struct::bitfield;
7
8use crate::error::{Error, InvalidMessage, PeerMisbehaved, Result};
9use crate::ffi::{recv_tls_record, send_tls_control_message};
10use crate::tls::{
11 AlertDescription, AlertLevel, ContentType, HandshakeType, KeyUpdateRequest, Peer,
12 ProtocolVersion, TlsSession,
13};
14use crate::utils::Buffer;
15
16#[derive(Debug)]
17/// The context for managing a kTLS connection.
18///
19/// This is a low-level structure, usually you don't need to use it directly
20/// unless you are implementing a higher-level abstraction.
21pub struct Context<C: TlsSession> {
22 // State of the current kTLS connection
23 state: State,
24
25 // Shared buffer
26 buffer: Buffer,
27
28 // TLS session
29 session: C,
30}
31
32impl<C: TlsSession> Context<C> {
33 /// Creates a new kTLS context with the given TLS session and optional
34 /// buffer (can be TLS early data received from peer during handshake, or a
35 /// pre-allocated buffer).
36 pub fn new(session: C, buffer: Option<Buffer>) -> Self {
37 Self {
38 state: State::new(),
39 buffer: buffer.unwrap_or_default(),
40 session,
41 }
42 }
43
44 #[inline]
45 /// Returns the current kTLS connection state.
46 pub const fn state(&self) -> &State {
47 &self.state
48 }
49
50 #[inline]
51 /// Returns a reference to the buffer.
52 pub const fn buffer(&self) -> &Buffer {
53 &self.buffer
54 }
55
56 #[inline]
57 /// Returns a mutable reference to the buffer.
58 pub const fn buffer_mut(&mut self) -> &mut Buffer {
59 &mut self.buffer
60 }
61
62 #[track_caller]
63 #[cfg(feature = "tls13-key-update")]
64 /// Sends a TLS 1.3 `key_update` message to refresh a connection's keys.
65 ///
66 /// This call refreshes our encryption keys. Once the peer receives the
67 /// message, it refreshes _its_ encryption and decryption keys and sends
68 /// a response. Once we receive that response, we refresh our decryption
69 /// keys to match. At the end of this process, keys in both directions
70 /// have been refreshed.
71 ///
72 /// # Notes
73 ///
74 /// Note that TLS implementations (including kTLS) may enforce limits on the
75 /// number of `key_update` messages allowed on a given connection to
76 /// prevent denial of service. Therefore, this should be called
77 /// sparingly.
78 ///
79 /// Since the kernel will never implicitly and automatically trigger key
80 /// updates according to the selected cipher suite's cryptographic
81 /// constraints, the application is responsible for calling this method
82 /// as needed to maintain security.
83 ///
84 /// Only Linux 6.13 or later supports TLS 1.3 rekey, see [the commit], and
85 /// we gate this method behind feature flag `tls13-key-update`. This method
86 /// might return an error `EBUSY`, which most likely indicates that the
87 /// running kernel does not support this feature.
88 ///
89 /// # Known Issues
90 ///
91 /// Under the condition that both parties are kTLS offloaded and the
92 /// server uses the Tokio asynchronous runtime, if the server initiates a
93 /// `KeyUpdate` by calling this method and then immediately performs a read
94 /// I/O operation, the program will hang (the read I/O operation returns
95 /// EAGAIN but the task waker does not seem to be registered correctly).
96 /// This issue needs further investigation.
97 ///
98 /// # Errors
99 ///
100 /// - Updating the TX secret fails.
101 /// - Sending the `KeyUpdate` message fails.
102 /// - Setting the TX secret on the socket fails.
103 ///
104 /// [the commit]: https://github.com/torvalds/linux/commit/47069594e67e882ec5c1d8d374f6aab037511509
105 pub fn refresh_traffic_keys<S: AsFd>(&mut self, socket: &S) -> Result<()> {
106 crate::trace!("Trigger traffic keys refreshing...");
107
108 if self.session.protocol_version() != ProtocolVersion::TLSv1_3 {
109 crate::warn!(
110 "Key update is only supported by TLS 1.3, current: {:?}",
111 self.session.protocol_version()
112 );
113
114 return Ok(());
115 }
116
117 let tls_crypto_info_tx = match self.session.update_tx_secret() {
118 Ok(tx) => tx,
119 Err(error) => {
120 // TODO: should we abort the connection here or just keep using the old key?
121
122 return self.abort(socket, error, AlertDescription::InternalError);
123 }
124 };
125
126 if let Err(error) = send_tls_control_message(
127 socket.as_fd().as_raw_fd(),
128 ContentType::Handshake,
129 &mut [
130 HandshakeType::KeyUpdate.to_int(), // typ
131 0,
132 0,
133 1, // length
134 KeyUpdateRequest::UpdateRequested.to_int(),
135 ],
136 )
137 .map_err(Error::KeyUpdateFailed)
138 {
139 // Failed to notify the peer, abort the connection.
140 crate::error!("Failed to send KeyUpdate message: {error}");
141
142 return self.abort(socket, error, AlertDescription::InternalError);
143 }
144
145 if let Err(error) = tls_crypto_info_tx.set(socket) {
146 // Failed to update tx secret, abort the connection.
147 crate::error!("Failed to set TX secret: {error}");
148
149 return self.abort(socket, error, AlertDescription::InternalError);
150 }
151
152 Ok(())
153 }
154
155 #[track_caller]
156 /// Handles [`io::Error`]s from I/O operations on kTLS-configured sockets.
157 ///
158 /// # Overview
159 ///
160 /// When a socket is configured with kTLS, it can be used like a normal
161 /// socket for data transmission - the kernel transparently handles
162 /// encryption and decryption. However, TLS control messages (e.g., TLS
163 /// alerts) from peers cannot be processed automatically by the kernel,
164 /// which returns `EIO` to notify userspace.
165 ///
166 /// This method helps handle such errors appropriately:
167 ///
168 /// - **`EIO`**: Attempts to process any received TLS control messages.
169 /// Returns `Ok(())` on success, allowing the caller to retry the
170 /// operation.
171 /// - **`Interrupted`**: Indicates the operation was interrupted by a
172 /// signal. Returns `Ok(())`, allowing the caller to retry the operation.
173 /// - **`WouldBlock`**: Indicates the operation would block (e.g.,
174 /// non-blocking socket). Returns `Ok(())`, allowing the caller to retry
175 /// the operation.
176 /// - **`BrokenPipe`**: Marks the stream as closed.
177 /// - Other errors: Aborts the connection with an `internal_error` alert and
178 /// returns the original error.
179 ///
180 /// # Notes
181 ///
182 /// Incorrect usage of this method MAY lead to unexpected behavior.
183 ///
184 /// # Errors
185 ///
186 /// Returns the original [`io::Error`] if it cannot be recovered from.
187 pub fn handle_io_error<S: AsFd>(&mut self, socket: &S, err: io::Error) -> io::Result<()> {
188 match err {
189 err if err.raw_os_error() == Some(libc::EIO) => {
190 crate::trace!("Received EIO, handling TLS control message");
191
192 self.handle_tls_control_message(socket)
193 .map_err(Into::into)
194 }
195 err if err.kind() == io::ErrorKind::Interrupted => {
196 crate::trace!("The I/O operation was interrupted, retrying...");
197
198 Ok(())
199 }
200 err if err.kind() == io::ErrorKind::WouldBlock => {
201 crate::trace!("The I/O operation would block, retrying...");
202
203 Ok(())
204 }
205 err if err.kind() == io::ErrorKind::BrokenPipe
206 || err.kind() == io::ErrorKind::ConnectionReset =>
207 {
208 crate::trace!("The kTLS offloaded stream is closed ({err})");
209
210 self.state.set_is_read_closed(true);
211 self.state.set_is_write_closed(true);
212
213 Err(err)
214 }
215 _ => {
216 crate::trace!(
217 "I/O operation failed, unrecoverable: {err}, try aborting the connection"
218 );
219
220 self.send_tls_alert(socket, AlertLevel::Fatal, AlertDescription::InternalError);
221
222 self.state.set_is_read_closed(true);
223 self.state.set_is_write_closed(true);
224
225 Err(err)
226 }
227 }
228 }
229
230 #[track_caller]
231 #[allow(clippy::too_many_lines)]
232 /// Handles TLS control messages received by kernel.
233 ///
234 /// The caller SHOULD first check if the raw os error returned were
235 /// `EIO`, which indicates that there is a TLS control message available.
236 ///
237 /// But in fact, this method can be called even if there's no TLS control
238 /// message (not recommended to do so).
239 fn handle_tls_control_message<S: AsFd>(&mut self, socket: &S) -> Result<()> {
240 match recv_tls_record(socket.as_fd().as_raw_fd(), &mut self.buffer) {
241 Ok(ContentType::Handshake) => {
242 return self.handle_tls_control_message_handshake(socket);
243 }
244 Ok(ContentType::Alert) => {
245 if let &[level, desc] = self.buffer.unfilled_initialized() {
246 return self.handle_tls_control_message_alert(
247 socket,
248 AlertLevel::from_int(level),
249 AlertDescription::from_int(desc),
250 );
251 }
252
253 // The peer sent an invalid alert. We send back an error
254 // and close the connection.
255
256 crate::error!(
257 "Invalid alert message received: {:?}, {:?}",
258 self.buffer.unfilled_initialized(),
259 self.buffer
260 );
261
262 return self.abort(
263 socket,
264 InvalidMessage::MessageTooLarge,
265 InvalidMessage::MessageTooLarge.description(),
266 );
267 }
268 Ok(ContentType::ChangeCipherSpec) => {
269 // ChangeCipherSpec should only be sent under the following conditions:
270 //
271 // * TLS 1.2: during a handshake or a rehandshake
272 // * TLS 1.3: during a handshake
273 //
274 // We don't have to worry about handling messages during a handshake
275 // and rustls does not support TLS 1.2 rehandshakes so we just emit
276 // an error here and abort the connection.
277
278 crate::warn!("Received unexpected ChangeCipherSpec message");
279
280 return self.abort(
281 socket,
282 PeerMisbehaved::IllegalMiddleboxChangeCipherSpec,
283 PeerMisbehaved::IllegalMiddleboxChangeCipherSpec.description(),
284 );
285 }
286 Ok(ContentType::ApplicationData) => {
287 // This shouldn't happen in normal operation.
288
289 crate::warn!(
290 "Received {} bytes of application data, unexpected usage",
291 self.buffer.unfilled_initialized().len()
292 );
293
294 self.buffer.set_filled_all();
295 }
296 Ok(ContentType::Heartbeat) => {
297 // For security reasons, we do not support Heartbeat messages.
298
299 crate::error!(
300 "Received unexpected TLS control message: content_type={:?}",
301 ContentType::Heartbeat
302 );
303
304 return self.abort(
305 socket,
306 InvalidMessage::InvalidContentType,
307 InvalidMessage::InvalidContentType.description(),
308 );
309 }
310 Ok(ContentType::Unknown(unknown_content_type)) => {
311 match self.session.handle_unknown_message(
312 unknown_content_type,
313 self.buffer.unfilled_initialized(),
314 ) {
315 Ok(()) => {
316 crate::trace!(
317 "Handled unknown content type message: \
318 content_type={unknown_content_type:?}",
319 );
320 }
321 Err(e) => {
322 crate::error!(
323 "Received unexpected TLS control message: \
324 content_type={unknown_content_type:?}",
325 );
326
327 return self.abort(
328 socket,
329 e,
330 InvalidMessage::InvalidContentType.description(),
331 );
332 }
333 }
334 }
335 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
336 // No TLS control message available, the caller should retry
337 // the I/O operation.
338
339 crate::trace!("No TLS control message available, retrying...");
340 }
341 Err(error) => {
342 crate::error!("Failed to receive TLS control message: {error}");
343
344 return self.abort(
345 socket,
346 Error::General(error),
347 AlertDescription::InternalError,
348 );
349 }
350 }
351
352 Ok(())
353 }
354
355 #[track_caller]
356 #[allow(clippy::too_many_lines)]
357 /// Handles a TLS alert received from the peer.
358 fn handle_tls_control_message_handshake<S: AsFd>(&mut self, socket: &S) -> Result<()> {
359 let mut messages =
360 HandshakeMessagesIter::new(self.buffer.unfilled_initialized()).enumerate();
361
362 while let Some((idx, payload)) = messages.next() {
363 let Ok((handshake_type, payload)) = payload else {
364 return self.abort(
365 socket,
366 InvalidMessage::MessageTooShort,
367 InvalidMessage::MessageTooShort.description(),
368 );
369 };
370
371 match handshake_type {
372 HandshakeType::KeyUpdate
373 if self.session.protocol_version() == ProtocolVersion::TLSv1_3 =>
374 {
375 if idx != 0 || messages.next().is_some() {
376 crate::error!(
377 "RFC 8446, section 5.1: Handshake messages MUST NOT span key changes."
378 );
379
380 return self.abort(
381 socket,
382 PeerMisbehaved::KeyEpochWithPendingFragment,
383 PeerMisbehaved::KeyEpochWithPendingFragment.description(),
384 );
385 }
386
387 let &[payload] = payload else {
388 crate::error!(
389 "Received invalid KeyUpdate message, expected 1 byte payload, got: \
390 {:?}",
391 payload
392 );
393
394 return self.abort(
395 socket,
396 InvalidMessage::InvalidKeyUpdate,
397 InvalidMessage::InvalidKeyUpdate.description(),
398 );
399 };
400
401 let key_update_request = KeyUpdateRequest::from_int(payload);
402
403 if !matches!(
404 key_update_request,
405 KeyUpdateRequest::UpdateNotRequested | KeyUpdateRequest::UpdateRequested
406 ) {
407 crate::warn!(
408 "Received KeyUpdate message with unknown request value: {payload}"
409 );
410
411 return self.abort(
412 socket,
413 InvalidMessage::InvalidKeyUpdate,
414 InvalidMessage::InvalidKeyUpdate.description(),
415 );
416 }
417
418 #[cfg(not(feature = "tls13-key-update"))]
419 {
420 crate::warn!(
421 "Received KeyUpdate [{key_update_request:?}], TLS 1.3 key update \
422 support is disabled"
423 );
424
425 return self.abort(
426 socket,
427 InvalidMessage::UnexpectedMessage(
428 "TLS 1.3 key update support is disabled",
429 ),
430 AlertDescription::InternalError,
431 );
432 }
433
434 #[cfg(feature = "tls13-key-update")]
435 {
436 if let Err(error) = self
437 .session
438 .update_rx_secret()
439 .and_then(|secret| secret.set(socket))
440 {
441 crate::error!("Failed to update secret: {error}");
442
443 return self.abort(socket, error, AlertDescription::InternalError);
444 }
445
446 match key_update_request {
447 KeyUpdateRequest::UpdateNotRequested => {}
448 KeyUpdateRequest::UpdateRequested => {
449 // Notify the peer that we are updating our TX secret as well.
450 if let Err(error) = send_tls_control_message(
451 socket.as_fd().as_raw_fd(),
452 ContentType::Handshake,
453 &mut [
454 HandshakeType::KeyUpdate.to_int(), // typ
455 0,
456 0,
457 1, // length
458 KeyUpdateRequest::UpdateNotRequested.to_int(),
459 ],
460 )
461 .map_err(Error::KeyUpdateFailed)
462 {
463 // Failed to notify the peer, abort the connection.
464 crate::error!("Failed to send KeyUpdate message: {error}");
465
466 return self.abort(
467 socket,
468 error,
469 AlertDescription::InternalError,
470 );
471 }
472
473 if let Err(error) = self
474 .session
475 .update_tx_secret()
476 .and_then(|secret| secret.set(socket))
477 {
478 crate::error!("Failed to update TX secret: {error}");
479
480 return self.abort(
481 socket,
482 error,
483 AlertDescription::InternalError,
484 );
485 }
486 }
487 KeyUpdateRequest::Unknown(_) => {
488 unreachable!()
489 }
490 }
491 }
492 }
493 HandshakeType::NewSessionTicket
494 if self.session.protocol_version() == ProtocolVersion::TLSv1_3 =>
495 {
496 if self.session.peer() != Peer::Client {
497 crate::warn!("TLS 1.2 peer sent a TLS 1.3 NewSessionTicket message");
498
499 return self.abort(
500 socket,
501 InvalidMessage::UnexpectedMessage(
502 "TLS 1.2 peer sent a TLS 1.3 NewSessionTicket message",
503 ),
504 AlertDescription::UnexpectedMessage,
505 );
506 }
507
508 if let Err(error) = self
509 .session
510 .handle_new_session_ticket(payload)
511 {
512 return self.abort(socket, error, AlertDescription::InternalError);
513 }
514 }
515 _ if self.session.protocol_version() == ProtocolVersion::TLSv1_3 => {
516 crate::error!(
517 "Unexpected handshake message for a TLS 1.3 connection: \
518 typ={handshake_type:?}",
519 );
520
521 return self.abort(
522 socket,
523 InvalidMessage::UnexpectedMessage(
524 "expected KeyUpdate or NewSessionTicket message",
525 ),
526 AlertDescription::UnexpectedMessage,
527 );
528 }
529 _ => {
530 crate::error!(
531 "Unexpected handshake message: ver={:?}, typ={handshake_type:?}",
532 self.session.protocol_version()
533 );
534
535 return self.abort(
536 socket,
537 InvalidMessage::UnexpectedMessage(
538 "handshake messages are not expected on TLS 1.2 connections",
539 ),
540 AlertDescription::UnexpectedMessage,
541 );
542 }
543 }
544 }
545
546 Ok(())
547 }
548
549 #[track_caller]
550 /// Handles a TLS alert received from the peer.
551 fn handle_tls_control_message_alert<S: AsFd>(
552 &mut self,
553 socket: &S,
554 level: AlertLevel,
555 desc: AlertDescription,
556 ) -> Result<()> {
557 match desc {
558 AlertDescription::CloseNotify
559 if self.session.protocol_version() == ProtocolVersion::TLSv1_2 =>
560 {
561 // RFC 5246, section 7.2.1: Unless some other fatal alert has been transmitted,
562 // each party is required to send a close_notify alert before closing the write
563 // side of the connection. The other party MUST respond with a close_notify
564 // alert of its own and close down the connection immediately, discarding any
565 // pending writes.
566 crate::trace!("Received `close_notify` alert, should shutdown the TLS stream");
567
568 self.shutdown(socket);
569 }
570 AlertDescription::CloseNotify => {
571 // RFC 8446, section 6.1: Each party MUST send a "close_notify" alert before
572 // closing its write side of the connection, unless it has already sent some
573 // error alert. This does not have any effect on its read side of the
574 // connection. Note that this is a change from versions of TLS prior to TLS 1.3
575 // in which implementations were required to react to a "close_notify" by
576 // discarding pending writes and sending an immediate "close_notify" alert of
577 // their own. That previous requirement could cause truncation in the read
578 // side. Both parties need not wait to receive a "close_notify" alert before
579 // closing their read side of the connection, though doing so would introduce
580 // the possibility of truncation.
581
582 crate::trace!(
583 "Received `close_notify` alert, should shutdown the read side of TLS stream"
584 );
585
586 self.state.set_is_read_closed(true);
587 }
588 _ if self.session.protocol_version() == ProtocolVersion::TLSv1_2
589 && level == AlertLevel::Warning =>
590 {
591 // RFC 5246, section 7.2.2: If an alert with a level of warning
592 // is sent and received, generally the connection can continue
593 // normally.
594
595 crate::warn!("Received non fatal alert, level={level:?}, desc: {desc:?}");
596 }
597 _ => {
598 // All other alerts are treated as fatal and result in us immediately shutting
599 // down the connection and emitting an error.
600
601 crate::error!("Received fatal alert, desc: {desc:?}");
602
603 self.state.set_is_read_closed(true);
604 self.state.set_is_write_closed(true);
605
606 return Err(Error::AlertReceived(desc));
607 }
608 }
609
610 Ok(())
611 }
612
613 #[track_caller]
614 /// Closes the read side of the kTLS connection and sends a `close_notify`
615 /// alert to the peer.
616 pub fn shutdown<S: AsFd>(&mut self, socket: &S) {
617 crate::trace!("Shutting down the TLS stream with `close_notify` alert...");
618
619 self.send_tls_alert(socket, AlertLevel::Warning, AlertDescription::CloseNotify);
620
621 if self.session.protocol_version() == ProtocolVersion::TLSv1_2 {
622 // See RFC 5246, section 7.2.1
623 self.state.set_is_read_closed(true);
624 }
625
626 self.state.set_is_write_closed(true);
627 }
628
629 #[track_caller]
630 /// Aborts the kTLS connection and sends a fatal alert to the peer.
631 fn abort<T, S, E, D>(&mut self, socket: &S, error: E, description: D) -> Result<T>
632 where
633 S: AsFd,
634 E: Into<Error>,
635 D: Into<AlertDescription>,
636 {
637 crate::trace!("Aborting the TLS stream with fatal alert...");
638
639 self.send_tls_alert(socket, AlertLevel::Fatal, description.into());
640
641 self.state.set_is_read_closed(true);
642 self.state.set_is_write_closed(true);
643
644 Err(error.into())
645 }
646
647 #[track_caller]
648 /// Sends a TLS alert to the peer.
649 fn send_tls_alert<S: AsFd>(
650 &mut self,
651 socket: &S,
652 level: AlertLevel,
653 description: AlertDescription,
654 ) {
655 if !self.state.is_write_closed() {
656 let _ = send_tls_control_message(
657 socket.as_fd().as_raw_fd(),
658 ContentType::Alert,
659 &mut [level.to_int(), description.to_int()],
660 )
661 .inspect_err(|_e| {
662 crate::trace!("Failed to send alert: {_e}");
663 });
664 }
665 }
666}
667
668#[bitfield(u8)]
669/// State of the kTLS connection.
670pub struct State {
671 /// Whether the read side is closed.
672 pub is_read_closed: bool,
673
674 /// Whether the write side is closed.
675 pub is_write_closed: bool,
676
677 #[bits(6)]
678 _reserved: u8,
679}
680
681impl State {
682 #[inline]
683 #[must_use]
684 /// Returns whether the connection is fully closed (both read and write
685 /// sides).
686 pub const fn is_closed(&self) -> bool {
687 self.is_read_closed() && self.is_write_closed()
688 }
689}
690
691struct HandshakeMessagesIter<'a> {
692 inner: Result<Option<&'a [u8]>, ()>,
693}
694
695impl<'a> HandshakeMessagesIter<'a> {
696 #[inline]
697 const fn new(payloads: &'a [u8]) -> Self {
698 Self {
699 inner: Ok(Some(payloads)),
700 }
701 }
702}
703
704impl<'a> Iterator for HandshakeMessagesIter<'a> {
705 type Item = Result<(HandshakeType, &'a [u8]), ()>;
706
707 #[inline]
708 fn next(&mut self) -> Option<Self::Item> {
709 match self.inner {
710 Ok(None) => None,
711 Ok(Some(&[typ, a, b, c, ref rest @ ..])) => {
712 let handshake_type = HandshakeType::from_int(typ);
713 let payload_length = u32::from_be_bytes([0, a, b, c]) as usize;
714
715 let Some((payload, rest)) = rest.split_at_checked(payload_length) else {
716 crate::error!(
717 "Received truncated handshake message payload, expected: \
718 {payload_length}, actual: {}",
719 rest.len()
720 );
721
722 self.inner = Err(());
723
724 return Some(Err(()));
725 };
726
727 if rest.is_empty() {
728 self.inner = Ok(None);
729 } else {
730 self.inner = Ok(Some(rest));
731 }
732
733 Some(Ok((handshake_type, payload)))
734 }
735 Ok(Some(_truncated)) => {
736 crate::error!("Received truncated handshake message payload: {_truncated:?}");
737
738 self.inner = Err(());
739
740 Some(Err(()))
741 }
742 Err(()) => Some(Err(())),
743 }
744 }
745}