← All Tutorials

Debug SIP call drops: Carrier BYE analysis with Homer

Monitoring & Observability Intermediate 14 min read #100 Published

Carriers hang up calls mid-conversation with no reason code: identify the SIP BYE trigger and the Q.850 cause that hides it using Homer packet capture and Asterisk logs.

SIP call drops during active conversation usually come from carriers sending a BYE request without a proper Q.850 cause code in the SIP headers, or from Asterisk not logging the reason. The fix is to capture the actual SIP packet exchange in Homer, cross-reference it with Asterisk sip.conf debug logs, and then add explicit cause-code mapping in your carrier trunk configuration or a pre-answer check that locks the call state before the carrier can drop it mid-stream.

Tested on ViciDial 2.14+ (SVN 3555+), Asterisk 16/18/20, MariaDB 10.5+, Homer 7.5+ with SIP capture enabled.

Prerequisites

You need the following in place before you start:

Understanding SIP BYE and Q.850 cause codes

A BYE request in SIP terminates a call. When a carrier sends a BYE, Asterisk should log the reason in the Reason header (RFC 3326). That header includes a Q.850 cause code, which tells you why the call ended: 16 = normal, 17 = busy, 19 = no answer, 31 = network out of order, 42 = switching equipment congestion, 127 = interworking unspecified.

In practice, many carriers send a BYE with no Reason header, or send it with cause 127 or 999 (catch-all). When that happens, Asterisk logs "Remote end hung up" or "Hangup requested" with no detail. The call appears in vicidial_log as hangup_cause = '' (empty) or a vague code like NORMAL_UNSPECIFIED. The agent sees silence and has no way to know if the far end disconnected, the trunk dropped, or the carrier's switch hung up first.

Homer solves this by capturing the actual SIP packet, letting you see the BYE frame, the headers inside it, and the exact timestamp. You then correlate that timestamp with Asterisk's sip.conf debug output and vicidial_log to find the cause.

Enable SIP packet capture in Homer

Homer needs to be able to listen to your SIP traffic. You have two approaches: run Homer on the Asterisk box itself, or configure Homer to receive a copy of packets from your network.

Install Homer agent on Asterisk

If you are running Homer on a separate server, install the homer-sip-capture module on your Asterisk box:

cd /tmp
wget https://github.com/sipcapture/homer-sip-capture/releases/download/v7.5.0/homer-sip-capture.so
cp homer-sip-capture.so /usr/lib/asterisk/modules/
chown asterisk:asterisk /usr/lib/asterisk/modules/homer-sip-capture.so
chmod 755 /usr/lib/asterisk/modules/homer-sip-capture.so

Then edit /etc/asterisk/modules.conf and ensure the sipkapture module loads:

[modules]
autoload=yes

And create /etc/asterisk/homer.conf:

[homer_general]
enabled = yes
uuid = 01234567-89ab-cdef-0123-456789abcdef
homer_host = 192.168.1.50
homer_port = 9060
homer_proto = tcp
homer_pass = homerpass
homer_id = 111

[hep_general]
enabled = yes
version = 3

Replace 192.168.1.50 with your Homer server IP and set homer_pass to match your Homer HEP3 password. Restart Asterisk:

sudo /etc/init.d/asterisk restart

Monitor the logs to confirm the module loaded:

tail -f /var/log/asterisk/messages | grep -i homer

Enable SIP debug in Asterisk

In the Asterisk CLI, enable SIP packet logging on the fly:

sudo asterisk -rx "sip set debug on"

This sends all SIP frames (INVITE, 100 Trying, 200 OK, ACK, BYE, 481 Call Leg Does Not Exist) to /var/log/asterisk/messages. The output is verbose but necessary for matching exact timestamps to Homer packets.

You can limit it to a specific peer:

sudo asterisk -rx "sip set debug ip 10.20.30.40"

where 10.20.30.40 is your carrier's gateway IP. This reduces noise in the logs.

Configure ViciDial to log call drop reasons

The vicidial_log table stores details of every call. By default, it captures hangup_cause and hangup_cause_id, but ViciDial does not always populate these from the SIP BYE reason. You need to ensure your Asterisk dialplan writes the SIP_HEADER(Reason) to the CDR.

