-
Notifications
You must be signed in to change notification settings - Fork 3
Library API
sipnab is primarily a CLI/TUI tool, but its analysis engine is a
published Rust crate. The curated public API is re-exported at the crate
root. Anything under a #[doc(hidden)] module (cli, tui, privilege,
…) is binary-internal and carries no semver guarantee.
[dependencies]
sipnab = { version = "0.5", default-features = false, features = ["native"] }| Item | What it is |
|---|---|
PcapReader |
Pure-Rust pcap/pcapng reader (iterator of packets) |
decompress_capture |
Transparent, bounded gunzip for gzip-compressed captures |
sip::parser::parse_sip / parse_sip_bytes
|
Parse raw bytes → SipMessage
|
capture::parse::parse_packet |
Decode a captured Packet → ParsedPacket
|
rtp::parser::parse_rtp_header |
Parse an RTP header |
sip::sdp::parse_sdp |
Parse an SDP body |
DialogStore / StreamStore
|
Capped, indexed dialog / RTP-stream stores (both Debug) |
SipMessage, SipDialog, RtpStream
|
Core value types |
SipMethod, DialogState
|
The method and dialog-state enums those types report in. Both #[non_exhaustive] — see Error handling
|
StreamKey |
What a StreamStore indexes by: SSRC plus the source and destination sockets, so one SSRC arriving on two paths stays two streams |
FilterExpr |
Compiled filter-DSL expression |
estimate_mos |
E-model MOS from jitter/loss/codec |
PcapReader yields the capture file's own records. parse_packet decodes a
Packet, which is the same frame plus the facts only the caller can supply —
the timestamp at full resolution, the captured-vs-wire lengths, and the link
type, which a multi-interface pcapng varies per packet rather than per
file. Building that bridge is the one step between a path and a parsed frame:
use sipnab::PcapReader;
use sipnab::capture::Packet;
use sipnab::capture::parse::parse_packet;
fn payload_bytes(path: &str) -> Result<usize, Box<dyn std::error::Error>> {
let data = std::fs::read(path)?;
let mut total = 0;
for pkt in PcapReader::new(&data)? {
let timestamp = chrono::DateTime::from_timestamp(
pkt.timestamp_secs as i64,
pkt.timestamp_usecs * 1000,
)
.unwrap_or_default();
let caplen = pkt.data.len();
let origlen = pkt.orig_len as usize;
let link_type = pkt.link_type as i32;
let frame =
Packet::new(timestamp, pkt.data, caplen, origlen, pkt.interface, link_type);
// A capture holds frames sipnab does not decode — ARP, a truncated
// header, an unsupported link type. That is an `Err` per frame, not a
// failed read: skipping it silently is how a partial result comes to
// look like a clean one.
if let Ok(parsed) = parse_packet(&frame) {
total += parsed.payload.len();
}
}
Ok(total)
}The parsing and capture entry points return typed, matchable error
enums — not anyhow::Result. All three are #[non_exhaustive]
thiserror enums (std::error::Error), so you can propagate them into
anyhow/Box<dyn Error> unchanged, or match on their variants.
| Function | Returns |
|---|---|
parse_sip, parse_sip_bytes, parse_rtp_header, parse_sdp
|
Result<_, ParseError> |
parse_packet, PcapReader::new
|
Result<_, CaptureError> |
config::Config::load, address/rule/CIDR parsing |
Result<_, Error> |
-
ParseError— protocol parsers. Variants includeEmpty { what },TooShort { what, need, got },InvalidUtf8 { what },MissingCrlf,NotSip { line },InvalidStatusCode { code },BadRtpVersion { version },SdpMissingVersion,BadSdpVersion { version }. -
CaptureError— capture-file / packet decode. Variants includeTooShort { what, need, got },UnsupportedLinkType(i32),EncapTooDeep { kind, limit },NotIp { what },NoTransport,Icmp,NetMonFormat,UnknownFormat { magic }. -
Error— config/CLI/validation surface.ConfigRead/ConfigParsechain the underlyingstd::io::Error/toml::de::Errorvia#[source].
Match on variants rather than message text:
use sipnab::ParseError;
use sipnab::rtp::parser::parse_rtp_header;
match parse_rtp_header(&[0x80, 0x00]) {
Err(ParseError::TooShort { need, got, .. }) => {
eprintln!("need {need} bytes, got {got}");
}
Ok(header) => { /* use header */ }
Err(e) => eprintln!("parse failed: {e}"),
}Because every one of these enums is #[non_exhaustive], a downstream
match must include a wildcard arm — sipnab can add a variant in a
minor release without it being a breaking change. Twenty-four other public
enums (TransportProto, RtcpPacket, FraudType, CipherSuite, …) carry
#[non_exhaustive] for the same reason, as do three public structs
(BurstGapAnalysis, RemoteReceptionReport, RemoteVoipMetrics), which a
downstream literal therefore cannot construct.
Four of the six programs in examples/ take the library path:
they run against a real capture and print a real answer. Each exists because
it demonstrates something a doctest on this page structurally cannot — state
accumulated across a whole file. (The other two, uprobe_discover and
uprobe_sock_offsets, report on the host rather than a capture.)
| Example | Run it against | What it is for |
|---|---|---|
call_summary.rs |
any capture with SIP | The path above carried to its end: file → frame → message → call. Dialog state is what you learn from every packet in order, so one INVITE in a doctest can only ever print Trying. |
rtp_quality.rs |
rtp-protocol.pcap, then invite-opus-bye.pcap
|
Jitter, loss and MOS per stream — none of which exist in a packet. The second capture shows StreamStore declining to score a stream whose clock rate no a=rtpmap grounded. |
filter_dialogs.rs |
any capture, plus an expression |
FilterExpr::parse once, then select_dialogs — the join across both stores that --report and --json-dialogs also go through, and the reason those two never disagree about a capture. |
export_vcon.rs |
any capture with SIP | The same four steps plus a fifth: one reconstructed call out as a vCon container a conserver accepts. Needs --features vcon, which the default build omits. |
cargo run --features native --example call_summary -- tests/pcap-samples/register-invite-reinvite-bye.pcapOne difference bites before any other: the binary defaults to
--portrange 5060-5061, and the library applies no port policy at all.
Nothing in parse_packet or parse_sip filters by port. A capture signaling
on 5080 gives the library twelve SIP messages and the tool "No SIP signaling
found" until you pass --portrange 1-65535. Neither is wrong — they answer
different questions, and knowing that is cheaper than rediscovering it against
a capture you care about.
The crate is heavily feature-gated (see Cargo.toml). For pure parsing
you only need native. tls, hep, api and mcp pull in their respective
subsystems. See the feature table in
install.md.
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