From d6805d7e48faf569425505fe66b1ef3e59532bea Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sat, 12 Jul 2025 08:42:50 +0200 Subject: [PATCH] chore(rust): bump to Rust 1.88 (#9714) Rust 1.88 has been released and brings with it a quite exciting feature: let-chains! It allows us to mix-and-match `if` and `let` expressions, therefore often reducing the "right-drift" of the relevant code, making it easier to read. Rust.188 also comes with a new clippy lint that warns when creating a mutable reference from an immutable pointer. Attempting to fix this revealed that this is exactly what we are doing in the eBPF kernel. Unfortunately, it doesn't seem to be possible to design this in a way that is both accepted by the borrow-checker AND by the eBPF verifier. Hence, we simply make the function `unsafe` and document for the programmer, what needs to be upheld. --- .github/actions/setup-rust/action.yml | 2 +- rust/apple-client-ffi/build.rs | 4 +- rust/bin-shared/src/known_dirs/macos.rs | 2 +- rust/clippy.toml | 3 - rust/connlib/l4-tcp-dns-server/lib.rs | 24 ++++---- rust/connlib/tunnel/src/client.rs | 14 ++--- rust/connlib/tunnel/src/peer/nat_table.rs | 10 ++-- rust/connlib/tunnel/src/tests/reference.rs | 4 +- rust/connlib/tunnel/src/tests/sim_client.rs | 58 +++++++++---------- rust/connlib/tunnel/src/tests/sim_gateway.rs | 32 +++++----- rust/gateway/src/main.rs | 16 ++--- rust/gui-client/src-admx-macro/lib.rs | 3 +- .../src-tauri/src/bin/firezone-gui-client.rs | 2 +- .../src-tauri/src/deep_link/windows.rs | 6 +- rust/gui-client/src-tauri/src/ipc/windows.rs | 2 +- rust/logging/src/err_with_sources.rs | 2 +- rust/logging/src/format.rs | 2 +- .../ebpf-turn-router/src/channel_data.rs | 9 ++- rust/relay/ebpf-turn-router/src/eth.rs | 8 ++- rust/relay/ebpf-turn-router/src/ip4.rs | 8 ++- rust/relay/ebpf-turn-router/src/ip6.rs | 8 ++- rust/relay/ebpf-turn-router/src/main.rs | 21 ++++--- rust/relay/ebpf-turn-router/src/ref_mut_at.rs | 7 ++- rust/relay/ebpf-turn-router/src/udp.rs | 8 ++- rust/relay/server/build.rs | 2 +- rust/relay/server/src/auth.rs | 4 +- rust/relay/server/tests/regression.rs | 6 +- rust/rust-toolchain.toml | 2 +- 28 files changed, 149 insertions(+), 120 deletions(-) diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index dd6ef304c..15586f880 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -64,7 +64,7 @@ runs: - name: Install nightly Rust id: nightly run: | - NIGHTLY="nightly-2024-12-13" + NIGHTLY="nightly-2025-05-30" rustup toolchain install $NIGHTLY rustup component add rust-src --toolchain $NIGHTLY diff --git a/rust/apple-client-ffi/build.rs b/rust/apple-client-ffi/build.rs index 117a2b5ba..d7744c202 100644 --- a/rust/apple-client-ffi/build.rs +++ b/rust/apple-client-ffi/build.rs @@ -5,9 +5,9 @@ fn main() { let bridges = vec!["src/lib.rs"]; for path in &bridges { - println!("cargo:rerun-if-changed={}", path); + println!("cargo:rerun-if-changed={path}"); } - println!("cargo:rerun-if-env-changed={}", XCODE_CONFIGURATION_ENV); + println!("cargo:rerun-if-env-changed={XCODE_CONFIGURATION_ENV}"); swift_bridge_build::parse_bridges(bridges) .write_all_concatenated(out_dir, env!("CARGO_PKG_NAME")); diff --git a/rust/bin-shared/src/known_dirs/macos.rs b/rust/bin-shared/src/known_dirs/macos.rs index 7b9d9b7f3..f5dcd1658 100644 --- a/rust/bin-shared/src/known_dirs/macos.rs +++ b/rust/bin-shared/src/known_dirs/macos.rs @@ -35,7 +35,7 @@ pub fn logs() -> Option { /// Runtime directory for temporary files pub fn runtime() -> Option { let user = std::env::var("USER").ok()?; - Some(PathBuf::from("/tmp").join(format!("{}-{}", BUNDLE_ID, user))) + Some(PathBuf::from("/tmp").join(format!("{BUNDLE_ID}-{user}"))) } /// User session data directory diff --git a/rust/clippy.toml b/rust/clippy.toml index 30d4edfea..ba5dd243e 100644 --- a/rust/clippy.toml +++ b/rust/clippy.toml @@ -3,8 +3,5 @@ disallowed-methods = [ { path = "std::collections::HashMap::iter", reason = "HashMap has non-deterministic iteration order, use BTreeMap instead" }, { 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 = "std::collections::HashSet::iter_mut", reason = "HashSet has non-deterministic iteration order, use BTreeSet instead" }, - { path = "std::collections::HashMap::into_iter", reason = "HashMap has non-deterministic iteration order, use BTreeMap instead" }, - { path = "std::collections::HashSet::into_iter", reason = "HashSet has non-deterministic iteration order, use BTreeSet instead" }, { path = "tracing::subscriber::set_global_default", reason = "Does not init `LogTracer`, use `firezone_logging::init` instead." }, ] diff --git a/rust/connlib/l4-tcp-dns-server/lib.rs b/rust/connlib/l4-tcp-dns-server/lib.rs index 3dcc122bc..86cb09a51 100644 --- a/rust/connlib/l4-tcp-dns-server/lib.rs +++ b/rust/connlib/l4-tcp-dns-server/lib.rs @@ -136,20 +136,20 @@ impl Server { })); } - if let Some(tcp_v4) = self.tcp_v4.as_mut() { - if let Poll::Ready((stream, from)) = tcp_v4.poll_accept(cx)? { - self.reading_tcp_queries - .push(read_tcp_query(stream, from).boxed()); - continue; - } + if let Some(tcp_v4) = self.tcp_v4.as_mut() + && let Poll::Ready((stream, from)) = tcp_v4.poll_accept(cx)? + { + self.reading_tcp_queries + .push(read_tcp_query(stream, from).boxed()); + continue; } - if let Some(tcp_v6) = self.tcp_v6.as_mut() { - if let Poll::Ready((stream, from)) = tcp_v6.poll_accept(cx)? { - self.reading_tcp_queries - .push(read_tcp_query(stream, from).boxed()); - continue; - } + if let Some(tcp_v6) = self.tcp_v6.as_mut() + && let Poll::Ready((stream, from)) = tcp_v6.poll_accept(cx)? + { + self.reading_tcp_queries + .push(read_tcp_query(stream, from).boxed()); + continue; } self.waker.register(cx.waker()); diff --git a/rust/connlib/tunnel/src/client.rs b/rust/connlib/tunnel/src/client.rs index 837064cf9..b19f9ed5f 100644 --- a/rust/connlib/tunnel/src/client.rs +++ b/rust/connlib/tunnel/src/client.rs @@ -622,13 +622,13 @@ impl ClientState { return Ok(Ok(())); }; - if let Some(old_gateway_id) = self.resources_gateways.insert(resource_id, gateway_id) { - if self.peers.get(&old_gateway_id).is_some() { - assert_eq!( - old_gateway_id, gateway_id, - "Resources are not expected to change gateways without a previous message, resource_id = {resource_id}" - ); - } + if let Some(old_gateway_id) = self.resources_gateways.insert(resource_id, gateway_id) + && self.peers.get(&old_gateway_id).is_some() + { + assert_eq!( + old_gateway_id, gateway_id, + "Resources are not expected to change gateways without a previous message, resource_id = {resource_id}" + ) } match self.node.upsert_connection( diff --git a/rust/connlib/tunnel/src/peer/nat_table.rs b/rust/connlib/tunnel/src/peer/nat_table.rs index 8a3c79a67..dc648e6ac 100644 --- a/rust/connlib/tunnel/src/peer/nat_table.rs +++ b/rust/connlib/tunnel/src/peer/nat_table.rs @@ -36,11 +36,11 @@ impl NatTable { Protocol::Icmp(_) => ICMP_TTL, }; - if now.duration_since(*e) >= ttl { - if let Some((inside, _)) = self.table.remove_by_right(outside) { - tracing::debug!(?inside, ?outside, ?ttl, "NAT session expired"); - self.expired.insert(*outside); - } + if now.duration_since(*e) >= ttl + && let Some((inside, _)) = self.table.remove_by_right(outside) + { + tracing::debug!(?inside, ?outside, ?ttl, "NAT session expired"); + self.expired.insert(*outside); } } } diff --git a/rust/connlib/tunnel/src/tests/reference.rs b/rust/connlib/tunnel/src/tests/reference.rs index ecb16d118..7029ab7fb 100644 --- a/rust/connlib/tunnel/src/tests/reference.rs +++ b/rust/connlib/tunnel/src/tests/reference.rs @@ -168,8 +168,8 @@ impl ReferenceState { "private keys must be unique", |(c, gateways, _, _, _, _, _, _, _)| { let different_keys = gateways - .iter() - .map(|(_, g)| g.inner().key) + .values() + .map(|g| g.inner().key) .chain(iter::once(c.inner().key)) .collect::>(); diff --git a/rust/connlib/tunnel/src/tests/sim_client.rs b/rust/connlib/tunnel/src/tests/sim_client.rs index c8e2f0993..e678658f3 100644 --- a/rust/connlib/tunnel/src/tests/sim_client.rs +++ b/rust/connlib/tunnel/src/tests/sim_client.rs @@ -204,20 +204,20 @@ impl SimClient { } fn update_sent_requests(&mut self, packet: &IpPacket) { - if let Some(icmp) = packet.as_icmpv4() { - if let Icmpv4Type::EchoRequest(echo) = icmp.icmp_type() { - self.sent_icmp_requests - .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); - return; - } + if let Some(icmp) = packet.as_icmpv4() + && let Icmpv4Type::EchoRequest(echo) = icmp.icmp_type() + { + self.sent_icmp_requests + .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); + return; } - if let Some(icmp) = packet.as_icmpv6() { - if let Icmpv6Type::EchoRequest(echo) = icmp.icmp_type() { - self.sent_icmp_requests - .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); - return; - } + if let Some(icmp) = packet.as_icmpv6() + && let Icmpv6Type::EchoRequest(echo) = icmp.icmp_type() + { + self.sent_icmp_requests + .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); + return; } if let Some(udp) = packet.as_udp() { @@ -311,20 +311,20 @@ impl SimClient { return; } - if let Some(icmp) = packet.as_icmpv4() { - if let Icmpv4Type::EchoReply(echo) = icmp.icmp_type() { - self.received_icmp_replies - .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); - return; - } + if let Some(icmp) = packet.as_icmpv4() + && let Icmpv4Type::EchoReply(echo) = icmp.icmp_type() + { + self.received_icmp_replies + .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); + return; } - if let Some(icmp) = packet.as_icmpv6() { - if let Icmpv6Type::EchoReply(echo) = icmp.icmp_type() { - self.received_icmp_replies - .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); - return; - } + if let Some(icmp) = packet.as_icmpv6() + && let Icmpv6Type::EchoReply(echo) = icmp.icmp_type() + { + self.received_icmp_replies + .insert((Seq(echo.seq), Identifier(echo.id)), packet.clone()); + return; } tracing::error!(?packet, "Unhandled packet"); @@ -521,12 +521,12 @@ impl RefClient { continue; } - if let Some(overlapping_resource) = table.exact_match(resource.address) { - if self.is_connected_to_internet_or_cidr(*overlapping_resource) { - tracing::debug!(%overlapping_resource, resource = %resource.id, address = %resource.address, "Already connected to resource with this exact address, retaining existing route"); + if let Some(overlapping_resource) = table.exact_match(resource.address) + && self.is_connected_to_internet_or_cidr(*overlapping_resource) + { + tracing::debug!(%overlapping_resource, resource = %resource.id, address = %resource.address, "Already connected to resource with this exact address, retaining existing route"); - continue; - } + continue; } tracing::debug!(resource = %resource.id, address = %resource.address, "Adding CIDR route"); diff --git a/rust/connlib/tunnel/src/tests/sim_gateway.rs b/rust/connlib/tunnel/src/tests/sim_gateway.rs index b701d00ec..5fd00d6e9 100644 --- a/rust/connlib/tunnel/src/tests/sim_gateway.rs +++ b/rust/connlib/tunnel/src/tests/sim_gateway.rs @@ -187,24 +187,24 @@ impl SimGateway { .icmp_error_for_ip(dst_ip) .map(|icmp_error| icmp_error_reply(&packet, icmp_error).unwrap()); - if let Some(icmp) = packet.as_icmpv4() { - if let Icmpv4Type::EchoRequest(echo) = icmp.icmp_type() { - let packet_id = u64::from_be_bytes(*icmp.payload().first_chunk().unwrap()); - tracing::debug!(%packet_id, "Received ICMP request"); - self.received_icmp_requests - .insert(packet_id, packet.clone()); - return self.handle_icmp_request(&packet, echo, icmp.payload(), icmp_error, now); - } + if let Some(icmp) = packet.as_icmpv4() + && let Icmpv4Type::EchoRequest(echo) = icmp.icmp_type() + { + let packet_id = u64::from_be_bytes(*icmp.payload().first_chunk().unwrap()); + tracing::debug!(%packet_id, "Received ICMP request"); + self.received_icmp_requests + .insert(packet_id, packet.clone()); + return self.handle_icmp_request(&packet, echo, icmp.payload(), icmp_error, now); } - if let Some(icmp) = packet.as_icmpv6() { - if let Icmpv6Type::EchoRequest(echo) = icmp.icmp_type() { - let packet_id = u64::from_be_bytes(*icmp.payload().first_chunk().unwrap()); - tracing::debug!(%packet_id, "Received ICMP request"); - self.received_icmp_requests - .insert(packet_id, packet.clone()); - return self.handle_icmp_request(&packet, echo, icmp.payload(), icmp_error, now); - } + if let Some(icmp) = packet.as_icmpv6() + && let Icmpv6Type::EchoRequest(echo) = icmp.icmp_type() + { + let packet_id = u64::from_be_bytes(*icmp.payload().first_chunk().unwrap()); + tracing::debug!(%packet_id, "Received ICMP request"); + self.received_icmp_requests + .insert(packet_id, packet.clone()); + return self.handle_icmp_request(&packet, echo, icmp.payload(), icmp_error, now); } if let Some(udp) = packet.as_udp() { diff --git a/rust/gateway/src/main.rs b/rust/gateway/src/main.rs index a09a43487..f20dcf79b 100644 --- a/rust/gateway/src/main.rs +++ b/rust/gateway/src/main.rs @@ -200,16 +200,16 @@ async fn try_main(cli: Cli, telemetry: &mut Telemetry) -> Result<()> { } async fn get_firezone_id(env_id: Option) -> Result { - if let Some(id) = env_id { - if !id.is_empty() { - return Ok(id); - } + if let Some(id) = env_id + && !id.is_empty() + { + return Ok(id); } - if let Ok(id) = tokio::fs::read_to_string(ID_PATH).await { - if !id.is_empty() { - return Ok(id); - } + if let Ok(id) = tokio::fs::read_to_string(ID_PATH).await + && !id.is_empty() + { + return Ok(id); } let device_id = device_id::get_or_create_at(Path::new(ID_PATH))?; diff --git a/rust/gui-client/src-admx-macro/lib.rs b/rust/gui-client/src-admx-macro/lib.rs index 86ba56d0d..c1d6c0557 100644 --- a/rust/gui-client/src-admx-macro/lib.rs +++ b/rust/gui-client/src-admx-macro/lib.rs @@ -54,8 +54,7 @@ fn try_admx(attr: TokenStream, item: TokenStream) -> syn::Result Result<(), io::Error> { let base = Path::new("Software").join("Classes").join(FZ_SCHEME); let (key, _) = hkcu.create_subkey(&base)?; - key.set_value("", &format!("URL:{}", id))?; + key.set_value("", &format!("URL:{id}"))?; key.set_value("URL Protocol", &"")?; let (icon, _) = hkcu.create_subkey(base.join("DefaultIcon"))?; - icon.set_value("", &format!("{},0", &exe))?; + icon.set_value("", &format!("{exe},0"))?; let (cmd, _) = hkcu.create_subkey(base.join("shell").join("open").join("command"))?; - cmd.set_value("", &format!("{} open-deep-link \"%1\"", &exe))?; + cmd.set_value("", &format!("{exe} open-deep-link \"%1\""))?; Ok(()) } diff --git a/rust/gui-client/src-tauri/src/ipc/windows.rs b/rust/gui-client/src-tauri/src/ipc/windows.rs index 88b7cb195..e12c5ca43 100644 --- a/rust/gui-client/src-tauri/src/ipc/windows.rs +++ b/rust/gui-client/src-tauri/src/ipc/windows.rs @@ -169,7 +169,7 @@ fn ipc_path(id: SocketId) -> String { /// Public because the GUI Client reuses this for deep links. Eventually that code /// will be de-duped into this code. pub fn named_pipe_path(id: &str) -> String { - format!(r"\\.\pipe\{}", id) + format!(r"\\.\pipe\{id}") } #[cfg(test)] diff --git a/rust/logging/src/err_with_sources.rs b/rust/logging/src/err_with_sources.rs index 6882448de..a15c010d4 100644 --- a/rust/logging/src/err_with_sources.rs +++ b/rust/logging/src/err_with_sources.rs @@ -15,7 +15,7 @@ impl fmt::Display for ErrorWithSources<'_> { write!(f, "{}", self.e)?; for cause in anyhow::Chain::new(self.e).skip(1) { - write!(f, ": {}", cause)?; + write!(f, ": {cause}")?; } Ok(()) diff --git a/rust/logging/src/format.rs b/rust/logging/src/format.rs index acebd44f1..3d8bd76d3 100644 --- a/rust/logging/src/format.rs +++ b/rust/logging/src/format.rs @@ -109,7 +109,7 @@ where if self.level { let fmt_level = FmtLevel::new(meta.level(), writer.has_ansi_escapes()); - write!(writer, "{} ", fmt_level)?; + write!(writer, "{fmt_level} ")?; } let dimmed = if writer.has_ansi_escapes() { diff --git a/rust/relay/ebpf-turn-router/src/channel_data.rs b/rust/relay/ebpf-turn-router/src/channel_data.rs index 71979167e..565210826 100644 --- a/rust/relay/ebpf-turn-router/src/channel_data.rs +++ b/rust/relay/ebpf-turn-router/src/channel_data.rs @@ -10,9 +10,14 @@ pub struct ChannelData<'a> { } impl<'a> ChannelData<'a> { + /// # SAFETY + /// + /// You must not create multiple [`ChannelData`] structs at same time. #[inline(always)] - pub fn parse(ctx: &'a XdpContext, ip_header_length: usize) -> Result { - let hdr = ref_mut_at::(ctx, EthHdr::LEN + ip_header_length + UdpHdr::LEN)?; + pub unsafe fn parse(ctx: &'a XdpContext, ip_header_length: usize) -> Result { + // Safety: We are forwarding the constraint. + let hdr = + unsafe { ref_mut_at::(ctx, EthHdr::LEN + ip_header_length + UdpHdr::LEN) }?; if !(0x4000..0x4FFF).contains(&u16::from_be_bytes(hdr.number)) { return Err(Error::NotAChannelDataMessage); diff --git a/rust/relay/ebpf-turn-router/src/eth.rs b/rust/relay/ebpf-turn-router/src/eth.rs index 3008130d7..30f5fb4d2 100644 --- a/rust/relay/ebpf-turn-router/src/eth.rs +++ b/rust/relay/ebpf-turn-router/src/eth.rs @@ -15,10 +15,14 @@ pub struct Eth<'a> { } impl<'a> Eth<'a> { + /// # SAFETY + /// + /// You must not create multiple [`Eth`] structs at same time. #[inline(always)] - pub fn parse(ctx: &'a XdpContext) -> Result { + pub unsafe fn parse(ctx: &'a XdpContext) -> Result { Ok(Self { - inner: ref_mut_at::(ctx, 0)?, + // Safety: We are forwarding the constraint. + inner: unsafe { ref_mut_at::(ctx, 0) }?, ctx, }) } diff --git a/rust/relay/ebpf-turn-router/src/ip4.rs b/rust/relay/ebpf-turn-router/src/ip4.rs index c1e7eaf5f..fb8f4bfeb 100644 --- a/rust/relay/ebpf-turn-router/src/ip4.rs +++ b/rust/relay/ebpf-turn-router/src/ip4.rs @@ -15,9 +15,13 @@ pub struct Ip4<'a> { } impl<'a> Ip4<'a> { + /// # SAFETY + /// + /// You must not create multiple [`Ip4`] structs at same time. #[inline(always)] - pub fn parse(ctx: &'a XdpContext) -> Result { - let ip4_hdr = ref_mut_at::(ctx, EthHdr::LEN)?; + pub unsafe fn parse(ctx: &'a XdpContext) -> Result { + // Safety: We are forwarding the constraint. + let ip4_hdr = unsafe { ref_mut_at::(ctx, EthHdr::LEN) }?; // IPv4 packets with options are handled in user-space. if usize::from(ip4_hdr.ihl()) * 4 != Ipv4Hdr::LEN { diff --git a/rust/relay/ebpf-turn-router/src/ip6.rs b/rust/relay/ebpf-turn-router/src/ip6.rs index 80e19f6d1..7c96a5706 100644 --- a/rust/relay/ebpf-turn-router/src/ip6.rs +++ b/rust/relay/ebpf-turn-router/src/ip6.rs @@ -15,11 +15,15 @@ pub struct Ip6<'a> { } impl<'a> Ip6<'a> { + /// # SAFETY + /// + /// You must not create multiple [`Ip6`] structs at same time. #[inline(always)] - pub fn parse(ctx: &'a XdpContext) -> Result { + pub unsafe fn parse(ctx: &'a XdpContext) -> Result { Ok(Self { ctx, - inner: ref_mut_at::(ctx, EthHdr::LEN)?, + // Safety: We are forwarding the constraint. + inner: unsafe { ref_mut_at::(ctx, EthHdr::LEN) }?, }) } diff --git a/rust/relay/ebpf-turn-router/src/main.rs b/rust/relay/ebpf-turn-router/src/main.rs index bbdf387a8..7e6768df2 100644 --- a/rust/relay/ebpf-turn-router/src/main.rs +++ b/rust/relay/ebpf-turn-router/src/main.rs @@ -109,7 +109,8 @@ pub fn handle_turn(ctx: XdpContext) -> u32 { #[inline(always)] fn try_handle_turn(ctx: &XdpContext) -> Result { - let eth = Eth::parse(ctx)?; + // Safety: This is the only instance of `Eth`. + let eth = unsafe { Eth::parse(ctx) }?; match eth.ether_type() { EtherType::Ipv4 => try_handle_turn_ipv4(ctx, eth)?, @@ -124,7 +125,8 @@ fn try_handle_turn(ctx: &XdpContext) -> Result { #[inline(always)] fn try_handle_turn_ipv4(ctx: &XdpContext, eth: Eth) -> Result<(), Error> { - let ipv4 = Ip4::parse(ctx)?; + // Safety: This is the only instance of `Ip4`. + let ipv4 = unsafe { Ip4::parse(ctx) }?; eth::save_mac_for_ipv4(ipv4.src(), eth.src()); eth::save_mac_for_ipv4(ipv4.dst(), eth.dst()); @@ -133,7 +135,8 @@ fn try_handle_turn_ipv4(ctx: &XdpContext, eth: Eth) -> Result<(), Error> { return Err(Error::NotUdp); } - let udp = Udp::parse(ctx, Ipv4Hdr::LEN)?; // TODO: Change the API so we parse the UDP header _from_ the ipv4 struct? + // Safety: This is the only instance of `Udp` in this scope. + let udp = unsafe { Udp::parse(ctx, Ipv4Hdr::LEN) }?; // TODO: Change the API so we parse the UDP header _from_ the ipv4 struct? let udp_payload_len = udp.payload_len(); trace!( @@ -170,7 +173,8 @@ fn try_handle_ipv4_channel_data_to_udp( ipv4: Ip4, udp: Udp, ) -> Result<(), Error> { - let cd = ChannelData::parse(ctx, Ipv4Hdr::LEN)?; + // Safety: This is the only instance of `Udp` in this scope. + let cd = unsafe { ChannelData::parse(ctx, Ipv4Hdr::LEN) }?; let key = ClientAndChannelV4::new(ipv4.src(), udp.src(), cd.number()); @@ -259,7 +263,8 @@ fn try_handle_ipv4_udp_to_channel_data( #[inline(always)] fn try_handle_turn_ipv6(ctx: &XdpContext, eth: Eth) -> Result<(), Error> { - let ipv6 = Ip6::parse(ctx)?; + // Safety: This is the only instance of `Ip6` in this scope. + let ipv6 = unsafe { Ip6::parse(ctx) }?; eth::save_mac_for_ipv6(ipv6.src(), eth.src()); eth::save_mac_for_ipv6(ipv6.dst(), eth.dst()); @@ -268,7 +273,8 @@ fn try_handle_turn_ipv6(ctx: &XdpContext, eth: Eth) -> Result<(), Error> { return Err(Error::NotUdp); } - let udp = Udp::parse(ctx, Ipv6Hdr::LEN)?; // TODO: Change the API so we parse the UDP header _from_ the ipv6 struct? + // Safety: This is the only instance of `Udp` in this scope. + let udp = unsafe { Udp::parse(ctx, Ipv6Hdr::LEN) }?; // TODO: Change the API so we parse the UDP header _from_ the ipv6 struct? let udp_payload_len = udp.payload_len(); trace!( @@ -354,7 +360,8 @@ fn try_handle_ipv6_channel_data_to_udp( ipv6: Ip6, udp: Udp, ) -> Result<(), Error> { - let cd = ChannelData::parse(ctx, Ipv6Hdr::LEN)?; + // Safety: This is the only instance of `ChannelData` in this scope. + let cd = unsafe { ChannelData::parse(ctx, Ipv6Hdr::LEN) }?; let key = ClientAndChannelV6::new(ipv6.src(), udp.src(), cd.number()); diff --git a/rust/relay/ebpf-turn-router/src/ref_mut_at.rs b/rust/relay/ebpf-turn-router/src/ref_mut_at.rs index cbefeeaed..5e37b0c40 100644 --- a/rust/relay/ebpf-turn-router/src/ref_mut_at.rs +++ b/rust/relay/ebpf-turn-router/src/ref_mut_at.rs @@ -6,8 +6,13 @@ use crate::error::Error; /// /// The length is based on the size of `T` and the bytes at the specified offset will simply be cast into `T`. /// `T` should therefore most definitely be `repr(C)`. +/// +/// # SAFETY +/// +/// You must not obtain overlapping mutable references from the context. #[inline(always)] -pub(crate) fn ref_mut_at(ctx: &XdpContext, offset: usize) -> Result<&mut T, Error> { +#[expect(clippy::mut_from_ref, reason = "The function is unsafe.")] +pub(crate) unsafe fn ref_mut_at(ctx: &XdpContext, offset: usize) -> Result<&mut T, Error> { let start = ctx.data(); let end = ctx.data_end(); let len = core::mem::size_of::(); diff --git a/rust/relay/ebpf-turn-router/src/udp.rs b/rust/relay/ebpf-turn-router/src/udp.rs index 712058757..18ad19ef7 100644 --- a/rust/relay/ebpf-turn-router/src/udp.rs +++ b/rust/relay/ebpf-turn-router/src/udp.rs @@ -10,11 +10,15 @@ pub struct Udp<'a> { } impl<'a> Udp<'a> { + /// # SAFETY + /// + /// You must not create multiple [`Udp`] structs at same time. #[inline(always)] - pub fn parse(ctx: &'a XdpContext, ip_header_length: usize) -> Result { + pub unsafe fn parse(ctx: &'a XdpContext, ip_header_length: usize) -> Result { Ok(Self { ctx, - inner: ref_mut_at::(ctx, EthHdr::LEN + ip_header_length)?, + // Safety: We are forwarding the constraint. + inner: unsafe { ref_mut_at::(ctx, EthHdr::LEN + ip_header_length) }?, }) } diff --git a/rust/relay/server/build.rs b/rust/relay/server/build.rs index d51a2dc6e..348a22aca 100644 --- a/rust/relay/server/build.rs +++ b/rust/relay/server/build.rs @@ -14,7 +14,7 @@ fn main() -> anyhow::Result<()> { aya_build::build_ebpf( [package], - aya_build::Toolchain::Custom("nightly-2024-12-13"), + aya_build::Toolchain::Custom("nightly-2025-05-30"), )?; Ok(()) diff --git a/rust/relay/server/src/auth.rs b/rust/relay/server/src/auth.rs index f15f58de1..e3875c245 100644 --- a/rust/relay/server/src/auth.rs +++ b/rust/relay/server/src/auth.rs @@ -80,7 +80,7 @@ impl MessageIntegrityExt for MessageIntegrity { let password = generate_password(relay_secret, expired, salt); self.check_long_term_credential( - &Username::new(format!("{}:{}", expiry_unix_timestamp, salt)) + &Username::new(format!("{expiry_unix_timestamp}:{salt}")) .map_err(|_| Error::InvalidUsername)?, &FIREZONE, &password, @@ -109,7 +109,7 @@ impl AuthenticatedMessage { let (expiry_unix_timestamp, salt) = split_username(username)?; let expired = systemtime_from_unix(expiry_unix_timestamp); - let username = Username::new(format!("{}:{}", expiry_unix_timestamp, salt)) + let username = Username::new(format!("{expiry_unix_timestamp}:{salt}")) .map_err(|_| Error::InvalidUsername)?; let password = generate_password(relay_secret, expired, salt); diff --git a/rust/relay/server/tests/regression.rs b/rust/relay/server/tests/regression.rs index 8fee057ff..7004fe244 100644 --- a/rust/relay/server/tests/regression.rs +++ b/rust/relay/server/tests/regression.rs @@ -753,7 +753,7 @@ impl TestServer { let Some(actual_output) = self.server.next_command() else { let msg = match expected_output { Output::SendMessage((recipient, msg)) => { - format!("to send message {:?} to {recipient}", msg) + format!("to send message {msg:?} to {recipient}") } CreateAllocation(port, family) => { format!("to create allocation on port {port} for address family {family}") @@ -793,8 +793,8 @@ impl TestServer { .unwrap(); if expected_bytes != payload { - let expected_message = format!("{:?}", message); - let actual_message = format!("{:?}", sent_message); + let expected_message = format!("{message:?}"); + let actual_message = format!("{sent_message:?}"); difference::assert_diff!(&expected_message, &actual_message, "\n", 0); } diff --git a/rust/rust-toolchain.toml b/rust/rust-toolchain.toml index ec5737980..6a0e04e65 100644 --- a/rust/rust-toolchain.toml +++ b/rust/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.87.0" +channel = "1.88.0" components = ["rust-src", "rust-analyzer", "clippy"] targets = ["x86_64-unknown-linux-musl"]