NetMap

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).


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 <your-repo-url>
cd netmap

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://<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, NetMap 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 NetMap 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 netmap-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:

  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://<host>: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://<crowdsec-host>: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

S
Description
Network traffic visualization
Readme 70 KiB
Languages
JavaScript 60.6%
Python 20%
CSS 18.4%
HTML 0.5%
Dockerfile 0.5%