Skip to content

Latest commit

 

History

History
1692 lines (1331 loc) · 33.7 KB

File metadata and controls

1692 lines (1331 loc) · 33.7 KB

VFM Programming Manual

Table of Contents

  1. Introduction
  2. VFLisp DSL Programming
  3. Getting Started with VFLisp
  4. VFLisp Language Reference
  5. VFLisp Examples
  6. Architecture Overview
  7. Assembly Programming
  8. Instruction Set Reference
  9. API Reference
  10. Command Line Tools
  11. Performance Optimization
  12. Debugging Techniques
  13. Best Practices
  14. Appendix A: Example Programs
  15. Appendix B: Error Codes

Introduction

The Velocity Filter Machine (VFM) is a high-performance packet filtering virtual machine designed to process network packets at line rate. VFM features both a high-level Lisp-like DSL (VFLisp) for easy filter development and a low-level bytecode interpreter for maximum performance.

Key Features

  • VFLisp DSL: High-level Lisp-like language for intuitive filter programming
  • Stack-based VM: Custom bytecode interpreter optimized for packet processing
  • Zero-copy packet access: Direct memory access without copying overhead
  • Static program verification: Safety analysis prevents crashes and ensures bounded execution
  • JIT compilation support: x86-64 and ARM64 native code generation
  • Flow table support: Stateful filtering with hash-based storage
  • BPF compilation: Convert filters to kernel-compatible BPF code

Design Philosophy

VFM prioritizes:

  1. Ease of Use: VFLisp DSL provides intuitive syntax for filter development
  2. Safety: All programs are verified before execution
  3. Performance: Optimized for millions of packets per second
  4. Flexibility: Both high-level DSL and low-level assembly programming
  5. Portability: Cross-platform support with platform-specific optimizations

Architecture Overview

Virtual Machine Model

VFM implements a stack-based virtual machine with the following components:

  • Stack: 64-bit values, maximum depth of 256
  • Program Counter: Points to current instruction
  • Packet Buffer: Read-only access to packet data
  • Flow Table: Optional stateful storage
  • Registers: Internal use for optimization

Memory Model

+------------------+
|  Program Memory  |  <- Bytecode instructions (read-only)
+------------------+
|      Stack       |  <- Computation stack (read/write)
+------------------+
|  Packet Buffer   |  <- Network packet data (read-only)
+------------------+
|   Flow Table     |  <- Stateful storage (read/write)
+------------------+

Execution Model

  1. Programs start at address 0
  2. Instructions execute sequentially unless control flow changes
  3. Execution terminates on RET instruction
  4. Return value is top of stack

VFLisp DSL Programming

VFLisp is a Lisp-like domain-specific language that makes packet filtering intuitive and expressive. It compiles to efficient VFM bytecode while providing high-level abstractions for common packet processing tasks.

Key Advantages

  • Intuitive Syntax: Familiar S-expression syntax for easy learning
  • Type Safety: Built-in validation for packet field access
  • Composability: Easy to combine and reuse filter components
  • Performance: Compiles to optimized VFM bytecode
  • Safety: Automatic bounds checking and stack management

Getting Started with VFLisp

Installation

# Build VFLisp compiler
make vflisp

# Or build everything
make all

# Run tests
make test

Your First VFLisp Filter

Create a file simple_filter.vfl:

; Accept all IPv4 TCP packets
(and (= ethertype 0x0800)
     (= proto 6))

Compile and test:

# Compile VFLisp to bytecode
./dsl/vflisp/vflispc -e '(= proto 6)' -o tcp_filter.bin

# Test the filter
./dsl/vflisp/vflispc -t tcp_filter.bin

# Disassemble to see generated bytecode
./dsl/vflisp/vflispc -d tcp_filter.bin

VFLisp Examples

; TCP port 80 filter
(and (= proto 6)
     (= dst-port 80))

; Block suspicious UDP traffic
(and (= proto 17)
     (> ip-len 1400))

; Accept HTTP/HTTPS only
(and (= proto 6)
     (or (= dst-port 80)
         (= dst-port 443)))

; Rate limiting with flow tracking
(if (= proto 6)
    (< (flow-count src-ip) 100)
    1)

C Integration

#include "vfm.h"

