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.
  • -O saves 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 INVITE to Asterisk at 192.0.2.10:5060,
  • then Asterisk’s own INVITE to the callee at 192.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 Trying to the caller is among them.
  • The second is Asterisk’s leg to the callee, with 6.
    • Its To column shows -.
    • That is because Asterisk addressed the callee as <sip:192.0.2.10>, with no user name.

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 PCMA packets from the caller to Asterisk,
    • 236 PCMA packets back, the callee’s echo.
  • On the callee’s leg, 236 PCMU packets 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-event stream.
    • After the key press, both legs show short PCMU and PCMA streams under new SSRCs:
      • 6 PCMU packets each way on the callee’s leg,
      • 10 PCMA packets toward the caller.

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 F4 with 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 ladder shows these messages, in the order they happened:
      • the caller’s INVITE to Asterisk,
      • Asterisk’s INVITE to the callee,
      • the callee’s answer,
      • Asterisk’s answer to the caller.
    • Press Esc to go back to the list.
  • Press Space, ↓ (the down arrow), Space again, then Enter.
    • Space stars the selected row, so this stars both legs.
    • With two rows starred, Enter opens one flow of both, in time order. It is the same three-column ladder.
    • Press Esc to go back to the list.
  • Press Tab for the RTP streams.
    • Each stream names the leg it belongs to in the Dialog column.

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 INVITE answered in a few milliseconds,
  • then the ACK,
  • the BYE 9 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-event stream shows mos=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_correlated which 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 To is 100:
# 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_match is false.
  • heuristic_only is 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, Completed with 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_hep module.
    • 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 until line waits for Asterisk to finish starting. It works as in Configure Asterisk in the Asterisk guide.
  • module show like hep lists res_hep.so, res_hep_pjsip.so and res_hep_rtcp.so as Running.
  • capture_id is the number Asterisk stamps on every copy it sends.
    • A collector uses it to tell its senders apart.
  • uuid_type = call-id labels 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:1234 is Asterisk’s capture_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 purge removes 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 sipnab user 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 ssh without -t.
    • Run it from an interactive login, or use -N as 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 read hep.conf.
    • core reload does 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”.