-
Notifications
You must be signed in to change notification settings - Fork 0
Cookbook
Recipe-style walkthroughs for the things people actually want to do. Each recipe states the problem, gives exact commands, tells you what to look for in the output, and flags common pitfalls.
If you're new, start with Recipe 1. If you have a specific symptom, jump to the matching one.
Problem: Someone handed you a capture.pcap and asked "is anything wrong?"
Commands:
Open the call list and scan it visually — the interactive TUI:
sipnab -I capture.pcapThe same capture as a headless overview: dialog count, methods, average PDD.
sipnab -N -I capture.pcapA one-flag diagnostic sweep — retransmits and failed dialogs:
sipnab -N -I capture.pcap --problemsThe same sweep in JSON, for piping into another tool:
sipnab -N -I capture.pcap --problems --jsonThe wider net: the problems DSL alias also covers one-way audio, loss/jitter, NAT mismatch, asymmetry and late media.
sipnab -N -I capture.pcap --filter problemsThe --problems sweep prints one line per SIP message of each flagged call, then the end-of-capture summary. You should see something like (abridged):
INVITE +15551234 -> +15559876 192.0.2.6:5060 -> 192.0.2.7:5060 Failed 408 Request Timeout
...
852 packets captured, 10 SIP messages, 839 RTP packets across 2 streams
What to look for:
- The
--problemsflag matchesretransmits > 0 OR state == 'Failed'— a deliberately narrow sweep. The same-named DSL alias, reached with--filter problems, is much broader:state == 'Failed' OR one_way == true OR rtp.loss > 2.0 OR rtp.jitter > 50.0 OR nat_mismatch == true OR retransmits > 3 OR pdd > 32.0 OR rtp.orphaned == true OR codec_asymmetry == true OR ptime_asymmetry == true OR payload_asymmetry == true OR duration_asymmetry == true OR late_media == true. Don't conflate the two. If both come back empty, the capture is probably clean. - The end-of-capture summary distinguishes RTP packets from RTP streams:
852 packets captured, 10 SIP messages, 839 RTP packets across 2 streams. A capture with media but no SIP usually means the SIP signaling happened off-pcap (different VLAN, different host, different port).
Pitfalls:
- The TUI requires a tty. If you're SSH'd in without
-t, force-Nmode. - For large pcaps (>1 GB), prefer
-Nfirst; the TUI loads everything into memory.
Problem: A user reports their calls are flaky. Capture only their traffic in real time.
Commands:
Capture on eth0, keeping only this user's calls — the filter matches From or To:
sudo sipnab -d eth0 --filter "from.user == '1001' OR to.user == '1001'"The same capture with a CLI summary line per dialog instead of the TUI:
sudo sipnab -N -d eth0 --filter "from.user == '1001' OR to.user == '1001'" --jsonWhat to look for:
- The TUI's call list updates as new dialogs appear. Press
Tabto switch to the RTP stream view; pressEnteron a stream to see jitter/loss/MOS history. - In CLI mode, each completed dialog is one line of JSON. Pipe to
jqorteeto a log.
Pitfalls:
- Live capture needs
CAP_NET_RAW(Linux) or root.setcap cap_net_raw,cap_net_admin=eip $(which sipnab)lets you skipsudoafter the first run. - The filter DSL evaluates against complete dialog records — once the dialog state machine has enough information (typically after the first response, or earlier for fields that only depend on the request). For per-header regex filtering on individual messages, use the older
--from,--to,--contact,--uaflags listed insipnab --help.
Problem: "We had a spike in failures around 14:00. What was it?"
Commands:
sipnab -N --json emits per-message records (one JSON line per SIP message), not per-dialog summaries. The status_code field is on response messages; combined with --filter (which evaluates against the dialog so all messages from matched dialogs flow through), you get a histogram of every response code seen during failed dialogs:
Every failed call's response messages — Call-ID, status_code and reason:
sipnab -N -I capture.pcap --filter "state == 'Failed'" --json \
| jq 'select(.is_request == false) | {call_id, status_code, reason}'A histogram of the response codes seen in failed dialogs:
sipnab -N -I capture.pcap --filter "state == 'Failed'" --json \
| jq -r 'select(.is_request == false) | .status_code' \
| sort | uniq -c | sort -rnA detailed report for one failure, in Markdown, ready to paste into a ticket:
sipnab -N -I capture.pcap --call-report 'abc123@host' --markdown > failure-report.mdThe histogram output looks like (uniq -c count, then status code):
23 100
14 486
6 503
3 488
What to look for:
- A 401/407 spike usually means a credential-rotation push hit the wrong realm.
- A 408 spike on outbound is upstream timeout — check rtpengine / SBC.
- A 488 spike (Not Acceptable Here) usually means a codec mismatch — combine with Recipe 11.
Pitfalls:
- The histogram counts all response codes seen in messages of failed dialogs (so a single failed call with
100 Trying → 488contributes both 100 and 488). For just the final response per call, use--call-report <id>per dialog. - The dialog summary returned by the REST API (
/v1/dialogs) has nostatus_codefield; that's a per-message field only available in CLI--jsonoutput or via/v1/dialogs/{id}(which includes the full message list).
Problem: A user said "I can hear them but they can't hear me." There's a Call-ID in the ticket.
Commands:
First, confirm the diagnosis engine flagged it. The diagnosis block lives on the dialog-level JSON that --call-report emits, not on per-message records.
sipnab -N -I capture.pcap --call-report 'abc123@host' --json --no-cli-print \
| jq '{call_id, state, diagnosis}'Then get the human-readable call report — NAT mismatch, SDP offer/answer, media path:
sipnab -N -I capture.pcap --call-report 'abc123@host' --markdown --no-cli-printFinally, inspect the actual RTP streams for that call in the TUI:
sipnab -I capture.pcap
# → press '/' to filter, type 'abc123', Enter
# → Tab to switch to RTP streams view
# → Enter on each stream to see packet count, jitter, lossThe first command should print a diagnosis object like:
{
"call_id": "abc123@host",
"state": "Completed",
"diagnosis": {
"one_way_audio": true,
"nat_mismatch": true,
"no_media": false,
"hints": [
"RTP from 203.0.113.7 -> 192.0.2.5 only. No reverse media flow detected.",
"SDP c= address (198.51.100.20) differs from actual RTP source (203.0.113.7) — likely NAT issue.",
"One-way audio combined with NAT mismatch — media likely being sent to the wrong address."
]
}
}What to look for:
-
diagnosis.one_way_audio: trueconfirms the engine saw RTP in only one direction for ≥6s after call establishment. -
diagnosis.nat_mismatch: trueis the usual root cause — the Contact header / Via address differs from the SDPc=line. Common when the upstream SBC isn't rewriting Contact. - In the TUI's RTP stream view, look for one stream with packets and one with
0 packets received— that's the silenced direction.
Pitfalls:
- If both streams show packets but the user still reports silence, the issue is downstream of sipnab (codec mismatch, jitter buffer underflow, bad headset). Use Recipe 11 for codec asymmetry checks.
The filter DSL has 31 fields and 7 operators. These five cover most operational triage:
Slow setup — every dialog that took more than 3 seconds from INVITE to 200 OK:
sipnab -N -I capture.pcap --filter "pdd > 3.0" --jsonREGISTER dialogs that failed:
sipnab -N -I capture.pcap --filter "method == 'REGISTER' AND state == 'Failed'" --jsonShort calls — completed, but under 10 seconds, which usually points at a UX or cancellation problem:
sipnab -N -I capture.pcap --filter "duration < 10.0 AND state == 'Completed'" --jsonHeavy retransmits, the signature of packet loss on the SIP path:
sipnab -N -I capture.pcap --filter "retransmits > 5" --jsonA specific User-Agent, matched as a regex:
sipnab -N -I capture.pcap --filter "ua =~ '(?i)friendly.*scanner|sipvicious'" --jsonFor per-call asymmetry checks (different codec on each leg, late media, etc.), see Recipe 11.
Pitfalls:
- String comparisons are case-sensitive. State names must match exactly (
'Completed', not'completed'). Use=~ '(?i)...'if you want case-insensitive. - Boolean fields only support
==and!=—one_way > trueis a parse error.
Problem: You want one sipnab box collecting traffic mirrors from multiple SIP servers.
Build sipnab with HEP support — skip this if you installed a package that already has it:
cargo build --release --no-default-features \
--features native,hep,api,mcp,mcp-httpRun it as a daemon. UDP :9060 receives HEP, TCP :9100 serves REST + Prometheus.
sipnab -N --hep-listen 0.0.0.0:9060 --api 0.0.0.0:9100 --no-priv-drop --syslogA ready-to-deploy systemd unit lives at contrib/observability/sipnab-hep.service — see Remote-sipnab deployment in the install guide.
OpenSIPS:
loadmodule "proto_hep.so"
modparam("proto_hep", "hep_id", "[hep_central]udp:capture.example.com:9060;version=3")
loadmodule "siptrace.so"
modparam("siptrace", "trace_id", "[hep_central]uri=hep:hep_central")
route {
sip_trace("hep_central", "d", "sip");
...
}Reload with opensipsctl restart (or systemctl reload opensips for graceful reload).
rtpengine:
# /etc/rtpengine/rtpengine.conf
homer = capture.example.com:9060
homer-protocol = udp
homer-id = 1Restart with systemctl restart rtpengine.
Kamailio:
loadmodule "siptrace.so"
modparam("siptrace", "duplicate_uri", "sip:capture.example.com:9060")
modparam("siptrace", "hep_mode_on", 1)
modparam("siptrace", "hep_version", 3)
route {
sip_trace();
...
}FreeSWITCH (mod_sofia):
<!-- conf/sip_profiles/external.xml -->
<param name="capture-server" value="udp:capture.example.com:9060;hep=3"/>
<param name="sip-capture" value="yes"/>On the sipnab host, tcpdump the HEP socket to see whether anything is arriving at all:
sudo tcpdump -i eth0 -n udp port 9060Confirm dialogs are being created from the HEP feed:
curl -s http://localhost:9100/v1/stats | jqWatch dialogs accumulate live:
watch -n 1 'curl -s http://localhost:9100/v1/dialogs?limit=5 | jq ".dialogs[] | {call_id, state}"'Pitfalls:
- HEP is UDP — silently drops if the listener can't keep up. The
--hep-rate-limit 50000default lets you tune. - The default HEP listener accepts from any source. Add
--hep-allow 192.0.2.0/24(repeatable) to lock it down to your SIP-server subnet. - If your central host is reachable by hostname only, set
--mcp-allowed-hostfor the MCP transport too (see Recipe 8).
Problem: TLS-encrypted SIP captures are unreadable without keys.
Build sipnab with the tls feature, or use --features full:
cargo build --release --features tls,hep,apiOn the SIP user agent — a different machine from the capture host — set SSLKEYLOGFILE in its environment:
SSLKEYLOGFILE=/tmp/sipua.keylog /opt/myua/bin/startThen, on the capture host, start sipnab watching that keylog file for live updates:
sudo sipnab -N -d eth0 \
--keylog /tmp/sipua.keylog --keylog-watchCapture the encrypted pcap normally:
sudo sipnab -N -d eth0 -O encrypted.pcapLater — minutes or months — decrypt it using the keylog the UA wrote during the call:
sipnab -I encrypted.pcap --keylog /tmp/sipua.keylogThe default mode writes decrypted plaintext payloads to the output pcap:
sipnab -I encrypted.pcap --keylog /tmp/sipua.keylog \
-O decrypted.pcap --pcap-export-mode decryptedThe encrypted+dsb mode instead keeps the encrypted bytes and adds a Decryption Secrets Block, so Wireshark itself can decrypt:
sipnab -I encrypted.pcap --keylog /tmp/sipua.keylog \
-O wireshark-friendly.pcap --pcap-export-mode encrypted+dsbAccepted values for --pcap-export-mode: decrypted (default), encrypted+dsb, raw.
sipnab -I capture.pcap --dtls-keylog /tmp/dtls.keylogPitfalls:
-
tlsis a build-time feature, not a runtime flag. There is nosipnab --features tlsinvocation; pass--featurestocargo buildand use the resulting binary.sipnab --versiononly prints the version string and a commit hash — it does not enumerate compiled-in features. To verify support,sipnab --help | grep -E '\-\-keylog|\-\-tls-key'— if the flags appear,tlswas compiled in. - The keylog format is the standard NSS
SSLKEYLOGFILE(one line per session). Same format Firefox/Chrome/curl produce. - TLS 1.3 + ECDH ephemeral handshakes are fully supported via the
ringbackend.
Problem: You want an AI agent (Claude Code, Claude Desktop, anything MCP-capable) to query a capture without you typing CLI flags.
One-shot: the agent reads a pcap you already have.
sipnab -N --mcp -I capture.pcap --quietThe same, against a live capture instead of a file:
sudo sipnab -N --mcp -d eth0 --quietClaude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"sipnab": {
"command": "sipnab",
"args": ["--mcp", "-N", "-I", "/path/to/capture.pcap", "--quiet"]
}
}
}Claude Code (in your project directory):
claude mcp add sipnab -- sipnab -N --mcp -I "$PWD/capture.pcap" --quietGenerate the bearer token once, when you first set the host up. openssl rand overwrites /etc/sipnab/mcp-token — run it against a live server and it starts serving a secret none of the configured agents hold, with no way to recover the old one.
# Run all of these, in order.
mkdir -p /etc/sipnab && chmod 0755 /etc/sipnab
openssl rand -hex 32 > /etc/sipnab/mcp-token
chmod 0600 /etc/sipnab/mcp-tokenThen run sipnab listening on a private network interface. This is the every-boot command: it reads the token file, it does not create one.
sipnab -N --mcp --mcp-transport http \
--mcp-bind 0.0.0.0:8731 \
--mcp-token-file /etc/sipnab/mcp-token \
--mcp-allowed-host capture.example.com \
--hep-listen 0.0.0.0:9060 --quietThe agent connects to http://capture.example.com:8731/mcp with Authorization: Bearer <token>.
Each probe below sends the bearer token, so read it into the shell first. The three requests are independent — none of them carries a session on to the next, so run whichever one you need.
TOKEN=$(cat /etc/sipnab/mcp-token)Initialize, pretending to be an MCP client:
curl -sS http://capture.example.com:8731/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18",
"capabilities":{},
"clientInfo":{"name":"curl","version":"0"}}}'List the 11 read-only tools:
curl -sS http://capture.example.com:8731/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'Call find_problems and get JSON of the problematic dialogs:
curl -sS http://capture.example.com:8731/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"find_problems",
"arguments":{"kinds":["one-way","nat-issues"]}}}'The tools/list response is a standard JSON-RPC envelope with a result.tools array (descriptions and input schemas truncated here):
{"jsonrpc":"2.0","id":2,"result":{"tools":[
{"name":"list_dialogs","description":"...","inputSchema":{"type":"object","properties":{"...":{}}}},
{"name":"get_dialog_report","description":"...","inputSchema":{"...":"..."}},
{"name":"find_problems","description":"...","inputSchema":{"...":"..."}}
]}}The 11 read-only tools you should see listed: list_dialogs, get_dialog_report, find_problems, get_dialog, get_message, render_ladder, rtp_stats, search_messages, tail_dialogs, security_findings, stats.
Pitfalls:
- Stdout is the JSON-RPC wire in stdio mode. Use
--quietand don't combine with--json/--report/etc. — sipnab refuses to start. - Non-loopback bind without a token: refused at startup. Loopback bind needs no token.
-
--mcp-allowed-hostis required when the client connects via the actual hostname (rmcp's default Host allowlist is justlocalhost/127.0.0.1/::1).
Problem: You want a dashboard tracking call rate, response codes, and PDD over time.
Clone the repository and take a copy of the sample environment. Do this once per host: cp .env.example .env overwrites .env, so on a host where you have already edited it, skip straight to starting the stack.
# Run all of these, in order.
git clone https://github.com/NormB/sipnab.git
cd sipnab/contrib/observability
cp .env.example .envIf sipnab runs on a different host, point the stack at it before starting: echo 'SIPNAB_HOST=192.0.2.10' >> .env, or the hostname (capture.example.com) if that is how it resolves.
Start the stack from contrib/observability — docker compose reads its compose file out of the working directory, so a fresh shell needs the cd above first:
docker compose up -dThis boots Prometheus (:9090), Grafana (:3000, admin/admin), an OTel Collector (:4317/:4318), and Tempo. The included Grafana dashboard provisions automatically — log in and look for the sipnab folder.
A standalone metrics endpoint:
sipnab -N -d eth0 --metrics 0.0.0.0:9100 --jsonOr serve the metrics from the REST API, so one port carries both:
sipnab -N -d eth0 --api 0.0.0.0:9100From the Prometheus host, confirm the target is up:
curl -s http://localhost:9090/api/v1/query?query=up{job=\"sipnab\"} | jqThen spot-check a metric value:
curl -s 'http://localhost:9090/api/v1/query?query=rate(sipnab_messages_total[1m])' | jq# Call rate (per method)
rate(sipnab_messages_total[5m])
# Active dialogs (in-progress)
sum(sipnab_dialogs_total{state=~"trying|ringing|incall"})
# Setup time p95
histogram_quantile(0.95, rate(sipnab_pdd_seconds_bucket[5m]))
# RTP MOS p10 (worst 10%)
histogram_quantile(0.1, rate(sipnab_mos_bucket[5m]))
Pitfalls:
- The dashboard ships with the metric names sipnab actually emits. If you wrote a custom panel using older docs, double-check against the metrics list in the API page.
- Some metrics (
sipnab_responses_total,sipnab_security_alerts_total) are declared but not yet wired — they'll stay empty until upstream populates them. Don't put alerts on them today.
Problem: Your honeypot or edge box is getting probed by friendly-scanner, sipvicious, etc.
sudo sipnab -N -d eth0 \
--kill-scanner \
--alert syslog \
--json--kill-scanner actively responds to known scanner User-Agents (uses the isolated kill-child process). The response code defaults to 200; pass --kill-response 403 (or any 100–699 code) to change it. --alert syslog writes alerts to LOCAL0 so you can pick them up from /var/log/syslog.
--fail2ban is a boolean flag — it switches sipnab's stdout to fail2ban-friendly log lines. Pipe to a file (or run under systemd and capture the unit's stdout).
# Run sipnab with fail2ban-format output, write to a logfile
sudo sipnab -N -d eth0 --kill-scanner --fail2ban \
>> /var/log/sipnab/fail2ban.log 2>&1Sample log line shape (from src/output/fail2ban.rs):
2026-05-05 12:34:56 sipnab[12345]: scanner_detected src=203.0.113.42 ua=friendly-scanner method=OPTIONS
2026-05-05 12:34:57 sipnab[12345]: reg_flood src=203.0.113.42 count=37
/etc/fail2ban/filter.d/sipnab.conf:
[Definition]
failregex = ^.*sipnab\[\d+\]: scanner_detected src=<HOST>.*$
^.*sipnab\[\d+\]: reg_flood src=<HOST>.*$
ignoreregex =/etc/fail2ban/jail.d/sipnab.local:
[sipnab]
enabled = true
filter = sipnab
logpath = /var/log/sipnab/fail2ban.log
findtime = 600
maxretry = 1
bantime = 86400
action = iptables-allportsSymptom: an unexpected spike of international or premium-rate calls, bursts of short calls to one number prefix (wangiri call-back bait), or sequential dialing through a number range.
# Live fraud heuristics on the edge box (batch mode required)
sudo sipnab -N -d eth0 --fraud-detect --alert syslog--fraud-detect runs three heuristics over INVITE traffic per source IP: VolumeSpike (call rate far above the rolling baseline), Wangiri (repeated short calls to the same number prefix), and SequentialScanning (consecutive destination numbers). Alerts fire through the same alert engine as the scanner detectors, so --alert syslog and --alert-exec both work.
You should see alert lines like:
[ALERT] fraud src=203.0.113.42 Wangiri: 4 short calls to prefix '+44900' in 60s
[ALERT] fraud src=203.0.113.42 SequentialScanning: sequential dialing detected: 3 consecutive numbers ending at 15550104
[ALERT] fraud src=203.0.113.42 VolumeSpike: 40 calls in 60s (baseline: 1.5/min)
What to look for: a Wangiri alert on a premium-rate prefix (+44 9xx, +2xx IRSF ranges) is the classic revenue-fraud signature — block the destination prefix at the trunk, not just the source IP. SequentialScanning from an external source usually precedes a toll-fraud attempt: feed the source IP to fail2ban (10b) and review outbound dial permissions.
For exec hooks instead of syslog/fail2ban:
sudo sipnab -N -d eth0 --kill-scanner \
--alert-exec '/usr/local/bin/notify-slack.sh "$SIPNAB_RULE" "$SIPNAB_SRC" "$SIPNAB_DETAIL"'Alert data reaches the hook as the SIPNAB_RULE, SIPNAB_SRC, and SIPNAB_DETAIL environment variables — never interpolated into the command string. Only the three legacy placeholders %rule, %src, and %detail are rewritten to those $SIPNAB_* references for you; anything else (%type%, %source_ip%, …) is passed through to the shell verbatim.
The hook is rate-limited (--exec-rate-limit 10 default) and runs in a sandboxed process.
Pitfalls:
- The kill-child process needs
CAP_NET_RAWto forge SIP responses. Run sipnab as root or with capabilities — privilege drop happens after the kill-child is spawned. -
--kill-ua "<regex>"adds a custom User-Agent pattern beyond the built-in scanner list.
Problem: A call sounds bad in one direction. The codec/ptime might differ between legs.
The asymmetry signals (Phase 8.7) live on sipnab's internal MediaDiagnosis struct and are exposed through the filter DSL — not the dialog JSON output's diagnosis block. --filter accepts the alias name directly (codec-asym) and falls back to the raw DSL expression if it isn't an alias. Both forms are equivalent.
All five asymmetry checks at once, via the problems DSL alias — not the --problems flag, which only covers retransmits and Failed:
sipnab -N -I capture.pcap --filter problems --jsonTargeted, one signal at a time:
-
sipnab -N -I capture.pcap --filter codec-asym --json— different codec on each leg -
sipnab -N -I capture.pcap --filter ptime-asym --json— different packetization interval on each leg -
sipnab -N -I capture.pcap --filter payload-asym --json— same codec, different dynamic payload type -
sipnab -N -I capture.pcap --filter duration-asym --json— the two streams ran for noticeably different lengths -
sipnab -N -I capture.pcap --filter late-media --json— media started well after the answering 200 OK
The equivalent raw-DSL forms, for the two most common of those:
sipnab -N -I capture.pcap --filter "codec_asymmetry == true" --jsonsipnab -N -I capture.pcap --filter "ptime_asymmetry == true" --json
Multiple signals OR'd together require raw DSL — an alias name covers only one signal each:
sipnab -N -I capture.pcap \
--filter "codec_asymmetry == true OR ptime_asymmetry == true OR late_media == true" \
--jsonFrom an MCP client, multiple alias names go through find_problems instead: tools/call find_problems {"kinds": ["codec-asym", "ptime-asym", "late-media"]}. See the MCP docs for the full client-side syntax.
What to look for:
-
codec_asymmetry: trueon a call from PSTN to internal: usually a transcoding policy that fired in one direction only. -
ptime_asymmetry: truebetween two SIP UAs: one is usingptime=20, the otherptime=30. Some downstream jitter buffers can't handle the mismatch. -
payload_asymmetry: true: same codec, but each side picked a different dynamic payload type number. Causes audio cut-out on RFC-strict implementations. -
late_media: true: media starts noticeably after the answering 200 OK. Usually means an SBC is doing late-attach NAT — first real RTP arrives only after media-binding.
Pitfalls:
-
sipnab -N --filter '<expr>' --jsonemits per-message records for every message of every matching dialog. Pipe throughjq -s 'unique_by(.call_id)'if you want one record per affected call. - The
diagnosisblock in CLI--jsonoutput and in the REST API today only exposesone_way_audio,nat_mismatch,no_media, and free-formhints. The five asymmetry booleans are filterable via the DSL but aren't in the JSON shape — if you need them in your output, generate a--call-reportper dialog (which does include them) or use the MCPfind_problemstool.
Problem: A support ticket needs full call details attached.
In -N (non-interactive) mode, sipnab normally prints each captured SIP message to stdout and then emits the report. Pass --no-cli-print to suppress the per-message dump so only the report reaches stdout. (-N is required: without it sipnab tries to start the TUI and the report output never reaches stdout.)
Markdown, to paste into a ticket or a markdown editor:
sipnab -N -I capture.pcap --call-report 'abc123@host' --markdown --no-cli-print > ticket.mdPlain text, the default report format:
sipnab -N -I capture.pcap --call-report 'abc123@host' --no-cli-print > ticket.txtJSON, for a tool on the other end:
sipnab -N -I capture.pcap --call-report 'abc123@host' --json --no-cli-print > ticket.jsonThe report covers: SIP message timeline, SDP offers/answers, RTP stream stats per direction, computed timing (PDD, setup time, retransmits), and the diagnosis engine's findings.
Tip: combine with Recipe 3's filter to bulk-generate reports for every failed call. The CLI --filter outputs per-message records, so deduplicate to call_ids first:
# Run all of these, in order.
mkdir -p /tmp/reports
# First pass: enumerate matching calls (no --no-cli-print here — we want the
# per-message JSON so jq can extract call_id).
sipnab -N -I capture.pcap --filter "state == 'Failed'" --json 2>/dev/null \
| jq -r '.call_id' | sort -u \
| while read cid; do
# Second pass per call: --no-cli-print so only the report is written.
sipnab -N -I capture.pcap --call-report "$cid" --markdown --no-cli-print \
> "/tmp/reports/$(echo "$cid" | tr '/' '_').md"
doneCompatibility note:
--no-cli-printwas added in v0.3.2. On older binaries strip the leading per-message text by piping throughsed -n '/^# Call Report:/,$p'(markdown) orawk '/^{$/{found=1} found'(JSON).
Problem: A call sounds bad. You want the actual audio to listen to or share.
sipnab -I capture.pcap
# → select the call in the call list (Up/Down)
# → press 'r' or Tab to switch to the RTP stream view
# → highlight a stream
# → F2 to open the Save dialog
# → cycle the format (Left/Right) until you reach "WAV — Decoded G.711 audio per RTP stream"
# → Enter to saveA timestamped .wav lands at the path you choose. The Save dialog also exposes PCAP, PCAP-NG, TXT, JSON, NDJSON, CSV, HTML, Markdown, RTP JSON, and SIPp XML formats — WAV is the format you want for audio.
If you've built with the audio feature (in default), P in the RTP stream view plays the highlighted stream through your local audio device.
Pitfalls:
- Supported codecs for WAV decode and playback: G.711 µ-law (PT 0), G.711 A-law (PT 8), Opus (dynamic PT). Other codecs (G.729, AMR, etc.) aren't decoded today.
- A failed audio device (headless servers, Tegra without ALSA) no longer crashes the TUI — it disables playback gracefully and surfaces a message suggesting F2 → WAV as an offline alternative.
- A CLI batch audio-export flag does not exist today. The library functions (
rtp::audio_export::export_stream_to_wav,export_dialog_to_wav) are available if you want to build it; until then, scripted batch export means driving the TUI underexpect/tmuxor writing a small Rust binary that links the library.
Problem: You don't want to install anything. The pcap is on your laptop. You want to look at it.
Open https://www.sipnab.com/analyze/ in any modern browser. Drag-and-drop a pcap or .pcapng file. Everything runs locally via WebAssembly — the pcap never leaves your machine.
The analyze page supports .pcap, .pcapng, .cap (pcap format), and their gzip-compressed variants (.pcap.gz, .pcapng.gz — decompressed transparently, with a notice), and gives you the same call list, ladder diagram, RTP stream view, search, and filter DSL as the native TUI. Keyboard shortcuts match the TUI (? opens the help popup).
Pitfalls:
- WASM has no network access — live capture is native-only.
- Very large pcaps (>200 MB) may strain browser memory. Use the native
sipnab -Nfor those.
The recipes above walk through a problem end to end. This section is the other shape: dense one-line commands to copy when you already know what you want and just need the invocation. Every flag used here is covered in cli-reference.md.
-
sudo sipnab -d eth0— watch SIP interactively on an interface (TUI, sngrep-style) -
sipnab -N -I capture.pcap --problems— show only problem calls from a pcap. The--problemsflag is a narrow sweep: retransmits, or a Failed dialog. For the broad diagnostic set (one-way audio, loss/jitter, NAT, asymmetry, late media) use the same-named DSL alias instead:sipnab -N -I capture.pcap --filter problems -
sipnab -N -I capture.pcap --call-report 'abc123@192.0.2.1'— deep-dive one call: ladder, timing, SDP, RTP quality, diagnosis -
sipnab -N -I capture.pcap --call-report 'abc123@192.0.2.1' --markdown > call.md— the same as a Markdown report for a ticket -
sipnab -N -I capture.pcap --report --no-cli-print— post-capture aggregate summary only, no per-message noise
-
sudo sipnab -N -d eth0 --from '^1001@' --to '^18005551212'— calls from/to specific users, matched as regexes -
sipnab -N -I capture.pcap --filter "method == 'INVITE' and rtp.mos < 3.5"— filter DSL: INVITE dialogs that ended with bad audio quality -
sipnab -N -I capture.pcap --filter codec-asym— diagnostic aliases go through the same flag (see docs/filter-dsl.md);sipnab -N -I capture.pcap --filter late-mediais the same flag with the late-media alias -
sipnab -N -I capture.pcap --slow-setup— slow call setup, meaning long post-dial delay
NDJSON to jq, counting failures by status code:
sipnab -N -I capture.pcap --json \
| jq -s 'map(select(.status_code >= 400)) | group_by(.status_code)
| map({code: .[0].status_code, n: length})'Every Call-ID seen on the wire, ready to feed back into --call-report:
sipnab -N -I capture.pcap --json | jq -r '.call_id // empty' | sort -uMore in output-formats.md.
Capture SIP+RTP to rotating pcapng files, 50 MiB chunks:
sudo sipnab -N -d eth0 -O /var/capture/sip.pcapng --pcapng --split filesize:50Decrypt SIPS signaling with a TLS key log and export decryptable pcapng. --keylog is the SIP/TLS NSS keylog — signaling only; it does not decrypt media.
sudo sipnab -N -d eth0 --keylog /tmp/sslkeys.log --keylog-watch \
-O decrypted.pcapng --pcapngSRTP needs media keys instead:
-
sipnab -N -I capture.pcap --dtls-keylog /tmp/dtls.keylog— keys recovered from DTLS-SRTP handshakes -
sipnab -N -I capture.pcap --srtp-keys /tmp/srtp-keys.txt— AES-CM master keys; SDESa=cryptokeys are also learned from SDP
-
sudo sipnab -N -d eth0 --kill-scanner --alert syslog— detect SIP scanners and answer them, rate-limited -
sudo sipnab -N -d eth0 --fail2ban— emit fail2ban-compatible lines for scanner/flood sources
-
sudo sipnab -N -d eth0 --on-dialog-exec '/usr/local/bin/call-logger'— run a command on every dialog state change; details arrive asSIPNAB_*env vars plus aSIPNAB_JSONpayload, never shell-interpolated -
sudo sipnab -N -d eth0 --on-quality-exec '/usr/local/bin/page-noc'— alert when RTP quality drops
-
sipnab -N -L 0.0.0.0:9060— receive HEP from Kamailio/OpenSIPS/Asterisk and analyze live.-L/--hep-listendecodes HEP on its own;--hep-parseis only for unwrapping HEP that arrives inside ordinary UDP capture -
sudo sipnab -N -d eth0 -H homer.example.net:9060— mirror captured traffic to Homer
- keybindings.md — the interactive TUI these captures feed
-
filter-dsl.md — the full filter language behind
--filter - mcp.md — drive the same analysis from an AI agent
Website · Repository · Issues · Generated from docs/ — edit there, not here.
Getting started
Using the TUI
CLI & automation
Configuration
Integrations (API & MCP)
Development & internals