int main() {
    // Create VM instance
    vfm_state_t *vm = vfm_create();
    
    // Compile VFLisp directly from string
    uint8_t *bytecode;
    uint32_t bytecode_len;
    char error_msg[256];
    
    if (vfl_compile_string("(= proto 6)", &bytecode, &bytecode_len, 
                          error_msg, sizeof(error_msg)) < 0) {
        fprintf(stderr, "Compilation failed: %s\n", error_msg);
        return 1;
    }
    
    // Load compiled program
    if (vfm_load_program(vm, bytecode, bytecode_len) != VFM_SUCCESS) {
        fprintf(stderr, "Failed to load program\n");
        return 1;
    }
    
    // Process packet
    uint8_t packet[1500];
    uint16_t packet_len = receive_packet(packet);
    
    int result = vfm_execute(vm, packet, packet_len);
    if (result == 1) {
        // Accept packet
        forward_packet(packet, packet_len);
    } else {
        // Drop packet
    }
    
    free(bytecode);
    vfm_destroy(vm);
    return 0;
}

VFLisp Language Reference

Syntax Overview

VFLisp uses S-expression syntax where operations are written in prefix notation:

(operator operand1 operand2 ...)

Data Types

Integers

42          ; Decimal
0xFF        ; Hexadecimal
0b1010      ; Binary (future)

Packet Fields

ethertype   ; Ethernet type field (offset 12)
proto       ; IP protocol field (offset 23)
src-ip      ; Source IP address (offset 26)
dst-ip      ; Destination IP address (offset 30)
src-port    ; Source port (offset 34)
dst-port    ; Destination port (offset 36)
ip-len      ; IP total length (offset 16)
tcp-flags   ; TCP flags (offset 47)

Arithmetic Operations

Basic Arithmetic

(+ 10 20)           ; Addition: 30
(- 100 25)          ; Subtraction: 75
(* 6 7)             ; Multiplication: 42
(/ 100 4)           ; Division: 25
(% 17 5)            ; Modulo: 2

Multi-argument Operations

(+ 1 2 3 4)         ; Chain: 1+2+3+4 = 10
(* 2 3 4)           ; Chain: 2*3*4 = 24

Comparison Operations

(= proto 6)         ; Equal: proto == 6
(!= src-port 80)    ; Not equal: src-port != 80
(> ip-len 1400)     ; Greater than: ip-len > 1400
(>= tcp-flags 2)    ; Greater or equal: tcp-flags >= 2
(< dst-port 1024)   ; Less than: dst-port < 1024
(<= ip-len 576)     ; Less or equal: ip-len <= 576

Logical Operations

Boolean Logic

(and (= proto 6) (= dst-port 80))    ; Logical AND
(or (= dst-port 80) (= dst-port 443)) ; Logical OR
(not (= proto 17))                   ; Logical NOT

Short-circuit Evaluation

; AND stops at first false value
(and (= ethertype 0x0800)    ; Check IPv4 first
     (= proto 6)             ; Only check if IPv4
     (= dst-port 443))       ; Only check if TCP

; OR stops at first true value
(or (= dst-port 80)          ; Check HTTP first
    (= dst-port 443)         ; Check HTTPS if not HTTP
    (= dst-port 8080))       ; Check alt-HTTP if neither

Bitwise Operations

(& tcp-flags 0x02)   ; Bitwise AND: check SYN flag
(| flags1 flags2)    ; Bitwise OR: combine flags
(^ val1 val2)        ; Bitwise XOR: exclusive or
(<< value 4)         ; Left shift: value << 4
(>> value 2)         ; Right shift: value >> 2

Control Flow

Conditional Expressions

(if condition
    then-expression
    else-expression)

Examples:

; Simple condition
(if (= proto 6) 1 0)

; Nested conditions
(if (= ethertype 0x0800)
    (if (= proto 6)
        (if (= dst-port 80) 1 0)
        0)
    1)

Packet Field Access

VFLisp provides convenient names for common packet fields:

Field Name Offset Size Description
ethertype 12 2 Ethernet frame type
proto 23 1 IP protocol number
src-ip 26 4 Source IP address
dst-ip 30 4 Destination IP address
src-port 34 2 Source port (TCP/UDP)
dst-port 36 2 Destination port (TCP/UDP)
ip-len 16 2 IP total length
tcp-flags 47 1 TCP flags byte

Compilation Process

VFLisp expressions compile to VFM bytecode through these steps:

  1. Parsing: Convert S-expressions to Abstract Syntax Tree (AST)
  2. Type Checking: Validate packet field access and operations
  3. Code Generation: Emit optimized VFM instructions
  4. Verification: Ensure generated bytecode is safe

Example compilation:

; VFLisp source
(= proto 6)

