cloudflare/wirefilter

repository·master·Indexed 22 days ago

https://github.com/cloudflare/wirefilter

A high-performance filter parser and execution engine for Wireshark-like filters. It allows users to define a Scheme of fields and types, parse filter strings into an AST, and execute them against dynamic runtime data. The project provides WebAssembly (WASM) bindings for use in web environments and Node.js, and supports hardware acceleration via AVX2 and SIMD128.

Tokens
16.4K
Snippets
60
Records
82
Agent score
77%

What's inside wirefilter

  1. View the WebAssembly demo

    master

    A simple demo of the WebAssembly bindings is available in the wasm directory. You can view it by opening index.html directly from your filesystem or by serving it via a local HTTP server.

    # Example using a local HTTP server (e.g., python)
    python3 -m http.server
  2. Set up AFL fuzzing for Wirefilter

    master

    To run fuzz tests for the bytes component, follow these steps:

    1. Install AFL:

      cargo install afl --force
    2. Build the fuzz target (from the fuzz/bytes directory):

      cd fuzz/bytes
      cargo afl build
    3. Run the fuzz test (from the fuzz/bytes directory):

      cargo afl fuzz -i in -o out ../../target/debug/fuzz-bytes

    Troubleshooting: If you encounter the error Looks like the target binary is not instrumented!, delete the existing compiled binary and re-run cargo afl build.

    cargo install afl --force
  3. Build the WebAssembly bindings with wasm-pack

    master

    To build the experimental WebAssembly bindings for the filter parser, you must have wasm-pack installed. Running the build command generates a Node.js package in the pkg directory, which can be used directly or published.

    Prerequisites:

    Build Command: Execute the following in the root directory of the project:

    wasm-pack build -t no-modules
  4. Use Wirefilter to parse, compile, and execute Wireshark-like filters

    master

    Wirefilter is an execution engine for Wireshark-like filters. The workflow consists of three main steps:

    1. Define a Scheme: Create a map of possible filter fields and their types (e.g., Bytes, Int) using the Scheme! macro.
    2. Parse and Compile: Use the Scheme to parse a filter string into an Abstract Syntax Tree (AST), then call .compile() on the AST to produce an executable filter.
    3. Execute: Create an ExecutionContext from the Scheme, populate it with runtime field values using .set_field_value(), and run the filter using .execute(&ctx).

    This allows you to test filter expressions against dynamic runtime data.

    use wirefilter::{ExecutionContext, Scheme};
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        // 1. Define the schema
        let scheme = Scheme! {
            http.method: Bytes,
            http.ua: Bytes,
            port: Int,
        }
        .build();
    
        // 2. Parse and Compile
        let ast = scheme.parse(
            r"#""
                http.method != "POST" &&
                not http.ua matches "(googlebot|facebook)" &&
                port in {80 443}
            ""#,
        )?;
        let filter = ast.compile();
    
        // 3. Execute against context
        let mut ctx = ExecutionContext::new(&scheme);
        ctx.set_field_value(scheme.get_field("http.method").unwrap(), "GET")?;
        ctx.set_field_value(
            scheme.get_field("http.ua").unwrap(),
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0",
        )?;
        ctx.set_field_value(scheme.get_field("port").unwrap(), 443)?;
    
        println!("Filter matches: {:?}", filter.execute(&ctx)?); // true
    
        Ok(())
    }
  5. Perform list membership checks with 'in'

    master

    The in operator allows you to check if a value exists within a predefined list (e.g., $even, $odd, or a custom list).

    Syntax: field in $list_name

    This is commonly used for checking ports, IP addresses, or other discrete values against a set of allowed or disallowed items.

    // Check if a port is in the 'even' list
    tcp.port in $even
    
    // Check if any port in an array is in the 'even' list
    any(tcp.ports[*] in $even)
  6. Comparison operators and syntax

    master

    Wirefilter supports various comparison operators for different data types:

    Boolean

    • ssl (Implicit IsTrue check)

    Ordering (for Int, Ip, and Bytes)

    • ==, !=, <, <=, >, >=
    • Note: Bytes can be compared using hex notation (e.g., 10:20:30...) or string literals (e.g., "example.org").

    Bitwise

    • & (Bitwise AND) for integer fields like tcp.port.

    Membership and Substring

    • in { ... }: Checks if a value is within a set of values, ranges, or CIDR blocks.
      • Example: tcp.port in { 80 443 2082..2083 }
      • Example: ip.addr in { 127.0.0.0/8 ::1 10.0.0.0..10.0.255.255 }
    • contains: Checks if a Bytes field contains a specific substring or byte sequence.
      • Example: http.host contains "abc"
      • Example: http.host contains 6F:72:67
  7. How Filter and FilterValue work

    master

    In Wirefilter, Filter and FilterValue are the primary IR (Intermediate Representation) structures used to execute compiled expressions. They act as a facade for a tree of compiled expressions.

    • Filter<U>: Represents a compiled expression that evaluates to a single boolean value. It is used for filtering logic (e.g., deciding if a packet or record matches a rule).
    • FilterValue<U>: Represents a compiled expression that evaluates to an LhsValue (a specific data value). It is used when the expression is intended to extract or compute a value rather than just a boolean result.

    Both types are bound to a specific Scheme. When executing, they verify that the provided ExecutionContext matches the expected Scheme. If the schemes do not match, they return a SchemeMismatchError.

  8. String escaping and case sensitivity in field expressions

    master

    When writing filter expressions for string fields, you can use different quoting and escaping styles. Wirefilter supports:

    • Raw strings: Prefixed with r (e.g., r"foo") or r# (e.g., r#"foo"#) to handle backslashes and quotes more easily.
    • Quoted strings: Standard double-quoted strings (e.g., "foo") where backslashes must be escaped (e.g., \\).
    • Hexadecimal escapes: Using \xHH (e.g., "\xaa\x22") to represent specific byte values.

    Case Sensitivity:

    • Normal comparison: Typically case-insensitive (e.g., "a" matches "A").
    • Strict comparison: Case-sensitive (e.g., "a" does not match "A").

    Note that the ? character is treated as a literal character and is not a special wildcard in string expressions.

    // Example of different string formats in expressions
    // Raw string with wildcard
    http.host r"foo?*\*"
    
    // Quoted string with hex escape
    http.host "\xaa\x22"
    
    // Case sensitivity examples
    http.host "a" // matches "A" in normal mode
    http.host strict "a" // does not match "A"
  9. Use MapEach with functions and memoization

    master

    When passing a MapEach index to a function, the function is applied to every element of the collection.

    If the function call includes additional arguments that are not part of the mapped collection, those arguments are evaluated once and then reused (memoized) for every element in the iteration. This is particularly efficient for expensive operations like nested function calls.

    Example: concat(http.cookies[*], lowercase(http.host)) will evaluate lowercase(http.host) once and append the result to every cookie in the array.

    // The result of lowercase(http.host) is computed once and applied to all cookies
    concat(http.cookies[*], lowercase(http.host))
  10. Comparison expression structure

    master

    A ComparisonExpr consists of a left-hand side (LHS) index expression and an operator with a right-hand side (RHS) value.

    Common patterns include:

    • Boolean verification: If the LHS is a boolean type, the expression can simply be the field itself (e.g., field_name), which is treated as IsTrue.
    • Ordering: field >= 100 or ip_field < 192.168.1.1.
    • Integer bitwise: field & 0xFF.
    • Byte matching: field contains "abc" or field ~ "regex_pattern".
    • Set membership: field in {1, 2, 3} or field in $my_list.