10 KiB
RouterOS TZSP capture architecture
MikroSuricata 0.11.3 uses a hybrid RouterOS capture path. The goal is to keep high-volume routed IPv4 off /tool/sniffer while still preserving visibility into ARP and other non-IPv4 Ethernet traffic.
Default topology
routed IPv4
RouterOS forwarding --------------------> /ip firewall mangle
action=sniff-tzsp
|
| TZSP UDP/37008
v
MikroSuricata Rust
|
v
TAP -> Suricata
non-IPv4 Ethernet
RouterOS interfaces --------------------> /tool/sniffer
filter-mac-protocol=!ip
filter-stream=yes
|
+---- TZSP UDP/37008 ----+
Both paths use the same TZSP receiver in the container.
Why hybrid capture
A full /tool/sniffer TZSP stream can become the limiting stage on some RouterOS systems long before the Rust receiver, TAP or Suricata are saturated. In testing on the system that motivated this change, full Packet Sniffer streaming plateaued around 150-270 Mbit/s while an IPv4 mangle action=sniff-tzsp rule delivered roughly 1.2 Gbit/s of encapsulated traffic to the container VETH.
This is not treated as a universal RouterOS speed guarantee. The deployer simply chooses the path that proved suitable for high-volume routed IPv4 and keeps Packet Sniffer for the much smaller non-IPv4 stream.
Routed IPv4
The deployer manages one rule:
/ip/firewall/mangle
add chain=forward \
action=sniff-tzsp \
sniff-target=172.31.255.2 \
sniff-target-port=37008 \
comment="MikroSuricata TZSP IPv4"
The target IP and port come from CONTAINER_IP and TZSP_PORT; the values above are examples.
sniff-tzsp clones the matching packet to the TZSP receiver and then continues processing the next mangle rule. MikroSuricata places its rule at the beginning of the IPv4 forward mangle chain so later existing mangle rules still run normally.
The deployer removes/replaces only the rule whose comment is exactly:
MikroSuricata TZSP IPv4
It does not delete, disable or reorder unrelated mangle rules.
What this IPv4 rule covers
chain=forward covers IPv4 routed through RouterOS. This is the main IDS path for client-to-Internet and inter-subnet traffic.
It intentionally does not capture:
- IPv4 traffic terminating on RouterOS itself (
input), - IPv4 traffic generated by RouterOS itself (
output), - pure L2 IPv4 switching that never enters the routed IPv4
forwardchain.
The first two exclusions also avoid any risk of recursively observing RouterOS-generated TZSP traffic. If router-local IPv4 inspection is required later, it should be added as a separate, explicitly designed path rather than by broadening the default rule blindly.
ARP, IPv6 and other non-IPv4 Ethernet traffic
Packet Sniffer is configured as the complement:
/tool/sniffer/set \
streaming-enabled=yes \
filter-stream=yes \
filter-interface=all \
filter-mac-protocol=!ip \
filter-direction=any
RouterOS names EtherType 0x0800 as ip and EtherType 0x86DD as ipv6. Therefore filter-mac-protocol=!ip means everything except IPv4 EtherType, not "everything except all IP".
With the default filter the stream can include, among other protocols:
- ARP (
0x0806), - IPv6 (
0x86DD), - LLDP,
- 802.1X/EAPoL,
- LACP,
- PPPoE session/discovery,
- MPLS EtherTypes,
- RARP,
- VLAN/service-VLAN frames when visible to the sniffer.
filter-stream=yes is kept enabled so packets destined for the configured sniffer server are ignored by Packet Sniffer rather than captured again.
IPv6 performance note
IPv6 is deliberately left in the Packet Sniffer complement in 0.11.3 because the requested design is "mangle for IPv4, stream everything except IPv4". If the network later carries sustained high-volume IPv6, the Packet Sniffer performance ceiling can reappear for that traffic. At that point IPv6 should be split into its own high-throughput capture path and the L2 sniffer narrowed to explicit non-IP EtherTypes.
No bridge or interface name is assumed
The deployer does not assume names such as bridge, bridge-trunk, br0, LAN, or any site-specific VLAN interface.
Default:
TZSP_L2_INTERFACE=
An empty value is translated to RouterOS:
filter-interface=all
If an operator intentionally wants to constrain non-IPv4 capture, set the exact RouterOS interface name:
TZSP_L2_INTERFACE=bridge-core
The deployer verifies that a non-empty interface name exists before applying the sniffer configuration.
CONTAINER_BRIDGE=br-ids is different: that is the new private bridge created for the IDS container, not the bridge from which production traffic is assumed to arrive. It remains configurable through deploy-routeros.env.
RouterOS sniffer endpoint syntax
Newer RouterOS CLI builds can expose the TZSP destination as a composite value:
streaming-server=172.31.255.2:37008
Some older documentation/builds expose the destination as separate properties:
streaming-server=172.31.255.2
streaming-port=37008
The deploy and migration helper try the composite IP:PORT form first and automatically fall back to the split form if RouterOS rejects it.
Deployment variables
Default capture settings:
TZSP_PORT=37008
CONFIGURE_TZSP_CAPTURE=true
CONFIGURE_IPV4_MANGLE=true
CONFIGURE_L2_SNIFFER=true
START_L2_SNIFFER=true
TZSP_L2_INTERFACE=
TZSP_L2_MAC_PROTOCOL=!ip
Meaning:
| Variable | Meaning |
|---|---|
CONFIGURE_TZSP_CAPTURE |
Master switch for capture changes made by full deploy |
CONFIGURE_IPV4_MANGLE |
Manage the project-owned IPv4 forward sniff-tzsp rule |
CONFIGURE_L2_SNIFFER |
Reconfigure global Packet Sniffer as the non-IPv4 complement |
START_L2_SNIFFER |
Start Packet Sniffer after configuring it |
TZSP_L2_INTERFACE |
Empty = all interfaces; otherwise exact interface/bridge name |
TZSP_L2_MAC_PROTOCOL |
Default !ip; normally should not be changed |
TZSP_PORT |
UDP port used by Rust TZSP receiver |
For backward compatibility, an old deploy-routeros.env containing CONFIGURE_SNIFFER and START_SNIFFER is still accepted as an alias for the new master/start settings. VLAN_ID is no longer used by the capture deploy.
Full deployment
Prepare configuration:
cp deploy-routeros.env.example deploy-routeros.env
Build/upload the image as usual, then deploy:
./scripts/deploy-routeros.sh routeros-suricata-tzsp-arm64.tar
The deployment:
- creates/updates the IDS container infrastructure,
- starts the new container,
- replaces only the MikroSuricata IPv4 mangle capture rule,
- stops the global Packet Sniffer,
- clears stale Packet Sniffer filters,
- configures Packet Sniffer for
!ip, - uses all RouterOS interfaces unless
TZSP_L2_INTERFACEis explicitly set, - starts the L2 sniffer when
START_L2_SNIFFER=true.
Switch an existing installation without rebuilding the container
The capture migration is independent from the container image:
./scripts/configure-routeros-tzsp-hybrid.sh
It reads the same deploy-routeros.env file.
Preview the exact generated RouterOS script without making changes:
DRY_RUN=true ./scripts/configure-routeros-tzsp-hybrid.sh
The helper only manages the project-owned IPv4 mangle rule. Packet Sniffer itself is a global RouterOS facility, so enabling CONFIGURE_L2_SNIFFER=true necessarily replaces its current global filters/settings. If the router already uses /tool/sniffer for another administrative purpose, either coordinate that use or set:
CONFIGURE_L2_SNIFFER=false
and manage non-IPv4 capture manually.
Manual RouterOS template
A standalone template is included:
routeros/02-tzsp-hybrid.rsc
Edit the local variables at the top before importing if the default container address differs.
Verification
Check the RouterOS capture configuration:
/ip/firewall/mangle/print stats where comment="MikroSuricata TZSP IPv4"
/tool/sniffer/print
/interface/monitor-traffic veth-ids
Or from the project host:
./scripts/routeros-status.sh
During a large IPv4 download, expect the mangle rule packet/byte counters and the container VETH TX counters to increase rapidly. In the MikroSuricata UI compare:
TZSP RX
Inspected throughput
capture %
queue %
loss/s
If TZSP RX is close to the expected traffic and capture remains close to 100%, RouterOS -> Rust and Rust -> TAP are keeping up.
Coverage and caveats
- Packet Sniffer cannot necessarily see traffic switched entirely in hardware by a hardware-offloaded bridge. Broadcast/multicast behavior can differ by platform.
- The default mangle rule covers routed IPv4 only, not bridge-only IPv4 switching.
- IPv6 remains on Packet Sniffer in 0.11.3 and can therefore inherit Packet Sniffer throughput limits under sustained high-rate IPv6 traffic.
filter-interface=allcan expose the same low-level broadcast/L2 frame on more than one logical/physical observation point on some topologies. If this is noisy, setTZSP_L2_INTERFACEexplicitly.- The global Packet Sniffer has no per-consumer instance. MikroSuricata therefore cannot preserve a second independent sniffer configuration while also owning the non-IPv4 TZSP stream.
- Do not run an additional full-traffic Packet Sniffer stream to the same TZSP destination at the same time; duplicate packets will inflate traffic and Suricata processing.
Why existing mangle rules continue to work
RouterOS defines sniff-tzsp as an action that sends a copy to a TZSP target and then passes the matched packet to the next mangle rule, similar to passthrough. The MikroSuricata rule therefore observes traffic without accepting, dropping, marking or rerouting the original packet.
This is why the rule can safely sit at the beginning of chain=forward: later QoS, marking, policy-routing and other mangle rules remain in the processing path.