; Generated VFM assembly
LD8 23          ; Load protocol field
PUSH 6          ; Push TCP constant
SUB             ; Calculate difference
DUP             ; Duplicate for test
PUSH 0          ; Push zero
JEQ true_branch ; Jump if equal
POP             ; Clean up
PUSH 0          ; Push false
JMP end
true_branch:
POP             ; Clean up
PUSH 1          ; Push true
end:
RET             ; Return result

VFLisp Examples

Basic Protocol Filtering

; Accept only TCP traffic
(= proto 6)

; Accept TCP or UDP
(or (= proto 6) (= proto 17))

; Block ICMP
(not (= proto 1))

Port-based Filtering

; Web traffic only
(and (= proto 6)
     (or (= dst-port 80)
         (= dst-port 443)))

; Block privileged ports
(and (= proto 6)
     (> dst-port 1024))

; SSH traffic
(and (= proto 6)
     (= dst-port 22))

Complex Traffic Analysis

; Detect large UDP packets (potential DDoS)
(and (= proto 17)
     (> ip-len 1400))

; HTTP POST detection (simplified)
(and (= proto 6)
     (= dst-port 80)
     (& tcp-flags 0x18))  ; PSH+ACK flags

; Detect SYN flood attempts
(and (= proto 6)
     (= tcp-flags 0x02))  ; SYN only

; Rate limiting per source
(and (= proto 6)
     (< (count-by src-ip) 100))

Multi-condition Filters

; Enterprise traffic policy
(if (= ethertype 0x0800)               ; IPv4 only
    (if (= proto 6)                    ; TCP traffic
        (or (= dst-port 80)            ; HTTP
            (= dst-port 443)           ; HTTPS
            (= dst-port 22)            ; SSH
            (= dst-port 25)            ; SMTP
            (= dst-port 993))          ; IMAPS
        (if (= proto 17)               ; UDP traffic
            (or (= dst-port 53)        ; DNS
                (= dst-port 123))      ; NTP
            0))                        ; Block other protocols
    1)                                 ; Accept non-IPv4

; Security filter with logging
(if (and (= proto 6)
         (= dst-port 22))              ; SSH traffic
    (if (> (count-by src-ip) 10)       ; Too many connections
        0                              ; Block
        1)                             ; Allow
    (if (and (= proto 17)
             (> ip-len 1024))          ; Large UDP
        0                              ; Block potential amplification
        1))                            ; Allow other traffic

Architecture Overview

Virtual Machine Model

VFM implements a stack-based virtual machine with the following components:

  • Stack: 64-bit values, maximum depth of 256
  • Program Counter: Points to current instruction
  • Packet Buffer: Read-only access to packet data
  • Flow Table: Optional stateful storage
  • Registers: Internal use for optimization

Memory Model

+------------------+
|  Program Memory  |  <- Bytecode instructions (read-only)
+------------------+
|      Stack       |  <- Computation stack (read/write)
+------------------+
|  Packet Buffer   |  <- Network packet data (read-only)
+------------------+
|   Flow Table     |  <- Stateful storage (read/write)
+------------------+

Execution Model

  1. Programs start at address 0
  2. Instructions execute sequentially unless control flow changes
  3. Execution terminates on RET instruction
  4. Return value is top of stack

Assembly Programming

For low-level control and maximum performance, VFM provides a assembly language that compiles directly to bytecode. While VFLisp is recommended for most use cases, assembly programming is useful for:

  • Performance-critical filters
  • Custom instruction sequences
  • Educational purposes
  • Debugging compiled VFLisp code

Assembly Getting Started

Create a file simple_filter.vfm:

; Accept all IPv4 packets
LD16 12         ; Load EtherType at offset 12
PUSH 0x0800     ; IPv4 EtherType
JEQ accept      ; Jump if equal
RET 0           ; Drop non-IPv4

accept:
RET 1           ; Accept IPv4

Compile and test:

# Compile to bytecode
./tools/vfm-asm simple_filter.vfm -o simple_filter.bin

# Disassemble to verify
./tools/vfm-dis simple_filter.bin

# Test with packet capture
./tools/vfm-test simple_filter.bin test_packets.pcap

Instruction Set Reference

Packet Access Instructions

LD8 - Load Byte

LD8 offset

Loads a single byte from packet at specified offset onto stack.

Example:

LD8 23      ; Load IP protocol field

LD16 - Load 16-bit Word

LD16 offset

Loads 16-bit value from packet (network byte order) and converts to host order.

Example:

LD16 12     ; Load EtherType
LD16 36     ; Load TCP destination port

LD32 - Load 32-bit Word

LD32 offset

Loads 32-bit value from packet (network byte order) and converts to host order.

Example:

LD32 26     ; Load source IP address

LD64 - Load 64-bit Word

LD64 offset

Loads 64-bit value from packet (network byte order) and converts to host order.

Stack Operations

PUSH - Push Immediate

PUSH value

Pushes 64-bit immediate value onto stack.

Example:

PUSH 0x0800     ; Push IPv4 EtherType
PUSH 80         ; Push HTTP port
PUSH -1         ; Push all bits set

POP - Pop Value

POP

Removes top value from stack.

Example:

PUSH 10
PUSH 20
POP         ; Stack now contains only 10

DUP - Duplicate Top

DUP

Duplicates top stack value.

Example:

PUSH 42
DUP         ; Stack now contains 42, 42

SWAP - Swap Top Two

SWAP

Exchanges top two stack values.

Example:

PUSH 10
PUSH 20
SWAP        ; Stack now contains 10, 20 (swapped)

Arithmetic Operations

All arithmetic operations pop two values, perform operation, and push result.

ADD - Addition

ADD

Pops b, pops a, pushes a + b.

Example:

PUSH 10
PUSH 5
ADD         ; Result: 15

SUB - Subtraction

SUB

Pops b, pops a, pushes a - b.

Example:

PUSH 10
PUSH 3
SUB         ; Result: 7

MUL - Multiplication

MUL

Pops b, pops a, pushes a * b.

DIV - Division

DIV

Pops b, pops a, pushes a / b. Returns error if b is zero.

MOD - Modulo

MOD

Pops b, pops a, pushes a % b.

Bitwise Operations

AND - Bitwise AND

AND

Pops two values, pushes bitwise AND.

Example:

LD8 47      ; Load TCP flags
PUSH 0x02   ; SYN flag
AND         ; Check if SYN is set

OR - Bitwise OR

OR

Pops two values, pushes bitwise OR.

XOR - Bitwise XOR

XOR

Pops two values, pushes bitwise XOR.

NOT - Bitwise NOT

NOT

Pops one value, pushes bitwise NOT.

SHL - Shift Left

SHL

Pops shift count, pops value, pushes value << count.

SHR - Shift Right

SHR

Pops shift count, pops value, pushes value >> count.

Control Flow

JMP - Unconditional Jump

JMP offset

Jumps to PC + offset (signed 16-bit).

Example:

JMP end     ; Jump forward
loop:
    ; ... code ...
    JMP loop    ; Jump backward
end:

JEQ - Jump if Equal

JEQ offset

Pops two values, jumps if equal.

Example:

LD16 12
PUSH 0x0800
JEQ ipv4_handler

JNE - Jump if Not Equal

JNE offset

Pops two values, jumps if not equal.

JGT - Jump if Greater Than

JGT offset

Pops b, pops a, jumps if a > b.

JLT - Jump if Less Than

JLT offset

Pops b, pops a, jumps if a < b.

JGE - Jump if Greater or Equal

JGE offset

Pops b, pops a, jumps if a >= b.

JLE - Jump if Less or Equal

JLE offset

Pops b, pops a, jumps if a <= b.

RET - Return

RET

Terminates execution, returns top of stack as result.

Example:

PUSH 1
RET         ; Accept packet

Special Operations

HASH5 - Hash 5-Tuple

HASH5

Computes hash of packet 5-tuple (protocol, src/dst IP, src/dst port).

Example:

HASH5           ; Get flow hash
FLOW_LOAD       ; Load counter for this flow

FLOW_LOAD - Load from Flow Table

FLOW_LOAD

Pops key, pushes value from flow table (0 if not found).

FLOW_STORE - Store to Flow Table

FLOW_STORE

Pops value, pops key, stores value in flow table.

Example:

; Rate limiting example
HASH5           ; Get flow hash
DUP             ; Duplicate for store
FLOW_LOAD       ; Load current count
PUSH 1
ADD             ; Increment
DUP             ; Duplicate for check
SWAP            ; Get hash back on top
SWAP            ; Get value on top
FLOW_STORE      ; Store new count
PUSH 100        ; Rate limit
JGT drop        ; Drop if over limit

Programming Guide

Basic Packet Filtering

Filtering by Protocol

; Accept only TCP packets
LD16 12         ; EtherType
PUSH 0x0800     ; IPv4
JNE drop

LD8 23          ; IP Protocol
PUSH 6          ; TCP
JNE drop

RET 1           ; Accept

drop:
RET 0           ; Drop

Port-Based Filtering

; Accept HTTP and HTTPS traffic
LD16 12         ; EtherType
PUSH 0x0800     ; IPv4
JNE drop

