use super::{types::*, *};
use crate::netlink::builder::MessageBuilder;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Expr {
Meta { dreg: Register, key: MetaKey },
Cmp {
sreg: Register,
op: CmpOp,
data: Vec<u8>,
},
Payload {
dreg: Register,
base: PayloadBase,
offset: u32,
len: u32,
},
Immediate { dreg: Register, data: Vec<u8> },
Verdict(Verdict),
Counter,
Limit {
rate: u64,
unit: LimitUnit,
burst: u32,
},
Masquerade,
Nat(NatExpr),
Redirect { port: Option<u16> },
Log {
prefix: Option<String>,
group: Option<u16>,
},
Ct { dreg: Register, key: CtKey },
Lookup { set: String, sreg: Register },
Bitwise {
sreg: Register,
dreg: Register,
len: u32,
mask: Vec<u8>,
xor: Vec<u8>,
},
FlowOffload {
table: String,
},
}
pub fn write_expressions(builder: &mut MessageBuilder, exprs: &[Expr]) {
let list = builder.nest_start(NFTA_RULE_EXPRESSIONS | 0x8000); for expr in exprs {
write_expr(builder, expr);
}
builder.nest_end(list);
}
fn write_expr(builder: &mut MessageBuilder, expr: &Expr) {
let elem = builder.nest_start(NFTA_LIST_ELEM | 0x8000);
match expr {
Expr::Meta { dreg, key } => {
builder.append_attr_str(NFTA_EXPR_NAME, "meta");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_META_DREG, *dreg as u32);
builder.append_attr_u32_be(NFTA_META_KEY, *key as u32);
builder.nest_end(data);
}
Expr::Cmp { sreg, op, data } => {
builder.append_attr_str(NFTA_EXPR_NAME, "cmp");
let expr_data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_CMP_SREG, *sreg as u32);
builder.append_attr_u32_be(NFTA_CMP_OP, *op as u32);
let cmp_data = builder.nest_start(NFTA_CMP_DATA | 0x8000);
builder.append_attr(NFTA_DATA_VALUE, data);
builder.nest_end(cmp_data);
builder.nest_end(expr_data);
}
Expr::Payload {
dreg,
base,
offset,
len,
} => {
builder.append_attr_str(NFTA_EXPR_NAME, "payload");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_PAYLOAD_DREG, *dreg as u32);
builder.append_attr_u32_be(NFTA_PAYLOAD_BASE, *base as u32);
builder.append_attr_u32_be(NFTA_PAYLOAD_OFFSET, *offset);
builder.append_attr_u32_be(NFTA_PAYLOAD_LEN, *len);
builder.nest_end(data);
}
Expr::Immediate { dreg, data } => {
builder.append_attr_str(NFTA_EXPR_NAME, "immediate");
let expr_data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_IMMEDIATE_DREG, *dreg as u32);
let imm_data = builder.nest_start(NFTA_IMMEDIATE_DATA | 0x8000);
builder.append_attr(NFTA_DATA_VALUE, data);
builder.nest_end(imm_data);
builder.nest_end(expr_data);
}
Expr::Verdict(verdict) => {
write_verdict_expr(builder, verdict);
}
Expr::Counter => {
builder.append_attr_str(NFTA_EXPR_NAME, "counter");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u64_be(NFTA_COUNTER_BYTES, 0);
builder.append_attr_u64_be(NFTA_COUNTER_PACKETS, 0);
builder.nest_end(data);
}
Expr::Limit { rate, unit, burst } => {
builder.append_attr_str(NFTA_EXPR_NAME, "limit");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u64_be(NFTA_LIMIT_RATE, *rate);
builder.append_attr_u64_be(NFTA_LIMIT_UNIT, unit.to_u64());
builder.append_attr_u32_be(NFTA_LIMIT_BURST, *burst);
builder.append_attr_u32_be(NFTA_LIMIT_TYPE, 0); builder.nest_end(data);
}
Expr::Masquerade => {
builder.append_attr_str(NFTA_EXPR_NAME, "masq");
}
Expr::Nat(nat) => {
builder.append_attr_str(NFTA_EXPR_NAME, "nat");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_NAT_TYPE, nat.nat_type as u32);
debug_assert!(
matches!(nat.family, Family::Ip | Family::Ip6),
"NAT family must be Ip or Ip6, got {:?} (Inet is not valid for NAT expressions)",
nat.family
);
builder.append_attr_u32_be(NFTA_NAT_FAMILY, nat.family as u32);
let mut flags = 0u32;
if nat.addr.reg_in_use() {
builder.append_attr_u32_be(NFTA_NAT_REG_ADDR_MIN, Register::R0 as u32);
builder.append_attr_u32_be(NFTA_NAT_REG_ADDR_MAX, Register::R0 as u32);
flags |= NF_NAT_RANGE_MAP_IPS;
}
if nat.port.is_some() {
builder.append_attr_u32_be(NFTA_NAT_REG_PROTO_MIN, Register::R1 as u32);
builder.append_attr_u32_be(NFTA_NAT_REG_PROTO_MAX, Register::R1 as u32);
flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
}
if flags != 0 {
builder.append_attr_u32_be(NFTA_NAT_FLAGS, flags);
}
builder.nest_end(data);
}
Expr::Redirect { port } => {
builder.append_attr_str(NFTA_EXPR_NAME, "redir");
if port.is_some() {
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_NAT_REG_PROTO_MIN, Register::R0 as u32);
builder.nest_end(data);
}
}
Expr::Log { prefix, group } => {
builder.append_attr_str(NFTA_EXPR_NAME, "log");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
if let Some(prefix) = prefix {
builder.append_attr_str(NFTA_LOG_PREFIX, prefix);
}
if let Some(group) = group {
builder.append_attr_u16_be(NFTA_LOG_GROUP, *group);
}
builder.nest_end(data);
}
Expr::Ct { dreg, key } => {
builder.append_attr_str(NFTA_EXPR_NAME, "ct");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_CT_DREG, *dreg as u32);
builder.append_attr_u32_be(NFTA_CT_KEY, *key as u32);
builder.nest_end(data);
}
Expr::Lookup { set, sreg } => {
builder.append_attr_str(NFTA_EXPR_NAME, "lookup");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_str(NFTA_LOOKUP_SET, set);
builder.append_attr_u32_be(NFTA_LOOKUP_SREG, *sreg as u32);
builder.nest_end(data);
}
Expr::Bitwise {
sreg,
dreg,
len,
mask,
xor,
} => {
builder.append_attr_str(NFTA_EXPR_NAME, "bitwise");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_BITWISE_SREG, *sreg as u32);
builder.append_attr_u32_be(NFTA_BITWISE_DREG, *dreg as u32);
builder.append_attr_u32_be(NFTA_BITWISE_LEN, *len);
builder.append_attr_u32_be(NFTA_BITWISE_OP, NFT_BITWISE_BOOL);
let mask_nest = builder.nest_start(NFTA_BITWISE_MASK | 0x8000);
builder.append_attr(NFTA_DATA_VALUE, mask);
builder.nest_end(mask_nest);
let xor_nest = builder.nest_start(NFTA_BITWISE_XOR | 0x8000);
builder.append_attr(NFTA_DATA_VALUE, xor);
builder.nest_end(xor_nest);
builder.nest_end(data);
}
Expr::FlowOffload { table } => {
builder.append_attr_str(NFTA_EXPR_NAME, "flow_offload");
let data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_str(NFTA_FLOWTABLE_NAME, table);
builder.nest_end(data);
}
}
builder.nest_end(elem);
}
fn write_verdict_expr(builder: &mut MessageBuilder, verdict: &Verdict) {
builder.append_attr_str(NFTA_EXPR_NAME, "immediate");
let expr_data = builder.nest_start(NFTA_EXPR_DATA | 0x8000);
builder.append_attr_u32_be(NFTA_IMMEDIATE_DREG, Register::Verdict as u32);
let imm_data = builder.nest_start(NFTA_IMMEDIATE_DATA | 0x8000);
let verdict_nest = builder.nest_start(NFTA_DATA_VERDICT | 0x8000);
let code = match verdict {
Verdict::Accept => NF_ACCEPT,
Verdict::Drop => NF_DROP,
Verdict::Continue => NFT_CONTINUE,
Verdict::Return => NFT_RETURN,
Verdict::JumpTo(_) => NFT_JUMP,
Verdict::GotoTo(_) => NFT_GOTO,
};
builder.append_attr_u32_be(NFTA_VERDICT_CODE, code as u32);
match verdict {
Verdict::JumpTo(chain) | Verdict::GotoTo(chain) => {
builder.append_attr_str(NFTA_VERDICT_CHAIN, chain.as_str());
}
_ => {}
}
builder.nest_end(verdict_nest);
builder.nest_end(imm_data);
builder.nest_end(expr_data);
}
use crate::netlink::attr::{AttrIter, get};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RuleExpr {
Counter {
packets: u64,
bytes: u64,
},
Verdict(Verdict),
Meta {
dreg: Register,
key: MetaKey,
},
Cmp {
sreg: Register,
op: CmpOp,
data: Vec<u8>,
},
Immediate {
dreg: Register,
data: Vec<u8>,
},
Payload {
dreg: Register,
base: PayloadBase,
offset: u32,
len: u32,
},
Unknown {
name: String,
data: Vec<u8>,
},
}
pub fn parse_expressions(bytes: &[u8]) -> Vec<RuleExpr> {
let mut exprs = Vec::new();
for (kind, elem) in AttrIter::new(bytes) {
if kind != NFTA_LIST_ELEM {
continue;
}
let mut name: Option<&str> = None;
let mut data: &[u8] = &[];
for (attr, payload) in AttrIter::new(elem) {
match attr {
NFTA_EXPR_NAME => name = get::string(payload).ok(),
NFTA_EXPR_DATA => data = payload,
_ => {}
}
}
let Some(name) = name else { continue };
exprs.push(parse_expr(name, data));
}
exprs
}
fn parse_expr(name: &str, data: &[u8]) -> RuleExpr {
let decoded = match name {
"counter" => parse_counter(data),
"immediate" => parse_immediate(data),
"meta" => parse_meta(data),
"cmp" => parse_cmp(data),
"payload" => parse_payload(data),
_ => None,
};
decoded.unwrap_or_else(|| RuleExpr::Unknown {
name: name.to_string(),
data: data.to_vec(),
})
}
fn parse_counter(data: &[u8]) -> Option<RuleExpr> {
let mut packets = None;
let mut bytes = None;
for (attr, payload) in AttrIter::new(data) {
match attr {
NFTA_COUNTER_PACKETS => packets = Some(get::u64_be(payload).ok()?),
NFTA_COUNTER_BYTES => bytes = Some(get::u64_be(payload).ok()?),
_ => {}
}
}
if packets.is_none() && bytes.is_none() {
return None;
}
Some(RuleExpr::Counter {
packets: packets.unwrap_or(0),
bytes: bytes.unwrap_or(0),
})
}
fn parse_immediate(data: &[u8]) -> Option<RuleExpr> {
let mut dreg = None;
let mut imm_nest: &[u8] = &[];
for (attr, payload) in AttrIter::new(data) {
match attr {
NFTA_IMMEDIATE_DREG => dreg = Register::from_u32(get::u32_be(payload).ok()?),
NFTA_IMMEDIATE_DATA => imm_nest = payload,
_ => {}
}
}
let dreg = dreg?;
for (attr, payload) in AttrIter::new(imm_nest) {
match attr {
NFTA_DATA_VALUE if dreg != Register::Verdict => {
return Some(RuleExpr::Immediate {
dreg,
data: payload.to_vec(),
});
}
NFTA_DATA_VERDICT if dreg == Register::Verdict => {
return parse_verdict(payload).map(RuleExpr::Verdict);
}
_ => {}
}
}
None
}
fn parse_verdict(nest: &[u8]) -> Option<Verdict> {
let mut code = None;
let mut chain = None;
for (attr, payload) in AttrIter::new(nest) {
match attr {
NFTA_VERDICT_CODE => code = Some(get::u32_be(payload).ok()? as i32),
NFTA_VERDICT_CHAIN => chain = get::string(payload).ok().map(str::to_string),
_ => {}
}
}
match code? {
NF_ACCEPT => Some(Verdict::Accept),
NF_DROP => Some(Verdict::Drop),
NFT_CONTINUE => Some(Verdict::Continue),
NFT_RETURN => Some(Verdict::Return),
NFT_JUMP => Some(Verdict::JumpTo(ChainName::new(chain?).ok()?)),
NFT_GOTO => Some(Verdict::GotoTo(ChainName::new(chain?).ok()?)),
_ => None,
}
}
fn parse_meta(data: &[u8]) -> Option<RuleExpr> {
let mut dreg = None;
let mut key = None;
for (attr, payload) in AttrIter::new(data) {
match attr {
NFTA_META_DREG => dreg = Register::from_u32(get::u32_be(payload).ok()?),
NFTA_META_KEY => key = MetaKey::from_u32(get::u32_be(payload).ok()?),
_ => {}
}
}
Some(RuleExpr::Meta {
dreg: dreg?,
key: key?,
})
}
fn parse_cmp(data: &[u8]) -> Option<RuleExpr> {
let mut sreg = None;
let mut op = None;
let mut value = None;
for (attr, payload) in AttrIter::new(data) {
match attr {
NFTA_CMP_SREG => sreg = Register::from_u32(get::u32_be(payload).ok()?),
NFTA_CMP_OP => op = CmpOp::from_u32(get::u32_be(payload).ok()?),
NFTA_CMP_DATA => {
for (inner, inner_payload) in AttrIter::new(payload) {
if inner == NFTA_DATA_VALUE {
value = Some(inner_payload.to_vec());
}
}
}
_ => {}
}
}
Some(RuleExpr::Cmp {
sreg: sreg?,
op: op?,
data: value?,
})
}
fn parse_payload(data: &[u8]) -> Option<RuleExpr> {
let mut dreg = None;
let mut base = None;
let mut offset = None;
let mut len = None;
for (attr, payload) in AttrIter::new(data) {
match attr {
NFTA_PAYLOAD_DREG => dreg = Register::from_u32(get::u32_be(payload).ok()?),
NFTA_PAYLOAD_BASE => base = PayloadBase::from_u32(get::u32_be(payload).ok()?),
NFTA_PAYLOAD_OFFSET => offset = Some(get::u32_be(payload).ok()?),
NFTA_PAYLOAD_LEN => len = Some(get::u32_be(payload).ok()?),
_ => {}
}
}
Some(RuleExpr::Payload {
dreg: dreg?,
base: base?,
offset: offset?,
len: len?,
})
}
impl super::types::RuleInfo {
pub fn expressions(&self) -> Vec<RuleExpr> {
parse_expressions(&self.expression_bytes)
}
pub fn counter(&self) -> Option<(u64, u64)> {
self.expressions().into_iter().find_map(|e| match e {
RuleExpr::Counter { packets, bytes } => Some((packets, bytes)),
_ => None,
})
}
}
#[cfg(test)]
mod verdict_tests {
use super::*;
fn encode_verdict(verdict: &Verdict) -> Vec<u8> {
let mut b = MessageBuilder::new(0, 0);
write_verdict_expr(&mut b, verdict);
b.as_bytes().to_vec()
}
#[test]
fn jumpto_emits_nft_jump_code_with_chain_name() {
let typed = Verdict::JumpTo(ChainName::new("input_filter").unwrap());
let bytes = encode_verdict(&typed);
assert!(!bytes.is_empty());
}
#[test]
fn goto_to_emits_nft_goto_code_with_chain_name() {
let typed = Verdict::GotoTo(ChainName::new("output_chain").unwrap());
let bytes = encode_verdict(&typed);
assert!(!bytes.is_empty());
}
#[test]
fn different_chain_names_produce_different_bytes() {
let a = Verdict::JumpTo(ChainName::new("a").unwrap());
let b = Verdict::JumpTo(ChainName::new("b").unwrap());
assert_ne!(encode_verdict(&a), encode_verdict(&b));
}
}
#[cfg(test)]
mod decode_tests {
use super::*;
fn encode(exprs: &[Expr]) -> Vec<u8> {
let mut b = MessageBuilder::new(0, 0);
write_expressions(&mut b, exprs);
b.as_bytes()[20..].to_vec()
}
fn build_elem(name: &str, data_payload: &[u8]) -> Vec<u8> {
let mut b = MessageBuilder::new(0, 0);
let elem = b.nest_start(NFTA_LIST_ELEM | 0x8000);
b.append_attr_str(NFTA_EXPR_NAME, name);
if !data_payload.is_empty() {
b.append_attr(NFTA_EXPR_DATA | 0x8000, data_payload);
}
b.nest_end(elem);
b.as_bytes()[16..].to_vec()
}
fn build_attrs(f: impl FnOnce(&mut MessageBuilder)) -> Vec<u8> {
let mut b = MessageBuilder::new(0, 0);
f(&mut b);
b.as_bytes()[16..].to_vec()
}
#[test]
fn roundtrip_meta_payload_cmp_immediate() {
let bytes = encode(&[
Expr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
},
Expr::Payload {
dreg: Register::R1,
base: PayloadBase::Transport,
offset: 2,
len: 2,
},
Expr::Cmp {
sreg: Register::R1,
op: CmpOp::Eq,
data: 443u16.to_be_bytes().to_vec(),
},
Expr::Immediate {
dreg: Register::R2,
data: vec![1, 2, 3, 4],
},
]);
let decoded = parse_expressions(&bytes);
assert_eq!(
decoded,
vec![
RuleExpr::Meta {
dreg: Register::R0,
key: MetaKey::L4Proto,
},
RuleExpr::Payload {
dreg: Register::R1,
base: PayloadBase::Transport,
offset: 2,
len: 2,
},
RuleExpr::Cmp {
sreg: Register::R1,
op: CmpOp::Eq,
data: 443u16.to_be_bytes().to_vec(),
},
RuleExpr::Immediate {
dreg: Register::R2,
data: vec![1, 2, 3, 4],
},
]
);
}
#[test]
fn roundtrip_verdict_all_variants() {
let verdicts = [
Verdict::Accept,
Verdict::Drop,
Verdict::Continue,
Verdict::Return,
Verdict::JumpTo(ChainName::new("subchain").unwrap()),
Verdict::GotoTo(ChainName::new("tailchain").unwrap()),
];
for v in verdicts {
let bytes = encode(&[Expr::Verdict(v.clone())]);
let decoded = parse_expressions(&bytes);
assert_eq!(decoded, vec![RuleExpr::Verdict(v)], "verdict round-trip");
}
}
#[test]
fn roundtrip_counter_write_side_zeroes() {
let bytes = encode(&[Expr::Counter]);
assert_eq!(
parse_expressions(&bytes),
vec![RuleExpr::Counter {
packets: 0,
bytes: 0,
}]
);
}
#[test]
fn counter_with_live_values_decodes_in_any_attr_order() {
for swapped in [false, true] {
let data = build_attrs(|b| {
if swapped {
b.append_attr_u64_be(NFTA_COUNTER_PACKETS, 7);
b.append_attr_u64_be(NFTA_COUNTER_BYTES, 4242);
} else {
b.append_attr_u64_be(NFTA_COUNTER_BYTES, 4242);
b.append_attr_u64_be(NFTA_COUNTER_PACKETS, 7);
}
});
let elem = build_elem("counter", &data);
assert_eq!(
parse_expressions(&elem),
vec![RuleExpr::Counter {
packets: 7,
bytes: 4242,
}]
);
}
}
#[test]
fn counter_short_payload_falls_back_to_unknown() {
let data = build_attrs(|b| b.append_attr(NFTA_COUNTER_PACKETS, &[0, 0, 0, 7]));
let elem = build_elem("counter", &data);
match &parse_expressions(&elem)[..] {
[RuleExpr::Unknown { name, data: raw }] => {
assert_eq!(name, "counter");
assert!(!raw.is_empty(), "raw payload preserved");
}
other => panic!("expected Unknown, got {other:?}"),
}
}
#[test]
fn unknown_expr_name_preserves_payload() {
let data = build_attrs(|b| b.append_attr(1, &[9, 9, 9, 9]));
let elem = build_elem("quota", &data);
assert_eq!(
parse_expressions(&elem),
vec![RuleExpr::Unknown {
name: "quota".to_string(),
data: data.clone(),
}]
);
}
#[test]
fn dataless_expr_yields_unknown_with_empty_data() {
let bytes = encode(&[Expr::Masquerade]);
assert_eq!(
parse_expressions(&bytes),
vec![RuleExpr::Unknown {
name: "masq".to_string(),
data: vec![],
}]
);
}
#[test]
fn verdict_break_code_falls_back_to_unknown() {
let verdict_nest = build_attrs(|b| {
b.append_attr_u32_be(NFTA_VERDICT_CODE, NFT_BREAK as u32);
});
let imm_nest = build_attrs(|b| b.append_attr(NFTA_DATA_VERDICT | 0x8000, &verdict_nest));
let data = build_attrs(|b| {
b.append_attr_u32_be(NFTA_IMMEDIATE_DREG, Register::Verdict as u32);
b.append_attr(NFTA_IMMEDIATE_DATA | 0x8000, &imm_nest);
});
let elem = build_elem("immediate", &data);
assert!(matches!(
&parse_expressions(&elem)[..],
[RuleExpr::Unknown { name, .. }] if name == "immediate"
));
}
#[test]
fn meta_without_dreg_falls_back_to_unknown() {
let data = build_attrs(|b| b.append_attr_u32_be(NFTA_META_KEY, MetaKey::Mark as u32));
let elem = build_elem("meta", &data);
assert!(matches!(
&parse_expressions(&elem)[..],
[RuleExpr::Unknown { name, .. }] if name == "meta"
));
}
#[test]
fn meta_unmodelled_key_falls_back_to_unknown() {
let data = build_attrs(|b| {
b.append_attr_u32_be(NFTA_META_DREG, Register::R0 as u32);
b.append_attr_u32_be(NFTA_META_KEY, 9999);
});
let elem = build_elem("meta", &data);
assert!(matches!(
&parse_expressions(&elem)[..],
[RuleExpr::Unknown { name, .. }] if name == "meta"
));
}
#[test]
fn nameless_elem_is_skipped_and_empty_input_is_empty() {
assert!(parse_expressions(&[]).is_empty());
let data = build_attrs(|b| b.append_attr(NFTA_COUNTER_BYTES, &42u64.to_be_bytes()));
let elem = {
let mut b = MessageBuilder::new(0, 0);
let e = b.nest_start(NFTA_LIST_ELEM | 0x8000);
b.append_attr(NFTA_EXPR_DATA | 0x8000, &data);
b.nest_end(e);
b.as_bytes()[16..].to_vec()
};
assert!(parse_expressions(&elem).is_empty());
}
#[test]
fn pathological_lengths_terminate_without_panic() {
let mut truncated = Vec::new();
truncated.extend_from_slice(&64u16.to_ne_bytes());
truncated.extend_from_slice(&(NFTA_LIST_ELEM | 0x8000).to_ne_bytes());
truncated.extend_from_slice(&[0u8; 4]);
assert!(parse_expressions(&truncated).is_empty());
let zero_len = [0u8, 0, 1, 0, 0, 0, 0, 0];
assert!(parse_expressions(&zero_len).is_empty());
let mut short = Vec::new();
short.extend_from_slice(&2u16.to_ne_bytes());
short.extend_from_slice(&NFTA_LIST_ELEM.to_ne_bytes());
assert!(parse_expressions(&short).is_empty());
}
#[test]
fn ruleinfo_expressions_and_counter_shortcut() {
let live_counter = {
let data = build_attrs(|b| {
b.append_attr_u64_be(NFTA_COUNTER_BYTES, 1_000_000);
b.append_attr_u64_be(NFTA_COUNTER_PACKETS, 1_000);
});
build_elem("counter", &data)
};
let mut expression_bytes = encode(&[
Expr::Meta {
dreg: Register::R0,
key: MetaKey::NfProto,
},
Expr::Verdict(Verdict::Accept),
]);
expression_bytes.extend_from_slice(&live_counter);
let rule = RuleInfo {
table: "t".into(),
chain: "c".into(),
family: Family::Inet,
handle: 1,
position: None,
comment: None,
userdata_raw: None,
expression_bytes,
};
let exprs = rule.expressions();
assert_eq!(exprs.len(), 3);
assert_eq!(rule.counter(), Some((1_000, 1_000_000)));
let no_counter = RuleInfo {
expression_bytes: encode(&[Expr::Verdict(Verdict::Drop)]),
..rule
};
assert_eq!(no_counter.counter(), None);
}
}