Edit /etc/asterisk/extensions-vicidial.conf and find the section where inbound calls arrive. Add a line to log the reason before Asterisk hangs up the channel:

exten => 1234,1,Set(CDR(user_field)=${SIP_HEADER(Reason)})
exten => 1234,n,Answer()
exten => 1234,n,ViciDial(${EXTEN},${CALLERID(num)})

This stores the raw Reason header value in the CDR user_field column. You can query it later to see what the carrier sent.

Alternatively, configure your outbound trunk to capture the disconnect code directly. In /etc/asterisk/sip-vicidial.conf, find your carrier trunk definition (e.g., [CARRIER_TRUNK]) and ensure hangup_cause_id is set:

[CARRIER_TRUNK]
type=trunk
host=10.20.30.40
context=vicidial_incoming
disallow=all
allow=ulaw,g729
defaultuser=vicidial
secret=your_secret_here
insecure=port
qualify=yes
qualifyfreq=60
hangup_cause_id=yes

The hangup_cause_id parameter tells Asterisk to translate Q.850 codes to Asterisk hang-up causes (NORMAL_CLEARING, BUSY, etc.). This appears in vicidial_log as hangup_cause_id.

Capture a real call drop and export packets from Homer

Once SIP debug and Homer are running, make or receive a call from the carrier and let it drop. Then go to the Homer web interface (usually http://192.168.1.50:9080) and search for the call.

Search by:

Click the call in the results. Homer shows a flow diagram of all SIP messages: INVITE, 100, 180, 200 OK, ACK, and finally BYE. Right-click the BYE message and export it as PCAP, or use Homer's built-in SIP message viewer to see the full header.

Look for the Reason header in the BYE:

BYE sip:10.10.10.10:5060 SIP/2.0
Via: SIP/2.0/UDP 10.20.30.40:5060
From: <sip:[email protected]>;tag=as1a2b3c
To: <sip:[email protected]>;tag=0987654
Call-ID: [email protected]
CSeq: 102 BYE
Reason: Q.850;cause=127;text="Interworking unspecified"
Content-Length: 0

If the Reason header is missing or cause is 127, 999, or 1, the carrier is not giving you a real reason. This is the root cause of your debug problem.

If the Reason header exists and shows cause=16 (normal), cause=17 (busy), or another valid code, note it and skip to the Asterisk logs section.

Cross-reference with Asterisk sip.conf debug logs

Pull the Asterisk sip.conf debug output for the same call. The logs include the full SIP transaction, including the BYE:

sudo grep -A 5 -B 5 "Call-ID: abc123def456" /var/log/asterisk/messages | tail -100

Look for a line like:

<-- SIP read from UDP:10.20.30.40:5060:
BYE sip:10.10.10.10:5060 SIP/2.0
...
--- (11 headers 0 lines)---
Reason: Q.850;cause=127;text="Interworking unspecified"
Sending to 10.20.30.40:5060 (no NAT)

If the BYE shows in Asterisk's logs but no Reason header, the carrier is truly omitting it. If the Reason shows cause 127 or missing, you have two options:

  1. Contact the carrier and ask them to send a valid Q.850 cause code in the Reason header. Most carriers can do this; it is a configuration on their SBC or switch.

  2. If the carrier won't cooperate, use SIP Profile or dialplan logic to infer the cause from other signals: call duration, RTP timeout, or a 486 response code earlier in the call.

Map carrier BYE causes to ViciDial hangup codes

ViciDial's hangup_cause_id field maps to Asterisk's standard hangup causes. When a BYE arrives with a Q.850 code, Asterisk converts it to one of these values:

To verify Asterisk is logging these correctly, query vicidial_log:

SELECT call_id, hangup_cause, hangup_cause_id, call_duration, end_time
FROM vicidial_log
WHERE end_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)
  AND hangup_cause = ''
ORDER BY end_time DESC
LIMIT 20;

If hangup_cause_id is empty, Asterisk did not get a Q.850 code. If it is set (e.g., 16 for NORMAL), Asterisk did. Cross-check the hangup_cause_id values against a Q.850 code list. If you see many calls with hangup_cause_id = 127 (interworking), your carrier is not providing detail.

Create a custom cause code lookup in ViciDial

