Firewall rules

AiFw rules are stateful pf rules that live in a dedicated aifw anchor and are persisted in SQLite. The rule engine compiles each row into pf syntax, calls pfctl (or the in-memory mock on Linux/WSL), and never touches the system pf config. Match by interface, address family, protocol, source/destination with optional invert, attach a schedule, and reorder by drag-and-drop.

Quickstart

Open the Web UI and go to Firewall → Rules → Add rule. Choose an action (pass, block, block drop, or block return), a direction (in, out, any), an IP version (inet, inet6, both — default), a protocol, source and destination addresses with optional ports, and an optional schedule.

Create one from the API:

curl -X POST https://aifw.local/api/v1/rules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "pass",
    "direction": "in",
    "ip_version": "both",
    "protocol": "tcp",
    "rule_match": {
      "src_addr": "any",
      "dst_addr": "any",
      "dst_port": "443"
    },
    "log": false,
    "quick": true,
    "label": "allow-https"
  }'

Reorder rules in bulk:

curl -X PUT https://aifw.local/api/v1/rules/reorder \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"order": ["<uuid-1>", "<uuid-2>", "<uuid-3>"]}'

Push the new ruleset to pf:

curl -X POST https://aifw.local/api/v1/reload \
  -H "Authorization: Bearer $TOKEN"

CLI

aifw rules add --action pass --direction in --proto tcp --dst-port 443 --label "allow-https"
aifw rules add --action block --direction in --proto tcp --dst-port 22 --src 10.0.0.0/8
aifw rules list
aifw rules remove <uuid>
aifw reload

API endpoints

Method Endpoint Description
GET /api/v1/rules List all AiFw rules
GET /api/v1/rules/system Show the system pf rules outside the aifw anchor
GET /api/v1/rules/{id} Get one rule
POST /api/v1/rules Create a rule
PUT /api/v1/rules/{id} Update a rule
DELETE /api/v1/rules/{id} Delete a rule
PUT /api/v1/rules/reorder Reorder rules by UUID array
PUT /api/v1/rules/block-logging Toggle the global block-rules-log default
POST /api/v1/reload Recompile and apply all rules + NAT to pf
GET /api/v1/aliases List aliases
POST /api/v1/aliases Create an alias
PUT /api/v1/aliases/{id} Update an alias
DELETE /api/v1/aliases/{id} Delete an alias
GET /api/v1/schedules List schedules
POST /api/v1/schedules Create a schedule

Aliases

An alias is a named bag of addresses, networks, hosts, or ports that is referenced in the source or destination of a rule. Aliases compile to pf tables on reload, so adding an entry is one INSERT plus one reload — no rule edit required. Use them for “trusted admins”, “blocked countries”, “internal RFC1918”, etc. They keep the rule list short and let you bulk-update membership without touching policy.

Scheduling

A schedule is a named time window with two fields:

  • time_ranges — one or more HH:MM-HH:MM ranges, e.g. "08:00-12:00,13:00-17:00".
  • days_of_week — comma-separated short names, e.g. "mon,tue,wed,thu,fri".

Attach a schedule_id to a rule and the rule is only loaded into pf during the active window. Outside the window the rule is silently dropped from the compiled ruleset.

Traffic shaping

Queue policy lives alongside rules. Three queue disciplines are supported via the aifw queue subcommand:

  • CoDel — default, low-latency AQM. Best for general LAN/WAN egress.
  • HFSC — hierarchical share-based scheduler with bandwidth percentages. Use when you need strict per-class allocations (VoIP, interactive, default, bulk).
  • PRIQ — strict priority queueing. Highest-priority class always preempts lower ones.
aifw queue add --name voip --interface em0 --type hfsc --bandwidth 1Gb --class voip --pct 20

Queues are a separate anchor; the firewall rule references the queue by name.

Configuration

Field Default Notes
Anchor name aifw All AiFw filter rules go here; the system pf ruleset just hooks the anchor
quick true The first matching rule wins. Disable for traditional last-match semantics
log false for pass; always on for block Block rules are forced to log so dropped traffic appears in pflog0
state_options.tracking keep state none, modulate state, and synproxy state are also supported
ip_version both Set to inet or inet6 to scope a rule to a single family
priority 100 Used as the default sort key; UI drag-and-drop overrides this via /rules/reorder

State tracking only applies to pass rules. Block rules in pf cannot keep state.

See also

Last updated: