Skip to content

CLI Reference

github-actions[bot] edited this page Aug 18, 2026 · 1 revision

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

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

CLI flags always override config file values (see config-reference.md). Boolean flags default to off (false) unless otherwise noted. For task-oriented recipes rather than a flag catalog, start with examples.md.

Common Recipes

A few flag combinations to get productive fast. For the full task-oriented collection — triage, filtering, recording, security, HEP — see the Cookbook. For symptom-driven diagnostics see Troubleshooting. This page is otherwise a flag reference (grouped below).

Debug a failed call

Start by listing every failed call in the pcap, which is where the Call-IDs worth chasing come from.

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

With one of those Call-IDs in hand, print just that dialog's call flow. --no-cli-print is what makes it just that: on its own, --call-report appends the report to the whole capture's per-message dump, so the report you came for arrives after every message in the file.

sipnab -N -I capture.pcap --call-report "abc123@host" --no-cli-print

When the finding belongs in a ticket, write the same report as Markdown to a file instead of reading it on the terminal. Keep --no-cli-print here too, or report.md opens with hundreds of lines of raw SIP before its first heading.

sipnab -N -I capture.pcap --call-report "abc123@host" --markdown --no-cli-print > report.md

Monitor live SIP quality

Watch live traffic for calls that are already degraded — MOS below 3.0, or audio flowing in only one direction.

sudo sipnab -N -d eth0 --filter "rtp.mos < 3.0 OR one_way == true"

Feed the same set of problem calls into a monitoring pipeline as NDJSON while keeping a copy on disk.

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

Hand each quality drop to an external alerting script rather than reading it yourself. --exec-rate-limit bounds the invocations per second, so one bad trunk cannot fork a process per stream.

sudo sipnab -d eth0 --on-quality-exec "/usr/local/bin/pagerduty-alert.sh" \
  --quality-threshold 3.0 --exec-rate-limit 5

Measure post-dial delay across calls

Find the calls whose setup took longer than three seconds, as NDJSON for whatever consumes it next.

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

The --slow-setup alias expands to that same threshold, so a quick check needs no filter expression at all.

sipnab -N -I capture.pcap --slow-setup --report

Security monitoring

Detect SIP scanning and append it in fail2ban's format to the log its jail reads. --kill-scanner is what detects — --fail2ban only chooses the format, so leaving it out leaves the log empty.

Every example below passes -N, and that is not a style choice. Only the headless single-capture path builds the detectors: the interactive TUI (the default when you leave -N off) and the --cores N parallel reader both run without them. Ask for detection on either and sipnab warns at startup that it accepts the flag and ignores it, because an empty finding list otherwise reads as an all-clear.

sudo sipnab -N -d eth0 --kill-scanner --fail2ban >> /var/log/sipnab/scanners.log

Before that log reaches a jail, run the same detectors over a capture of an ordinary hour and read the list of addresses they would ban. On a carrier trunk it routinely names your own SBCs, because the enumeration signature and a busy hunt group look alike.

sipnab -N -I capture.pcap --kill-scanner --fail2ban \
  | grep -oE 'src=[^ ]+' | sort | uniq -c | sort -rn

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

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

Run the whole sweep at once — scanners, fraud heuristics, and registration floods — with alerts going to both syslog and structured JSON.

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

Export for Wireshark analysis

Hand the capture to Wireshark with a display filter already applied.

sipnab -I capture.pcap --wireshark

Or print a tshark-compatible filter string, when the next step is a shell pipeline rather than the Wireshark GUI.

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

Export call audio as WAV

Audio export is a TUI workflow — see Keybindings.

Pipe through jq for custom analysis

Count failures by response code, so the dominant one is obvious.

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

List every distinct User-Agent the capture saw, to find the odd endpoint out.

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

Bound, split, and multi-interface captures

Stop after a fixed packet count and summarize the capture.

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

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

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

Keep only the newest four of those files, so a long capture fits a fixed amount of disk. --split-keep deletes the older files as rotation creates new ones, and it deletes only the files this run wrote.

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

--split-keep deletes capture files. sipnab deletes nothing unless you pass the flag, and nothing at --split-keep 0, because 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 an earlier run, another tool, or you left beside them stays where it is, 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 and counts them in the closing summary.

Capture across every interface at once, timestamping each message relative to the one before it. On Linux the any pseudo-device is what makes this every interface — --multi-device is for naming a specific list, as below.

sipnab -d any --delta-time

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

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

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


Capture

Flag Value Default Description
-d, --device <IFACE> platform default 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
-I, --input <FILE|DIR|GLOB> -- Read packets from a capture file, a directory 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
--recursive -- off Descend into subdirectories when -I names a directory
--input-name <GLOB> -- Read only files whose name matches this pattern when -I names a directory. Applies at every depth under --recursive
-O, --output <FILE> -- Write captured packets to a pcap file
-B, --buffer <MIB> 64 Kernel capture buffer size in MiB (per device). See Tuning capture
--buffer-budget <MIB> 64 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
--snaplen <BYTES> 65535 Snapshot length for packet capture (bytes)
--capture-profile signaling|full -- Picks a --snaplen for you. signaling uses 1500, keeping every SIP header whole while dropping the bulk of an RTP stream; full uses 65535, which is what sipnab has always done. A large snaplen costs on EVERY packet — the kernel copy and the ring occupancy — and that is what makes a busy server drop, so the saving is real. It is a named profile rather than a smaller default because truncation is not free: it breaks --retain-audio, WAV export and Opus decode, which need RTP payload and not just headers, and it degrades -O re-emit to truncated frames. 1500 rather than a tighter 200-400: one INVITE with a full Record-Route set, a long Contact, ISUP encapsulation or a fat SDP offer passes 400 bytes routinely, and a snaplen that cuts a header makes the message stop parsing — reporting the peer that sent a valid message as broken. An explicit --snaplen overrides it. See sipnab_capture_snapped_frames_total for how much of a capture arrived truncated
-S, --limitlen <BYTES> -- Parse only the first N bytes of each packet (sipgrep -S). Caps what the SIP parser and matchers inspect, independent of --snaplen (capture length) and --payload-limit (display truncation)
--no-reassembly -- off Disable IP-fragment and TCP-segment reassembly; sipnab parses every packet standalone (inverse of sipgrep -a). Useful for pure single-packet UDP scanning
-x, --quiet-bad-parse -- off Suppress the per-packet "SIP parse error" diagnostic emitted when a SIP-looking packet fails to parse (sipgrep -x). sipnab drops the packet either way; this only silences the notice on a noisy link
--portrange <RANGE> 5060-5061 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
--ws-portrange <RANGE> 80, 443, 8080, 8443 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. Config: [capture] ws_ports
--multi-device -- off 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 Disable RTP capture and analysis
-p, --no-promisc -- off Do not put the interface into promiscuous mode (sipgrep -p). Promisc is on by default for a named device; the any pseudo-device is never promiscuous
--bpf-file <FILE> -- Read BPF filter from a file
--capture-tunnels [<PORTS>] off 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> -- Stop after receiving N packets (counts every packet received, including any a HEP listener later drops by allowlist, rate limit, or auth)
--duration <DURATION> -- Stop after duration (e.g., 30s, 5m, 1h)
--autostop <CONDITION> -- 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> -- Split output files (e.g., filesize:50 for 50 MiB chunks)
--split-keep <N> -- 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 Replay packets from a pcap file at original timing
--pcapng -- off Use pcapng format for output files. pcapng Metadata covers the metadata sipnab writes into pcapng output
<BPF_FILTER>... positional -- BPF display filter expression (trailing positional args)

