CLI Reference - NormB/sipnab GitHub Wiki
Quick start:
sipnab -I capture.pcapto analyze a file, orsudo sipnabfor live capture on the default interface. Add-Nfor non-interactive output.
Complete flag reference for sipnab. This page groups flags by function.
A first run needs only a handful of these flags:
-
-I,--inputreads 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,--devicenames the network interface to capture on. -
-N,--no-tuiturns on non-interactive mode, with no TUI. The batch and output flags require it. -
--reportgenerates a summary report after capture completes. Requires-N. -
--jsonprints output as NDJSON, one JSON object per line. Requires-N. -
--filtertakes a Filter DSL expression or a diagnostic alias name, such ascodec-asymorlate-media, and a named filter as@name.
The Since column names the first sipnab release whose command line accepted the flag. The flag may have its current name or one of its aliases.
- The column comes from
src/cli.rsat each release tag,v0.3.0onward. - The pre-release tags
v0.1.0-alphaandv0.2.0-betado not count as releases. - A release may remove a flag and a later release may restore it. That flag keeps the release it first appeared in.
- The changelog lists four version numbers that were never tagged or released: 0.3.1, 0.4.1, 0.5.76 and 0.5.111. A flag first added in one of them shows the next release.
-
Unreleasedmeans no release has the flag yet. It is in the source but not in a published version.
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.
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).
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-printis what makes it just that. - On its own,
--call-reportappends 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-printWhen 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-printhere too. - Without it,
report.mdopens 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.mdWatch 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.ndjsonHand each quality drop to an external alerting script rather than reading it yourself.
-
--exec-rate-limitbounds 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 5Find 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" --jsonThe --slow-setup alias asks the same question against the run's own post-dial delay threshold.
- The threshold is 11 seconds, unless
--pdd-thresholdor[diagnosis] post_dial_delay_secsmoves it. - So a quick check needs no filter expression at all.
sipnab -N -I capture.pcap --slow-setup --reportDetect SIP scanning and append it in fail2ban's format to the log its jail reads.
-
--kill-scanneris what detects. -
--fail2banonly chooses the format, so leaving it out leaves the log empty.
Every example below passes -N, and that is not a style choice.
- Only the headless single-capture path builds the detectors.
- Two paths run without them:
- the interactive TUI (the default when you leave
-Noff), - the
--cores Nparallel reader.
- the interactive TUI (the default when you leave
- Ask for detection on either and sipnab warns at startup. The warning says it accepts the flag and ignores it.
- Without that warning, an empty finding list reads as an all-clear.
sudo sipnab -N -d eth0 --kill-scanner --fail2ban >> /var/log/sipnab/scanners.logBefore 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 -rnAudit a capture for digest credentials that went out where anyone could read them.
sipnab -N -I capture.pcap --digest-leakRun 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 --syslogPrint a display filter to paste into Wireshark after opening the capture.
sipnab -N -I capture.pcap --wiresharkOr print a tshark-compatible filter string. Use this when the next step is a shell pipeline rather than the Wireshark GUI.
sipnab -I capture.pcap --tshark-filter 'sip.from.user == "1001"'Audio export is a TUI workflow. See Keybindings.
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 -rnList every distinct User-Agent the capture saw. This finds the odd endpoint out.
sipnab -N -I capture.pcap --json \
| jq -r '.ua // empty' | sort -uStop after a fixed packet count and summarize the capture.
sipnab -N -d eth0 -n 1000 --reportRoll 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:50Keep only the newest four of those files. Then a long capture fits a fixed amount of disk.
-
--split-keepdeletes 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 4The drop user creates the output files. Run as root, sipnab does this:
- It opens the capture device.
- It drops privileges to
nobody(or the--useryou name) before it creates the-Ofile. - It creates every
--splitfile later still. - Right after the drop, before it reads the first packet, it checks that the drop user can create these:
- the
-Ofile, -
--export-vcon-dir, -
--vcon-out, -
--redact-map.
- the
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
--usernaming an account that can already write it. - Keep root for the whole run with
--no-priv-drop.
--split-keepdeletes 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
anypseudo-device is what makes this every interface. -
--multi-deviceis for naming a specific list, as below.
sipnab -d any --delta-timeCapture on two named interfaces instead, one libpcap handle each.
sipnab -d eth0,eth1 --multi-device --delta-timeTip: 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.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
-d, --device
|
<IFACE> |
platform default | 0.3.0 | Network 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.0 | Read 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 |
-- | off | 0.5.70 | Descend into subdirectories when -I names a directory |
--input-name |
<GLOB> |
-- | 0.5.70 | Read 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.219 | Decrypt 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.219 | Read 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.219 | Read 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.219 | Fetch 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.0 | Write 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> |
64 |
0.3.0 | Kernel 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> |
64 |
0.4.4 | Memory budget for the in-flight captureβprocessing queue. The queue grows under load up to this budget (capped, never OOM) and shrinks when idle; overrides [capture] buffer_budget_mb. Config: [capture] buffer_budget_mb
|
--snaplen |
<BYTES> |
65535 |
0.3.0 | Snapshot 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-profile |
signaling|full |
-- | 0.5.109 | Picks 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.9 | Parse 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 |
-- | off | 0.5.9 | Disable 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
|
-- | off | 0.5.10 | Suppress 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-5061 |
0.3.0 | SIP 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, 8443 |
0.5.99 | Ports carrying SIP-over-WebSocket (RFC 7118), as one inclusive START-END range in the same grammar as --portrange. The shipped set is the browser's view of the web, not a deployment's: Kamailio, OpenSIPS and Janus each default to WSS outside it, and behind a reverse proxy sipnab sees whichever port the proxy forwards to β so the whole WebRTC signaling leg stays invisible. 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 (OpenSIPS writes the header and the payload as two), several frames in one record, and a message fragmented across frames, up to MAX_WS_MESSAGE_SIZE (65,536) bytes per message. It reads the HTTP upgrade and its 101 answer across TLS records too (OpenSIPS writes its 101 as three), up to MAX_WS_HANDSHAKE_SIZE (8,192) bytes, and frames start at the byte after the blank line that ends them, in the same record or a later one. A frame, message or upgrade it cannot finish or decode counts in the NOT DECODED line. Config: [capture] ws_ports
|
--multi-device |
-- | off | 0.3.0 | Open 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 |
-- | off | 0.3.0 | Disable RTP capture and analysis. Config: [capture] no_rtp
|
--rtp |
-- | off | 0.5.204 | Capture and analyze RTP even when [capture] no_rtp = true. The last of --no-rtp / --rtp typed wins. Config: [capture] no_rtp
|
-p, --no-promisc
|
-- | off | 0.5.8 | Do 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.0 | Read 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>] |
off | 0.5.80 | Also 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 because it is not a narrowing filter β BPF cannot walk a GTP-U extension-header chain to the inner port, so covering these means taking the whole port, which on a mobile core is the entire user plane. Ignored when you supply your own filter |
-n, --count
|
<N> |
-- | 0.3.0 | Stop 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.0 | Stop 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.0 | Autostop 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.0 | Split output files (e.g., filesize:50 for 50 MiB chunks) |
--split-keep |
<N> |
-- | 0.5.100 | Keep 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 |
-- | off | 0.3.0 | Replay packets from a pcap file at original timing |
--pcapng |
-- | off | 0.3.0 | Use pcapng format for output files. pcapng Metadata covers the metadata sipnab writes into pcapng output |
<BPF_FILTER>... |
positional | -- | 0.3.0 | BPF 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
portrangewould match none of that traffic.- See How the generated filter reaches encapsulated SIP for the offsets it probes 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-rtpthe filter stays signaling-only. Nothing would read the media.- With
--hep-listenas 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.
- Up to 0.5.192 the filter admitted signaling only. A default live capture then measured no media at all.
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_compileexactly 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:
Platform Default Scope Linux the anypseudo-deviceevery interface at once, loopback included macOS / BSD libpcap's default device, from the routing table; otherwise the first non-loopback interface one interface On Linux this default is deliberate.
- A SIP proxy often talks to itself over loopback. So capturing only
eth0silently misses it.- Pass
-d anyto say so explicitly.- Promiscuous mode does not apply to
any. So--no-promiscchanges 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
-dexplicitly.
Reading a set of files: order comes from the packets, not the names.
tcpdump -C 100 -W 10writes a ring buffer,tg.pcap0throughtg.pcap9.- It then wraps and overwrites the oldest file in place.
- A real set measured for this feature ran
tg.pcap7,tg.pcap8,tg.pcap9,tg.pcap0β¦tg.pcap6in time order.- The numeric suffix records where tcpdump was in its cycle. It does not record when the packets arrived.
So sipnab sorts by each file's first packet timestamp.
- Neither lexicographic nor natural-numeric filename order reconstructs that capture.
- Replaying it out of order corrupts every timing derivation. These all assume timestamps only move forward:
- post-dial delay,
- setup time,
- retransmission detection,
- the RFC 3261 Timer B/C/H bounds.
sipnab recognizes a capture by opening it, not by its extension.
tg.pcap0has the extensionpcap0. Plenty of captures have none at all.- It decompresses gzip members transparently. So a directory holding both
.pcapand.pcap.gzneeds nothing special.A file you name directly with
-Ithat sipnab cannot read is an error. sipnab may instead discover a file by expanding a directory or glob. It skips such a file with a warning, because directories hold other things.Why it matters beyond tidiness: a call's INVITE may land in one file and its BYE in the next.
- Reading a split capture as a set is the only way to see that call.
- Analyzed one file at a time, that call appears as one that never ends plus a stray BYE. Neither half is the truth.
- On the 10-file, 921 MB set above, 2271 of 20512 calls (11%) spanned a boundary.
-Iand-dare 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
-Irather than adding-dbeside it.
--portrangedecides 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 reaches no message count, no dialog, and no output format.
- So every total you read describes the range and not the capture. So does every ratio you compute from one.
- sipnab counts what it skipped. It says so on stderr and at the end of the run.
- It names the busiest ports, so there is something to widen to.
--reportnames the count in itsINCOMPLETE RUNblock:NOT ANALYZED: 1 further SIP message(s) were seen on ports outside --portrange and are in none of the totals above. Busiest: 8090 (1). Re-run with --portrange 1-65535 to include them.
--portrange 1-65535analyzes 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 DECODEDis the other line to read before the totals.
--portrangeis 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: 49 of 49 frame(s) (100.0%) produced nothing and are in none of the counts above. Reasons: unsupported link type 0 (49). 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 0says the file isDLT_NULL.editcap -T ether in.pcap out.pcapconverts it.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}plussipnab_capture_undecoded_fractionon/metrics.
docs/troubleshooting.mdtables 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 ofbest-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--countso the log cannot run away, then check where the records went. A connected audit daemon has them inausearch -m SECCOMP. A pid of 0 means they are indmesg -
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β thesngrephabitsngrep INVITE udp port 5060, written for sipnab: the match expression goes after-eand 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, soanalyze_vconandvalidate_vconread 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 insigning-ca.pemissued 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 -
sipnab -N -I signed.vcon.json --vcon-signer-sha256 40:97:43:61:0C:C7:47:BD:40:82:E2:28:1C:53:A6:54:88:74:EB:E7:A7:2B:E4:A3:C8:CB:1B:CA:C8:97:BC:D6 --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 409743610cc747bd4082e2281c53a6548874ebe7a72be4a3c8cb1bcac897bcd6 --reportβ require both: the signing certificate chains tosigning-ca.pemand 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 anx5uURL: 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 thex5uchain from a server the CA inpki-ca.pemissued, and read the vCon only when its signer chains tosigning-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-deviceneeds 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. Checksipnab_capture_snapped_frames_totalafterwards 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 (deepRecord-Routesets, ISUP encapsulation). Use--capture-profile fullinstead when you need--retain-audio, WAV export or a faithful-Ore-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
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
-Inamed. - 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.pcapstill opens. - a gzip-compressed member such as
-
sipnab accounts for every member. A member that is not a capture gets a
Skippingline 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, such as 7-Zip,
zstd,xzorbzip2.
The closing
-I resolved toline 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/xlands 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-framereads that member straight back out of the archive.
- sipnab writes each member to a file it names itself, in a private directory
under
-
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_shortin thesipnab_runrecord.
- exit status
- Every byte any gzip layer produces counts against
-
--input-namefilters members by file name, the same way it filters a directory's files. On a single compressed capture it still refuses. There is nothing to choose between. -
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.
Captures often travel as password-protected ZIP files. They carry subscriber identities, numbers and credentials.
- Unpacking one by hand leaves a decrypted copy on disk. That defeats the password.
- Point
-Iat 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
fullbuild, - every release binary, the static musl tarballs and the
-noaudiopackages among them.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--archive-password-file |
<FILE> |
-- | 0.5.187 | Read 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.187 | Run 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 |
-- | off | 0.5.187 | Read an archive password from the first line of stdin, like docker login --password-stdin
|
--archive-password |
<PASSWORD> |
-- | 0.5.187 | An 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> |
all | 0.5.187 | Try passwords in one encoding only: utf-8, cp437, cp850 or cp1252
|
--no-password-prompt |
-- | off | 0.5.187 | Never 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-passwordexists, sipnab reads it like a password file. - So
LoadCredential=archive-password:/etc/sipnab/pcaps.pworLoadCredentialEncrypted=in the unit is all it takes. - Writing
--archive-password-file %d/archive-passwordin the unit works too.
- When
- The
SIPNAB_ARCHIVE_PASSWORDenvironment 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
-Inames, before it draws. - A capture opened inside the TUI asks in its own window, described in Archive password popup.
- It reads
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
unzipand 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). - The reason: a crafted archive could otherwise make one attempt take hours.
- sipnab refuses one asking for more than 7-Zip's own ceiling of 2^24
rounds, as
sipnab reads ZipCrypto and warns once per archive that it protects nothing.
- The
bkcracktool 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 toline 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-coredumpwarns 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-annotatedcopy 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 inpsand 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 inps; 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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
-N, --no-tui
|
-- | off | 0.3.0 | Non-interactive mode (no TUI). Required for batch/output flags |
-c, --calls-only
|
-- | off | 0.3.0 | Show 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
|
-- | off | 0.3.0 | Decode telephone-event (DTMF) RTP payloads and log each event at info, digit value masked as x
|
--dtmf-cleartext |
-- | off | 0.5.82 | Log the DTMF digit VALUES instead of the mask, at debug. Publishes PINs and card numbers |
-q, --quiet
|
-- | off | 0.3.0 | Suppress 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 setSIPNAB_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 needsSIPNAB_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.
- 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
debugwhile the masked line stays atinfo. 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.logWhere the events go. sipnab writes one masked line per decoded event and keeps a count. Nothing else carries the digits: no report, no JSON field, no MCP tool.
Two consequences follow, and both bite the obvious command lines.
- Adding
-tto a TUI session shows you nothing. TUI mode floors the log level aterrorto keep the alternate screen intact. - Adding
--quietalso hides them. It floors the level atwarn. - Use
-Nwithout--quiet.
SIPNAB_LOG=info does override the TUI floor. But sipnab sets that floor to
stop log lines corrupting the alternate screen. So redirect stderr if you do.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
-e, --match
|
<PATTERN> |
-- | 0.5.6 | SIP 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
|
-- | off | 0.3.0 | Case-insensitive matching for header filters and patterns |
-v, --invert
|
-- | off | 0.3.0 | Invert the match: show messages that do NOT match |
-w, --word
|
-- | off | 0.3.0 | Match whole words only |
--single-line |
-- | off | 0.3.0 | Prevent . from matching newlines in payload regexes; does not unfold headers |
--from |
<PATTERN> |
-- | 0.3.0 | Filter by SIP From header (regex pattern). Config: [filter] from
|
--to |
<PATTERN> |
-- | 0.3.0 | Filter by SIP To header (regex pattern). Config: [filter] to
|
--contact |
<PATTERN> |
-- | 0.3.0 | Filter by SIP Contact header (regex pattern) |
--ua |
<PATTERN> |
-- | 0.3.0 | Filter by User-Agent header (regex pattern) |
--filter |
<EXPR> |
-- | 0.3.0 | Filter 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.214 | Keep 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. ANDed with --filter. Needs -N
|
--until |
<RFC3339> |
-- | 0.5.214 | Keep 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 "[email protected]" --ignore-caseβ show every dialog that mentions [email protected], 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
A query flag answers one question about the capture and exits.
- Each one prints the same answer as the REST route and the MCP tool for that question. All three call the same function.
- The output is:
- a table by default,
- Markdown with
--markdown, - with
--json, one JSON object on the last line of stdout, in exactly the shape the REST route returns.
- Every query honors
--filterand the time window below.
--since and --until keep the dialogs whose first message falls in the
half-open window [--since, --until).
- A dialog at exactly
--sinceis in. - A dialog at exactly
--untilis 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
Zto UTC.
The window is the one every surface applies, under three spellings:
| Surface | Spelling |
|---|---|
| CLI |
--since <RFC3339> / --until <RFC3339>
|
| REST | GET /v1/dialogs?after=<RFC3339>&before=<RFC3339> |
| MCP |
search_by_time with start / end
|
| Filter DSL | time >= '<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 compiled
filter. So these all see the same dialogs:
-
--report, -
--json-dialogs, -
--analyze, - the per-message stream,
-
--export-vcon-when, - every query.
--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 exits 0 with empty listings.
- It says so on stderr, naming the window and the span the capture's dialogs cover.
These exit 2:
- an
--untilthat is not after--since, - a value that is not RFC 3339.
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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--resolve |
-- | off | 0.4.3 | Turn 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 |
-- | off | 0.5.204 | Show 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 |
-- | off | 0.4.3 | Also use reverse DNS (PTR) lookups. Implies --resolve. Emits DNS queries for captured IPs. Config: [names] reverse_dns
|
--no-reverse-dns |
-- | off | 0.5.204 | Do 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> |
4096 |
0.5.100 | Reverse-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.3 | Preload 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β annotate an offline pcap with names, preloading two mapping files on top of /etc/hosts -
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β replay an offline pcap and resolve its addresses with reverse DNS, supplemented by 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β the opposite, for a small lab capture on a memory-tight box, where a few hundred entries cover every host in the file
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--strip-secrets |
<OUTPUT> |
-- | 0.4.3 | With -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. |
--show-frame |
<POINTER> |
-- | 0.5.77 | Resolve 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 --pcapngexport (whenever--resolve/--namesapply). - 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.
- the Section Header Block records the producing application
(
- 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 asUNVERIFIEDbecause 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
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.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--notes |
<FILE> |
-- | 0.5.185 | A 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.185 | With -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
Shortcut flags that expand to predefined filter DSL expressions. See filter-dsl.md for the exact expansion of each alias.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--problems |
-- | off | 0.3.0 | Show 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 |
-- | off | 0.3.0 | Show calls whose post-dial delay passes --pdd-threshold (11 seconds by default, from [diagnosis] post_dial_delay_secs) |
--short-calls |
-- | off | 0.3.0 | Show completed calls shorter than --fraud-short-call (3 seconds by default, from [security] fraud_short_call_secs) |
--one-way |
-- | off | 0.3.0 | Show calls with potential one-way audio issues |
--nat-issues |
-- | off | 0.3.0 | Show calls whose RTP arrived from an address no SDP advertised (NAT-rewritten media source) |
Multiple diagnostic flags combine with OR.
- Adding
--filterANDs 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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--json |
-- | off | 0.3.0 | Output as NDJSON (one JSON object per line, schema in output-formats.md). Requires -N
|
--json-pretty |
-- | off | 0.3.0 | Output each message as pretty-printed multi-line JSON (use --json for line-oriented NDJSON). Requires -N
|
--json-dialogs |
-- | off | 0.5.68 | NDJSON, 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.69 | Load 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 |
-- | off | 0.3.0 | Generate 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.0 | Generate a detailed report for a specific Call-ID. Implies non-interactive |
--export-vcon |
<CALL-ID> |
-- | 0.5.125 | Export 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.127 | Emit 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. Reusing the language rather than growing a flag per policy is deliberate: one flag for failures, another for calls with media, another for a duration threshold, and so on enumerates the cases somebody thought of. The case nobody thought of is the one an operator needs at three in the morning. Parsed before the capture opens, so a malformed expression fails the run instead of leaving an empty directory a reader takes for "nothing matched". 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.127 | Directory 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, because a Call-ID is text whoever placed the call chose and this is the first path sipnab builds from one. Requires --export-vcon-when: a destination for containers no predicate selects is a flag that does nothing |
--vcon-max-inline-media |
<MIB> |
5 |
0.5.128 | Largest inline media body a vCon may carry, in MiB. The shipped 5 comes from a measurement rather than taste: one probed vCon store answered HTTP 204 for a roughly 12 MB container, wrote the record to its database, and then had its file spool refuse the payload, with neither transport reporting the partial write. That budget is a property of the CONSUMER and not of the format, so raise it once you know what reads your containers. 0 refuses every inline body, which says "never inline media" 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.127 | Suppress content for any dialog carrying this header. No default, so the feature stays inert until you name one: sipnab ships no opinion about which header your switches emit, and a built-in guess would either miss yours or silently match one you did not mean. PRESENCE suppresses and the value plays no part, because a rule keyed on a value raises the question of what an unrecognized value means, and the only safe answer to "I do not understand this deny flag" is to deny. Matched case-insensitively, as SIP header names are (RFC 3261 7.3.1). Deny only. A header asking sipnab to RECORD is an assertion by whoever sent the request, and this tool already refuses that class of claim -- every vCon party carries validation: "none". Acting on such an assertion to be more conservative costs at worst a container nobody kept. Acting on one to retain content would hand the retention decision to anyone who can set a header, so no permit flag exists here. Needs the vcon Cargo feature |
--content-deny-tombstone |
-- | off | 0.5.128 | Write 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, and the trade is worth stating: 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> |
stdout | 0.5.125 | Write the --export-vcon container to this path instead of stdout. Requires --export-vcon. Refuses a path that names a capture this run reads, before opening any writer β a container written over the capture it describes destroys the evidence it summarizes. A write that fails exits non-zero rather than reporting a file that is not there. 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 |
-- | off | 0.5.128 | Print a SHA-256 of every container written, in sha256sum format, on stdout while the progress summary stays on stderr. Deliberately not a signature and deliberately outside the container: a conserver adds fields on ingest, so a signature over the bytes sipnab emitted would fail for an ordinary reason and tell an operator nothing. A digest makes the smaller, honest claim β this is what sipnab wrote, at this path, at this moment β which binds an emission to a store's own ledger entry out of band. sipnab ... --vcon-digest > SHA256SUMS and a later sha256sum -c SHA256SUMS both work with no glue. Implies --no-cli-print, so no message line shares stdout with the digests. Needs the vcon Cargo feature |
--redact |
-- | off | 0.5.130 | Replace identities, addresses, hostnames and correlation identifiers in an exported vCon with keyed pseudonyms. Not masking. Every identity becomes a keyed token equal exactly when the original was equal, and every address goes through a prefix-preserving map, so "these forty failures came from one subscriber" and "the media went to a subnet the SDP never advertised" both stay answerable. Masking answers neither. sipnab DELETES digest credentials and inline audio rather than tokenizing them, because no pseudonym of either carries diagnostic value. 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 β a --redact that wrote nothing redacted would read as a redacted capture. Needs the vcon Cargo feature |
--redact-key-file |
<FILE> |
fresh key | 0.5.130 | Read the redaction secret from this file instead of drawing a fresh one from the operating system. Without it nothing can join the tokens against any other export and nothing anywhere can reverse them, which is the safe default; supply a file when the same subscriber has to read the same across yesterday's containers and today's. The whole file is the secret, trailing newline included, so neither a 32-byte key nor a passphrase is silently truncated. Requires --redact
|
--redact-keep-prefix |
<N> |
0 |
0.5.130 | Keep this many leading digits of a number verbatim, so route and NPA analysis survives. Zero by default, and the default is an argument: every retained digit is a digit of a real subscriber number published in the clear, and sipnab has no basis for choosing how many β a country code is one to three digits, an NANP area code is three, a national destination code is anything. Requires --redact
|
--redact-map |
<FILE> |
-- | 0.5.130 | Write 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 exactly as sensitive as the capture it came from. Refuses to write over an existing file: that file may be the map for containers already sent somewhere. Requires --redact
|
--markdown |
-- | off | 0.3.0 | Format report output as Markdown |
--stun |
-- | off | 0.5.112 | Report the STUN and TURN activity in the capture, and what it achieved: one row per transaction (Binding, Allocate, Refresh, ChannelBind, β¦) naming the method, how many requests it took, whether anything answered, and the reflexive or relayed address the server reported back. The row that matters is the unanswered one β a client whose Binding Request draws no reply never learns its public address, advertises the private one in its SDP, and the media never arrives. 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 -- the ICE analogue of the mapped address, and the only thing that says which 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 |
-- | off | 0.5.112 | NDJSON, 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 |
-- | off | 0.5.112 | Analyze 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 |
-- | off | 0.5.112 | The --analyze result as one JSON object, emitted after capture. One object rather than a line per finding: the frames read and the dialogs examined are properties of the run, and a clean capture must still serialize to something that states them. Requires -N
|
--yang-analyze |
-- | off | 0.5.185 | The --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 |
-- | off | 0.5.185 | Print 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 |
-- | off | 0.3.0 | Include hex dump of SIP payloads. Requires -N
|
--delta-time |
-- | off | 0.3.0 | Show delta time between consecutive messages. Config: [display] delta_time
|
--no-delta-time |
-- | off | 0.5.204 | Show 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.0 | Show N messages after each match (like grep -A) |
--show-empty (--full) |
-- | off | 0.3.0 | Show the full header block of bodyless messages (responses, OPTIONS, REGISTER, ACK, BYE); by default they show only the summary line |
--proto-number |
-- | off | 0.5.10 | Annotate 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 |
-- | off | 0.3.0 | Flush output after each line (useful for piping) |
--color |
<WHEN> |
auto |
0.3.0 | Color output mode: auto, always, never. Config: [display] color
|
--from-to-mode |
<MODE> |
default |
0.4.4 | Default 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.0 | Maximum payload bytes to display. Config: [display] payload_limit
|
-T, --text-dump
|
-- | off | 0.3.0 | Dump raw SIP message text (like the CLI matcher -T) |
--no-cli-print |
-- | off | 0.3.2 | Suppress per-message CLI output (useful with --report / --call-report so only the post-capture summary reaches stdout) |
--wireshark |
-- | off | 0.3.0 | Print a display filter for the current capture to paste into Wireshark; use with -N
|
--tshark-filter |
<EXPR> |
-- | 0.3.0 | Generate a tshark-compatible display filter string |
--fail2ban |
-- | off | 0.3.0 | Switch 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 (or --kill-ua) 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 |
-- | off | 0.5.84 | Run 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. The rule catalog is in sip-lint-rules.md, and over MCP as explain_rule
|
--lint-fail-on |
<SEVERITY> |
-- | 0.5.84 | Exit 3 when the linter reports a finding at or above this severity: info, warning (or warn), or error. Needs --lint. Exit 3 is deliberately not 1 or 2, so a pipeline can tell a broken sipnab (1) and a wrong invocation (2) apart from a non-conformant CAPTURE (3). info parses and buys nothing, because that severity exists for findings that are never a reason to fail a build |
--group-by |
<FIELD> |
-- | 0.3.0 | Group output by field (e.g., call-id, from, method) |
--max-groups |
<N> |
100000 |
0.5.97 | Distinct --group-by keys one run retains, the same figure -l/--limit ships so a grouped run cannot outgrow an ordinary capture. Past it sipnab refuses new keys and warns that the output is incomplete; -l/--limit bounds tracked dialogs and never reached this buffer. Requires --group-by. Config: [limits] max_groups
|
--max-grouped-messages |
<N> |
200000 |
0.5.97 | Messages --group-by buffers across every group. Grouping cannot stream β the last packet may belong to the first group β so this is memory sipnab holds until the capture ends. Requires --group-by. Config: [limits] max_grouped_messages
|
--node-name |
<NAME> |
hostname | 0.5.81 | Name this box reports as, in capture_identity.node on every MCP and REST answer. Lets an agent querying several servers at once tell WHICH one saw a given fact β "answered 407" is incomplete until you know where. Distinct from the capture instance, which rotates when a different capture loads; the node is the box and stays put, so a capture restart does not read as a topology change. 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 at0600 -
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-mapwritten 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 aboveerror, 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 aLAPSEDstatus 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.falsemeans 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 [email protected] analysis.jsonvalidates 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 samerankorder -
sipnab --print-yang-module > [email protected]β save the YANG module this build's RFC 7951 export validates against, under the file name RFC 7950 gives it, foryanglintorpyang -
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 --payload-limit 1000 > messages.jsonβ export every SIP message from a capture as pretty-printed JSON, truncating displayed payloads to 1000 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 '[email protected]' > call.vcon-- hand one observed dialog to a conversation-data pipeline in the interchange format it already reads, instead of a sipnab-shaped JSON nobody else parses. The container names sipnab as an observer party and carries what this capture missed, so a consumer weighs it as an observation rather than as a recording -
sipnab -N -I capture.pcap --export-vcon '[email protected]' --vcon-out ./call.vcon --no-cli-print-- the same container to a file, with the run otherwise silent. Use this spelling in a script: the exit code is the whole answer, and a path sipnab cannot write fails the run rather than leaving an operator to discover the missing file later -
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 asked sipnab to leave it alone. A switch that marks sensitive calls can keep them out of an export without sipnab needing to understand why -
sipnab -N -I ./captures --export-vcon-when 'duration > 30' --export-vcon-dir ./long/ --content-deny-header Privacy-- presence is the whole rule, soPrivacy: nonesuppresses exactly asPrivacy: iddoes. If you do not understand a deny flag, denying is the only safe reading of it -
sipnab -N -I capture.pcap --export-vcon-when "state == 'Failed'" --export-vcon-dir ./failed/-- one container per failed dialog, for handing a morning's failures to a conversation-data pipeline without naming each Call-ID by hand. A capture where nothing failed writes nothing and says so on stderr, which is the answer an operator needs rather than an empty directory -
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 same conditions that narrow a report also choose a container set. The containers carry the same ICMP media evidence the report shows, because both go through one selection path -
sipnab -N -I ./captures --export-vcon '[email protected]' --vcon-out ./captures/1-1966.vcon-- refused before anything opens a writer.--vcon-outgoes through the guard-Odoes, so a container cannot land inside a directory this run reads: a name that is free today becomes an input on the next run, and the container would then overwrite the evidence it summarizes -
sipnab -N -I capture.pcap --export-vcon '[email protected]' | jq -r '.analysis[0].body.capture_completeness.note'-- read the caveat before the contents. The analysisbodydeclaresencoding: "json", so it is the JSON object itself andjqindexes into it directly. The note comes from this run's own counters and says what sipnab READ and dropped. It never says the call was short, silent or broken, because none of that follows from a capture that missed something -
sipnab -N -I ./captures --export-vcon-when 'duration > 30' --export-vcon-dir ./long/ --vcon-max-inline-media 64-- carry audio the shipped 5 MiB budget would refuse. One store answers HTTP 204 and then drops the payload in its file spool, telling the producer nothing, and that measurement set the default -- so raise it only when you know what reads your containers -
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.0says "never inline media" without turning the exporter off, and the refusal still appears in the completeness caveat rather than passing as a call that had no 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 call that was deliberately withheld from one that never happened. It carries no message trace, no media and no bodies, and declaresredactedwithtypealone, because no fuller version of the container exists anywhere to point at -
sipnab -N -I capture.pcap --export-vcon-when 'duration > 0' --export-vcon-dir ./out/ --content-deny-header Privacy --content-deny-tombstone-- off by default for a reason worth stating: a tombstone reveals that the call EXISTED. If the header means "this call must leave no trace", leave the flag off and let the dialog produce nothing at all -
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 insha256sumformat while the progress summary stays on stderr, so the redirect captures the one and not the other -
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, so the spool carries its own manifest. The name in each line is the bare file name rather than a path, so a spool that is later moved or mounted elsewhere still verifies from inside its own directory -
cd ./long/ && sha256sum -c ../SHA256SUMS-- verify later with the standard tool and no glue. This is a digest, not a signature. A signature over sipnab's bytes could never verify against the object a store holds, because a conserver adds fields on ingest. What the digest buys is binding an emission to a store's own ledger entry out of band -
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-Yverbatim, so it takes WIRESHARK display-filter syntax rather than sipnab's--filterDSL:sip.Method, notmethod. Quote the whole expression in single quotes so the inner double quotes survive the shell
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
-l, --limit
|
<N> |
100000 |
0.3.0 | Maximum 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
|
-- | on | 0.3.0 | Evict the oldest dialog at --limit capacity (LRU). On by default; kept for back-compat/explicitness |
--no-rotate |
-- | off | 0.4.5 | Disable rotation: drop new dialogs at capacity instead of evicting the oldest (inverts the safe default) |
--dialog-track |
<METHOD> |
call-id |
0.3.0 | Group messages by call-id (one unit per dialog) or branch (one per SIP transaction) |
--leg-correlation-window |
<MS> |
2000 |
0.5.100 | How far apart one call's two legs may start and still correlate on TIMING alone. The B2BUA timing heuristic's whole content, and the only strategy left once a B2BUA has rewritten every identifier the other six strategies compare. The shipped two seconds describes a PBX placing the outbound leg immediately, not one doing an LNP or ENUM dip, or walking an LCR cascade, before it places one. Every correlation still reports the strategy that matched, so a widened window does not turn a guess into a claim. Config: [sip] leg_correlation_window_ms
|
--active-idle-window |
<SECS> |
3600 |
0.5.100 | Seconds a dialog may go untouched and still count toward the active-dialog and active-call gauges every surface publishes. The shipped hour is twice RFC 4028's default Session-Expires, which grounds it for a trunk carrying session timers and not for a contact center, where a caller parked on hold past an hour is a channel in use the gauge stops counting. Widening it widens the opposite error -- a call that never sent its BYE keeps counting for longer, and that one never recovers on its own. Config: [sip] active_idle_window_secs
|
--no-dialog |
-- | off | 0.3.0 | Disable dialog tracking entirely (message-only mode) |
--tag |
<TAG> |
-- | 0.3.0 | Filter dialogs by tag value |
branchcounts 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
--limitthen 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 keyed by Via branch, dropping new dialogs (instead of evicting old ones) past 20000 tracked -
sipnab -N -I capture.pcap --tag 1928301774 --rotateβ show only dialogs carrying a specific From/To tag, with explicit LRU rotation -
sudo sipnab -d eth0 --tag as7d60e14a --no-rotateβ live-follow dialogs matching a tag while refusing new dialogs once the tracker is full -
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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--max-streams |
<N> |
50000 |
0.3.0 | Maximum number of RTP streams to track simultaneously. At least 1: sipnab refuses 0. Config: [limits] max_streams
|
--max-lost-sequences |
<N> |
1000 |
0.5.97 | Lost 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
|
--quality-interval |
<SECONDS> |
5 |
0.5.160 | Seconds 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
|
--quality-threshold |
<MOS> |
3.0 |
0.3.0 | MOS quality threshold for alerts (1.0-5.0 scale) |
--rtpengine-control |
<ADDR> |
-- | 0.5.124 | Ask 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. Only rtpengine's read-only list and query are reachable from this path β there is no value in the code that means delete or start recording, so sipnab cannot change a relay's behavior through it. Not a poller: it asks at startup, before the capture opens, and again only when a stream turns up that nothing explains β never on a timer. sipnab asks about each relay-side socket at most once for the run, under a per-run ceiling on control transactions that does not grow with the traffic. Refused on -I <file>: a live relay would answer about calls that are up TODAY, which are not the calls in the capture. Feature: native
|
--rtpproxy-control |
<ADDR:PORT> |
-- | 0.5.196 | Read 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
|
--relay-stats |
-- | -- | 0.5.169 | Ask 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
|
--relay-stats-call |
<CALL-ID> |
-- | 0.5.169 | Ask 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
|
--relay-stats-list |
-- | -- | 0.5.169 | List 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
|
--relay-stats-interval |
<SECONDS> |
-- | 0.5.170 | Poll the relay named by --rtpengine-control for its statistics every SECONDS, for as long as the capture runs. Nothing polls by default: naming an interval is the request, because a poll transmits where every other answer comes from bytes sipnab already holds. It runs on its own thread, and the interval is the spend bound -- 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). Same live-source gate as --relay-stats. Range 1--3600. Feature: native
|
--relay-compare |
<CALL-ID> |
-- | 0.5.169 | Compare 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
|
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 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. sipnab sends onlylistandquery, 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β refused, and says so: reading a file, sipnab never transmits, and the relay's answer would describe today's calls rather than the capture's -
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 themrelay_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β refused, and says why: asking a relay transmits, and a file-backed run may not, so it printsnot_permittedrather than talking to an address a capture named -
sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-stats-call [email protected]β 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 [email protected] --rtpengine-control 127.0.0.1:22222β refused on a file run for the same reason the relay-wide form is: asking transmits, so it printsnot_permitted -
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β refused on a file run like the other relay-stats forms: listing asks the relay, and asking transmits, so it printsnot_permittedrather than talking to an address the capture named -
sudo sipnab -N -d eth0 --rtpengine-control 127.0.0.1:22222 --relay-compare [email protected]β after capture, put the relay's own RTP packet count for the call beside sipnab's measured count, each labeled with its tier, with amatch/differverdict 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 [email protected] --rtpengine-control 127.0.0.1:22222β refused on a file run: comparing asks the relay for its side, and asking transmits, so it printsnot_permittedrather than reaching an address the capture named -
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 markedpolledwith 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β refused on a file run like every relay-stats form: polling asks the relay, and asking transmits, so it printsnot_permittedrather than reaching an address the capture named -
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--jsonand 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 isudp: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
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.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--pdd-threshold |
<SECS> |
11.0 |
0.5.96 | Post-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.0 |
0.5.96 | Seconds 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.0 |
0.5.96 | Seconds 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.0 |
0.5.96 | Percentage 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.0 |
0.5.96 | Absolute 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> |
500 |
0.5.96 | Milliseconds 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.3 |
0.5.99 | Share 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
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
-Nrun prints the measurements themselves rather than a color. - So sipnab validates a band set on a non-interactive run and then never consults it.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--jitter-warn-ms |
<MS> |
30.0 |
0.5.97 | Jitter at or above which the column turns yellow. Config: [quality] jitter_warn_ms
|
--jitter-bad-ms |
<MS> |
50.0 |
0.5.97 | Jitter at or above which the column turns red. Config: [quality] jitter_bad_ms
|
--loss-warn-pct |
<PCT> |
1.0 |
0.5.97 | Loss 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
|
--loss-bad-pct |
<PCT> |
5.0 |
0.5.97 | Loss at or above which the column turns red. Config: [quality] loss_bad_pct
|
--mos-warn |
<MOS> |
4.0 |
0.5.97 | MOS below which the column turns yellow. MOS bands run downward, so this must sit at or above --mos-bad. Config: [quality] mos_warn
|
--mos-bad |
<MOS> |
3.0 |
0.5.97 | MOS below which the column turns red. Config: [quality] mos_bad
|
--rtt-warn-ms |
<MS> |
300.0 |
0.5.97 | Round 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
|
--rtt-bad-ms |
<MS> |
800.0 |
0.5.97 | Round 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
|
sipnab refuses a warn boundary that sits above its matching bad boundary. It does not silently reorder the pair.
- That pair leaves an unreachable middle. Nothing would ever render as a warning.
- Whoever wrote it would see green until the value was already bad.
A boundary that is not a finite, non-negative number fails for a worse reason.
- Every comparison against
NaNis false. - So a single one would paint the whole column green.
- It would report a healthy network in the middle of an outage.
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 a Tuesday rather than an incident -
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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--kill-scanner |
-- | off | 0.3.0 | Detect 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). Config: [security] kill_scanner
|
--no-kill-scanner |
-- | off | 0.5.204 | Do 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-ua |
<PATTERN> |
-- | 0.3.0 | Add a custom scanner User-Agent pattern (regex) to --kill-scanner detection. Refused without it: --kill-scanner (or [security] kill_scanner = 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> |
200 |
0.3.0 | SIP response code for the kill response (100-699). Config: [security] kill_response
|
-K, --kill-target
|
<ADDR[:PORT-RANGE]> |
-- | 0.5.7 | Targeted 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) |
--kill-spoof |
<MODE> |
auto |
0.5.12 | Source-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> |
10 |
0.5.96 | Scanner-kill responses per second sipnab may put on the wire. This bounds the one feature that answers an address out of the capture, and the sender of each packet sipnab answers chose the address that answer goes to, so there is no unlimited setting and 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> |
off |
0.5.164 | Bound 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: Landlock's network rules reach TCP bind and connect only, which would miss the HEP UDP listener and the pre-drop raw socket, so this ruleset governs the filesystem alone. Nothing here can end a run β Landlock denies an open, it does not signal β and a kernel without it degrades to a warning unless required says otherwise |
--seccomp |
<MODE> |
off |
0.5.165 | Record 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 β it exists to derive an allowlist from evidence rather than a guess. 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 |
--fraud-detect |
-- | off | 0.3.0 | Enable fraud detection heuristics. Config: [security] fraud_detect
|
--no-fraud-detect |
-- | off | 0.5.204 | Do 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|-> |
off | 0.5.148 | Publish 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; 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,...> |
off | 0.5.147 | Destination 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.96 | Business hours in whole UTC hours, for example 8-18, or 22-6 for an overnight window. This is what makes the off-hours fraud detection reachable: with no window declared there is no outside for a call to fall in. Needs --fraud-detect. Config: [security] business_hours
|
--fraud-short-call |
<SECS> |
3 |
0.5.96 | Measured 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> |
3 |
0.5.96 | Short calls to one destination prefix before --fraud-detect reports wangiri. Config: [security] fraud_wangiri_calls
|
--fraud-sequential-calls |
<N> |
3 |
0.5.96 | Consecutive refused numbers before --fraud-detect reports sequential scanning. Config: [security] fraud_sequential_calls
|
--fraud-volume-multiplier |
<N> |
5 |
0.5.96 | Multiple 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> |
6 |
0.5.96 | Calls 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> |
60 |
0.5.100 | How 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> |
60 |
0.5.100 | How 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> |
10 |
0.5.98 | Probes 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> |
5 |
0.5.98 | Distinct 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> |
5 |
0.5.98 | Rejected 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> |
5 |
0.5.98 | Probes 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> |
5 |
0.5.98 | How 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> |
4 |
0.5.98 | How much more evidence --kill-scanner needs from a source that has completed a registration or a call. A registered endpoint that starts probing is a compromised phone worth reporting, but it is also the peer whose ordinary working traffic looks most like probing, and the peer a false positive costs most. Config: [security] scanner_established_factor
|
--scanner-answer-grace |
<MS> |
500 |
0.5.98 | How 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 |
-- | off | 0.3.0 | Detect 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> |
50 |
0.5.96 | Challenged 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 is a carrier-registrar figure: it never sees the ten-a-second brute force a small PBX gets. 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> |
1 |
0.5.189 | How 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> |
32000 |
0.5.189 | How 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 |
-- | off | 0.3.0 | Detect digest credential leaks in SIP messages |
--allow-action |
<TARGET:SURFACE[,SURFACE]> |
none | 0.5.196 | Enable 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. Config: [actions] tfps
|
--journal-dir |
<DIR> |
/var/lib/sipnab/journal |
0.5.196 | Where 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.196 | Print 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.196 | Back 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.148 | Where 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.141 | Print 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> |
1000 |
0.5.96 | Security findings kept in memory for later retrieval. 0 keeps none. Config: [security] findings_history
|
--alert |
<CHANNEL> |
-- | 0.3.0 | Repeatable 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.0 | Execute this command when an alert fires. Config: [security] alert_exec
|
--alert-json |
-- | off | 0.4.11 | Emit each security alert as a structured JSON line on stderr (in addition to the human [ALERT] line). Config: [security] alert
|
--stir-shaken |
-- | off | 0.3.0 | Report STIR/SHAKEN Identity claims β decodes the PASSporT, does NOT verify the signature |
--run-provenance-file |
<FILE> |
-- | 0.5.131 | Record 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). A report, a vCon container or an exported pcap says what sipnab concluded and nothing in it says which invocation produced it β which capture, which filter, which port range, which retention caps. --portrange alone changes what a run can see, so a report produced under a narrow range is afterwards indistinguishable from one that examined everything. capture is the same instance token the MCP and REST surfaces stamp on their answers, which is what joins a record to an artefact. 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, because argv holds capture paths and a path holds a customer name. 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, because a best-effort line would leave its own absence ambiguous between "not enabled" and "the disk was full". Leave it off and nothing changes |
--tui-audit-file |
<FILE> |
-- | 0.5.131 | Record 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. A keystroke log of the TUI bindings would be mostly navigation, unreadable at review time, and a privacy hazard of its own, so 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 β an operator holding a live capture that exists nowhere else must not lose it because a log partition filled; the lost record leaves a permanent hole 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
|
--alertaccepts a channel or a threshold rule. Channels aresyslog,jsonorexec.
--syslogand--alert-jsonare 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 ans/m/hsuffix.Rule names are
scanner,fraud,digestandreg-flood(alsoreg_flood).
- An unknown rule name exits 2 at startup.
- A rule counts detector findings, not raw packets.
An unrecognized bare word draws a warning naming the valid channels. It used to fail silently. So a documented
--alert syslogenabled nothing at all.
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 = truewould arm -
sipnab -N -I capture.pcap --kill-scanner --no-kill-scanner --reportβ the last of the pair wins, so sipnab arms no response -
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-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 annftrule 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. Readblocks.txtbefore 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, sotfps_statusandtfps_bannedanswer from that installation rather than whateverPATHfinds -
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 letGET /v1/tfps/labelsfetch 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 actionsask TFPS to ban and unban throughPOST /v1/tfps/banand/unban, and back a ban out throughPOST /v1/actions/revert. Every other credential still reads the TFPS routes and gets401on those three -
sudo sipnab -N -d eth0 --mcp --allow-action tfps:mcpβ let the local agent this process serves over stdio calltfps_ban,tfps_unbanandactions_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-actionstakes -
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-showprinted, asking the TFPS installation named outright
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--on-dialog-exec |
<CMD> |
-- | 0.3.0 | Execute command when a dialog state changes |
--on-quality-exec |
<CMD> |
-- | 0.3.0 | Execute command when RTP quality drops below threshold |
--exec-rate-limit |
<N> |
10 |
0.3.0 | Maximum exec invocations per second (rate limit). 0 means no limit |
--exec-queue-depth |
<N> |
100 |
0.5.100 | Hook 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-execsetsSIPNAB_CALL_ID,SIPNAB_FROM,SIPNAB_TO,SIPNAB_STATE,SIPNAB_METHODandSIPNAB_JSON(the whole dialog). -
--on-quality-execsetsSIPNAB_SSRC,SIPNAB_SRC,SIPNAB_MOS,SIPNAB_JITTER,SIPNAB_LOSSandSIPNAB_STREAM_JSON. That last one is the stream object under its own name, neverSIPNAB_JSON. - Rule hooks add
SIPNAB_RULE,SIPNAB_DETAILandSIPNAB_VARIABLE.
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_TOKENandSIPNAB_HEP_AUTHif you set them there. Authentication recommends exactly that. - This is deliberate:
- a hook that must call back into the API needs the credential,
- stripping the environment would break that without making anything safer, since a hook already runs as you.
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_PRIVSat 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,pkexecandpingstart, 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
systemdunit it triggers.
Root runs have always behaved this way. Unprivileged runs
(sipnab --setup-caps) now do too.
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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--metrics |
<ADDR> |
-- | 0.3.0 | Prometheus 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 sipnab --metrics ... && ... fails the way a script expects, 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.0 | HTTP 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.28 | Read 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.203 | Serve 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.203 | PEM 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.0 | REST API endpoint (e.g., 0.0.0.0:8080). Feature: api
|
--api-key |
<KEY> |
-- | 0.3.0 | API 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.0 | Serve 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.0 | PEM 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> |
100 |
0.3.0 | Maximum 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.198 | Additional 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.3 | HMAC 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.3 | Read an API signing key from a file (contents trimmed); it becomes the minting key. Feature: api
|
--api-revoked-file |
<FILE> |
-- | 0.4.3 | Revocation denylist: one revoked token id per line; reloaded on mtime change. Feature: api
|
--api-token-ttl |
<SECS> |
3600 |
0.4.3 | Default TTL (seconds) when minting API tokens with --mint-token. Feature: api
|
--api-max-rows |
<N> |
1000 |
0.5.100 | Rows one list-style REST response returns. The REST counterpart of --mcp-max-rows, settable for the same reason: the right ceiling belongs to the consumer, not to sipnab. A batch consumer piping /v1/dialogs to a file wants every row; a dashboard drawing a table wants far fewer. 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> |
100 |
0.5.100 | REST 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 refusal is 503 rather than 429 because the limiter runs before authentication, so it says nothing about the credential. Config: [limits] api_rate_limit_per_peer Feature: api
|
--api-allow-relay-query |
-- | -- | 0.5.170 | Let REST clients query the relay named by --rtpengine-control through the GET /v1/relay/... routes, 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 this flag those routes answer not_permitted; without a relay, or on a file-backed run, not_configured. Each response is HTTP 200 carrying an outcome, so a refusal is content, not a 4xx. Polling on an interval is not offered over REST at all. Feature: api
|
--api-allow-vcon-fetch |
-- | off | 0.5.221 | Let 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 |
-- | off | 0.5.187 | Accept 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. Feature: api
|
--api-file-root |
<DIR> |
-- | 0.5.177 | Directory 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 .. or an absolute prefix 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> |
16 |
0.5.100 | Metrics scrapes served at once before further ones get 503. The gate stops a burst of slow clients exhausting threads and taking monitoring down, and sixteen suits one Prometheus; an HA pair, a federating parent, a remote_write shard, an alertmanager sidecar and one engineer's curl reach it without anything unusual happening. A refused scrape leaves a hole in the series that reads as a capture that died rather than as a busy endpoint. Config: [limits] metrics_max_conn Feature: metrics
|
-L, --hep-listen
|
<ADDR> |
-- | 0.3.0 | Listen 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 (up to 0.5.192 it got the signaling filter, captured no media and saw every mirrored message a second time); 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. --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. Feature: hep
|
-H, --hep-send
|
<ADDR> |
-- | 0.3.0 | Send captured packets via HEP to a remote collector: SIP as protocol type 1 and RTCP as type 5, so the collector can report media quality and not only call setup. RTP is never forwarded. On -I <file> this forwards the file's contents: every SIP message and RTCP report sipnab reads out of the capture goes to <ADDR> as recorded, 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, failures by kind (connect, tls_handshake, write) and reconnects, at warn when anything failed. 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. Feature: hep
|
--hep-send-transport |
<udp|tcp|tls> |
udp |
0.5.150 | Transport --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. A write that fails drops the connection and the next packet dials again β one reconnect per packet, never a loop, so a collector that is down does not turn forwarding into a spin. Every connect, the first and each reconnect, gives up after 3 seconds and counts as a connect failure, as a refused one does. A collector whose host or firewall drops the connection request without answering otherwise held forwarding, and the shutdown that waits for it, for the operating system's own limit, about 127 seconds on Linux. Three seconds lets the first request and one retransmission go unanswered before sipnab gives up, and the next packet dials again. Refused without --hep-send, which is the side it governs. Feature: hep
|
--hep-listen-transport |
<udp|tcp|tls> |
udp |
0.5.150 | Transport --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.150 | Certificate 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.203 | Certificate 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.150 | Server 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.150 | Private 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> |
1 |
0.5.8 | Capture-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.8 | Homer 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.28 | Read 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> |
plain |
0.5.28 | HEP 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> |
30 |
0.5.100 | Seconds either side of now within which sipnab still honors a --hep-auth-mode hmac token's timestamp. On an agent/collector pair with poor NTP sipnab turns every packet away as out-of-window, and what the operator sees is a collector receiving NOTHING -- a symptom they attribute to routing, a firewall, or a dead agent long before a clock. Widening it is a security trade rather than a convenience: the window is exactly how long a packet an on-path attacker captured stays acceptable, and how far back the receiver's nonce cache must remember. 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> |
30 |
0.5.185 | Seconds 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 |
-- | off | 0.5.185 | At 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
|
-E, --hep-parse
|
-- | off | 0.3.0 | Parse 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 |
-- | off | 0.5.204 | Do 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.0 | Allowed 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. Refused without --hep-listen, the side it governs, exit 2; -E reads no allowlist. Feature: hep
|
--hep-rate-limit |
<N> |
50000 |
0.3.0 | Maximum 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> |
off |
0.5.28 | Maximum 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 |
-- | off | 0.5.28 | Allow 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 |
-- | off | 0.3.0 | Send alerts to syslog. Config: [security] alert
|
--mint-token |
-- | off | 0.4.3 | Mint 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.3 | Token id (jti) for --mint-token, used for revocation. Defaults to a generated id. |
--token-scope |
<full|metrics|read|actions|audio> |
full |
0.5.59 | Scope 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. audio reaches the REST API's live call audio routes (/v1/calls/...) and nothing else, and no other scope reaches them. A cross-surface mint fails at mint time, so sipnab refuses metrics and audio 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: oneGET /v1/dialogs?limit=100000drains 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/statsreturns its counters as one JSON object with anoutcome, transmitting once per request. Off without this flag, when those routes answernot_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 withoutcome: 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 withGET /v1/vcon/fetch/:uuidfrom the store[vcon_fetch]names; without the flag that route answers503 -
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/pcapswith the operator's passwords, and a local client may add one per request in theSipnab-Archive-Passwordheader -
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 sendSipnab-Archive-Password, which without the flag get403because 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.pcapreads two files from that one directory and nothing outside it, ranking what moved. Off without the flag, when the route answers503 -
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 ischmod 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 forwardsHost: sipnab.example.comunchanged: sipnab serves that name, while a page that rebinds any other name to127.0.0.1gets403 -
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 servesHost: capture.example.com:8443and 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 onhttps://even on the same host;api.pemmust name127.0.0.1for 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 loadedtoday.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, aremote_writeshard and an alertmanager sidecar all scraping one sipnab: at sixteen slots a slow scrape turns the next one away with503, 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 withtail -1 -
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 onhttps://even on the same host;mcp.pemmust name127.0.0.1for 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 onlyhomer-ca.pem -
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-allowis 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 withscheme: httpsand the issuing CA intls_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 speakshttps://;metrics.pemmust name127.0.0.1for 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
--hep-send <ADDR> forwards every SIP message and every RTCP report sipnab
reads to the collector at <ADDR>. It forwards them byte for byte as the
capture holds them.
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.
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
--portrangeand 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:
WARN sipnab::app::bootstrap: --hep-send collector.example:9060 forwards every
SIP message and every RTCP report this run reads to that address, and this run
is reading a capture FILE (customer.pcap). The signaling in those captures
leaves this machine ...
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.
- No code path exists that turns a recorded address into an export target.
- 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 filegrants it nothing at all. See Security.
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:
| Number | Sender | What the sender means | sipnab reports |
|---|---|---|---|
22 (IPPROTO_IDP) |
OpenSIPS tracer
|
TLS | TLS |
22 (IPPROTO_IDP) |
Kamailio siptrace
|
TLS, WS or WSS | TLS, or WS or WSS as the top Via says |
50 (IPPROTO_ESP) |
OpenSIPS tracer
|
WS or WSS | WS, or WSS as the top Via says |
| 6 (TCP) | Kamailio siptrace, for a message it sends |
TCP, or WS on a TCP connection | TCP, 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 sources:
- OpenSIPS
modules/tracer/tracer.cand its own receivermodules/proto_hep/proto_hep.c, - Kamailio
src/modules/siptrace/siptrace_hep.c,siptrace.candsiptrace_send.c.
The top Via names the transport of the hop a message travels on.
- RFC 3261 section 18.2.2 has a response travel back over the transport its Via names.
-
RFC 7118 section 5.1 defines
WSandWSSas Via transports.
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.
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-idpair that issues MCP bearer tokens. It serves the REST API too.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--mcp |
-- | off | 0.3.2 | Run sipnab as an MCP server. With the default stdio transport, stdout carries the JSON-RPC wire, so --mcp implies -N/--no-tui and rejects stdout-writing flags (--json, --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-transport |
stdio|http |
stdio |
0.3.2 | MCP 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.2 | HTTP MCP bind address. Non-loopback requires --mcp-token. Feature: mcp-http
|
--mcp-token |
<TOKEN> |
-- | 0.3.2 | Bearer token for HTTP MCP; required for non-loopback binds. Precedence: explicit flag, token file, then $SIPNAB_MCP_TOKEN. Feature: mcp-http
|
--mcp-token-file |
<FILE> |
-- | 0.3.2 | Read bearer token from file (preferred over env in systemd units). Feature: mcp-http
|
--mcp-tls-cert |
<FILE> |
-- | 0.5.203 | Serve 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.203 | PEM 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.3 | HMAC 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.3 | Read an MCP signing key from a file (contents trimmed); it becomes the minting key. Feature: mcp-http
|
--mcp-revoked-file |
<FILE> |
-- | 0.4.3 | MCP revocation denylist (one token id per line; reloaded on mtime change). Feature: mcp-http
|
--mcp-token-ttl |
<SECS> |
3600 |
0.4.3 | Default TTL (seconds) when minting MCP tokens with --mint-token. Feature: mcp-http
|
--mcp-audit-file |
<FILE> |
-- | 0.5.130 | Append 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 already ride the normal log under the mcp_audit target, but that is a console view --quiet suppresses and SIPNAB_LOG filters; this is the durable copy for the question the record exists for β what did an agent look at in this capture β which somebody who did not choose the log level asks later. 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> |
100 |
0.5.84 | Maximum 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 β an unbounded backlog behind the cap is the exhaustion the cap prevents. The default mirrors --api-max-conn and bounds a flooding client without impeding an agent's ordinary parallel calls. Applies to both stdio and HTTP servers, though a network-exposed HTTP server is the case it matters for. Feature: mcp
|
--mcp-tools |
<LIST> |
full |
0.5.130 | Which 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 then stays in the model's context for the whole session. That is a fixed cost paid before the agent asks anything, and it grows with the surface rather than with the question. 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] |
off | 0.5.197 | Send each tool's output schema on tools/list. Off by default: MCP makes output schemas optional, and they are more than half the bytes the tools cost a client (78,920 bytes for every tool without them, 181,691 with them, measured on 0.5.196). 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.93 | One-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> |
1000 |
0.5.93 | Maximum 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> |
4096 |
0.5.97 | Maximum 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> |
1048576 |
0.5.212 | Maximum 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> |
60 |
0.5.141 | Longest 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 carrying nothing. await_condition lets an agent wait for a condition on a live capture instead of re-asking tail_dialogs and paying a model turn per empty reply, and an unbounded wait is a held connection by another name. 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> |
1000 |
0.5.100 | Findings the MCP save_findings tool accepts before refusing further writes. The one WRITE budget on that surface: --mcp-max-rows and --mcp-max-body-bytes bound what an agent may READ, this bounds what it puts into the operator's journal. Past it sipnab refuses the write and says so, and drops nothing to make room -- a finding is a log line the journal already holds, so sipnab keeps no copy a newer one could displace. Raise it for a long agent session on a large capture. Config: [limits] mcp_max_findings
|
--mcp-sweep-max-files |
<N> |
20 |
0.5.207 | Files 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> |
30000 |
0.5.207 | Milliseconds 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> |
4 |
0.5.208 | MCP 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> |
16 |
0.5.208 | Finished 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> |
600 |
0.5.208 | Seconds 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> |
off | 0.5.130 | Requests per hour sipnab may ask the CLIENT's model to narrate. Off unless set, because client support for the sampling primitive is thin and uneven and nothing here may depend on a narration arriving; a client that did not advertise the capability is never asked and gets structured evidence instead. Requests dedupe by finding signature before they spend from this budget, so a rule a scanner trips five hundred times costs one narration. 0 means NONE, not unlimited: elsewhere a zero limit removes a ceiling, but this one spends the operator's money and the client's rate limit, so zero reads restrictively. sipnab forwards named fields only -- control characters stripped, length clamped, under a system prompt stating every value is untrusted observation -- and never raw message text. Feature: mcp
|
--mcp-rate-limit-per-peer |
<N> |
100 |
0.5.87 | Maximum tool calls one peer may make per second (0 = unlimited). The other half of --mcp-max-concurrent: that caps calls in flight, this caps their arrival rate, and without it an agent that stays under the concurrency cap while looping as fast as sipnab answers has no bound at all. 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.2 | Additional 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.141 | Public 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. The operator names it rather than sipnab guessing: behind the TLS-terminating proxy this deployment expects, sipnab sees a cleartext request and no scheme, and anything derived from the socket or the Host header 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.70 | Directory 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.168 | Retain 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 |
-- | off | 0.5.70 | Permit 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 |
-- | off | 0.5.74 | Permit 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 |
-- | off | 0.5.139 | Permit 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 β an agent that could name the destination would turn the MCP surface into a way to send packets to a host of its own choosing. 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
|
--mcp-allow-tls-capture |
-- | off | 0.5.102 | Let 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 |
-- | off | 0.5.221 | Permit 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 |
-- | off | 0.5.81 | Permit 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 |
-- | off | 0.5.84 | Retain 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: call audio is content, not signaling, and holding it is an operator decision rather than a side effect of running a capture. It used to require --mcp, back when the MCP server was the only reader; that would now keep the one finding that needs samples behind a server nobody analyzing a pcap wants to start. 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: the parallel workers used to discard payload regardless, so a run could accept the flag and keep nothing |
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
-k, --tls-key
|
<FILE> |
-- | 0.3.0 | RSA 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.0 | TLS 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: ... No such file or directory. Feature: tls
|
--keylog-fd |
<N> |
-- | 0.5.101 | Read 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 |
-- | off | 0.3.0 | Watch the key log for new entries (live decryption). Feature: tls
|
--tls-lockon-window |
<RECORDS> |
1048576 | 0.5.115 | How 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. Feature: tls
|
--dtls-keylog |
<FILE> |
-- | 0.3.0 | DTLS 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 (MAX_RECORD_LEN, the ciphertext limit TLS sets and DTLS inherits). Feature: tls
|
--srtp-keys |
<FILE> |
-- | 0.3.0 | SRTP master-keys file for media decryption (AES-CM, RFC 3711); also honors SDES a=crypto keys from SDP. Feature: tls
|
--pcap-export-mode |
<MODE> |
raw |
0.3.0 |
raw 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 |
--allow-coredump |
-- | off | 0.3.0 | Allow core dumps (do not call prctl to disable them) |
--uprobe-tls |
-- | off | 0.5.102 | Read 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> |
discovered | 0.5.102 | Probe 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 flavor | 0.5.102 | Write 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> |
all | 0.5.102 | Probe only these flavors (openssl, wolfssl); repeatable. Feature: native. Also accepts --uprobe-flavour, the spelling the flag shipped with through 0.5.104: a released flag is a contract rather than a spelling choice |
--uprobe-list |
-- | off | 0.5.102 | List 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. Feature: native
|
--uprobe-backend |
<NAME> |
tracefs |
0.5.102 | Which 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, deliberately, stop -
sipnab -N -I capture.pcap --mcp --mcp-sampling-budget 20β let sipnab ask the connected client's model to characterize an alert in a sentence, at most twenty times an hour. No API key in the config and no weights in the binary: the client already has a model, and this borrows it -
sipnab -d eth0 -N --mcp --mcp-sampling-budget 0β sampling explicitly refused rather than left unset, which is what to write when a policy forbids sending observations to a model at all. The difference from omitting the flag is that this states the decision -
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 intoeth0, honestly: 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β harmless and pointless together, which is worth knowing: 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. Both opt-ins are separate on purpose -
sipnab -N -I capture.pcap --config sipnab.toml --mcp --mcp-allow-vcon-fetchβ let an agent read a vCon by uuid withfetch_vconfrom 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 thevconbundle loaded, so the agent can fetch a stored call and runanalyze_vconon 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 withjournalctl -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 canexport_audioa 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-audiothe 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 readdiagnosis.amplitudefor 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 wholeINVITEwith 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 withtruncated_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 perawait_conditioncall, 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 onefind_in_capturescall 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 inheritsPR_SET_NO_NEW_PRIVSand so can never acquireCAP_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/runafterwards -
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>/rootor 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-symbolit 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 defaultSSL_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 carryuprobe:<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. Verified live: a REGISTER and its 200 OK came back as127.0.0.1:36160 -> 127.0.0.1:15061and the reverse, each connection carrying its own ephemeral port. 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 thebpfbackend 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-capturethe agent can still calllist_tls_librariesand report what a capture would have seen
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--user |
<USER> |
-- | 0.3.0 | Drop 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 |
-- | off | 0.3.0 | Do not drop privileges after opening capture devices. Config: [privilege] no_priv_drop
|
--priv-drop |
-- | off | 0.5.204 | Drop 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.0 | Chroot to this directory after initialization. Config: [privilege] chroot
|
--setup-caps |
-- | off | 0.4.3 | Grant 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. |
--user does nothing unless the process is root.
- sipnab skips the drop when
geteuid() != 0. It says so only atdebug. - Take the recommended install (
--setup-caps, then run withoutsudo):- clap parses the flag,
- the log line goes where nobody watches,
- the drop never arms.
- That is the intended behavior. An unprivileged process has nothing to drop.
- This note exists because the flag's presence otherwise reads as a guarantee.
The platforms also differ.
- On Linux, root plus
--userdrops to that user. - On macOS, sipnab skips the drop even as root unless you pass
--user. Dropping tonobodystrands 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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--max-capture-sources |
<N> |
65536 |
0.5.197 | Distinct 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> |
10000 |
0.3.0 | Maximum concurrent TCP/TLS reassembly sessions. At least 1: sipnab refuses 0. Config: [limits] max_reassembly
|
--reassembly-ttl |
<SECS> |
30 |
0.5.100 | Seconds sipnab holds an incomplete IP datagram or half-read TCP stream before a sweep drops it. --max-reassembly bounds how MANY entries sipnab holds and says nothing about how long. Thirty seconds describes IP fragments in flight, and the TCP reassembler inherited it: a persistent SIP/TCP or SIP/TLS trunk to a carrier goes quiet for far longer on any ordinary night, and sweeping its half-read stream means the next segment re-initializes mid-message, so the peer that sent a valid message is the one reported broken. Raise it on such a trunk; --max-reassembly caps the extra state either way. Config: [limits] reassembly_ttl_secs
|
--lint-max-per-rule |
<N> |
25 |
0.5.96 | Findings 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> |
discovered | 0.5.130 | Read lint suppressions from this file instead of discovering one. Without it the binary looks for a .sipnablint beside the capture and climbs toward the project root β the nearest ancestor holding a .git β which is exactly what the MCP lint tools already do. Until this shipped the two surfaces disagreed about the same file on disk: the MCP tools honored a .sipnablint checked in beside a capture while the binary silently ignored it, so the CI user was on the side that could not see it. 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 |
-- | off | 0.5.130 | Ignore any .sipnablint, including one named by --lint-suppress-file. The "show me everything, including what we have agreed to live with" switch; it wins over both the explicit file and discovery, so a wrapper script that always passes a suppression file can still be overridden from the command line. Needs --lint
|
--cores |
<N> |
1 |
0.4.14 | CPU cores for offline pcap reconstruction (-I). At least 1: sipnab refuses 0. 1 = single-threaded; >1 shards by host pair for multi-core throughput (dialog+RTP reconstruction, --report/--json). 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> |
2147483648 |
0.5.97 | Bytes 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> |
1073741824 |
0.5.97 | Bytes 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. The documented alternative β gunzip the file and open the plain one β costs the disk the compression was saving. 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> |
65536 |
0.5.99 | Bytes 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β 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.sipnablintsitting besidecalls.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 whosecollector.tomlsets[limits] max_tracked_peers = 100000for 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 longRecord-Routesets, 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-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-authand--vcon-spool-statushas a[vcon_forward]key in the config file. The flag overrides the key. - Needs the
vconCargo feature. - More: Deliver the spool to a store.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--vcon-forward |
<SPOOL_DIR> |
-- | 0.5.205 | Deliver 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> |
generic |
0.5.206 | The 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.205 | Where 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.206 | The 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.205 | File 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>/delivered |
0.5.205 | Where 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>/failed |
0.5.205 | Where 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.205 | When 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 |
-- | off | 0.5.205 | Make 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> |
5 |
0.5.205 | Seconds between passes, 1 to 3600. Config: [vcon_forward] interval
|
--vcon-forward-timeout |
<SECS> |
30 |
0.5.205 | Seconds 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 bundle | 0.5.205 | Trust 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.219 | Trust 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> |
2 |
0.5.206 | Seconds 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> |
300 |
0.5.206 | The 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> |
65536 |
0.5.206 | The 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> |
8192 |
0.5.206 | The 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.219 | Register 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.219 | File 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 bundle | 0.5.219 | Trust 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.219 | The 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.219 | The 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.219 | The 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/json |
0.5.219 | The 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. A conserver answered 422 to application/vcon and 204 to the other two, so the default stays application/json. See What it does with each container. Config: [vcon_forward] content_type
|
--vcon-spool-status |
<SPOOL_DIR> |
-- | 0.5.212 | Print 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 inspoolto a self-hosted conserver'ssipnabingress list once, and exit0when 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 asAuthorization: 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'ssipnabingress list once: the kind adds/vcon/external-ingress?ingress_list=sipnaband sends the key asx-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, exportSIPNAB_VCON_FORWARD_AUTHinstead -
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 theconserverkind sends asx-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.jsonrecord -
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 answers409for 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 insentandheld -
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β registervcon_createdandvcon_sentwith the transparency service athttps://ts.example.com/entriesfor 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 asapplication/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 asapplication/vcon+json; a gzip format container still goes asapplication/vcon+gzip -
sipnab --vcon-spool-status spoolβ print how many containers wait inspool, 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-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-outand--vcon-fetch-overwritehas a[vcon_fetch]key in the config file. The flag overrides the key. - Needs the
vconCargo feature. - More: Fetch a stored vCon.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--vcon-fetch |
<UUID>... |
-- | 0.5.209 | Read 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> |
generic |
0.5.209 | The 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.209 | The 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.209 | File 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 directory | 0.5.209 | Where the fetcher writes each container, as <uuid>.vcon.json, mode 0600. The fetcher creates it, mode 0700, if missing |
--vcon-fetch-overwrite |
-- | off | 0.5.209 | Replace 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> |
30 |
0.5.209 | Seconds 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> |
67108864 |
0.5.209 | The 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> |
65536 |
0.5.209 | The 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 bundle | 0.5.209 | Trust 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.219 | Trust 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.218 | Decrypt 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.219 | Write 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.219 | Write 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 asAuthorization: Bearer <key>, and removes the_metamember 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 asx-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 asAuthorization: Bearer <key>, and keeps thevconmember 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 inuuids.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 inuuids.txtfrom 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 inuuids.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 insigning-ca.pemissued 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 inuuids.txtand 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 40:97:43:61:0C:C7:47:BD:40:82:E2:28:1C:53:A6:54:88:74:EB:E7:A7:2B:E4:A3:C8:CB:1B:CA:C8:97:BC:D6 --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 409743610cc747bd4082e2281c53a6548874ebe7a72be4a3c8cb1bcac897bcd6 --vcon-fetch-out fetchedβ require both: the signing certificate chains tosigning-ca.pemand has this fingerprint
--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.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--vcon-resign |
<FILE> |
-- | 0.5.219 | Re-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.219 | The 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.219 | The PEM certificate chain of --vcon-resign-key, leaf first, carried in the new signature's x5c
|
--vcon-resign-out |
<FILE> |
-- | 0.5.219 | Where 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 insigning-ca.pemissued -
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 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
-Ireads: unsigned, signed, encrypted or gzip.--vcon-key,--vcon-signer-caand--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-statusand--vcon-resign. - The other flags in this section need it. None has a config key.
- Needs the
vconCargo feature. - More: Fetch the audio a vCon names.
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
--vcon-audio-fetch |
<FILE> |
-- | 0.5.221 | For 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.221 | Where 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 |
-- | off | 0.5.221 | Read 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 bundle | 0.5.221 | Trust 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.221 | Trust 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> |
67108864 |
0.5.221 | The 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 itsurlnames -
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
| Flag | Value | Default | Since | Description |
|---|---|---|---|---|
-f, --config
|
<FILE> |
-- | 0.3.0 | Path to configuration file (must exist) |
-F, --no-config
|
-- | off | 0.3.0 | Skip loading any configuration file |
-D, --dump-config
|
-- | off | 0.3.0 | Dump effective configuration and exit |
--completions |
<SHELL> |
-- | 0.5.3 | Print 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
- Output flags (
--json,--json-pretty,--report,--hexdump,--fail2ban) require-N/--no-tuimode, unless--call-reportis also specified. -
--kill-responseaccepts values 100-699 only. - Feature-gated flags (
tls,hep,api,mcp,mcp-http) produce startup errors when the required feature is not compiled in. -
--mcpis incompatible with stdout-writing flags. sipnab refuses to start.- The flags are
--json,--json-pretty,--report,--call-report,--hexdump,--wiresharkand--tshark-filter. - This holds on every transport, not just stdio.
- Combine
--mcpwith--quietto suppress text-mode capture output.
- The flags are
- HTTP MCP transport (
--mcp --mcp-transport http) on a non-loopback--mcp-bindrequires--mcp-token/--mcp-token-file/SIPNAB_MCP_TOKEN. Loopback binds need no token.
-
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'β BPF display filter -
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-printand 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
Scripts can rely on these:
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Runtime 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 |
2 |
Invalid 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 |
3 |
Lint gate tripped β --lint --lint-fail-on <severity> found a conformance finding at or above that severity. Distinct from 1 on purpose: the tool worked, the CAPTURE is non-conformant. Also the vCon forwarder stopping because the store answered 401 or 403 (--vcon-forward) |
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.
1 says something went wrong.
- It cannot say what.
- A consumer reading
--json-dialogson a pipe never sees it at all.
So a run that could not deliver the whole answer also says so in its output:
-
--json-dialogsemits one extra NDJSON line, after the dialogs.- The line is under a top-level
sipnab_runkey that no dialog object has. - A clean run emits nothing extra. So runs with nothing wrong with them look exactly as they do today.
- The line is under a top-level
-
--reportends with anINCOMPLETE RUNblock 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 appears because those ladders are short.
- It is deliberately NOT a failure. A retention policy doing what you configured it to do is not a run that went wrong.
port_gate counts SIP that sipnab did not analyze because of the port it used:
-
sip_outside_portrangefor messages with both ports outside--portrange, -
sip_outside_ws_portsfor 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