Skip to content

Layer 7 Protocol Matching

RouterOS Layer 7 (L7) protocol matching identifies application-layer traffic by running POSIX extended regex patterns against connection payloads. Unlike port-based matching, L7 can detect protocols regardless of port—useful for P2P, VoIP, and custom application traffic.

:::warning Performance impact L7 matching runs in software on every candidate packet. Apply it only after scoping with cheaper matchers (interface, protocol, port) to prevent CPU saturation on busy links. :::

Block BitTorrent on a LAN-to-WAN path:

# 1. Define the pattern
/ip firewall layer7-protocol add \
name=bittorrent \
regexp="^(\\x13bittorrent protocol|azver\\x01|get /scrape\\?info_hash=)"
# 2. Accept established/related first (must come before L7 rule)
/ip firewall filter add chain=forward \
connection-state=established,related action=accept
# 3. Drop BitTorrent
/ip firewall filter add chain=forward \
in-interface-list=LAN out-interface-list=WAN \
protocol=tcp layer7-protocol=bittorrent \
action=drop comment="Block BitTorrent TCP"

RouterOS inspects the first 10 packets or first 2 KB of each connection (whichever threshold is reached first). After that window, no further L7 inspection occurs—the pattern either matched or it did not.

Connection start
├── Packet 1 ──▶ regex scan
├── Packet 2 ──▶ regex scan
│ ...
├── Packet 10 ─▶ regex scan ◀── last chance to match
└── Packet 11+ ─▶ NO L7 inspection (window exhausted)

Both directions of traffic must pass through the same router. Asymmetric routing causes L7 to see only half the conversation and patterns that depend on bidirectional payloads will never match.

RequirementDetail
Connection trackingMust be enabled — L7 uses the connection table to associate packets with flows
Bidirectional visibilityBoth upstream and downstream must transit the same router
Plaintext payloadRegex matches raw bytes — TLS/QUIC encrypted payloads are opaque
Inspection windowFirst 10 packets or 2 KB; nothing matched outside this window

Patterns live in /ip firewall layer7-protocol. Each entry has a name and a regexp.

/ip firewall layer7-protocol add name=<name> regexp="<posix-extended-regex>"

The regex is applied to the raw bytes of the payload. Use \x hex escapes for non-printable bytes.

# P2P: BitTorrent handshake
/ip firewall layer7-protocol add \
name=bittorrent \
regexp="^(\\x13bittorrent protocol|azver\\x01|get /scrape\\?info_hash=)"
# VoIP: SIP signaling
/ip firewall layer7-protocol add \
name=sip \
regexp="^(invite|register|ack|bye|options|cancel|message|subscribe|notify).+sip:"
# VoIP: H.323 call signaling
/ip firewall layer7-protocol add \
name=h323 \
regexp="h323|q931|ras"
# DNS over plain UDP (for identification, not blocking)
/ip firewall layer7-protocol add \
name=dns-query \
regexp="^.{2}\\x01\\x00.{4}"
# HTTP GET/POST requests (plaintext HTTP only)
/ip firewall layer7-protocol add \
name=http \
regexp="^(get|post|head|put|delete|options|connect) .+ http/1"
# List all defined patterns
/ip firewall layer7-protocol print
# Remove a pattern
/ip firewall layer7-protocol remove [find name=bittorrent]
# Edit a pattern
/ip firewall layer7-protocol set [find name=sip] regexp="<new-pattern>"

Tip: Test patterns offline before deploying. Use echo "payload" | grep -P "pattern" to validate regex syntax. Faulty patterns silently fail to match rather than generating errors.


Reference a pattern by name with the layer7-protocol= matcher in filter rules. L7 can be combined with any other matcher.

/ip firewall filter
# Must accept established/related BEFORE L7 rules
# (L7 only operates in the 10-packet window; established flows won't be inspected)
add chain=forward connection-state=established,related action=accept
add chain=forward connection-state=invalid action=drop
# Drop BitTorrent TCP
add chain=forward \
in-interface-list=LAN out-interface-list=WAN \
protocol=tcp layer7-protocol=bittorrent \
action=drop comment="Block BitTorrent TCP"
# Drop BitTorrent UDP
add chain=forward \
in-interface-list=LAN out-interface-list=WAN \
protocol=udp layer7-protocol=bittorrent \
action=drop comment="Block BitTorrent UDP"
/ip firewall filter add chain=input \
in-interface-list=WAN \
protocol=udp dst-port=5060 \
layer7-protocol=sip \
action=drop comment="Block unsolicited SIP from WAN"

