Capture filters
Redhound compiles filters to classic BPF in Ruby. Live capture attaches the verified program to the kernel; file capture executes the same instructions in the Ruby VM. No libpcap or external command is needed at runtime.
program = Redhound::Filter.compile('ip and udp dst port 53', linktype: :ethernet)
program.match?(packet)
program.instructions # [code, jt, jf, k] tuples
program.packed # native struct sock_filter bytes
program.disassemble(format: :text) # :ruby and :decimal are also available
Supported primitives include ip, ip6, arp, rarp, tcp, udp, sctp, icmp, icmp6, and igmp; host, net, port, portrange, proto; src, dst, src or dst, src and dst; Ethernet addresses and EtherTypes; vlan [id]; greater, less, broadcast, and multicast. Numeric IPv4 and IPv6 addresses, CIDR networks, IPv4 netmasks, host names, protocol names, and service names are resolved when compiling. For reserved protocol names after proto, tcpdump spells them with an escape, e.g. ip proto \tcp; Redhound also accepts the unescaped spelling.
Identical qualifiers carry forward: tcp dst port 80 or 443 means tcp dst port 80 or tcp dst port 443. Parenthesized operand lists work as well: tcp dst port (80 or 443).
As specified by pcap-filter, and and or have equal precedence and associate from left to right; not binds more tightly. Thus udp or tcp and port 443 means (udp or tcp) and port 443. Use parentheses to express a different grouping. This corrects the separate AND/OR precedence in the original design’s EBNF and is checked against tcpdump.
Arithmetic supports len, packet accesses of width 1, 2, or 4, + - * / % & | ^ << >>, unary minus, and comparisons = == != < <= > >= with unsigned 32-bit values:
ip[2:2] > 576
tcp[tcpflags] & (tcp-syn | tcp-fin) != 0
(ip[0] & 15) * 4 = 20
icmp6[0] = 128
len >= 100
Transport accesses and ports check IPv4 fragment offsets and honor IPv4 options. IPv6 ports require a directly following TCP/UDP/SCTP header; extension-header traversal (protochain) is outside the v2 subset. Plain protocol predicates additionally recognize an IPv6 fragment header’s next-header field. tcp[], udp[], icmp[], and igmp[] access IPv4 transport headers, matching libpcap; icmp6[] accesses a directly following ICMPv6 header.
vlan changes the offsets for all following primitives, including those in subsequent or branches, matching tcpdump. Repeated vlan predicates support nested tags. Linux live Ethernet filters also recognize hardware-stripped tags through VLAN ancillary loads; the VM reads these from packet.meta[:vlan_tci] (including tag ID zero). Linux any filters compile for kernel network-layer bytes and protocol metadata, while file filters use the synthetic SLL2 header.
Linktypes are Ethernet (1), RAW (101), Linux SLL (113), Linux SLL2 (276), NULL (0), LOOP (108), IPv4 (228), and IPv6 (229). Their corresponding symbol names are :ethernet, :raw, :linux_sll, :linux_sll2, :null, :loop, :ipv4, and :ipv6. Ethernet address and VLAN predicates require Ethernet. IPv4 broadcast without an interface netmask recognizes zero and all-one destinations; link broadcast compares the Ethernet destination.
FilterSyntaxError supplies the original expression and error position. Programs are limited to 4096 instructions and 16 scratch words; oversized programs raise FilterTooLarge. The verifier rejects invalid opcodes, jumps, memory accesses, uninitialized scratch reads, and constant zero divisors. Truncated packet loads and dynamic zero divisors reject the packet. Conditional branches beyond 255 instructions use forward jump trampolines.
The differential suite contains more than 160 expressions and compares selected packets and tcpdump-generated bytecode on Ethernet, RAW, SLL, SLL2, and NULL. tcpdump is a test dependency only. Historical protocols, gateway, and protochain are unsupported; network operands use numeric addresses rather than /etc/networks aliases.