Esc

Filter DSL

Quick start: sipnab --filter "state == 'Failed'" to find all failed calls, or sipnab --problems for a one-flag diagnostic sweep.

sipnab includes a declarative, non-Turing-complete filter language for matching SIP dialogs and their associated RTP streams. Expressions are passed via the --filter CLI flag or the expression key in the [filter] config section.

Grammar

expr        = or_expr
or_expr     = and_expr ("OR" and_expr)*
and_expr    = not_expr ("AND" not_expr)*
not_expr    = "NOT" atom | atom
atom        = comparison | "(" expr ")"
comparison  = field operator value

Operator precedence (highest to lowest): NOT, AND, OR. Use parentheses to override.

Fields

All 30 addressable fields, organized by type.

String Fields

FieldDescriptionExample Values
from.userUser part of the SIP From header"1001", "alice"
to.userUser part of the SIP To header"1002", "bob"
methodSIP request method"INVITE", "REGISTER", "BYE"
uaUser-Agent header (first non-empty across dialog messages)"Olle", "friendly-scanner"
call_idSIP Call-ID header"abc123@host"
src.ipSource IP address (first message)"10.0.0.1"
dst.ipDestination IP address (first message)"10.0.0.2"
stateDialog state machine value"Trying", "InCall", "Failed"
rtp.codecRTP codec name (first stream)"PCMU", "opus"
rtp.ssrcRTP SSRC in hex format (first stream)"0x12345678"

Valid state values: Trying, Ringing, InCall, Completed, Cancelled, Failed, Registered, Expired, Pending, Active, Terminated, Transferring

Numeric Fields

FieldDescriptionUnit
src.portSource port (first message)port number
dst.portDestination port (first message)port number
durationDialog durationseconds (float)
msg_countNumber of SIP messages in dialogcount
pddPost-dial delay (time to first ringing/response)seconds (float)
setup_timeCall setup time (INVITE to 200 OK)seconds (float)
retransmitsTotal retransmit count in dialogcount
rtp.mosMean Opinion Score (worst across streams, E-model R-factor approximation)1.0 - 5.0
rtp.jitterJitter (worst/highest across streams)milliseconds
rtp.lossPacket loss (worst/highest across streams)percentage (0-100)
rtp.packetsTotal RTP packets (sum across all streams)count

Boolean Fields

FieldDescription
rtp.orphanedTrue if any associated RTP stream has no matching SIP dialog
one_wayTrue if one-way audio detected (via diagnosis engine)
nat_mismatchTrue if NAT mismatch detected (Contact/Via IP discrepancy)
no_mediaTrue if no media detected for established call
codec_asymmetryTrue if A and B legs negotiated different RTP codecs
ptime_asymmetryTrue if the two legs use different ptime (packetization interval)
payload_asymmetryTrue if dynamic payload type numbers differ across legs (with the same codec)
duration_asymmetryTrue if one leg’s media duration is materially shorter than the other’s
late_mediaTrue if RTP starts noticeably later than the answering 200 OK

Operators

OperatorApplies ToDescription
==string, numeric, booleanEqual
!=string, numeric, booleanNot equal
<string, numericLess than
>string, numericGreater than
<=string, numericLess than or equal
>=string, numericGreater than or equal
=~stringRegex match (Rust regex syntax)

Notes:

  • Boolean fields only support == and !=.
  • Regex (=~) is not applicable to numeric or boolean fields.
  • Numeric equality uses epsilon comparison for floating-point precision.

Note: String comparisons are case-sensitive. State values must match exactly: 'Completed', 'Failed', 'Trying', 'Ringing', 'InCall', 'Cancelled', 'Terminated'. Use =~ with a case-insensitive regex pattern if you need case-insensitive matching: state =~ '(?i)failed'.

Values

SyntaxTypeExamples
'...' or "..."String'INVITE', "alice"
NumberNumeric (f64)3.0, 100, 0.5
true / falseBoolean (case-insensitive)true, FALSE
'...' with =~Regex'friendly.*scanner', '^1001'

Tip: Regex patterns are compiled once and reused across all messages. Avoid unbounded quantifiers on large captures (e.g., prefer from.user =~ '^100[0-9]$' over from.user =~ '.*100[0-9].*').

Boolean Combinators

KeywordDescription
ANDBoth sides must match (case-insensitive)
OREither side must match (case-insensitive)
NOTNegates the following atom (case-insensitive)

Parentheses ( ) group sub-expressions to override default precedence.

Named Aliases

These preset expressions are available as CLI flags (--problems, etc.) and expand to DSL expressions internally.