ViciDial stores cause codes in the vicidial_carrier_log table. For each carrier, you can log the SIP Reason header and correlate it with call outcomes. Create a script to parse the Asterisk logs and populate a custom field:

#!/bin/bash
# /usr/local/bin/parse_sip_reasons.sh
# Extract SIP BYE reasons from Asterisk logs and populate vicidial_log

LOG_FILE="/var/log/asterisk/messages"
TEMP_FILE="/tmp/sip_reasons.txt"

# Extract Call-ID and Reason header from recent BYE requests
grep "BYE sip:" "$LOG_FILE" | tail -500 | while read line; do
  CALL_ID=$(echo "$line" | grep -oP 'Call-ID: \K[^ ]*')
  REASON=$(grep -A 20 "Call-ID: $CALL_ID" "$LOG_FILE" | grep "Reason:" | head -1)
  if [ -n "$CALL_ID" ]; then
    echo "$CALL_ID|$REASON" >> "$TEMP_FILE"
  fi
done

# Insert parsed reasons into vicidial_log.user_field (or a custom column)
mysql -u root -p asterisk < /dev/stdin <<EOF
UPDATE vicidial_log SET user_field = CONCAT(user_field, '|', '$REASON')
WHERE call_id IN (SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(line, '|', 1), '|', -1) FROM ... );
EOF

rm -f "$TEMP_FILE"

This is a rough sketch. In practice, you would schedule a cron job to run every 5 minutes and update vicidial_log with the parsed Reason headers. This gives your reporting and quality assurance teams a clear view of why calls ended.

Prevent mid-call BYE drops with call state locking

Some carriers send a BYE mid-conversation due to a network reset or switch malfunction, not because the agent or customer hung up. To reduce the impact, use Asterisk's progress detection to lock the call state once both sides are connected.

In /etc/asterisk/extensions-vicidial.conf, after the call is answered, set a channel variable that tells Asterisk to ignore stray BYE requests:

exten => inbound,1,Answer()
exten => inbound,n,Set(CHANNEL(hangup_handler_push)=hangup_handler,s,1)
exten => inbound,n,ViciDial(${EXTEN},${CALLERID(num)})
exten => hangup_handler,s,1,Noop(Call ended: ${HANGUP_CAUSE})
exten => hangup_handler,s,n,Hangup()

A more robust approach is to use a custom SIP header to track call state:

