SipFlow: Call Signaling, Recordings, and Cluster Deployment

M
Miuda Team
Building conversational AI tooling
Rust Telephony SIP SBC

Debugging SIP calls usually means running tcpdump, downloading PCAP files, and opening Wireshark. It works, but it’s slow, requires server access, and doesn’t scale.

SipFlow is RustPBX’s built-in signaling capture system. It records every SIP message and RTP packet, stores them efficiently, and presents a visual timeline in the web console. Need a recording? Generate a WAV from the captured RTP data with one click. Need to debug a call from last week? Query it by call ID.

And when a single node isn’t enough, SipFlow supports cluster deployment with consistent hashing.

How SipFlow Captures Data

RustPBX’s SIP proxy mirrors every SIP message and RTP packet to the SipFlow subsystem in real time. This happens inside the process – no port mirroring, no external taps.

Each captured packet includes:

  • Timestamp (microsecond precision)
  • Source and destination IP:port
  • Message type (SIP or RTP)
  • Full payload (SIP headers + body, or RTP header + audio)

Packets are encoded in a compact binary format (magic 0x5346) and written to local storage.

Local Storage

By default, SipFlow stores data locally. Configure it in config.toml:

[sipflow]
type = "local"
root = "/data/sipflow"
subdirs = "hourly"       # none, daily, or hourly
flush_count = 500         # Batch write every 500 packets
flush_interval_secs = 10  # Or every 10 seconds

Storage Structure

With subdirs = "Hourly", SipFlow creates a directory per hour:

/data/sipflow/
  2026/
    06/
      01/
        00/
          sipflow.db    # SQLite index (call_meta, sip_msgs, media_msgs)
          data.raw      # ZSTD-compressed binary packets
        01/
          sipflow.db
          data.raw
        ...

The SQLite database maps call IDs to packet offsets. The raw data file is ZSTD-compressed for efficient disk usage.

Disk Usage Estimate

Calls/DaySIP StorageRTP StorageTotal (Hourly)
1,000~50 MB~200 MB~250 MB
10,000~500 MB~2 GB~2.5 GB
100,000~5 GB~20 GB~25 GB

SIP-only capture is much lighter. If you don’t need RTP, you can disable media capture and keep storage minimal.

Viewing SIP Flows in the Console

From any Call Record, click the SipFlow tab to see the signaling timeline:

SipFlow timeline

The timeline shows every SIP message in order:

  • INVITE, 100 Trying, 180 Ringing, 200 OK, ACK
  • BYE, 200 OK
  • Any error responses (403, 404, 408, 486, 503)
  • Message details: headers, SDP, timestamps

This is invaluable for debugging:

  • Why did a call fail? Check the final response code and reason header
  • Was the SDP negotiated correctly? Compare offer and answer
  • Did the carrier reject the call? Look at the 403/503 from the trunk side

No SSH, no tcpdump, no Wireshark. Just open the console and look.

Generating Recordings from RTP

If RTP capture is enabled, you can generate a WAV recording on demand:

  1. Open the call record
  2. Click Download WAV
  3. SipFlow reads the RTP packets, decodes the audio, and generates a WAV file

WAV generation

This works even for calls where file-based recording wasn’t enabled. SipFlow’s RTP capture is independent of the recording policy.

The media stats view also shows per-leg packet counts, helping you diagnose one-way audio or packet loss issues.

Cluster Deployment

When you run multiple RustPBX nodes, each node captures its own traffic. To query any call from a central location, deploy SipFlow in cluster mode.

Architecture

                  ┌──────────────┐
                  │  RustPBX     │
                  │  Node 1      │──── UDP 3000 ────┐
                  └──────────────┘                   │
                                                     ▼
                  ┌──────────────┐          ┌──────────────┐
                  │  RustPBX     │          │  SipFlow     │
                  │  Node 2      │──── UDP ─│  Standalone  │
                  └──────────────┘    3000   │  Server      │
                                          └──────────────┘
                  ┌──────────────┐           │
                  │  RustPBX     │           │ HTTP API
                  │  Node 3      │──── UDP ──│  port 3001
                  └──────────────┘   3000    │
                                          └──────────────┘

The standalone SipFlow server:

  • Receives packets from all nodes via UDP (port 3000)
  • Stores them in the same hourly structure
  • Exposes an HTTP API (port 3001) for queries: /flow?callid=xxx, /media?callid=xxx

Consistent Hashing

For high-volume deployments, run multiple SipFlow servers. Each packet is routed to a specific server based on a consistent hash of the call_id. This ensures all messages for a given call land on the same server.

When a node is added or removed, only 1/N of calls are redistributed – the rest stay put.

Configuring RustPBX for Remote SipFlow

[sipflow]
type = "remote"
timeout_secs = 5

[[sipflow.nodes]]
udp = "10.0.1.50:3000"
http = "http://10.0.1.50:3001"

[[sipflow.nodes]]
udp = "10.0.1.51:3000"
http = "http://10.0.1.51:3001"

The console transparently queries the right node when you view a call’s SipFlow data.

Deploying the Standalone Binary

cargo build --bin sipflow --release
/static/blog/assets/sipflow --udp 0.0.0.0:3000 --http 0.0.0.0:3001 --dir /data/sipflow

Or via Docker. The binary is lightweight – it only handles packet ingestion and queries, no SIP proxy overhead.

Upload to External Storage

Both local and remote modes support uploading to S3 or an HTTP endpoint for long-term retention:

[sipflow.upload]
type = "s3"
vendor = "aws"
bucket = "my-sipflow-archive"
region = "us-east-1"
access_key = "your-access-key"
secret_key = "your-secret-key"
endpoint = "https://s3.us-east-1.amazonaws.com"
root = "sipflow/"

This runs after each hourly rotation, so your local disk stays clean.

Summary

FeatureBenefit
SIP captureDebug any call from the console, no server access needed
RTP captureGenerate recordings on demand, independent of recording policy
Visual timelineSee full SIP flow with headers and SDP
Cluster modeScale across multiple nodes with consistent hashing
External uploadArchive to S3 for long-term storage

What’s Next?

So far we’ve covered internal communication, carrier connectivity, privacy numbers, and operations. The final piece? Running a full contact center – queues, agents, skill-based routing, supervisor monitoring, and more. That’s the CC addon, and it’s the topic of our last post.