Docs Configure sipnab

Configure sipnab

TOML configuration file format and all configurable sections.

On this page

sipnab reads configuration from a TOML file. A CLI flag (cli-reference.md) overrides the config file value it matches, with three kinds of exception, each stated in its key’s row:

  • --names adds files to [names] hosts_file. Where both name one address, the hosts_file entry wins.
  • --allow-action adds surfaces to [actions] tfps.
  • --alert-json and --syslog turn their alert channel on beside the channels [security] alert lists.

Configuration is optional.

  • With no file present, sipnab runs on its built-in defaults.
  • A config file exists to set persistent defaults for your environment.

Minimal Config

If you only need to override a few defaults, keep it short:

# ~/.config/sipnab/sipnab.toml
[capture]
device = "eth0"
# Your trunks also use 5080: widen the signaling ports.
portrange = "5060-5080"

[diagnosis]
# Local traffic: report setup slower than 6 seconds.
post_dial_delay_secs = 6.0

sipnab -D prints the configuration it loaded, so you can check the file took effect. It warns on stderr about each key it does not know:

WARN sipnab::config: Unknown config key: capture.devise

-D refuses a file a run would refuse for its values, with the same message and exit status, and prints nothing else. A run’s checks of [actions] tfps, [action_limits], [security] alert, [filters], [pbx] addresses, [capture] portrange, [filter] expression, [mcp] tools and [[vcon.rule]] all apply. A port range written backwards:

Invalid [capture] portrange: Port range start (9) > end (1)

sipnab exits with status 1.

File Locations

sipnab reads configuration from the first file it finds in this order:

PrioritySource
1--config <FILE> (must exist; errors if missing)
2$SIPNAB_CONFIG environment variable
3$XDG_CONFIG_HOME/sipnab/sipnab.toml, or ~/.config/sipnab/sipnab.toml when $XDG_CONFIG_HOME is unset, empty or not an absolute path
4~/.sipnabrc
5/etc/sipnab/sipnab.toml

sipnab reads only that first file.

  • It does not merge a later one into it.
  • So a setting in ~/.sipnabrc does nothing while ~/.config/sipnab/sipnab.toml exists.

When more than one of rows 3 to 5 exists, sipnab says which file it read and which it did not. It says so in three places:

  • a warning on stderr;
  • a line in --dump-config output;
  • the status line when the terminal UI opens.

A --config file or $SIPNAB_CONFIG is your choice, so it raises no such notice.

# Loaded from: /home/you/.config/sipnab/sipnab.toml
# Also present and NOT read: /home/you/.sipnabrc
  • Use --no-config (-F) to skip all file loading.
  • Use --dump-config (-D) to print which file sipnab loaded and the keys it set. See the note under Full example for what -D does and does not show.

Unknown keys produce a warning and go no further. So one config can span versions. The exceptions are the [[vcon.rule]] and [[vcon.enrich]] tables: an unknown key in one of them stops the run with exit 1, naming the key and the keys the table accepts. See [[vcon.rule]].

Environment variables in values

Any string value may contain ${NAME}. sipnab substitutes that environment variable’s value when it reads the file:

[crash]
report_dir = "/home/${SUDO_USER}/sipnab"

The case this exists for is sudo.

  • Capture needs privileges, so sipnab often runs under sudo.
  • After sudo -i, a file written by that run lands in root’s directory. It does not land in the directory of whoever ran the command.
  • ${SUDO_USER} is the only thing that can say which one that is at the moment sipnab reads the file.

Four rules, and they hold everywhere a string does:

  • ${NAME} expands.
    • NAME is an ASCII letter or _ followed by letters, digits or _.
    • sipnab rejects shell’s ${NAME:-default} and ${NAME-other} outright rather than half-supporting them.
  • An unset variable is an error.
    • sipnab names the setting and the variable and does not start.
    • Expanding to nothing would turn /home/${SUDO_USER}/sipnab into /home//sipnab.
    • That is a real, writable directory that is not the one you meant.
  • A variable whose value is empty expands to nothing.
    • sipnab does not refuse it. With the report_dir above, SUDO_USER= sipnab --config sipnab.toml -D prints report_dir = "/home//sipnab".
    • Run sipnab -D under the same environment as the real run to check what each value expands to.
  • $$ is a literal $. Write $${NAME} for a literal ${NAME}.
  • A bare $NAME is literal text. Braces are the whole syntax.

What a variable expands to is data. sipnab never scans it again. So when a variable’s value itself contains ${HOME}, those seven characters stay as they are.

--dump-config prints values after expansion. That is what sipnab is actually using.

Format

Standard TOML. All sections and keys are optional. Only set values you want to change from defaults.

A file sipnab cannot read, or a value in it sipnab refuses, stops the run before anything starts.

  • The exit status is 1, and the message names the key.
  • The same value given as a flag exits 2.
  • See Exit codes.

Sections

[capture]

Packet capture defaults.

KeyTypeDefaultDescription
devicestring–Default network interface. --device overrides it
node_namestringhostnameName this box reports as, in the capture_identity.node field of MCP answers such as list_dialogs, get_dialog, triage_call and capture_status (MCP across an estate lists them), and of the REST answers about the loaded capture, such as GET /v1/dialogs and GET /v1/stats (the REST reference lists them). Lets an agent querying several servers tell WHICH one saw a given fact. --node-name overrides it, so a deployed config can name the box while a one-off command relabels it. The default puts the hostname on the wire. Clipped to 64 characters. sipnab refuses an empty value (exit 1)
portrangestring"5060-5061"SIP signaling port range; media is never gated by it. sipnab skips any SIP message with both ports outside the range, and a skipped message reaches no count, no dialog and no output — so this key decides how much of a capture you analyze at all. Widen it ("1-65535") unless you know every port in play. --portrange overrides it
ws_portsstringunset: ports 80, 443, 8080 and 8443Ports carrying SIP-over-WebSocket (RFC 7118), as one inclusive "START-END" range in the same grammar as portrange. Kamailio, OpenSIPS and Janus each default to WSS on a port outside the shipped set, and behind a reverse proxy sipnab sees whichever port the proxy forwards to; set the range to cover those ports, or sipnab does not see the WebRTC signaling leg. 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. --ws-portrange overrides it
snapleninteger65535Snapshot length in bytes. sipnab refuses 0, which keeps no byte of any packet, as --snaplen does. --capture-profile overrides it; --snaplen overrides it
bufferinteger64Kernel capture buffer size in MiB (per device). sipnab refuses 0, which asks the kernel for no ring, as --buffer does. --buffer overrides it
buffer_budget_mbinteger64Sets the size of the in-flight capture→processing queue. sipnab converts the budget to a packet count at an assumed 2048 bytes per packet, so 64 holds 32768 packets, and keeps the count between 10000 and 5000000: 0 and any value up to 19 give the 10000-packet floor. The limit counts packets, not bytes. A queued packet can be as large as --snaplen, so with full 65535-byte packets the 64 MiB default can hold about 2 GiB. --buffer-budget overrides it
no_rtpbooleanfalseDisable RTP capture by default. --no-rtp turns it on. --rtp forces it off
promiscbooleantruePut a named interface into promiscuous mode (the any device is never promiscuous). --no-promisc overrides this to false
hep_parsebooleanfalseUnwrap HEP-encapsulated SIP found in the capture, as -E / --hep-parse does: each message takes the addresses and the time the HEP packet carries, or the time sipnab captured the wrapper when the HEP packet carries none. For a proxy that mirrors HEP to a loopback port that sipnab sniffs (device = "lo"), so that several readers can see the same copies. Keep a receiver bound to that port: with no process bound to it, some senders lose messages, as the section below this table describes. --no-hep-parse turns it off for one run. Feature: hep
bpf_filterstring–Capture (BPF) filter, as the trailing positional filter on the command line takes, handed to libpcap as typed. Not the display filter, which is [filter] expression. While it holds a filter, sipnab generates none from portrange. The trailing positional filter (<BPF_FILTER>) or --bpf-file replaces it. It applies to capture files too, and a run that reads a file through it says so on stderr
[capture]
device = "eth0"
portrange = "5060-5080"
ws_ports = "8080-8090"
snaplen = 65535
buffer = 16
buffer_budget_mb = 64
no_rtp = false
promisc = true

Read a proxy’s HEP mirror on loopback

Use this when a proxy mirrors HEP to a UDP port on lo and you want several readers to see the same copies. This file reads the copies sent to UDP/9063:

[capture]
device = "lo"
portrange = "1-65535"
ws_ports = "1-65535"
hep_parse = true
bpf_filter = "udp dst port 9063"

Keep a process bound to UDP/9063 while you sniff it.

  • With no listener on the port, the kernel answers each datagram with an ICMP port-unreachable message.
  • A sender that uses a connected UDP socket then fails its next send, and the readers see fewer messages than the proxy sent.
  • sipnab’s own HEP sender (-H) uses an unconnected socket and loses no message this way. Whether a proxy’s HEP module does depends on its sender.

A sipnab HEP listener binds the port and reads the copies itself:

sipnab -L 127.0.0.1:9063 -N

It prints each message it receives with origin=hep. With tests/pcap-samples/sip-rtp-g711.pcap replayed to it as shown below, the first lines read:

14:52:59.666 10.0.2.20:5060 -> 10.0.2.15:5060 INVITE (SDP) UDP origin=hep
14:52:59.666 10.0.2.15:5060 -> 10.0.2.20:5060 100 Trying UDP origin=hep
14:52:59.670 10.0.2.15:5060 -> 10.0.2.20:5060 200 OK (SDP) UDP origin=hep
14:52:59.670 10.0.2.20:5060 -> 10.0.2.15:5060 ACK UDP origin=hep

To test the path without the proxy, replay a capture to the port with -H:

sipnab -I sip-rtp-g711.pcap -N --portrange 1-65535 -H 127.0.0.1:9063

The last lines on stderr report what it sent:

INFO sipnab::app::batch: HEP export to 127.0.0.1:9063 over udp: 10 packet(s) sent, 10 queued, 0 replayed after an outage, none dropped, 0 still queued, none failed, 0 reconnect(s); sent means handed to the kernel: UDP reports no delivery, so a collector that is down produces no failure here unless its host refuses with ICMP

[display]

Output and TUI display settings.