LD8 23          ; IP Protocol
PUSH 6          ; TCP
JNE drop

LD16 36         ; Destination port
DUP             ; Duplicate for second check
PUSH 80         ; HTTP
JEQ accept
PUSH 443        ; HTTPS
JEQ accept

drop:
RET 0

accept:
RET 1

Stateful Filtering

Connection Tracking

; Simple SYN flood detection
LD16 12         ; EtherType
PUSH 0x0800     ; IPv4
JNE accept      ; Not IPv4, accept

LD8 23          ; IP Protocol
PUSH 6          ; TCP
JNE accept      ; Not TCP, accept

LD8 47          ; TCP Flags
PUSH 0x02       ; SYN flag
AND
PUSH 0x02
JNE accept      ; Not SYN, accept

; Count SYNs per source
LD32 26         ; Source IP
DUP             ; Duplicate for store
FLOW_LOAD       ; Get current count
PUSH 1
ADD             ; Increment
DUP             ; Duplicate for comparison
SWAP            ; Get IP back
SWAP            ; Get count on top
FLOW_STORE      ; Store new count

PUSH 10         ; Threshold
JGT drop        ; Too many SYNs

accept:
RET 1

drop:
RET 0

Advanced Techniques

Subroutines Using Stack

VFM doesn't have explicit subroutines, but you can simulate them:

; Main program
PUSH return1    ; Push return address
JMP check_ipv4
return1:
; ... rest of program ...

check_ipv4:
    LD16 12
    PUSH 0x0800
    JEQ is_ipv4
    PUSH 0      ; Not IPv4
    SWAP        ; Get return address
    JMP 0       ; Jump to return address (dynamic)
is_ipv4:
    PUSH 1      ; Is IPv4
    SWAP        ; Get return address
    JMP 0       ; Jump to return address (dynamic)

Packet Parsing

; Parse and validate IPv4 header
LD16 12         ; EtherType
PUSH 0x0800
JNE invalid

LD8 14          ; Version/IHL
DUP
PUSH 4
SHR             ; Get version
PUSH 4
JNE invalid     ; Not IPv4

PUSH 0x0F
AND             ; Get IHL
PUSH 5
JLT invalid     ; IHL < 5 is invalid
PUSH 15
JGT invalid     ; IHL > 15 is invalid

; Calculate header length
PUSH 4
MUL             ; IHL * 4 = header length

; Validate total length
LD16 16         ; Total length
DUP
PUSH 20         ; Minimum IP packet
JLT invalid

RET 1           ; Valid

invalid:
RET 0

API Reference

Core Functions

vfm_create

vfm_state_t* vfm_create(void);

Creates new VM instance.

Returns:

  • Pointer to VM state on success
  • NULL on failure

Example:

vfm_state_t *vm = vfm_create();
if (!vm) {
    fprintf(stderr, "Failed to create VM\n");
    return -1;
}

vfm_destroy

void vfm_destroy(vfm_state_t *vm);

Destroys VM instance and frees resources.

vfm_load_program

int vfm_load_program(vfm_state_t *vm, const uint8_t *program, uint32_t len);

Loads bytecode program into VM.

Parameters:

  • vm: VM instance
  • program: Bytecode array
  • len: Program length in bytes

Returns:

  • VFM_SUCCESS on success
  • Error code on failure

vfm_load_program_file

int vfm_load_program_file(vfm_state_t *vm, const char *filename);

Loads bytecode program from file.

vfm_execute

int vfm_execute(vfm_state_t *vm, const uint8_t *packet, uint16_t packet_len);

Executes loaded program on packet.

Parameters:

  • vm: VM instance with loaded program
  • packet: Packet data
  • packet_len: Packet length

Returns:

  • Program return value on success
  • Negative error code on failure

Example:

int result = vfm_execute(vm, packet_data, packet_len);
switch (result) {
    case 1:
        // Accept packet
        break;
    case 0:
        // Drop packet
        break;
    default:
        if (result < 0) {
            // Handle error
            fprintf(stderr, "Execution error: %d\n", result);
        }
}

Flow Table Functions

vfm_flow_table_init

int vfm_flow_table_init(vfm_state_t *vm, uint32_t size);

Initializes flow table with specified size.

vfm_flow_table_clear

void vfm_flow_table_clear(vfm_state_t *vm);

Clears all entries from flow table.

Verification Functions

vfm_verify

int vfm_verify(const uint8_t *program, uint32_t len);

Verifies program safety before execution.

Returns:

  • VFM_SUCCESS if program is safe
  • Error code indicating verification failure

