MorkNetVisualizer
A real-time network traffic visualization tool for home and small office networks. It displays live traffic flows as animated arcs on a world map, showing where your devices are connecting to and where inbound traffic originates from.
Important disclaimer
This project was built entirely through AI-assisted development (Claude by Anthropic). No human has audited, reviewed, or manually written the code. Use it at your own risk in trusted network environments. Do not expose it to the public internet without adding authentication in front of it (e.g. via a reverse proxy with access control).
Download
Or clone the repository and build from source.
What it does
- Captures real network flows via NetFlow v9/IPFIX from your router
- Geolocates source and destination IPs using a local MaxMind database
- Draws animated arcs on a world map showing traffic direction and volume
- Shows active devices, traffic volume, protocol breakdown, and top talkers
- Overlays CrowdSec banned IPs as markers on the map (optional)
- Displays a day/night overlay based on current solar position
- Supports arc history replay, connection heatmap, device grouping, and more
Requirements
Network
- A router that supports NetFlow v9 or IPFIX export (tested with UniFi Dream Router)
- The router must be able to reach the host running this tool over UDP
Host
- Docker and Docker Compose
- At least 512 MB RAM
- Network reachable from the router for NetFlow (UDP) and from your browser over HTTP
Accounts and files
MaxMind GeoLite2 (required)
NetFlow gives you IP addresses. MaxMind translates those IPs into geographic coordinates for map display. Without it the map will show no arcs.
- Create a free account at https://www.maxmind.com/en/geolite2/signup
- Download GeoLite2-City in the binary .mmdb format
- Place it at
data/GeoLite2-City.mmdbin the project directory
The database is updated weekly by MaxMind. To refresh it, replace the file and restart the backend container.
UniFi API key (required)
Used to fetch device names and active client list.
- Log into your UniFi controller
- Go to Settings -> System -> Advanced -> API Keys
- Create a new API key and copy it
CrowdSec bouncer API key (optional)
See the CrowdSec section below.
Tech stack
Backend
- Python 3.12
- FastAPI with uvicorn (HTTP API and WebSocket server)
- Native NetFlow v5/v9 parser written in pure Python using the struct module
- geoip2 (MaxMind GeoLite2 database reader)
- httpx (async HTTP client for UniFi and external APIs)
Frontend
- React 18 with Vite
- d3-geo with Natural Earth projection for the SVG world map
- topojson-client for country geometry
- WebSocket for live data streaming
- All state persistence via localStorage (no external dependencies for UI state)
Infrastructure
- Docker Compose with two containers: backend (Python) and frontend (nginx serving built React)
- NetFlow collected on UDP (default port 2055)
- nginx proxies WebSocket and API calls internally so only one port is exposed
Installation
1. Clone or download the project
git clone <your-repo-url>
cd MorkNetVisualizer
2. Place the GeoIP database
mkdir -p data
cp /path/to/GeoLite2-City.mmdb data/
3. Configure the environment
cp backend/.env.example backend/.env
Edit backend/.env and fill in your values. See the configuration section below.
4. Configure NetFlow on your router
On a UniFi Dream Router:
- Go to Settings -> CyberSecure -> Traffic Logging -> NetFlow (IPFIX)
- Enable it
- Set collector address to the IP of the host running this tool
- Set collector port to 2055 (or whatever you set in NETFLOW_PORT)
- Set version to 9
- Set sampling to Off
5. Start the stack
docker compose up -d --build
The first build takes 2-3 minutes. After that, open a browser to:
http://<host-ip>:3500
Configuration
All configuration lives in backend/.env. Copy backend/.env.example as a starting point.
# UniFi Controller
UNIFI_URL=https://192.168.1.1 # IP or hostname of your UniFi controller
UNIFI_API_KEY= # API key from UniFi settings
UNIFI_SITE=default # Site name, usually "default"
UNIFI_VERIFY_SSL=false # Set to false for self-signed certificates
# GeoIP
GEOIP_DB_PATH=/data/GeoLite2-City.mmdb
# NetFlow collector
NETFLOW_PORT=2055 # Must match what your router is configured to send to
# Home location (shown as the origin point on the map)
HOME_LAT=51.5074
HOME_LON=-0.1278
HOME_NAME=Home
# CrowdSec (optional, leave blank to disable)
CROWDSEC_URL=
CROWDSEC_API_KEY=
CrowdSec integration (optional)
CrowdSec is an open source threat detection tool. When configured, MorkNetVisualizer will display banned IPs as red X markers on the map, with new bans showing a brief pulse animation. You can filter between locally detected bans and the community blocklist.
Setup
CrowdSec must be running on a host reachable from the MorkNetVisualizer container.
By default, CrowdSec only listens on localhost (127.0.0.1:8080). To allow
remote access from another host, edit /etc/crowdsec/config.yaml on the
CrowdSec host:
api:
server:
listen_uri: 0.0.0.0:8080
Then restart CrowdSec:
sudo systemctl restart crowdsec
Create a bouncer API key:
sudo cscli bouncers add MorkNetVisualizer-reader
Copy the generated key. Then in backend/.env:
CROWDSEC_URL=http://<crowdsec-host-ip>:8080
CROWDSEC_API_KEY=<key from above>
Restart the backend:
docker compose restart backend
Updating the GeoIP database
MaxMind releases updated databases weekly. To update:
- Download the new GeoLite2-City.mmdb from your MaxMind account
- Replace
data/GeoLite2-City.mmdb - Restart the backend:
docker compose restart backend
Troubleshooting
No arcs on the map
- Check that NetFlow is enabled on your router and pointing at the correct host and port
- Verify the backend is receiving packets:
curl http://<host>:3500/api/statusLook fornetflow_stats.packetsincrementing - Confirm the GeoLite2 database is present:
docker compose exec backend ls /data - Check backend logs:
docker compose logs backend
Reconnecting shown in the UI
- The WebSocket connection to the backend failed
- Check
docker compose logs backendfor errors - Verify the backend container is running:
docker compose ps
CrowdSec shows no bans
- Run
curl http://<crowdsec-host>:8080/v1/decisionswith the API key header to verify the CrowdSec API is reachable - Check that
listen_uriin/etc/crowdsec/config.yamlis0.0.0.0:8080and not127.0.0.1:8080
ASN/Org shows Unknown
- ip-api.com has a rate limit of 45 requests per minute on the free tier
- The backend caches results, so this only affects the first lookup per IP
- If you are clicking many different IPs quickly, wait a moment and try again
Ports
| Port | Protocol | Purpose |
|---|---|---|
| 3500 | TCP | Web interface (nginx, proxies to backend) |
| 2055 | UDP | NetFlow/IPFIX collector |
Data privacy
All data stays on your host. No traffic data, IP addresses, or device information is sent anywhere. The only outbound requests are:
- MaxMind GeoLite2 downloads (manual, on your schedule)
- ASN lookups to ip-api.com and ipinfo.io (triggered by clicking an arc in the UI)
- World map GeoJSON loaded from a CDN on first page load (cdn.jsdelivr.net)
License
MIT