RouterOS TZSP + Suricata IDS
Project version: 0.3.2
A lightweight IDS stack designed to run as a single container on MikroTik RouterOS. RouterOS mirrors selected traffic with TZSP, the container decodes the frames into a TAP interface, Suricata analyzes them, and the Python service stores EVE alerts in SQLite and exposes a small web dashboard.
Architecture
VLAN / RouterOS traffic
|
v
RouterOS Packet Sniffer
|
| TZSP UDP/37008
v
single RouterOS container
Debian slim
+ Python TZSP receiver
+ TAP suritap0
+ Suricata IDS
+ EVE JSON watcher
+ SQLite
+ Web UI :8080
+ optional RouterOS REST blocking
The RouterOS deployment workflow is:
Docker/Podman build host
|
| build for RouterOS CPU architecture
v
container image
|
| docker save / podman save
v
*.tar
|
| SCP
v
RouterOS external disk
|
| /container/add file=...
v
running IDS container
Docker Compose is only provided for Linux integration testing. RouterOS receives one saved container image as a TAR archive.
Local Web UI development without Docker
The web dashboard can be started locally without Docker, Suricata, TAP, /dev/net/tun, or root privileges.
Requirements:
- Linux or macOS
- Python 3
- Python
venvsupport
Start the development dashboard:
./dev.sh
The script will:
- create
.venv/if needed, - install dependencies from
requirements.txt, - create
data/dev/, - start the web-only application.
Open:
http://127.0.0.1:8080
This mode intentionally does not start Suricata, TZSP capture, TAP, or RouterOS integration.
Status API
The dashboard and external monitoring systems can use:
GET /api/status
Example:
curl http://127.0.0.1:8080/api/status
The response includes:
- overall application status and uptime,
- Web UI/API status,
- TZSP receiver status,
- TAP interface status,
- Suricata process status and PID,
- EVE JSON watcher status,
- RouterOS REST integration status,
- listening/outbound port information,
- runtime packet, alert, and block counters.
GET /api/health is kept as a compatibility alias and returns the same status payload.
Add a sample alert
To start the dashboard with one demo alert in an empty development database:
DEV_SEED_DATA=true ./dev.sh
Development database:
data/dev/ids.db
To use another port:
WEB_PORT=8090 ./dev.sh
To listen on all local interfaces:
WEB_BIND=0.0.0.0 ./dev.sh
Do not expose the development server directly to an untrusted network.
Local tests
Install dependencies and run tests:
./dev.sh --test
Or run tests before starting the dashboard:
RUN_TESTS=true ./dev.sh
The project currently uses only the Python standard library, including unittest for tests. requirements.txt is kept as the canonical dependency file so future packages can be installed automatically by dev.sh.
Docker integration test
Prepare local environment configuration:
cp .env.example .env
Build and start:
docker compose up -d --build
Dashboard:
http://127.0.0.1:8080
Send the built-in TZSP test packet:
./scripts/selftest.sh
The test rule should generate:
LOCAL TZSP PIPELINE TEST
SID 1000001
Stop the stack:
docker compose down
RouterOS requirements
Before deployment, verify that the router has:
- a RouterOS version and package set that supports containers,
- the
containerpackage installed, - container and sniffer functionality enabled in device mode,
- SSH access from the build machine,
- enough storage for the image, container root directory, Suricata rules, logs, and SQLite database,
- preferably an external disk instead of small internal flash storage.
Useful RouterOS checks:
/system/resource/print
/system/package/print
/system/device-mode/print
/disk/print
/container/print
Automated RouterOS deployment
Create the deployment configuration:
cp deploy-routeros.env.example deploy-routeros.env
Edit at least:
ROUTER_HOST=192.168.88.1
ROUTER_USER=admin
ROUTER_ARCH=auto
ROUTER_DISK=disk1
ROUTER_SCP_DIR=disk1
VLAN_ID=100
MONITORED_NETWORKS=192.168.100.0/24
AUTO_BLOCK=false
Deploy:
./scripts/deploy-routeros.sh
The deployer performs the following sequence:
SSH architecture detection
-> build image for the detected CPU
-> docker save / podman save to build/*.tar
-> calculate SHA256
-> generate deployment .rsc
-> SCP TAR to RouterOS
-> SCP .rsc to RouterOS
-> create bridge/VETH/NAT/mounts/environment
-> /container/add file=<image.tar>
-> wait for extraction
-> start the container
-> optionally configure/start TZSP sniffer
-> print container status and logs
For SSH key authentication set:
ROUTER_IDENTITY_FILE=/home/user/.ssh/id_ed25519
Build RouterOS image without deployment
ARM64:
./scripts/build-routeros.sh arm64
AMD64/x86_64:
./scripts/build-routeros.sh amd64
ARMv7/armhf:
./scripts/build-routeros.sh arm
Generated files are written to build/, for example:
build/routeros-suricata-tzsp-arm64.tar
build/routeros-suricata-tzsp-arm64.tar.sha256
Manual SCP and RouterOS import
Example:
./scripts/build-routeros.sh arm64
scp build/routeros-suricata-tzsp-arm64.tar admin@192.168.88.1:disk1/
RouterOS templates are located in routeros/:
01-container-network.rsc
02-sniffer-vlan100.rsc
03-rest-and-firewall.rsc
04-container-import-amd64.rsc
04-container-import-arm64.rsc
04-container-import-arm.rsc
rollback.rsc
After extraction, inspect and start the container:
/container/print detail
/container/start suricata-ids
/log/print where message~"suricata|TZSP|IDS"
Persistent data on RouterOS
The deployment keeps application state outside the image root directory:
<disk>/containers/suricata-ids-data -> /data
<disk>/containers/suricata-ids-logs -> /var/log/suricata
<disk>/containers/suricata-ids-rules -> /var/lib/suricata
This preserves SQLite data, logs, and downloaded Suricata rules when the application image is replaced.
TZSP capture
The default deployment configuration can configure RouterOS Packet Sniffer to stream VLAN traffic to the container address on UDP port 37008.
Relevant settings in deploy-routeros.env:
CONFIGURE_SNIFFER=true
START_SNIFFER=true
VLAN_ID=100
If the router already uses Packet Sniffer for another purpose, disable automatic sniffer configuration:
CONFIGURE_SNIFFER=false
START_SNIFFER=false
Then configure the capture manually.
Hardware-offloaded bridge traffic may require additional verification on the specific RouterOS device because some switched traffic can bypass software capture paths.
TAP and Suricata
Inside the container the application creates:
suritap0
The pipeline is:
TZSP datagram
-> Python decoder
-> Ethernet frame
-> TAP suritap0
-> Suricata
-> eve.json
-> Python EVE watcher
-> SQLite / Web UI
If the RouterOS container cannot create the TAP interface, the application will fail early with an error related to /dev/net/tun, TUNSETIFF, or permissions. This is the main platform-specific capability to validate on the target router.
RouterOS REST reaction
Automatic blocking is intentionally disabled by default:
AUTO_BLOCK=false
Observation mode should be used first. After validating alerts and false positives, the optional reaction engine can add selected addresses to a RouterOS firewall address list through REST.
Relevant settings:
AUTO_BLOCK=true
AUTO_BLOCK_MAX_SEVERITY=1
ROUTEROS_URL=https://172.31.255.1
ROUTEROS_USER=suricata-api
ROUTEROS_PASSWORD=CHANGE_ME
ROUTEROS_ADDRESS_LIST=IDS-BLOCK
BLOCK_TIMEOUT=1h
Do not enable automatic blocking until the monitored networks, exclusion list, REST credentials, firewall rule placement, and alert policy have been reviewed.
Important files
Dockerfile
Dockerfile / Debian slim runtime image
dev.sh
Local web-only development launcher
requirements.txt
Local Python development/test dependencies
app/main.py
Full RouterOS/container application entry point
app/dev_web.py
Local web-only entry point
app/tzsp.py
TZSP receiver and decoder
app/tap.py
TAP interface handling
app/eve.py
Suricata EVE JSON watcher
app/policy.py
Alert/blocking policy
app/routeros.py
RouterOS REST client
app/store.py
SQLite alert storage
app/webui.py
Dashboard and JSON API
scripts/build-routeros.sh
Build and save image to TAR
scripts/deploy-routeros.sh
Build, SCP, import, and start on RouterOS
routeros/
RouterOS configuration templates
Safety defaults
The default configuration is observation-oriented:
AUTO_BLOCK=false
UPDATE_RULES_ON_START=false
ROUTEROS_PASSWORD=CHANGE_ME
Keep automatic firewall actions disabled until the capture path and alert quality are validated on the real network.