sipnab

The SIP & RTP analysis tool for people who ship voice infrastructure.

One static musl binary, zero dependencies — reads live traffic, a pcap, or the HEP feed from every proxy in your estate, and shows you the call flow, RTP quality, and security signals.

No install required — the capture is parsed locally, in your browser. Nothing is uploaded.

sipnab interactive TUI — call-flow ladder of a complete call: INVITE with SDP, 100 Trying, 180 Ringing, 200 OK, ACK, the RTP media line, then BYE and its 200 OK, with the decoded INVITE in the detail pane. One of seven dialogs in the same sample capture the analyze page loads

Demos

See It In Action

One capture, read end to end: a phone registers, the PBX probes it with an OPTIONS, two subscriptions come and go, and then a call is placed, answered and hung up with a BYE. The ladder puts the media beside the signaling, so the 64.3% packet loss on one direction of the audio — arriving in three bursts rather than spread evenly, which sounds far worse — sits next to the transactions it belongs to.

demos/mcp-stdio.sh tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap list_dialogs \
  | jq -c '.dialogs[] | {method, state, msg_count, duration_sec}'

{"method":"REGISTER","state":"Registered","msg_count":4,"duration_sec":0.01}
{"method":"OPTIONS","state":"Completed","msg_count":2,"duration_sec":0.008}
{"method":"SUBSCRIBE","state":"Terminated","msg_count":4,"duration_sec":0.018}
{"method":"SUBSCRIBE","state":"Terminated","msg_count":4,"duration_sec":0.015}
{"method":"INVITE","state":"Completed","msg_count":13,"duration_sec":23.712}
demos/mcp-stdio.sh tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap render_ladder \
  '{"call_id":"ZDYzOWVlNjEwM2NjZTBjNzliNmM1ZTNiOGZjNWFhN2E.","format":"text"}' \
  | sed -n '/^Result:/p; /^SIP Transactions:/,$p'

Result:     Completed (BYE)
SIP Transactions:
  INVITE -> 401
  ACK
  INVITE -> 100 (7ms) -> 180 (30ms) -> 200 (7650ms)
  ACK
  INVITE -> 200 (23624ms)
  ACK
  BYE -> 200 (23712ms)

Media Streams:
  RTP 192.168.10.40->192.168.10.41 PCMU SSRC=0xb72a7104 pkts=790 jitter=1ms loss=0.1%
    Loss pattern: random (not bursty)
  RTP 192.168.10.41->192.168.10.40 PCMU SSRC=0xbee0f2ed pkts=205 jitter=0ms loss=64.3%
    Loss pattern: BURSTY (3 bursts, avg duration 2473ms) — perceptually worse than random loss
  RTP 192.168.10.41->192.168.10.2 PCMU SSRC=0xbee0f2ed pkts=2 jitter=0ms loss=0.0%

Issues Detected:
  - Duration asymmetry: A leg lasted 15.8s, B leg 11.5s (Δ 4.4s) — one side may have hung up or dropped media early.

An agent asks why a call failed and gets a verdict, not a packet list: signaling rather than media, the 486 that ended it, and media explicitly ruled out. Over MCP on stdio — no daemon, no port, no database.

demos/mcp-stdio.sh tests/pcap-samples/sip-problem-call.pcap \
  triage_call '{"call_id":"[email protected]"}'

{
  "call_id": "[email protected]",
  "final_status_code": 486,
  "media": {
    "hints": [],
    "nat_mismatch": false,
    "no_media": false,
    "one_way_audio": false,
    "problem": false,
    "stream_count": 0
  },
  "schema_version": 1,
  "signaling": {
    "hints": [
      "Call failed: 486 Busy Here."
    ],
    "problem": true
  },
  "source_exhausted": true,
  "source_stopped_early": false,
  "state": "Failed",
  "verdict": "signaling"
}

Every finding cites the RFC section it comes from, says what the message should have carried and what it actually carried, and explains the consequence — so an agent can act on it instead of guessing what a lint code meant.

demos/mcp-stdio.sh tests/pcap-samples/sip-lint-findings.pcap \
  lint_dialog '{"call_id":"[email protected]"}' \
  | jq '.findings[] | select(.severity == "error")'

{
  "basis": "must",
  "expected": "Contact: <sip:user@host>",
  "explanation": "§12.1.1 makes the UAS add a Contact to the response. It is the remote target for the dialog the 2xx creates, so without it the caller has nowhere to send the ACK and nowhere to send the BYE. The call answers and then cannot be hung up cleanly.",
  "frame_ref": "tests/pcap-samples/sip-lint-findings.pcap#1@ad0e14efbbab6ffa",
  "message_index": 1,
  "observed": "⟦untrusted-capture-data⟧2xx to INVITE with no Contact header field⟦/untrusted-capture-data⟧",
  "rfc": 3261,
  "rule_id": "SIP-3261-12.1.1-CONTACT-MISSING-IN-2XX",
  "section": "12.1.1",
  "severity": "error"
}

Any answer can be taken back to the bytes it came from. The pointer carries a digest, and verified means sipnab re-read that frame and it still hashes to what it hashed to when the answer was made. File access stays off until you name a root.

MCP_FILE_ROOT=tests/pcap-samples demos/mcp-stdio.sh \
  tests/pcap-samples/sip-problem-call.pcap show_evidence \
  '{"refs":["…/sip-problem-call.pcap#5@884d4525ec86f71b"],"max_bytes":64}'

{
  "frames": [
    {
      "frame_bytes": 540,
      "hex": "02 00 00 00 00 01 02 00 00 00 00 02 08 00 45 00 02 0e 00 00 40 00 40 11 b4 98 c0 00 02 1e c0 00 02 28 13 c4 13 c4 01 fa 00 00 49 4e 56 49 54 45 20 73 69 70 3a 64 61 76 65 40 31 39 32 2e 30 2e",
      "hex_bytes_shown": 64,
      "ordinal": 5,
      "pointer": "tests/pcap-samples/sip-problem-call.pcap#5@884d4525ec86f71b",
      "source": "sip-problem-call.pcap",
      "status": "verified",
      "truncated": true
    }
  ],
  "requested": 1,
  "resolved": 1,
  "schema_version": 1,
  "summary": "1 of 1 pointer(s) resolved; 1 verified against a recorded digest",
  "verified": 1
}

A B2BUA answers on one Call-ID and dials out on another, so the two halves of one conversation share no identifier. sipnab returns the matched leg with the strategy that matched it and flags heuristic_only when nothing but timing tied them together — an agent that is told how weak the match is can weigh it.

demos/mcp-stdio.sh tests/pcap-samples/b2bua-asterisk.pcapng \
  find_correlated '{"call_id":"[email protected]"}'

{
  "capture_identity": {
    "dialog_generation": 64,
    "instance": "3990fa18d04ed7cf971eae-2",
    "node": "sbc-edge-1",
    "stream_generation": 2
  },
  "heuristic_only": true,
  "legs": [
    {
      "call_id": "[email protected]:5060",
      "identifier_match": false,
      "observed_gap_ms": 3,
      "score": 50,
      "strategy": "timing_heuristic"
    }
  ],
  "schema_version": 1,
  "source_call_id": "[email protected]",
  "source_exhausted": true,
  "source_stopped_early": false,
  "timing_clock": {
    "available": true,
    "est_error_us": 0,
    "max_error_us": 524000,
    "synchronized": true
  },
  "total_matched": 1
}

Browse SIP dialogs, select one, and drill into the call-flow ladder diagram with parsed message detail. Open a different pcap any time with o — no restart.

sipnab TUI demo: browsing dialogs and the call-flow ladder

Read the parsed message beside the ladder — cycle SDP display with d and toggle wrapping with w for long headers.

sipnab detail pane demo: SDP display modes and wrap toggle

Press m to mark any message, then navigate — the Δ badge shows the exact millisecond delta.

sipnab mark and delta demo

Press / to narrow the dialog list as you type, matching method, From, To, and Call-ID in real time.

sipnab live search demo narrowing the dialog list

Follow a B2BUA call end to end — press x for extended flow to stitch the correlated back-to-back-UA legs into one ladder.

sipnab multi-leg B2BUA extended-flow demo

Press Tab for RTP Streams, then drill into per-stream detail — SSRC, codec, jitter, loss, and MOS with quality over time.

sipnab RTP streams and per-stream quality demo

Run sipnab headless: emit dialogs as --json, surface trouble calls with --problems, and print a full call report with --report.

sipnab CLI demo: JSON dialogs, --problems surfacing failed calls, and a call report

Quick Start

Up and running in one command

bash
# Install (macOS: brew install NormB/tap/sipnab)
curl -fsSL https://sipnab.com/install.sh | sh

# Capture live SIP traffic
sudo sipnab -d eth0

# Analyze a pcap file
sipnab -I capture.pcap

# Headless: JSON out, problem calls only
sipnab -N --json -I capture.pcap --problems

Reference

What sipnab Does

Capability Details
Interactive TUI + CLIBoth modes in one binary. TUI for interactive analysis, CLI for batch/scripting.
RTP quality analysisJitter, packet loss, estimated MOS (ITU-T G.107 E-model) per stream. RTCP XR (RFC 3611) parsing.
VoIP diagnosisOne-way audio detection, NAT mismatch, SDP timeline, PDD measurement.
Filter DSL33 fields, 7 operators, boolean combinators. Named aliases for common queries.
SecurityScanner detection, toll fraud, digest leak, registration flood. Fail2ban output.
TLS/SRTP decryptionTLS 1.2 + 1.3 via SSLKEYLOGFILE. SRTP auth verification (SDES key extraction).
WASM pluginsLoad third-party dialog detections as sandboxed WebAssembly (--plugin). Off by default, so a stock binary carries no interpreter.
eBPF TLS capture (root, same host)This does not break TLS. It decrypts nothing, reads no other machine and recovers no key. On a host you already have root on, kernel uprobes read the SIP plaintext where the daemon hands it to OpenSSL or wolfSSL — before encryption — so no key, certificate or restart is needed. The default backend names the process rather than the peer, because it sees no socket. The eBPF backend recovers the real peer addresses, and needs a build carrying the bpf feature — released binaries do not carry it — plus a kernel with BTF. Needs Linux with uprobes. Read the security implications first.
Export formatsPCAP, PCAP-NG, TXT, JSON, NDJSON, CSV, HTML, Markdown, WAV audio, SIPp XML, RTP JSON.
HEP v2/v3HEP v3 send, HEP v2/v3 receive. Feed Homer/SIPCAPTURE, or point Kamailio, OpenSIPS and Asterisk at a sipnab listener and read a whole estate from one box — the proxies already speak HEP, so nothing goes on them.
REST APIQuery dialogs, streams and stats over HTTP — what sipnab saw.
Prometheus metricsCounters and gauges about sipnab itself: what it captured, what it lost, what it could not read.
MCP serverDrive sipnab from an AI agent (Claude Code, Claude Desktop, …) over stdio or HTTP. 32 tools cover dialogs, RTP, diagnostics, and security findings. 27 are read-only; the five that write — file export, capture swap, findings, shutdown — each stay off until you enable them server-side.
Browser analysisAnalyze pcap files in the browser via WASM. Zero upload, zero install.
Built in RustMemory-safe by construction. 7413 automated tests. Under 14 MB static binary.

Standards-based

Quality Metrics

sipnab reports the metrics VoIP engineers already work in — one card per ITU-T or IETF standard, listing the figures it grounds. Every figure comes from the packets, and a figure nothing measured stays absent rather than reading as zero.

ITU-T G.107

The E-model

  • MOS — E-model R = 93.2 − Id − Ie-eff from delay plus jitter, codec and loss, mapped to a score by Annex B and clamped to [1.0, 4.5].
  • Delay input — the one-way delay behind the score: your [media] one_way_delay_ms, else half an RTCP round trip, else 100 ms marked assumed.

ITU-T G.113

Codec impairment

  • MOS grounding — the equipment impairment Ie from Table I.1 for G.711 and G.729, Opus scored as G.711, or your own [media.codec_ie]; any other codec gets a placeholder and mos_grounding: unpublished.

ITU-T G.114

Delay guidance

  • Round-trip verdict — warn and bad thresholds default to the 150 ms and 400 ms one-way guidance, doubled; a stream nobody reported a round trip for carries round_trip_note, never a pass.

RFC 3550

RTP and RTCP

  • Jitter — interarrival jitter per stream by the running estimator the specification defines, grounded only when the clock rate is known.
  • Packet loss — lost versus expected packets from sequence-number gaps, with the lost sequence numbers kept for the loss map.
  • Round-trip time — from a receiver report's LSR and DLSR fields (§6.4.1), anchored on when sipnab saw the report, with round_trip_source saying so.
  • Reception reports — the far end's own fraction lost, cumulative loss and jitter from SR and RR blocks, kept under endpoint_reported and never fed to the MOS.

RFC 3611

RTCP Extended Reports

  • VoIP Metrics — the XR (PT=207) block of Section 4.7: the endpoint's own R-factor, MOS-LQ and MOS-CQ, loss and discard rates, burst and gap durations, round-trip and end-system delay, under endpoint_reported.
  • XR round trip — the reporting endpoint's own round trip, which wins over the SR echo when both exist: round_trip_source: xr_voip_metrics.

On the wire

Standards Implemented

The protocols and formats sipnab parses, decrypts, sends and receives, one card per specification with what sipnab does under it. Every title links to the document itself.

RFC 3261

Session Initiation Protocol

  • Parsing — every request and response, header by header.
  • Dialogs and timing — dialog tracking with post-dial delay, setup, ring and teardown times.
  • Lint — conformance findings against the specification, each pointing at the frame that earned it.

RFC 8866

Session Description Protocol

  • Offer/answer parsing — connection and media lines, codecs, direction attributes and a=crypto.
  • SDP timeline — every offer and answer in order, with hold, resume, codec changes and media anchor moves called out.

RFC 3551

RTP profile for audio and video

  • Codec identification — static payload types and clock rates from Tables 4 and 5, or the a=rtpmap an offer declared.

RFC 4733

Telephone events in RTP

  • Digit extraction — telephone-event payloads decoded, with the repeated end packets deduplicated.
  • Masking — every surface prints x for a digit until --dtmf-cleartext opts in, because the digits after answer are the PIN.

RFC 3711

Secure RTP

  • Decryption — AES-CM key derivation and payload decryption with keys from SDES a=crypto lines or a key file.
  • Authentication — stateful auth-tag verification, HMAC-SHA1 through the crypto backend.

RFC 8446

Transport Layer Security

  • TLS 1.3 — record decryption from the traffic secrets in an SSLKEYLOGFILE.
  • TLS 1.2 — from a CLIENT_RANDOM master secret plus the captured ServerHello (RFC 5246), and no plaintext sipnab cannot authenticate.

RFC 5389

Session Traversal Utilities for NAT

  • Binding diagnosis — Binding Requests and Responses parsed, and the request that never came back reported as the reason a phone offered its private address.

HEP v3

Homer Encapsulation Protocol

  • Send — HEP v3 to a Homer collector with -H.
  • Receive — versions 2 and 3 from every proxy in the estate with -L, behind a source allowlist and a rate limit.

draft-ietf-vcon-vcon-core

Virtualized Conversation

  • Export — one observed dialog as a vCon container: the parties, the SIP ladder, a diagnosis of the call and what the capture missed.
  • Audio inline — RTP the run retained travels inside the container as a recording Dialog Object with a sha512 content hash.
Contributor License Agreement OpenSSF Best Practices — Passing Signed build provenance & SBOM