Skip to content

Akshat-Pandey16/MeshHawk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MeshHawk

Local-first WiFi mesh-network detector and analyzer.

Python FastAPI React Tailwind Postgres Redis License

Drop in any .pcap / .pcapng. MeshHawk reconstructs the 802.11 topology, scores every connected component for "mesh-ness", and shows you a live map of who's really talking to whom — without a single byte leaving your machine.


What it does

  • Topology reconstruction — extracts QoS-data frames with scapy, resolves source / destination per the 802.11 DS flags, and builds a directed MAC graph in networkx.
  • Mesh detection — partitions the graph into weakly-connected components and scores each one. A component is classified as a mesh when every node has degree ≥ 70 % of the component size.
  • Per-component details — density, undirected diameter, BSSIDs, and the full device list with OUI vendor lookup (cached on disk after the first call).
  • Two ingest paths — upload a capture from disk, or — when a WiFi card is in 802.11 monitor mode — sniff straight from the browser. The live-capture capability is auto-detected at runtime.
  • Printable report — a per-capture report with all components, devices, and metrics, ready for Cmd/Ctrl + P.

Architecture

                  ┌─────────────────────────┐
                  │   React + Vite          │
                  │   (apps/web, :5173)     │
                  └────────────┬────────────┘
                               │  JWT (Bearer)
                               ▼
                  ┌─────────────────────────┐
                  │   FastAPI               │
                  │   (apps/api, :8000)     │
                  │                         │
                  │  /auth  /captures       │
                  │  /analyses  /live       │
                  │  /system                │
                  └─────┬──────────────┬────┘
                        │              │
       SQLAlchemy async │              │ ARQ enqueue
                        ▼              ▼
                  ┌──────────┐   ┌──────────┐
                  │ Postgres │   │  Redis   │
                  │   :5432  │   │   :6379  │
                  └──────────┘   └────┬─────┘
                                      │ pop job
                                      ▼
                  ┌─────────────────────────┐
                  │   ARQ worker            │
                  │   (analyze_capture)     │
                  │                         │
                  │   scapy  → networkx     │
                  │   → vendor cache        │
                  │   → Postgres            │
                  └─────────────────────────┘

Per-capture pipeline:

  1. Upload — multipart pcap (+ optional airodump csv) lands in MESHHAWK_UPLOAD_DIR. The API returns 201 with status pending and enqueues analyze_capture(capture_id) to Redis.
  2. Parse — scapy walks the capture (auto-detects pcap vs pcapng), keeps only QoS-data frames (type == 2 && Dot11QoS present), resolves source/dest/BSSID per the to_ds / from_ds flags, filters broadcast/null/multicast MACs.
  3. Graph — every unique (src, dst) becomes a directed edge in a networkx.DiGraph. Components are extracted via weakly_connected_components.
  4. Score — per component: density, undirected diameter, and the mesh heuristic (every node has degree ≥ 0.7 × (n − 1)).
  5. Enrich — first 3 MAC octets (OUI) → vendor via api.macvendors.com, cached on disk in the mac_vendors table.
  6. Persist — full result lands in analyses.result (JSONB, GIN-indexed), capture status flips to ready, and the frontend's React Query auto-refetches.

A printable PDF report is generated on demand from a Jinja2 HTML template, with each component's topology rendered as an inline SVG (matplotlib

  • networkx). The route is GET /analyses/by-capture/{id}/report.pdf.

Quickstart

Prerequisites

Tool Version Install
Docker 27+ https://docs.docker.com/engine/install/
Python 3.13 system / pyenv
uv 0.5+ curl -LsSf https://astral.sh/uv/install.sh | sh
Node 22+ LTS (24 tested) nvm recommended
Bun or pnpm or npm latest Makefile auto-detects
Pango (libpango-1.0) system Linux: sudo apt install libpango-1.0-0 libpangoft2-1.0-0 — required by WeasyPrint for PDF reports

Verify with:

make doctor

First run

make setup            # .env, deps, postgres+redis, migrations, demo user
make dev              # API (:8000) + worker + web (:5173)

Then open http://localhost:5173 and sign in with the seeded demo account:

demo@meshhawk.dev / meshhawk

Drop in a .pcap to see the topology.


Makefile reference

make help                   # show all targets

# Setup
make setup                  # one-shot first-run (env + deps + db + seed)
make env                    # copy .env.example → .env if missing
make doctor                 # verify toolchain (uv, docker, node/bun)