The auto-generated filter looks through VLAN, QinQ, PPPoE and MPLS. On a live capture with no filter of your own, sipnab installs one built from --portrange. It is not a bare portrange 5060-5061: that one matches the outer headers only, so on a tagged trunk, a PPPoE access link or an MPLS core it matches nothing, and the kernel discards the frames where no sipnab counter, metric or report can see them. You get "No SIP traffic found" on a link carrying calls.

The generated filter adds an encapsulated arm instead, covering one VLAN tag (802.1Q, 802.1ad or 0x9100), QinQ, PPPoE Session, VLAN over PPPoE, and one or two MPLS labels, for IPv4 and IPv6, UDP and TCP. The arm still demands a signaling port, so it matches more of the same traffic, not a new class of it: VLAN-tagged RTP reaches sipnab no more often than untagged RTP did.

It covers cooked captures too, so omitting -d costs you nothing. The arm asks "does this frame carry an encapsulation?" through libpcap's ether proto, which resolves to the right byte offset for whatever link type the filter compiles against — offset 12 on Ethernet, 14 on Linux cooked v1, 0 on Linux cooked v2, and a constant false on raw IP and the two loopback link types, which carry no protocol field at all. Measured on a capture of each type with tcpdump -d.

Asking the same question with a fixed ether[12:2] is the trap this avoids. That offset holds the EtherType on Ethernet and part of the link-layer address on a cooked capture, so an arm written that way compiles, runs and matches nothing there: 1 of 11 encapsulated SIP frames on cooked v1 and cooked v2, against 11 of 11 on Ethernet. Cooked is what Linux gives you when you name no interface, so that shape would have left the default invocation blind.

Two limits worth knowing. On the encapsulated arm an IPv4 header carrying options stays unmatched. A BPF byte offset has to be a constant, so the arm cannot multiply the IHL nibble into the port offset the way libpcap's own portrange does. The untagged portrange handles those, so this costs you only IPv4-options traffic that is also encapsulated.

And one filter string serving three link types has to carry all three sets of inner offsets, because BPF offers no way to ask which link type it compiled against. Seven offsets get probed on every link type, four of which belong to a different link header. Those four can fire only on a frame that already carries one of the six encapsulating protocols, and only if its bytes at the wrong offset spell a complete IPv4-or-IPv6 header with a signaling port — so the worst case is a stray tagged packet reaching userspace, where the parser rejects it. Ordinary traffic never reaches those probes, because the outer ether proto test is exact.

UDP tunnels are opt-in. GTP-U, VXLAN and GENEVE are not covered by default and sipnab says so at startup. BPF cannot parse a variable-length GTP-U extension-header chain to reach the inner port, so the only way to cover them is to capture everything on the port — see --capture-tunnels.

A filter you supply is never rewritten. It goes to pcap_compile exactly as typed. If it looks encapsulation-blind, sipnab says so once and 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 any pseudo-device every 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 is deliberate and matches sngrep: a SIP proxy often talks to itself over loopback, so capturing only eth0 silently misses it. Pass -d any to say so explicitly. Promiscuous mode does not apply to any, so --no-promisc changes nothing there.

On macOS you get a single interface. If SIP is not on the one libpcap picked, you see nothing and the capture looks merely quiet — name -d explicitly.

Reading a set of files: order comes from the packets, not the names. tcpdump -C 100 -W 10 writes a ring buffer — tg.pcap0 through tg.pcap9 — and then wraps, overwriting the oldest file in place. A real set measured for this feature ran tg.pcap7, tg.pcap8, tg.pcap9, tg.pcap0tg.pcap6 in time order: the numeric suffix records where tcpdump was in its cycle, not when the packets arrived.

So sipnab sorts by each file's first packet timestamp. Neither lexicographic nor natural-numeric filename order reconstructs that capture, and replaying it out of order corrupts every timing derivation — post-dial delay, setup time, retransmission detection, and the RFC 3261 Timer B/C/H bounds all assume timestamps only move forward.

sipnab recognizes a capture by opening it, not by its extension — tg.pcap0 has the extension pcap0, and plenty of captures have none at all. It decompresses gzip members transparently, so a directory holding both .pcap and .pcap.gz needs nothing special.

A file you name directly with -I that sipnab cannot read is an error. One it discovers by expanding a directory or glob it skips with a warning, because directories hold other things.

Why it matters beyond tidiness: reading a split capture as a set is the only way to see a call whose INVITE lands in one file and whose BYE lands in the next. Analyzed one file at a time, that call appears as one that never ends plus a stray BYE, and neither half is the truth. On the 10-file, 921 MB set above, 2271 of 20512 calls — 11% — spanned a boundary.

-I and -d are alternatives, not companions. sipnab accepts both, and the FILE wins: sipnab reads it, never opens the interface, and the output looks like a normal run. sipnab warns on stderr when you do this. To switch a file command to live capture, remove -I rather than adding -d beside it.

--portrange decides how much of the file you analyze. The default, 5060-5061, is narrow. SIP on other ports is ordinary: carriers and SBCs use 5070, 5080 and others routinely, and 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, and every ratio you compute from one, describes the range and not the capture. sipnab counts what it skipped and says so on stderr and at the end of the run, naming the busiest ports so there is something to widen to:

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-65535 analyses everything the capture holds. Reach for it first on an unfamiliar capture, then narrow once you know what is in there.

