$npx -y skills add tikoci/routeros-skills --skill routeros-firewallRouterOS firewall filter, NAT, mangle, and address-list configuration. Use when: writing firewall rules in RouterOS, configuring NAT, setting up address-lists or interface-lists, writing idempotent firewall scripts, configuring DNS redirect or port forwarding, or when the user me
| 1 | # RouterOS Firewall |
| 2 | |
| 3 | ## Rule Ordering — Sequential, Not Priority-Based |
| 4 | |
| 5 | Rules are evaluated **top-to-bottom** — first match wins. This is the biggest source of iptables confusion. |
| 6 | |
| 7 | - `place-before=0` inserts at the top; default `add` appends at the bottom |
| 8 | - An `action=accept` rule must appear BEFORE any `action=drop` for the same traffic |
| 9 | - **Non-terminal actions do NOT stop evaluation:** `action=add-src-to-address-list`, `action=add-dst-to-address-list`, `action=log`, and any rule with `passthrough=yes` continue to the next rule. A `drop` rule below an `add-src-to-address-list` will still fire. |
| 10 | |
| 11 | ```routeros |
| 12 | # WRONG — drop fires before accept can match |
| 13 | /ip/firewall/filter/add chain=input action=drop |
| 14 | /ip/firewall/filter/add chain=input src-address=10.0.0.1 action=accept |
| 15 | |
| 16 | # CORRECT — accept first, drop catches the rest |
| 17 | /ip/firewall/filter/add chain=input src-address=10.0.0.1 action=accept place-before=0 |
| 18 | /ip/firewall/filter/add chain=input action=drop |
| 19 | ``` |
| 20 | |
| 21 | ## Address-Lists as Dynamic Selectors |
| 22 | |
| 23 | LLMs rarely suggest this pattern — they write one rule per IP address instead. Address-lists scale to hundreds of IPs with a single firewall rule. |
| 24 | |
| 25 | ```routeros |
| 26 | # Build the list (static or dynamic with auto-expiry) |
| 27 | /ip/firewall/address-list/add list=trusted-mgmt address=192.168.1.0/24 |
| 28 | /ip/firewall/address-list/add list=trusted-mgmt address=10.0.0.5 timeout=1h |
| 29 | |
| 30 | # One rule handles all list members |
| 31 | /ip/firewall/filter/add chain=input src-address-list=trusted-mgmt action=accept \ |
| 32 | comment="myapp-accept-mgmt" |
| 33 | ``` |
| 34 | |
| 35 | Dynamic entries with `timeout=` expire automatically — the primary pattern for DoS blacklists. |
| 36 | |
| 37 | ## Interface-Lists as Rule Selectors |
| 38 | |
| 39 | `in-interface-list=` / `out-interface-list=` — powerful RouterOS pattern LLMs never propose. Eliminates duplicate rules when multiple interfaces serve the same role. |
| 40 | |
| 41 | ```routeros |
| 42 | # Define the group once |
| 43 | /interface/list/add name=WAN |
| 44 | /interface/list/member/add list=WAN interface=ether1 |
| 45 | /interface/list/member/add list=WAN interface=pppoe-out1 |
| 46 | |
| 47 | # One rule applies to all WAN interfaces |
| 48 | /ip/firewall/filter/add chain=input in-interface-list=WAN action=drop \ |
| 49 | comment="myapp-drop-all-wan" |
| 50 | ``` |
| 51 | |
| 52 | ## Comment-as-Tag Pattern (Idempotent Scripts) |
| 53 | |
| 54 | RouterOS has no "upsert" — re-running a script without cleanup creates duplicate rules. Use a comment prefix as a tag: |
| 55 | |
| 56 | ```routeros |
| 57 | # Remove only rules we own — preserves rules from other tools |
| 58 | /ip/firewall/filter/remove [find comment~"myapp-"] |
| 59 | /ip/firewall/address-list/remove [find comment~"myapp-"] |
| 60 | /ip/firewall/nat/remove [find comment~"myapp-"] |
| 61 | |
| 62 | # Add with consistent tag — readable in /print output |
| 63 | /ip/firewall/filter/add chain=input src-address-list=trusted-mgmt action=accept \ |
| 64 | comment="myapp-accept-mgmt" |
| 65 | /ip/firewall/filter/add chain=input in-interface-list=WAN action=drop \ |
| 66 | comment="myapp-drop-wan" |
| 67 | ``` |
| 68 | |
| 69 | **Never use `remove [find dynamic=no]`** — this deletes ALL static rules including those added by management tools. Some tools (e.g. OptiWize) mark their rules with `comment~"#orchestrator-*"` — a bulk remove silently breaks remote management. |
| 70 | |
| 71 | ## Connection State |
| 72 | |
| 73 | RouterOS `connection-state=` is not iptables `-m state`: |
| 74 | |
| 75 | ```routeros |
| 76 | /ip/firewall/filter/add chain=input connection-state=established,related action=accept |
| 77 | /ip/firewall/filter/add chain=input connection-state=invalid action=drop |
| 78 | ``` |
| 79 | |
| 80 | RouterOS states: `new`, `established`, `related`, `invalid`, `untracked`. |
| 81 | |
| 82 | `untracked` matches packets explicitly marked via `/ip/firewall/raw action=notrack` — it does NOT match FastTrack flows. FastTrack is a separate fast-path that keeps flows in conntrack but bypasses mangle. Never combine `fasttrack-connection` with mangle-based routing marks on the same traffic — mangle marks are not applied to fasttracked packets. |
| 83 | |
| 84 | ## NAT Patterns |
| 85 | |
| 86 | ```routeros |
| 87 | # Port forward (dst-nat) |
| 88 | /ip/firewall/nat/add chain=dstnat \ |
| 89 | dst-port=8080 protocol=tcp in-interface=ether1 \ |
| 90 | action=dst-nat to-addresses=192.168.1.10 to-ports=80 \ |
| 91 | comment="portfwd-web" |
| 92 | |
| 93 | # Force DNS through router (prevents DNS bypass) |
| 94 | /ip/firewall/nat/add chain=dstnat action=redirect \ |
| 95 | in-interface-list=LAN dst-port=53 protocol=udp to-ports=53 \ |
| 96 | comment="force-dns-udp" |
| 97 | /ip/firewall/nat/add chain=dstnat action=redirect \ |
| 98 | in-interface-list=LAN dst-port=53 protocol=tcp to-ports=53 \ |
| 99 | comment="force-dns-tcp" |
| 100 | |
| 101 | # Masquerade outgoing traffic |
| 102 | /ip/firewall/nat/add chain=srcnat action=masquerade \ |
| 103 | out-interface=ether1 comment="nat-wan" |
| 104 | ``` |
| 105 | |
| 106 | ## Layer7 Protocol (L7) |
| 107 | |
| 108 | L7 matches unencrypted payload content with a POSIX regex — CPU-intensive, use sparingly: |