mod nicira;
pub mod nxm;
pub mod types;
#[cfg(test)]
mod tests;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::fmt;
use crate::match_fields::MacAddr;
use crate::oxm::{OxmClass, OxmField};
#[allow(unused_imports)]
pub use nicira::{learn_flags, LearnSpec, NxLearn};
pub use types::{ct_flags, nat_flags, nat_range, port, NICIRA_VENDOR_ID};
use types::ActionType;
use nicira::{
decode_nicira_action, encode_nx_ct, encode_nx_ct_nat, encode_nx_learn, encode_nx_move,
encode_nx_reg_load_nxm, encode_nx_resubmit, encode_set_tunnel_id,
};
pub const CT_COMMIT: u16 = ct_flags::COMMIT;
#[derive(Debug, Clone)]
pub struct NatConfig {
pub flags: u16,
pub ipv4_min: Option<Ipv4Addr>,
pub ipv4_max: Option<Ipv4Addr>,
pub ipv6_min: Option<Ipv6Addr>,
pub ipv6_max: Option<Ipv6Addr>,
pub port_min: Option<u16>,
pub port_max: Option<u16>,
}
impl NatConfig {
pub fn snat(addr: Ipv4Addr) -> Self {
Self {
flags: nat_flags::SRC,
ipv4_min: Some(addr),
ipv4_max: None,
ipv6_min: None,
ipv6_max: None,
port_min: None,
port_max: None,
}
}
pub fn snat_range(min: Ipv4Addr, max: Ipv4Addr) -> Self {
Self {
flags: nat_flags::SRC,
ipv4_min: Some(min),
ipv4_max: Some(max),
ipv6_min: None,
ipv6_max: None,
port_min: None,
port_max: None,
}
}
pub fn dnat(addr: Ipv4Addr) -> Self {
Self {
flags: nat_flags::DST,
ipv4_min: Some(addr),
ipv4_max: None,
ipv6_min: None,
ipv6_max: None,
port_min: None,
port_max: None,
}
}
pub fn dnat_range(min: Ipv4Addr, max: Ipv4Addr) -> Self {
Self {
flags: nat_flags::DST,
ipv4_min: Some(min),
ipv4_max: Some(max),
ipv6_min: None,
ipv6_max: None,
port_min: None,
port_max: None,
}
}
pub fn snat_v6(addr: Ipv6Addr) -> Self {
Self {
flags: nat_flags::SRC,
ipv4_min: None,
ipv4_max: None,
ipv6_min: Some(addr),
ipv6_max: None,
port_min: None,
port_max: None,
}
}
pub fn dnat_v6(addr: Ipv6Addr) -> Self {
Self {
flags: nat_flags::DST,
ipv4_min: None,
ipv4_max: None,
ipv6_min: Some(addr),
ipv6_max: None,
port_min: None,
port_max: None,
}
}
pub fn snat_v6_range(min: Ipv6Addr, max: Ipv6Addr) -> Self {
Self {
flags: nat_flags::SRC,
ipv4_min: None,
ipv4_max: None,
ipv6_min: Some(min),
ipv6_max: Some(max),
port_min: None,
port_max: None,
}
}
pub fn dnat_v6_range(min: Ipv6Addr, max: Ipv6Addr) -> Self {
Self {
flags: nat_flags::DST,
ipv4_min: None,
ipv4_max: None,
ipv6_min: Some(min),
ipv6_max: Some(max),
port_min: None,
port_max: None,
}
}
pub fn port(mut self, port: u16) -> Self {
self.port_min = Some(port);
self.port_max = None;
self
}
pub fn port_range(mut self, min: u16, max: u16) -> Self {
self.port_min = Some(min);
self.port_max = Some(max);
self
}
pub fn persistent(mut self) -> Self {
self.flags |= nat_flags::PERSISTENT;
self
}
pub fn hash(mut self) -> Self {
self.flags |= nat_flags::PROTO_HASH;
self.flags &= !nat_flags::PROTO_RANDOM;
self
}
pub fn random(mut self) -> Self {
self.flags |= nat_flags::PROTO_RANDOM;
self.flags &= !nat_flags::PROTO_HASH;
self
}
pub(crate) fn range_present(&self) -> u16 {
let mut flags = 0u16;
if self.ipv4_min.is_some() {
flags |= nat_range::IPV4_MIN;
}
if self.ipv4_max.is_some() {
flags |= nat_range::IPV4_MAX;
}
if self.ipv6_min.is_some() {
flags |= nat_range::IPV6_MIN;
}
if self.ipv6_max.is_some() {
flags |= nat_range::IPV6_MAX;
}
if self.port_min.is_some() {
flags |= nat_range::PROTO_MIN;
}
if self.port_max.is_some() {
flags |= nat_range::PROTO_MAX;
}
flags
}
}
#[derive(Debug, Clone)]
pub enum Action {
Output(OutputPort),
Drop,
Controller { max_len: u16 },
SetEthSrc(MacAddr),
SetEthDst(MacAddr),
SetVlanVid(u16),
PushVlan(u16),
PopVlan,
SetIpv4Src(Ipv4Addr),
SetIpv4Dst(Ipv4Addr),
SetTpSrc(u16),
SetTpDst(u16),
SetTtl(u8),
DecTtl,
GotoTable(u8),
WriteMetadata { metadata: u64, mask: u64 },
Meter(u32),
Group(u32),
SetTunnelId(u64),
NxResubmit {
port: Option<u16>,
table: Option<u8>,
},
NxLearn(NxLearn),
NxCt {
flags: u16,
zone: u16,
table: Option<u8>,
},
NxCtNat {
flags: u16,
zone: u16,
table: Option<u8>,
nat: NatConfig,
},
NxMove {
src_field: u32,
dst_field: u32,
n_bits: u16,
src_ofs: u16,
dst_ofs: u16,
},
NxRegLoad {
dst_field: u32,
dst_ofs: u16,
n_bits: u16,
value: u64,
},
}
#[derive(Debug, Clone, Copy)]
pub enum OutputPort {
Port(u32),
Controller,
Flood,
All,
InPort,
Local,
Normal,
None,
}
impl From<u32> for OutputPort {
fn from(port: u32) -> Self {
Self::Port(port)
}
}
#[derive(Debug, Clone, Default)]
pub struct ActionList {
actions: Vec<Action>,
}
impl ActionList {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, action: Action) {
self.actions.push(action);
}
pub fn output(mut self, port: impl Into<OutputPort>) -> Self {
self.actions.push(Action::Output(port.into()));
self
}
pub fn controller(mut self, max_len: u16) -> Self {
self.actions.push(Action::Controller { max_len });
self
}
pub fn drop(mut self) -> Self {
self.actions.push(Action::Drop);
self
}
pub fn set_eth_dst(mut self, mac: MacAddr) -> Self {
self.actions.push(Action::SetEthDst(mac));
self
}
pub fn set_eth_src(mut self, mac: MacAddr) -> Self {
self.actions.push(Action::SetEthSrc(mac));
self
}
pub fn push_vlan(mut self, tpid: u16) -> Self {
self.actions.push(Action::PushVlan(tpid));
self
}
pub fn pop_vlan(mut self) -> Self {
self.actions.push(Action::PopVlan);
self
}
pub fn set_vlan_vid(mut self, vid: u16) -> Self {
self.actions.push(Action::SetVlanVid(vid));
self
}
pub fn goto_table(mut self, table: u8) -> Self {
self.actions.push(Action::GotoTable(table));
self
}
pub fn dec_ttl(mut self) -> Self {
self.actions.push(Action::DecTtl);
self
}
pub fn flood(mut self) -> Self {
self.actions.push(Action::Output(OutputPort::Flood));
self
}
pub fn all(mut self) -> Self {
self.actions.push(Action::Output(OutputPort::All));
self
}
pub fn normal(mut self) -> Self {
self.actions.push(Action::Output(OutputPort::Normal));
self
}
pub fn in_port(mut self) -> Self {
self.actions.push(Action::Output(OutputPort::InPort));
self
}
pub fn set_tunnel_id(mut self, tunnel_id: u64) -> Self {
self.actions.push(Action::SetTunnelId(tunnel_id));
self
}
pub fn group(mut self, group_id: u32) -> Self {
self.actions.push(Action::Group(group_id));
self
}
pub fn resubmit(mut self, port: Option<u16>, table: Option<u8>) -> Self {
self.actions.push(Action::NxResubmit { port, table });
self
}
pub fn resubmit_table(mut self, table: u8) -> Self {
self.actions.push(Action::NxResubmit { port: None, table: Some(table) });
self
}
pub fn ct(mut self, flags: u16, zone: u16, table: Option<u8>) -> Self {
self.actions.push(Action::NxCt { flags, zone, table });
self
}
pub fn ct_commit(mut self, zone: u16) -> Self {
self.actions.push(Action::NxCt { flags: CT_COMMIT, zone, table: None });
self
}
pub fn ct_nat(mut self, flags: u16, zone: u16, table: Option<u8>, nat: NatConfig) -> Self {
self.actions.push(Action::NxCtNat { flags, zone, table, nat });
self
}
pub fn ct_snat(mut self, zone: u16, table: Option<u8>, addr: Ipv4Addr) -> Self {
self.actions.push(Action::NxCtNat {
flags: CT_COMMIT,
zone,
table,
nat: NatConfig::snat(addr),
});
self
}
pub fn ct_dnat(mut self, zone: u16, table: Option<u8>, addr: Ipv4Addr) -> Self {
self.actions.push(Action::NxCtNat {
flags: CT_COMMIT,
zone,
table,
nat: NatConfig::dnat(addr),
});
self
}
pub fn learn(mut self, learn: NxLearn) -> Self {
self.actions.push(Action::NxLearn(learn));
self
}
pub fn move_field(
mut self,
src_field: u32,
dst_field: u32,
n_bits: u16,
src_ofs: u16,
dst_ofs: u16,
) -> Self {
self.actions.push(Action::NxMove {
src_field,
dst_field,
n_bits,
src_ofs,
dst_ofs,
});
self
}
pub fn load_field(mut self, dst_field: u32, dst_ofs: u16, n_bits: u16, value: u64) -> Self {
self.actions.push(Action::NxRegLoad {
dst_field,
dst_ofs,
n_bits,
value,
});
self
}
pub fn set_arp_op(self, opcode: u16) -> Self {
self.load_field(nxm::ARP_OP, 0, 16, opcode as u64)
}
pub fn set_arp_spa(self, ip: u32) -> Self {
self.load_field(nxm::ARP_SPA, 0, 32, ip as u64)
}
pub fn set_arp_tpa(self, ip: u32) -> Self {
self.load_field(nxm::ARP_TPA, 0, 32, ip as u64)
}
pub fn set_arp_sha(self, mac: u64) -> Self {
self.load_field(nxm::ARP_SHA, 0, 48, mac)
}
pub fn set_arp_tha(self, mac: u64) -> Self {
self.load_field(nxm::ARP_THA, 0, 48, mac)
}
pub fn actions(&self) -> &[Action] {
&self.actions
}
pub fn is_empty(&self) -> bool {
self.actions.is_empty()
}
pub fn len(&self) -> usize {
self.actions.len()
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
for action in &self.actions {
buf.extend(action.encode());
}
let padding = (8 - (buf.len() % 8)) % 8;
buf.extend(std::iter::repeat_n(0u8, padding));
buf
}
pub fn decode(data: &[u8]) -> crate::Result<Self> {
let mut actions = Vec::new();
let mut offset = 0;
while offset < data.len() {
if data.len() - offset < 4 {
break;
}
let length = u16::from_be_bytes([data[offset + 2], data[offset + 3]]) as usize;
if length == 0 {
break;
}
let (action, consumed) = Action::decode(&data[offset..])?;
if !matches!(action, Action::Drop) {
actions.push(action);
}
offset += consumed;
}
Ok(Self { actions })
}
}
impl OutputPort {
pub const fn to_wire_port(self) -> u32 {
match self {
Self::Port(p) => p,
Self::Controller => port::CONTROLLER,
Self::Flood => port::FLOOD,
Self::All => port::ALL,
Self::InPort => port::IN_PORT,
Self::Local => port::LOCAL,
Self::Normal => port::NORMAL,
Self::None => port::NONE,
}
}
pub const fn from_wire(port_num: u32) -> Self {
match port_num {
port::CONTROLLER => Self::Controller,
port::FLOOD => Self::Flood,
port::ALL => Self::All,
port::IN_PORT => Self::InPort,
port::LOCAL => Self::Local,
port::NORMAL => Self::Normal,
port::NONE => Self::None,
p => Self::Port(p),
}
}
}
fn format_mac(mac: MacAddr) -> String {
format!(
"{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
)
}
impl fmt::Display for OutputPort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Port(p) => write!(f, "{p}"),
Self::Controller => write!(f, "CONTROLLER"),
Self::Flood => write!(f, "FLOOD"),
Self::All => write!(f, "ALL"),
Self::InPort => write!(f, "IN_PORT"),
Self::Local => write!(f, "LOCAL"),
Self::Normal => write!(f, "NORMAL"),
Self::None => write!(f, "NONE"),
}
}
}
impl fmt::Display for NatConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "nat(")?;
if self.flags & nat_flags::SRC != 0 {
write!(f, "src")?;
} else if self.flags & nat_flags::DST != 0 {
write!(f, "dst")?;
}
if let Some(ip) = self.ipv4_min {
write!(f, "={ip}")?;
if let Some(max) = self.ipv4_max {
write!(f, "-{max}")?;
}
} else if let Some(ip) = self.ipv6_min {
write!(f, "={ip}")?;
if let Some(max) = self.ipv6_max {
write!(f, "-{max}")?;
}
}
if let Some(port) = self.port_min {
write!(f, ":{port}")?;
if let Some(max) = self.port_max {
write!(f, "-{max}")?;
}
}
write!(f, ")")
}
}
impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Output(OutputPort::Normal) => write!(f, "NORMAL"),
Self::Output(OutputPort::Flood) => write!(f, "FLOOD"),
Self::Output(OutputPort::All) => write!(f, "ALL"),
Self::Output(OutputPort::InPort) => write!(f, "IN_PORT"),
Self::Output(OutputPort::Local) => write!(f, "LOCAL"),
Self::Output(port) => write!(f, "output:{port}"),
Self::Drop => write!(f, "drop"),
Self::Controller { max_len } => write!(f, "CONTROLLER:{max_len}"),
Self::SetEthSrc(mac) => write!(f, "set_eth_src:{}", format_mac(*mac)),
Self::SetEthDst(mac) => write!(f, "set_eth_dst:{}", format_mac(*mac)),
Self::SetVlanVid(vid) => write!(f, "set_vlan_vid:{vid}"),
Self::PushVlan(ethertype) => write!(f, "push_vlan:0x{ethertype:04x}"),
Self::PopVlan => write!(f, "pop_vlan"),
Self::SetIpv4Src(ip) => write!(f, "set_ipv4_src:{ip}"),
Self::SetIpv4Dst(ip) => write!(f, "set_ipv4_dst:{ip}"),
Self::SetTpSrc(port) => write!(f, "set_tp_src:{port}"),
Self::SetTpDst(port) => write!(f, "set_tp_dst:{port}"),
Self::SetTtl(ttl) => write!(f, "set_ttl:{ttl}"),
Self::DecTtl => write!(f, "dec_ttl"),
Self::GotoTable(table) => write!(f, "goto_table:{table}"),
Self::WriteMetadata { metadata, mask } => {
write!(f, "write_metadata:0x{metadata:x}/0x{mask:x}")
}
Self::Meter(id) => write!(f, "meter:{id}"),
Self::Group(id) => write!(f, "group:{id}"),
Self::SetTunnelId(id) => write!(f, "set_tunnel:{id:#x}"),
Self::NxResubmit { port, table } => {
let port_str = port.map_or(String::new(), |p| p.to_string());
let table_str = table.map_or(String::new(), |t| t.to_string());
write!(f, "resubmit({port_str},{table_str})")
}
Self::NxLearn(learn) => {
write!(f, "learn(table={}", learn.table_id)?;
if learn.idle_timeout > 0 {
write!(f, ",idle_timeout={}", learn.idle_timeout)?;
}
if learn.hard_timeout > 0 {
write!(f, ",hard_timeout={}", learn.hard_timeout)?;
}
if learn.priority > 0 {
write!(f, ",priority={}", learn.priority)?;
}
write!(f, ")")
}
Self::NxCt { flags, zone, table } => {
write!(f, "ct(")?;
let mut parts = Vec::new();
if *flags & types::ct_flags::COMMIT != 0 {
parts.push("commit".to_string());
}
if *zone != 0 {
parts.push(format!("zone={zone}"));
}
if let Some(t) = table {
parts.push(format!("table={t}"));
}
write!(f, "{})", parts.join(","))
}
Self::NxCtNat { flags, zone, table, nat } => {
write!(f, "ct(")?;
let mut parts = Vec::new();
if *flags & types::ct_flags::COMMIT != 0 {
parts.push("commit".to_string());
}
if *zone != 0 {
parts.push(format!("zone={zone}"));
}
if let Some(t) = table {
parts.push(format!("table={t}"));
}
parts.push(nat.to_string());
write!(f, "{})", parts.join(","))
}
Self::NxMove { src_field, dst_field, n_bits, src_ofs, dst_ofs } => {
write!(f, "move:NXM({src_field:#x})[{src_ofs}..{n_bits}]->NXM({dst_field:#x})[{dst_ofs}..{n_bits}]")
}
Self::NxRegLoad { dst_field, dst_ofs, n_bits, value } => {
write!(f, "load:{value:#x}->NXM({dst_field:#x})[{dst_ofs}..{}]", dst_ofs + n_bits)
}
}
}
}
impl fmt::Display for ActionList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.actions.is_empty() {
return write!(f, "drop");
}
for (i, action) in self.actions.iter().enumerate() {
if i > 0 {
write!(f, ",")?;
}
write!(f, "{action}")?;
}
Ok(())
}
}
impl Action {
#[allow(clippy::match_same_arms)]
pub fn encode(&self) -> Vec<u8> {
match self {
Self::Output(port) => encode_output(port.to_wire_port(), 0xffff),
Self::Drop => Vec::new(), Self::Controller { max_len } => encode_output(port::CONTROLLER, *max_len),
Self::SetEthSrc(mac) => encode_set_field_mac(OxmField::EthSrc, *mac),
Self::SetEthDst(mac) => encode_set_field_mac(OxmField::EthDst, *mac),
Self::SetVlanVid(vid) => encode_set_field_u16(OxmField::VlanVid, *vid | 0x1000),
Self::PushVlan(ethertype) => encode_push_vlan(*ethertype),
Self::PopVlan => encode_pop_vlan(),
Self::SetIpv4Src(addr) => encode_set_field_u32(OxmField::Ipv4Src, (*addr).into()),
Self::SetIpv4Dst(addr) => encode_set_field_u32(OxmField::Ipv4Dst, (*addr).into()),
Self::SetTpSrc(port) => encode_set_field_u16(OxmField::TcpSrc, *port),
Self::SetTpDst(port) => encode_set_field_u16(OxmField::TcpDst, *port),
Self::SetTtl(ttl) => encode_set_nw_ttl(*ttl),
Self::DecTtl => encode_dec_ttl(),
Self::GotoTable(_) => Vec::new(), Self::WriteMetadata { .. } => Vec::new(), Self::Meter(_) => Vec::new(), Self::Group(group_id) => encode_group(*group_id),
Self::SetTunnelId(tun_id) => encode_set_tunnel_id(*tun_id),
Self::NxResubmit { port, table } => encode_nx_resubmit(*port, *table),
Self::NxLearn(learn) => encode_nx_learn(learn),
Self::NxCt { flags, zone, table } => encode_nx_ct(*flags, *zone, *table),
Self::NxCtNat { flags, zone, table, nat } => {
encode_nx_ct_nat(*flags, *zone, *table, nat)
}
Self::NxMove { src_field, dst_field, n_bits, src_ofs, dst_ofs } => {
encode_nx_move(*src_field, *dst_field, *n_bits, *src_ofs, *dst_ofs)
}
Self::NxRegLoad { dst_field, dst_ofs, n_bits, value } => {
encode_nx_reg_load_nxm(*dst_field, *dst_ofs, *n_bits, *value)
}
}
}
#[allow(clippy::too_many_lines)]
pub fn decode(data: &[u8]) -> crate::Result<(Self, usize)> {
if data.len() < 4 {
return Err(crate::Error::Parse("action too short".into()));
}
let action_type = u16::from_be_bytes([data[0], data[1]]);
let length = u16::from_be_bytes([data[2], data[3]]) as usize;
if data.len() < length {
return Err(crate::Error::Parse("action truncated".into()));
}
let action_type = ActionType::try_from(action_type)?;
let action = match action_type {
ActionType::Output => {
if length < 16 {
return Err(crate::Error::Parse("output action too short".into()));
}
let port_num = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let max_len = u16::from_be_bytes([data[8], data[9]]);
let output_port = OutputPort::from_wire(port_num);
if port_num == port::CONTROLLER {
Self::Controller { max_len }
} else {
Self::Output(output_port)
}
}
ActionType::PopVlan => Self::PopVlan,
ActionType::PushVlan => {
if length < 8 {
return Err(crate::Error::Parse("push_vlan action too short".into()));
}
let ethertype = u16::from_be_bytes([data[4], data[5]]);
Self::PushVlan(ethertype)
}
ActionType::DecNwTtl => Self::DecTtl,
ActionType::SetNwTtl => {
if length < 8 {
return Err(crate::Error::Parse("set_nw_ttl action too short".into()));
}
Self::SetTtl(data[4])
}
ActionType::Group => {
if length < 8 {
return Err(crate::Error::Parse("group action too short".into()));
}
let group_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
Self::Group(group_id)
}
ActionType::SetField => {
decode_set_field_action(&data[4..length])?
}
ActionType::Experimenter => {
if length < 10 {
return Err(crate::Error::Parse("experimenter action too short".into()));
}
let vendor = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
if vendor == NICIRA_VENDOR_ID {
decode_nicira_action(&data[8..length])?
} else {
return Err(crate::Error::Parse(format!(
"unknown experimenter vendor: {vendor:#x}"
)));
}
}
ActionType::CopyTtlOut
| ActionType::CopyTtlIn
| ActionType::SetMplsTtl
| ActionType::DecMplsTtl
| ActionType::PushMpls
| ActionType::PopMpls
| ActionType::SetQueue
| ActionType::PushPbb
| ActionType::PopPbb => {
Self::Drop
}
};
Ok((action, length))
}
}
fn encode_output(port_num: u32, max_len: u16) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend((ActionType::Output as u16).to_be_bytes());
buf.extend(16u16.to_be_bytes()); buf.extend(port_num.to_be_bytes());
buf.extend(max_len.to_be_bytes());
buf.extend([0u8; 6]); buf
}
fn encode_pop_vlan() -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.extend((ActionType::PopVlan as u16).to_be_bytes());
buf.extend(8u16.to_be_bytes()); buf.extend([0u8; 4]); buf
}
fn encode_push_vlan(ethertype: u16) -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.extend((ActionType::PushVlan as u16).to_be_bytes());
buf.extend(8u16.to_be_bytes()); buf.extend(ethertype.to_be_bytes());
buf.extend([0u8; 2]); buf
}
fn encode_dec_ttl() -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.extend((ActionType::DecNwTtl as u16).to_be_bytes());
buf.extend(8u16.to_be_bytes()); buf.extend([0u8; 4]); buf
}
fn encode_set_nw_ttl(ttl: u8) -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.extend((ActionType::SetNwTtl as u16).to_be_bytes());
buf.extend(8u16.to_be_bytes()); buf.push(ttl);
buf.extend([0u8; 3]); buf
}
fn encode_group(group_id: u32) -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.extend((ActionType::Group as u16).to_be_bytes());
buf.extend(8u16.to_be_bytes()); buf.extend(group_id.to_be_bytes());
buf
}
fn encode_set_field_mac(field: OxmField, mac: [u8; 6]) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend((ActionType::SetField as u16).to_be_bytes());
buf.extend(16u16.to_be_bytes());
let oxm_header =
((OxmClass::OpenflowBasic as u32) << 16) | ((field as u32) << 9) | 6;
buf.extend(oxm_header.to_be_bytes());
buf.extend(mac);
buf.extend([0u8; 2]); buf
}
fn encode_set_field_u16(field: OxmField, value: u16) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend((ActionType::SetField as u16).to_be_bytes());
buf.extend(16u16.to_be_bytes());
let oxm_header =
((OxmClass::OpenflowBasic as u32) << 16) | ((field as u32) << 9) | 2;
buf.extend(oxm_header.to_be_bytes());
buf.extend(value.to_be_bytes());
buf.extend([0u8; 6]); buf
}
fn encode_set_field_u32(field: OxmField, value: u32) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend((ActionType::SetField as u16).to_be_bytes());
buf.extend(16u16.to_be_bytes());
let oxm_header =
((OxmClass::OpenflowBasic as u32) << 16) | ((field as u32) << 9) | 4;
buf.extend(oxm_header.to_be_bytes());
buf.extend(value.to_be_bytes());
buf.extend([0u8; 4]); buf
}
fn decode_set_field_action(data: &[u8]) -> crate::Result<Action> {
if data.len() < 4 {
return Err(crate::Error::Parse("set_field action too short".into()));
}
let oxm_header = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let oxm_class = (oxm_header >> 16) as u16;
let field = ((oxm_header >> 9) & 0x7f) as u8;
let length = (oxm_header & 0xff) as usize;
if data.len() < 4 + length {
return Err(crate::Error::Parse("set_field value truncated".into()));
}
let value = &data[4..4 + length];
if oxm_class == OxmClass::OpenflowBasic as u16 {
match field {
f if f == OxmField::EthSrc as u8 && length >= 6 => {
let mut mac = [0u8; 6];
mac.copy_from_slice(&value[..6]);
Ok(Action::SetEthSrc(mac))
}
f if f == OxmField::EthDst as u8 && length >= 6 => {
let mut mac = [0u8; 6];
mac.copy_from_slice(&value[..6]);
Ok(Action::SetEthDst(mac))
}
f if f == OxmField::VlanVid as u8 && length >= 2 => {
let vid = u16::from_be_bytes([value[0], value[1]]);
Ok(Action::SetVlanVid(vid & 0x0fff))
}
f if f == OxmField::Ipv4Src as u8 && length >= 4 => {
let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
Ok(Action::SetIpv4Src(addr))
}
f if f == OxmField::Ipv4Dst as u8 && length >= 4 => {
let addr = Ipv4Addr::new(value[0], value[1], value[2], value[3]);
Ok(Action::SetIpv4Dst(addr))
}
f if f == OxmField::TcpSrc as u8 && length >= 2 => {
let port = u16::from_be_bytes([value[0], value[1]]);
Ok(Action::SetTpSrc(port))
}
f if f == OxmField::TcpDst as u8 && length >= 2 => {
let port = u16::from_be_bytes([value[0], value[1]]);
Ok(Action::SetTpDst(port))
}
f if f == OxmField::UdpSrc as u8 && length >= 2 => {
let port = u16::from_be_bytes([value[0], value[1]]);
Ok(Action::SetTpSrc(port))
}
f if f == OxmField::UdpDst as u8 && length >= 2 => {
let port = u16::from_be_bytes([value[0], value[1]]);
Ok(Action::SetTpDst(port))
}
_ => {
Ok(Action::Drop)
}
}
} else if oxm_class == OxmClass::Nxm1 as u16 {
if field == 16 && length >= 8 {
let tun_id = u64::from_be_bytes([
value[0], value[1], value[2], value[3],
value[4], value[5], value[6], value[7],
]);
Ok(Action::SetTunnelId(tun_id))
} else {
Ok(Action::Drop)
}
} else {
Ok(Action::Drop)
}
}