Skip to content
MikroTik RouterOS Docs

MikroTik RouterOS Firewall Mangle: Packet Marking and Traffic Classification

MikroTik RouterOS Firewall Mangle: Packet Marking and Traffic Classification

Section titled “MikroTik RouterOS Firewall Mangle: Packet Marking and Traffic Classification”

For the impatient: here’s the 30-second version.

# Mark web traffic connections and packets for QoS
/ip firewall mangle add chain=forward connection-state=new protocol=tcp dst-port=80,443 action=mark-connection new-connection-mark=web_conn
/ip firewall mangle add chain=forward connection-mark=web_conn action=mark-packet new-packet-mark=web_pkt passthrough=no

The firewall mangle is RouterOS’s traffic classification engine - it marks packets, connections, and routes for processing by other facilities like queues, NAT, and routing tables. Unlike the firewall filter (which allows or blocks traffic), mangle identifies and categorizes traffic without affecting whether it passes or not.

Understanding mangle is essential because:

  • QoS depends on it: Queue trees use packet marks to apply bandwidth policies
  • Policy routing needs it: Route marks direct traffic through specific gateways
  • Traffic shaping requires it: Connection marks enable per-client bandwidth management
  • Advanced NAT uses it: Marked connections can be treated differently by NAT rules

This guide explains how mangle fits into the packet processing pipeline, the three types of marks, and practical patterns for traffic classification.

Mangle operates at specific points in the packet flow. Understanding where each chain processes packets is critical for effective traffic marking:

┌─────────────┐
│ INPUT │
│ chain │──→ Local Process
└─────────────┘
↑
Internet ──→ [PREROUTING] ──→ [Routing] ──┤
chain Decision │
↓
┌─────────────┐
│ FORWARD │
│ chain │
└─────────────┘
│
↓
Local Process ──→ [OUTPUT] ──→ [Routing] ──→ [POSTROUTING] ──→ Internet
chain Decision chain

Key insight: PREROUTING runs before the routing decision, making it ideal for policy routing marks. FORWARD only processes transit traffic, making it efficient for QoS marking of client traffic.

RouterOS has five predefined mangle chains that cannot be deleted. Each processes packets at a specific point:

When: Immediately after packet arrives on interface, before routing decision.

Use cases:

  • Policy routing (mark-routing)
  • Early traffic classification
  • Marking traffic before it splits into INPUT or FORWARD

Example: Mark all traffic from a specific VLAN for a separate routing table:

/ip firewall mangle add chain=prerouting in-interface=vlan100 action=mark-routing new-routing-mark=isp2

When: After routing decision, for packets destined to the router itself.

Use cases:

  • Mark management traffic for QoS
  • Classify services running on the router
  • Monitor traffic to router services

Example: Mark DNS queries to the router:

/ip firewall mangle add chain=input protocol=udp dst-port=53 action=mark-packet new-packet-mark=dns_queries

When: After routing decision, for packets passing through the router.

Use cases:

  • QoS packet marking for client traffic
  • Connection marking for bandwidth management
  • Traffic classification between networks

Example: Mark HTTP traffic for queue tree:

/ip firewall mangle add chain=forward protocol=tcp dst-port=80 action=mark-packet new-packet-mark=http_traffic

When: After local process generates packet, before routing decision.

Use cases:

  • Mark router-generated traffic for policy routing
  • Classify traffic from router services
  • QoS for router’s own traffic

Example: Mark router’s backup traffic:

/ip firewall mangle add chain=output protocol=tcp dst-port=21 action=mark-routing new-routing-mark=backup_link

When: Just before packet leaves the router, after all routing decisions.

Use cases:

  • Final packet modifications (DSCP, TTL)
  • Marking after NAT has been applied
  • Last chance to classify outbound traffic

Example: Set DSCP for all outbound VoIP traffic:

/ip firewall mangle add chain=postrouting protocol=udp dst-port=5060 action=change-dscp new-dscp=46

Mangle creates three distinct types of marks, each serving different purposes:

Scope: Individual packet only Persistence: Transient - exists only while packet is processed Primary use: Queue trees for bandwidth management

/ip firewall mangle add chain=forward protocol=tcp dst-port=80 action=mark-packet new-packet-mark=web_traffic

How queues use packet marks:

/queue tree add name=web_queue parent=global packet-mark=web_traffic max-limit=10M

Important: Packet marks are router-internal only. They are not transmitted across the network and cannot be seen by other devices.

Scope: All packets in a connection Persistence: Stored in connection tracking table Primary use: Reduce CPU by marking once, matching many