Mangle is the preferred place for L7 when the goal is traffic classification rather than blocking. Mark connections during the inspection window, then use the mark in downstream filter, queue, or routing rules.

/ip firewall mangle
# Mark SIP connections in prerouting (catches early packets)
add chain=prerouting \
protocol=udp dst-port=5060 \
layer7-protocol=sip \
action=mark-connection new-connection-mark=voip-sip passthrough=yes \
comment="Mark SIP connection"
# Mark all packets belonging to the SIP connection
add chain=prerouting \
connection-mark=voip-sip \
action=mark-packet new-packet-mark=voip-priority passthrough=no \
comment="Mark SIP packets for QoS"

Then apply a queue with priority to voip-priority packet-marked traffic.

/ip firewall mangle
# Mark HTTP traffic for accounting
add chain=forward \
protocol=tcp dst-port=80 \
layer7-protocol=http \
action=mark-connection new-connection-mark=http-conn passthrough=yes
add chain=forward \
connection-mark=http-conn \
action=mark-packet new-packet-mark=http-traffic passthrough=no

L7 is the most CPU-intensive firewall matcher. Every candidate packet in the inspection window has the regex applied to its payload in software.

Pre-filter with cheap matchers to minimize the number of packets reaching the L7 rule:

/ip firewall mangle
# Stage 1: Identify candidates with cheap port-based match
add chain=forward \
protocol=tcp dst-port=6881-6889 \
action=mark-connection new-connection-mark=bt-candidates passthrough=yes \
comment="Candidate BitTorrent ports"
# Stage 2: Apply L7 only to candidates
add chain=forward \
connection-mark=bt-candidates \
layer7-protocol=bittorrent \
action=mark-connection new-connection-mark=bt-confirmed passthrough=no \
comment="Confirm with L7 regex"
/ip firewall filter
# Stage 3: Drop confirmed — cheap mark-based match
add chain=forward \
connection-mark=bt-confirmed \
action=drop comment="Drop confirmed BitTorrent"
PracticeReason
Accept established,related before L7Established flows are never inspected anyway — skip them early
Scope with in-interface-list / out-interface-listLimit L7 to the relevant traffic path
Scope with protocol= and dst-port=Reduce candidate set before regex runs
Use passthrough=no on final mangle markStop processing once matched
Use prerouting chain in mangleCatches packets earlier in the window

LimitationImpact
10-packet / 2 KB windowProtocols with late handshakes may not be caught
Encrypted trafficTLS, QUIC, WireGuard — regex cannot match ciphertext
Asymmetric routingOne-sided visibility means patterns that need bidirectional data will fail
P2P obfuscationModern BitTorrent and other P2P clients randomize or encrypt handshakes
CPU costOn gigabit links with many flows, L7 can saturate a CPU core
No protocol versioningRegex patterns must be updated manually when application protocols change
MethodEncryptedCPU CostAccuracyRouterOS Built-in
L7 regexNoMediumGood for plaintextYes
Port-basedN/AVery lowProtocol-agnosticYes
Address listsN/ALowIP-level onlyYes
DSCP matchingN/AVery lowRelies on upstream markingYes
External DPIYes (with SSL inspection)HighHighNo

Problem: Rule hit counter stays at 0.

Check connection tracking:

/ip firewall connection tracking print
# tracking must be enabled (auto or yes)

Check asymmetric routing: Both directions of traffic must pass through the router. Verify with:

/tool traceroute <destination>

Check rule ordering: The L7 rule must come before any rule that accepts the connection. If connection-state=established,related action=accept appears first, packets in the inspection window are accepted before reaching the L7 rule.

# Correct order: L7 rule BEFORE the established/related accept
/ip firewall filter print
# Verify L7 rule number is lower than established/related accept rule

Check inspection window: If the connection was already established before the rule was added, existing connections won’t be re-inspected. Test with a fresh connection.

Validate regex offline:

# On a Linux machine, test the payload against the pattern
echo -n "payload bytes here" | grep -P "your-pattern"

Use Torch to verify traffic reaches the interface:

/tool torch interface=ether1 src-address=<client-ip>

Check rule statistics:

# See hit counts per rule
/ip firewall filter print stats
/ip firewall mangle print stats
# List defined L7 patterns
/ip firewall layer7-protocol print
# View connections with a specific mark
/ip firewall connection print where connection-mark=bt-confirmed
# Count matched connections
/ip firewall connection print count-only where connection-mark=bt-confirmed