Live capture is different, and worse. With no explicit BPF filter sipnab compiles the range into the filter, so the kernel drops the traffic before sipnab sees it — nothing downstream, this counter included, can report what went missing. Set the range correctly before the capture, because no rerun recovers it.

NOT DECODED is the other line to read before the totals. --portrange is about SIP sipnab chose not to analyze; this 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) and reaches no message, dialog or stream, so on its own the summary reports the same thing whether 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, because that number is what you act on: unsupported link type 0 says the file is DLT_NULL and editcap -T ether in.pcap out.pcap converts 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, because it has no basis for one. The same breakdown appears as a NOT DECODED (capture-wide) section in --report, and as sipnab_capture_undecodable_frames_total{reason} plus sipnab_capture_undecoded_fraction on /metrics.

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

Examples

  • 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 (sipgrep -x)
  • sudo sipnab -N -d eth0,eth1 --multi-device --output capture.pcap --autostop filesize:100 — capture on two named interfaces at once, headlessly, stopping once the output file reaches 100 MiB. --multi-device needs the list; without one it is a no-op
  • sipnab -N --input capture.pcap --replay --no-rtp — replay a pcap at its original timing with RTP capture and analysis disabled
  • sudo sipnab -N -d eth0 --capture-profile signaling --output signaling.pcap — record signaling on a busy link: 1500 bytes keeps every SIP header whole while dropping the bulk of each RTP packet, which is where the ring pressure comes from. Check sipnab_capture_snapped_frames_total afterwards to see how many frames arrived short
  • sudo sipnab -N -d eth0 --capture-profile signaling --snaplen 4096 — the explicit number wins over the profile, for a trunk carrying INVITEs too large even for one MTU (deep Record-Route sets, ISUP encapsulation). Use --capture-profile full instead when you need --retain-audio, WAV export or a faithful -O re-emit, all of which need RTP payload
  • sipnab -N --input /var/captures/ --json-dialogs --no-cli-print — read every capture in a directory as one timeline, so a call split across the ring buffer resolves to one dialog instead of two fragments
  • sipnab -N --input /var/captures/ --recursive --input-name '*.pcap.gz' --json-dialogs --no-cli-print — descend into per-day subdirectories and read only the compressed archives
  • sipnab -N --input 'captures/tg.pcap[0-4]' --report — analyze the first five members of a ring buffer with a glob sipnab expands itself, no shell needed
  • sipnab -N --input 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 sipgrep-style: 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 (sipgrep -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

Mode

Flag Value Default Description
-N, --no-tui -- off Non-interactive mode (no TUI). Required for batch/output flags
-c, --calls-only -- off Show only SIP dialogs (calls), not standalone messages
-t, --telephone-event -- off Decode telephone-event (DTMF) RTP payloads and log each event at info, digit value masked as x
--dtmf-cleartext -- off Log the DTMF digit VALUES instead of the mask, at debug. Publishes PINs and card numbers
-q, --quiet -- off Suppress informational output; only show results

Examples

  • sipnab --no-tui -I capture.pcap --calls-only — analyze a pcap headlessly, showing only complete SIP dialogs (calls), not standalone messages
  • 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 complete dialogs, and log how many DTMF events each one carried
  • sipnab --no-tui -I lab.pcap --telephone-event --dtmf-cleartext — read a capture you own and disclose the digit values; also set SIPNAB_LOG=debug, or the run prints nothing but the mask
  • sudo sipnab --no-tui -d eth0 --telephone-event --dtmf-cleartext 2>dtmf.log — capture live and steer the cleartext digits into a file whose permissions you control instead of a shared terminal or journald; again needs SIPNAB_LOG=debug

Read this before using --dtmf-cleartext. DTMF digits keyed after answer are PINs, calling-card numbers, account numbers and credit-card numbers with their CVVs, and 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 — that an event arrived, its duration, its SSRC, its timestamp — with the digit value replaced by x:

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, and every aggregator downstream of journald. Turning it on takes two deliberate acts, because sipnab writes the cleartext line at debug while the masked line stays at info: pass the flag and raise the level. Either one alone shows you nothing but the mask. Both, in one copyable line:

SIPNAB_LOG=debug sipnab --no-tui -I lab.pcap --telephone-event --dtmf-cleartext 2>dtmf.log

Where 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 -t to a TUI session shows you nothing, because TUI mode floors the log level at error to keep the alternate screen intact. Adding --quiet also hides them, because it floors the level at warn. Use -N without --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.

Matching

Flag Value Default Description
-e, --match <PATTERN> -- SIP payload match-expression (the sngrep/sipgrep 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). Honors -i/-v/-w/--single-line. Independent of the trailing <BPF_FILTER> positional
-i, --ignore-case -- off Case-insensitive matching for header filters and patterns
-v, --invert -- off Invert the match: show messages that do NOT match
-w, --word -- off Match whole words only
--single-line -- off Treat multi-line SIP headers as a single line for matching
--from <PATTERN> -- Filter by SIP From header (regex pattern)
--to <PATTERN> -- Filter by SIP To header (regex pattern)
--contact <PATTERN> -- Filter by SIP Contact header (regex pattern)
--ua <PATTERN> -- Filter by User-Agent header (regex pattern)
--filter <EXPR> -- Filter DSL expression OR a diagnostic alias name (codec-asym, late-media, etc.) — see filter-dsl.md

Examples

  • sipnab -N -I capture.pcap --match "alice@example.com" --ignore-case — show every dialog that mentions alice@example.com, case-insensitively (dialog-following payload match)
  • sipnab -N -I capture.pcap --match "486 Busy Here" --word --single-line — whole-word match for 486 rejections, folding multi-line headers into one line before matching
  • 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, matching even when headers span folded lines
  • sipnab -N -I capture.pcap --match "OPTIONS" --word --invert — suppress keep-alive noise: show messages that do not contain the whole word OPTIONS

Name resolution

Flag Value Default Description
--resolve -- off 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
--reverse-dns -- off Also use reverse DNS (PTR) lookups. Implies --resolve. Emits DNS queries for captured IPs
--dns-cache-entries <N> 4096 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> -- Preload IP → name mappings from an /etc/hosts-format file. Repeatable

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

Examples

  • 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

pcapng metadata

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

Examples

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

Diagnostic aliases

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

Flag Value Default Description
--problems -- off Show calls matching any diagnostic signal: failed state, one-way audio, RTP loss > 2%, jitter > 50 ms, NAT mismatch, more than 3 retransmits, PDD > 32 s, codec/ptime/payload/duration asymmetry, or late media — 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 Show calls with post-dial delay > 3 seconds
--short-calls -- off Show completed calls shorter than 5 seconds
--one-way -- off Show calls with potential one-way audio issues
--nat-issues -- off Show calls whose RTP arrived from an address no SDP advertised (NAT-rewritten media source)

Examples

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

Output

Flag Value Default Description
--json -- off Output as NDJSON (one JSON object per line, schema in output-formats.md). Requires -N
--json-pretty -- off Output each message as pretty-printed multi-line JSON (use --json for line-oriented NDJSON). Requires -N
--json-dialogs -- off 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 read INVITE transactions only and are null on a REGISTER/OPTIONS/SUBSCRIBE dialog, where signaling_diagnosis.final_failure.code carries it instead.
--plugin <PATH> -- 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 Generate summary report after capture completes. Requires -N
--call-report <CALL-ID> -- Generate a detailed report for a specific Call-ID. Implies non-interactive
--markdown -- off Format report output as Markdown
--hexdump -- off Include hex dump of SIP payloads. Requires -N
--delta-time -- off Show delta time between consecutive messages
-A, --after <N> -- Show N messages after each match (like grep -A)
--show-empty (--full) -- off Show the full header block of bodyless messages (responses, OPTIONS, REGISTER, ACK, BYE); by default they show only the summary line
--proto-number -- off Annotate the transport tag with the IANA IP protocol number, e.g. UDP(17) / TCP(6) (sipgrep -N). Long-only because -N is --no-tui here; TLS/WS report their TCP carrier's number (6)
--line-buffer -- off Flush output after each line (useful for piping)
--color <WHEN> auto Color output mode: auto, always, never
--from-to-mode <MODE> default 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
--payload-limit <BYTES> -- Maximum payload bytes to display
-T, --text-dump -- off Dump raw SIP message text (like sipgrep -T)
--no-cli-print -- off Suppress per-message CLI output (useful with --report / --call-report so only the post-capture summary reaches stdout)
--wireshark -- off Launch Wireshark with a display filter for the current capture
--tshark-filter <EXPR> -- Generate a tshark-compatible display filter string
--fail2ban -- off 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"
--group-by <FIELD> -- Group output by field (e.g., call-id, from, method)
--max-groups <N> 100000 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 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 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

Examples

  • 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 --lint --lint-fail-on error --no-cli-print — the CI gate. Exits 3 when any finding is at or above error, so a pipeline stops on a non-conformant capture. Exit 3 is not 1 or 2, so a failing gate is distinguishable from a failing tool and from a bad invocation
  • sipnab -N -I calls.pcap --lint --lint-fail-on warning --no-cli-print — a stricter gate: stop on warnings as well as errors, for a pipeline that treats interop degradation as a build failure rather than a note
  • sipnab -N -I capture.pcap --json-dialogs --no-cli-print --plugin ./short-calls.wasm — run a custom detection over every dialog and emit its findings beside sipnab's own
  • sudo sipnab -d eth0 -N --json-dialogs --no-cli-print --plugin ./site-rules.wasm --plugin ./fraud.wasm — stack two site-specific detections over live traffic; each plugin is sandboxed and a failure in one never stops the capture
  • sipnab -N -I capture.pcap --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 -d eth0 --from-to-mode host-port --wireshark — watch the live TUI with host:port From/To columns and hand the capture to Wireshark with a matching display filter
  • 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 --tshark-filter 'sip.Method == "INVITE"' — print a tshark-compatible display filter for the INVITE traffic in a capture. sipnab hands the expression to tshark's -Y verbatim, so it takes WIRESHARK display-filter syntax rather than sipnab's --filter DSL: sip.Method, not method. Quote the whole expression in single quotes so the inner double quotes survive the shell

Dialog

Flag Value Default Description
-l, --limit <N> 100000 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
-R, --rotate -- on Evict the oldest dialog at --limit capacity (LRU). On by default; kept for back-compat/explicitness
--no-rotate -- off Disable rotation: drop new dialogs at capacity instead of evicting the oldest (inverts the safe default)
--dialog-track <METHOD> call-id Group messages by call-id (one unit per dialog) or branch (one per SIP transaction)
--leg-correlation-window <MS> 2000 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 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 Disable dialog tracking entirely (message-only mode)
--tag <TAG> -- Filter dialogs by tag value

branch counts transactions, not calls. RFC 3261 gives the ACK to a 2xx a new branch (§17.1.1.3) and 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 — load generators, proxies under test — and note that --limit then counts transactions too.

Examples

  • sipnab -N -I loadtest.pcapng --dialog-track branch --report — per-transaction view of a load-generator capture that reuses one Call-ID
  • sipnab -N -I loadtest.pcapng --dialog-track call-id --report — same capture as dialogs (the default), for a per-call view
  • sudo sipnab -d eth0 --limit 5000 --rotate — monitor a busy proxy with a tight 5000-dialog memory bound, explicitly evicting the oldest dialog at capacity
  • sipnab -N -I capture.pcap --limit 20000 --no-rotate — analyze a capture 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

RTP

Flag Value Default Description
--max-streams <N> 50000 Maximum number of RTP streams to track simultaneously
--max-lost-sequences <N> 1000 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-threshold <MOS> 3.0 MOS quality threshold for alerts (1.0-5.0 scale)

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

Diagnosis thresholds

The numbers the signaling and media checks compare against. Each decides whether a call that is working gets reported as broken, so the defaults are standards figures for the general case and your own network beats them. Every flag has a config key under [diagnosis], and the flag wins.

Flag Value Default Description
--pdd-threshold <SECS> 11.0 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. Config: [diagnosis] post_dial_delay_secs
--ack-timeout <SECS> 32.0 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. Config: [diagnosis] ack_timeout_secs
--no-final-response-timeout <SECS> 180.0 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. Config: [diagnosis] no_final_response_secs
--duration-asymmetry-pct <PCT> 5.0 Percentage difference between the two legs' durations that counts as asymmetric. Config: [diagnosis] duration_asymmetry_pct
--duration-asymmetry-secs <SECS> 2.0 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. Config: [diagnosis] duration_asymmetry_secs
--late-media-ms <MS> 500 Milliseconds after the 200 OK that media may start before it gets reported as late. Config: [diagnosis] late_media_ms
--cn-suppression-ratio <RATIO> 0.3 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

Quality color bands

Where the quality color column turns yellow, and where it turns red. This is a different question from the diagnosis thresholds above: those decide whether a working call counts as broken, while 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, and the right values belong to the network you are watching — 30 ms of jitter is already a fault on a LAN PBX, and 1 percent loss is unremarkable on an international one. A column tuned for neither is wrong in both directions.

These bands paint the TUI. A -N run prints the measurements themselves rather than a color, so sipnab validates a band set on a non-interactive run and then never consults it.

Flag Value Default Description
--jitter-warn-ms <MS> 30.0 Jitter at or above which the column turns yellow. Config: [quality] jitter_warn_ms
--jitter-bad-ms <MS> 50.0 Jitter at or above which the column turns red. Config: [quality] jitter_bad_ms
--loss-warn-pct <PCT> 1.0 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 Loss at or above which the column turns red. Config: [quality] loss_bad_pct
--mos-warn <MOS> 4.0 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 MOS below which the column turns red. Config: [quality] mos_bad
--rtt-warn-ms <MS> 300.0 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 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, rather than silently reordering the pair, because that pair leaves an unreachable middle: nothing would ever render as a warning, and 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 NaN is false, so a single one would paint the whole column green and 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

Security

Flag Value Default Description
--kill-scanner -- off Detect SIP scanning (known UA signatures + behavioral rate/enumeration), alert on it, and send the kill response back to the scanner (sipgrep -J/-j)
--kill-ua <PATTERN> -- Add a custom scanner User-Agent pattern (regex) to --kill-scanner detection
--kill-response <CODE> 200 SIP response code for the kill response (100-699)
-K, --kill-target <ADDR[:PORT-RANGE]> -- Targeted kill (sipgrep -K): 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 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 Scanner-kill responses per second sipnab may put on the wire. This bounds the one feature that transmits, 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
--fraud-detect -- off Enable fraud detection heuristics
--business-hours <START-END> -- 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 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 Short calls to one destination prefix before --fraud-detect reports wangiri. Config: [security] fraud_wangiri_calls
--fraud-sequential-calls <N> 3 Consecutive refused numbers before --fraud-detect reports sequential scanning. Config: [security] fraud_sequential_calls
--fraud-volume-multiplier <N> 5 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 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 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 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 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 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 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 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 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 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 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 Detect registration flood attacks
--reg-flood-threshold <N> 50 REGISTER requests per second from one source before --reg-flood reports a flood. The default is a carrier-registrar figure: it never sees the ten-a-second brute force a small PBX gets, and it fires all through a re-REGISTER storm on a registrar that just restarted. Config: [security] reg_flood_threshold
--digest-leak -- off Detect digest credential leaks in SIP messages
--findings-history <N> 1000 Security findings kept in memory for later retrieval. 0 keeps none. Config: [security] findings_history
--alert <CHANNEL> -- Alert channels (repeatable): syslog, json, exec
--alert-exec <CMD> -- Execute this command when an alert fires
--alert-json -- off Emit each security alert as a structured JSON line on stderr (in addition to the human [ALERT] line)
--stir-shaken -- off Report STIR/SHAKEN Identity claims — decodes the PASSporT, does NOT verify the signature

--alert takes a channel name, not a rule. syslog, json or exec. --syslog and --alert-json are the equivalent boolean forms; naming the channel here does the same thing. A value containing : is instead parsed as an alert rule (<name>:<threshold>/<window>[:<cooldown>], window needs an s/m/h suffix). An unrecognised bare word draws a warning naming the valid channels. It used to fail silently, so a documented --alert syslog enabled nothing at all.

Examples

  • 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
  • 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 -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
  • 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

Event execution

Flag Value Default Description
--on-dialog-exec <CMD> -- Execute command when a dialog state changes
--on-quality-exec <CMD> -- Execute command when RTP quality drops below threshold
--exec-rate-limit <N> 10 Maximum exec invocations per second
--exec-queue-depth <N> 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

Hooks cannot gain privileges. sipnab sets PR_SET_NO_NEW_PRIVS at startup on Linux, on every run and whether or not it is root, and every command it spawns inherits that flag. A hook may run anything you can already run. What it cannot do is get more than you have through a setuid or setgid helper: sudo, pkexec and ping start, then fail for want of the privilege they normally acquire. A hook that needs to act privileged should ask something that already is — a socket to a daemon, a systemd unit it triggers — rather than trying to become privileged itself. 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

Network listeners

Flag Value Default Description
--metrics <ADDR> -- 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> -- 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> -- 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
--api <ADDR> -- REST API endpoint (e.g., 0.0.0.0:8080). Feature: api
--api-key <KEY> -- API key for REST API authentication. Also reads $SIPNAB_API_KEY Feature: api
--api-tls-cert <FILE> -- Not yet implemented — nothing wires up built-in API TLS, and sipnab exits when you pass this. Terminate TLS at a reverse proxy instead. Feature: api
--api-tls-key <FILE> -- Not yet implemented — see --api-tls-cert; terminate TLS at a reverse proxy. Feature: api
--api-max-conn <N> 100 Maximum concurrent API connections Feature: api
--api-signing-key <KEY> -- 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. See auth.md. Feature: api
--api-signing-key-file <FILE> -- Read an API signing key from a file (contents trimmed); it becomes the minting key. Feature: api
--api-revoked-file <FILE> -- Revocation denylist: one revoked token id per line; reloaded on mtime change. Feature: api
--api-token-ttl <SECS> 3600 Default TTL (seconds) when minting API tokens with --mint-token. Feature: api
--api-max-rows <N> 1000 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 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
--metrics-max-conn <N> 16 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> -- Listen for HEP (Homer Encapsulation Protocol) packets. Feature: hep
-H, --hep-send <ADDR> -- 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. Feature: hep
--hep-id <ID> 1 Capture-agent id (HEP 0x000c chunk) stamped on packets sent via --hep-send. Feature: hep
--hep-auth <KEY> -- 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. 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> -- Read the HEP shared secret from a file (contents trimmed), keeping it out of the process list. Takes precedence over --hep-auth. Feature: hep
--hep-auth-mode <plain|hmac> plain 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. Feature: hep
--hep-hmac-window <SECS> 30 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. Config: [security] hep_hmac_window_secs Feature: hep
-E, --hep-parse -- off Parse incoming HEP packets (enable HEP decoding). Feature: hep
--hep-allow <ADDR> -- Allowed source addresses for HEP input (repeatable). Takes CIDR (10.0.0.0/8, 2001:db8::/32) or a bare address (10.0.0.40), which means that host alone — /32 for IPv4, /128 for IPv6. A missing prefix always narrows, never widens: 10.0.0.0 is one host, not 10.0.0.0/8. sipnab refuses a non-loopback --hep-listen bind unless you pass either this or --hep-auth/--hep-auth-file. Feature: hep
--hep-rate-limit <N> 50000 Maximum HEP packets per second (global ceiling across all senders); 0 disables the global ceiling, consistent with off on the per-peer knob Feature: hep
--hep-rate-limit-per-peer <N|auto|off> off 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. Feature: hep
--hep-allow-kill -- off Allow scanner-kill to send active responses for packets received via HEP. 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. Only enable with authenticated, trusted HEP input. Feature: hep
--syslog -- off Send alerts to syslog
--mint-token -- off 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> -- Token id (jti) for --mint-token, used for revocation. Defaults to a generated id.
--token-scope <full|metrics|read> full 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 read-only tools and refuses the five that write. A cross-surface mint fails at mint time, so metrics with MCP and read with the REST API are both refused rather than issued and then rejected.

Examples

  • 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 (terminate TLS at a reverse proxy)
  • sudo sipnab -d eth0 --api 0.0.0.0:8080 --api-signing-key-file /etc/sipnab/signing.key --api-token-ttl 3600 --api-max-conn 100 --metrics 127.0.0.1:9090 --metrics-auth bob:hunter2 — public-facing API tuned to 100 connections and 1h token TTL, with its own auth'd metrics endpoint
  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --api-max-rows 100000 --api-rate-limit-per-peer 0 — a batch consumer's API: one GET /v1/dialogs?limit=100000 drains the whole store in a single page, and the per-peer cap is off so a scripted pager is not throttled against itself
  • sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --api-max-rows 200 --api-rate-limit-per-peer 600 — the opposite, for a dashboard: short pages a browser can render, and six hundred requests a second per address, so a dozen browsers refreshing twice a second behind one office NAT share an allowance that fits them
  • sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --metrics-max-conn 64 — an HA Prometheus pair, a federating parent, a remote_write shard and an alertmanager sidecar all scraping one sipnab: at sixteen slots a slow scrape turns the next one away with 503, and the hole in the series reads as a capture that died
  • sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --metrics-max-conn 2 — a single scraper on a small box, where two slots covers every client this endpoint has, and a third connection is something to turn away
  • sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac --hep-hmac-window 120 — a collector whose agents run on hardware with a drifting clock: two minutes of tolerance keeps them heard while someone repairs the NTP problem
  • sipnab -N -L 127.0.0.1:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac --hep-hmac-window 5 — the opposite, where every agent shares a local time source: five seconds narrows the replay window a captured packet stays valid in
  • 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 --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
  • 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
  • sipnab --mint-token --token-id alice-2026 --api-signing-key-file /etc/sipnab/signing.key --api-token-ttl 3600 — mint a signed bearer token with a fixed id (for later revocation) and a 1-hour TTL, then exit
  • sipnab --mint-token --token-scope metrics --token-id prom-scraper --api-signing-key-file /etc/sipnab/signing.key --api-token-ttl 86400 — mint a scrape-only token for Prometheus: it reaches /metrics, and every /v1/ route refuses it
  • sipnab --mint-token --token-scope full --token-id ops-oncall --api-signing-key-file /etc/sipnab/signing.key — the default scope, stated explicitly: full access to the REST API surface

What --hep-send sends

--hep-send <ADDR> forwards every SIP message and every RTCP report sipnab reads to the collector at <ADDR>, byte for byte as the capture holds it. On a live capture that matches what the flag sounds like. Traffic passes the interface, and a copy reaches Homer.

RTCP travels as HEP protocol type 5, which 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 §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, and 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, and any message bodies it holds. sipnab redacts nothing and drops nothing beyond what --portrange and the matching flags already exclude. Testing a HEP pipeline against a customer capture therefore ships that customer's signaling to whatever <ADDR> names.

sipnab announces this before it reads the first packet:

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, and 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, which belong to third parties who have nothing to do with your analysis, so -I file grants it nothing at all. See Security.

MCP server

Run sipnab as a Model Context Protocol server so an AI agent can drive it. See MCP Server for the full guide. Network Listeners lists the --mint-token / --token-id pair that issues MCP bearer tokens — it serves the REST API too.

Flag Value Default Description
--mcp -- off Run sipnab as an MCP server. Requires -N/--no-tui (stdout carries the JSON-RPC wire) — sipnab exits with an error without it — and rejects stdout-writing flags (--json, --report, …). Feature: mcp (or mcp-http for HTTP transport). See mcp.md.
--mcp-transport stdio|http stdio 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) HTTP MCP bind address. Non-loopback requires --mcp-token. Feature: mcp-http
--mcp-token <TOKEN> -- Bearer token for HTTP MCP; required for non-loopback binds. Also reads $SIPNAB_MCP_TOKEN. Feature: mcp-http
--mcp-token-file <FILE> -- Read bearer token from file (preferred over env in systemd units). Feature: mcp-http
--mcp-signing-key <KEY> -- 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. See auth.md. Feature: mcp-http
--mcp-signing-key-file <FILE> -- Read an MCP signing key from a file (contents trimmed); it becomes the minting key. Feature: mcp-http
--mcp-revoked-file <FILE> -- MCP revocation denylist (one token id per line; reloaded on mtime change). Feature: mcp-http
--mcp-token-ttl <SECS> 3600 Default TTL (seconds) when minting MCP tokens with --mint-token. Feature: mcp-http
--mcp-max-concurrent <N> 100 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
--one-way-delay <MS> -- 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 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
--mcp-max-body-bytes <N> 4096 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-findings <N> 1000 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-rate-limit-per-peer <N> 100 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> -- Additional Host header values the HTTP MCP server accepts (repeatable). rmcp's DNS-rebind protection defaults to localhost, 127.0.0.1, ::1 only — add the public hostname or bind IP when clients connect via 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-file-root <DIR> -- 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-allow-shutdown -- off 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. Feature: mcp
--mcp-allow-open-capture -- off 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. Feature: mcp
--mcp-allow-tls-capture -- off 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. Feature: mcp
--mcp-allow-save-findings -- off Permit the save_findings MCP tool to record an agent's conclusion. The only write verb on sipnab's network surface, and 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. Feature: mcp
--retain-audio -- off Retain RTP audio payload in memory so the export_audio MCP tool can decode it. Off by default: call audio is content, not signaling, and holding it is an operator decision rather than a side effect of enabling MCP. Requires --mcp — the MCP server is the only batch-mode reader of these buffers. Costs a per-packet payload clone, bounded by [limits] max_audio_frames per stream across --max-streams streams. Without it export_audio refuses and names this flag. Feature: mcp

