Documentation
Reference for the VoIP Health Check desktop client, REST API, Flask dashboard, and deployment workflow. Use the menu on the left to jump to a section.
Overview
VoIP Health Check is a two-part system: a portable Windows desktop client that gathers evidence from a phone-system network, and a Flask web service that ingests those reports, stores them in SQLite, and renders a live analytics dashboard. The whole pipeline is built around one principle — evidence over guesses. Every conclusion the app surfaces is backed by data captured during the scan.
Operators use the desktop client at the customer site to
produce a ScanReport, which is
automatically uploaded to
/api/v2/scan/upload. From there the
Flask app normalises results into a typed schema (sessions,
issues, latency targets, ports, artifacts) and exposes them on
this site at /dashboard
and on per-scan detail pages.
Getting started
End users (technicians)
- Download the latest portable Windows package from the
GitHub
Releases page — choose the
VoIPHealthCheck-windows-package-<version>.zipasset for the bundlednmap/folder. (Releases are the permanent download location; per-commit dev builds remain available as Build LocalScanner Windows EXE workflow artifacts.) - Unzip into a fresh folder (e.g.
C:\Tools\VoIPHealthCheck-2.2.0\). No installer is required. - Launch the versioned executable
(
VoIPHealthCheck-2.2.0.exe) so the file name itself confirms which build you are running. - Click Quick Scan. When the scan finishes the GUI switches to a plain-English summary, the raw log is written to disk, and the report is uploaded to this dashboard.
Developers
The Flask app lives in web/. Run
it locally with:
pip install -r requirements.txt FLASK_APP=web.app flask run # or directly python -m web.app
The desktop client lives in LocalScanner/
and is launched via
python -m voipscan or by running
LocalScanner/run.bat on Windows.
Desktop client
The Windows GUI is a Tkinter / ttk app organised around three cards: Quick Scan, an Optional details panel, and a Scan Results / Log pane that swaps between a streaming log and a plain-English summary when the scan completes.
- Quick Scan — primary one-click
scan. Auto-detects the gateway, runs the diagnostics
pipeline, generates a
ScanReport, saves a raw log, and uploads to the dashboard. - Start / Stop Packet Capture — dedicated
buttons for capturing packets independently of a scan,
backed by
dumpcapwhen Wireshark/Npcap is present andpktmonas a fallback. - Optional → Advanced — collapsible panel exposing Hosted Platform (Auto, On-Prem, Cloud Only, Remote Phone), Gateway IP, Firewall IP, Starbox IP, and an optional SIP test endpoint. Every field is optional and is auto-detected or skipped cleanly when blank.
- Run Advanced Scan — the bottom button runs every Quick Scan check plus a deeper SIP/RTP port sweep, longer latency sampling, and a service-version nmap pass when nmap is available.
- Download Results / Show Raw Log / Show Summary — export the report as JSON or text, or toggle between the streaming log and the summary view.
The 2.2.0 release uses a light, modern theme with a teal accent, ttk-styled buttons, equal-width primary actions, and status badges in the summary. The window is resizeable; all long-form content scrolls.
Evidence collection
Each scan produces a structured
ScanReport defined in
LocalScanner/voipscan/report.py.
The report is the single source of truth and is what the
server stores and renders. Evidence sources include:
- Network info — hostname, gateway
detection from OS routes, public IP lookup, and local
interface enumeration via
netinfo.py. - SIP ALG checks — probes that look
for evidence of carrier-grade NAT or router SIP-helper
rewriting (
sipalg.py). - Port reachability — targeted TCP/UDP
probes against SIP, RTP, and Sangoma-relevant ports
(
porttests.py,sangoma_ports.py). - VLAN signal — LLDP / 802.1Q hints
where the OS exposes them
(
vlan.py). - Latency / jitter — ICMP
round-trip-time, jitter, and packet loss against a small
set of voice-relevant targets
(
latency.py). - DHCP — lease state and assignment
evidence (
dhcp.py).
Confidence is reported per-section; the app deliberately uses wording such as "likely on" / "likely off" rather than absolute claims when the evidence is indirect.
SIP ALG, ports, VLAN, latency & DHCP
The diagnostics layer is broken into focused modules so each
check can be reasoned about (and tested) independently. Each
produces a section the GUI renders with a status badge
(OK, WARN,
BAD, INFO,
UNK) plus a plain-English summary,
supporting bullets, and suggested fixes.
- SIP ALG — compares observed SIP behaviour to known ALG-rewriting fingerprints.
- Ports — measures open / blocked / filtered counts for the VoIP-relevant set, surfaces likely blocking on the dashboard.
- VLAN — reports tag visibility where the OS exposes it.
- Latency — per-target RTT, jitter, and
packet-loss percentages, classified
good/warn/bad. - DHCP — distinguishes static-vs-DHCP assignment and surfaces evidence of double-NAT.
Packet capture
The capture engine
(LocalScanner/voipscan/capture.py)
prefers dumpcap from Wireshark/Npcap
because it produces standards-compliant
.pcapng files. When Npcap is
unavailable it falls back to Windows
pktmon which produces
.etl files (or text dumps depending
on the OS build).
Captures run in a separate session so the Start/Stop buttons stay live during a scan. On stop, output files are listed in the log and automatically uploaded to the server, attached to the most recent scan session when one exists.
Upload pipeline
Uploads are handled by
LocalScanner/voipscan/upload.py.
After a scan finishes, the GUI:
- Posts the structured
ScanReportJSON toPOST /api/v2/scan/upload. - Stores the server-assigned
session_idin memory so subsequent capture artifacts can be attached. - Posts the raw streaming log as a
logartifact viaPOST /api/v2/scan/<id>/artifact. - On every Stop Capture, posts each output file as a
captureartifact attached to the same scan, or viaPOST /api/v2/capture/uploadwhen no scan id exists.
Upload failures never break the local scan. The full report, raw log, and any capture file remain on the operator's machine regardless of network state.
Backend & database
The server is a single Flask app
(web/app.py) backed by SQLite. The
schema is defined and migrated by
web/db.py.
- scan_sessions — one row per uploaded scan, with normalised columns for hostname, IPs, SIP ALG overall, latency status, issue counts, port counts, and likely-cause hints.
- issues — per-session issues with
severity (
critical/warning/info) and a category. - latency_targets — per-target RTT, jitter, packet loss, and classification.
- ports — per-port reachability state.
- artifacts — logs, captures, report JSON dumps, with size, sha256, and storage path.
- schema_version — tracks the active schema and guards the one-time legacy reset.
On the first deploy under schema version 2, the previous
audit_data.db (single
audits JSON blob) is automatically
backed up to
audit_data.db.legacy_<timestamp>.bak
next to the original file, dropped, and recreated. Subsequent
restarts are no-ops.
REST API
| Endpoint | Purpose |
|---|---|
POST /api/v2/scan/upload | JSON ScanReport upload (auto-called by client). |
POST /api/v2/scan/<id>/artifact | Multipart artifact attached to a scan (log, capture). |
POST /api/v2/capture/upload | Standalone capture upload, no session required. |
GET /api/v2/scan/<id> | JSON dump of a session including artifacts. |
GET /api/v2/scans | List recent sessions. |
GET /api/v2/artifact/<id>/download | Raw artifact download. |
GET /api/v2/scan/<id>/report.json | Canonical report download. |
GET /api/v2/status | Health, schema version, KPIs. |
Security model
- Bearer-token auth on uploads — when
VOIPSCAN_UPLOAD_TOKENis set on the server, every upload endpoint requiresAuthorization: Bearer <token>. Comparisons usehmac.compare_digestto defeat timing attacks. - Filename hardening — uploads are
sanitised through
secure_filenameplus a regex fallback, then truncated; only an allow-list of extensions (.pcap, .pcapng, .cap, .etl, .txt, .log, .json) is accepted. - Size cap — configurable via
VOIPSCAN_MAX_UPLOAD; the streaming handler aborts and removes any partial file before the response is sent. - Storage isolation — artifacts land
under
VOIPSCAN_ARTIFACT_DIR(default~/voipscan_api/artifacts/) in per-session subdirectories with timestamped filenames and a SHA-256 recorded in the database. - Safe Quick Scan profile — the desktop client deliberately replaced the legacy broad nmap sweep with a constrained profile so the scanner cannot be pointed at unrelated networks. The active profile is logged at startup for traceability.
- No PII on disk — the report focuses on network evidence (IPs, ports, RTTs) rather than customer-identifying content.
Dashboard analytics
/dashboard aggregates
across the entire scan_sessions
table:
- KPI strip — total scans, critical issues, warnings, blocked / filtered ports, average scan duration.
- Latency trend — rolling chart of average RTT, jitter and packet loss across the most recent 20 sessions, drawn by Chart.js.
- Recent scans table — per-session severity badges, SIP ALG verdict, VLAN status, latency status, likely cause, port counts, and links to the raw JSON / artifact set.
Per-scan detail pages (/scan/<id>) expose the full evidence: KPIs, every issue with severity, every probed port, every latency target, and download links for the JSON report and captured artifacts.
Deployment & operations
The Flask app is designed to run behind
gunicorn on a small VPS. The
included update.py automates a
pull-and-restart cycle. Useful environment variables:
VOIPSCAN_UPLOAD_TOKEN— bearer token required for upload endpoints.VOIPSCAN_ARTIFACT_DIR— where artifacts are stored on disk.VOIPSCAN_MAX_UPLOAD— upload size cap in bytes (default 25 MiB).
The Windows package is published as a
GitHub Release
by
.github/workflows/release-localscanner.yml
(manually triggered via Run workflow; tags each
release as localscanner-v<version>).
Releases are the permanent, expiry-free download
location for end users. The original
.github/workflows/build-localscanner.yml
still publishes per-commit dev builds as Actions
workflow-run artifacts. Each download ships a versioned
executable plus BUILD_INFO.txt
and VERSION.txt so an
operator can confirm the exact build from the file name
and a single log line.
Troubleshooting
Scan appears to hang or shows the wrong version
The 2.2.0 build replaced the legacy broad nmap sweep with the
Safe Quick Scan profile. On startup the
client logs the version, executable path, working directory,
application root, and the contents of
BUILD_INFO.txt. If your log shows
version 2.0.0 or no
Executable: line, you are running
an old executable — almost always a stale shortcut to a
previous VoipScanner_Desktop
folder. Delete the old folder, download the latest
package zip from the
GitHub
Releases page, extract into a fresh folder, and
launch the versioned exe.
Captures fail to start
dumpcap requires Npcap. Without it
the engine falls back to Windows
pktmon, which requires elevation
on most builds. Run the app as Administrator if you need
pktmon output.
Upload returns 401
The server has VOIPSCAN_UPLOAD_TOKEN
set. Configure the matching token on the client either via the
VOIPSCAN_UPLOAD_TOKEN environment
variable or
%LOCALAPPDATA%\VoipScan\upload.json.
Future roadmap
- Optional macOS / Linux build of the desktop client.
- Per-customer tagging on the dashboard for multi-tenant deployments.
- Pluggable export to SARIF for CI integration.
- Richer visualisation on per-scan detail (per-target latency sparklines, port heatmap).
- Automated regression suite around the
interpretmodule so plain-English output cannot drift silently.
Maintenance checklist
Whenever the desktop client, Flask app, REST API, schema, or deployment story changes, update the documentation in the same pull request. This page is the canonical, in-app reference and must stay in sync.
- New REST endpoint → add a row in the Backend & database table.
- New diagnostics module → add a bullet under Evidence collection and Diagnostics.
- Database / schema change → update Backend & database and the legacy-reset notes.
- Auth / security control change → update Security model.
- New environment variable or deployment step → update Deployment & operations.
- GUI change → refresh the Desktop client section and the screenshot in the README.
- Bumped version → refresh the troubleshooting banner text under Troubleshooting.
The same checklist is mirrored in
README.md; treat them as one
document split across two surfaces.