← All writing
Network EngineeringDS-2026-005

NetHero: a Go network scanner built for VoIP readiness checks

Abstract

NetHero is a Go CLI that runs structured VoIP readiness checks from declarative YAML templates. This post covers the architecture, how discovery works, and why the Go rewrite mattered.

My last post on this topic ended with a note about rewriting the VoIP scanner in Go. This is what that looks like.

The Python version worked but never solved distribution. Getting it onto a client machine meant confirming Python was installed, running pip, and working through at least one Windows compatibility issue. For a diagnostic tool, that overhead is a problem. Go produces a single self-contained binary — nethero.exe, no runtime, no install — and that alone made the rewrite worth doing.

Template-driven checks

Check parameters in the Python scanner were scattered across config files, profile YAML, and hardcoded probe functions. In NetHero, everything a scan needs is in one YAML template: what to test, what thresholds apply, which checks run, and the remediation text for each failure.

Template (hosted-ucaas.yaml — hosted UCaaS platform)
  ├─ voip_cloud: SBC fleet across multiple regions
  ├─ Control channel: TCP control-channel (same SBC fleet as SIP)
  ├─ Provisioning: platform provisioning endpoint:443
  ├─ Thresholds: 150ms warn / 300ms fail, 30ms jitter, 1% loss
  └─ Remediation text per failed check

Adding a new platform means writing a YAML file, not touching the scanner code. That matters when you're covering five different PBX environments in the same week.

Validated in production

The template has been validated against a live hosted UCaaS deployment. Sample output from a healthy site:

NetHero Scan  nhr_[…]
Template:     hosted-ucaas v1   |   Mode: standard   |   Duration: 4772ms
Results:      20 pass  0 fail  0 warn  1 skip  0 error

DNS:
  [PASS]  [provisioning-host]         resolved to 3 addresses in 1ms        [VERIFIED]

Connectivity:
  [PASS]  voip-platform-sbc           [sbc]:5060 reachable in 139ms         [VERIFIED]
  [PASS]  SBC cloud-proxy-1           reachable in 134ms                    [VERIFIED]
  [PASS]  SBC cloud-proxy-2           reachable in 114ms                    [VERIFIED]
  [PASS]  SBC cloud-proxy-3           reachable in 101ms                    [VERIFIED]
  [PASS]  SBC cloud-proxy-4           reachable in 102ms                    [VERIFIED]
  [PASS]  SBC cloud-proxy-5           reachable in 101ms                    [VERIFIED]
  [PASS]  SBC cloud-proxy-6           reachable in 205ms                    [VERIFIED]
  [PASS]  Control-channel proxy-1…    TCP [ctrl] reachable in 102ms         [VERIFIED]
  [PASS]  Control-channel proxy-2…    TCP [ctrl] reachable in 103ms         [VERIFIED]
  [PASS]  Control-channel proxy-3…    TCP [ctrl] reachable in 102ms         [VERIFIED]
  [PASS]  Control-channel proxy-4…    TCP [ctrl] reachable in 101ms         [VERIFIED]
  [PASS]  Control-channel proxy-5…    TCP [ctrl] reachable in 79ms          [VERIFIED]
  [PASS]  Control-channel proxy-6…    TCP [ctrl] reachable in 125ms         [VERIFIED]
  [PASS]  Platform Provisioning…      HTTP 200 in 378ms                     [VERIFIED]

VoIP / SIP:
  [PASS]  voip-platform-sbc           SIP 200 in 203ms                      [VERIFIED]

Performance:
  [PASS]  voip-platform-sbc           Latency 90ms, jitter 17ms, loss 0.0% [VERIFIED]

Discovery:
  [PASS]  local interfaces            1 active interface found              [VERIFIED]
  [PASS]  ARP discovered devices      1 device on local network             [VERIFIED]
  [SKIP]  vps_probe                   no external probe endpoint configured

The SIP OPTIONS probe returned a 200 from the SBC — confirming registration is responding and the SIP stack is healthy, not just that a TCP port is open. At 90ms average RTT with 17ms jitter and zero packet loss, this site is well within standard VoIP quality thresholds.

Every control-channel path was reachable. That channel matters because it's the persistent outbound connection that keeps the phone registered after initial SIP. Blocking it is a common misconfiguration on firewall upgrades — phones appear registered but calls fail intermittently or drop mid-session.

What a blocked site looks like

For comparison, here's the output pattern on a site where the firewall has SIP ALG enabled and the control-channel port is blocked:

VoIP / SIP:
  [FAIL]  voip-platform-sbc           SIP OPTIONS timed out after 5000ms
          → Disable SIP ALG on the router/firewall. Verify outbound
            SIP is permitted to the platform's SBC addresses.

Connectivity:
  [FAIL]  Control-channel proxy-1…    TCP [ctrl] connection refused
  [FAIL]  Control-channel proxy-2…    TCP [ctrl] connection refused
          → Outbound control-channel port to the platform must be permitted.
            Phone may connect but will fail to maintain registration.

Remediation text is injected per-check directly from the template — whoever is reading the output doesn't need to know the platform internals to understand what to fix.

Network discovery

The discovery check enumerates the ARP cache and classifies each device by combining four signals in parallel:

Per host (up to 30 concurrent, 1.5s timeout each):
  ├─ OUI lookup     → MAC vendor
  ├─ Reverse DNS    → hostname pattern matching
  ├─ HTTP/S banner  → firmware version strings
  └─ SSH banner     → device header identification
       └─ Device type + confidence score

Confidence scoring matters in practice. An OUI match alone on a phone is interesting. An OUI match plus Poly hostname pattern plus firmware strings in the HTTP response is a definite match. Low-confidence results stay out of normal output and surface in verbose mode.

This is useful before you run connectivity checks because SIP ALG and double-NAT are router problems, not phone problems. Knowing what's sitting between the voice VLAN and the SBC before you start troubleshooting cuts diagnosis time significantly — the scanner has surfaced router models with SIP ALG enabled by default before a single probe was run, which narrowed the diagnosis immediately.

Check types

Check What it validates
tcp_port TCP connectivity to SBC and provisioning endpoints
cloud_reachability All cloud endpoints defined in the template
latency RTT, jitter, and packet loss to the SBC
sip_options SIP stack responding with SIP 200 (RFC 3261 §11)
dns Forward resolution for provisioning hostnames
discovery LAN device inventory and classification
vps_probe External path validation via deployed VPS

What's next

Core coverage is solid. Remaining gaps: SNMP walk for switch config verification, VLAN tagging validation on the wire, and an RTP stream analysis module. Templates for additional platforms — hosted UCaaS, carrier SIP trunks, on-prem Asterisk/FreePBX — are the next priority.

Passive probing is also on the roadmap. An earlier version of this tool had a working network topology map built entirely from passive observation — ARP traffic, SIP REGISTER activity, passive DNS — with no active scanning. It was one of the better features of that build. The plan is to bring that capability forward properly: passive discovery running alongside the active check suite, feeding a topology view that builds up as traffic is observed rather than as probes are sent.


References

Related posts

Network Engineering

SIP one-way audio, missed inbound calls, and dropped calls: a diagnostic playbook

Network Engineering

Packet capture field notes: VoIP diagnostics

Network Engineering

Three attempts at a VoIP scanner, and what the first two got wrong