The connection marking pattern is essential for performance:

# Rule 1: Mark new connections (inspects headers once)
/ip firewall mangle add chain=forward connection-state=new src-address=192.168.88.0/24 \
action=mark-connection new-connection-mark=lan_conn
# Rule 2: Mark packets based on connection mark (fast lookup)
/ip firewall mangle add chain=forward connection-mark=lan_conn \
action=mark-packet new-packet-mark=lan_traffic

Why this matters: Without connection marks, every packet requires header inspection. With connection marks, only the first packet of each connection is fully analyzed - subsequent packets use a fast connection table lookup.

Scope: Packet’s routing decision Persistence: Until routing decision is made Primary use: Direct traffic through specific routing tables

# RouterOS v7: Create routing table first (required)
/routing table add name=via_isp2 fib
# Mark traffic for secondary ISP (exclude local traffic!)
/ip firewall mangle add chain=prerouting src-address=192.168.100.0/24 \
dst-address-type=!local action=mark-routing new-routing-mark=via_isp2
# Route marked traffic through ISP2
/ip route add dst-address=0.0.0.0/0 gateway=10.0.0.1 routing-table=via_isp2

Critical notes:

  • RouterOS v7 requires pre-created routing tables: Create with /routing table add name=myTable fib before using mark-routing
  • Always exclude local traffic: Use dst-address-type=!local to prevent breaking router’s own connectivity
  • FastTrack incompatible: Traffic with routing marks cannot use FastTrack

The passthrough parameter controls whether rule processing continues after a match:

passthrough=yes (default): Continue to next rule after action passthrough=no: Stop processing after action

Use when you need multiple marks on the same packet:

# Mark connection first (passthrough=yes by default)
/ip firewall mangle add chain=forward connection-state=new src-address=192.168.88.0/24 \
action=mark-connection new-connection-mark=internal_conn
# Then mark packet (same packet, second mark)
/ip firewall mangle add chain=forward connection-mark=internal_conn \
action=mark-packet new-packet-mark=internal_pkt passthrough=no

Use when the mark is final and no further processing is needed:

# Final classification - stop processing
/ip firewall mangle add chain=forward protocol=tcp dst-port=22 \
action=mark-packet new-packet-mark=ssh_traffic passthrough=no

Performance tip: Use passthrough=no on your final rules to avoid unnecessary rule traversal.

ActionDescriptionUse Case
mark-packetAdd packet markQueue tree classification
mark-connectionAdd connection markEfficient per-connection classification
mark-routingAdd routing markPolicy-based routing
change-dscpModify DSCP field (0-63)QoS marking for upstream devices
change-ttlModify TTL valuePrevent traceroute, hide hops
change-mssModify Maximum Segment SizeFix path MTU discovery issues
set-prioritySet VLAN/WMM priorityLayer 2 QoS
clear-dfRemove Don’t Fragment flagAllow fragmentation
sniff-tzspExport to TZSP receiverPacket capture to Wireshark

This section provides a practical configuration that demonstrates the core mangle concepts.

Step 1: Create Connection Marks for Traffic Classification

Section titled “Step 1: Create Connection Marks for Traffic Classification”

Mark connections by traffic type for efficient processing:

/ip firewall mangle
add chain=forward connection-state=new protocol=tcp dst-port=80,443 \
action=mark-connection new-connection-mark=web_conn comment="Mark web connections"
add chain=forward connection-state=new protocol=udp dst-port=53 \
action=mark-connection new-connection-mark=dns_conn comment="Mark DNS connections"
add chain=forward connection-state=new protocol=tcp dst-port=22 \
action=mark-connection new-connection-mark=ssh_conn comment="Mark SSH connections"

Convert connection marks to packet marks for queue trees:

/ip firewall mangle
add chain=forward connection-mark=web_conn action=mark-packet \
new-packet-mark=web_pkt passthrough=no comment="Web packet mark"
add chain=forward connection-mark=dns_conn action=mark-packet \
new-packet-mark=dns_pkt passthrough=no comment="DNS packet mark"
add chain=forward connection-mark=ssh_conn action=mark-packet \
new-packet-mark=ssh_pkt passthrough=no comment="SSH packet mark"

Apply bandwidth policies based on marks:

/queue tree
add name=download parent=global max-limit=100M
add name=web_download parent=download packet-mark=web_pkt max-limit=50M
add name=dns_download parent=download packet-mark=dns_pkt max-limit=5M priority=1
add name=ssh_download parent=download packet-mark=ssh_pkt max-limit=10M priority=2

Confirm your mangle rules are active and processing traffic.

