Skip to main content

dpdk_stdlib/
port.rs

1//! DPDK Ethernet Port Management
2//!
3//! This module provides safe Rust wrappers for DPDK ethernet port operations.
4//! It handles port configuration, queue setup, and packet I/O.
5
6use crate::error::{DpdkError, DpdkResult};
7use crate::mbuf::{Mbuf, Mempool};
8use std::ptr;
9
10/// Default number of RX/TX descriptors per queue
11pub const DEFAULT_RX_DESC: u16 = 1024;
12pub const DEFAULT_TX_DESC: u16 = 1024;
13
14/// Default burst size for packet I/O
15pub const DEFAULT_BURST_SIZE: u16 = 32;
16
17/// Hardware offload flags for RX
18#[derive(Debug, Clone, Copy, Default)]
19pub struct RxOffload {
20    /// Hardware VLAN stripping
21    pub vlan_strip: bool,
22    /// Hardware IPv4 checksum verification
23    pub ipv4_cksum: bool,
24    /// Hardware UDP checksum verification
25    pub udp_cksum: bool,
26    /// Hardware TCP checksum verification
27    pub tcp_cksum: bool,
28}
29
30impl RxOffload {
31    /// Convert to DPDK offload flags
32    pub fn to_flags(&self) -> u64 {
33        let mut flags = 0u64;
34        if self.vlan_strip {
35            flags |= dpdk_sys::RTE_ETH_RX_OFFLOAD_VLAN_STRIP;
36        }
37        if self.ipv4_cksum {
38            flags |= dpdk_sys::RTE_ETH_RX_OFFLOAD_IPV4_CKSUM;
39        }
40        if self.udp_cksum {
41            flags |= dpdk_sys::RTE_ETH_RX_OFFLOAD_UDP_CKSUM;
42        }
43        if self.tcp_cksum {
44            flags |= dpdk_sys::RTE_ETH_RX_OFFLOAD_TCP_CKSUM;
45        }
46        flags
47    }
48}
49
50/// Hardware offload flags for TX
51#[derive(Debug, Clone, Copy, Default)]
52pub struct TxOffload {
53    /// Hardware VLAN insertion
54    pub vlan_insert: bool,
55    /// Hardware IPv4 checksum calculation
56    pub ipv4_cksum: bool,
57    /// Hardware UDP checksum calculation
58    pub udp_cksum: bool,
59    /// Hardware TCP checksum calculation
60    pub tcp_cksum: bool,
61}
62
63impl TxOffload {
64    /// Convert to DPDK offload flags
65    pub fn to_flags(&self) -> u64 {
66        let mut flags = 0u64;
67        if self.vlan_insert {
68            flags |= dpdk_sys::RTE_ETH_TX_OFFLOAD_VLAN_INSERT;
69        }
70        if self.ipv4_cksum {
71            flags |= dpdk_sys::RTE_ETH_TX_OFFLOAD_IPV4_CKSUM;
72        }
73        if self.udp_cksum {
74            flags |= dpdk_sys::RTE_ETH_TX_OFFLOAD_UDP_CKSUM;
75        }
76        if self.tcp_cksum {
77            flags |= dpdk_sys::RTE_ETH_TX_OFFLOAD_TCP_CKSUM;
78        }
79        flags
80    }
81}
82
83/// Configuration for an Ethernet port
84#[derive(Debug, Clone)]
85pub struct PortConfig {
86    /// Number of RX queues
87    pub nb_rx_queues: u16,
88    /// Number of TX queues
89    pub nb_tx_queues: u16,
90    /// Number of RX descriptors per queue
91    pub nb_rx_desc: u16,
92    /// Number of TX descriptors per queue
93    pub nb_tx_desc: u16,
94    /// Enable promiscuous mode
95    pub promiscuous: bool,
96    /// MTU size (0 for default)
97    pub mtu: u32,
98    /// RX hardware offload configuration
99    pub rx_offload: RxOffload,
100    /// TX hardware offload configuration
101    pub tx_offload: TxOffload,
102}
103
104impl Default for PortConfig {
105    fn default() -> Self {
106        Self {
107            nb_rx_queues: 1,
108            nb_tx_queues: 1,
109            nb_rx_desc: DEFAULT_RX_DESC,
110            nb_tx_desc: DEFAULT_TX_DESC,
111            promiscuous: true,
112            mtu: 0,
113            rx_offload: RxOffload::default(),
114            tx_offload: TxOffload::default(),
115        }
116    }
117}
118
119impl PortConfig {
120    /// Create a new PortConfig with default values
121    pub fn new() -> Self {
122        Self::default()
123    }
124
125    /// Set the number of RX/TX queues
126    pub fn with_queues(mut self, rx: u16, tx: u16) -> Self {
127        self.nb_rx_queues = rx;
128        self.nb_tx_queues = tx;
129        self
130    }
131
132    /// Set the number of RX/TX descriptors
133    pub fn with_descriptors(mut self, rx: u16, tx: u16) -> Self {
134        self.nb_rx_desc = rx;
135        self.nb_tx_desc = tx;
136        self
137    }
138
139    /// Enable or disable promiscuous mode
140    pub fn with_promiscuous(mut self, enable: bool) -> Self {
141        self.promiscuous = enable;
142        self
143    }
144
145    /// Set the MTU
146    pub fn with_mtu(mut self, mtu: u32) -> Self {
147        self.mtu = mtu;
148        self
149    }
150
151    /// Configure RX hardware offloads
152    pub fn with_rx_offload(mut self, offload: RxOffload) -> Self {
153        self.rx_offload = offload;
154        self
155    }
156
157    /// Configure TX hardware offloads
158    pub fn with_tx_offload(mut self, offload: TxOffload) -> Self {
159        self.tx_offload = offload;
160        self
161    }
162
163    /// Enable all checksum offloads (RX and TX)
164    pub fn with_checksum_offload(mut self) -> Self {
165        self.rx_offload = RxOffload {
166            ipv4_cksum: true,
167            udp_cksum: true,
168            tcp_cksum: true,
169            ..Default::default()
170        };
171        self.tx_offload = TxOffload {
172            ipv4_cksum: true,
173            udp_cksum: true,
174            tcp_cksum: true,
175            ..Default::default()
176        };
177        self
178    }
179}
180
181/// Ethernet MAC address
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
183pub struct MacAddress(pub [u8; 6]);
184
185impl MacAddress {
186    /// Create a new MAC address from bytes
187    pub fn new(bytes: [u8; 6]) -> Self {
188        Self(bytes)
189    }
190
191    /// Create a broadcast MAC address (ff:ff:ff:ff:ff:ff)
192    pub fn broadcast() -> Self {
193        Self([0xff; 6])
194    }
195
196    /// Create a zero MAC address (00:00:00:00:00:00)
197    pub fn zero() -> Self {
198        Self([0; 6])
199    }
200
201    /// Get the bytes of the MAC address as a slice
202    pub fn as_bytes(&self) -> &[u8; 6] {
203        &self.0
204    }
205
206    /// Get the bytes of the MAC address as an array (alias for common API)
207    pub fn octets(&self) -> [u8; 6] {
208        self.0
209    }
210
211    /// Check if this is a broadcast address (ff:ff:ff:ff:ff:ff)
212    pub fn is_broadcast(&self) -> bool {
213        self.0 == [0xff; 6]
214    }
215
216    /// Check if this is a multicast address
217    pub fn is_multicast(&self) -> bool {
218        (self.0[0] & 0x01) != 0
219    }
220
221    /// Check if this is a zero/unset address
222    pub fn is_zero(&self) -> bool {
223        self.0 == [0; 6]
224    }
225}
226
227impl std::fmt::Display for MacAddress {
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        write!(
230            f,
231            "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
232            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
233        )
234    }
235}
236
237/// Link status information
238#[derive(Debug, Clone, Copy, Default)]
239pub struct LinkStatus {
240    /// Link speed in Mbps (0 if down)
241    pub speed: u32,
242    /// Full duplex if true
243    pub full_duplex: bool,
244    /// Auto-negotiation enabled
245    pub autoneg: bool,
246    /// Link is up
247    pub link_up: bool,
248}
249
250/// Port statistics
251#[derive(Debug, Clone, Copy, Default)]
252pub struct PortStats {
253    /// Total received packets
254    pub rx_packets: u64,
255    /// Total transmitted packets
256    pub tx_packets: u64,
257    /// Total received bytes
258    pub rx_bytes: u64,
259    /// Total transmitted bytes
260    pub tx_bytes: u64,
261    /// Missed packets (no buffer available)
262    pub rx_missed: u64,
263    /// RX errors
264    pub rx_errors: u64,
265    /// TX errors
266    pub tx_errors: u64,
267}
268
269/// Device capability information
270#[derive(Debug, Clone, Copy, Default)]
271pub struct DeviceCapabilities {
272    /// Supported RX offloads
273    pub rx_offload_capa: u64,
274    /// Supported TX offloads
275    pub tx_offload_capa: u64,
276    /// Maximum RX queues
277    pub max_rx_queues: u16,
278    /// Maximum TX queues
279    pub max_tx_queues: u16,
280}
281
282impl DeviceCapabilities {
283    /// Check if RX IPv4 checksum offload is supported
284    pub fn supports_rx_ipv4_cksum(&self) -> bool {
285        (self.rx_offload_capa & dpdk_sys::RTE_ETH_RX_OFFLOAD_IPV4_CKSUM) != 0
286    }
287
288    /// Check if RX UDP checksum offload is supported
289    pub fn supports_rx_udp_cksum(&self) -> bool {
290        (self.rx_offload_capa & dpdk_sys::RTE_ETH_RX_OFFLOAD_UDP_CKSUM) != 0
291    }
292
293    /// Check if TX IPv4 checksum offload is supported
294    pub fn supports_tx_ipv4_cksum(&self) -> bool {
295        (self.tx_offload_capa & dpdk_sys::RTE_ETH_TX_OFFLOAD_IPV4_CKSUM) != 0
296    }
297
298    /// Check if TX UDP checksum offload is supported
299    pub fn supports_tx_udp_cksum(&self) -> bool {
300        (self.tx_offload_capa & dpdk_sys::RTE_ETH_TX_OFFLOAD_UDP_CKSUM) != 0
301    }
302
303    /// Check if VLAN stripping is supported
304    pub fn supports_vlan_strip(&self) -> bool {
305        (self.rx_offload_capa & dpdk_sys::RTE_ETH_RX_OFFLOAD_VLAN_STRIP) != 0
306    }
307
308    /// Check if VLAN insertion is supported
309    pub fn supports_vlan_insert(&self) -> bool {
310        (self.tx_offload_capa & dpdk_sys::RTE_ETH_TX_OFFLOAD_VLAN_INSERT) != 0
311    }
312}
313
314/// A DPDK Ethernet port
315pub struct Port {
316    port_id: u16,
317    config: PortConfig,
318    started: bool,
319    mac_address: MacAddress,
320    /// Device capabilities (what the NIC supports)
321    capabilities: DeviceCapabilities,
322    /// Actual offloads enabled (may differ from config if not supported)
323    active_rx_offload: u64,
324    active_tx_offload: u64,
325}
326
327impl Port {
328    /// Get the number of available DPDK ports
329    pub fn count_available() -> u16 {
330        unsafe { dpdk_sys::rte_eth_dev_count_avail() }
331    }
332
333    /// Check if a port ID is valid
334    pub fn is_valid(port_id: u16) -> bool {
335        port_id < Self::count_available()
336    }
337
338    /// Initialize a port with the given configuration
339    ///
340    /// This will:
341    /// 1. Validate the port ID
342    /// 2. Get device info to check capabilities
343    /// 3. Configure the port
344    /// 4. Set up RX and TX queues
345    pub fn init(port_id: u16, config: PortConfig, mempool: &Mempool) -> DpdkResult<Self> {
346        // Validate port ID
347        if !Self::is_valid(port_id) {
348            return Err(DpdkError::InvalidPortId(port_id));
349        }
350
351        // Get device info
352        let mut dev_info = dpdk_sys::rte_eth_dev_info::default();
353        let ret = unsafe { dpdk_sys::rte_eth_dev_info_get(port_id, &mut dev_info) };
354        if ret != 0 {
355            return Err(DpdkError::PortConfigFailed(ret));
356        }
357
358        // Store capabilities
359        let capabilities = DeviceCapabilities {
360            rx_offload_capa: dev_info.rx_offload_capa,
361            tx_offload_capa: dev_info.tx_offload_capa,
362            max_rx_queues: dev_info.max_rx_queues,
363            max_tx_queues: dev_info.max_tx_queues,
364        };
365
366        // Validate queue counts against device limits
367        let nb_rx_queues = config.nb_rx_queues.min(dev_info.max_rx_queues);
368        let nb_tx_queues = config.nb_tx_queues.min(dev_info.max_tx_queues);
369
370        // Only enable offloads that are supported by the device
371        let requested_rx_offload = config.rx_offload.to_flags();
372        let requested_tx_offload = config.tx_offload.to_flags();
373        let active_rx_offload = requested_rx_offload & dev_info.rx_offload_capa;
374        let active_tx_offload = requested_tx_offload & dev_info.tx_offload_capa;
375
376        // Log if any requested offloads were not available
377        if active_rx_offload != requested_rx_offload {
378            let unsupported = requested_rx_offload & !dev_info.rx_offload_capa;
379            eprintln!("Warning: Some RX offloads not supported by device (flags: 0x{:x})", unsupported);
380        }
381        if active_tx_offload != requested_tx_offload {
382            let unsupported = requested_tx_offload & !dev_info.tx_offload_capa;
383            eprintln!("Warning: Some TX offloads not supported by device (flags: 0x{:x})", unsupported);
384        }
385
386        // Configure the port with only supported offloads
387        let eth_conf = dpdk_sys::rte_eth_conf {
388            rxmode: dpdk_sys::rte_eth_rxmode {
389                mtu: config.mtu,
390                offloads: active_rx_offload,
391                ..Default::default()
392            },
393            txmode: dpdk_sys::rte_eth_txmode {
394                offloads: active_tx_offload,
395                ..Default::default()
396            },
397            ..Default::default()
398        };
399
400        let ret = unsafe {
401            dpdk_sys::rte_eth_dev_configure(port_id, nb_rx_queues, nb_tx_queues, &eth_conf)
402        };
403        if ret != 0 {
404            return Err(DpdkError::PortConfigFailed(ret));
405        }
406
407        // Get socket ID for this port (for NUMA awareness)
408        let socket_id = unsafe { dpdk_sys::rte_socket_id() };
409
410        // Set up RX queues
411        for queue_id in 0..nb_rx_queues {
412            let ret = unsafe {
413                dpdk_sys::rte_eth_rx_queue_setup(
414                    port_id,
415                    queue_id,
416                    config.nb_rx_desc,
417                    socket_id as u32,
418                    ptr::null(),
419                    mempool.as_raw(),
420                )
421            };
422            if ret != 0 {
423                return Err(DpdkError::PortConfigFailed(ret));
424            }
425        }
426
427        // Set up TX queues
428        for queue_id in 0..nb_tx_queues {
429            let ret = unsafe {
430                dpdk_sys::rte_eth_tx_queue_setup(
431                    port_id,
432                    queue_id,
433                    config.nb_tx_desc,
434                    socket_id as u32,
435                    ptr::null(),
436                )
437            };
438            if ret != 0 {
439                return Err(DpdkError::PortConfigFailed(ret));
440            }
441        }
442
443        // Get MAC address
444        let mut mac_addr = dpdk_sys::rte_ether_addr::default();
445        let ret = unsafe { dpdk_sys::rte_eth_macaddr_get(port_id, &mut mac_addr) };
446        if ret != 0 {
447            return Err(DpdkError::PortConfigFailed(ret));
448        }
449
450        let mac_address = MacAddress::new(mac_addr.addr_bytes);
451
452        Ok(Self {
453            port_id,
454            config: PortConfig {
455                nb_rx_queues,
456                nb_tx_queues,
457                ..config
458            },
459            started: false,
460            mac_address,
461            capabilities,
462            active_rx_offload,
463            active_tx_offload,
464        })
465    }
466
467    /// Create a port handle without full initialization (for testing)
468    pub fn new(port_id: u16) -> DpdkResult<Self> {
469        if !Self::is_valid(port_id) {
470            return Err(DpdkError::InvalidPortId(port_id));
471        }
472
473        Ok(Self {
474            port_id,
475            config: PortConfig::default(),
476            started: false,
477            mac_address: MacAddress::default(),
478            capabilities: DeviceCapabilities::default(),
479            active_rx_offload: 0,
480            active_tx_offload: 0,
481        })
482    }
483
484    /// Get the device capabilities
485    pub fn capabilities(&self) -> &DeviceCapabilities {
486        &self.capabilities
487    }
488
489    /// Get the NUMA node of this NIC port.
490    ///
491    /// Returns the NUMA socket ID where the NIC is attached. Use this to
492    /// allocate mempools and pin lcores on the same NUMA node for optimal
493    /// memory access latency.
494    pub fn numa_node(&self) -> i32 {
495        unsafe { dpdk_sys::rte_eth_dev_socket_id(self.port_id) }
496    }
497
498    /// Get the active RX offload flags
499    pub fn active_rx_offload(&self) -> u64 {
500        self.active_rx_offload
501    }
502
503    /// Get the active TX offload flags
504    pub fn active_tx_offload(&self) -> u64 {
505        self.active_tx_offload
506    }
507
508    /// Check if a specific RX offload is active
509    pub fn is_rx_offload_active(&self, offload: u64) -> bool {
510        (self.active_rx_offload & offload) != 0
511    }
512
513    /// Check if a specific TX offload is active
514    pub fn is_tx_offload_active(&self, offload: u64) -> bool {
515        (self.active_tx_offload & offload) != 0
516    }
517
518    /// Get the port ID
519    pub fn port_id(&self) -> u16 {
520        self.port_id
521    }
522
523    /// Get the MAC address of this port
524    pub fn mac_address(&self) -> MacAddress {
525        self.mac_address
526    }
527
528    /// Get the port configuration
529    pub fn config(&self) -> &PortConfig {
530        &self.config
531    }
532
533    /// Check if the port is started
534    pub fn is_started(&self) -> bool {
535        self.started
536    }
537
538    /// Start the port
539    pub fn start(&mut self) -> DpdkResult<()> {
540        if self.started {
541            return Ok(());
542        }
543
544        let ret = unsafe { dpdk_sys::rte_eth_dev_start(self.port_id) };
545        if ret != 0 {
546            return Err(DpdkError::PortConfigFailed(ret));
547        }
548
549        // Enable promiscuous mode if configured
550        if self.config.promiscuous {
551            unsafe {
552                dpdk_sys::rte_eth_promiscuous_enable(self.port_id);
553            }
554        }
555
556        self.started = true;
557        Ok(())
558    }
559
560    /// Stop the port
561    pub fn stop(&mut self) -> DpdkResult<()> {
562        if !self.started {
563            return Ok(());
564        }
565
566        let ret = unsafe { dpdk_sys::rte_eth_dev_stop(self.port_id) };
567        if ret != 0 {
568            return Err(DpdkError::PortConfigFailed(ret));
569        }
570
571        self.started = false;
572        Ok(())
573    }
574
575    /// Get link status
576    pub fn link_status(&self) -> LinkStatus {
577        let mut link = dpdk_sys::rte_eth_link::default();
578        unsafe {
579            dpdk_sys::rte_eth_link_get_nowait(self.port_id, &mut link);
580        }
581
582        LinkStatus {
583            speed: link.link_speed,
584            full_duplex: link.link_duplex() != 0,
585            autoneg: link.link_autoneg() != 0,
586            link_up: link.link_status() != 0,
587        }
588    }
589
590    /// Get port statistics
591    pub fn stats(&self) -> DpdkResult<PortStats> {
592        let mut stats = dpdk_sys::rte_eth_stats::default();
593        let ret = unsafe { dpdk_sys::rte_eth_stats_get(self.port_id, &mut stats) };
594        if ret != 0 {
595            return Err(DpdkError::PortConfigFailed(ret));
596        }
597
598        Ok(PortStats {
599            rx_packets: stats.ipackets,
600            tx_packets: stats.opackets,
601            rx_bytes: stats.ibytes,
602            tx_bytes: stats.obytes,
603            rx_missed: stats.imissed,
604            rx_errors: stats.ierrors,
605            tx_errors: stats.oerrors,
606        })
607    }
608
609    /// Reset port statistics
610    pub fn reset_stats(&self) -> DpdkResult<()> {
611        let ret = unsafe { dpdk_sys::rte_eth_stats_reset(self.port_id) };
612        if ret != 0 {
613            return Err(DpdkError::PortConfigFailed(ret));
614        }
615        Ok(())
616    }
617
618    /// Receive a burst of packets from the specified queue
619    ///
620    /// Returns a vector of received Mbufs. The maximum number of packets
621    /// returned is limited by `max_packets`.
622    pub fn rx_burst(&self, queue_id: u16, max_packets: u16) -> DpdkResult<Vec<Mbuf>> {
623        if !self.started {
624            return Ok(Vec::new());
625        }
626
627        let mut rx_pkts: Vec<*mut dpdk_sys::rte_mbuf> = vec![ptr::null_mut(); max_packets as usize];
628
629        let nb_rx = unsafe {
630            dpdk_sys::rte_eth_rx_burst(
631                self.port_id,
632                queue_id,
633                rx_pkts.as_mut_ptr(),
634                max_packets,
635            )
636        };
637
638        let mut mbufs = Vec::with_capacity(nb_rx as usize);
639        for i in 0..nb_rx as usize {
640            if let Some(mbuf) = unsafe { Mbuf::from_raw(rx_pkts[i]) } {
641                mbufs.push(mbuf);
642            }
643        }
644
645        Ok(mbufs)
646    }
647
648    /// Transmit a burst of packets on the specified queue
649    ///
650    /// Returns the number of packets successfully transmitted.
651    /// Packets that are sent are consumed (freed) by the driver.
652    pub fn tx_burst(&self, queue_id: u16, packets: &mut Vec<Mbuf>) -> DpdkResult<u16> {
653        if !self.started || packets.is_empty() {
654            return Ok(0);
655        }
656
657        // Convert Mbufs to raw pointers
658        let mut tx_pkts: Vec<*mut dpdk_sys::rte_mbuf> = packets
659            .iter()
660            .map(|m| m.as_raw())
661            .collect();
662
663        let nb_tx = unsafe {
664            dpdk_sys::rte_eth_tx_burst(
665                self.port_id,
666                queue_id,
667                tx_pkts.as_mut_ptr(),
668                tx_pkts.len() as u16,
669            )
670        };
671
672        // Remove sent packets from the vector (they're now owned by the driver)
673        // We need to forget them to prevent double-free
674        for mbuf in packets.drain(..nb_tx as usize) {
675            std::mem::forget(mbuf);
676        }
677
678        Ok(nb_tx)
679    }
680
681    // ========================================================================
682    // Promiscuous Mode
683    // ========================================================================
684
685    /// Enable promiscuous mode on the port
686    ///
687    /// In promiscuous mode, the port receives all packets regardless of
688    /// destination MAC address.
689    pub fn set_promiscuous(&mut self, enable: bool) -> DpdkResult<()> {
690        let ret = if enable {
691            unsafe { dpdk_sys::rte_eth_promiscuous_enable(self.port_id) }
692        } else {
693            unsafe { dpdk_sys::rte_eth_promiscuous_disable(self.port_id) }
694        };
695
696        if ret != 0 {
697            return Err(DpdkError::PortConfigFailed(ret));
698        }
699
700        self.config.promiscuous = enable;
701        Ok(())
702    }
703
704    /// Check if promiscuous mode is enabled
705    pub fn is_promiscuous(&self) -> bool {
706        unsafe { dpdk_sys::rte_eth_promiscuous_get(self.port_id) != 0 }
707    }
708
709    // ========================================================================
710    // All-Multicast Mode
711    // ========================================================================
712
713    /// Enable all-multicast mode on the port
714    ///
715    /// In all-multicast mode, the port receives all multicast packets
716    /// regardless of whether they match configured multicast addresses.
717    pub fn set_allmulticast(&self, enable: bool) -> DpdkResult<()> {
718        let ret = if enable {
719            unsafe { dpdk_sys::rte_eth_allmulticast_enable(self.port_id) }
720        } else {
721            unsafe { dpdk_sys::rte_eth_allmulticast_disable(self.port_id) }
722        };
723
724        if ret != 0 {
725            return Err(DpdkError::PortConfigFailed(ret));
726        }
727        Ok(())
728    }
729
730    /// Check if all-multicast mode is enabled
731    pub fn is_allmulticast(&self) -> bool {
732        unsafe { dpdk_sys::rte_eth_allmulticast_get(self.port_id) != 0 }
733    }
734
735    // ========================================================================
736    // Multicast Address Management
737    // ========================================================================
738
739    /// Set the list of multicast MAC addresses to receive
740    ///
741    /// This configures the hardware multicast filter. Pass an empty slice
742    /// to clear all multicast addresses.
743    pub fn set_multicast_addrs(&self, addrs: &[MacAddress]) -> DpdkResult<()> {
744        if addrs.is_empty() {
745            // Clear multicast list
746            let ret = unsafe {
747                dpdk_sys::rte_eth_dev_set_mc_addr_list(
748                    self.port_id,
749                    ptr::null_mut(),
750                    0,
751                )
752            };
753            if ret != 0 {
754                return Err(DpdkError::PortConfigFailed(ret));
755            }
756            return Ok(());
757        }
758
759        // Convert MacAddresses to rte_ether_addr
760        let mut mc_addrs: Vec<dpdk_sys::rte_ether_addr> = addrs
761            .iter()
762            .map(|mac| dpdk_sys::rte_ether_addr {
763                addr_bytes: mac.octets(),
764            })
765            .collect();
766
767        let ret = unsafe {
768            dpdk_sys::rte_eth_dev_set_mc_addr_list(
769                self.port_id,
770                mc_addrs.as_mut_ptr(),
771                mc_addrs.len() as u32,
772            )
773        };
774
775        if ret != 0 {
776            return Err(DpdkError::PortConfigFailed(ret));
777        }
778        Ok(())
779    }
780}
781
782impl Drop for Port {
783    fn drop(&mut self) {
784        if self.started {
785            let _ = self.stop();
786        }
787        unsafe {
788            dpdk_sys::rte_eth_dev_close(self.port_id);
789        }
790    }
791}
792
793// ============================================================================
794// Unit Tests
795// ============================================================================
796
797#[cfg(test)]
798mod tests {
799    use super::*;
800
801    #[test]
802    fn test_mac_address_display() {
803        let mac = MacAddress::new([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
804        assert_eq!(mac.to_string(), "02:00:00:00:00:01");
805    }
806
807    #[test]
808    fn test_mac_address_broadcast() {
809        let broadcast = MacAddress::new([0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
810        assert!(broadcast.is_broadcast());
811        assert!(broadcast.is_multicast());
812
813        let unicast = MacAddress::new([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
814        assert!(!unicast.is_broadcast());
815        assert!(!unicast.is_multicast());
816    }
817
818    #[test]
819    fn test_mac_address_multicast() {
820        // Multicast addresses have LSB of first byte set
821        let multicast = MacAddress::new([0x01, 0x00, 0x5e, 0x00, 0x00, 0x01]);
822        assert!(multicast.is_multicast());
823        assert!(!multicast.is_broadcast());
824    }
825
826    #[test]
827    fn test_mac_address_zero() {
828        let zero = MacAddress::default();
829        assert!(zero.is_zero());
830
831        let non_zero = MacAddress::new([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
832        assert!(!non_zero.is_zero());
833    }
834
835    #[test]
836    fn test_port_config_default() {
837        let config = PortConfig::default();
838        assert_eq!(config.nb_rx_queues, 1);
839        assert_eq!(config.nb_tx_queues, 1);
840        assert_eq!(config.nb_rx_desc, DEFAULT_RX_DESC);
841        assert_eq!(config.nb_tx_desc, DEFAULT_TX_DESC);
842        assert!(config.promiscuous);
843    }
844
845    #[test]
846    fn test_link_status_default() {
847        let status = LinkStatus::default();
848        assert_eq!(status.speed, 0);
849        assert!(!status.full_duplex);
850        assert!(!status.link_up);
851    }
852
853    #[test]
854    fn test_port_stats_default() {
855        let stats = PortStats::default();
856        assert_eq!(stats.rx_packets, 0);
857        assert_eq!(stats.tx_packets, 0);
858        assert_eq!(stats.rx_bytes, 0);
859        assert_eq!(stats.tx_bytes, 0);
860    }
861
862    #[test]
863    fn test_port_count_available() {
864        // With stubs, this returns 1
865        let count = Port::count_available();
866        assert!(count >= 1);
867    }
868
869    #[test]
870    fn test_port_is_valid() {
871        // Port 0 should be valid (stubs return count of 1)
872        assert!(Port::is_valid(0));
873        // Port 100 should be invalid
874        assert!(!Port::is_valid(100));
875    }
876
877    #[test]
878    fn test_port_new() {
879        let port = Port::new(0);
880        assert!(port.is_ok());
881        let port = port.unwrap();
882        assert_eq!(port.port_id(), 0);
883        assert!(!port.is_started());
884    }
885
886    #[test]
887    fn test_port_new_invalid() {
888        let port = Port::new(100);
889        assert!(port.is_err());
890        match port {
891            Err(DpdkError::InvalidPortId(id)) => assert_eq!(id, 100),
892            _ => panic!("Expected InvalidPortId error"),
893        }
894    }
895
896    #[test]
897    fn test_port_start_stop() {
898        let mut port = Port::new(0).unwrap();
899
900        // Start
901        assert!(port.start().is_ok());
902        assert!(port.is_started());
903
904        // Start again should be no-op
905        assert!(port.start().is_ok());
906
907        // Stop
908        assert!(port.stop().is_ok());
909        assert!(!port.is_started());
910
911        // Stop again should be no-op
912        assert!(port.stop().is_ok());
913    }
914
915    #[test]
916    fn test_port_link_status() {
917        let port = Port::new(0).unwrap();
918        let status = port.link_status();
919        // With stubs, link shows as up at 10 Gbps
920        assert!(status.link_up);
921        assert_eq!(status.speed, 10000);
922    }
923
924    #[test]
925    fn test_port_stats() {
926        let port = Port::new(0).unwrap();
927        let stats = port.stats();
928        assert!(stats.is_ok());
929        let stats = stats.unwrap();
930        assert_eq!(stats.rx_packets, 0);
931        assert_eq!(stats.tx_packets, 0);
932    }
933
934    #[test]
935    fn test_port_reset_stats() {
936        let port = Port::new(0).unwrap();
937        assert!(port.reset_stats().is_ok());
938    }
939
940    #[test]
941    fn test_port_rx_burst_not_started() {
942        let port = Port::new(0).unwrap();
943        let packets = port.rx_burst(0, 32);
944        assert!(packets.is_ok());
945        assert!(packets.unwrap().is_empty()); // Not started, returns empty
946    }
947
948    #[test]
949    fn test_port_tx_burst_not_started() {
950        let port = Port::new(0).unwrap();
951        let mut packets: Vec<Mbuf> = Vec::new();
952        let sent = port.tx_burst(0, &mut packets);
953        assert!(sent.is_ok());
954        assert_eq!(sent.unwrap(), 0); // Not started, returns 0
955    }
956}