use super::types::{NxActionSubtype, NICIRA_VENDOR_ID, ActionType};
#[allow(dead_code)]
pub mod learn_flags {
pub const SEND_FLOW_REM: u16 = 1 << 0;
pub const DELETE_LEARNED: u16 = 1 << 1;
pub const WRITE_RESULT: u16 = 1 << 2;
}
#[derive(Debug, Clone, Default)]
pub struct NxLearn {
pub idle_timeout: u16,
pub hard_timeout: u16,
pub priority: u16,
pub cookie: u64,
pub flags: u16,
pub table_id: u8,
pub fin_idle_timeout: u16,
pub fin_hard_timeout: u16,
pub specs: Vec<LearnSpec>,
}
#[derive(Debug, Clone)]
pub enum LearnSpec {
MatchField {
src_field: u32,
dst_field: u32,
n_bits: u16,
},
MatchImmediate {
dst_field: u32,
value: Vec<u8>,
n_bits: u16,
},
LoadField {
src_field: u32,
dst_field: u32,
n_bits: u16,
},
LoadImmediate {
dst_field: u32,
value: Vec<u8>,
n_bits: u16,
},
OutputField {
src_field: u32,
n_bits: u16,
},
}
impl NxLearn {
pub fn new() -> Self {
Self::default()
}
pub fn idle_timeout(mut self, timeout: u16) -> Self {
self.idle_timeout = timeout;
self
}
pub fn hard_timeout(mut self, timeout: u16) -> Self {
self.hard_timeout = timeout;
self
}
pub fn priority(mut self, priority: u16) -> Self {
self.priority = priority;
self
}
pub fn cookie(mut self, cookie: u64) -> Self {
self.cookie = cookie;
self
}
pub fn table(mut self, table_id: u8) -> Self {
self.table_id = table_id;
self
}
pub fn flags(mut self, flags: u16) -> Self {
self.flags = flags;
self
}
pub fn match_field(mut self, src_field: u32, dst_field: u32, n_bits: u16) -> Self {
self.specs.push(LearnSpec::MatchField { src_field, dst_field, n_bits });
self
}
pub fn match_immediate(mut self, dst_field: u32, value: Vec<u8>, n_bits: u16) -> Self {
self.specs.push(LearnSpec::MatchImmediate { dst_field, value, n_bits });
self
}
pub fn load_field(mut self, src_field: u32, dst_field: u32, n_bits: u16) -> Self {
self.specs.push(LearnSpec::LoadField { src_field, dst_field, n_bits });
self
}
pub fn load_immediate(mut self, dst_field: u32, value: Vec<u8>, n_bits: u16) -> Self {
self.specs.push(LearnSpec::LoadImmediate { dst_field, value, n_bits });
self
}
pub fn output_field(mut self, src_field: u32, n_bits: u16) -> Self {
self.specs.push(LearnSpec::OutputField { src_field, n_bits });
self
}
}
pub(crate) fn encode_nx_header(subtype: NxActionSubtype, len: u16) -> Vec<u8> {
let mut buf = Vec::with_capacity(16);
buf.extend((ActionType::Experimenter as u16).to_be_bytes());
buf.extend(len.to_be_bytes());
buf.extend(NICIRA_VENDOR_ID.to_be_bytes());
buf.extend((subtype as u16).to_be_bytes());
buf
}
pub(crate) fn encode_set_tunnel_id(tun_id: u64) -> Vec<u8> {
let mut buf = encode_nx_header(NxActionSubtype::RegLoad2, 24);
let oxm_header: u32 = (1 << 16) | (16 << 9) | 8;
buf.extend(oxm_header.to_be_bytes());
buf.extend(tun_id.to_be_bytes());
buf.extend([0u8; 2]); buf
}
pub(crate) fn encode_nx_resubmit(in_port: Option<u16>, table: Option<u8>) -> Vec<u8> {
let mut buf = encode_nx_header(NxActionSubtype::ResubmitTable, 16);
buf.extend(in_port.unwrap_or(0xfff8).to_be_bytes()); buf.push(table.unwrap_or(255)); buf.extend([0u8; 3]); buf
}
pub(crate) fn encode_nx_ct(flags: u16, zone: u16, table: Option<u8>) -> Vec<u8> {
let mut buf = encode_nx_header(NxActionSubtype::Ct, 24);
buf.extend(flags.to_be_bytes());
buf.extend(0u32.to_be_bytes()); buf.extend(zone.to_be_bytes()); buf.push(table.unwrap_or(255)); buf.extend([0u8; 3]); buf.extend(0u16.to_be_bytes()); buf
}
pub(crate) fn encode_nx_ct_nat(
flags: u16,
zone: u16,
table: Option<u8>,
nat: &super::NatConfig,
) -> Vec<u8> {
let nat_action = encode_nx_nat(nat);
let ct_header_len = 24; let total_len = ct_header_len + nat_action.len();
let padded_len = (total_len + 7) & !7;
let mut buf = Vec::with_capacity(padded_len);
buf.extend((ActionType::Experimenter as u16).to_be_bytes());
buf.extend((padded_len as u16).to_be_bytes());
buf.extend(NICIRA_VENDOR_ID.to_be_bytes());
buf.extend((NxActionSubtype::Ct as u16).to_be_bytes());
buf.extend(flags.to_be_bytes());
buf.extend(0u32.to_be_bytes()); buf.extend(zone.to_be_bytes()); buf.push(table.unwrap_or(255)); buf.extend([0u8; 3]); buf.extend(0u16.to_be_bytes());
buf.extend(nat_action);
buf.resize(padded_len, 0);
buf
}
fn encode_nx_nat(nat: &super::NatConfig) -> Vec<u8> {
let range_present = nat.range_present();
let mut optional_len = 0;
if nat.ipv4_min.is_some() {
optional_len += 4;
}
if nat.ipv4_max.is_some() {
optional_len += 4;
}
if nat.ipv6_min.is_some() {
optional_len += 16;
}
if nat.ipv6_max.is_some() {
optional_len += 16;
}
if nat.port_min.is_some() {
optional_len += 2;
}
if nat.port_max.is_some() {
optional_len += 2;
}
let header_len = 16;
let total_len = header_len + optional_len;
let padded_len = (total_len + 7) & !7;
let mut buf = encode_nx_header(NxActionSubtype::Nat, padded_len as u16);
buf.extend([0u8; 2]); buf.extend(nat.flags.to_be_bytes());
buf.extend(range_present.to_be_bytes());
if let Some(addr) = nat.ipv4_min {
buf.extend(addr.octets());
}
if let Some(addr) = nat.ipv4_max {
buf.extend(addr.octets());
}
if let Some(addr) = nat.ipv6_min {
buf.extend(addr.octets());
}
if let Some(addr) = nat.ipv6_max {
buf.extend(addr.octets());
}
if let Some(port) = nat.port_min {
buf.extend(port.to_be_bytes());
}
if let Some(port) = nat.port_max {
buf.extend(port.to_be_bytes());
}
buf.resize(padded_len, 0);
buf
}
#[allow(dead_code)]
pub fn encode_nx_reg_load(reg_num: u8, value: u32, start_bit: u8, n_bits: u8) -> Vec<u8> {
let mut buf = encode_nx_header(NxActionSubtype::RegLoad, 24);
let ofs_nbits = ((start_bit as u16) << 6) | ((n_bits - 1) as u16);
buf.extend(ofs_nbits.to_be_bytes());
let dst_header: u32 = (1 << 16) | ((reg_num as u32) << 9) | 4;
buf.extend(dst_header.to_be_bytes());
buf.extend((value as u64).to_be_bytes());
buf
}
pub(crate) fn encode_nx_reg_load_nxm(dst_field: u32, dst_ofs: u16, n_bits: u16, value: u64) -> Vec<u8> {
let mut buf = encode_nx_header(NxActionSubtype::RegLoad, 24);
let ofs_nbits = (dst_ofs << 6) | (n_bits - 1);
buf.extend(ofs_nbits.to_be_bytes());
buf.extend(dst_field.to_be_bytes());
buf.extend(value.to_be_bytes());
buf
}
pub(crate) fn encode_nx_move(
src_field: u32,
dst_field: u32,
n_bits: u16,
src_ofs: u16,
dst_ofs: u16,
) -> Vec<u8> {
let mut buf = encode_nx_header(NxActionSubtype::Move, 24);
buf.extend(n_bits.to_be_bytes());
buf.extend(src_ofs.to_be_bytes());
buf.extend(dst_ofs.to_be_bytes());
buf.extend(src_field.to_be_bytes());
buf.extend(dst_field.to_be_bytes());
buf
}
pub(crate) fn encode_nx_learn(learn: &NxLearn) -> Vec<u8> {
let specs_bytes = encode_learn_specs(&learn.specs);
let header_and_fields = 32; let total_len = header_and_fields + specs_bytes.len();
let padded_len = (total_len + 7) & !7;
let mut buf = Vec::with_capacity(padded_len);
buf.extend((ActionType::Experimenter as u16).to_be_bytes());
buf.extend((padded_len as u16).to_be_bytes());
buf.extend(NICIRA_VENDOR_ID.to_be_bytes());
buf.extend((NxActionSubtype::Learn as u16).to_be_bytes());
buf.extend(learn.idle_timeout.to_be_bytes());
buf.extend(learn.hard_timeout.to_be_bytes());
buf.extend(learn.priority.to_be_bytes());
buf.extend(learn.cookie.to_be_bytes());
buf.extend(learn.flags.to_be_bytes());
buf.push(learn.table_id);
buf.push(0); buf.extend(learn.fin_idle_timeout.to_be_bytes());
buf.extend(learn.fin_hard_timeout.to_be_bytes());
buf.extend(specs_bytes);
buf.resize(padded_len, 0);
buf
}
mod learn_spec_header {
pub const SRC_FIELD: u16 = 0 << 13;
pub const SRC_IMMEDIATE: u16 = 1 << 13;
pub const DST_MATCH: u16 = 0 << 11;
pub const DST_LOAD: u16 = 1 << 11;
pub const DST_OUTPUT: u16 = 2 << 11;
}
fn encode_learn_subfield(buf: &mut Vec<u8>, nxm_header: u32, ofs: u16) {
buf.extend(nxm_header.to_be_bytes());
buf.extend(ofs.to_be_bytes());
}
fn encode_learn_specs(specs: &[LearnSpec]) -> Vec<u8> {
let mut buf = Vec::new();
for spec in specs {
match spec {
LearnSpec::MatchField { src_field, dst_field, n_bits } => {
let header = learn_spec_header::SRC_FIELD
| learn_spec_header::DST_MATCH
| (n_bits - 1);
buf.extend(header.to_be_bytes());
encode_learn_subfield(&mut buf, *src_field, 0);
encode_learn_subfield(&mut buf, *dst_field, 0);
}
LearnSpec::MatchImmediate { dst_field, value, n_bits } => {
let header = learn_spec_header::SRC_IMMEDIATE
| learn_spec_header::DST_MATCH
| (n_bits - 1);
buf.extend(header.to_be_bytes());
let value_len = (*n_bits as usize).div_ceil(16) * 2;
let mut padded_value = vec![0u8; value_len];
let copy_len = value.len().min(value_len);
padded_value[value_len - copy_len..].copy_from_slice(&value[..copy_len]);
buf.extend(padded_value);
encode_learn_subfield(&mut buf, *dst_field, 0);
}
LearnSpec::LoadField { src_field, dst_field, n_bits } => {
let header = learn_spec_header::SRC_FIELD
| learn_spec_header::DST_LOAD
| (n_bits - 1);
buf.extend(header.to_be_bytes());
encode_learn_subfield(&mut buf, *src_field, 0);
encode_learn_subfield(&mut buf, *dst_field, 0);
}
LearnSpec::LoadImmediate { dst_field, value, n_bits } => {
let header = learn_spec_header::SRC_IMMEDIATE
| learn_spec_header::DST_LOAD
| (n_bits - 1);
buf.extend(header.to_be_bytes());
let value_len = (*n_bits as usize).div_ceil(16) * 2;
let mut padded_value = vec![0u8; value_len];
let copy_len = value.len().min(value_len);
padded_value[value_len - copy_len..].copy_from_slice(&value[..copy_len]);
buf.extend(padded_value);
encode_learn_subfield(&mut buf, *dst_field, 0);
}
LearnSpec::OutputField { src_field, n_bits } => {
let header = learn_spec_header::SRC_FIELD
| learn_spec_header::DST_OUTPUT
| (n_bits - 1);
buf.extend(header.to_be_bytes());
encode_learn_subfield(&mut buf, *src_field, 0);
}
}
}
buf
}
use super::Action;
use crate::oxm::OxmClass;
pub(crate) fn decode_nicira_action(data: &[u8]) -> crate::Result<Action> {
if data.len() < 2 {
return Err(crate::Error::Parse("nicira action too short".into()));
}
let subtype = u16::from_be_bytes([data[0], data[1]]);
match subtype {
s if s == NxActionSubtype::ResubmitTable as u16 => {
if data.len() < 6 {
return Err(crate::Error::Parse("resubmit action too short".into()));
}
let in_port = u16::from_be_bytes([data[2], data[3]]);
let table = data[4];
let port = if in_port == 0xfff8 { None } else { Some(in_port) };
let table = if table == 255 { None } else { Some(table) };
Ok(Action::NxResubmit { port, table })
}
s if s == NxActionSubtype::Resubmit as u16 => {
if data.len() < 4 {
return Err(crate::Error::Parse("resubmit action too short".into()));
}
let in_port = u16::from_be_bytes([data[2], data[3]]);
let port = if in_port == 0xfff8 { None } else { Some(in_port) };
Ok(Action::NxResubmit { port, table: None })
}
s if s == NxActionSubtype::Ct as u16 => {
if data.len() < 10 {
return Err(crate::Error::Parse("ct action too short".into()));
}
let flags = u16::from_be_bytes([data[2], data[3]]);
let zone = u16::from_be_bytes([data[8], data[9]]);
let recirc_table = if data.len() > 10 { data[10] } else { 255 };
let table = if recirc_table == 255 { None } else { Some(recirc_table) };
Ok(Action::NxCt { flags, zone, table })
}
s if s == NxActionSubtype::RegLoad2 as u16 => {
if data.len() < 6 {
return Err(crate::Error::Parse("reg_load2 action too short".into()));
}
let oxm_header = u32::from_be_bytes([data[2], data[3], data[4], data[5]]);
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() < 6 + length {
return Err(crate::Error::Parse("reg_load2 value truncated".into()));
}
let value = &data[6..6 + length];
if oxm_class == OxmClass::Nxm1 as u16 && 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)
}
}
s if s == NxActionSubtype::Learn as u16 => {
if data.len() < 22 {
return Err(crate::Error::Parse("learn action too short".into()));
}
let idle_timeout = u16::from_be_bytes([data[2], data[3]]);
let hard_timeout = u16::from_be_bytes([data[4], data[5]]);
let priority = u16::from_be_bytes([data[6], data[7]]);
let cookie = u64::from_be_bytes([
data[8], data[9], data[10], data[11],
data[12], data[13], data[14], data[15],
]);
let flags = u16::from_be_bytes([data[16], data[17]]);
let table_id = data[18];
let fin_idle_timeout = u16::from_be_bytes([data[20], data[21]]);
let fin_hard_timeout = if data.len() > 23 {
u16::from_be_bytes([data[22], data[23]])
} else {
0
};
let specs = if data.len() > 24 {
decode_learn_specs(&data[24..])
} else {
Vec::new()
};
Ok(Action::NxLearn(NxLearn {
idle_timeout,
hard_timeout,
priority,
cookie,
flags,
table_id,
fin_idle_timeout,
fin_hard_timeout,
specs,
}))
}
_ => {
Ok(Action::Drop)
}
}
}
pub(crate) fn decode_learn_specs(data: &[u8]) -> Vec<LearnSpec> {
let mut specs = Vec::new();
let mut offset = 0;
while offset + 2 <= data.len() {
let header = u16::from_be_bytes([data[offset], data[offset + 1]]);
if header == 0 {
break; }
offset += 2;
let n_bits = (header & 0x07ff) + 1; let src_type = (header >> 13) & 0x01; let dst_type = (header >> 11) & 0x03;
match (src_type, dst_type) {
(0, 0) => {
if offset + 12 > data.len() {
break;
}
let src_field = u32::from_be_bytes([
data[offset], data[offset + 1], data[offset + 2], data[offset + 3],
]);
let dst_field = u32::from_be_bytes([
data[offset + 6], data[offset + 7], data[offset + 8], data[offset + 9],
]);
offset += 12;
specs.push(LearnSpec::MatchField { src_field, dst_field, n_bits });
}
(1, 0) => {
let value_len = (n_bits as usize).div_ceil(16) * 2;
if offset + value_len + 6 > data.len() {
break;
}
let value = data[offset..offset + value_len].to_vec();
offset += value_len;
let dst_field = u32::from_be_bytes([
data[offset], data[offset + 1], data[offset + 2], data[offset + 3],
]);
offset += 6; specs.push(LearnSpec::MatchImmediate { dst_field, value, n_bits });
}
(0, 1) => {
if offset + 12 > data.len() {
break;
}
let src_field = u32::from_be_bytes([
data[offset], data[offset + 1], data[offset + 2], data[offset + 3],
]);
let dst_field = u32::from_be_bytes([
data[offset + 6], data[offset + 7], data[offset + 8], data[offset + 9],
]);
offset += 12;
specs.push(LearnSpec::LoadField { src_field, dst_field, n_bits });
}
(1, 1) => {
let value_len = (n_bits as usize).div_ceil(16) * 2;
if offset + value_len + 6 > data.len() {
break;
}
let value = data[offset..offset + value_len].to_vec();
offset += value_len;
let dst_field = u32::from_be_bytes([
data[offset], data[offset + 1], data[offset + 2], data[offset + 3],
]);
offset += 6; specs.push(LearnSpec::LoadImmediate { dst_field, value, n_bits });
}
(0, 2) => {
if offset + 6 > data.len() {
break;
}
let src_field = u32::from_be_bytes([
data[offset], data[offset + 1], data[offset + 2], data[offset + 3],
]);
offset += 6; specs.push(LearnSpec::OutputField { src_field, n_bits });
}
_ => {
break;
}
}
}
specs
}