/ip firewall mangle print

Expected Output:

Flags: X - disabled, I - invalid, D - dynamic
# CHAIN ACTION CONNECTION-MARK NEW-CONN-MARK NEW-PKT-MARK
0 forward mark-connection web_conn
1 forward mark-connection dns_conn
2 forward mark-connection ssh_conn
3 forward mark-packet web_conn web_pkt
4 forward mark-packet dns_conn dns_pkt
5 forward mark-packet ssh_conn ssh_pkt
/ip firewall mangle print stats

Expected Output:

# CHAIN ACTION BYTES PACKETS
0 forward mark-connection 0 0
1 forward mark-connection 0 0
2 forward mark-connection 0 0
3 forward mark-packet 1,234,567 8,901
4 forward mark-packet 45,678 234
5 forward mark-packet 98,765 543
/ip firewall connection print where connection-mark!=""

Expected Output:

# PROTOCOL SRC-ADDRESS DST-ADDRESS CONNECTION-MARK
0 tcp 192.168.88.10:4521 93.184.216.34:443 web_conn
1 udp 192.168.88.10:5353 8.8.8.8:53 dns_conn

Mark each client’s traffic separately for individual queue limits:

# Mark connections per client
/ip firewall mangle
add chain=forward connection-state=new src-address=192.168.88.10 \
action=mark-connection new-connection-mark=client10_conn
add chain=forward connection-state=new src-address=192.168.88.11 \
action=mark-connection new-connection-mark=client11_conn
# Mark packets for queuing
add chain=forward connection-mark=client10_conn action=mark-packet \
new-packet-mark=client10_pkt passthrough=no
add chain=forward connection-mark=client11_conn action=mark-packet \
new-packet-mark=client11_pkt passthrough=no

Mark traffic for different ISPs based on source:

# Mark traffic from different subnets for different ISPs
/ip firewall mangle
add chain=prerouting src-address=192.168.10.0/24 action=mark-routing \
new-routing-mark=to_isp1
add chain=prerouting src-address=192.168.20.0/24 action=mark-routing \
new-routing-mark=to_isp2
# Create routing table entries
/ip route
add dst-address=0.0.0.0/0 gateway=10.0.1.1 routing-mark=to_isp1
add dst-address=0.0.0.0/0 gateway=10.0.2.1 routing-mark=to_isp2

Fix path MTU discovery issues by clamping TCP MSS for tunneled connections:

# Clamp MSS for outbound traffic through PPPoE
/ip firewall mangle add chain=forward protocol=tcp tcp-flags=syn \
out-interface=pppoe-out tcp-mss=1301-65535 action=change-mss new-mss=1300 \
passthrough=yes comment="Clamp MSS for PPPoE outbound"
# Clamp MSS for inbound traffic through PPPoE
/ip firewall mangle add chain=forward protocol=tcp tcp-flags=syn \
in-interface=pppoe-out tcp-mss=1301-65535 action=change-mss new-mss=1300 \
passthrough=yes comment="Clamp MSS for PPPoE inbound"

Why this works: PPPoE adds 8 bytes of overhead, reducing effective MTU. When PMTUD fails (ICMP blocked), TCP connections stall with large packets. MSS clamping forces smaller TCP segments, avoiding fragmentation issues.

Set DSCP values for traffic that will be prioritized by upstream devices:

/ip firewall mangle
add chain=postrouting protocol=udp dst-port=5060-5061 action=change-dscp \
new-dscp=46 comment="VoIP signaling - EF"
add chain=postrouting protocol=udp dst-port=10000-20000 action=change-dscp \
new-dscp=46 comment="VoIP RTP - EF"
add chain=postrouting protocol=tcp dst-port=22 action=change-dscp \
new-dscp=26 comment="SSH - AF31"

When using policy routing, ensure marked traffic bypasses FastTrack:

# FastTrack unmarked traffic only
/ip firewall filter
add chain=forward connection-state=established,related routing-mark="" \
action=fasttrack-connection
add chain=forward connection-state=established,related action=accept

Problem: “Mangle rules show zero packet counts”

Section titled “Problem: “Mangle rules show zero packet counts””

Cause: Rules may be unreachable, or traffic doesn’t match criteria.

Solution:

  1. Check rule order - earlier rules may match first with passthrough=no
  2. Verify traffic exists: /ip firewall connection print
  3. Check chain selection - use forward for transit traffic, not input
  4. Ensure connection tracking is enabled: /ip firewall connection tracking print

Problem: “Connection marks not appearing in connection table”

Section titled “Problem: “Connection marks not appearing in connection table””

