diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index 228a976d5..78297eca9 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -74,10 +74,10 @@ runs: echo "SCCACHE_AZURE_BLOB_CONTAINER=sccache" >> $GITHUB_ENV echo "RUSTC_WRAPPER=$SCCACHE_PATH" >> $GITHUB_ENV - - name: Install nightly Rust + - name: Install nightly Rust to use for certain tools (fuzzing, cargo-udeps) id: nightly run: | - NIGHTLY="nightly-2025-05-30" + NIGHTLY="nightly-2025-09-30" # Check if nightly toolchain is already installed if ! rustup toolchain list | grep -q "$NIGHTLY"; then rustup toolchain install $NIGHTLY @@ -86,6 +86,16 @@ runs: echo "nightly=$NIGHTLY" >> $GITHUB_OUTPUT shell: bash + - name: Install nightly Rust for compiling eBPF code + run: | + NIGHTLY="nightly-2025-05-30" + # Check if nightly toolchain is already installed + if ! rustup toolchain list | grep -q "$NIGHTLY"; then + rustup toolchain install $NIGHTLY + rustup component add rust-src --toolchain $NIGHTLY + fi + shell: bash + - name: Start sccache run: $SCCACHE_PATH --start-server shell: bash diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 93326debe..3b50fadd4 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -3861,17 +3861,6 @@ dependencies = [ "mach2", ] -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - [[package]] name = "ip-packet" version = "0.1.0" @@ -8028,30 +8017,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", "socket2 0.6.0", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ac4d15e81..e353aa2f5 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -185,7 +185,7 @@ test-case = "3.3.1" test-strategy = "0.4.3" thiserror = "2.0.17" time = "0.3.43" -tokio = "1.47" +tokio = "1.48.0" tokio-stream = "0.1.17" tokio-tungstenite = "0.28.0" tokio-util = "0.7.16" diff --git a/rust/Dockerfile b/rust/Dockerfile index 2fb2dd80c..9d79b7ef8 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -63,7 +63,7 @@ COPY ${PACKAGE} . FROM runtime AS dev ARG PACKAGE -ARG RUST_VERSION="1.90.0" # Keep in sync with `rust-toolchain.toml` +ARG RUST_VERSION="1.91.0" # Keep in sync with `rust-toolchain.toml` WORKDIR /app diff --git a/rust/bin-shared/src/dns_control/linux.rs b/rust/bin-shared/src/dns_control/linux.rs index b1929ed77..1fb67d1a2 100644 --- a/rust/bin-shared/src/dns_control/linux.rs +++ b/rust/bin-shared/src/dns_control/linux.rs @@ -7,7 +7,7 @@ use std::{net::IpAddr, process::Command, str::FromStr}; mod etc_resolv_conf; -#[derive(clap::ValueEnum, Clone, Copy, Debug)] +#[derive(clap::ValueEnum, Clone, Copy, Debug, Default)] pub enum DnsControlMethod { /// Explicitly disable DNS control. /// @@ -22,15 +22,10 @@ pub enum DnsControlMethod { /// Cooperate with `systemd-resolved` /// /// Suitable for most Ubuntu systems, probably + #[default] SystemdResolved, } -impl Default for DnsControlMethod { - fn default() -> Self { - Self::SystemdResolved - } -} - impl DnsController { pub fn deactivate(&mut self) -> Result<()> { tracing::debug!("Deactivating DNS control..."); diff --git a/rust/bin-shared/src/tun_device_manager/linux.rs b/rust/bin-shared/src/tun_device_manager/linux.rs index 43b574c86..12beeb9cc 100644 --- a/rust/bin-shared/src/tun_device_manager/linux.rs +++ b/rust/bin-shared/src/tun_device_manager/linux.rs @@ -793,7 +793,7 @@ fn set_non_blocking(fd: RawFd) -> io::Result<()> { } fn create_tun_device() -> io::Result<()> { - let path = Path::new(TUN_FILE.to_str().expect("path is valid utf-8")); + let path = Path::new(TUN_FILE.to_str().map_err(io::Error::other)?); if path.exists() { return Ok(()); diff --git a/rust/client-ffi/src/lib.rs b/rust/client-ffi/src/lib.rs index 4472e3658..1bc03563d 100644 --- a/rust/client-ffi/src/lib.rs +++ b/rust/client-ffi/src/lib.rs @@ -8,7 +8,7 @@ use std::{ time::Duration, }; -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, Result, anyhow}; use backoff::ExponentialBackoffBuilder; use firezone_logging::sentry_layer; use firezone_telemetry::{Telemetry, analytics}; @@ -563,7 +563,7 @@ fn init_logging(log_dir: &Path, log_filter: String) -> Result<()> { LOGGER_STATE .set((handle, reload_handle)) - .expect("Logging guard should never be initialized twice"); + .map_err(|_| anyhow!("Logging guard should never be initialized twice"))?; Ok(()) } diff --git a/rust/connlib/phoenix-channel/src/login_url.rs b/rust/connlib/phoenix-channel/src/login_url.rs index f75eda422..32675c0f3 100644 --- a/rust/connlib/phoenix-channel/src/login_url.rs +++ b/rust/connlib/phoenix-channel/src/login_url.rs @@ -269,7 +269,7 @@ fn get_websocket_path( { let mut paths = api_url .path_segments_mut() - .expect("scheme guarantees valid URL"); + .map_err(|_| LoginUrlError::MissingHost)?; paths.pop_if_empty(); paths.push(mode); @@ -321,7 +321,7 @@ fn set_ws_scheme(url: &mut Url) -> Result<(), LoginUrlError> { }; url.set_scheme(scheme) - .expect("Developer error: the match before this should make sure we can set this"); + .map_err(|_| LoginUrlError::InvalidUrlScheme(scheme.to_owned()))?; Ok(()) } diff --git a/rust/connlib/snownet/src/node.rs b/rust/connlib/snownet/src/node.rs index 486aad8b2..ba863e4c5 100644 --- a/rust/connlib/snownet/src/node.rs +++ b/rust/connlib/snownet/src/node.rs @@ -1756,7 +1756,6 @@ impl PeerSocket { matches!(self, Self::RelayToPeer { .. } | Self::RelayToRelay { .. }) } - // TODO: Return `Arguments` here once we hit 1.89 fn fmt(&self, relay: RId) -> String where RId: fmt::Display, diff --git a/rust/connlib/snownet/src/node/connections.rs b/rust/connlib/snownet/src/node/connections.rs index a595a9569..a36d4ac14 100644 --- a/rust/connlib/snownet/src/node/connections.rs +++ b/rust/connlib/snownet/src/node/connections.rs @@ -49,36 +49,23 @@ where const RECENT_DISCONNECT_TIMEOUT: Duration = Duration::from_secs(5); pub(crate) fn handle_timeout(&mut self, events: &mut VecDeque>, now: Instant) { - self.initial.retain(|id, conn| { - if conn.is_failed { - events.push_back(Event::ConnectionFailed(*id)); - return false; + for (id, _) in self.initial.extract_if(.., |_, conn| conn.is_failed) { + events.push_back(Event::ConnectionFailed(id)); + } + + for (id, conn) in self.established.extract_if(.., |_, conn| conn.is_failed()) { + events.push_back(Event::ConnectionFailed(id)); + + for (index, _) in self + .established_by_wireguard_session_index + .extract_if(.., |_, c| *c == id) + { + self.disconnected_session_indices.insert(index, now); } - - true - }); - - self.established.retain(|id, conn| { - if conn.is_failed() { - events.push_back(Event::ConnectionFailed(*id)); - self.established_by_wireguard_session_index - .retain(|index, c| { - if c == id { - self.disconnected_session_indices.insert(*index, now); - - return false; - } - - true - }); - self.disconnected_public_keys - .insert(conn.tunnel.remote_static_public().to_bytes(), now); - self.disconnected_ids.insert(*id, now); - return false; - } - - true - }); + self.disconnected_public_keys + .insert(conn.tunnel.remote_static_public().to_bytes(), now); + self.disconnected_ids.insert(id, now); + } self.disconnected_ids .retain(|_, v| now.duration_since(*v) < Self::RECENT_DISCONNECT_TIMEOUT); diff --git a/rust/connlib/tunnel/src/gateway/client_on_gateway.rs b/rust/connlib/tunnel/src/gateway/client_on_gateway.rs index e80f2bfa0..9e5df322f 100644 --- a/rust/connlib/tunnel/src/gateway/client_on_gateway.rs +++ b/rust/connlib/tunnel/src/gateway/client_on_gateway.rs @@ -174,17 +174,10 @@ impl ClientOnGateway { let cid = self.id; let mut any_expired = false; - // TODO: Replace with `extract_if` once we are on Rust 1.91 - self.resources.retain(|rid, r| { - if r.is_allowed(&now) { - return true; - } - + for (rid, _) in self.resources.extract_if(.., |_, r| !r.is_allowed(&now)) { tracing::info!(%cid, %rid, "Access to resource expired"); - any_expired = true; - false - }); + } if any_expired { self.recalculate_filters(); @@ -252,15 +245,12 @@ impl ClientOnGateway { } pub(crate) fn retain_authorizations(&mut self, authorization: BTreeSet) { - // TODO: Replace with `extract_if` once we are on Rust 1.91 - self.resources.retain(|rid, _| { - if authorization.contains(rid) { - return true; - } - + for (rid, _) in self + .resources + .extract_if(.., |rid, _| !authorization.contains(rid)) + { tracing::info!(%rid, "Revoking resource authorization"); - false - }); + } self.recalculate_filters(); } diff --git a/rust/gui-client/src-tauri/src/bin/firezone-client-tunnel.rs b/rust/gui-client/src-tauri/src/bin/firezone-client-tunnel.rs index 4bcdaff01..113ee3d60 100644 --- a/rust/gui-client/src-tauri/src/bin/firezone-client-tunnel.rs +++ b/rust/gui-client/src-tauri/src/bin/firezone-client-tunnel.rs @@ -1,5 +1,6 @@ #![cfg_attr(test, allow(clippy::unwrap_used))] +use anyhow::anyhow; use clap::Parser as _; use firezone_bin_shared::{DnsControlMethod, TOKEN_ENV_KEY}; use firezone_gui_client::service; @@ -8,7 +9,7 @@ use std::path::PathBuf; fn main() -> anyhow::Result<()> { rustls::crypto::ring::default_provider() .install_default() - .expect("Calling `install_default` only once per process should always succeed"); + .map_err(|_| anyhow!("Failed to install default crypto provider"))?; // Docs indicate that `remove_var` should actually be marked unsafe // SAFETY: We haven't spawned any other threads, this code should be the first @@ -58,22 +59,17 @@ pub struct Cli { max_partition_time: Option, } -#[derive(clap::Subcommand)] +#[derive(clap::Subcommand, Default)] enum Cmd { /// Needed to test the Tunnel service on aarch64 Windows, /// where the Tauri MSI bundler doesn't work yet Install, + #[default] Run, RunDebug, RunSmokeTest, } -impl Default for Cmd { - fn default() -> Self { - Self::Run - } -} - #[cfg(test)] mod tests { use super::{Cli, Cmd}; diff --git a/rust/gui-client/src-tauri/src/controller.rs b/rust/gui-client/src-tauri/src/controller.rs index ea71149af..8e26947db 100644 --- a/rust/gui-client/src-tauri/src/controller.rs +++ b/rust/gui-client/src-tauri/src/controller.rs @@ -115,9 +115,10 @@ pub enum Failure { Panic, } -#[derive(derive_more::Debug)] +#[derive(derive_more::Debug, Default)] pub enum Status { /// Firezone is disconnected. + #[default] Disconnected, Quitting, // The user asked to quit and we're waiting for the tunnel daemon to gracefully disconnect so we can flush telemetry. /// Firezone is ready to use. @@ -131,12 +132,6 @@ pub enum Status { WaitingForTunnel, } -impl Default for Status { - fn default() -> Self { - Self::Disconnected - } -} - impl Status { /// True if we should react to `OnUpdateResources` fn needs_resource_updates(&self) -> bool { diff --git a/rust/gui-client/src-tauri/src/gui/system_tray.rs b/rust/gui-client/src-tauri/src/gui/system_tray.rs index 529035cc3..aebca39ce 100644 --- a/rust/gui-client/src-tauri/src/gui/system_tray.rs +++ b/rust/gui-client/src-tauri/src/gui/system_tray.rs @@ -804,7 +804,7 @@ mod tests { "MyCorp GitLab", Menu::default() .item( - Event::Url("https://gitlab.mycorp.com".parse().unwrap()), + Event::Url("https://gitlab.mycorp.com".parse()?), "", ) .separator() @@ -864,7 +864,7 @@ mod tests { actual, expected, "{}", - serde_json::to_string_pretty(&actual).unwrap() + serde_json::to_string_pretty(&actual)? ); Ok(()) @@ -910,7 +910,7 @@ mod tests { "MyCorp GitLab", Menu::default() .item( - Event::Url("https://gitlab.mycorp.com".parse().unwrap()), + Event::Url("https://gitlab.mycorp.com".parse()?), "", ) .separator() @@ -946,7 +946,7 @@ mod tests { actual, expected, "{}", - serde_json::to_string_pretty(&actual).unwrap(), + serde_json::to_string_pretty(&actual)?, ); Ok(()) diff --git a/rust/headless-client/src/main.rs b/rust/headless-client/src/main.rs index 75cbd3d7b..14c6e67c2 100644 --- a/rust/headless-client/src/main.rs +++ b/rust/headless-client/src/main.rs @@ -188,7 +188,7 @@ fn main() { fn try_main() -> Result<()> { rustls::crypto::ring::default_provider() .install_default() - .expect("Calling `install_default` only once per process should always succeed"); + .map_err(|_| anyhow!("Failed to install default crypto provider"))?; let cli = Cli::parse(); diff --git a/rust/relay/server/src/server.rs b/rust/relay/server/src/server.rs index a9283a7a9..4176749ab 100644 --- a/rust/relay/server/src/server.rs +++ b/rust/relay/server/src/server.rs @@ -1021,43 +1021,39 @@ where let port = allocation.port; - self.channels_by_client_and_number - .retain(|(cs, number), c| { - if c.allocation != port { - return true; - } + for ((cs, number), c) in self + .channels_by_client_and_number + .extract_if(.., |_, c| c.allocation == port) + { + debug_assert_eq!(cs, client, "internal state should be consistent"); - debug_assert_eq!(cs, &client, "internal state should be consistent"); + let peer = c.peer_address; - let peer = c.peer_address; + if let Some(existing) = self + .channel_numbers_by_client_and_peer + .remove(&(client, peer)) + { + debug_assert_eq!(existing, number, "internal state should be consistent"); + } - if let Some(existing) = self - .channel_numbers_by_client_and_peer - .remove(&(client, peer)) - { - debug_assert_eq!(existing, *number, "internal state should be consistent"); - } + if let Some((existing_cs, existing_n)) = self + .channel_and_client_by_port_and_peer + .remove(&(port, peer)) + { + debug_assert_eq!(existing_cs, cs, "internal state should be consistent"); + debug_assert_eq!(existing_n, number, "internal state should be consistent"); + } - if let Some((existing_cs, existing_n)) = self - .channel_and_client_by_port_and_peer - .remove(&(port, peer)) - { - debug_assert_eq!(&existing_cs, cs, "internal state should be consistent"); - debug_assert_eq!(&existing_n, number, "internal state should be consistent"); - } + self.pending_commands + .push_back(Command::DeleteChannelBinding { + client: cs, + channel_number: number, + peer: c.peer_address, + allocation_port: c.allocation, + }); - self.pending_commands - .push_back(Command::DeleteChannelBinding { - client: *cs, - channel_number: *number, - peer: c.peer_address, - allocation_port: c.allocation, - }); - - tracing::info!(%peer, %number, allocation = %port, "Deleted channel binding"); - - false - }); + tracing::info!(%peer, %number, allocation = %port, "Deleted channel binding"); + } self.allocations_up_down_counter.add(-1, &[]); self.pending_commands.push_back(Command::FreeAllocation { diff --git a/rust/rust-toolchain.toml b/rust/rust-toolchain.toml index 8c6b3feae..f800031f3 100644 --- a/rust/rust-toolchain.toml +++ b/rust/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.90.0" # Keep in sync with `Dockerfile` +channel = "1.91.0" # Keep in sync with `Dockerfile` components = ["rust-src", "rust-analyzer", "clippy"] targets = ["x86_64-unknown-linux-musl"]