KeyTypeDefaultDescription
colorstring"auto"Color mode: "auto", "always", "never". sipnab refuses any other value, naming the key. --color overrides it
payload_limitinteger–Maximum payload bytes the -N text output prints for each message; a longer payload ends with [truncated]. Does not change --json or --json-pretty output. --payload-limit overrides it
delta_timebooleanfalseShow delta time between messages by default. --delta-time turns it on. --no-delta-time forces it off
from_tostring"default"From/To column display: "default" (user else host:port), "host-port", "user", "user-host-port". Cycle at runtime with u; --from-to-mode overrides this
mouse_capturebooleantrueWhether the TUI captures the mouse when it starts. false opens it with capture off, so the terminal’s own drag-to-select works; F12 toggles capture at runtime. With capture on, the mouse wheel scrolls
view_filter_detailstring"summary"How status line 3 shows the filter the F7 dialog built. "summary" names each selection in a few words, such as F7: 7 methods, From~alice. "full" shows the expression. A filter typed as an expression is always shown in full. The filter_detail key (default F) toggles it at runtime. sipnab refuses any other value, naming the key
visible_columnsarray of stringsall columnsCall-list columns to show, by name (case-insensitive): "#", "Method", "From", "To", "Source", "Destination", "State", "Msgs", "Date", "PDD", "Duration". Adjust at runtime with F10; s in the column selector writes the layout into the config file this run loaded (or the user config file ($XDG_CONFIG_HOME/sipnab/sipnab.toml, by default ~/.config/sipnab/sipnab.toml) when none was), so it persists across sessions. sipnab refuses that save when the settings came from /etc/sipnab/sipnab.toml
[display]
color = "always"
payload_limit = 4096
delta_time = true
from_to = "user-host-port"
view_filter_detail = "full"
visible_columns = ["method", "from", "to", "source", "destination", "state", "msgs", "pdd"]

[filter]

Default filter presets applied at startup.

KeyTypeDefaultDescription
fromstring–Default From header filter (regex). sipnab refuses a pattern that does not compile when the file loads, naming the key. --from overrides it
tostring–Default To header filter (regex). sipnab refuses a pattern that does not compile when the file loads, naming the key. --to overrides it
expressionstring–Default filter DSL expression. --filter overrides it
[filter]
from = "^1001@"
to = "^1002@"
expression = "method == 'INVITE'"

[filters]

Named filters. Each key is a name and each value a filter expression.

Use a name as @name wherever sipnab takes a filter expression:

  • --filter;
  • --export-vcon-when;
  • [filter] expression;
  • the REST filter parameter;
  • an MCP tool’s filter argument;
  • the TUI filter dialog’s Named filter field.

See Named filters.

KeyTypeDefaultDescription
any namestring–A filter expression, which may reference other names and the built-in aliases as @name. A name is 1 to 64 ASCII letters, digits, - or _, and starts with a letter or digit
[filters]
carrier-a = "src.ip in_subnet '203.0.113.0/24'"
support   = "to.user == '7000' OR header.X-Queue == 'support'"
sales     = "@carrier-a AND to.user =~ '^8[0-9]{3}$'"

sipnab resolves every name when it starts. It refuses to start, exit code 1, naming the entry, when:

  • a name does not parse or is a built-in alias;
  • an entry references a name nothing defines;
  • entries reference each other in a loop;
  • an expansion is not a valid expression.

[vcon]

vCon export settings: the export rules and enrichment services, each an array of tables that you write once per entry, and the sealing keys every rule inherits when it does not set its own.

KeyTypeDefaultDescription
rulearray of tables[]Export rules, each [[vcon.rule]]. See the export rule keys
enricharray of tables[]Enrichment services, each [[vcon.enrich]]. See the enrichment keys
encrypt_tostring or array–The encrypt_to of every [[vcon.rule]] that sets none: with it, every rule except a sign_only one encrypts. One certificate, or a list of them. sipnab refuses an empty list. A rule’s own value overrides it
sign_keystring–The sign_key of every encrypting or sign_only [[vcon.rule]] that sets none. A rule’s own value overrides it
sign_certstring–The sign_cert of every encrypting [[vcon.rule]] that sets none. A rule’s own value overrides it
compressstringnoneThe compress of every encrypting [[vcon.rule]] that sets none: none or gzip. A rule’s own value overrides it
uuid_domainstring–A fully qualified host name, such as pbx.example.com. When set, every container uuid this process writes, through every door, follows the layout of draft-ietf-vcon-vcon-core-04 section 4.1.2: the last 62 bits are the high 62 bits of SHA-1 over the name. sipnab hashes the name in lower case without a trailing dot. Unset, sipnab keys the uuid on the node, the dialog and the capture. sipnab refuses at startup a value that is not a host name of two or more labels (exit 1). See The uuid under a domain

[[vcon.rule]]

Export rules. Each pairs a filter with the directory its vCon containers go to and what they carry.

  • A run can hold several.
  • An unknown key in a rule stops the run with exit 1. The message names the key and lists the keys a rule accepts.
  • sipnab writes a call matching several once per rule.
  • See Write calls by rule.
KeyTypeDefaultDescription
namestring–The rule’s name, 1 to 64 ASCII letters, digits, - or _. Required and unique
whenstring–The filter a call must match; @name named filters resolve. Required
dirstring–The directory the rule writes to. Required
audiostringomitinline carries the retained audio inside each container; url writes it to a file beside the container and names it by URL (see audio_url_base); both turn audio retention on for the run. omit carries none
audio_url_basestring–For audio = "url": the HTTPS base URL your storage serves the rule’s audio files from. sipnab writes each file beside its container and names <base>/<file> in it
audio_formatstringwavThe format of the audio an inline or url rule carries. wav: audio/x-wav. opus: Ogg Opus, audio/ogg, at 24 kbit/s per channel; needs a build with the opus feature. sipnab refuses any other value, and the key on an omit rule, at startup. See Audio as Ogg Opus
max_inline_media_mibinteger--vcon-max-inline-mediaThe largest audio this rule’s containers inline, in MiB; sipnab writes a call above it without its audio, saying so
live_audiobooleanfalseServe each matching call’s audio while it is in progress on the REST API’s /v1/calls/{call_id}/audio WebSocket, and list the calls at /v1/calls/live. Turns audio retention on. See Hear a call while it is in progress
live_rtpstring–A UDP host:port. Send each matching call’s audio there while it is in progress, as RTP with an L16 payload at 16 kHz mono, one stream per leg, with JSON start and stop messages per call. Loopback only, unless the rule sets live_rtp_allow_remote = true; sipnab refuses another address, and one that does not resolve, at startup. Turns audio retention on. See Send a call’s audio as RTP
live_rtp_allow_remotebooleanfalseLet live_rtp name an address other than loopback. RTP has no encryption or authentication, so anyone on the path can hear the call
livebooleanfalseOn a live capture, write each matching call’s container as the call starts and rewrite it in place, with updated_at, each time the call changes. No effect with -I. See Follow a call while it is in progress
encrypt_tostring or array–A PEM certificate, or a list of them, whose RSA or EC P-256 keys the rule encrypts its containers to: one container with one recipient entry per certificate. Set, the rule writes only the encrypted form of draft-ietf-vcon-vcon-core-04 section 5.3. Needs sign_key and sign_cert. sipnab refuses an empty list. See Encrypt what a rule writes
sign_keystring–The PEM RSA or EC P-256 private key an encrypting or sign_only rule signs with, readable by its owner only. An RSA key signs RS256, an EC P-256 key ES256
sign_certstring–The PEM certificate chain of sign_key, leaf first, carried in each signature’s x5c
sign_cert_urlstring–An https:// URL that serves the PEM chain of sign_cert. sipnab writes it as each signature’s x5u (draft-ietf-vcon-vcon-core-04 section 5.2.2), beside the x5c. Refused when it is not https://, and on a rule that does not sign. See Name the chain by URL
sign_onlybooleanfalsetrue: write the signed form of draft-ietf-vcon-vcon-core-04 section 5.2 alone, signed with sign_key and sign_cert, with no encryption. The rule takes sign_key, sign_cert and compress from [vcon], and not [vcon] encrypt_to. Refused beside the rule’s own encrypt_to, and without a signing key. See Sign without encrypting
compressstringnoneFor an encrypting or sign_only rule: gzip signs the gzip (RFC 1952) of the vCon instead of the vCon, as draft-ietf-vcon-vcon-core-04 section 5.2 allows, and the encrypted form names cty: "application/vcon+gzip"; none signs the vCon as it is. Refused on a rule without encrypt_to. See Encrypt what a rule writes
sealed_updatesstringnew-uuidWhat an encrypting rule writes when a call it already wrote changes. new-uuid: a new instance with a new uuid whose amended names the prior one (draft-ietf-vcon-vcon-core-04 section 4.1.9). same-uuid: rewrite the file under the same uuid, with a startup warning. final-only: write once, when the call ends. sipnab refuses any other value at startup. No effect without encrypt_to. See When an encrypted call changes
sip_messagesbooleanfalseAttach the initial INVITE and its final response in wire format, as sip-invite and sip-response, and each one’s SDP as sip-sdp. The rule’s inline budget covers them. --redact removes them. See The INVITE and the final response
dtmf_eventsbooleanfalseWrite each DTMF key press as keydown and keyup events, with the digit, in the signaling Dialog Object’s party_history, from INFO application/dtmf-relay and RFC 4733 telephone events. Turns telephone-event decoding on for the run, as --telephone-event does. --redact removes the events. See What the Dialog Object says about the call
campaign_headerstring–The SIP header whose value becomes the signaling Dialog Object’s campaign, from the first message that carries it. See Contact-center labels
skill_headerstring–The SIP header whose value becomes skill, such as X-Queue
interaction_id_headerstring–The SIP header whose value becomes interaction_id
groupingstringlegleg: one container per dialog, each naming the call’s other legs. call: one container per call, holding every leg the rule selects and the ties between them. See Calls through a PBX or a B2BUA
[[vcon.rule]]
name  = "support-with-audio"
when  = "to.user == '7000'"
dir   = "/var/spool/sipnab-vcon/support"
audio = "inline"

[[vcon.enrich]]

Enrichment services.

  • An unknown key in one stops the run with exit 1. The message names the key and lists the keys this table accepts.
  • sipnab sends each one the calls of the rules it names, before it writes their containers.
  • It appends the Analysis Objects of its answer.

See Enrich a call before it is written.

KeyTypeDefaultDescription
namestring–The service’s name, recorded in every container sipnab sent it. 1 to 64 ASCII letters, digits, - or _. Required and unique
rulesarray of strings–The [[vcon.rule]] names whose calls go to this service. Required
urlstring–Where sipnab POSTs the request: https://, or http:// to a loopback address only. One of url and command
commandstring–A program run per call, reading the request on stdin and answering on stdout. One of url and command
auth_filestring–A file holding one Header-Name: value line sent with each request, such as Authorization: Bearer <token>. Never written to a container
timeout_secsinteger120How long sipnab waits for the answer, in seconds, 1 to 3600. sipnab refuses any other value at startup, exit 1, naming the service
sendarray of strings["audio", "signaling"]What sipnab sends the service: audio (each leg’s WAV), signaling (the container), or both. Sending audio turns audio retention on for the run
lawful_basisboolfalseCarry a lawful_basis attachment the service answers with, checked for the shape of draft-howe-vcon-lawful-basis-02 and recorded as the service’s. With false, an answer holding any attachment is malformed

[pbx]

The PBX sipnab runs beside, for the filter field side.

KeyTypeDefaultDescription
addressesarray of strings[]The PBX’s own addresses, each an IP address or a CIDR range. side is inbound for a call whose opening request went to one of them from another address, outbound for one sent from one of them to another address, and internal for one sent from one of them to another of them. A call that touches none of them has an empty side. sipnab refuses to start, exit code 1, naming the entry, when an entry is neither
[pbx]
addresses = ["192.0.2.10", "198.51.100.0/24"]

