use std::net::{IpAddr, Ipv4Addr};
use crate::ast::*;
use crate::bpf::{Insn, Program, BPF_ACCEPT, BPF_DROP, BPF_LD, BPF_LEN};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkType {
Ethernet,
RawIp,
LinuxSll,
}
impl LinkType {
pub(crate) fn net_offset(self) -> u32 {
match self {
LinkType::Ethernet => 14,
LinkType::RawIp => 0,
LinkType::LinuxSll => 16,
}
}
pub(crate) fn ether_proto_offset(self) -> Option<u32> {
match self {
LinkType::Ethernet => Some(12),
LinkType::LinuxSll => Some(14),
LinkType::RawIp => None,
}
}
}
#[derive(Debug, Clone, Copy)]
enum Patch {
Jt(usize),
Jf(usize),
Ja(usize),
}
#[derive(Default)]
struct Patches {
success: Vec<Patch>,
failure: Vec<Patch>,
}
struct Codegen {
insns: Vec<Insn>,
link: LinkType,
}
impl Codegen {
fn new(link: LinkType) -> Self {
Self {
insns: Vec::new(),
link,
}
}
fn push(&mut self, insn: Insn) -> usize {
let idx = self.insns.len();
self.insns.push(insn);
idx
}
fn resolve(&mut self, patch: Patch, target_idx: usize) {
match patch {
Patch::Jt(i) => {
self.insns[i].jt = Self::offset(i, target_idx, &self.insns);
}
Patch::Jf(i) => {
self.insns[i].jf = Self::offset(i, target_idx, &self.insns);
}
Patch::Ja(i) => {
let off = Self::offset(i, target_idx, &self.insns) as u32;
self.insns[i].k = off;
}
}
}
fn resolve_all(&mut self, patches: Vec<Patch>, target_idx: usize) {
for p in patches {
self.resolve(p, target_idx);
}
}
fn offset(from: usize, to: usize, _insns: &[Insn]) -> u8 {
debug_assert!(to > from, "BPF jump target must be forward");
let diff = to - from - 1;
debug_assert!(
diff <= 255,
"BPF jump offset overflow; program is too large"
);
diff as u8
}
fn emit_expr(&mut self, expr: &Expr) -> Result<Patches> {
match expr {
Expr::And(l, r) => self.emit_and(l, r),
Expr::Or(l, r) => self.emit_or(l, r),
Expr::Not(e) => self.emit_not(e),
Expr::Primitive(p) => self.emit_primitive(p),
}
}
fn emit_and(&mut self, left: &Expr, right: &Expr) -> Result<Patches> {
let left_p = self.emit_expr(left)?;
let right_start = self.insns.len();
self.resolve_all(left_p.success, right_start);
let right_p = self.emit_expr(right)?;
Ok(Patches {
success: right_p.success,
failure: left_p.failure.into_iter().chain(right_p.failure).collect(),
})
}
fn emit_or(&mut self, left: &Expr, right: &Expr) -> Result<Patches> {
let left_p = self.emit_expr(left)?;
let ja_idx = self.push(Insn::ja(0));
let right_start = self.insns.len();
self.resolve_all(left_p.failure, right_start);
let right_p = self.emit_expr(right)?;
let mut success = left_p.success;
success.push(Patch::Ja(ja_idx));
success.extend(right_p.success);
Ok(Patches {
success,
failure: right_p.failure,
})
}
fn emit_not(&mut self, inner: &Expr) -> Result<Patches> {
let inner_p = self.emit_expr(inner)?;
let ja_idx = self.push(Insn::ja(0));
let not_succ_start = self.insns.len();
self.resolve_all(inner_p.failure, not_succ_start);
Ok(Patches {
success: Vec::new(), failure: inner_p
.success
.into_iter()
.chain(std::iter::once(Patch::Ja(ja_idx)))
.collect(),
})
}
fn emit_primitive(&mut self, prim: &Primitive) -> Result<Patches> {
match prim {
Primitive::Proto(p) => self.emit_proto(p),
Primitive::Host { addr, dir } => self.emit_host(*addr, *dir),
Primitive::Net { net, dir } => self.emit_net(net, *dir),
Primitive::Port { port, dir, proto } => self.emit_port(*port, *dir, *proto),
Primitive::PortRange { lo, hi, dir, proto } => {
self.emit_portrange(*lo, *hi, *dir, *proto)
}
Primitive::EtherHost { addr, dir } => self.emit_ether_host(addr, *dir),
Primitive::EtherProto(et) => self.emit_ethertype(*et as u32),
Primitive::EtherMulticast => self.emit_ether_multicast(),
Primitive::IpBroadcast => self.emit_ip_broadcast(),
Primitive::IpMulticast => self.emit_ip_multicast(),
Primitive::Ip6Multicast => self.emit_ip6_multicast(),
Primitive::Vlan { id } => self.emit_vlan(*id),
Primitive::Mpls { label } => self.emit_mpls(*label),
Primitive::PppoeDiscovery => self.emit_ethertype(0x8863),
Primitive::PppoeSession => self.emit_ethertype(0x8864),
Primitive::Len { op, value } => self.emit_len(*op, *value),
Primitive::Inbound | Primitive::Outbound => Err(Error::CodegenError {
message: "inbound/outbound direction cannot be expressed in standard BPF".into(),
}),
Primitive::ByteAccess(ba) => self.emit_byte_access(ba),
}
}
fn check_halfword(&mut self, off: u32, expected: u32) -> Patches {
self.push(Insn::ldh_abs(off));
let idx = self.push(Insn::jeq_k(expected, 0, 0xff));
Patches {
success: vec![],
failure: vec![Patch::Jf(idx)],
}
}
fn check_byte(&mut self, off: u32, expected: u32) -> Patches {
self.push(Insn::ldb_abs(off));
let idx = self.push(Insn::jeq_k(expected, 0, 0xff));
Patches {
success: vec![],
failure: vec![Patch::Jf(idx)],
}
}
fn check_word(&mut self, off: u32, expected: u32) -> Patches {
self.push(Insn::ldw_abs(off));
let idx = self.push(Insn::jeq_k(expected, 0, 0xff));
Patches {
success: vec![],
failure: vec![Patch::Jf(idx)],
}
}
fn emit_ethertype(&mut self, et: u32) -> Result<Patches> {
if let Some(off) = self.link.ether_proto_offset() {
Ok(self.check_halfword(off, et))
} else {
if et == 0x0800 {
Ok(Patches::default()) } else {
Err(Error::CodegenError {
message: format!("ethertype 0x{:04x} cannot be matched on RawIp captures", et),
})
}
}
}
fn ip4_guard(&mut self) -> Result<Patches> {
self.emit_ethertype(0x0800)
}
fn emit_ip4_l4(&mut self, proto_num: u8) -> Result<Patches> {
let mut p = self.ip4_guard()?;
let off = self.link.net_offset() + 9; let q = self.check_byte(off, proto_num as u32);
p.failure.extend(q.failure);
p.success.extend(q.success);
Ok(p)
}
fn emit_ip6_l4(&mut self, next_hdr: u8) -> Result<Patches> {
let mut p = self.emit_ethertype(0x86dd)?;
let off = self.link.net_offset() + 6; let q = self.check_byte(off, next_hdr as u32);
p.failure.extend(q.failure);
Ok(p)
}
fn emit_proto(&mut self, proto: &Proto) -> Result<Patches> {
match proto {
Proto::Ip => self.emit_ethertype(0x0800),
Proto::Ip6 => self.emit_ethertype(0x86dd),
Proto::Arp => self.emit_ethertype(0x0806),
Proto::Rarp => self.emit_ethertype(0x8035),
Proto::Tcp => self.emit_ip4_l4(6),
Proto::Udp => self.emit_ip4_l4(17),
Proto::Icmp => self.emit_ip4_l4(1),
Proto::Igmp => self.emit_ip4_l4(2),
Proto::Sctp => self.emit_ip4_l4(132),
Proto::Icmp6 => self.emit_ip6_l4(58),
Proto::Num(n) => self.emit_ip4_l4(*n),
Proto::Ip6Proto(n) => self.emit_ip6_l4(*n),
}
}
fn emit_host(&mut self, addr: IpAddr, dir: Dir) -> Result<Patches> {
match addr {
IpAddr::V4(a) => self.emit_host4(a, dir),
IpAddr::V6(a) => self.emit_host6(a, dir),
}
}
fn emit_host4(&mut self, addr: Ipv4Addr, dir: Dir) -> Result<Patches> {
let mut p = self.ip4_guard()?;
let base = self.link.net_offset();
let src_off = base + 12; let dst_off = base + 16; let k = u32::from(addr);
let q = self.check_addr4(k, src_off, dst_off, dir);
p.failure.extend(q.failure);
p.success.extend(q.success);
Ok(p)
}
fn check_addr4(&mut self, k: u32, src_off: u32, dst_off: u32, dir: Dir) -> Patches {
match dir {
Dir::Src => {
self.push(Insn::ldw_abs(src_off));
let i = self.push(Insn::jeq_k(k, 0, 0xff));
Patches {
success: vec![],
failure: vec![Patch::Jf(i)],
}
}
Dir::Dst => {
self.push(Insn::ldw_abs(dst_off));
let i = self.push(Insn::jeq_k(k, 0, 0xff));
Patches {
success: vec![],
failure: vec![Patch::Jf(i)],
}
}
Dir::SrcAndDst => {
self.push(Insn::ldw_abs(src_off));
let i1 = self.push(Insn::jeq_k(k, 0, 0xff));
self.push(Insn::ldw_abs(dst_off));
let i2 = self.push(Insn::jeq_k(k, 0, 0xff));
Patches {
success: vec![],
failure: vec![Patch::Jf(i1), Patch::Jf(i2)],
}
}
Dir::SrcOrDst => {
self.push(Insn::ldw_abs(src_off));
let i_src = self.push(Insn::jeq_k(k, 0xff, 0)); self.push(Insn::ldw_abs(dst_off));
let i_dst = self.push(Insn::jeq_k(k, 0, 0xff));
Patches {
success: vec![Patch::Jt(i_src)],
failure: vec![Patch::Jf(i_dst)],
}
}
}
}
fn emit_host6(&mut self, addr: std::net::Ipv6Addr, dir: Dir) -> Result<Patches> {
let mut p = self.emit_ethertype(0x86dd)?;
let base = self.link.net_offset();
let src_off = base + 8;
let dst_off = base + 24;
let segs = addr.segments();
let check_ip6_addr = |cg: &mut Codegen, off: u32, fail: &mut Vec<Patch>| {
for (i, &seg) in segs.iter().enumerate() {
cg.push(Insn::ldh_abs(off + i as u32 * 2));
let idx = cg.push(Insn::jeq_k(seg as u32, 0, 0xff));
fail.push(Patch::Jf(idx));
}
};
match dir {
Dir::Src => check_ip6_addr(self, src_off, &mut p.failure),
Dir::Dst => check_ip6_addr(self, dst_off, &mut p.failure),
Dir::SrcAndDst => {
check_ip6_addr(self, src_off, &mut p.failure);
check_ip6_addr(self, dst_off, &mut p.failure);
}
Dir::SrcOrDst => {
let mut src_fails = Vec::new();
check_ip6_addr(self, src_off, &mut src_fails);
let ja_idx = self.push(Insn::ja(0)); let dst_start = self.insns.len();
for fp in src_fails {
self.resolve(fp, dst_start);
}
check_ip6_addr(self, dst_off, &mut p.failure);
p.success.push(Patch::Ja(ja_idx));
}
}
Ok(p)
}
fn emit_net(&mut self, net: &IpNet, dir: Dir) -> Result<Patches> {
let mut p = self.ip4_guard()?;
let base = self.link.net_offset();
let src_off = base + 12;
let dst_off = base + 16;
let mask = net.mask;
let masked = u32::from(net.addr) & mask;
let check = |cg: &mut Codegen, off: u32, fail: &mut Vec<Patch>| {
cg.push(Insn::ldw_abs(off));
cg.push(Insn::and_k(mask));
let idx = cg.push(Insn::jeq_k(masked, 0, 0xff));
fail.push(Patch::Jf(idx));
};
match dir {
Dir::Src => check(self, src_off, &mut p.failure),
Dir::Dst => check(self, dst_off, &mut p.failure),
Dir::SrcAndDst => {
check(self, src_off, &mut p.failure);
check(self, dst_off, &mut p.failure);
}
Dir::SrcOrDst => {
self.push(Insn::ldw_abs(src_off));
self.push(Insn::and_k(mask));
let i_src = self.push(Insn::jeq_k(masked, 0xff, 0));
self.push(Insn::ldw_abs(dst_off));
self.push(Insn::and_k(mask));
let i_dst = self.push(Insn::jeq_k(masked, 0, 0xff));
p.success.push(Patch::Jt(i_src));
p.failure.push(Patch::Jf(i_dst));
}
}
Ok(p)
}
fn emit_port(&mut self, port: u16, dir: Dir, proto: Option<Proto>) -> Result<Patches> {
let mut p = self.emit_port_prereqs(proto)?;
let base = self.link.net_offset();
let src_port_off = base; let dst_port_off = base + 2; let k = port as u32;
match dir {
Dir::Src => {
self.push(Insn::ldh_ind(src_port_off));
let i = self.push(Insn::jeq_k(k, 0, 0xff));
p.failure.push(Patch::Jf(i));
}
Dir::Dst => {
self.push(Insn::ldh_ind(dst_port_off));
let i = self.push(Insn::jeq_k(k, 0, 0xff));
p.failure.push(Patch::Jf(i));
}
Dir::SrcAndDst => {
self.push(Insn::ldh_ind(src_port_off));
let i1 = self.push(Insn::jeq_k(k, 0, 0xff));
self.push(Insn::ldh_ind(dst_port_off));
let i2 = self.push(Insn::jeq_k(k, 0, 0xff));
p.failure.extend([Patch::Jf(i1), Patch::Jf(i2)]);
}
Dir::SrcOrDst => {
self.push(Insn::ldh_ind(src_port_off));
let i_src = self.push(Insn::jeq_k(k, 0xff, 0));
self.push(Insn::ldh_ind(dst_port_off));
let i_dst = self.push(Insn::jeq_k(k, 0, 0xff));
p.success.push(Patch::Jt(i_src));
p.failure.push(Patch::Jf(i_dst));
}
}
Ok(p)
}
fn emit_portrange(
&mut self,
lo: u16,
hi: u16,
dir: Dir,
proto: Option<Proto>,
) -> Result<Patches> {
let mut p = self.emit_port_prereqs(proto)?;
let base = self.link.net_offset();
let src_port_off = base;
let dst_port_off = base + 2;
let check_range = |cg: &mut Codegen, off: u32, fail: &mut Vec<Patch>| {
cg.push(Insn::ldh_ind(off));
let i_lo = cg.push(Insn::jge_k(lo as u32, 0, 0xff)); fail.push(Patch::Jf(i_lo));
let i_hi = cg.push(Insn::jgt_k(hi as u32, 0xff, 0)); fail.push(Patch::Jt(i_hi));
};
match dir {
Dir::Src => check_range(self, src_port_off, &mut p.failure),
Dir::Dst => check_range(self, dst_port_off, &mut p.failure),
Dir::SrcAndDst => {
check_range(self, src_port_off, &mut p.failure);
check_range(self, dst_port_off, &mut p.failure);
}
Dir::SrcOrDst => {
let mut src_fails = Vec::new();
check_range(self, src_port_off, &mut src_fails);
let ja_idx = self.push(Insn::ja(0));
let dst_start = self.insns.len();
for fp in src_fails {
self.resolve(fp, dst_start);
}
check_range(self, dst_port_off, &mut p.failure);
p.success.push(Patch::Ja(ja_idx));
}
}
Ok(p)
}
fn emit_port_prereqs(&mut self, proto: Option<Proto>) -> Result<Patches> {
let mut p = self.ip4_guard()?;
let proto_off = self.link.net_offset() + 9;
match proto {
Some(Proto::Tcp) => {
let q = self.check_byte(proto_off, 6);
p.failure.extend(q.failure);
}
Some(Proto::Udp) => {
let q = self.check_byte(proto_off, 17);
p.failure.extend(q.failure);
}
Some(Proto::Sctp) => {
let q = self.check_byte(proto_off, 132);
p.failure.extend(q.failure);
}
None => {
self.push(Insn::ldb_abs(proto_off));
let i_tcp = self.push(Insn::jeq_k(6, 0xff, 0)); let i_udp = self.push(Insn::jeq_k(17, 0, 0xff));
p.failure.push(Patch::Jf(i_udp));
let msh_idx = self.insns.len();
self.push(Insn::ldx_msh(self.link.net_offset()));
self.resolve(Patch::Jt(i_tcp), msh_idx);
return Ok(p);
}
Some(pr) => {
return Err(Error::CodegenError {
message: format!("port filter with proto {:?} is not supported", pr),
});
}
}
self.push(Insn::ldx_msh(self.link.net_offset()));
Ok(p)
}
fn emit_ether_host(&mut self, addr: &MacAddr, dir: Dir) -> Result<Patches> {
if self.link == LinkType::RawIp {
return Err(Error::CodegenError {
message: "ether host cannot be used with RawIp link type".into(),
});
}
let check_mac = |cg: &mut Codegen, offset: u32, fail: &mut Vec<Patch>| {
let word = u32::from_be_bytes([addr.0[0], addr.0[1], addr.0[2], addr.0[3]]);
cg.push(Insn::ldw_abs(offset));
let i1 = cg.push(Insn::jeq_k(word, 0, 0xff));
fail.push(Patch::Jf(i1));
let half = u32::from_be_bytes([0, 0, addr.0[4], addr.0[5]]);
cg.push(Insn::ldh_abs(offset + 4));
let i2 = cg.push(Insn::jeq_k(half, 0, 0xff));
fail.push(Patch::Jf(i2));
};
let mut p = Patches::default();
match dir {
Dir::Src => check_mac(self, 6, &mut p.failure),
Dir::Dst => check_mac(self, 0, &mut p.failure),
Dir::SrcAndDst => {
check_mac(self, 0, &mut p.failure);
check_mac(self, 6, &mut p.failure);
}
Dir::SrcOrDst => {
let mut src_fails = Vec::new();
check_mac(self, 0, &mut src_fails); let ja_idx = self.push(Insn::ja(0));
let src_start = self.insns.len();
for fp in src_fails {
self.resolve(fp, src_start);
}
check_mac(self, 6, &mut p.failure); p.success.push(Patch::Ja(ja_idx));
}
}
Ok(p)
}
fn emit_ether_multicast(&mut self) -> Result<Patches> {
if self.link == LinkType::RawIp {
return Err(Error::CodegenError {
message: "ether multicast cannot be used with RawIp link type".into(),
});
}
self.push(Insn::ldb_abs(0));
let idx = self.push(Insn::jset_k(0x01, 0, 0xff)); Ok(Patches {
success: vec![],
failure: vec![Patch::Jf(idx)],
})
}
fn emit_ip_broadcast(&mut self) -> Result<Patches> {
let mut p = self.ip4_guard()?;
let dst_off = self.link.net_offset() + 16;
let q = self.check_word(dst_off, 0xffffffff);
p.failure.extend(q.failure);
Ok(p)
}
fn emit_ip_multicast(&mut self) -> Result<Patches> {
let mut p = self.ip4_guard()?;
let dst_off = self.link.net_offset() + 16;
self.push(Insn::ldw_abs(dst_off));
self.push(Insn::and_k(0xf000_0000));
let idx = self.push(Insn::jeq_k(0xe000_0000, 0, 0xff));
p.failure.push(Patch::Jf(idx));
Ok(p)
}
fn emit_ip6_multicast(&mut self) -> Result<Patches> {
let mut p = self.emit_ethertype(0x86dd)?;
let dst_off = self.link.net_offset() + 24;
let q = self.check_byte(dst_off, 0xff);
p.failure.extend(q.failure);
Ok(p)
}
fn emit_vlan(&mut self, id: Option<u16>) -> Result<Patches> {
let mut p = self.emit_ethertype(0x8100)?;
if let Some(vid) = id {
let tci_off = self.link.ether_proto_offset().unwrap_or(14) + 2;
self.push(Insn::ldh_abs(tci_off));
self.push(Insn::and_k(0x0fff)); let idx = self.push(Insn::jeq_k(vid as u32, 0, 0xff));
p.failure.push(Patch::Jf(idx));
}
Ok(p)
}
fn emit_mpls(&mut self, label: Option<u32>) -> Result<Patches> {
if let Some(off) = self.link.ether_proto_offset() {
self.push(Insn::ldh_abs(off));
let i_unicast = self.push(Insn::jeq_k(0x8847, 0xff, 0)); let i_mcast = self.push(Insn::jeq_k(0x8848, 0, 0xff)); let mut p = Patches {
success: vec![Patch::Jt(i_unicast)],
failure: vec![Patch::Jf(i_mcast)],
};
if let Some(lbl) = label {
let lse_off = off + 2;
self.push(Insn::ldw_abs(lse_off));
self.push(Insn::rsh_k(12));
let idx = self.push(Insn::jeq_k(lbl, 0, 0xff));
p.failure.push(Patch::Jf(idx));
}
Ok(p)
} else {
Err(Error::CodegenError {
message: "mpls cannot be matched on RawIp captures".into(),
})
}
}
fn emit_len(&mut self, op: CmpOp, value: u32) -> Result<Patches> {
self.push(Insn {
code: BPF_LD | BPF_LEN,
jt: 0,
jf: 0,
k: 0,
});
self.emit_cmp(op, value)
}
fn emit_byte_access(&mut self, ba: &ByteAccess) -> Result<Patches> {
match ba.layer {
Layer::Raw => {
let off = ba.offset as u32;
self.load_sized(off, ba.size, false);
}
Layer::Net => {
let off = self.link.net_offset() + ba.offset as u32;
self.load_sized(off, ba.size, false);
}
Layer::Trans => {
self.push(Insn::ldx_msh(self.link.net_offset()));
let off = self.link.net_offset() + ba.offset as u32;
self.load_sized(off, ba.size, true); }
}
if let Some(mask) = ba.mask {
self.push(Insn::and_k(mask));
}
let p = self.emit_cmp(ba.op, ba.value)?;
Ok(p)
}
fn load_sized(&mut self, off: u32, size: AccessSize, indirect: bool) {
let insn = match (size, indirect) {
(AccessSize::Byte, false) => Insn::ldb_abs(off),
(AccessSize::Half, false) => Insn::ldh_abs(off),
(AccessSize::Word, false) => Insn::ldw_abs(off),
(AccessSize::Byte, true) => Insn::ldb_abs(off), (AccessSize::Half, true) => Insn::ldh_ind(off),
(AccessSize::Word, true) => Insn::ldw_ind(off),
};
self.push(insn);
}
fn emit_cmp(&mut self, op: CmpOp, value: u32) -> Result<Patches> {
let (insn, fail_field) = match op {
CmpOp::Eq => (Insn::jeq_k(value, 0, 0xff), PatchField::Jf),
CmpOp::Ne => (Insn::jeq_k(value, 0xff, 0), PatchField::Jt), CmpOp::Gt => (Insn::jgt_k(value, 0, 0xff), PatchField::Jf),
CmpOp::Ge => (Insn::jge_k(value, 0, 0xff), PatchField::Jf),
CmpOp::Lt => (Insn::jge_k(value, 0xff, 0), PatchField::Jt), CmpOp::Le => (Insn::jgt_k(value, 0xff, 0), PatchField::Jt), CmpOp::BitAnd => (Insn::jset_k(value, 0, 0xff), PatchField::Jf), };
let idx = self.push(insn);
let patch = match fail_field {
PatchField::Jt => Patch::Jt(idx),
PatchField::Jf => Patch::Jf(idx),
};
Ok(Patches {
success: vec![],
failure: vec![patch],
})
}
}
enum PatchField {
Jt,
Jf,
}
pub fn compile(expr: &Expr, link: LinkType) -> Result<Program> {
let mut cg = Codegen::new(link);
let patches = cg.emit_expr(expr)?;
let accept_idx = cg.insns.len();
cg.push(Insn::ret_k(BPF_ACCEPT));
let drop_idx = cg.insns.len();
cg.push(Insn::ret_k(BPF_DROP));
cg.resolve_all(patches.success, accept_idx);
cg.resolve_all(patches.failure, drop_idx);
Ok(Program::new(cg.insns))
}