| # Minimal SIP entry point β guide |
|
|
| Turn a low-end Linux VPS into a **pure SIP relay** between AgentBridge and a smartphone. |
| The box does **no codec handling, no transcoding, no voicemail** β it only forwards SIP |
| signalling (Kamailio) and relays RTP (rtpengine, userspace). Both AgentBridge and the |
| phone act as *clients* of this box, so neither needs open ports at its own location. |
|
|
| ``` |
| A = AgentBridge (home PC, NAT) B = this server C = smartphone (mobile NAT) |
| A ββREGISTERβββΊ B βββINVITEββ C signalling (UDP 5060) |
| A ββββRTPβββββββΊ B βββββRTPβββ C media relay (UDP 40000-41000, no codecs) |
| ``` |
|
|
| ## Requirements |
|
|
| - Debian 12 / Ubuntu (bookworm+) VPS with a **public IP**, root access, ~256 MB RAM is enough. |
| - Access to the **provider's firewall panel** (cloud panel, e.g. IONOS): you must open |
| `UDP 5060` (SIP) and `UDP 40000-41000` (RTP relay) β without this, inbound UDP is |
| silently dropped and nothing works. This is the #1 pitfall. |
| - The AgentBridge side needs: `Registrar sip:<this-host>:5060`, `Username`, shared |
| `Password`, and a non-standard `ListenPort` **if the AgentBridge ISP drops inbound UDP |
| on 5060** (common on home lines β see troubleshooting). |
|
|
| ## Quick start (unattended) |
|
|
| Copy the whole folder to the server and run: |
|
|
| ```bash |
| sudo bash setup-entrypoint.sh |
| ``` |
|
|
| The script installs `kamailio kamailio-extra-modules rtpengine`, writes |
| `/etc/kamailio/kamailio.cfg`, `/etc/rtpengine/rtpengine.conf`, enables the services, |
| auto-generates the shared secret and prints everything you need: |
|
|
| ```text |
| SIP entry point ready |
| server: 195.20.235.5:5060/udp (SIP signalling) |
| media relay: UDP 40000-41000 (rtpengine, userspace) |
| AgentBridge: Username=agent Password=<printed> RegisterExpiry=60 |
| smartphone: proxy 195.20.235.5:5060 (UDP), ANY non-empty password, dial sip:agent@195.20.235.5 |
| REMEMBER: open UDP 5060 and UDP 40000-41000 in the provider firewall |
| ``` |
|
|
| Customize with environment variables (all optional): |
|
|
| ```bash |
| SIP_SECRET=mysecret SIP_USER=agent SIP_PUBLIC_IP=1.2.3.4 SIP_PORT=5060 sudo bash setup-entrypoint.sh |
| ``` |
|
|
| ## What the script does, step by step |
|
|
| 1. `apt install kamailio kamailio-extra-modules rtpengine` |
| (`kamailio-extra-modules` provides the `rtpengine` module; on Debian 12 there is **no |
| `rtpproxy` package** β rtpengine is the equivalent userspace media relay). |
| 2. **rtpengine** β `/etc/rtpengine/rtpengine.conf`: userspace-only (`table = -1`), media |
| ports `40000-41000`, control socket `udp:127.0.0.1:2223`. A systemd drop-in empties the |
| stock `ExecStartPre`/`ExecStartPost` (they call `rtpengine-iptables-setup`, part of a |
| package we do not need in userspace mode). |
| 3. **Kamailio** β `/etc/kamailio/kamailio.cfg` (template in this folder): |
| - `REGISTER` for the AgentBridge AOR (`__USER__`) is protected by **digest auth** |
| (shared secret, HA1 precomputed, `pv_auth_check` + `www_challenge` 401 flow). |
| - `REGISTER` for **any other user** (the phone) is answered `200 OK` but **not stored** |
| β the phone registers happily but can never hijack the AgentBridge AOR. |
| - Every `INVITE` is `rtpengine_offer/answer("relay")` (media forced through the relay) |
| and routed via `lookup("location")` to the AgentBridge registration. **NAT media |
| handling**: rtpengine's **endpoint learning must stay ON** (the default) β the relay |
| learns each leg's real NAT-mapped address from the first RTP packets and forwards |
| there. Do NOT set `endpoint-learning = off`, and do NOT rely on `fix_nated_sdp()`: |
| in Kamailio 5.6 it needs specific flags and empirically did not rewrite the private |
| SDP addresses, so with learning off the relay forwards to the unreachable private IPs |
| and the media dies (this bit us live β welcome message not heard, no DTMF feedback). |
| - `nat_bflag 6` + `received_avp`/`fix_nated_register()`: registrations behind NAT keep |
| the real source address so replies and INVITEs are routed back through the NAT. |
| - `DTMF INFO` (RFC 2976) for the AgentBridge AOR is forwarded straight to the |
| registration before the generic dialog routing (some clients build an incomplete Route |
| set and `loose_route` would drop them with 404) β the PIN gate is the real security. |
| - `listen=udp:0.0.0.0:<port> advertise <public-ip>:<port>`: Via/Contact/Record-Route must |
| carry the public IP, never the 0.0.0.0 bind address (the global `advertised_address` |
| core parameter does NOT affect `record_route()`). |
| 4. **Firewall**: the script cleans a leftover kernel-module iptables hook. The actual |
| UDP opening must be done in the **provider panel** (cloud firewalls sit outside the VM). |
| |
| ## Configure AgentBridge (the A side) |
|
|
| On the machine running AgentBridge, from the TUI: |
|
|
| ``` |
| /sip config set Enabled true |
| /sip config set Registrar sip:<this-host>:5060 |
| /sip config set Username agent |
| /sip config set Password <shared secret from the script> |
| /sip config set ListenPort 6070 # only if the home ISP blocks inbound UDP 5060 |
| /sip config set RegisterExpiry 60 # keep NAT mappings alive on consumer routers |
| /sip config set Pin 12345 |
| /sip config set Lang it |
| ``` |
|
|
| `/sip status` shows `registered on` once the entry point accepted the REGISTER. Every |
| `set` persists to `appsettings.json` and restarts the SIP transport automatically. |
| (There are also non-interactive config scripts: `AgentBridge/scripts/sip-config.bat` and |
| `sip-config.sh` β same result without the TUI.) |
|
|
| ## Configure the smartphone (the C side) |
|
|
| Any standard SIP client (Linphone, Zoiper, β¦): |
| - Account: username anything (e.g. `phone`), **any non-empty password**, domain/proxy |
| `<this-host>` port `5060`, transport **UDP**. |
| - Dial: `sip:<user>@<this-host>` β spoken welcome β type the PIN β conversation. |
|
|
| ## Testing the full chain |
|
|
| `AgentBridge/e2e/SipClientTest` is a console softphone that dials, answers the PIN with |
| DTMF, speaks an Italian TTS greeting and captures the agent's reply: |
|
|
| ```bash |
| dotnet run --project e2e\SipClientTest # Windows, defaults to sip:agent@<host> |
| dotnet run --project e2e/SipClientTest -- sip:agent@<host> 12345 5071 45 |
| ``` |
|
|
| Success looks like: `answered: Ok`, PIN sent, greeting spoken, and the agent log shows |
| `SIP caller said: Ciao, mi senti?` β `SIP agent replied: β¦` (the agent speaks the reply |
| back over RTP). |
|
|
| ## Troubleshooting |
|
|
| | Symptom | Cause / fix | |
| |---|---| |
| | REGISTER reaches the server but never completes (client retransmits) | **Provider firewall** not opened (UDP 5060), or the **home ISP drops inbound UDP 5060** β move AgentBridge to a non-standard `ListenPort` (e.g. 6070) | |
| | Calls fail intermittently (408) between successful ones | Home **NAT mapping times out** before the REGISTER refresh β set `RegisterExpiry 60` (the mapping is re-created every minute) | |
| | Phone registers but the call goes nowhere | The phone is registering with the AgentBridge AOR user β only `sip:<user>@β¦` routes to the agent; other registrations are acknowledged but not stored | |
| | Call connects, no audio | RTP range `40000-41000` blocked in the provider firewall; or a client-side firewall drops the relayed media; or `endpoint-learning = off` was set on rtpengine (relay forwards to the private SDP IPs) β keep it ON | |
| | Call rings, phone hears silence, hangs up; next call gets busy (486); 200 OK retransmitted in tcpdump | **Two Kamailio bugs** (fixed 2026-08-23 in the template): (1) the 200 OK Contact of the NAT'd AgentBridge was not rewritten β ACK/BYE to the private LAN IP were unroutable, so the dialog never stabilized and the softphone held off sending RTP (the media relay never learned its address β welcome dropped). Fix: `fix_nated_contact()` in `onreply_route`. (2) `lookup("location")` ran on *in-dialog* requests too β their RURI is the Contact URI, not the agent AOR, so ACK/BYE got 404'd. Fix: route in-dialog requests (ACK/BYE/re-INVITE) with `loose_route()` + `t_relay()` only; lookup only on new INVITEs. Re-verified end-to-end: welcome + PIN + reply all arrive through the relay. | |
| | No welcome message heard, no DTMF feedback | Same media-path cause as above (verify with `e2e/SipClientTest` β it must receive the agent's speech over RTP) | |
| | `kamcmd` unavailable on the server | The `ctl` module was added to the template after the first deploy β rerun the script or add `loadmodule "ctl.so"` + `modparam("ctl","binrpc","unix:/run/kamailio/kamailio.sock")` and restart | |
|
|