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}