TLS / decryption

Flag Value Default Description
-k, --tls-key <FILE> -- 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> -- 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. Feature: tls
--keylog-fd <N> -- 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 Watch the key log for new entries (live decryption). Feature: tls
--dtls-keylog <FILE> -- DTLS key log (NSS SSLKEYLOGFILE); extracts SRTP keys from DTLS-SRTP handshakes (RFC 5764 exporter, AES-CM profiles). Feature: tls
--srtp-keys <FILE> -- 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> decrypted Pcap export mode for encrypted traffic: decrypted (plaintext payloads, no DSB), raw (original encrypted bytes, no DSB), encrypted+dsb (original encrypted bytes + Decryption Secrets Block so Wireshark can decrypt)
--allow-coredump -- off Allow core dumps (do not call prctl to disable them)
--uprobe-tls -- off 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 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 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 Probe only these flavors (openssl, wolfssl); repeatable. Feature: native
--uprobe-list -- off 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 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-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
  • 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 --mcp --mcp-allow-save-findings — let an agent write its conclusions into the log while it works through a capture; read them back with journalctl -u sipnab, never through a tool
  • sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-allow-save-findings — a live triage session whose findings survive in the journal after the agent disconnects, without granting it any other write
  • sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab-exports --retain-audio — hold call audio in memory so an agent can export_audio a WAV of a problem call
  • sudo sipnab -N -d eth0 "portrange 5060-5061 or portrange 10000-20000" --mcp --retain-audio --mcp-file-root /var/spool/sipnab-exports — live capture with media in scope AND retained; without --retain-audio the same run measures quality but keeps no payload to export
  • 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 sat-trunk.pcap --one-way-delay 280 — score MOS for a satellite trunk, where the real one-way delay is 280 ms; without it sipnab falls back to the trunk's own RTCP and, on a capture carrying none, to an assumed 100 ms that reports roughly a full point too high, because G.107's delay penalty has a knee at 177.3 ms the assumption never crosses
  • sipnab -N -I lan.pcap --one-way-delay 5 — a LAN capture, where assuming 100 ms understates the score; the declared figure also beats any round trip the far end reports or sipnab derives from RTCP, since no packet on the wire can rewrite a config value
  • sipnab -N -I capture.pcap --mcp --mcp-max-rows 50 — cap every list-style MCP response at fifty rows, for an agent whose context window a thousand-row page would swamp; a caller asking for more gets fifty
  • sipnab -N -I capture.pcap --mcp --mcp-max-rows 5000 — raise the ceiling above the 1000 default for a batch client that pipes whole pages to a file, where the round trips cost more than the bytes
  • sipnab -N -I capture.pcap --mcp --mcp-max-body-bytes 65536 — let an agent read a whole INVITE with a long SDP body rather than the first 4096 bytes of it, on a capture whose bodies are what the investigation is about
  • sipnab -N -I capture.pcap --mcp --mcp-max-rows 20 --mcp-max-body-bytes 512 — a small-context agent: few rows, and each one short, so a page of hits fits the window it has to reason in
  • 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 and pipes them straight in, so nothing is ever written to disk. sipnab cannot launch the extractor itself, because every child inherits PR_SET_NO_NEW_PRIVS and so can never acquire CAP_BPF
  • sudo sipnab -N -d eth0 --keylog-fd 3 --user sipnab 3< /run/sip.keys — take the secrets on descriptor 3 opened by the shell, then drop to an unprivileged user for the capture itself; the pipe is already open, so nothing has to be reachable from /run afterwards
  • sudo mkfifo -m 600 /run/sip.keys && sudo sipnab -N -d eth0 --keylog /run/sip.keys --keylog-watch — the same idea through a named pipe rather than a descriptor, for a producer started separately by systemd; sipnab opens the FIFO while still privileged, before it drops to an unprivileged user and can no longer reach /run
  • sudo sipnab --uprobe-listrun this first. Report which TLS libraries processes on this host are actually mapping, and exit without installing a single probe. Answers the only question that matters before starting: is the daemon you care about using a library sipnab can read?
  • sudo sipnab --uprobe-list --uprobe-flavor wolfssl — the same listing narrowed to one flavor, so the output says what this command would probe rather than what merely exists
  • sudo sipnab -N --uprobe-tls — read SIP over TLS with no certificate, no key and no restart of the SIP daemon. sipnab probes every mapped TLS library, so one command covers a host running OpenSSL for one daemon and wolfSSL for another
  • sudo sipnab -N --uprobe-tls --uprobe-flavor openssl — probe only the OpenSSL side on a mixed host, when the wolfSSL processes are something else entirely and their plaintext is not yours to read
  • sudo sipnab -N --uprobe-tls --uprobe-library /usr/lib/x86_64-linux-gnu/libssl.so.3 — skip discovery and probe one named library, which is how you attach to a daemon that has not started yet: discovery can only see what is already mapped
  • sudo sipnab -N --uprobe-tls --uprobe-library /proc/$(docker inspect -f '{{.State.Pid}}' opensips)/root/usr/lib/libssl.so.3 — probe the OpenSSL inside a container. The path a container process sees names a different file from sipnab's namespace, so the probe must go through /proc/<pid>/root or it silently attaches to the host's copy and captures nothing
  • sudo sipnab -N --uprobe-tls --uprobe-library /opt/vendor/libtls-custom.so --uprobe-symbol vendor_write — probe a library sipnab cannot classify by name; without --uprobe-symbol it refuses rather than guessing which function to attach to
  • sudo sipnab -N --uprobe-tls --uprobe-library /usr/lib/libssl.so.3 --uprobe-symbol SSL_write_ex — probe OpenSSL 3's newer write entry point instead of the default SSL_write, for a daemon built against it; the argument positions match, so the probe shape does not change
  • sudo sipnab -N --uprobe-tls --json — the same capture as JSON. Dialogs from this source carry uprobe:<comm>/<pid> as their interface and unspecified addresses with port 0, because a uprobe sees the bytes an application handed its TLS library and nothing about the socket beneath; sipnab names the process rather than inventing a peer
  • sudo sipnab -N --uprobe-tls --uprobe-backend bpf --portrange 0-65535 — read SIP over TLS with the peer addresses. Verified live: a REGISTER and its 200 OK came back as 127.0.0.1:36160 -> 127.0.0.1:15061 and 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 the bpf backend cannot run at all; dialogs then name the process instead of a peer
  • sudo sipnab -N -I done.pcap --mcp --mcp-allow-tls-capture — let an agent decide, mid-investigation, that the answer is in traffic it cannot see, and start reading TLS plaintext itself. Finish the file source first: sipnab's stores have one writer, and a uprobe capture is a live one
  • sudo sipnab -N -I done.pcap --mcp --mcp-allow-tls-capture --mcp-allow-save-findings — the same, plus somewhere for the agent to record what it concluded; without --mcp-allow-tls-capture the agent can still call list_tls_libraries and report what a capture would have seen

