- Introduction
- VFLisp DSL Programming
- Getting Started with VFLisp
- VFLisp Language Reference
- VFLisp Examples
- Architecture Overview
- Assembly Programming
- Instruction Set Reference
- API Reference
- Command Line Tools
- Performance Optimization
- Debugging Techniques
- Best Practices
- Appendix A: Example Programs
- Appendix B: Error Codes
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.
- 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
VFM prioritizes:
- Ease of Use: VFLisp DSL provides intuitive syntax for filter development
- Safety: All programs are verified before execution
- Performance: Optimized for millions of packets per second
- Flexibility: Both high-level DSL and low-level assembly programming
- Portability: Cross-platform support with platform-specific optimizations
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
+------------------+
| Program Memory | <- Bytecode instructions (read-only)
+------------------+
| Stack | <- Computation stack (read/write)
+------------------+
| Packet Buffer | <- Network packet data (read-only)
+------------------+
| Flow Table | <- Stateful storage (read/write)
+------------------+
- Programs start at address 0
- Instructions execute sequentially unless control flow changes
- Execution terminates on RET instruction
- Return value is top of stack
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.
- 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
# Build VFLisp compiler
make vflisp
# Or build everything
make all
# Run tests
make testCreate 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; 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)#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 uses S-expression syntax where operations are written in prefix notation:
(operator operand1 operand2 ...)42 ; Decimal
0xFF ; Hexadecimal
0b1010 ; Binary (future)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)(+ 10 20) ; Addition: 30
(- 100 25) ; Subtraction: 75
(* 6 7) ; Multiplication: 42
(/ 100 4) ; Division: 25
(% 17 5) ; Modulo: 2(+ 1 2 3 4) ; Chain: 1+2+3+4 = 10
(* 2 3 4) ; Chain: 2*3*4 = 24(= 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(and (= proto 6) (= dst-port 80)) ; Logical AND
(or (= dst-port 80) (= dst-port 443)) ; Logical OR
(not (= proto 17)) ; Logical NOT; 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(& 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(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)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 |
VFLisp expressions compile to VFM bytecode through these steps:
- Parsing: Convert S-expressions to Abstract Syntax Tree (AST)
- Type Checking: Validate packet field access and operations
- Code Generation: Emit optimized VFM instructions
- 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; Accept only TCP traffic
(= proto 6)
; Accept TCP or UDP
(or (= proto 6) (= proto 17))
; Block ICMP
(not (= proto 1)); 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)); 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)); 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 trafficVFM 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
+------------------+
| Program Memory | <- Bytecode instructions (read-only)
+------------------+
| Stack | <- Computation stack (read/write)
+------------------+
| Packet Buffer | <- Network packet data (read-only)
+------------------+
| Flow Table | <- Stateful storage (read/write)
+------------------+
- Programs start at address 0
- Instructions execute sequentially unless control flow changes
- Execution terminates on RET instruction
- Return value is top of stack
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
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 IPv4Compile 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.pcapLD8 offsetLoads a single byte from packet at specified offset onto stack.
Example:
LD8 23 ; Load IP protocol fieldLD16 offsetLoads 16-bit value from packet (network byte order) and converts to host order.
Example:
LD16 12 ; Load EtherType
LD16 36 ; Load TCP destination portLD32 offsetLoads 32-bit value from packet (network byte order) and converts to host order.
Example:
LD32 26 ; Load source IP addressLD64 offsetLoads 64-bit value from packet (network byte order) and converts to host order.
PUSH valuePushes 64-bit immediate value onto stack.
Example:
PUSH 0x0800 ; Push IPv4 EtherType
PUSH 80 ; Push HTTP port
PUSH -1 ; Push all bits setPOPRemoves top value from stack.
Example:
PUSH 10
PUSH 20
POP ; Stack now contains only 10DUPDuplicates top stack value.
Example:
PUSH 42
DUP ; Stack now contains 42, 42SWAPExchanges top two stack values.
Example:
PUSH 10
PUSH 20
SWAP ; Stack now contains 10, 20 (swapped)All arithmetic operations pop two values, perform operation, and push result.
ADDPops b, pops a, pushes a + b.
Example:
PUSH 10
PUSH 5
ADD ; Result: 15SUBPops b, pops a, pushes a - b.
Example:
PUSH 10
PUSH 3
SUB ; Result: 7MULPops b, pops a, pushes a * b.
DIVPops b, pops a, pushes a / b. Returns error if b is zero.
MODPops b, pops a, pushes a % b.
ANDPops two values, pushes bitwise AND.
Example:
LD8 47 ; Load TCP flags
PUSH 0x02 ; SYN flag
AND ; Check if SYN is setORPops two values, pushes bitwise OR.
XORPops two values, pushes bitwise XOR.
NOTPops one value, pushes bitwise NOT.
SHLPops shift count, pops value, pushes value << count.
SHRPops shift count, pops value, pushes value >> count.
JMP offsetJumps to PC + offset (signed 16-bit).
Example:
JMP end ; Jump forward
loop:
; ... code ...
JMP loop ; Jump backward
end:JEQ offsetPops two values, jumps if equal.
Example:
LD16 12
PUSH 0x0800
JEQ ipv4_handlerJNE offsetPops two values, jumps if not equal.
JGT offsetPops b, pops a, jumps if a > b.
JLT offsetPops b, pops a, jumps if a < b.
JGE offsetPops b, pops a, jumps if a >= b.
JLE offsetPops b, pops a, jumps if a <= b.
RETTerminates execution, returns top of stack as result.
Example:
PUSH 1
RET ; Accept packetHASH5Computes hash of packet 5-tuple (protocol, src/dst IP, src/dst port).
Example:
HASH5 ; Get flow hash
FLOW_LOAD ; Load counter for this flowFLOW_LOADPops key, pushes value from flow table (0 if not found).
FLOW_STOREPops 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; 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; 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; 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 0VFM 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); 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 0vfm_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;
}void vfm_destroy(vfm_state_t *vm);Destroys VM instance and frees resources.
int vfm_load_program(vfm_state_t *vm, const uint8_t *program, uint32_t len);Loads bytecode program into VM.
Parameters:
vm: VM instanceprogram: Bytecode arraylen: Program length in bytes
Returns:
VFM_SUCCESSon success- Error code on failure
int vfm_load_program_file(vfm_state_t *vm, const char *filename);Loads bytecode program from file.
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 programpacket: Packet datapacket_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);
}
}int vfm_flow_table_init(vfm_state_t *vm, uint32_t size);Initializes flow table with specified size.
void vfm_flow_table_clear(vfm_state_t *vm);Clears all entries from flow table.
int vfm_verify(const uint8_t *program, uint32_t len);Verifies program safety before execution.
Returns:
VFM_SUCCESSif 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;
}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.
#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 -9Converts VFM assembly to bytecode.
vfm-asm [options] input.vfm -o output.binOptions:
-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.binConverts 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.asmTests filters against packet captures.
vfm-test [options] filter.bin packets.pcapOptions:
-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.pcapBad:
PUSH 10
PUSH 20
ADD
PUSH 5
SUBGood:
PUSH 10
PUSH 20
ADD
PUSH 5
SUBBad:
LD16 12
PUSH 0x0800
JNE not_ipv4
JMP is_ipv4
not_ipv4:
RET 0
is_ipv4:
RET 1Good:
LD16 12
PUSH 0x0800
JNE drop
RET 1
drop:
RET 0Bad:
HASH5
FLOW_LOAD
; ... some code ...
HASH5
FLOW_LOAD ; RedundantGood:
HASH5
DUP ; Keep hash on stack
FLOW_LOAD
; ... some code ...
SWAP ; Get hash back
FLOW_LOADEnable JIT for maximum performance:
vfm_state_t *vm = vfm_create();
vfm_enable_jit(vm); // Enable JIT compilation
vfm_load_program_file(vm, "filter.bin");#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);
}Always verify compiled bytecode:
# Compile
./tools/vfm-asm myfilter.vfm -o myfilter.bin
# Verify
./tools/vfm-dis -a -x myfilter.binCreate 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 0Implement 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;
}if (vfm_verify(program, len) != VFM_SUCCESS) {
// Reject untrusted program
return -1;
}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
}
}Common offsets:
- Ethernet header: 0-13
- IP header: 14-33 (minimum)
- TCP header: 34-53 (minimum)
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: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; 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; 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; 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; 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 0Operation completed successfully.
Memory allocation failed. Check system resources.
Program structure is invalid. Check program size and format.
Unknown instruction encountered. Verify bytecode integrity.
Stack limit exceeded. Reduce stack usage or increase limit.
Attempted to pop from empty stack. Check program logic.
Packet access out of bounds. Validate packet offsets.
Division by zero attempted. Add zero checks before division.
Instruction limit exceeded. Optimize program or increase limit.
Program failed safety verification. Check for:
- Invalid jumps
- Unreachable code
- Stack inconsistencies
- Infinite loops
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.