Aliases are accepted by --filter directly (--filter codec-asym), as dedicated CLI flags where they exist (--problems, etc.), and as kinds entries in the MCP find_problems tool.

AliasDedicated CLI FlagExpansion
problems--problemsstate == 'Failed' OR one_way == true OR rtp.loss > 2.0 OR rtp.jitter > 50.0 OR nat_mismatch == true OR retransmits > 3 OR pdd > 32.0 OR rtp.orphaned == true OR codec_asymmetry == true OR ptime_asymmetry == true OR payload_asymmetry == true OR duration_asymmetry == true OR late_media == true
slow-setup--slow-setuppdd > 3.0
short-calls--short-callsduration < 5.0 AND state == 'Completed'
one-way--one-wayone_way == true
nat-issues--nat-issuesnat_mismatch == true
codec-asym— (use --filter codec-asym)codec_asymmetry == true
ptime-asym— (use --filter ptime-asym)ptime_asymmetry == true
payload-asym— (use --filter payload-asym)payload_asymmetry == true
duration-asym— (use --filter duration-asym)duration_asymmetry == true
late-media— (use --filter late-media)late_media == true

--filter first tries to resolve its argument as an alias name, then falls back to parsing it as a DSL expression. Both forms below are equivalent:

sipnab -N -I capture.pcap --filter codec-asym
sipnab -N -I capture.pcap --filter "codec_asymmetry == true"

Examples

Basic field matching

method == 'INVITE'
from.user == '1001'
state == 'InCall'

Regex matching

ua =~ 'friendly-scanner'
from.user =~ '^100[0-9]'
call_id =~ 'abc.*@'

Numeric comparisons

pdd > 3.0
rtp.mos < 3.0
rtp.loss > 2.0
duration < 5.0
retransmits > 3
rtp.jitter > 50.0

Boolean fields

one_way == true
nat_mismatch == true
rtp.orphaned == true
no_media == true

Compound expressions

method == 'INVITE' AND rtp.mos < 3.0
from.user =~ '^1001' AND state == 'Failed'
pdd > 3.0 OR retransmits > 5
NOT ua =~ 'friendly-scanner'
(state == 'Failed' OR state == 'Cancelled') AND duration < 1.0

Real-world diagnostic queries

# Find calls with poor quality from a specific extension
from.user =~ '^1001' AND rtp.mos < 3.0

# Find failed registrations from a subnet
method == 'REGISTER' AND state == 'Failed' AND src.ip =~ '^10\.0\.1\.'

# Find short calls that completed (possible robocalls)
duration < 5.0 AND state == 'Completed' AND method == 'INVITE'

# Find calls with audio issues
one_way == true OR no_media == true OR rtp.jitter > 100.0

# Find scanner activity by User-Agent
ua =~ 'sipvicious|friendly-scanner|sipcli'

Real-World Scenarios

Scenario-based examples showing how to combine filter expressions with CLI flags for common operational tasks.

Find calls with poor audio quality

rtp.mos < 3.0 AND state == 'Completed'

Only completed calls – in-progress calls may not have enough RTP data for an accurate MOS calculation.

sipnab -N -I capture.pcap --filter "rtp.mos < 3.0 AND state == 'Completed'" --json

Find all failed international calls

from.user =~ '^\+' AND (state == 'Failed' OR state == 'Cancelled')

The ^\+ regex matches E.164 formatted numbers (international prefix).

Find registration storms

method == 'REGISTER' AND retransmits > 5

High retransmit counts on REGISTER indicate network issues, DNS failures, or server overload. Combine with source IP filtering to isolate a specific endpoint:

method == 'REGISTER' AND retransmits > 5 AND src.ip == '10.0.0.50'

Find calls with NAT issues

nat_mismatch == true AND method == 'INVITE'

NAT mismatch means the Contact header IP/port doesn’t match the actual packet source. This is a common cause of one-way audio and call setup failures behind NAT.

Find one-way audio after call establishment

one_way == true AND duration > 10.0

The duration check avoids false positives during early call setup when RTP hasn’t started flowing yet.

Complex B2BUA debugging

(from.user == '1001' OR to.user == '1001') AND rtp.loss > 1.0

Track a specific user’s calls that have packet loss, regardless of call direction.

Find chatty dialogs (debugging retransmissions)

msg_count > 20 AND method == 'INVITE'

Dialogs with many messages often indicate retransmission issues or complex call flows (transfers, re-INVITEs).

Monitor for SIP trunk failures