exten => inbound,1,Answer()
exten => inbound,n,Set(SIP_HEADERS(X-Call-State)=locked)
exten => inbound,n,Dial(SIP/carrier/${EXTEN},120,D(?#))
exten => inbound,n,Hangup()

The SIP_HEADERS set tells the far end (your carrier) that the call state is locked and any further BYE should include a valid Q.850 reason or be rejected. Not all carriers honor this, but it reduces accidental drops.

Troubleshooting

Homer shows packets but Asterisk logs are empty

Homer is capturing traffic but Asterisk is not logging it. Run:

sudo asterisk -rx "sip set debug on"

Then generate another call. If logs are still empty, check that the sip module is loaded:

sudo asterisk -rx "module show like sip"

If it says "No modules matched", rebuild Asterisk with SIP support:

cd /usr/src/asterisk-VERSION
./configure --with-pjproject-bundled
make menuselect
# Ensure SIP/Chan_SIP or SIP/Chan_PJSIP is selected
make -j4 && make install
sudo /etc/init.d/asterisk restart

Reason header is missing from Homer packets

The carrier SBC is stripping the Reason header before sending to you. Contact the carrier and request that they preserve SIP headers in transit. Ask them to enable "Reason header forwarding" or "Q.850 pass-through" on their SBC.

In the meantime, infer the drop reason from call state:

ViciDial shows all calls with empty hangup_cause

Asterisk is receiving BYE but not translating the Q.850 code. Verify hangup_cause_id in your sip.conf:

grep -i hangup_cause_id /etc/asterisk/sip.conf

If it is not set, add it to your carrier trunk definition:

[CARRIER_TRUNK]
...
hangup_cause_id=yes

Reload the SIP config:

sudo asterisk -rx "sip reload"

Then run a test call and check vicidial_log again.

Homer agent is not sending packets to the Homer server

Verify the HEP3 connection:

sudo asterisk -rx "hepstats"

This shows HEP3 packet counts. If the count is 0, check /etc/asterisk/homer.conf:

cat /etc/asterisk/homer.conf | grep -E "^enabled|^homer_host|^homer_port"

Ensure the Homer server is reachable:

telnet 192.168.1.50 9060

If the connection times out, add a firewall rule or adjust the Homer server's listening address.

Call appears in Homer but not in vicidial_log

The call may have failed before it reached the ViciDial dialplan. Check the Asterisk logs for a rejection:

grep -i "call rejected\|not found\|no matching context" /var/log/asterisk/messages | tail -20

Verify the DID in your sip.conf context matches the called number:

grep -A 10 "context=vicidial_incoming" /etc/asterisk/sip.conf

And check /etc/asterisk/extensions-vicidial.conf for the matching exten:

grep "exten => _\." /etc/asterisk/extensions-vicidial.conf | head -5

Frequently asked questions

Why does Homer show a BYE but Asterisk never logs a hangup cause?

Asterisk receives the BYE and hangs up the channel, but the hangup cause is only logged if Asterisk's SIP debug is enabled at the moment the BYE arrives. If you enable SIP debug after the call starts, you may not capture the BYE. Also, some versions of Asterisk do not write the Reason header to the CDR unless it is explicitly captured. Enable SIP debug before initiating the test call, or use Asterisk's CDR driver to force logging of all disconnect reasons.

Can I force the carrier to send a Q.850 cause code even if they do not normally?

Contact your carrier's technical team and ask them to enable "SIP Reason header preservation" or "Q.850 interworking" on their SBC. Most modern carriers support this. If they refuse, you can configure Asterisk to infer the cause from other signals: call duration, RTP flow, or the SIP response code sent earlier in the call (e.g., 486 Busy, 503 Unavailable).

How do I prevent calls from being cut off mid-conversation if the carrier sends a spurious BYE?

Use Asterisk's SIP session timer (Session-Expires header) to ensure both ends refresh the session periodically. Add to your carrier trunk in sip.conf: session_expires = 1800, session_minse = 900. This forces the far end to re-INVITE every 30 minutes, which tears down stale calls and prevents zombie sessions. You can also configure a hangup handler that logs the BYE and re-establishes the call if the hangup cause is NETWORK_OUT_OF_ORDER or similar.

Where do I find the Q.850 cause codes in my Asterisk logs?

Search for "Reason:" in /var/log/asterisk/messages: grep "Reason:" /var/log/asterisk/messages. Extract the cause= value from the line. Compare it to the Q.850 table: 16 = normal, 17 = busy, 19 = no answer, 21 = call rejected, 31 = network failure, 42 = congestion. If cause is missing or 127, the carrier is not providing detail.

How do I export the Homer call data to a CSV for analysis?

Log in to Homer's web interface, run a search, and click the Export button (usually a down arrow icon). Select CSV format. Homer exports a list of all SIP messages in the call with timestamps, source/dest IP, SIP method, and response codes. Open in Excel or import to a database. You can then correlate the Homer data with vicidial_log by Call-ID to map SIP events to ViciDial call records.

Summary

SIP call drops with missing or vague Q.850 cause codes are usually caused by carriers stripping Reason headers or not populating them at all. Enable Homer SIP capture and Asterisk sip.conf debug on your carrier trunk, then initiate a test call that drops. Extract the BYE packet from Homer, note whether the Reason header is present and what cause code it contains. Cross-check the timestamp and Call-ID in Asterisk's /var/log/asterisk/messages logs. If the Reason header is missing, request that your carrier enable Q.850 pass-through on their SBC. If it is present, map the cause code to Asterisk's hangup_cause_id in vicidial_log and update your dialplan to log it for reporting. Verify that hangup_cause_id=yes is set in your carrier trunk definition in sip.conf. Next, query vicidial_log to confirm that hangup_cause_id is now populated on all inbound calls, and use that data to identify patterns of why calls are dropping at your carrier's edge.

Stuck on something specific?

Book a free 30-minute call. I run ViciDial centers across 3 countries and can usually unblock your setup in one session — or build it for you.

Book a Free Consultation