Skip to main content

dpdk_stdlib/
mbuf.rs

1//! Memory buffer management for DPDK packets
2//!
3//! The mbuf (memory buffer) is the fundamental data structure for packet handling
4//! in DPDK. This module provides safe Rust wrappers around DPDK's mbuf operations.
5
6use crate::error::{DpdkError, DpdkResult};
7use std::ffi::CString;
8use std::ptr::NonNull;
9
10/// A memory buffer for packet data
11///
12/// Mbufs are the basic unit for carrying packet data in DPDK.
13/// They are allocated from memory pools and contain both metadata
14/// and actual packet data.
15pub struct Mbuf {
16    raw: NonNull<dpdk_sys::rte_mbuf>,
17}
18
19// Safety: Mbufs can be sent between threads as long as only one thread
20// accesses them at a time (which is enforced by ownership)
21unsafe impl Send for Mbuf {}
22
23impl Mbuf {
24    /// Create a new Mbuf from a raw pointer
25    ///
26    /// # Safety
27    ///
28    /// The caller must ensure:
29    /// - The pointer is valid and properly aligned
30    /// - The mbuf was allocated from a valid mempool
31    /// - Ownership is being transferred (the raw pointer should not be freed elsewhere)
32    pub unsafe fn from_raw(ptr: *mut dpdk_sys::rte_mbuf) -> Option<Self> {
33        NonNull::new(ptr).map(|raw| Self { raw })
34    }
35
36    /// Get the raw pointer to the underlying mbuf
37    pub fn as_raw(&self) -> *mut dpdk_sys::rte_mbuf {
38        self.raw.as_ptr()
39    }
40
41    /// Consume self and return the raw pointer
42    ///
43    /// After calling this, the caller is responsible for freeing the mbuf
44    pub fn into_raw(self) -> *mut dpdk_sys::rte_mbuf {
45        let ptr = self.raw.as_ptr();
46        std::mem::forget(self); // Don't run Drop
47        ptr
48    }
49
50    /// Get the data offset within the buffer
51    pub fn data_offset(&self) -> u16 {
52        unsafe { (*self.raw.as_ptr()).data_off }
53    }
54
55    /// Get the total packet length
56    pub fn packet_len(&self) -> u32 {
57        unsafe { (*self.raw.as_ptr()).pkt_len }
58    }
59
60    /// Get the data length in this segment
61    pub fn data_len(&self) -> u16 {
62        unsafe { (*self.raw.as_ptr()).data_len }
63    }
64
65    /// Get the buffer length (total available space)
66    pub fn buf_len(&self) -> u16 {
67        unsafe { (*self.raw.as_ptr()).buf_len }
68    }
69
70    /// Get a slice to the packet data
71    ///
72    /// Returns None if the buffer address is null
73    pub fn data(&self) -> Option<&[u8]> {
74        unsafe {
75            let mbuf = self.raw.as_ptr();
76            let buf_addr = (*mbuf).buf_addr;
77            if buf_addr.is_null() {
78                return None;
79            }
80            let data_ptr = (buf_addr as *const u8).add((*mbuf).data_off as usize);
81            Some(std::slice::from_raw_parts(data_ptr, (*mbuf).data_len as usize))
82        }
83    }
84
85    /// Get a mutable slice to the packet data
86    ///
87    /// Returns None if the buffer address is null
88    pub fn data_mut(&mut self) -> Option<&mut [u8]> {
89        unsafe {
90            let mbuf = self.raw.as_ptr();
91            let buf_addr = (*mbuf).buf_addr;
92            if buf_addr.is_null() {
93                return None;
94            }
95            let data_ptr = (buf_addr as *mut u8).add((*mbuf).data_off as usize);
96            Some(std::slice::from_raw_parts_mut(data_ptr, (*mbuf).data_len as usize))
97        }
98    }
99
100    /// Set the data length
101    pub fn set_data_len(&mut self, len: u16) {
102        unsafe {
103            (*self.raw.as_ptr()).data_len = len;
104        }
105    }
106
107    /// Set the packet length
108    pub fn set_packet_len(&mut self, len: u32) {
109        unsafe {
110            (*self.raw.as_ptr()).pkt_len = len;
111        }
112    }
113}
114
115impl Drop for Mbuf {
116    fn drop(&mut self) {
117        unsafe {
118            dpdk_sys::rte_pktmbuf_free(self.raw.as_ptr());
119        }
120    }
121}
122
123/// Memory pool for packet buffers
124///
125/// A mempool is a fixed-size pool of mbufs. All packet buffers in DPDK
126/// must be allocated from a mempool.
127pub struct Mempool {
128    raw: NonNull<dpdk_sys::rte_mempool>,
129    name: String,
130}
131
132// Safety: Mempools are thread-safe in DPDK
133unsafe impl Send for Mempool {}
134unsafe impl Sync for Mempool {}
135
136/// Default number of mbufs in a pool
137pub const DEFAULT_POOL_SIZE: u32 = 8192;
138
139/// Default per-core cache size
140pub const DEFAULT_CACHE_SIZE: u32 = 256;
141
142/// Default data room size (2KB + headroom)
143pub const DEFAULT_DATA_ROOM_SIZE: u16 = dpdk_sys::RTE_MBUF_DEFAULT_BUF_SIZE as u16;
144
145/// Maximum length for mempool names
146pub const MAX_MEMPOOL_NAME_LEN: usize = 32;
147
148/// Configuration for creating a mempool
149#[derive(Debug, Clone)]
150pub struct MempoolConfig {
151    /// Number of elements in the pool
152    pub n: u32,
153    /// Per-core cache size (0 to disable caching)
154    pub cache_size: u32,
155    /// Size of data buffer in each mbuf
156    pub data_room_size: u16,
157    /// NUMA socket ID (-1 for any socket)
158    pub socket_id: i32,
159}
160
161impl Default for MempoolConfig {
162    fn default() -> Self {
163        Self {
164            n: DEFAULT_POOL_SIZE,
165            cache_size: DEFAULT_CACHE_SIZE,
166            data_room_size: DEFAULT_DATA_ROOM_SIZE,
167            socket_id: dpdk_sys::SOCKET_ID_ANY,
168        }
169    }
170}
171
172impl MempoolConfig {
173    /// Create a new mempool configuration with default values
174    pub fn new() -> Self {
175        Self::default()
176    }
177
178    /// Set the pool size
179    pub fn with_size(mut self, n: u32) -> Self {
180        self.n = n;
181        self
182    }
183
184    /// Set the cache size
185    pub fn with_cache_size(mut self, cache_size: u32) -> Self {
186        self.cache_size = cache_size;
187        self
188    }
189
190    /// Set the data room size
191    pub fn with_data_room_size(mut self, size: u16) -> Self {
192        self.data_room_size = size;
193        self
194    }
195
196    /// Set the NUMA socket ID
197    pub fn with_socket_id(mut self, socket_id: i32) -> Self {
198        self.socket_id = socket_id;
199        self
200    }
201}
202
203impl Mempool {
204    /// Create a new mempool for packet buffers
205    ///
206    /// # Arguments
207    ///
208    /// * `name` - Unique name for this mempool (max 32 characters)
209    /// * `n` - Number of elements in the pool
210    /// * `cache_size` - Per-core cache size (0 to disable)
211    /// * `data_room_size` - Size of data buffer in each mbuf
212    /// * `socket_id` - NUMA socket ID (-1 for any)
213    ///
214    /// # Errors
215    ///
216    /// Returns an error if:
217    /// - The name is empty or too long
218    /// - The name contains null bytes
219    /// - DPDK mempool creation fails (e.g., out of memory, invalid parameters)
220    pub fn create(
221        name: &str,
222        n: u32,
223        cache_size: u32,
224        data_room_size: u16,
225        socket_id: i32,
226    ) -> DpdkResult<Self> {
227        // Validate name
228        if name.is_empty() {
229            return Err(DpdkError::InvalidName("mempool name cannot be empty".to_string()));
230        }
231        if name.len() > MAX_MEMPOOL_NAME_LEN {
232            return Err(DpdkError::InvalidName(format!(
233                "mempool name too long (max {} characters)",
234                MAX_MEMPOOL_NAME_LEN
235            )));
236        }
237
238        // Convert name to C string
239        let c_name = CString::new(name).map_err(|_| {
240            DpdkError::InvalidName("mempool name contains null bytes".to_string())
241        })?;
242
243        // Create the mempool using DPDK
244        let ptr = unsafe {
245            dpdk_sys::rte_pktmbuf_pool_create(
246                c_name.as_ptr(),
247                n,
248                cache_size,
249                0, // priv_size - typically 0 for standard packet pools
250                data_room_size,
251                socket_id,
252            )
253        };
254
255        NonNull::new(ptr)
256            .map(|raw| Self {
257                raw,
258                name: name.to_string(),
259            })
260            .ok_or_else(|| {
261                // Get the DPDK errno for more detailed error message
262                let errno = unsafe { dpdk_sys::rte_errno() };
263                DpdkError::MempoolCreateFailed(format!(
264                    "rte_pktmbuf_pool_create failed for '{}' (errno: {})",
265                    name, errno
266                ))
267            })
268    }
269
270    /// Create a new mempool with configuration
271    ///
272    /// # Arguments
273    ///
274    /// * `name` - Unique name for this mempool
275    /// * `config` - Mempool configuration
276    pub fn create_with_config(name: &str, config: &MempoolConfig) -> DpdkResult<Self> {
277        Self::create(
278            name,
279            config.n,
280            config.cache_size,
281            config.data_room_size,
282            config.socket_id,
283        )
284    }
285
286    /// Create a mempool with default configuration
287    pub fn create_default(name: &str) -> DpdkResult<Self> {
288        Self::create_with_config(name, &MempoolConfig::default())
289    }
290
291    /// Get the name of this mempool
292    pub fn name(&self) -> &str {
293        &self.name
294    }
295
296    /// Allocate an mbuf from this pool
297    pub fn alloc(&self) -> DpdkResult<Mbuf> {
298        unsafe {
299            let ptr = dpdk_sys::rte_pktmbuf_alloc(self.raw.as_ptr());
300            Mbuf::from_raw(ptr).ok_or(DpdkError::MemoryAllocationFailed)
301        }
302    }
303
304    /// Allocate multiple mbufs from this pool in a batch
305    ///
306    /// This is more efficient than calling `alloc()` multiple times.
307    /// Returns Ok with a vector of mbufs, or Err if allocation fails.
308    pub fn alloc_bulk(&self, count: usize) -> DpdkResult<Vec<Mbuf>> {
309        if count == 0 {
310            return Ok(Vec::new());
311        }
312
313        let mut ptrs: Vec<*mut dpdk_sys::rte_mbuf> =
314            vec![std::ptr::null_mut(); count];
315
316        let ret = unsafe {
317            dpdk_sys::rte_pktmbuf_alloc_bulk(
318                self.raw.as_ptr(),
319                ptrs.as_mut_ptr(),
320                count as u32,
321            )
322        };
323
324        if ret != 0 {
325            return Err(DpdkError::MemoryAllocationFailed);
326        }
327
328        let mbufs: Vec<Mbuf> = ptrs
329            .into_iter()
330            .filter_map(|ptr| unsafe { Mbuf::from_raw(ptr) })
331            .collect();
332
333        if mbufs.len() != count {
334            // Some allocations failed, free the ones we got and return error
335            // (mbufs will be dropped automatically)
336            return Err(DpdkError::MemoryAllocationFailed);
337        }
338
339        Ok(mbufs)
340    }
341
342    /// Get the number of free elements in the pool
343    pub fn available_count(&self) -> u32 {
344        unsafe { dpdk_sys::rte_mempool_avail_count(self.raw.as_ptr()) }
345    }
346
347    /// Get the number of elements in use
348    pub fn in_use_count(&self) -> u32 {
349        unsafe { dpdk_sys::rte_mempool_in_use_count(self.raw.as_ptr()) }
350    }
351
352    /// Check if the pool is full (all elements available)
353    pub fn is_full(&self) -> bool {
354        unsafe { dpdk_sys::rte_mempool_full(self.raw.as_ptr()) != 0 }
355    }
356
357    /// Check if the pool is empty (no elements available)
358    pub fn is_empty(&self) -> bool {
359        unsafe { dpdk_sys::rte_mempool_empty(self.raw.as_ptr()) != 0 }
360    }
361
362    /// Get the raw pointer to the mempool
363    pub fn as_raw(&self) -> *mut dpdk_sys::rte_mempool {
364        self.raw.as_ptr()
365    }
366}
367
368impl Drop for Mempool {
369    fn drop(&mut self) {
370        unsafe {
371            dpdk_sys::rte_mempool_free(self.raw.as_ptr());
372        }
373    }
374}
375
376/// Builder for creating mbufs with specific content
377pub struct MbufBuilder {
378    data: Vec<u8>,
379}
380
381impl MbufBuilder {
382    pub fn new() -> Self {
383        Self { data: Vec::new() }
384    }
385
386    /// Add ethernet header
387    pub fn ethernet(mut self, dst_mac: [u8; 6], src_mac: [u8; 6], ethertype: u16) -> Self {
388        self.data.extend_from_slice(&dst_mac);
389        self.data.extend_from_slice(&src_mac);
390        self.data.extend_from_slice(&ethertype.to_be_bytes());
391        self
392    }
393
394    /// Add IPv4 header (simplified)
395    pub fn ipv4(mut self, src: [u8; 4], dst: [u8; 4], protocol: u8, payload_len: u16) -> Self {
396        let total_len = 20 + payload_len;
397        self.data.push(0x45); // Version + IHL
398        self.data.push(0x00); // DSCP + ECN
399        self.data.extend_from_slice(&total_len.to_be_bytes());
400        self.data.extend_from_slice(&[0, 0]); // Identification
401        self.data.extend_from_slice(&[0, 0]); // Flags + Fragment offset
402        self.data.push(64); // TTL
403        self.data.push(protocol);
404        self.data.extend_from_slice(&[0, 0]); // Checksum (to be calculated)
405        self.data.extend_from_slice(&src);
406        self.data.extend_from_slice(&dst);
407        self
408    }
409
410    /// Add UDP header
411    pub fn udp(mut self, src_port: u16, dst_port: u16, payload_len: u16) -> Self {
412        let udp_len = 8 + payload_len;
413        self.data.extend_from_slice(&src_port.to_be_bytes());
414        self.data.extend_from_slice(&dst_port.to_be_bytes());
415        self.data.extend_from_slice(&udp_len.to_be_bytes());
416        self.data.extend_from_slice(&[0, 0]); // Checksum
417        self
418    }
419
420    /// Add payload data
421    pub fn payload(mut self, data: &[u8]) -> Self {
422        self.data.extend_from_slice(data);
423        self
424    }
425
426    /// Build into a byte vector (for use with synthetic testing)
427    pub fn build(self) -> Vec<u8> {
428        self.data
429    }
430}
431
432impl Default for MbufBuilder {
433    fn default() -> Self {
434        Self::new()
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441
442    // ========================================================================
443    // MbufBuilder Tests
444    // ========================================================================
445
446    #[test]
447    fn test_mbuf_builder() {
448        let frame = MbufBuilder::new()
449            .ethernet([0xff; 6], [0x00; 6], 0x0800)
450            .ipv4([192, 168, 1, 1], [192, 168, 1, 2], 17, 16)
451            .udp(12345, 9000, 8)
452            .payload(b"test")
453            .build();
454
455        // Ethernet: 14 bytes, IP: 20 bytes, UDP: 8 bytes, payload: 4 bytes
456        assert_eq!(frame.len(), 14 + 20 + 8 + 4);
457    }
458
459    #[test]
460    fn test_mbuf_builder_empty() {
461        let frame = MbufBuilder::new().build();
462        assert!(frame.is_empty());
463    }
464
465    #[test]
466    fn test_mbuf_builder_payload_only() {
467        let payload = b"hello world";
468        let frame = MbufBuilder::new().payload(payload).build();
469        assert_eq!(frame.len(), payload.len());
470        assert_eq!(&frame, payload);
471    }
472
473    #[test]
474    fn test_mbuf_builder_ethernet_only() {
475        let frame = MbufBuilder::new()
476            .ethernet([0x01, 0x02, 0x03, 0x04, 0x05, 0x06], [0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f], 0x0800)
477            .build();
478
479        // Ethernet header is 14 bytes: 6 dst + 6 src + 2 ethertype
480        assert_eq!(frame.len(), 14);
481
482        // Verify dst MAC
483        assert_eq!(&frame[0..6], &[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
484        // Verify src MAC
485        assert_eq!(&frame[6..12], &[0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]);
486        // Verify ethertype (0x0800 = IPv4)
487        assert_eq!(&frame[12..14], &[0x08, 0x00]);
488    }
489
490    // ========================================================================
491    // MempoolConfig Tests
492    // ========================================================================
493
494    #[test]
495    fn test_mempool_config_default() {
496        let config = MempoolConfig::default();
497        assert_eq!(config.n, DEFAULT_POOL_SIZE);
498        assert_eq!(config.cache_size, DEFAULT_CACHE_SIZE);
499        assert_eq!(config.data_room_size, DEFAULT_DATA_ROOM_SIZE);
500        assert_eq!(config.socket_id, dpdk_sys::SOCKET_ID_ANY);
501    }
502
503    #[test]
504    fn test_mempool_config_builder() {
505        let config = MempoolConfig::new()
506            .with_size(4096)
507            .with_cache_size(128)
508            .with_data_room_size(2048)
509            .with_socket_id(0);
510
511        assert_eq!(config.n, 4096);
512        assert_eq!(config.cache_size, 128);
513        assert_eq!(config.data_room_size, 2048);
514        assert_eq!(config.socket_id, 0);
515    }
516
517    #[test]
518    fn test_mempool_config_chaining() {
519        // Test that builder methods can be chained
520        let config = MempoolConfig::new()
521            .with_size(1024)
522            .with_cache_size(64);
523
524        assert_eq!(config.n, 1024);
525        assert_eq!(config.cache_size, 64);
526        // Other fields should retain defaults
527        assert_eq!(config.data_room_size, DEFAULT_DATA_ROOM_SIZE);
528    }
529
530    // ========================================================================
531    // Mempool Creation Tests
532    // ========================================================================
533
534    #[test]
535    fn test_mempool_create() {
536        let pool = Mempool::create("test_pool", 128, 32, 2048, -1);
537        assert!(pool.is_ok());
538        let pool = pool.unwrap();
539        assert_eq!(pool.name(), "test_pool");
540    }
541
542    #[test]
543    fn test_mempool_create_with_config() {
544        let config = MempoolConfig::new().with_size(256).with_cache_size(64);
545        let pool = Mempool::create_with_config("config_pool", &config);
546        assert!(pool.is_ok());
547        let pool = pool.unwrap();
548        assert_eq!(pool.name(), "config_pool");
549    }
550
551    #[test]
552    fn test_mempool_create_default() {
553        let pool = Mempool::create_default("default_pool");
554        assert!(pool.is_ok());
555        let pool = pool.unwrap();
556        assert_eq!(pool.name(), "default_pool");
557    }
558
559    #[test]
560    fn test_mempool_create_empty_name() {
561        let result = Mempool::create("", 128, 32, 2048, -1);
562        assert!(result.is_err());
563        match result {
564            Err(DpdkError::InvalidName(msg)) => {
565                assert!(msg.contains("empty"));
566            }
567            _ => panic!("Expected InvalidName error"),
568        }
569    }
570
571    #[test]
572    fn test_mempool_create_name_too_long() {
573        let long_name = "a".repeat(MAX_MEMPOOL_NAME_LEN + 1);
574        let result = Mempool::create(&long_name, 128, 32, 2048, -1);
575        assert!(result.is_err());
576        match result {
577            Err(DpdkError::InvalidName(msg)) => {
578                assert!(msg.contains("too long"));
579            }
580            _ => panic!("Expected InvalidName error"),
581        }
582    }
583
584    #[test]
585    fn test_mempool_create_name_with_null() {
586        let result = Mempool::create("test\0pool", 128, 32, 2048, -1);
587        assert!(result.is_err());
588        match result {
589            Err(DpdkError::InvalidName(msg)) => {
590                assert!(msg.contains("null"));
591            }
592            _ => panic!("Expected InvalidName error"),
593        }
594    }
595
596    #[test]
597    fn test_mempool_name_max_length() {
598        let max_name = "a".repeat(MAX_MEMPOOL_NAME_LEN);
599        let result = Mempool::create(&max_name, 128, 32, 2048, -1);
600        assert!(result.is_ok());
601    }
602
603    // ========================================================================
604    // Mempool Operations Tests
605    // ========================================================================
606
607    #[test]
608    fn test_mempool_alloc() {
609        let pool = Mempool::create("alloc_pool", 128, 32, 2048, -1).unwrap();
610        let mbuf = pool.alloc();
611        assert!(mbuf.is_ok());
612    }
613
614    #[test]
615    fn test_mempool_alloc_bulk_zero() {
616        let pool = Mempool::create("bulk_zero_pool", 128, 32, 2048, -1).unwrap();
617        let mbufs = pool.alloc_bulk(0);
618        assert!(mbufs.is_ok());
619        assert!(mbufs.unwrap().is_empty());
620    }
621
622    #[test]
623    fn test_mempool_alloc_bulk() {
624        let pool = Mempool::create("bulk_pool", 128, 32, 2048, -1).unwrap();
625        let mbufs = pool.alloc_bulk(4);
626        assert!(mbufs.is_ok());
627        let mbufs = mbufs.unwrap();
628        assert_eq!(mbufs.len(), 4);
629    }
630
631    #[test]
632    fn test_mempool_available_count() {
633        let pool = Mempool::create("avail_pool", 128, 32, 2048, -1).unwrap();
634        let count = pool.available_count();
635        // With stubs, initial count should be the pool size
636        assert!(count > 0);
637    }
638
639    #[test]
640    fn test_mempool_in_use_count() {
641        let pool = Mempool::create("inuse_pool", 128, 32, 2048, -1).unwrap();
642        let count = pool.in_use_count();
643        // Initially nothing should be in use
644        assert_eq!(count, 0);
645    }
646
647    #[test]
648    fn test_mempool_is_full() {
649        let pool = Mempool::create("full_pool", 128, 32, 2048, -1).unwrap();
650        // Initially pool should be full (all elements available)
651        assert!(pool.is_full());
652    }
653
654    #[test]
655    fn test_mempool_is_empty() {
656        let pool = Mempool::create("empty_pool", 128, 32, 2048, -1).unwrap();
657        // Initially pool should not be empty
658        assert!(!pool.is_empty());
659    }
660
661    #[test]
662    fn test_mempool_as_raw() {
663        let pool = Mempool::create("raw_pool", 128, 32, 2048, -1).unwrap();
664        let raw = pool.as_raw();
665        assert!(!raw.is_null());
666    }
667
668    // ========================================================================
669    // Mbuf Tests (with stubs)
670    // ========================================================================
671
672    #[test]
673    fn test_mbuf_data_offset() {
674        let pool = Mempool::create("offset_pool", 128, 32, 2048, -1).unwrap();
675        let mbuf = pool.alloc().unwrap();
676        // Data offset should be set (typically RTE_PKTMBUF_HEADROOM)
677        let offset = mbuf.data_offset();
678        assert!(offset >= 0);
679    }
680
681    #[test]
682    fn test_mbuf_packet_len() {
683        let pool = Mempool::create("pktlen_pool", 128, 32, 2048, -1).unwrap();
684        let mbuf = pool.alloc().unwrap();
685        // Initially packet length should be 0
686        assert_eq!(mbuf.packet_len(), 0);
687    }
688
689    #[test]
690    fn test_mbuf_data_len() {
691        let pool = Mempool::create("datalen_pool", 128, 32, 2048, -1).unwrap();
692        let mbuf = pool.alloc().unwrap();
693        // Initially data length should be 0
694        assert_eq!(mbuf.data_len(), 0);
695    }
696
697    #[test]
698    fn test_mbuf_set_lengths() {
699        let pool = Mempool::create("setlen_pool", 128, 32, 2048, -1).unwrap();
700        let mut mbuf = pool.alloc().unwrap();
701
702        mbuf.set_data_len(100);
703        assert_eq!(mbuf.data_len(), 100);
704
705        mbuf.set_packet_len(100);
706        assert_eq!(mbuf.packet_len(), 100);
707    }
708
709    #[test]
710    fn test_mbuf_into_raw() {
711        let pool = Mempool::create("intoraw_pool", 128, 32, 2048, -1).unwrap();
712        let mbuf = pool.alloc().unwrap();
713        let raw = mbuf.into_raw();
714        assert!(!raw.is_null());
715
716        // Manually free to avoid leak (in real code DPDK would handle this)
717        unsafe {
718            dpdk_sys::rte_pktmbuf_free(raw);
719        }
720    }
721
722    #[test]
723    fn test_mbuf_from_raw_null() {
724        let result = unsafe { Mbuf::from_raw(std::ptr::null_mut()) };
725        assert!(result.is_none());
726    }
727}