dst.ip == '192.168.1.100' AND state == 'Failed' AND method == 'INVITE'

Filter for failures targeting a specific SIP trunk IP.

Note: The filter DSL evaluates against dialogs, not individual messages. A filter like method == 'INVITE' matches dialogs that were initiated with an INVITE, including all subsequent messages in that dialog (180, 200, ACK, BYE, etc.).

RTP Quality & Media Queries

The filter DSL provides direct access to RTP stream metrics. These fields query the aggregate quality of all RTP streams associated with a dialog.

MOS-Based Quality Monitoring

# Find calls with MOS below carrier threshold
sipnab -N -I capture.pcap --filter "rtp.mos < 3.5" --json

# Find calls with excellent quality (verify your codecs are performing)
sipnab -N -I capture.pcap --filter "rtp.mos > 4.0 AND state == 'Completed'"

# Live alert: MOS drops below 3.0 on any active call
sudo sipnab -N -d eth0 --filter "rtp.mos < 3.0" --json | tee /var/log/sipnab/quality.ndjson

MOS values follow the ITU-T G.107 E-model: 4.0+ is toll quality, 3.5-4.0 is acceptable, below 3.0 is noticeable degradation.

Jitter & Packet Loss

# High jitter (network congestion indicator)
sipnab -N -I capture.pcap --filter "rtp.jitter > 50.0" --json

# Packet loss above 1% (codec-dependent threshold)
sipnab -N -I capture.pcap --filter "rtp.loss > 1.0" --json

# Combined: calls where quality is degraded by both jitter AND loss
sipnab -N -I capture.pcap --filter "rtp.jitter > 30.0 AND rtp.loss > 0.5" --report

Jitter is reported in milliseconds (RFC 3550 interarrival jitter algorithm). Loss is a percentage (0.0–100.0).

RTP Stream Investigation

# Find calls with orphaned RTP streams (no matching SDP)
sipnab -N -I capture.pcap --filter "rtp.orphaned == true" --json

# Filter by codec (useful for codec-specific quality analysis)
sipnab -N -I capture.pcap --filter "rtp.codec == 'PCMU'" --json

# Find calls with specific SSRC (trace a specific media stream)
sipnab -N -I capture.pcap --filter "rtp.ssrc == '12345678'" --json

# High packet count calls (long duration or high-rate codecs)
sipnab -N -I capture.pcap --filter "rtp.packets > 10000" --json

One-Way Audio & Media Path Issues

# Detect one-way audio (one direction has zero RTP packets)
sipnab -N -I capture.pcap --filter "one_way == true" --json

# Calls with no media at all (SDP negotiated but no RTP ever flowed)
sipnab -N -I capture.pcap --filter "no_media == true" --json

# NAT mismatch + one-way audio (the classic NAT problem)
sipnab -N -I capture.pcap --filter "nat_mismatch == true AND one_way == true" --report

# One-way audio after call establishment (filter out early-media false positives)
sipnab -N -I capture.pcap --filter "one_way == true AND duration > 10.0" --json

RTCP Extended Reports

When RTCP XR (PT=207) is present in the capture, sipnab extracts VoIP Metrics (RFC 3611 Section 4.7) including:

  • Round-trip delay, end-system delay
  • Signal/noise levels
  • R-factor, external R-factor
  • MOS-LQ, MOS-CQ
  • Burst/gap loss metrics

These metrics appear in the call flow detail panel and in JSON/report output, augmenting the RTP-derived MOS calculation with endpoint-reported quality data.

Combining RTP with SIP Filters

# Failed calls that also had quality issues (correlate signaling + media)
sipnab -N -I capture.pcap --filter "state == 'Failed' AND rtp.mos < 3.0" --json

# Calls from a specific user with packet loss
sipnab -N -I capture.pcap --filter "from.user == '1001' AND rtp.loss > 0.5" --report

# Trunk monitoring: all calls to a specific destination with quality metrics
sipnab -N -I capture.pcap --filter "dst.ip == '10.0.0.100' AND rtp.mos < 4.0" --json

# Registration + quality correlation (find endpoints with both reg and quality issues)
sipnab -N -I capture.pcap --filter "method == 'INVITE' AND retransmits > 3 AND rtp.jitter > 30"

Parser Constraints

  • Maximum parenthesis nesting depth: 50 levels
  • Maximum regex pattern size: 1 MB (1,000,000 bytes)
  • Empty expressions produce a parse error
  • Trailing unparsed input produces a parse error with position
  • Unknown field names produce a parse error
  • Invalid regex patterns produce a parse error