-
Notifications
You must be signed in to change notification settings - Fork 3
Output Formats
sipnab has four output modes: interactive TUI (default), per-message CLI
text (-N), structured NDJSON (--json), and the MCP server.
This page documents the machine-readable formats.
Prerequisites:
--jsonrequires non-interactive mode (-N/--no-tui) — sipnab refuses to start otherwise (exception:--call-reportimplies non-interactive output). NDJSON needs no extra build feature: it is part of the default build. Full flag reference: CLI reference.
--json emits one JSON object per SIP message — newline-delimited, so
each line is independently parseable and the stream is pipe-friendly:
sipnab -N -I capture.pcap --json | jq .Message record (fields with no value drop out rather than reading null):
{
"schema_version": 1,
"timestamp": "2026-06-12T14:03:21.412345+00:00",
"src": "192.0.2.1",
"src_port": 5060,
"dst": "192.0.2.2",
"dst_port": 5060,
"transport": "UDP",
"is_request": true,
"method": "INVITE",
"call_id": "abc123@192.0.2.1",
"from": "\"Alice\" <sip:1001@192.0.2.1>;tag=1c145053",
"to": "<sip:1002@192.0.2.2>",
"contact": "<sip:1001@192.0.2.1:5060>",
"ua": "FreePBX-16",
"sdp": "v=0\r\no=- 1 1 IN IP4 192.0.2.1\r\ns=-\r\nc=IN IP4 192.0.2.1\r\nt=0 0\r\nm=audio 40000 RTP/AVP 0 96\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:96 telephone-event/8000\r\n",
"cseq": { "number": 1, "method": "INVITE" }
}A response record carries is_request: false plus status_code,
reason, and response_context instead of method:
{
"schema_version": 1,
"timestamp": "2026-06-12T14:03:21.598204+00:00",
"src": "192.0.2.2",
"src_port": 5060,
"dst": "192.0.2.1",
"dst_port": 5060,
"transport": "UDP",
"is_request": false,
"status_code": 180,
"reason": "Ringing",
"call_id": "abc123@192.0.2.1",
"from": "\"Alice\" <sip:1001@192.0.2.1>;tag=1c145053",
"to": "<sip:1002@192.0.2.2>;tag=782609",
"cseq": { "number": 1, "method": "INVITE" },
"response_context": "1 INVITE"
}from and to are the full From / To header values — display
name, URI and tags included — not the bare user part. (The aggregated
dialog object below is the one that carries the user part.) Match them
with a regex or a substring test, not with equality.
cseq (the parsed CSeq header — { number, method }) appears on every
message, requests included, so re-requests within a dialog (e.g. two REGISTERs
with CSeq 1 and 2) stay distinguishable. It drops out only when the CSeq
header is absent or unparseable.
contact is the Contact header value when present (routing-critical, omitted
otherwise). sdp is the raw SDP body, emitted only when Content-Type is
application/sdp and the body is valid UTF-8 — it lets a consumer verify the
negotiated media (connection / m= / a=rtpmap) that dynamic-PT decode depends
on. Omitted for non-SDP or non-UTF-8 bodies.
response_context ("<num> <method>", responses only — what the response
answers) is a deprecated alias of cseq, retained for backward compatibility
under schema_version 1. Prefer cseq.
malformed is a list of structural-defect diagnostics, present only when a
message arrives malformed. A well-formed message omits the field. It surfaces crafted
or broken input rather than silently accepting it: missing mandatory headers
(Call-ID/CSeq/From/To/Via), an unparseable CSeq, a Content-Length
larger than the body actually present (truncated/lying length), and control/NUL
bytes in a header. Example: "malformed": ["missing mandatory header: Call-ID"].
extension_headers is every header the fields above do not already carry, in
wire form ("Name: value") and in wire order, one entry per header line.
Call-ID, From, To, Contact, User-Agent and CSeq never appear —
those are fields of their own — and sipnab drops nothing else, so Via,
Max-Forwards and Content-Length join the vendor-specific headers this
exists for. The list keeps duplicates and wire order: three Via lines are
three entries in the order they arrived. A message whose
every header is already a field omits the key rather than sending an empty
array.
input_origin names the capture source that delivered the message — wire,
hep or uprobe — and it is what keeps frame honest. A uprobe read carries a
pointer of the same shape as a capture offset (uprobe:opensips/954#3 beside
capture.pcap#4212), and only one of the two leads back to bytes anyone can
read: wire came from an IP header sipnab observed, hep carries addressing a
remote HEP sender asserted, and uprobe names plaintext sipnab lifted out of a
process's TLS library, which was never on a wire at all. Feeding a uprobe: pointer to --show-frame
therefore gets a refusal that names the process, not a frame. The field drops
out when the message came from no captured packet, because wire there would
claim an observation sipnab never made.
The -N text output and the TUI's raw-message viewer carry the same fact as a
trailing origin=hep or origin=uprobe, and leave an ordinary wire capture
unmarked:
12:57:47.360 0.0.0.0:0 -> 0.0.0.0:0 REGISTER TCP origin=uprobe
schema_version increments on breaking field changes — pin your
consumers to it.
--json-pretty emits the identical message record, indented across several
lines instead of packed onto one. Reach for it when a person reads the output:
sipnab -N -I capture.pcap --json-prettyIt costs you the NDJSON property, and that matters to anything downstream: a
line no longer holds a whole record, so a tolerant concatenated-JSON parser
still reads the stream and a tool that splits on newlines no longer does.
Anything a pipeline consumes wants --json. Both flags need -N /
--no-tui, and passing both gives the indented form — --json-pretty wins
the branch. Both fall silent under --no-cli-print, which suppresses the
per-message stream whatever shape it takes.
One recipe per question, each a complete pipeline — run the one you want, not the whole list.
Keep only the INVITEs:
sipnab -N -I capture.pcap --json | jq 'select(.method == "INVITE")'Find the calls placed by one user. from carries the whole From header, so
test it for a substring rather than comparing it for equality:
sipnab -N -I capture.pcap --json | jq 'select(.from // "" | test("sip:1001@"))'Count the messages per method, to see what a capture holds before reading any of it:
sipnab -N -I capture.pcap --json \
| jq -s 'group_by(.method) | map({method: .[0].method, n: length})'Pull the failed responses (4xx/5xx/6xx) with the reason each one carries:
sipnab -N -I capture.pcap --json \
| jq 'select(.status_code != null and .status_code >= 400)
| {ts: .timestamp, code: .status_code, reason, call_id}'List the distinct Call-IDs seen — this is the list you feed into
--call-report:
sipnab -N -I capture.pcap --json | jq -r '.call_id' | sort -uBuild a response-code histogram, counting how many of each status code the capture holds:
sipnab -N -I capture.pcap --json \
| jq -r 'select(.is_request == false) | .status_code' \
| sort | uniq -c | sort -rn--json prints a line per message. For end-of-run summaries instead,
combine the report flags with
--no-cli-print (which suppresses the
per-message stream but not the report).
For the aggregate report over the whole capture, and nothing else:
sipnab -N -I capture.pcap --report --no-cli-printFor a deep dive into a single call, named by its Call-ID:
sipnab -N -I capture.pcap --call-report 'abc123@192.0.2.1' --no-cli-printThe richer aggregated dialog object — state, timing (PDD / setup /
ring / teardown milliseconds, retransmit counts), sdp_timeline,
streams with jitter and loss, and the four diagnosis flags
(one_way_audio, nat_mismatch, no_media, private_media_address) plus
hints — is one shape produced by a single serializer. One place documents
it, with a full worked example, under
GET /v1/dialogs/{call_id} in the REST API reference.
--json is a per-message stream. --json-dialogs emits the same dialog document
the REST API returns, one compact line per call, after capture completes:
sipnab -N -I capture.pcap --json-dialogs --no-cli-print --quiet \
| jq -c 'select(.state == "Failed") | {call_id, final_status_code, final_status_reason}'Reach for it when the question is which calls failed and why rather than what
did the wire carry. A dialog-level filter like state == 'Failed' selects
dialogs, and --json then emits every message belonging to them — the 100
Trying and the 401 challenge alongside the 488 that actually failed the call, all
carrying the same call_id. Joining those back together is work the per-dialog
form does for you.
final_status_code is the response that decided the outcome, with auth
challenges excluded: a call challenged and then answered reports 200, not the
401. final_status_reason is the phrase from the wire, which RFC 3261 §7.2
leaves as free text — 500 Service Unavailable is legal and common, so match on
the code.
Both fields read INVITE transactions only, and are absent from every other
dialog. A REGISTER rejected 403, an OPTIONS that timed out 408, a
failed SUBSCRIBE — each carries state: "Failed" and no
final_status_code at all, because no INVITE CSeq exists to take a code from.
The recipe above is therefore a call recipe: point it at registration or
keepalive traffic and every row comes back empty. For those, read
signaling_diagnosis instead — final_failure.code carries the status for any
dialog, and registration_failure answers the registration question directly:
sipnab -N -I capture.pcap --json-dialogs --no-cli-print --quiet \
| jq -c 'select(.state == "Failed") |
{call_id, method,
code: (.final_status_code // .signaling_diagnosis.final_failure.code)}'A signaling_diagnosis object sits beside diagnosis when something is wrong
with the signaling rather than the media, and drops out entirely when
the detections found nothing — so a consumer that does not read the field sees
a healthy dialog unchanged. Nine detections run over every dialog, each
naming the messages it drew on as indices into the dialog's own message list:
| Field | Meaning |
|---|---|
final_failure |
The dialog ended on a 4xx/5xx/6xx. Carries code, reason_phrase, and the Reason: (RFC 3326) and Warning: headers when present, which frequently hold the real cause behind a generic status code. |
auth_loop |
Three or more 401/407 challenges with no 2xx. kind is credential_failure when the client answers each challenge and is re-challenged, or silent_drop when it never sends Authorization at all — different faults with different fixes. |
retransmissions |
A request retransmitted with no response, identified by CSeq plus top-Via branch. Reports method, count and span_sec, because "7 INVITEs over 32 seconds" is diagnostic and "retransmissions detected" is not. icmp_cause is present only when the capture also held an ICMP error for the dialog, and carries the network's own words for the silence — see below. |
ack_missing |
A 2xx answer to an INVITE that no ACK followed, once the observation window passed RFC 3261 Timer H (32 s). Carries waited_sec and answer_transmissions — a UAS retransmits its answer until Timer H, so a count above one is the peer agreeing the ACK never arrived. |
abandoned |
The dialog never reached a final response. kind separates the two cases, which are not the same claim: canceled means someone sent a CANCEL, no_final_response means the wait outlived RFC 3261 Timer C (180 s) — a statement about the capture, not a failed call. Carries elapsed_sec. |
post_dial_delay |
INVITE to first provisional response over 11 s, the ITU-T E.721 Table 2 ninety-fifth-percentile target for an international connection. Carries delay_sec, the threshold_sec it exceeded, and the responded_with code that ended the wait. |
registration_failure |
A REGISTER rejected (kind: rejected), or granted less time than it asked for (kind: shortened_expiry, code: 200). Carries requested_expiry_sec and granted_expiry_sec when the messages said. Kept separate from final_failure because "is this phone online?" is a different question from "why did this call fail?". |
icmp_unreachable |
An ICMP or ICMPv6 error quoting one of this dialog's requests. Carries unreachable_endpoint (the host that did not answer), reported_by (the router or host that sent the error — a different machine), description of the ICMP cause, the raw icmp_type / icmp_code bytes, the quoted request's method, an exact errors count, and truncated. |
source_disagreement |
Present only on a run reading from TWO capture sources at once (-d with -L), and only when they told different stories about this call. A HEP mirror reports what the proxy believes it did; the local capture reports what actually left the box, which is why the pair is worth having and why the disagreement is the finding. Carries agreed (messages both witnesses carried -- the denominator), mirror_only (the proxy believes it sent something that never left the box), wire_only (the box did something its own trace does not admit to) and sdp_differs (both carried it, and each advertised different media endpoints -- a rewrite). Each sdp_differs entry names mirror and wire side by side and neither as the truth. |
Every finding carries an evidence array of message indices, and hints
carries one plain-language line per finding.
The first seven fields are always present: null there means "checked, nothing
found". The last two are omitted when absent, because neither can run on
every capture and a null would claim a check that never happened:
icmp_unreachable needs a capture that holds ICMP at all, and
source_disagreement needs two capture sources that both carried the call.
icmp_unreachable is worth singling out because it is the only finding that
reports a fact rather than an inference. The others read the SIP and reason
about what the silence means. This one carries a router's own statement that the
far end is not reachable. Two fields repay attention:
-
errorsis the exact number of ICMP errors the dialog drew, not the number of quotes sipnab retained — a cap bounds retention, and on a real capture 720 of 3,232 errors fell past it. Reporting the retained count would have said "8 times" for a peer that failed thirty.evidencedraws on the retained quotes only, so on a heavily hit dialog it names fewer messages thanerrorscounts. -
unreachable_endpointis the socket to go and look at.reported_byis the device that noticed and said so, usually a working router in the path. Sending an engineer to the reporter wastes the finding.
truncated is true in the ordinary case: RFC 792 guarantees only 8 bytes past
the quoted IP header, so most quotes are a prefix. The field exists so a reader
knows the quote was partial rather than assuming the fields came from a whole
datagram.
The ICMP description is the network's wording, not an instruction, and the
commonest codes send you to different devices. sipnab spells that out in the
hint: port unreachable means the host answered and no service holds that
port, so the fault is the service. administratively prohibited means a
firewall or router ACL refused the packet -- the peer may be perfectly healthy
and the fix is the filter. host unreachable means nothing reached the host at
all, so the capture says nothing about its ports. On one real corpus a single capture
held 433 host-unreachable, 262 administratively prohibited and 63
port-unreachable errors, so one sentence for all three would have been wrong
for most of them.
diagnosis.private_media_address is a warning: the SDP c= line names an
RFC 1918 / RFC 4193 / link-local address, and the peer is not itself private, so
the far end cannot route back to it. It is correct inside one LAN, and correct
behind an SBC, ALG or media proxy that rewrites the SDP downstream — which is
why the hint asks the reader to check which of those they are in.
diagnosis.stun_sdp_mismatch is the evidence that settles it, and it is
omitted on any capture with no STUN in it, so a consumer written before it
existed sees no change. It is never present without private_media_address also
being true: one address, one problem, with STUN as the corroboration rather
than as a second finding.
| Field | Meaning |
|---|---|
reason |
ignored — STUN told the client a public address and the SDP advertised the private one anyway. relay_ignored — a TURN server allocated the client a relayed address and the SDP advertised the private one anyway. unanswered — the client's request drew nothing, so it never learned a public address to advertise. |
client |
The socket the STUN request left from, ip:port. |
server |
The STUN or TURN server it went to. The box to check when the reason is unanswered. |
mapped_address / relayed_address
|
The reachable address the server returned and the client did not use. Absent on unanswered, where there is none — that is the point of it. |
advertised |
The unroutable address the SDP named instead. |
request_count |
Requests sent for that transaction. Above one is a retransmission, which RFC 5389 §7.2.1 sends only on timeout — so it is itself proof the earlier attempts drew silence. |
observed_offset_secs |
Seconds from the start of this dialog to the STUN evidence; negative when the probe came first, which is the ordinary case. Absent when either time is unknown. |
observed_offset_secs exists because the correlation is by client IP alone.
Nothing in a Binding Request names a Call-ID, so a probe from the right address
matches this dialog whether it happened during setup or an hour earlier. Inside
a two-minute window the finding is an observation of this call. Well outside it,
the same finding is an inference that the client's NAT-discovery failure
persisted — usually true, and not the same claim. Past that window the hint
appends a note saying so, and inside it says nothing, because a caveat printed
every time is one nobody reads.
diagnosis.media_relay is context rather than a finding, and is likewise
omitted on any capture with no TURN relay in it. It answers "where did this
call's audio actually go", which for a relayed call nothing else in the capture
answers: client, server, relayed_address (the address the far end really
sends to), channel, peer, and lapsed. lapsed: true is the capture-level
turn_allocation_lapsed finding narrowed to this call's media, and is the
only shape that also adds a hint — a relay doing its job needs no sentence in a
list an operator reads for problems.
A relay_ignored or ignored finding raises private_media_address on its own,
with no observed stream needed: a public mapped or relayed address is proof the
client's traffic reaches the internet. unanswered does so only when the server
it asked is itself public — silence from a STUN server on the same LAN proves
nothing, and flagging it would fire on every LAN-only capture.
retransmissions and icmp_unreachable frequently appear on the same dialog,
and they are not two views of one thing. retransmissions measures how hard the
sender tried before giving up. icmp_unreachable states why nothing came back.
The ICMP fact therefore annotates the retransmission finding rather than
replacing it: sipnab sets retransmissions.icmp_cause to the ICMP description,
the hint stops offering "a one-way path or an unreachable peer" as a guess, and the
count and span_sec survive. Suppressing the finding would have deleted a
measurement to remove a sentence.
With no ICMP in the capture, icmp_cause is absent and the retransmission hint
offers the inference on its own -- the honest answer when nothing better is
available.
The same document is what you get from:
- the REST
GET /v1/dialogs/{call_id}andGET /v1/dialogs/{call_id}/reportendpoints, - MCP tool responses (
get_dialog_reportwithformat: "json"— see the MCP tool reference), and -
SIPNAB_JSONin--on-dialog-exechooks. Note the dialog hook fillsstreams: []and a defaultdiagnosis— it fires on a dialog event, when media analysis for that call may not be complete, so the stream and diagnosis fields are placeholders there rather than populated data.
--on-quality-exec is separate: it passes the stream object on its own,
under its own variable name — SIPNAB_STREAM_JSON, not SIPNAB_JSON.
NDJSON, emitted after capture: one object per STUN or TURN transaction, then one
per TURN allocation. Every line carries a record field naming its kind, so a
consumer never has to infer the shape from which keys happen to be present.
sipnab -N -I capture.pcap --json-stun --no-cli-print \
| jq -c 'select(.record == "transaction" and .responded_at == null)
| {client, server, method_name, request_count}'A transaction carries the hex transaction_id, client and server sockets,
method and method_name, first_request / last_request / responded_at
timestamps, request_count, rtt_ms, and whatever the exchange produced:
mapped_address, relayed_address, peer_address, lifetime_secs,
channel_number, error_code, auth_challenge, software, ice_role,
use_candidate and fingerprint_valid.
responded_at: null is the finding: nothing answered. request_count above one
is a retransmission, which is itself proof the earlier attempts drew silence.
auth_challenge: true says the opposite — the server was reachable and asked
for credentials, so nothing in the network path is at fault.
fingerprint_valid is three-valued on purpose: true verified, false present
and WRONG, null absent — sipnab did not check, and reporting that as false
would accuse a message nobody examined.
A turn_allocation carries client, server, relayed_address,
lifetime_secs, allocated_at, refreshed_at, refreshes, last_activity,
released, channels, unattributed_frames, and the derived lapsed:
sipnab -N -I relay.pcap --json-stun --no-cli-print | jq 'select(.lapsed == true)'lapsed: true means traffic was still crossing the relay after the lifetime the
server last granted had run out, with no Refresh seen in between. A TURN server
tears an allocation down the moment its lifetime lapses and the media stops with
it, mid-call, with no SIP message anywhere to explain it — the signaling
shows a healthy call that went quiet. A deliberate release (a Refresh with
LIFETIME 0) sets released and is never lapsed: the client asked for the
teardown.
channels is what ties the relay to the media. Each entry carries the
channel number, the peer the ChannelBind named (absent when no bind appears
in the capture — the frames still attribute, and the far side is simply not in
this file), bound, frames, bytes, first_seen / last_seen, and the
ssrcs observed inside the frames. Those SSRCs are the join to the stream
list: sipnab unwraps ChannelData and the RTP inside reaches the stream store as
an ordinary stream — but that stream carries phone-to-relay addresses, so
without this nothing said the relay had carried it at all, and a lapsed
allocation could say it lapsed while naming not one packet that died with it.
sipnab -N -I relay.pcap --json-stun --no-cli-print \
| jq -r 'select(.lapsed == true) | .channels[].ssrcs[]'ssrcs counts media only: RTCP relayed on the same channel raises frames and
bytes and contributes no SSRC, because its bytes 8..12 are not a stream in the
sense the stream list means. unattributed_frames counts relayed frames whose
channel fell outside the per-allocation cap. Non-zero means channels holds a
sample of what crossed the allocation, and every text surface says so when it
does. ssrcs_dropped is the same statement one level down.
An ice record appears once, and only on a capture holding ICE connectivity
checks — Binding Requests carrying the PRIORITY and role attributes
RFC 8445 §7.2.1 requires,
which is what tells them from a plain server-reflexive probe to a STUN server:
| Field | Meaning |
|---|---|
checks / checks_answered
|
Connectivity checks seen, and how many drew an answer of either kind. checks_answered: 0 with checks above zero means ICE never completed and the call has no media path — the individual transactions are already in the transaction records above, which is why this is a count and not a second finding. |
nominated |
Candidate pairs a USE-CANDIDATE check nominated and the peer confirmed. Each carries local, remote, role, priority, nominated_at and rtt_ms. This is the ICE analogue of mapped_address: it names the path the media actually took. |
role_conflicts |
Pairs where both agents claimed one role, or where one answered 487 Role Conflict. Each carries a, b, the role both claimed, role_conflict_responses, and resolved. |
nominated_total / role_conflicts_total
|
Exact counts, which stay right past the retention cap that bounds the two lists above. |
resolved: true means the agents nominated a pair between them anyway. ICE
fixed the conflict itself, at the cost of one round trip of repeated checks.
resolved: false means they never nominated anything between them, which makes
the conflict a candidate cause of media that never started.
One JSON object for the whole run, not a line per finding: frames_read,
dialogs_examined and streams_examined are properties of the run rather than
of any finding, and a clean capture must still serialize to something that
states them.
sipnab -N -I capture.pcap --json-analyze --no-cli-print \
| jq '.findings[] | select(.severity == "critical") | {kind, occurrences}'findings ranks worst first. Each carries a stable kind, a severity, an
exact occurrences count, a summary, and an evidence array pointing back at
the capture with Call-IDs, endpoints, timestamps and counts.
complete is the field to read first. It is false when the run did not decode
everything it received — undecodable frames, SIP a port gate discarded, records
a retention cap dropped — and then every count below it is a floor. The
analysis refuses a clean verdict over a capture it could not fully read:
sipnab -N -I capture.pcap --json-analyze --no-cli-print | jq '.complete'--analyze derives nothing new. --analyze aggregates the per-dialog diagnosis
sipnab already computes and the capture-level evidence it already holds. It
ranks and counts them, and adds no judgement of its own.
A different kind of output from everything above. The formats on this page are sipnab's own shapes, read by sipnab and by whatever you write around it. A vCon is an interchange container other people's systems already parse — a conversation archive, a compliance store, an agent that reasons over calls.
sipnab -N -I call.pcap --export-vcon 'CALL-ID' --vcon-out out.jsonOne dialog per container, never a whole capture. sipnab writes it as an OBSERVER: it names itself as a party that took no part in the call, carries the audio inline when the run retained the RTP payload, and states in two places what the capture missed. It signs nothing.
Behind the non-default vcon feature, so a stock build refuses the flag and
says so. The vCon page covers what a consumer may and may not
conclude from one, which matters more here than the field list.
-O <file> writes captured packets, and --pcapng selects PCAP-NG. With TLS
decryption, --pcap-export-mode controls whether decryption secrets
(DSBs) travel with the file for Wireshark. Rotation: --split filesize:N /
--split duration:N, or SIGUSR1 on demand.
pcapng timestamps are nanosecond-resolution, declared via if_tsresol=9
in the Interface Description Block. Files written by sipnab <= 0.5.0
stored nanosecond ticks but omitted that declaration, so other readers
inflated every time value ×1000 (capinfos reporting year 58484).
scripts/repair_pcapng_tsresol.py
repairs such old captures in place without touching packet data.
- cli-reference.md — every output flag
- examples.md — NDJSON-to-jq pipeline recipes
Website · Repository · Issues · Generated from docs/ — edit there, not here.
Getting started
- Installation
- Cookbook
- Real World Captures
- Troubleshooting
- Tuning Capture
- Encapsulations
- Rtpengine Relays
- Backers
Using the TUI
CLI & automation
- CLI Reference
- Filter DSL
- Output Formats
- SIP Response Codes
- SIP Methods
- SIP Header Fields
- SIP Parameters
- SIP Lint Rules
- MOS and Codecs
Configuration
Integrations (API & MCP)
- REST API
- Prometheus Metrics
- Authentication
- MCP
- MCP Deployment
- MCP Across an Estate
- MCP Tools
- MCP Protocol
- Uprobe Walkthrough
- Capture SIP over TLS
- WASM Plugins
- vCon Export
- vCon Capture Stack
Development & internals
- Internals Index
- Internals Subsystem Guide
- Internals Invariants
- Internals Testing
- Internals Walkthroughs
- Internals Build CI Release
- Internals Profiling
- Internals Domain Primer
- Library API
- Benchmarks
- Fault Model
- Architecture
- Internals Threading
- Internals TUI Testing
- Internals Zero Copy Payloads
- Internals Uprobe Capture
- Internals Rtpengine Control Plane
- Internals vCon Exporter