[sip]

SIP protocol handling.

KeyTypeDefaultDescription
xcid_headersarray[]Header names used to correlate B2BUA call legs (sip.xcid). A dialog whose message carries one of these headers pointing at another dialog’s Call-ID joins that dialog. Add your estate’s headers here. Empty by default: sipnab picks no X- name for you (RFC 6648), and the identifier that crosses a B2BUA by design is RFC 7989 Session-ID, which correlates with no configuration. An empty list turns the header strategy off
leg_correlation_window_msinteger2000How far apart, in milliseconds, one call’s two legs may start and still correlate on TIMING alone. Timing is the strategy sipnab falls back on when a B2BUA has rewritten every identifier the other strategies compare. Widen it for a PBX that does an LNP or ENUM dip, or walks an LCR cascade, before it places the outbound leg. Every correlation reports the strategy that matched. Must be at least 1: sipnab refuses 0. --leg-correlation-window overrides it
active_idle_window_secsinteger3600Seconds a dialog may go untouched and still count toward the active-dialog and active-call gauges every surface publishes. The default is twice RFC 4028’s default Session-Expires. Raise it where calls stay quiet for longer, such as a caller on hold for over an hour; a call that never sent its BYE then also keeps counting for longer. --active-idle-window overrides it. 0 fails validation and names the key
th_callid_secret_filepathnoneFile holding the OpenSIPS th_callid_passwd, so sipnab can join the two legs of a call through topology_hiding("C") by decoding the hidden Call-ID. sipnab reads it once at startup and removes one trailing line end. sipnab refuses a world-readable file, a file larger than MAX_SECRET_FILE_BYTES (4096) bytes, an empty secret and a NUL byte, and never prints or logs the secret. A decoded join also requires matching From tags, so a wrong secret never joins two legs; sipnab reports it instead (th_callid status undecodable or unmatched). Needs a tls build and no change to OpenSIPS. Not with th_callid_secret. See Decode the hidden Call-ID
th_callid_secretstringnoneAccepts only "opensips-default": decode with the OpenSIPS default secret, which the OpenSIPS source publishes, so it hides nothing. sipnab refuses any other value and does not show it; a real secret goes in th_callid_secret_file. Not with th_callid_secret_file
th_callid_prefixstring"DLGCH_"The OpenSIPS th_callid_prefix that marks a hidden Call-ID. Must not be empty
th_mapping_maxinteger100000Most OpenSIPS E_SIPNAB_TH_MAP mappings sipnab holds at once, DEFAULT_MAX_MAPPINGS (100000) unless set. Past it, sipnab refuses a new mapping, counts it as refused_full and logs a warning; held mappings stay. Must be at least 1. See Have OpenSIPS send the mapping
th_mapping_ttl_secsinteger300Seconds sipnab keeps an E_SIPNAB_TH_MAP mapping after the store last held either of its legs, DEFAULT_TTL_SECS (300) unless set. A mapping whose SIP never arrives, or whose calls the store has evicted, expires then, counted as expired. sipnab checks at most once a second, as SIP or events arrive. Must be at least 1
[sip]
xcid_headers = ["X-Call-ID", "X-CID"]
leg_correlation_window_ms = 8000
active_idle_window_secs = 7200

[security]

Security detection defaults.

KeyTypeDefaultDescription
kill_scannerbooleanfalseEnable scanner detection. --kill-scanner turns it on. --no-kill-scanner forces it off
kill_scanner_detect_onlybooleanfalseDetect scanners as kill_scanner does and never answer them: sipnab starts no scanner-kill worker and sends nothing, on a live capture too. It turns scanner detection on by itself, and wins over kill_scanner = true. sipnab refuses it beside -K/--kill-target (exit 2). --kill-scanner-detect-only turns it on. --no-kill-scanner-detect-only forces it off
kill_responseinteger200SIP response code for scanner reports (100-699). --kill-response overrides it
fraud_detectbooleanfalseEnable fraud detection heuristics. --fraud-detect turns it on. --no-fraud-detect forces it off
alertarray of strings[]Alert channels ("syslog", "json", "exec") and alert rules, as --alert takes them. A rule is "<name>:<threshold>/<window>[:<cooldown>]": name is scanner, digest, fraud or reg_flood, and window and cooldown carry a unit, s, m or h, for example "scanner:5/1m:30s". An unknown channel or rule name, or a window without a unit, stops the run with exit 1 naming the key. --alert overrides it; --alert-json also turns it on; --syslog also turns it on
alert_execstring–Command to execute on alert. --alert-exec overrides it
reg_flood_thresholdinteger50Challenged failures from one source inside one reg_flood_window_secs window before --reg-flood reports a flood: REGISTERs that carried credentials and drew a 401 or 407 on the same transaction. The default is a carrier-registrar figure: it never sees the ten-a-second brute force a small PBX gets. Counted in capture time, so a file replays as the traffic it recorded. --reg-flood-threshold overrides it. 0 fails validation and names the key
reg_flood_window_secsinteger1How much capture time one registration-flood counting window spans, in seconds. reg_flood_threshold counts failures per window, so this decides how concentrated a credential-guessing run has to be: one refusal every two seconds never puts two inside the default one-second window. Range 1-3600. sipnab refuses 0, which would reset the count on every packet, and anything past an hour, where the count is a daily tally rather than a rate, and names the key. --reg-flood-window overrides it
reg_flood_transaction_timeout_msinteger32000How long a credentialed REGISTER stays open to the 401/407 that answers it, in milliseconds. The default is RFC 3261 Timer F, 64 times T1 at the default T1 of 500 ms. sipnab never counts a challenge that arrives later as a failure. Set it to 64 times your network’s T1, or to your registrar-side proxy’s final-response timer when that is longer (see Registration-flood timers). Range 1000-600000. Below one second every challenge arrives after its transaction ends and the detector counts nothing, so sipnab refuses the value and names the key. --reg-flood-transaction-timeout overrides it
kill_rate_limitinteger10Scanner-kill responses per second sipnab may put on the wire. There is no unlimited setting: 0 fails validation and names the key. 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. --kill-rate-limit overrides it
business_hoursstring–Business hours as "START-END" in whole UTC hours, for example "8-18". A wrapping range such as "22-6" is the 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. --business-hours overrides it
fraud_short_call_secsinteger3Measured call duration below which --fraud-detect counts a completed call as short for wangiri detection. Lower it where a normal ring-no-answer on your carriers lasts under three seconds, or sipnab reports ordinary unanswered calls as lures. Must be at least 1: sipnab refuses 0. --fraud-short-call overrides it
fraud_wangiri_callsinteger3Short calls to one destination prefix before --fraud-detect reports wangiri. Must be at least 1: sipnab refuses 0. --fraud-wangiri-calls overrides it
fraud_destinationstring–Destination countries --fraud-detect reports an INVITE to, as comma-separated ISO 3166-1 alpha-2 codes (DO,VG,MA). sipnab reads numbers through the common international prefixes (+, 00, 011) and resolves them by longest calling code, so +1 809 is DO, not US; a number with no prefix is domestic and never matches. --fraud-destination overrides it
fraud_sequential_callsinteger3Consecutive refused numbers before --fraud-detect reports sequential scanning. Must be at least 1: sipnab refuses 0. --fraud-sequential-calls overrides it
fraud_volume_multiplierinteger5Multiple of a source’s own baseline call rate that --fraud-detect reports as a volume spike. Must be at least 1: sipnab refuses 0. --fraud-volume-multiplier overrides it
fraud_volume_min_callsinteger6Calls a source must place inside the volume window before --fraud-detect reports a spike at all. Must be at least 1: sipnab refuses 0. --fraud-volume-min-calls overrides it
fraud_volume_window_secsinteger60How much capture time one volume-spike window spans, in seconds. The count and the source’s own baseline are both measured over this window, so a steady source reads the same at any width; what the width alone decides is how CONCENTRATED a burst has to be, since a burst shorter than the window averages into the ordinary traffic beside it. Must be at least 1: sipnab refuses 0. --fraud-volume-window overrides it
fraud_wangiri_window_secsinteger60How much capture time one wangiri window spans, in seconds. 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: the only count that reports anything is one, which reports every ordinary short call as a lure too. Must be at least 1: sipnab refuses 0. --fraud-wangiri-window overrides it
scanner_behavioral_probesinteger10Probes from one source inside the scanner window, above which --kill-scanner reports a rate detection. Raise it where an SBC puts every source behind one address, or sipnab reports that site’s ordinary traffic as one scanner. --scanner-behavioral-probes overrides it
scanner_enumeration_targetsinteger5Distinct target extensions from one source inside the scanner window, above which --kill-scanner reports extension enumeration. --scanner-enumeration-targets overrides it
scanner_rejected_probesinteger5Rejected probes inside the scanner window at which a source reads as probing rather than operating. Neither the rate nor the enumeration detection reports a source until it reaches this or scanner_unanswered_probes. --scanner-rejected-probes overrides it
scanner_unanswered_probesinteger5Probes 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. --scanner-unanswered-probes overrides it
scanner_window_secsinteger5How much capture time one scanner window spans, in seconds. 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. --scanner-window overrides it
scanner_established_factorinteger4How much more evidence --kill-scanner needs from a source that has completed a registration or a call. For such a source, sipnab multiplies scanner_rejected_probes and scanner_unanswered_probes by this value, so sipnab still reports a registered phone that starts probing, on more evidence. --scanner-established-factor overrides it
scanner_answer_grace_msinteger500How long a probe may go without a response before --kill-scanner counts it as unanswered, in milliseconds. 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. --scanner-answer-grace overrides it
findings_historyinteger1000Security findings kept in memory for later retrieval. 0 keeps none, which is a real setting rather than a mistake. --findings-history overrides it
hep_hmac_window_secsinteger30Seconds either side of now within which sipnab still honors a --hep-auth-mode hmac token’s timestamp. When the sender’s and the collector’s clocks differ by more than the window, sipnab refuses every packet and the collector receives nothing; fix the clocks first. A wider window keeps a captured packet replayable for longer and makes the receiver remember more of the one-time values it has accepted. Range 1-300; sipnab refuses any other value and names the key (exit 1). On a run without --hep-listen it does nothing, and sipnab says so in a startup warning naming the key. --hep-hmac-window overrides it

Upgrade a fleet that uses HMAC authentication

sipnab 0.5.131 and later sign HEP packets with version 2 of the --hep-auth-mode hmac token. Releases up to 0.5.130 use version 1. Neither accepts the other’s token, and no setting makes them.

  • A collector on 0.5.131 or later drops every packet from a version-1 sender.
  • A collector on 0.5.130 or earlier drops every packet from a version-2 sender.

In both cases the collector receives nothing from that sender. A newer collector also logs this warning once:

WARN sipnab::capture::hep: HEP HMAC auth is REFUSING version-1 tokens: that scheme signed only the payload, so the source and destination chunks a sender asserts were not authenticated and could be appended to a captured packet by anyone. There is no compatibility switch — upgrade the sending sipnab to a build that emits version 2 tokens. Every packet from a version-1 sender is dropped, so this looks like a collector that receives nothing.

To upgrade without losing packets, upgrade each sender and the collectors it sends to in the same maintenance window. Check every host with:

sipnab --version

Registration-flood ranges

sipnab refuses a reg_flood_window_secs outside 1 to 3600 and a reg_flood_transaction_timeout_ms outside 1000 to 600000.

  • In the config file, the run stops with exit 1. The message names the key, the range and the value:

    invalid config value: [security] reg_flood_window_secs must be 1-3600 (seconds of capture time one failure count spans), got 3601
    
  • The --reg-flood-window and --reg-flood-transaction-timeout flags refuse the same values with exit 2:

    error: invalid value '3601' for '--reg-flood-window <SECS>': 3601 is not in 1..=3600
    

Every scanner_* key above rejects 0 and names the key.

  • A zero count reports the first probe of any kind as a scanner.
  • A zero window resets the counters on every packet, so nothing ever accumulates.
  • A zero grace counts every probe still in flight as unanswered.
[security]
kill_scanner = true
kill_response = 403
kill_rate_limit = 10
fraud_detect = true
business_hours = "8-18"
fraud_short_call_secs = 2
fraud_wangiri_window_secs = 900
reg_flood_threshold = 10
reg_flood_window_secs = 10
reg_flood_transaction_timeout_ms = 64000
scanner_window_secs = 60
scanner_behavioral_probes = 40
scanner_enumeration_targets = 12
findings_history = 5000
alert = ["syslog", "json"]
alert_exec = "/usr/local/bin/sipnab-alert.sh"

[tfps]

The peer this needs. These read TFPS through its tfps_ctl program in JSON mode, the --json flag.

  • TFPS gained that mode in sippulse/tfps#6.
  • No tagged release carries it yet. v0.2.1, the newest, rejects --json.

Until the next release, build TFPS from its master branch.

  • To check the tfps_ctl you have, run tfps_ctl status --json.
    • One line of JSON means it is ready.
    • unknown option: --json means it predates the mode.
  • Against an older tfps_ctl, these answer with that error and name what to install.

The kernel-drop question (GET /v1/tfps/dropped, the tfps_dropped MCP tool) is the exception. It needs a dropped subcommand that no TFPS build has, released or on master.

Where the toll-fraud prevention system (TFPS) is, when one runs on this host.

TFPS is optional peer software. It condemns sources and enforces that decision in the firewall.

  • sipnab bans nothing on its own.
  • It asks TFPS to ban only when you turn that on under [actions].
  • The tfps_* MCP tools and the /v1/tfps/ REST routes ask it through its tfps_ctl program.
  • This section says where that program and its database are.
  • Leave the section out, and sipnab:
    • looks for tfps_ctl on PATH the moment a TFPS tool runs;
    • does nothing about TFPS at any other time.
KeyTypeDefaultDescription
ctlstring–Path to tfps_ctl. --tfps-ctl overrides it. Absent: sipnab looks on PATH the moment a TFPS tool runs. On a machine with no tfps_ctl anywhere, every read answers installed: false, and sipnab refuses a ban that actions allow, as one it could not ask TFPS about (502 over REST). An empty string fails validation and names the key
dbstring–The TFPS database, passed to every tfps_ctl call as --db <path>: two arguments, which a wrapper script standing in for tfps_ctl receives as "$@". Absent: tfps_ctl uses its own default. An empty string fails validation and names the key
[tfps]
ctl = "/usr/local/bin/tfps_ctl"
db = "/var/lib/tfps/tfps.db"

[actions]

What sipnab may change in another system, and from where. Empty by default: sipnab changes no system outside itself.

  • It publishes what it saw to destinations you name.
  • It asks other software read-only questions.
  • An action, such as asking TFPS to ban a source, runs only when enabled here or with --allow-action. The two add together.

One key per target. Each is a list of the surfaces that may ask:

  • "rest" for the REST API;
  • "mcp" for the MCP tools.

A target or surface sipnab does not know stops it at startup. sipnab does not read it as “nothing enabled”.

KeyTypeDefaultDescription
tfpslist of strings[]Surfaces that may ask TFPS to ban and unban, and back a ban out: POST /v1/tfps/ban, /unban and /v1/actions/revert over REST; tfps_ban, tfps_unban and actions_revert over MCP. A REST caller also needs a token minted with --token-scope actions, and so does an MCP client over HTTP. --allow-action adds to it

Enabling anything needs the actions journal. sipnab refuses to start without a directory it can use for it.

[actions]
tfps = ["rest"]

[action_limits]

The limits every action passes. They exist for two reasons:

  • so a stolen credential or a runaway agent can do only so much;
  • so that every ban ends on its own.

Rules for the keys:

  • Each key left out keeps the value shown.

  • None can be 0 while [actions] enables an action. That would turn the limit off, so sipnab refuses to start instead. With no action enabled, sipnab does not use the limits and does not check them.

  • sipnab checks them before the capture opens. A refused value exits 1, and the message names the key:

    [action_limits] per_minute: action rate limits cannot be turned off: every limit must be at least 1
    
  • The limits survive a restart. sipnab rebuilds them from the journal, so crashing it does not reset them.

KeyTypeDefaultDescription
per_minuteinteger10Actions per minute from everyone together
per_caller_per_minuteinteger5Actions per minute from one caller: one token, or one MCP stdio client. Cannot be above per_minute
address_cooldown_secsinteger60Seconds an address rests after an action on it before the next, so no one can flip a ban on and off
default_ban_secsinteger3600How long a ban lasts when the caller does not say
max_ban_secsinteger604800The longest ban sipnab asks for (7 days). sipnab refuses a longer request, or 0 (TFPS’s “forever”), rather than shortening it

Whatever these say, sipnab never bans some addresses:

  • 0.0.0.0;
  • the broadcast address;
  • loopback and multicast addresses.
[action_limits]
per_minute = 20
per_caller_per_minute = 5
address_cooldown_secs = 60
default_ban_secs = 1800
max_ban_secs = 86400

[journal]

Where sipnab records the actions it takes. After a restart or a crash, it then knows:

  • which bans are its own;
  • what was in flight;
  • how much of each limit it has spent.

sipnab --journal-show prints what it holds. sipnab --revert-actions backs actions out.

KeyTypeDefaultDescription
dirstring/var/lib/sipnab/journalThe journal directory. --journal-dir overrides it. sipnab creates it with mode 0700 if absent, and the packaged service gets /var/lib/sipnab from systemd. Each record reaches the disk before the action it describes runs, and the files hold no token or request body. Files close at 16 MiB, and sipnab deletes closed files more than 90 days old when the next one starts. An empty string fails validation and names the key
[journal]
dir = "/srv/sipnab/journal"

[diagnosis]

Thresholds the signaling and media checks compare against.

  • A number here decides whether a call that is working gets reported as broken.
  • The defaults are standards figures.
  • Where you know your network’s own figures, set them here.

Every value must be a finite number greater than zero.

  • sipnab refuses 0, a negative number, nan and inf, naming the key.
  • duration_asymmetry_pct must also be 100 or less.
  • cn_suppression_ratio must be 1 or less.
  • late_media_ms is a whole number of at least 1.
  • The matching flags follow the same rules.
KeyTypeDefaultDescription
post_dial_delay_secsfloat11.0Post-dial delay over which sipnab reports a call as slow, in seconds. 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. --pdd-threshold overrides it
ack_timeout_secsfloat32.0Seconds 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. --ack-timeout overrides it
no_final_response_secsfloat180.0Seconds 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. --no-final-response-timeout overrides it
duration_asymmetry_pctfloat5.0Percentage difference between the two legs’ durations that counts as asymmetric. Must be 100 or less. --duration-asymmetry-pct overrides it
duration_asymmetry_secsfloat2.0Absolute difference between the two legs’ durations that counts as asymmetric, in seconds. A call has to clear both this and the percentage, so raising either one alone quiets the detection. --duration-asymmetry-secs overrides it
late_media_msinteger500Milliseconds after the 200 OK that media may start before it gets reported as late. --late-media-ms overrides it
cn_suppression_ratiofloat0.3Share 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. Above the ratio, sipnab does not report one-way audio for the call. A VoLTE or mobile trunk running aggressive voice-activity detection can pass 30 percent comfort noise: raise it toward 1 on such a trunk; lower it where a call carrying any comfort noise at all still has to be bidirectional. Must be greater than 0 and 1 or less, and a value that is not fails validation and names the key. --cn-suppression-ratio overrides it
[diagnosis]
post_dial_delay_secs = 6.0
ack_timeout_secs = 32.0
no_final_response_secs = 180.0
duration_asymmetry_pct = 5.0
duration_asymmetry_secs = 2.0
late_media_ms = 500
cn_suppression_ratio = 0.3

[media]

Properties of the observed media path that a passive tap cannot measure for itself.

KeyTypeDefaultDescription
one_way_delay_msfloat–One-way network path delay in milliseconds, feeding the delay term of every MOS. sipnab uses, in order: this value; an RTCP-reported round trip; the round trip it derives from a sender-report echo in a receiver report, which is a lower bound; and otherwise 100 ms, labeled assumed. --one-way-delay overrides it
codec_ietable–Equipment impairment factors (ITU-T G.107 Ie) for codecs sipnab has no published value for, written as a [media.codec_ie] sub-table of "CODEC" = <Ie> pairs. sipnab knows G.711, G.729 and Opus; every other codec, such as G.722, G.726, iLBC, AMR and EVS, scores with a placeholder. A declared codec’s MOS carries mos_grounding = "operator_declared"; a codec nobody declared still says its MOS is a placeholder. Keys match case-insensitively. Values must be at least 0.0 and below 95.0; sipnab fails validation on any other value and names the codec
listening_contextstring"monotic"How the far end listens, for wideband (AMR-WB) MOS: "monotic" (a handset or one-ear headset, ITU-T G.113 Table IV.1) or "diotic" (a stereo headset or speakerphone, Table IV.3). At 6.6 kbit/s the two differ by about 0.59 MOS, and a capture cannot tell which one was in use, so you declare it. Every wideband score names the context sipnab read it in. Case and surrounding spaces do not matter; any other value stops the run with an error naming the key
[media]
one_way_delay_ms = 45.0
listening_context = "diotic"   # the far end uses a stereo headset or speakerphone

# Impairment factors for codecs sipnab has no published value for.
[media.codec_ie]
G722 = 12.0
iLBC = 11.0

[quality]

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

  • A number here decides only what catches an operator’s eye during triage.
  • That is a different question from [diagnosis]. That one decides whether a call that is working counts as broken.

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

  • 30 ms of jitter is already a fault on a LAN PBX.
  • 1 percent loss is unremarkable on an international one.

