Docs Look up a command-line flag

Look up a command-line flag

Every flag sipnab takes, grouped by job: capture, matching, output, analysis, security, the listeners and MCP.

On this page

Quick start: sipnab -I capture.pcap to analyze a file, or sudo sipnab for live capture on the default interface. Add -N for non-interactive output.

Complete flag reference for sipnab. This page groups flags by function.

Start here

A first run needs only a handful of these flags:

  • -I, --input reads packets from a capture file, a directory of them, an archive of them, or a glob, instead of live capture. It reads stored vCon containers too.
  • -d, --device names the network interface to capture on.
  • -N, --no-tui turns on non-interactive mode, with no TUI. The batch and output flags require it.
  • --report generates a summary report after capture completes. Requires -N.
  • --json prints output as NDJSON, one JSON object per line. Requires -N.
  • --filter takes a Filter DSL expression or a diagnostic alias name, such as codec-asym or late-media, and a named filter as @name.

The Since column names the first sipnab release whose command line accepted the flag, under its current name or one of its aliases. Unreleased means no published release has the flag yet. Check what your own binary accepts with sipnab --help.

CLI flags always override config file values (see config-reference.md).

  • Boolean flags default to off (false) unless otherwise noted.
  • For task-oriented recipes rather than a flag catalog, start with examples.md.

Common recipes

A few flag combinations to get productive fast.

  • For the full task-oriented collection, see the Cookbook. It covers triage, filtering, recording, security and HEP.
  • For symptom-driven diagnostics, see Troubleshooting.
  • This page is otherwise a flag reference (grouped below).

Debug a failed call

Start by listing every failed call in the pcap. The Call-IDs worth chasing come from this list.

sipnab -N -I capture.pcap --filter "state == 'Failed'"

With one of those Call-IDs in hand, print just that dialog’s call flow.

  • --no-cli-print is what makes it just that.
  • On its own, --call-report appends the report to the whole capture’s per-message dump.
  • So without it, the report you came for arrives after every message in the file.
sipnab -N -I capture.pcap --call-report "abc123@host" --no-cli-print

When the finding belongs in a ticket, write the same report as Markdown to a file. Do not read it on the terminal.

  • Keep --no-cli-print here too.
  • Without it, report.md opens with hundreds of lines of raw SIP before its first heading.
sipnab -N -I capture.pcap --call-report "abc123@host" --markdown --no-cli-print > report.md

Monitor live SIP quality

Watch live traffic for calls that are already degraded:

  • MOS below 3.0, or
  • audio flowing in only one direction.
sudo sipnab -N -d eth0 --filter "rtp.mos < 3.0 OR one_way == true"

Feed the same set of problem calls into a monitoring pipeline as NDJSON. Keep a copy on disk at the same time.

sudo sipnab -N -d eth0 --problems --json | tee /var/log/sipnab/problems.ndjson

Hand each quality drop to an external alerting script rather than reading it yourself.

  • --exec-rate-limit bounds the invocations per second.
  • So one bad trunk cannot fork a process per stream.
sudo sipnab -d eth0 --on-quality-exec "/usr/local/bin/pagerduty-alert.sh" \
  --quality-threshold 3.0 --exec-rate-limit 5

Measure post-dial delay across calls

Find the calls whose setup took longer than three seconds. The output is NDJSON for whatever consumes it next.

sipnab -N -I capture.pcap --filter "pdd > 3.0" --json

The --slow-setup alias asks the same question against the run’s own post-dial delay threshold.

  • The threshold is 11 seconds, unless --pdd-threshold or [diagnosis] post_dial_delay_secs moves it.
  • So a quick check needs no filter expression at all.
sipnab -N -I capture.pcap --slow-setup --report

Security monitoring

Detect SIP scanning and append it in fail2ban’s format to the log its jail reads.

  • --kill-scanner is what detects.
  • --fail2ban only chooses the format, so leaving it out leaves the log empty.

Every example below passes -N:

  • -N runs the detectors and writes --fail2ban lines. The fail2ban output needs -N.
  • The interactive TUI (the default when you leave -N off) runs the same detectors and shows their findings, but never sends a response to a scanner.
  • --cores N refuses the detection flags with exit 2: the parallel reader runs no detectors. sipnab refuses --stir-shaken with them. Drop --cores to detect.
sudo sipnab -N -d eth0 --kill-scanner --fail2ban >> /var/log/sipnab/scanners.log

Before that log reaches a jail, run the same detectors over a capture of an ordinary hour. Read the list of addresses they would ban.

  • On a carrier trunk it routinely names your own SBCs.
  • The reason: the enumeration signature and a busy hunt group look alike.
sipnab -N -I capture.pcap --kill-scanner --fail2ban \
  | grep -oE 'src=[^ ]+' | sort | uniq -c | sort -rn

Audit a capture for digest credentials that went out where anyone could read them.

sipnab -N -I capture.pcap --digest-leak

Run the whole sweep at once: scanners, fraud heuristics, and registration floods. Alerts go to both syslog and structured JSON.

sudo sipnab -N -d eth0 --kill-scanner --fraud-detect --reg-flood \
  --alert syslog --alert json --syslog

Export for Wireshark analysis

Print a display filter to paste into Wireshark after opening the capture.

sipnab -N -I capture.pcap --wireshark

Or print a tshark command that applies a display filter to the capture. Use this when the next step is a shell pipeline rather than the Wireshark GUI.

sipnab -N -I capture.pcap --tshark-filter 'sip.from.user == "1001"'

After the per-message lines it prints the command to run:

tshark -r 'capture.pcap' -Y 'sip.from.user == "1001"' -V

Export call audio as WAV

Audio export is a TUI workflow. See Keybindings.

Pipe through jq for custom analysis

Count failures by response code. The dominant one is then obvious.

sipnab -N -I capture.pcap --filter "state == 'Failed'" --json \
  | jq -r 'select(.is_request == false) | .status_code' \
  | sort | uniq -c | sort -rn

List every distinct User-Agent the capture saw. This finds the odd endpoint out.

sipnab -N -I capture.pcap --json \
  | jq -r '.ua // empty' | sort -u

Bound, split, and multi-interface captures

Stop after a fixed packet count and summarize the capture.

sipnab -N -d eth0 -n 1000 --report

Roll to a new pcapng every 50 MiB. Then a long capture does not become one file too large to open.

sipnab -d eth0 -O /var/captures/sip.pcapng --pcapng --split filesize:50

Keep only the newest four of those files. Then a long capture fits a fixed amount of disk.

  • --split-keep deletes the older files as rotation creates new ones.
  • It deletes only the files this run wrote.
sipnab -d eth0 -O /var/captures/sip.pcapng --pcapng --split filesize:50 --split-keep 4

The drop user creates the output files. Run as root, sipnab does this:

  • It opens the capture device.
  • It drops privileges to nobody (or the --user you name) before it creates the -O file.
  • It creates every --split file later still.
  • Right after the drop, before it reads the first packet, it checks that the drop user can create these:
    • the -O file,
    • --export-vcon-dir,
    • --vcon-out,
    • --redact-map.

So a directory only root may write ends the run at startup with exit code 1. One example is a directory made with a plain sudo mkdir:

Failed to create output file '/var/captures/sip.pcapng': Permission denied (os error 13). sipnab dropped privileges to user 'nobody' before opening output; make the directory writable by that user (e.g. install -d -o nobody <dir>), pass --user <name> for a user that can write there, or --no-priv-drop.

There are three fixes:

  • Give the directory to the drop user with sudo install -d -o nobody /var/captures.
  • Pass --user naming an account that can already write it.
  • Keep root for the whole run with --no-priv-drop.

--split-keep deletes capture files. sipnab deletes nothing unless you pass the flag. It deletes nothing at --split-keep 0. The reason: a capture is very often the only copy of the evidence.

  • sipnab deletes only the files the running process created and named.
  • It never lists the directory. So a file left beside them stays where it is:
    • a file from an earlier run, from another tool, or from you,
    • however closely its name resembles a rotation.

A run that dies mid-capture leaves behind whatever it had not yet deleted.

  • The next run starts its own list and never adopts those files.
  • sipnab names each file it deletes in the log.
  • It counts them in the closing summary.

Capture across every interface at once. Timestamp each message relative to the one before it.

  • On Linux the any pseudo-device is what makes this every interface.
  • --multi-device is for naming a specific list, as below.
sipnab -d any --delta-time

Capture on two named interfaces instead, one libpcap handle each.

sipnab -d eth0,eth1 --multi-device --delta-time

Tip: Every output flag (--json, --report, --fail2ban, etc.) needs -N. Think of it as “non-interactive mode”. It disables the TUI and writes to stdout instead.


Capture

FlagValueDefaultSinceDescription
-d, --device<IFACE>platform default0.3.0Network interface to capture on. With no -d, no -I file and no -L HEP listener, sipnab picks a default that differs by platform — see the note below. Config: [capture] device
-I, --input<FILE|DIR|GLOB>–0.3.0Read packets from a capture file, a directory of them, an archive of them, or a glob, instead of live capture. Repeatable. sipnab reads the files in capture order, never in filename order — see the note below. A .tar, .tgz or .tar.gz reads like a directory: see Archives read like directories. A stored vCon container (*.vcon.json, or any file whose first character other than whitespace is {) reads as the SIP messages it stores, marked origin=vcon, with no media. sipnab reports a schema finding and reads the container anyway, and skips one with no SIP it can read, naming the reason: see Analyze a stored vCon
--recursive–off0.5.70Descend into subdirectories when -I names a directory
--input-name<GLOB>–0.5.70Read only files whose name matches this pattern when -I names a directory or an archive. Applies at every depth under --recursive, and to an archive’s members at every depth
--vcon-key<FILE>–0.5.219Decrypt an encrypted vCon container (draft-ietf-vcon-vcon-core-04 section 5.3) with the RSA private key in this PEM file, then verify the signed form inside. Applies to every vCon reader: -I, the MCP analyze_vcon and validate_vcon tools, and POST /v1/vcon/analyze and POST /v1/vcon/validate. The file must be readable by its owner only. Without it sipnab refuses an encrypted container and names this flag. A signed container needs no key: sipnab verifies its signature. sipnab reads a gzip format container (section 6.2) with or without it. See A signed, encrypted or compressed container. Config: [vcon_input] key
--vcon-signer-ca<FILE|system>–0.5.219Read a signed or encrypted vCon container only when its signing certificate chains, through the intermediates in its x5c, to a CA certificate in this PEM file, with every certificate in the chain within its validity period. system uses the host’s CA bundle; write ./system for a file of that name. While this flag is in force, sipnab refuses an unsigned container: it has no signer to check. Config: [vcon_input] signer_ca
--vcon-signer-sha256<HEX>–0.5.219Read a signed or encrypted vCon container only when its signing certificate has this SHA-256 fingerprint: 64 hex digits, plain or in colon-separated pairs, either case, as openssl x509 -noout -fingerprint -sha256 prints it. While this flag is in force, sipnab refuses an unsigned container. Config: [vcon_input] signer_sha256
--vcon-x5u-ca<FILE|system>–0.5.219Fetch the certificate chain of a signed vCon container that names it only by an x5u URL (draft-ietf-vcon-vcon-core-04 section 5.2.2), over HTTPS, trusting the server only when it chains to a CA certificate in this PEM file; system uses the host’s CA bundle. sipnab reads at most 256 KiB of the answer, waits at most 10 seconds, fetches each URL once per run, and refuses an x5u that is not https://. Without this flag sipnab fetches nothing, and refuses such a container naming this flag. sipnab verifies a signature that carries x5c with it, and does not fetch its x5u. Config: [vcon_input] x5u_ca
-O, --output<FILE>–0.3.0Write captured packets to a pcap file. --filter chooses what sipnab shows and analyzes, not what this file holds: a BPF filter (the positional argument, such as "udp port 5060") narrows the file. Run as root, sipnab creates this file, and every --split file, after it drops privileges, so the drop user (nobody unless --user names another) must be able to write the directory. sipnab checks this before it reads the first packet and exits 1 when the drop user cannot. See the note above
-B, --buffer<MIB>640.3.0Kernel capture buffer size in MiB (per device). See Tuning capture. sipnab refuses 0 (no ring at all) with exit 2, and [capture] buffer = 0 with exit 1. Config: [capture] buffer
--buffer-budget<MIB>640.4.4Sets the size of the in-flight capture→processing queue. sipnab converts the budget to a packet count at an assumed 2048 bytes per packet, so 64 holds 32768 packets, and keeps the count between 10000 and 5000000: 0 and any value up to 19 give the 10000-packet floor. The limit counts packets, not bytes. A queued packet can be as large as the snapshot length, so with full 65535-byte packets the 64 MiB default can hold about 2 GiB; overrides [capture] buffer_budget_mb. Config: [capture] buffer_budget_mb
--snaplen<BYTES>655350.3.0Snapshot length for packet capture (bytes). sipnab refuses 0 (no byte of any packet) with exit 2, and [capture] snaplen = 0 with exit 1. Config: [capture] snaplen
--capture-profilesignaling|full–0.5.109Picks a --snaplen for you: signaling is 1500 bytes, which keeps every SIP header whole and drops most of each RTP payload, and full is 65535. An explicit --snaplen overrides it. More: Tuning capture. Config: [capture] snaplen
-S, --limitlen<BYTES>–0.5.9Parse only the first N bytes of each packet. Caps what the SIP parser and matchers inspect, independent of --snaplen (capture length) and --payload-limit (display truncation). At least 1: sipnab refuses 0
--no-reassembly–off0.5.9Disable IP-fragment and TCP-segment reassembly; sipnab parses every packet standalone (inverse of segment reassembly). Useful for pure single-packet UDP scanning
-x, --quiet-bad-parse–off0.5.10Suppress the per-packet “SIP parse error” diagnostic emitted when a SIP-looking packet fails to parse. sipnab drops the packet either way; this only silences the notice on a noisy link. sipnab logs the notice at debug level, so it shows only with SIPNAB_LOG=debug
--portrange<RANGE>5060-50610.3.0SIP signaling port range. Media is never gated — RTP uses SDP-negotiated dynamic ports. The default is narrow and carriers routinely run SIP on 5070, 5080 and elsewhere, so widen it or analyze a fraction of the file — see the note below. HEP input (-L, --hep-parse) is never gated: the sender already chose what to send, and the ports in its HEP chunks are its claim, not something sipnab captured. Config: [capture] portrange
--ws-portrange<RANGE>80, 443, 8080, 84430.5.99Ports carrying SIP-over-WebSocket (RFC 7118), as one inclusive START-END range in the same grammar as --portrange. Kamailio, OpenSIPS and Janus each default to a WSS port outside the shipped set, and behind a reverse proxy sipnab sees the port the proxy forwards to: name that port here, or sipnab reads none of that signaling. A range replaces the shipped set, exactly as --portrange replaces the default signaling ports. sipnab counts the SIP-over-WebSocket it declines to unwrap and names the ports it arrived on. The range governs plain WebSocket only: sipnab reads a WebSocket frame it decrypts out of TLS with --keylog on any port and labels it WSS. It joins a frame split across TLS records, several frames in one record, and a message fragmented across frames, up to 65,536 bytes per message, and reads an HTTP upgrade and its 101 answer split across TLS records, up to 8,192 bytes. A frame, message or upgrade it cannot finish or decode counts in the NOT DECODED line. Config: [capture] ws_ports
--multi-device–off0.3.0Open one capture per interface named in a comma-separated -d list, e.g. -d eth0,docker0 --multi-device. It does not enumerate interfaces for you: with a single -d (or none) it falls back to an ordinary single capture. On Linux the zero-argument default already sniffs every interface via the any pseudo-device
--no-rtp–off0.3.0Disable RTP capture and analysis. Config: [capture] no_rtp
--rtp–off0.5.204Capture and analyze RTP even when [capture] no_rtp = true. The last of --no-rtp / --rtp typed wins. Config: [capture] no_rtp
-p, --no-promisc–off0.5.8Do not put the interface into promiscuous mode. Promisc is on by default for a named device; the any pseudo-device is never promiscuous. Config: [capture] promisc
--bpf-file<FILE>–0.3.0Read the BPF capture filter from a file. It takes precedence over the positional filter and replaces [capture] bpf_filter, and like them it applies to -I capture files as well as live capture. Config: [capture] bpf_filter
--capture-tunnels[<PORTS>]off0.5.80Also capture all traffic on the UDP tunnel ports, so SIP inside GTP-U, VXLAN or GENEVE reaches sipnab. Bare flag means 2152,4789,6081; pass a list for non-standard ports (--capture-tunnels=8472). Off by default: it takes every packet on those ports, which on a mobile core is the entire user plane, because a BPF filter cannot reach the inner port behind a GTP-U extension-header chain. Ignored when you supply your own filter
-n, --count<N>–0.3.0Stop after receiving N packets (counts every packet received, including any a HEP listener later drops by allowlist, rate limit, or auth). At least 1: sipnab refuses 0
--duration<DURATION>–0.3.0Stop after duration (e.g., 30s, 5m, 1h). A bare number is seconds. Must be greater than zero: sipnab refuses 0, 0s, 0m and 0h with exit 2
--autostop<CONDITION>–0.3.0Autostop condition: filesize:N stops after N MiB of output, duration:N after N seconds. filesize counts MiB, the unit --split filesize and -B/--buffer also use
--split<CONDITION>–0.3.0Split output files (e.g., filesize:50 for 50 MiB chunks)
--split-keep<N>–0.5.100Keep only the newest N split files: sipnab deletes the older ones as --split rotates, turning -O into a ring buffer. Off unless you pass it, and off at 0. sipnab deletes only the files the running process created and named, so a file left by an earlier run, another tool, or you survives however closely its name resembles a rotation. See the warning above
--replay–off0.3.0Replay packets from a pcap file at original timing
--pcapng–off0.3.0Use pcapng format for output files. pcapng Metadata covers the metadata sipnab writes into pcapng output
<BPF_FILTER>...positional–0.3.0BPF capture filter expression (trailing positional args), handed to libpcap as typed. Not the display filter (--filter). Replaces the filter sipnab would otherwise generate from --portrange. --bpf-file takes precedence, and either one replaces [capture] bpf_filter. The filter applies to -I capture files as well as live capture. When [capture] bpf_filter filters a file run, sipnab prints a warning naming the key, so a file whose calls fall outside the filter does not look empty. Unlike sngrep and sipgrep, sipnab takes no match expression here; match SIP text with -e. A filter typed here that does not compile says so, and sipnab refuses a lone argument that names an existing file, naming the -I it was missing. Config: [capture] bpf_filter

The auto-generated filter looks through VLAN, QinQ, PPPoE and MPLS. With no filter of your own on a live capture, sipnab builds one from --portrange.

  • It also matches SIP inside these, on Ethernet and on Linux cooked captures:
    • one VLAN tag,
    • QinQ,
    • PPPoE Session,
    • one or two MPLS labels.
  • A bare portrange would match none of that traffic.
  • See How the generated filter reaches encapsulated SIP for what it covers and its two limits.

The auto-generated filter admits the media too. RTP travels on whatever ports SDP negotiated.

  • So the filter admits UDP on any port whose first payload byte marks RTP version 2. This covers IPv4 and IPv6 without extension headers.
  • Media inside a VLAN tag or a tunnel is not admitted. Name it in a filter of your own.
  • With --no-rtp the filter stays signaling-only. Nothing would read the media.
  • With --hep-listen as well as -d, the interface takes the media only.
    • The HEP mirror already delivers the signaling.
    • Taking it off the wire too would deliver every message twice.

UDP tunnels are opt-in. GTP-U, VXLAN and GENEVE are not covered by default, and sipnab says so at startup.

  • BPF cannot parse a variable-length GTP-U extension-header chain to reach the inner port.
  • So the only way to cover them is to capture everything on the port. See --capture-tunnels.

A filter you supply is never rewritten. It goes to pcap_compile exactly as typed. If it looks encapsulation-blind, sipnab says so once. It still uses your expression.

What you get when you omit -d. The default is not the same everywhere, and the difference decides whether you see loopback traffic:

PlatformDefaultScope
Linuxthe any pseudo-deviceevery interface at once, loopback included
macOS / BSDlibpcap’s default device, from the routing table; otherwise the first non-loopback interfaceone interface

On Linux:

  • A SIP proxy often talks to itself over loopback. So capturing only eth0 silently misses it.
  • Pass -d any to say so explicitly.
  • Promiscuous mode does not apply to any. So --no-promisc changes nothing there.

On macOS you get a single interface.

  • If SIP is not on the one libpcap picked, you see nothing.
  • The capture then looks merely quiet.
  • Name -d explicitly.

-I and -d are alternatives, not companions. sipnab accepts both, and the FILE wins.

  • sipnab reads the file and never opens the interface.
  • The output looks like a normal run.
  • sipnab warns on stderr when you do this.
  • To switch a file command to live capture, remove -I rather than adding -d beside it.

--portrange decides how much of the file you analyze. The default, 5060-5061, is narrow.

  • SIP on other ports is ordinary. Carriers and SBCs use 5070, 5080 and others routinely.
  • A capture from a real trunk commonly carries a large share of its signaling outside the default.

Reading a file, sipnab skips any SIP message whose source and destination ports both fall outside the range. A skipped message is in no count, dialog or output, so check for skips before you trust a total.

The run says how many it skipped on stderr, with the busiest port:

NOT ANALYZED: 7 further SIP message(s) were seen on ports outside --portrange and are in none of the totals above. Busiest: 5060 (7). Re-run with --portrange 1-65535 to include them.

--report includes the same fact:

INCOMPLETE RUN — this report does not describe the whole capture:
  - 7 SIP message(s) were seen on ports outside --portrange and were not analyzed; re-run with --portrange 1-65535 to include them

Both came from sipnab -N -I sip_call.pcap --portrange 1-2 --report, a capture whose SIP is all on port 5060.

--portrange 1-65535 analyzes everything the capture holds. Reach for it first on an unfamiliar capture. Narrow it once you know what is in there.

Live capture is different, and worse.

  • With no explicit BPF filter, sipnab compiles the range into the filter.
  • So the kernel drops the traffic before sipnab sees it.
  • Nothing downstream, this counter included, can report what went missing.
  • Set the range correctly before the capture, because no rerun recovers it.

NOT DECODED is the other line to read before the totals.

  • --portrange is about SIP sipnab chose not to analyze.
  • This line is about frames it could not read at all:
    • an unsupported link type,
    • an EtherType carrying no IP,
    • an IP protocol that is no transport,
    • a truncated frame,
    • a decode error.
  • Such a frame counts as a packet (it arrived). It reaches no message, dialog or stream.
  • So on its own, the summary reports the same thing in two cases: the capture held no SIP, or sipnab understood none of it:
NOT DECODED: 3 of 3 frame(s) (100.0%) produced nothing and are in none of
the counts above. Reasons: unsupported link type 147 (3). NOTHING IN THIS
CAPTURE WAS READ — every frame failed to decode, so the totals above describe
no traffic whatsoever and a zero among them is not evidence of absence.

Every reason carries the number that identifies it. That number is what you act on.

  • unsupported link type 147 names the file’s pcap link type, here LINKTYPE_USER0, which sipnab does not decode.
  • Convert the capture with editcap -T ether in.pcap out.pcap when its frames are Ethernet underneath, or open an issue naming the number.

A small count is normal. ARP is undecodable by definition and appears on any Ethernet capture. So read the share, not the count.

When the share is high, sipnab additionally refuses to state “No SIP traffic found” as a finding. It has no basis for one. The same breakdown appears in two more places:

  • as a NOT DECODED (capture-wide) section in --report,
  • as sipnab_capture_undecodable_frames_total{reason} plus sipnab_capture_undecoded_fraction on /metrics.

docs/troubleshooting.md tables what each reason means and what to do about it.

Examples

  • sipnab -N -I capture.pcap --sandbox best-effort --report — analyze a capture with the filesystem bounded to the input and the crash directory. On a kernel without Landlock this warns and analyzes anyway, which is the point of best-effort

  • sudo sipnab -N -d eth0 -O /var/captures/live.pcap --sandbox required — refuse to capture at all unless the ruleset installed. Use this where an unsandboxed capture is worse than none. The run exits non-zero and names the reason rather than starting

  • sipnab -N -I capture.pcap --seccomp log — read a capture with every system call recorded. The filter refuses nothing, so the analysis matches a run without the flag. What you get is the record of which calls a file-reading run makes

  • sudo sipnab -N -d eth0 --count 5000 --seccomp log && auditctl -s — record the calls a LIVE capture makes, bounded by --count so the log cannot run away, then check where the records went. A connected audit daemon has them in ausearch -m SECCOMP. A pid of 0 means they are in dmesg

  • sipnab -N -I capture.pcap --rtp --report — analyze the media too when your config file sets [capture] no_rtp = true

  • sipnab -N -I capture.pcap --no-rtp --rtp --report — the last of the pair wins, so this run analyzes RTP

  • sipnab -N -I capture.pcap -e INVITE udp port 5060 — the sngrep habit sngrep INVITE udp port 5060, written for sipnab: the match expression goes after -e and the words after the options are the capture filter

  • sipnab -N -I 0192d6a4-1b2c-8d3e-9f40-5a6b7c8d9e0f.vcon.json --vcon-key recipient.key --json-dialogs — analyze an encrypted vCon sipnab wrote: decrypt it with the recipient’s private key, verify its signature, and print its dialogs

  • sipnab --mcp -N -I capture.pcap --vcon-key /etc/sipnab/vcon-recipient.key — serve the MCP tools with a key, so analyze_vcon and validate_vcon read encrypted containers an agent hands over

  • sipnab -N -I signed.vcon.json --vcon-signer-ca signing-ca.pem --report — analyze a signed vCon only when the CA in signing-ca.pem issued its signing certificate

  • sipnab -N -I sealed/ --vcon-key recipient.key --vcon-signer-ca system --json-dialogs — read a directory of encrypted vCons, each only when a CA in the host’s CA bundle issued its signer

  • openssl x509 -in signer.pem -noout -fingerprint -sha256 — print the fingerprint --vcon-signer-sha256 takes, from the signing certificate. For the signing certificate in the next example it prints sha256 Fingerprint=4A:27:CF:DC:D8:87:A5:E5:A9:B5:DE:7E:27:37:C2:48:28:2B:86:44:8A:B3:60:6F:42:99:B2:E2:26:FC:0F:19; pass the part after =. sha256sum signer.pem hashes the PEM text and gives a different value

  • sipnab -N -I signed.vcon.json --vcon-signer-sha256 4A:27:CF:DC:D8:87:A5:E5:A9:B5:DE:7E:27:37:C2:48:28:2B:86:44:8A:B3:60:6F:42:99:B2:E2:26:FC:0F:19 --json-dialogs — analyze a signed vCon only when one known certificate signed it

  • sipnab -N -I sealed.vcon.json --vcon-key recipient.key --vcon-signer-ca signing-ca.pem --vcon-signer-sha256 4a27cfdcd887a5e5a9b5de7e2737c248282b86448ab3606f4299b2e226fc0f19 --report — require both: the signing certificate chains to signing-ca.pem and has this fingerprint

  • sipnab -N -I signed.vcon.json --vcon-x5u-ca system --json-dialogs — analyze a signed vCon whose signature names its certificate chain only by an x5u URL: fetch the chain over HTTPS from a server the host’s CA bundle trusts

  • sipnab -N -I signed.vcon.json --vcon-x5u-ca pki-ca.pem --vcon-signer-ca signing-ca.pem --report — fetch the x5u chain from a server the CA in pki-ca.pem issued, and read the vCon only when its signer chains to signing-ca.pem

  • sudo sipnab --device eth0 --output capture.pcap --portrange 5060-5080 --count 10000 — record up to 10000 packets from eth0 into a pcap, watching a widened SIP port range

  • sudo sipnab --device eth0 --buffer 16 --buffer-budget 128 --snaplen 2048 --quiet-bad-parse — live-capture a busy link with bigger kernel and queue buffers, a capped snapshot length, and parse-error notices silenced (the CLI matcher -x)

  • sudo sipnab -N -d eth0,eth1 --multi-device --output capture.pcap --autostop filesize:100 — capture on two named interfaces at once, headlessly, stopping once the output file reaches 100 MiB. --multi-device needs the list. Without one it is a no-op

  • sipnab -N --input capture.pcap --replay --no-rtp — replay a pcap at its original timing with RTP capture and analysis disabled

  • sudo sipnab -N -d eth0 --capture-profile signaling --output signaling.pcap — record signaling on a busy link: 1500 bytes keeps every SIP header whole while dropping the bulk of each RTP packet, which is where the ring pressure comes from. Check sipnab_capture_snapped_frames_total afterwards to see how many frames arrived short

  • sudo sipnab -N -d eth0 --capture-profile signaling --snaplen 4096 — the explicit number wins over the profile, for a trunk carrying INVITEs too large even for one MTU (deep Record-Route sets, ISUP encapsulation). Use --capture-profile full instead when you need --retain-audio, WAV export or a faithful -O re-emit, all of which need RTP payload

  • sipnab -N --input /var/captures/ --json-dialogs --no-cli-print — read every capture in a directory as one timeline, so a call split across the ring buffer resolves to one dialog instead of two fragments

  • sipnab -N --input /var/captures/ --recursive --input-name '*.pcap.gz' --json-dialogs --no-cli-print — descend into per-day subdirectories and read only the compressed archives

  • sipnab -N --input 'captures/tg.pcap[0-4]' --report — analyze the first five members of a ring buffer with a glob sipnab expands itself, no shell needed

  • sipnab -N --input session.tgz --report — analyze every capture in an archive as one timeline without unpacking it, each member not read named with the reason

  • sipnab -N --input a.pcap --input b.pcap --json-dialogs --no-cli-print — read two named captures as a single set, ordered by their packets

  • sipnab -N --input /var/captures/ --input-name 'edge1-*' --recursive --json — pick one host’s captures out of a tree holding several

  • sipnab -N --input capture.pcap --limitlen 512 --no-reassembly --quiet-bad-parse — scan a pcap quickly: parse only the first 512 bytes of each packet, every packet standalone (no reassembly), without parse-error noise

  • sudo sipnab --device eth0 --bpf-file sip.bpf --no-promisc --duration 5m — capture for 5 minutes using a BPF filter read from sip.bpf, without putting the interface into promiscuous mode (the CLI matcher -p)

  • sudo sipnab -N --device eth0 --capture-tunnels --buffer 64 --duration 5m — capture SIP traveling inside GTP-U, VXLAN or GENEVE as well as the encapsulations the auto-filter already covers. This takes every packet on ports 2152, 4789 and 6081, so the same command widens the kernel buffer. Check the drop counters in the summary before trusting a long run

  • sudo sipnab -N --device eth0 --capture-tunnels=8472 --portrange 5060-5080 --report — cover a Linux VXLAN fabric on its pre-IANA port 8472 instead of the three defaults, across a widened signaling range

  • sudo sipnab --device eth0 --portrange 5060-5090 --buffer 8 --buffer-budget 256 --duration 1h — monitor an hour of traffic across a wide SIP port range with enlarged capture buffers

  • sipnab -N --input capture.pcap --replay --limitlen 1500 --no-rtp — replay signaling only from a pcap, parsing at most 1500 bytes of each packet

  • sudo sipnab --device eth0 --bpf-file sip.bpf --no-promisc --snaplen 9000 --count 500 — stop after 500 packets that pass the sip.bpf filter, non-promiscuous, with the snapshot length sized for jumbo frames

  • sudo sipnab -N --device eth0 --output capture.pcap --autostop duration:60 --no-reassembly — write a one-minute capture that treats every packet standalone (IP-fragment and TCP-segment reassembly off)

  • sipnab -N --input webrtc.pcap --ws-portrange 8081-8081 --portrange 1-65535 --json-dialogs --no-cli-print — a WSS listener behind a reverse proxy that forwards to 8081: without the range the entire WebRTC signaling leg is invisible, and sipnab reports how many messages it skipped and on which port

  • sudo sipnab -d eth0 --ws-portrange 1-65535 --portrange 1-65535 — unwrap SIP-over-WebSocket wherever it appears on a box whose WSS port you do not know yet, then read the skip line to learn which ports were carrying it

Read a ring buffer or split capture as one capture

A call’s INVITE may be in one file of a rotated capture and its BYE in the next. Read the whole set in one run, so each call appears once, complete.

Name the files with a glob, a directory, or several -I flags. Quote a glob so sipnab expands it rather than the shell:

sipnab -N -I 'ring/tg.pcap*' --json-dialogs --no-cli-print

sipnab orders the files by the timestamp of each file’s first packet, never by name. A tcpdump -C 100 -W 10 ring buffer overwrites its oldest file in place, so its numeric suffixes do not follow time. The first log line names the file sipnab reads first:

INFO sipnab::app::bootstrap: Reading 2 capture files in timestamp order (first: 'ring/tg.pcap1')
  • sipnab recognizes a capture by its contents, not its extension. A file named tg.pcap0, or one with no extension, opens.
  • It decompresses gzip files as it reads them, so a directory holding both .pcap and .pcap.gz files needs nothing extra.
  • A file you name with -I that sipnab cannot read stops the run with an error. sipnab skips, with a warning, a file it finds by expanding a directory or glob and cannot read.

Reading the files one run at a time instead splits each call that crosses a file boundary into two incomplete dialogs: one that never ends, and a BYE with no INVITE.

Archives read like directories

Point -I at a .tar, a .tgz, a .tar.gz or a .zip, and sipnab reads it the way it reads a directory. See Archives for password-protected ones.

  • Every capture inside joins the set, in capture order, alongside anything else -I named.
  • Nobody has to unpack it first.
  • The answer matches what reading the unpacked directory gives.

The rules:

  • Layers nest. These unwrap on the way to the capture, up to four layers deep:

    • a gzip-compressed member such as ring-3.pcap.gz,
    • a tar inside a .tgz,
    • or both.

    sipnab identifies each layer by its first bytes, never by its name. So a gzip file named c.pcap still opens.

  • sipnab accounts for every member. A member that is not a capture gets a Skipping line naming it and the reason. The reasons are:

    • empty,
    • not a capture (with its first four bytes),
    • a link,
    • a device node,
    • a sparse file,
    • a format sipnab does not unwrap: zstd, xz, bzip2 or LZ4, or ZIP and 7z in a build without the archive feature.

    The closing -I resolved to line counts them.

    A member may have a link type sipnab does not decode, such as an LTE MAC log.

    • It gets a line naming it.
    • Its frames count as not decoded.
    • A BPF filter that cannot compile against that link type skips the member. It does not end the run.
  • Names come from the archive, paths never do.

    • sipnab writes each member to a file it names itself, in a private directory under $TMPDIR.
    • It deletes that directory when the run ends.
    • A member called ../../etc/x lands in that directory like any other.
    • Output, logs and frame pointers name a member <archive>/<member>. For example: session.tgz/ring/a.pcap#12.
    • --show-frame reads that member straight back out of the archive.
  • Inflation stops at a ceiling.

    • Every byte any gzip layer produces counts against --max-gunzip-bytes. sipnab sums the count across the layers of one input.
    • One archive may hold at most 10,000 entries.
    • An archive cut short, or stopped at a ceiling, keeps what arrived. It marks the run incomplete:
      • exit status 1,
      • archives.cut_short in the sipnab_run record.
  • --input-name filters members by file name, the same way it filters a directory’s files. On a file you name directly with -I, such as a single call.pcap.gz, a pattern that matches the file’s name reads it, and one that does not stops the run with exit 1:

    -I/--input: -I 'call.pcap.gz' names a file directly, which --input-name '*.pcap' does not match; --input-name filters only files found in a directory or by a glob. Drop one of the two.
    
  • Decryption works on members exactly as on plain files. Each of these decrypts a member the same way:

    • --keylog,
    • the secrets a pcapng embeds,
    • SDES keys in the SDP,
    • --dtls-keylog.

    It does not matter whether the member arrived plain, gzip-compressed, in a tar, in a .tgz, or gzip-compressed inside a .tgz.

A run may die without cleaning up, from a crash or kill -9.

  • It leaves its directory behind, holding a lock that died with it.
  • The next run that unpacks an archive removes it.

Archives

Captures often travel as password-protected ZIP files. They carry subscriber identities, numbers and credentials. Read captures from tar, ZIP and 7z archives gives the steps for each password source, with the output of each.

  • Unpacking one by hand leaves a decrypted copy on disk. That defeats the password.
  • Point -I at the ZIP instead. sipnab then:
    • opens it,
    • reads the captures inside,
    • deletes its own owner-only copy of each member when the run ends.
  • A ZIP reads like a .tgz. So everything in Archives read like directories applies to it.
  • A ZIP nested in a tar, or a tar in a ZIP, unwraps too.

Builds with the archive feature read ZIP and 7z files. These include it:

  • the full build,
  • every release binary, the static musl tarballs and the -noaudio packages among them.
FlagValueDefaultSinceDescription
--archive-password-file<FILE>–0.5.187Read archive passwords from a file, one per line. sipnab strips only the line ending: spaces belong to the password. sipnab refuses a file you own that other users can read (chmod 600 it), the rule ssh applies to a private key, and accepts a file another user owns, such as a Kubernetes secret mount. /dev/fd/3 and <(pass show pcaps) work
--archive-password-command<CMD>–0.5.187Run a command and use the first line it prints, like restic --password-command. sipnab runs it without a shell and passes its stderr through, so a secret manager can prompt. A non-zero exit fails the run, and sipnab never shows the command’s output. The command gets 120 seconds and 64 KiB of output
--archive-password-stdin–off0.5.187Read an archive password from the first line of stdin, like docker login --password-stdin
--archive-password<PASSWORD>–0.5.187An archive password on the command line. Insecure, and sipnab warns on every use: other local users can read it in ps, and your shell saves it in its history
--archive-password-encoding<ENC>all0.5.187Try passwords in one encoding only: utf-8, cp437, cp850 or cp1252
--no-password-prompt–off0.5.187Never ask for an archive password on the terminal

Where a password comes from, best first. sipnab tries every source you configure, in this order, before it gives up on a member:

  • --archive-password-file, for scripts and services.
  • --archive-password-command.
  • --archive-password-stdin.
  • The systemd credential archive-password.
    • When $CREDENTIALS_DIRECTORY/archive-password exists, sipnab reads it like a password file.
    • So LoadCredential=archive-password:/etc/sipnab/pcaps.pw or LoadCredentialEncrypted= in the unit is all it takes.
    • Writing --archive-password-file %d/archive-password in the unit works too.
  • The SIPNAB_ARCHIVE_PASSWORD environment variable. It ranks below every source above because a process’s environment leaks:
    • child processes inherit it,
    • systemd and OWASP both advise against secrets there.
  • --archive-password, which warns on every use.
  • The prompt. When no configured password opens a member, sipnab asks on the terminal, the way ssh and sudo do: Password for evidence.zip (member voip/call3.pcap, attempt 1 of 3):.
    • It reads /dev/tty, not stdin. So it still asks with stdin redirected.
    • It echoes nothing.
    • Each archive gets three attempts.
    • An empty entry, or Ctrl-D, skips that archive’s locked members.
    • Ctrl-U clears the line.
    • Ctrl-C stops the run.
    • sipnab restores the terminal on every one of those paths.
    • Sometimes it never asks and never waits. Locked members then count as encrypted_no_password. This happens:
      • with no terminal, as in cron, CI or a detached agent,
      • or with --no-password-prompt.
    • A TUI run asks here for the archives -I names, before it draws.
    • A capture opened inside the TUI asks in its own window, described in Archive password popup.

How sipnab tries them.

  • The password that opened one member of an archive goes first for that archive’s other members.
  • It never goes to another archive unless you configured it.
  • ZIP never records which encoding its creator typed the password in. So sipnab also tries a non-ASCII password in these forms:
    • its NFC and NFD forms,
    • the CP437, CP850 and CP1252 code pages that unzip and 7-Zip fall back to.
  • All of those spellings count as one attempt.
  • Passwords may be up to 4096 bytes. sipnab refuses a longer one whole rather than cutting it short.

What it reads.

  • ZIP members stored or deflated:
    • unencrypted,
    • encrypted with WinZip AES (128, 192 or 256-bit),
    • or encrypted with the legacy ZipCrypto.
  • 7z archives compressed with LZMA or LZMA2:
    • unencrypted or AES-256,
    • with or without an encrypted member list.
  • A 7z password covers the whole archive. The format stores it as UTF-16 text. So sipnab tries its NFC and NFD forms but no code pages.
  • A 7z sets how much work deriving its key takes.
    • sipnab refuses one asking for more than 7-Zip’s own ceiling of 2^24 rounds, as bound_exceeded (7z key derivation).

sipnab reads ZipCrypto and warns once per archive that it protects nothing.

  • The bkcrack tool recovers its keys from 12 known bytes.
  • A capture’s first bytes are predictable.
  • A wrong password fails the format’s own check.
  • One wrong password in 256 slips past ZipCrypto’s check byte. For that one, the member’s CRC fails instead, and sipnab moves on to the next candidate.

What it reports. Each member sipnab does not read gets a Skipping line with the reason. The reason is one of these:

  • encrypted, and no password was supplied,
  • encrypted, and no password supplied opens it,
  • a format it cannot decrypt.

You may name an archive with -I and sipnab may read nothing from it.

  • That fails the run with exit status 1.
  • It names each member with its reason code, such as evidence.zip/call3.pcap: encrypted_no_password.
  • If some members open, the run succeeds. The closing -I resolved to line counts the rest.

Where the password never goes. No log line at any level contains it. No error message, output format or export contains it either.

  • A run that holds a password turns core dumps off.
  • --allow-coredump warns that a dump would contain it.

Exports carry the data out unencrypted.

  • These each warn once that they write that data unencrypted, when they write from a member decrypted out of a password archive:
    • -O,
    • --strip-secrets,
    • --write-annotated.
  • The section comment of a --write-annotated copy says so too. It names no password.

Examples

  • sipnab -N -I evidence.zip --archive-password-file ~/.config/sipnab/pcaps.pw --report — read a password-protected ZIP with the password in an owner-only file
  • sipnab -N -I evidence.zip --archive-password-file <(pass show pcaps/lab) --json-dialogs --no-cli-print — take the password straight from a password manager through a pipe, so it never touches the disk
  • sipnab -N -I evidence.zip --archive-password-command 'pass show pcaps/lab' --report — let sipnab run the password manager itself
  • printf '%s\n' "$PCAP_PW" | sipnab -N -I evidence.zip --archive-password-stdin --report — pass a password a CI job holds in a masked variable
  • sipnab -N -I evidence.zip --archive-password-file pw.txt --archive-password-encoding cp437 --report — try only the DOS code page, for an archive you know an old Windows tool made
  • sipnab -N -I evidence.zip --archive-password-stdin --archive-password-encoding cp1252 --json-dialogs --no-cli-print < pw.txt — read the password from a redirected file and try only the Windows code page
  • sipnab -N -I 'captures/*.zip' --archive-password-command 'op read op://lab/pcaps/password' --json-dialogs --no-cli-print — open every ZIP a glob finds with one password from 1Password’s CLI
  • sipnab -N -I evidence.zip --archive-password 'correct horse' --report — a throwaway lab archive only: the password lands in ps and your shell history, and sipnab warns
  • sipnab -N -I lab.zip --archive-password "$LAB_PW" --report — the same trade-off from a variable, which still shows in ps; prefer --archive-password-stdin
  • sipnab -N -I evidence.zip --report — no password configured: sipnab asks on the terminal for each locked archive, three tries each
  • sipnab -N -I evidence.zip --no-password-prompt --json-dialogs --no-cli-print — in a script that must never wait on a keyboard: sipnab skips and counts the locked members instead
  • sipnab -N -I 'drop/*.zip' --archive-password-file pw.txt --no-password-prompt --report — try the configured passwords and nothing else, even from a terminal

Mode

FlagValueDefaultSinceDescription
-N, --no-tui–off0.3.0Non-interactive mode (no TUI). Required for batch/output flags
-c, --calls-only–off0.3.0Show only calls: dialogs an INVITE started. The dialog outputs apply it (--json-dialogs, --report, the per-message printout), and the TUI opens with only INVITE checked in its filter dialog. It leaves out OPTIONS, REGISTER, SUBSCRIBE and other dialogs. Like --filter, it does not narrow what -O writes
-t, --telephone-event–off0.3.0Decode telephone-event (DTMF) RTP payloads and log each event at info, digit value masked as x
--dtmf-cleartext–off0.5.82Log each DTMF digit VALUE on a second line, at debug, after the masked info line. Needs -t: without it nothing decodes the events and sipnab logs nothing. Publishes PINs and card numbers
-q, --quiet–off0.3.0Suppress informational output; only show results

Examples

  • sipnab --no-tui -I capture.pcap --calls-only — analyze a pcap headlessly, showing only calls (dialogs an INVITE started), without OPTIONS pings, registrations or subscriptions
  • sudo sipnab --no-tui -d eth0 --telephone-event — headless live capture that decodes DTMF and logs each event with its duration and SSRC, digit value masked
  • sipnab --no-tui -I capture.pcap --calls-only --telephone-event — read a capture headlessly, report only calls, and log how many DTMF events each one carried
  • sipnab --no-tui -I lab.pcap --telephone-event --dtmf-cleartext — read a capture you own and disclose the digit values. Also set SIPNAB_LOG=debug, or the run prints nothing but the mask
  • sudo sipnab --no-tui -d eth0 --telephone-event --dtmf-cleartext 2>dtmf.log — capture live and steer the cleartext digits into a file whose permissions you control instead of a shared terminal or journald. Again needs SIPNAB_LOG=debug

Read this before using --dtmf-cleartext. DTMF digits keyed after answer include:

  • PINs,
  • calling-card numbers,
  • account numbers,
  • credit-card numbers with their CVVs.

RFC 4733 carries them in the clear no matter how well the signaling layer protected the call. So -t alone logs everything you diagnose with, with the digit value replaced by x:

  • that an event arrived,
  • its duration,
  • its SSRC,
  • its timestamp.

DTMF digit='x' duration=200ms ssrc=0xdeadbeef

--dtmf-cleartext adds a second line carrying the value. It is not a display setting, and it needs -t: without -t nothing decodes the events, so neither line appears.

  • It puts a caller’s PIN wherever this run’s log goes:
    • your terminal,
    • a redirected file,
    • journald,
    • every aggregator downstream of journald.
  • Turning it on takes two deliberate acts. sipnab writes the cleartext line at debug while the masked line stays at info. So:
    • pass the flag and
    • raise the level.
  • Either one alone shows you nothing but the mask. Both, in one copyable line:
SIPNAB_LOG=debug sipnab --no-tui -I lab.pcap --telephone-event --dtmf-cleartext 2>dtmf.log

Each event then logs two lines, the masked one and the cleartext one:

INFO sipnab::app::batch: DTMF digit='x' duration=120ms ssrc=0x5711bf84
DEBUG sipnab::app::batch: DTMF cleartext digit='6' duration=120ms ssrc=0x5711bf84

Where the events go. sipnab writes one masked line per decoded event and keeps a count. No report, JSON field or MCP tool carries the digits. One output does: a vCon written by a [[vcon.rule]] with dtmf_events = true records each key press as keydown and keyup events whose button is the digit (see What the Dialog Object says about the call). --redact removes them.

To see the log lines:

  • Run with -N. A TUI session shows none of them, because TUI mode sets the log level to error so log lines do not overwrite the screen.
  • Leave out --quiet. It sets the level to warn and hides them.
  • SIPNAB_LOG=info overrides the TUI level. Redirect stderr to a file when you use it, or the lines draw over the TUI.

Matching

FlagValueDefaultSinceDescription
-e, --match<PATTERN>–0.5.6SIP payload match-expression (the positional match expression). Regex tested against the whole raw message; once any message in a dialog matches, sipnab shows the rest of that dialog too (dialog-following). In the TUI, the call list holds only dialogs one of whose messages matched, and every later message of each; the REST API and MCP server a TUI run starts read the same dialogs. Honors -i/-v/-w/--single-line. Independent of the trailing <BPF_FILTER> positional
-i, --ignore-case–off0.3.0Case-insensitive matching for header filters and patterns
-v, --invert–off0.3.0Invert the match: select messages that do NOT match. Dialog-following still applies, so once sipnab selects a message of a dialog, every later message of that dialog prints, including ones that match
-w, --word–off0.3.0Match whole words only
--single-line–off0.3.0Prevent . from matching newlines in payload regexes; does not unfold headers
--from<PATTERN>–0.3.0Filter by SIP From header (regex pattern). Config: [filter] from
--to<PATTERN>–0.3.0Filter by SIP To header (regex pattern). Config: [filter] to
--contact<PATTERN>–0.3.0Filter by SIP Contact header (regex pattern)
--ua<PATTERN>–0.3.0Filter by User-Agent header (regex pattern)
--filter<EXPR>–0.3.0Filter DSL expression OR a diagnostic alias name (codec-asym, late-media, etc.) — see filter-dsl.md. A value may be bare or quoted: method == INVITE and method == 'INVITE' are the same filter. The expression may use a named filter from [filters] as @name. Config: [filter] expression
--since<RFC3339>–0.5.214Keep only dialogs whose first message is at or after this instant (2026-10-09T10:00:00Z). The window is half-open, [--since, --until): see Scope a run to a time window, and the recipe Narrow a capture to when the problem happened. ANDed with --filter. Needs -N
--until<RFC3339>–0.5.214Keep only dialogs whose first message is before this instant; a dialog at exactly --until belongs to the next window. Must be after --since. ANDed with --filter. Needs -N

Examples

  • sipnab -N -I capture.pcap --match "alice@example.com" --ignore-case — show every dialog that mentions alice@example.com, case-insensitively (dialog-following payload match)
  • sipnab -N -I capture.pcap --match "486 Busy Here" --word --single-line — whole-word match for 486 rejections, preventing . from spanning header lines
  • sudo sipnab -d eth0 --match "REGISTER" --invert — live view of everything except REGISTER traffic (inverted match)
  • sudo sipnab -d eth0 --ua "friendly-scanner" --contact "203\.0\.113\." --ignore-case — flag scanner traffic live: a known scanner User-Agent (any case) with a Contact pointing into 203.0.113.0/24
  • sipnab -N -I capture.pcap --ua "sipcli" --contact "192\.0\.2\." --single-line — filter a pcap by User-Agent and a Contact in 192.0.2.0/24, restricting payload regex dots to a single line
  • sipnab -N -I capture.pcap --match "OPTIONS" --word --invert — suppress keep-alive noise: show messages that do not contain the whole word OPTIONS
  • sipnab -N -I capture.pcap --report --since 2026-10-09T10:00:00Z --until 2026-10-09T11:00:00Z — report only the calls that started between 10:00 and 11:00 UTC

Scope a run to a time window

--since and --until keep the dialogs whose first message falls in the half-open window [--since, --until).

  • A dialog at exactly --since is in.
  • A dialog at exactly --until is in the next window.
  • So adjacent windows never count a call twice.
  • Either bound alone leaves the other side open.
  • Values are RFC 3339 instants. sipnab converts an offset other than Z to UTC.

The window is the one every surface applies, under three spellings:

SurfaceSpelling
CLI--since <RFC3339> / --until <RFC3339>
RESTGET /v1/dialogs?after=<RFC3339>&before=<RFC3339>
MCPsearch_by_time with start / end
Filter DSLtime >= '<RFC3339>' AND time < '<RFC3339>'

The CLI cannot use REST’s names. -A/--after already sets the context lines printed after a match.

The window joins --filter and the diagnostic alias flags in one filter. So these all see the same dialogs:

  • --report,
  • --json-dialogs,
  • --analyze,
  • the per-message stream,
  • --export-vcon-when.

--call-report and --export-vcon name one call by Call-ID and are not narrowed.

A window may hold none of the capture’s dialogs. The run then exits 0 with empty listings, and says so on stderr, naming the window and the span the capture’s dialogs cover:

--since/--until: no dialog's first message falls in [2030-01-01T00:00:00Z, unbounded); this capture's dialogs begin between 2023-11-14T22:13:20Z and 2023-11-14T22:13:20Z

These exit 2:

  • an --until that is not after --since,
  • a value that is not RFC 3339.
--until 2023-11-14T22:13:20Z is not after --since 2023-11-14T23:00:00Z: the window [--since, --until) would hold no instant

The flags need -N. In the TUI, set the time range in the call list’s filter dialog.

Examples

  • sipnab -N -I capture.pcap --json-dialogs --no-cli-print --since 2026-10-09T10:00:00Z --until 2026-10-09T11:00:00Z — one JSON line per call that started between 10:00 and 11:00 UTC
  • sipnab -N -I capture.pcap --report --filter "state == 'Failed'" --since 2026-10-09T10:00:00Z — the failed calls that started at or after 10:00 UTC

Name resolution

FlagValueDefaultSinceDescription
--resolve–off0.4.3Turn name resolution on (manual mappings + /etc/hosts). In the TUI, press n to cycle Off / Static / DNS; in headless -O --pcapng export it embeds a Name Resolution Block. Config: [names] enabled
--no-resolve–off0.5.204Show addresses, not names, whatever else would turn name resolution on: [names] enabled, --names, --reverse-dns, [names] hosts_file or [names.manual]. Also stops reverse-DNS lookups. The last of --resolve / --no-resolve typed wins. Config: [names] enabled
--reverse-dns–off0.4.3Also use reverse DNS (PTR) lookups. Implies --resolve. Emits DNS queries for captured IPs. Config: [names] reverse_dns
--no-reverse-dns–off0.5.204Do not make reverse-DNS lookups even when [names] reverse_dns = true. The last of --reverse-dns / --no-reverse-dns typed wins. Config: [names] reverse_dns
--dns-cache-entries<N>40960.5.100Reverse-DNS results held at once. Past the cap sipnab drops the oldest entry, so a capture touching more hosts than this – a carrier edge, a peering point, or any long --reverse-dns window – keeps re-looking-up addresses it already resolved. Nothing reports that: a dropped lookup only shows as an address displayed unresolved, so the symptom is names that flicker. The worker queue’s depth follows this figure; sipnab derives it rather than taking a second number. Config: [names] dns_cache_entries
--names<FILE>–0.4.3Preload IP → name mappings from an /etc/hosts-format file. Repeatable. Config: [names] hosts_file

See the Name Resolution keys for in-TUI naming (N) and persistence.

Examples

  • sipnab -N -I capture.pcap --no-reverse-dns — make no DNS queries when your config file sets [names] reverse_dns = true

  • sipnab -N -I capture.pcap --reverse-dns --no-reverse-dns — the last of the pair wins, so sipnab makes no lookups

  • sipnab -N -I capture.pcap --no-resolve — show raw addresses when your config file turns names on, through [names] enabled, a hosts file or [names.manual]

  • sipnab -N -I capture.pcap --resolve --no-resolve — the last of the pair wins, so addresses print as addresses

  • sudo sipnab -d eth0 --resolve --names /etc/sipnab/hosts.map — live capture with name resolution from a static hosts-format mapping file

  • sipnab -N -I capture.pcap --resolve --names /etc/sipnab/hosts.map --names ~/.config/sipnab/lab-names -O named.pcapng --pcapng --no-cli-print — write a copy of an offline pcap whose Name Resolution Block carries the names from two mapping files and /etc/hosts; Wireshark and tshark -q -z hosts show them. When both files name one address, the later file wins. A headless run prints addresses, not names: the names go only into the pcapng file

  • sudo sipnab -d eth0 --reverse-dns — live capture that also resolves captured IPs via reverse DNS (PTR) lookups

  • sipnab -N -I capture.pcap --reverse-dns --names ~/.config/sipnab/lab-names -O named.pcapng --pcapng --no-cli-print — write a copy of an offline pcap whose Name Resolution Block carries names from reverse DNS and a local mapping file

  • sudo sipnab -N -d eth0 --reverse-dns --dns-cache-entries 65536 — a peering point or carrier edge, where a capture touches far more than four thousand hosts: the wider cache stops sipnab re-resolving addresses it already knows, and the worker queue widens with it

  • sipnab -N -I lab.pcap --reverse-dns --dns-cache-entries 256 -O lab-named.pcapng --pcapng --no-cli-print — the opposite, for a small lab capture on a memory-tight box, where a few hundred entries cover every host in the file

pcapng metadata

FlagValueDefaultSinceDescription
--strip-secrets<OUTPUT>–0.4.3With -I <input>, write a copy of the input pcapng to <OUTPUT> with all Decryption Secrets Blocks removed (the editcap --discard-all-secrets analog), then exit. sipnab never touches the input and writes the output atomically. Steps: Share a capture without its keys.
--show-frame<POINTER>–0.5.77Resolve a frame pointer from a previous run, print that frame, then exit. Takes <source>#<ordinal> or <source>#<ordinal>@<digest> — the form the frame field of --json-dialogs, --report, the REST API and MCP carries. With a digest, sipnab checks the bytes against it and refuses a capture that changed after sipnab minted the pointer, writing nothing to stdout. Without one, sipnab prints the frame and marks it UNVERIFIED.

Note: with resolution active, sipnab saves name mappings into a pcapng Name Resolution Block.

  • This happens on both the TUI save path and the headless -O --pcapng export (whenever --resolve/--names apply).
  • Headless pcapng exports also describe themselves:
    • the Section Header Block records the producing application (sipnab <version>) and OS,
    • the Interface Description Block records the capture source as the interface name.
  • Opening a pcapng reads embedded NRB names and DSB TLS secrets back. sipnab decrypts with them.
  • See the design doc.

Examples

  • sipnab --show-frame 'capture.pcap#41@6f3a1c02b8d4e795' — print the frame a dialog opened in, verifying the capture has not changed since
  • sipnab --show-frame 'capture.pcap#41' — same frame, printed as UNVERIFIED because the short form carries nothing to check against
  • sipnab -N -I capture.pcapng --strip-secrets clean.pcapng — write a sanitized copy of a pcapng with every Decryption Secrets Block removed
  • sipnab -N -I tls-call.pcapng --strip-secrets tls-call-clean.pcapng — strip embedded TLS secrets from a decrypted-session capture before sharing it in a support ticket

Operator notes

A note is a remark a person writes about one SIP message. It travels to the person who opens the capture next.

  • sipnab writes each note into a pcapng packet comment. Wireshark shows it beside the frame.
  • sipnab never reads a packet comment back. A note is output, never part of the analysis.
FlagValueDefaultSinceDescription
--notes<FILE>–0.5.185A JSON Lines file of notes, one {"frame": "<pointer>", "note": "<text>"} per line. With --write-annotated, the notes to write into the copy; take each pointer from the frame field of --json. In the TUI, the session’s notes file: sipnab loads it at start when it exists, and the save dialog’s NOTES format writes there (C on a message types a note; see keybindings). In any other run sipnab refuses the flag, because nothing would read it. sipnab refuses a note over 4096 bytes, or one holding a control character other than newline and tab, an SDES inline: key, a TLS key-log line or a digest response= value, and one bad line refuses the whole file. The format is in output formats.
--write-annotated<OUTPUT>–0.5.185With -I <capture> and --notes, write a pcapng copy of that one capture to <OUTPUT> with each note as a packet comment on the frame it names, then exit. sipnab never touches the input. Every note must name its frame with a digest (#<ordinal>@<digest>), and the frame’s bytes must still match it: when the capture changed after someone wrote the note, sipnab refuses, exits 1, and writes nothing. The copy carries the original frames byte for byte, drops decryption secrets, name resolution blocks and the input’s own comments, and says so in its section comment. sipnab refuses a .pcap or .cap output, because classic pcap has no field for a comment.

Examples

  • sipnab -N -I call.pcap --notes call.notes.jsonl --write-annotated call-annotated.pcapng — hand a carrier the call with your notes on the frames they are about, readable in Wireshark
  • sipnab -N -I ticket-4711.pcapng --notes ticket-4711.notes.jsonl --write-annotated ticket-4711-for-vendor.pcapng — annotate a copy of a pcapng from a ticket without touching the original, which keeps its decryption secrets and the copy does not
  • sipnab -I call.pcap --notes call.notes.jsonl — open the call in the TUI with the notes you saved last time, and save new ones back to the same file

Diagnostic aliases

Shortcut flags that expand to predefined filter DSL expressions. See filter-dsl.md for the exact expansion of each alias.

FlagValueDefaultSinceDescription
--problems–off0.3.0Show calls matching any diagnostic signal: failed state, one-way audio, RTP loss past --loss-bad-pct (5% by default), jitter past --jitter-bad-ms (50 ms by default), NAT mismatch, more than 3 retransmits, post-dial delay past --pdd-threshold (11 s by default), codec/ptime/payload/duration asymmetry, or late media. Every one of those thresholds tracks the run’s own setting, so a network that tuned [diagnosis] or [quality] to its own SLA gets an alias that agrees with the rest of the run — see Named Aliases for the exact expansion. Orphaned RTP is not among them: an orphaned stream belongs to no dialog, so it cannot select one. Find it in the “Orphaned Streams” section of --report, or /v1/streams?orphaned=true
--slow-setup–off0.3.0Show calls whose post-dial delay passes --pdd-threshold (11 seconds by default, from [diagnosis] post_dial_delay_secs)
--short-calls–off0.3.0Show completed calls shorter than --fraud-short-call (3 seconds by default, from [security] fraud_short_call_secs)
--one-way–off0.3.0Show calls with potential one-way audio issues
--nat-issues–off0.3.0Show calls whose RTP arrived from an address no SDP advertised (NAT-rewritten media source)

Multiple diagnostic flags combine with OR.

  • Adding --filter ANDs its expression with that selection.
  • For example, --problems --filter "from.user == '1001'" selects only problem calls from user 1001.

Examples

  • sipnab -N -I capture.pcap --short-calls --one-way — flag completed calls under 3 seconds and calls with suspected one-way audio in a capture
  • sudo sipnab -d eth0 -N --one-way --nat-issues — live-monitor for one-way audio and NAT-rewritten media sources
  • sipnab -N -I capture.pcap --short-calls --report — summarize short completed calls from a capture in a post-run report

Output

FlagValueDefaultSinceDescription
--json–off0.3.0Output as NDJSON (one JSON object per line, schema in output-formats.md). Requires -N
--json-pretty–off0.3.0Output each message as pretty-printed multi-line JSON (use --json for line-oriented NDJSON). Requires -N
--json-dialogs–off0.5.68NDJSON, one object per dialog, emitted after capture (needs -N; pair with --no-cli-print to get only the objects). --json is per message: a dialog filter such as state == 'Failed' selects dialogs and then emits every message of them, provisional responses included. This is the per-call shape, carrying final_status_code and final_status_reason so a failed call says which code failed it — those two are the final answer to the dialog’s INVITE, or to the REGISTER/OPTIONS/SUBSCRIBE/PUBLISH that opened a dialog without one, and are null while it has none.
--plugin<PATH>–0.5.69Load a WASM plugin that contributes its own dialog detections; repeatable. Findings appear under plugin_findings. Requires the plugins Cargo feature (not in the default set). A plugin runs with no imports — no filesystem, network or clock — but still sees each message’s headers, so loading one is a trust decision. See wasm-plugin-api.md
--report–off0.3.0Generate summary report after capture completes. Requires -N. When the inputs include stored vCon containers, the report adds one row per container, read or skipped with the reason, and their totals; with --json or --json-pretty, as one JSON object under the key sipnab_vcon_containers. See Read many containers at once
--call-report<CALL-ID>–0.3.0Generate a detailed report for a specific Call-ID. Implies non-interactive
--export-vcon<CALL-ID>–0.5.125Export one dialog as an observer vCon container (draft-ietf-vcon-vcon-core, syntax 0.4.0): unsigned, with each party’s name taken only from a From or To display name and marked validation: "none", with the audio this run retained inline up to --vcon-max-inline-media, and with what the capture missed attached. Goes to stdout unless --vcon-out names a file, and then silences the per-message stream so the container stays parseable. Implies non-interactive. On a live capture (-d or --hep-listen), sipnab writes the container when that call ends, about ten seconds after its last message, while the capture runs. A stopped live run writes nothing, so stopping sipnab leaves no call data behind. With -I, sipnab writes it at the end of the run. A live run refuses --redact and --redact-map, which only write at the end of a run; use them with -I. Needs the vcon Cargo feature, which full has and the default set does not. More: Export a call as a vCon
--export-vcon-when<EXPR>–0.5.127Emit a vCon for every dialog matching this filter expression, one container per dialog, into --export-vcon-dir. EXPR is the language --filter already speaks (docs/filter-dsl.md), so state == 'Failed' and response_code >= 400 and rtp.codec == 'PCMU' work without new syntax. sipnab parses the expression before the capture opens, so a malformed one fails the run rather than leaving an empty directory. Conflicts with --export-vcon, which answers the same question for one named call. Implies non-interactive, and unlike --export-vcon it leaves stdout alone, because the containers go to a directory. On a live capture (-d or --hep-listen), sipnab writes each matching call’s container while the capture runs, about ten seconds after the call ends. A stopped live run writes nothing more, so stopping sipnab leaves no call data behind. With -I, sipnab writes every container at the end of the run. A live run refuses --redact, --redact-map and --content-deny-tombstone, which only write at the end of a run; use them with -I. Needs the vcon Cargo feature
--export-vcon-dir<DIR>–0.5.127Directory for the containers --export-vcon-when produces, created if absent. Each file takes its Call-ID as a name, with every character outside A-Za-z0-9._- becoming _ and the whole truncated. Requires --export-vcon-when
--vcon-max-inline-media<MIB>50.5.128Largest inline media body a vCon may carry, in MiB. Raise it when you know the store that reads your containers accepts larger ones: some stores answer success for a container above their own limit and then drop its payload. 0 refuses every inline body without turning the exporter off, and the refusal still appears in the completeness caveat. Batch export, the REST server and the MCP server all read this one value. Needs the vcon Cargo feature
--content-deny-header<NAME>–0.5.127Suppress content for any dialog carrying this header. No default: sipnab suppresses nothing until you name a header. The header’s PRESENCE suppresses and its value plays no part, so Privacy: none suppresses exactly as Privacy: id does. Matched case-insensitively, as SIP header names are (RFC 3261 7.3.1). Deny only: no header makes sipnab keep content it would otherwise leave out. Needs the vcon Cargo feature
--content-deny-tombstone–off0.5.128Write an identity-only container for each dialog --content-deny-header suppressed, instead of nothing. Requires --content-deny-header. The container carries the dialog’s identity and a redacted object declaring type alone, because no fuller version of the container exists anywhere to point at. It carries no message trace, no media and no bodies. Off by default. A tombstone reveals that the call EXISTED, so leave it off when the header means “this call must leave no trace”. Needs the vcon Cargo feature
--vcon-out<PATH>stdout0.5.125Write the --export-vcon container to this path instead of stdout. Requires --export-vcon. Refuses a path that names a capture this run reads, or that lies inside a directory this run reads, before opening any writer. A write that fails exits non-zero. Leaves the per-message stream alone, since the container no longer shares stdout with it. Pair with --no-cli-print for a silent run
--vcon-digest–off0.5.128Print a SHA-256 of every container --export-vcon-when writes into --export-vcon-dir, in sha256sum format, on stdout while the progress summary stays on stderr. Each line names the bare file name, so sha256sum -c run inside that directory verifies it. It prints nothing for --export-vcon, with or without --vcon-out. Implies --no-cli-print, so no message line shares stdout with the digests. Prints to stdout, so it needs -N and --mcp refuses it. Needs the vcon Cargo feature
--redact–off0.5.130Replace identities, addresses, hostnames and correlation identifiers in an exported vCon with keyed pseudonyms. Every identity becomes a keyed token that is equal exactly when the original was equal, and every address goes through a prefix-preserving map, so a reader can still tell that forty failures came from one subscriber, or that media went to a subnet the SDP never advertised. sipnab deletes digest credentials and inline audio rather than tokenizing them. Rewrites the serialized container only: the TUI, the reports and every in-process analysis keep the real values. Refused when the run exports no container. Needs the vcon Cargo feature
--redact-key-file<FILE>fresh key0.5.130Read the redaction secret from this file instead of drawing a fresh one from the operating system. Without it no token joins against any other export and nothing can reverse them; supply a file when the same subscriber must carry the same token across exports. The whole file is the secret, trailing newline included. Requires --redact
--redact-keep-prefix<N>00.5.130Keep this many leading digits of a number verbatim, so route and NPA analysis survives. Every retained digit is a digit of a real subscriber number in the clear. A country code is one to three digits and an NANP area code is three, so choose the count for your numbering plan. Requires --redact
--redact-map<FILE>–0.5.130Write the token-to-original table to this file, mode 0600, set at creation rather than by a chmod afterwards. It reverses every pseudonym the run produced, so it is as sensitive as the capture it came from. Refuses to write over an existing file. Requires --redact
--markdown–off0.3.0Format report output as Markdown
--stun–off0.5.112Report the STUN and TURN activity in the capture, and what it achieved: one row per transaction (for example Binding, Allocate, Refresh, CreatePermission and ChannelBind) naming the method, how many requests it took, whether anything answered, and the reflexive or relayed address the server reported back. An unanswered Binding Request means the client never learned its public address, so it advertised its private one in its SDP and media to it does not arrive. A TURN allocations section appears when a relay is in the capture, with the lifetime that decides when each one lapses. Columns for the relayed address, the ICE role and a FAILED FINGERPRINT appear only when the capture holds one. On a capture holding ICE connectivity checks an ICE section names the candidate pair the agents nominated, which is the path the media took, and any pair where both agents claimed the same role or answered 487 Role Conflict. Where a relay carried media, the allocations section says which channel carried which SSRC, so a reader can trace a relayed stream in the stream list back to the allocation that carried it. Silent when the capture holds no STUN and no TURN. Requires -N
--json-stun–off0.5.112NDJSON, one object per STUN/TURN transaction and per TURN allocation, emitted after capture. Each carries a record field (transaction, turn_allocation or ice) so a consumer never infers the kind from which keys are present; an allocation also carries the derived lapsed and the channels that attribute relayed media to it, and the single ice record carries the checks, the nominated pairs and any role conflicts. The machine-readable form of --stun. Requires -N
--analyze–off0.5.112Analyze the capture and print every problem in it, worst first — one ranked list instead of one dialog at a time. It aggregates the diagnosis sipnab already computes (one-way audio, media that never arrived, an SDP address STUN contradicts, ICMP unreachables, failed and unacknowledged calls, codec and framing asymmetry) plus capture-level evidence, counted exactly and evidenced with Call-IDs, addresses, timestamps and packet counts. It derives nothing new. What sipnab did NOT read — undecodable frames, SIP a port gate discarded, records a retention cap dropped — appears at the top, because those make every count below them a floor, and a capture that did not fully decode never gets a clean verdict. Requires -N
--json-analyze–off0.5.112The --analyze result as one JSON object, emitted after capture: the findings, plus the frames read and the dialogs examined, so a clean capture still prints an object. Requires -N
--yang-analyze–off0.5.185The --analyze result RFC 7951-encoded: the same analysis --json-analyze prints, as one JSON document that validates against the sipnab-diagnosis YANG module. 64-bit integers are strings, names use hyphens, and each finding carries its rank. Asked for with --json-analyze, both come from one analysis. Requires -N
--print-yang-module–off0.5.185Print the sipnab-diagnosis YANG 1.1 module and exit: the model the RFC 7951 export of the capture analysis validates against, byte for byte the file committed under yang/. Needs no capture, config or privileges
--hexdump–off0.3.0Include hex dump of SIP payloads. Requires -N
--delta-time–off0.3.0Show delta time between consecutive messages. Config: [display] delta_time
--no-delta-time–off0.5.204Show absolute times even when [display] delta_time = true. The last of --delta-time / --no-delta-time typed wins. Config: [display] delta_time
-A, --after<N>–0.3.0Show N messages after each match (like grep -A)
--show-empty (--full)–off0.3.0Show the full header block of bodyless messages (responses, OPTIONS, REGISTER, ACK, BYE); by default they show only the summary line
--proto-number–off0.5.10Annotate the transport tag with the IANA IP protocol number, e.g. UDP(17) / TCP(6). Long-only because -N is --no-tui here; TLS, WS and WSS report their TCP carrier’s number (6)
--line-buffer–off0.3.0Flush output after each line (useful for piping)
--color<WHEN>auto0.3.0Color output mode: auto, always, never. Config: [display] color
--from-to-mode<MODE>default0.4.4Default TUI From/To column display: default (user else host:port), host-port, user, user-host-port. Cycle at runtime with u. Overrides [display] from_to. Config: [display] from_to
--payload-limit<BYTES>–0.3.0Maximum payload bytes the per-message text printout shows for each message, with --show-empty and --hexdump too. It does not shorten --json, --json-pretty or -T/--text-dump output. Config: [display] payload_limit
-T, --text-dump–off0.3.0Dump raw SIP message text (like the CLI matcher -T)
--no-cli-print–off0.3.2Suppress per-message CLI output (useful with --report / --call-report so only the post-capture summary reaches stdout)
--wireshark–off0.3.0Print a display filter for the current capture to paste into Wireshark; use with -N
--tshark-filter<EXPR>–0.3.0Generate a tshark-compatible display filter string
--fail2ban–off0.3.0Switch the per-message stream to fail2ban-readable log lines. Requires -N. It selects a format, not a detection: only two events ever reach it, and each needs its own detector armed beside it — --kill-scanner (with --kill-ua to add User-Agents it matches) produces scanner_detected, --reg-flood produces reg_flood. On its own it emits nothing, and warns on stderr about the coming silence, because an empty jail log reads as “nothing attacked me”. Detections carried by HEP input (--hep-listen, --hep-parse) never reach it without --hep-allow-kill: the inner addresses are the sender’s claim, a jail line would ban whatever address the sender chose, and the run says so once at startup
--lint–off0.5.84Run the RFC conformance linter over every dialog and print each finding with its rule identifier and the RFC section it reads from. The linter compares what the capture holds against what the cited section calls for, so a finding names a section rather than an opinion. Informational on its own: it changes what gets printed and never the exit code. Pair it with --lint-fail-on to make a pipeline stop. Prints to stdout, so it needs -N and --mcp refuses it. The rule catalog is in sip-lint-rules.md, and over MCP as explain_rule
--lint-fail-on<SEVERITY>–0.5.84Exit 3 when the linter reports a finding at or above this severity: info, notice, warning (or warn), or error. Any other value exits 2 and names the four. Needs --lint. A pipeline can tell a runtime failure (1) and a wrong invocation (2) apart from a non-conformant capture (3). info, the lowest severity, trips on any finding
--group-by<FIELD>–0.3.0Group output by field (e.g., call-id, from, method)
--max-groups<N>1000000.5.97Distinct --group-by keys one run retains. Past it sipnab refuses new keys and warns that the output is incomplete. -l/--limit bounds tracked dialogs, not this buffer. Requires --group-by. Config: [limits] max_groups
--max-grouped-messages<N>2000000.5.97Messages --group-by buffers across every group. sipnab holds them in memory until the capture ends, because the last packet may belong to the first group. Requires --group-by. Config: [limits] max_grouped_messages
--node-name<NAME>hostname0.5.81Name this box reports as, in capture_identity.node on every MCP and REST answer about the loaded capture. The exceptions are MCP capture_health, the hep_senders tool and its REST route, a yang-json report body, and a vCon container served as the body of GET /v1/dialogs/{call_id}/vcon; MCP across an estate and the REST reference list each answer. Lets an agent querying several servers at once tell which one saw a given fact. Distinct from the capture instance, which changes when a different capture loads; the node name stays the same. The default puts your hostname on the wire. Clipped to 64 characters. sipnab refuses an empty value (exit 2). Config: [capture] node_name

Examples

  • sipnab -N -I capture.pcap --no-delta-time — print absolute times when your config file sets [display] delta_time = true

  • sipnab -N -I capture.pcap --delta-time --no-delta-time — the last of the pair wins, so times print absolute

  • sipnab -N -I calls.pcap --lint --no-cli-print — run the RFC conformance linter over every dialog in a capture and print each finding with the rule identifier and the RFC section it reads from. Informational: it leaves the exit code alone

  • sipnab -N -I calls.pcap --export-vcon-when "state == 'Failed'" --export-vcon-dir out --redact --redact-keep-prefix 4 --no-cli-print — hand a vendor every failed call with keyed tokens standing in for the subscriber numbers and the area code intact, so they can still see which route the failures came from

  • sipnab -N -I calls.pcap --export-vcon call-1 --vcon-out out.vcon --redact --redact-key-file team.key --redact-map out.map --no-cli-print — a stable key, so today’s tokens join against last week’s, and the reversal table written beside the container at 0600

  • sipnab -N -I calls.pcap --export-vcon-when "state == 'Completed'" --export-vcon-dir out --redact --redact-keep-prefix 3 --redact-map out.map --no-cli-print — export every completed call, keep the NANP area code readable so a route analysis still works, and write the reversal table beside the export. Three digits is a choice about a real subscriber number, not a default sipnab can make for you: a country code is one to three digits and a national destination code is anything

  • sipnab -N -I calls.pcap --export-vcon-when "state == 'Failed'" --export-vcon-dir out --redact --redact-key-file team.key --redact-keep-prefix 0 --no-cli-print — the shape to send outside the team, here for every failed call. A shared key still joins a subscriber across exports, no digits survive in the clear, and with no --redact-map written nothing anywhere reverses the tokens

  • sipnab -N -I calls.pcap --lint --lint-fail-on error --no-cli-print — the CI gate. Exits 3 when any finding is at or above error, so a pipeline stops on a non-conformant capture. Exit 3 is not 1 or 2, so a failing gate is distinguishable from a failing tool and from a bad invocation

  • sipnab -N -I calls.pcap --lint --lint-fail-on warning --no-cli-print — a stricter gate: stop on warnings as well as errors, for a pipeline that treats interop degradation as a build failure rather than a note

  • sipnab -N -I capture.pcap --json-dialogs --no-cli-print --plugin ./short-calls.wasm — run a custom detection over every dialog and emit its findings beside sipnab’s own

  • sudo sipnab -d eth0 -N --json-dialogs --no-cli-print --plugin ./site-rules.wasm --plugin ./fraud.wasm — stack two site-specific detections over live traffic. Each plugin is sandboxed and a failure in one never stops the capture

  • sipnab -N -I capture.pcap --stun --no-cli-print — read what NAT traversal actually achieved: which probes drew an answer, what public address came back, and which drew nothing. A capture holding only failed STUN is not an empty capture, it is the cause of a one-way-audio complaint

  • sipnab -N -I relay.pcap --stun --markdown --no-cli-print — the same tables as Markdown, for pasting into a ticket. On a capture that went through a TURN relay this adds the allocations section, where a LAPSED status means the relay tore the allocation down while media was still crossing it

  • sipnab -N -I capture.pcap --json-stun --no-cli-print | jq 'select(.record == "transaction" and .responded_at == null)' — every transaction nothing answered, as JSON

  • sipnab -N -I relay.pcap --json-stun --no-cli-print | jq 'select(.lapsed == true)' — the relay allocations that ran out under live media, which no SIP message anywhere reports

  • sipnab -N -I capture.pcap --analyze --no-cli-print — one ranked list of everything wrong with the file, worst first, with the evidence for each finding

  • sipnab -N -I capture.pcap --analyze --filter "state == 'Failed'" --no-cli-print — narrow the DIALOG findings to failed calls. The capture-level evidence (undecodable frames, discarded ports, dropped records) is deliberately not narrowed, because it bounds every count in the report

  • sipnab -N -I capture.pcap --json-analyze --no-cli-print | jq '.findings[] | select(.severity == "critical")' — the critical findings only, for a pipeline

  • sipnab -N -I capture.pcap --json-analyze --no-cli-print | jq '.complete' — whether the capture decoded fully. false means every count in the analysis is a floor, so do not trust a clean-looking verdict

  • sipnab -N -I capture.pcap --yang-analyze --no-cli-print > analysis.json — the capture analysis as a YANG instance document. yanglint -t data sipnab-diagnosis@2026-10-09.yang analysis.json validates it

  • sipnab -N -I capture.pcap --json-analyze --yang-analyze --no-cli-print — both encodings of ONE analysis, one line each: the same findings, the same counts, the same rank order

  • sipnab --print-yang-module > sipnab-diagnosis@2026-10-09.yang — save the YANG module this build’s RFC 7951 export validates against, under the file name RFC 7950 gives it, for yanglint or pyang

  • sipnab --print-yang-module | pyang -f tree — the shape of the capture analysis as a YANG tree: every node, its type, and which are lists

  • sipnab -N -I capture.pcap --json-dialogs --no-cli-print --quiet | jq -c 'select(.state == "Failed")' — one line per failed call, each carrying the code that failed it, instead of every message of every failed dialog

  • sudo sipnab -d eth0 -N --json-dialogs --no-cli-print --line-buffer > calls.ndjson — record one summary object per call from live traffic, flushed per line for a downstream collector

  • sudo sipnab -N -d eth0 --node-name sbc-edge-1 --mcp --mcp-transport http — one node of a federated setup, naming itself so an agent can attribute each answer to this box rather than another

  • sudo sipnab -N -d eth0 --node-name pbx-core-2 --report — override the hostname on a box whose real name should not travel, while still labeling the capture

  • sipnab -N -I capture.pcap --json-pretty > messages.json — export every SIP message from a capture as pretty-printed JSON, each payload whole

  • sipnab -N -I capture.pcap --show-empty --payload-limit 200 — print every message with its headers, each payload cut at 200 bytes

  • sudo sipnab -d eth0 -N --json-pretty --group-by method --line-buffer > live.json — stream live SIP traffic as pretty-printed JSON grouped by method, flushing after each line for downstream tooling

  • sipnab -N -I capture.pcap --text-dump --hexdump --proto-number --color never — dump raw SIP text with hex payloads and IANA protocol numbers, uncolored for log archiving

  • sudo sipnab -d eth0 -N --match REGISTER --after 2 --text-dump --line-buffer --color always — follow live REGISTER traffic in real time, printing raw text plus 2 messages of context after each match

  • sipnab -N -I capture.pcap --show-empty --delta-time --hexdump --group-by call-id — review a capture with per-message delta times, empty-bodied messages included, and hex dumps grouped per call

  • sudo sipnab -d eth0 -N --match OPTIONS --after 5 --show-empty --proto-number --payload-limit 256 — inspect OPTIONS keepalives with 5 messages of trailing context, empty bodies shown, and display capped at 256 payload bytes

  • sudo sipnab -N -d eth0 --from-to-mode host-port --wireshark — capture live with host:port From/To columns and print a Wireshark display filter when capture ends

  • sipnab -I capture.pcap --from-to-mode user-host-port — browse an existing capture in the TUI with full user@host:port From/To columns

  • sipnab -N -I busy-day.pcap --group-by call-id --max-groups 250000 --no-cli-print — group a capture holding more calls than the shipped 100000-key cap, instead of taking the first hundred thousand and a warning naming how many keys sipnab turned away

  • sipnab -N -I busy-day.pcap --group-by from --max-grouped-messages 2000000 --json — regroup a large capture by caller with room for every message, keeping the output one valid JSON object per line

  • sipnab -N -I untrusted.pcap --group-by call-id --max-groups 500 --max-grouped-messages 5000 --no-cli-print — group a capture from outside your network under a tight pair of caps, so an attacker-chosen Call-ID cannot buy more memory than you allowed

  • sipnab -N -I capture.pcap --export-vcon '1-1966@10.0.2.20' > call.vcon – hand one observed dialog to a conversation-data pipeline as a vCon. The container names sipnab as an observer party and records what this capture missed

  • sipnab -N -I capture.pcap --export-vcon '1-1966@10.0.2.20' --vcon-out ./call.vcon --no-cli-print – the same container to a file, with the run otherwise silent. Use this form in a script: a path sipnab cannot write fails the run with a non-zero exit

  • sipnab -N -I capture.pcap --export-vcon-when "state == 'Failed'" --export-vcon-dir ./failed/ --content-deny-header X-No-Record – the same failures, minus any call whose signaling carries an X-No-Record header

  • sipnab -N -I ./captures --export-vcon-when 'duration > 30' --export-vcon-dir ./long/ --content-deny-header Privacy – leave out every call carrying a Privacy header, whatever its value: Privacy: none suppresses exactly as Privacy: id does

  • sipnab -N -I capture.pcap --export-vcon-when "state == 'Failed'" --export-vcon-dir ./failed/ – one container per failed dialog, without naming each Call-ID. A capture where nothing failed writes nothing and says so on stderr

  • sipnab -N -I ./captures --export-vcon-when "duration > 30 and rtp.codec == 'PCMU'" --export-vcon-dir ./long-g711/ --no-cli-print – the predicate is the filter language, so the conditions that narrow a report also choose which calls get a container

  • sipnab -N -I ./captures --export-vcon '1-1966@10.0.2.20' --vcon-out ./captures/1-1966.vcon – refused before anything opens a writer: --vcon-out may not name a path inside a directory this run reads, the same rule -O follows

  • sipnab -N -I capture.pcap --export-vcon '1-1966@10.0.2.20' | jq -r '.analysis[0].body.capture_completeness.note' – read the completeness note before the contents. The analysis body declares encoding: "json", so jq indexes into it directly. The note says what sipnab read and what it dropped, from this run’s own counters

  • sipnab -N -I ./captures --export-vcon-when 'duration > 30' --export-vcon-dir ./long/ --vcon-max-inline-media 64 – carry audio up to 64 MiB per container, for a store you know accepts containers that large

  • sipnab -N -I ./captures --export-vcon-when 'response_code >= 200' --export-vcon-dir ./calls/ --vcon-max-inline-media 0 --retain-audio – keep the containers and inline none of the audio. Each container’s completeness caveat records that sipnab refused the audio

  • sipnab -N -I ./captures --export-vcon-when "state == 'Failed'" --export-vcon-dir ./failed/ --content-deny-header X-No-Record --content-deny-tombstone – write an identity-only container for each denied dialog instead of nothing, so a consumer can tell a withheld call from one that never happened. It carries no message trace, no media and no bodies, and declares redacted with type alone

  • sipnab -N -I capture.pcap --export-vcon-when 'duration > 0' --export-vcon-dir ./out/ --content-deny-header Privacy --content-deny-tombstone – write a tombstone for every call carrying a Privacy header. A tombstone reveals that the call existed; leave the flag off when the header means the call must leave no trace

  • sipnab -N -I ./captures --export-vcon-when 'duration > 30' --export-vcon-dir ./long/ --vcon-digest > SHA256SUMS – record what sipnab wrote. The digests go to stdout in sha256sum format and the progress summary stays on stderr, so the redirect captures only the digests

  • sipnab -N -I capture.pcap --export-vcon-when 'duration > 0' --export-vcon-dir ./out/ --vcon-digest | tee ./out/SHA256SUMS – keep the digests beside the containers they describe. Each line names the bare file name, so the directory verifies wherever it is later moved

  • cd ./long/ && sha256sum -c ../SHA256SUMS – verify the containers later with the standard tool. The digest covers the bytes sipnab wrote, not the copy a store holds after it adds its own fields

  • sipnab -N -I capture.pcap --tshark-filter 'sip.Method == "INVITE"' — print a tshark-compatible display filter for the INVITE traffic in a capture. sipnab hands the expression to tshark’s -Y verbatim, so it takes WIRESHARK display-filter syntax rather than sipnab’s --filter DSL: sip.Method, not method. Quote the whole expression in single quotes so the inner double quotes survive the shell

Queries

A query flag answers one question about the capture after sipnab has read it, then exits. Answer questions about a capture shows which flag answers which question, and how to read each answer. Each one:

  • counts only the dialogs the run’s filter admits: --filter, the diagnostic aliases and --since/--until;
  • makes the run headless, as --call-report does, so -N is optional;
  • prints a table, or the same table in Markdown under --markdown;
  • under --json, prints one JSON object on the last line of stdout, with the shape the matching REST route returns. --json-pretty prints the same object indented.

Add --no-cli-print to leave out the per-message lines, and --quiet to leave out the end-of-run summary on stderr.

FlagValueDefaultSinceDescription
--timeline[=<SECONDS>]600.5.230Print how many dialogs opened in each interval of this many seconds, oldest first. Intervals align to the epoch, and an empty interval shows a count of zero. --json prints the shape of GET /v1/timeline. 0 exits 2. A span of more than 100,000 intervals exits 1
--count-by<DIMENSION>0.5.230Count the dialogs by one dimension, largest first: state, response_code, method, from.user, to.user, ua, src.ip, dst.ip or rtp.codec. --json prints the shape of GET /v1/aggregate. Any other dimension exits 2
--rates-by<DIMENSION>0.5.230Print the carrier metrics of each value of one dimension: ASR, NER, ACD, post-dial delay, the tenth-percentile MOS and the retransmit rate. Takes every --count-by dimension, plus to_domain, hour and next_hop. --json prints the shape of GET /v1/dialogs/rates. Any other dimension exits 2
--talkers<BY>0.5.230Rank the busiest participants, most dialogs first: by sending address (ip), User-Agent or Server banner (ua), or the dialed number’s leading digits (prefix). --json prints the shape of GET /v1/talkers. Any other value exits 2
--prefix-digits<N>40.5.230How many leading digits --talkers prefix ranks by. Needs --talkers; only prefix reads it. 0 exits 2
--call-tree<CALL-ID>0.5.231Print every leg of one call as a tree across B2BUA, SBC and PBX hops, with the strategy that matched each leg. --json prints the shape of GET /v1/dialogs/{call_id}/tree. A Call-ID the run did not track exits 1
--correlate<CALL-ID>0.5.231Print the other legs of one call and the strategy that matched each: an identifier the legs share, or a timing guess. --json prints the shape of GET /v1/dialogs/{call_id}/correlated. A Call-ID the run did not track exits 1
--compare-dialogs<CALL-ID-A> <CALL-ID-B>0.5.231Put two calls side by side: state, final response code, message count and methods, with the fields that differ marked, and each call’s diagnosis hints. --json prints the shape of GET /v1/dialogs/compare. A Call-ID the run did not track exits 1
--endpoint-ip<IP>0.5.231Print everything one address did: dialogs by method and state, messages sent and received, call outcomes, registration, banners, the signaling stack, media, and the most recent dialogs. --json prints the shape of GET /v1/endpoints. An address that does not parse exits 2
--endpoint-user<USER>0.5.231Print everything one SIP user did, matched against the user part of each dialog’s From and To: the --endpoint-ip rollup without the message counts. --json prints the shape of GET /v1/endpoints. Not with --endpoint-ip
--export-audio<CALL-ID>0.5.231Write one call’s decoded RTP audio to a WAV file, and print the export summary. Holds the call’s audio payload for the run, as --retain-audio does. A Call-ID the capture does not hold, a call with nothing to decode, or an existing file exits 1
--export-audio-out<FILE>the Call-ID with .wav, each character other than a letter, a digit, -, _ or . written as _0.5.231The file --export-audio writes. It must not exist. Needs --export-audio
--vcon-validate<FILE>0.5.231Check a vCon container file against sipnab’s vendored schema and print the verdict and every finding. Exits 0 when valid and 3 when invalid, and 1 when sipnab cannot read or check the file. Needs no -I. --json prints the shape of POST /v1/vcon/validate. Needs the vcon feature
--compare-captures<A> <B>0.5.231Compare two capture files: for each dimension, the dialogs per value in A (the baseline) and B, and the change, largest movement first. Needs no -I. --json prints the shape of GET /v1/captures/compare. The same file twice exits 2; a file that yields no dialogs exits 1
--compare-dimensions<DIMENSIONS>state,response_code0.5.231The dimensions --compare-captures diffs, comma-separated, from the --count-by set. Any other exits 2. Needs --compare-captures
--find-in-files<PATH>...0.5.231Find which capture files hold the dialogs the run’s filter selects (--filter, a diagnostic alias, --since/--until). A directory stands for the capture files directly in it. Exits 0 when it read every file and 1 when it could not; with no filter it exits 2. --json prints the sweep the MCP tool find_in_captures reports
--expect<FILE>0.5.231Judge the capture against the rules of an expectation suite (sipnab.expect.toml), print each rule’s verdict, and exit with the suite’s: 0 every rule that ran passed, 3 a rule failed, 2 no rule ran. A suite sipnab cannot read, or one holding a rule the evaluator refuses, exits 2 before sipnab reads the capture. --json prints the report the MCP tool evaluate_expectations returns. See Fail a build on what a capture shows
--relay-holdings–0.5.231Ask the relay named by --rtpengine-control which calls it holds right now, print their Call-IDs, and exit. Asking transmits one list request, so it needs a live source (-d or -L): on -I it asks nothing and exits 2. It runs before the capture opens and answers nothing else. Exits 0 with the list, and 1 when the relay did not answer (unreachable), declined (refused) or sent a reply sipnab cannot trust (suspect). --json prints the body of GET /v1/relay/holdings, those classifications included. See Ask the relay which calls it holds. Feature: native. How-to: Ask a media relay about itself
--relay-holdings-max<CALLS>320.5.231The most Call-IDs --relay-holdings asks the relay for, REST’s max_calls. When the relay returns that many it may hold more, and the answer says so (truncated under --json). Needs --relay-holdings; 0 exits 2
--top<N>500.5.230How many rows --count-by, --rates-by or --talkers keeps, or --compare-captures keeps per dimension, or how many recent dialogs --endpoint-ip or --endpoint-user lists. One line sums or counts the rest, as other_count or truncated does under --json. Needs one of those flags; 0 exits 2

Call volume over time

Count the calls that opened in each five-second interval:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --timeline=5 --no-cli-print --quiet
Call volume: 3 buckets of 5 s, 5 dialogs
Start                      Dialogs
----------------------------------
2023-11-14T22:13:20+00:00  2
2023-11-14T22:13:25+00:00  2
2023-11-14T22:13:30+00:00  1

Give the width after =. A bare --timeline uses 60-second intervals. An interval with no call in it shows a count of zero, so a gap in traffic shows as a row of zeros.

One histogram holds at most 100,000 intervals. When the calls span more intervals of the width than that, sipnab prints no histogram, writes on stderr how many intervals the span needs and a width that fits, and exits 1. Run it again with that width or a wider one.

Count only the failed calls:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --timeline=5 --no-cli-print --quiet --filter 'state == Failed'
Call volume: 3 buckets of 5 s, 4 dialogs
Start                      Dialogs
----------------------------------
2023-11-14T22:13:20+00:00  1
2023-11-14T22:13:25+00:00  2
2023-11-14T22:13:30+00:00  1

Read the answer as JSON. capture_identity names the run that produced it, so it differs from run to run. jq drops it here:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --timeline --json --no-cli-print --quiet | jq -c 'del(.capture_identity)'
{"schema_version":1,"buckets":[{"start":"2023-11-14T22:13:00+00:00","bucket_seconds":60,"dialogs":5}],"returned":1,"bucket_seconds":60}

The REST route GET /v1/timeline?bucket_seconds=60 and the MCP tool timeline answer the same question about a loaded capture.

Dialogs by one dimension

Count the calls in a capture by their state:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --count-by state --no-cli-print --quiet
Dialogs by state: 2 values, 5 dialogs
Value      Dialogs
------------------
Failed     4
Completed  1

The values are largest first, and values with the same count are in alphabetical order. A dialog with no value for the dimension counts under (none).

Keep only the first values with --top. The last line sums the rest:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --count-by response_code --top 2 --no-cli-print --quiet
Dialogs by response_code: 5 values, 5 dialogs
Value  Dialogs
--------------
200    1
404    1
Other: 3 dialogs in 3 values past --top 2

Count one dimension at a time. To count a subset of the calls, narrow the run with --filter instead of adding a second dimension. Count who the failed calls were for, keeping three:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --count-by to.user --filter 'state == Failed' --top 3 --no-cli-print --quiet
Dialogs by to.user: 4 values, 4 dialogs
Value  Dialogs
--------------
dave   1
frank  1
heidi  1
Other: 1 dialog in 1 value past --top 3

Read the answer as JSON. jq drops capture_identity, which names the run:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --count-by state --json --no-cli-print --quiet | jq -c 'del(.capture_identity)'
{"schema_version":1,"group_by":"state","buckets":[{"value":"Failed","count":4},{"value":"Completed","count":1}],"other_count":0,"distinct_values":2,"total_matched":5}

The REST route GET /v1/aggregate?by=state and the MCP tool aggregate_dialogs answer the same question about a loaded capture.

Carrier metrics by one dimension

Print the answer-seizure ratio (ASR), network effectiveness ratio (NER), average call duration (ACD), post-dial delay (PDD), the tenth-percentile MOS and the retransmit rate for each call state:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --rates-by state --no-cli-print --quiet
Carrier metrics by state: 2 values, 5 dialogs
Value      Dialogs  asr  ner  acd   pdd_p50  pdd_p95  mos_p10  retransmit_rate
------------------------------------------------------------------------------
Failed     4        0    50   -     -        -        -        0
Completed  1        100  100  17.9  850      850      -        0
Units: asr percent, ner percent, acd seconds, pdd_p50 milliseconds, pdd_p95 milliseconds, mos_p10 mos, retransmit_rate retransmissions per dialog
-: the group's population could not support the figure; --json names why in not_grounded
  • asr and ner are percentages of the INVITEs that reached a final response. NER also credits the outcomes the far end decided, such as busy and decline, so it can be higher than ASR.
  • acd is the mean conversation time of the calls answered and torn down inside the capture.
  • - means the group holds no call to compute the figure from. Here the far end answered none of the failed calls, so that group has no ACD.

--rates-by takes every --count-by dimension, plus to_domain, hour and next_hop. --top keeps the largest groups and sums the rest:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --rates-by response_code --top 2 --no-cli-print --quiet
Carrier metrics by response_code: 5 values, 5 dialogs
Value  Dialogs  asr  ner  acd   pdd_p50  pdd_p95  mos_p10  retransmit_rate
--------------------------------------------------------------------------
200    1        100  100  17.9  850      850      -        0
404    1        0    0    -     -        -        -        0
Other: 3 dialogs in 3 values past --top 2
Units: asr percent, ner percent, acd seconds, pdd_p50 milliseconds, pdd_p95 milliseconds, mos_p10 mos, retransmit_rate retransmissions per dialog
-: the group's population could not support the figure; --json names why in not_grounded

Under --json, each group carries its population, and not_grounded says why each null figure is missing:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --rates-by state --json --no-cli-print --quiet | jq -c '.groups[] | {value, asr: .metrics.asr, ner: .metrics.ner, acd_why: .not_grounded.acd}'
{"value":"Failed","asr":0.0,"ner":50.0,"acd_why":"no call in this group was both answered and torn down inside the capture, so no conversation was timed"}
{"value":"Completed","asr":100.0,"ner":100.0,"acd_why":null}

The REST route GET /v1/dialogs/rates?by=state and the MCP tool group_dialogs answer the same question about a loaded capture.

The busiest talkers

Rank the addresses that sent the most dialogs, keeping three:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --talkers ip --top 3 --no-cli-print --quiet
Talkers by ip: 10 talkers, 5 dialogs
Talker      Dialogs  Messages  INVITEs  Answered  Failed  Share%
----------------------------------------------------------------
192.0.2.20  1        4         1        1         0       20
192.0.2.10  1        3         1        1         0       20
192.0.2.30  1        2         1        0         1       20
Other: 7 talkers past --top 3

Among talkers with the same number of dialogs, the one with more messages comes first, then the lower key. Share% is the share of the counted dialogs each talker took part in. A dialog counts for every participant, so the shares can add up to more than 100.

Rank the dialed numbers by their first five digits:

sipnab -I tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap --talkers prefix --prefix-digits 5 --no-cli-print --quiet
Talkers by prefix: 2 talkers, 5 dialogs
Talker  Dialogs  Messages  INVITEs  Answered  Failed  Share%
------------------------------------------------------------
10009   3        10        0        0         0       60
10008   2        17        1        1         0       40

prefix uses four digits when --prefix-digits is not given. Four digits put both numbers under one prefix:

sipnab -I tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap --talkers prefix --prefix-digits 4 --no-cli-print --quiet
Talkers by prefix: 1 talker, 5 dialogs
Talker  Dialogs  Messages  INVITEs  Answered  Failed  Share%
------------------------------------------------------------
1000    5        27        1        1         0       100

Rank the software by its User-Agent or Server banner:

sipnab -I tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap --talkers ua --no-cli-print --quiet
Talkers by ua: 2 talkers, 5 dialogs
Talker                            Dialogs  Messages  INVITEs  Answered  Failed  Share%
--------------------------------------------------------------------------------------
X-Lite 4 release 4.0 stamp 58832  4        9         1        1         0       80
Asterisk PBX 1.6.0.6              2        4         1        1         0       40

The REST route GET /v1/talkers?by=ip and the MCP tool top_talkers answer the same question about a loaded capture.

Every leg of one call

A call through a B2BUA, an SBC or a PBX is several dialogs, one per hop. Name one of them to see the others as a tree:

sipnab -I tests/pcap-samples/b2bua-asterisk.pcapng --call-tree b2bua-caller-synth@203.0.113.1 --no-cli-print --quiet
Call tree from b2bua-caller-synth@203.0.113.1: 2 legs, depth 1, 28 messages, 1 timing guess
Leg                                   Strategy          Method  Score  Match  Followed  TH
------------------------------------------------------------------------------------------
b2bua-caller-synth@203.0.113.1        root              -       -      -      yes       -
  b2bua-leg-synth@203.0.113.101:5060  timing_heuristic  timing  50     guess  no        -
  • Each hop indents the leg two more spaces.
  • Match is identifier when the legs share an identifier (a header named in [sip] xcid_headers, Session-ID, the SDP origin, P-Charging-Vector or a Via branch, or a topology-hidden Call-ID that decodes to the other leg’s or that OpenSIPS named in an E_SIPNAB_TH_MAP event), and guess when only their timing ties them. Follow one call across its legs shows how to get an identifier match.
  • Method says how sipnab made the link: mapping (an OpenSIPS topology-hiding leg the OpenSIPS script named in an E_SIPNAB_TH_MAP event), decoded (an OpenSIPS topology-hiding Call-ID, decoded with the configured secret; see Calls through topology hiding), mapping (an OpenSIPS E_SIPNAB_TH_MAP event named both legs), header (a SIP header both legs carry: one named in [sip] xcid_headers, Session-ID, P-Charging-Vector or a Via branch), sdp (the SDP origin) or timing (the guess).
  • Followed is no on a guess: sipnab lists the leg but does not look for that leg’s own matches, so one wrong guess does not pull in unrelated calls.
  • TH is the leg’s own OpenSIPS topology-hiding status, - for a leg that is not hidden: decoded, no_secret, undecodable, unmatched, mapped or conflict. To see the mappings behind mapped and conflict, use --th-mappings.

The Call-ID must match exactly, and --report lists the ones the run holds. Under --json, each leg names its parent and depth:

sipnab -I tests/pcap-samples/b2bua-asterisk.pcapng --call-tree b2bua-caller-synth@203.0.113.1 --json --no-cli-print --quiet | jq -c '.legs[] | {call_id, depth, strategy, followed}'
{"call_id":"b2bua-caller-synth@203.0.113.1","depth":0,"strategy":null,"followed":true}
{"call_id":"b2bua-leg-synth@203.0.113.101:5060","depth":1,"strategy":"timing_heuristic","followed":false}

The REST route GET /v1/dialogs/{call_id}/tree and the MCP tool get_call_tree walk the same tree.

The other legs of one call

List the calls sipnab ties to one call, and how:

sipnab -I tests/pcap-samples/sip-rtp-g711.pcap --correlate 1-1966@10.0.2.20 --no-cli-print --quiet
Legs correlated with 1-1966@10.0.2.20: 1 leg
Leg               Strategy    Method  Score  Match       Gap ms  TH
-------------------------------------------------------------------
1-1968@10.0.2.20  sdp_origin  sdp     90     identifier  -       -

The two calls carry the same SDP origin, an identifier, so the score is high. When no identifier ties two legs and only their timing does, the match is a guess, Gap ms shows how far apart the two calls began, and the answer says so:

sipnab -I tests/pcap-samples/b2bua-asterisk.pcapng --correlate b2bua-caller-synth@203.0.113.1 --no-cli-print --quiet
Legs correlated with b2bua-caller-synth@203.0.113.1: 1 leg
Leg                                 Strategy          Method  Score  Match  Gap ms  TH
--------------------------------------------------------------------------------------
b2bua-leg-synth@203.0.113.101:5060  timing_heuristic  timing  50     guess  3       -
Every leg here is a timing guess: no identifier ties it to this call.

A call that no other leg ties to prints no rows and exits 0. A Call-ID the run did not track exits 1. --call-tree follows these matches leg by leg. The REST route GET /v1/dialogs/{call_id}/correlated and the MCP tool find_correlated answer for one leg as this flag does.

Two calls side by side

Compare a call that failed with another, to see what they have in common:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --compare-dialogs busy-3a2b1c@192.0.2.30 unavail-4e5f60@192.0.2.50 --no-cli-print --quiet
Comparing busy-3a2b1c@192.0.2.30 (A) with unavail-4e5f60@192.0.2.50 (B): 1 field differs
Field              A           B           Differs
--------------------------------------------------
state              Failed      Failed      
final_status_code  486         503         differs
msg_count          4           4           
methods            ACK,INVITE  ACK,INVITE  
Hints for A: Call failed: 486 Busy Here.
Hints for B: Call failed: 503 Service Unavailable.

Give the two Call-IDs after the flag, each matched exactly. sipnab compares the four fields in the table, and differs marks the ones that are not the same. It lists the hints for each call without comparing them.

Under --json, differences names the fields that differ:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --compare-dialogs completed-9f8e7d@192.0.2.10 busy-3a2b1c@192.0.2.30 --json --no-cli-print --quiet | jq -c '{differences, a: .a.final_status_code, b: .b.final_status_code}'
{"differences":["state","final_status_code","msg_count","methods"],"a":200,"b":486}

The REST route GET /v1/dialogs/compare, given the two Call-IDs as a and b, and the MCP tool compare_dialogs compare the same fields.

Everything one endpoint did

Name an address to see what it did in the capture, keeping its three most recent dialogs:

sipnab -I tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap --endpoint-ip 192.168.10.2 --top 3 --no-cli-print --quiet
Endpoint ip 192.168.10.2: 5 dialogs
Dialogs by method: INVITE 1, OPTIONS 1, REGISTER 1, SUBSCRIBE 2
Dialogs by state: Completed 2, Registered 1, Terminated 2
Messages: 14 sent, 13 received
Calls: 1 INVITE, 1 with a final response, 0 failed (0%); final responses: 200 x1
Registration: 1 dialog, 1 succeeded, 0 failed, 0 authentication loops
Banners: User-Agent Asterisk PBX 1.6.0.6 (4)
Signaling stack: chan_sip, 2 of 3 observations agree, from 4 requests
Media: 1 stream (0 orphaned), 2 packets, 0 lost, max jitter 0.03 ms, codecs PCMU
Most recent dialogs (3 of 5):
Call-ID                                       Method     State       Final  Started
------------------------------------------------------------------------------------------------------------
ZDYzOWVlNjEwM2NjZTBjNzliNmM1ZTNiOGZjNWFhN2E.  INVITE     Completed   200    2010-09-27T07:12:58.755873+00:00
MDA3MzcwMWQxNmNhNTBmMTZiNGNkY2JjYjZkNzViZTM.  SUBSCRIBE  Terminated  404    2010-09-27T07:12:50.010873+00:00
OGY5ZTk4MzFkYzZhM2FlYjNjNTA3ZTM2ZWY0YzI1ODA.  SUBSCRIBE  Terminated  404    2010-09-27T07:12:50.003249+00:00
  • Messages counts what the address sent and what other hosts sent to it.
  • Banners are the User-Agent and Server values it sent.
  • Signaling stack names the SIP library its requests look built by, and how many of three observations (the Via branch, the From tag and the Call-ID) agree.
  • The counts cover every dialog the address took part in. --filter does not narrow them, and --top bounds only the list of recent dialogs.

Name a SIP user instead, matched against the user part of each dialog’s From and To:

sipnab -I tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap --endpoint-user 10009 --top 2 --no-cli-print --quiet
Endpoint user 10009: 5 dialogs
Dialogs by method: INVITE 1, OPTIONS 1, REGISTER 1, SUBSCRIBE 2
Dialogs by state: Completed 2, Registered 1, Terminated 2
Calls: 1 INVITE, 1 with a final response, 0 failed (0%); final responses: 200 x1
Registration: 1 dialog, 1 succeeded, 0 failed, 0 authentication loops
Banners: none
Signaling stack: not inferred
Media: 3 streams (0 orphaned), 997 packets, 370 lost, max jitter 0.56 ms, codecs PCMU
Most recent dialogs (2 of 5):
Call-ID                                       Method     State       Final  Started
------------------------------------------------------------------------------------------------------------
ZDYzOWVlNjEwM2NjZTBjNzliNmM1ZTNiOGZjNWFhN2E.  INVITE     Completed   200    2010-09-27T07:12:58.755873+00:00
MDA3MzcwMWQxNmNhNTBmMTZiNGNkY2JjYjZkNzViZTM.  SUBSCRIBE  Terminated  404    2010-09-27T07:12:50.010873+00:00

A user has no message counts, because messages belong to an address. Under --json, the answer has the fields GET /v1/endpoints returns:

sipnab -I tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap --endpoint-ip 192.168.10.2 --json --no-cli-print --quiet | jq -c '{dialogs, messages_sent, messages_received, calls: .calls.invites}'
{"dialogs":5,"messages_sent":14,"messages_received":13,"calls":1}
sipnab -I tests/pcap-samples/Asterisk_ZFONE_XLITE.pcap --endpoint-user 10009 --json --no-cli-print --quiet | jq -c '{dialogs, by_method, codecs: .streams.codecs}'
{"dialogs":5,"by_method":{"INVITE":1,"OPTIONS":1,"REGISTER":1,"SUBSCRIBE":2},"codecs":["PCMU"]}

The REST route GET /v1/endpoints, given ip or user, and the MCP tool describe_endpoint describe the same endpoint.

One call’s audio as a WAV file

Write the decoded audio of one call to a file:

sipnab -I tests/pcap-samples/sip-rtp-g711.pcap --export-audio 1-1966@10.0.2.20 --no-cli-print --quiet
Exported 8.5s of mu-law audio (425 frames, PCMU/8000Hz) to 1-1966_10.0.2.20.wav — PARTIAL: media in only ONE direction reached this capture; no stream going the other way was observed. That may be a one-way call, or a capture point that only sees one leg -- this file cannot tell you which.
  • The file is in the current directory, named after the Call-ID with every character other than a letter, a digit, -, _ or . replaced by _.
  • One stream makes a mono file and two make a stereo one.
  • The summary says when the file holds less than the call. Here the capture saw the audio in one direction only, so the summary says PARTIAL. The file carries the same note inside it.
  • sipnab does not keep audio unless asked. --export-audio asks, for this run, as --retain-audio does.

Name the file with --export-audio-out:

sipnab -I tests/pcap-samples/sip-rtp-g711.pcap --export-audio 1-1966@10.0.2.20 --export-audio-out call.wav --no-cli-print --quiet
Exported 8.5s of mu-law audio (425 frames, PCMU/8000Hz) to call.wav — PARTIAL: media in only ONE direction reached this capture; no stream going the other way was observed. That may be a one-way call, or a capture point that only sees one leg -- this file cannot tell you which.

sipnab never replaces an existing file: if the file is there, the run exits 1 and leaves it as it was. Under --json, the answer names the file and what it holds:

sipnab -I tests/pcap-samples/sip-rtp-g711.pcap --export-audio 1-1966@10.0.2.20 --export-audio-out call.wav --json --no-cli-print --quiet
{"schema_version":1,"path":"call.wav","summary":"Exported 8.5s of mu-law audio (425 frames, PCMU/8000Hz) to call.wav — PARTIAL: media in only ONE direction reached this capture; no stream going the other way was observed. That may be a one-way call, or a capture point that only sees one leg -- this file cannot tell you which.","partial":true,"dialog_seen":true,"duration_secs":8.5,"sample_rate":8000,"channels":1,"bytes":136516}

partial and dialog_seen are the two facts the REST route GET /v1/dialogs/{call_id}/audio sends as headers beside the same WAV bytes. The MCP tool export_audio writes the same file into its file root.

Check a vCon file against the schema

Check that a vCon container conforms to the draft-ietf-vcon-vcon-core-04 schema sipnab ships:

sipnab --vcon-validate tests/fixtures/vcon/sip-rtp-g711-1966.vcon.json
tests/fixtures/vcon/sip-rtp-g711-1966.vcon.json: valid against https://ietf.org/vcon/schemas/unsigned-vcon.json

--vcon-validate reads the file and exits. It needs no capture, so give no -I. For a signed or encrypted container, sipnab checks the unsigned vCon inside.

The exit status is the verdict, so a CI step can stop on a container that does not conform:

  • 0: the container is valid.
  • 3: the container is invalid. sipnab prints one row per finding: the JSON Pointer to the value, the schema keyword that refused it, and why.
  • 1: sipnab could not check it: the file is missing, it is not JSON, it is JSON but not an object, or a critical extension forbids reading it.

Under --json, the answer is the one POST /v1/vcon/validate returns:

sipnab --vcon-validate tests/fixtures/vcon/sip-rtp-g711-1966.vcon.json --json
{"schema_version":2,"verdict":"valid","schema_id":"https://ietf.org/vcon/schemas/unsigned-vcon.json","schema_path":"tests/schemas/vcon.schema.json","errors":[]}

The MCP tool validate_vcon runs the same check on a container an agent passes it.

Compare two captures

Hold one capture against another, such as today’s against yesterday’s, and see what moved:

sipnab --compare-captures tests/pcap-samples/sip-problem-call.pcap tests/pcap-samples/sip-rtp-g711.pcap
A: tests/pcap-samples/sip-problem-call.pcap, 23 packets, 5 dialogs, 0 streams
B: tests/pcap-samples/sip-rtp-g711.pcap, 852 packets, 2 dialogs, 2 streams

By state:
Value      A  B  Change
-----------------------
Failed     4  0  -4
InCall     0  1  +1
Completed  1  1  0

By response_code:
Value  A  B  Change
-------------------
200    1  2  +1
404    1  0  -1
486    1  0  -1
503    1  0  -1
603    1  0  -1

'tests/pcap-samples/sip-problem-call.pcap' (5 dialogs) is the baseline; 'tests/pcap-samples/sip-rtp-g711.pcap' (2 dialogs) is held against it, so delta is b minus a.
  • The first file, A, is the baseline. Change is B minus A, so a negative change means the value became rarer.
  • Each dimension lists the values that moved most first.
  • sipnab reads both files and exits. It needs no capture, so give no -I.

Choose the dimensions with --compare-dimensions, and keep fewer values with --top. An (other) row sums the values past the cut:

sipnab --compare-captures tests/pcap-samples/sip-problem-call.pcap tests/pcap-samples/sip-rtp-g711.pcap --compare-dimensions method,ua --top 1
A: tests/pcap-samples/sip-problem-call.pcap, 23 packets, 5 dialogs, 0 streams
B: tests/pcap-samples/sip-rtp-g711.pcap, 852 packets, 2 dialogs, 2 streams

By method:
Value   A  B  Change
--------------------
INVITE  5  2  -3

By ua:
Value    A  B  Change
---------------------
(none)   5  0  -5
(other)  0  2  +2

'tests/pcap-samples/sip-problem-call.pcap' (5 dialogs) is the baseline; 'tests/pcap-samples/sip-rtp-g711.pcap' (2 dialogs) is held against it, so delta is b minus a.

(none) counts the dialogs with no value for the dimension. Here no call in A carried a User-Agent. Compare the codecs the two captures carried:

sipnab --compare-captures tests/pcap-samples/sip-problem-call.pcap tests/pcap-samples/sip-rtp-g711.pcap --compare-dimensions rtp.codec
A: tests/pcap-samples/sip-problem-call.pcap, 23 packets, 5 dialogs, 0 streams
B: tests/pcap-samples/sip-rtp-g711.pcap, 852 packets, 2 dialogs, 2 streams

By rtp.codec:
Value   A  B  Change
--------------------
(none)  5  0  -5
PCMA    0  1  +1
PCMU    0  1  +1

'tests/pcap-samples/sip-problem-call.pcap' (5 dialogs) is the baseline; 'tests/pcap-samples/sip-rtp-g711.pcap' (2 dialogs) is held against it, so delta is b minus a.

Under --json, the answer is the one GET /v1/captures/compare returns. List the values that moved in each dimension:

sipnab --compare-captures tests/pcap-samples/sip-problem-call.pcap tests/pcap-samples/sip-rtp-g711.pcap --json | jq -c '[.dimensions[] | {dimension, moved: [.buckets[] | select(.delta != 0) | .value]}]'
[{"dimension":"state","moved":["Failed","InCall"]},{"dimension":"response_code","moved":["200","404","486","503","603"]}]

The REST route reads its two files from --api-file-root, and its summary adds that neither file is the capture the server holds. The MCP tool compare_captures runs the same comparison.

Find the files that hold a call

Find which of several captures hold failed calls:

sipnab --find-in-files tests/pcap-samples/sip-problem-call.pcap tests/pcap-samples/sip-rtp-g711.pcap tests/pcap-samples/sip-auth-failure.pcapng --problems
Found in 3 of 3 files; every file was read.
File                     Dialogs  First Call-ID
----------------------------------------------------------------------
sip-problem-call.pcap    4        busy-3a2b1c@192.0.2.30
sip-rtp-g711.pcap        2        1-1966@10.0.2.20
sip-auth-failure.pcapng  1        auth-fail-register-synth@203.0.113.1
  • The filter is the one a run with the same flags applies: --filter, a diagnostic alias such as --problems, and --since/--until.
  • Each row is a file with a match, how many of its dialogs matched, and the first matching Call-ID.
  • sipnab reads the files and exits. It needs no capture, so give no -I.

Name a directory to search the capture files directly in it. Here the search is for one Call-ID:

sipnab --find-in-files tests/pcap-samples --filter 'call_id == "busy-3a2b1c@192.0.2.30"'
Found in 1 of 36 files; not complete: 34 of 36 read, so a file not read may hold a match.
File                   Dialogs  First Call-ID
------------------------------------------------------
sip-problem-call.pcap  1        busy-3a2b1c@192.0.2.30
Not read: c07-sip-r2.cap: Failed to open pcap file 'tests/pcap-samples/c07-sip-r2.cap': libpcap error: unknown file format
Not read: rtsp-packets.cap: Failed to open pcap file 'tests/pcap-samples/rtsp-packets.cap': libpcap error: unknown file format

The two .cap files are in a format sipnab does not read, so the answer says it could not read every file, and the run exits 1. When a search is not complete, a call it did not find may still be in a file it did not read.

Under --json, complete says whether sipnab read every file:

sipnab --find-in-files tests/pcap-samples/sip-problem-call.pcap tests/pcap-samples/sip-rtp-g711.pcap --filter 'state == Failed' --json | jq -c '{complete, files: [.matches[] | {filename, dialogs_matched}]}'
{"complete":true,"files":[{"filename":"sip-problem-call.pcap","dialogs_matched":4}]}

The MCP tool find_in_captures runs the same search over its file root.

Fail a build on what a capture shows

Fail a build on what a capture shows walks through writing a suite, running it in a pipeline and reading its failures.

Write the thresholds the traffic must meet in a suite file, and let the exit status fail the CI step that runs it. Each rule names a metric (count, asr, lint_errors, or a MOS percentile such as mos_p10), a comparison, and a threshold, and may narrow itself with a filter: scope:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --expect tests/fixtures/expect/sip-problem-call.expect.toml --no-cli-print --quiet
Expectations: fail, 1 of 3 rules passed, 2 failed
Rule                  Metric  Op  Threshold  Observed  Sample  Verdict
----------------------------------------------------------------------
calls captured        count   >=  5          5         5       pass
answer-seizure ratio  asr     >=  50         20        5       fail
no busy answers       count   ==  0          1         5       fail
answer-seizure ratio: 20 is NOT >= 50 over 5 seizure(s)
no busy answers: 1 is NOT == 0 over 5 dialog(s)

The run exits with the suite’s verdict:

  • 0: every rule that ran passed.
  • 3: at least one rule failed, as here. 3 is the code every sipnab check exits with when the input fails it, so a pipeline can tell a failed rule from a run that failed (1).
  • 2: no rule ran, because every rule’s population was below the min_sample it declared. A suite that checked nothing does not pass.

sipnab reads and checks the suite before it reads the capture, so a misspelled metric or a filter that does not parse stops the run at once with exit 2. The rules judge every dialog of the capture: each rule’s scope narrows it, and --filter does not. For lint_errors rules, --lint-suppress-file applies.

Under --json, the answer is the report the MCP tool evaluate_expectations returns, and exit_code is the run’s exit status:

sipnab -I tests/pcap-samples/sip-problem-call.pcap --expect tests/fixtures/expect/sip-problem-call.expect.toml --json --no-cli-print --quiet | jq -c '{verdict, exit_code, failed: [.results[] | select(.verdict == "fail") | .name]}'
{"verdict":"fail","exit_code":3,"failed":["answer-seizure ratio","no busy answers"]}

Ask the relay which calls it holds

During an incident, ask the media relay which calls it is carrying right now, without starting a capture. sipnab sends the relay named by --rtpengine-control one list request, prints the Call-IDs it answers with, and exits:

sudo sipnab -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-holdings
Holdings of relay (rtpengine at 127.0.0.1:22222), asked 2026-10-25T08:10:20.740734142+00:00
3 calls held.
Call-ID
--------------------------------
a84b4c76e66710@192.0.2.10
3848276298220188511@198.51.100.7
1-4242@192.0.2.10

Asking transmits, so sipnab asks only from a live source: an interface (-d) or a HEP listener (-L). On a run that reads a file it asks nothing, says why, and exits 2. sipnab answers before it opens the capture, so the run reads no packet and answers no other query flag.

The relay returns at most 32 Call-IDs unless you ask for a different number. When it returns as many as you asked for, it may hold more, and the answer says so:

sudo sipnab -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-holdings --relay-holdings-max 2
Holdings of relay (rtpengine at 127.0.0.1:22222), asked 2026-10-25T08:12:28.613317624+00:00
2 calls returned, as many as asked for, so the relay may hold more.
Call-ID
--------------------------------
a84b4c76e66710@192.0.2.10
3848276298220188511@198.51.100.7

On a busy relay, raise the limit. A HEP listener is a live source too, so a host that only receives HEP can ask:

sipnab -L 0.0.0.0:9060 --rtpengine-control 127.0.0.1:22222 --relay-holdings --relay-holdings-max 500

rtpengine answers over UDP, and a large list may not fit in one datagram.

Under --json, the answer is the body GET /v1/relay/holdings returns:

sudo sipnab -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-holdings --json
{"call_ids":["a84b4c76e66710@192.0.2.10","3848276298220188511@198.51.100.7","1-4242@192.0.2.10"],"kind":"calls","obtained_at":"2026-10-25T08:10:20.788134229+00:00","origin":"asked","outcome":"ok","relay":"relay (rtpengine at 127.0.0.1:22222)","truncated":false}

A relay that sends no answer is not a relay that holds no calls. sipnab prints the reason on stderr and exits 1. Under --json the answer names the classification instead of a list:

--relay-holdings: unreachable: relay (rtpengine at 127.0.0.1:22222) did not answer (no reply from 127.0.0.1:22222 within 2s); asked, nothing came back
{"detail":"relay (rtpengine at 127.0.0.1:22222) did not answer (no reply from 127.0.0.1:22222 within 2s); asked, nothing came back","outcome":"unreachable","responsibility":"relay_or_network"}

Dialog

FlagValueDefaultSinceDescription
-l, --limit<N>1000000.3.0Maximum dialogs held in TOTAL over the run. Not a concurrency limit — nothing removes a completed dialog, so this bound scales with uptime rather than load: a box carrying five concurrent calls still evicts once 100,000 have completed, oldest first. Lower it for untrusted/high-volume capture. Config: [limits] dialog_limit
-R, --rotate–on0.3.0Evict the oldest dialog at --limit capacity (LRU). On by default; pass it to state the default explicitly
--no-rotate–off0.4.5Disable rotation: drop new dialogs at capacity instead of evicting the oldest (inverts the safe default)
--dialog-track<METHOD>call-id0.3.0Group messages by call-id (one unit per dialog) or branch (one per SIP transaction)
--leg-correlation-window<MS>20000.5.100How far apart one call’s two legs may start and still correlate on TIMING alone. sipnab falls back to this timing match when a B2BUA has rewritten every identifier the other correlation strategies compare. Two seconds suits a PBX that places the outbound leg at once; widen it for one that does an LNP or ENUM lookup, or walks an LCR list, first. Each correlation reports the strategy that matched. Config: [sip] leg_correlation_window_ms
--active-idle-window<SECS>36000.5.100Seconds a dialog may go untouched and still count toward the active-dialog and active-call gauges every surface publishes. One hour is twice the default Session-Expires of RFC 4028. Raise it where calls stay on hold longer than that, such as a contact center; a call whose BYE never arrived then also keeps counting for longer. Config: [sip] active_idle_window_secs
--no-dialog–off0.3.0Disable dialog tracking entirely (message-only mode)
--tag<TAG>–0.3.0Add this label to every dialog the run records, in the dialog’s tags (--json-dialogs, GET /v1/dialogs, GET /v1/dialogs/:call_id, and MCP list_dialogs and get_dialog). It labels; it does not filter.

branch counts transactions, not calls.

  • RFC 3261 gives the ACK to a 2xx a new branch (RFC 3261 section 17.1.1.3). It gives the BYE another.
  • So one ordinary call appears as three or more units.
  • That is the transaction view working as intended.
  • Use it when a capture reuses one Call-ID across many transactions, such as load generators or proxies under test.
  • Note that --limit then counts transactions too.

Examples

  • sipnab -N -I loadtest.pcapng --dialog-track branch --report — per-transaction view of a load-generator capture that reuses one Call-ID
  • sipnab -N -I loadtest.pcapng --dialog-track call-id --report — same capture as dialogs (the default), for a per-call view
  • sudo sipnab -d eth0 --limit 5000 --rotate — monitor a busy proxy with a tight 5000-dialog memory bound, explicitly evicting the oldest dialog at capacity
  • sipnab -N -I capture.pcap --limit 20000 --no-rotate — analyze a capture, dropping new dialogs (instead of evicting old ones) past 20000 tracked
  • sipnab -N -I day.pcap --limit 1000 --rotate --report — report on the last 1000 calls of a long capture: each new call past 1000 evicts the oldest
  • sipnab -N -I day.pcap --limit 1000 --no-rotate --report — report on the first 1000 calls of a long capture: sipnab drops the calls past 1000 and counts them
  • sipnab -N -I capture.pcap --tag site-a --json-dialogs — label every dialog from this capture site-a, so a store that merges several captures can tell them apart: each dialog line carries "tags":["site-a"]
  • sipnab -N -I capture.pcap --tag site-a --api 127.0.0.1:8080 — serve the capture over REST with every dialog labeled site-a, so GET /v1/dialogs/:call_id shows which site the call came from
  • sipnab -N -I capture.pcap --no-dialog — scan a capture message-by-message with dialog tracking disabled entirely
  • sudo sipnab -d eth0 -N --no-dialog — watch raw live SIP messages on an interface without keeping any per-dialog state
  • sipnab -N -I sbc.pcap --leg-correlation-window 8000 --mcp — correlate the two legs of a call across a B2BUA that dips an ENUM or LNP database before placing the outbound leg, which the shipped two seconds cannot reach
  • sipnab -N -I gateway.pcap --leg-correlation-window 500 --report — a PBX that places the outbound leg immediately, where a tighter window stops a busy server’s unrelated calls turning into one
  • sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --active-idle-window 14400 — a contact center parking callers on hold for hours: at the shipped hour the active-call gauge stops counting them, and four hours covers the queue
  • sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --active-idle-window 300 — a trunk where every call refreshes on a short session timer, so five minutes of silence already means the BYE went missing and counting it longer only inflates the gauge

RTP

FlagValueDefaultSinceDescription
--max-streams<N>500000.3.0Maximum number of RTP streams to track simultaneously. At least 1: sipnab refuses 0. Config: [limits] max_streams
--max-lost-sequences<N>10000.5.97Lost RTP sequence numbers retained per stream, for the Packet Loss Map and the burst/gap analysis. The default is about a minute of a call losing 1 % at 50 packets a second, so on a half-hour call the map shows the tail and marks itself truncated. The burst/gap window widens with it, and each retained loss costs two bytes per stream. Config: [limits] max_lost_sequences. How-to: Measure call quality
--quality-interval<SECONDS>50.5.160Seconds between RTP quality snapshots — the resolution of the per-stream quality trend, which carries a MOS, an R-factor and an acceptable/degraded/not_scorable verdict per interval. The shipped five seconds averages away any burst shorter than itself, so three half-second dropouts read as a mild five-second average. The trend still covers an hour of call time whatever this is, so one second retains 3600 snapshots per stream instead of 720: resolution costs memory, never history. Accepts 1 to 300. Config: [limits] quality_interval_secs. How-to: Measure call quality
--quality-threshold<MOS>3.00.3.0MOS quality threshold for alerts (1.0-5.0 scale). How-to: Measure call quality
--g729-tables<PATH>–0.5.222The G.729 codebook table file, for decoding G.729 audio in WAV exports, vCon recordings, the live audio route and the dead-air measurement. sipnab ships no ITU-T table values: sipnab g729 install builds this file from the ITU-T G.729 software package. Absent, sipnab reads [g729] tables, else g729-tables.bin in the directory of the user config file ($XDG_CONFIG_HOME/sipnab, else ~/.config/sipnab). sipnab reads a path given here as it is: when no file is there, sipnab leaves G.729 streams out of the audio and the export says G.729 not decoded: codebook tables not installed; run sipnab g729 install. Feature: g729. Config: [g729] tables
--rtpengine-control<ADDR>–0.5.124Ask an rtpengine relay which calls it currently has up, so a capture that started mid-call can still name the dialog behind a stream. Off unless you give an address, and the address is never inferred from captured traffic. sipnab sends only rtpengine’s read-only list, query and statistics commands, so it cannot change a relay’s behavior. statistics goes only with --relay-stats and its relatives. Not a poller: to name calls it asks at startup, before the capture opens, and again only when a stream turns up that nothing explains. Nothing polls unless --relay-stats-interval names an interval. sipnab asks about each relay-side socket at most once for the run, under a per-run ceiling on control transactions. On -I <file> it sends nothing: sipnab warns that a live relay would describe the calls up today rather than the calls in the capture, still decodes any relay control traffic the capture holds, and analyzes the file. Feature: native. How-to: Let sipnab name rtpengine’s media
--rtpproxy-control<ADDR:PORT>–0.5.196Read rtpproxy’s control traffic on this UDP socket and name the call each relayed stream belongs to, even on the relay’s own machine where no SIP passes. The SIP proxy’s command names the call and rtpproxy’s reply names the port it opened; sipnab pairs the two by the cookie they share. Recording streams (R, C) are never named as the call’s media. Name the socket; sipnab assumes none: rtpproxy’s default control socket is a UNIX socket, which a capture cannot see, so give it a UDP one with -s udp:ADDR:PORT (22222 when you leave out the port) and pass the same address and port here. sipnab reads only datagrams to and from exactly that socket, because they carry no credential. Passive: sipnab never sends rtpproxy anything, and rtpproxy has no command that lists its sessions, so sipnab names a call already up when the capture starts from its next command. Works on live captures and on files, and applies to a capture opened inside the TUI. Feature: native. How-to: Let sipnab name rtpproxy’s media
--relay-stats––0.5.169Ask the relay named by --rtpengine-control for its OWN statistics – packets relayed, sessions, its own loss and jitter – and print them. They are relay_reported: a claim from the box, not a measurement sipnab made, and the header says so. Same live source --rtpengine-control needs, because asking transmits; on -I <file> it prints why it declines to ask rather than asking, and with no --rtpengine-control it says to name one. Feature: native. How-to: Ask a media relay about itself
--relay-stats-call<CALL-ID>–0.5.169Ask the relay named by --rtpengine-control for its own counters about ONE call, by Call-ID: per-stream and per-SSRC packet and byte counts, RTP and RTCP totals, as the relay counts them (relay_reported). A relay not holding the call answers in its own words, which sipnab reports rather than inventing a not-found. Same live-source gate as --relay-stats. Feature: native. How-to: Ask a media relay about itself
--relay-stats-list––0.5.169List which statistics the relay named by --rtpengine-control knows, rather than their values. The key set is version-specific, so “what can I ask for?” is a real question; sipnab asks the relay rather than reading a built-in table, and the header names how it got the set (for rtpengine, the relay listed them in a statistics reply). Same live-source gate as --relay-stats, because listing asks the relay and asking transmits. Feature: native. How-to: Ask a media relay about itself
--relay-stats-interval<SECONDS>–0.5.170Poll the relay named by --rtpengine-control for its statistics every SECONDS, for as long as the capture runs. Nothing polls by default. One transaction per interval, the next beginning only once the previous returns, so a slow relay slows the cadence rather than stacking requests. Each polled reading says it was polled and names the interval. CLI only (a poll is a standing instruction to transmit; the REST/MCP caller does not own the host). Its newest reading is readable over REST GET /v1/relay/stats/polled and the MCP tool relay_stats_polled, which send nothing and need no opt-in. Same live-source gate as --relay-stats. Range 1–3600. A headless run prints each reading to stdout, so --mcp on stdio refuses it; --mcp-transport http accepts it. Feature: native. How-to: Ask a media relay about itself, and Read the newest poll without asking again
--relay-compare<CALL-ID>–0.5.169Compare the relay’s own RTP packet count for one call against what sipnab measured on the wire. It shows the relay’s totals.RTP.packets for the call (relay_reported) beside sipnab’s own count of the RTP packets it captured for it (sipnab_measured), both labeled, with a word verdict (match or differ) and a direction-aware note. The two count different sockets over different windows, so an ordinary gap is not a relay fault: if sipnab counted fewer the note names the undercount causes (a mirror-port drop, a mid-call restart) and points at capture_health; if sipnab counted more it names the usual cause – a capture that sees both sides of the relay hairpin counts each packet twice, so a point upstream and downstream reads about twice the relay’s count. It never sums the counts. Runs after capture, when sipnab’s tally is final; sipnab reports a call the relay does not hold, or one it captured no RTP for, distinctly rather than comparing it against a made-up zero. Same live-source gate as --relay-stats. Feature: native. How-to: Ask a media relay about itself

Examples

  • sudo sipnab -d eth0 --quality-threshold 3.5 --max-streams 10000 — monitor live RTP with MOS alerts below 3.5. sipnab reports stream statistics once, at end of capture. There is no periodic interval report
  • sipnab -N -I capture.pcap --max-streams 100000 — batch-analyze RTP streams with a raised stream cap. The statistics arrive once, when the capture ends
  • sipnab -N -I long-call.pcap --max-lost-sequences 100000 --json-dialogs --no-cli-print — keep every loss from a half-hour call that an operator escalated, so the Packet Loss Map covers the whole call and the burst count is the real one rather than the tail’s
  • sudo sipnab -d eth0 --max-lost-sequences 200 — watch a busy trunk on a small box, holding a fifth of the shipped loss history per stream. The map still shows where loss is landing right now and marks itself truncated
  • sipnab -N -I g729-call.pcap --retain-audio --g729-tables /srv/sipnab/g729-tables.bin --export-vcon 'a84b4c76e66710@pc33.example.com' > call.vcon — export a G.729 call’s audio into a vCon, reading the tables from a file copied in from another host
  • sipnab -N -I dead-air.pcap --quality-interval 1 --json-dialogs --no-cli-print — one snapshot a second on the call somebody complained about, so a half-second dropout appears as its own degraded interval instead of disappearing into a five-second mean
  • sudo sipnab -d eth0 --quality-interval 30 --max-streams 20000 — the other direction, on a busy trunk where twenty thousand streams each holding 720 snapshots is memory better spent elsewhere. Thirty seconds still covers the retained hour, in 120 entries per stream
  • sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 — start capturing on a proxy that shares a host with rtpengine, and ask the relay once, at startup, which calls are already up. Streams whose offer happened before sipnab started get a Call-ID, where otherwise sipnab would report them as orphans
  • sudo sipnab -N -d eth0 --rtpengine-control 192.0.2.50:22222 --max-streams 10000 — the relay on its own host: same question, over the network to its ng control port. To name calls sipnab sends only list and query, and asks again only when a stream turns up that the signaling does not explain — each socket once, under a ceiling it reports when it reaches
  • sipnab -N -I capture.pcap --rtpengine-control 127.0.0.1:22222 — sends nothing to the relay: sipnab warns that a file run never transmits, then analyzes the capture and exits 0
  • sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-stats — capture live and print the relay’s own counters at startup: packets relayed, sessions held, its own loss. The header marks them relay_reported, because the relay reports on itself rather than sipnab measuring the wire
  • sipnab -N -I capture.pcap --relay-stats --rtpengine-control 127.0.0.1:22222 — asks nothing on a file run. sipnab logs --relay-stats will not ask a relay on a run that reads a file, analyzes the capture, and exits 0
  • sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-stats-call 1-7@203.0.113.9 — the relay’s own packet and byte counts for one call, by Call-ID, relay_reported. A header names the call and the moment asked
  • sipnab -N -I capture.pcap --relay-stats-call 1-7@203.0.113.9 --rtpengine-control 127.0.0.1:22222 — asks nothing on a file run, and logs the same --relay-stats will not ask a relay line as the relay-wide form
  • sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-stats-list — ask the relay which statistics it knows, listed by name and not by value, so a caller learns what it can ask for before a request fails on a name this build does not have. The header says the relay listed them, rather than sipnab guessing from a table
  • sipnab -N -I capture.pcap --relay-stats-list --rtpengine-control 127.0.0.1:22222 — asks nothing on a file run, and logs the same --relay-stats will not ask a relay line
  • sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-compare 1-7@203.0.113.9 — after capture, put the relay’s own RTP packet count for the call beside sipnab’s measured count, each labeled with its tier, with a match/differ verdict and a note explaining that an ordinary gap is not a relay fault. It shows both counts and never sums them
  • sipnab -N -I capture.pcap --relay-compare 1-7@203.0.113.9 --rtpengine-control 127.0.0.1:22222 — compares nothing on a file run: sipnab logs --relay-compare will not ask a relay on a run that reads a file and exits 0
  • sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-stats-interval 30 — capture live and ask the relay for its own counters every thirty seconds, each reading marked polled with the interval so it reads as a timer’s output rather than a one-shot answer. Stop it with Ctrl-C. Nothing keeps transmitting after the process ends
  • sipnab -N -I capture.pcap --relay-stats-interval 30 --rtpengine-control 127.0.0.1:22222 — polls nothing on a file run: sipnab logs --relay-stats-interval will not poll a relay on a run that reads a file and exits 0
  • sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-stats --json — the same relay counters as one JSON object instead of the table, each value carrying its tier and refusals listed separately with the relay’s code. Both forms read the same data, so --json and the table cannot disagree about a number
  • sipnab -N -I relay.pcap --rtpproxy-control 192.0.2.40:7722 --report — read a capture taken on an rtpproxy relay whose control socket is udp:192.0.2.40:7722. Each stream on a port rtpproxy opened gets the Call-ID from the SIP proxy’s command, and the report lists those calls as named by a media relay instead of reporting their media as orphaned
  • sudo sipnab -N -d eth0 --rtpproxy-control 127.0.0.1:22222 — capture live beside an rtpproxy started with -s udp:127.0.0.1, which listens on 22222. sipnab only reads the control traffic and never sends rtpproxy anything

Diagnosis thresholds

The numbers the signaling and media checks compare against.

  • Each decides whether a call that is working gets reported as broken.
  • So the defaults are standards figures for the general case. Your own network beats them.
  • Every flag has a config key under [diagnosis], and the flag wins.
FlagValueDefaultSinceDescription
--pdd-threshold<SECS>11.00.5.96Post-dial delay over which sipnab reports a call as slow. The default is the ITU-T E.721 Table 2 target that 95 percent of international connections must meet, because a capture does not say which kind of call it holds. Tighten it to 8.0 for toll or 6.0 for local traffic. Must be a finite number greater than 0: sipnab refuses 0, a negative number, nan and inf. Config: [diagnosis] post_dial_delay_secs
--ack-timeout<SECS>32.00.5.96Seconds a 2xx may go unacknowledged before the missing ACK counts as a fault rather than as a capture that stopped early. The default is RFC 3261 Timer H. Must be a finite number greater than 0: sipnab refuses 0, a negative number, nan and inf. Config: [diagnosis] ack_timeout_secs
--no-final-response-timeout<SECS>180.00.5.96Seconds an INVITE may sit without a final response before the silence gets reported. The default is RFC 3261 Timer C. Below it, every call still ringing when the capture stopped gets reported. Must be a finite number greater than 0: sipnab refuses 0, a negative number, nan and inf. Config: [diagnosis] no_final_response_secs
--duration-asymmetry-pct<PCT>5.00.5.96Percentage difference between the two legs’ durations that counts as asymmetric. Greater than 0 and at most 100: sipnab refuses 0, a negative number, a number over 100, nan and inf. Config: [diagnosis] duration_asymmetry_pct
--duration-asymmetry-secs<SECS>2.00.5.96Absolute difference between the two legs’ durations that counts as asymmetric. A call has to clear both this and the percentage, so raising either one alone quiets the detection. Must be a finite number greater than 0: sipnab refuses 0, a negative number, nan and inf. Config: [diagnosis] duration_asymmetry_secs
--late-media-ms<MS>5000.5.96Milliseconds after the 200 OK that media may start before it gets reported as late. A whole number of at least 1: sipnab refuses 0 and a negative number. Config: [diagnosis] late_media_ms
--cn-suppression-ratio<RATIO>0.30.5.99Share of a call’s packets, as a fraction of 1, that must be comfort noise before sipnab accepts comfort noise as the explanation for one-directional media. The one threshold here that withholds a finding instead of raising one, so it fails as silence: a VoLTE or mobile trunk running aggressive voice-activity detection routinely passes 30 percent comfort noise, and above the ratio sipnab never reports one-way audio on that trunk. Must be greater than 0 and 1 or less. Config: [diagnosis] cn_suppression_ratio

Examples

  • sipnab -N -I calls.pcap --json-dialogs --pdd-threshold 6 --no-cli-print — judge a capture you know is local traffic against E.721’s local target rather than the international one the default assumes
  • sipnab -N -I trunk.pcap --json-dialogs --late-media-ms 150 --duration-asymmetry-secs 0.5 --no-cli-print — a tight media audit for a trunk where a 150 ms media gap is already a clipped first syllable
  • sipnab -N -I sat-trunk.pcap --json-dialogs --pdd-threshold 15 --late-media-ms 900 --no-cli-print — the other direction, for a satellite path where the shipped figures report every healthy call as slow
  • sipnab -N -I proxy.pcap --json-dialogs --ack-timeout 8 --no-final-response-timeout 30 --no-cli-print — a proxy tap where the interesting window is far shorter than the RFC 3261 timers, so a stalled transaction shows up while the capture is still running
  • sipnab -N -I trunk.pcap --json-dialogs --ack-timeout 64 --no-final-response-timeout 300 --no-cli-print — a lossy trunk where the RFC timers themselves fire too early, so only a genuinely dead transaction gets reported
  • sipnab -N -I b2bua.pcap --json-dialogs --duration-asymmetry-pct 25 --duration-asymmetry-secs 5 --no-cli-print — a B2BUA capture where the legs never tear down together, so only a large gap is worth a line
  • sipnab -N -I b2bua.pcap --json-dialogs --duration-asymmetry-pct 1 --duration-asymmetry-secs 0.2 --no-cli-print — the strict form of the same audit, for hunting a leg that drops media a fraction early
  • sipnab -N -I volte-trunk.pcap --json-dialogs --cn-suppression-ratio 0.8 --no-cli-print — a mobile trunk whose voice-activity detection sends comfort noise on well over 30 percent of packets: at the shipped ratio sipnab treats that as the explanation for a one-directional flow and reports no one-way audio on any call
  • sipnab -N -I pbx.pcap --json-dialogs --cn-suppression-ratio 0.05 --no-cli-print — the opposite, for a LAN PBX where a call carrying any comfort noise at all is still expected to be bidirectional

Quality color bands

Where the quality color column turns yellow, and where it turns red.

  • This is a different question from the diagnosis thresholds above.
  • Those decide whether a working call counts as broken.
  • These decide only what catches an operator’s eye during triage.
  • Every flag has a config key under [quality], and the flag wins.

The shipped figures suit a general-purpose trunk. The right values belong to the network you are watching.

  • 30 ms of jitter is already a fault on a LAN PBX.
  • 1 percent loss is unremarkable on an international one.
  • A column tuned for neither is wrong in both directions.

These bands paint the TUI.

  • A -N run prints the measurements themselves rather than a color.
  • So sipnab validates a band set on a non-interactive run and then never consults it.
FlagValueDefaultSinceDescription
--jitter-warn-ms<MS>30.00.5.97Jitter at or above which the column turns yellow. Config: [quality] jitter_warn_ms. How-to: Measure call quality
--jitter-bad-ms<MS>50.00.5.97Jitter at or above which the column turns red. Config: [quality] jitter_bad_ms. How-to: Measure call quality
--loss-warn-pct<PCT>1.00.5.97Loss at or above which the column turns yellow. 0 is a legitimate setting: it means any loss at all is worth a color. Config: [quality] loss_warn_pct. How-to: Measure call quality
--loss-bad-pct<PCT>5.00.5.97Loss at or above which the column turns red. Config: [quality] loss_bad_pct. How-to: Measure call quality
--mos-warn<MOS>4.00.5.97MOS below which the column turns yellow. MOS bands run downward, so this must sit at or above --mos-bad. Config: [quality] mos_warn. How-to: Measure call quality
--mos-bad<MOS>3.00.5.97MOS below which the column turns red. Config: [quality] mos_bad. How-to: Measure call quality
--rtt-warn-ms<MS>300.00.5.97Round trip at or above which the column turns yellow. The default is ITU-T G.114’s 150 ms one-way guidance doubled. Config: [quality] rtt_warn_ms. How-to: Measure call quality
--rtt-bad-ms<MS>800.00.5.97Round trip at or above which the column turns red. The default is G.114’s 400 ms one-way figure doubled. Config: [quality] rtt_bad_ms. How-to: Measure call quality

sipnab refuses, with exit 2, a warn boundary above its matching bad boundary:

--jitter-warn-ms (60) is above --jitter-bad-ms (50), so no jitter could ever be a warning

It also refuses a boundary that is not a finite number of 0 or more, such as nan.

Examples

  • sipnab -I lan-pbx.pcap --jitter-warn-ms 10 --jitter-bad-ms 20 — a LAN PBX, where the shipped 30 ms boundary hides a fault worth chasing
  • sipnab -I wifi-softphone.pcap --jitter-warn-ms 60 --jitter-bad-ms 120 — the other direction, for a Wi-Fi leg where the defaults paint every healthy call yellow
  • sipnab -I intl-trunk.pcap --loss-warn-pct 2 --loss-bad-pct 8 — an international trunk, where 1 percent loss is routine
  • sipnab -I strict-lan.pcap --loss-warn-pct 0 --loss-bad-pct 1 — the strict form: any loss at all takes a color
  • sipnab -I sat-trunk.pcap --rtt-warn-ms 700 --rtt-bad-ms 1200 — a satellite path, where G.114’s terrestrial figures report every call as bad
  • sipnab -I campus.pcap --rtt-warn-ms 50 --rtt-bad-ms 150 — a campus network, where a 300 ms round trip is already an escalation
  • sipnab -I hd-codec.pcap --mos-warn 4.3 --mos-bad 3.8 — a wideband codec deployment, where 4.0 is not the good score it is on narrowband
  • sipnab -I gsm-gateway.pcap --mos-warn 3.6 --mos-bad 2.8 — a low-bitrate gateway, where nothing ever clears the shipped 4.0 warning
  • sipnab -I triage.pcap --jitter-warn-ms 15 --loss-warn-pct 0.5 --rtt-warn-ms 120 --mos-warn 4.2 — one strict pass across all four columns, for a first look at a network you have not seen before

Security

FlagValueDefaultSinceDescription
--kill-scanner–off0.3.0Detect SIP scanning (known UA signatures + behavioral rate/enumeration), alert on it, and send the kill response back to the scanner (the CLI matcher -J/-j). To detect without answering, use --kill-scanner-detect-only. Config: [security] kill_scanner
--no-kill-scanner–off0.5.204Do not answer scanners even when [security] kill_scanner = true, so you can disarm, for one run, a response that a config file arms. The last of --kill-scanner / --no-kill-scanner typed wins. Config: [security] kill_scanner
--kill-scanner-detect-only–off0.5.227Detect and report SIP scanners exactly as --kill-scanner does, and never answer them. sipnab starts no scanner-kill worker and sends nothing, on a live capture too. It builds the scanner detector by itself, so --kill-scanner is not needed; given together, detect-only wins. The run says Scanner detection is detect-only at startup and scanner detection was detect-only in its end-of-run summary, and security_findings reports scanner_response: "detect_only". sipnab refuses it together with -K/--kill-target (exit 2), because -K asks sipnab to answer a named source. Config: [security] kill_scanner_detect_only
--no-kill-scanner-detect-only–off0.5.227Answer scanners again even when [security] kill_scanner_detect_only = true, for one run. The last of --kill-scanner-detect-only / --no-kill-scanner-detect-only typed wins. Config: [security] kill_scanner_detect_only
--kill-ua<PATTERN>–0.3.0Add a custom scanner User-Agent pattern (regex) to --kill-scanner detection. Refused without it: --kill-scanner or --kill-scanner-detect-only (or [security] kill_scanner = true or kill_scanner_detect_only = true) builds the detector that reads this pattern, so given alone it would feed nothing and the run would report no scanners — which is what a clean capture looks like. sipnab does not arm the detector for you, because on a live capture --kill-scanner also arms the response path. sipnab refuses an empty or blank pattern, which would match every User-Agent, and a pattern that is not a valid regular expression
--kill-response<CODE>2000.3.0SIP response code for the kill response (100-699). Config: [security] kill_response
-K, --kill-target<ADDR[:PORT-RANGE]>–0.5.7Targeted kill: send the kill response to any SIP request whose source matches ADDR and an optional port range (192.0.2.1:5060-5090, [::1]:5060), regardless of UA/behavioral detection. Repeatable; spawns the kill worker on its own (no --kill-scanner needed). Refused (exit 2) together with --kill-scanner-detect-only or [security] kill_scanner_detect_only = true
--kill-spoof<MODE>auto0.5.12Source-address strategy for the kill response (Linux only; other platforms always ephemeral). auto forges the victim’s ip:port via a raw socket when CAP_NET_RAW is available (so the reply appears to come from the targeted SIP port), falling back to an ephemeral source otherwise; raw requires the spoof and errors when it cannot open the raw socket; ephemeral never spoofs
--kill-rate-limit<N>100.5.96Scanner-kill responses per second sipnab may put on the wire. There is no unlimited setting: sipnab rejects 0. A per-destination cap of 3 per minute applies underneath, so raising this widens how many distinct hosts sipnab answers, never how hard it hits one. Config: [security] kill_rate_limit
--sandbox<MODE>off0.5.164Bound which files this process may reach, using Landlock. best-effort installs what the kernel supports and captures either way; required refuses to capture when no sandbox is in force. Reads and writes outside the input set, the output directory, the keylog and the crash directory come back EACCES. Sockets are not bounded: the ruleset governs the filesystem only. A denied open fails with EACCES and does not end the run, and a kernel without Landlock degrades to a warning unless required says otherwise Steps: Capture with the least privilege.
--seccomp<MODE>off0.5.165Record every system call (log), or refuse the ones outside a host-derived allowlist (enforce). log installs a seccomp filter whose only action is SECCOMP_RET_LOG. It denies nothing and protects nothing: use it to record which calls a run makes, as the input to an allowlist. enforce kills the process on a call outside the allowlist SIPNAB_SECCOMP_ALLOWLIST names, derived on the capture host with scripts/derive-seccomp-allowlist.sh; when sipnab cannot install an enforcing filter (no list named, a list it cannot read, a kernel that refuses), it refuses to start, exit status 1. Where the records land depends on the host: auditctl -s prints a connected audit daemon’s pid and the records are then in ausearch -m SECCOMP; a pid of 0 means no daemon and they are in dmesg | grep 'type=1326'. Point it at a bounded offline run — a live capture emits one record per received packet and floods the log Steps: Capture with the least privilege.
--fraud-detect–off0.3.0Enable fraud detection heuristics. Config: [security] fraud_detect
--no-fraud-detect–off0.5.204Do not run fraud detection even when [security] fraud_detect = true. The last of --fraud-detect / --no-fraud-detect typed wins. Config: [security] fraud_detect
--evidence-out<PATH|->off0.5.148Publish every security finding that names a source as JSON Lines, for a system that decides what to do with it. - is standard output, for a pipe, and --mcp refuses it; a path receives one appended line per finding, and sipnab opens it at startup, so a path it cannot write is an error before the first packet. sipnab takes no action on these lines: the system reading them decides. A finding carried by HEP publishes nothing without --hep-allow-kill, for the reason --fail2ban gives: the inner address is the sender’s claim
--fraud-destination<ISO,...>off0.5.147Destination countries --fraud-detect reports an INVITE to, as ISO 3166-1 alpha-2 codes (DO,VG,MA). sipnab reads numbers through the common international prefixes (+, 00, 011) and resolves them by longest calling code, so +1 809 is DO, not US; a number with no prefix is domestic and never matches. Every fraud finding also names the resolved destination. Absent: nothing changes. Config: [security] fraud_destination
--business-hours<START-END>–0.5.96Business hours in whole UTC hours, for example 8-18, or 22-6 for an overnight window. The off-hours fraud detection reports nothing until you set a window. Needs --fraud-detect. Config: [security] business_hours
--fraud-short-call<SECS>30.5.96Measured duration below which --fraud-detect counts a completed call as short for wangiri detection. Three seconds is under a normal ring-no-answer on some carriers. Config: [security] fraud_short_call_secs
--fraud-wangiri-calls<N>30.5.96Short calls to one destination prefix before --fraud-detect reports wangiri. Config: [security] fraud_wangiri_calls
--fraud-sequential-calls<N>30.5.96Consecutive refused numbers before --fraud-detect reports sequential scanning. Config: [security] fraud_sequential_calls
--fraud-volume-multiplier<N>50.5.96Multiple of a source’s own baseline call rate that --fraud-detect reports as a volume spike. Config: [security] fraud_volume_multiplier
--fraud-volume-min-calls<N>60.5.96Calls a source must place inside the volume window before --fraud-detect reports a spike at all. Config: [security] fraud_volume_min_calls
--fraud-volume-window<SECS>600.5.100How much capture time one volume-spike window spans. The count and the baseline are both measured over it, so a steady source reads the same at any width; the width alone decides how concentrated a burst has to be, since a burst shorter than the window averages into the traffic beside it. Config: [security] fraud_volume_window_secs
--fraud-wangiri-window<SECS>600.5.100How much capture time one wangiri window spans. The detector drops short calls older than this, so it decides how slowly a lure may arrive and still count as one pattern. No setting of --fraud-wangiri-calls reaches a lure paced wider than the window. Config: [security] fraud_wangiri_window_secs
--scanner-behavioral-probes<N>100.5.98Probes from one source inside the scanner window, above which --kill-scanner reports a rate detection. Behind an SBC every source collapses to one address, so ordinary aggregated traffic clears ten in five seconds and the whole site reads as one scanner. Config: [security] scanner_behavioral_probes
--scanner-enumeration-targets<N>50.5.98Distinct target extensions from one source inside the scanner window, above which --kill-scanner reports extension enumeration. Config: [security] scanner_enumeration_targets
--scanner-rejected-probes<N>50.5.98Rejected probes inside the scanner window at which a source reads as probing rather than operating. This is the evidence gate: neither behavioral signal reports anything until a source clears this or --scanner-unanswered-probes, which is what separates an enumeration sweep from a trunk running keepalives at the same rate. Config: [security] scanner_rejected_probes
--scanner-unanswered-probes<N>50.5.98Probes inside the scanner window that drew no response, at which a source reads as sweeping, provided they also outnumber the rest of what it sent. Config: [security] scanner_unanswered_probes
--scanner-window<SECS>50.5.98How much capture time one scanner window spans. Every scanner count above is per window, so this is the binding constraint on a paced sweep rather than the counts: one probe every ten seconds never puts two inside the shipped five-second window, so the rate and the spread both stay at one however low the counts go. Config: [security] scanner_window_secs
--scanner-established-factor<N>40.5.98How much more evidence --kill-scanner needs from a source that has completed a registration or a call. 1 judges a registered endpoint like any other source; raise it to report fewer of your own endpoints. Config: [security] scanner_established_factor
--scanner-answer-grace<MS>5000.5.98How long a probe may go without a response before --kill-scanner counts it as unanswered. The default is RFC 3261’s Timer T1, the round-trip estimate at which SIP itself gives up waiting and retransmits. Raise it on a link whose round trip runs longer than that, where the default reports every probe still in flight as one nobody answered. Config: [security] scanner_answer_grace_ms
--reg-flood–off0.3.0Detect registration floods: credentialed REGISTERs the registrar keeps refusing. sipnab reports a source when the REGISTERs it sent with Authorization that drew a 401/407 on the same transaction exceed --reg-flood-threshold inside one --reg-flood-window of capture time, one second by default. A REGISTER on its own is never evidence, so a re-REGISTER storm the registrar accepts after a restart is the customer’s SBC and not a flood, and any 2xx to a REGISTER clears the source’s count
--reg-flood-threshold<N>500.5.96Challenged failures from one source inside one --reg-flood-window before --reg-flood reports a flood: REGISTERs that carried credentials and drew a 401 or 407. The default suits a carrier registrar; set it lower for a small PBX. Counted in capture time, so a file replays as the traffic it recorded rather than as fast as the disk reads it. Config: [security] reg_flood_threshold
--reg-flood-window<SECS>10.5.189How much capture time one --reg-flood counting window spans, 1-3600 seconds. The threshold counts failures per window, so this decides how concentrated a credential-guessing run has to be: one refusal every two seconds never puts two inside the default one-second window, whatever the threshold. sipnab refuses 0 and anything past an hour. Config: [security] reg_flood_window_secs
--reg-flood-transaction-timeout<MS>320000.5.189How long a credentialed REGISTER stays open to the 401/407 that answers it, 1000-600000 milliseconds. The default is RFC 3261 Timer F, 64 times T1 at the default T1 of 500 ms. sipnab never counts a challenge that arrives later as a failure. Set it to 64 times the T1 your network runs, or to your registrar-side proxy’s final-response timer when that is longer: fr_timeout in OpenSIPS (seconds, so multiply by 1000) or fr_timer in Kamailio (already milliseconds). See Registration-flood timers. Config: [security] reg_flood_transaction_timeout_ms
--digest-leak–off0.3.0Detect digest credential leaks in SIP messages
--allow-action<TARGET:SURFACE[,SURFACE]>none0.5.196Enable an action that changes another system, from the named surfaces. sipnab changes no external system by default: it publishes what it saw to destinations you name and asks read-only questions, and an action, such as asking TFPS to ban a source, runs only when enabled here or under [actions] in the config file. Targets: tfps. Surfaces: rest, mcp. Repeatable, and added to what the config file enables. Enabling is one of two locks: a REST caller also needs a token minted with scope actions (--token-scope actions), and so does an MCP client over HTTP. A full token, or a static --api-key, reads everything and acts on nothing. Over MCP stdio the enabling is the only lock, because the client is a process you started. Refused, it answers 403 (REST) or a tool error (MCP) naming this flag, and tfps_ctl never runs. Enabled, sipnab records every action in the actions journal (see --journal-dir) before it asks TFPS, and refuses to start if it cannot use the journal. Every action also passes fixed rules and rate limits: some addresses are never banned (loopback, broadcast, multicast, 0.0.0.0), every ban expires (an hour unless asked otherwise, 7 days at most), and there are at most 10 actions a minute for the server, 5 for one caller, and one per address a minute. [action_limits] in the config file tightens or loosens those, and nothing turns them off. sipnab lifts only bans it placed itself, and a token lifts only the bans it placed unless it holds scope admin. Config: [actions] tfps
--journal-dir<DIR>/var/lib/sipnab/journal0.5.196Where sipnab records the actions it takes on other systems, so a restart knows what it did: which bans are its own, what was in flight when it stopped, and how much of each rate limit is already spent. Needed whenever --allow-action enables anything: sipnab creates the directory (mode 0700) if it can, and refuses to start if it cannot use it, or if another sipnab is already using it. The packaged service gets /var/lib/sipnab from systemd’s StateDirectory=. sipnab writes each record to disk before the action runs, and the files hold no token or request body. A file closes at 16 MiB, and sipnab deletes closed files more than 90 days old when the next one starts. A file that a root run of --revert-actions creates takes the directory’s owner, so the service can still read it. Config: [journal] dir
--journal-show––0.5.196Print what the actions journal says, then exit: the bans sipnab placed that are still in force, newest first, each with its action id, expiry and who asked; actions a crash left unfinished; bans TFPS shows that sipnab cannot prove it placed; how the last run ended; and the last hour’s refusals. Only reads, so it works while another sipnab holds the journal
--revert-actions<all|ACTION_ID>–0.5.196Back out what sipnab did, then exit: all lifts every ban sipnab placed that is still in force, newest first; an action id from --journal-show lifts that one. Works with actions switched off, because after misuse the first thing to do is switch them off. sipnab records each unban as a revert. It reports a ban TFPS already dropped rather than unbanning it, and leaves alone a ban it cannot prove it placed. While another sipnab holds the journal it refuses: revert through that sipnab (POST /v1/actions/revert or the actions_revert MCP tool), or stop it first. Changing TFPS’s blocks needs the privileges described in the TFPS guide, so on the TFPS host run it as root. Exits 0 when it backed out everything you named or it had already ended, 1 when something remains, 2 when the revert could not run
--tfps-ctl<PATH>–0.5.148Where the toll-fraud prevention system’s tfps_ctl program is, for the tfps_* MCP tools and the /v1/tfps/ REST routes. TFPS is optional peer software that condemns sources and enforces the decision in the firewall. sipnab reads its state; asking it to ban or unban a source is an action, refused unless --allow-action enables it. Absent: sipnab looks for tfps_ctl on PATH only the moment a TFPS tool runs, and a machine without one answers installed: false rather than failing. Config: [tfps] ctl; the database goes in [tfps] db
--recommend-block<DIALECT>–0.5.141Print a firewall rule for every source the detectors accused: fail2ban, nftables, iptables or all. sipnab recommends and does not apply — the rule is text on stdout, and nothing here reaches a firewall or holds a credential. Each block carries its evidence (how many findings named the address, which rules they tripped, and when) and its COUNTER-evidence beside it: a source that also completed a registration or a call is one a block would disconnect, so the address-specific dialects comment their commands out for it and the fail2ban dialect puts it in ignoreip. Needs a detector armed beside it; with none, nothing is ever accused and the empty output says which silence that is rather than reading as an all-clear. Requires -N
--findings-history<N>10000.5.96Security findings kept in memory for later retrieval. 0 keeps none. Config: [security] findings_history
--alert<CHANNEL>–0.3.0Repeatable alert channel (syslog, json, exec) or rule (<name>:<threshold>/<window>[:<cooldown>]). sipnab refuses a channel other than these three, and a rule naming a finding kind no detector reports, with exit 2; the same value in [security] alert exits 1, naming the key. Config: [security] alert
--alert-exec<CMD>–0.3.0Execute this command when an alert fires. Config: [security] alert_exec
--alert-json–off0.4.11Emit each security alert as a structured JSON line on stderr (in addition to the human [ALERT] line). Config: [security] alert
--stir-shaken–off0.3.0Report STIR/SHAKEN Identity claims — decodes the PASSporT, does NOT verify the signature. With --cores N sipnab refuses it with exit 2, as it refuses the detection flags, because the parallel reader does not report them
--run-provenance-file<FILE>–0.5.131Record the command that started this run, as one JSON line appended to FILE (seq, ts, record, argv, cwd, uid, user, pid, started, version, features, capture). Use it to trace a report, vCon container or exported pcap back to the capture, filter, port range and retention caps that produced it. capture is the same instance token the MCP and REST surfaces stamp on their answers, so a record joins to what that run produced. sipnab writes it once, at startup, before the config takes effect and before it opens any capture device; it reads the config file first, so capture.node is the node --node-name or [capture] node_name sets. Opened O_APPEND and never truncated, so successive runs accumulate; created mode 0600 when absent. The record replaces the value of a flag that takes a secret inline, such as --hep-auth or --api-key, with [redacted]. A record sipnab cannot write stops the run. Leave it off and nothing changes
--tui-audit-file<FILE>–0.5.131Record what the operator did in the TUI, one JSON line per action appended to FILE (seq, ts, record, action, target, format, caller, outcome, error). Actions, not keystrokes: the capture opened, the capture swapped, a filter applied or cleared, an export and its destination, including one sipnab refused. The search field is never recorded, neither the query nor the fact that the operator typed one. Same writer and same file shape as --mcp-audit-file: append-only, never truncated, one sequence number per record so a gap is a lost record, created mode 0600. A path sipnab cannot open stops the run before sipnab takes the terminal, and sipnab refuses -N rather than accepting it with nothing to record. A write that fails mid-session does NOT stop the TUI: the lost record leaves a permanent gap in the numbering, the status line says the trail is incomplete, and the closing session_end record and the exit message on standard error name the count. Leave it off and nothing changes. Feature: tui

--alert accepts a channel or a threshold rule. Channels are syslog, json or exec.

  • --syslog and --alert-json are the equivalent boolean forms. Naming the channel here does the same thing.
  • A value containing : is instead parsed as an alert rule (<name>:<threshold>/<window>[:<cooldown>]). The window needs an s/m/h suffix.

Rule names are scanner, fraud, digest and reg-flood (also reg_flood).

  • An unknown rule name exits 2 at startup.
  • A rule counts detector findings, not raw packets.

An unknown channel exits 2 and names the valid ones:

--alert: Unknown alert channel 'bogus': expected one of syslog, json, exec, or a rule written name:threshold/window

Examples

  • sipnab -N -I capture.pcap --no-fraud-detect --report — skip fraud detection for one run when your config file sets [security] fraud_detect = true

  • sipnab -N -I capture.pcap --fraud-detect --no-fraud-detect --report — the last of the pair wins, so no fraud detector runs

  • sipnab -N -I capture.pcap --no-kill-scanner --report — analyze without arming the scanner response that your config file’s [security] kill_scanner = true would arm

  • sipnab -N -I capture.pcap --kill-scanner --no-kill-scanner --report — the last of the pair wins, so sipnab arms no response

  • sipnab -N -I capture.pcap --kill-scanner-detect-only --report — report the scanners in a capture; on a live interface the same flag still sends them nothing

  • sudo sipnab -N -d eth0 --kill-scanner-detect-only --fail2ban — write fail2ban lines for the scanners a live interface sees, and send the scanners nothing

  • sipnab -N -I capture.pcap --no-kill-scanner-detect-only --report — analyze without the detect-only setting that your config file’s [security] kill_scanner_detect_only = true turns on

  • sudo sipnab -N -d eth0 --kill-scanner --no-kill-scanner-detect-only — answer scanners for one run although your config file sets [security] kill_scanner_detect_only = true

  • sudo sipnab -d eth0 --kill-scanner --kill-ua 'friendly-scanner' --kill-response 486 --kill-spoof auto — detect SIP scanners (plus a custom UA pattern) and reply 486 with the victim’s spoofed source

  • sudo sipnab -d eth0 --kill-scanner --kill-target 192.0.2.66:5060-5090 --kill-ua 'sipvicious' --kill-response 480 --kill-spoof raw — targeted kill of a scanning host across a port range, plus a second scanner UA, replying 480 via raw-socket spoof

  • sudo sipnab -d eth0 --kill-target 198.51.100.77:5060 --kill-spoof ephemeral — kill requests from one more source port using a non-spoofed ephemeral reply

  • sudo sipnab -N -d eth0 --reg-flood --digest-leak --fraud-detect --stir-shaken --alert json --alert-json --alert-exec '/usr/local/bin/notify.sh' — live security monitoring: registration floods, digest leaks, fraud, STIR/SHAKEN, with JSON alerts and an exec hook

  • sipnab -N -I capture.pcap --stir-shaken --digest-leak --alert-json — offline audit of a pcap for digest leaks and STIR/SHAKEN attestation claims (as the originator presented them — sipnab checks no signature), emitting structured JSON alerts

  • sudo sipnab -N -d eth0 --reg-flood --reg-flood-threshold 10 --fraud-detect --business-hours 8-18 --fraud-short-call 1 — tune the detectors to a small PBX: ten REGISTERs a second is a brute force here, calls outside office hours are worth a line, and a one-second call is the only one short enough to be a lure

  • sudo sipnab -N -d eth0 --reg-flood --reg-flood-threshold 400 --fraud-detect --business-hours 22-6 — the carrier-registrar shape instead: only a genuine flood clears 400 REGISTERs a second, and the quiet window is overnight

  • sudo sipnab -N -d eth0 --reg-flood --reg-flood-threshold 20 --reg-flood-window 60 — a slow credential-guessing run: twenty refused credentialed REGISTERs from one source inside a minute is a flood, even when no single second holds more than one

  • sipnab -N -I registrar.pcap --reg-flood --reg-flood-transaction-timeout 64000 — a network whose phones run T1 at one second, so RFC 3261 Timer F is 64 seconds and a registrar challenge forty seconds after the REGISTER still answers an open transaction

  • sipnab -N -I trunk.pcap --fraud-detect --fraud-short-call 6 --fraud-wangiri-calls 5 --fraud-sequential-calls 6 — audit a wholesale trunk where short calls are ordinary, so a lure needs five of them and a dial-plan walk needs six consecutive dead numbers

  • sipnab -d eth0 -N --kill-scanner --evidence-out - — publish findings on standard output for a reader to consume: sipnab says what it saw, and whatever consumes the stream decides what to do about it

  • sipnab -N -I trunk.pcap --kill-scanner --reg-flood --evidence-out findings.jsonl — replay a capture and collect every source-naming finding in one file, to read before you wire anything to a response

  • sipnab -d eth0 -N --fraud-detect --fraud-destination DO,VG,MA — an SBC that never dials the Dominican Republic, the British Virgin Islands or Morocco, three destinations where premium-rate fraud concentrates. The first INVITE to any of them alerts on sight

  • sipnab -N -I trunk.pcap --fraud-detect --fraud-destination CU,KP --json — replay a trunk capture and write every INVITE to an embargoed destination as one JSON line, for the audit that asked whether any call ever went there

  • sipnab -N -I pbx.pcap --fraud-detect --fraud-wangiri-calls 2 --fraud-sequential-calls 2 --fraud-volume-multiplier 3 --fraud-volume-min-calls 4 — the sensitive form for a small PBX, where two short calls to one prefix and four calls in a minute are already unusual

  • sipnab -N -I trunk.pcap --fraud-detect --fraud-volume-multiplier 20 --fraud-volume-min-calls 200 — a busy carrier trunk, where a spike has to be twenty times its own baseline and 200 calls a minute before it means anything

  • sipnab -N -I trunk.pcap --fraud-detect --fraud-wangiri-window 900 --fraud-short-call 5 — hunt a paced lure: three short calls to one prefix over fifteen minutes, which the shipped sixty-second window forgets between calls

  • sipnab -N -I pbx.pcap --fraud-detect --fraud-volume-window 5 --fraud-volume-min-calls 20 — catch a burst shorter than a minute, which a sixty-second window averages into the ordinary traffic around it

  • sudo sipnab -N -d eth0 --fraud-detect --fraud-volume-window 300 --fraud-wangiri-window 300 — a low-volume site where five minutes is the shortest span that holds enough calls to say anything about either pattern

  • sipnab -N -I sbc.pcap --kill-scanner --scanner-behavioral-probes 200 --scanner-enumeration-targets 60 — an SBC that fronts a whole site behind one address, where ordinary aggregated traffic clears the shipped ten probes and five extensions within seconds

  • sipnab -N -I slow-sweep.pcap --kill-scanner --scanner-window 600 --scanner-enumeration-targets 8 — hunt a paced sweep across a ten-minute window: one probe every ten seconds never puts two inside the shipped five-second window, so widening the window is the only setting that reaches it

  • sudo sipnab -N -d eth0 --kill-scanner --scanner-window 60 --scanner-behavioral-probes 40 --scanner-rejected-probes 20 — a busy registrar that refuses every unauthenticated first attempt, so refusals only count as evidence once there are twenty of them in a minute

  • sipnab -N -I sat-trunk.pcap --kill-scanner --scanner-answer-grace 3000 --scanner-unanswered-probes 20 — a satellite trunk whose round trip runs well past RFC 3261’s Timer T1, where the shipped 500 ms grace calls every probe still in flight unanswered

  • sudo sipnab -N -d eth0 --kill-scanner --scanner-established-factor 1 --scanner-rejected-probes 8 — judge a registered endpoint like any other source, for a site hunting compromised handsets rather than outside scanners

  • sipnab -N -I pbx.pcap --kill-scanner --scanner-answer-grace 1500 --scanner-established-factor 8 --scanner-unanswered-probes 10 — a small PBX on a slow access circuit, where a registered phone needs eight times the evidence and every probe gets a second and a half to draw a reply

  • sudo sipnab -N -d eth0 --kill-scanner --kill-rate-limit 2 --findings-history 20000 — answer scanners at a deliberately small two responses a second while keeping a long detection history for an agent to read back

  • sipnab -N -I capture.pcap --kill-scanner --recommend-block nftables — read a capture you already have and print an nft rule per accused source, with the evidence and the counter-evidence in the same block. sipnab prints. You decide and run it

  • sipnab -N -I trunk.pcap --kill-scanner --reg-flood --digest-leak --recommend-block all --quiet > blocks.txt — every dialect for every accused source on a trunk capture, with the packet echo suppressed so the file holds nothing but the recommendation. Read blocks.txt before running any of it: an address that also completed a call is one a block disconnects, and the block says so

  • sudo sipnab -N -d eth0 --kill-target 192.0.2.66 --kill-rate-limit 50 --findings-history 0 — a targeted response with a wider transmit budget and no findings retained in memory

  • sipnab -N -I capture.pcap --json --run-provenance-file /var/log/sipnab-runs.jsonl — record the invocation beside the report, so a reader can trace the JSON above back to the capture, the filters and the build that produced it

  • sudo sipnab -N -d eth0 --portrange 5060-5090 --report --run-provenance-file /var/log/sipnab-runs.jsonl — the case the record exists for: the port range decided what this run could see, and without the record a later reader cannot tell a narrow run from a complete one

  • sipnab -I capture.pcap --tui-audit-file /var/log/sipnab-tui.jsonl — record which capture the analyst opened, what they filtered it down to and what they exported, for the review that opens with “who exported which calls”

  • sudo sipnab -d eth0 --tui-audit-file /var/log/sipnab-tui.jsonl --run-provenance-file /var/log/sipnab-runs.jsonl — both halves on a live capture: one line saying how the session started, and one record per state-changing thing the operator then did

  • sipnab -N -d eth0 --mcp --tfps-ctl /usr/local/bin/tfps_ctl — serve the MCP tools with TFPS named outright, so tfps_status and tfps_banned answer from that installation rather than whatever PATH finds

  • sipnab -N -I trunk.pcap --api 127.0.0.1:8080 --api-key "$SIPNAB_API_KEY" --tfps-ctl /opt/tfps/bin/tfps_ctl — replay a capture behind the REST API and let GET /v1/tfps/labels fetch the verdict log the label harness scores it against

  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-signing-key-file /etc/sipnab/api.key --allow-action tfps:rest — let a REST caller holding a token minted with --token-scope actions ask TFPS to ban and unban through POST /v1/tfps/ban and /unban, and back a ban out through POST /v1/actions/revert. Every other credential still reads the TFPS routes and gets 401 on those three

  • sudo sipnab -N -d eth0 --mcp --allow-action tfps:mcp — let the local agent this process serves over stdio call tfps_ban, tfps_unban and actions_revert. Without the flag the three tools refuse and name it, and the handshake tells the agent this server changes no external system

  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-signing-key-file /etc/sipnab/api.key --allow-action tfps:rest --journal-dir /srv/sipnab/journal — the same REST actions with the journal on a dedicated volume rather than under /var/lib

  • sipnab -N -I capture.pcap --mcp --allow-action tfps:mcp --journal-dir "$HOME/.local/state/sipnab/journal" — try the MCP ban tools as an unprivileged user, with the journal in your home directory instead of the system one

  • sipnab --journal-show — list the bans sipnab placed that are still in force, with the action ids --revert-actions takes

  • sipnab --journal-show --journal-dir /srv/sipnab/journal — the same for a journal kept somewhere other than the default, while the sipnab that writes it keeps running

  • sudo systemctl stop sipnab && sudo sipnab --revert-actions all — after a stolen token or a bad automation run: stop the service, then lift every ban sipnab placed, newest first. Run as root, because changing TFPS’s blocks needs privileges the service gets from its unit

  • sudo sipnab --revert-actions a-r-68d9c1f2-4412-3 --tfps-ctl /opt/tfps/bin/tfps_ctl — with the service stopped, lift one ban by the action id --journal-show printed, asking the TFPS installation named outright

Event execution

FlagValueDefaultSinceDescription
--on-dialog-exec<CMD>–0.3.0Execute command when a dialog state changes
--on-quality-exec<CMD>–0.3.0Execute command when RTP quality drops below threshold. How-to: Measure call quality
--exec-rate-limit<N>100.3.0Maximum exec invocations per second (rate limit). 0 means no limit
--exec-queue-depth<N>1000.5.100Hook commands allowed to be running at once before sipnab drops --on-dialog-exec and --on-quality-exec events. The second ceiling above --exec-rate-limit, and the binding one for any hook that takes longer than a second: its slot is still occupied when the next second’s budget arrives, so on a busy trunk this is what events actually meet. Config: [limits] exec_queue_depth

What a hook receives. Every command gets the event as environment variables.

  • --on-dialog-exec sets SIPNAB_CALL_ID, SIPNAB_FROM, SIPNAB_TO, SIPNAB_STATE, SIPNAB_METHOD and SIPNAB_JSON (the whole dialog).
  • --on-quality-exec sets SIPNAB_SSRC, SIPNAB_MOS, SIPNAB_JITTER, SIPNAB_LOSS and SIPNAB_STREAM_JSON. That last one is the stream object under its own name, never SIPNAB_JSON.
  • --alert-exec hooks set SIPNAB_SRC (the source address), SIPNAB_RULE and SIPNAB_DETAIL.

A hook inherits sipnab’s whole environment, credentials included.

  • sipnab does not clear the environment before spawning.
  • So a hook sees SIPNAB_API_SIGNING_KEY, SIPNAB_MCP_TOKEN and SIPNAB_HEP_AUTH if you set them there. Authentication recommends exactly that.
  • A hook that calls back into the API uses those credentials.

It does mean a hook command is as trusted as the process.

  • Do not pass one a string built from capture data.
  • Do not run one you would not run by hand with your keys exported.

Hooks cannot gain privileges.

  • sipnab sets PR_SET_NO_NEW_PRIVS at startup on Linux. It does so on every run, whether or not it is root.
  • Every command it spawns inherits that flag.
  • A hook may run anything you can already run.
  • What it cannot do is get more than you have through a setuid or setgid helper. sudo, pkexec and ping start, then fail for want of the privilege they normally acquire.

A hook that needs to act privileged should ask something that already is, rather than trying to become privileged itself. For example:

  • a socket to a daemon,
  • a systemd unit it triggers.

This applies to root runs and to unprivileged runs set up with sipnab --setup-caps.

Examples

  • sudo sipnab -d eth0 --on-dialog-exec 'logger sipnab $SIPNAB_CALL_ID' --exec-rate-limit 5 --exec-queue-depth 20 — a slow syslog hook on a busy trunk, where twenty concurrent children is the ceiling events actually meet rather than the five-a-second budget
  • sipnab -N -I trunk.pcap --on-quality-exec 'curl -m 30 -X POST http://hook/quality' --exec-queue-depth 4 — a webhook that may take thirty seconds, held to four in flight so a stalled endpoint cannot fork the box flat

Network listeners

FlagValueDefaultSinceDescription
--metrics<ADDR>–0.3.0Prometheus metrics endpoint (e.g., 127.0.0.1:9090). Serves in BOTH TUI and headless (-N) runs — headless is where a container or systemd unit uses it. sipnab refuses a non-loopback bind (e.g. 0.0.0.0:9090) unless you also pass --metrics-auth/--metrics-auth-file, and the run then exits non-zero rather than carrying on without an endpoint — so in sipnab -N -d eth0 --metrics 0.0.0.0:9090 && echo serving the echo never runs, matching --api. Note a file run (-I) exits as soon as it finishes the capture, so there is little to scrape; the endpoint is for long-lived runs — a live device, --hep-listen, or a served API/MCP. Not served on the --cores N parallel offline path, which finishes and exits before a scrape could land; sipnab warns when you combine them. Feature: metrics
--metrics-auth<USER:PASS>–0.3.0HTTP Basic auth credentials (user:pass) required by the metrics endpoint; requests must send Authorization: Basic <base64>. Prefer --metrics-auth-file. Feature: metrics
--metrics-auth-file<FILE>–0.5.28Read the metrics Basic-auth user:pass from a file (contents trimmed), keeping the secret out of the process list. Takes precedence over --metrics-auth. Feature: metrics
--metrics-tls-cert<FILE>–0.5.203Serve the metrics endpoint over HTTPS with this PEM certificate chain, the server’s certificate first. Needs --metrics-tls-key; either alone stops sipnab at startup rather than serving plain HTTP on a port meant for HTTPS. The port then speaks HTTPS only: TLS 1.2 and 1.3, no client certificates; scrapers still authenticate with --metrics-auth. A file sipnab cannot read, or that holds no certificate, stops it at startup naming the file. Refused without --metrics. See Metrics TLS. sipnab refuses an empty path with exit 2, and an empty [metrics] tls_cert with exit 1. Feature: metrics. Config: [metrics] tls_cert
--metrics-tls-key<FILE>–0.5.203PEM private key for --metrics-tls-cert. sipnab refuses a key any other user on the host can read (chmod 600 it; group-readable stays allowed), a file with no private key, and a key that does not belong to the certificate, each at startup and naming the file. Refused without --metrics. sipnab refuses an empty path with exit 2, and an empty [metrics] tls_key with exit 1. Feature: metrics. Config: [metrics] tls_key
--api<ADDR>–0.3.0REST API endpoint (e.g., 0.0.0.0:8080). Feature: api
--api-key<KEY>–0.3.0API key for REST API authentication. Also reads $SIPNAB_API_KEY. --help names the variable and never shows its value. Feature: api
--api-tls-cert<FILE>–0.3.0Serve the REST API over HTTPS with this PEM certificate chain, the server’s certificate first. Needs --api-tls-key; either alone stops sipnab at startup rather than serving plain HTTP on a port meant for HTTPS. The port then speaks HTTPS only: TLS 1.2 and 1.3, ALPN http/1.1 (no HTTP/2), no client certificates. A file sipnab cannot read, or that holds no certificate, stops it at startup naming the file. A reverse proxy in front of a loopback bind remains an alternative. See API TLS. sipnab refuses an empty path with exit 2, and an empty [api] tls_cert with exit 1. Feature: api. Config: [api] tls_cert
--api-tls-key<FILE>–0.3.0PEM private key for --api-tls-cert. sipnab refuses a key any other user on the host can read (chmod 600 it; group-readable stays allowed), a file with no private key, and a key that does not belong to the certificate, each at startup and naming the file. sipnab refuses an empty path with exit 2, and an empty [api] tls_key with exit 1. Feature: api. Config: [api] tls_key
--api-max-conn<N>1000.3.0Maximum API requests handled at once; sipnab answers 503 to a request past it. 0 means no limit. Feature: api
--api-allowed-host<HOST>–0.5.198Additional Host header values the REST API accepts (repeatable), against DNS rebinding: a web page that points its own name at 127.0.0.1 and reads a loopback API with no key from your browser. Accepted by default: localhost, 127.0.0.1, ::1 and the address --api binds, on any port, and any IP address when --api binds 0.0.0.0 or ::. Add the name clients use, such as a reverse proxy’s public name; name:port accepts that port only. Any other Host gets 403, and a request with no Host gets 400. * disables the check (not recommended). See Which Host names the API answers. Config: [api] allowed_hosts, which the flag replaces Feature: api
--api-signing-key<KEY>–0.4.3HMAC signing key for self-describing bearer tokens, taken as raw bytes (any string — not hex-decoded). Repeatable: the first mints, verification accepts every one, so keys can rotate with overlap. Also reads $SIPNAB_API_SIGNING_KEY; --help names the variable and never shows its value. See auth.md. Feature: api
--api-signing-key-file<FILE>–0.4.3Read an API signing key from a file (contents trimmed); it becomes the minting key. Feature: api
--api-revoked-file<FILE>–0.4.3Revocation denylist: one revoked token id per line; reloaded on mtime change. Feature: api
--api-token-ttl<SECS>36000.4.3Default TTL (seconds) when minting API tokens with --mint-token. Feature: api
--api-max-rows<N>10000.5.100Rows one list-style REST response returns. The REST counterpart of --mcp-max-rows. Raise it for a batch consumer that reads every row of /v1/dialogs; lower it for a dashboard. A caller may always ask for less with ?limit=, and nothing it sends asks for more than this. Config: [limits] api_max_rows Feature: api
--api-rate-limit-per-peer<N>1000.5.100REST requests one client IP may make per second; 0 disables the cap, the reading --mcp-rate-limit-per-peer and --hep-rate-limit also give it. The limiter counts by source address, so a dashboard polling /v1/streams on a short timer, or several collectors behind one NAT, share a single allowance. A refused request gets 503, not 429: the limiter runs before authentication and says nothing about the credential. Config: [limits] api_rate_limit_per_peer Feature: api
--api-allow-relay-query––0.5.170Let REST clients query the relay named by --rtpengine-control through the six GET /v1/relay/ routes (stats, stats/names, stats/call/{call_id}, compare/{call_id}, holdings and holdings/{call_id}), which transmit. The REST counterpart of --mcp-allow-relay-query, off for the same reason: every other REST answer comes from bytes sipnab already holds, and these put a packet on the network — at the address that flag names, never one a client chooses. Without a relay address (no --rtpengine-control), those routes answer not_configured. With one, they answer not_permitted when this flag is absent, and on a file-backed run even with the flag. Each response is HTTP 200 carrying an outcome, so a refusal is content, not a 4xx. Starting a poll is not offered over REST. GET /v1/relay/stats/polled reads what this run’s --relay-stats-interval poll obtained; it sends nothing and needs no flag. Feature: api. How-to: Ask a media relay about itself
--api-allow-vcon-fetch–off0.5.221Let REST clients read one vCon by uuid from the store the [vcon_fetch] keys configure, through GET /v1/vcon/fetch/:uuid, which connects to that store. The REST counterpart of --mcp-allow-vcon-fetch, off for the same reason. The store, its kind, the credential and the decryption key come from [vcon_fetch] only, never from the request, and the answer holds no key. Without this flag the route answers 503 naming it; with it and no [vcon_fetch] url, 503 naming the keys. sipnab stops at startup when it cannot read the store’s credential or key file. The container comes back in the answer, and sipnab writes no file. Feature: api and vcon
--api-accept-archive-passwords–off0.5.187Accept the Sipnab-Archive-Password header on GET /v1/captures/compare from a client that is not on this host. Without --api-tls-cert sipnab serves plain HTTP, so the password crosses the network in the clear unless TLS terminates in front: making sure it does is your job when you pass this. Without it, only a loopback client may send one, and a remote one gets 403, also when --api-tls-cert serves HTTPS. Feature: api
--api-file-root<DIR>–0.5.177Directory of capture files GET /v1/captures/compare may diff. The REST counterpart of --mcp-file-root, off for the same reason: every other REST answer comes from bytes sipnab already holds, while this reads FILES a client names. It takes a bare FILENAME, never a path: sipnab rejects a separator, a .., an absolute prefix or a control character before touching the filesystem, and refuses a symlink that resolves out of the root at open, by the same check -O uses. Without this flag the compare route answers 503. Naming one directory means the worst a client can do is name files inside it. Feature: api
--metrics-max-conn<N>160.5.100Metrics scrapes served at once before further ones get 503. Sixteen suits one Prometheus server; raise it when an HA pair, a federating parent, remote_write shards or sidecars scrape the same endpoint. A refused scrape leaves a gap in the series. Config: [limits] metrics_max_conn Feature: metrics
-L, --hep-listen<ADDR>–0.3.0Listen for HEP (Homer Encapsulation Protocol) packets. Combines with -d <iface>: HEP then supplies signaling while the interface supplies the RTP a HEP feed cannot carry, and streams bind to dialogs by SDP media endpoint. With no BPF expression of your own, the interface gets a media-only default, since the mirror carries the signaling; an explicit expression such as sipnab -N -d eth0 -L 127.0.0.1:9060 "udp portrange 10000-20000" narrows it to your media ports. One interface and one listener only: --multi-device with -L, -I with -L, and -O alongside the pair are each refused with the reason. See cookbook recipe “Take signaling from HEP and media off the wire, in one process”. The decrypted TLS and WebSocket legs OpenSIPS and Kamailio trace arrive with IP protocol 22, 50 or 6 and decode as TLS, WS or WSS, see How --hep-listen reads the transport. A UDP listener also takes the OpenSIPS E_SIPNAB_TH_MAP event, a change to the OpenSIPS script only, from a sender --hep-allow lists (Have OpenSIPS send the mapping). --portrange does not apply to HEP messages, so a proxy tracing SIP on 7060 needs no --portrange 1-65535. Each message takes the time its HEP packet carries; a HEP v3 packet without a TS_SEC chunk, and a HEP v1 packet, take the time sipnab received them. sipnab reads HEP v1 and v2 packets in the layout the captagent capture agent writes and Kamailio’s sipcapture module reads: IPv4 or IPv6 addresses by the family byte, the transport by the IP protocol byte, and for v2 the time and capture ID in its time header. Steps for each SIP server, a shared secret, TCP and TLS: Collect and forward HEP. Feature: hep
-H, --hep-send<ADDR>–0.3.0Send captured packets via HEP to a remote collector: SIP as protocol type 1, and RTCP as type 5 in the JSON form heplify sends, carrying the Call-ID of the call whose SDP set up that media. RTCP for a call whose SDP sipnab never saw is not sent, since Homer stores none of it. RTP is never forwarded. On -I <file> this forwards the file’s contents: every SIP message sipnab reads out of the capture goes to <ADDR> as recorded, and every RTCP report of a call in it as JSON, redacted in no way. sipnab announces that at startup, naming the flag, the destination and the capture files, before it reads the first packet. See What --hep-send sends. A headless run ends with one line saying what it exported: packets sent, queued, replayed after an outage, dropped by reason (queue_full, stopped, unsent_at_exit), still queued, failed attempts by kind (connect, tls_handshake, write) and reconnects, at warn when anything failed or sipnab dropped a packet. Over UDP “sent” means handed to the kernel, and a collector that is down produces no failure; the line says so. runtime_stats, GET /v1/runtime (hep_export) and the Prometheus sipnab_hep_export_* series carry the same counts while it runs. Steps for each SIP server, a shared secret, TCP and TLS: Collect and forward HEP. Feature: hep
--hep-send-transport<udp|tcp|tls>udp0.5.150Transport --hep-send uses to reach the collector. Homer’s collectors accept all three. HEP v3 carries its own total length, so a tcp or tls feed is packets laid end to end with no extra framing, and sipnab sets TCP_NODELAY because these are small packets whose value is timeliness. sipnab opens the first connection at startup: when the collector refuses it, or it does not complete within --hep-send-timeout (default 3 seconds), sipnab stops with exit 2 and names the error, such as Failed to connect HEP sender to '127.0.0.1:9061' over TCP: Connection refused (os error 111). After that, a write that fails keeps its packet queued and drops the connection, and the sending thread dials again with backoff, never in a tight loop. Each dial gives up after --hep-send-timeout and counts as a connect failure, as a refused one does. See When the --hep-send collector goes away. Refused without --hep-send, which is the side it governs. Feature: hep
--hep-send-queue<MIB>640.5.226How many MiB of HEP packets --hep-send holds while the collector is unreachable, 1 to 4096. Sending runs on its own thread behind this queue, so a slow or dead collector never slows the capture, and sipnab replays the queue in capture order when the collector answers. At the bound sipnab drops the oldest packet and counts it as queue_full. A stop (SIGINT/SIGTERM) discards the queue and counts it as stopped: sipnab flushes nothing at shutdown. Refused without --hep-send; [hep] send_queue_mib on a run without --hep-send gets a startup warning naming it instead. Config: [hep] send_queue_mib. Feature: hep
--hep-send-timeout<SECS>30.5.226Seconds one TCP dial or stalled write to the --hep-send collector may take before the collector counts as unreachable, 1 to 300. It also bounds the first connection at startup, which fails the run with exit 2. A TLS handshake keeps its own 10-second bound. Retries back off from 0.1 s, doubling, to at most 5 s. A file read to its end waits for the queue to drain, and gives up after two timeouts plus 5 s with no delivery. Refused without --hep-send; [hep] send_timeout_secs on a run without --hep-send gets a startup warning naming it instead. Config: [hep] send_timeout_secs. Feature: hep
--hep-listen-transport<udp|tcp|tls>udp0.5.150Transport --hep-listen accepts. A tcp or tls listener serves several agents at once, one connection each, and reads every connection as HEP v3 packets delimited by the total length in each header. HEP v1 and v2 are datagram-only: they declare no total length, so nothing can delimit it on a stream. A peer that sends bytes that are not HEP v3 loses its connection, because a stream offers no point to resynchronize at. Refused without --hep-listen, which is the side it governs. Feature: hep
--hep-tls-ca<FILE>–0.5.150Certificate authority (PEM) this sender checks the collector’s certificate against under --hep-send-transport tls. A named CA replaces the trust store rather than joining it, so a private collector’s issuer is the whole of what this sender accepts (--hep-tls-extra-ca adds a CA to the host’s bundle instead); a file sipnab cannot read, or that holds no certificate, raises an error rather than an empty store. Omit it to use the host’s CA bundle ($SSL_CERT_FILE, else /etc/ssl/certs/ca-certificates.crt and its equivalents). sipnab refuses it unless --hep-send-transport tls names the transport. Feature: hep. Config: [hep] tls_ca
--hep-tls-extra-ca<FILE>–0.5.203Certificate authority (PEM) this sender trusts in addition to the host’s CA bundle under --hep-send-transport tls: the sender accepts the collector’s certificate when the host’s bundle or this file issued it. Use it for a collector whose certificate comes from a private CA on a host whose public roots should still count; use --hep-tls-ca to trust only the named file. sipnab refuses the two flags together. The file must hold at least one certificate, and the host must have a CA bundle to add it to: on a host without one, sipnab refuses at startup and names --hep-tls-ca. sipnab refuses it unless --hep-send-transport tls names the transport. Feature: hep. Config: [hep] tls_extra_ca
--hep-tls-cert<FILE>–0.5.150Server certificate chain (PEM, leaf first) a --hep-listen-transport tls listener presents to connecting agents. It must be an end-entity certificate: rustls refuses a CA certificate offered as a server certificate. sipnab refuses it unless --hep-listen-transport tls names the transport, and that transport requires it. Feature: hep. Config: [hep] tls_cert
--hep-tls-key<FILE>–0.5.150Private key (PEM) for --hep-tls-cert. sipnab refuses a key any other user on the host can read — chmod 600 it. Group-readable stays allowed, because root:sipnab 0640 is how a key is normally handed to a service account. sipnab refuses it unless --hep-listen-transport tls names the transport, and that transport requires it. Feature: hep. Config: [hep] tls_key
--hep-id<ID>10.5.8Capture-agent id (HEP 0x000c chunk) stamped on packets sent via --hep-send. Refused without --hep-send, the side it governs, exit 2. Feature: hep
--hep-auth<KEY>–0.5.8Homer authenticate key (HEP 0x000e chunk). On --hep-send sipnab stamps it on every outgoing packet; on --hep-listen it enables receiver-side authentication — incoming packets must carry a matching key, which sipnab compares in constant time, or it drops them. Also read from SIPNAB_HEP_AUTH; --help names the variable and never shows its value. Security note: the key travels in cleartext inside the HEP datagram, so it defeats blind/off-path spoofing but an on-path sniffer can capture and replay it. Over an untrusted path, tunnel HEP through WireGuard/IPsec/stunnel (the same posture as terminating API TLS in a reverse proxy) rather than relying on the key alone. Feature: hep
--hep-auth-file<FILE>–0.5.28Read the HEP shared secret from a file (contents trimmed), keeping it out of the process list. Takes precedence over --hep-auth. Refused without --hep-listen or --hep-send, the two sides it governs, exit 2. Feature: hep
--hep-auth-mode<plain|hmac>plain0.5.28HEP auth mode. plain sends/expects the shared secret verbatim in the 0x000e chunk (Homer-compatible, but replayable by an on-path sniffer). hmac sends/expects a per-message token (timestamp + nonce + HMAC-SHA256 over the payload) that resists replay — sipnab-to-sipnab only; a stock Homer/Kamailio peer does not understand it. Refused without --hep-listen or --hep-send, the two sides it governs, exit 2. Feature: hep
--hep-hmac-window<SECS>300.5.100Seconds either side of now within which sipnab still honors a --hep-auth-mode hmac token’s timestamp. When the agent’s and the collector’s clocks differ by more than this, sipnab refuses every packet as out of window and the listener admits nothing; --hep-silence-warn then names hmac_timestamp_out_of_window. A wider window keeps a packet captured on the path replayable for longer. Range 1-300. Refused without --hep-listen, the side that checks the window, exit 2; a [security] hep_hmac_window_secs on a run without --hep-listen gets a startup warning naming it instead. Config: [security] hep_hmac_window_secs. Feature: hep
--hep-silence-warn<SECS>300.5.185Seconds a --hep-listen listener may go without admitting a packet before it logs a warning; 0 turns the warning off. Only an admitted packet counts as traffic. When packets keep arriving and sipnab refuses every one, the warning says no packets admitted and names the refusal most of them met (auth_mismatch, allowlist, hmac_timestamp_out_of_window and so on) and a peer that sent them: the sender is reaching the port and sipnab is turning it away, which is a key, mode or allowlist problem rather than a network one. When nothing arrives at all the warning keeps its no packets for wording. Refused without --hep-listen, which is the side it governs. Feature: hep
--hep-senders–off0.5.185At the end of a headless (-N) run, print who fed the --hep-listen listener: each sender, keyed by the capture id it claims and the address it sent from, with its packet count, when sipnab last heard from it, how long it has been idle and whether it went silent past --hep-silence-warn; then every address the listener refused, with counts by reason, and the refusal totals. The capture id is the sender’s claim, never proven. With --json, the same as one JSON object on the last line of stdout, the shape GET /v1/hep/senders and the MCP hep_senders tool return. The roster covers this run only and nothing persists it. Refused without --hep-listen, which is the side it describes. Feature: hep
--th-mappings–off0.5.233At the end of a headless (-N) run, print the OpenSIPS topology-hiding mappings the --hep-listen listener received in E_SIPNAB_TH_MAP events: a line with the event counts, a line with the hidden legs by status (decoded, no_secret, undecodable, unmatched, mapped, conflict), then one row per mapping with its caller-side and callee-side Call-IDs, its state (conflict, pending or joined) and whether sipnab captured each leg, conflicts first. A pending row names the leg whose SIP did not arrive. With --json, the same as one JSON object on the last line of stdout, the shape GET /v1/th-mappings and the MCP th_mappings tool return. Refused without --hep-listen, where the events arrive. How-to: Find why a call through topology hiding did not join. Feature: hep
-E, --hep-parse–off0.3.0Parse incoming HEP packets (enable HEP decoding): sipnab unwraps a HEP datagram on the wire or in a capture file and reads its message with the addresses the HEP chunks carry and the time the HEP packet carries, as -L does, not the time sipnab sniffed the wrapper. A HEP packet that carries no time (a HEP v3 packet without a TS_SEC chunk, or a HEP v1 packet) keeps the time sipnab sniffed the wrapper; a HEP v2 packet carries its time in its time header. Sniffing a HEP port that no process listens on loses messages from some senders: see the note under [capture] in the configuration reference. The transport comes from the IP protocol chunk by the same rule --hep-listen uses, so a TLS, WS or WSS leg an OpenSIPS or Kamailio tracer sends reads the same from a file as from a socket, see How --hep-listen reads the transport. --portrange does not apply to the HEP messages it unwraps. It still gates SIP that sipnab captures directly. It applies wherever sipnab reads packets: a headless run, --cores N, the TUI’s own capture, and a capture opened or re-scanned inside the TUI. --no-hep-parse turns it off again when the config file sets [capture] hep_parse = true. Config: [capture] hep_parse. Feature: hep
--no-hep-parse–off0.5.204Do not unwrap HEP found in the capture, even when [capture] hep_parse = true. The last of -E / --no-hep-parse typed wins. Config: [capture] hep_parse. Feature: hep
--hep-allow<ADDR>–0.3.0Allowed source addresses for HEP input (repeatable). Takes CIDR (192.0.2.0/8, 2001:db8::/32) or a bare address (192.0.2.40), which means that host alone — /32 for IPv4, /128 for IPv6. A missing prefix always narrows, never widens: 192.0.2.0 is one host, not 192.0.2.0/8. sipnab refuses a non-loopback --hep-listen bind unless you pass either this or --hep-auth/--hep-auth-file. It is also the only check on an OpenSIPS E_SIPNAB_TH_MAP event, which carries no HEP authentication: the listener accepts an event only from an address listed here, and with no list it refuses every event, counted as event_sender_not_listed. List the address OpenSIPS sends the event from: 127.0.0.1 when OpenSIPS and sipnab share a machine (Have OpenSIPS send the mapping). Refused without --hep-listen, the side it governs, exit 2; -E reads no allowlist. Feature: hep
--hep-rate-limit<N>500000.3.0Maximum HEP packets per second (global ceiling across all senders); 0 disables the global ceiling, consistent with off on the per-peer knob. Refused without --hep-listen, the side it governs, exit 2; a [limits] hep_rate_limit on a run without --hep-listen gets a startup warning naming it instead, because one config file serves runs with and without a listener. Feature: hep. Config: [limits] hep_rate_limit
--hep-rate-limit-per-peer<N|auto|off>off0.5.28Maximum HEP packets/second from any single source IP: a number, off (the default), or auto. Adds fairness so one flooding peer cannot exhaust the global --hep-rate-limit. auto divides the global ceiling evenly across the --hep-allow sources (stays off without an allowlist). The listener logs its active limiters at startup. Refused without --hep-listen, the side it governs, exit 2. Feature: hep
--hep-allow-kill–off0.5.28Allow scanner-kill to send active responses for packets received via HEP, and admit HEP-carried detections to the --fail2ban jail log. Off by default: a HEP sender asserts the inner src/dst, so absent --hep-auth an attacker could aim the kill at a victim of their choosing, or have the jail ban an address of their choosing. Only enable with authenticated, trusted HEP input. Feature: hep
--syslog–off0.3.0Send alerts to syslog. Config: [security] alert
--mint-token–off0.4.3Mint a signed bearer token from the first configured signing key (API or MCP), print it to stdout, and exit (no capture/servers). See auth.md.
--token-id<ID>–0.4.3Token id (jti) for --mint-token, used for revocation. Defaults to a generated id.
--token-scope<full|metrics|read|actions|admin|audio|inventory>full0.5.59Scope for --mint-token. metrics reaches GET /metrics and returns 401 everywhere else — mint one for a scrape job rather than a credential that also reads /v1/dialogs and the message bodies underneath. read is the MCP counterpart: it reaches the tools annotated read-only and refuses every tool that writes or replaces state (export_capture, open_capture, shutdown_server, tfps_ban and the rest; tools/list marks each one with readOnlyHint: false). actions reaches everything full does plus the actions that change another system, such as TFPS ban and unban, where the server enabled them with --allow-action; a full token never reaches an action. A ban belongs to the caller that placed it, so an actions token lifts only its own bans; admin reaches everything actions does and also lifts a ban another caller placed. audio reaches the REST API’s live call audio routes (GET /v1/calls/live, GET /v1/calls/live/events and GET /v1/calls/{call_id}/audio) and nothing else, and no other scope reaches them. inventory reaches the REST API’s GET /v1/inventory and nothing else, for sipnab fleet inventory. A cross-surface mint fails at mint time, so sipnab refuses metrics, audio and inventory with MCP and read with the REST API rather than issuing them and then rejecting them.

Examples

  • sipnab -N -I capture.pcap --no-hep-parse --report — read HEP datagrams as plain UDP when your config file sets [capture] hep_parse = true

  • sipnab -N -I capture.pcap -E --no-hep-parse --report — the last of the pair wins, so HEP is not unwrapped

  • sudo sipnab -d eth0 --api 127.0.0.1:8080 --api-signing-key-file /etc/sipnab/signing.key --api-revoked-file /etc/sipnab/revoked.txt --api-token-ttl 7200 --api-max-conn 200 --metrics 127.0.0.1:9090 --metrics-auth alice:s3cret — live capture serving a signed-token REST API, a revocation list, and a Basic-auth’d Prometheus endpoint (loopback, so no TLS)

  • sudo sipnab -d eth0 --api 0.0.0.0:8080 --api-signing-key-file /etc/sipnab/signing.key --api-token-ttl 3600 --api-max-conn 100 --metrics 127.0.0.1:9090 --metrics-auth bob:hunter2 — public-facing API tuned to 100 connections and 1h token TTL, with its own auth’d metrics endpoint

  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --api-max-rows 100000 --api-rate-limit-per-peer 0 — a batch consumer’s API: one GET /v1/dialogs?limit=100000 drains the whole store in a single page, and the per-peer cap is off so a scripted pager is not throttled against itself

  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --api-max-rows 200 --api-rate-limit-per-peer 600 — the opposite, for a dashboard: short pages a browser can render, and six hundred requests a second per address, so a dozen browsers refreshing twice a second behind one office NAT share an allowance that fits them

  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --rtpengine-control 127.0.0.1:22222 --api-allow-relay-query — a live capture whose REST clients may also ask the relay: GET /v1/relay/stats returns its counters as one JSON object with an outcome, transmitting once per request. Off without this flag, when those routes answer not_permitted

  • sipnab -N -I capture.pcap --api 127.0.0.1:8080 --api-key s3cret --api-allow-relay-query --rtpengine-control 127.0.0.1:22222 — the relay routes still answer on a file run, but with outcome: not_permitted: a file-backed run holds no transmit permit, so no packet reaches the relay, and the addresses in a capture belong to third parties

  • sipnab -N -I capture.pcap --config sipnab.toml --api 127.0.0.1:8080 --api-key s3cret --api-allow-vcon-fetch — REST clients may read one vCon by uuid with GET /v1/vcon/fetch/:uuid from the store [vcon_fetch] names; without the flag that route answers 503

  • sipnab -N -I capture.pcap --config sipnab.toml --api 127.0.0.1:8080 --api-key s3cret --api-allow-vcon-fetch --mcp --mcp-transport http --mcp-allow-vcon-fetch — both doors, each opted in by its own flag and reading the one store [vcon_fetch] names

  • sipnab -N -I today.pcap --api 127.0.0.1:8080 --api-key s3cret --api-file-root /srv/pcaps --archive-password-file /etc/sipnab/pcaps.pw — the compare route opens password-protected ZIPs under /srv/pcaps with the operator’s passwords, and a local client may add one per request in the Sipnab-Archive-Password header

  • sipnab -N -I today.pcap --api 0.0.0.0:8080 --api-key s3cret --api-file-root /srv/pcaps --api-accept-archive-passwords — behind a TLS proxy on another host: remote clients may send Sipnab-Archive-Password, which without the flag get 403 because the password would cross the network in the clear

  • sipnab -N -I today.pcap --api 127.0.0.1:8080 --api-key s3cret --api-file-root /srv/pcaps --api-accept-archive-passwords --archive-password-file /etc/sipnab/pcaps.pw — both at once: configured passwords first, then the request’s

  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --api-file-root /var/lib/sipnab/captures — a live capture whose REST clients may also diff rotations: GET /v1/captures/compare?a=yesterday.pcap&b=today.pcap reads two files from that one directory and nothing outside it, ranking what moved. Off without the flag, when the route answers 503

  • sudo sipnab -N -d eth0 --api 0.0.0.0:8443 --api-signing-key-file /etc/sipnab/signing.key --api-tls-cert /etc/sipnab/api.pem --api-tls-key /etc/sipnab/api.key — a public-facing API over HTTPS with no reverse proxy: signed tokens, a certificate whose key is chmod 600, and no plain-HTTP warning at startup

  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --api-allowed-host sipnab.example.com — behind a reverse proxy that forwards Host: sipnab.example.com unchanged: sipnab serves that name, while a page that rebinds any other name to 127.0.0.1 gets 403

  • sudo sipnab -N -d eth0 --api 0.0.0.0:8443 --api-signing-key-file /etc/sipnab/signing.key --api-tls-cert /etc/sipnab/api.pem --api-tls-key /etc/sipnab/api.key --api-allowed-host capture.example.com:8443 — a public-facing HTTPS API reached by name on port 8443 only: sipnab serves Host: capture.example.com:8443 and refuses the same name on any other port. Clients reaching it by IP address need no entry on a wildcard bind

  • sipnab -N -I today.pcap --api 127.0.0.1:8443 --api-key s3cret --api-tls-cert api.pem --api-tls-key api.key — HTTPS on loopback, for a client that insists on https:// even on the same host; api.pem must name 127.0.0.1 for the client to accept it

  • sipnab -N -I today.pcap --api 127.0.0.1:8080 --api-key s3cret --api-file-root /srv/pcaps — the compare route works on a file-backed run too: it diffs the two NAMED files under /srv/pcaps, never the loaded today.pcap, so a monitoring box can poll one sipnab for a rolling day-over-day comparison

  • sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --metrics-max-conn 64 — an HA Prometheus pair, a federating parent, a remote_write shard and an alertmanager sidecar all scraping one sipnab: at sixteen slots a slow scrape turns the next one away with 503, and the hole in the series reads as a capture that died

  • sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --metrics-max-conn 2 — a single scraper on a small box, where two slots covers every client this endpoint has, and a third connection is something to turn away

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac --hep-hmac-window 120 — a collector whose agents run on hardware with a drifting clock: two minutes of tolerance keeps them heard while someone repairs the NTP problem

  • sipnab -N -L 127.0.0.1:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac --hep-hmac-window 5 — the opposite, where every agent shares a local time source: five seconds narrows the replay window a captured packet stays valid in

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-silence-warn 300 — a collector fed by PBXes that go quiet overnight: five minutes without an admitted packet before the listener warns, so a site with no calls overnight does not fill the journal

  • sipnab -N -L 127.0.0.1:9060 --hep-parse --hep-silence-warn 0 — a lab listener fed by hand, where long silences are normal and the warning would only be noise

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --duration 300 --hep-senders — five minutes of a collector’s feed, then the roster: which proxies sent and how much, which went silent, and which addresses the listener turned away and why

  • sipnab -N -L 127.0.0.1:9060 --hep-parse --hep-senders --count 5000 --json — the same roster as one JSON object on the last line of stdout, after the per-message lines, for a script to pick up with tail -1

  • sipnab -N -L 127.0.0.1:9060 --hep-parse --hep-allow 127.0.0.1/32 --duration 300 --th-mappings — five minutes of an OpenSIPS proxy’s HEP mirror and its E_SIPNAB_TH_MAP events, then each mapping with its state: a pending row names the leg whose SIP never arrived, and a conflict row a Call-ID two events disagree about

  • sipnab -N -L 127.0.0.1:9060 --hep-parse --hep-allow 127.0.0.1/32 --duration 300 --th-mappings --json | tail -1 | jq '.mappings[] | select(.state != "joined")' — the same listing as one JSON object on the last line of stdout, filtered to the mappings that did not join

  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-bind 127.0.0.1:8731 --mcp-token t0ken-alice --mcp-signing-key-file /etc/sipnab/mcp-signing.key --mcp-revoked-file /etc/sipnab/mcp-revoked.txt --mcp-token-ttl 1800 — loopback HTTP MCP server with a bearer token, file-loaded signing key, revocation denylist, and a 30-minute mint TTL

  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-bind 0.0.0.0:8731 --mcp-token t0ken-bob --mcp-signing-key-file /etc/sipnab/mcp-signing.key --mcp-revoked-file /etc/sipnab/mcp-revoked.txt --mcp-allowed-host mcp.example.com — non-loopback HTTP MCP server (token required) accepting an extra Host header for named clients

  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-bind 0.0.0.0:8731 --mcp-token-file /etc/sipnab/mcp.token --mcp-allowed-host mcp.example.com --mcp-tls-cert /etc/sipnab/mcp.pem --mcp-tls-key /etc/sipnab/mcp.key — a non-loopback MCP server serving HTTPS itself, with no nginx in front: the bearer token and every tool result cross the network encrypted

  • sipnab -N -I today.pcap --mcp --mcp-transport http --mcp-bind 127.0.0.1:8731 --mcp-tls-cert mcp.pem --mcp-tls-key mcp.key — HTTPS on loopback, for an agent that insists on https:// even on the same host; mcp.pem must name 127.0.0.1 for the agent to accept it

  • sudo sipnab -N -d eth0 --hep-send 192.0.2.10:9060 --hep-id 42 --hep-auth s3cr3t-homer-key — forward captured packets to a Homer collector, stamping capture-agent id 42 and an authenticate key

  • sudo sipnab -N -d eth0 --hep-send 198.51.100.20:9060 --hep-id 7 --hep-auth homerkey2 — forward to a second collector under a different agent id and auth key

  • sudo sipnab -N -d eth0 --hep-send 198.51.100.30:9060 --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac — replay-resistant forwarding to another sipnab: HMAC-token auth over an untrusted path (both ends must set –hep-auth-mode hmac)

  • sipnab -N -I archive.pcap --hep-send 127.0.0.1:9060 --hep-id 9 — replay an archived capture into a collector on this host. sipnab warns at startup that the file’s signaling leaves the machine, then forwards it

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac — the matching sipnab-to-sipnab HMAC collector: verifies the per-message token and rejects replays

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-allow 192.0.2.0/24 --hep-allow 198.51.100.20/32 --hep-rate-limit 20000 — run a HEP collector that parses incoming packets, only from two allowed CIDRs, capped at 20k pkts/sec

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-rate-limit 40000 --hep-rate-limit-per-peer 5000 — authenticated HEP collector on a routable address: incoming packets must carry the shared secret, with a 5k/s per-peer fairness cap

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-allow-kill --kill-scanner — authenticated HEP collector that may also actively kill scanners seen in the HEP stream (only safe because the feed carries authentication)

  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth s3cr3t-homer-key --hep-rate-limit-per-peer 2000 --hep-allow-kill --kill-target 198.51.100.7 — inline HEP secret (visible in the process list. Prefer –hep-auth-file.) with a tight per-peer cap for a busy multi-proxy fleet

  • sudo sipnab -N -d eth0 --hep-send 192.0.2.10:9061 --hep-send-transport tcp --hep-id 42 — forward to a collector that speaks HEP over TCP, so a busy collector orders and keeps the feed instead of losing datagrams

  • sipnab -N -I archive.pcap --hep-send 127.0.0.1:9061 --hep-send-transport tcp — replay an archived capture into a local TCP collector. The stream is packets laid end to end, framed by each HEP v3 header’s total length

  • sudo sipnab -N -d eth0 --hep-send collector.example.com:9063 --hep-send-transport tls --hep-tls-ca /etc/sipnab/collector-ca.pem — encrypted forwarding across a path you do not control, verifying the collector against your own issuer

  • sudo sipnab -N -d eth0 --hep-send homer.example.net:9063 --hep-send-transport tls --hep-tls-ca /etc/pki/tls/certs/homer-ca.pem --hep-auth-file /etc/sipnab/hep.key — the same, with the Homer authenticate key inside the session as well as the session around it

  • sudo sipnab -N -d eth0 --hep-send collector.example.com:9063 --hep-send-transport tls --hep-tls-extra-ca /etc/sipnab/collector-ca.pem — encrypted forwarding to a collector whose certificate comes from a private CA, while the host’s public roots still count: the sender accepts a certificate from either

  • sudo sipnab -N -d eth0 --hep-send homer.example.net:9063 --hep-send-transport tls --hep-tls-extra-ca /etc/sipnab/homer-ca.pem --hep-auth-file /etc/sipnab/hep.key — the same with the Homer authenticate key inside the session; compare --hep-tls-ca, which would trust only homer-ca.pem

  • sudo sipnab -N -d eth0 --hep-send 192.0.2.10:9061 --hep-send-transport tcp --hep-send-queue 512 — a busy SBC whose collector restarts for maintenance: eight times the default queue rides out an outage eight times as long before the oldest packets go

  • sudo sipnab -N -d eth0 --hep-send 192.0.2.10:9061 --hep-send-transport tcp --hep-send-queue 16 --hep-send-timeout 2 — a small box on the collector’s LAN: a 16 MiB ceiling on the memory an outage can take, and a collector that does not answer a dial within two seconds counts as down

  • sudo sipnab -N -d eth0 --hep-send collector.example.com:9063 --hep-send-transport tls --hep-tls-ca /etc/sipnab/collector-ca.pem --hep-send-timeout 15 — a collector across a slow WAN, where a dial can take longer than the default three seconds

  • sipnab -N -L 0.0.0.0:9061 --hep-listen-transport tcp --hep-parse --hep-auth-file /etc/sipnab/hep.key — an authenticated TCP HEP collector: several proxies connect at once and sipnab reads each connection as a stream of HEP v3 packets

  • sipnab -N -L 127.0.0.1:9061 --hep-listen-transport tcp --hep-parse — the loopback version, for an agent mirroring to a collector on the same host over a connection rather than datagrams

  • sipnab -N -L 0.0.0.0:9063 --hep-listen-transport tls --hep-tls-cert /etc/sipnab/collector.pem --hep-tls-key /etc/sipnab/collector.key --hep-parse --hep-auth-file /etc/sipnab/hep.key — a TLS HEP collector: agents reach it over an encrypted stream and still prove who they are with the shared secret

  • sipnab -N -L 0.0.0.0:9063 --hep-listen-transport tls --hep-tls-cert /etc/sipnab/collector.pem --hep-tls-key /etc/sipnab/collector.key --hep-parse --hep-allow 192.0.2.0/24 — the same listener bounded by source address instead: TLS encrypts the path, and --hep-allow is still what says which addresses may speak on it

  • sipnab -N -I capture.pcap --metrics 127.0.0.1:9090 --metrics-auth-file /etc/sipnab/metrics.cred — loopback metrics endpoint reading its Basic-auth credential from a file (keeps user:pass out of the process list)

  • sudo sipnab -d eth0 --metrics 0.0.0.0:9090 --metrics-auth-file /etc/sipnab/metrics.cred — routable metrics endpoint (non-loopback requires auth) using a file-backed credential. Terminate TLS at a reverse proxy

  • sudo sipnab -N -d eth0 --metrics 0.0.0.0:9090 --metrics-auth-file /etc/sipnab/metrics.cred --metrics-tls-cert /etc/sipnab/metrics.pem --metrics-tls-key /etc/sipnab/metrics.key — the same routable endpoint serving HTTPS itself, so the Basic credential crosses the network encrypted with no reverse proxy; Prometheus scrapes it with scheme: https and the issuing CA in tls_config

  • sipnab -N -L 127.0.0.1:9060 --hep-parse --metrics 127.0.0.1:9443 --metrics-tls-cert metrics.pem --metrics-tls-key metrics.key — HTTPS on loopback, for a scraper on the same host that only speaks https://; metrics.pem must name 127.0.0.1 for the scraper to accept it

  • sipnab --mint-token --token-id alice-2026 --api-signing-key-file /etc/sipnab/signing.key --api-token-ttl 3600 — mint a signed bearer token with a fixed id (for later revocation) and a 1-hour TTL, then exit

  • sipnab --mint-token --token-scope metrics --token-id prom-scraper --api-signing-key-file /etc/sipnab/signing.key --api-token-ttl 86400 — mint a scrape-only token for Prometheus: it reaches /metrics, and every /v1/ route refuses it

  • sipnab --mint-token --token-scope full --token-id ops-oncall --api-signing-key-file /etc/sipnab/signing.key — the default scope, stated explicitly: full access to the REST API surface

What --hep-send sends

--hep-send <ADDR> forwards every SIP message sipnab reads to the collector at <ADDR>, byte for byte as the capture holds it. It also forwards the RTCP reports of every call whose SDP it read.

On a live capture that matches what the flag sounds like. Traffic passes the interface, and a copy reaches Homer.

Each datagram’s IP protocol chunk carries the transport the message arrived on:

  • 17 for UDP,
  • 6 for TCP, TLS and WebSocket,
  • 132 for SCTP.

A collector filtering by transport finds a TCP trunk under TCP.

RTCP travels as HEP protocol type 5. That is what lets a remote collector report media quality (loss, jitter, MOS) rather than only whether calls connect.

Each RTCP report goes in the form heplify sends:

  • sipnab decodes the report to JSON.
  • The Call-ID of the call it belongs to goes in the correlation-ID chunk (0x0011).
  • sipnab finds the call from the SDP that set up the media: the RTP port, the port above it, or an a=rtcp port.

Homer’s collector keeps an RTCP report only under a Call-ID. So a report whose call sipnab never saw the SDP of is not sent, as heplify and captagent do not send it. Capture the signaling as well as the media to let the reports through.

SIP goes without a correlation-ID chunk, as heplify sends it. The collector still derives the call from X-CID or Call-ID itself.

RTP is never forwarded.

  • RTCP is a control channel that RFC 3550 section 6.2 holds to a small fraction of session bandwidth.
  • So it carries the quality summary at a rate a WAN link and a UDP feed can absorb.
  • The media itself is the opposite on both counts.
  • Forwarding it would make this a call recorder pointed at the collector.

On -I <file> the same sentence carries a sharper meaning.

  • The messages sipnab reads come out of the capture file.
  • So that file’s signaling leaves the machine:
    • request lines,
    • headers,
    • URIs,
    • any message bodies it holds.
  • sipnab redacts nothing. It drops nothing beyond what --portrange and the matching flags already exclude.
  • Testing a HEP pipeline against a customer capture therefore ships that customer’s signaling to whatever <ADDR> names.

sipnab announces this before it reads the first packet. For sipnab -N -I customer.pcap --hep-send collector.example:9060 --no-cli-print the warning reads:

WARN sipnab::app::bootstrap: --hep-send collector.example:9060 forwards every SIP message this run reads, and the RTCP reports of the calls in it, to that address, and this run is reading a capture FILE (customer.pcap). The signaling in those captures leaves this machine: request lines, headers, URIs, and any message bodies they hold. The RTCP carries the media quality summary alongside it — SSRCs, loss, jitter, the endpoints reporting them and the Call-ID they belong to — but not the audio, which is never forwarded. sipnab redacts nothing. Point --hep-send at a collector you control, or drop it to analyze the captures without forwarding them.

That line is a warning rather than a refusal, because you chose the destination. Replaying an archive into your own collector stays a supported workflow. Two habits keep it uneventful:

  • Name a collector you control.
    • sipnab never takes the destination from the capture.
    • The address always comes from your command line or your config file.
  • Read the startup warning before you walk away. It names the flag, the destination, and the capture files.

The scanner-kill path works the other way round and refuses to run offline.

  • It aims at addresses recorded inside the capture.
  • Those belong to third parties who have nothing to do with your analysis.
  • So -I file grants it nothing at all. See Security.

When the --hep-send collector goes away

Sending runs on its own thread, behind a queue. The capture hands each HEP packet to the queue and moves on, so a collector that is slow, unreachable or accepting connections without reading never slows the capture or costs it a packet.

sipnab still makes the first connection at startup. A collector that refuses it, or does not answer within --hep-send-timeout, stops the run with exit 2, as does a TLS collector whose certificate sipnab refuses.

While the collector is unreachable after that:

  • Packets queue, up to --hep-send-queue MiB (default 64). At that bound sipnab drops the oldest packet and counts it as queue_full. Dropping the oldest puts the gap at the start of the outage, so the replay runs straight into the live feed. Dropping the newest would cut the calls still in progress, which are the ones you look for after an outage.
  • The sender redials with backoff: 0.1 s after the first failure, doubling to at most 5 s. Each dial and each stalled write gives up after --hep-send-timeout seconds (default 3). A TLS handshake gives up after 10 seconds.
  • When the collector answers, sipnab replays the queue in capture order, then the live feed continues. The replayed count grows by one per packet.

Delivery is at-least-once across a reconnect for every packet sipnab still holds. sipnab sends a packet whose write failed partway again, whole, on the new connection, so a collector that kept the first fragment can see that packet twice. Packets the kernel had already accepted are out of sipnab’s hands: a collector that vanishes without closing its connection (power, network) can take the unsent contents of the socket’s send buffer with it, and no error names them. A collector that you stop and restart closes its connections, and sipnab checks for that before every write, so that case loses nothing.

Over UDP, sipnab cannot see an outage. sipnab sends each datagram from an unconnected UDP socket, so a collector that is down, or a host that answers with ICMP port unreachable, produces no failure, and sipnab has nothing to hold or replay. Use --hep-send-transport tcp or tls when an outage must not lose signaling.

A stop discards the queue. On SIGINT or SIGTERM sipnab sends nothing it still holds: captured SIP carries credentials, numbers and media keys, and data that outlives a stop is data you believed gone. The stopped count says how many packets it discarded. The end of the input is not a stop. A file read to its end, -c or --duration delivers what the queue holds, and gives up only after two --hep-send-timeout periods plus 5 s pass with no delivery. It counts what it gave up as unsent_at_exit.

The end-of-run line states every count. For a run whose collector restarted once:

HEP export to 192.0.2.10:9061 over tcp: 1840 packet(s) sent, 1840 queued, 212
replayed after an outage, none dropped, 0 still queued, 14 failed attempt(s)
(connect 14), 1 reconnect(s); sent means written to the connection: a write
that fails is counted, the packet stays queued, and the sender dials again with
backoff

failed attempt(s) counts attempts, not lost packets: a failed attempt keeps its packet. Losses are the dropped count. queued always equals sent plus dropped plus still queued. The same counts appear in runtime_stats and GET /v1/runtime (hep_export) and as the Prometheus sipnab_hep_export_* series.

How --hep-listen reads the transport

A HEP packet names the transport of the message it carries in its IP protocol chunk (0x0002).

  • The HEP3 specification defines that chunk as the IP protocol ID.
  • sipnab reads 17 as UDP, 6 as TCP and 132 as SCTP.

OpenSIPS and Kamailio also send a decrypted TLS or WebSocket message over HEP.

  • Its payload is plain SIP.
  • No IP protocol number means TLS or WebSocket. So both proxies put a fake number in the chunk.
  • Neither number tells WS from WSS.
  • Kamailio’s does not even tell TLS from WebSocket.
  • So sipnab reads the number first. It lets the message’s top Via narrow it to WS or WSS:
NumberSenderWhat the sender meanssipnab reports
22 (IPPROTO_IDP)OpenSIPS tracerTLSTLS
22 (IPPROTO_IDP)Kamailio siptraceTLS, WS or WSSTLS, or WS or WSS as the top Via says
50 (IPPROTO_ESP)OpenSIPS tracerWS or WSSWS, or WSS as the top Via says
6 (TCP)Kamailio siptrace, for a message it sendsTCP, or WS on a TCP connectionTCP, or WS or WSS as the top Via says

The last row exists because of how Kamailio traces a message it sends.

  • It uses the protocol of the socket it sends from.
  • A WebSocket connection’s socket is a TCP one.
  • So a reply to a WS client arrives as 6, and only its Via says WebSocket.
  • The Via only ever narrows to WS or WSS.
  • A Via naming UDP never turns a TCP leg into UDP.

The top Via names the transport of the hop a message travels on.

The transport field of each --json message carries the name. So jq 'select(.transport == "WSS")' picks these legs out of a run that also captures the wire.

A WebSocket leg captured from the wire itself reports:

  • WS when it is plain,
  • WSS when sipnab decrypted it with --keylog.

--hep-parse reads a HEP datagram it finds on the wire or in a capture file by the same rule. A datagram whose number names no transport counts in the NOT DECODED line by that number, as a listener does.

sipnab applies all of this to HEP input only. On a frame read from an interface or a file, as before:

  • 50 is a real IPsec ESP packet,
  • sipnab refuses 22,
  • 6 is TCP whatever the payload says.

Any other number a HEP sender puts in the chunk stays in the NOT DECODED line, by number.

MCP server

Run sipnab as a Model Context Protocol server so an AI agent can drive it.

  • See MCP Server for the full guide.
  • Network Listeners lists the --mint-token / --token-id pair that issues MCP bearer tokens. It serves the REST API too.
FlagValueDefaultSinceDescription
--mcp–off0.3.2Run sipnab as an MCP server. With the default stdio transport, stdout carries the JSON-RPC wire, so --mcp implies -N/--no-tui and rejects every flag that writes its answer to stdout, such as --json and --report. With --mcp-transport http the TUI stays up and the server answers beside it, reading the capture the TUI shows; add -N for a headless server. See Query a capture over MCP while the TUI is open. Feature: mcp (or mcp-http for HTTP transport). See mcp.md.
--mcp-transportstdio|httpstdio0.3.2MCP transport: stdio (default) or http (requires the mcp-http feature). Feature: mcp
--mcp-bind<ADDR>– (defaults to 127.0.0.1:8731 at runtime when --mcp-transport http appears without an explicit bind)0.3.2HTTP MCP bind address. A non-loopback address needs a credential: a bearer token (--mcp-token, --mcp-token-file or $SIPNAB_MCP_TOKEN) or a signing key (--mcp-signing-key, --mcp-signing-key-file or $SIPNAB_MCP_SIGNING_KEY). Without one, sipnab refuses to start, with exit 2. Feature: mcp-http
--mcp-token<TOKEN>–0.3.2Bearer token for HTTP MCP. A non-loopback bind needs this or a signing key (--mcp-signing-key). Precedence: explicit flag, token file, then $SIPNAB_MCP_TOKEN. Feature: mcp-http
--mcp-token-file<FILE>–0.3.2Read bearer token from file (preferred over env in systemd units). Feature: mcp-http
--mcp-tls-cert<FILE>–0.5.203Serve MCP over HTTP with HTTPS, using this PEM certificate chain, the server’s certificate first. Needs --mcp-tls-key; either alone stops sipnab at startup rather than serving plain HTTP on a port meant for HTTPS. The port then speaks HTTPS only: TLS 1.2 and 1.3, ALPN http/1.1, no client certificates; clients still authenticate with the bearer token. A file sipnab cannot read, or that holds no certificate, stops it at startup naming the file. sipnab refuses it without --mcp --mcp-transport http. See MCP TLS. sipnab refuses an empty path with exit 2, and an empty [mcp] tls_cert with exit 1. Feature: mcp-http. Config: [mcp] tls_cert
--mcp-tls-key<FILE>–0.5.203PEM private key for --mcp-tls-cert. sipnab refuses a key any other user on the host can read (chmod 600 it; group-readable stays allowed), a file with no private key, and a key that does not belong to the certificate, each at startup and naming the file. sipnab refuses an empty path with exit 2, and an empty [mcp] tls_key with exit 1. Feature: mcp-http. Config: [mcp] tls_key
--mcp-signing-key<KEY>–0.4.3HMAC signing key for MCP bearer tokens, taken as raw bytes (any string — not hex-decoded). Repeatable: the first mints, verification accepts every one. Also reads $SIPNAB_MCP_SIGNING_KEY; --help names the variable and never shows its value. See auth.md. Feature: mcp-http
--mcp-signing-key-file<FILE>–0.4.3Read an MCP signing key from a file (contents trimmed); it becomes the minting key. Feature: mcp-http
--mcp-revoked-file<FILE>–0.4.3MCP revocation denylist (one token id per line; reloaded on mtime change). Feature: mcp-http
--mcp-token-ttl<SECS>36000.4.3Default TTL (seconds) when minting MCP tokens with --mint-token. Feature: mcp-http
--mcp-audit-file<FILE>–0.5.130Append every MCP tool call to this file, one JSON record per line (seq, ts, tool, id, caller, outcome, elapsed_ms, args, error). The same facts go to the normal log under the mcp_audit target, where --quiet and SIPNAB_LOG can hide them; this file keeps every record whatever the log level. Opened O_APPEND and never truncated, so restarts and a second sipnab on the same path add to it; created mode 0600 when absent. The seq counter starts at 1 each run, so a gap within a run is a missing record. sipnab refuses a call whose record it cannot write, rather than answering it, so no result leaves the server that is not in the file; a path sipnab cannot open stops the run at startup. Records reach the kernel rather than an fsync. Leave it off and nothing changes. Feature: mcp
--mcp-max-concurrent<N>1000.5.84Maximum tool calls the MCP server runs at once (0 = unlimited). sipnab refuses a call that cannot take a slot immediately, with a retry-shortly error, rather than queueing it. The default matches --api-max-conn. Applies to both stdio and HTTP servers, though a network-exposed HTTP server is the case it matters for. Feature: mcp
--mcp-tools<LIST>full0.5.130Which tools the MCP server registers: a comma-separated list of bundles (core, signaling, captures, security, media, relay, tfps, server, vcon, tls), single tool names, names from [mcp.bundles], or full. Every registered tool’s name, description and schema goes to the client on tools/list and stays in the model’s context for the whole session, so fewer tools means a smaller context. core holds nine tools that still answer a whole call end to end: capture_status, list_dialogs, get_dialog, triage_call, rtp_stats, find_problems, aggregate_dialogs, search_messages, get_capture_report. Choosing which tools load lists every bundle and its size. An unknown or empty name refuses the run, exit 2 (exit 1 when it came from [mcp] tools). sipnab removes the tools you did not choose from the router rather than hiding them, so a call to one of them fails as an unknown tool. The handshake names the tools sipnab loaded and the bundles it left out. Replaces [mcp] tools. Feature: mcp. Config: [mcp] tools
--mcp-output-schemas[=BOOL]off0.5.197Send each tool’s output schema on tools/list. Off by default: MCP makes output schemas optional, and they more than double the size of the tools/list answer. Responses carry the same JSON, as text and as structuredContent, either way; only a client that validates responses against a schema needs them. --mcp-output-schemas=false turns them off over a config that turns them on. Config: [mcp] output_schemas. Feature: mcp
--one-way-delay<MS>–0.5.93One-way network path delay, in milliseconds — the single MOS input a passive tap cannot measure directly. Declared here it beats an RTCP-reported round trip, which an unauthenticated packet can move, and that in turn beats the round trip sipnab derives from a sender-report echo carried in a receiver report; with none of the three, sipnab assumes 100 ms and says so. Config: [media] one_way_delay_ms
--mcp-max-rows<N>10000.5.93Maximum rows in one list-style MCP response. The consumer decides the right value: a small-context agent wants fewer, a batch client wants more. Config: [limits] mcp_max_rows. Do not mistake this for -l/--limit, which bounds dialogs tracked over the run. At least 1: sipnab refuses 0
--mcp-max-body-bytes<N>40960.5.97Maximum bytes of SIP body or matched snippet in one MCP response. --mcp-max-rows bounds how many rows an answer carries; this bounds how wide one row may be, and a caller can ask for fewer rows but cannot widen one. An SDP body with a dozen codecs and ICE candidates passes the default, and the agent reading the clipped half cannot tell a truncated answer from a short one. Config: [limits] mcp_max_body_bytes
--mcp-max-response-bytes<N>10485760.5.212Maximum bytes of ONE whole paged MCP response: the JSON page, the provenance note and the completeness fields. --mcp-max-rows bounds how many rows a page carries and --mcp-max-body-bytes how wide one body in a row can be; this bounds the page as a whole. Past it the tool drops whole rows from the end of the page, never part of one, and answers truncated_by: "bytes" with a next_cursor to the rest. A page always keeps at least one row. A caller narrows it per call with max_response_bytes and cannot exceed it. Config: [limits] mcp_max_response_bytes. At least 1: sipnab refuses 0
--mcp-max-wait-seconds<N>600.5.141Longest ONE await_condition MCP call may wait, in seconds. --mcp-max-rows and --mcp-max-body-bytes bound how much an answer carries; this bounds how long a caller may hold one of --mcp-max-concurrent slots while it waits for a condition on a live capture. sipnab clamps a larger request to it and says so in the response. It also bounds wait_seconds on find_in_captures and find_in_captures_status, how long one of those calls waits for a background sweep before it answers with the running job. Config: [limits] mcp_max_wait_seconds
--mcp-max-findings<N>10000.5.100Findings the MCP save_findings tool accepts before refusing further writes. Past it sipnab refuses the write and says so, and drops nothing to make room. Raise it for a long agent session on a large capture. Config: [limits] mcp_max_findings
--mcp-sweep-max-files<N>200.5.207Files one MCP find_in_captures sweep may open, from 1 to 4294967295. It is the ceiling on the tool’s per-call max_files: sipnab clamps a larger request to it, and 0 or no request means this value. A rotated spool of forty files needs 40 here before one sweep can reach the oldest file. The response reports the limits applied and files_examined, files_total and complete. Config: [limits] mcp_sweep_max_files
--mcp-sweep-deadline-ms<MS>300000.5.207Milliseconds one MCP find_in_captures sweep may spend, from 1 to 43200000 (twelve hours). It is the ceiling on the tool’s per-call deadline_ms, applied the same way as --mcp-sweep-max-files. The sweep runs as a background job on its own thread and checks it before each file and before each packet. Config: [limits] mcp_sweep_deadline_ms
--mcp-sweep-max-running<N>40.5.208MCP find_in_captures sweeps one server runs at once, from 1 to 64. Each running sweep is an operating system thread reading capture files. sipnab refuses a start past this many with a message that reports the bound and names this flag, rather than queueing it. Config: [limits] mcp_sweep_max_running
--mcp-sweep-max-held-results<N>160.5.208Finished MCP find_in_captures results one server holds for collection, from 1 to 256. Past this many, sipnab drops the result of the earliest-started sweep first. Config: [limits] mcp_sweep_max_held_results
--mcp-sweep-result-retention-secs<SECS>6000.5.208Seconds a finished MCP find_in_captures result waits for its poll, from 1 to 43200 (twelve hours), counted from the moment the sweep finished. After it, the job id is unknown. Config: [limits] mcp_sweep_result_retention_secs
--mcp-sampling-budget<PER_HOUR>off0.5.130Deprecated, and has no effect: sipnab never sends an MCP sampling request. SEP-2577 deprecates MCP sampling, and the loop that would have asked the client’s model to narrate alerts was never armed. The flag is still accepted, so a command line that carries it keeps working, and the run warns at startup. No value, 0 included, sends any observation to a model. A later release removes it. Feature: mcp
--mcp-rate-limit-per-peer<N>1000.5.87Maximum tool calls one peer may make per second (0 = unlimited). --mcp-max-concurrent caps calls in flight; this caps their arrival rate. A call over the cap gets the same retry-shortly error, never a queue slot. A peer is the source IP over HTTP (the address, not the socket, so reconnecting mints no fresh allowance) and the pipe itself over stdio. Shares its per-peer accounting with --hep-rate-limit-per-peer. Feature: mcp
--mcp-allowed-host<HOST>–0.3.2Additional Host header values the HTTP MCP server accepts (repeatable). DNS-rebind protection accepts localhost, 127.0.0.1, ::1 and the address --mcp-bind binds, on any port, and any IP address when it binds 0.0.0.0 or ::: the same rule as --api-allowed-host. Add the public hostname when clients connect by that name. Use * to disable host checking entirely (not recommended; pair the resulting open binding with a network-level source-IP allowlist). Feature: mcp-http
--mcp-resource-url<URL>–0.5.141Public URL clients reach this MCP server at, published as the OAuth 2.0 protected-resource identifier (RFC 9728). Setting it turns on discovery: the 401 challenge gains a resource_metadata parameter and sipnab serves the metadata document unauthenticated at the path RFC 9728 section 3.1 derives from this value, so https://sipnab.example.com/mcp publishes at /.well-known/oauth-protected-resource/mcp. Leave it unset and the challenge still appears — only the metadata half is off. sipnab does not derive it from the request: behind a TLS-terminating proxy that would name http:// for a resource the client reached over https://, which RFC 9728 section 3.3 makes a conformant client discard. Discovery only: sipnab issues and validates no OAuth tokens, so the document advertises no authorization_servers, and the bearer tokens it accepts are still its own. Must be absolute, http or https, with no userinfo, query or fragment; a rejected value is a startup error. Feature: mcp-http
--mcp-file-root<DIR>–0.5.70Directory the MCP file tools (export_capture, export_audio, list_captures) may read and write. Without it those tools refuse to run. They take a bare FILENAME, never a path — an agent cannot escape this directory. Feature: mcp
--mcp-evidence-ring<MIB>–0.5.168Retain this many MiB of raw frames so a provenance pointer into a LIVE capture resolves. A reader can seek a capture file again and needs none of this; a device or a HEP listener offers no second read, because sipnab holds parsed messages and not frames. The ring keeps frames only from a source nothing can read twice. A pointer it cannot answer names which kind of miss it hit — the ring dropped that frame, the ring has not reached that ordinal, or the ring keeps nothing for that source — because only one of those means “use a bigger ring”. A frame it does answer carries the label retained rather than resolved: those bytes come from this process’s own buffer, and no second reader can confirm them. Feature: mcp
--mcp-allow-shutdown–off0.5.70Permit the shutdown_server MCP tool to stop this process. Off by default, so an agent cannot stop a stock server. Even enabled, the tool dry-runs unless told otherwise and refuses to discard an unsaved live capture. sipnab refuses it with the TUI up, because the TUI would stay on screen after the tool stopped the run; add -N. Feature: mcp
--mcp-allow-open-capture–off0.5.74Permit the open_capture MCP tool to load a different capture from --mcp-file-root, discarding every dialog and stream held. Off by default, so a stock server keeps the capture the command line named. The tool refuses while the source is live or still filling the stores, loads in the background, and mints a new capture identity every later answer carries. sipnab refuses it with the TUI up, because the tool would replace the capture the operator is reading; add -N. Feature: mcp
--mcp-allow-relay-query–off0.5.139Permit the query_relay MCP tool to ask the configured relay what calls it holds right now. Off by default because this tool transmits: every other MCP tool answers from bytes sipnab already has, and this one puts a packet on the network at the address --rtpengine-control names. That address comes from the flag and from nowhere else, never from a tool argument. Without --rtpengine-control, or on a run reading a file, the tool refuses and names which of the two is missing. sipnab refuses it with the TUI up, because the TUI’s relay statistics view holds the run’s one transmit permit; add -N. Feature: mcp. How-to: Ask a media relay about itself
--mcp-allow-tls-capture–off0.5.102Let an agent install kernel uprobes and read TLS plaintext (start_tls_capture, stop_tls_capture). The most consequential opt-in on this surface: it lets an agent read the plaintext of TLS sessions belonging to processes it does not own, needs the server to still be root, and creates kernel state that outlives a crash. list_tls_libraries stays available without it, so an agent can always report what a capture WOULD see. sipnab refuses it with the TUI up, because the capture would write into the stores the operator is reading; add -N. Feature: mcp
--mcp-allow-vcon-fetch–off0.5.221Permit the fetch_vcon MCP tool to read one vCon by uuid from the store the [vcon_fetch] keys configure. Off by default because this tool connects to that store: every other vCon tool answers from bytes sipnab already has. The store, its kind, the credential and the decryption key come from [vcon_fetch] only, never from a tool argument; the tool takes a uuid and nothing else, and its answer holds no key. Without [vcon_fetch] url the tool refuses and names the keys. sipnab stops at startup when it cannot read the store’s credential or key file. The container comes back in the answer, and sipnab writes no file. Feature: mcp and vcon
--mcp-allow-save-findings–off0.5.81Permit the save_findings MCP tool to record an agent’s conclusion. Off by default. A finding goes to sipnab’s log and nowhere else: no tool reads it back, it appears in no query result, and no analysis consumes it, so it cannot return as evidence in a later answer. Clipped at 500 characters of summary and bounded at 1000 findings per process, both reported rather than silent. sipnab refuses it with the TUI up, because a TUI run logs only errors and nobody would see the finding; add -N. Feature: mcp
--retain-audio–off0.5.84Retain RTP audio payload in memory, so sipnab can look at the audio itself. Two things read it back: the export_audio MCP tool, which decodes a WAV, and the amplitude measurement in the media diagnosis, which reports dead-air spans and hard-clip runs on --call-report and --json-dialogs. Off by default, because call audio is content. Works with or without --mcp. Keeps whole calls, from the first frame. Costs a per-packet payload clone, bounded by one budget across every stream, [limits] max_audio_bytes (default 1 GiB, which holds 67 concurrent ten-minute G.711 calls; see Audio retention budget). At the budget sipnab releases the oldest stream’s earliest frames first, and that call’s export and vCon say PARTIAL with the count. [limits] max_audio_frames caps each stream as well when set. Without it export_audio refuses and names this flag, and the amplitude object is absent — both describe what the run kept, never whether the call carried audio. --cores honors this flag

TLS / decryption

FlagValueDefaultSinceDescription
-k, --tls-key<FILE>–0.3.0RSA private key (PEM) for TLS 1.2 RSA-key-exchange decryption. Non-PFS RSA only; ECDHE/DHE handshakes need --keylog. Feature: tls
--keylog<FILE>–0.3.0TLS key log file (NSS SSLKEYLOGFILE format). Accepts a FIFO here and reads it as a live stream, so a producer can feed secrets in without writing them to disk. sipnab opens the file before it drops privileges, so a keylog only the proxy’s user can read still loads when sipnab runs as root and drops to nobody, and --keylog-watch keeps reading what the producer appends through that open file. A producer that replaces the file after the drop needs it reachable by the user sipnab dropped to. When sipnab cannot read the keylog it refuses the run with exit 1, as it does for --tls-key and --dtls-keylog, and the error names the reason, such as Failed to load --keylog /run/keys.log: Loading keylog from /run/keys.log: Failed to read keylog file: /run/keys.log: No such file or directory (os error 2). Feature: tls
--keylog-fd<N>–0.5.101Read NSS keylog lines from an already-open descriptor, for a privileged producer that hands secrets over a pipe rather than a file. Implies --keylog-watch, and conflicts with --keylog — pass one, never both. sipnab cannot start that producer itself: it sets PR_SET_NO_NEW_PRIVS at startup and every child inherits it, so a child can never acquire the CAP_BPF an eBPF extractor needs. Start it from a supervisor and pass the read end here. Feature: tls
--keylog-watch–off0.3.0Watch the key log for new entries (live decryption). Feature: tls
--tls-lockon-window<RECORDS>10485760.5.115How far into an established TLS connection a capture may start and still be readable. No TLS version puts the record number on the wire, so a capture that joined a running connection searches for it, widening only as records fail to open — raising this costs nothing on a connection captured from its handshake, because the search stops at the first candidate that authenticates. Raise it for a carrier trunk held open for days; lower it where key material for unrelated connections is common. At least 1: 0 would turn the search off, so sipnab refuses it with exit 2. Feature: tls
--dtls-keylog<FILE>–0.3.0DTLS key log (NSS SSLKEYLOGFILE); extracts SRTP keys from DTLS-SRTP handshakes (RFC 5764 exporter, AES-CM profiles). sipnab does not treat a record declaring more than 2^14 + 2048 bytes as DTLS (the ciphertext limit TLS sets and DTLS inherits). Feature: tls
--srtp-keys<FILE>–0.3.0SRTP master-keys file for media decryption (AES-CM, RFC 3711); also honors SDES a=crypto keys from SDP. Feature: tls The file format, with an example: The --srtp-keys file.
--pcap-export-mode<MODE>raw0.3.0raw writes original packets without TLS keys. encrypted+dsb explicitly embeds TLS keys in PCAP-NG for Wireshark. decrypted writes what sipnab decrypted as plaintext, and no keys: SIP from TLS and WSS as plain TCP frames and SRTP as plain RTP, on the captured addresses, ports and times, with everything else as captured. The file can hold readable signaling and listenable audio. Frames wait up to 5 s of capture time for a late decryption, and a stop discards what is still waiting. The TUI’s live -O rewrites SRTP only, because the TUI does not decrypt TLS What each mode writes, with output: Write the decrypted call to a file.
--allow-coredump–off0.3.0Allow core dumps (do not call prctl to disable them)
--uprobe-tls–off0.5.102Read SIP plaintext straight out of the TLS libraries this host is running, using kernel uprobes. No certificate, no private key, no keylog and no restart of the process it observes. Probes every mapped TLS library rather than one, because an ordinary host runs OpenSSL and wolfSSL together. Needs root (or CAP_SYS_ADMIN + CAP_PERFMON) and a mounted tracefs. Linux only. Read the walkthrough before using this: it reads the plaintext of every SIP session on the host, and states what that means. Feature: native
--uprobe-library<PATH>discovered0.5.102Probe this library instead of discovering them; repeatable. Bypasses discovery, so it also reaches a library nothing has mapped yet. For a process inside a container, give the path as sipnab sees it: /proc/<pid>/root/usr/lib/libssl.so.3. Feature: native
--uprobe-symbol<NAME>per flavor0.5.102Write symbol to probe. Defaults to the one the library’s flavor exports — SSL_write for OpenSSL, wolfSSL_write for wolfSSL — so you need it only for a library sipnab cannot classify by name. Feature: native
--uprobe-flavor<NAME>all0.5.102Probe only these flavors (openssl, wolfssl); repeatable. Feature: native. Also accepts the spelling --uprobe-flavour
--uprobe-list–off0.5.102List the TLS libraries sipnab would probe, then exit without installing anything in the kernel. Run this first: it answers the question that decides whether the capture is worth starting. Exits 1 when nothing is visible, so a health check does not read “no TLS library” as success. With --uprobe-library it lists the named libraries and the symbol it would probe in each, instead of discovering them; a path that names no file shows as (MISSING) and the command exits 1, and a library whose flavor its name does not tell, given without --uprobe-symbol, exits 2. Feature: native
--uprobe-backend<NAME>tracefs0.5.102Which machinery reads the plaintext. tracefs works on any Linux with tracefs mounted and sees no socket, so its dialogs name a process rather than a peer. bpf pairs each write with its tcp_sendmsg and so recovers the real addresses — but needs a sipnab built with --features bpf and a kernel with CONFIG_DEBUG_INFO_BTF (BTF is the BPF Type Format, which tells sipnab where the socket’s fields sit on this kernel). sipnab refuses bpf without those rather than quietly downgrading: the addresses are the only reason to ask for it. Feature: native

Examples

  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab-exports — let an agent save captures and audio, confined to one directory
  • sudo sipnab -N -d eth0 --mcp --mcp-file-root /var/spool/sipnab-exports --mcp-allow-shutdown — a live capture an agent may export from and stop
  • sipnab -N -I capture.pcap --mcp --mcp-allow-shutdown — a replay session an agent may end when it has finished. Nothing to lose, since the file is already on disk
  • sipnab -N -I first.pcap --mcp --mcp-transport http --mcp-file-root /var/spool/sipnab-captures --mcp-allow-open-capture — a long-lived service an agent may move through a corpus with, one capture at a time
  • sudo sipnab -N -d eth0 --mcp --mcp-evidence-ring 256 — a live capture an agent can quote FROM. Without it sipnab refuses every pointer into eth0: it holds parsed messages and not frames, so nothing remains to seek to. 256 MiB buys a window. Outside it the refusal reports that the ring dropped the frame rather than that the frame never existed
  • sipnab -N -I capture.pcap --mcp --mcp-evidence-ring 64 — the ring has no effect on a file run: a reader can open a capture file again, so the ring keeps nothing and never spends the 64 MiB. It holds frames only from a source nothing can read twice
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab-captures --mcp-allow-open-capture --mcp-allow-shutdown — the same, plus the ability to end the session. Each permission needs its own flag
  • sipnab -N -I capture.pcap --config sipnab.toml --mcp --mcp-allow-vcon-fetch — let an agent read a vCon by uuid with fetch_vcon from the store the config file’s [vcon_fetch] section names; the agent gives the uuid and never a URL or key
  • sipnab -N -I capture.pcap --config sipnab.toml --mcp --mcp-transport http --mcp-allow-vcon-fetch --mcp-tools vcon — the same over HTTP, with only the vcon bundle loaded, so the agent can fetch a stored call and run analyze_vcon on it
  • sipnab -N -I capture.pcap --mcp --mcp-allow-save-findings — let an agent write its conclusions into the log while it works through a capture. Read them back with journalctl -u sipnab, never through a tool
  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-allow-save-findings — a live triage session whose findings survive in the journal after the agent disconnects, without granting it any other write
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab-exports --retain-audio — hold call audio in memory so an agent can export_audio a WAV of a problem call
  • sudo sipnab -N -d eth0 "portrange 5060-5061 or portrange 10000-20000" --mcp --retain-audio --mcp-file-root /var/spool/sipnab-exports — live capture with media in scope AND retained. Without --retain-audio the same run measures quality but keeps no payload to export
  • sipnab -N -I dead-air.pcap --retain-audio --call-report <call-id> --json — no MCP server involved: retain the samples so the media diagnosis can measure them, and read diagnosis.amplitude for dead-air spans and clip runs with the thresholds that produced them
  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-bind 127.0.0.1:8731 --mcp-max-concurrent 8 — a network-facing MCP server that runs at most eight tool calls at once and refuses the ninth with a retry-shortly error rather than queueing it
  • sipnab -N -I capture.pcap --mcp --mcp-max-concurrent 0 — a stdio replay for one trusted agent with no concurrency cap (0 = unlimited)
  • sipnab -N -I capture.pcap --mcp --mcp-tools core — a small-context agent: nine tools instead of the whole surface, so the schema block sipnab sends it before it asks anything is a fraction of the size
  • sipnab -N -I capture.pcap --mcp --mcp-tools core,relay,get_sdp_timeline — a small-context agent that also chases relay-side media: the core set, the relay bundle and one signaling tool, rather than every bundle
  • sipnab -N -I capture.pcap --mcp --mcp-output-schemas — the whole surface with its output schemas, for a client that validates each response against the tool’s declared shape
  • sipnab -N -I capture.pcap --mcp --config sipnab.toml --mcp-output-schemas=false — a host config that turns output schemas on, overridden for one client that does not validate responses
  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-tools full — the default written out, for a service definition that should say what it offers rather than leave it to whatever the next release registers
  • sipnab -N -I capture.pcap --mcp --quiet --mcp-audit-file /var/log/sipnab-mcp.jsonl — record every tool call to a file that survives --quiet, for the question asked after the fact rather than at the console
  • sipnab -N -I sat-trunk.pcap --one-way-delay 280 — score MOS for a satellite trunk, where the real one-way delay is 280 ms. Without it sipnab falls back to the trunk’s own RTCP and, on a capture carrying none, to an assumed 100 ms that reports roughly a full point too high, because G.107’s delay penalty has a knee at 177.3 ms the assumption never crosses
  • sipnab -N -I lan.pcap --one-way-delay 5 — a LAN capture, where assuming 100 ms understates the score. The declared figure also beats any round trip the far end reports or sipnab derives from RTCP, since no packet on the wire can rewrite a config value
  • sipnab -N -I capture.pcap --mcp --mcp-max-rows 50 — cap every list-style MCP response at fifty rows, for an agent whose context window a thousand-row page would swamp. A caller asking for more gets fifty
  • sipnab -N -I capture.pcap --mcp --mcp-max-rows 5000 — raise the ceiling above the 1000 default for a batch client that pipes whole pages to a file, where the round trips cost more than the bytes
  • sipnab -N -I capture.pcap --mcp --mcp-max-body-bytes 65536 — let an agent read a whole INVITE with a long SDP body rather than the first 4096 bytes of it, on a capture whose bodies are what the investigation is about
  • sipnab -N -I capture.pcap --mcp --mcp-max-rows 20 --mcp-max-body-bytes 512 — a small-context agent: few rows, and each one short, so a page of hits fits the window it has to reason in
  • sipnab -N -I capture.pcap --mcp --mcp-max-response-bytes 65536 — no answer larger than 64 KiB, however many rows the caller asks for or however long their bodies run; a page that would pass it ends early with truncated_by: "bytes" and a cursor to the rest
  • sipnab -N -I capture.pcap --mcp --mcp-max-rows 5000 --mcp-max-response-bytes 4194304 — a batch client allowed five thousand rows per page, with each answer still held to 4 MiB, so a page of long SIP messages ends early with a cursor rather than growing without a bound
  • sudo sipnab -N -d eth0 --mcp --mcp-max-wait-seconds 300 — let an agent sit on a live capture for up to five minutes per await_condition call, on a box watching for a fault that reproduces slowly
  • sudo sipnab -N -d eth0 --mcp --mcp-max-wait-seconds 5 — hold every wait to five seconds, for a host where MCP slots are scarce and a stalled agent must not occupy one
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-max-files 40 — let one find_in_captures call sweep a spool that rotates forty files, so the sweep reaches a call in the oldest file
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-max-files 40 --mcp-sweep-deadline-ms 120000 — the same spool of large files, with two minutes for the sweep to read them
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-deadline-ms 5000 — end every sweep after five seconds, for a host where MCP slots are scarce and one sweep must not hold a slot for longer
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-max-running 1 — run one sweep at a time, for a host whose capture spool sits on a single slow disk that two concurrent sweeps would share
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-max-running 8 --mcp-sweep-max-held-results 32 — let an agent run eight sweeps side by side on a spool spread over several disks, and hold four uncollected results per running sweep, the ratio the defaults carry
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-max-held-results 4 — hold at most four uncollected results, for a host where memory is scarce and agents poll each sweep they start
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-result-retention-secs 3600 — keep a finished result for an hour, for an agent that starts long sweeps and returns to them later in the session
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab --mcp-sweep-result-retention-secs 60 — drop a result one minute after its sweep finished, for a scripted client that polls at once and never returns to a job it abandoned
  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-rate-limit-per-peer 20 — a network-facing MCP server where any one client may make twenty tool calls a second. sipnab answers the twenty-first that second with a retry-shortly error instead of serving it
  • sipnab -N -I capture.pcap --mcp --mcp-max-concurrent 8 --mcp-rate-limit-per-peer 0 — bound how many calls run at once but put no cap on the arrival rate (0 = unlimited), for a scripted client that sweeps a capture as fast as it can
  • sipnab -N -I capture.pcap --tls-key /etc/sipnab/tls-rsa.key --keylog /etc/sipnab/keys.log --allow-coredump — decrypt TLS 1.2 RSA-key-exchange SIP from a pcap using an RSA private key, with core dumps left enabled
  • sipnab -N -I capture.pcap --srtp-keys /etc/sipnab/srtp.keys --dtls-keylog /etc/sipnab/dtls.log — decrypt SRTP media in an offline pcap from an SRTP master-keys file plus DTLS-SRTP handshake keys
  • sudo sipnab -d eth0 --tls-key /etc/sipnab/tls-rsa.key --srtp-keys /etc/sipnab/srtp.keys --keylog /etc/sipnab/keys.log --keylog-watch --allow-coredump — live decrypt both SIP (RSA key) and SRTP media, watching the key log for new PFS session keys
  • sudo sh -c 'ecapture tls -m keylog --keylogfile=/dev/stdout | sipnab -N -d eth0 --keylog-fd 0' — read SIP over TLS with no certificate and no restart of the SIP daemon: an eBPF extractor pulls session secrets out of the running daemon’s OpenSSL. It pipes them straight in, so nothing is ever written to disk. sipnab cannot launch the extractor itself, because every child inherits PR_SET_NO_NEW_PRIVS and so can never acquire CAP_BPF
  • sudo sipnab -N -d eth0 --keylog-fd 3 --user sipnab 3< /run/sip.keys — take the secrets on descriptor 3 opened by the shell, then drop to an unprivileged user for the capture itself. The pipe is already open, so nothing has to be reachable from /run afterwards
  • sudo mkfifo -m 600 /run/sip.keys && sudo sipnab -N -d eth0 --keylog /run/sip.keys --keylog-watch — the same idea through a named pipe rather than a descriptor, for a producer started separately by systemd. sipnab opens the FIFO while still privileged. It does so before it drops to an unprivileged user and can no longer reach /run
  • sudo sipnab -N -d eth0 --keylog /run/sip.keys --keylog-watch --tls-lockon-window 8388608 — read a carrier trunk that has been up for days without restarting it. The record counter is not on the wire, so a capture joining an established connection has to find it. The default reaches about a day of a trunk at ten records a second, and this raises the ceiling to roughly a week
  • sipnab -N -I trunk.pcap --keylog keys.log --tls-lockon-window 4096 — narrow the search when reading a capture whose key log holds secrets for many unrelated connections, so a session that can never match gives up sooner instead of spending the run’s trial budget
  • sudo sipnab --uprobe-list — run this first. Report which TLS libraries processes on this host are actually mapping, and exit without installing a single probe. Answers the only question that matters before starting: is the daemon you care about using a library sipnab can read?
  • sudo sipnab --uprobe-list --uprobe-flavor wolfssl — the same listing narrowed to one flavor, so the output says what this command would probe rather than what merely exists
  • sudo sipnab -N --uprobe-tls — read SIP over TLS with no certificate, no key and no restart of the SIP daemon. sipnab probes every mapped TLS library, so one command covers a host running OpenSSL for one daemon and wolfSSL for another
  • sudo sipnab -N --uprobe-tls --uprobe-flavor openssl — probe only the OpenSSL side on a mixed host, when the wolfSSL processes are something else entirely and their plaintext is not yours to read
  • sudo sipnab -N --uprobe-tls --uprobe-library /usr/lib/x86_64-linux-gnu/libssl.so.3 — skip discovery and probe one named library, which is how you attach to a daemon that has not started yet: discovery can only see what is already mapped
  • sudo sipnab -N --uprobe-tls --uprobe-library /proc/$(docker inspect -f '{{.State.Pid}}' opensips)/root/usr/lib/libssl.so.3 — probe the OpenSSL inside a container. The path a container process sees names a different file from sipnab’s namespace, so the probe must go through /proc/<pid>/root or it silently attaches to the host’s copy and captures nothing
  • sudo sipnab -N --uprobe-tls --uprobe-library /opt/vendor/libtls-custom.so --uprobe-symbol vendor_write — probe a library sipnab cannot classify by name. Without --uprobe-symbol it refuses rather than guessing which function to attach to
  • sudo sipnab -N --uprobe-tls --uprobe-library /usr/lib/libssl.so.3 --uprobe-symbol SSL_write_ex — probe OpenSSL 3’s newer write entry point instead of the default SSL_write, for a daemon built against it. The argument positions match, so the probe shape does not change
  • sudo sipnab -N --uprobe-tls --json — the same capture as JSON. Dialogs from this source carry uprobe:<comm>/<pid> as their interface and unspecified addresses with port 0, because a uprobe sees the bytes an application handed its TLS library and nothing about the socket beneath. sipnab names the process rather than inventing a peer
  • sudo sipnab -N --uprobe-tls --uprobe-backend bpf --portrange 0-65535 — read SIP over TLS with the peer addresses: each dialog names the real source and destination address and port of its connection. Widen --portrange, because a TLS trunk on 5061 is the exception and the port a uprobe reports is whatever the socket used
  • sudo sipnab -N --uprobe-tls --uprobe-backend tracefs — the default, spelled out. Use it on a kernel without BTF, where the bpf backend cannot run at all. Dialogs then name the process instead of a peer
  • sudo sipnab -N -I done.pcap --mcp --mcp-allow-tls-capture — let an agent decide, mid-investigation, that the answer is in traffic it cannot see, and start reading TLS plaintext itself. Finish the file source first: sipnab’s stores have one writer, and a uprobe capture is a live one
  • sudo sipnab -N -I done.pcap --mcp --mcp-allow-tls-capture --mcp-allow-save-findings — the same, plus somewhere for the agent to record what it concluded. Without --mcp-allow-tls-capture the agent can still call list_tls_libraries and report what a capture would have seen

Privilege

FlagValueDefaultSinceDescription
--user<USER>–0.3.0Drop privileges to this user after opening capture devices. Only takes effect when the process is root — see the note below. Config: [privilege] user
--no-priv-drop–off0.3.0Do not drop privileges after opening capture devices. Config: [privilege] no_priv_drop
--priv-drop–off0.5.204Drop privileges even when [privilege] no_priv_drop = true. The last of --no-priv-drop / --priv-drop typed wins. Config: [privilege] no_priv_drop
--chroot<DIR>–0.3.0Chroot to this directory after initialization. It applies to -I file runs too, and needs root privileges: as an ordinary user the run exits 1, and --chroot /tmp fails with Failed to chroot: chroot(/tmp) failed: Operation not permitted (os error 1). Config: [privilege] chroot Steps: Capture with the least privilege.
--setup-caps–off0.4.3Grant this binary the Linux capabilities for live capture (cap_net_raw,cap_net_admin+ep via setcap) so it runs without sudo, then exit. Re-invokes through sudo when not already root. Linux only. Steps: Capture with the least privilege.

--user does nothing unless the process is root.

  • sipnab skips the drop when the effective user is not root, and says so only at debug level.
  • So after the recommended install (--setup-caps, then run without sudo), sipnab accepts --user and does nothing with it. An unprivileged process has nothing to drop.

The platforms also differ.

  • On Linux, root plus --user drops to that user.
  • On macOS, sipnab skips the drop even as root unless you pass --user. Dropping to nobody strands the process without a per-user login session. That crashes CoreAudio the moment anything touches it.

Examples

  • sipnab -N -I capture.pcap --priv-drop --report — drop privileges as usual when your config file sets [privilege] no_priv_drop = true

  • sipnab -N -I capture.pcap --no-priv-drop --priv-drop --report — the last of the pair wins, so sipnab drops privileges

  • sudo sipnab -d eth0 --user sipnab — live capture that drops root to the sipnab service user once the capture device is open

  • sudo sipnab -d eth0 --user nobody --chroot /var/empty — long-running monitor that drops to nobody and confines itself to an empty chroot

  • sudo sipnab -d eth0 --chroot /var/empty --no-priv-drop — chrooted capture that keeps root privileges for the whole run

  • sudo sipnab --setup-caps — grant the binary the capture capabilities (cap_net_raw,cap_net_admin) so future runs work without sudo, then exit

Resource limits

FlagValueDefaultSinceDescription
--max-capture-sources<N>655360.5.197Distinct capture sources sipnab remembers by name: input files, the device, HEP senders, traced processes. A packet from a new source past the limit is still analyzed but carries no frame pointer, so --show-frame cannot reach its bytes; sipnab counts it (sipnab_capture_sources_refused_total) and warns once. sipnab refuses to start, exit 2, when the limit cannot hold the input files or device plus [limits] max_tracked_peers HEP senders. Accepts 1 and up. Config: [limits] max_capture_sources
--max-reassembly<N>100000.3.0Maximum concurrent TCP/TLS reassembly sessions. At least 1: sipnab refuses 0. Config: [limits] max_reassembly
--reassembly-ttl<SECS>300.5.100Seconds sipnab holds an incomplete IP datagram or half-read TCP stream before a sweep drops it. --max-reassembly bounds how MANY entries sipnab holds; this bounds how long. A persistent SIP/TCP or SIP/TLS trunk can stay quiet for longer than 30 seconds, and when sipnab drops its half-read stream the next segment starts mid-message and the sender’s valid message reports as malformed. Raise it on such a trunk; --max-reassembly caps the extra state either way. Config: [limits] reassembly_ttl_secs
--lint-max-per-rule<N>250.5.96Findings one lint rule may report for one dialog. A dialog that retransmits an INVITE eleven times trips a message rule eleven times and every one of them is true, so this decides whether the other rules stay readable underneath. Needs --lint. Config: [limits] lint_max_per_rule
--lint-suppress-file<FILE>discovered0.5.130Read lint suppressions from this file instead of discovering one. Without it sipnab looks for a .sipnablint beside the capture and then in each parent directory up to the nearest one holding a .git, the same file the MCP lint tools read. A named file sipnab cannot open fails hard rather than falling back to a full-catalog run. sipnab names whatever applies on stderr, with a count of what it silenced. Needs --lint
--lint-no-suppress–off0.5.130Ignore any .sipnablint, including one named by --lint-suppress-file. It wins over both the explicit file and discovery, so it overrides a wrapper script that always passes a suppression file. Needs --lint
--cores<N>10.4.14CPU cores for offline pcap reconstruction (-I). At least 1: sipnab refuses 0. 1 = single-threaded; >1 shards by host pair for multi-core throughput and prints the whole-capture views (--report, --json-dialogs, --analyze, --call-report). A run that asks for none of them exits 2 before reading. A flag it does not carry out, such as --json, --hexdump, a matcher, a key file, an exec hook, a server or --sandbox, exits 2 naming it. Started as root it opens every input file first, then drops to --user (or nobody) as the single-core read does, so a root-only capture still reads. At any core count sipnab reads a plain uncompressed .pcap by mapping it, which is where most of the multi-core gain comes from; pcapng, gzip, a non-regular file, or any run with a BPF filter reads through libpcap instead. Set SIPNAB_NO_MMAP=1 to force the libpcap path everywhere — an escape hatch for a filesystem where mapping misbehaves, such as some network or FUSE mounts. Results are identical either way, except that libpcap reads a nanosecond-resolution pcap’s timestamps to the microsecond. With --retain-audio each worker gets an equal share of [limits] max_audio_bytes, so the run as a whole stays inside the budget
--max-metadata-file-bytes<BYTES>21474836480.5.97Bytes of pcapng sipnab reads into memory for embedded names and TLS secrets. A tcpdump -C or dumpcap -b ring member passes 2 GiB on a host with the RAM to spare, and the refusal is fatal. A memory-exhaustion guard on untrusted input: raising it to N lets ONE file claim N bytes of this host’s RAM, roughly 2N while --strip-secrets writes its copy, on nothing but a file size and before sipnab can tell the file is a capture at all. Raise it for captures you produced. Config: [limits] max_metadata_file_bytes
--max-gunzip-bytes<BYTES>10737418240.5.97Bytes sipnab may inflate out of one gzip-compressed input: a -I capture.pcap.gz, every gzip layer of an archive summed across the layers of one input, the embedded names and TLS secrets it reads out of a .pcapng.gz, the copy --strip-secrets rewrites, and the whole capture in the browser build. A gzip-bomb guard: inflation stops one byte past the ceiling, so raising it to N lets a few kilobytes of input claim N bytes of RAM or disk. Raise it for archives you compressed yourself. Config: [limits] max_gunzip_bytes
--max-tcp-buffer<BYTES>655360.5.99Bytes one SIP/TCP direction may buffer before sipnab flushes it. The only limit here that destroys data rather than truncating a report. TCP sets no such ceiling and neither does RFC 3261: on a carrier trunk a message carrying ISUP encapsulation, a long Record-Route set or a fat SDP offer passes 64 KiB legitimately, and sipnab then flushes the buffer mid-message — both halves parse as malformed, the cut destroys the framing for every message behind it, and the peer that sent a valid message is the one sipnab reports as broken. Raising it to N lets one TCP direction hold N bytes. The floor is one SIP header line (8192), below which no message survives, and sipnab refuses a smaller value by name. Config: [limits] max_tcp_buffer

Examples

  • sudo sipnab -d eth0 --max-reassembly 50000 — live capture on a busy TCP/TLS trunk with a raised reassembly-session ceiling
  • sipnab -N -I capture.pcap --cores 4 --max-reassembly 2000 --report — offline reconstruction sharded across 4 cores, with a tight reassembly bound for an untrusted capture
  • sipnab -N -I calls.pcap --lint --lint-max-per-rule 3 --no-cli-print — keep the lint output to three repeats of any one rule, so a capture full of retransmissions still shows what else it trips
  • sipnab -N -I calls.pcap --lint --lint-max-per-rule 500 --no-cli-print — the opposite: every repeat, for counting how often one rule actually fires
  • sipnab -N -I calls.pcap --lint --lint-fail-on error --no-cli-print — honors the .sipnablint sitting beside calls.pcap, and names it on stderr with a count of what it silenced. A short finding list always says why it is short
  • sipnab -N -I calls.pcap --lint --lint-suppress-file ci.sipnablint --no-cli-print — a suppression list of the pipeline’s own, for a CI job whose tolerances differ from the tree’s
  • sipnab -N -I calls.pcap --lint --lint-no-suppress --no-cli-print — the full catalog, ignoring every suppression file, for auditing what the project has agreed to live with
  • sipnab -N -I ring-00042.pcapng --max-metadata-file-bytes 8589934592 --report — read the embedded names and TLS secrets out of an 8 GiB ring member you captured yourself, which the shipped ceiling refuses outright
  • sipnab -N -I ring-00042.pcapng --max-metadata-file-bytes 8589934592 --strip-secrets sanitized.pcapng — the same file, sanitized for handover. The copy costs roughly twice the ceiling in memory, so raise it only for a file you trust
  • sipnab -N -L 0.0.0.0:9060 --hep-allow 192.0.2.0/24 --config collector.toml --max-capture-sources 100000 — a HEP collector whose collector.toml sets [limits] max_tracked_peers = 100000 for a large agent fleet: at the shipped 65536 sipnab refuses to start, exit 2, because the table could not hold every sender the listener may admit
  • sipnab -N -I a.pcap -I b.pcap --max-capture-sources 2 --report — the smallest table two input files can run with; one less and sipnab refuses to start, naming the setting
  • sipnab -N -I archive.pcapng.gz --max-gunzip-bytes 8589934592 --strip-secrets sanitized.pcapng — sanitize an 8 GiB pcapng you compressed yourself, without spending the disk that decompressing it by hand would need
  • sipnab -N -I from-customer.pcapng.gz --max-gunzip-bytes 268435456 --report — the opposite, for a file that arrived from outside: a quarter-gigabyte ceiling on the embedded names and secrets sipnab reads out of it, so a gzip bomb wearing a capture’s name cannot take the box down
  • sipnab -N -I isup-trunk.pcap --max-tcp-buffer 1048576 --json-dialogs --no-cli-print — a carrier trunk whose SIP/TCP messages carry encapsulated ISUP bodies past 64 KiB: at the shipped ceiling sipnab cuts each one in half and reports it malformed, and at 1 MiB the same bytes produce the call
  • sudo sipnab -d eth0 --max-tcp-buffer 262144 — watch a live SBC that answers with long Record-Route sets, holding a quarter-megabyte per TCP direction so a large response frames whole instead of arriving as two malformed fragments
  • sudo sipnab -N -d eth0 --reassembly-ttl 600 --max-reassembly 50000 — a persistent SIP/TLS trunk to a carrier that goes quiet overnight: ten minutes of patience keeps the half-read stream, so the first segment of the morning continues a message rather than landing in the middle of one
  • sipnab -N -I untrusted.pcap --reassembly-ttl 5 --max-reassembly 2000 — the opposite, for a capture from outside: a five-second wait drops a half-sent stream almost at once, so a peer that opens streams and never finishes them cannot hold state
  • sipnab -N -I calls.pcap --mcp --mcp-allow-save-findings --mcp-max-findings 20000 — a long agent session over a large capture, where a thousand annotations is a session doing its job and the journal on this box can take twenty
  • sipnab -N -I calls.pcap --mcp --mcp-allow-save-findings --mcp-max-findings 25 — a scripted agent run on a box with a small journal: twenty-five annotations, then sipnab refuses further writes and says so rather than filling the disk

vCon forwarder

--vcon-forward runs sipnab as the process that delivers an --export-vcon-dir spool to a vCon store.

  • It captures nothing.
  • sipnab refuses it beside any capture source, listener or export flag.
  • The other flags in this section need it, except --vcon-spool-status. That flag reads a spool’s state and runs on its own.
  • Each flag but --vcon-forward, --vcon-forward-once, --vcon-forward-auth and --vcon-spool-status has a [vcon_forward] key in the config file. The flag overrides the key.
  • Needs the vcon Cargo feature.
  • More: Deliver the spool to a store.
FlagValueDefaultSinceDescription
--vcon-forward<SPOOL_DIR>–0.5.205Deliver each container in this spool to --vcon-forward-url, byte for byte, and move it out of the spool by the store’s answer: 2xx to --vcon-forward-done, another 4xx to --vcon-forward-failed beside a <name>.error.json record, 5xx, a timeout or no connection left in place and retried after --vcon-forward-backoff-first (2 s), doubling to at most --vcon-forward-backoff-cap (5 minutes). A 401 or 403 refuses the credentials or the client for every container, so the forwarder stops instead: it sends nothing more, moves no file, logs the status and the first 200 bytes of the answer, and exits 3, in either mode. Skips dot-prefixed names and names that do not end in .json. A container sipnab rewrites under the same name goes again. A 2xx means the store accepted the container, not that it stored it. Needs a URL and a credential, each from its flag or its [vcon_forward] key; without one sipnab refuses the run with exit 2, naming both
--vcon-forward-kind<KIND>generic0.5.206The kind of store: generic, vcon-store or conserver. A kind supplies the ingest path when the URL names no path and the header when the credential is a bare key; an explicit setting overrides each. No kind changes the container sent. generic supplies nothing. The table: Choose the store kind. Config: [vcon_forward] kind
--vcon-forward-url<URL>–0.5.205Where the forwarder POSTs each container, http:// or https://, with its path and query. With a --vcon-forward-kind other than generic, a URL with no path is the store’s base URL and the kind adds its ingest path. sipnab refuses credentials in the URL: they belong in the auth file. Plain http:// to a host other than loopback logs a warning. Config: [vcon_forward] url
--vcon-forward-auth<HEADER>–0.5.206The credential itself, also read from SIPNAB_VCON_FORWARD_AUTH, as the auth file would hold it: one Header-Name: value line, or the bare key with a --vcon-forward-kind other than generic. A flag’s value is visible in the process list to other users of the host: prefer the environment variable or --vcon-forward-auth-file. Refused when empty, beside --vcon-forward-auth-file, and beside [vcon_forward] auth_file. --help names the variable and never shows its value. The value never appears in a log line, an error or a failure record. A capture run ignores the variable
--vcon-forward-auth-file<FILE>–0.5.205File holding one line, Header-Name: value, sent with every request: Authorization: Bearer <token>, or x-conserver-api-token: <key>. With a --vcon-forward-kind other than generic it may hold the bare key instead. Refused when other users can read it (chmod 600 it), by the rule sipnab applies to its other secret files. A file sipnab cannot read, or one that holds no credential, stops the forwarder with exit 2. The same file named by [vcon_forward] auth_file exits 1. The value never appears in a log line, an error or a failure record. Config: [vcon_forward] auth_file
--vcon-forward-done<DIR><SPOOL_DIR>/delivered0.5.205Where a delivered container goes, under its own name. Created mode 0700 if missing. Must be on the spool’s file system. Config: [vcon_forward] done
--vcon-forward-failed<DIR><SPOOL_DIR>/failed0.5.205Where a refused container goes, beside <name>.error.json: the status, the method and URL, and the first --vcon-forward-max-error-body bytes (8 KiB) of the store’s answer with the auth value removed, or the forwarder’s own reason. Created mode 0700 if missing. Must be on the spool’s file system. Config: [vcon_forward] failed
--vcon-forward-replace-url<TEMPLATE>–0.5.205When the POST answers 409, PUT the same bytes to this URL with {uuid} replaced by the container’s uuid. Without it a 409 is a refusal like any other 4xx. Config: [vcon_forward] replace_url
--vcon-forward-once–off0.5.205Make one pass and exit: 0 when the store accepted every container or the spool held none, 1 when the store refused one or one is still waiting, 2 when sipnab refuses the settings, 3 when the store answered 401 or 403. A config file sipnab cannot read, or one that holds a [vcon_forward] number or kind outside its range or list, an empty path, or a url or replace_url sipnab refuses, exits 1 before the pass, as any run does; the same refusal of --vcon-forward-url or --vcon-forward-replace-url exits 2. Without it the forwarder passes over the spool every --vcon-forward-interval seconds until SIGTERM or SIGINT, and sends nothing after the signal
--vcon-forward-interval<SECS>50.5.205Seconds between passes, 1 to 3600. Config: [vcon_forward] interval
--vcon-forward-timeout<SECS>300.5.205Seconds to wait to connect, and for each read and write, before the store counts as unreachable and the container waits for a retry, 1 to 600. It also bounds each SCITT registration: polling a 202’s Location for the receipt ends within it. Config: [vcon_forward] timeout
--vcon-forward-ca<FILE>host bundle0.5.205Trust only the CA certificates in this PEM file for an https:// store. Without it the forwarder trusts the host’s CA bundle. Config: [vcon_forward] ca
--vcon-forward-extra-ca<FILE>–0.5.219Trust the CA certificates in this PEM file for an https:// store in addition to the host’s CA bundle: the forwarder accepts the store’s certificate when the host’s bundle or this file issued it. Use --vcon-forward-ca instead to trust only the named file; the forwarder refuses the two together. Either flag replaces both [vcon_forward] ca and [vcon_forward] extra_ca. Config: [vcon_forward] extra_ca
--vcon-forward-backoff-first<SECS>20.5.206Seconds a container waits after its first failed try (a 5xx, a timeout or no connection); each failed try doubles the wait, up to --vcon-forward-backoff-cap. 1 to 4294967295, and no longer than the cap. Config: [vcon_forward] backoff_first
--vcon-forward-backoff-cap<SECS>3000.5.206The longest a container waits between tries, 1 to 4294967295, and no shorter than --vcon-forward-backoff-first. Config: [vcon_forward] backoff_cap
--vcon-forward-max-response-head<BYTES>655360.5.206The most bytes of a store’s status line and headers read, 1 to 4294967295. The forwarder treats an answer with more as no answer, and the container waits for a retry. Config: [vcon_forward] max_response_head
--vcon-forward-max-error-body<BYTES>81920.5.206The most bytes of a store’s answer to a refused container that its <name>.error.json record keeps, 1 to 4294967295. Config: [vcon_forward] max_error_body
--vcon-forward-scitt-url<URL>–0.5.219Register SCITT lifecycle statements (draft-howe-vcon-lifecycle-01) with the transparency service at this base URL. For each delivered container, vcon_created when sipnab wrote it once and vcon_sent every time, each a COSE_Sign1 Signed Statement POSTed to <URL>/entries. After a 202, sipnab polls its Location for the receipt within --vcon-forward-timeout, honoring Retry-After, and only on the scheme, host and port of this URL (draft-ietf-scitt-scrapi-11 section 2.4); the record marks a receipt still running then as pending. What the service answered goes into <name>.scitt beside the delivered container. sipnab logs and records a statement that does not register, and does not retry it; the delivery stands. Needs --vcon-forward-scitt-sign-key, --vcon-forward-scitt-sign-cert and --vcon-forward-scitt-issuer, each from its flag or its key; without one sipnab refuses the run with exit 2, naming it. More: Register lifecycle statements with a transparency service. Config: [vcon_forward] scitt_url
--vcon-forward-scitt-auth-file<FILE>–0.5.219File holding one Header-Name: value line sent with each registration. Refused when other users can read it (chmod 600 it). The store’s credential never goes to the transparency service, and this one never goes to the store. Config: [vcon_forward] scitt_auth_file
--vcon-forward-scitt-ca<FILE>host bundle0.5.219Trust only the CA certificates in this PEM file for an https:// transparency service. Config: [vcon_forward] scitt_ca
--vcon-forward-scitt-sign-key<FILE>–0.5.219The PEM private key that signs the statements: an RSA key signs RS256 (COSE alg -257), an EC P-256 key ES256 (alg -7). The same kinds of key an encrypting [[vcon.rule]] names in sign_key. Refused when other users can read it (chmod 600 it), with exit 2; the same file named by [vcon_forward] scitt_sign_key exits 1. Config: [vcon_forward] scitt_sign_key
--vcon-forward-scitt-sign-cert<FILE>–0.5.219The PEM certificate chain of the signing key, leaf first, carried in each statement’s x5chain. sipnab refuses a chain whose first certificate is not the key’s before it sends anything, naming both flags, with exit 2 (1 when [vcon_forward] scitt_sign_key named the key). Config: [vcon_forward] scitt_sign_cert
--vcon-forward-scitt-issuer<URI>–0.5.219The issuer each statement names in its CWT Claims (iss): a URI of 1 to 8192 characters, such as https://pbx.example.com/sipnab. Config: [vcon_forward] scitt_issuer
--vcon-forward-content-type<TYPE>application/json0.5.219The Content-Type of a JSON container: application/json, application/vcon+json or application/vcon. A gzip format container always goes as application/vcon+gzip, the type draft-ietf-vcon-vcon-core-04 section 5.4 names, whatever this says. Choose application/vcon only for a store you know accepts it. See What it does with each container. Config: [vcon_forward] content_type
--vcon-spool-status<SPOOL_DIR>–0.5.212Print the state of an --export-vcon-dir spool as JSON and exit: containers pending, delivered and failed, and the <name>.error.json record beside each failed container. Reads the forwarder’s default delivered/ and failed/ directories beside the spool. A snapshot of the files: it sends, moves, creates and deletes nothing. The MCP tool vcon_spool_status and GET /v1/vcon/spool give the same answer. Exit 0 after printing, also for a spool directory that does not exist yet, 1 when sipnab cannot read a directory that exists. Refused beside any capture flag, --vcon-forward and --vcon-fetch

Examples

  • sipnab --vcon-forward spool --vcon-forward-url 'http://127.0.0.1:8000/vcon/external-ingress?ingress_list=sipnab' --vcon-forward-auth-file vcon-forward.auth --vcon-forward-once — deliver what is in spool to a self-hosted conserver’s sipnab ingress list once, and exit 0 when the conserver accepted all of it
  • sipnab --vcon-forward spool --vcon-forward-kind vcon-store --vcon-forward-url https://api.vcon.store --vcon-forward-auth-file vcon-store.key — keep delivering to vcon.store until stopped: the kind adds /v1/vcons, and sends the key in the file as Authorization: Bearer <key>
  • sipnab --vcon-forward spool --vcon-forward-kind conserver --vcon-forward-url http://127.0.0.1:8000 --vcon-forward-auth-file conserver.key --vcon-forward-once — deliver to a self-hosted conserver’s sipnab ingress list once: the kind adds /vcon/external-ingress?ingress_list=sipnab and sends the key as x-conserver-api-token
  • sipnab --vcon-forward spool --config /etc/sipnab/vcon-store.toml — take every forwarder setting from a config file’s [vcon_forward] section
  • sipnab --vcon-forward spool --vcon-forward-url http://127.0.0.1:8000/v1/vcons --vcon-forward-auth 'Authorization: Bearer test-token' --vcon-forward-once — a test against a store on this machine, with the credential on the command line, where every user of the host can read it in the process list; for anything else, export SIPNAB_VCON_FORWARD_AUTH instead
  • sipnab --vcon-forward spool --vcon-forward-kind conserver --vcon-forward-url http://127.0.0.1:8000 --vcon-forward-auth test-key --vcon-forward-once — the same with a bare key, which the conserver kind sends as x-conserver-api-token: test-key
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-backoff-first 10 --vcon-forward-backoff-cap 600 — a store that recovers slowly: wait 10 s after the first failed try, doubling to at most 10 minutes
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-backoff-first 1 --vcon-forward-backoff-cap 30 --vcon-forward-once — retry quickly, never waiting more than 30 s between tries
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-max-error-body 65536 --vcon-forward-once — keep up to 64 KiB of each refusal’s answer in its <name>.error.json record
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-max-error-body 512 --vcon-forward-max-response-head 16384 — keep 512 bytes of each refusal, and treat an answer whose headers exceed 16 KiB as no answer
  • sipnab --vcon-forward spool --vcon-forward-url https://proxy.example.com/vcon --vcon-forward-auth-file vcon.auth --vcon-forward-max-response-head 131072 --vcon-forward-once — a store behind a proxy that adds large headers: read up to 128 KiB of them
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-ca store-ca.pem --vcon-forward-timeout 10 — a store whose certificate a private CA issued: trust only that CA, and give up on a silent store after ten seconds rather than thirty
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-ca store-ca.pem --vcon-forward-interval 60 --vcon-forward-timeout 120 — a store across a slow link: pass over the spool once a minute and wait up to two minutes for each answer
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-extra-ca store-ca.pem — a store whose certificate a private CA issued, on a host whose CA bundle should still count: trust that CA as well as the host’s bundle
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-extra-ca /etc/sipnab/store-ca.pem --vcon-forward-once — deliver once to such a store and exit
  • sipnab --vcon-forward /var/spool/sipnab-vcon --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-done /var/spool/sipnab-vcon-sent --vcon-forward-failed /var/spool/sipnab-vcon-held --vcon-forward-interval 2 — keep delivered and refused containers beside the spool rather than inside it, on the same file system, and pass every two seconds
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-replace-url 'https://vcon.example.com/v1/vcons/{uuid}' --vcon-forward-once — a store that answers 409 for a uuid it holds: PUT the rewritten container to the uuid’s own URL instead of filing it as refused
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-replace-url 'https://vcon.example.com/v1/vcons/{uuid}' --vcon-forward-done sent --vcon-forward-failed held — the same, polling, with the delivered and refused containers in sent and held
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-scitt-url https://ts.example.com --vcon-forward-scitt-sign-key scitt.key --vcon-forward-scitt-sign-cert scitt.crt --vcon-forward-scitt-issuer https://pbx.example.com/sipnab — register vcon_created and vcon_sent with the transparency service at https://ts.example.com/entries for each container delivered
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-scitt-url https://ts.example.com --vcon-forward-scitt-auth-file scitt.auth --vcon-forward-scitt-ca ts-ca.pem --vcon-forward-scitt-sign-key scitt.key --vcon-forward-scitt-sign-cert scitt-chain.pem --vcon-forward-scitt-issuer urn:example:pbx-1 --vcon-forward-once — the same once, to a service that needs a credential and has a certificate from a private CA
  • sipnab --vcon-forward spool --config /etc/sipnab/forward.toml --vcon-forward-scitt-auth-file scitt-rotated.auth --vcon-forward-scitt-ca ts-ca-2026.pem — take the other forwarder and SCITT settings from the config file, and replace its transparency service credential and CA for this run
  • sipnab --vcon-forward spool --vcon-forward-url https://vcon.example.com/v1/vcons --vcon-forward-auth-file vcon.auth --vcon-forward-content-type application/vcon --vcon-forward-once — a store that wants the registered vCon media type: send each JSON container as application/vcon
  • sipnab --vcon-forward /var/spool/sipnab-vcon --vcon-forward-kind conserver --vcon-forward-url http://127.0.0.1:8000 --vcon-forward-auth-file conserver.key --vcon-forward-content-type application/vcon+json — send each JSON container to a conserver as application/vcon+json; a gzip format container still goes as application/vcon+gzip
  • sipnab --vcon-spool-status spool — print how many containers wait in spool, how many the forwarder delivered and refused, and the store’s answer for each refusal
  • sipnab --vcon-spool-status /var/spool/sipnab-vcon — the same for a spool given by absolute path; a spool the capture has not written yet prints zeros and "exists": false

vCon fetcher

--vcon-fetch runs sipnab as the process that reads stored vCons back from a store by uuid and writes each to a file.

  • It captures nothing.
  • sipnab refuses it beside any capture source, listener or export flag, and beside --vcon-forward.
  • The other flags in this section need it.
  • Each flag but --vcon-fetch, --vcon-fetch-out and --vcon-fetch-overwrite has a [vcon_fetch] key in the config file. The flag overrides the key.
  • Needs the vcon Cargo feature.
  • More: Fetch a stored vCon.
FlagValueDefaultSinceDescription
--vcon-fetch<UUID>...–0.5.209Read the container with each uuid from the store and write it to <uuid>.vcon.json in --vcon-fetch-out. - reads more uuids from standard input, one per line, and passes over blank lines and lines starting with #. The fetcher asks once for a uuid given twice. A uuid is 1 to 64 characters, each a letter, a digit or -. Exit 0 when the fetcher wrote every container and the schema accepts each, 1 when it wrote none for some uuid (404, another status, a size or shape refusal, a file that exists) or the schema refuses one, 2 when sipnab refuses the settings, 3 when the store answered 401 or 403: the fetcher then asks for nothing more. Needs a URL and a credential, each from its flag or its [vcon_fetch] key; without one sipnab refuses the run with exit 2, naming both
--vcon-fetch-kind<KIND>generic0.5.209The kind of store: generic, vcon-store, conserver or vcon-mcp. A kind supplies the read path after the path the URL names, the header when the credential is a bare key, and the removal of what the store wraps around the container. generic supplies nothing. The table: Fetch a stored vCon. Config: [vcon_fetch] kind
--vcon-fetch-url<URL>–0.5.209The store, http:// or https://. With a --vcon-fetch-kind other than generic, the store’s base URL: the kind adds its read path after the path the URL names. The fetcher takes a URL holding {uuid} as written, the uuid filled in, for any kind, and the generic kind needs one. sipnab refuses credentials in the URL. Plain http:// to a host other than loopback logs a warning. Config: [vcon_fetch] url
--vcon-fetch-auth-file<FILE>–0.5.209File holding the credential, as the forwarder’s auth file does: one Header-Name: value line, or, with a --vcon-fetch-kind other than generic, the bare key, which the kind sends in its header. Refused when other users can read it (chmod 600 it). A file sipnab cannot read, or one that holds no credential, stops the fetcher with exit 2; the same file named by [vcon_fetch] auth_file exits 1. The value never appears in a log line or an error. Config: [vcon_fetch] auth_file
--vcon-fetch-out<DIR>current directory0.5.209Where the fetcher writes each container, as <uuid>.vcon.json, mode 0600. The fetcher creates it, mode 0700, if missing
--vcon-fetch-overwrite–off0.5.209Replace a <uuid>.vcon.json that exists. Without it the fetcher does not ask the store for that uuid, keeps the file, and exits 1. Neither form writes through a symbolic link
--vcon-fetch-timeout<SECS>300.5.209Seconds to wait to connect, and for each read and write, before the fetcher gives up on a container, 1 to 600. Config: [vcon_fetch] timeout
--vcon-fetch-max-size<BYTES>671088640.5.209The largest answer the fetcher reads for one container, 1 to 4294967295. The fetcher refuses a larger one and writes nothing for it. The default, 64 MiB, holds a container with an hour of inline G.711 audio. Config: [vcon_fetch] max_size
--vcon-fetch-max-response-head<BYTES>655360.5.209The most bytes of a store’s status line and headers read, 1 to 4294967295. The fetcher refuses an answer with more. Config: [vcon_fetch] max_response_head
--vcon-fetch-ca<FILE>host bundle0.5.209Trust only the CA certificates in this PEM file for an https:// store. Without it the fetcher trusts the host’s CA bundle. Config: [vcon_fetch] ca
--vcon-fetch-extra-ca<FILE>–0.5.219Trust the CA certificates in this PEM file for an https:// store in addition to the host’s CA bundle: the fetcher accepts the store’s certificate when the host’s bundle or this file issued it. Use --vcon-fetch-ca instead to trust only the named file; the fetcher refuses the two together. Either flag replaces both [vcon_fetch] ca and [vcon_fetch] extra_ca. Config: [vcon_fetch] extra_ca
--vcon-fetch-decrypt-key<FILE>–0.5.218Decrypt each encrypted container with the RSA private key in this PEM file, verify its signature, and write the unsigned vCon inside. The file must be readable by its owner only. Without it the fetcher writes an encrypted container as the store holds it. See Fetch an encrypted vCon. Config: [vcon_fetch] decrypt_key
--vcon-fetch-signer-ca<FILE|system>–0.5.219Write a decrypted or signed container only when its signing certificate chains, through the intermediates in its x5c, to a CA certificate in this PEM file, with every certificate in the chain within its validity period. system uses the host’s CA bundle; write ./system for a file of that name. A container that is not signed has no signer to check and the fetcher writes nothing for it. Needs no key for a signed container; an encrypted one needs --vcon-fetch-decrypt-key, and without it the fetcher writes nothing for an encrypted container. See Check who signed it. Config: [vcon_fetch] signer_ca
--vcon-fetch-signer-sha256<HEX>–0.5.219Write a decrypted or signed container only when its signing certificate has this SHA-256 fingerprint: 64 hex digits, plain or in colon-separated pairs, either case, as openssl x509 -noout -fingerprint -sha256 prints it. A container that is not signed has no signer to check and the fetcher writes nothing for it. Needs no key for a signed container; an encrypted one needs --vcon-fetch-decrypt-key, and without it the fetcher writes nothing for an encrypted container. Config: [vcon_fetch] signer_sha256

Examples:

  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind vcon-store --vcon-fetch-url https://api.vcon.store --vcon-fetch-auth-file vcon-store.key --vcon-fetch-out fetched — read one vCon from vcon.store: the kind adds /v1/vcons/{uuid}, sends the key as Authorization: Bearer <key>, and removes the _meta member the store adds
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-out fetched — read one from a self-hosted conserver: the kind adds /vcon/{uuid} and sends the key as x-conserver-api-token
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind vcon-mcp --vcon-fetch-url http://127.0.0.1:3000 --vcon-fetch-auth-file vcon-mcp.key --vcon-fetch-out fetched — read one from a vcon-mcp server’s REST API: the kind adds /api/v1/vcons/{uuid}, sends the key as Authorization: Bearer <key>, and keeps the vcon member of the answer
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-url 'https://vcon.example.com/store/{uuid}' --vcon-fetch-auth-file vcon.auth --vcon-fetch-out fetched — a store of no listed kind: the URL template says where the uuid goes, and the auth file holds the full header line
  • sipnab --vcon-fetch - --vcon-fetch-kind vcon-store --vcon-fetch-url https://api.vcon.store --vcon-fetch-auth-file vcon-store.key --vcon-fetch-out fetched < uuids.txt — read every uuid listed in uuids.txt, one per line
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-out fetched --vcon-fetch-overwrite — read it again, replacing the file an earlier run wrote
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-url 'https://vcon.example.com/store/{uuid}' --vcon-fetch-auth-file vcon.auth --vcon-fetch-ca store-ca.pem --vcon-fetch-timeout 10 --vcon-fetch-max-size 268435456 — a store whose certificate a private CA issued, holding containers up to 256 MiB: trust only that CA, and give up on a silent store after ten seconds
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-url 'https://proxy.example.com/vcon/{uuid}' --vcon-fetch-auth-file vcon.auth --vcon-fetch-max-response-head 131072 — a store behind a proxy that adds large headers: read up to 128 KiB of them
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --config /etc/sipnab/vcon-store.toml — take every fetcher setting from a config file’s [vcon_fetch] section
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind vcon-store --vcon-fetch-url https://api.vcon.store --vcon-fetch-auth-file vcon-store.key --vcon-fetch-out fetched --vcon-fetch-overwrite --vcon-fetch-timeout 120 — replace an earlier copy across a slow link, waiting up to two minutes for each read
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url https://conserver.example.com --vcon-fetch-auth-file conserver.key --vcon-fetch-ca conserver-ca.pem --vcon-fetch-max-size 1048576 --vcon-fetch-max-response-head 16384 — a conserver whose certificate a private CA issued, holding containers without inline audio: refuse any answer over 1 MiB, or with more than 16 KiB of headers
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url https://conserver.example.com --vcon-fetch-auth-file conserver.key --vcon-fetch-extra-ca conserver-ca.pem — a conserver whose certificate a private CA issued, on a host whose CA bundle should still count: trust that CA as well as the host’s bundle
  • sipnab --vcon-fetch - --vcon-fetch-kind conserver --vcon-fetch-url https://conserver.example.com --vcon-fetch-auth-file conserver.key --vcon-fetch-extra-ca /etc/sipnab/conserver-ca.pem --vcon-fetch-out fetched < uuids.txt — fetch every vCon listed in uuids.txt from such a store
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-decrypt-key recipient.key --vcon-fetch-out fetched — read an encrypted vCon from a conserver, decrypt it with the recipient’s private key, verify its signature, and write the unsigned vCon
  • sipnab --vcon-fetch - --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-decrypt-key recipient.key --vcon-fetch-out fetched < uuids.txt — decrypt every vCon listed in uuids.txt, one uuid per line; the fetcher writes a container that is not encrypted as the store holds it
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-decrypt-key recipient.key --vcon-fetch-signer-ca signing-ca.pem --vcon-fetch-out fetched — decrypt a vCon and write it only when the CA in signing-ca.pem issued its signing certificate
  • sipnab --vcon-fetch - --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-decrypt-key recipient.key --vcon-fetch-signer-ca system --vcon-fetch-out fetched < uuids.txt — decrypt every vCon listed in uuids.txt and write each only when a CA in the host’s CA bundle issued its signing certificate
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-decrypt-key recipient.key --vcon-fetch-signer-sha256 4A:27:CF:DC:D8:87:A5:E5:A9:B5:DE:7E:27:37:C2:48:28:2B:86:44:8A:B3:60:6F:42:99:B2:E2:26:FC:0F:19 --vcon-fetch-out fetched — decrypt a vCon and write it only when one known certificate signed it
  • sipnab --vcon-fetch 018bcfe5-6800-8a6b-a667-78f1c5213800 --vcon-fetch-kind conserver --vcon-fetch-url http://127.0.0.1:8000 --vcon-fetch-auth-file conserver.key --vcon-fetch-decrypt-key recipient.key --vcon-fetch-signer-ca signing-ca.pem --vcon-fetch-signer-sha256 4a27cfdcd887a5e5a9b5de7e2737c248282b86448ab3606f4299b2e226fc0f19 --vcon-fetch-out fetched — require both: the signing certificate chains to signing-ca.pem and has this fingerprint

vCon archive

--vcon-resign re-signs a signed vCon for a long-term archive, as draft-ietf-vcon-vcon-core-04 section 5.2.3 describes. It reads one file and writes one, and captures nothing.

FlagValueDefaultSinceDescription
--vcon-resign<FILE>–0.5.219Re-sign the signed vCon in this file. sipnab verifies every signed layer, requires the outermost signing certificate to be within its validity period now, and signs the file whole, byte for byte, as the payload of a new JWS with the new signer’s x5c and the vCon’s uuid. --vcon-signer-ca and --vcon-signer-sha256 check the input’s signer too. Exit 0 when written, 1 when sipnab refuses the input or the output exists, 2 when a key, certificate or signer check does not load. Needs --vcon-resign-key, --vcon-resign-cert and --vcon-resign-out. See Re-sign for a long-term archive
--vcon-resign-key<FILE>–0.5.219The new signer’s PEM private key: RSA, which signs RS256, or EC P-256, which signs ES256. Readable by its owner only
--vcon-resign-cert<FILE>–0.5.219The PEM certificate chain of --vcon-resign-key, leaf first, carried in the new signature’s x5c
--vcon-resign-out<FILE>–0.5.219Where sipnab writes the re-signed vCon, mode 0600. sipnab never replaces a file that exists, and exits 1 instead

Examples

  • sipnab --vcon-resign signed/0158a120-0392-8337-ae55-829bc914b643.vcon.json --vcon-resign-key archive.key --vcon-resign-cert archive.crt --vcon-resign-out archived.vcon.json — re-sign one signed vCon with the archive’s key before its signing certificate expires
  • sipnab --vcon-resign archived.vcon.json --vcon-resign-key archive-2036.key --vcon-resign-cert archive-2036.crt --vcon-resign-out archived-2036.vcon.json — re-sign the archive again with the next key: the result nests three signed layers
  • sipnab --vcon-resign signed.vcon.json --vcon-resign-key archive.key --vcon-resign-cert archive-chain.pem --vcon-resign-out archived.vcon.json --vcon-signer-ca signing-ca.pem — re-sign only a vCon whose signer the CA in signing-ca.pem issued
  • sipnab --vcon-resign signed.vcon.json --vcon-resign-key archive.key --vcon-resign-cert archive.crt --vcon-resign-out archived.vcon.json --config /etc/sipnab/sipnab.toml — take the signer checks from the config file’s [vcon_input] section

vCon audio fetch

--vcon-audio-fetch reads back the audio a [[vcon.rule]] with audio = "url" wrote beside its container. It reads one container, fetches each recording the container names by url, and captures nothing.

  • It reads the container in any form -I reads: unsigned, signed, encrypted or gzip. --vcon-key, --vcon-signer-ca and --vcon-signer-sha256, or their [vcon_input] keys, open it.
  • sipnab refuses it beside any capture source, listener or export flag, and beside the forwarder, the fetcher, --vcon-spool-status and --vcon-resign.
  • The other flags in this section need it. None has a config key.
  • Needs the vcon Cargo feature.
  • More: Fetch the audio a vCon names.
FlagValueDefaultSinceDescription
--vcon-audio-fetch<FILE>–0.5.221For each recording Dialog Object in this vCon with a url: fetch the file over https://, check its content_hash (draft-ietf-vcon-vcon-core-04 section 2.2) before anything else reads it, decrypt it with --vcon-key when it is a JWE, and write the audio to <uuid>-dialog<N>.wav or .ogg in --vcon-audio-fetch-out. sipnab refuses a url that is not https://, without connecting. Exit 0 when sipnab wrote every recording, 1 when the container does not open, names no recording by url, or sipnab refused any recording, 2 when sipnab cannot use a key, CA or output directory. Needs --vcon-audio-fetch-out
--vcon-audio-fetch-out<DIR>–0.5.221Where sipnab writes each recording’s audio, mode 0600. sipnab creates it, mode 0700, if missing. sipnab never replaces a file that exists: it fetches nothing for that recording, and exits 1
--vcon-audio-fetch-local–off0.5.221Read each recording from the directory that holds the vCon, by the last segment of its url, instead of fetching it: for an --export-vcon-dir spool whose files your storage does not serve yet. The content_hash check still applies
--vcon-audio-fetch-ca<FILE>host bundle0.5.221Trust only the CA certificates in this PEM file for the https:// server that holds the audio. Without it sipnab trusts the host’s CA bundle
--vcon-audio-fetch-extra-ca<FILE>–0.5.221Trust the CA certificates in this PEM file for the https:// server that holds the audio in addition to the host’s CA bundle. sipnab refuses it beside --vcon-audio-fetch-ca
--vcon-audio-fetch-max-size<BYTES>671088640.5.221The largest file sipnab reads for one recording, 1 to 4294967295. sipnab refuses a larger one and writes nothing for it. The default is 64 MiB

Examples

  • sipnab --vcon-audio-fetch fetched/0158a120-0392-891a-9455-bc2e6fad94df.vcon.json --vcon-audio-fetch-out audio --vcon-key recipient.key — fetch, check and decrypt the audio of one encrypted vCon from the storage its url names
  • sipnab --vcon-audio-fetch spool/0158a120-0392-891a-9455-bc2e6fad94df.vcon.json --vcon-audio-fetch-out audio --vcon-audio-fetch-local --vcon-key recipient.key — read the audio from the spool beside the container instead
  • sipnab --vcon-audio-fetch call.vcon.json --vcon-audio-fetch-out audio --vcon-audio-fetch-ca media-ca.pem --vcon-audio-fetch-max-size 268435456 — storage whose certificate a private CA issued, holding recordings up to 256 MiB
  • sipnab --vcon-audio-fetch call.vcon.json --vcon-audio-fetch-out audio --vcon-audio-fetch-extra-ca media-ca.pem — trust that CA as well as the host’s bundle
  • sipnab --vcon-audio-fetch fetched/0158a120-0392-891a-9455-bc2e6fad94df.vcon.json --vcon-audio-fetch-out audio --vcon-key recipient.key --vcon-audio-fetch-ca media-ca.pem — decrypt the audio of an encrypted vCon from storage whose certificate only that CA issued
  • sipnab --vcon-audio-fetch fetched/0158a120-0392-891a-9455-bc2e6fad94df.vcon.json --vcon-audio-fetch-out audio --vcon-key recipient.key --vcon-audio-fetch-extra-ca media-ca.pem --vcon-audio-fetch-max-size 1073741824 — the same, trusting the host’s bundle too, for a recording up to 1 GiB

Fleet inventory (sipnab fleet)

sipnab fleet inventory asks every sipnab instance in a hosts file for its inventory (GET /v1/inventory) and prints one row per instance and the findings across them. It opens no capture. Manage many sipnab instances is the full how-to.

  • --hosts <FILE>: the hosts file, one [[host]] table per instance with name, url, token_file and an optional ca_file.
  • --json: print the report as JSON instead of a table.
  • --timeout <SECS>: seconds to wait for each instance to connect and answer, 1 to 300. Default 10.

It exits 0 when every instance answered and there is no finding, 1 when an instance did not answer or there is a finding, and 2 when it cannot use the hosts file.

G.729 tables (sipnab g729)

sipnab g729 installs and checks the G.729 codebook tables that --g729-tables reads. A codebook table is the set of numeric values the G.729 decoder needs. sipnab does not ship them. G.729 audio is the full how-to.

  • sipnab g729 install downloads the ITU-T G.729 software package, checks its sha256, builds the table file from it and writes the file. It asks before it installs.
  • sipnab g729 status prints whether the table file holds the tables, the path it read and the hash of the values. It exits 0 when the file holds the tables and 1 when it does not.

Both commands take these options:

  • --g729-tables <PATH>: the table file, as for a capture run. Without it, [g729] tables, else g729-tables.bin in the directory of the user config file.
  • -f, --config <FILE>: the config file to read [g729] tables from, as -f for a run.
  • --no-config: read no config file.

sipnab g729 install also takes:

  • --yes: answer yes to the question before installing, for scripts and Dockerfiles.
  • --from <PATH>: install from the package zip, a directory extracted from it, or the table file of another host, without opening a connection.

Examples

  • sipnab g729 status --g729-tables ./g729-tables.bin — check one table file. With no tables there it prints G.729 tables: not installed and Table file: ./g729-tables.bin, and exits 1
  • sipnab g729 status --no-config — check the default path, g729-tables.bin in the directory of the user config file, whatever the config file sets

Config

FlagValueDefaultSinceDescription
-f, --config<FILE>–0.3.0Path to configuration file (must exist)
-F, --no-config–off0.3.0Skip loading any configuration file
-D, --dump-config–off0.3.0Dump effective configuration and exit
--completions<SHELL>–0.5.3Print a shell completion script (bash, zsh, fish, elvish, powershell) to stdout and exit

Examples

  • sipnab --config /etc/sipnab/sipnab.toml --dump-config — dump the effective configuration produced by a specific config file, then exit
  • sipnab --no-config --dump-config — dump the built-in defaults, skipping any configuration file, then exit
  • sudo sipnab -d eth0 --config ~/.config/sipnab/config.toml — live capture using a per-user configuration file
  • sipnab -N -I capture.pcap --no-config — analyze an offline pcap with all configuration files ignored
  • sipnab --completions bash > sipnab.bash — print a bash completion script into a file suitable for /etc/bash_completion.d
  • sipnab --completions zsh > _sipnab — print a zsh completion script into a file suitable for the zsh fpath

Validation rules

  • Flags that print their answer to stdout require -N / --no-tui mode, unless --call-report or --export-vcon is also given: --json, --json-pretty, --json-dialogs, --report, --stun, --json-stun, --analyze, --json-analyze, --yang-analyze, --hexdump, --fail2ban, --group-by, --wireshark, --tshark-filter, --recommend-block, --lint and --vcon-digest.
  • --kill-response accepts values 100-699 only.
  • Feature-gated flags (tls, hep, api, mcp, mcp-http) produce startup errors when the required feature is not compiled in.
  • --mcp refuses every flag in that list, --call-report, --export-vcon without --vcon-out, --evidence-out -, and --relay-stats-interval on a headless run, because on stdio those would write into the JSON-RPC wire.
    • This holds on every transport, not just stdio.
    • Combine --mcp with --quiet to suppress text-mode capture output.
  • HTTP MCP transport (--mcp --mcp-transport http) on a non-loopback --mcp-bind requires a bearer token (--mcp-token / --mcp-token-file / SIPNAB_MCP_TOKEN) or a signing key (--mcp-signing-key / --mcp-signing-key-file / SIPNAB_MCP_SIGNING_KEY). Loopback binds need neither.

Examples

  • sipnab -d eth0 — capture on eth0
  • sipnab -I capture.pcap — read from pcap file
  • sipnab -N --json -I capture.pcap — non-interactive JSON output
  • sipnab --problems — show problematic calls
  • sipnab --kill-scanner -d eth0 — detect SIP scanners
  • sipnab --from alice --to bob — filter by From/To headers
  • sipnab 'host 192.0.2.1 and port 5060' — capture filter (BPF): frames that do not match are never read
  • sipnab --filter "method == 'INVITE' AND rtp.mos < 3.0" — advanced filter DSL
  • sipnab -N -I capture.pcap --call-report "abc123@host" --markdown --no-cli-print — generate detailed report for a call (drop --no-cli-print and the whole capture’s message dump precedes it)
  • sipnab -d eth0 -H 192.0.2.50:9060 — capture with HEP mirror
  • sipnab -d eth0 --keylog /tmp/sslkeys.log --keylog-watch — live TLS decryption

Exit codes

Scripts can rely on these:

CodeMeaning
0Success
1Runtime failure — capture error, I/O error, a capture file that was not read in full (a truncated pcap, a member that would not open, a BPF filter that would not compile against one), a --plugin that would not load, or sipnab could not produce a requested report (e.g. --call-report Call-ID not found). Also a refused configuration: a config file sipnab cannot read, or a value in it sipnab refuses, whichever startup step refuses it
2Invalid usage — bad flag value or combination given on the command line, or a flag whose feature is not compiled into this binary. A refusal that a flag takes part in (a flag value, or a pair with one half from a flag and the other from the config file) is this code
3A check failed — the tool worked and the input failed a check you asked for. --lint --lint-fail-on <severity> found a conformance finding at or above that severity, an --expect rule failed, --vcon-validate found the container invalid, or the vCon forwarder stopped because the store answered 401 or 403 (--vcon-forward). Distinct from 1 on purpose: 1 means the tool failed, 3 means the input did
101Internal failure — sipnab panicked. Its panic hook writes a crash report and ends the process with 101. With [crash] core = true it aborts instead, so the kernel can write a core dump, and the shell reports the signal (134 for SIGABRT)
130Interrupted — Ctrl-C at the archive password prompt (an encrypted .zip or .7z input read on a terminal). sipnab reads nothing more and exits

--expect exits with its suite’s verdict, the exit_code its report carries: 0 every rule that ran passed, 3 a rule failed, 2 no rule ran (no verdict) or the suite itself is at fault. A run that fails for another reason exits 1 first, so 1 never means a failed rule.

Error messages lists the message sipnab prints with a non-zero exit code, what it means, and what to do.

A run may read its input in full and stop where you asked it to stop (-l/--limit, --duration). It still exits 0.

  • The distinction is whether sipnab failed to do what you asked.
  • It is not whether it read everything there was.

When the answer is partial

1 says something went wrong.

  • It cannot say what.
  • A consumer reading --json-dialogs on a pipe never sees it at all.

So a run that could not deliver the whole answer also says so in its output:

  • --json-dialogs emits one extra NDJSON line, after the dialogs.
    • The line is under a top-level sipnab_run key that no dialog object has.
    • A clean run emits nothing extra.
  • --report ends with an INCOMPLETE RUN block naming each reason.
{"sipnab_run":{"input_complete":false,"reasons":["1 of 1 capture file(s) was not read to the end; every report from this run rests on a partial read"],"files":{"given":1,"read_in_full":0,"stopped_early":1,"skipped":0,"not_reached":0},"plugins":{"requested":0,"loaded":0,"failed":0},"retention":{"messages_dropped":0},"archives":{"cut_short":false},"port_gate":{"sip_outside_portrange":0,"sip_outside_ws_ports":0}}}

input_complete is the same predicate as the exit status. So a script reading stdout and a script reading $? cannot reach different verdicts.

archives.cut_short is true when sipnab could not unpack an archive -I named to its end. That happens when:

  • the archive stops early,
  • a header fails its checksum,
  • or the walk reaches a ceiling.

Members past that point are in no report. That is data missing from the input. It fails the run like a truncated file does.

retention.messages_dropped counts captured messages that idle compaction discarded ([limits] idle_compact_after_secs).

  • It is not a failure: the retention policy did what you configured.

port_gate counts SIP that sipnab did not analyze because of the port it used:

  • sip_outside_portrange for messages with both ports outside --portrange,
  • sip_outside_ws_ports for SIP over WebSocket on a port outside the WebSocket port set (--ws-portrange).

Like retention, it is not a failure, because the range is what you asked for. --report names either count in its INCOMPLETE RUN block. It uses the words the run summary uses on stderr:

INCOMPLETE RUN — this report does not describe the whole capture:
  - 12 SIP message(s) were seen on ports outside --portrange and were not analyzed; re-run with --portrange 1-65535 to include them