diagnostics: System → Diagnostics with a network test probe; RTP_PORT_START/END

The server checks itself (public IP, DNS, local networks, phone ACL, SIP transports, free audio
ports) and says how to fix each problem. A quick or complete network test asks the probe
(apps/probe, own image) to test this server from outside: HTTP/HTTPS, SIP over UDP/TCP/TLS, the
browser phone's /ws, sampled audio ports. The probe only tests the address that asks.
The audio port range comes from RTP_PORT_START/END (default 10000-20000).
This commit is contained in:
iamdoubz committed 2026-10-21 12:37:16 -05:00
1 parent 90b3081368
commit 65a0438dde
50 files changed
+1704 -59

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ server is behind one; see [Network](network.md)):
| 80 | TCP | Only with the bundled Caddy: getting the HTTPS certificate |
| 5060 | UDP (+TCP) | SIP: voip.ms and desk phones |
| 5061 | TCP | Only if you turn on SIP over TLS |
| 10000–20000 | UDP | Call audio (RTP) |
| 10000–20000 | UDP | Call audio (RTP); another range with `RTP_PORT_START`/`RTP_PORT_END` ([Network](network.md#call-audio-ports)) |
Everything else (the database, the api on 8080, Asterisk's 5038 and 8088) listens on
127.0.0.1 only. Don't open them.
+54 -2
View File
@@ -15,7 +15,7 @@ the wrong place.
"<router model> disable SIP ALG".
2. **Forward ports** to the server's LAN IP:
- UDP 5060 (SIP);
- UDP 10000–20000 (audio);
- UDP 10000–20000 (audio; see [Call audio ports](#call-audio-ports) to use another range);
- TCP 443 (and 80 with the bundled Caddy);
- TCP 5061 only if you use SIP over TLS.
3. **Double NAT** (the ISP's box in front of your own router): forward the ports on both, or put
@@ -34,6 +34,58 @@ the wrong place.
![Settings → Network](img/settings-network.png)
## Call audio ports
Call audio (RTP) uses UDP **10000–20000** unless you set another range in `.env` (or, in a
Portainer stack, in the api's environment):
```sh
RTP_PORT_START=40000
RTP_PORT_END=40199
```
Then `docker compose up -d` (the api writes the range into Asterisk's config), and open **the same
range** on the firewall and the router instead of 10000–20000. Each call uses two ports, so 200
ports is plenty for a small office. Pick a range nothing else on the server uses: System →
Diagnostics counts how many ports in the range are already taken.
## Diagnostics: is the server reachable?
**System → Diagnostics** checks what usually stops calls from working outside the office, and
says how to fix each problem it finds.
**This server** runs every time you open the page:
- the public IP is set, and is the address the server really reaches the internet from;
- the host name's DNS record points at that address (with Cloudflare, it must be "DNS only": its
proxy carries web pages, not SIP);
- the server's LAN address is in the local networks;
- whether phones may sign in from anywhere (the phone network limit refuses phone apps on mobile
data), and whether phone setup is limited to the local networks;
- Asterisk is listening on the SIP ports;
- how many call audio ports are free.
**From the internet** needs a **network test probe**: a small program, run on a machine outside
your network, that connects back to the server the way a phone on mobile data or your SIP provider
does. A server can't test this itself: most routers either don't route a connection to their own
public address back in, or route it without going through the port forwards.
- **Quick network test**: TCP 80 and 443, and SIP on 5060 over UDP (and TCP if it's on).
- **Complete network test**: also SIP over TLS (if on), the browser phone's `/ws`, and a sample
of the call audio ports (the server listens on them while the probe sends to them).
To run a probe, on any machine outside your network (a small VPS is plenty):
```sh
docker compose -f deploy/probe/compose.yml up -d
```
Then put its address, for example `http://203.0.113.50:8080`, in **Settings → Network → Network
test probe**. The probe only ever tests the address a request comes from, so nobody can use it
against someone else, and each address gets a few tests per 10 minutes. Its SIP test is one
OPTIONS request; if you run fail2ban or CrowdSec, add the probe's address to their allow list so a
test never counts toward a ban.
## Firewall
Allow only the ports in the [port table](README.md#ports). All SelfPhoneNG containers use the
@@ -46,7 +98,7 @@ ufw allow OpenSSH
ufw allow 443/tcp
ufw allow 80/tcp # only with the bundled Caddy
ufw allow 5060/udp
ufw allow 10000:20000/udp
ufw allow 10000:20000/udp # or your RTP_PORT_START:RTP_PORT_END
ufw enable
```
+16 -1
View File
@@ -49,11 +49,26 @@ The call connects but one side (or both) hears nothing. It's almost always NAT
1. **SIP ALG** must be off on every router in the path. This is the first suspect.
2. **Public IP:** Settings → Network must show the server's current public IP. If it changed,
update it and restart telephony (`docker compose restart asterisk`).
3. **Audio ports:** UDP 10000–20000 must be forwarded to the server and open on its firewall.
3. **Audio ports:** UDP 10000–20000 (or your `RTP_PORT_START`–`RTP_PORT_END`) must be forwarded to the server and open on its firewall. **System → Diagnostics → Complete network test** checks it from outside.
4. **Local networks** (setup wizard, Network step) must list your office networks, and only them.
5. **Calls drop after about 30 seconds:** same causes. The answer never reaches the other side,
so it gives up.
## Phones work on the office Wi-Fi, not on mobile data
A phone app (Linphone) registers and calls on the office network, but not elsewhere. Open
**System → Diagnostics**: it checks each of these and says what to change.
1. **The phone network limit** (Settings → Security, "Only accept desk phones from the networks
below") refuses phones outside the local networks. Turn it off, or add the networks they use.
2. **Public IP** (Settings → Network) must be set: otherwise phones outside are given the
server's private address.
3. **DNS**: the phone finds the server by its host name, which must point at the public IP. Behind
Cloudflare's proxy (orange cloud) web pages work but SIP doesn't: set it to "DNS only".
4. **Port forwards**: UDP 5060 and the audio ports forwarded on the router, as well as open on the
server's firewall. On the office Wi-Fi the phone reaches the server directly, so missing
forwards only show up outside. The **Quick network test** checks 5060 from outside.
## The trunk won't register
Trunks shows **Rejected** or **Not registered**.
+4 -1
View File
@@ -382,7 +382,10 @@ rtpend = 20000
#include selfphoneng/rtp_selfphoneng.conf
```
(The generator writes `icesupport`, `stunaddr` and `[ice_host_candidates]` into
`rtp_selfphoneng.conf`, see `03-config-generator.md` §4.)
`rtp_selfphoneng.conf`, see `03-config-generator.md` §4. Since 2026-10-21 it also writes
`rtpstart`/`rtpend` there from the api's `RTP_PORT_START`/`RTP_PORT_END`: Asterisk takes a
setting's last value, so they win over the defaults above, which only cover the first boot.
`call-test.ts` checks `rtp show settings` with 31000–31200.)
### 3.7 `pjsip.conf`
```ini
+1 -1
View File
@@ -48,7 +48,7 @@ Left sidebar navigation, in this order:
| SMS | `/admin/sms` | Same UI as the user one, with all DIDs |
| Blocked numbers | `/admin/blocked` | |
| Settings | `/admin/settings` | Tabs: General, Network (public IP detect, LAN IP, local nets, STUN/TURN), SIP (ports, TCP/TLS), Numbering ranges, Email (SMTP + test), Voicemail, Recording (retention, announcement sound + **legal notice text, 08 §5**), Emergency (default E911 caller ID, notify emails/extensions + a **test notification** button that does NOT dial 911), Backups (schedule, keep, include recordings, list + download + "Back up now"), Webhooks, API tokens. Security: desk phone networks, **outside calls at once** (per extension, whole system; 0 = no limit), phone setup files, SIP TLS |
| System | `/admin/system` | Status JSON pretty-printed, Asterisk version, generator runs list, "Apply config now", "Rollback", "Restart telephony", generated-files viewer (read-only, masked), allowlisted CLI dropdown (04 §7.3) with output panel, audit log. As built (owner follow-ups to M11-T7, 2026-10-20), laid out after 3CX's admin dashboard (not copied): a **System information** panel (version, host name, company, public IP static/dynamic, LAN IPs, ports SIP/SIPS/media/HTTPS, telephony running, restart needed; copy buttons) with how to restart (`docker compose restart asterisk` on the server; no restart button), and an **Event log** panel (severity, event code, time, source, details; Load more; Download = `/api/system/events.csv`). Events (`system_events`, kept 90 days): `trunk.down`/`trunk.up` (registration changes), `telephony.down`/`telephony.up` (AMI), `telephony.restartNeeded`, `config.failed`, `emergency.call`/`emergency.test`, `network.publicIpChanged`, `backup.done`/`backup.failed`, `disk.low`. Admin edits stay in the audit log (not shown yet). The dashboard's `restart_required` warning links here. Generator runs, generated files and CLI views aren't built; the admin guide's troubleshooting page covers them from the shell |
| System | `/admin/system` | Status JSON pretty-printed, Asterisk version, generator runs list, "Apply config now", "Rollback", "Restart telephony", generated-files viewer (read-only, masked), allowlisted CLI dropdown (04 §7.3) with output panel, audit log. As built (owner follow-ups to M11-T7, 2026-10-20), laid out after 3CX's admin dashboard (not copied): a **System information** panel (version, host name, company, public IP static/dynamic, LAN IPs, ports SIP/SIPS/media/HTTPS, telephony running, restart needed; copy buttons) with how to restart (`docker compose restart asterisk` on the server; no restart button),, a **Diagnostics** panel (08 §2.6: the server's own checks, then a Quick or Complete network test through the probe, each line with how to fix it), and an **Event log** panel (severity, event code, time, source, details; Load more; Download = `/api/system/events.csv`). Events (`system_events`, kept 90 days): `trunk.down`/`trunk.up` (registration changes), `telephony.down`/`telephony.up` (AMI), `telephony.restartNeeded`, `config.failed`, `emergency.call`/`emergency.test`, `network.publicIpChanged`, `backup.done`/`backup.failed`, `disk.low`. Admin edits stay in the audit log (not shown yet). The dashboard's `restart_required` warning links here. Generator runs, generated files and CLI views aren't built; the admin guide's troubleshooting page covers them from the shell |
### 2.1 First-run setup wizard (`/setup`)
+23
View File
@@ -95,6 +95,29 @@ get the proxy's address only (below). The setting is on Settings → Security, w
- `.env` mode 0600 (install.sh sets it).
- Recordings and voicemail are served only through authenticated api routes, never as static files.
### 2.6 Network diagnostics (owner, 2026-10-21)
System → Diagnostics (`services/diagnostics.ts`, admin only). Every failed or doubtful line says
how to fix it.
- **This server** (`GET /api/system/diagnostics`): public IP set and equal to what
api.ipify.org sees; `PUBLIC_HOSTNAME`'s A records include it (Cloudflare proxy named in the fix);
`network.lanIp` inside `network.localNets`; the phone ACL (§2.2) and `provisioning.lanOnly`
flagged as limits on phones elsewhere; `pjsip show transports` has the enabled transports; the
RTP range's even ports bound once each, a warning past a quarter taken; whether a probe is set.
- **From the internet** (`POST /api/system/diagnostics/network {mode}`) needs the probe,
`apps/probe` (no dependencies, its own image `selfphoneng-probe`, `deploy/probe/compose.yml`),
run outside the network; `network.probeUrl` points at it. A server can't test its own ports:
hairpin NAT is missing or skips the port forwards, and browsers refuse port 5060.
- `POST /v1/check {host, tests}`: the probe tests **only the address the request came from** (the
last X-Forwarded-For entry with `TRUST_PROXY=true`), so it can't be aimed at anyone else; 20
tests at most, 10 checks per address per 10 minutes, 4 s per test.
- Quick: HTTP 80, HTTPS 443 `/healthz` (status and certificate), SIP OPTIONS over UDP (and TCP
when on) to `sip.udpPort`. Complete adds SIP over TLS (when on), a WebSocket upgrade on `/ws`
(protocol `sip`), and up to four even ports of the RTP range: the api binds them and the probe
sends a token to each.
- The answer's `ip` is checked too: a private one means the probe is inside the network; another
public one than `network.publicIp` means a VPN or a changed address.
- The RTP range comes from `RTP_PORT_START`/`RTP_PORT_END` (api env, default 10000–20000; 01 §3.6).
## 3. Operations
### 3.1 Install / update