Rules for the values:

  • Unset keys keep the shipped default. So a file may move one boundary without restating the other seven.
  • Every value must be a finite number of zero or more.
  • A warn boundary must not sit past its matching bad boundary: above it for jitter, loss and round trip, below it for MOS. A set that does fails validation and names both keys.
  • sipnab accepts a warn boundary equal to its bad boundary. It checks the bad color first, so the warning color then never shows for that metric.
  • Zero itself counts as a real setting. loss_warn_pct = 0.0 means any loss at all is worth a color.

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.
KeyTypeDefaultDescription
jitter_warn_msfloat30.0Jitter at or above which the column turns yellow, in milliseconds. --jitter-warn-ms overrides it
jitter_bad_msfloat50.0Jitter at or above which the column turns red, in milliseconds. --jitter-bad-ms overrides it
loss_warn_pctfloat1.0Loss at or above which the column turns yellow, in percent. --loss-warn-pct overrides it
loss_bad_pctfloat5.0Loss at or above which the column turns red, in percent. --loss-bad-pct overrides it
mos_warnfloat4.0MOS below which the column turns yellow. MOS bands run downward, so this must sit at or above mos_bad. --mos-warn overrides it
mos_badfloat3.0MOS below which the column turns red. --mos-bad overrides it
rtt_warn_msfloat300.0Round trip at or above which the column turns yellow, in milliseconds. The default is ITU-T G.114’s 150 ms one-way guidance doubled. --rtt-warn-ms overrides it
rtt_bad_msfloat800.0Round trip at or above which the column turns red, in milliseconds. The default is G.114’s 400 ms one-way figure doubled. --rtt-bad-ms overrides it
[quality]
jitter_warn_ms = 10.0
jitter_bad_ms = 20.0
loss_warn_pct = 0.5
loss_bad_pct = 2.0
mos_warn = 4.2
mos_bad = 3.5
rtt_warn_ms = 120.0
rtt_bad_ms = 300.0

[limits]

Resource limits to prevent unbounded memory growth.

KeyTypeDefaultDescription
dialog_limitinteger100000Maximum tracked dialogs. Must be at least 1: sipnab refuses 0. --limit overrides it
mcp_max_rowsinteger1000Maximum rows in ONE list-style MCP response. Distinct from dialog_limit above, which bounds the whole run; these differ by 100x and bound different things. 0 fails validation and names the key. --mcp-max-rows overrides it
max_streamsinteger50000Maximum RTP streams. 0 fails validation and names the key. --max-streams overrides it
max_reassemblyinteger10000Maximum TCP reassembly sessions. 0 fails validation and names the key. --max-reassembly overrides it
reassembly_ttl_secsinteger30Seconds 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. A persistent SIP/TCP or SIP/TLS trunk can go quiet for longer than that; when sipnab drops its half-read stream, the next segment starts mid-message and sipnab reports the message as malformed. Raise it on such a trunk; max_reassembly caps the extra state either way. --reassembly-ttl overrides it. 0 fails validation and names the key
hep_rate_limitinteger50000Maximum HEP packets per second. On a run without --hep-listen it does nothing, and sipnab says so in a startup warning naming the key. --hep-rate-limit overrides it
max_header_lineinteger8192Maximum bytes in a single SIP header (defense-in-depth). Must be at least 256: sipnab refuses a smaller value
max_headers_per_messageinteger200Maximum SIP headers per message (defense-in-depth). Must be at least 1: sipnab refuses 0
max_messages_per_dialoginteger500Maximum stored messages per dialog (defense-in-depth). Must be at least 1: sipnab refuses 0
idle_compact_after_secsinteger600Seconds of silence before sipnab compacts a dialog’s stored messages. 0 fails validation and names the key
keep_messages_per_idle_dialoginteger20Messages an idle dialog keeps after compaction. Must be at least 1: sipnab refuses 0
max_audio_framesintegerunsetMaximum RTP payload frames retained per stream for WAV and vCon export. Unset keeps the whole call. When set, the oldest frame goes first, and the export says PARTIAL and counts the frames dropped. At 20 ms packetization, 1500 frames is 30 seconds. 0 fails validation and names the key
max_audio_bytesinteger1073741824Bytes of retained RTP payload across every stream, the bound on audio retention. See Audio retention budget. Below 1048576 (1 MiB) fails validation and names the key
lint_max_per_ruleinteger25Findings 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. --lint-max-per-rule overrides it. 0 fails validation and names the key
exec_queue_depthinteger100Hook 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. --exec-queue-depth overrides it. 0 fails validation and names the key
mcp_max_body_bytesinteger4096Bytes 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. --mcp-max-body-bytes overrides it. 0 fails validation and names the key
mcp_max_response_bytesinteger1048576Bytes of ONE whole paged MCP response: the JSON page, the provenance note and the completeness fields. mcp_max_rows bounds how many rows a page carries and mcp_max_body_bytes how wide one body in a row can be; this bounds the page as a whole. Past it the tool drops whole rows from the end of the page, never part of one, and answers truncated: true, truncated_by: "bytes" and a next_cursor to the rest. A page always keeps at least one row, so following the cursor always moves forward. A caller narrows it per call with max_response_bytes and cannot exceed it. --mcp-max-response-bytes overrides it. 0 fails validation and names the key
mcp_max_wait_secondsinteger60Longest ONE await_condition MCP call may wait, in seconds. mcp_max_rows and mcp_max_body_bytes bound what an answer carries; this bounds how long a caller may hold one of --mcp-max-concurrent slots while carrying nothing. sipnab clamps a larger request to it and says so in the response. It also bounds wait_seconds on find_in_captures and find_in_captures_status. --mcp-max-wait-seconds overrides it. 0 fails validation and names the key
mcp_max_findingsinteger1000Findings the MCP save_findings tool accepts before refusing further writes. Past it sipnab refuses the write and says so, and drops nothing to make room. Raise it for a long agent session on a large capture. --mcp-max-findings overrides it. 0 fails validation and names the key
mcp_sweep_max_filesinteger20Files one MCP find_in_captures sweep may open. It is the ceiling on the tool’s per-call max_files: sipnab clamps a larger request to it, and 0 or no request means this value. A rotated spool of forty files needs 40 here before one sweep can reach the oldest file. The response reports the limits applied and files_examined, files_total and complete, so a sweep that stopped early says so. --mcp-sweep-max-files overrides it. 0 and values above 4294967295 (the largest per-call max_files, a 32-bit unsigned integer) fail validation and name the key
mcp_sweep_deadline_msinteger30000Milliseconds one MCP find_in_captures sweep may spend. It is the ceiling on the tool’s per-call deadline_ms, applied the same way as mcp_sweep_max_files. The sweep runs as a background job and checks it before each file and before each packet. --mcp-sweep-deadline-ms overrides it. 0 and values above 43200000 (twelve hours, the longest one background sweep may run) fail validation and name the key
mcp_sweep_max_runninginteger4MCP find_in_captures sweeps one server runs at once. Each running sweep is an operating system thread reading capture files. sipnab refuses a start past this many with a message that reports the bound and names this key and --mcp-sweep-max-running, rather than queueing it. --mcp-sweep-max-running overrides it. 0 and values above 64 (each sweep holds a thread and its own scratch stores) fail validation and name the key
mcp_sweep_max_held_resultsinteger16Finished MCP find_in_captures results one server holds for collection. Past this many, sipnab drops the result of the earliest-started sweep first, and refuses a poll of its job id. --mcp-sweep-max-held-results overrides it. 0 and values above 256 (four held results per running sweep at the running maximum of 64) fail validation and name the key
mcp_sweep_result_retention_secsinteger600Seconds a finished MCP find_in_captures result waits for its poll, counted from the moment the sweep finished. After it, the job id is unknown, and sipnab refuses a poll of it with a message that reports this value. --mcp-sweep-result-retention-secs overrides it. 0 and values above 43200 (twelve hours, the longest one sweep may run) fail validation and name the key
max_lost_sequencesinteger1000Lost RTP sequence numbers retained per stream. This is the window the Packet Loss Map draws and the burst/gap analysis reasons over, so a 30-minute call losing 1 % shows only its last minute at the default. The burst/gap window widens with it. --max-lost-sequences overrides it. 0 fails validation and names the key
quality_interval_secsinteger5Seconds between RTP quality snapshots, which is the resolution of the per-stream quality trend on rtp_stats, GET /v1/streams/{id}, the TUI and the browser analyzer. The shipped five seconds averages away any burst shorter than itself. The trend still covers an hour of call time whatever this is, so a one-second setting retains 3600 snapshots per stream rather than 720 — resolution costs memory, never history. --quality-interval overrides it. Anything outside 1-300 fails validation and names the key
max_groupsinteger100000Distinct --group-by keys one run retains, the same figure dialog_limit ships so a grouped run cannot outgrow an ordinary capture. Past it sipnab refuses new keys and warns that the output is incomplete. Must be at least 1: sipnab refuses 0. --max-groups overrides it
max_grouped_messagesinteger200000Messages --group-by buffers across every group. Grouping cannot stream — the last packet may belong to the first group — so this is memory held until the capture ends. Must be at least 1: sipnab refuses 0. --max-grouped-messages overrides it
max_metadata_file_bytesinteger2147483648Bytes of pcapng sipnab reads into memory for embedded names and TLS secrets. 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, before sipnab can tell the file is a capture at all. Raise it for captures you produced; leave it for captures someone sent you. Must be at least 1: sipnab refuses 0. --max-metadata-file-bytes overrides it
max_gunzip_bytesinteger1073741824Bytes a gzip-compressed capture may inflate to where sipnab does the inflating: the embedded names and TLS secrets read out of a .pcapng.gz, the copy --strip-secrets rewrites, a -I capture.pcap.gz input, and the whole capture in the browser build. sipnab refuses an -I file that inflates past the limit: decompressing reached the <N>-byte ceiling, exit 1. A gzip-bomb guard. Inflation stops one byte past the ceiling, so raising it to N lets a few kilobytes claim N bytes of RAM. Raise it for archives you compressed yourself. Must be at least 1: sipnab refuses 0. --max-gunzip-bytes overrides it
max_tcp_bufferinteger65536Bytes one SIP/TCP direction may buffer before sipnab flushes it. A message larger than this, such as one carrying ISUP encapsulation, a long Record-Route set or a large SDP offer, makes sipnab flush the buffer mid-message and report both halves as malformed. Raise it on such a trunk. The minimum is 8192, one SIP header line; sipnab refuses a smaller value and names the key. --max-tcp-buffer overrides it
api_max_rowsinteger1000Rows 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. --api-max-rows overrides it. 0 fails validation and names the key
api_rate_limit_per_peerinteger100REST requests one client IP may make per second. 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 and see 503 (503 rather than 429 because the limiter runs before authentication, so the refusal says nothing about the credential). 0 disables the cap, the reading hep_rate_limit and mcp_rate_limit_per_peer also give it. At most 4294967295; a larger value fails validation and names the key. --api-rate-limit-per-peer overrides it
metrics_max_conninteger16Metrics scrapes served at once before further ones get 503. A refused scrape leaves a gap in the series. Raise it where several collectors scrape one sipnab. --metrics-max-conn overrides it. 0 fails validation and names the key
max_tracked_peersinteger4096Distinct peers one rate-limit window holds, across every surface sipnab meters: HEP source addresses and MCP callers. Past it sipnab REFUSES a peer it has not already seen this second rather than waving it through, so on a collector aggregating from more agents than this the surplus never enters the capture. Raise it there. The floor is 2, and sipnab refuses a smaller value by name: at 1 the first peer to send in a window takes the only slot and sipnab turns every other peer away for the rest of it
max_capture_sourcesinteger65536Distinct capture sources sipnab remembers by name: each input file, the device, every HEP sender (capture_id@address) and every traced process. The table holds a name so a packet can point back at the bytes it came from: the frame field of --json, which --show-frame follows and --mcp-evidence-ring keys on. Past the limit a packet from a NEW source is still analyzed, but carries no frame pointer, and sipnab counts it in sipnab_capture_sources_refused_total and warns once. sipnab refuses to start when the limit cannot hold every source the run can have: the input files, or the device, plus max_tracked_peers senders on a HEP listener. Raise it, or lower max_tracked_peers. The floor is 1. Flag: --max-capture-sources
[limits]
dialog_limit = 50000
max_streams = 25000
max_reassembly = 5000
hep_rate_limit = 25000
max_header_line = 8192
max_headers_per_message = 200
max_messages_per_dialog = 500
idle_compact_after_secs = 600
keep_messages_per_idle_dialog = 20
max_audio_bytes = 1073741824