Privilege

Flag Value Default Description
--user <USER> -- Drop privileges to this user after opening capture devices
--no-priv-drop -- off Do not drop privileges after opening capture devices
--chroot <DIR> -- Chroot to this directory after initialization
--setup-caps -- off 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.

Examples

  • sudo sipnab -d eth0 --user sipnab — live capture that drops root to the sipnab service user once the capture device is open
  • sudo sipnab -d eth0 --user nobody --chroot /var/empty — long-running monitor that drops to nobody and confines itself to an empty chroot
  • sudo sipnab -d eth0 --chroot /var/empty --no-priv-drop — chrooted capture that keeps root privileges for the whole run
  • sudo sipnab --setup-caps — grant the binary the capture capabilities (cap_net_raw,cap_net_admin) so future runs work without sudo, then exit

Resource limits

Flag Value Default Description
--max-reassembly <N> 10000 Maximum concurrent TCP/TLS reassembly sessions
--reassembly-ttl <SECS> 30 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 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
--cores <N> 1 CPU cores for offline pcap reconstruction (-I). 1 = single-threaded; >1 shards by host pair for multi-core throughput (dialog+RTP reconstruction, --report/--json). At 2 or more cores 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, exactly as before. 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
--max-metadata-file-bytes <BYTES> 2147483648 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 Bytes a gzip-compressed capture may inflate to where sipnab does the inflating: 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. libpcap inflates the packet stream of a -I capture.pcap.gz run, and this does not bound that. 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. Raise it for archives you compressed yourself. Config: [limits] max_gunzip_bytes
--max-tcp-buffer <BYTES> 65536 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 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 sanitised.pcapng — the same file, sanitised for handover; the copy costs roughly twice the ceiling in memory, so raise it only for a file you trust
  • sipnab -N -I archive.pcapng.gz --max-gunzip-bytes 8589934592 --strip-secrets sanitised.pcapng — sanitise an 8 GiB pcapng you compressed yourself, without spending the disk that decompressing it by hand would need
  • sipnab -N -I from-customer.pcapng.gz --max-gunzip-bytes 268435456 --report — the opposite, for a file that arrived from outside: a quarter-gigabyte ceiling on the embedded names and secrets sipnab reads out of it, so a gzip bomb wearing a capture's name cannot take the box down
  • sipnab -N -I isup-trunk.pcap --max-tcp-buffer 1048576 --json-dialogs --no-cli-print — a carrier trunk whose SIP/TCP messages carry encapsulated ISUP bodies past 64 KiB: at the shipped ceiling sipnab cuts each one in half and reports it malformed, and at 1 MiB the same bytes produce the call
  • sudo sipnab -d eth0 --max-tcp-buffer 262144 — watch a live SBC that answers with long Record-Route sets, holding a quarter-megabyte per TCP direction so a large response frames whole instead of arriving as two malformed fragments
  • sudo sipnab -N -d eth0 --reassembly-ttl 600 --max-reassembly 50000 — a persistent SIP/TLS trunk to a carrier that goes quiet overnight: ten minutes of patience keeps the half-read stream, so the first segment of the morning continues a message rather than landing in the middle of one
  • sipnab -N -I untrusted.pcap --reassembly-ttl 5 --max-reassembly 2000 — the opposite, for a capture from outside: a five-second wait drops a half-sent stream almost at once, so a peer that opens streams and never finishes them cannot hold state
  • sipnab -N -I calls.pcap --mcp --mcp-allow-save-findings --mcp-max-findings 20000 — a long agent session over a large capture, where a thousand annotations is a session doing its job and the journal on this box can take twenty
  • sipnab -N -I calls.pcap --mcp --mcp-allow-save-findings --mcp-max-findings 25 — a scripted agent run on a box with a small journal: twenty-five annotations, then sipnab refuses further writes and says so rather than filling the disk