# Dev
make dev                    # api + worker + web concurrently
make api.dev                # uvicorn only
make worker.dev             # ARQ worker only (auto-reload)
make web.dev                # Vite only

# Install / Upgrade
make install                # api + web deps
make upgrade                # bump everything to latest allowed
make api.upgrade            # uv lock --upgrade + sync
make web.upgrade            # bun/pnpm/npm update
make web.upgrade.latest     # rewrites package.json to absolute latest
make web.outdated           # what's behind

# Services / DB
make services.up            # postgres + redis (docker compose)
make services.down
make services.reset         # wipe volumes, migrate, seed
make db.migrate             # alembic upgrade head
make db.revision m="..."    # generate a new migration
make db.seed                # demo user
make db.shell               # psql

# Quality
make lint                   # ruff + biome
make fmt                    # ruff format + biome write
make test                   # pytest

# Build
make build                  # api wheel + web dist

Repo layout

meshhawk/
├── apps/
│   ├── api/                              # FastAPI service
│   │   ├── alembic/
│   │   ├── src/meshhawk/
│   │   │   ├── main.py                   # app factory + lifespan + exc handlers
│   │   │   ├── config.py                 # pydantic-settings (MESHHAWK_ env vars)
│   │   │   ├── db.py                     # async engine, sessionmaker, deps
│   │   │   ├── core/                     # security, exceptions, logging
│   │   │   ├── models/                   # SQLAlchemy ORM (User, Capture, Analysis, MacVendor)
│   │   │   ├── schemas/                  # Pydantic request/response DTOs
│   │   │   ├── repos/                    # repository pattern (query layer)
│   │   │   ├── services/
│   │   │   │   ├── pcap_parser.py        # scapy → PcapStats (pcap + pcapng)
│   │   │   │   ├── csv_parser.py         # airodump CSV → two sections
│   │   │   │   ├── graph_analyzer.py     # networkx → GraphMetrics
│   │   │   │   ├── mac_vendor.py         # OUI lookup with on-disk cache
│   │   │   │   ├── live_capture.py       # scapy live + capability detection
│   │   │   │   ├── storage.py            # safe file uploads
│   │   │   │   └── analysis_pipeline.py  # orchestrates parser → graph → vendor
│   │   │   ├── queue/                    # ARQ worker settings + Redis pool
│   │   │   ├── tasks/                    # ARQ task functions
│   │   │   ├── api/                      # FastAPI routers + deps
│   │   │   └── scripts/seed.py
│   │   ├── tests/
│   │   ├── alembic.ini
│   │   └── pyproject.toml
│   └── web/                              # Vite + React
│       ├── src/
│       │   ├── main.tsx, router.tsx
│       │   ├── index.css                 # Tailwind 4 design tokens
│       │   ├── lib/                      # api client, queryClient, types, utils
│       │   ├── stores/                   # zustand (auth, theme)
│       │   ├── hooks/                    # TanStack Query hooks
│       │   ├── components/
│       │   │   ├── ui/                   # shadcn primitives
│       │   │   ├── layout/               # AuroraBackground, Navbar, CustomCursor, …
│       │   │   ├── network/              # NetworkGraph (React Flow), ComponentCard
│       │   │   ├── viz/                  # PowerChart (Recharts)
│       │   │   └── common/               # StatCard, StatusPill, EmptyState
│       │   └── features/                 # one folder per page-flow
│       └── package.json, vite.config.ts, biome.json
├── docker/postgres/init.sql              # citext + pgcrypto extensions
├── docker-compose.yml                    # postgres + redis
├── Makefile
├── .env.example
└── README.md

Database

Migrations live in apps/api/alembic/. Four tables:

Table Notes
users CITEXT email (case-insensitive unique), Argon2id hash, last_login_at, composite index on (is_active, created_at)
captures Per-user pcap metadata; composite indexes on (user_id, created_at DESC) and (user_id, status); FK cascade
analyses One-to-one with captures (unique capture_id), aggregate stats + JSONB result with GIN index, check-constraint mesh_component_count ≤ component_count
mac_vendors OUI → vendor cache, primary key oui CHAR(6) with regex check ^[0-9a-f]{6}$

Every table uses UUID PKs (gen_random_uuid() via pgcrypto), timezone-aware timestamps with server_default now(), and predictable constraint names via MetaData(naming_convention=…) so Alembic autogen stays clean.