How the limits behave when they fill:

  • The compaction pair is idle_compact_after_secs and keep_messages_per_idle_dialog.
    • This is where a limit discards a ladder sipnab already holds.
    • So a call that went quiet shows fewer messages than crossed the wire.
    • sipnab warns once per run when this first happens.
  • The retention logs (max_audio_frames when set, max_audio_bytes, max_lost_sequences) drop their oldest entries as they fill.
  • Every other key here refuses to take something in.

Raise both when the ladder matters more than the footprint. These all go quiet for longer than ten minutes while still being the thing under investigation:

  • a call parked on hold;
  • a dialog waiting on a slow PSTN leg;
  • a capture you paused.

For example:

[limits]
idle_compact_after_secs = 3600
keep_messages_per_idle_dialog = 500

Audio retention budget

The terminal UI, and any run with --retain-audio, keeps each RTP stream’s G.711, G.722 and Opus payload. Then sipnab can write it to a WAV file or a vCon.

  • By default sipnab keeps the whole call, from the first frame.
  • max_audio_bytes bounds the memory this uses, across every stream at once.

sipnab charges each retained frame against the budget:

  • the payload, plus a quarter of its length, plus 32 bytes: an upper bound on what the memory allocator spends on it;
  • 32 bytes for each slot of the stream’s frame buffer, spare capacity included.

For 20 ms G.711 (160 bytes a frame) that is 264 bytes a frame:

  • 13,200 bytes a second for each direction;
  • 26,400 bytes a second for a call with media both ways.

The default of 1 GiB (1,073,741,824 bytes) therefore holds 40,672 call-seconds, which is:

Calls held at onceEach lasting
111.3 hours
6710 minutes
2253 minutes

The charge is an upper bound, so the memory the audio really holds stays below the budget. With --cores N, each worker gets 1/N of the budget, so the run as a whole stays inside it.

When retained audio reaches the budget, sipnab releases frames:

  • It releases the earliest frames of the oldest stream that still holds audio.
  • It releases one frame at a time, until the total is back inside it.
  • On a live capture the oldest stream is normally a call that has already ended.

sipnab counts the frames released on the stream.

  • That call’s WAV export, its vCon media_note, and the note inside the WAV then say PARTIAL: the [limits] max_audio_bytes retention budget was reached and released N earlier frame(s).
  • Its vCon wraps the recording in a recording-set that carries the call’s own start and duration.
  • sipnab never drops audio without counting it.

A live capture that writes vCon containers (--export-vcon-when or --export-vcon with a device) releases each call’s audio once it writes the call’s container. The container carries it. An export of that call made later says how many frames sipnab released and why.

Raise max_audio_bytes for a host that keeps more or longer calls in memory at once.

  • The default is 1073741824 (1 GiB).

  • The minimum is 1048576 (1 MiB). A smaller value stops the run with exit 1:

    invalid config value: [limits] max_audio_bytes must be >= 1048576 (1 MiB), got 1048575; to keep no audio at all, leave --retain-audio off
    
  • Set max_audio_frames to cap each stream as well. For example, 1500 frames is the last 30 seconds of a 20 ms stream.

[mcp]

Which tools the MCP server registers, and whether it sends their output schemas.

  • Every tool sipnab offers a client costs its context window before the agent asks anything.
  • So give a client that needs a few tools only those few.
  • See Choosing which tools load for what each bundle holds.
KeyTypeDefaultDescription
toolslist of strings["full"]Bundles (core, signaling, captures, security, media, relay, tfps, server, vcon, tls), single tool names, names from [mcp.bundles], or full. sipnab refuses to start, naming the entry, when a name is unknown or empty. Names are case-sensitive. --mcp-tools replaces the list
output_schemasbooleanfalseSend each tool’s output schema on tools/list. MCP makes them optional, and they are more than half of what the tools cost a client. Responses carry the same JSON either way. --mcp-output-schemas overrides it
bundlestable–[mcp.bundles]: your own bundles, each a name for a list of tool names and built-in bundles. A custom bundle cannot reuse a built-in bundle or tool name, cannot be empty, and cannot hold full or another custom bundle; sipnab refuses to start, naming the bundle, when one breaks a rule, even when nothing uses it
tls_certstring–PEM certificate chain MCP over HTTP serves HTTPS with, the server’s certificate first. Needs tls_key. Used only with --mcp --mcp-transport http. See MCP TLS. An empty string fails validation and names the key. --mcp-tls-cert overrides it
tls_keystring–PEM private key for tls_cert. sipnab refuses a key any other user on the host can read. An empty string fails validation and names the key. --mcp-tls-key overrides it
[mcp]
tools = ["core", "voice"]

[mcp.bundles]
voice = ["media", "get_sdp_timeline", "check_codec_negotiation"]

[api]

Which Host names the REST API answers, beyond localhost, 127.0.0.1, ::1 and the address --api binds (any IP address on a 0.0.0.0 or :: bind).

  • The check stops a web page from reaching the API through your browser by pointing its own name at 127.0.0.1 (DNS rebinding).
  • See Which Host names the API answers.
KeyTypeDefaultDescription
allowed_hostslist of strings[]Extra Host values to serve, such as a reverse proxy’s public name. name:port accepts that port only. "*" turns the check off. --api-allowed-host replaces the list
tls_certstring–PEM certificate chain the REST API serves HTTPS with, the server’s certificate first. Needs tls_key. Used only when --api starts the API. See API TLS. An empty string fails validation and names the key. --api-tls-cert overrides it
tls_keystring–PEM private key for tls_cert. sipnab refuses a key any other user on the host can read. An empty string fails validation and names the key. --api-tls-key overrides it
[api]
allowed_hosts = ["sipnab.example.com"]
tls_cert = "/etc/sipnab/api.pem"
tls_key = "/etc/sipnab/api.key"

TLS files in the config file

[api], [mcp] and [metrics] each take a tls_cert and tls_key. [hep] takes the HEP sender’s and listener’s TLS files.

  • The listener itself still starts on the command line (--api, --mcp --mcp-transport http, --metrics, --hep-send, --hep-listen).
  • The keys say what it serves with when it starts.

How the flags and keys combine:

  • Each flag replaces its own key. A certificate from the command line and its key from the file make a pair.
  • Once sipnab combines the flags and the file, a certificate without a key, or a key without a certificate, stops it at startup.
    • The message names the half you set.
    • It also names both the flag and the key that would set the other.
    • This check covers every listener’s keys, whether or not this run starts it.
  • Each of these stops sipnab at startup when the listener starts, naming the file:
    • a file sipnab cannot read;
    • a file with no certificate or no private key;
    • a key any other user can read;
    • a key that is not the certificate’s.

[metrics]

How the metrics endpoint serves when --metrics starts it.

KeyTypeDefaultDescription
tls_certstring–PEM certificate chain the metrics endpoint serves HTTPS with, the server’s certificate first. Needs tls_key. See Metrics TLS. An empty string fails validation and names the key. --metrics-tls-cert overrides it
tls_keystring–PEM private key for tls_cert. sipnab refuses a key any other user on the host can read. An empty string fails validation and names the key. --metrics-tls-key overrides it
[metrics]
tls_cert = "/etc/sipnab/metrics.pem"
tls_key = "/etc/sipnab/metrics.key"

[hep]

TLS for HEP, and the HEP sender’s queue:

  • the CA the sender (--hep-send-transport tls) checks the collector’s certificate against;
  • the certificate a TLS listener (--hep-listen-transport tls) presents;
  • how much the sender holds, and how long it waits, while its collector is unreachable.
KeyTypeDefaultDescription
tls_capath–CA (PEM) the sender checks the collector’s certificate against. It replaces the host’s CA bundle: the sender accepts only certificates this file issued. Set this or tls_extra_ca, not both. An empty string fails validation and names the key. On a run without --hep-send it does nothing, and sipnab says so in a startup warning naming the key. --hep-tls-ca overrides it
tls_extra_capath–CA (PEM) the sender trusts in addition to the host’s CA bundle. Set this or tls_ca, not both. The host must have a CA bundle; without one sipnab refuses at startup. An empty string fails validation and names the key. On a run without --hep-send it does nothing, and sipnab says so in a startup warning naming the key. --hep-tls-extra-ca overrides it
tls_certpath–PEM certificate chain a TLS HEP listener presents, the server’s certificate first. A TLS listener needs this and tls_key, from here or from the flags. An empty string fails validation and names the key. On a run without --hep-listen it does nothing, and sipnab says so in a startup warning naming the key. --hep-tls-cert overrides it
tls_keypath–PEM private key for tls_cert. sipnab refuses a key any other user on the host can read. An empty string fails validation and names the key. On a run without --hep-listen it does nothing, and sipnab says so in a startup warning naming the key. --hep-tls-key overrides it
send_queue_mibinteger64MiB of HEP packets the sender queues while its collector is unreachable, 1 to 4096. At the bound sipnab drops the oldest packet and counts it as queue_full. A value outside the range fails validation and names the key. On a run without --hep-send it does nothing, and sipnab says so in a startup warning naming the key. --hep-send-queue overrides it. See When the --hep-send collector goes away
send_timeout_secsinteger3Seconds one sender TCP dial or stalled write may take before the collector counts as unreachable, 1 to 300. It also bounds the first connection at startup. A value outside the range fails validation and names the key. On a run without --hep-send it does nothing, and sipnab says so in a startup warning naming the key. --hep-send-timeout overrides it

