Mangle: Packet Marking and Traffic Classification
Mangle: Packet Marking and Traffic Classification
Section titled “Mangle: Packet Marking and Traffic Classification”The RouterOS mangle table is the classification layer between the network and your policies. It does not accept or drop packets — instead it marks them so that other subsystems (queues, routing, NAT) can act on those marks. A mangle rule that matches a packet can assign a connection-mark, packet-mark, or routing-mark, or it can rewrite header fields such as DSCP or TTL.
Mangle is the foundation for:
- QoS — packet marks drive queue-tree priorities and rates
- Policy-based routing (PBR) — routing marks select which routing table a packet uses
- DSCP remarking — modify the DSCP/ToS byte for interoperability with upstream equipment
Quick Start
Section titled “Quick Start”Mark all DNS traffic for a priority queue and route VoIP via a secondary gateway:
# Mark DNS connections and their packets/ip firewall mangleadd chain=prerouting protocol=udp dst-port=53 \ action=mark-connection new-connection-mark=dns-conn passthrough=yesadd chain=prerouting connection-mark=dns-conn \ action=mark-packet new-packet-mark=dns-pkt passthrough=no
# Mark VoIP for policy routingadd chain=prerouting protocol=udp dst-port=5060,10000-20000 \ action=mark-routing new-routing-mark=voip-table passthrough=noChains
Section titled “Chains”Mangle has five chains, each running at a different point in the packet flow:
| Chain | Traversed by | Typical use |
|---|---|---|
prerouting | All incoming packets, before routing | Classify inbound traffic; PBR routing marks |
input | Packets destined for the router itself | Mark traffic to the router |
forward | Packets routed through the router | Mark transit traffic |
output | Packets originating from the router | Mark router-generated traffic |
postrouting | All outgoing packets, after routing | Final classification before egress |
Chain Selection Guide
Section titled “Chain Selection Guide”- PBR routing marks — must be set in
prerouting(before the routing lookup) - QoS packet marks —
preroutingorforwardboth work; useforwardto skip locally-generated traffic - Upload shaping —
postroutingcatches packets as they leave, ensuring marks are set after NAT - Router’s own traffic —
outputchain
Actions
Section titled “Actions”mark-connection
Section titled “mark-connection”Attaches a mark to the connection tracking entry. All subsequent packets in the same connection inherit this mark, which you can then match with connection-mark.
/ip firewall mangle add \ chain=prerouting \ src-address=192.168.10.0/24 \ action=mark-connection \ new-connection-mark=lan-conn \ passthrough=yesmark-connection is almost always used with passthrough=yes so that a mark-packet rule can run immediately after.
mark-packet
Section titled “mark-packet”Attaches a mark to the individual packet. Queue trees and simple queues match on packet-mark.
/ip firewall mangle add \ chain=prerouting \ connection-mark=lan-conn \ action=mark-packet \ new-packet-mark=lan-pkt \ passthrough=nomark-routing
Section titled “mark-routing”Sets the routing table used for the forwarding lookup. The named table must exist under /ip route with the matching routing-table value.
/ip firewall mangle add \ chain=prerouting \ src-address=192.168.20.0/24 \ action=mark-routing \ new-routing-mark=isp2-table \ passthrough=nochange-dscp
Section titled “change-dscp”Rewrites the 6-bit DSCP field in the IP header. Use this to enforce a DSCP policy at the network edge or to remark traffic before it leaves your network.
# Remark VoIP to EF (Expedited Forwarding, DSCP 46)/ip firewall mangle add \ chain=postrouting \ protocol=udp \ dst-port=10000-20000 \ action=change-dscp \ new-dscp=46change-ttl
Section titled “change-ttl”Modifies the IP TTL value. Useful for hiding the hop count behind a CPE or for forcing packets to expire at a specific point.
/ip firewall mangle add \ chain=postrouting \ out-interface=ether1 \ action=change-ttl \ new-ttl=set:64new-ttl accepts set:<value>, increment:<n>, or decrement:<n>.
fasttrack-connection
Section titled “fasttrack-connection”Offloads an established connection to the FastPath, bypassing most firewall and queue processing. Primarily used in the filter table but also available in mangle.
fasttrack-connection in mangle bypasses queue processing. Do not use it on connections you intend to shape with queues.
strip-ipv4-options
Section titled “strip-ipv4-options”Removes IP options from the packet header. Used to prevent IP source routing and similar abuses.
/ip firewall mangle add \ chain=prerouting \ action=strip-ipv4-options \ passthrough=yesMarking New Connections vs. Established
Section titled “Marking New Connections vs. Established”RouterOS connection tracking assigns every packet a connection-state: new, established, related, or invalid. Exploiting this in mangle rules avoids running expensive matchers (port checks, protocol inspection, address lists) on every packet.
The Efficient Two-Rule Pattern
Section titled “The Efficient Two-Rule Pattern”Mark the connection once — on the first (new) packet — then derive the packet mark from the connection mark on all subsequent packets:
/ip firewall mangle
# Step 1: Mark the CONNECTION only on new packets# (runs once per connection, not on every packet)add chain=prerouting connection-state=new protocol=tcp dst-port=443 \ action=mark-connection new-connection-mark=https-conn passthrough=yes
# Step 2: Mark every PACKET using the connection mark# (simple lookup — no protocol/port re-evaluation)add chain=prerouting connection-mark=https-conn \ action=mark-packet new-packet-mark=https-pkt passthrough=noWithout connection-state=new, the mark-connection rule fires on every packet — unnecessary since the connection tracking entry is already marked after the first packet.
Why This Matters
Section titled “Why This Matters”| Approach | CPU cost | When mark is set |
|---|---|---|
mark-connection + connection-state=new | Low — runs once per connection | First packet only |
mark-connection without state filter | Medium — runs on every packet | Every packet (redundant after first) |
mark-packet with port matchers, no connection mark | High — evaluates all matchers per packet | Every packet |
Handling Asymmetric Flows
Section titled “Handling Asymmetric Flows”In asymmetric routing scenarios (where reply packets take a different path), connection-state=established,related may never be seen on a given router. In that case, mark every packet with port matchers rather than relying on connection tracking:
# Asymmetric: use direct packet-mark on every packetadd chain=prerouting protocol=tcp dst-port=443 \ action=mark-packet new-packet-mark=https-pkt passthrough=noMarking Reply Traffic
Section titled “Marking Reply Traffic”To mark the return direction of a connection, match on connection-mark in a separate chain. Because the destination of the reply is the original source, use forward or postrouting depending on where you need the mark:
# Mark outbound VoIP connectionsadd chain=prerouting connection-state=new protocol=udp dst-port=10000-20000 \ action=mark-connection new-connection-mark=voip-conn passthrough=yes
# Mark packets in both directionsadd chain=prerouting connection-mark=voip-conn \ action=mark-packet new-packet-mark=voip passthrough=noadd chain=postrouting connection-mark=voip-conn \ action=mark-packet new-packet-mark=voip passthrough=nopassthrough vs. non-passthrough
Section titled “passthrough vs. non-passthrough”Every mangle action has a passthrough flag that controls whether rule evaluation continues after a match:
passthrough | Behaviour |
|---|---|
yes | Set mark and continue evaluating subsequent rules |
no | Set mark and stop — no further mangle rules are evaluated for this packet |
Use passthrough=yes on mark-connection rules so that a mark-packet rule can fire on the same packet. Use passthrough=no as the final action to avoid unnecessary rule checks.
# Pattern: connection mark with passthrough=yes, packet mark with passthrough=noadd chain=prerouting protocol=tcp dst-port=443 \ action=mark-connection new-connection-mark=https-conn passthrough=yesadd chain=prerouting connection-mark=https-conn \ action=mark-packet new-packet-mark=https-pkt passthrough=noKey Matchers
Section titled “Key Matchers”Mangle shares all matchers with the firewall filter. The most commonly used in classification rules:
| Matcher | Example value | Notes |
|---|---|---|
src-address | 192.168.1.0/24 | Source IP or subnet |
dst-address | 10.0.0.0/8 | Destination IP or subnet |
protocol | tcp, udp, icmp | IP protocol |
src-port / dst-port | 80, 443, 5060-5090 | Requires protocol set |
in-interface | ether1, vlan10 | Ingress interface |
out-interface | ether2 | Egress interface (postrouting/output) |
connection-state | established,related | Connection tracking state |
connection-mark | voip-conn | Match on a previously set connection mark |
packet-mark | bulk-pkt | Match on a previously set packet mark |
dscp | 46 | DSCP value (0–63) |
packet-size | 0-100 | Bytes, useful for marking small (interactive) packets |
layer7-protocol | <l7 protocol name> | Deep-packet inspection — high CPU cost |
Practical Examples
Section titled “Practical Examples”QoS: Three-class Traffic Marking
Section titled “QoS: Three-class Traffic Marking”Mark traffic into three priority classes for use with a queue tree:
/ip firewall mangle
# VoIP — highest priorityadd chain=prerouting protocol=udp dst-port=5060,10000-20000 \ action=mark-connection new-connection-mark=voip-conn passthrough=yesadd chain=prerouting connection-mark=voip-conn \ action=mark-packet new-packet-mark=voip passthrough=no
# Interactive — medium priority (SSH, DNS, HTTP/S small packets)add chain=prerouting protocol=tcp dst-port=22,80,443 \ packet-size=0-512 \ action=mark-connection new-connection-mark=interactive-conn passthrough=yesadd chain=prerouting connection-mark=interactive-conn \ action=mark-packet new-packet-mark=interactive passthrough=no
# Bulk — everything else (default, mark-packet=bulk-pkt)add chain=prerouting \ action=mark-packet new-packet-mark=bulk passthrough=noThen reference the marks in a queue tree:
/queue treeadd name=voip parent=global packet-mark=voip priority=1add name=interactive parent=global packet-mark=interactive priority=4add name=bulk parent=global packet-mark=bulk priority=8Policy-Based Routing: Dual ISP
Section titled “Policy-Based Routing: Dual ISP”Route traffic from one subnet via ISP2, all other traffic via ISP1 (default route):
# Create a routing table for ISP2/routing table add name=isp2-table fib
# Add ISP2 default route in that table/ip route add dst-address=0.0.0.0/0 gateway=203.0.113.1 routing-table=isp2-table
# Mangle: mark LAN2 traffic to use isp2-table/ip firewall mangle add \ chain=prerouting \ src-address=192.168.2.0/24 \ action=mark-routing \ new-routing-mark=isp2-table \ passthrough=noDSCP Remarking at the Edge
Section titled “DSCP Remarking at the Edge”Honour DSCP from trusted internal hosts, remark untrusted external traffic to Best Effort (DSCP 0):
# Trust internal DSCP — no action needed, leave it# Remark inbound external traffic to BE/ip firewall mangle add \ chain=prerouting \ in-interface=ether1-wan \ action=change-dscp \ new-dscp=0 \ comment="Remark untrusted inbound to Best Effort"
# Re-mark internal VoIP to EF before egress/ip firewall mangle add \ chain=postrouting \ src-address=192.168.1.0/24 \ protocol=udp dst-port=10000-20000 \ action=change-dscp new-dscp=46 \ comment="EF for VoIP egress"Per-Connection Upload Shaping
Section titled “Per-Connection Upload Shaping”Mark upload connections in postrouting (after NAT, so correct source addresses are visible):
/ip firewall mangleadd chain=postrouting out-interface=ether1-wan \ src-address=192.168.0.0/16 \ action=mark-connection new-connection-mark=upload-conn passthrough=yesadd chain=postrouting connection-mark=upload-conn \ action=mark-packet new-packet-mark=upload-pkt passthrough=noDual-ISP Load Balancing with PCC
Section titled “Dual-ISP Load Balancing with PCC”PCC (Per Connection Classifier) hashes selected packet header fields — source address, destination address, and/or ports — to distribute connections evenly across two or more uplinks while keeping each individual connection pinned to a single WAN interface (avoiding TCP resets from IP changes mid-flow).
The pattern is:
- Mark inbound WAN connections to their respective ISP (for reply symmetry)
- Use PCC to split new LAN-originated connections 50/50 between ISPs
- Convert connection marks to routing marks
- Install per-ISP default routes in dedicated routing tables
Assumptions: ether1 = WAN1 (gateway 1.1.1.1), ether2 = WAN2 (gateway 2.2.2.1), bridge = LAN (192.168.88.0/24).
# Step 1: Create routing tables/routing tableadd name=to-wan1 fibadd name=to-wan2 fib
# Step 2: Install per-table default routes with gateway health check/ip routeadd dst-address=0.0.0.0/0 gateway=1.1.1.1 routing-table=to-wan1 check-gateway=pingadd dst-address=0.0.0.0/0 gateway=2.2.2.1 routing-table=to-wan2 check-gateway=ping# Main table routes (for router itself and fallback)add dst-address=0.0.0.0/0 gateway=1.1.1.1 distance=1 check-gateway=pingadd dst-address=0.0.0.0/0 gateway=2.2.2.1 distance=2 check-gateway=ping
# Step 3: Mangle — mark inbound WAN connections for reply symmetry/ip firewall mangleadd chain=prerouting in-interface=ether1 connection-state=new \ action=mark-connection new-connection-mark=conn-wan1 passthrough=yes \ comment="Tag connections entering via WAN1"add chain=prerouting in-interface=ether2 connection-state=new \ action=mark-connection new-connection-mark=conn-wan2 passthrough=yes \ comment="Tag connections entering via WAN2"
# Step 4: Mangle — PCC split for LAN-originated new connectionsadd chain=prerouting in-interface=bridge connection-state=new \ dst-address-type=!local \ per-connection-classifier=both-addresses-and-ports:2/0 \ action=mark-connection new-connection-mark=conn-wan1 passthrough=yes \ comment="PCC bucket 0 -> WAN1"add chain=prerouting in-interface=bridge connection-state=new \ dst-address-type=!local \ per-connection-classifier=both-addresses-and-ports:2/1 \ action=mark-connection new-connection-mark=conn-wan2 passthrough=yes \ comment="PCC bucket 1 -> WAN2"
# Step 5: Mangle — convert connection marks to routing marksadd chain=prerouting in-interface=bridge \ connection-mark=conn-wan1 action=mark-routing \ new-routing-mark=to-wan1 passthrough=noadd chain=prerouting in-interface=bridge \ connection-mark=conn-wan2 action=mark-routing \ new-routing-mark=to-wan2 passthrough=no
# Step 6: Apply same routing marks to router-originated trafficadd chain=output connection-mark=conn-wan1 \ action=mark-routing new-routing-mark=to-wan1 passthrough=noadd chain=output connection-mark=conn-wan2 \ action=mark-routing new-routing-mark=to-wan2 passthrough=no
# Step 7: NAT — masquerade per WAN interface/ip firewall natadd chain=srcnat out-interface=ether1 action=masqueradeadd chain=srcnat out-interface=ether2 action=masqueradeHow check-gateway=ping provides failover: When a gateway stops responding to pings, RouterOS marks its routes as inactive. Traffic marked for the dead ISP’s routing table finds no active default route in that table and falls back to the main table, using the surviving ISP.
How Mangle Interacts with Queues and Routing
Section titled “How Mangle Interacts with Queues and Routing”Queues
Section titled “Queues”Simple queues and queue trees match on packet-mark. The mark is checked at the point where the packet enters the queue:
- Simple queue — evaluated top-down; first matching
packet-markwins - Queue tree — hierarchical; parent queues aggregate child queues by packet mark
Ensure packet marks are set before the queuing point in the packet flow. prerouting and forward marks are visible to both simple queues and queue trees on the router.
Routing Tables
Section titled “Routing Tables”mark-routing selects a named routing table (/routing table). This runs after the prerouting mangle chain and before the main routing lookup. If no route is found in the named table, the packet falls back to the main table unless fib was explicitly configured without a fallback.
Troubleshooting
Section titled “Troubleshooting”Marks Not Applied
Section titled “Marks Not Applied”Check rule order — mangle rules are evaluated top-down. A passthrough=no rule earlier in the chain can prevent later rules from running.
# View mangle rules with statistics/ip firewall mangle print statsLook at the bytes and packets columns. A rule with zero packets has never matched.
Routing Mark Has No Effect
Section titled “Routing Mark Has No Effect”Verify:
- The rule is in
prerouting(notforward) - The routing table exists:
/routing table print - A route exists in that table:
/ip route print where routing-table=<name>
# Check if the routing mark is being applied/ip firewall mangle print stats where action=mark-routingQoS Marks Not Reaching Queues
Section titled “QoS Marks Not Reaching Queues”If FastTrack is enabled in the filter table, marked connections are bypassed at the FastTrack stage — the queue never sees them. Either disable FastTrack for connections you want to shape, or use connection-mark=no-mark as a FastTrack guard:
# Only FastTrack unmarked connections/ip firewall filter add chain=forward \ connection-state=established,related \ connection-mark=no-mark \ action=fasttrack-connectionVerifying Active Marks
Section titled “Verifying Active Marks”Inspect the connection table to confirm marks are being set:
/ip firewall connection print where connection-mark~"."This shows all tracked connections that have a non-empty connection mark.
Common Pitfalls
Section titled “Common Pitfalls”mark-routing in the wrong chain
Section titled “mark-routing in the wrong chain”mark-routing only affects the routing lookup if it is set before that lookup runs. The routing lookup happens after prerouting mangle. Placing mark-routing in forward or postrouting has no effect on which table is used — the routing decision is already made.
| Chain | mark-routing works? | Reason |
|---|---|---|
prerouting | Yes (forwarded traffic) | Before routing lookup |
output | Yes (router-originated) | Before routing lookup for local traffic |
forward | No | After routing lookup |
postrouting | No | After routing lookup |
passthrough misuse
Section titled “passthrough misuse”passthrough=yes on a terminal marking rule allows subsequent rules to overwrite the mark. This leads to silent re-marking bugs that are hard to diagnose. Use passthrough=no on the last rule that sets a mark for a given traffic class. Reserve passthrough=yes for mark-connection rules that are immediately followed by a mark-packet rule.
# Wrong: passthrough=yes on terminal rule — later rules can overwrite the markadd chain=prerouting protocol=tcp dst-port=443 \ action=mark-packet new-packet-mark=https passthrough=yes # BUG
# Correct: passthrough=no stops evaluation after the mark is setadd chain=prerouting protocol=tcp dst-port=443 \ action=mark-packet new-packet-mark=https passthrough=no # OKMissing connection-state=new on mark-connection
Section titled “Missing connection-state=new on mark-connection”Without connection-state=new, a mark-connection rule fires on every packet in the connection — even though the connection tracking entry is already marked after the first packet. This adds unnecessary CPU work on high-throughput links.
# Inefficient: runs the matcher on every packetadd chain=prerouting protocol=udp dst-port=5060 \ action=mark-connection new-connection-mark=sip-conn passthrough=yes
# Efficient: matcher runs once per connectionadd chain=prerouting connection-state=new protocol=udp dst-port=5060 \ action=mark-connection new-connection-mark=sip-conn passthrough=yesFastTrack bypasses mangle marks and queues
Section titled “FastTrack bypasses mangle marks and queues”When FastTrack is active in the filter table, connections offloaded to the FastPath bypass mangle processing entirely — queue trees never see those packets. FastTrack also only works with the main routing table, so policy-routed connections that are fasttracked will silently use the wrong gateway.
# Guard: only FastTrack connections with no routing or queue marks/ip firewall filteradd chain=forward connection-state=established,related \ connection-mark=no-mark action=fasttrack-connectionadd chain=forward connection-state=established,related action=acceptPacket mark limit
Section titled “Packet mark limit”RouterOS enforces a hard limit of 4096 unique packet marks. Exceeding this limit produces a bad new packet mark error and the rule is silently ignored. Use a flat, small set of named marks (e.g., voip, interactive, bulk) rather than per-host or per-port marks.
Asymmetric routing and invalid-state drops
Section titled “Asymmetric routing and invalid-state drops”A common firewall template includes:
/ip firewall filter add chain=forward connection-state=invalid action=dropIn asymmetric routing topologies (where forward and return paths differ), valid return packets may appear invalid to the router because it never saw the SYN. Dropping them silently breaks those flows. Remove or narrow this rule when asymmetric routing is intentional.
Related Topics
Section titled “Related Topics”- Packet Flow in RouterOS — where mangle chains sit in the processing pipeline
- Firewall Filter Rules — accept/drop decisions
- Queue Tree — using packet marks for hierarchical QoS
- Queue Types — PCQ, RED, SFQ using packet marks
- Connection Tracking — connection states and how marks persist
- Firewall Case Studies — complete multi-WAN and QoS configurations