Skip to main content

rovs_openflow/action/
mod.rs

1//! OpenFlow actions.
2//!
3//! This module provides types and functions for working with OpenFlow actions,
4//! including standard actions and Nicira vendor extensions.
5
6mod nicira;
7pub mod nxm;
8pub mod types;
9
10#[cfg(test)]
11mod tests;
12
13use std::net::{Ipv4Addr, Ipv6Addr};
14
15use std::fmt;
16
17use crate::match_fields::MacAddr;
18use crate::oxm::{OxmClass, OxmField};
19
20// Re-export public types
21#[allow(unused_imports)]
22pub use nicira::{learn_flags, LearnSpec, NxLearn};
23pub use types::{ct_flags, nat_flags, nat_range, port, NICIRA_VENDOR_ID};
24
25// Use ActionType internally (NxActionSubtype is used by nicira module)
26use types::ActionType;
27
28// Import nicira encoding/decoding functions
29use nicira::{
30    decode_nicira_action, encode_nx_ct, encode_nx_ct_nat, encode_nx_learn, encode_nx_move,
31    encode_nx_reg_load_nxm, encode_nx_resubmit, encode_set_tunnel_id,
32};
33
34/// CT commit flag (shorthand).
35pub const CT_COMMIT: u16 = ct_flags::COMMIT;
36
37/// NAT configuration for use with ct() action.
38///
39/// # Examples
40///
41/// ```ignore
42/// // SNAT to a single IP
43/// NatConfig::snat(Ipv4Addr::new(10, 0, 0, 1))
44///
45/// // SNAT with IP range
46/// NatConfig::snat_range(
47///     Ipv4Addr::new(10, 0, 0, 1),
48///     Ipv4Addr::new(10, 0, 0, 10),
49/// )
50///
51/// // DNAT with port
52/// NatConfig::dnat(Ipv4Addr::new(192, 168, 1, 1)).port(8080)
53///
54/// // SNAT with port range and random selection
55/// NatConfig::snat(Ipv4Addr::new(10, 0, 0, 1))
56///     .port_range(5000, 6000)
57///     .random()
58/// ```
59#[derive(Debug, Clone)]
60pub struct NatConfig {
61    /// NAT flags (SRC/DST, persistent, hash, random)
62    pub flags: u16,
63    /// Minimum IPv4 address (if IPv4 NAT)
64    pub ipv4_min: Option<Ipv4Addr>,
65    /// Maximum IPv4 address (if range)
66    pub ipv4_max: Option<Ipv4Addr>,
67    /// Minimum IPv6 address (if IPv6 NAT)
68    pub ipv6_min: Option<Ipv6Addr>,
69    /// Maximum IPv6 address (if range)
70    pub ipv6_max: Option<Ipv6Addr>,
71    /// Minimum port (if port NAT)
72    pub port_min: Option<u16>,
73    /// Maximum port (if port range)
74    pub port_max: Option<u16>,
75}
76
77impl NatConfig {
78    /// Create a SNAT configuration with a single IPv4 address.
79    pub fn snat(addr: Ipv4Addr) -> Self {
80        Self {
81            flags: nat_flags::SRC,
82            ipv4_min: Some(addr),
83            ipv4_max: None,
84            ipv6_min: None,
85            ipv6_max: None,
86            port_min: None,
87            port_max: None,
88        }
89    }
90
91    /// Create a SNAT configuration with an IPv4 address range.
92    pub fn snat_range(min: Ipv4Addr, max: Ipv4Addr) -> Self {
93        Self {
94            flags: nat_flags::SRC,
95            ipv4_min: Some(min),
96            ipv4_max: Some(max),
97            ipv6_min: None,
98            ipv6_max: None,
99            port_min: None,
100            port_max: None,
101        }
102    }
103
104    /// Create a DNAT configuration with a single IPv4 address.
105    pub fn dnat(addr: Ipv4Addr) -> Self {
106        Self {
107            flags: nat_flags::DST,
108            ipv4_min: Some(addr),
109            ipv4_max: None,
110            ipv6_min: None,
111            ipv6_max: None,
112            port_min: None,
113            port_max: None,
114        }
115    }
116
117    /// Create a DNAT configuration with an IPv4 address range.
118    pub fn dnat_range(min: Ipv4Addr, max: Ipv4Addr) -> Self {
119        Self {
120            flags: nat_flags::DST,
121            ipv4_min: Some(min),
122            ipv4_max: Some(max),
123            ipv6_min: None,
124            ipv6_max: None,
125            port_min: None,
126            port_max: None,
127        }
128    }
129
130    /// Create a SNAT configuration with a single IPv6 address.
131    pub fn snat_v6(addr: Ipv6Addr) -> Self {
132        Self {
133            flags: nat_flags::SRC,
134            ipv4_min: None,
135            ipv4_max: None,
136            ipv6_min: Some(addr),
137            ipv6_max: None,
138            port_min: None,
139            port_max: None,
140        }
141    }
142
143    /// Create a DNAT configuration with a single IPv6 address.
144    pub fn dnat_v6(addr: Ipv6Addr) -> Self {
145        Self {
146            flags: nat_flags::DST,
147            ipv4_min: None,
148            ipv4_max: None,
149            ipv6_min: Some(addr),
150            ipv6_max: None,
151            port_min: None,
152            port_max: None,
153        }
154    }
155
156    /// Create a SNAT configuration with an IPv6 address range.
157    pub fn snat_v6_range(min: Ipv6Addr, max: Ipv6Addr) -> Self {
158        Self {
159            flags: nat_flags::SRC,
160            ipv4_min: None,
161            ipv4_max: None,
162            ipv6_min: Some(min),
163            ipv6_max: Some(max),
164            port_min: None,
165            port_max: None,
166        }
167    }
168
169    /// Create a DNAT configuration with an IPv6 address range.
170    pub fn dnat_v6_range(min: Ipv6Addr, max: Ipv6Addr) -> Self {
171        Self {
172            flags: nat_flags::DST,
173            ipv4_min: None,
174            ipv4_max: None,
175            ipv6_min: Some(min),
176            ipv6_max: Some(max),
177            port_min: None,
178            port_max: None,
179        }
180    }
181
182    /// Set a single port for NAT.
183    pub fn port(mut self, port: u16) -> Self {
184        self.port_min = Some(port);
185        self.port_max = None;
186        self
187    }
188
189    /// Set a port range for NAT.
190    pub fn port_range(mut self, min: u16, max: u16) -> Self {
191        self.port_min = Some(min);
192        self.port_max = Some(max);
193        self
194    }
195
196    /// Use persistent NAT mapping (survives restarts).
197    pub fn persistent(mut self) -> Self {
198        self.flags |= nat_flags::PERSISTENT;
199        self
200    }
201
202    /// Use hash-based port selection.
203    pub fn hash(mut self) -> Self {
204        self.flags |= nat_flags::PROTO_HASH;
205        self.flags &= !nat_flags::PROTO_RANDOM;
206        self
207    }
208
209    /// Use random port selection.
210    pub fn random(mut self) -> Self {
211        self.flags |= nat_flags::PROTO_RANDOM;
212        self.flags &= !nat_flags::PROTO_HASH;
213        self
214    }
215
216    /// Calculate the range_present flags for encoding.
217    pub(crate) fn range_present(&self) -> u16 {
218        let mut flags = 0u16;
219        if self.ipv4_min.is_some() {
220            flags |= nat_range::IPV4_MIN;
221        }
222        if self.ipv4_max.is_some() {
223            flags |= nat_range::IPV4_MAX;
224        }
225        if self.ipv6_min.is_some() {
226            flags |= nat_range::IPV6_MIN;
227        }
228        if self.ipv6_max.is_some() {
229            flags |= nat_range::IPV6_MAX;
230        }
231        if self.port_min.is_some() {
232            flags |= nat_range::PROTO_MIN;
233        }
234        if self.port_max.is_some() {
235            flags |= nat_range::PROTO_MAX;
236        }
237        flags
238    }
239}
240
241/// An OpenFlow action.
242#[derive(Debug, Clone)]
243pub enum Action {
244    /// Output to a port
245    Output(OutputPort),
246    /// Drop the packet (implicit, no action)
247    Drop,
248    /// Send to controller
249    Controller { max_len: u16 },
250    /// Set source MAC
251    SetEthSrc(MacAddr),
252    /// Set destination MAC
253    SetEthDst(MacAddr),
254    /// Set VLAN ID
255    SetVlanVid(u16),
256    /// Push VLAN tag
257    PushVlan(u16),
258    /// Pop VLAN tag
259    PopVlan,
260    /// Set IPv4 source
261    SetIpv4Src(Ipv4Addr),
262    /// Set IPv4 destination
263    SetIpv4Dst(Ipv4Addr),
264    /// Set TCP/UDP source port
265    SetTpSrc(u16),
266    /// Set TCP/UDP destination port
267    SetTpDst(u16),
268    /// Set IP TTL
269    SetTtl(u8),
270    /// Decrement IP TTL
271    DecTtl,
272    /// Go to table (OF 1.1+)
273    GotoTable(u8),
274    /// Write metadata
275    WriteMetadata { metadata: u64, mask: u64 },
276    /// Apply meter
277    Meter(u32),
278    /// Output to group
279    Group(u32),
280    /// Set tunnel ID
281    SetTunnelId(u64),
282    /// Resubmit to table (Nicira extension)
283    NxResubmit {
284        port: Option<u16>,
285        table: Option<u8>,
286    },
287    /// Learn action (Nicira extension)
288    NxLearn(NxLearn),
289    /// Conntrack action (Nicira extension)
290    NxCt {
291        flags: u16,
292        zone: u16,
293        table: Option<u8>,
294    },
295    /// Conntrack with NAT action (Nicira extension)
296    NxCtNat {
297        flags: u16,
298        zone: u16,
299        table: Option<u8>,
300        nat: NatConfig,
301    },
302    /// Move/copy bits between fields (Nicira extension)
303    NxMove {
304        /// Source NXM field header
305        src_field: u32,
306        /// Destination NXM field header
307        dst_field: u32,
308        /// Number of bits to copy
309        n_bits: u16,
310        /// Bit offset in source field
311        src_ofs: u16,
312        /// Bit offset in destination field
313        dst_ofs: u16,
314    },
315    /// Load immediate value into field (Nicira extension)
316    NxRegLoad {
317        /// Destination NXM field header
318        dst_field: u32,
319        /// Bit offset in destination field
320        dst_ofs: u16,
321        /// Number of bits to load
322        n_bits: u16,
323        /// Value to load
324        value: u64,
325    },
326}
327
328/// Output port specification.
329#[derive(Debug, Clone, Copy)]
330pub enum OutputPort {
331    /// Physical or logical port number
332    Port(u32),
333    /// Send to controller
334    Controller,
335    /// Flood (all ports except input)
336    Flood,
337    /// All ports except input
338    All,
339    /// Input port
340    InPort,
341    /// Local (management) port
342    Local,
343    /// Normal L2/L3 processing
344    Normal,
345    /// No output (drop)
346    None,
347}
348
349impl From<u32> for OutputPort {
350    fn from(port: u32) -> Self {
351        Self::Port(port)
352    }
353}
354
355/// A list of actions.
356#[derive(Debug, Clone, Default)]
357pub struct ActionList {
358    actions: Vec<Action>,
359}
360
361impl ActionList {
362    /// Create a new empty action list.
363    pub fn new() -> Self {
364        Self::default()
365    }
366
367    /// Add an action to the list.
368    pub fn push(&mut self, action: Action) {
369        self.actions.push(action);
370    }
371
372    /// Output to a port.
373    pub fn output(mut self, port: impl Into<OutputPort>) -> Self {
374        self.actions.push(Action::Output(port.into()));
375        self
376    }
377
378    /// Send to controller.
379    pub fn controller(mut self, max_len: u16) -> Self {
380        self.actions.push(Action::Controller { max_len });
381        self
382    }
383
384    /// Drop the packet.
385    pub fn drop(mut self) -> Self {
386        self.actions.push(Action::Drop);
387        self
388    }
389
390    /// Set destination MAC.
391    pub fn set_eth_dst(mut self, mac: MacAddr) -> Self {
392        self.actions.push(Action::SetEthDst(mac));
393        self
394    }
395
396    /// Set source MAC.
397    pub fn set_eth_src(mut self, mac: MacAddr) -> Self {
398        self.actions.push(Action::SetEthSrc(mac));
399        self
400    }
401
402    /// Push VLAN tag.
403    pub fn push_vlan(mut self, tpid: u16) -> Self {
404        self.actions.push(Action::PushVlan(tpid));
405        self
406    }
407
408    /// Pop VLAN tag.
409    pub fn pop_vlan(mut self) -> Self {
410        self.actions.push(Action::PopVlan);
411        self
412    }
413
414    /// Set VLAN ID.
415    pub fn set_vlan_vid(mut self, vid: u16) -> Self {
416        self.actions.push(Action::SetVlanVid(vid));
417        self
418    }
419
420    /// Go to another table.
421    pub fn goto_table(mut self, table: u8) -> Self {
422        self.actions.push(Action::GotoTable(table));
423        self
424    }
425
426    /// Decrement TTL.
427    pub fn dec_ttl(mut self) -> Self {
428        self.actions.push(Action::DecTtl);
429        self
430    }
431
432    /// Output to all ports except input port (flood).
433    pub fn flood(mut self) -> Self {
434        self.actions.push(Action::Output(OutputPort::Flood));
435        self
436    }
437
438    /// Output to all ports.
439    pub fn all(mut self) -> Self {
440        self.actions.push(Action::Output(OutputPort::All));
441        self
442    }
443
444    /// Output using normal L2/L3 switching.
445    pub fn normal(mut self) -> Self {
446        self.actions.push(Action::Output(OutputPort::Normal));
447        self
448    }
449
450    /// Output to ingress port.
451    pub fn in_port(mut self) -> Self {
452        self.actions.push(Action::Output(OutputPort::InPort));
453        self
454    }
455
456    /// Set tunnel ID (Nicira extension).
457    pub fn set_tunnel_id(mut self, tunnel_id: u64) -> Self {
458        self.actions.push(Action::SetTunnelId(tunnel_id));
459        self
460    }
461
462    /// Output to group table.
463    pub fn group(mut self, group_id: u32) -> Self {
464        self.actions.push(Action::Group(group_id));
465        self
466    }
467
468    /// Resubmit to another table (Nicira extension).
469    ///
470    /// - `port`: Input port to use (None = current input port)
471    /// - `table`: Table to resubmit to (None = current table)
472    pub fn resubmit(mut self, port: Option<u16>, table: Option<u8>) -> Self {
473        self.actions.push(Action::NxResubmit { port, table });
474        self
475    }
476
477    /// Resubmit to a specific table (Nicira extension).
478    ///
479    /// Convenience method for `resubmit(None, Some(table))`.
480    pub fn resubmit_table(mut self, table: u8) -> Self {
481        self.actions.push(Action::NxResubmit { port: None, table: Some(table) });
482        self
483    }
484
485    /// Connection tracking action (Nicira extension).
486    ///
487    /// - `flags`: CT flags (commit, force, etc.)
488    /// - `zone`: CT zone ID
489    /// - `table`: Table to recirculate to after CT (None = no recirc)
490    pub fn ct(mut self, flags: u16, zone: u16, table: Option<u8>) -> Self {
491        self.actions.push(Action::NxCt { flags, zone, table });
492        self
493    }
494
495    /// Connection tracking with commit (Nicira extension).
496    ///
497    /// Commits the connection to the connection tracking table.
498    pub fn ct_commit(mut self, zone: u16) -> Self {
499        self.actions.push(Action::NxCt { flags: CT_COMMIT, zone, table: None });
500        self
501    }
502
503    /// Connection tracking with NAT (Nicira extension).
504    ///
505    /// Performs connection tracking with Network Address Translation.
506    ///
507    /// # Arguments
508    ///
509    /// - `flags`: CT flags (commit, force, etc.)
510    /// - `zone`: CT zone ID
511    /// - `table`: Table to recirculate to after CT (None = no recirc)
512    /// - `nat`: NAT configuration (SNAT or DNAT)
513    ///
514    /// # Example
515    ///
516    /// ```ignore
517    /// use rovs_openflow::{ActionList, NatConfig, CT_COMMIT};
518    /// use std::net::Ipv4Addr;
519    ///
520    /// // SNAT to 10.0.0.1
521    /// ActionList::new().ct_nat(
522    ///     CT_COMMIT,
523    ///     1,
524    ///     Some(2),
525    ///     NatConfig::snat(Ipv4Addr::new(10, 0, 0, 1)),
526    /// )
527    /// ```
528    pub fn ct_nat(mut self, flags: u16, zone: u16, table: Option<u8>, nat: NatConfig) -> Self {
529        self.actions.push(Action::NxCtNat { flags, zone, table, nat });
530        self
531    }
532
533    /// Connection tracking with SNAT (Nicira extension).
534    ///
535    /// Source NAT: translates the source IP address.
536    ///
537    /// # Example
538    ///
539    /// ```ignore
540    /// use std::net::Ipv4Addr;
541    ///
542    /// // SNAT to 10.0.0.1, commit and recirculate to table 2
543    /// ActionList::new().ct_snat(
544    ///     1,                              // zone
545    ///     Some(2),                        // recirc table
546    ///     Ipv4Addr::new(10, 0, 0, 1),     // NAT address
547    /// )
548    /// ```
549    pub fn ct_snat(mut self, zone: u16, table: Option<u8>, addr: Ipv4Addr) -> Self {
550        self.actions.push(Action::NxCtNat {
551            flags: CT_COMMIT,
552            zone,
553            table,
554            nat: NatConfig::snat(addr),
555        });
556        self
557    }
558
559    /// Connection tracking with DNAT (Nicira extension).
560    ///
561    /// Destination NAT: translates the destination IP address.
562    ///
563    /// # Example
564    ///
565    /// ```ignore
566    /// use std::net::Ipv4Addr;
567    ///
568    /// // DNAT to 192.168.1.100:8080
569    /// ActionList::new().ct_dnat(
570    ///     1,
571    ///     Some(2),
572    ///     Ipv4Addr::new(192, 168, 1, 100),
573    /// )
574    /// ```
575    pub fn ct_dnat(mut self, zone: u16, table: Option<u8>, addr: Ipv4Addr) -> Self {
576        self.actions.push(Action::NxCtNat {
577            flags: CT_COMMIT,
578            zone,
579            table,
580            nat: NatConfig::dnat(addr),
581        });
582        self
583    }
584
585    /// Learn action (Nicira extension).
586    ///
587    /// Creates flows dynamically based on packet content.
588    pub fn learn(mut self, learn: NxLearn) -> Self {
589        self.actions.push(Action::NxLearn(learn));
590        self
591    }
592
593    /// Move/copy bits between fields (Nicira extension).
594    ///
595    /// Copies `n_bits` bits from `src_field[src_ofs..]` to `dst_field[dst_ofs..]`.
596    /// Use NXM constants from the `nxm` module for field headers.
597    ///
598    /// # Example
599    /// ```ignore
600    /// // Copy ARP source IP to ARP target IP
601    /// actions.move_field(nxm::ARP_SPA, nxm::ARP_TPA, 32, 0, 0)
602    /// ```
603    pub fn move_field(
604        mut self,
605        src_field: u32,
606        dst_field: u32,
607        n_bits: u16,
608        src_ofs: u16,
609        dst_ofs: u16,
610    ) -> Self {
611        self.actions.push(Action::NxMove {
612            src_field,
613            dst_field,
614            n_bits,
615            src_ofs,
616            dst_ofs,
617        });
618        self
619    }
620
621    /// Load immediate value into field (Nicira extension).
622    ///
623    /// Loads `value` into `dst_field[dst_ofs..dst_ofs+n_bits]`.
624    /// Use NXM constants from the `nxm` module for field headers.
625    ///
626    /// # Example
627    /// ```ignore
628    /// // Set ARP opcode to 2 (reply)
629    /// actions.load_field(nxm::ARP_OP, 0, 16, 2)
630    /// ```
631    pub fn load_field(mut self, dst_field: u32, dst_ofs: u16, n_bits: u16, value: u64) -> Self {
632        self.actions.push(Action::NxRegLoad {
633            dst_field,
634            dst_ofs,
635            n_bits,
636            value,
637        });
638        self
639    }
640
641    /// Set ARP opcode (Nicira extension).
642    ///
643    /// Common values: 1 = request, 2 = reply
644    pub fn set_arp_op(self, opcode: u16) -> Self {
645        self.load_field(nxm::ARP_OP, 0, 16, opcode as u64)
646    }
647
648    /// Set ARP source protocol address (sender IP).
649    pub fn set_arp_spa(self, ip: u32) -> Self {
650        self.load_field(nxm::ARP_SPA, 0, 32, ip as u64)
651    }
652
653    /// Set ARP target protocol address (target IP).
654    pub fn set_arp_tpa(self, ip: u32) -> Self {
655        self.load_field(nxm::ARP_TPA, 0, 32, ip as u64)
656    }
657
658    /// Set ARP source hardware address (sender MAC).
659    ///
660    /// Note: MAC is passed as a u64 with the MAC in the lower 48 bits.
661    pub fn set_arp_sha(self, mac: u64) -> Self {
662        self.load_field(nxm::ARP_SHA, 0, 48, mac)
663    }
664
665    /// Set ARP target hardware address (target MAC).
666    ///
667    /// Note: MAC is passed as a u64 with the MAC in the lower 48 bits.
668    pub fn set_arp_tha(self, mac: u64) -> Self {
669        self.load_field(nxm::ARP_THA, 0, 48, mac)
670    }
671
672    /// Get the actions.
673    pub fn actions(&self) -> &[Action] {
674        &self.actions
675    }
676
677    /// Check if the list is empty.
678    pub fn is_empty(&self) -> bool {
679        self.actions.is_empty()
680    }
681
682    /// Get the number of actions.
683    pub fn len(&self) -> usize {
684        self.actions.len()
685    }
686
687    /// Encode all actions to wire format.
688    ///
689    /// Actions are concatenated and padded to 8-byte alignment.
690    pub fn encode(&self) -> Vec<u8> {
691        let mut buf = Vec::new();
692        for action in &self.actions {
693            buf.extend(action.encode());
694        }
695        // Pad to 8-byte boundary
696        let padding = (8 - (buf.len() % 8)) % 8;
697        buf.extend(std::iter::repeat_n(0u8, padding));
698        buf
699    }
700
701    /// Decode all actions from wire format.
702    ///
703    /// Reads actions until the data is exhausted.
704    pub fn decode(data: &[u8]) -> crate::Result<Self> {
705        let mut actions = Vec::new();
706        let mut offset = 0;
707
708        while offset < data.len() {
709            // Need at least 4 bytes for action header
710            if data.len() - offset < 4 {
711                break;
712            }
713
714            // Check for zero-length padding at end
715            let length = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as usize;
716            if length == 0 {
717                break;
718            }
719
720            let (action, consumed) = Action::decode(&data[offset..])?;
721            // Skip Drop actions (used as placeholder for unsupported types)
722            if !matches!(action, Action::Drop) {
723                actions.push(action);
724            }
725            offset += consumed;
726        }
727
728        Ok(Self { actions })
729    }
730}
731
732impl OutputPort {
733    /// Convert to wire format port number.
734    pub const fn to_wire_port(self) -> u32 {
735        match self {
736            Self::Port(p) => p,
737            Self::Controller => port::CONTROLLER,
738            Self::Flood => port::FLOOD,
739            Self::All => port::ALL,
740            Self::InPort => port::IN_PORT,
741            Self::Local => port::LOCAL,
742            Self::Normal => port::NORMAL,
743            Self::None => port::NONE,
744        }
745    }
746
747    /// Create from wire format port number.
748    pub const fn from_wire(port_num: u32) -> Self {
749        match port_num {
750            port::CONTROLLER => Self::Controller,
751            port::FLOOD => Self::Flood,
752            port::ALL => Self::All,
753            port::IN_PORT => Self::InPort,
754            port::LOCAL => Self::Local,
755            port::NORMAL => Self::Normal,
756            port::NONE => Self::None,
757            p => Self::Port(p),
758        }
759    }
760}
761
762/// Format a MAC address as `aa:bb:cc:dd:ee:ff`.
763fn format_mac(mac: MacAddr) -> String {
764    format!(
765        "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
766        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
767    )
768}
769
770impl fmt::Display for OutputPort {
771    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
772        match self {
773            Self::Port(p) => write!(f, "{p}"),
774            Self::Controller => write!(f, "CONTROLLER"),
775            Self::Flood => write!(f, "FLOOD"),
776            Self::All => write!(f, "ALL"),
777            Self::InPort => write!(f, "IN_PORT"),
778            Self::Local => write!(f, "LOCAL"),
779            Self::Normal => write!(f, "NORMAL"),
780            Self::None => write!(f, "NONE"),
781        }
782    }
783}
784
785impl fmt::Display for NatConfig {
786    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
787        write!(f, "nat(")?;
788        if self.flags & nat_flags::SRC != 0 {
789            write!(f, "src")?;
790        } else if self.flags & nat_flags::DST != 0 {
791            write!(f, "dst")?;
792        }
793        if let Some(ip) = self.ipv4_min {
794            write!(f, "={ip}")?;
795            if let Some(max) = self.ipv4_max {
796                write!(f, "-{max}")?;
797            }
798        } else if let Some(ip) = self.ipv6_min {
799            write!(f, "={ip}")?;
800            if let Some(max) = self.ipv6_max {
801                write!(f, "-{max}")?;
802            }
803        }
804        if let Some(port) = self.port_min {
805            write!(f, ":{port}")?;
806            if let Some(max) = self.port_max {
807                write!(f, "-{max}")?;
808            }
809        }
810        write!(f, ")")
811    }
812}
813
814impl fmt::Display for Action {
815    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
816        match self {
817            Self::Output(OutputPort::Normal) => write!(f, "NORMAL"),
818            Self::Output(OutputPort::Flood) => write!(f, "FLOOD"),
819            Self::Output(OutputPort::All) => write!(f, "ALL"),
820            Self::Output(OutputPort::InPort) => write!(f, "IN_PORT"),
821            Self::Output(OutputPort::Local) => write!(f, "LOCAL"),
822            Self::Output(port) => write!(f, "output:{port}"),
823            Self::Drop => write!(f, "drop"),
824            Self::Controller { max_len } => write!(f, "CONTROLLER:{max_len}"),
825            Self::SetEthSrc(mac) => write!(f, "set_eth_src:{}", format_mac(*mac)),
826            Self::SetEthDst(mac) => write!(f, "set_eth_dst:{}", format_mac(*mac)),
827            Self::SetVlanVid(vid) => write!(f, "set_vlan_vid:{vid}"),
828            Self::PushVlan(ethertype) => write!(f, "push_vlan:0x{ethertype:04x}"),
829            Self::PopVlan => write!(f, "pop_vlan"),
830            Self::SetIpv4Src(ip) => write!(f, "set_ipv4_src:{ip}"),
831            Self::SetIpv4Dst(ip) => write!(f, "set_ipv4_dst:{ip}"),
832            Self::SetTpSrc(port) => write!(f, "set_tp_src:{port}"),
833            Self::SetTpDst(port) => write!(f, "set_tp_dst:{port}"),
834            Self::SetTtl(ttl) => write!(f, "set_ttl:{ttl}"),
835            Self::DecTtl => write!(f, "dec_ttl"),
836            Self::GotoTable(table) => write!(f, "goto_table:{table}"),
837            Self::WriteMetadata { metadata, mask } => {
838                write!(f, "write_metadata:0x{metadata:x}/0x{mask:x}")
839            }
840            Self::Meter(id) => write!(f, "meter:{id}"),
841            Self::Group(id) => write!(f, "group:{id}"),
842            Self::SetTunnelId(id) => write!(f, "set_tunnel:{id:#x}"),
843            Self::NxResubmit { port, table } => {
844                let port_str = port.map_or(String::new(), |p| p.to_string());
845                let table_str = table.map_or(String::new(), |t| t.to_string());
846                write!(f, "resubmit({port_str},{table_str})")
847            }
848            Self::NxLearn(learn) => {
849                write!(f, "learn(table={}", learn.table_id)?;
850                if learn.idle_timeout > 0 {
851                    write!(f, ",idle_timeout={}", learn.idle_timeout)?;
852                }
853                if learn.hard_timeout > 0 {
854                    write!(f, ",hard_timeout={}", learn.hard_timeout)?;
855                }
856                if learn.priority > 0 {
857                    write!(f, ",priority={}", learn.priority)?;
858                }
859                write!(f, ")")
860            }
861            Self::NxCt { flags, zone, table } => {
862                write!(f, "ct(")?;
863                let mut parts = Vec::new();
864                if *flags & types::ct_flags::COMMIT != 0 {
865                    parts.push("commit".to_string());
866                }
867                if *zone != 0 {
868                    parts.push(format!("zone={zone}"));
869                }
870                if let Some(t) = table {
871                    parts.push(format!("table={t}"));
872                }
873                write!(f, "{})", parts.join(","))
874            }
875            Self::NxCtNat { flags, zone, table, nat } => {
876                write!(f, "ct(")?;
877                let mut parts = Vec::new();
878                if *flags & types::ct_flags::COMMIT != 0 {
879                    parts.push("commit".to_string());
880                }
881                if *zone != 0 {
882                    parts.push(format!("zone={zone}"));
883                }
884                if let Some(t) = table {
885                    parts.push(format!("table={t}"));
886                }
887                parts.push(nat.to_string());
888                write!(f, "{})", parts.join(","))
889            }
890            Self::NxMove { src_field, dst_field, n_bits, src_ofs, dst_ofs } => {
891                write!(f, "move:NXM({src_field:#x})[{src_ofs}..{n_bits}]->NXM({dst_field:#x})[{dst_ofs}..{n_bits}]")
892            }
893            Self::NxRegLoad { dst_field, dst_ofs, n_bits, value } => {
894                write!(f, "load:{value:#x}->NXM({dst_field:#x})[{dst_ofs}..{}]", dst_ofs + n_bits)
895            }
896        }
897    }
898}
899
900impl fmt::Display for ActionList {
901    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
902        if self.actions.is_empty() {
903            return write!(f, "drop");
904        }
905        for (i, action) in self.actions.iter().enumerate() {
906            if i > 0 {
907                write!(f, ",")?;
908            }
909            write!(f, "{action}")?;
910        }
911        Ok(())
912    }
913}
914
915impl Action {
916    /// Encode action to OpenFlow wire format.
917    #[allow(clippy::match_same_arms)]
918    pub fn encode(&self) -> Vec<u8> {
919        match self {
920            Self::Output(port) => encode_output(port.to_wire_port(), 0xffff),
921            Self::Drop => Vec::new(), // Drop is implicit (no actions)
922            Self::Controller { max_len } => encode_output(port::CONTROLLER, *max_len),
923            Self::SetEthSrc(mac) => encode_set_field_mac(OxmField::EthSrc, *mac),
924            Self::SetEthDst(mac) => encode_set_field_mac(OxmField::EthDst, *mac),
925            Self::SetVlanVid(vid) => encode_set_field_u16(OxmField::VlanVid, *vid | 0x1000),
926            Self::PushVlan(ethertype) => encode_push_vlan(*ethertype),
927            Self::PopVlan => encode_pop_vlan(),
928            Self::SetIpv4Src(addr) => encode_set_field_u32(OxmField::Ipv4Src, (*addr).into()),
929            Self::SetIpv4Dst(addr) => encode_set_field_u32(OxmField::Ipv4Dst, (*addr).into()),
930            Self::SetTpSrc(port) => encode_set_field_u16(OxmField::TcpSrc, *port),
931            Self::SetTpDst(port) => encode_set_field_u16(OxmField::TcpDst, *port),
932            Self::SetTtl(ttl) => encode_set_nw_ttl(*ttl),
933            Self::DecTtl => encode_dec_ttl(),
934            Self::GotoTable(_) => Vec::new(), // GotoTable is an instruction, not action
935            Self::WriteMetadata { .. } => Vec::new(), // WriteMetadata is an instruction
936            Self::Meter(_) => Vec::new(), // Meter is an instruction
937            Self::Group(group_id) => encode_group(*group_id),
938            Self::SetTunnelId(tun_id) => encode_set_tunnel_id(*tun_id),
939            Self::NxResubmit { port, table } => encode_nx_resubmit(*port, *table),
940            Self::NxLearn(learn) => encode_nx_learn(learn),
941            Self::NxCt { flags, zone, table } => encode_nx_ct(*flags, *zone, *table),
942            Self::NxCtNat { flags, zone, table, nat } => {
943                encode_nx_ct_nat(*flags, *zone, *table, nat)
944            }
945            Self::NxMove { src_field, dst_field, n_bits, src_ofs, dst_ofs } => {
946                encode_nx_move(*src_field, *dst_field, *n_bits, *src_ofs, *dst_ofs)
947            }
948            Self::NxRegLoad { dst_field, dst_ofs, n_bits, value } => {
949                encode_nx_reg_load_nxm(*dst_field, *dst_ofs, *n_bits, *value)
950            }
951        }
952    }
953
954    /// Decode action from OpenFlow wire format.
955    ///
956    /// Returns the decoded action and the number of bytes consumed.
957    #[allow(clippy::too_many_lines)]
958    pub fn decode(data: &[u8]) -> crate::Result<(Self, usize)> {
959        if data.len() < 4 {
960            return Err(crate::Error::Parse("action too short".into()));
961        }
962
963        let action_type = u16::from_be_bytes([data[0], data[1]]);
964        let length = u16::from_be_bytes([data[2], data[3]]) as usize;
965
966        if data.len() < length {
967            return Err(crate::Error::Parse("action truncated".into()));
968        }
969
970        let action_type = ActionType::try_from(action_type)?;
971
972        let action = match action_type {
973            ActionType::Output => {
974                if length < 16 {
975                    return Err(crate::Error::Parse("output action too short".into()));
976                }
977                let port_num = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
978                let max_len = u16::from_be_bytes([data[8], data[9]]);
979                let output_port = OutputPort::from_wire(port_num);
980                if port_num == port::CONTROLLER {
981                    Self::Controller { max_len }
982                } else {
983                    Self::Output(output_port)
984                }
985            }
986            ActionType::PopVlan => Self::PopVlan,
987            ActionType::PushVlan => {
988                if length < 8 {
989                    return Err(crate::Error::Parse("push_vlan action too short".into()));
990                }
991                let ethertype = u16::from_be_bytes([data[4], data[5]]);
992                Self::PushVlan(ethertype)
993            }
994            ActionType::DecNwTtl => Self::DecTtl,
995            ActionType::SetNwTtl => {
996                if length < 8 {
997                    return Err(crate::Error::Parse("set_nw_ttl action too short".into()));
998                }
999                Self::SetTtl(data[4])
1000            }
1001            ActionType::Group => {
1002                if length < 8 {
1003                    return Err(crate::Error::Parse("group action too short".into()));
1004                }
1005                let group_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
1006                Self::Group(group_id)
1007            }
1008            ActionType::SetField => {
1009                decode_set_field_action(&data[4..length])?
1010            }
1011            ActionType::Experimenter => {
1012                if length < 10 {
1013                    return Err(crate::Error::Parse("experimenter action too short".into()));
1014                }
1015                let vendor = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
1016                if vendor == NICIRA_VENDOR_ID {
1017                    decode_nicira_action(&data[8..length])?
1018                } else {
1019                    // Unknown vendor, skip
1020                    return Err(crate::Error::Parse(format!(
1021                        "unknown experimenter vendor: {vendor:#x}"
1022                    )));
1023                }
1024            }
1025            // Actions we don't fully decode yet - return a placeholder
1026            ActionType::CopyTtlOut
1027            | ActionType::CopyTtlIn
1028            | ActionType::SetMplsTtl
1029            | ActionType::DecMplsTtl
1030            | ActionType::PushMpls
1031            | ActionType::PopMpls
1032            | ActionType::SetQueue
1033            | ActionType::PushPbb
1034            | ActionType::PopPbb => {
1035                // Skip unsupported actions by returning Drop as a placeholder
1036                Self::Drop
1037            }
1038        };
1039
1040        Ok((action, length))
1041    }
1042}
1043
1044// ============================================================================
1045// Standard Action Encoding Functions
1046// ============================================================================
1047
1048/// Encode Output action (16 bytes).
1049///
1050/// ```text
1051/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1052/// |         type (0)            |          length (16)            |
1053/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1054/// |                            port                               |
1055/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1056/// |          max_len            |           pad (zeros)           |
1057/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1058/// |                           pad (zeros)                         |
1059/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1060/// ```
1061fn encode_output(port_num: u32, max_len: u16) -> Vec<u8> {
1062    let mut buf = Vec::with_capacity(16);
1063    buf.extend((ActionType::Output as u16).to_be_bytes());
1064    buf.extend(16u16.to_be_bytes()); // length
1065    buf.extend(port_num.to_be_bytes());
1066    buf.extend(max_len.to_be_bytes());
1067    buf.extend([0u8; 6]); // padding
1068    buf
1069}
1070
1071/// Encode PopVlan action (8 bytes).
1072fn encode_pop_vlan() -> Vec<u8> {
1073    let mut buf = Vec::with_capacity(8);
1074    buf.extend((ActionType::PopVlan as u16).to_be_bytes());
1075    buf.extend(8u16.to_be_bytes()); // length
1076    buf.extend([0u8; 4]); // padding
1077    buf
1078}
1079
1080/// Encode PushVlan action (8 bytes).
1081fn encode_push_vlan(ethertype: u16) -> Vec<u8> {
1082    let mut buf = Vec::with_capacity(8);
1083    buf.extend((ActionType::PushVlan as u16).to_be_bytes());
1084    buf.extend(8u16.to_be_bytes()); // length
1085    buf.extend(ethertype.to_be_bytes());
1086    buf.extend([0u8; 2]); // padding
1087    buf
1088}
1089
1090/// Encode DecNwTtl action (8 bytes).
1091fn encode_dec_ttl() -> Vec<u8> {
1092    let mut buf = Vec::with_capacity(8);
1093    buf.extend((ActionType::DecNwTtl as u16).to_be_bytes());
1094    buf.extend(8u16.to_be_bytes()); // length
1095    buf.extend([0u8; 4]); // padding
1096    buf
1097}
1098
1099/// Encode SetNwTtl action (8 bytes).
1100fn encode_set_nw_ttl(ttl: u8) -> Vec<u8> {
1101    let mut buf = Vec::with_capacity(8);
1102    buf.extend((ActionType::SetNwTtl as u16).to_be_bytes());
1103    buf.extend(8u16.to_be_bytes()); // length
1104    buf.push(ttl);
1105    buf.extend([0u8; 3]); // padding
1106    buf
1107}
1108
1109/// Encode Group action (8 bytes).
1110fn encode_group(group_id: u32) -> Vec<u8> {
1111    let mut buf = Vec::with_capacity(8);
1112    buf.extend((ActionType::Group as u16).to_be_bytes());
1113    buf.extend(8u16.to_be_bytes()); // length
1114    buf.extend(group_id.to_be_bytes());
1115    buf
1116}
1117
1118// ============================================================================
1119// SetField Actions
1120// ============================================================================
1121
1122/// Encode SetField action for MAC address (16 bytes).
1123///
1124/// SetField uses OXM format: action header + OXM header + value + padding.
1125fn encode_set_field_mac(field: OxmField, mac: [u8; 6]) -> Vec<u8> {
1126    let mut buf = Vec::with_capacity(16);
1127    buf.extend((ActionType::SetField as u16).to_be_bytes());
1128    buf.extend(16u16.to_be_bytes()); // length
1129
1130    // OXM header for MAC field: class=0x8000, field, has_mask=false, length=6
1131    let oxm_header =
1132        ((OxmClass::OpenflowBasic as u32) << 16) | ((field as u32) << 9) | 6;
1133    buf.extend(oxm_header.to_be_bytes());
1134    buf.extend(mac);
1135    buf.extend([0u8; 2]); // padding to 16 bytes
1136    buf
1137}
1138
1139/// Encode SetField action for u16 value (16 bytes).
1140fn encode_set_field_u16(field: OxmField, value: u16) -> Vec<u8> {
1141    let mut buf = Vec::with_capacity(16);
1142    buf.extend((ActionType::SetField as u16).to_be_bytes());
1143    buf.extend(16u16.to_be_bytes()); // length
1144
1145    // OXM header for u16 field: class=0x8000, field, has_mask=false, length=2
1146    let oxm_header =
1147        ((OxmClass::OpenflowBasic as u32) << 16) | ((field as u32) << 9) | 2;
1148    buf.extend(oxm_header.to_be_bytes());
1149    buf.extend(value.to_be_bytes());
1150    buf.extend([0u8; 6]); // padding to 16 bytes
1151    buf
1152}
1153
1154/// Encode SetField action for u32 value (16 bytes).
1155fn encode_set_field_u32(field: OxmField, value: u32) -> Vec<u8> {
1156    let mut buf = Vec::with_capacity(16);
1157    buf.extend((ActionType::SetField as u16).to_be_bytes());
1158    buf.extend(16u16.to_be_bytes()); // length
1159
1160    // OXM header for u32 field: class=0x8000, field, has_mask=false, length=4
1161    let oxm_header =
1162        ((OxmClass::OpenflowBasic as u32) << 16) | ((field as u32) << 9) | 4;
1163    buf.extend(oxm_header.to_be_bytes());
1164    buf.extend(value.to_be_bytes());
1165    buf.extend([0u8; 4]); // padding to 16 bytes
1166    buf
1167}
1168
1169// ============================================================================
1170// Action Decoding Functions
1171// ============================================================================
1172
1173/// Decode SetField action.
1174///
1175/// SetField uses OXM format: OXM header (4 bytes) + value.
1176fn decode_set_field_action(data: &[u8]) -> crate::Result<Action> {
1177    if data.len() < 4 {
1178        return Err(crate::Error::Parse("set_field action too short".into()));
1179    }
1180
1181    let oxm_header = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
1182    let oxm_class = (oxm_header >> 16) as u16;
1183    let field = ((oxm_header >> 9) & 0x7f) as u8;
1184    let length = (oxm_header & 0xff) as usize;
1185
1186    if data.len() < 4 + length {
1187        return Err(crate::Error::Parse("set_field value truncated".into()));
1188    }
1189
1190    let value = &data[4..4 + length];
1191
1192    // OpenFlow Basic class
1193    if oxm_class == OxmClass::OpenflowBasic as u16 {
1194        match field {
1195            f if f == OxmField::EthSrc as u8 && length >= 6 => {
1196                let mut mac = [0u8; 6];
1197                mac.copy_from_slice(&value[..6]);
1198                Ok(Action::SetEthSrc(mac))
1199            }
1200            f if f == OxmField::EthDst as u8 && length >= 6 => {
1201                let mut mac = [0u8; 6];
1202                mac.copy_from_slice(&value[..6]);
1203                Ok(Action::SetEthDst(mac))
1204            }
1205            f if f == OxmField::VlanVid as u8 && length >= 2 => {
1206                let vid = u16::from_be_bytes([value[0], value[1]]);
1207                // Remove CFI bit
1208                Ok(Action::SetVlanVid(vid & 0x0fff))
1209            }
1210            f if f == OxmField::Ipv4Src as u8 && length >= 4 => {
1211                let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
1212                Ok(Action::SetIpv4Src(addr))
1213            }
1214            f if f == OxmField::Ipv4Dst as u8 && length >= 4 => {
1215                let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
1216                Ok(Action::SetIpv4Dst(addr))
1217            }
1218            f if f == OxmField::TcpSrc as u8 && length >= 2 => {
1219                let port = u16::from_be_bytes([value[0], value[1]]);
1220                Ok(Action::SetTpSrc(port))
1221            }
1222            f if f == OxmField::TcpDst as u8 && length >= 2 => {
1223                let port = u16::from_be_bytes([value[0], value[1]]);
1224                Ok(Action::SetTpDst(port))
1225            }
1226            f if f == OxmField::UdpSrc as u8 && length >= 2 => {
1227                let port = u16::from_be_bytes([value[0], value[1]]);
1228                Ok(Action::SetTpSrc(port))
1229            }
1230            f if f == OxmField::UdpDst as u8 && length >= 2 => {
1231                let port = u16::from_be_bytes([value[0], value[1]]);
1232                Ok(Action::SetTpDst(port))
1233            }
1234            _ => {
1235                // Unknown field, return Drop as placeholder
1236                Ok(Action::Drop)
1237            }
1238        }
1239    } else if oxm_class == OxmClass::Nxm1 as u16 {
1240        // NXM1 class (Nicira extensions)
1241        // Field 16 is tunnel ID
1242        if field == 16 && length >= 8 {
1243            let tun_id = u64::from_be_bytes([
1244                value[0], value[1], value[2], value[3],
1245                value[4], value[5], value[6], value[7],
1246            ]);
1247            Ok(Action::SetTunnelId(tun_id))
1248        } else {
1249            Ok(Action::Drop)
1250        }
1251    } else {
1252        // Unknown class
1253        Ok(Action::Drop)
1254    }
1255}