The sender’s trust is one setting.

  • When the command line names --hep-tls-ca or --hep-tls-extra-ca, it replaces both tls_ca and tls_extra_ca.
  • So a tls_ca in the file cannot combine with an --hep-tls-extra-ca on the command line.
[hep]
tls_extra_ca = "/etc/sipnab/collector-ca.pem"
send_queue_mib = 256
send_timeout_secs = 5

[vcon_forward]

The vCon forwarder’s settings (sipnab --vcon-forward <SPOOL_DIR>, see Deliver the spool to a store).

  • The spool and --vcon-forward-once stay on the command line.
  • The credential’s value has no key.
    • auth_file names the file that holds it.
    • --vcon-forward-auth or the SIPNAB_VCON_FORWARD_AUTH environment variable carries it without a file.
  • A flag replaces its key.
  • With neither, a kind other than generic supplies the value. Then the default applies.
KeyTypeDefaultDescription
kindstring"generic"The kind of store: generic, vcon-store or conserver. A kind supplies the ingest path when url names no path and the header when the credential is a bare key; no kind changes the container sent. generic supplies nothing. --vcon-forward-kind overrides it
urlstring–Where the forwarder POSTs each container, http:// or https://. With a kind other than generic and no path, the store’s base URL. Checked when the forwarder starts, by the rule --vcon-forward-url follows: sipnab refuses a URL with a user name or password in it, and the message shows that part as [redacted]. A URL refused from this key exits 1, as every refused config value does; the same URL from --vcon-forward-url exits 2. --vcon-forward-url overrides it
replace_urlstring–URL template the forwarder PUTs a container to when the POST answers 409, with {uuid} replaced by the container’s uuid. Checked when the forwarder starts. A template refused from this key exits 1; from --vcon-forward-replace-url, 2. --vcon-forward-replace-url overrides it
auth_filepath–File holding the credential: one Header-Name: value line, or the bare key with a kind other than generic. Refused when other users can read it, and beside --vcon-forward-auth or SIPNAB_VCON_FORWARD_AUTH. A file sipnab cannot read, or one that holds no credential, stops the forwarder with exit 1, as any refused key does. --vcon-forward-auth-file overrides it
capathhost bundleThe only CA (PEM) trusted for an https:// store. --vcon-forward-ca overrides it
extra_capath–A CA (PEM) trusted for an https:// store in addition to the host’s CA bundle. Refused beside ca. --vcon-forward-ca and --vcon-forward-extra-ca each replace both keys
donepath<SPOOL_DIR>/deliveredWhere a delivered container goes. Must be on the spool’s file system. --vcon-forward-done overrides it
failedpath<SPOOL_DIR>/failedWhere a refused container goes, beside <name>.error.json. Must be on the spool’s file system. --vcon-forward-failed overrides it
intervalinteger5Seconds between passes over the spool, 1 to 3600. --vcon-forward-interval overrides it
timeoutinteger30Seconds to wait to connect, and for each read and write, 1 to 600. It also bounds each SCITT registration, its POST and its polling for the receipt. --vcon-forward-timeout overrides it
backoff_firstinteger2Seconds a container waits after its first failed try, 1 to 4294967295; each failed try doubles the wait. No longer than backoff_cap. --vcon-forward-backoff-first overrides it
backoff_capinteger300The longest wait between tries, in seconds, 1 to 4294967295. --vcon-forward-backoff-cap overrides it
max_response_headinteger65536The most bytes of a store’s status line and headers read, 1 to 4294967295. The forwarder treats an answer with more as no answer and retries the container. --vcon-forward-max-response-head overrides it
max_error_bodyinteger8192The most bytes of a refusal’s body a <name>.error.json record keeps, 1 to 4294967295. --vcon-forward-max-error-body overrides it
scitt_urlstring–The SCITT transparency service the forwarder registers lifecycle statements with, vcon_created and vcon_sent, by POSTing each to <scitt_url>/entries. After a 202, sipnab polls its Location for the receipt within timeout, and only on the scheme, host and port of this URL. Unset, it registers nothing. Needs scitt_sign_key, scitt_sign_cert and scitt_issuer. --vcon-forward-scitt-url overrides it
scitt_auth_filepath–File holding one Header-Name: value line sent with each registration. Refused when other users can read it. --vcon-forward-scitt-auth-file overrides it
scitt_capathhost bundleThe only CA (PEM) trusted for an https:// transparency service. --vcon-forward-scitt-ca overrides it
scitt_sign_keypath–The PEM private key that signs the statements: RSA signs RS256, EC P-256 signs ES256. Refused when other users can read it: a refusal from this key exits 1, from the flag 2. --vcon-forward-scitt-sign-key overrides it
scitt_sign_certpath–The PEM certificate chain of scitt_sign_key, leaf first, carried in each statement’s x5chain. sipnab refuses a first certificate that is not the key’s. --vcon-forward-scitt-sign-cert overrides it
scitt_issuerstring–The issuer each statement names (iss): a URI of 1 to 8192 characters. --vcon-forward-scitt-issuer overrides it
content_typestring"application/json"The Content-Type of a JSON container: application/json, application/vcon+json or application/vcon. A gzip format container always goes as application/vcon+gzip. --vcon-forward-content-type overrides it
[vcon_forward]
kind = "vcon-store"
url = "https://api.vcon.store"
auth_file = "/etc/sipnab/vcon-store.key"
done = "/var/spool/sipnab-vcon-sent"
failed = "/var/spool/sipnab-vcon-held"
interval = 10

[vcon_fetch]

The vCon fetcher’s settings (sipnab --vcon-fetch <UUID>, which takes one or more uuids, see Fetch a stored vCon).

  • The uuids, --vcon-fetch-out and --vcon-fetch-overwrite stay on the command line.
  • The credential’s value has no key. auth_file names the file that holds it.
  • A flag replaces its key.
  • With neither, a kind other than generic supplies the value. Then the default applies.
  • A server opted in with --mcp-allow-vcon-fetch or --api-allow-vcon-fetch reads the same store with these keys alone, for the MCP fetch_vcon tool and GET /v1/vcon/fetch/:uuid (Fetch from an agent or a REST client).
KeyTypeDefaultDescription
kindstring"generic"The kind of store: generic, vcon-store, conserver or vcon-mcp. A kind supplies the read path after the path the URL names, the header when the credential is a bare key, and the removal of what the store wraps around the container. generic supplies nothing. --vcon-fetch-kind overrides it
urlstring–The store’s base URL, or, for the generic kind, a URL template holding {uuid}. Checked when the fetcher starts, by the rule --vcon-fetch-url follows. A URL refused from this key exits 1; the same URL from --vcon-fetch-url exits 2. --vcon-fetch-url overrides it
auth_filepath–File holding the credential: one Header-Name: value line, or the bare key with a kind other than generic. Refused when other users can read it. A file sipnab cannot read, or one that holds no credential, stops the fetcher with exit 1, as any refused key does. --vcon-fetch-auth-file overrides it
capathhost bundleThe only CA (PEM) trusted for an https:// store. --vcon-fetch-ca overrides it
extra_capath–A CA (PEM) trusted for an https:// store in addition to the host’s CA bundle. Refused beside ca. --vcon-fetch-ca and --vcon-fetch-extra-ca each replace both keys
decrypt_keypath–The RSA private key (PEM, readable by its owner only) that decrypts an encrypted container. --vcon-fetch-decrypt-key overrides it
signer_capath–The CA (PEM) a signed or decrypted container’s signing certificate must chain to, through the intermediates in its x5c, every certificate within its validity period; system for the host’s CA bundle. Needs no key for a signed container; an encrypted one needs decrypt_key. --vcon-fetch-signer-ca overrides it
signer_sha256string–The SHA-256 fingerprint the signing certificate of a signed or decrypted container must have: 64 hex digits, plain or colon-separated, either case. Needs no key for a signed container; an encrypted one needs decrypt_key. --vcon-fetch-signer-sha256 overrides it
timeoutinteger30Seconds to wait to connect, and for each read and write, 1 to 600. --vcon-fetch-timeout overrides it
max_sizeinteger67108864The largest answer read for one container, in bytes, 1 to 4294967295. --vcon-fetch-max-size overrides it
max_response_headinteger65536The most bytes of a store’s status line and headers read, 1 to 4294967295. --vcon-fetch-max-response-head overrides it
[vcon_fetch]
kind = "vcon-store"
url = "https://api.vcon.store"
auth_file = "/etc/sipnab/vcon-store.key"
timeout = 60

[vcon_input]

How the vCon readers open a signed or encrypted container (see A signed, encrypted or compressed container).

  • The readers are -I, the MCP analyze_vcon and validate_vcon tools, and POST /v1/vcon/analyze and POST /v1/vcon/validate.
  • A flag replaces its key.
  • sipnab reads the files when it reads the first container.
KeyTypeDefaultDescription
keypath–The RSA private key (PEM, readable by its owner only) that decrypts an encrypted container. Without it sipnab refuses an encrypted container. --vcon-key overrides it
signer_capath–The CA (PEM) a signed or encrypted container’s signing certificate must chain to, through the intermediates in its x5c, every certificate within its validity period; system for the host’s CA bundle. sipnab then refuses an unsigned container. --vcon-signer-ca overrides it
signer_sha256string–The SHA-256 fingerprint the signing certificate must have: 64 hex digits, plain or colon-separated, either case. sipnab then refuses an unsigned container. --vcon-signer-sha256 overrides it
x5u_capath–The CA (PEM) the HTTPS server of an x5u URL must chain to, or system for the host’s CA bundle. Set, sipnab fetches the chain of a signature that names it only by x5u, within 256 KiB and 10 seconds. --vcon-x5u-ca overrides it
[vcon_input]
key = "/etc/sipnab/vcon-recipient.key"
signer_ca = "/etc/sipnab/vcon-signer-ca.pem"

[privilege]

Privilege separation settings (Linux only).

KeyTypeDefaultDescription
userstring"nobody"User to drop privileges to after opening capture devices. --user overrides it
no_priv_dropbooleanfalseDisable privilege dropping. --no-priv-drop turns it on. --priv-drop forces it off
chrootstring–Chroot directory after initialization. It applies to every run, -I file analysis included, and changing root needs root privileges: a run as an ordinary user with this key set exits 1. With chroot = "/var/lib/sipnab" and that directory present, the error reads Failed to chroot: chroot(/var/lib/sipnab) failed: Operation not permitted (os error 1). To analyze a file as an ordinary user, run with -F (read no config file) or give --config a file without this key. --chroot overrides it
[privilege]
user = "sipnab"
no_priv_drop = false
chroot = "/var/lib/sipnab"

[names]

Address name-resolution settings (display host:port instead of ip:port).

