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