# 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 [Download v1.0 tar.gz](https://github.com/danielmorkcode/MorkNetVisualizer/releases/download/v1.0.0/netmapFinal.tar.gz) 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. 1. Create a free account at https://www.maxmind.com/en/geolite2/signup 2. Download GeoLite2-City in the binary .mmdb format 3. Place it at `data/GeoLite2-City.mmdb` in 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. 1. Log into your UniFi controller 2. Go to Settings -> System -> Advanced -> API Keys 3. 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 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: 1. Go to Settings -> CyberSecure -> Traffic Logging -> NetFlow (IPFIX) 2. Enable it 3. Set collector address to the IP of the host running this tool 4. Set collector port to 2055 (or whatever you set in NETFLOW_PORT) 5. Set version to 9 6. 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://: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: ```yaml 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://:8080 CROWDSEC_API_KEY= ``` Restart the backend: ``` docker compose restart backend ``` --- ## Updating the GeoIP database MaxMind releases updated databases weekly. To update: 1. Download the new GeoLite2-City.mmdb from your MaxMind account 2. Replace `data/GeoLite2-City.mmdb` 3. 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://:3500/api/status` Look for `netflow_stats.packets` incrementing - 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 backend` for errors - Verify the backend container is running: `docker compose ps` **CrowdSec shows no bans** - Run `curl http://:8080/v1/decisions` with the API key header to verify the CrowdSec API is reachable - Check that `listen_uri` in `/etc/crowdsec/config.yaml` is `0.0.0.0:8080` and not `127.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