Rule Engine Design: Aegis Packet Engine
1. Rule Types & Formats
Aegis matches packets against blocking rules in real-time. The rule engine must evaluate packets in nanoseconds.
Supported Matchers:
- IP Match: Match a single IPv4 (e.g.
192.168.1.100) or IPv6 address. - Subnet / CIDR Match: Match an IP against a CIDR range (e.g.
10.0.0.0/8,192.168.1.0/24). - Application Match: Match an
AppType(e.g.AppType::YOUTUBE). - Domain Match: Substring or exact suffix matching on the SNI or HTTP Host (e.g.
facebook.com). - Port & Protocol Match: Match destination port (e.g. 443) and protocol (TCP/UDP).
2. IP & Subnet Matching (CIDR)
For matching IP addresses against subnets, a linear array scan is slow if there are many rules.
Matching Strategies:
- Small Rule Sets (< 64 rules): A flat array of
(IP, Mask)structs. Checked using SIMD or simple bitwise operations: - Large Rule Sets: A binary Prefix Tree (Trie) where each bit of the IP decides the path. The leaves contain the block actions. This provides $O(1)$ lookup time relative to rule count (max 32 steps for IPv4).
3. Domain Matching Optimization
Domain rules are stored in a structure optimized for suffix matching.
Design:
- Suffix Trie: Domains are reversed and inserted into a Trie.
- Example:
youtube.comis stored asm.o.c.e.b.u.t.u.o.y. - When a packet with SNI
www.youtube.comarrives, we reverse the hostname (m.o.c.e.b.u.t.u.o.y.w.w.w) and scan the Trie from root. - This allows fast exact and suffix matches (
*.youtube.com).
4. Concurrent Rule Hot-Reload (RCU Pattern)
Administrators must be able to reload rules without pausing packet processing or introducing lock contention on worker threads.
Double-Buffering Rule Swap:
- Rules Container: Rules are encapsulated in a
RuleTableclass. - Atomic Pointer: The engine maintains an atomic pointer to the current rule set:
std::atomic<const RuleTable*> active_rules_. - Worker Processing Loop:
- Reload Thread:
- Parses the new rules file.
- Allocates a new
RuleTableon the heap:RuleTable* new_rules = new RuleTable(...). - Swaps the atomic pointer:
const RuleTable* old_rules = active_rules_.exchange(new_rules, std::memory_order_acq_rel). - Waits briefly (or schedules deletion) to ensure all worker threads have finished processing packets using the
old_rulespointer (epoch-based reclamation or simple safe delay), then deletesold_rules.