Wirefilter: a Rust engine that compiles Wireshark-style filters into an IR
An execution engine for Wireshark-like filters
At a glance
- What is it?
- Wirefilter is a Rust library that parses Wireshark-like filter expressions into an AST, compiles them into an executable IR, and runs them against field values you supply. It suits anyone who needs packet or field level filtering in their own product, and it ships as a workspace with an engine, a C FFI, a WebAssembly target and fuzz targets.
- Who is it for?
- Wirefilter fits a Rust codebase that needs filtering over a fixed set of fields, whether that is a packet filter, a firewall rule matcher or a query over captured traffic, because the scheme is declared in code and the same filter text can be reused across the engine, the FFI and the WebAssembly build.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- cloudflare/wirefilter1,177 ★ · +12
| Repository | 2026-10-19 | 2026-11-07 |
|---|---|---|
| cloudflare/wirefilter | 1.2k | 1.2k |
12 Stars in the last 19 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Three public APIs: parse, compile, execute
Wirefilter is an execution engine for Wireshark-like filters, and the design is a straight pipeline with a public API at each stage. There are APIs for parsing filter syntax, for compiling the parsed form into an executable IR, and for executing filters against provided values.
That staging is the point of the library. A caller that only needs to check whether a filter string is valid can stop after parsing. A caller that wants to filter a stream of packets parses once and compiles once, then executes the compiled filter many times against a context of field values. A caller with a user interface can parse the text as they type and report syntax errors before anything is executed.
The runtime model is an `ExecutionContext` built from a `Scheme`. The scheme is a set of named fields with types, and it is the boundary of what a filter can talk about. Anything not declared in the scheme does not exist as far as the engine is concerned, so the scheme is the type signature of the whole filter language in your application.
Values are set individually on the context before execution, and the same context can be reused with a changed value to test the filter again, which is exactly what the example in the documentation does with a port value.
The project is written in Rust, licensed under the MIT licence, and the repository links to a Cloudflare blog post about building fast interpreters in Rust, which is where the design reasoning behind the IR and the execution model is written up.
The scheme macro is where you define the whole filter surface
A scheme declares the fields a filter may reference, each with a type. The macro form used in the example is:
let scheme = Scheme! {
http.method: Bytes,
http.ua: Bytes,
port: Int,
}
.build();Two things are happening here. The field names carry structure, using a dot to make a namespace, which is what lets a filter say `http.method` without colliding with a top-level `method`. And the types are constrained, so the engine knows how to compare and match each field, rather than treating everything as an opaque string.
The scheme is a builder. The macro produces something you call `.build()` on, and the result is the object that both parses filters and creates execution contexts. That is a deliberate inversion of the usual arrangement: the schema is the object you program against, and the filter text is data that arrives later.
The example's filter then uses those three fields, with a comparison on the method, a negated regex match on the user agent, and a set membership test on the port:
http.method != "POST" &&
not http.ua matches "(googlebot|facebook)" &&
port in {80 443}The syntax is close to Wireshark's display filter language rather than identical to it, and the operator set includes the `matches` keyword for regular expressions, a `not` prefix, `&&` for conjunction and a set literal in braces.
Execution is a context lookup, so reuse is cheap
The compiled filter is executed against an `ExecutionContext`, and the values live in that context rather than being passed per call. The example sets three values, executes, then amends one and executes again:
let mut ctx = ExecutionContext::new(&scheme);
ctx.set_field_value(scheme.get_field("http.method").unwrap(), "GET")?;
ctx.set_field_value(scheme.get_field("port").unwrap(), 443)?;
println!("Filter matches: {:?}", filter.execute(&ctx)?); // trueThe shape of this matters for throughput. Setting a field value by name on every packet would mean a lookup per field per packet, and the code shows a different pattern: look the field up once with `scheme.get_field(...)` and set the value on the context. Whether the engine caches that lookup internally is not something the documentation states, so a caller writing a hot loop should hold onto the resolved field handles rather than re-resolving names, and that is advice the API shape supports rather than a claim about the internals.
The `execute` call returns a `Result`, so an error in a field value, such as a string where an integer is expected, surfaces as an error rather than a panic. The result of a successful execution is a boolean.
One detail of the example is worth noting as a design signal. The same compiled filter is executed twice against one context whose port value is amended, and the answer changes from true to false. Nothing is recompiled, and nothing is mutated inside the filter. The compiled artifact is immutable and the state lives on the side.
One workspace, four delivery shapes
The Cargo manifest defines a workspace with resolver version 2 and six members: `engine`, `ffi`, `fuzz/bytes`, `fuzz/map-keys`, `fuzz/raw-string` and `wasm`.
The `engine` directory holds the core crate, published on crates.io as `wirefilter-engine`, which is the package the README badge points at. The `ffi` directory is a C foreign function interface, so the engine can be linked into a C or C++ codebase. The `wasm` member is a WebAssembly build using `wasm-bindgen`, and the dependency list includes `js-sys` and `serde-wasm-bindgen`, which is what a JavaScript-facing build needs.
The three fuzz members are targets rather than products. `fuzz/bytes`, `fuzz/map-keys` and `fuzz/raw-string` each attack a different part of the input handling, and the `bytes` one is what the documentation walks through.
The workspace metadata shows the release shape. The version is 0.7.0, the edition is 2024, and `publish = true`, so the crates are published rather than kept internal. A 0.x version means the API is not covered by the usual stability expectations of a 1.0 release, which is a factor to weigh when the engine sits in a path you cannot easily change.
The release profile sets `lto = true` and `panic = "unwind"`, while the dev profile keeps `panic = "unwind"` as well, so the library unwinds rather than aborting on panic, and a caller can catch an unwind at the boundary if it needs to.
The dependency list is where the type system shows. `regex-automata` at 0.4.18 handles the regular expression matching for the `matches` operator, `cidr` at 0.2.3 with the serde feature suggests address and prefix types are first class in the scheme, `wildcard` at 0.3.0 covers glob style patterns, and `arcstr` provides cheap string handling. `thiserror` and `erased-serde` handle errors and serialization, `num_enum` maps between integers and enum variants, and `memchr` and `sliceslice` handle byte searching.
Fuzzing is documented as a first class practice
A filter engine that parses untrusted text is exactly the kind of code where a fuzzer finds things a test suite does not, and the repository carries three fuzz targets for that reason.
The workflow is documented step by step. Install the fuzzer:
cargo install afl --forceBuild the bytes fuzz target:
cd fuzz/bytes
cargo afl buildThen run it, from inside the same directory:
cargo afl fuzz -i in -o out ../../target/debug/fuzz-bytesThe documentation even names the error you are likely to hit. If the run complains that the target binary is not instrumented, the fix is to delete the compiled binary and rebuild with `cargo afl build`.
Three separate targets is a reasonable signal about where the complexity sits. `bytes` points at the byte handling, `map-keys` at the key handling of the scheme's maps, and `raw-string` at raw string parsing. A parser with a `matches` operator, a set literal syntax and a macro-built schema has a lot of surface where a malformed input can reach an assertion, and a corpus in each of those directories is the practical way to find out.
For a team planning to accept filter text from users, the presence of these targets is a reasonable signal that the maintainers thought about adversarial input, and running them is the cheapest way to find out whether that effort holds up for your own fork.
Where the engine stops and what the alternatives are
The first limit is the filter language itself. Wirefilter implements a Wireshark-like syntax, and the documentation does not claim the full Wireshark display filter language with its protocol-aware field registry. Your scheme is what defines the available fields, so the engine's reach is your scheme's reach, and the field types available are fixed by the library rather than by the protocol you are filtering.
The second limit is the interface. This is a library with an FFI and a WebAssembly build, not a packet capture stack. It does not read pcap files or sniff traffic, so you still need a source of packets and a way to map their fields into the scheme. A team that thinks Wirefilter replaces Wireshark's dissection engine is on the wrong track; it replaces the filter evaluation step.
The alternative approach is to write the filter evaluation yourself, which is what most teams end up doing with a hand-rolled string matcher, and the difference in approach shows up in what you inherit. A hand-rolled matcher gives you exactly the operators you need and nothing else, but every operator, every precedence rule and every error case is code you own and test. Wirefilter gives you a parsed AST you can inspect, an IR you can compile once, and a separation between the syntax and the execution, plus a fuzz suite and an IR to inspect. The cost is a 0.x API, a dependency on the scheme discipline, and a build that pulls in a regular expression engine, a CIDR crate and a glob matcher whether you use them or not.
The project is not archived, the last push was on 2026-10-19, and there are no GitHub releases, so versions come from crates.io rather than from release tags.
Editorial conclusion
Wirefilter fits a Rust codebase that needs filtering over a fixed set of fields, whether that is a packet filter, a firewall rule matcher or a query over captured traffic, because the scheme is declared in code and the same filter text can be reused across the engine, the FFI and the WebAssembly build. Skip it if you need the full Wireshark display filter language, since the engine implements a Wireshark-like syntax rather than the complete one, and do not expect a stable API contract, because the workspace version is 0.7.0 and the crates are published under a 0.x version. Before you build on it, decide which entry point you want, the Rust crate, the C FFI or the WebAssembly module, and read the `engine/` directory for the field type list, since the supported field types are what your scheme is limited to.
Frequently asked questions
What is Wirefilter and what does it do?
It is an execution engine for Wireshark-like filters in Rust, with public APIs to parse filter syntax into an AST, compile that into an executable IR, and execute the compiled filter against field values you provide.
How do I define which fields a Wirefilter filter can use?
You declare a scheme with the `Scheme!` macro, naming each field and its type, such as `http.method: Bytes` and `port: Int`, and call `.build()` on the result. The scheme is what both parsing and execution work against.
How do I run the Wirefilter fuzz tests?
Install afl with `cargo install afl --force`, build the target with `cargo afl build` inside the target directory such as `fuzz/bytes`, then run `cargo afl fuzz -i in -o out ../../target/debug/fuzz-bytes` from that same directory.
Can Wirefilter be used outside Rust?
Yes. The Cargo workspace includes an `ffi` member for a C foreign function interface and a `wasm` member built on wasm-bindgen, so the engine can be linked into C code or compiled to WebAssembly.
What licence is Wirefilter under?
MIT. The README states the project is licensed under the MIT licence and points to the LICENSE file for details.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cloudflare-wirefilter)Star history chart for your README
[](https://hysenlabs.com/projects/cloudflare-wirefilter)Was this useful?