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