-
Notifications
You must be signed in to change notification settings - Fork 0
CLI Reference
Quick start:
sipnab -I capture.pcapto analyze a file, orsudo sipnabfor live capture on the default interface. Add-Nfor non-interactive output.
Complete flag reference for sipnab. This page groups flags by function.
CLI flags always override config file values (see config-reference.md). Boolean flags default to off (false) unless otherwise noted. For task-oriented recipes rather than a flag catalog, start with examples.md.
A few flag combinations to get productive fast. For the full task-oriented collection — triage, filtering, recording, security, HEP — see the Cookbook. For symptom-driven diagnostics see Troubleshooting. This page is otherwise a flag reference (grouped below).
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-printWhen 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.mdWatch live traffic for calls that are already degraded — MOS below 3.0, or audio flowing in only one direction.
sudo sipnab -N -d eth0 --filter "rtp.mos < 3.0 OR one_way == true"Feed the same set of problem calls into a monitoring pipeline as NDJSON while keeping a copy on disk.
sudo sipnab -N -d eth0 --problems --json | tee /var/log/sipnab/problems.ndjsonHand each quality drop to an external alerting script rather than reading it
yourself. --exec-rate-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 5Find 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" --jsonThe --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 --reportDetect 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.logBefore 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 -rnAudit a capture for digest credentials that went out where anyone could read them.
sipnab -N -I capture.pcap --digest-leakRun the whole sweep at once — scanners, fraud heuristics, and registration floods — with alerts going to both syslog and structured JSON.
sudo sipnab -N -d eth0 --kill-scanner --fraud-detect --reg-flood \
--alert syslog --alert json --syslogHand the capture to Wireshark with a display filter already applied.
sipnab -I capture.pcap --wiresharkOr 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"'Audio export is a TUI workflow — see Keybindings.
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 -rnList every distinct User-Agent the capture saw, to find the odd endpoint out.
sipnab -N -I capture.pcap --json \
| jq -r '.ua // empty' | sort -uStop after a fixed packet count and summarize the capture.
sipnab -N -d eth0 -n 1000 --reportRoll to a new pcapng every 50 MiB, so a long capture does not become one file too large to open.
sipnab -d eth0 -O /var/captures/sip.pcapng --pcapng --split filesize:50Keep only the newest four of those files, 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-keepdeletes 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-timeCapture on two named interfaces instead, one libpcap handle each.
sipnab -d eth0,eth1 --multi-device --delta-timeTip: Every output flag (
--json,--report,--fail2ban, etc.) needs-N. Think of it as "non-interactive mode" -- it disables the TUI and writes to stdout instead.
| Flag | Value | Default | 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 bareportrange 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
-dcosts you nothing. The arm asks "does this frame carry an encapsulation?" through libpcap'sether 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 withtcpdump -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
portrangedoes. The untaggedportrangehandles 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 prototest 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_compileexactly 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 anypseudo-deviceevery interface at once, loopback included macOS / BSD libpcap's default device, from the routing table; otherwise the first non-loopback interface one interface On Linux this is deliberate and matches sngrep: a SIP proxy often talks to itself over loopback, so capturing only
eth0silently misses it. Pass-d anyto say so explicitly. Promiscuous mode does not apply toany, so--no-promiscchanges 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
-dexplicitly.
Reading a set of files: order comes from the packets, not the names.
tcpdump -C 100 -W 10writes a ring buffer —tg.pcap0throughtg.pcap9— and then wraps, overwriting the oldest file in place. A real set measured for this feature rantg.pcap7,tg.pcap8,tg.pcap9,tg.pcap0…tg.pcap6in 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.pcap0has the extensionpcap0, and plenty of captures have none at all. It decompresses gzip members transparently, so a directory holding both.pcapand.pcap.gzneeds nothing special.A file you name directly with
-Ithat sipnab cannot read is an error. 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.
-Iand-dare 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-Irather than adding-dbeside it.
--portrangedecides how much of the file you analyze. The default,5060-5061, is narrow. SIP on other ports is ordinary: carriers and SBCs use 5070, 5080 and others routinely, 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-65535analyses 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 DECODEDis the other line to read before the totals.--portrangeis 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 0says the file isDLT_NULLandeditcap -T ether in.pcap out.pcapconverts it. A small count is normal — ARP is undecodable by definition and appears on any Ethernet capture — so read the share, not the count. When the share is high, sipnab additionally refuses to state "No SIP traffic found" as a finding, because it has no basis for one. The same breakdown appears as aNOT DECODED (capture-wide)section in--report, and assipnab_capture_undecodable_frames_total{reason}plussipnab_capture_undecoded_fractionon/metrics.
docs/troubleshooting.mdtables 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-deviceneeds the list; without one it is a no-op -
sipnab -N --input capture.pcap --replay --no-rtp— replay a pcap at its original timing with RTP capture and analysis disabled -
sudo sipnab -N -d eth0 --capture-profile signaling --output signaling.pcap— record signaling on a busy link: 1500 bytes keeps every SIP header whole while dropping the bulk of each RTP packet, which is where the ring pressure comes from. Checksipnab_capture_snapped_frames_totalafterwards to see how many frames arrived short -
sudo sipnab -N -d eth0 --capture-profile signaling --snaplen 4096— the explicit number wins over the profile, for a trunk carrying INVITEs too large even for one MTU (deepRecord-Routesets, ISUP encapsulation). Use--capture-profile fullinstead when you need--retain-audio, WAV export or a faithful-Ore-emit, all of which need RTP payload -
sipnab -N --input /var/captures/ --json-dialogs --no-cli-print— read every capture in a directory as one timeline, so a call split across the ring buffer resolves to one dialog instead of two fragments -
sipnab -N --input /var/captures/ --recursive --input-name '*.pcap.gz' --json-dialogs --no-cli-print— descend into per-day subdirectories and read only the compressed archives -
sipnab -N --input 'captures/tg.pcap[0-4]' --report— analyze the first five members of a ring buffer with a glob sipnab expands itself, no shell needed -
sipnab -N --input 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
| 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 setSIPNAB_LOG=debug, or the run prints nothing but the mask -
sudo sipnab --no-tui -d eth0 --telephone-event --dtmf-cleartext 2>dtmf.log— capture live and steer the cleartext digits into a file whose permissions you control instead of a shared terminal or journald; again needsSIPNAB_LOG=debug
Read this before using --dtmf-cleartext. DTMF digits keyed after answer 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.logWhere the events go. sipnab writes one masked line per decoded event and
keeps a count. Nothing else carries the digits: no report, no JSON field, no MCP
tool. Two consequences follow, and both bite the obvious command lines. Adding
-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.
| 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
| 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
| 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 asUNVERIFIEDbecause the short form carries nothing to check against -
sipnab -N -I capture.pcapng --strip-secrets clean.pcapng— write a sanitized copy of a pcapng with every Decryption Secrets Block removed -
sipnab -N -I tls-call.pcapng --strip-secrets tls-call-clean.pcapng— strip embedded TLS secrets from a decrypted-session capture before sharing it in a support ticket
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
| 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 aboveerror, so a pipeline stops on a non-conformant capture. Exit 3 is not 1 or 2, so a failing gate is distinguishable from a failing tool and from a bad invocation -
sipnab -N -I calls.pcap --lint --lint-fail-on warning --no-cli-print— a stricter gate: stop on warnings as well as errors, for a pipeline that treats interop degradation as a build failure rather than a note -
sipnab -N -I capture.pcap --json-dialogs --no-cli-print --plugin ./short-calls.wasm— run a custom detection over every dialog and emit its findings beside sipnab's own -
sudo sipnab -d eth0 -N --json-dialogs --no-cli-print --plugin ./site-rules.wasm --plugin ./fraud.wasm— stack two site-specific detections over live traffic; each plugin is sandboxed and a failure in one never stops the capture -
sipnab -N -I capture.pcap --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-Yverbatim, so it takes WIRESHARK display-filter syntax rather than sipnab's--filterDSL:sip.Method, notmethod. Quote the whole expression in single quotes so the inner double quotes survive the shell
| Flag | Value | Default | 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 |
branchcounts 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--limitthen counts transactions too.
Examples
-
sipnab -N -I loadtest.pcapng --dialog-track branch --report— per-transaction view of a load-generator capture that reuses one Call-ID -
sipnab -N -I loadtest.pcapng --dialog-track call-id --report— same capture as dialogs (the default), for a per-call view -
sudo sipnab -d eth0 --limit 5000 --rotate— monitor a busy proxy with a tight 5000-dialog memory bound, explicitly evicting the oldest dialog at capacity -
sipnab -N -I capture.pcap --limit 20000 --no-rotate— analyze a capture keyed by Via branch, dropping new dialogs (instead of evicting old ones) past 20000 tracked -
sipnab -N -I capture.pcap --tag 1928301774 --rotate— show only dialogs carrying a specific From/To tag, with explicit LRU rotation -
sudo sipnab -d eth0 --tag as7d60e14a --no-rotate— live-follow dialogs matching a tag while refusing new dialogs once the tracker is full -
sipnab -N -I capture.pcap --no-dialog— scan a capture message-by-message with dialog tracking disabled entirely -
sudo sipnab -d eth0 -N --no-dialog— watch raw live SIP messages on an interface without keeping any per-dialog state -
sipnab -N -I sbc.pcap --leg-correlation-window 8000 --mcp— correlate the two legs of a call across a B2BUA that dips an ENUM or LNP database before placing the outbound leg, which the shipped two seconds cannot reach -
sipnab -N -I gateway.pcap --leg-correlation-window 500 --report— a PBX that places the outbound leg immediately, where a tighter window stops a busy server's unrelated calls turning into one -
sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --active-idle-window 14400— a contact center parking callers on hold for hours: at the shipped hour the active-call gauge stops counting them, and four hours covers the queue -
sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --active-idle-window 300— a trunk where every call refreshes on a short session timer, so five minutes of silence already means the BYE went missing and counting it longer only inflates the gauge
| Flag | Value | Default | 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
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
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
| 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 |
--alerttakes a channel name, not a rule.syslog,jsonorexec.--syslogand--alert-jsonare the equivalent boolean forms; naming the channel here does the same thing. A value containing:is instead parsed as an alert rule (<name>:<threshold>/<window>[:<cooldown>], window needs ans/m/hsuffix). An unrecognised bare word draws a warning naming the valid channels. It used to fail silently, so a documented--alert syslogenabled 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
| 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
| 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: oneGET /v1/dialogs?limit=100000drains the whole store in a single page, and the per-peer cap is off so a scripted pager is not throttled against itself -
sudo sipnab -N -d eth0 --api 127.0.0.1:8080 --api-key s3cret --api-max-rows 200 --api-rate-limit-per-peer 600— the opposite, for a dashboard: short pages a browser can render, and six hundred requests a second per address, so a dozen browsers refreshing twice a second behind one office NAT share an allowance that fits them -
sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --metrics-max-conn 64— an HA Prometheus pair, a federating parent, aremote_writeshard and an alertmanager sidecar all scraping one sipnab: at sixteen slots a slow scrape turns the next one away with503, and the hole in the series reads as a capture that died -
sudo sipnab -N -d eth0 --metrics 127.0.0.1:9090 --metrics-max-conn 2— a single scraper on a small box, where two slots covers every client this endpoint has, and a third connection is something to turn away -
sipnab -N -L 0.0.0.0:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac --hep-hmac-window 120— a collector whose agents run on hardware with a drifting clock: two minutes of tolerance keeps them heard while someone repairs the NTP problem -
sipnab -N -L 127.0.0.1:9060 --hep-parse --hep-auth-file /etc/sipnab/hep.key --hep-auth-mode hmac --hep-hmac-window 5— the opposite, where every agent shares a local time source: five seconds narrows the replay window a captured packet stays valid in -
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
--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.
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
|
| 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 withjournalctl -u sipnab, never through a tool -
sudo sipnab -N -d eth0 --mcp --mcp-transport http --mcp-allow-save-findings— a live triage session whose findings survive in the journal after the agent disconnects, without granting it any other write -
sipnab -N -I capture.pcap --mcp --mcp-file-root /var/spool/sipnab-exports --retain-audio— hold call audio in memory so an agent canexport_audioa WAV of a problem call -
sudo sipnab -N -d eth0 "portrange 5060-5061 or portrange 10000-20000" --mcp --retain-audio --mcp-file-root /var/spool/sipnab-exports— live capture with media in scope AND retained; without--retain-audiothe same run measures quality but keeps no payload to export -
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 wholeINVITEwith a long SDP body rather than the first 4096 bytes of it, on a capture whose bodies are what the investigation is about -
sipnab -N -I capture.pcap --mcp --mcp-max-rows 20 --mcp-max-body-bytes 512— a small-context agent: few rows, and each one short, so a page of hits fits the window it has to reason in -
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 inheritsPR_SET_NO_NEW_PRIVSand so can never acquireCAP_BPF -
sudo sipnab -N -d eth0 --keylog-fd 3 --user sipnab 3< /run/sip.keys— take the secrets on descriptor 3 opened by the shell, then drop to an unprivileged user for the capture itself; the pipe is already open, so nothing has to be reachable from/runafterwards -
sudo mkfifo -m 600 /run/sip.keys && sudo sipnab -N -d eth0 --keylog /run/sip.keys --keylog-watch— the same idea through a named pipe rather than a descriptor, for a producer started separately by systemd; sipnab opens the FIFO while still privileged, before it drops to an unprivileged user and can no longer reach/run -
sudo sipnab --uprobe-list— run this first. Report which TLS libraries processes on this host are actually mapping, and exit without installing a single probe. Answers the only question that matters before starting: is the daemon you care about using a library sipnab can read? -
sudo sipnab --uprobe-list --uprobe-flavor wolfssl— the same listing narrowed to one flavor, so the output says what this command would probe rather than what merely exists -
sudo sipnab -N --uprobe-tls— read SIP over TLS with no certificate, no key and no restart of the SIP daemon. sipnab probes every mapped TLS library, so one command covers a host running OpenSSL for one daemon and wolfSSL for another -
sudo sipnab -N --uprobe-tls --uprobe-flavor openssl— probe only the OpenSSL side on a mixed host, when the wolfSSL processes are something else entirely and their plaintext is not yours to read -
sudo sipnab -N --uprobe-tls --uprobe-library /usr/lib/x86_64-linux-gnu/libssl.so.3— skip discovery and probe one named library, which is how you attach to a daemon that has not started yet: discovery can only see what is already mapped -
sudo sipnab -N --uprobe-tls --uprobe-library /proc/$(docker inspect -f '{{.State.Pid}}' opensips)/root/usr/lib/libssl.so.3— probe the OpenSSL inside a container. The path a container process sees names a different file from sipnab's namespace, so the probe must go through/proc/<pid>/rootor it silently attaches to the host's copy and captures nothing -
sudo sipnab -N --uprobe-tls --uprobe-library /opt/vendor/libtls-custom.so --uprobe-symbol vendor_write— probe a library sipnab cannot classify by name; without--uprobe-symbolit refuses rather than guessing which function to attach to -
sudo sipnab -N --uprobe-tls --uprobe-library /usr/lib/libssl.so.3 --uprobe-symbol SSL_write_ex— probe OpenSSL 3's newer write entry point instead of the defaultSSL_write, for a daemon built against it; the argument positions match, so the probe shape does not change -
sudo sipnab -N --uprobe-tls --json— the same capture as JSON. Dialogs from this source carryuprobe:<comm>/<pid>as their interface and unspecified addresses with port 0, because a uprobe sees the bytes an application handed its TLS library and nothing about the socket beneath; sipnab names the process rather than inventing a peer -
sudo sipnab -N --uprobe-tls --uprobe-backend bpf --portrange 0-65535— read SIP over TLS with the peer addresses. Verified live: a REGISTER and its 200 OK came back as127.0.0.1:36160 -> 127.0.0.1:15061and the reverse, each connection carrying its own ephemeral port. Widen--portrange, because a TLS trunk on 5061 is the exception and the port a uprobe reports is whatever the socket used -
sudo sipnab -N --uprobe-tls --uprobe-backend tracefs— the default, spelled out. Use it on a kernel without BTF, where thebpfbackend cannot run at all; dialogs then name the process instead of a peer -
sudo sipnab -N -I done.pcap --mcp --mcp-allow-tls-capture— let an agent decide, mid-investigation, that the answer is in traffic it cannot see, and start reading TLS plaintext itself. Finish the file source first: sipnab's stores have one writer, and a uprobe capture is a live one -
sudo sipnab -N -I done.pcap --mcp --mcp-allow-tls-capture --mcp-allow-save-findings— the same, plus somewhere for the agent to record what it concluded; without--mcp-allow-tls-capturethe agent can still calllist_tls_librariesand report what a capture would have seen
| Flag | Value | Default | 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
| 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 longRecord-Routesets, holding a quarter-megabyte per TCP direction so a large response frames whole instead of arriving as two malformed fragments -
sudo sipnab -N -d eth0 --reassembly-ttl 600 --max-reassembly 50000— a persistent SIP/TLS trunk to a carrier that goes quiet overnight: ten minutes of patience keeps the half-read stream, so the first segment of the morning continues a message rather than landing in the middle of one -
sipnab -N -I untrusted.pcap --reassembly-ttl 5 --max-reassembly 2000— the opposite, for a capture from outside: a five-second wait drops a half-sent stream almost at once, so a peer that opens streams and never finishes them cannot hold state -
sipnab -N -I calls.pcap --mcp --mcp-allow-save-findings --mcp-max-findings 20000— a long agent session over a large capture, where a thousand annotations is a session doing its job and the journal on this box can take twenty -
sipnab -N -I calls.pcap --mcp --mcp-allow-save-findings --mcp-max-findings 25— a scripted agent run on a box with a small journal: twenty-five annotations, then sipnab refuses further writes and says so rather than filling the disk
| 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
- Output flags (
--json,--json-pretty,--report,--hexdump,--fail2ban) require-N/--no-tuimode, unless--call-reportis also specified. -
--kill-responseaccepts values 100-699 only. - Feature-gated flags (
tls,hep,api,mcp,mcp-http) produce startup errors when the required feature is not compiled in. -
--mcpis incompatible with stdout-writing flags (--json,--json-pretty,--report,--call-report,--hexdump,--wireshark,--tshark-filter) on every transport, not just stdio — sipnab refuses to start. Combine--mcpwith--quietto suppress text-mode capture output. - HTTP MCP transport (
--mcp --mcp-transport http) on a non-loopback--mcp-bindrequires--mcp-token/--mcp-token-file/SIPNAB_MCP_TOKEN; loopback binds need no token.
-
sipnab -d eth0— capture on eth0 -
sipnab -I capture.pcap— read from pcap file -
sipnab -N --json -I capture.pcap— non-interactive JSON output -
sipnab --problems— show problematic calls -
sipnab --kill-scanner -d eth0— detect SIP scanners -
sipnab --from alice --to bob— filter by From/To headers -
sipnab 'host 192.0.2.1 and port 5060'— BPF display filter -
sipnab --filter "method == 'INVITE' AND rtp.mos < 3.0"— advanced filter DSL -
sipnab -N -I capture.pcap --call-report "abc123@host" --markdown --no-cli-print— generate detailed report for a call (drop--no-cli-printand the whole capture's message dump precedes it) -
sipnab -d eth0 -H 192.0.2.50:9060— capture with HEP mirror -
sipnab -d eth0 --keylog /tmp/sslkeys.log --keylog-watch— live TLS decryption
Scripts can rely on these:
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Runtime failure — capture error, I/O error, 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 |
Website · Repository · Issues · Generated from docs/ — edit there, not here.
Getting started
Using the TUI
CLI & automation
- CLI Reference
- Filter DSL
- Output Formats
- SIP Response Codes
- SIP Methods
- SIP Header Fields
- SIP Parameters
- SIP Lint Rules
- MOS and Codecs
Configuration
Integrations (API & MCP)
- REST API
- Prometheus Metrics
- Authentication
- MCP
- MCP Walkthrough
- Uprobe Walkthrough
- Capture SIP over TLS
- WASM Plugins
Development & internals
- Internals Index
- Internals Subsystem Guide
- Internals Invariants
- Internals Testing
- Internals Walkthroughs
- Internals Build CI Release
- Internals Profiling
- Internals Domain Primer
- Library API
- Benchmarks
- Fault Model
- Architecture
- Internals Threading
- Internals TUI Testing
- Internals Zero Copy Payloads
- Internals Uprobe Capture