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}