📡 NetFlow v5 Collector – Finale Version

Für MikroTik RouterOS 7 | Container (Alpine) | HTTP Plaintext API | /30 Subnetz
⚡ Finale Lösung – Minimalistisches Python-Script, kein Differenz-Overhead, reine Rohdaten im gewünschten Format

📘 Dokumentation: NetFlow-basierter Traffic-Collector

🎯 Ziel
Ersetzt das alte /ip accounting aus RouterOS 6. Sammelt NetFlow v5 Daten auf dem Router und liefert sie im Klartext-Format:
SRC_IP DST_IP BYTES PACKETS SRC_PORT PROTOCOL

📌 Voraussetzungen

Hardware

KomponenteMindestanforderungEmpfehlung
RouterRouterOS 7.20+ (arm64)RB5009, CCR2004, CHR
RAM1 GB frei2+ GB
Speicher200 MB freiUSB-Stick oder SMB-Share
NetzwerkContainer bekommt eigenes Subnetz (172.17.0.0/30)

Software

KomponenteVersionHinweis
RouterOS7.22+ (stable)Container-Funktion muss aktivierbar sein
Container-PaketinstalliertIn Extra-Paketen enthalten
Windows-PCbeliebigFür SSH und curl-Abrufe
⚠️ Wichtiger Hinweis: Container-Modus aktivieren

Nach /system/device-mode/update container=yes muss der Router physikalisch vom Strom getrennt werden (Cold Reboot).
Ein normaler Software-Neustart reicht nicht aus!

⚙️ Schritt-für-Schritt-Anleitung

1. Container-Modus aktivieren (⚠️ Strom ziehen!)

/system/device-mode/update container=yes
Jetzt sofort den Netzstecker ziehen, 10 Sekunden warten, wieder einstecken.

2. Bridge und virtuelles Interface erstellen (mit /30 Subnetz)

/interface veth add name=veth1 address=172.17.0.2/30 gateway=172.17.0.1
/interface bridge add name=container-bridge
/ip address add address=172.17.0.1/30 interface=container-bridge
/interface bridge port add bridge=container-bridge interface=veth1

3. Masquerading für Container-Internet (NEU – wichtig!)

/ip firewall nat add chain=srcnat out-interface-list=WAN src-address=172.17.0.0/30 action=masquerade
Warum Masquerading nötig ist:
Ohne diese NAT-Regel hat der Container keine Verbindung ins Internet. Du siehst das z. B. daran, dass apk update fehlschlägt.
Wichtig: Ersetze out-interface-list=WAN durch dein tatsächliches WAN-Interface (z. B. ether1), falls du keine Interface-Liste nutzt.

4. Container erstellen und starten

/container add remote-image=arm64v8/alpine:latest root-dir=disk1/alpine interface=veth1 cmd="python3 /netflow.py" start-on-boot=yes
/container start alpine:latest
/container print

5. Python im Container installieren

/container shell alpine:latest
apk update
apk add python3
exit

6. Finales NetFlow-Script im Container erstellen

/container shell alpine:latest
cat > /netflow.py << 'EOF'
import socket
import struct
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread

cache = []
MAX_CACHE = 100000

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain')
        self.end_headers()
        self.wfile.write("\n".join(cache[-MAX_CACHE:]).encode())
    def log_message(self, *args): pass

def udp_listener():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('0.0.0.0', 2055))
    while True:
        data, addr = sock.recvfrom(65535)
        if len(data) < 24: continue
        if struct.unpack('>H', data[0:2])[0] != 5: continue
        count = struct.unpack('>H', data[2:4])[0]
        off = 24
        for i in range(count):
            if off + 48 > len(data): break
            src = socket.inet_ntoa(data[off:off+4])
            dst = socket.inet_ntoa(data[off+4:off+8])
            pkts = struct.unpack('>I', data[off+16:off+20])[0]
            bytes_flow = struct.unpack('>I', data[off+20:off+24])[0]
            port = struct.unpack('>H', data[off+32:off+34])[0]
            proto = data[off+38]
            cache.append(f"{src} {dst} {bytes_flow} {pkts} {port} {proto}")
            off += 48
        if len(cache) > 2 * MAX_CACHE:
            cache[:MAX_CACHE] = []

