A SIP softphone built for troubleshooting: place the call, see the ladder, and know why it failed.
Test calls over UDP, TCP, TLS or WebRTC, SRTP included, pcap and pcapng analysis, RTP quality and MOS, plain-language diagnostics,
an AI second opinion with your own keys, automated test suites and an MCP server, for Windows, macOS, Linux and the browser.
Download · Open it in the browser · Test suite examples · Contribute
Alpha: expect rough edges. A capture that Sidetone reads wrong or a finding that misleads? Open an issue with a redacted pcapng (Export › Redacted pcapng): real traces are what improve the rules most.
Three lines, two of them in a local conference, Opus on the wire, BLF lamps, every event and result in plain words.
Features · Everything in the box · Automation · AI · Under the hood · Get started · Help wanted · Downloads
| Several accounts and lines, hold, mute, blind and attended transfer, DTMF three ways, DND and auto answer, call forwarding, a voicemail lamp, SIP MESSAGE, microphone and speaker. SIP over UDP, TCP or TLS from the desktop, with SDES or DTLS-SRTP, or over WebSocket with WebRTC from the browser. | Around 40 deterministic rules read signaling and media and say what went wrong in words: missing ACK, NAT in SDP, one-way audio, codec mismatch, loss and jitter, with the message numbers as evidence. | Annotated pcapng with findings as Wireshark packet comments, TLS keys embedded when available, and a redacted export that masks numbers, names, credentials and public IPs. | A live SIP trace, every packet captured without a driver, DNS SRV and NAT checks, and a CLI, JSON test suites and an MCP server so scripts and AI agents can do the same. |
Every screenshot is Sidetone itself, captured headless by
scripts/readme-shots.mjs against the throwaway PBX in lab/ and the
built-in sample captures, with a test tone instead of a microphone. Some
are in the dark theme (the Theme button, or your system setting).
| INVITE without response · failure codes explained · 200 OK retransmitted · no ACK · drop at 32 s · session timer expiry · credentials rejected · challenge not answered · 481 in dialog · glare · slow first response · post-dial delay · private SDP or Contact behind a public source · wrong interface on an unanswered REGISTER · no common codec · answer with codecs not offered · SRTP vs RTP · missing crypto or fingerprint · DTLS role conflict · DTMF not negotiated · ptime mismatch · hold · one-way audio · no RTP · asymmetric RTP · loss · jitter · gaps · SSRC change · unnegotiated payload types · low MOS · far-end loss, round trip and MOS from RTCP and vq-rtcpxr · phone boot provisioning (DHCP option 66/150 missing, TFTP file not found, no answer, cut off) · RTP that carries only silence · clipping · Q.850 Reason causes · missing mandatory headers · Content-Length errors · Max-Forwards near zero |
Capture live on any network interface (libpcap or Npcap, loaded at run time) with a BPF filter, watch SIP go by, then analyze or save it.
Read: pcap and pcapng (Ethernet, VLAN, Linux cooked v1 and v2, raw IP, loopback, Wireshark exported PDUs), IPv4 and IPv6, SIP over UDP, TCP and decrypted TLS, TLS key logs from pcapng. Search: Wireshark-style display filters over SIP and SDP ( ip.addr == 10.0.0.5 && sip.status >= 400, sdp.codec == PCMA, sip.from contains "2001") in the trace and in Analysis.
Listen: pick any RTP stream, hear it and see its waveform with a playhead (G.711 and Opus), save it as WAV. Write: the phone's own traffic as it travelled (SIP over TCP as TCP, over TLS as decrypted exported PDUs, like Wireshark's Export PDUs); annotated pcapng with findings as packet comments and TLS keys kept; redacted pcapng for sharing; one call or the whole capture; ladder as SVG; report as JSON; stereo WAV of each test call (left sent, right received). |
Several accounts with re-registration at 90% of the expiry and retry on failure · SIP over UDP, TCP and TLS (certificate check on or off per account) · SRTP with SDES keys or DTLS-SRTP (certificate checked against a=fingerprint), AES_CM_128_HMAC_SHA1_80 and _32 and AEAD_AES_128/256_GCM, optional or mandatory · digest auth (MD5, qop auth) · several lines with automatic hold · hold and resume by re-INVITE · mute · blind transfer (REFER) and attended transfer (REFER with Replaces), and being transferred · three-way conference mixed locally · BLF (SUBSCRIBE to the dialog event package) showing idle, ringing and busy, with presence (PIDF) when the server offers it, and your own presence published with PUBLISH · DTMF as RFC 4733, SIP INFO or inband tones, and received digits shown · DND and auto answer with the reject code you choose · intercom answer (Call-Info, Alert-Info) · PRACK, UPDATE, session timers, late offer · ringback and ring tones · per-call WAV recording · DNS SRV and outbound proxy · direct, unregistered calls to any SIP URI (anonymous From, for testing URI dialing) · dial strings with headers and post-answer DTMF (600?Subject=x,,123#) · 16 codecs, each tested against Asterisk: G.711 μ-law and A-law, G.722, Opus (16 kHz audio path, in-band FEC), G.729A, iLBC (20 and 30 ms), Speex narrowband and wideband, GSM 06.10, G.726-16/24/32/40 (RFC 3551 or AAL2 bit order), linear PCM at 8, 16 and 44.1 kHz; a global list in order of preference, a list per account and a list per call · configuration import and export as one JSON file (in the app and with sidetone config export|import), passwords optional, also loaded from an HTTP(S) provisioning URL · call forwarding (always, busy, no answer) with 302 · voicemail lamp from message-summary (RFC 3842) · SIP MESSAGE (RFC 3428) sent and received · HEPv3 copies of every SIP message to a Homer collector · live call table with duration, codec, transport, RTP counters and the result of the last action (hold, transfer or DTMF refused, unsupported or unanswered, with the reason) · phone-only mode that turns the desktop window into a compact softphone · interface in English, Portuguese (Brazil), Spanish, German, French, Chinese (Simplified) and Hindi, with built-in help · WebRTC with Opus, ICE and DTLS-SRTP in the browser, with several lines, attended transfer and conference
|
analyze · export · ai · call · answer · register · options · test · test-gen · test-format · samples · profile · serve · mcp
|
analyze_capture · get_ladder · get_message · get_call_context · export_pcapng · generate_sample · sip_options · test_call · get_test_suite_format · run_test_suite
|
Seven synthetic captures with known problems, so every rule can be seen without real customer traffic: a clean call with DTMF, NAT with one-way audio and a 32 s drop, a 488 codec rejection, a lossy call, a REGISTER that never gets an answer, an SRTP mismatch, and all of them together. |
The CLI and the web app share one engine, so anything the window does, a script does too.
# Read a capture: calls, ladder, RTP, findings
sidetone analyze trace.pcapng --ladder
sidetone analyze trace.pcapng --call 3 --svg call3.svg
sidetone analyze trace.pcapng --json > report.json
# Share it: findings as Wireshark comments, or masked for a vendor ticket
sidetone export trace.pcapng -o annotated.pcapng
sidetone export trace.pcapng -o share.pcapng --call 3 --redact
# Be a Homer collector: receive HEPv3 from Kamailio, OpenSIPS, FreeSWITCH
# or heplify, save it, and analyze it like any capture
sidetone hep --listen 0.0.0.0:9060 -o hep.pcapng
# Place calls from a script, then read what happened
sidetone call 600 --domain pbx.lab:5060 --user 2001 --password "$PW" \
--register --dtmf 123# --pcap call.pcapng --wav call.wav
sidetone answer --domain pbx.lab --user 3001 --password "$PW" --register --audio echo
sidetone options pbx.labTest suites are JSON: cases made of register, options, call, wait
and unregister steps, each with expectations (final status, maximum
errors, minimum MOS, loss, jitter, post-dial delay, talk time, codec, RTP
received, rules that must or must not appear). Each case saves its pcapng.
SIP_PASSWORD=... sidetone test examples/asterisk-lab.suite.json --junit junit.xml
sidetone test suite.json --ai-explain # failed cases get an AI explanation
sidetone test-gen "register 2001 on pbx.lab and check echo 600 with DTMF and MOS above 4" -o new.suite.json{ "name": "echo test with DTMF",
"steps": [ { "call": { "target": "600", "duration": 4, "dtmf": "123#",
"expect": { "status": 200, "min_mos": 4.0, "rtp_received": true,
"rules_present": ["rtp.dtmf"], "rules_absent": ["rtp.one_way"] } } } ] }Serve the analyzer and the phone to an AI agent over the Model Context Protocol:
{ "mcpServers": { "sidetone": { "command": "/path/to/sidetone", "args": ["mcp"] } } }Sidetone has no AI of its own and no account. Detection is deterministic; the model is only asked to interpret, which keeps answers grounded and cheap. Pick Anthropic, any OpenAI-compatible endpoint (a gateway, LM Studio, vLLM) or a local Ollama; nothing is sent until you ask, masking is on by default, and "Show what will be sent" prints the exact prompt.
export ANTHROPIC_API_KEY=...
sidetone ai trace.pcapng --call 2 --lang "Portuguese (Brazil)"
sidetone ai trace.pcapng --provider ollama --model llama3.1
sidetone ai trace.pcapng --dry-runIn the browser the key stays in that browser and goes only to the provider;
Ollama needs OLLAMA_ORIGINS to include the page origin.
- Engine first:
crates/sidetone-coreholds capture I/O, SIP/SDP/RTP parsing, call correlation, the rules, the ladder, export, redaction and the AI prompt, with no network or audio code, so it runs natively and as WebAssembly. - One frontend, three homes:
web/runs on GitHub Pages with the analyzer in WASM, behindsidetone serve, and inside the Tauri desktop app, which starts the local server on a random loopback port protected by a per-launch token. - A phone engine, not a script:
crates/sidetone-uaruns one signaling thread with RFC 3261 client transactions and timers, dialogs, re-INVITE, REFER/NOTIFY and Replaces, and a media thread pair per line; the UI drives it through a small HTTP API and reads snapshots, events and the trace. Accounts, settings and history live in your user configuration folder (SIDETONE_CONFIG_DIRoverrides it). - Capture without drivers: the phone records its own datagrams with real addresses and timestamps, so no Npcap, libpcap or root is needed.
- The interface the OS would use: the local address comes from the route to the server, so a VPN or virtual adapter does not end up in Via, Contact and SDP.
- Tested against a real PBX: CI runs unit tests on Linux, Windows and
macOS, builds the WASM, starts Asterisk from
lab/, runs the example suite, drives the phone engine through calls, hold, DTMF, both kinds of transfer, DND and auto answer, and opens every exported capture with tshark.
git clone https://github.com/igtoth/sidetone
cd sidetone
cargo run --release -p sidetone-cli -- serve # phone and analyzer on http://127.0.0.1:8640
cargo run --release -p sidetone-cli -- samples ./samples
cargo test --workspace --exclude sidetone-desktopA recent stable Rust (CI uses stable). The desktop app builds with
npx @tauri-apps/cli@2 build in app/; on Linux it needs WebKitGTK 4.1.
For a local PBX to call: lab/run-asterisk.sh (Asterisk on UDP, TCP, TLS
and WebSocket, with SDES and DTLS-SRTP users, a WebRTC user, BLF and presence
hints, echo, busy and unallocated numbers).
Important
Status: alpha. The analyzer, the desktop and browser phones, test suites and the MCP server work and are covered by tests and a live PBX in CI, but Sidetone is young: expect rough edges with PBXs and SBCs the lab does not cover, and please report them. MOS is an estimate from one capture point, as any single-point measurement is.
Sidetone can load account settings from a small XML file served over HTTP(S).
In Settings, open an account and press Provision, then paste the URL.
A valid file is saved and the account registers.
Besides its own strict format, the desktop app reads the Yealink .cfg,
Polycom XML and Cisco SEP<MAC>.cnf.xml files a PBX already serves to desk
phones (the first configured line becomes the account), over HTTP, HTTPS or
TFTP, with {mac} in the URL filled with the device MAC. It can ask DHCP for
the server phones are given (options 66, 150, 160), Diagnostics fetches what
a phone with a given MAC would get, and Analysis explains DHCP and TFTP in a
capture of a phone booting. Every key, the vendor mappings, the samples and
the resync rules are in docs/provisioning.md. To back
up or move the whole configuration instead, use Settings, Import and export
(one JSON file, from a file or a URL).
Example (sidetone-provision version 1; display_name is required):
<?xml version="1.0" encoding="UTF-8"?>
<sidetone-provision version="1">
<account.1.user>2001</account.1.user>
<account.1.display_name>Lab 2001</account.1.display_name>
<account.1.domain>pbx.example.com</account.1.domain>
<account.1.password>secret</account.1.password>
<account.1.transport>udp</account.1.transport>
</sidetone-provision>Browser accounts also need account.1.ws and usually account.1.ice.
When the registrar sends SIP NOTIFY with Event: check-sync or Event: resync,
Sidetone re-downloads the stored provisioning URL for that account. If
Accept requests only from the registrar is on, check-sync applies only when
the NOTIFY comes from the registrar. A resync during a call is deferred until
that account's last call ends; if a new call starts while the file is
downloading, apply waits again. Downloads for the same account are at least
10 seconds apart, measured from the start of the last attempt.
The desktop app fetches provisioning URLs directly (it does not use
HTTPS_PROXY). TLS is checked against public roots unless you turn off
Check the server's TLS certificate in the provision dialog; the browser
phone always uses the browser's TLS rules and cannot disable verification. An
HTTPS-hosted page cannot fetch an http:// provisioning URL (mixed content);
use the desktop app for a LAN HTTP server.
In the browser, account.1.remember_password set to 0 keeps the password only
until you reload the page (unless you save with remember enabled in Settings).
- Try it on your own traces. A capture Sidetone reads wrong, or a finding that misleads, sent as a redacted pcapng in an issue, improves the rules more than anything else.
- Write a rule. Each rule is a few lines in
crates/sidetone-core/src/rules.rsplus a synthetic scenario insynth.rsthat proves it. - Share a test suite for your PBX, SBC or carrier in
examples/. - Package it for the system you use (Homebrew, winget, AUR, Flatpak).
Each version gets a release with installers for every platform: take the latest release, or pick a file below (version 0.1.25).
| Platform | Install | Portable (no install) | Command line (also standalone) |
|---|---|---|---|
| Windows 10/11 (x64) | Installer (.exe) · MSI | Sidetone_0.1.25_x64_portable.zip | sidetone-v0.1.25-x86_64-pc-windows-msvc.zip |
| macOS 11+ (Apple silicon) | Sidetone_0.1.25_aarch64.dmg | Sidetone_aarch64.app.tar.gz | sidetone-v0.1.25-aarch64-apple-darwin.tar.gz |
| macOS 11+ (Intel) | Sidetone_0.1.25_x64.dmg | Sidetone_x64.app.tar.gz | sidetone-v0.1.25-x86_64-apple-darwin.tar.gz |
| Linux (x86_64) | .deb · .rpm | AppImage | sidetone-v0.1.25-x86_64-unknown-linux-gnu.tar.gz |
| Linux (arm64) | sidetone-v0.1.25-aarch64-unknown-linux-gnu.tar.gz | ||
| Web | no download: open it in the browser |
Portable and standalone. Nothing has to be installed:
- Windows: unzip
Sidetone_..._portable.zipanywhere (a USB stick works) and runSidetone.exe. It uses the WebView2 that comes with Windows 10 and 11. - macOS: unpack
Sidetone_....app.tar.gzand openSidetone.appfrom any folder. - Linux:
chmod +xthe AppImage and run it. On distributions without FUSE 2, run it with--appimage-extract-and-run. - Command line, any platform: a single executable with the interface built in. Double click
sidetone(or run it with no arguments) and the full app opens in your browser, with no WebView or system library needed. Closing its window stops it.sidetone serve --opendoes the same from a terminal.
Settings, accounts and history live in your user configuration folder in every form, so you can switch between them. To keep everything with the program instead (a USB stick, say), create a folder named sidetone-data next to the executable, or set SIDETONE_CONFIG_DIR.
Windows. Run the .exe installer (or the .msi for managed
deployments). The downloads are not code-signed yet: if SmartScreen warns,
choose More info › Run anyway. Allow Sidetone through Windows
Firewall when asked, or incoming calls cannot reach it.
macOS. Open the .dmg and drag Sidetone to Applications. The app is
not signed yet, so the first time right-click it and choose Open (or run
xattr -dr com.apple.quarantine /Applications/Sidetone.app). macOS asks
for microphone access on the first call.
Linux. Debian and Ubuntu: sudo apt install ./Sidetone_0.1.25_amd64.deb.
Fedora and openSUSE: sudo dnf install ./Sidetone-0.1.25-1.x86_64.rpm.
Anywhere else: chmod +x Sidetone_0.1.25_amd64.AppImage and run it. The
desktop app needs WebKitGTK 4.1 (present on current distributions).
Command line. Unpack the archive and put sidetone on your PATH:
tar xzf sidetone-v0.1.25-x86_64-unknown-linux-gnu.tar.gz
sudo install sidetone-v0.1.25-x86_64-unknown-linux-gnu/sidetone /usr/local/bin/
sidetone # the full app in your browser (same as sidetone serve --open)On macOS remove the quarantine flag first
(xattr -d com.apple.quarantine sidetone). The archive also has the
example test suites.
Capturing other hosts' traffic (Diagnostics, Capture on an interface)
needs a packet capture library and the right to use it. Windows: install
Npcap (Wireshark installs it). macOS: libpcap is built
in; install Wireshark's ChmodBPF or run as root. Linux: libpcap is usually
installed; give the binary the capability once with
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/sidetone-desktop (or
the sidetone CLI), or run it with sudo. Everything else works without it.
From source. See Get started.
| Where | What it covers |
|---|---|
docs/provisioning.md |
provisioning XML, samples, and SIP resync |
sidetone --help, sidetone test-format |
every command and the test suite format |
examples/ |
test suites, including one that fails on purpose |
lab/ |
the Asterisk lab used by CI and the screenshots |
crates/sidetone-core/src/rules.rs |
every rule and its explanation |
crates/sidetone-core/src/synth.rs |
the sample scenarios |
.github/workflows/ |
CI, releases and the GitHub Pages build |
Sidetone is licensed under the MIT License. Copyright © 2026 Ighor Toth.
Written by Ighor Toth with Claude Code, Anthropic's AI coding assistant,
as co-author: commits it helped write carry a Co-Authored-By line.
Codecs: G.722 from libg722 (Steve Underwood, public domain, and CMU) through
the ezk-g722 translation (MIT); G.726 from soundkit-g726 (MIT); GSM 06.10 from
libgsm (Jutta Degener and Carsten Bormann, TU Berlin); Speex from libspeex
(Xiph.Org, BSD) and iLBC from WebRTC (BSD), both bundled in
crates/sidetone-ua/native with their licences; G.729 from the g729-sys crate,
which describes itself as MIT but is a port of bcg729 (GPL-3.0): builds that
must avoid GPL code can leave it out.
The interface uses IBM Plex Sans and IBM Plex Mono (SIL Open Font License), loaded from Google Fonts. Every capture in the screenshots and samples was made for Sidetone.
Sidetone is an independent open-source project, not affiliated with, sponsored by or endorsed by any other software vendor. Product names such as Wireshark and Asterisk belong to their owners and are used only to describe compatibility. The software is provided "as is", without warranty of any kind, as the license states.