Example:

uint8_t program[256];
uint32_t len = load_program(program);

if (vfm_verify(program, len) != VFM_SUCCESS) {
    fprintf(stderr, "Program failed verification\n");
    return -1;
}

Compilation Functions

vfm_to_bpf

int vfm_to_bpf(const uint8_t *vfm_prog, uint32_t vfm_len,
               bpf_insn_t *bpf_prog, uint32_t *bpf_len);

Compiles VFM bytecode to classic BPF.

Error Codes

#define VFM_SUCCESS                  0
#define VFM_ERROR_NO_MEMORY         -1
#define VFM_ERROR_INVALID_PROGRAM   -2
#define VFM_ERROR_INVALID_OPCODE    -3
#define VFM_ERROR_STACK_OVERFLOW    -4
#define VFM_ERROR_STACK_UNDERFLOW   -5
#define VFM_ERROR_BOUNDS            -6
#define VFM_ERROR_DIVISION_BY_ZERO  -7
#define VFM_ERROR_LIMIT             -8
#define VFM_ERROR_VERIFICATION_FAILED -9

Command Line Tools

vfm-asm - Assembler

Converts VFM assembly to bytecode.

vfm-asm [options] input.vfm -o output.bin

Options:

  • -o, --output: Output file (required)
  • -v, --verbose: Verbose output
  • -h, --help: Show help

Example:

# Basic assembly
./tools/vfm-asm filter.vfm -o filter.bin

# Verbose mode
./tools/vfm-asm -v complex_filter.vfm -o complex_filter.bin

vfm-dis - Disassembler

Converts bytecode back to assembly.

vfm-dis [options] input.bin [output.asm]

Options:

  • -a, --addresses: Show bytecode addresses
  • -x, --hex: Show hex dump
  • -v, --verbose: Verbose output
  • -h, --help: Show help

Example:

# Basic disassembly
./tools/vfm-dis filter.bin

# With addresses and hex
./tools/vfm-dis -a -x filter.bin filter_debug.asm

vfm-test - Filter Tester

Tests filters against packet captures.

vfm-test [options] filter.bin packets.pcap

Options:

  • -v, --verbose: Verbose output
  • -s, --stats: Show statistics
  • -h, --help: Show help

Example:

# Test filter
./tools/vfm-test tcp_filter.bin capture.pcap

# With statistics
./tools/vfm-test -s -v ddos_filter.bin attack_trace.pcap

Performance Optimization

Optimization Techniques

1. Minimize Stack Operations

Bad:

PUSH 10
PUSH 20
ADD
PUSH 5
SUB

Good:

PUSH 10
PUSH 20
ADD
PUSH 5
SUB

2. Use Conditional Jumps Efficiently

Bad:

LD16 12
PUSH 0x0800
JNE not_ipv4
JMP is_ipv4
not_ipv4:
    RET 0
is_ipv4:
    RET 1

Good:

LD16 12
PUSH 0x0800
JNE drop
RET 1
drop:
RET 0

3. Cache Flow Table Lookups

Bad:

HASH5
FLOW_LOAD
; ... some code ...
HASH5
FLOW_LOAD  ; Redundant

Good:

HASH5
DUP        ; Keep hash on stack
FLOW_LOAD
; ... some code ...
SWAP       ; Get hash back
FLOW_LOAD

JIT Compilation

Enable JIT for maximum performance:

vfm_state_t *vm = vfm_create();
vfm_enable_jit(vm);  // Enable JIT compilation
vfm_load_program_file(vm, "filter.bin");

Benchmarking

#include <time.h>