Config

Flag Value Default Description
-f, --config <FILE> -- Path to configuration file (must exist)
-F, --no-config -- off Skip loading any configuration file
-D, --dump-config -- off Dump effective configuration and exit
--completions <SHELL> -- 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

Validation rules

  • Output flags (--json, --json-pretty, --report, --hexdump, --fail2ban) require -N / --no-tui mode, unless --call-report is also specified.
  • --kill-response accepts values 100-699 only.
  • Feature-gated flags (tls, hep, api, mcp, mcp-http) produce startup errors when the required feature is not compiled in.
  • --mcp is incompatible with stdout-writing flags (--json, --json-pretty, --report, --call-report, --hexdump, --wireshark, --tshark-filter) on every transport, not just stdio — sipnab refuses to start. Combine --mcp with --quiet to suppress text-mode capture output.
  • HTTP MCP transport (--mcp --mcp-transport http) on a non-loopback --mcp-bind requires --mcp-token / --mcp-token-file / SIPNAB_MCP_TOKEN; loopback binds need no token.

Examples

  • sipnab -d eth0 — capture on eth0
  • sipnab -I capture.pcap — read from pcap file
  • sipnab -N --json -I capture.pcap — non-interactive JSON output
  • sipnab --problems — show problematic calls
  • sipnab --kill-scanner -d eth0 — detect SIP scanners
  • sipnab --from alice --to bob — filter by From/To headers
  • sipnab 'host 192.0.2.1 and port 5060' — 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-print and the whole capture's message dump precedes it)
  • sipnab -d eth0 -H 192.0.2.50:9060 — capture with HEP mirror
  • sipnab -d eth0 --keylog /tmp/sslkeys.log --keylog-watch — live TLS decryption

Exit codes

Scripts can rely on these:

Code Meaning
0 Success
1 Runtime failure — capture error, I/O error, or sipnab could not produce a requested report (e.g. --call-report Call-ID not found)
2 Invalid usage — bad flag value or combination, or a flag whose feature is not compiled into this binary
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

Clone this wiki locally