Docs Run sipnab beside Asterisk
Run sipnab beside Asterisk
Install sipnab on the Asterisk machine and watch a call through it as its two legs: the report, the terminal interface, a call report, MCP, the REST API, and Asterisk's own HEP.
On this page
sipnab reads the SIP and the audio that pass through Asterisk off the wire. So it needs nothing from Asterisk: no module, no configuration change.
Asterisk is a B2BUA. Every call through it is two SIP calls, called legs:
- the caller’s leg into Asterisk,
- Asterisk’s leg out to the callee.
Each leg has its own Call-ID. sipnab does three things with the two legs:
- It lists them as two dialogs.
- It shows them together as one call flow.
- It measures the audio on each.
This guide adds sipnab to the Asterisk machine from Use Asterisk as your voice stack’s PBX. It does the following:
- It watches a test call.
- It shows its two legs in the terminal interface.
- It explains it from a saved capture.
- It answers about it over MCP and the REST API.
- Finally, it receives Asterisk’s own copies of its SIP over HEP.
What works without Asterisk. sipnab does not depend on Asterisk.
- The sections from “Watch a call through Asterisk”
to “Keep sipnab running, and query it over REST”
use sipnab’s own capture.
- They work the same way beside any SIP server.
- The section “Receive Asterisk’s HEP” is the one that uses an
Asterisk module.
- You can leave it out.
Tested on
Every block on this page ran as written, in order, on 2026-10-12.
- It ran with sipnab 0.5.211 from its release package.
- It ran on the Debian 13 (kernel 6.12.63) and Ubuntu 24.04 (kernel 6.8.0) machines that the Asterisk guide had set up.
- The terminal interface ran in a terminal 160 columns wide and 40 lines high.
- The test call took place while it ran.
- The keys in “See both legs as one call flow” showed what that section describes.
- Every test call carried audio both ways.
- On both, causing each fault under When something does not work produced the message it quotes.
The examples use 192.0.2.10 as the machine’s address. Replace it with yours
everywhere it appears.
Install sipnab
# Run all of these, in order.
V=$(curl -fsSL https://api.github.com/repos/NormB/sipnab/releases/latest \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"].lstrip("v"))')
curl -fsSLO https://github.com/NormB/sipnab/releases/download/v$V/sipnab_${V}_amd64.deb
curl -fsSL https://github.com/NormB/sipnab/releases/download/v$V/SHA256SUMS.txt \
| grep " sipnab_${V}_amd64.deb$" | sha256sum -c -
sudo apt-get install -y ./sipnab_${V}_amd64.deb jq
sipnab --version
The commands in the sections from
“Explain the call from the saved capture”
to “Keep sipnab running, and query it over REST”
use jq to pick fields out of sipnab’s JSON. So the block installs it too.
Watch a call through Asterisk
Start sipnab on every interface for 30 seconds.
- It prints each SIP message as it passes.
- It prints a report of the calls at the end.
-Osaves every packet it captured to/tmp/asterisk-call.pcap.- The sections “Explain the call from the saved capture” and “Ask about the call over MCP” use that file.
sudo sipnab -N -d any --duration 30 --report -O /tmp/asterisk-call.pcap
While it runs, place a test call from a second terminal. Do it as in Place a test call in the Asterisk guide:
# Run all of these, in order.
cd ~/sipp
sipp -sf uas.xml -i 192.0.2.10 -p 5070 -min_rtp_port 7000 -max_rtp_port 7100 -rtp_echo -m 1 -bg
sudo sipp -sn uac_pcap 192.0.2.10:5060 -i 192.0.2.10 -p 5080 -s 100 -m 1 -timeout 90s
sipnab prints, in order:
- the caller’s
INVITEto Asterisk at192.0.2.10:5060, - then Asterisk’s own
INVITEto the callee at192.0.2.10:5070.- It has a Call-ID Asterisk generated.
- It has an SDP offer of its own.
When the 30 seconds are up, the report lists the two legs as two calls. Both
are Completed with code 200:
Call-ID From To State Code Duration Msgs PDD Tags
-------------------------------------------------------------------------------------------------------------------------
1-50232@192.0.2.10 sipp 100 Completed 200 9s 7 0.0s -
37e58306-c14d-4a0f-9fa1-41a... sipp - Completed 200 9s 6 0.0s -
- The first is the caller’s leg, to extension
100, with 7 messages.- Asterisk’s
100 Tryingto the caller is among them.
- Asterisk’s
- The second is Asterisk’s leg to the callee, with 6.
- Its
Tocolumn shows-. - That is because Asterisk addressed the callee as
<sip:192.0.2.10>, with no user name.
- Its
Below them, the RTP Streams: table shows that the audio goes through
Asterisk. Each leg has a different codec:
- On the caller’s leg:
- 236
PCMApackets from the caller to Asterisk, - 236
PCMApackets back, the callee’s echo.
- 236
- On the callee’s leg, 236
PCMUpackets each way between Asterisk and the callee.- Asterisk converts the caller’s PCMA to PCMU.
- PCMU is the only codec the callee takes.
- The caller’s key press: 10
telephone-event(DTMF) packets to Asterisk.- Asterisk passes it to the callee as a tone inside the audio.
- So the callee’s leg carries no
telephone-eventstream. - After the key press, both legs show short PCMU and PCMA streams under new
SSRCs:
- 6
PCMUpackets each way on the callee’s leg, - 10
PCMApackets toward the caller.
- 6
See both legs as one call flow
The terminal interface shows the same calls, and updates as they happen:
sudo sipnab -d any
Place the test call from a second terminal, as in
“Watch a call through Asterisk”. The call list
shows two rows, one per leg. Both are Completed, with 7 and 6 messages. Then,
in the terminal interface:
- Press
F4with the first row selected.- The extended flow draws both legs in one ladder with three columns:
- the caller at
192.0.2.10:5080, - Asterisk at
192.0.2.10:5060, - the callee at
192.0.2.10:5070.
- the caller at
- The ladder shows these messages, in the order they happened:
- the caller’s
INVITEto Asterisk, - Asterisk’s
INVITEto the callee, - the callee’s answer,
- Asterisk’s answer to the caller.
- the caller’s
- Press
Escto go back to the list.
- The extended flow draws both legs in one ladder with three columns:
- Press
Space,↓(the down arrow),Spaceagain, thenEnter.Spacestars the selected row, so this stars both legs.- With two rows starred,
Enteropens one flow of both, in time order. It is the same three-column ladder. - Press
Escto go back to the list.
- Press
Tabfor the RTP streams.- Each stream names the leg it belongs to in the
Dialogcolumn.
- Each stream names the leg it belongs to in the
Press q, then y, to quit.
Explain the call from the saved capture
--call-report explains one call:
- its SIP transactions with their timings,
- for each audio stream, its packets, jitter, loss and estimated MOS (call quality on a scale from 1 to 5).
Reading a file needs no root. The first line finds the caller’s leg in the capture from “Watch a call through Asterisk”:
# Run all of these, in order.
CALL=$(sipnab -N -I /tmp/asterisk-call.pcap --json-dialogs --no-cli-print | jq -r 'select(.to == "100") | .call_id')
sipnab -N -I /tmp/asterisk-call.pcap --call-report "$CALL" --no-cli-print
The report covers the caller’s leg. Its SIP Transactions show:
- the
INVITEanswered in a few milliseconds, - then the
ACK, - the
BYE9 seconds later:
SIP Transactions:
INVITE -> 100 -> 180 (1ms) -> 200 (3ms)
ACK
BYE -> 200 (9008ms)
Under Media Streams:
- The two PCMA streams of 236 packets, one each way, show no loss and
mos=4.36 R=90.8. - The
telephone-eventstream showsmos=unknown (unpublished).- sipnab gives no quality estimate for DTMF events.
- The last stream is the 10 PCMA packets toward the caller.
The report ends with Issues Detected: None.
Ask about the call over MCP
sipnab’s MCP server lets an AI agent ask about a capture. The MCP guide sets it up for a client. This block does the following:
- It sends the three messages a client would, straight from the shell.
- It asks
find_correlatedwhich other leg belongs to the caller’s call. - Its first line takes the caller’s Call-ID from the report. It uses the row
whose
Tois100:
# Run all of these, in order.
CALL=$(sipnab -N -I /tmp/asterisk-call.pcap --report --no-cli-print | awk '$3 == "100" {print $1}')
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"shell","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"find_correlated\",\"arguments\":{\"call_id\":\"$CALL\"}}}" \
| sipnab --mcp -N -I /tmp/asterisk-call.pcap --quiet \
| jq 'select(.id == 2) | .result.content[0].text | fromjson | {total_matched, heuristic_only, legs}'
The answer names Asterisk’s leg:
{
"total_matched": 1,
"heuristic_only": true,
"legs": [
{
"call_id": "37e58306-c14d-4a0f-9fa1-41a2b3c05f96",
"identifier_match": false,
"observed_gap_ms": 1,
"score": 50,
"strategy": "timing_heuristic"
}
]
}
timing_heuristic means that sipnab matched the legs for two reasons:
- They share an address.
- They started within 2 seconds of each other. Here they were 1 millisecond
apart (
observed_gap_ms).
That is a guess, and the answer says so:
identifier_matchis false.heuristic_onlyis true.
Asterisk puts no header on its leg that names the caller’s leg. So sipnab has no identifier to match on.
Keep sipnab running, and query it over REST
The sipnab package installs a systemd service, sipnab. It captures on every
interface without the terminal interface. Turn on its REST API.
- sipnab reads the API key from the environment.
- Keep the key in a file only root can read. That keeps it out of the command line, where every user on the machine could read it:
# Run all of these, in order.
sudo sh -c 'umask 077; printf "SIPNAB_API_KEY=%s\n" "$(openssl rand -hex 32)" > /etc/sipnab/api.env'
sudo mkdir -p /etc/systemd/system/sipnab.service.d
sudo tee /etc/systemd/system/sipnab.service.d/api.conf >/dev/null <<'EOF'
[Service]
EnvironmentFile=/etc/sipnab/api.env
ExecStart=
ExecStart=/usr/bin/sipnab -N -d any --no-cli-print --syslog --metrics 127.0.0.1:9090 --api 127.0.0.1:8080
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now sipnab
systemctl is-active sipnab
The ExecStart line keeps the package’s own arguments and adds --api. The
API listens on 127.0.0.1 only.
Place the test call again, as in “Watch a call through Asterisk”. Then ask sipnab for:
- its calls,
- the leg that goes with the caller’s,
- the audio streams:
# Run all of these, in order.
KEY=$(sudo sed -n 's/^SIPNAB_API_KEY=//p' /etc/sipnab/api.env)
curl -s -H "Authorization: Bearer $KEY" 127.0.0.1:8080/v1/dialogs \
| jq -r '.dialogs[] | [.call_id, .to_user // "-", .state, .msg_count] | @tsv'
CALL=$(curl -s -H "Authorization: Bearer $KEY" 127.0.0.1:8080/v1/dialogs | jq -r '.dialogs[] | select(.to_user == "100") | .call_id')
curl -s -H "Authorization: Bearer $KEY" "127.0.0.1:8080/v1/dialogs/$CALL/correlated" | jq -c '.legs[]'
curl -s -H "Authorization: Bearer $KEY" 127.0.0.1:8080/v1/streams \
| jq -r '.streams[] | [.associated_dialog, .codec, .packets, .loss_pct] | @tsv'
- The first answer lists the two legs,
Completedwith 7 and 6 messages. - The second names Asterisk’s leg with
"strategy":"timing_heuristic".- MCP did the same in “Ask about the call over MCP”.
- The third lists each stream with:
- the leg it belongs to,
- its codec,
- its packets,
- its loss.
The REST API reference gives every route.
Receive Asterisk’s HEP
HEP is the protocol SIP servers use to send a copy of their traffic to a collector such as Homer.
- Asterisk sends HEP with its
res_hepmodule.- The build in the Asterisk guide includes it.
- sipnab can receive it.
- sipnab then sees the SIP as Asterisk handled it, with no capture on the wire.
Point Asterisk at sipnab’s HEP listener, on port 9060 of the loopback address. Then restart it:
# Run all of these, in order.
sudo tee /etc/asterisk/hep.conf >/dev/null <<'EOF'
[general]
enabled = yes
capture_address = 127.0.0.1:9060
capture_id = 1234
uuid_type = call-id
EOF
sudo systemctl restart asterisk
until sudo asterisk -rx 'core waitfullybooted' 2>/dev/null; do sleep 1; done
sudo asterisk -rx 'module show like hep'
- The
untilline waits for Asterisk to finish starting. It works as in Configure Asterisk in the Asterisk guide. module show like heplistsres_hep.so,res_hep_pjsip.soandres_hep_rtcp.soasRunning.capture_idis the number Asterisk stamps on every copy it sends.- A collector uses it to tell its senders apart.
uuid_type = call-idlabels each copy with the SIP Call-ID.
Start sipnab’s HEP listener for 30 seconds:
sudo sipnab -N -L 127.0.0.1:9060 --duration 30 --report --hep-senders
While it runs, place the test call from a second terminal, as in “Watch a call through Asterisk”.
- sipnab prints each message as it arrives, marked
origin=hep. - When the 30 seconds are up, the report lists the same two legs, and who sent them:
SOURCE PACKETS LAST SEEN IDLE STATE
hep:1234@127.0.0.1 15 2026-10-12T07:52:29.691Z 14s sending
hep:1234is Asterisk’scapture_id.- The last line counts
15 packets captured, 13 SIP messages. - The other two packets are RTCP summaries from
res_hep_rtcp.- Asterisk sends them as JSON.
- sipnab’s report shows nothing from them.
- HEP carries no audio, so this report has no
RTP Streams:table.- For the audio, capture on the wire as in “Watch a call through Asterisk”.
HEP over UDP carries no authentication of its own. The capture id is what the
sender claims. Listening on 127.0.0.1 keeps every sender but this machine
out.
Remove what this guide added
# Run all of these, in order.
sudo systemctl disable --now sipnab
sudo apt-get purge -y sipnab
sudo rm -rf /etc/systemd/system/sipnab.service.d /etc/sipnab
sudo systemctl daemon-reload
sudo rm -f /etc/asterisk/hep.conf /tmp/asterisk-call.pcap
sudo systemctl restart asterisk
apt-get purgeremoves the package and its own configuration.- The API key file and the drop-in are yours, so the block removes them.
- The package leaves the
sipnabuser in place.
When something does not work
- sipnab says
sipnab: the terminal UI could not start: No such device or address (os error 6). It needs a terminal; add -N for a run without one.- The terminal interface needs an interactive terminal.
- This session has none, as with a command passed to
sshwithout-t. - Run it from an interactive login, or use
-Nas in “Watch a call through Asterisk”.
- sipnab’s HEP listener ends with
HEP listener on 127.0.0.1:9060 finished: 0 packets received.- Asterisk sent nothing.
- Run
sudo asterisk -rx 'module show like hep'. - If the three modules show
Not Running, Asterisk has not readhep.conf. core reloaddoes not start them. Restart Asterisk.
- The REST API answers
{"status":401,"title":"Unauthorized","type":"https://sipnab.com/problems/unauthorized"}.- The request carried no API key, or the wrong one.
- Read it from
/etc/sipnab/api.env, as in “Keep sipnab running, and query it over REST”.