Thread(target=udp_listener, daemon=True).start()
HTTPServer(('0.0.0.0', 8080), Handler).serve_forever()
EOF
exit

7. Container neu starten

/container stop alpine:latest
/container start alpine:latest

8. Traffic-Flow auf Router aktivieren

/ip traffic-flow set enabled=yes interfaces=sfp-sfpplus1
/ip traffic-flow target add dst-address=172.17.0.2 port=2055 version=5
/ip traffic-flow set active-flow-timeout=5s inactive-flow-timeout=5s
Hinweis: Ersetze sfp-sfpplus1 durch das Interface, das du überwachen möchtest (z. B. ether1 oder bridge).

9. Testen

Auf dem Router Traffic erzeugen:

/tool bandwidth-test address=8.8.8.8 duration=5

Daten abrufen (vom Windows-PC):

curl.exe http://172.17.0.2:8080/

📤 Ausgabeformat

62.146.206.118 3.67.32.184 226 3 62958 6
80.190.223.26 8.210.130.2 92 2 38262 6
62.146.214.242 149.154.167.41 546 4 51960 6

Bedeutung der Felder:

FeldBedeutung
src_ipQuell-IP-Adresse
dst_ipZiel-IP-Adresse
bytesBytes (kumulativ, aus NetFlow v5)
packetsPakete (kumulativ, aus NetFlow v5)
src_portQuell-Port (0 bei ICMP)
protocol6=TCP, 17=UDP, 1=ICMP

🔧 Fehlersuche

ProblemLösung
Container bleibt S (stopped)Container-Modus aktivieren (Schritt 1)
curl liefert leere AntwortBandwidth-Test laufen lassen, Target prüfen
apk add python3 schlägt fehlMasquerading prüfen (Schritt 3), DNS setzen: echo "nameserver 8.8.8.8" > /etc/resolv.conf
Container startet nicht nach Reboot/container set alpine:latest start-on-boot=yes
Port 8080 nicht erreichbarFirewall-Regel auf Router prüfen, Container-IP richtig?
veth1 ist I - INACTIVEContainer läuft nicht: /container start alpine:latest

📦 Persistente Datenspeicherung (optional)

/container/mounts add dst=/data src=disk1/netflow_data
/container set alpine:latest mounts=netflow-list
/container stop alpine:latest && /container start alpine:latest

Script anpassen: DATA_DIR = "/data"

🌐 Netzwerkübersicht (Subnetz 172.17.0.0/30)

IPVerwendung
172.17.0.0Netzadresse
172.17.0.1Gateway (Router / Bridge)
172.17.0.2Container
172.17.0.3Broadcast

🎯 Zusammenfassung

FunktionStatus
NetFlow-Export vom Router✅ aktiv
Container mit Python✅ läuft
HTTP-API auf Port 8080✅ verfügbar
Gewünschtes Ausgabeformat✅ implementiert
Masquerading für Internet✅ eingerichtet

📘 Documentation: NetFlow-based Traffic Collector

🎯 Goal
Replace the old /ip accounting from RouterOS 6. Collect NetFlow v5 data on the router and deliver it in plain text format:
SRC_IP DST_IP BYTES PACKETS SRC_PORT PROTOCOL

📌 Requirements

Hardware

ComponentMinimumRecommendation
RouterRouterOS 7.20+ (arm64)RB5009, CCR2004, CHR
RAM1 GB free2+ GB
Storage200 MB freeUSB stick or SMB share
NetworkContainer gets own subnet (172.17.0.0/30)
⚠️ Important: Activate Container Mode (Cold Reboot required!)

After /system/device-mode/update container=yes, you must physically disconnect power from the router.
A normal software reboot is not sufficient!

⚙️ Step-by-Step Guide

1. Activate Container Mode (⚠️ Cold Reboot!)

/system/device-mode/update container=yes
Immediately unplug the power cord, wait 10 seconds, plug it back in.

2. Create Bridge and Virtual Interface (with /30 subnet)

/interface veth add name=veth1 address=172.17.0.2/30 gateway=172.17.0.1
/interface bridge add name=container-bridge
/ip address add address=172.17.0.1/30 interface=container-bridge
/interface bridge port add bridge=container-bridge interface=veth1

3. Masquerading for Container Internet Access (CRITICAL!)