void benchmark_filter(vfm_state_t *vm, uint8_t *packets[], 
                     uint16_t lengths[], int count) {
    struct timespec start, end;
    
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    for (int i = 0; i < count; i++) {
        vfm_execute(vm, packets[i], lengths[i]);
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    double elapsed = (end.tv_sec - start.tv_sec) + 
                    (end.tv_nsec - start.tv_nsec) / 1e9;
    double pps = count / elapsed;
    
    printf("Processed %d packets in %.3f seconds (%.1f Mpps)\n",
           count, elapsed, pps / 1e6);
}

Debugging Techniques

Using the Disassembler

Always verify compiled bytecode:

# Compile
./tools/vfm-asm myfilter.vfm -o myfilter.bin

# Verify
./tools/vfm-dis -a -x myfilter.bin

Adding Debug Output

Create debug filters that push intermediate values:

; Debug version
LD16 12         ; EtherType
DUP             ; Duplicate for debug
PUSH 0x0800
JNE not_ipv4
POP             ; Remove debug value
RET 1

not_ipv4:
; Top of stack contains actual EtherType
RET 0

Single-Step Execution

Implement a debug executor:

int debug_execute(vfm_state_t *vm, const uint8_t *packet, 
                  uint16_t len) {
    // Save original limit
    uint32_t saved_limit = vm->hot.insn_limit;
    
    // Set limit to 1 for single-step
    vm->hot.insn_limit = 1;
    
    int result;
    uint32_t step = 0;
    
    while ((result = vfm_execute(vm, packet, len)) == VFM_ERROR_LIMIT) {
        printf("Step %u: PC=%u, Stack depth=%u\n", 
               step++, vm->pc, vm->sp);
        
        // Print stack
        for (uint32_t i = 0; i <= vm->sp; i++) {
            printf("  [%u] = %llu\n", i, vm->stack[i]);
        }
        
        // Reset for next instruction
        vm->hot.insn_count = 0;
        vm->hot.insn_limit = 1;
    }
    
    // Restore limit
    vm->hot.insn_limit = saved_limit;
    return result;
}

Best Practices

1. Always Verify Programs

if (vfm_verify(program, len) != VFM_SUCCESS) {
    // Reject untrusted program
    return -1;
}

2. Handle All Error Cases

int result = vfm_execute(vm, packet, len);
if (result < 0) {
    switch (result) {
        case VFM_ERROR_BOUNDS:
            log_error("Packet too small");
            break;
        case VFM_ERROR_LIMIT:
            log_error("Execution limit exceeded");
            break;
        // ... handle other errors
    }
}

3. Use Appropriate Packet Offsets

Common offsets:

  • Ethernet header: 0-13
  • IP header: 14-33 (minimum)
  • TCP header: 34-53 (minimum)

4. Validate Packet Structure

Always check packet type before accessing protocol fields:

; Validate IPv4 before accessing IP fields
LD16 12         ; EtherType
PUSH 0x0800
JNE skip_ip_checks

; Now safe to access IP fields
LD8 23          ; IP protocol
; ...

skip_ip_checks:

5. Optimize for Common Case

Place most likely conditions first:

; If most traffic is TCP
LD8 23
DUP
PUSH 6          ; TCP
JEQ handle_tcp
PUSH 17         ; UDP
JEQ handle_udp
; ... other protocols

Appendix A: Example Programs

TCP Port Scanner Detection

; Detect TCP SYN to multiple ports from same source
; Uses flow table to track port count per source IP

LD16 12         ; EtherType
PUSH 0x0800     ; IPv4
JNE accept

LD8 23          ; IP Protocol
PUSH 6          ; TCP
JNE accept

LD8 47          ; TCP Flags
PUSH 0x02       ; SYN
AND
PUSH 0x02
JNE accept      ; Not pure SYN

; Create unique key: src_ip | dst_port
LD32 26         ; Source IP
PUSH 16
SHL             ; Shift left 16 bits
LD16 36         ; Destination port
OR              ; Combine

DUP             ; Duplicate key
FLOW_LOAD       ; Check if seen before
PUSH 0
JNE accept      ; Already seen this combination

; Mark as seen
PUSH 1
FLOW_STORE

; Count unique ports per source
LD32 26         ; Source IP
DUP
FLOW_LOAD       ; Get current count
PUSH 1
ADD             ; Increment
DUP             ; Duplicate for comparison
SWAP            ; Get IP back
SWAP            ; Get count on top
FLOW_STORE      ; Store new count

PUSH 20         ; Threshold for port scan
JGT drop        ; More than 20 different ports

accept:
RET 1

drop:
RET 0

DNS Amplification Attack Filter

; Drop DNS responses that are too large (amplification attack)

LD16 12         ; EtherType
PUSH 0x0800     ; IPv4
JNE accept

LD8 23          ; IP Protocol
PUSH 17         ; UDP
JNE accept

LD16 34         ; Source port
PUSH 53         ; DNS
JNE accept

; Check packet size
LD16 16         ; IP Total Length
PUSH 512        ; DNS limit
JGT drop        ; Suspiciously large

accept:
RET 1

drop:
RET 0

HTTP Request Logger

; Log HTTP GET requests (accept all, but mark GETs)

LD16 12         ; EtherType
PUSH 0x0800     ; IPv4
JNE not_http

LD8 23          ; IP Protocol  
PUSH 6          ; TCP
JNE not_http

LD16 36         ; Destination port
PUSH 80         ; HTTP
JNE not_http

; Check for minimum TCP header + "GET "
LD16 16         ; IP Total Length
PUSH 44         ; Min IP + TCP + 4 bytes
JLT not_http

; Calculate TCP data offset
LD8 46          ; TCP Data offset
PUSH 4
SHR             ; Upper 4 bits
PUSH 4
MUL             ; Convert to bytes

; Check for "GET " (0x47455420)
; Note: This is simplified - real implementation would calculate correct offset
LD32 54         ; Assume standard headers
PUSH 0x47455420
JEQ http_get

not_http:
RET 1           ; Accept but don't log

http_get:
RET 2           ; Special return code for logging

Stateful TCP Connection Tracker

; Basic TCP connection state tracking
; Track SYN, SYN-ACK, and established connections

LD16 12         ; EtherType
PUSH 0x0800     ; IPv4
JNE accept

LD8 23          ; IP Protocol
PUSH 6          ; TCP
JNE accept

; Create connection key from 4-tuple
LD32 26         ; Source IP
LD32 30         ; Dest IP
XOR             ; Mix IPs
LD16 34         ; Source Port
LD16 36         ; Dest Port
XOR             ; Mix ports
XOR             ; Final connection ID

DUP             ; Keep connection ID

; Check TCP flags
LD8 47          ; TCP Flags
DUP
PUSH 0x02       ; SYN
AND
PUSH 0x02
JEQ handle_syn

PUSH 0x12       ; SYN-ACK
AND
PUSH 0x12
JEQ handle_syn_ack

PUSH 0x10       ; ACK
AND
PUSH 0x10
JEQ handle_ack

; Other flags - check if connection exists
FLOW_LOAD
PUSH 2          ; Established state
JEQ accept      ; Known connection
RET 0           ; Unknown connection, drop

handle_syn:
POP             ; Remove flags
PUSH 1          ; SYN seen state
FLOW_STORE
RET 1

handle_syn_ack:
POP             ; Remove flags
FLOW_LOAD       ; Check current state
PUSH 1          ; Should be SYN seen
JNE drop
POP             ; Remove old state
PUSH 2          ; Established state
FLOW_STORE
RET 1

handle_ack:
POP             ; Remove flags
FLOW_LOAD       ; Check if established
PUSH 2
JEQ accept
RET 0           ; Not established, drop

accept:
RET 1

drop:
RET 0

Appendix B: Error Codes

VFM_SUCCESS (0)

Operation completed successfully.

VFM_ERROR_NO_MEMORY (-1)

Memory allocation failed. Check system resources.

VFM_ERROR_INVALID_PROGRAM (-2)

Program structure is invalid. Check program size and format.

VFM_ERROR_INVALID_OPCODE (-3)

Unknown instruction encountered. Verify bytecode integrity.

VFM_ERROR_STACK_OVERFLOW (-4)

Stack limit exceeded. Reduce stack usage or increase limit.

VFM_ERROR_STACK_UNDERFLOW (-5)

Attempted to pop from empty stack. Check program logic.

VFM_ERROR_BOUNDS (-6)

Packet access out of bounds. Validate packet offsets.

VFM_ERROR_DIVISION_BY_ZERO (-7)

Division by zero attempted. Add zero checks before division.

VFM_ERROR_LIMIT (-8)

Instruction limit exceeded. Optimize program or increase limit.

VFM_ERROR_VERIFICATION_FAILED (-9)

Program failed safety verification. Check for:

  • Invalid jumps
  • Unreachable code
  • Stack inconsistencies
  • Infinite loops

Error Handling Example

const char* vfm_error_string(int error) {
    switch (error) {
        case VFM_SUCCESS:
            return "Success";
        case VFM_ERROR_NO_MEMORY:
            return "Out of memory";
        case VFM_ERROR_INVALID_PROGRAM:
            return "Invalid program";
        case VFM_ERROR_INVALID_OPCODE:
            return "Invalid opcode";
        case VFM_ERROR_STACK_OVERFLOW:
            return "Stack overflow";
        case VFM_ERROR_STACK_UNDERFLOW:
            return "Stack underflow";
        case VFM_ERROR_BOUNDS:
            return "Packet bounds exceeded";
        case VFM_ERROR_DIVISION_BY_ZERO:
            return "Division by zero";
        case VFM_ERROR_LIMIT:
            return "Instruction limit exceeded";
        case VFM_ERROR_VERIFICATION_FAILED:
            return "Verification failed";
        default:
            return "Unknown error";
    }
}

This manual provides comprehensive documentation for developing with VFM. For additional examples and use cases, see the examples/ directory in the VFM distribution.