xdp_util/
packet.rs

1//! # UDP Packet Header Construction
2//!
3//! ## Purpose
4//!
5//! This module provides a utility to construct the headers for a UDP/IPv4/Ethernet
6//! packet. This is essential for applications using AF_XDP that need to manually
7//! build entire packets before sending them.
8//!
9//! ## How it works
10//!
11//! It uses the `etherparse` crate's `PacketBuilder` to efficiently layer the Ethernet II,
12//! IPv4, and UDP headers. The headers are written directly into a fixed-size `[u8; 42]`
13//! array to avoid heap allocations. A custom `HdrWrite` struct, which implements
14//! `std::io::Write`, acts as a temporary writer to capture the builder's output.
15//!
16//! ## Main components
17//!
18//! - `write_udp_header_for()`: The primary function that takes source/destination
19//!   addresses and ports and returns a 42-byte array containing the packet header.
20//! - `HdrWrite`: A helper struct implementing `io::Write` to enable writing into a
21//!   fixed-size buffer without extra allocations.
22
23use etherparse::PacketBuilder;
24use std::io;
25use std::net::Ipv4Addr;
26
27/// Constructs Ethernet, IPv4, and UDP headers for a packet.
28///
29/// This function uses `etherparse::PacketBuilder` to create the necessary headers
30/// and writes them into a 42-byte array.
31///
32/// # Arguments
33/// * `data` - The payload data, used to calculate the UDP payload length.
34/// * `src_addr` - Source IPv4 address.
35/// * `src_mac` - Source MAC address.
36/// * `src_port` - Source UDP port.
37/// * `dst_addr` - Destination IPv4 address.
38/// * `dst_mac` - Destination MAC address.
39/// * `dst_port` - Destination UDP port.
40///
41/// # Returns
42/// A `Result` containing a 42-byte array with the complete L2/L3/L4 headers,
43/// or an `io::Error` on failure.
44pub fn write_udp_header_for(
45    data: &[u8],
46    src_addr: Ipv4Addr,
47    src_mac: [u8; 6],
48    src_port: u16,
49    dst_addr: Ipv4Addr,
50    dst_mac: [u8; 6],
51    dst_port: u16,
52) -> io::Result<[u8; 42]> {
53    let mut hdr = [0u8; 42]; // 14 bytes for Ethernet header + 20 bytes for IPv4 header + 8 bytes for UDP header
54
55    let builder = PacketBuilder::
56    // Layer 2: Ethernet II header
57    ethernet2(src_mac, dst_mac)
58    // Layer 3: IPv4 header
59    .ipv4(src_addr.octets(), dst_addr.octets(), 64) // 64 is a common TTL
60    // Layer 4: UDP header
61    .udp(src_port, dst_port);
62
63    match builder.write(&mut HdrWrite(&mut hdr, 0), data) {
64        Ok(_) => Ok(hdr),
65        Err(e) => Err(io::Error::other(format!(
66            "Error writing packet header: {e}",
67        ))),
68    }
69}
70
71/// A helper struct that implements `std::io::Write` for a fixed-size byte array.
72///
73/// This is used to capture the output of `etherparse::PacketBuilder` without
74/// requiring heap allocations.
75pub struct HdrWrite<'a>(
76    /// The buffer to write into.
77    pub &'a mut [u8; 42],
78    /// The current write position within the buffer.
79    pub usize,
80);
81impl io::Write for HdrWrite<'_> {
82    /// Writes a buffer into the inner array, respecting the fixed size.
83    ///
84    /// It copies bytes from `buf` into the internal array. If `buf` is larger
85    /// than the remaining space, the data is truncated.
86    fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
87        if self.1 < 42 {
88            let len = buf.len().min(self.0.len() - self.1);
89            self.0[self.1..self.1 + len].copy_from_slice(&buf[..len]);
90        }
91        self.1 += buf.len();
92        Ok(buf.len())
93    }
94
95    /// A no-op flush implementation, as the write is immediate.
96    fn flush(&mut self) -> io::Result<()> {
97        Ok(())
98    }
99}
100
101//
102// ================================================================================================
103//   UNITTESTS
104// ================================================================================================
105//
106#[cfg(test)]
107mod tests {
108    use super::HdrWrite;
109    use std::io::Write;
110    use std::net::Ipv4Addr;
111
112    #[test]
113    fn test_write_udp_header() {
114        let src_addr = Ipv4Addr::new(192, 168, 1, 1);
115        let dst_addr = Ipv4Addr::new(192, 168, 1, 2);
116        let src_mac = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55];
117        let dst_mac = [0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb];
118        let src_port = 12345;
119        let dst_port = 54321;
120        let data = b"Hello, XDP!";
121        let hdr = super::write_udp_header_for(
122            data, src_addr, src_mac, src_port, dst_addr, dst_mac, dst_port,
123        )
124        .unwrap();
125        assert_eq!(hdr.len(), 42);
126        let mut buf = [0u8; 42 + 11];
127        buf[..42].clone_from_slice(&hdr);
128        buf[42..].copy_from_slice(data);
129        match etherparse::SlicedPacket::from_ethernet(&buf) {
130            Ok(packet) => match packet.transport {
131                Some(etherparse::TransportSlice::Udp(_udp)) => {}
132                _ => panic!("Not udp packet"),
133            },
134            Err(e) => panic!("Failed to parse packet: {}", e),
135        };
136    }
137
138    #[test]
139    fn test_hdrwrite() {
140        let mut hdr = [0u8; 42];
141        let data = b"Test data";
142        let written = {
143            let mut writer = HdrWrite(&mut hdr, 0);
144            writer.write(data).unwrap()
145        };
146        assert_eq!(written, data.len());
147        assert_eq!(&hdr[..written], data);
148        let data = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaTest data";
149        let written = {
150            let mut writer = HdrWrite(&mut hdr, 0);
151            writer.write(data).unwrap()
152        };
153        assert_eq!(written, data.len());
154        assert_eq!(&hdr[..], &data[..42]);
155    }
156}