use pktbaffle::bpf::Insn;
use pktbaffle::{compile, LinkType, Target};
const LDH_ABS: u16 = 0x28; const LDB_ABS: u16 = 0x30; const LDW_ABS: u16 = 0x20; const LDH_IND: u16 = 0x48; const LDX_MSH: u16 = 0xb1; const LD_LEN: u16 = 0x80; const AND_K: u16 = 0x54; const RSH_K: u16 = 0x74; const JEQ_K: u16 = 0x15; const JGT_K: u16 = 0x25; const JGE_K: u16 = 0x35; const JSET_K: u16 = 0x45; const RET_K: u16 = 0x06;
const ACCEPT: u32 = 0xffff_ffff;
const DROP: u32 = 0;
fn insn(code: u16, jt: u8, jf: u8, k: u32) -> Insn {
Insn { code, jt, jf, k }
}
fn eth(filter: &str) -> Vec<Insn> {
compile(filter, LinkType::Ethernet, Target::Classic)
.unwrap_or_else(|e| panic!("compile({filter:?}): {e}"))
.instructions()
.to_vec()
}
#[test]
fn less_64_uses_jgt_jt_drop() {
let prog = eth("less 64");
assert_eq!(
prog,
vec![
insn(LD_LEN, 0, 0, 0),
insn(JGT_K, 1, 0, 64), insn(RET_K, 0, 0, ACCEPT),
insn(RET_K, 0, 0, DROP),
]
);
}
#[test]
fn greater_1500_uses_jge_jf_drop() {
let prog = eth("greater 1500");
assert_eq!(
prog,
vec![
insn(LD_LEN, 0, 0, 0),
insn(JGE_K, 0, 1, 1500), insn(RET_K, 0, 0, ACCEPT),
insn(RET_K, 0, 0, DROP),
]
);
}
#[test]
fn ether_multicast_checks_bit0_of_dst_mac() {
let prog = eth("ether multicast");
assert_eq!(
prog,
vec![
insn(LDB_ABS, 0, 0, 0), insn(JSET_K, 0, 1, 0x01), insn(RET_K, 0, 0, ACCEPT),
insn(RET_K, 0, 0, DROP),
]
);
}
#[test]
fn ip_broadcast_checks_destination_ip() {
let prog = eth("ip broadcast");
assert_eq!(prog[0], insn(LDH_ABS, 0, 0, 12));
assert_eq!(prog[1].code, JEQ_K);
assert_eq!(prog[1].k, 0x0800);
assert_eq!(prog[2], insn(LDW_ABS, 0, 0, 30));
assert_eq!(prog[3].code, JEQ_K);
assert_eq!(prog[3].k, 0xffff_ffff);
assert_eq!(prog[4], insn(RET_K, 0, 0, ACCEPT));
assert_eq!(prog[5], insn(RET_K, 0, 0, DROP));
assert_eq!(prog.len(), 6);
}
#[test]
fn ip_multicast_masks_and_compares_224_block() {
let prog = eth("ip multicast");
assert_eq!(prog[2], insn(LDW_ABS, 0, 0, 30));
assert_eq!(prog[3], insn(AND_K, 0, 0, 0xf000_0000));
assert_eq!(prog[4].code, JEQ_K);
assert_eq!(prog[4].k, 0xe000_0000);
assert_eq!(prog.len(), 7);
}
#[test]
fn ip6_multicast_checks_first_dest_byte() {
let prog = eth("ip6 multicast");
assert_eq!(prog[0], insn(LDH_ABS, 0, 0, 12));
assert_eq!(prog[1].code, JEQ_K);
assert_eq!(prog[1].k, 0x86dd);
assert_eq!(prog[2], insn(LDB_ABS, 0, 0, 38));
assert_eq!(prog[3].code, JEQ_K);
assert_eq!(prog[3].k, 0xff);
assert_eq!(prog[4], insn(RET_K, 0, 0, ACCEPT));
assert_eq!(prog[5], insn(RET_K, 0, 0, DROP));
assert_eq!(prog.len(), 6);
}
#[test]
fn vlan_any_checks_ethertype_8100() {
let prog = eth("vlan");
assert_eq!(
prog,
vec![
insn(LDH_ABS, 0, 0, 12),
insn(JEQ_K, 0, 1, 0x8100),
insn(RET_K, 0, 0, ACCEPT),
insn(RET_K, 0, 0, DROP),
]
);
}
#[test]
fn vlan_with_id_masks_tci() {
let prog = eth("vlan 100");
assert_eq!(prog[0], insn(LDH_ABS, 0, 0, 12));
assert_eq!(prog[1].code, JEQ_K);
assert_eq!(prog[1].k, 0x8100);
assert_eq!(prog[1].jf, 4); assert_eq!(prog[2], insn(LDH_ABS, 0, 0, 14));
assert_eq!(prog[3], insn(AND_K, 0, 0, 0x0fff));
assert_eq!(prog[4].code, JEQ_K);
assert_eq!(prog[4].k, 100);
assert_eq!(prog[5], insn(RET_K, 0, 0, ACCEPT));
assert_eq!(prog[6], insn(RET_K, 0, 0, DROP));
assert_eq!(prog.len(), 7);
}
#[test]
fn mpls_any_accepts_both_ethertypes() {
let prog = eth("mpls");
assert_eq!(prog[0], insn(LDH_ABS, 0, 0, 12));
assert_eq!(prog[1].code, JEQ_K);
assert_eq!(prog[1].k, 0x8847);
assert_eq!(prog[1].jt, 1); assert_eq!(prog[2].code, JEQ_K);
assert_eq!(prog[2].k, 0x8848);
assert_eq!(prog[2].jt, 0); assert_eq!(prog[3], insn(RET_K, 0, 0, ACCEPT));
assert_eq!(prog[4], insn(RET_K, 0, 0, DROP));
assert_eq!(prog.len(), 5);
}
#[test]
fn mpls_with_label_uses_rsh_12() {
let prog = eth("mpls 12345");
assert_eq!(prog[3], insn(LDW_ABS, 0, 0, 14));
assert_eq!(prog[4], insn(RSH_K, 0, 0, 12));
assert_eq!(prog[5].code, JEQ_K);
assert_eq!(prog[5].k, 12345);
assert_eq!(prog.len(), 8);
}
#[test]
fn port_no_proto_tcp_shortcut_resolves_to_msh_not_accept() {
let prog = eth("port 80");
assert_eq!(prog[2], insn(LDB_ABS, 0, 0, 23));
assert_eq!(prog[3].code, JEQ_K);
assert_eq!(prog[3].k, 6);
assert_eq!(prog[3].jt, 1, "TCP shortcut must jump to MSH, not ACCEPT");
assert_eq!(prog[4].code, JEQ_K);
assert_eq!(prog[4].k, 17);
assert_eq!(prog[5], insn(LDX_MSH, 0, 0, 14));
assert_eq!(prog[6], insn(LDH_IND, 0, 0, 14));
assert_eq!(prog[7].code, JEQ_K);
assert_eq!(prog[7].k, 80);
assert_eq!(prog[8], insn(LDH_IND, 0, 0, 16));
assert_eq!(prog[9].code, JEQ_K);
assert_eq!(prog[9].k, 80);
assert_eq!(prog[10], insn(RET_K, 0, 0, ACCEPT));
assert_eq!(prog[11], insn(RET_K, 0, 0, DROP));
assert_eq!(prog.len(), 12);
}
#[test]
fn portrange_no_proto_tcp_shortcut_resolves_to_msh() {
let prog = eth("portrange 1024-65535");
assert_eq!(prog[3].code, JEQ_K);
assert_eq!(prog[3].k, 6);
assert_ne!(
prog[3].jt, 0xff,
"TCP shortcut must be resolved to MSH, not left as 0xff"
);
let msh_pos = prog
.iter()
.position(|i| i.code == LDX_MSH)
.expect("MSH missing");
assert!(
msh_pos < prog.len() - 2,
"MSH should appear before accept/drop"
);
let tcp_idx = 3usize;
let target = tcp_idx + 1 + prog[tcp_idx].jt as usize;
assert_eq!(prog[target].code, LDX_MSH, "TCP jt must land on MSH");
}
#[test]
fn ip_proto_6_identical_to_tcp() {
assert_eq!(eth("ip proto 6"), eth("tcp"));
}
#[test]
fn ip6_proto_58_identical_to_icmp6() {
assert_eq!(eth("ip6 proto 58"), eth("icmp6"));
}
#[test]
fn ip6_proto_6_identical_to_ip6_tcp() {
let prog = eth("ip6 proto 6");
assert_eq!(prog[0], insn(LDH_ABS, 0, 0, 12));
assert_eq!(prog[1].k, 0x86dd); assert_eq!(prog[2].code, LDB_ABS);
assert_eq!(prog[2].k, 20); assert_eq!(prog[3].code, JEQ_K);
assert_eq!(prog[3].k, 6); }
#[test]
fn net_mask_syntax_identical_to_cidr() {
assert_eq!(eth("net 10.0.0.0 mask 255.0.0.0"), eth("net 10.0.0.0/8"),);
}
#[test]
fn tcpflags_constant_identical_to_literal_offset() {
assert_eq!(
eth("tcp[tcpflags] & tcp-syn != 0"),
eth("tcp[13] & 0x02 != 0"),
);
}
#[test]
fn icmp_echo_constant_identical_to_literal() {
assert_eq!(eth("icmp[icmptype] = icmp-echo"), eth("icmp[icmptype] = 8"),);
}
#[test]
fn len_eq_60() {
let prog = eth("len = 60");
assert_eq!(prog[0], insn(LD_LEN, 0, 0, 0));
assert_eq!(prog[1].code, JEQ_K);
assert_eq!(prog[1].k, 60);
assert_eq!(prog[1].jf, 1); }
#[test]
fn len_ne_60() {
let prog = eth("len != 60");
assert_eq!(prog[0], insn(LD_LEN, 0, 0, 0));
assert_eq!(prog[1].code, JEQ_K);
assert_eq!(prog[1].k, 60);
assert_eq!(prog[1].jt, 1); }
#[test]
fn len_gt_1000() {
let prog = eth("len > 1000");
assert_eq!(prog[0], insn(LD_LEN, 0, 0, 0));
assert_eq!(prog[1].code, JGT_K);
assert_eq!(prog[1].k, 1000);
assert_eq!(prog[1].jf, 1); }
#[test]
fn len_lt_64() {
let prog = eth("len < 64");
assert_eq!(prog[0], insn(LD_LEN, 0, 0, 0));
assert_eq!(prog[1].code, JGE_K);
assert_eq!(prog[1].k, 64);
assert_eq!(prog[1].jt, 1); }
#[test]
fn len_ge_1000() {
let prog = eth("len >= 1000");
assert_eq!(prog[0], insn(LD_LEN, 0, 0, 0));
assert_eq!(prog[1].code, JGE_K);
assert_eq!(prog[1].k, 1000);
assert_eq!(prog[1].jf, 1); }
#[test]
fn len_le_64() {
let prog = eth("len <= 64");
assert_eq!(prog[0], insn(LD_LEN, 0, 0, 0));
assert_eq!(prog[1].code, JGT_K);
assert_eq!(prog[1].k, 64);
assert_eq!(prog[1].jt, 1); }
#[test]
fn less_n_equals_len_le_n() {
assert_eq!(eth("less 64"), eth("len <= 64"));
}
#[test]
fn greater_n_equals_len_ge_n() {
assert_eq!(eth("greater 1500"), eth("len >= 1500"));
}