diff --git a/dc/s2n-quic-dc/src/socket/bpf.rs b/dc/s2n-quic-dc/src/socket/bpf.rs index 5b7594cd94..9b36a08b6e 100644 --- a/dc/s2n-quic-dc/src/socket/bpf.rs +++ b/dc/s2n-quic-dc/src/socket/bpf.rs @@ -11,7 +11,7 @@ use std::{io, net::UdpSocket}; /// High-level algorithm with asm is available here: https://godbolt.org/z/crxT4d53j pub static ROUTER: Program = Program::new(&[ // load the first byte of the packet - ldb(0), + ldb(abs(0)), // mask off the LSBs and(!stream::Tag::MAX as _), // IF: diff --git a/dc/s2n-quic-dc/src/socket/snapshots/s2n_quic_dc__socket__bpf__tests__snapshot_test.snap b/dc/s2n-quic-dc/src/socket/snapshots/s2n_quic_dc__socket__bpf__tests__snapshot_test.snap index 2240748410..002c2c0257 100644 --- a/dc/s2n-quic-dc/src/socket/snapshots/s2n_quic_dc__socket__bpf__tests__snapshot_test.snap +++ b/dc/s2n-quic-dc/src/socket/snapshots/s2n_quic_dc__socket__bpf__tests__snapshot_test.snap @@ -2,8 +2,8 @@ source: dc/s2n-quic-dc/src/socket/bpf.rs expression: ROUTER --- -LDB [0] -AND #192 -JEQ #64,0,1 -RET #0 -RET #1 +l0 : LDB [0] +l1 : AND #192 +l2 : JEQ #64,l3,l4 +l3 : RET #0 +l4 : RET #1 diff --git a/quic/s2n-quic-platform/Cargo.toml b/quic/s2n-quic-platform/Cargo.toml index d3430657cb..eead01fa4f 100644 --- a/quic/s2n-quic-platform/Cargo.toml +++ b/quic/s2n-quic-platform/Cargo.toml @@ -42,6 +42,7 @@ bolero-generator = "0.13" futures = { version = "0.3", features = ["std"] } insta = { version = "1", features = ["json"] } s2n-quic-core = { path = "../s2n-quic-core", features = ["testing"] } +tempfile = "3" tokio = { version = "1", features = ["full"] } tracing = { version = "0.1" } diff --git a/quic/s2n-quic-platform/src/bpf.rs b/quic/s2n-quic-platform/src/bpf.rs index f134c816b2..36640b67ac 100644 --- a/quic/s2n-quic-platform/src/bpf.rs +++ b/quic/s2n-quic-platform/src/bpf.rs @@ -5,6 +5,8 @@ pub mod instruction; #[macro_use] mod common; +#[macro_use] +pub mod ancillary; pub mod cbpf; pub mod ebpf; diff --git a/quic/s2n-quic-platform/src/bpf/ancillary.rs b/quic/s2n-quic-platform/src/bpf/ancillary.rs new file mode 100644 index 0000000000..c490b215a7 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/ancillary.rs @@ -0,0 +1,188 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Ancillary data extensions are used to look up additional information about a packet +//! +//! See + +macro_rules! skf_value { + ($name:ident) => { + (libc::SKF_AD_OFF + libc::$name) as _ + }; +} + +#[derive(Clone, Copy, Debug)] +pub struct Info { + /// The special extension name used in `bpf_asm` + pub extension: &'static str, + /// The `C` interface + pub capi: &'static str, +} + +/// Returns the [`Info`] for a given offset +pub const fn lookup(offset: u32) -> Option { + macro_rules! map { + ($(($value:expr, $extension:expr, $capi:expr),)*) => { + match offset { + $(_ if offset == $value => Some(Info { extension: $extension, capi: $capi }),)* + _ => None, + } + }; + } + + map!( + (skb::protocol(), "proto", "skb->protocol"), + (skb::pkt_type(), "type", "skb->pkt_type"), + (skb::ifindex(), "ifidx", "skb->dev->ifindex"), + (skb::mark(), "mark", "skb->mark"), + (skb::queue_mapping(), "queue", "skb->queue_mapping"), + (skb::dev_type(), "hatype", "skb->dev->type"), + (skb::hash(), "rxhash", "skb->hash"), + (skb::vlan_tci(), "vlan_tci", "skb_vlan_tag_get(skb)"), + (skb::vlan_avail(), "vlan_avail", "skb_vlan_tag_present(skb)"), + (skb::vlan_proto(), "vlan_tpid", "skb->vlan_tproto"), + (payload_offset(), "poff", "payload_offset()"), + (raw_smp_processor_id(), "cpu", "raw_smp_processor_id()"), + (get_random_u32(), "rand", "get_random_u32()"), + ) +} + +pub mod skb { + macro_rules! skb_value { + ($name:ident, $value:ident) => { + #[inline] + pub const fn $name() -> u32 { + skf_value!($value) + } + }; + } + + skb_value!(protocol, SKF_AD_PROTOCOL); + skb_value!(pkt_type, SKF_AD_PKTTYPE); + skb_value!(ifindex, SKF_AD_IFINDEX); + skb_value!(mark, SKF_AD_MARK); + skb_value!(queue_mapping, SKF_AD_QUEUE); + skb_value!(dev_type, SKF_AD_HATYPE); + skb_value!(hash, SKF_AD_RXHASH); + skb_value!(vlan_tci, SKF_AD_VLAN_TAG); + skb_value!(vlan_avail, SKF_AD_VLAN_TAG_PRESENT); + skb_value!(vlan_proto, SKF_AD_VLAN_TPID); +} + +#[inline] +pub const fn payload_offset() -> u32 { + skf_value!(SKF_AD_PAY_OFFSET) +} + +#[inline] +pub const fn raw_smp_processor_id() -> u32 { + skf_value!(SKF_AD_CPU) +} + +#[inline] +pub const fn get_random_u32() -> u32 { + skf_value!(SKF_AD_RANDOM) +} + +macro_rules! impl_ancillary { + () => { + /// Ancillary data extensions are used to look up additional information about a packet + /// + /// See + pub mod ancillary { + use super::{super::ancillary, *}; + + /// Data associated with the socket buffer (skb) + pub mod skb { + use super::{ancillary::skb, *}; + + /// Loads the `skb->len` into the `A` register + pub const fn len() -> K { + // use the dialect-specific instruction to load the skb len + // + // in the case of CBPF, there is a single `Mode` for `LEN` + super::super::len() + } + + /// Loads the `skb->protocol` into the `A` register + pub const fn protocol() -> K { + abs(skb::protocol()) + } + + /// Loads the `skb->pkt_type` into the `A` register + pub const fn pkt_type() -> K { + abs(skb::pkt_type()) + } + + /// Loads the `skb->ifindex` into the `A` register + pub const fn ifindex() -> K { + abs(skb::ifindex()) + } + + /// Loads the `skb->mark` into the `A` register + pub const fn mark() -> K { + abs(skb::mark()) + } + + /// Loads the `skb->queue_mapping` into the `A` register + pub const fn queue_mapping() -> K { + abs(skb::queue_mapping()) + } + + /// Loads the `skb->dev->type` into the `A` register + pub const fn dev_type() -> K { + abs(skb::dev_type()) + } + + /// Loads the `skb->hash` into the `A` register + pub const fn hash() -> K { + abs(skb::hash()) + } + + /// Loads the VLAN Tag value into the `A` register + pub const fn vlan_tci() -> K { + abs(skb::vlan_tci()) + } + + /// Loads the VLAN Tag value into the `A` register + /// + /// This is used for compatibility with the C API + pub const fn vlan_tag_get() -> K { + vlan_tci() + } + + /// Loads if the VLAN Tag is present into the `A` register + pub const fn vlan_avail() -> K { + abs(skb::vlan_avail()) + } + + /// Loads if the VLAN Tag is present into the `A` register + /// + /// This is used for compatibility with the C API + pub const fn vlan_tag_present() -> K { + vlan_avail() + } + + /// Loads the `skb->vlan_proto` (VLAN Protocol) into the `A` register + pub const fn vlan_proto() -> K { + abs(skb::vlan_proto()) + } + } + + /// Loads the payload offset into the `A` register + pub const fn payload_offset() -> K { + abs(ancillary::payload_offset()) + } + + /// Loads the CPU ID into the `A` register + pub const fn raw_smp_processor_id() -> K { + abs(ancillary::raw_smp_processor_id()) + } + + /// Loads a random `u32` into the `A` register + pub const fn get_random_u32() -> K { + abs(ancillary::get_random_u32()) + } + } + }; +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf.rs b/quic/s2n-quic-platform/src/bpf/cbpf.rs index b7e209f809..0eaaf601d9 100644 --- a/quic/s2n-quic-platform/src/bpf/cbpf.rs +++ b/quic/s2n-quic-platform/src/bpf/cbpf.rs @@ -3,6 +3,9 @@ use core::fmt; +#[cfg(all(test, not(miri)))] +mod tests; + pub use super::common::*; pub type Instruction = super::Instruction; pub type Program<'a> = super::Program<'a, Cbpf>; @@ -53,15 +56,20 @@ impl super::instruction::Dialect for Cbpf { f.field("jf", &jf); } - f.field("k", &k).finish() + let prefix = if k == 0 { "" } else { "0x" }; + f.field("k", &format_args!("{prefix}{k:x}")).finish() } - fn display(i: &Instruction, f: &mut fmt::Formatter) -> fmt::Result { + fn display(i: &Instruction, f: &mut fmt::Formatter, line: Option) -> fmt::Result { let code = i.code; let k = i.k; let jt = i.jt; let jf = i.jf; + if let Some(line) = line { + write!(f, "l{line:<4}: ")?; + } + let class = Class::decode(code); match class { @@ -71,7 +79,20 @@ impl super::instruction::Dialect for Cbpf { match mode { Mode::IMM => return write!(f, "{class}{size} #{k}"), - Mode::ABS => return write!(f, "{class}{size} [{k}]"), + Mode::ABS => { + let prefix = if k == 0 { "" } else { "0x" }; + return if let Some(info) = super::ancillary::lookup(k) { + write!( + f, + "{class}{size} {} ; [{prefix}{k:x}] // {}", + info.extension, info.capi + ) + } else { + write!(f, "{class}{size} [{prefix}{k:x}]") + }; + } + Mode::LEN => return write!(f, "{class}{size} len"), + Mode::IND => return write!(f, "{class}{size} [x + {k}]"), _ => {} } } @@ -88,17 +109,37 @@ impl super::instruction::Dialect for Cbpf { let op = Jump::decode(code); let source = Source::decode(code); - return match source { - Source::K => write!(f, "{op} #{k},{jt},{jf}"), - Source::X => write!(f, "{op} x,{jt},{jf}"), - }; + match source { + Source::K => write!(f, "{op} #{k}")?, + Source::X => write!(f, "{op} x")?, + } + + if let Some(line) = line { + let line = line + 1; + let jt = line + jt as usize; + let jf = line + jf as usize; + write!(f, ",l{jt},l{jf}")? + } else { + write!(f, ",{jt},{jf}")? + } + + return Ok(()); } Class::RET => { let source = Source::decode(code); + let size = Size::decode(code); - return match source { - Source::K => write!(f, "{class} #{k}"), - Source::X => write!(f, "{class} x"), + return match (source, size) { + (Source::K, Size::B) if k == 0 => write!(f, "{class} %a"), + (Source::K, _) => write!(f, "{class} #{k}"), + (Source::X, _) => write!(f, "{class} %x"), + }; + } + Class::MISC => { + let misc = Misc::decode(code); + return match misc { + Misc::TAX => write!(f, "tax"), + Misc::TXA => write!(f, "txa"), }; } _ => {} @@ -189,42 +230,28 @@ define!( } ); +define!( + #[mask(0xf0)] + pub enum Misc { + TAX = 0x00, + TXA = 0x80, + } +); + impl_ops!(); impl_ret!(); -#[cfg(test)] -mod tests { - use crate::bpf::cbpf::*; - - static PROGRAM: Program = { - const MAX: u8 = 0b0011_1111; - - Program::new(&[ - // load the first byte of the packet - ldb(0), - // mask off the LSBs - and(!MAX as _), - // IF: - // the control bit is set - jneq(MAX as u32 + 1, 1, 0), - // THEN: - // return a 0 indicating we want to route to the writer socket - ret(0), - // ELSE: - // return a 1 indicating we want to route to the reader socket - ret(1), - ]) - }; - - #[test] - #[cfg_attr(miri, ignore)] - fn static_program_debug() { - insta::assert_debug_snapshot!(PROGRAM); +pub const fn len() -> K { + K { + mode: Mode::LEN, + value: 0, } +} - #[test] - #[cfg_attr(miri, ignore)] - fn static_program_display() { - insta::assert_snapshot!(PROGRAM); - } +pub const fn tax() -> Instruction { + Instruction::raw(Class::MISC as u16 | Misc::TAX as u16, 0, 0, 0) +} + +pub const fn txa() -> Instruction { + Instruction::raw(Class::MISC as u16 | Misc::TXA as u16, 0, 0, 0) } diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_c_literal.snap new file mode 100644 index 0000000000..15fe3dd3e1 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_c_literal.snap @@ -0,0 +1,10 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x20, 0, 0, 0000000000 }, +{ 0x25, 3, 0, 0x00000001 }, +{ 0x25, 0, 2, 0x00000002 }, +{ 0x15, 1, 0, 0x00000003 }, +{ 0x15, 0, 0, 0x00000004 }, +{ 0x06, 0, 0, 0000000000 }, diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_debug.snap new file mode 100644 index 0000000000..e18dab3548 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_debug.snap @@ -0,0 +1,61 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +Program { + instructions: [ + Instruction { + code: 32, + jt: 0, + jf: 0, + class: LD, + size: W, + mode: ABS, + k: 0, + }, + Instruction { + code: 37, + jt: 3, + jf: 0, + class: JMP, + op: JGT, + jt: 3, + k: 0x1, + }, + Instruction { + code: 37, + jt: 0, + jf: 2, + class: JMP, + op: JGT, + jf: 2, + k: 0x2, + }, + Instruction { + code: 21, + jt: 1, + jf: 0, + class: JMP, + op: JEQ, + jt: 1, + k: 0x3, + }, + Instruction { + code: 21, + jt: 0, + jf: 0, + class: JMP, + op: JEQ, + k: 0x4, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0, + }, + ], +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_display.snap new file mode 100644 index 0000000000..f50d3df895 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__comparisons_display.snap @@ -0,0 +1,10 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LD [0] +l1 : JGT #1,l5,l2 +l2 : JGT #2,l3,l5 +l3 : JEQ #3,l5,l4 +l4 : JEQ #4,l5,l5 +l5 : RET #0 diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_c_literal.snap new file mode 100644 index 0000000000..fe466fe4fc --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_c_literal.snap @@ -0,0 +1,8 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x20, 0, 0, 0xfffff008 }, +{ 0x15, 0, 1, 0x0000000d }, +{ 0x06, 0, 0, 0xffffffff }, +{ 0x06, 0, 0, 0000000000 }, diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_debug.snap new file mode 100644 index 0000000000..2ff5f098ca --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_debug.snap @@ -0,0 +1,44 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +Program { + instructions: [ + Instruction { + code: 32, + jt: 0, + jf: 0, + class: LD, + size: W, + mode: ABS, + k: 0xfffff008, + }, + Instruction { + code: 21, + jt: 0, + jf: 1, + class: JMP, + op: JEQ, + jf: 1, + k: 0xd, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0xffffffff, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0, + }, + ], +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_display.snap new file mode 100644 index 0000000000..7060acbcee --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_interface_index_13_display.snap @@ -0,0 +1,8 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LD ifidx ; [0xfffff008] // skb->dev->ifindex +l1 : JEQ #13,l2,l3 +l2 : RET #4294967295 +l3 : RET #0 diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_c_literal.snap new file mode 100644 index 0000000000..273fe65aa5 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_c_literal.snap @@ -0,0 +1,10 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x28, 0, 0, 0x0000000c }, +{ 0x15, 0, 3, 0x00000800 }, +{ 0x30, 0, 0, 0x00000017 }, +{ 0x15, 0, 1, 0x00000006 }, +{ 0x06, 0, 0, 0xffffffff }, +{ 0x06, 0, 0, 0000000000 }, diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_debug.snap new file mode 100644 index 0000000000..cc0f0eaa36 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_debug.snap @@ -0,0 +1,62 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +Program { + instructions: [ + Instruction { + code: 40, + jt: 0, + jf: 0, + class: LD, + size: H, + mode: ABS, + k: 0xc, + }, + Instruction { + code: 21, + jt: 0, + jf: 3, + class: JMP, + op: JEQ, + jf: 3, + k: 0x800, + }, + Instruction { + code: 48, + jt: 0, + jf: 0, + class: LD, + size: B, + mode: ABS, + k: 0x17, + }, + Instruction { + code: 21, + jt: 0, + jf: 1, + class: JMP, + op: JEQ, + jf: 1, + k: 0x6, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0xffffffff, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0, + }, + ], +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_display.snap new file mode 100644 index 0000000000..96fef8d4e2 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_ipv4_tcp_packets_display.snap @@ -0,0 +1,10 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LDH [0xc] +l1 : JEQ #2048,l2,l5 +l2 : LDB [0x17] +l3 : JEQ #6,l4,l5 +l4 : RET #4294967295 +l5 : RET #0 diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_c_literal.snap new file mode 100644 index 0000000000..e8d152067c --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_c_literal.snap @@ -0,0 +1,8 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x20, 0, 0, 0xfffff02c }, +{ 0x15, 0, 1, 0x0000000a }, +{ 0x06, 0, 0, 0xffffffff }, +{ 0x06, 0, 0, 0000000000 }, diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_debug.snap new file mode 100644 index 0000000000..5594fc8501 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_debug.snap @@ -0,0 +1,44 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +Program { + instructions: [ + Instruction { + code: 32, + jt: 0, + jf: 0, + class: LD, + size: W, + mode: ABS, + k: 0xfffff02c, + }, + Instruction { + code: 21, + jt: 0, + jf: 1, + class: JMP, + op: JEQ, + jf: 1, + k: 0xa, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0xffffffff, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0, + }, + ], +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_display.snap new file mode 100644 index 0000000000..35fc2ce265 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__example_vlan_w_id_10_display.snap @@ -0,0 +1,8 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LD vlan_tci ; [0xfffff02c] // skb_vlan_tag_get(skb) +l1 : JEQ #10,l2,l3 +l2 : RET #4294967295 +l3 : RET #0 diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_c_literal.snap new file mode 100644 index 0000000000..9db537a2e8 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_c_literal.snap @@ -0,0 +1,9 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x30, 0, 0, 0000000000 }, +{ 0x54, 0, 0, 0x000000c0 }, +{ 0x15, 0, 1, 0x00000040 }, +{ 0x06, 0, 0, 0000000000 }, +{ 0x06, 0, 0, 0x00000001 }, diff --git a/quic/s2n-quic-platform/src/bpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__static_program_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_debug.snap similarity index 86% rename from quic/s2n-quic-platform/src/bpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__static_program_debug.snap rename to quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_debug.snap index 68e467fd15..43a76b4536 100644 --- a/quic/s2n-quic-platform/src/bpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__static_program_debug.snap +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_debug.snap @@ -1,6 +1,6 @@ --- -source: quic/s2n-quic-platform/src/bpf/cbpf.rs -expression: PROGRAM +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog --- Program { instructions: [ @@ -19,7 +19,7 @@ Program { jf: 0, class: ALU, op: AND, - k: 192, + k: 0xc0, }, Instruction { code: 21, @@ -28,7 +28,7 @@ Program { class: JMP, op: JEQ, jf: 1, - k: 64, + k: 0x40, }, Instruction { code: 6, @@ -46,7 +46,7 @@ Program { class: RET, size: W, mode: IMM, - k: 1, + k: 0x1, }, ], } diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_display.snap new file mode 100644 index 0000000000..9981dbac34 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__first_byte_routing_display.snap @@ -0,0 +1,9 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LDB [0] +l1 : AND #192 +l2 : JEQ #64,l3,l4 +l3 : RET #0 +l4 : RET #1 diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_c_literal.snap new file mode 100644 index 0000000000..485eb0f68f --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_c_literal.snap @@ -0,0 +1,7 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x20, 0, 0, 0xfffff020 }, +{ 0x54, 0, 0, 0x00000001 }, +{ 0x16, 0, 0, 0000000000 }, diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_debug.snap new file mode 100644 index 0000000000..92ef9f076a --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_debug.snap @@ -0,0 +1,34 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +Program { + instructions: [ + Instruction { + code: 32, + jt: 0, + jf: 0, + class: LD, + size: W, + mode: ABS, + k: 0xfffff020, + }, + Instruction { + code: 84, + jt: 0, + jf: 0, + class: ALU, + op: AND, + k: 0x1, + }, + Instruction { + code: 22, + jt: 0, + jf: 0, + class: RET, + size: B, + mode: IMM, + k: 0, + }, + ], +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_display.snap new file mode 100644 index 0000000000..53505d27db --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__hash_routing_display.snap @@ -0,0 +1,7 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LD rxhash ; [0xfffff020] // skb->hash +l1 : AND #1 +l2 : RET %a diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_c_literal.snap new file mode 100644 index 0000000000..cdb0b51879 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_c_literal.snap @@ -0,0 +1,8 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x80, 0, 0, 0000000000 }, +{ 0x25, 1, 0, 0x00000064 }, +{ 0x06, 0, 0, 0000000000 }, +{ 0x06, 0, 0, 0x00000001 }, diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_debug.snap new file mode 100644 index 0000000000..c0083bd71b --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_debug.snap @@ -0,0 +1,44 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +Program { + instructions: [ + Instruction { + code: 128, + jt: 0, + jf: 0, + class: LD, + size: W, + mode: LEN, + k: 0, + }, + Instruction { + code: 37, + jt: 1, + jf: 0, + class: JMP, + op: JGT, + jt: 1, + k: 0x64, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0x1, + }, + ], +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_display.snap new file mode 100644 index 0000000000..f41284ba93 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__len_check_display.snap @@ -0,0 +1,8 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LD len +l1 : JGT #100,l3,l2 +l2 : RET #0 +l3 : RET #1 diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_c_literal.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_c_literal.snap new file mode 100644 index 0000000000..bfac9ca6f6 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_c_literal.snap @@ -0,0 +1,15 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: actual_c_literal +--- +{ 0x01, 0, 0, 0000000000 }, +{ 0x50, 0, 0, 0000000000 }, +{ 0x74, 0, 0, 0x00000006 }, +{ 0x04, 0, 0, 0x00000001 }, +{ 0x25, 3, 0, 0x00000002 }, +{ 0x04, 0, 0, 0x00000001 }, +{ 0x15, 1, 0, 0x00000004 }, +{ 0x04, 0, 0, 0x00000004 }, +{ 0x0c, 0, 0, 0000000000 }, +{ 0x07, 0, 0, 0000000000 }, +{ 0x06, 0, 0, 0000000000 }, diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_debug.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_debug.snap new file mode 100644 index 0000000000..44ba3b0183 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_debug.snap @@ -0,0 +1,100 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +Program { + instructions: [ + Instruction { + code: 1, + jt: 0, + jf: 0, + class: LDX, + size: W, + mode: IMM, + k: 0, + }, + Instruction { + code: 80, + jt: 0, + jf: 0, + class: LD, + size: B, + mode: IND, + k: 0, + }, + Instruction { + code: 116, + jt: 0, + jf: 0, + class: ALU, + op: RSH, + k: 0x6, + }, + Instruction { + code: 4, + jt: 0, + jf: 0, + class: ALU, + op: ADD, + k: 0x1, + }, + Instruction { + code: 37, + jt: 3, + jf: 0, + class: JMP, + op: JGT, + jt: 3, + k: 0x2, + }, + Instruction { + code: 4, + jt: 0, + jf: 0, + class: ALU, + op: ADD, + k: 0x1, + }, + Instruction { + code: 21, + jt: 1, + jf: 0, + class: JMP, + op: JEQ, + jt: 1, + k: 0x4, + }, + Instruction { + code: 4, + jt: 0, + jf: 0, + class: ALU, + op: ADD, + k: 0x4, + }, + Instruction { + code: 12, + jt: 0, + jf: 0, + class: ALU, + op: ADD, + k: 0, + }, + Instruction { + code: 7, + jt: 0, + jf: 0, + class: MISC, + k: 0, + }, + Instruction { + code: 6, + jt: 0, + jf: 0, + class: RET, + size: W, + mode: IMM, + k: 0, + }, + ], +} diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_display.snap b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_display.snap new file mode 100644 index 0000000000..2b23a0d7f1 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__varint_skip_display.snap @@ -0,0 +1,15 @@ +--- +source: quic/s2n-quic-platform/src/bpf/cbpf/tests.rs +expression: prog +--- +l0 : LDX #0 +l1 : LDB [x + 0] +l2 : RSH #6 +l3 : ADD #1 +l4 : JGT #2,l8,l5 +l5 : ADD #1 +l6 : JEQ #4,l8,l7 +l7 : ADD #4 +l8 : ADD x +l9 : tax +l10 : RET #0 diff --git a/quic/s2n-quic-platform/src/bpf/cbpf/tests.rs b/quic/s2n-quic-platform/src/bpf/cbpf/tests.rs new file mode 100644 index 0000000000..fa7eadac95 --- /dev/null +++ b/quic/s2n-quic-platform/src/bpf/cbpf/tests.rs @@ -0,0 +1,417 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use super::*; +use crate::socket::options::{Options, ReusePort}; +use std::{ + io::{self, Write}, + net::UdpSocket, +}; + +macro_rules! test { + ($name:ident,no_run, $asm:expr, $instructions:expr $(,)?) => { + #[test] + fn $name() { + Test { + name: stringify!($name), + asm: $asm, + instructions: $instructions, + checks: Checks { + run: false, + ..Default::default() + }, + } + .check(); + } + }; + ($name:ident, $asm:expr, $instructions:expr $(,)?) => { + #[test] + fn $name() { + Test { + name: stringify!($name), + asm: $asm, + instructions: $instructions, + checks: Default::default(), + } + .check(); + } + }; +} + +test!( + example_ipv4_tcp_packets, + r#" + ldh [12] + jne #0x800, drop + ldb [23] + jneq #6, drop + ret #-1 + drop: ret #0 + "#, + &[ + ldh(abs(12)), + jneq(0x800, 3, 0), + ldb(abs(23)), + jneq(6, 1, 0), + ret(u32::MAX), + ret(0) + ], +); + +test!( + example_interface_index_13, + r#" + ld ifidx + jneq #13, drop + ret #-1 + drop: ret #0 + "#, + &[ + ld(ancillary::skb::ifindex()), + jneq(13, 1, 0), + ret(u32::MAX), + ret(0), + ], +); + +test!( + example_vlan_w_id_10, + r#" + ld vlan_tci + jneq #10, drop + ret #-1 + drop: ret #0 + "#, + &[ + ld(ancillary::skb::vlan_tci()), + jneq(10, 1, 0), + ret(u32::MAX), + ret(0), + ], +); + +const FIRST_BYTE_MAX: u8 = 0b0011_1111; + +test!( + first_byte_routing, + &format!( + r#" + ldb [0] + and #{} + jneq #{}, reader + ret #0 + reader: ret #1 + "#, + !FIRST_BYTE_MAX, + FIRST_BYTE_MAX + 1 + ), + &[ + // load the first byte of the packet + ldb(abs(0)), + // mask off the LSBs + and(!FIRST_BYTE_MAX as _), + // IF: + // the control bit is set + jneq(FIRST_BYTE_MAX as u32 + 1, 1, 0), + // THEN: + // return a 0 indicating we want to route to the writer socket + ret(0), + // ELSE: + // return a 1 indicating we want to route to the reader socket + ret(1), + ], +); + +test!( + hash_routing, + r#" + ld rxhash + and #0x1 + ret %a + "#, + &[ld(ancillary::skb::hash()), and(1), ret_a(),] +); + +test!( + len_check, + r#" + ld len + jgt #100, drop + ret #0 + drop: ret #1 + "#, + &[ld(len()), jgt(100, 1, 0), ret(0), ret(1)] +); + +test!( + comparisons, + r#" + ld [0] + jgt #1, drop + jle #2, drop + jeq #3, drop + jneq #4, drop + drop: ret #0 + "#, + &[ + ld(abs(0)), + jgt(1, 3, 0), + jle(2, 2, 0), + jeq(3, 1, 0), + jneq(4, 0, 0), + ret(0) + ] +); + +// pkt += match *pkt >> 6 { +// 0 => 1, +// 1 => 2, +// 2 => 4, +// 3 => 8, +// }; +test!( + varint_skip, + r#" + ldx #0 ;; initialize the cursor + + varint_skip: + ldb [x + 0] ;; load the current byte from the cursor + rsh #6 ;; shift the first byte into the 2 bit mask + add #1 ;; add 1 to the mask + jgt #2, varint_skip_done ;; if the mask is 1 or 2, we're done + + add #1 ;; increment by 1 - either it's 4 or 8 + jeq #4, varint_skip_done ;; if the mask is 4, we're done + + add #4 ;; increment by 4, getting us to 8 + + varint_skip_done: + add %x ;; add the varint length to the cursor + tax ;; put the new cursor into x + ret #0 ;; return 0 just for the test + "#, + &[ + // initialize the cursor + ldx(imm(0)), + // load the current byte from the cursor + ldb(ind(0)), + // shift the first byte into the 2 bit mask + rsh(6), + // add 1 to the mask + add(1), + // if the mask is 1 or 2, we're done + jgt(2, 3, 0), + // increment by 1 - either it's 4 or 8 + add(1), + // if the mask is 4, we're done + jeq(4, 1, 0), + // increment by 4, getting us to 8 + add(4), + // add the varint length to the cursor + add_x(), + // put the new cursor into x + tax(), + // return 0 just for the test + ret(0), + ] +); + +struct Test<'a> { + name: &'a str, + asm: &'a str, + instructions: &'a [Instruction], + checks: Checks, +} + +impl Test<'_> { + fn check(&self) { + let prog = Program::new(self.instructions); + + insta::assert_snapshot!(format!("{}_display", self.name), prog); + insta::assert_debug_snapshot!(format!("{}_debug", self.name), prog); + let mut actual_c_literal = String::new(); + for i in self.instructions { + use core::fmt::Write; + writeln!(actual_c_literal, "{i:#},").unwrap(); + } + insta::assert_snapshot!(format!("{}_c_literal", self.name), actual_c_literal); + + self.checks.compile( + &[ + // this ensures the instruction functions actually map to the correct asm + self.asm, + // this ensures that the disassembly can reproduce the original asm code + &prog.to_string(), + ], + &actual_c_literal, + ); + self.checks.run(&prog); + } +} + +struct BpfAsm { + path: String, +} + +impl BpfAsm { + /// Tries to resolve a `bpf_asm` binary capable of assembling BPF programs. + /// + /// If none is found, then `None` is returned. + fn resolve() -> Option<&'static Self> { + use std::sync::OnceLock; + + static INSTANCE: OnceLock> = OnceLock::new(); + + INSTANCE + .get_or_init(|| { + let bpf_asm = std::process::Command::new("which") + .arg("bpf_asm") + .output() + .ok()?; + + let bpf_asm = core::str::from_utf8(&bpf_asm.stdout).unwrap_or("").trim(); + if bpf_asm.is_empty() { + return None; + } + + Some(Self { + path: bpf_asm.to_string(), + }) + }) + .as_ref() + } + + fn assemble(&self, asm: &str) -> String { + let mut file = tempfile::NamedTempFile::new().unwrap(); + file.write_all(asm.as_bytes()).unwrap(); + + let output = std::process::Command::new(&self.path) + .arg("-c") + .arg(file.path()) + .output() + .unwrap(); + + assert!(output.status.success(), "{output:?}\nINPUT:\n{asm}"); + let output = core::str::from_utf8(&output.stdout).unwrap(); + + output.to_string() + } +} + +#[derive(Debug)] +struct Checks { + compile: bool, + run: bool, +} + +impl Default for Checks { + fn default() -> Self { + Self { + compile: true, + run: true, + } + } +} + +impl Checks { + fn compile(&self, asm: &[&str], expected: &str) { + if !self.compile { + return; + } + let Some(bpf_asm) = BpfAsm::resolve() else { + eprintln!("`bpf_asm` not found in environment - skipping tests"); + return; + }; + + for asm in asm { + let out = bpf_asm.assemble(asm); + assert_eq!( + expected, out, + "\nINPUT:\n{asm}\nBPF_OUT:\n{out}\nEXPECTED:\n{expected}\n" + ); + } + } + + fn run(&self, prog: &Program) { + if !self.run { + return; + } + + use std::sync::OnceLock; + + static PAIR: OnceLock> = OnceLock::new(); + + let pair = PAIR.get_or_init(|| { + udp_pair().and_then(|(a, b)| { + probe(&a)?; + probe(&b)?; + Ok((a, b)) + }) + }); + + let Ok((a, b)) = pair else { + eprintln!("skipping tests due to environment not supporting bpf programs"); + return; + }; + + for socket in [a, b] { + prog.attach(socket).unwrap(); + } + } +} + +fn udp_pair() -> io::Result<(UdpSocket, UdpSocket)> { + let mut options = Options { + gro: false, + blocking: false, + // set the reuse port option after binding to avoid port collisions + reuse_port: ReusePort::AfterBind, + ..Default::default() + }; + + let writer = options.build_udp()?; + + // bind the sockets to the same address + options.addr = writer.local_addr()?; + // now that we have a concrete port from the OS, we set the option before the bind call + options.reuse_port = ReusePort::BeforeBind; + + let reader = options.build_udp()?; + + Ok((writer, reader)) +} + +/// Probe for BPF support in the environment +fn probe(socket: &F) -> io::Result<()> { + use libc::{sock_filter, sock_fprog}; + + // $ echo 'ret #0' | bfp_asm -c + // { 0x06, 0, 0, 0000000000 }, + let instructions = [sock_filter { + code: 0x06, + jt: 0, + jf: 0, + k: 0, + }]; + + let prog = sock_fprog { + filter: instructions.as_ptr() as *const _ as *mut _, + len: instructions.len() as _, + }; + + let ret = unsafe { + libc::setsockopt( + socket.as_raw_fd(), + libc::SOL_SOCKET, + ::SOCKOPT as _, + &prog as *const _ as *const _, + core::mem::size_of::() as _, + ) + }; + + if ret < 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} diff --git a/quic/s2n-quic-platform/src/bpf/common.rs b/quic/s2n-quic-platform/src/bpf/common.rs index 14c3981a81..4a7d85136c 100644 --- a/quic/s2n-quic-platform/src/bpf/common.rs +++ b/quic/s2n-quic-platform/src/bpf/common.rs @@ -12,26 +12,26 @@ define!( macro_rules! impl_ld { () => { - impl_ld!(ld, ldk, W); - impl_ld!(ldb, ldbk, B); - impl_ld!(ldh, ldhk, H); + impl_ld!(ld, ldx, W); + impl_ld!(ldb, ldbx, B); + impl_ld!(ldh, ldhx, H); }; - ($lower:ident, $k:ident, $size:ident) => { - pub const fn $lower(value: u32) -> Instruction { + ($ld:ident, $x:ident, $size:ident) => { + pub const fn $ld(k: K) -> Instruction { Instruction::raw( - Mode::ABS as u16 | Class::LD as u16 | Size::$size as u16, + k.mode as u16 | Class::LD as u16 | Size::$size as u16, 0, 0, - value, + k.value, ) } - pub const fn $k(value: u32) -> Instruction { + pub const fn $x(k: K) -> Instruction { Instruction::raw( - Mode::IMM as u16 | Class::LD as u16 | Source::K as u16 | Size::$size as u16, + k.mode as u16 | Class::LDX as u16 | Size::$size as u16, 0, 0, - value, + k.value, ) } }; @@ -39,16 +39,16 @@ macro_rules! impl_ld { macro_rules! impl_alu { () => { - impl_alu!(add, addx, ADD); - impl_alu!(sub, subx, SUB); - impl_alu!(mul, mulx, MUL); - impl_alu!(div, divx, DIV); - impl_alu!(rem, remx, MOD); - impl_alu!(and, andx, AND); - impl_alu!(or, orx, OR); - impl_alu!(xor, xorx, XOR); - impl_alu!(lsh, lshx, LSH); - impl_alu!(rsh, rshx, RSH); + impl_alu!(add, add_x, ADD); + impl_alu!(sub, sub_x, SUB); + impl_alu!(mul, mul_x, MUL); + impl_alu!(div, div_x, DIV); + impl_alu!(rem, rem_x, MOD); + impl_alu!(and, and_x, AND); + impl_alu!(or, or_x, OR); + impl_alu!(xor, xor_x, XOR); + impl_alu!(lsh, lsh_x, LSH); + impl_alu!(rsh, rsh_x, RSH); }; ($lower:ident, $x:ident, $upper:ident) => { pub const fn $lower(value: u32) -> Instruction { @@ -82,11 +82,11 @@ macro_rules! impl_jmp { ) } - impl_jmp!(jgt, jgtx, JGT, false); - impl_jmp!(jle, jlex, JGT, true); - impl_jmp!(jeq, jeqx, JEQ, false); - impl_jmp!(jneq, jneqx, JEQ, true); - impl_jmp!(jset, jsetx, JSET, false); + impl_jmp!(jgt, jgt_x, JGT, false); + impl_jmp!(jle, jle_x, JGT, true); + impl_jmp!(jeq, jeq_x, JEQ, false); + impl_jmp!(jneq, jneq_x, JEQ, true); + impl_jmp!(jset, jset_x, JSET, false); }; ($lower:ident, $x:ident, $upper:ident, $invert:expr) => { pub const fn $lower(value: u32, mut jt: u8, mut jf: u8) -> Instruction { @@ -127,16 +127,48 @@ macro_rules! impl_ret { Instruction::raw(Class::RET as u16 | Source::K as u16, 0, 0, value) } - pub const fn retx() -> Instruction { + pub const fn ret_x() -> Instruction { Instruction::raw(Class::RET as u16 | Source::X as u16, 0, 0, 0) } + + pub const fn ret_a() -> Instruction { + Instruction::raw(Class::RET as u16 | Size::B as u16, 0, 0, 0) + } }; } macro_rules! impl_ops { () => { + #[derive(Clone, Copy)] + pub struct K { + pub mode: Mode, + pub value: u32, + } + + pub const fn abs(value: u32) -> K { + K { + mode: Mode::ABS, + value, + } + } + + pub const fn imm(value: u32) -> K { + K { + mode: Mode::IMM, + value, + } + } + + pub const fn ind(value: u32) -> K { + K { + mode: Mode::IND, + value, + } + } + impl_ld!(); impl_alu!(); impl_jmp!(); + impl_ancillary!(); }; } diff --git a/quic/s2n-quic-platform/src/bpf/ebpf.rs b/quic/s2n-quic-platform/src/bpf/ebpf.rs index 2b1b2590a4..4a1603ec6f 100644 --- a/quic/s2n-quic-platform/src/bpf/ebpf.rs +++ b/quic/s2n-quic-platform/src/bpf/ebpf.rs @@ -55,12 +55,16 @@ impl super::instruction::Dialect for Ebpf { f.field("k", &k).finish() } - fn display(i: &Instruction, f: &mut fmt::Formatter) -> fmt::Result { + fn display(i: &Instruction, f: &mut fmt::Formatter, line: Option) -> fmt::Result { let code = i.code; let k = i.k; let jt = i.jt; let jf = i.jf; + if let Some(line) = line { + write!(f, "l{line:<4}: ")?; + } + let class = Class::decode(code); match class { @@ -70,7 +74,18 @@ impl super::instruction::Dialect for Ebpf { match mode { Mode::IMM => return write!(f, "{class}{size} #{k}"), - Mode::ABS => return write!(f, "{class}{size} [{k}]"), + Mode::ABS => { + let prefix = if k == 0 { "" } else { "0x" }; + return if let Some(info) = super::ancillary::lookup(k) { + write!( + f, + "{class}{size} {} ; [{prefix}{k:x}] // {}", + info.extension, info.capi + ) + } else { + write!(f, "{class}{size} [{prefix}{k:x}]") + }; + } _ => {} } } @@ -87,10 +102,21 @@ impl super::instruction::Dialect for Ebpf { let op = Jump::decode(code); let source = Source::decode(code); - return match source { - Source::K => write!(f, "{op} #{k},{jt},{jf}"), - Source::X => write!(f, "{op} x,{jt},{jf}"), - }; + match source { + Source::K => write!(f, "{op} #{k}")?, + Source::X => write!(f, "{op} x")?, + } + + if let Some(line) = line { + let line = line + 1; + let jt = line + jt as usize; + let jf = line + jf as usize; + write!(f, ",l{jt},l{jf}")? + } else { + write!(f, ",{jt},{jf}")? + } + + return Ok(()); } _ => {} } @@ -205,3 +231,8 @@ define!( ); impl_ops!(); + +pub const fn len() -> K { + // still need to figure out what the API is for eBPF - cBPF has a dedicated Mode + todo!() +} diff --git a/quic/s2n-quic-platform/src/bpf/instruction.rs b/quic/s2n-quic-platform/src/bpf/instruction.rs index 1a0578c750..0ff382c8b5 100644 --- a/quic/s2n-quic-platform/src/bpf/instruction.rs +++ b/quic/s2n-quic-platform/src/bpf/instruction.rs @@ -9,7 +9,11 @@ pub trait Dialect: Sized { const SOCKOPT: libc::c_int; fn debug(instruction: &Instruction, f: &mut fmt::Formatter) -> fmt::Result; - fn display(instruction: &Instruction, f: &mut fmt::Formatter) -> fmt::Result; + fn display( + instruction: &Instruction, + f: &mut fmt::Formatter, + idx: Option, + ) -> fmt::Result; } #[derive(Clone, Copy)] @@ -35,13 +39,14 @@ impl fmt::Display for Instruction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if f.alternate() { // write the C literal format if requested + let k_prefix = if self.k == 0 { "00" } else { "0x" }; write!( f, - "{{ {}, {}, {}, {} }}", + "{{ 0x{:0>2x}, {:>2}, {:>2}, {k_prefix}{:0>8x} }}", self.code, self.jt, self.jf, self.k ) } else { - D::display(self, f) + D::display(self, f, None) } } } diff --git a/quic/s2n-quic-platform/src/bpf/program.rs b/quic/s2n-quic-platform/src/bpf/program.rs index e5f7a71a81..e64aa94ed7 100644 --- a/quic/s2n-quic-platform/src/bpf/program.rs +++ b/quic/s2n-quic-platform/src/bpf/program.rs @@ -54,8 +54,9 @@ impl fmt::Debug for Program<'_, D> { impl fmt::Display for Program<'_, D> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for inst in self.instructions { - writeln!(f, "{inst}")?; + for (idx, inst) in self.instructions.iter().enumerate() { + D::display(inst, f, Some(idx))?; + writeln!(f)?; } Ok(()) } diff --git a/quic/s2n-quic-platform/src/bpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__static_program_display.snap b/quic/s2n-quic-platform/src/bpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__static_program_display.snap deleted file mode 100644 index e565273adf..0000000000 --- a/quic/s2n-quic-platform/src/bpf/snapshots/s2n_quic_platform__bpf__cbpf__tests__static_program_display.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: quic/s2n-quic-platform/src/bpf/cbpf.rs -expression: PROGRAM ---- -LDB [0] -AND #192 -JEQ #64,0,1 -RET #0 -RET #1