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)

  1. Download the latest portable Windows package from the GitHub Releases page — choose the VoIPHealthCheck-windows-package-<version>.zip asset for the bundled nmap/ folder. (Releases are the permanent download location; per-commit dev builds remain available as Build LocalScanner Windows EXE workflow artifacts.)
  2. Unzip into a fresh folder (e.g. C:\Tools\VoIPHealthCheck-2.2.0\). No installer is required.
  3. Launch the versioned executable (VoIPHealthCheck-2.2.0.exe) so the file name itself confirms which build you are running.
  4. 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 dumpcap when Wireshark/Npcap is present and pktmon as 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:

  1. Posts the structured ScanReport JSON to POST /api/v2/scan/upload.
  2. Stores the server-assigned session_id in memory so subsequent capture artifacts can be attached.
  3. Posts the raw streaming log as a log artifact via POST /api/v2/scan/<id>/artifact.
  4. On every Stop Capture, posts each output file as a capture artifact attached to the same scan, or via POST /api/v2/capture/upload when 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

EndpointPurpose
POST /api/v2/scan/uploadJSON ScanReport upload (auto-called by client).
POST /api/v2/scan/<id>/artifactMultipart artifact attached to a scan (log, capture).
POST /api/v2/capture/uploadStandalone capture upload, no session required.
GET /api/v2/scan/<id>JSON dump of a session including artifacts.
GET /api/v2/scansList recent sessions.
GET /api/v2/artifact/<id>/downloadRaw artifact download.
GET /api/v2/scan/<id>/report.jsonCanonical report download.
GET /api/v2/statusHealth, schema version, KPIs.

Security model

  • Bearer-token auth on uploads — when VOIPSCAN_UPLOAD_TOKEN is set on the server, every upload endpoint requires Authorization: Bearer <token>. Comparisons use hmac.compare_digest to defeat timing attacks.
  • Filename hardening — uploads are sanitised through secure_filename plus 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 interpret module 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.

The same checklist is mirrored in README.md; treat them as one document split across two surfaces.