Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 215 additions & 68 deletions beeper/src/dfa.rs

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions beeper/src/h1/parser.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ struct h1_action {
u8 mid;
};

// these restrictions are needed to make the verifier happy. All three are
// masked onto an index, so all three have to be powers of two.
// these restrictions are needed to make the verifier happy. `MAX_STATES` and
// `MAX_ACTIONS` are masked onto an index, so both have to be powers of two.
#define MAX_STATES 512
#define MAX_TRANS 128
#define MAX_ACTIONS 256
#define MAX_TRANS 257
#define ANY_TRANS 256

// The transition table of the DFA, indexed by state and input byte, and the
// actions its transitions carry. User space fills both in before the program is
Expand All @@ -65,11 +66,12 @@ static __always_inline struct h1_action _action(u16 id) {
// none either, back to `s_any`.
static __always_inline void _next(u16 state, u8 input, u16 *next_state, u16 *action) {
state &= MAX_STATES - 1;
input &= MAX_TRANS - 1;

// `input` is a byte and the row holds a column for every one of them, so it
// needs no bound of its own
struct trans t = s2ts[state][input];
if (t.state == 0 && t.action == 0) {
t = s2ts[state]['*'];
t = s2ts[state][ANY_TRANS];
if (t.state == 0 && t.action == 0) {
*next_state = s_any;
*action = 0;
Expand All @@ -93,9 +95,10 @@ static __always_inline void _next(u16 state, u8 input, u16 *next_state, u16 *act
// Returns the number of bytes it consumed once the DFA is done, or minus the
// number of bytes it looked at if the data ran out first.
static __always_inline int _parse_from(u8 *data, u8 *data_end, u16 start, struct hdr_match *ms, u32* cidx, u16* s, u16 *null_prefix) {
u32 len = (u32)(data_end - data) & MAX_BYTES;
u32 len = (u32)(data_end - data);
bpf_clamp_uminmax(len, 0, MAX_BYTES);

if (len-start == 0) {
if (start >= len) {
return 0;
}

Expand Down
105 changes: 72 additions & 33 deletions beeper/src/h1/parser.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unused_imports)]
use crate::{
Dfa, MatchId, autoload_and_attach,
dfa::{ANY_STATE, INIT_STATE},
dfa::{ANY_STATE, INIT_STATE, fmt_input},
h1::action::Action,
header::{METHOD, PATH, STATUS},
};
Expand All @@ -15,8 +15,12 @@ use xbpf::libbpf::{
skel::{OpenSkel, Skel, SkelBuilder},
};

/// The sequence terminating the lines of a message.
const CRLF: &str = "\r\n";
const CR: &str = "\r";
const LF: &str = "\n";

/// The number of ranges a parser can be configured to capture. Must stay in
/// sync with `MAX_MATCHES` of beeper.h.
const MAX_MATCHES: u16 = 32;

/// A parser for HTTP/1.x messages.
///
Expand Down Expand Up @@ -112,7 +116,17 @@ impl Parser {
}

/// Returns an unused match id.
///
/// # Panics
///
/// Panics if the parser is already configured with [`MAX_MATCHES`] matches,
/// as the parser program has no room to tell one more apart from them.
fn new_match(&mut self) -> MatchId {
assert!(
self.num_matches < MAX_MATCHES,
"a parser captures at most {MAX_MATCHES} ranges"
);

let id = MatchId(self.num_matches);
self.num_matches += 1;
id
Expand All @@ -136,19 +150,31 @@ impl Parser {
}

let mid = self.new_match();
self.dfa
.start_pattern(ANY_STATE)
.push_ci(CRLF)
let mut pattern = self.dfa.start_pattern(ANY_STATE);
pattern
.push(LF)
.push_ci(name.as_str())
.push_optional("\t")
.push_optional(" ")
.push_optional("\t", true)
.push_optional(" ", true)
.push_ci(":")
.push_optional("\t")
.push_optional(" ")
.with(Action::StartCapture(mid))
.push_optional("\t", true)
.push_optional(" ", true)
.with(Action::StartCapture(mid));

// the value begins here, and it may be empty
let value = pattern.state();
pattern
.push_any(1..)
.with(Action::EndCapture(mid))
.restart_with(CRLF);
.push_optional(CR, false)
.restart_with(LF);

// an empty value ends its line where it would have begun, and there is
// nothing in it to capture
self.dfa
.start_pattern(value)
.push_optional(CR, false)
.restart_with(LF);

self
}
Expand All @@ -165,7 +191,10 @@ impl Parser {
self.dfa
.start_pattern(INIT_STATE)
.with(Action::StartCapture(mid))
.push(&format!("PRI * HTTP/2.0{}{}SM{}{}", CRLF, CRLF, CRLF, CRLF))
.push(&format!(
"PRI * HTTP/2.0{}{}{}{}SM{}{}{}{}",
CR, LF, CR, LF, CR, LF, CR, LF
))
.with(Action::EndCaptureAndDone(mid));

self
Expand All @@ -176,8 +205,10 @@ impl Parser {
fn done_on_hdr_end(mut self) -> Parser {
self.dfa
.start_pattern(ANY_STATE)
.push(CRLF)
.push(CRLF)
.push_optional(CR, false)
.push(LF)
.push_optional(CR, false)
.push(LF)
.with(Action::Done);

self
Expand All @@ -204,7 +235,8 @@ impl Parser {
.push(" ")
.push_any(1..)
.push_ci(" HTTP/1.1")
.restart_with(CRLF);
.push_optional(CR, false)
.restart_with(LF);
} else if name == &PATH {
let mid = self.new_match();
self.dfa
Expand All @@ -215,7 +247,8 @@ impl Parser {
.push_any(1..)
.with(Action::EndCapture(mid))
.push_ci(" HTTP/1.1")
.restart_with(CRLF);
.push_optional(CR, false)
.restart_with(LF);
} else {
panic!(
"capture_status_line_hdr called with unsupported header name: {}",
Expand All @@ -238,7 +271,8 @@ impl Parser {
.push_any(3..=3)
.with(Action::EndCapture(mid))
.push_any(1..)
.restart_with(CRLF);
.push_optional(CR, false)
.restart_with(LF);

self
}
Expand All @@ -265,9 +299,9 @@ impl Parser {
let skel_builder = ParserSkelBuilder::default();
let mut open_obj: MaybeUninit<OpenObject> = MaybeUninit::uninit();
let mut open_skel = skel_builder.open(&mut open_obj)?;
if tracing::event_enabled!(Level::TRACE) {
if tracing::event_enabled!(target: "bpf", Level::TRACE) {
open_skel.progs.parse_msg.set_log_level(1);
open_skel.progs.parse_buf.set_log_level(1);
open_skel.progs.parse_skb.set_log_level(1);
open_skel.progs.parse_buf.set_log_level(1);
}

Expand Down Expand Up @@ -331,30 +365,35 @@ impl Parser {
);
}

let num_edges = self.dfa.num_edges();
if num_edges > data.a2as.len() {
bail!(
"the patterns take {} edges, the parser holds {}",
num_edges,
data.a2as.len()
);
}

// action index 0 is reserved for the noop action
let mut action_idx = HashMap::new();
action_idx.insert(None, 0usize);

for (from, input, to, action) in self.dfa.iter_transitions() {
let new_action_idx = action_idx.len();
let action = action_idx.entry(action).or_insert(new_action_idx);
let action = *action as u16;
let action = *action_idx.entry(action).or_insert(new_action_idx);
if action >= data.a2as.len() {
bail!(
"the patterns take more actions than the {} the parser holds",
data.a2as.len()
);
}

let action = action as u16;
let input = input as usize;
if input >= data.s2ts[0].len() {
bail!("the patterns read inputs the parser has no column for: {input}");
}

trace!(
"inject; from={} to={} input={} action={}",
from.0, to.0, input as char, action
from.0,
to.0,
fmt_input(input as u16),
action
);

data.s2ts[from.0 as usize][input as usize] = trans {
data.s2ts[from.0 as usize][input] = trans {
state: to.0,
action,
};
Expand Down
15 changes: 8 additions & 7 deletions beeper/src/h2/hpack.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Dfa, h2::action::*};
use crate::{Dfa, dfa::Input, h2::action::*};
use std::collections::HashMap;

/// Builds the transitions of every field representation of RFC 7541 into
Expand All @@ -15,7 +15,8 @@ pub fn dfa() -> Dfa<Action> {
/// Inserts the transitions of the first byte of a representation, see section 6
/// of RFC 7541.
fn insert_field_row(dfa: &mut Dfa<Action>) {
let mut edge = |input: u8, to, action| dfa.insert_edge(S_FIELD, input, to, Some(action));
let mut edge =
|input: u8, to, action| dfa.insert_edge(S_FIELD, Input::from(input), to, Some(action));

// an indexed field, 7 bit prefix. The index 0 is not used
edge(0x80, S_DEAD, Action::new(Kind::Err, 0, 0));
Expand Down Expand Up @@ -91,11 +92,11 @@ fn insert_length_rows(dfa: &mut Dfa<Action>) {
for (base, flags, cont) in [(0x00u8, 0, cont), (0x80u8, F_HUFF, cont_huff)] {
for len in 0..0x7F {
let action = Action::new(kind, len, flags);
dfa.insert_edge(from, base | len as u8, to, Some(action));
dfa.insert_edge(from, Input::from(base | len as u8), to, Some(action));
}

let action = Action::new(Kind::IntStart, 0x7F, 0);
dfa.insert_edge(from, base | 0x7F, cont, Some(action));
dfa.insert_edge(from, Input::from(base | 0x7F), cont, Some(action));
}
}
}
Expand All @@ -118,10 +119,10 @@ fn insert_continuation_rows(dfa: &mut Dfa<Action>) {
for (from, kind, to, flags) in rows {
for input in 0..0x80u8 {
let action = Action::new(kind, 0, flags | F_CONT);
dfa.insert_edge(from, input, to, Some(action));
dfa.insert_edge(from, Input::from(input), to, Some(action));

let action = Action::new(Kind::IntCont, 0, 0);
dfa.insert_edge(from, 0x80 | input, from, Some(action));
dfa.insert_edge(from, Input::from(0x80 | input), from, Some(action));
}
}
}
Expand Down Expand Up @@ -250,7 +251,7 @@ mod tests {
let dfa = dfa();

for state in representation_states() {
let inputs: HashSet<u8> = dfa
let inputs: HashSet<Input> = dfa
.iter_transitions()
.filter(|(from, ..)| *from == state)
.map(|(_, input, _, _)| input)
Expand Down
Loading
Loading