Files
firezone/rust/etherparse-ext/src/udp_header_slice_mut.rs
Thomas Eizinger d1d0874699 refactor(rust): introduce etherparse-ext crate (#8500)
Within Firezone's Rust codebase, we use the `etherparse` crate
extensively to parse network packets. To provide a more ergonomic API,
this is all encapsulated in our `ip-packet` crate.

For #7518, we need to write an eBPF kernel that parses and manipulates
network packets. Etherparse itself doesn't provide any facilities to
manipulate network packets. That is an open feature request:
https://github.com/JulianSchmid/etherparse/issues/9. For the packet
manipulation that we are doing in `connlib`, we already wrote certain
extensions to the `etherparse` crate but today, those are all within the
`ip-packet` crate.

In order to reuse that within the eBPF kernel, we cannot just depend on
`ip-packet` directly because eBPF is a no-std and no-alloc environment,
thus no crate in the dependency tree is allowed to depend on Rust's
std-lib. `etherparse` itself actually has an `std` feature flag that we
can turn off. Introducing the same in `ip-packet` would require a lot of
conditional-compilation gates using `#[cfg]`. it is much easier to just
introduce a new crate that houses all our in-house extensions to
`etherparse`. Eventually, we can hopefully upstream those which is
another motivator to separate this out.
2025-03-25 22:33:14 +00:00

83 lines
2.4 KiB
Rust

use crate::slice_utils::write_to_offset_unchecked;
use etherparse::UdpHeaderSlice;
pub struct UdpHeaderSliceMut<'a> {
slice: &'a mut [u8],
}
impl<'a> UdpHeaderSliceMut<'a> {
/// Creates a new [`UdpHeaderSliceMut`].
pub fn from_slice(slice: &'a mut [u8]) -> Result<Self, etherparse::err::LenError> {
UdpHeaderSlice::from_slice(slice)?;
Ok(Self { slice })
}
/// Creates a new [`UdpHeaderSliceMut`] without checking the slice.
///
/// # Safety
///
/// The caller must guarantee that the slice is at least of length 8.
pub unsafe fn from_slice_unchecked(slice: &'a mut [u8]) -> Self {
Self { slice }
}
pub fn get_source_port(&self) -> u16 {
u16::from_be_bytes([self.slice[0], self.slice[1]])
}
pub fn get_destination_port(&self) -> u16 {
u16::from_be_bytes([self.slice[2], self.slice[3]])
}
pub fn set_source_port(&mut self, src: u16) {
// Safety: Slice it at least of length 8 as checked in the ctor.
unsafe { write_to_offset_unchecked(self.slice, 0, src.to_be_bytes()) };
}
pub fn set_destination_port(&mut self, dst: u16) {
// Safety: Slice it at least of length 8 as checked in the ctor.
unsafe { write_to_offset_unchecked(self.slice, 2, dst.to_be_bytes()) };
}
pub fn set_length(&mut self, length: u16) {
// Safety: Slice it at least of length 8 as checked in the ctor.
unsafe { write_to_offset_unchecked(self.slice, 4, length.to_be_bytes()) };
}
pub fn set_checksum(&mut self, checksum: u16) {
// Safety: Slice it at least of length 8 as checked in the ctor.
unsafe { write_to_offset_unchecked(self.slice, 6, checksum.to_be_bytes()) };
}
}
#[cfg(test)]
mod tests {
use super::*;
use etherparse::PacketBuilder;
#[test]
fn smoke() {
let mut buf = Vec::new();
PacketBuilder::ipv4([0u8; 4], [0u8; 4], 0)
.udp(10, 20)
.write(&mut buf, &[])
.unwrap();
let mut slice = UdpHeaderSliceMut::from_slice(&mut buf[20..]).unwrap();
slice.set_source_port(30);
slice.set_destination_port(40);
slice.set_length(50);
slice.set_checksum(60);
let slice = UdpHeaderSlice::from_slice(&buf[20..]).unwrap();
assert_eq!(slice.source_port(), 30);
assert_eq!(slice.destination_port(), 40);
assert_eq!(slice.length(), 50);
assert_eq!(slice.checksum(), 60);
}
}