sip-doctor 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sip_doctor-0.3.2/LICENSE +21 -0
- sip_doctor-0.3.2/MANIFEST.in +2 -0
- sip_doctor-0.3.2/PKG-INFO +348 -0
- sip_doctor-0.3.2/README.md +322 -0
- sip_doctor-0.3.2/pyproject.toml +38 -0
- sip_doctor-0.3.2/setup.cfg +4 -0
- sip_doctor-0.3.2/sip_doctor.egg-info/PKG-INFO +348 -0
- sip_doctor-0.3.2/sip_doctor.egg-info/SOURCES.txt +30 -0
- sip_doctor-0.3.2/sip_doctor.egg-info/dependency_links.txt +1 -0
- sip_doctor-0.3.2/sip_doctor.egg-info/entry_points.txt +2 -0
- sip_doctor-0.3.2/sip_doctor.egg-info/top_level.txt +1 -0
- sip_doctor-0.3.2/sipdoctor/__init__.py +3 -0
- sip_doctor-0.3.2/sipdoctor/__main__.py +5 -0
- sip_doctor-0.3.2/sipdoctor/calls.py +452 -0
- sip_doctor-0.3.2/sipdoctor/capture.py +214 -0
- sip_doctor-0.3.2/sipdoctor/cli.py +694 -0
- sip_doctor-0.3.2/sipdoctor/compat.py +98 -0
- sip_doctor-0.3.2/sipdoctor/diagnose.py +570 -0
- sip_doctor-0.3.2/sipdoctor/engine.py +354 -0
- sip_doctor-0.3.2/sipdoctor/hep.py +87 -0
- sip_doctor-0.3.2/sipdoctor/htmlreport.py +80 -0
- sip_doctor-0.3.2/sipdoctor/kb.py +825 -0
- sip_doctor-0.3.2/sipdoctor/packet.py +153 -0
- sip_doctor-0.3.2/sipdoctor/pcap.py +151 -0
- sip_doctor-0.3.2/sipdoctor/render.py +595 -0
- sip_doctor-0.3.2/sipdoctor/rtp.py +304 -0
- sip_doctor-0.3.2/sipdoctor/sip.py +351 -0
- sip_doctor-0.3.2/sipdoctor/tls.py +261 -0
- sip_doctor-0.3.2/tests/live_traffic.py +109 -0
- sip_doctor-0.3.2/tests/run-matrix.sh +22 -0
- sip_doctor-0.3.2/tests/samplegen.py +1014 -0
- sip_doctor-0.3.2/tests/test_sipdoctor.py +597 -0
sip_doctor-0.3.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ramesh Audireddy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sip-doctor
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Trace SIP calls (UDP, TCP, TLS, proxies, HEP) live on a Linux server or from pcap files, and explain failures
|
|
5
|
+
Author: Ramesh Audireddy
|
|
6
|
+
Keywords: sip,voip,pcap,tcpdump,kamailio,asterisk,freeswitch,hep
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Environment :: Console
|
|
20
|
+
Classifier: Topic :: Communications :: Telephony
|
|
21
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
22
|
+
Requires-Python: >=3.6
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# sip-doctor
|
|
28
|
+
|
|
29
|
+
Trace SIP calls on a Linux server, live or from pcap files, and get a plain-English explanation of what went wrong
|
|
30
|
+
and how to fix it.
|
|
31
|
+
|
|
32
|
+
- **Transports:** SIP over UDP (including IP-fragmented messages), TCP (stream reassembly, several messages per
|
|
33
|
+
segment, lost or out-of-order segments) and TLS. For TLS, the handshake, certificate and alerts are inspected. The
|
|
34
|
+
SIP inside TLS is encrypted, so to see those calls, receive them decrypted through HEP.
|
|
35
|
+
- **Proxies and B2BUAs:** every hop of a proxied call is shown (caller → proxy → callee, including failover), so you
|
|
36
|
+
can see which hop failed. B2BUA legs with different Call-IDs (Asterisk, FreeSWITCH, SBCs) are linked together.
|
|
37
|
+
- **Audio:** every RTP stream is matched to its call through the SDP, with packet loss, jitter, dropouts, an
|
|
38
|
+
estimated MOS and DTMF digits. It detects one-way and missing audio, audio arriving from an address the SDP didn't
|
|
39
|
+
announce (NAT), and audio that starts late or stops early.
|
|
40
|
+
- **Mid-call events:** hold and resume, codec changes, transfers (REFER and their result), DTMF over SIP INFO, and
|
|
41
|
+
rejected re-INVITEs.
|
|
42
|
+
- **Live:** capture from any interface with tcpdump (the kernel filters the traffic) or a raw socket. You can also
|
|
43
|
+
receive HEP from Kamailio, OpenSIPS, FreeSWITCH or Asterisk.
|
|
44
|
+
- **Reports:** a call table, a ranked problem list, per-peer statistics, a time-window filter, JSON, and an HTML
|
|
45
|
+
report to attach to tickets.
|
|
46
|
+
- **Files:** pcap and pcapng files from tcpdump, Wireshark or `tcpdump -i any`, with Ethernet, VLAN, Linux cooked
|
|
47
|
+
capture, IPv4 or IPv6. You can also pipe a capture in on stdin.
|
|
48
|
+
- **No dependencies:** only the Python standard library is needed. It works on **Python 3.6 to 3.14**, from CentOS 7 /
|
|
49
|
+
FreePBX SNG7 and Ubuntu 18.04 (3.6) to current releases, and each version is tested to produce identical reports.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
━━ Call #1 ANSWERED 2001 → 3001 03:55:00 UDP 203.0.113.10 → 198.51.100.20
|
|
53
|
+
ringing after 50ms · answered after 1.0s · talk 32.0s · callee hung up · codecs PCMU, PCMA, telephone-event
|
|
54
|
+
Call-ID missing-ack@203.0.113.10
|
|
55
|
+
|
|
56
|
+
203.0.113.10:5060 198.51.100.20:5060
|
|
57
|
+
caller callee
|
|
58
|
+
+0.000 |---------- INVITE (SDP) ---------->|
|
|
59
|
+
+0.050 |<---------- 180 Ringing -----------| ringing after 50ms
|
|
60
|
+
+1.000 |<--------- 200 OK (SDP) -----------| answered after 1.0s
|
|
61
|
+
+1.500 |<------- 200 OK ×7 retrans --------| until +20.500 · ✖ no ACK came back
|
|
62
|
+
+33.000 |<-------------- BYE ---------------| callee hung up · talk 32.0s
|
|
63
|
+
+33.020 |---------- 200 OK (BYE) ---------->|
|
|
64
|
+
|
|
65
|
+
✖ Missing ACK: the call dropped after ~32 seconds
|
|
66
|
+
The 200 OK was sent 8 times but no ACK came back to it. The answering side gives up and hangs up after 32 seconds.
|
|
67
|
+
Fix: The ACK is sent to the 200 OK's Contact (<sip:3001@192.168.1.50:5060>) via any Record-Route proxies. Make sure that address is reachable from the caller (NAT: set the external/public address on the callee/PBX, or enable Record-Route on the proxy).
|
|
68
|
+
▲ NAT problem: 198.51.100.20 advertises a private Contact (192.168.1.50)
|
|
69
|
+
Packets come from public 198.51.100.20 but its 200 OK Contact says 192.168.1.50. In-dialog requests (ACK, BYE, re-INVITE) will be sent to an unreachable address.
|
|
70
|
+
Fix: Set the external/public IP on that device (or enable NAT handling / fix_nated_contact on the proxy).
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Install on a Linux server
|
|
74
|
+
|
|
75
|
+
Pick one of these methods.
|
|
76
|
+
|
|
77
|
+
**Single file:** no pip and no internet needed on the server.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
./deploy/build-pyz.sh # on your machine: builds dist/sip-doctor (~45 KB)
|
|
81
|
+
scp dist/sip-doctor root@server:/usr/local/bin/
|
|
82
|
+
ssh root@server sip-doctor --version
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**pipx:**
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
scp -r sip-doctor server: # or git clone
|
|
89
|
+
sudo pipx install ./sip-doctor --global # pipx >= 1.5; older pipx: sudo PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install ./sip-doctor
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Virtualenv:** this works on Debian 12 and Ubuntu 24, where system-wide `pip install` is blocked.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
sudo python3 -m venv /opt/sip-doctor
|
|
96
|
+
sudo /opt/sip-doctor/bin/pip install ./sip-doctor
|
|
97
|
+
sudo ln -s /opt/sip-doctor/bin/sip-doctor /usr/local/bin/sip-doctor
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Without installing:** run `sudo python3 -m sipdoctor ...` from inside the project folder.
|
|
101
|
+
|
|
102
|
+
**Which method suits your server's Python:**
|
|
103
|
+
|
|
104
|
+
| Server (default `python3`) | Recommended |
|
|
105
|
+
|-------------------------------------------------------------|-------------------------------------------------------------|
|
|
106
|
+
| CentOS 7, FreePBX SNG7, Ubuntu 18.04, RHEL 8 (3.6) | the single file, or `pip3 install sip_doctor-*.whl` (build it with `python -m build -w` on another machine) |
|
|
107
|
+
| Debian 10 (3.7), RHEL 8 with `python38`, Ubuntu 20.04 (3.8) | any method |
|
|
108
|
+
| Debian 11/12, Ubuntu 22.04/24.04, RHEL 9, Rocky/Alma 9 (3.9-3.12) | any method (virtualenv or pipx on Debian 12 / Ubuntu 24.04) |
|
|
109
|
+
|
|
110
|
+
`pip install` from the source folder needs Python 3.7 or newer, because the packaging tools no longer run on 3.6.
|
|
111
|
+
The single file and the wheel work on 3.6. Check the version with `python3 --version`.
|
|
112
|
+
|
|
113
|
+
Install `tcpdump` as well (`apt install tcpdump` or `dnf install tcpdump`). When tcpdump is available, sip-doctor uses
|
|
114
|
+
it so the kernel filters packets before Python sees them, which matters on busy servers. Without tcpdump, sip-doctor
|
|
115
|
+
falls back to a raw socket. Live capture needs root (or `CAP_NET_RAW`); reading files does not.
|
|
116
|
+
|
|
117
|
+
## Live tracing
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
sudo sip-doctor live # all interfaces, ports 5060,5061,5080,5081
|
|
121
|
+
sudo sip-doctor live -i eth0 -p 5060-5070 # one interface, a port range
|
|
122
|
+
sudo sip-doctor live --number 1001 # only calls from/to 1001
|
|
123
|
+
sudo sip-doctor live --ip 203.0.113.50 # only traffic to/from one peer (e.g. a trunk)
|
|
124
|
+
sudo sip-doctor live -q # no per-message lines; a call flow per finished call
|
|
125
|
+
sudo sip-doctor live -q -v # call flows with headers and SDP under each message
|
|
126
|
+
sudo sip-doctor live -w /tmp/sip.pcap --max-mb 200 # also save the SIP packets (rotates to .1)
|
|
127
|
+
sudo sip-doctor live --json-log /var/log/sip-doctor/calls.jsonl # one JSON line per finished call
|
|
128
|
+
sudo sip-doctor live --rtp # also analyse the audio: one-way audio, loss, jitter, MOS
|
|
129
|
+
sudo sip-doctor live -p any # find SIP on any port (slower: every TCP/UDP packet is checked)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Each SIP message prints as it arrives. When a call ends, its call flow and findings follow:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
20:09:06.064 #2 127.0.0.1:48548 → 127.0.0.9:5080 TCP INVITE sip:1002@127.0.0.9;transport=tcp [SDP]
|
|
136
|
+
20:09:06.118 #2 127.0.0.9:5080 → 127.0.0.1:48548 TCP 100 Trying (INVITE)
|
|
137
|
+
20:09:06.118 #2 127.0.0.9:5080 → 127.0.0.1:48548 TCP 486 Busy Here (INVITE)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
OPTIONS keep-alives and successful registrations are hidden unless they fail. Add `-a` to show everything. Press
|
|
141
|
+
Ctrl-C to stop; any calls still in progress are then summarised.
|
|
142
|
+
|
|
143
|
+
### Run it permanently (systemd)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
sudo cp deploy/sip-doctor.service /etc/systemd/system/
|
|
147
|
+
sudo systemctl daemon-reload && sudo systemctl enable --now sip-doctor
|
|
148
|
+
journalctl -u sip-doctor -f # call summaries
|
|
149
|
+
tail -f /var/log/sip-doctor/calls.jsonl | jq . # structured log
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Reading capture files
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
sip-doctor capture.pcap # overview, call table, call flow + findings per call
|
|
156
|
+
sip-doctor capture.pcap -v # call flows with key headers and SDP under each message
|
|
157
|
+
sip-doctor capture.pcap -vv # ... and the complete SIP messages
|
|
158
|
+
sip-doctor capture.pcap --no-flow # summaries and findings only
|
|
159
|
+
sip-doctor capture.pcap --all # also OPTIONS and REGISTER transactions
|
|
160
|
+
sip-doctor capture.pcap --call 7 # one call (number or part of its Call-ID)
|
|
161
|
+
sip-doctor capture.pcap --call 7 -w call7.pcap # save that call's SIP and RTP, for Wireshark (Telephony › VoIP Calls › Play)
|
|
162
|
+
sip-doctor capture.pcap --from 14:00 --to 14:30 # only calls in that window (or 'YYYY-MM-DD HH:MM')
|
|
163
|
+
sip-doctor capture.pcap --html report.html # also save the report as a web page, to attach to a ticket
|
|
164
|
+
sip-doctor capture.pcap --no-rtp # skip the audio analysis
|
|
165
|
+
sip-doctor capture.pcap --json # machine-readable (includes audio streams and events)
|
|
166
|
+
sudo tcpdump -i any -w - port 5060 | sip-doctor read - # analyse a live tcpdump stream
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Call flow through a proxy that asks for authentication (in a capture with more than 20 calls, flows are drawn
|
|
170
|
+
only for calls with problems unless you add `-l`):
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
10.0.0.5:5060 10.0.0.1:5060 10.0.0.9:5060
|
|
174
|
+
caller proxy callee
|
|
175
|
+
+0.000 |------------ INVITE (SDP) ----------->| |
|
|
176
|
+
+0.010 |< 407 Proxy Authentication Required --| |
|
|
177
|
+
+0.020 |---------------- ACK ---------------->| |
|
|
178
|
+
+0.030 |------------ INVITE (SDP) ----------->| |
|
|
179
|
+
+0.040 |<------------ 100 Trying -------------| |
|
|
180
|
+
+0.050 | |------------ INVITE (SDP) ----------->|
|
|
181
|
+
+0.060 | |<------------ 100 Trying -------------|
|
|
182
|
+
+0.800 | |<----------- 180 Ringing -------------|
|
|
183
|
+
+0.810 |<----------- 180 Ringing -------------| |
|
|
184
|
+
+3.000 | |<----------- 200 OK (SDP) ------------|
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## What a call report contains
|
|
188
|
+
|
|
189
|
+
Each call shows its result and timings, who called whom (display name, P-Asserted-Identity, forwarding), the software
|
|
190
|
+
on each side, every proxy hop with its response time, a timeline of mid-call events, an audio quality table, and the
|
|
191
|
+
call flow with RTP streams and notes on each step:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
━━ Call #1 ANSWERED alice → 1012 04:15:00 UDP 10.0.0.5 → 10.0.0.9
|
|
195
|
+
ringing after 300ms · answered after 1.0s · talk 18.2s · caller hung up · codecs PCMU, PCMA, telephone-event
|
|
196
|
+
caller name "Alice Smith" · asserted identity +14155550123 · forwarded from 1000 · setup 1.0s
|
|
197
|
+
Call-ID holdxfer@10.0.0.5
|
|
198
|
+
Software: caller Yealink T54W 96.86 · callee Asterisk PBX 20.5.0
|
|
199
|
+
Events:
|
|
200
|
+
+5.000 caller put the call on hold → 200 OK
|
|
201
|
+
+9.000 caller resumed the call → 200 OK
|
|
202
|
+
+10.000 re-INVITE from caller: media update (G729) → 488 Not Acceptable Here
|
|
203
|
+
+14.000 caller transferred the call to 1099 → 202 Accepted
|
|
204
|
+
+15.100 transfer result: 404 Not Found
|
|
205
|
+
Audio:
|
|
206
|
+
→ callee 10.0.0.5:20030 → 10.0.0.9:30030 PCMU 900 pkts 18.0s loss 0.0% jitter 0ms MOS 4.4
|
|
207
|
+
→ caller 10.0.0.9:30030 → 10.0.0.5:20030 PCMU 900 pkts 18.0s loss 0.0% jitter 0ms MOS 4.4
|
|
208
|
+
|
|
209
|
+
10.0.0.5:5060 10.0.0.9:5060
|
|
210
|
+
caller callee
|
|
211
|
+
+0.000 |---------- INVITE (SDP) ---------->|
|
|
212
|
+
+0.300 |<---------- 180 Ringing -----------| ringing after 300ms
|
|
213
|
+
+1.000 |<--------- 200 OK (SDP) -----------| answered after 1.0s
|
|
214
|
+
+1.020 |--------------- ACK -------------->|
|
|
215
|
+
+1.100 |======== RTP PCMU 900 pkts =======>| 18.0s · loss 0.0% · jitter 0ms · MOS 4.4
|
|
216
|
+
+1.100 |<======= RTP PCMU 900 pkts ========| 18.0s · loss 0.0% · jitter 0ms · MOS 4.4
|
|
217
|
+
+5.000 |---------- INVITE (SDP) ---------->| caller put the call on hold → 200 OK
|
|
218
|
+
+5.050 |<--------- 200 OK (SDP) -----------|
|
|
219
|
+
+5.060 |--------------- ACK -------------->|
|
|
220
|
+
+9.000 |---------- INVITE (SDP) ---------->| caller resumed the call → 200 OK
|
|
221
|
+
+9.050 |<--------- 200 OK (SDP) -----------|
|
|
222
|
+
+9.060 |--------------- ACK -------------->|
|
|
223
|
+
+10.000 |---------- INVITE (SDP) ---------->| re-INVITE from caller: media update (G729) → 488 Not Acceptable Here
|
|
224
|
+
+10.050 |<---- 488 Not Acceptable Here -----|
|
|
225
|
+
+10.060 |--------------- ACK -------------->|
|
|
226
|
+
+11.000 |-------------- INFO -------------->| DTMF 4
|
|
227
|
+
+11.010 |<--------- 200 OK (INFO) ----------|
|
|
228
|
+
+12.000 |-------------- INFO -------------->| DTMF 2
|
|
229
|
+
+12.010 |<--------- 200 OK (INFO) ----------|
|
|
230
|
+
+14.000 |-------------- REFER ------------->| caller transferred the call to 1099 → 202 Accepted
|
|
231
|
+
+14.020 |<----- 202 Accepted (REFER) -------|
|
|
232
|
+
...
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The audio table flags bad streams:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
Audio:
|
|
239
|
+
→ caller 10.0.0.99:41000 → 10.0.0.5:20020 PCMU 848 pkts 20.0s loss 15.2% jitter 25ms MOS 2.9 1 dropout(s) ≤1.5s
|
|
240
|
+
→ callee 10.0.0.5:20020 → 10.0.0.9:30020 PCMU 1000 pkts 20.0s loss 0.0% jitter 0ms MOS 4.4
|
|
241
|
+
|
|
242
|
+
✖ Poor audio callee → caller: estimated MOS 2.9
|
|
243
|
+
▲ Audio callee → caller comes from 10.0.0.99:41000, not from the SDP address
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
With many calls, the report first prints a call table, a **Problems** list ranked across the whole capture, and a
|
|
247
|
+
**Peers** table: for every trunk, gateway or phone that INVITEs were sent to, it shows attempts, answer rate (ASR),
|
|
248
|
+
calls with no reply, average reply time and post-dial delay, and failure codes. RTP that no captured SIP set up (for
|
|
249
|
+
example because the SIP was encrypted) is listed separately.
|
|
250
|
+
|
|
251
|
+
## Troubleshooting explanations
|
|
252
|
+
|
|
253
|
+
The first time each kind of problem appears in a report, sip-doctor prints a full troubleshooting entry under it:
|
|
254
|
+
|
|
255
|
+
- **What it means:** the problem in plain words.
|
|
256
|
+
- **User impact:** what callers notice.
|
|
257
|
+
- **Likely causes.**
|
|
258
|
+
- **How to confirm:** numbered checks with concrete commands.
|
|
259
|
+
- **How to fix.**
|
|
260
|
+
- **Platform settings:** the relevant setting or command for Asterisk (PJSIP and chan_sip), FreeSWITCH, Kamailio and
|
|
261
|
+
OpenSIPS.
|
|
262
|
+
- **The RFC.**
|
|
263
|
+
|
|
264
|
+
Later occurrences of the same problem point back to it. Add `--brief` to hide the explanations.
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
✖ Missing ACK: the call dropped after ~32 seconds
|
|
268
|
+
...
|
|
269
|
+
┌─ Troubleshooting: Missing ACK
|
|
270
|
+
│ What it means: When a call is answered, the callee sends 200 OK and the caller must confirm it with an ACK...
|
|
271
|
+
│ User impact: The call connects and audio may work, but it always drops after about 32 seconds.
|
|
272
|
+
│ Likely causes:
|
|
273
|
+
│ • NAT: the callee's 200 OK has a private Contact (192.168.x.x / 10.x.x.x), so the ACK goes nowhere
|
|
274
|
+
│ ...
|
|
275
|
+
│ How to confirm:
|
|
276
|
+
│ 1. Look at the 200 OK's Contact and Record-Route headers (run with -v)
|
|
277
|
+
│ ...
|
|
278
|
+
│ Platform settings:
|
|
279
|
+
│ Asterisk (PJSIP): ... rtp_symmetric=yes, force_rport=yes, rewrite_contact=yes, direct_media=no
|
|
280
|
+
│ Kamailio: record_route() for initial INVITEs; loose_route() + handle_ruri_alias() for in-dialog requests ...
|
|
281
|
+
└─
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
You can also look anything up directly:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
sip-doctor explain # list every topic
|
|
288
|
+
sip-doctor explain one-way-audio # a problem
|
|
289
|
+
sip-doctor explain 488 # a SIP status code: meaning, causes, checks, fixes, platform commands
|
|
290
|
+
sip-doctor explain q850-34 # a Q.850 cause from a Reason header
|
|
291
|
+
sip-doctor explain unknown_ca # a TLS alert
|
|
292
|
+
sip-doctor explain mos jitter asr # terms
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`--json` output includes each finding's `topic` and, once per topic, the full explanation under `explanations`.
|
|
296
|
+
|
|
297
|
+
## SIP over TLS: what you can see
|
|
298
|
+
|
|
299
|
+
On the wire, sip-doctor shows each TLS connection's SNI, TLS version, server certificate (TLS 1.2 and older), alerts,
|
|
300
|
+
and whether the handshake completed. It reports:
|
|
301
|
+
|
|
302
|
+
- a certificate rejected by the client (unknown CA, self-signed), an expired certificate, or a name mismatch
|
|
303
|
+
- version or cipher mismatches, and a client certificate required (mutual TLS)
|
|
304
|
+
- a refused or timed-out TCP connection to 5061, and plain SIP sent to a TLS port
|
|
305
|
+
|
|
306
|
+
The SIP messages inside TLS are encrypted. To trace those calls, have the proxy or PBX send a decrypted copy over
|
|
307
|
+
HEP and run:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
sudo sip-doctor live --hep 9060 # HEP plus the normal capture
|
|
311
|
+
sip-doctor live -i none --hep 9060 # HEP only (no root needed)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
| Server | HEP configuration |
|
|
315
|
+
|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
316
|
+
| Kamailio | `loadmodule "siptrace.so"` · `modparam("siptrace", "duplicate_uri", "sip:127.0.0.1:9060")` · `modparam("siptrace", "hep_mode_on", 1)` · `modparam("siptrace", "hep_version", 3)`, then call `sip_trace()` in your routes (or set `trace_mode`) |
|
|
317
|
+
| OpenSIPS | `loadmodule "proto_hep.so"` · `modparam("proto_hep", "hep_id", "[hep_dst] 127.0.0.1:9060;transport=udp;version=3")` · `loadmodule "tracer.so"` · `modparam("tracer", "trace_id", "[tid]uri=hep:hep_dst")`, then `trace("tid", "d", "sip");` |
|
|
318
|
+
| FreeSWITCH | `sofia.conf.xml`: `<param name="capture-server" value="udp:127.0.0.1:9060"/>`; profile: `<param name="sip-capture" value="yes"/>` (or `sofia global capture on`) |
|
|
319
|
+
| Asterisk | load `res_hep` and `res_hep_pjsip`; `hep.conf`: `enabled = yes`, `capture_address = 127.0.0.1:9060` |
|
|
320
|
+
|
|
321
|
+
HEP received on UDP 9060 inside a pcap file is decoded automatically.
|
|
322
|
+
|
|
323
|
+
## What it diagnoses
|
|
324
|
+
|
|
325
|
+
| Area | Findings |
|
|
326
|
+
|---------------|--------------------------------------------------------------------------------------------------------------------------------|
|
|
327
|
+
| Reachability | no response from a hop (with retransmission count), INVITE retransmissions, TCP connection refused or timed out, TCP reset |
|
|
328
|
+
| Call failures | every 4xx/5xx/6xx with its meaning and fix, which hop sent it, per-hop results through proxies, `Reason:` (Q.850) and `Warning:` headers |
|
|
329
|
+
| Auth | challenge with no credentials sent, and wrong username/password (401/407/403 after credentials) for calls and registrations |
|
|
330
|
+
| Dialog | missing ACK (the call drops after 32 s), BYE rejected (481), session-timer expiry, slow post-dial delay |
|
|
331
|
+
| NAT | private Contact or SDP audio address sent from a public IP (no ACK/BYE, one-way audio) |
|
|
332
|
+
| Media (SDP) | 488/606 with the offered codecs, audio rejected (port 0), no common codec, SRTP |
|
|
333
|
+
| Audio (RTP) | one-way or no audio, packet loss, jitter, dropouts, MOS, RTCP loss reports, NAT source mismatch, unexpected codec, late or early-stopping audio, DTMF (RFC 4733) |
|
|
334
|
+
| Mid-call | hold/resume, re-INVITE or UPDATE rejected (488, 491 glare), transfer (REFER) and its result, DTMF via SIP INFO, PRACK |
|
|
335
|
+
| Peers | per destination: attempts, ASR, no-reply count, reply time, post-dial delay, failure codes |
|
|
336
|
+
| Transport | SIP over UDP above ~1300 bytes or IP-fragmented, TLS failures (see above) |
|
|
337
|
+
| Other | REGISTER results, unanswered OPTIONS pings (repeats collapsed as ×N), failed MESSAGE/SUBSCRIBE/REFER |
|
|
338
|
+
|
|
339
|
+
## Development
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
python3 -m unittest discover -s tests # tests (no pytest needed)
|
|
343
|
+
tests/run-matrix.sh # the tests on Python 3.6 ... 3.14 in Docker
|
|
344
|
+
python3 tests/samplegen.py samples # regenerate the example captures in samples/
|
|
345
|
+
sip-doctor samples/all_scenarios.pcap # every scenario in one file (see samples/README.md for the list)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`tests/live_traffic.py` sends real SIP over loopback (UDP, TCP, HEP), for testing `sip-doctor live` on Linux.
|