Skip to content

1. Overview

Mark Bednarczyk edited this page Nov 12, 2024 · 3 revisions

JNetRuntime BPF Compiler and VM Overview

This page provides an overview of the supported filter expression dialects and how to use the BPF compiler and virtual machine interpreter.

Filter Expression Dialects

JNetRuntime BPF compiler supports three major filter expression dialects, each with its own syntax and semantics but all compiling to standard BPF bytecode.

1. PCAP/TCPDump Filter Expressions

The traditional and most widely used packet filtering syntax, originated from the Berkeley Packet Filter.

// Example usage
BpfCompiler compiler = new PcapCompiler();
byte[] bytecode = compiler.compile("tcp port 80 and not broadcast");

Key characteristics:

  • Simple, concise syntax
  • Protocol-based filtering
  • Direct access to packet fields
  • Widely documented and understood
  • Compatible with tcpdump and libpcap

Detailed PCAP/TCPDump Grammar

2. Wireshark Display Filters

More expressive and field-oriented syntax, familiar to Wireshark users.

// Example usage
BpfCompiler compiler = new WiresharkCompiler();
byte[] bytecode = compiler.compile("http.request.method == \"GET\" && ip.addr != 10.0.0.0/8");

Key characteristics:

  • Protocol-field based syntax
  • Rich comparison operators
  • String and pattern matching
  • Function support
  • Extensive protocol support

Detailed Wireshark Filter Grammar

3. Napatech NTPL Filters

Hardware-acceleration oriented syntax with explicit protocol layering.

// Example usage
BpfCompiler compiler = new NtplCompiler();
byte[] bytecode = compiler.compile("Layer3Protocol == IPv4 AND TCP[DstPort] == 80");

Key characteristics:

  • Explicit protocol layer handling
  • Hardware-friendly constructs
  • Rich set of protocol fields
  • Support for complex protocol stacks
  • Performance-oriented design

Detailed NTPL Grammar

Compiler Usage

Common Interface

All dialect compilers implement the BpfCompiler interface:

public interface BpfCompiler {
    byte[] compile(String expression) throws CompilerException;
    
    // Optional: compile with specific options
    byte[] compile(String expression, CompilerOptions options) throws CompilerException;
}

Factory-based Instantiation

// Using the factory
BpfCompiler compiler = BpfCompiler.forDialect(FilterDialect.PCAP);
BpfCompiler compiler = BpfCompiler.forDialect(FilterDialect.WIRESHARK);
BpfCompiler compiler = BpfCompiler.forDialect(FilterDialect.NTPL);

// Or direct instantiation
BpfCompiler pcapCompiler = new PcapCompiler();
BpfCompiler wiresharkCompiler = new WiresharkCompiler();
BpfCompiler ntplCompiler = new NtplCompiler();

Compiler Options

CompilerOptions options = CompilerOptions.builder()
    .optimizationLevel(OptimizationLevel.AGGRESSIVE)
    .debug(true)
    .maxInstructions(1024)
    .build();

byte[] bytecode = compiler.compile(expression, options);

BPF Virtual Machine Interpreter

The BPF VM is designed to be stateless, with all execution state maintained in a context object. This allows for concurrent execution of programs across multiple threads.

Basic VM Usage

// Create VM instance (stateless)
BPFVirtualMachine vm = new BPFVirtualMachine();

// Create execution context (holds state)
BPFContext context = new BPFContext();

// Load program
byte[] bytecode = compiler.compile("tcp port 80");
BPFProgram program = BPFProgram.load(bytecode);

// Process multiple packets concurrently
void processPacket(byte[] packet) {
    // Reset context for new execution
    context.reset();
    
    // Set packet data
    context.setPacket(packet, packet.length);
    
    // Execute program
    boolean matches = program.execute(context);
}

Thread-Safe Execution

// Example of concurrent execution
public class PacketProcessor {
    private final BPFProgram program;
    // ThreadLocal ensures each thread has its own context
    private final ThreadLocal<BPFContext> contextHolder;
    
    public PacketProcessor(String filter) {
        byte[] bytecode = compiler.compile(filter);
        this.program = BPFProgram.load(bytecode);
        this.contextHolder = ThreadLocal.withInitial(BPFContext::new);
    }
    
    public boolean processPacket(byte[] packet) {
        BPFContext context = contextHolder.get();
        context.reset();
        context.setPacket(packet, packet.length);
        return program.execute(context);
    }
}

Performance Considerations

  1. Context Reuse

    // Reuse context within the same thread
    BPFContext context = new BPFContext();
    for (byte[] packet : packets) {
        context.reset();
        context.setPacket(packet, packet.length);
        program.execute(context);
    }
  2. Batch Processing

    // Process packets in batches
    public void processBatch(List<byte[]> packets) {
        BPFContext context = contextHolder.get();
        for (byte[] packet : packets) {
            context.reset();
            context.setPacket(packet, packet.length);
            if (program.execute(context)) {
                // Handle matching packet
            }
        }
    }
  3. Multiple Programs

    // Run multiple programs on same packet
    public boolean matchesAny(byte[] packet, List<BPFProgram> programs) {
        BPFContext context = contextHolder.get();
        for (BPFProgram program : programs) {
            context.reset();
            context.setPacket(packet, packet.length);
            if (program.execute(context)) {
                return true;
            }
        }
        return false;
    }

Error Handling

try {
    byte[] bytecode = compiler.compile(expression);
} catch (SyntaxException e) {
    // Handle syntax errors
    System.err.println("Syntax error at position " + e.getPosition());
} catch (CompilerException e) {
    // Handle general compilation errors
} catch (BPFException e) {
    // Handle VM execution errors
}

References