Cause: Rule may be in wrong chain or connection-state not specified.

Solution:

  1. For new connection marks, use connection-state=new
  2. Use prerouting or forward chain, not postrouting
  3. Verify rule matches: add log=yes temporarily to debug

Problem: “Policy routing marks don’t affect traffic”

Section titled “Problem: “Policy routing marks don’t affect traffic””

Cause: Routing table or routes may be misconfigured.

Solution:

  1. Verify routing mark is set in prerouting chain (before routing decision)
  2. Check route exists with matching routing-mark: /ip route print where routing-mark=your_mark
  3. Ensure FastTrack isn’t processing marked connections
  4. Verify with: /ip firewall mangle print stats

Cause: RouterOS has a limit of 4096 unique packet marks.

Solution:

  1. Use connection marks instead of packet marks where possible
  2. Consolidate similar traffic under fewer marks
  3. Review if all marks are necessary
  4. Check for dynamic mark generation that may be creating excessive marks

Problem: “Queue tree doesn’t apply to marked traffic”

Section titled “Problem: “Queue tree doesn’t apply to marked traffic””

Cause: Packet marks may not match queue configuration.

Solution:

  1. Verify exact packet mark name in both mangle and queue
  2. Check queue tree parent interface matches traffic path
  3. Ensure passthrough=no on final packet mark rule
  4. Verify marks are created: /ip firewall mangle print stats

Wrong approach (CPU intensive):

# Every packet inspected
/ip firewall mangle add chain=forward src-address=192.168.88.0/24 \
action=mark-packet new-packet-mark=lan_pkt

Right approach (efficient):

# First packet inspected, connection marked
/ip firewall mangle add chain=forward connection-state=new src-address=192.168.88.0/24 \
action=mark-connection new-connection-mark=lan_conn
# Subsequent packets use fast connection lookup
/ip firewall mangle add chain=forward connection-mark=lan_conn \
action=mark-packet new-packet-mark=lan_pkt
  1. Place most frequently matched rules at the top
  2. Use passthrough=no when no further matching is needed
  3. Mark connections first, then derive packet marks
  4. Group related rules together to improve cache efficiency

FastTrack bypasses firewall processing for marked connections. This is incompatible with:

  • Policy routing (routing marks)
  • Queue trees requiring packet marks
  • Any mangle processing of established connections

To use mangle with FastTrack, exclude marked traffic:

/ip firewall filter add chain=forward connection-state=established,related \
connection-mark="" action=fasttrack-connection

Wrong: Marking transit traffic in INPUT chain

# INPUT is for traffic TO the router, not through it
/ip firewall mangle add chain=input src-address=192.168.88.0/24 action=mark-packet new-packet-mark=client_pkt

Right: Use FORWARD for transit traffic

/ip firewall mangle add chain=forward src-address=192.168.88.0/24 action=mark-packet new-packet-mark=client_pkt

Wrong: Setting routing mark after routing decision

# Routing decision already made - mark has no effect
/ip firewall mangle add chain=forward action=mark-routing new-routing-mark=isp2

Right: Set routing marks in PREROUTING

# Before routing decision - mark affects route selection
/ip firewall mangle add chain=prerouting action=mark-routing new-routing-mark=isp2

Problem: Packet gets marked multiple times, wasting CPU

# All rules process because passthrough=yes (default)
/ip firewall mangle add chain=forward protocol=tcp dst-port=80 action=mark-packet new-packet-mark=web
/ip firewall mangle add chain=forward protocol=tcp dst-port=443 action=mark-packet new-packet-mark=https
/ip firewall mangle add chain=forward action=mark-packet new-packet-mark=other

Solution: Add passthrough=no to terminal rules

/ip firewall mangle add chain=forward protocol=tcp dst-port=80 action=mark-packet new-packet-mark=web passthrough=no
/ip firewall mangle add chain=forward protocol=tcp dst-port=443 action=mark-packet new-packet-mark=https passthrough=no
/ip firewall mangle add chain=forward action=mark-packet new-packet-mark=other passthrough=no

Inefficient: Inspecting every packet

/ip firewall mangle add chain=forward src-address=192.168.88.100 action=mark-packet new-packet-mark=client100

Efficient: Mark connection once, then match connection mark

/ip firewall mangle add chain=forward connection-state=new src-address=192.168.88.100 \
action=mark-connection new-connection-mark=client100_conn
/ip firewall mangle add chain=forward connection-mark=client100_conn \
action=mark-packet new-packet-mark=client100
  • DHCP Client - multi-WAN with mangle-based load balancing