API surface

Base path: /api/v1. OpenAPI docs at http://localhost:8000/docs.

Auth

Method Path Purpose
POST /auth/signup Create user
POST /auth/login JSON body → { access_token, refresh_token }
POST /auth/token OAuth2 password-flow (for /docs "Authorize")
POST /auth/refresh Refresh access token
GET /auth/me Current user

Captures

Method Path Purpose
GET /captures Paginated list (most recent first)
POST /captures Multipart upload (pcap required, csv optional) — enqueues analysis
GET /captures/{id} Single capture (incl. status)
DELETE /captures/{id} Removes capture + files + analysis
GET /captures/{id}/file Download original pcap
GET /captures/{id}/csv Download attached CSV (if any)

Analyses

Method Path Purpose
GET /analyses/by-capture/{id} Full graph result (404 until ready)
POST /analyses/by-capture/{id}/rerun Re-queue analysis (202)
GET /analyses/by-capture/{id}/report.pdf Render a printable PDF report

Live capture

Method Path Purpose
GET /live/capabilities Tells the UI whether to show the live-scan toggle, and why not if not
POST /live/captures Sniff { interface, duration_seconds } and queue analysis

System

Method Path Purpose
GET /system/health Returns version + DB connectivity

All errors return a consistent envelope:

{ "error": { "code": "invalid_credentials", "message": "Invalid email or password." } }

Environment

Defaults in .env.example work as-is for local dev. Override anything via process env or .env:

MESHHAWK_ENV=development                                    # development | production | test
MESHHAWK_DATABASE_URL=postgresql+asyncpg://…/meshhawk
MESHHAWK_REDIS_URL=redis://localhost:6379/0
MESHHAWK_JWT_SECRET=...long-random...
MESHHAWK_ACCESS_TOKEN_TTL_MIN=60
MESHHAWK_REFRESH_TOKEN_TTL_DAYS=14
MESHHAWK_CORS_ORIGINS=http://localhost:5173                 # comma-separated
MESHHAWK_UPLOAD_DIR=./.data/uploads
MESHHAWK_MAX_UPLOAD_MB=200
MESHHAWK_DEMO_USER_EMAIL=demo@meshhawk.dev
MESHHAWK_DEMO_USER_PASSWORD=meshhawk

VITE_API_BASE_URL=http://localhost:8000

Frontend

  • Vite + React + TypeScript with strict mode
  • Tailwind v4 (CSS-first config, no tailwind.config.js) — see src/index.css for the design tokens, with full light + dark variants
  • shadcn/ui primitives, Radix under the hood
  • TanStack Query v5 for server state (smart polling: rows refresh every 1.2 s while analyzing, then back off to 30 s)
  • Zustand for the bits of client state (auth + theme), both persist-ed
  • React Router v7
  • React Flow (@xyflow/react) for the network graph; Recharts for power bars
  • Motion (formerly Framer Motion) for transitions and status ripples
  • Biome for lint + format

The whole UI is responsive and supports Light / Dark / System via the navbar toggle.


Live capture

The /live/capabilities route does runtime detection:

  • POSIX check
  • ip or iw in PATH
  • At least one interface with /sys/class/net/<iface>/type == 803 (monitor)
  • geteuid() == 0

When all pass, the UI lights up LiveScanPage with the detected interfaces and a duration slider. Otherwise the reason is rendered inline ("No interface in monitor mode…", "Live capture requires root…").

To enable, put your card in monitor mode and run the API with sudo:

sudo airmon-ng start wlan0
sudo -E uv run uvicorn meshhawk.main:app --reload

Testing

make test                            # apps/api: pytest -q

Unit tests cover:

  • graph_analyzer — empty graph, multi-component, triangle is mesh, single-edge is not, threshold validation
  • csv_parser — both airodump sections, empty input, single-section
  • core.security — password roundtrip, access/refresh roundtrip, type validation, tampered token rejection

Demo data

make db.seed (idempotent) creates:

  • demo@meshhawk.dev / meshhawk (superuser)

Wipe and re-seed:

make services.reset    # nukes Postgres + Redis volumes, re-runs migrations + seed

License

MIT.

Authors

Akshat Pandey, Sanskar Dwivedi, Yash Sakre, Ranjit Ranjan, Jayash Tripathi, Poorva Diwan.

About

Mesh Detector and Analyzer application built on React and FASTApi

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors