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.
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.
Read the parsed message beside the ladder — cycle SDP display with d and toggle wrapping with w for long headers.
Press m to mark any message, then navigate — the Δ badge shows the exact millisecond delta.
Press / to narrow the dialog list as you type, matching method, From, To, and Call-ID in real time.
Follow a B2BUA call end to end — press x for extended flow to stitch the correlated back-to-back-UA legs into one ladder.
Press Tab for RTP Streams, then drill into per-stream detail — SSRC, codec, jitter, loss, and MOS with quality over time.
Run sipnab headless: emit dialogs as --json, surface trouble calls with --problems, and print a full call report with --report.
Capabilities
Everything, in one binary
Inspect
Ladder diagrams, auth collapse, retransmit folding, mark+delta timing — the whole call at a glance.
Diagnose
Per-stream jitter, loss, and MOS. One-way audio, NAT mismatch, post-dial delay — found, not guessed.
Export
PCAP-NG, JSON/NDJSON, Mermaid, Wireshark filters, fail2ban, WAV — pipe it anywhere. The full list is below.
Quick Start
Up and running in one command
# 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 + CLI | Both modes in one binary. TUI for interactive analysis, CLI for batch/scripting. |
| RTP quality analysis | Jitter, packet loss, estimated MOS (ITU-T G.107 E-model) per stream. RTCP XR (RFC 3611) parsing. |
| VoIP diagnosis | One-way audio detection, NAT mismatch, SDP timeline, PDD measurement. |
| Filter DSL | 33 fields, 7 operators, boolean combinators. Named aliases for common queries. |
| Security | Scanner detection, toll fraud, digest leak, registration flood. Fail2ban output. |
| TLS/SRTP decryption | TLS 1.2 + 1.3 via SSLKEYLOGFILE. SRTP auth verification (SDES key extraction). |
| WASM plugins | Load 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 formats | PCAP, PCAP-NG, TXT, JSON, NDJSON, CSV, HTML, Markdown, WAV audio, SIPp XML, RTP JSON. |
| HEP v2/v3 | HEP 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 API | Query dialogs, streams and stats over HTTP — what sipnab saw. |
| Prometheus metrics | Counters and gauges about sipnab itself: what it captured, what it lost, what it could not read. |
| MCP server | Drive 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 analysis | Analyze pcap files in the browser via WASM. Zero upload, zero install. |
| Built in Rust | Memory-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 markedassumed.
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 andmos_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_sourcesaying so. - Reception reports — the far end's own fraction lost, cumulative loss and jitter from SR and RR blocks, kept under
endpoint_reportedand 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=rtpmapan offer declared.
RFC 4733
Telephone events in RTP
- Digit extraction — telephone-event payloads decoded, with the repeated end packets deduplicated.
- Masking — every surface prints
xfor a digit until--dtmf-cleartextopts 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=cryptolines 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_RANDOMmaster 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
recordingDialog Object with asha512content hash.
Production-ready
Engineered for Production
Engineering notes
How sipnab is actually built and debugged — regressions found and fixed, protocol traps, and reasoning that is hard to see from the outside. Real problems, real numbers.
- A finding is a floor, not a verdict The same problem query answered 0 and then 127 on one capture, thirty seconds apart. Four questions to ask of any finding before you act on it, and the fields that answer each one.
- A total that cannot say what it missed is a floor Kernel drops, interface drops, unusable timestamps, frames no decoder read, and SIP the port gate set aside — five separate channels, kept separate because their remedies disagree. Plus the run record that ties an artifact back to the command that made it.
- An instruction with no gate is a suggestion A written rule said to delete every temp file in the same turn. The checkout held 21 stray logs, three abandoned worktrees and a 1.6 TB target directory. What the enforcement had to prove before it could delete anything.