/ip firewall nat add chain=srcnat out-interface-list=WAN src-address=172.17.0.0/30 action=masquerade
Why masquerading is needed:
Without this NAT rule, the container has no internet connection. You will see this when apk update fails.
Important: Replace out-interface-list=WAN with your actual WAN interface (e.g. ether1) if not using an interface list.

4. Create and Start Container

/container add remote-image=arm64v8/alpine:latest root-dir=disk1/alpine interface=veth1 cmd="python3 /netflow.py" start-on-boot=yes
/container start alpine:latest
/container print

5. Install Python Inside Container

/container shell alpine:latest
apk update
apk add python3
exit

6. Create Final NetFlow Script Inside Container

/container shell alpine:latest
cat > /netflow.py << 'EOF'
import socket
import struct
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread

cache = []
MAX_CACHE = 5000

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain')
        self.end_headers()
        self.wfile.write("\n".join(cache[-MAX_CACHE:]).encode())
    def log_message(self, *args): pass

def udp_listener():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('0.0.0.0', 2055))
    while True:
        data, addr = sock.recvfrom(65535)
        if len(data) < 24: continue
        if struct.unpack('>H', data[0:2])[0] != 5: continue
        count = struct.unpack('>H', data[2:4])[0]
        off = 24
        for i in range(count):
            if off + 48 > len(data): break
            src = socket.inet_ntoa(data[off:off+4])
            dst = socket.inet_ntoa(data[off+4:off+8])
            pkts = struct.unpack('>I', data[off+16:off+20])[0]
            bytes_flow = struct.unpack('>I', data[off+20:off+24])[0]
            port = struct.unpack('>H', data[off+32:off+34])[0]
            proto = data[off+38]
            cache.append(f"{src} {dst} {bytes_flow} {pkts} {port} {proto}")
            off += 48
        if len(cache) > 2 * MAX_CACHE:
            cache[:MAX_CACHE] = []

Thread(target=udp_listener, daemon=True).start()
HTTPServer(('0.0.0.0', 8080), Handler).serve_forever()
EOF
exit

7. Restart Container

/container stop alpine:latest
/container start alpine:latest

8. Enable Traffic Flow on Router

/ip traffic-flow set enabled=yes interfaces=sfp-sfpplus1
/ip traffic-flow target add dst-address=172.17.0.2 port=2055 version=5
/ip traffic-flow set active-flow-timeout=5s inactive-flow-timeout=5s
Note: Replace sfp-sfpplus1 with the interface you want to monitor (e.g. ether1 or bridge).

9. Testing

Generate traffic on router:

/tool bandwidth-test address=8.8.8.8 duration=5

Fetch data (from Windows PC):

curl.exe http://172.17.0.2:8080/

📤 Output Format

62.146.206.118 3.67.32.184 226 3 62958 6
80.190.223.26 8.210.130.2 92 2 38262 6
62.146.214.242 149.154.167.41 546 4 51960 6

Field meaning:

FieldMeaning
src_ipSource IP address
dst_ipDestination IP address
bytesBytes (cumulative from NetFlow v5)
packetsPackets (cumulative from NetFlow v5)
src_portSource port (0 for ICMP)
protocol6=TCP, 17=UDP, 1=ICMP

🔧 Troubleshooting

ProblemSolution
Container stays S (stopped)Activate container mode (step 1)
curl returns empty responseRun bandwidth test, check target
apk add python3 failsCheck masquerading (step 3), set DNS: echo "nameserver 8.8.8.8" > /etc/resolv.conf
Container doesn't start after reboot/container set alpine:latest start-on-boot=yes
Port 8080 unreachableCheck firewall rules on router, container IP correct?
veth1 shows I - INACTIVEContainer not running: /container start alpine:latest

📦 Persistent Storage (optional)

/container/mounts add dst=/data src=disk1/netflow_data
/container set alpine:latest mounts=netflow-list
/container stop alpine:latest && /container start alpine:latest

Adjust script: DATA_DIR = "/data"

🌐 Network Overview (Subnet 172.17.0.0/30)

IPUsage
172.17.0.0Network address
172.17.0.1Gateway (Router / Bridge)
172.17.0.2Container
172.17.0.3Broadcast

🎯 Summary

FeatureStatus
NetFlow export from router✅ active
Container with Python✅ running
HTTP API on port 8080✅ available
Desired output format✅ implemented
Masquerading for internet✅ configured