KeyTypeDefaultDescription
enabledbooleanfalseStart with name resolution on (offline sources). --resolve turns it on. --no-resolve forces it off. false does not keep names off when the file or the command line names another source: reverse_dns, hosts_file, a non-empty [names.manual] or a --names file each turn name resolution on. Only --no-resolve turns it off then
reverse_dnsbooleanfalseAlso use reverse DNS (PTR) lookups. --reverse-dns turns it on. --no-reverse-dns forces it off
hosts_filestring–/etc/hosts-format file of IP → name mappings to preload. --names adds to it
persist_to_configbooleanfalseWhen set, in-TUI N edits are also written into the [names.manual] table below, in the file this run loaded, preserving the rest of it. With no file loaded they go to the user config file ($XDG_CONFIG_HOME/sipnab/sipnab.toml, by default ~/.config/sipnab/sipnab.toml); when the settings came from /etc/sipnab/sipnab.toml they are not written and the status line says so when the session opens
dns_cache_entriesinteger4096Reverse-DNS results (positive and negative) 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. Must be at least 1: sipnab refuses 0. --dns-cache-entries overrides it
manualtable–Inline "IP" = "name" mappings, loaded at startup (highest-priority manual layer)
[names]
enabled = true
reverse_dns = false
hosts_file = "/etc/sipnab/hosts"
persist_to_config = true

# Inline mappings (also written here when persist_to_config = true):
[names.manual]
"192.0.2.1" = "sbc-edge"
"2001:db8::1" = "core6"

[crash]

What happens when sipnab panics. The panic hook:

  • restores the terminal, so raw mode and mouse capture do not stay on;
  • writes a crash report;
  • then either exits cleanly or aborts so the OS can produce a core dump.
KeyTypeDefaultDescription
reportsbooleantrueWrite a crash-report file on panic (message, location, thread, version, backtrace)
backtracebooleantrueCapture a full backtrace in the report (independent of RUST_BACKTRACE)
report_dirstring~/.local/state/sipnabDirectory crash reports land in, one file per crash named sipnab-crash-<UTC time>-<pid>-<n>.log (for example sipnab-crash-20261016-010341-2420651-0.log; <n> counts the reports this process wrote). An empty string fails validation and names the key
corebooleanfalsetrue: abort after the report so the kernel can dump core (subject to ulimit -c / core_pattern); false: exit cleanly with status 101, suppressing the core
[crash]
reports = true
backtrace = true
report_dir = "/var/lib/sipnab"
core = false

Under the unit the .deb and .rpm packages install, the default report_dir is not writable and sipnab prints failed to write crash report: Read-only file system (os error 30). /var/lib/sipnab, the unit’s state directory, is. See Keep crash reports.

[g729]

Where the G.729 codebook tables are. sipnab ships no ITU-T table values, so a host decodes G.729 only after sipnab g729 install has built the table file from the ITU-T G.729 software package (G.729 decoding).

KeyTypeDefaultDescription
tablesstringg729-tables.bin beside the user config fileThe table file. An empty string fails validation and names the key. Flag: --g729-tables
auto_installbooleanfalsetrue: the terminal UI downloads the tables from itu.int without asking, the first time a G.729 stream needs them. The command line, REST, MCP and vCon export never download, whatever this says
[g729]
tables = "/srv/sipnab/g729-tables.bin"
auto_install = false

[theme]

TUI color theme with 11 semantic color slots (plus highlight, a legacy alias for selected).

  • Each field accepts a color name or a hex RGB value.
  • Unset fields use built-in defaults.
  • See theme-guide.md for the full customization guide and its preset themes.
KeyTypeDefaultDescription
backgroundstring"reset" (terminal default)Terminal background
foregroundstring"reset"Default text color (reset is the terminal’s own)
highlightstring–Legacy alias for selected (backward compat)
headerstring"cyan"Status bar, column headers, endpoint labels
selectedstring"yellow"Selected/highlighted row, cursor, focused item
accentstring"magenta"Correlation info, PDD, extended flow labels
goodstring"green"Positive quality, success states (InCall, Registered)
warningstring"yellow"Medium quality, caution states (Ringing, CANCEL)
badstring"red"Poor quality, failures, errors
mutedstring"dark_gray"Separators, pipes, disabled text, timestamps
borderstring"white"Widget borders, panel frames
status_bgstring"#303040"Status bar background band, kept distinct from the terminal background so the status line stays visible

Supported color values:

  • Named: black, white, red, green, yellow, blue, magenta, cyan, gray, dark_gray, reset
  • Hex RGB: "#RRGGBB" (e.g., "#ff8800")
[theme]
background = "#1a1a2e"
foreground = "#e0e0e0"
header = "cyan"
selected = "#e94560"
accent = "magenta"
good = "green"
warning = "yellow"
bad = "red"
muted = "dark_gray"
border = "#444466"

[keybindings]

TUI key binding overrides. The 12 configurable actions appear below. Unset fields use built-in defaults.

Accepted key formats:

  • Single characters: "q", "/", "A"
  • Function keys: "F1" through "F12"
  • Special names: "Esc", "Space", "Enter", "Tab", "Backspace"
KeyTypeDefaultDescription
quitstring"q"Quit the application
helpstring"F1"Show help overlay
filterstring"F7"Open filter dialog
savestring"F2"Open save capture dialog
searchstring"/"Activate search
settingsstring"F8"Open settings popup
pausestring"p"Pause/resume capture
autoscrollstring"A"Toggle autoscroll
extended_flowstring"F4"Toggle extended multi-leg flow
clear_callsstring"F5"Clear all calls
column_selectorstring"F10"Open column selector
filter_detailstring"F"Show the filter the F7 dialog built as a summary or in full on status line 3. Works in every view, ahead of the view’s own keys; sipnab warns at startup when it takes a key a view uses

See keybindings.md for the full shortcut reference, including the keys that are not remappable.

[keybindings]
quit = "q"
help = "F1"
filter = "F7"
save = "F2"
search = "/"
settings = "F8"
pause = "p"
autoscroll = "A"
extended_flow = "F4"
clear_calls = "F5"
column_selector = "F10"
filter_detail = "F"

Full example

A configuration for a SIP monitoring server:

# /etc/sipnab/sipnab.toml
# Production SIP monitoring configuration

# -- Packet capture --
[capture]
device = "eth0"                    # Primary SIP-facing interface
portrange = "5060-5080"            # Cover SIP, SIP-TLS, and alternate ports
snaplen = 65535                    # Full packet capture (no truncation)
buffer = 32                        # 32 MiB kernel buffer for burst tolerance
buffer_budget_mb = 128             # Cap on the in-flight capture->processing queue
no_rtp = false                     # RTP analysis enabled

# -- Display settings --
[display]
color = "always"                   # Force color even when piped
payload_limit = 8192               # -N prints up to 8 KiB of each message
delta_time = true                  # Show timing between messages by default
from_to = "host-port"              # From/To columns show host:port
# visible_columns = ["method", "from", "to", "state", "msgs", "pdd"]  # Persistent column prefs

# -- Default filter (optional) --
[filter]
from = "^1001@"
to = "^1002@"
expression = "method == 'INVITE' OR method == 'REGISTER'"

# -- Security detection --
[security]
kill_scanner = true                # Detect SIP scanners (sipvicious, etc.)
# kill_scanner_detect_only = true  # Detect scanners and never answer them
kill_response = 403                # Reply to scanners with 403
fraud_detect = true                # Heuristic fraud detection
alert = ["syslog", "json"]        # Send alerts to syslog and JSON log
alert_exec = "/usr/local/bin/sipnab-alert.sh"  # Custom alert handler
reg_flood_threshold = 10           # Refused credentialed REGISTERs per window that is a flood
reg_flood_window_secs = 1          # The counting window, in seconds of capture time
reg_flood_transaction_timeout_ms = 32000  # RFC 3261 Timer F: 64 x T1 (500 ms)
kill_rate_limit = 10               # Kill responses/sec sipnab may transmit
business_hours = "8-18"            # Enables off-hours fraud detection (UTC hours)
scanner_window_secs = 60           # Wide enough to hold a sweep paced at one probe/10s
scanner_behavioral_probes = 40     # Raised: this site aggregates behind one SBC address
fraud_wangiri_window_secs = 900    # A lure paced over fifteen minutes is still one lure

# -- Diagnosis thresholds --
[diagnosis]
post_dial_delay_secs = 8.0         # Toll-call target; the default 11.0 is international
ack_timeout_secs = 32.0            # RFC 3261 Timer H
late_media_ms = 500                # Media allowed to start this late after the 200 OK

# -- Resource limits --
[limits]
dialog_limit = 50000               # Max tracked dialogs (tune for RAM)
max_streams = 25000                # Max RTP streams
max_reassembly = 5000              # Max TCP reassembly sessions
hep_rate_limit = 25000             # Max HEP packets/sec
lint_max_per_rule = 25             # Repeats of one lint finding per dialog
exec_queue_depth = 20              # Hook commands allowed to run at once

# -- Privilege separation (Linux) --
[privilege]
user = "sipnab"                    # Drop to unprivileged user after device open
no_priv_drop = false               # Keep privilege dropping enabled
chroot = "/var/lib/sipnab"         # Chroot after initialization; needs root, also on -I runs

# -- Address naming --
[names]
enabled = true                     # Resolve addresses to names at startup
hosts_file = "/etc/sipnab/hosts"   # Preloaded IP -> name mappings

[names.manual]
"192.0.2.1" = "sbc-edge"

# -- Crash handling --
[crash]
reports = true                     # Write a crash report on panic
backtrace = true                   # Include a full backtrace
core = false                       # Exit 101 rather than dumping core

# -- Theme: Catppuccin Mocha --
[theme]
background = "#1e1e2e"
foreground = "#cdd6f4"
header = "#89b4fa"
selected = "#f9e2af"
accent = "#cba6f7"
good = "#a6e3a1"
warning = "#fab387"
bad = "#f38ba8"
muted = "#585b70"
border = "#6c7086"

# -- Keybindings (defaults shown) --
[keybindings]
quit = "q"
help = "F1"
filter = "F7"
save = "F2"
search = "/"
settings = "F8"
pause = "p"
autoscroll = "A"
extended_flow = "F4"
clear_calls = "F5"
column_selector = "F10"

Tip: Use sipnab --dump-config to see which file sipnab actually loaded and what that file set.

  • It prints the path it came from.
  • It then prints every section header with the keys that file supplied under each.

Read the omissions carefully, because -D shows less than “effective configuration” suggests:

  • Built-in defaults do not appear.
    • A key you did not set prints nothing, not its default.
    • sipnab -F --dump-config therefore prints the section headers with almost nothing under them. The exceptions are four lists that print empty: addresses = [] under [pbx], rule = [] and enrich = [] under [vcon], and tfps = [] under [actions]. [mcp.bundles] prints as its own header. That is correct output and not a fault.
    • The defaults are the ones in the tables on this page.
  • CLI flags do not appear.
    • They arrive later in startup, so -D cannot show what a flag would override.
    • Compare against the CLI reference for that.
  • There is no environment-variable override layer. SIPNAB_CONFIG only selects which file to read.

So -D answers “did sipnab read the file I meant, and does it know my keys?”. It does not answer “what value is this setting running with?”. It refuses the values a run refuses from the file alone. See the top of this page.