Filter DSL - NormB/sipnab GitHub Wiki
Quick start:
sipnab -N -I capture.pcap --filter "state == 'Failed'"shows the failed calls in a capture.sipnab -N -I capture.pcap --problemssweeps for every kind of problem at once.- Keep the
-N. Without it, the TUI opens instead.
sipnab includes a declarative, non-Turing-complete filter language. It matches SIP dialogs and their associated RTP streams.
- You pass expressions through either:
- the
--filterCLI flag, or - the
expressionkey in the[filter]config section.
- the
- The Diagnostic Aliases CLI flags (
--problems,--slow-setup, and so on) expand to the named aliases documented below.
A filter selects dialogs, and every output that lists dialogs honors it:
- The per-message stream (the default output,
--json,-T) emits only messages belonging to matching dialogs. -
--json-dialogsemits one line per matching dialog. -
--reportprints only matching dialogs.- The RTP tables beneath the dialog table hold only the streams linked to them.
- With no filter, it still lists every stream the capture holds, orphans included.
-
--call-report <CALL-ID>is not narrowed. It names one call, and a lookup by name is not a listing.
The --since/--until time window
joins the filter.
- The run's filter is
(<your filter>) AND time >= '<since>' AND time < '<until>'. -
--json-analyzereports it in that form.
Each entry below is one complete expression. --filter takes exactly one.
These are catalogs to pick a line from. They are not blocks to copy whole.
method == 'INVITE'from.user == '1001'state == 'InCall'
ua =~ 'friendly-scanner'from.user =~ '^100[0-9]'call_id =~ 'abc.*@'
pdd > 3.0rtp.mos < 3.0rtp.loss > 2.0duration < 5.0retransmits > 3rtp.jitter > 50.0rtp.packets > 10000
one_way == truenat_mismatch == trueno_media == truecodec_asymmetry == truelate_media == true
method == 'INVITE' AND rtp.mos < 3.0from.user =~ '^1001' AND state == 'Failed'pdd > 3.0 OR retransmits > 5NOT ua =~ 'friendly-scanner'(state == 'Failed' OR state == 'Canceled') AND duration < 1.0
The DSL has no comment syntax, so this page labels each query in prose. A #
line handed to --filter is a parse error, not a note.
- 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 =~ '^198\.51\.100\.' - 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'
Note: The filter DSL evaluates against dialogs, not individual messages. A filter like
method == 'INVITE'matches dialogs that opened with an INVITE. That includes all subsequent messages in that dialog (180, 200, ACK, BYE, etc.).
Filter-DSL recipes, one per real-world task. Each one is a complete command line.
- Swap
-I capture.pcapfor-d eth0(as root) to run any of them against live traffic. - For broader task recipes beyond the DSL, see the Cookbook and Troubleshooting.
sipnab -N -I capture.pcap --filter "rtp.mos < 3.0 AND rtp.packets > 0 AND state == 'Completed'" --jsonAn unmeasured rtp.mos is unknown. An unknown value matches no threshold in
either direction.
- A dialog that carried no RTP never matches
rtp.mos < 3.0. - So
rtp.packets > 0no longer guards against a0.0.
This page used to say the clause was mandatory. A signaling-only dialog
reported 0.0 and satisfied the threshold. That was true before the fix and
is not true now.
The clause is still worth keeping for a different reason.
- It excludes calls that carried too few packets for the E-model to mean anything.
- That is a judgment about sample size. It is not a guard against a phantom zero.
- To select the calls that carry no media, write
rtp.packets == 0orno_media == trueinstead.
state == 'Completed' keeps only completed calls. In-progress calls may not
have enough RTP data for an accurate MOS calculation.
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.
sipnab -N -I capture.pcap --filter "one_way == true AND duration > 10.0" --reportThe duration check avoids false positives during early call setup, when RTP hasn't started flowing yet.
For calls where no RTP ever flowed at all, use no_media == true instead.
sipnab -N -I capture.pcap --filter "nat_mismatch == true AND method == 'INVITE'" --jsonNAT mismatch means RTP reached the capture point from an address that no SDP in the dialog advertised. That is what a NAT rewriting the media source looks like from the wire.
- It is a common cause of one-way audio, and of call setup failures behind NAT.
- Combine it with the
one_wayfield to catch the classic case.
sipnab -N -I capture.pcap --filter "rtp.jitter > 50.0 OR rtp.loss > 1.0" --json- Jitter arrives in milliseconds (RFC 3550 interarrival jitter algorithm). High values indicate network congestion.
- Loss is a percentage (0.0-100.0). Its acceptable thresholds are codec-dependent.
sipnab -N -I capture.pcap --filter "from.user =~ '^\+' AND (state == 'Failed' OR state == 'Canceled')" --jsonThe ^\+ regex matches E.164 formatted numbers (international prefix).
sipnab -N -I capture.pcap --filter "method == 'REGISTER' AND retransmits > 5" --reportHigh retransmit counts on REGISTER indicate:
- network issues,
- DNS failures,
- server overload.
Append AND src.ip == '192.0.2.50' to isolate a specific endpoint.
sipnab -N -I capture.pcap --filter "ua =~ 'sipvicious|friendly-scanner|sipcli'" --jsonsipnab -N -I capture.pcap --filter "duration < 5.0 AND state == 'Completed' AND method == 'INVITE'" --jsonsipnab -N -I capture.pcap --filter "dst.ip == '198.51.100.100' AND state == 'Failed' AND method == 'INVITE'" --reportFilter for failures targeting a specific SIP trunk IP.
sipnab -N -I capture.pcap --reportOrphaned streams have no matching SIP dialog or SDP.
- No dialog filter can select them, and the DSL offers no field for them.
- The boolean-field note above covers why
rtp.orphanedis a parse error.
Where to find them:
- The
--reportoutput carries an "Orphaned Streams" section. - The REST
API answers the same question at
/v1/streams?orphaned=truewhen you run sipnab with--api.
Orphans usually mean one of these:
- RTP arriving on unexpected ports (check your NAT/ALG config),
- calls that started before capture began.
sipnab -N -I capture.pcap --filter "(from.user == '1001' OR to.user == '1001') AND rtp.loss > 0.5" --reportTracks a specific user's calls that have packet loss, regardless of call direction.
sipnab -N -I capture.pcap --filter "msg_count > 20 AND method == 'INVITE'" --jsonDialogs with many messages often indicate:
- retransmission issues,
- complex call flows (transfers, re-INVITEs).
Select every dialog carrying one codec, for codec-specific quality analysis:
sipnab -N -I capture.pcap --filter "rtp.codec == 'PCMU'" --jsonTrace a single media stream by its SSRC.
-
rtp.ssrccompares against the SSRC rendered as0x-prefixed lowercase hex. - So the literal needs the
0xprefix, or it matches nothing:
sipnab -N -I capture.pcap --filter "rtp.ssrc == '0x12345678'" --jsonWhen a capture carries RTCP XR (PT=207), sipnab decodes the VoIP Metrics block (RFC 3611 Section 4.7). It keeps the block beside the stream the block names. The block holds:
- Round-trip delay and end-system delay
- Signal level, noise level and residual echo return loss
- R-factor and external R-factor
- MOS-LQ and MOS-CQ
- Burst and gap loss densities and durations
- Jitter buffer nominal, maximum and absolute maximum delay
Every figure above belongs to the endpoint that sent it, not to sipnab.
- The TUI Stream Detail view shows them in a Reported by Far End (RTCP XR) section of their own, below everything sipnab measured.
- Nothing there feeds the MOS, jitter or loss sipnab computes.
- If the two disagree, that is the finding. It is not a conflict to resolve.
The reasons:
- RTCP carries no authentication.
- A mid-path capture watches a different path segment than the endpoint reports on.
- This is the same rule the reception-report figures follow. See mos-and-codecs.md.
RFC 3611 reserves the value 127 for "this parameter is unavailable". It does this on all seven of its single-byte quality fields:
- the R-factor and the external R-factor,
- both MOS fields,
- the signal and noise levels,
- the residual echo return loss.
sipnab renders each of those as n/a. A raw render would put:
- an R-factor of 127 on a scale that stops at 100,
- a MOS of 12.7 on a scale that stops at 5.0.
No surface reads any other XR block type yet.
- The parser turns three of them into typed values: Loss RLE, Duplicate RLE and Receiver Reference Time.
- It records Packet Receipt Times, DLRR and Statistics Summary by block-type number alone.
--json, --report, the REST API and the Prometheus exporter carry sipnab's own
measurements only.
No filter DSL field matches an XR value.
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.
All 42 addressable fields, organized by type.
- They answer to 43 names.
response_codeis also spelledfinal_status_code. An alias is not a second field. - Beyond these,
header.<name>reads any SIP header by a name you choose. See Any header by name.
| Field | Description | Example Values |
|---|---|---|
from.user |
User part of the SIP From header |
"1001", "alice"
|
to.user |
User part of the SIP To header |
"1002", "bob"
|
from.host |
Host of the From URI, without the port |
"carrier-a.example", "203.0.113.5"
|
to.host |
Host of the To URI, without the port | "pbx.example" |
from.uri |
The From URI as written, URI parameters included, without the display name or tag
|
"sip:[email protected];user=phone" |
to.uri |
The To URI as written | "sip:[email protected]" |
ruri |
Request-URI of the request that opened the dialog; empty when the capture began on a response | "sip:[email protected]" |
pai.user |
User part of the opening request's P-Asserted-Identity; empty when it has none | "+15559876543" |
side |
Where the opening request went relative to the PBX addresses in [pbx] addresses: inbound (sent to the PBX), outbound (sent by it), internal (between two of its addresses), or empty |
"inbound", "outbound"
|
endpoint |
The name the run gives the source or destination address, from the hosts file, --names files and [names.manual]; matches when either end's name matches. Reverse DNS is never used here |
"pbx-1", "carrier-a-gw"
|
method |
SIP request method |
"INVITE", "REGISTER", "BYE"
|
ua |
User-Agent header (first non-empty across dialog messages) |
"Olle", "friendly-scanner"
|
call_id |
SIP Call-ID header | "abc123@host" |
payload |
Raw message content โ matches when ANY message in the dialog contains/matches the value (payload grep) |
"X-Custom-Header", "sipsak"
|
src.ip |
Source IP address (first message) | "192.0.2.1" |
dst.ip |
Destination IP address (first message) | "192.0.2.2" |
state |
Dialog state machine value |
"Trying", "InCall", "Failed"
|
response_class |
IANA class of the final response |
"2xx", "4xx", "5xx", "6xx"
|
rtp.codec |
RTP codec name (matches if ANY linked stream matches) |
"PCMU", "opus"
|
rtp.codecs |
Every codec the dialog's streams used, distinct, sorted and comma-joined; empty with no streams | "PCMU,telephone-event" |
rtp.ssrc |
RTP SSRC in hex format (matches if ANY linked stream matches) | "0x12345678" |
Valid state values: Trying, Ringing, InCall, Completed, Canceled, Failed, Redirected, Registered, Expired, Pending, Active, Terminated, Transferring
payloadvs-e/--match:
- The
payloadfield matches per dialog (true if any message matches).- The
-e/--matchflag is the per-message match-expression.
- It selects the matching messages and then follows the dialog.
- sipnab emits every message after the first match in that dialog too.
- Use
-efor grep-style streaming output.- Use
payloadinside a larger--filterexpression.
| Field | Description | Unit |
|---|---|---|
src.port |
Source port (first message) | port number |
dst.port |
Destination port (first message) | port number |
duration |
Dialog duration | seconds (float) |
msg_count |
Number of SIP messages in dialog | count |
response_code |
Final response code of the dialog's INVITE, or of the request that opened a dialog without one (REGISTER, PUBLISH, SUBSCRIBE โฆ). Also spelled final_status_code, which is the name the JSON, the schema and the MCP answers return. Unknown while the call is in progress, so a ringing dialog matches no comparison at all |
100-699 |
pdd |
Post-dial delay (time to first ringing/response) | seconds (float) |
setup_time |
Call setup time (INVITE to 200 OK) | seconds (float) |
retransmits |
Total retransmit count in dialog | count |
rtp.mos |
Mean Opinion Score (worst across streams, E-model R-factor approximation) | 1.0 - 5.0, unknown with no RTP |
rtp.jitter |
Jitter (worst/highest across streams) | milliseconds, unknown with no RTP |
rtp.loss |
Packet loss (worst/highest across streams) | percentage (0-100), unknown with no RTP |
rtp.packets |
Total RTP packets (sum across all streams) | count (0 is a real count, never unknown) |
An unmeasured value matches no comparison.
pdd,setup_time,rtp.mos,rtp.jitterandrtp.lossare unknown when the capture holds nothing to measure them from:
- no RTP for the media fields,
- no captured INVITE or 200 OK for the timing ones.
- An unknown matches no numeric operator, including
!=. It is not "different from 3.0". It is unknown.- This is the rule SQL uses for
NULL, for the same reason.Reading them as
0instead would put them below every threshold anyone would type.
- Then
rtp.mos < 3.0would select every call carrying no media at all.- On one real trunk capture, that is 2292 of 2311 dialogs, rather than the 2 genuine ones.
- A saved filter carrying an
AND rtp.packets > 0guard is redundant rather than wrong.To select the calls that carry no media, ask for that directly:
rtp.packets == 0is a real count of zero.no_mediais the diagnosis itself.Pair any
rtp.*threshold withrtp.packets > 0to ask about calls that actually had media.
The IANA registry
groups every response code into six classes. response_class is that
class. Ask by number or by the registry's own name. They mean the same thing:
| Class | Number | Name |
|---|---|---|
| Provisional | 1xx |
provisional |
| Successful | 2xx |
successful, success
|
| Redirection | 3xx |
redirection, redirect
|
| Request Failure | 4xx |
request failure, client failure
|
| Server Failure | 5xx |
server failure |
| Global Failures | 6xx |
global failures, global failure
|
sipnab -N -I capture.pcap --filter "response_class == 'server failure'"failure means all three failure classes: 4xx, 5xx and 6xx together.
- That is what the word means at a console.
-
failuresandfailedare the same question. - Naming one class is how you ask for one class:
sipnab -N -I capture.pcap --filter "response_class == 'failure'"Matching is membership, not string equality, so != stays honest.
- A 503 is a server failure by every spelling.
- So
response_class != 'Server Failure'is false for it. - Case, hyphens and underscores are typing variants, not different questions.
response_code >= 500 AND response_code < 600 says the same as '5xx'. It
says it as arithmetic. A bound wrong by one is silent.
A dialog is never 1xx, and that is not an omission.
- This is the class of the FINAL response.
- A provisional one is by definition not final.
- A call sitting on 183 has no outcome yet. So it has no class at all, the same as a call that has heard nothing.
- Reach for
statethere instead.
This is not the classification on the response-code reference.
- That page groups codes by what they mean for a CALL, and it is narrower.
- There, a challenge, a cancellation and a decline are each not failures. Each tells the operator to do something different.
-
response_classis the numeric registry and nothing else. - Neither one borrows the other's name, deliberately.
state answers what happened. response_code answers why. They are not the
same question.
Every one of 403, 404, 408, 486, 503 and 603 is state == 'Failed'. They have
different owners:
- a 403 is authorization,
- a 408 is a timer,
- a 486 is the callee,
- a 503 is capacity upstream.
Asking "which release cause dominates on this trunk in the last ten minutes" needs the code. Asking it by class needs a numeric field rather than a set of names:
Every server-side failure, whatever the specific cause:
sipnab -N -I capture.pcap --filter "response_code >= 500 AND response_code < 600"The one cause you suspect, on the carrier you suspect:
sipnab -N -I capture.pcap --filter "response_code == 503 AND dst.ip == '198.51.100.7'"A call still in progress has no final response. It matches NOTHING:
- not
response_code < 400, - not
>= 400, - not
== 0.
A zero default would sweep every ringing call into the success bucket.
Auth challenges follow
final_status_code.
- A call challenged and then answered reports 200, not the 407. The challenge was intermediate.
- A call only ever challenged reports the challenge. Then it was the outcome.
| Field | Description |
|---|---|
one_way |
True if one-way audio detected (via diagnosis engine) |
nat_mismatch |
True if RTP arrived from an address no SDP in the dialog advertised โ see the note below |
no_media |
True if an answered call negotiated media that never arrived โ see the note below |
codec_asymmetry |
True if A and B legs negotiated different RTP codecs |
ptime_asymmetry |
True if the two legs use different ptime (packetization interval) |
payload_asymmetry |
True if dynamic payload type numbers differ across legs (with the same codec) |
duration_asymmetry |
True if one leg's media duration is materially shorter than the other's |
late_media |
True if RTP starts noticeably later than the answering 200 OK |
What
nat_mismatchcompares.
- A dialog advertises a media address on each side:
- the caller's in the offer,
- the callee's in the answer.
- A re-INVITE may add more.
nat_mismatchis true when a stream carries a source address that none of them named.It uses set membership, not equality against one
c=line.
- A healthy two-way call sources RTP from two different advertised addresses.
- So comparing each stream against a single
c=would flag one direction of every call in the capture.It compares addresses only, never ports. NAT and RTP proxies rewrite the port on ordinary calls without breaking anything.
What
no_mediarequires. All four:
- the far end answered an offer,
- the dialog reached a 2xx,
- at least one exchange described media that should carry audio,
- no RTP arrived for the call.
"Should carry audio" excludes the forms that ask for silence:
a=inactive,m=port0,- a black-holed
c=0.0.0.0,- non-RTP transports such as T.38
m=image ... udptl.So a call held for its whole life is not reported as a media failure.
no_mediais silent on a capture that holds no RTP.
- A signaling-only tap, a HEP feed or
--no-rtpcannot show that one call had no audio. No call in it has any.- On such a capture, the flag stays false by design. It does not select every answered dialog.
- Pair
no_mediawith the knowledge of where your tap sits. If media does not traverse it, the question is unanswerable, and sipnab declines to answer it.
rtp.orphanedis a parse error, not a field.
- It would ask whether a stream belonging to this dialog belongs to no dialog.
- A stream counts as an orphan exactly when no dialog claims it. So the two halves exclude each other.
- The field would match nothing on any capture, while
NOT rtp.orphanedwould match everything.- Rejecting it at the parser beats a silent falsehood.
Orphaned media is real and still reachable. Both of these model streams rather than dialogs:
- the "Orphaned Streams" section of
--report,- the REST API's
/v1/streams?orphaned=true.
| Field | Description | Value |
|---|---|---|
time |
The instant of the dialog's first message | a quoted RFC 3339 instant, such as '2026-10-09T10:00:00Z'
|
time takes <, <=, >, >= and ==, and nothing else.
-
!=,=~andin_subnetare parse errors. - So is a literal that is not a quoted RFC 3339 instant.
- sipnab converts an offset other than
Z. So'2026-10-09T12:00:00+02:00'is the same instant as'2026-10-09T10:00:00Z'.
It is the instant that these bound:
- the REST
/v1/dialogsafter/beforewindow, - the MCP
search_by_timetool, - the
--since/--untilflags.
Write a window half-open, >= at the start and < at the end. Then it selects
what those select:
time >= '2026-10-09T10:00:00Z' AND time < '2026-10-09T11:00:00Z'
Define a filter once in the config file's [filters]
table. Use it by name, as @name, wherever sipnab takes a filter expression.
- A name can appear inside a larger expression and inside another named filter.
- An expression can reference the built-in aliases the same way:
[filters]
carrier-a = "src.ip in_subnet '203.0.113.0/24'"
sales = "@carrier-a AND to.user =~ '^8[0-9]{3}$'"
bad-sales = "@sales AND @problems"sipnab -N -I capture.pcap --config sipnab.toml --filter "@sales AND method == 'INVITE'"- sipnab replaces
@namewith the named filter's expression in parentheses. So each definition keeps the precedence it has as written. - An
@inside a quoted string is text, not a reference. - sipnab resolves every name when it starts. See
[filters]for what it refuses. - sipnab refuses a filter given at run time that names something undefined.
- This applies on the command line or through REST or MCP.
- sipnab lists the defined names.
header.<name> compares the value of a SIP header you name, as a string.
- It matches when any header of that name satisfies the comparison. That header can be on any message sipnab holds for the dialog.
- sipnab compares a header that appears on several lines one line at a time.
- It matches header names without regard to case.
- It expands a compact form first, so
header.freadsFrom.
header.X-Trunk == 'north-east'
header."P-Asserted-Identity" =~ 'sip:alice@'
Write the name bare. Use quotes when it holds a character that would end a bare name. Either way, it must be an RFC 3261 token of at most 256 bytes (see Parser constraints).
| Operator | Applies To | Description |
|---|---|---|
== |
string, numeric, boolean, time
|
Equal |
!= |
string, numeric, boolean | Not equal |
< |
string, numeric, time
|
Less than (for time, earlier) |
> |
string, numeric, time
|
Greater than (for time, later) |
<= |
string, numeric, time
|
Less than or equal |
>= |
string, numeric, time
|
Greater than or equal |
=~ |
string | Regex match (Rust regex syntax) |
in_subnet |
address-valued string | The left address falls inside the right CIDR block |
Notes:
- Boolean fields only support
==and!=. - Regex (
=~) is not applicable to numeric or boolean fields. -
in_subnettakes a CIDR literal on the right and an address-valued field on the left (src.ip,dst.ip).- It uses the same rule as
--hep-allow. - So IPv4, IPv6 and IPv4-mapped IPv6 addresses (RFC 4291 section 2.5.5.2) all compare as the allowlist compares them.
- A bare address with no prefix reads as a host route.
- An unparseable address or block matches nothing, rather than matching everything.
- It uses the same rule as
- Numeric equality uses epsilon comparison for floating-point precision.
- For computed values (
duration,pdd,rtp.mos), prefer range operators (>=,<) over==. - An exact match on a derived float rarely hits.
- For computed values (
Note: String comparisons are case-sensitive.
statevalues must exactly match one of the 13 values listed under String Fields above ('Failed', not'failed').- Use
=~with a case-insensitive regex pattern if you need case-insensitive matching:state =~ '(?i)failed'.
| Syntax | Type | Examples |
|---|---|---|
'...' or "..."
|
String |
'INVITE', "alice"
|
| Number | Numeric (f64) |
3.0, 100, 0.5
|
true / false
|
Boolean (case-insensitive) |
true, FALSE
|
'...' with =~
|
Regex |
'friendly.*scanner', '^1001'
|
'...' after time
|
RFC 3339 instant |
'2026-10-09T10:00:00Z', '2026-10-09T12:00:00+02:00'
|
Inside a quoted string, a backslash escapes the next character.
- So the delimiter itself is expressible.
\'and\"yield a literal quote ('it\'s',"say \"hi\""). - Every other
\xsequence, including\\, stays verbatim, backslash and all. - So regex metacharacters and classes still reach the engine unchanged
(
from.user =~ '\d\d\d\d',payload =~ 'example\.com', and'\\'for a literal backslash in a regex).
Tip: sipnab compiles each regex once and reuses it across all messages. Avoid unbounded quantifiers on large captures (e.g., prefer
from.user =~ '^100[0-9]$'overfrom.user =~ '.*100[0-9].*').
| Keyword | Description |
|---|---|
AND |
Both sides must match (case-insensitive) |
OR |
Either side must match (case-insensitive) |
NOT |
Negates the following atom (case-insensitive) |
Parentheses ( ) group sub-expressions to override default precedence.
These preset expressions are available as:
- dedicated CLI flags where they exist (
--problems, etc.), - shorthand to
--filter(e.g.--filter codec-asym), - the value of any MCP tool's
filterargument, - the value of the REST API's
filterquery parameter (e.g./v1/dialogs?filter=codec-asym), -
kindsentries for the MCPfind_problemstool.
They expand to DSL expressions internally, through one function. They use the thresholds the run's flags and config set.
| Alias | Dedicated CLI Flag | Expansion |
|---|---|---|
problems |
--problems |
state == 'Failed' OR one_way == true OR rtp.loss > 5.0 OR rtp.jitter > 50.0 OR nat_mismatch == true OR retransmits > 3 OR pdd > 11.0 OR codec_asymmetry == true OR ptime_asymmetry == true OR payload_asymmetry == true OR duration_asymmetry == true OR late_media == true |
slow-setup |
--slow-setup |
pdd > 11.0 |
short-calls |
--short-calls |
duration < 3.0 AND state == 'Completed' |
one-way |
--one-way |
one_way == true |
nat-issues |
--nat-issues |
nat_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 the argument as an alias name. If no alias
matches, it parses the argument as a DSL expression.
The alias and the expression it expands to select the same dialogs. So these are equivalent:
sipnab -N -I capture.pcap --filter codec-asymsipnab -N -I capture.pcap --filter "codec_asymmetry == true"
Pick whichever reads better in the command you are writing.
The dedicated flag, the --filter <alias> spelling and the MCP kinds entry
are three names for one expression. These all select the same dialogs:
sipnab --short-callssipnab --filter short-callssipnab --filter "duration < 3.0 AND state == 'Completed'"
Combining flags:
- Combining several flags ORs their expansions together.
- If
--filteris also present, its expression is ANDed with that result. - For example,
--problems --filter "from.user == '1001'"keeps problem calls from user 1001.
--nat-issues / --filter nat-issues selects the calls whose RTP arrived from
an address the SDP never advertised. The boolean-field note above says:
- what that means,
- what it deliberately ignores.
-
Expression nodes: 1024 (
MAX_EXPRESSION_NODES).- This is the bound a caller is most likely to reach.
- The reason it exists is not tidiness. Without it, a deeply repetitive expression drove the parser into a stack overflow. That aborts the process rather than returning an error.
-
validate_filterrefuses a filter over the cap, and so does every flag that takes one.
- Maximum parenthesis nesting depth: 50 levels (
MAX_NESTING_DEPTH).- It bounds recursion in the parser itself.
- How large the resulting tree may grow is the separate node bound above.
-
Header name length: 256 bytes (
MAX_HEADER_NAME_LEN), for theheader.<name>field.- Whoever sent the message chose the header name. So sipnab bounds the field that names one, like every other input.
-
FilterExpr::parserefuses a longer name and states the limit.
- 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
-
cli-reference.md โ the
--filterflag and the dedicated diagnostic-alias flags - keybindings.md โ the TUI filter dialog (F7), which compiles its fields down to this DSL
- examples.md โ recipes that put these filters to work