From bcf4ccf817f8581efe90fa92514622d5fae4547f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 25 Nov 2025 15:14:17 +1100 Subject: [PATCH] fix(rust): introduce dedicated downcast functions for `anyhow` (#10966) The downcasting abilities of `anyhow` are pretty powerful. Unfortunately, they can also be a bit tricky to get right. Whilst `is` and `downcast` work fine for any errors that are within the `anyhow` error chain, they don't check the chain of errors prior to that. In other words, if we already have a nested `std::error::Error` with several causes, `anyhow` cannot downcast to these causes directly. In order to avoid this footgun, we create a thin-layer on top of the `anyhow` crate with some downcasting functions that always try to do the right thing. --- rust/Cargo.lock | 58 ++++--- rust/Cargo.toml | 3 +- rust/anyhow-ext/Cargo.toml | 14 ++ rust/anyhow-ext/lib.rs | 152 ++++++++++++++++++ rust/client-shared/src/eventloop.rs | 17 +- rust/clippy.toml | 5 + rust/connlib/snownet/src/node/connections.rs | 1 + rust/connlib/socket-factory/src/lib.rs | 5 +- rust/connlib/tun/src/unix.rs | 6 +- rust/connlib/tunnel/src/client.rs | 4 +- rust/connlib/tunnel/src/gateway.rs | 4 +- .../tunnel/src/gateway/client_on_gateway.rs | 1 + rust/connlib/tunnel/src/io.rs | 4 +- rust/connlib/tunnel/src/lib.rs | 4 +- rust/connlib/tunnel/src/sockets.rs | 6 +- rust/gateway/src/eventloop.rs | 19 +-- .../src-tauri/src/bin/firezone-gui-client.rs | 13 +- rust/gui-client/src-tauri/src/controller.rs | 5 +- rust/gui-client/src-tauri/src/service.rs | 6 +- rust/relay/server/Cargo.toml | 2 +- 20 files changed, 249 insertions(+), 80 deletions(-) create mode 100644 rust/anyhow-ext/Cargo.toml create mode 100644 rust/anyhow-ext/lib.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e1412c205..1aea44f6a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -311,6 +311,14 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "anyhow-ext" +version = "0.1.0" +dependencies = [ + "anyhow", + "thiserror 2.0.17", +] + [[package]] name = "arbitrary" version = "1.4.2" @@ -1293,7 +1301,7 @@ name = "client-ffi" version = "0.1.0" dependencies = [ "android_log-sys", - "anyhow", + "anyhow-ext", "backoff", "client-shared", "connlib-model", @@ -1327,7 +1335,7 @@ dependencies = [ name = "client-shared" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "backoff", "bimap", "chrono", @@ -2043,7 +2051,7 @@ dependencies = [ name = "dns-over-tcp" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "dns-types", "firezone-bin-shared", "firezone-logging", @@ -2352,7 +2360,7 @@ dependencies = [ name = "firezone-bin-shared" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "atomicwrites", "axum", "bufferpool", @@ -2405,7 +2413,7 @@ dependencies = [ name = "firezone-cli" version = "1.0.0" dependencies = [ - "anyhow", + "anyhow-ext", "clap", "nix 0.30.1", "rpassword", @@ -2418,7 +2426,7 @@ dependencies = [ name = "firezone-gateway" version = "1.4.19" dependencies = [ - "anyhow", + "anyhow-ext", "backoff", "boringtun", "caps", @@ -2468,7 +2476,7 @@ name = "firezone-gui-client" version = "1.5.9" dependencies = [ "admx-macro", - "anyhow", + "anyhow-ext", "arboard", "atomicwrites", "backoff", @@ -2536,7 +2544,7 @@ dependencies = [ name = "firezone-headless-client" version = "1.5.5" dependencies = [ - "anyhow", + "anyhow-ext", "backoff", "clap", "client-shared", @@ -2571,7 +2579,7 @@ dependencies = [ name = "firezone-logging" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "firezone-telemetry", "nu-ansi-term", "output_vt100", @@ -2591,7 +2599,7 @@ dependencies = [ name = "firezone-relay" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "axum", "aya", "aya-build", @@ -2643,7 +2651,7 @@ dependencies = [ name = "firezone-telemetry" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "flume", "futures", "hex", @@ -2668,7 +2676,7 @@ dependencies = [ name = "firezone-tunnel" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "base64 0.22.1", "bimap", "boringtun", @@ -3293,7 +3301,7 @@ dependencies = [ name = "gui-smoke-test" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "clap", "subprocess", "tracing", @@ -3554,7 +3562,7 @@ dependencies = [ name = "http-client" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "bytes", "futures", "http 1.3.1", @@ -3576,7 +3584,7 @@ dependencies = [ name = "http-test-server" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "axum", "futures", "serde", @@ -3908,7 +3916,7 @@ dependencies = [ name = "ip-packet" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "arbitrary", "bufferpool", "etherparse", @@ -4164,7 +4172,7 @@ dependencies = [ name = "l3-tcp" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "ip-packet", "smoltcp", "tracing", @@ -4174,7 +4182,7 @@ dependencies = [ name = "l3-udp-dns-client" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "dns-types", "ip-packet", "rand 0.8.5", @@ -4185,7 +4193,7 @@ dependencies = [ name = "l4-tcp-dns-server" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "dns-types", "futures", "tokio", @@ -4196,7 +4204,7 @@ dependencies = [ name = "l4-udp-dns-client" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "dns-types", "futures", "socket-factory", @@ -4208,7 +4216,7 @@ dependencies = [ name = "l4-udp-dns-server" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "dns-types", "futures", "tokio", @@ -5492,7 +5500,7 @@ dependencies = [ name = "phoenix-channel" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "backoff", "base64 0.22.1", "firezone-logging", @@ -7072,7 +7080,7 @@ dependencies = [ name = "snownet" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "bnum", "boringtun", "bufferpool", @@ -7099,7 +7107,7 @@ dependencies = [ name = "socket-factory" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "bufferpool", "bytes", "derive_more 2.0.1", @@ -8533,7 +8541,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" name = "tun" version = "0.1.0" dependencies = [ - "anyhow", + "anyhow-ext", "ip-packet", "libc", "tokio", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index fa6fc42ee..e4e900c50 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "anyhow-ext", "bin-shared", "cli", "client-ffi", @@ -44,7 +45,7 @@ edition = "2024" [workspace.dependencies] admx-macro = { path = "gui-client/src-admx-macro" } -anyhow = "1.0.99" +anyhow = { package = "anyhow-ext", path = "anyhow-ext" } arbitrary = "1.4.2" arboard = { version = "3.6.1", default-features = false } async-trait = { version = "0.1", default-features = false } diff --git a/rust/anyhow-ext/Cargo.toml b/rust/anyhow-ext/Cargo.toml new file mode 100644 index 000000000..8f307ad8e --- /dev/null +++ b/rust/anyhow-ext/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "anyhow-ext" +version = "0.1.0" +edition = { workspace = true } +license = { workspace = true } + +[lib] +path = "lib.rs" + +[dependencies] +anyhow = "1.0.99" + +[dev-dependencies] +thiserror = { workspace = true } diff --git a/rust/anyhow-ext/lib.rs b/rust/anyhow-ext/lib.rs new file mode 100644 index 000000000..b34020e72 --- /dev/null +++ b/rust/anyhow-ext/lib.rs @@ -0,0 +1,152 @@ +pub use anyhow::*; + +pub trait ErrorExt { + fn any_is(&self) -> bool + where + T: std::error::Error + Send + Sync + 'static; + fn any_downcast_ref(&self) -> Option<&T> + where + T: std::error::Error + Send + Sync + 'static; +} + +impl ErrorExt for anyhow::Error { + #[expect( + clippy::disallowed_methods, + reason = "We are implementing the alternative." + )] + fn any_is(&self) -> bool + where + T: std::error::Error + Send + Sync + 'static, + { + self.is::() || self.chain().any(|e| e.is::()) + } + + #[expect( + clippy::disallowed_methods, + reason = "We are implementing the alternative." + )] + fn any_downcast_ref(&self) -> Option<&T> + where + T: std::error::Error + Send + Sync + 'static, + { + std::iter::empty() + .chain(self.downcast_ref::()) + .chain(self.chain().flat_map(|e| e.downcast_ref::())) + .next() + } +} + +#[cfg(test)] +mod tests { + use std::io; + + use anyhow::{Context, Result}; + + use super::*; + + #[test] + fn any_is_works_for_context() { + let error = Result::<(), _>::Err(io::Error::other("Test")) + .context("Foobar") + .unwrap_err(); + + assert!(error.any_is::()) + } + + #[test] + fn any_is_works_for_typed_context() { + let error = Result::<(), _>::Err(io::Error::other("Test")) + .context(FooError) + .unwrap_err(); + + assert!(error.any_is::()) + } + + #[test] + fn any_is_works_for_anyhow_new() { + let error = Result::<(), _>::Err(anyhow::Error::new(io::Error::other("Test"))) + .context("Foobar") + .unwrap_err(); + + assert!(error.any_is::()) + } + + #[test] + fn any_is_works_for_custom_error_with_source() { + let error = Result::<(), _>::Err(BazError(BarError(FooError))) + .context("Foobar") + .unwrap_err(); + + assert!(error.any_is::()); + assert!(error.any_is::()); + assert!(error.any_is::()); + } + + #[test] + fn any_downcast_ref_works_for_context() { + let error = Result::<(), _>::Err(io::Error::other("Test")) + .context("Foobar") + .unwrap_err(); + + assert_eq!( + error.any_downcast_ref::().unwrap().to_string(), + "Test" + ) + } + + #[test] + fn any_downcast_ref_works_for_typed() { + let error = Result::<(), _>::Err(io::Error::other("Test")) + .context(FooError) + .unwrap_err(); + + assert_eq!( + error.any_downcast_ref::().unwrap().to_string(), + "Foo" + ) + } + + #[test] + fn any_downcast_ref_works_for_anyhow_new() { + let error = Result::<(), _>::Err(anyhow::Error::new(io::Error::other("Test"))) + .context("Foobar") + .unwrap_err(); + + assert_eq!( + error.any_downcast_ref::().unwrap().to_string(), + "Test" + ) + } + + #[test] + fn any_downcast_ref_works_for_custom_error_with_source() { + let error = Result::<(), _>::Err(BazError(BarError(FooError))) + .context("Foobar") + .unwrap_err(); + + assert_eq!( + error.any_downcast_ref::().unwrap().to_string(), + "Baz" + ); + assert_eq!( + error.any_downcast_ref::().unwrap().to_string(), + "Bar" + ); + assert_eq!( + error.any_downcast_ref::().unwrap().to_string(), + "Foo" + ); + } + + #[derive(Debug, thiserror::Error)] + #[error("Foo")] + struct FooError; + + #[derive(Debug, thiserror::Error)] + #[error("Bar")] + struct BarError(#[from] FooError); + + #[derive(Debug, thiserror::Error)] + #[error("Baz")] + struct BazError(#[from] BarError); +} diff --git a/rust/client-shared/src/eventloop.rs b/rust/client-shared/src/eventloop.rs index 951698b36..8b0b3d1c5 100644 --- a/rust/client-shared/src/eventloop.rs +++ b/rust/client-shared/src/eventloop.rs @@ -1,5 +1,5 @@ use crate::PHOENIX_TOPIC; -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, ErrorExt as _, Result}; use connlib_model::{PublicKey, ResourceView}; use firezone_tunnel::messages::RelaysPresence; use firezone_tunnel::messages::client::{ @@ -88,7 +88,7 @@ impl From for DisconnectError { impl DisconnectError { pub fn is_authentication_error(&self) -> bool { - let Some(e) = self.0.downcast_ref::() else { + let Some(e) = self.0.any_downcast_ref::() else { return false; }; @@ -308,8 +308,7 @@ impl Eventloop { fn handle_tunnel_error(&mut self, mut e: TunnelError) -> Result<()> { for e in e.drain() { - if e.root_cause() - .downcast_ref::() + if e.any_downcast_ref::() .is_some_and(is_unreachable) { tracing::debug!("{e:#}"); // Log these on DEBUG so they don't go completely unnoticed. @@ -317,16 +316,14 @@ impl Eventloop { } // Invalid Input can be all sorts of things but we mostly see it with unreachable addresses. - if e.root_cause() - .downcast_ref::() + if e.any_downcast_ref::() .is_some_and(|e| e.kind() == io::ErrorKind::InvalidInput) { tracing::debug!("{e:#}"); continue; } - if e.root_cause() - .downcast_ref::() + if e.any_downcast_ref::() .is_some_and(|e| e.kind() == io::ErrorKind::PermissionDenied) { if !mem::replace(&mut self.logged_permission_denied, true) { @@ -338,9 +335,7 @@ impl Eventloop { continue; } - if e.root_cause() - .is::() - { + if e.any_is::() { return Err(e); } diff --git a/rust/clippy.toml b/rust/clippy.toml index 9276330f5..0a8af34b1 100644 --- a/rust/clippy.toml +++ b/rust/clippy.toml @@ -6,4 +6,9 @@ disallowed-methods = [ { path = "std::collections::HashSet::iter", reason = "HashSet has non-deterministic iteration order, use BTreeSet instead" }, { path = "std::collections::HashMap::iter_mut", reason = "HashMap has non-deterministic iteration order, use BTreeMap instead" }, { path = "tracing::subscriber::set_global_default", reason = "Does not init `LogTracer`, use `firezone_logging::init` instead." }, + { path = "anyhow::Error::is", reason = "Does not check entire source chain, use `any_is` instead" }, + { path = "anyhow::Error::downcast_ref", reason = "Does not check entire source chain, use `any_downcast_ref` instead" }, + { path = "anyhow::Error::downcast_mut", reason = "Does not check entire source chain, use `any_downcast_ref` instead" }, + { path = "anyhow::Error::downcast", reason = "Does not check entire source chain, use `any_downcast_ref` instead" }, + { path = "anyhow::Error::root_cause", reason = "Does not check entire source chain, use `any_downcast_ref` instead" }, ] diff --git a/rust/connlib/snownet/src/node/connections.rs b/rust/connlib/snownet/src/node/connections.rs index 6204f4016..8c05f0813 100644 --- a/rust/connlib/snownet/src/node/connections.rs +++ b/rust/connlib/snownet/src/node/connections.rs @@ -484,6 +484,7 @@ mod tests { (id, idx, key) } + #[expect(clippy::disallowed_methods, reason = "This is a test.")] fn assert_disconnected( connections: &mut Connections, id: u32, diff --git a/rust/connlib/socket-factory/src/lib.rs b/rust/connlib/socket-factory/src/lib.rs index 748bf7461..944f7691c 100644 --- a/rust/connlib/socket-factory/src/lib.rs +++ b/rust/connlib/socket-factory/src/lib.rs @@ -518,8 +518,9 @@ fn is_equal_modulo_scope_for_ipv6_link_local(expected: SocketAddr, actual: Socke #[cfg(any(target_os = "linux", target_os = "android"))] fn is_os_error_5(e: &anyhow::Error) -> bool { - e.root_cause() - .downcast_ref::() + use anyhow::ErrorExt; + + e.any_downcast_ref::() .and_then(|e| e.raw_os_error()) .is_some_and(|c| c == libc::EIO) } diff --git a/rust/connlib/tun/src/unix.rs b/rust/connlib/tun/src/unix.rs index 535185e3a..e4e04b93d 100644 --- a/rust/connlib/tun/src/unix.rs +++ b/rust/connlib/tun/src/unix.rs @@ -1,4 +1,4 @@ -use anyhow::{Context as _, Result, bail}; +use anyhow::{Context as _, ErrorExt, Result, bail}; use ip_packet::{IpPacket, IpPacketBuf}; use std::io; use std::os::fd::AsRawFd; @@ -80,7 +80,7 @@ where break; }; } - Err(e) if e.root_cause().is::() => { + Err(e) if e.any_is::() => { tracing::debug!("{e:#}"); // Log on debug to be less noisy. continue; } @@ -109,6 +109,6 @@ mod tests { let final_error = anyhow::Error::new(io_error).context("Failed to read from TUN fd"); - assert!(final_error.root_cause().is::()) + assert!(final_error.any_is::()) } } diff --git a/rust/connlib/tunnel/src/client.rs b/rust/connlib/tunnel/src/client.rs index 3a4ee4bb1..f8cb4cd7c 100644 --- a/rust/connlib/tunnel/src/client.rs +++ b/rust/connlib/tunnel/src/client.rs @@ -26,7 +26,7 @@ use crate::messages::{IceCredentials, SecretKey}; use crate::peer_store::PeerStore; use crate::unique_packet_buffer::UniquePacketBuffer; use crate::{IPV4_TUNNEL, IPV6_TUNNEL, IpConfig, TunConfig, dns, is_peer, p2p_control}; -use anyhow::Context; +use anyhow::{Context, ErrorExt}; use connlib_model::{ GatewayId, IceCandidate, PublicKey, RelayId, ResourceId, ResourceStatus, ResourceView, }; @@ -520,7 +520,7 @@ impl ClientState { } Err(e) if response.transport == dns::Transport::Udp - && e.downcast_ref::() + && e.any_downcast_ref::() .is_some_and(|e| e.kind() == io::ErrorKind::TimedOut) => { tracing::debug!("Recursive UDP DNS query timed out"); diff --git a/rust/connlib/tunnel/src/gateway.rs b/rust/connlib/tunnel/src/gateway.rs index 3f08dbc39..8381d3e12 100644 --- a/rust/connlib/tunnel/src/gateway.rs +++ b/rust/connlib/tunnel/src/gateway.rs @@ -15,7 +15,7 @@ use crate::messages::gateway::{Client, ResourceDescription, Subject}; use crate::messages::{Answer, IceCredentials, ResolveRequest}; use crate::peer_store::PeerStore; use crate::{FailedToDecapsulate, GatewayEvent, IpConfig, p2p_control, packet_kind}; -use anyhow::{Context, Result}; +use anyhow::{Context, ErrorExt, Result}; use boringtun::x25519::{self, PublicKey}; use chrono::{DateTime, Utc}; use connlib_model::{ClientId, IceCandidate, RelayId, ResourceId}; @@ -133,7 +133,7 @@ impl GatewayState { let encrypted_packet = match self.node.encapsulate(cid, &packet, now) { Ok(Some(encrypted_packet)) => encrypted_packet, Ok(None) => return Ok(None), - Err(e) if e.is::() => { + Err(e) if e.any_is::() => { return Err(e.context(UnroutablePacket::not_connected(&packet))); } Err(e) => return Err(e), diff --git a/rust/connlib/tunnel/src/gateway/client_on_gateway.rs b/rust/connlib/tunnel/src/gateway/client_on_gateway.rs index 8ddd8a40e..c25f7d947 100644 --- a/rust/connlib/tunnel/src/gateway/client_on_gateway.rs +++ b/rust/connlib/tunnel/src/gateway/client_on_gateway.rs @@ -1024,6 +1024,7 @@ mod tests { now += nat_table::UDP_TTL; peer.handle_timeout(now); + #[expect(clippy::disallowed_methods, reason = "This is a test.")] let err = peer .translate_inbound(response, now) .unwrap_err() diff --git a/rust/connlib/tunnel/src/io.rs b/rust/connlib/tunnel/src/io.rs index 9fe1978fc..9d1d33d07 100644 --- a/rust/connlib/tunnel/src/io.rs +++ b/rust/connlib/tunnel/src/io.rs @@ -5,7 +5,7 @@ mod tcp_dns; mod udp_dns; use crate::{TunnelError, device_channel::Device, dns, otel, sockets::Sockets}; -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, ErrorExt, Result}; use chrono::{DateTime, Utc}; use dns_types::DoHUrl; use futures::FutureExt as _; @@ -361,7 +361,7 @@ impl Io { if let Poll::Ready(response) = &dns_response && let dns::Upstream::DoH { server } = &response.server && let Err(e) = &response.message - && e.is::() + && e.any_is::() { tracing::debug!(%server, "Connection of DoH client failed"); diff --git a/rust/connlib/tunnel/src/lib.rs b/rust/connlib/tunnel/src/lib.rs index 40bef8ed5..63aad9c22 100644 --- a/rust/connlib/tunnel/src/lib.rs +++ b/rust/connlib/tunnel/src/lib.rs @@ -5,7 +5,7 @@ #![cfg_attr(test, allow(clippy::unwrap_used))] -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, ErrorExt as _, Result}; use chrono::Utc; use connlib_model::{ClientId, GatewayId, IceCandidate, PublicKey, ResourceId, ResourceView}; use dns_types::DomainName; @@ -448,7 +448,7 @@ impl GatewayTunnel { } Err(e) => { let routing_error = e - .downcast_ref::() + .any_downcast_ref::() .map(|e| e.reason()) .unwrap_or(gateway::RoutingError::Other); diff --git a/rust/connlib/tunnel/src/sockets.rs b/rust/connlib/tunnel/src/sockets.rs index 91dc0a467..c54c70d35 100644 --- a/rust/connlib/tunnel/src/sockets.rs +++ b/rust/connlib/tunnel/src/sockets.rs @@ -1,5 +1,5 @@ use crate::otel; -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, ErrorExt as _, Result}; use futures::{SinkExt, ready}; use gat_lending_iterator::LendingIterator; use socket_factory::DatagramOut; @@ -271,7 +271,7 @@ impl ThreadedUdpSocket { tokio::task::yield_now().await; if let Err(e) = socket.send(datagram).await { - if let Some(io) = e.downcast_ref::() { + if let Some(io) = e.any_downcast_ref::() { io_error_counter.add( 1, &[ @@ -307,7 +307,7 @@ impl ThreadedUdpSocket { if let Some(io) = result .as_ref() .err() - .and_then(|e| e.downcast_ref::()) + .and_then(|e| e.any_downcast_ref::()) { io_error_counter.add( 1, diff --git a/rust/gateway/src/eventloop.rs b/rust/gateway/src/eventloop.rs index 91419d201..1354aa5c1 100644 --- a/rust/gateway/src/eventloop.rs +++ b/rust/gateway/src/eventloop.rs @@ -1,4 +1,4 @@ -use anyhow::{Context as _, Result}; +use anyhow::{Context as _, ErrorExt, Result}; use boringtun::x25519::PublicKey; #[cfg(not(target_os = "windows"))] use dns_lookup::{AddrInfoHints, AddrInfoIter, LookupError}; @@ -288,8 +288,7 @@ impl Eventloop { fn handle_tunnel_error(&mut self, mut e: TunnelError) -> Result<()> { for e in e.drain() { - if e.root_cause() - .downcast_ref::() + if e.any_downcast_ref::() .is_some_and(is_unreachable) { tracing::debug!("{e:#}"); // Log these on DEBUG so they don't go completely unnoticed. @@ -297,16 +296,14 @@ impl Eventloop { } // Invalid Input can be all sorts of things but we mostly see it with unreachable addresses. - if e.root_cause() - .downcast_ref::() + if e.any_downcast_ref::() .is_some_and(|e| e.kind() == io::ErrorKind::InvalidInput) { tracing::debug!("{e:#}"); continue; } - if e.root_cause() - .downcast_ref::() + if e.any_downcast_ref::() .is_some_and(|e| e.kind() == io::ErrorKind::PermissionDenied) { if !mem::replace(&mut self.logged_permission_denied, true) { @@ -318,20 +315,18 @@ impl Eventloop { continue; } - if e.root_cause().is::() { + if e.any_is::() { // Some IP packets cannot be translated and should be dropped "silently". // Do so by ignoring the error here. continue; } - if let Some(e) = e.downcast_ref::() { + if let Some(e) = e.any_downcast_ref::() { tracing::debug!(src = %e.source(), dst = %e.destination(), proto = %e.proto(), "{e:#}"); continue; } - if e.root_cause() - .is::() - { + if e.any_is::() { return Err(e); } diff --git a/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs b/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs index 918ed95b3..05a0b0caf 100644 --- a/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs +++ b/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs @@ -6,7 +6,7 @@ use std::process::ExitCode; -use anyhow::{Context as _, Result, bail}; +use anyhow::{Context as _, ErrorExt, Result, bail}; use clap::{Args, Parser}; use controller::Failure; use firezone_gui_client::{controller, deep_link, elevation, gui, logging, settings}; @@ -169,28 +169,25 @@ fn try_main( return Err(anyhow); } - if anyhow.root_cause().is::() { + if anyhow.any_is::() { return Ok(()); } - if anyhow.root_cause().is::() { + if anyhow.any_is::() { show_error_dialog( "Firezone is already running but not responding. Please force-stop it first.", )?; return Err(anyhow); } - if anyhow - .root_cause() - .is::() - { + if anyhow.any_is::() { show_error_dialog( "Couldn't find Firezone Tunnel service. Is the service running?", )?; return Err(anyhow); } - if anyhow.root_cause().is::() { + if anyhow.any_is::() { show_error_dialog( "The Firezone Tunnel service is not responding. If the issue persists, contact your administrator.", )?; diff --git a/rust/gui-client/src-tauri/src/controller.rs b/rust/gui-client/src-tauri/src/controller.rs index 8e26947db..e3e97df5d 100644 --- a/rust/gui-client/src-tauri/src/controller.rs +++ b/rust/gui-client/src-tauri/src/controller.rs @@ -8,7 +8,7 @@ use crate::{ updates, uptime, view::{GeneralSettingsForm, SessionViewModel}, }; -use anyhow::{Context, Result, anyhow, bail}; +use anyhow::{Context, ErrorExt as _, Result, anyhow, bail}; use connlib_model::ResourceView; use firezone_logging::FilterReloadHandle; use firezone_telemetry::Telemetry; @@ -669,8 +669,7 @@ impl Controller { Ok(()) => {} Err(error) if error - .root_cause() - .downcast_ref::() + .any_downcast_ref::() .is_some_and(|e| matches!(e, auth::Error::NoInflightRequest)) => { tracing::debug!("Ignoring deep-link; no local state"); diff --git a/rust/gui-client/src-tauri/src/service.rs b/rust/gui-client/src-tauri/src/service.rs index 69f27417e..c3f00bffc 100644 --- a/rust/gui-client/src-tauri/src/service.rs +++ b/rust/gui-client/src-tauri/src/service.rs @@ -2,7 +2,7 @@ use crate::{ ipc::{self, SocketId}, logging, }; -use anyhow::{Context as _, Result, bail}; +use anyhow::{Context as _, ErrorExt as _, Result, bail}; use atomicwrites::{AtomicFile, OverwriteBehavior}; use backoff::ExponentialBackoffBuilder; use connlib_model::ResourceView; @@ -399,7 +399,7 @@ impl<'a> Handler<'a> { if let Some(e) = result .as_ref() .err() - .and_then(|e| e.root_cause().downcast_ref::()) + .and_then(|e| e.any_downcast_ref::()) { tracing::debug!("Still cannot connect to Firezone: {e}"); @@ -534,7 +534,7 @@ impl<'a> Handler<'a> { if let Some(e) = result .as_ref() .err() - .and_then(|e| e.root_cause().downcast_ref::()) + .and_then(|e| e.any_downcast_ref::()) { tracing::debug!( "Encountered IO error when connecting to portal, most likely we don't have Internet: {e}" diff --git a/rust/relay/server/Cargo.toml b/rust/relay/server/Cargo.toml index f12df5cd1..3f24b6d05 100644 --- a/rust/relay/server/Cargo.toml +++ b/rust/relay/server/Cargo.toml @@ -55,7 +55,7 @@ aya-log = { workspace = true } ebpf-shared = { workspace = true, features = ["std"] } [target.'cfg(target_os = "linux")'.build-dependencies] -anyhow = "1" +anyhow = { workspace = true } aya-build = { workspace = true } [dev-dependencies]