Skip to content

Trunking SIP ​

CallAgent termină SIP el însuși, prin FreeSWITCH, în loc să împacheteze un carrier gestionat, așa că aducerea trunk-ului tău e o cale de primă clasă: carrier-ul tău, DID-urile tale, tarifele tale.

Folosește-l când ai deja o relație cu un carrier, când numerele tale nu pot fi mutate, sau când ai nevoie de un trunk pentru care CallAgent nu are adaptor. Dacă nu ești în niciunul dintre cazurile astea, un număr gestionat e mai puțină muncă.

Starea onestă ​

Citește asta înainte să îți planifici o migrare

Niciun apel pe trunk SIP nu a ajuns vreodată la un asistent.

Autentificarea trunk-ului funcționează și e dovedită pe un peer SIP real: registration, provocare digest, raportarea eșecului de credențiale, admitere și refuz prin ACL, reînregistrare înainte de expirare. Toate astea sunt reale.

Ce nu funcționează e pasul de după admitere — predarea media către voice gateway. Un INVITE de intrare e admis, marcat cu DID-ul și trunk-ul lui, transferat către extensia de predare a media, și moare acolo cu handoff-failed. Cauza e că dialplan-ul FreeSWITCH așteaptă un socket ESL de ieșire pe portul 8084, iar gateway-ul ascultă pe un singur port, 3010, pentru HTTP și WebSocket. Pe 8084 nu ascultă nimic.

Tot ce urmează despre configurarea unui trunk e corect și merită făcut. Tot ce urmează se oprește înainte de audio. Detalii complete în Ce nu este construit.

Modelul ​

Patru obiecte, în ordinea asta:

ObiectCe este
TrunkRelația cu un carrier: credențiale sau allowlist de IP-uri, codec-uri, mod DTMF, transport, formatarea numerelor, limite.
GatewayUn host către care trimiți, sau de la care primești, sub trunk-ul acela. Un trunk cu mai multe gateways are failover; fiecare e verificat separat.
DIDUn număr de intrare, sau un tipar, pe trunk-ul acela, și unde ar trebui să meargă un apel către el.
RutăIeșire: prin ce trunk pleacă un număr apelat.

Fiecare gateway devine un fișier de profil Sofia generat, pe partea de FreeSWITCH, numit cu ULID-ul gateway-ului. Numele acela e cel pe care îl apelează dialplan-ul și cu care e marcat un apel de intrare, deci e stabil la redenumiri.

Creează un trunk ​

API-ul de telefonie e camelCase. (API-ul de asistenți e snake_case; vezi convențiile API.)

bash
curl -X POST "$CALLAGENT_API_URL/telephony/sip-trunks" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Carrier A",
    "direction": "both",
    "authMode": "credentials",
    "username": "1002001",
    "password": "…",
    "realm": "sip.carrier-a.example",
    "register": true,
    "registerExpirySeconds": 3600,
    "transport": "udp",
    "codecs": ["PCMA", "PCMU", "G722"],
    "dtmfMode": "rfc2833",
    "inboundRequireAcl": true,
    "inboundSourceAcl": ["203.0.113.0/24"],
    "outboundCallerIdNumber": "+40312345901",
    "maxConcurrentCalls": 30
  }'

Apoi cel puțin un gateway:

bash
curl -X POST "$CALLAGENT_API_URL/telephony/sip-trunks/$TRUNK_ID/gateways" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "sbc-1",
    "host": "sbc1.carrier-a.example",
    "port": 5060,
    "priority": 10,
    "weight": 100
  }'

Gateways-urile sunt încercate crescător după priority, apoi descrescător după weight în interiorul unei priorități. Un gateway cu failoverOnly: true se sortează ultimul, indiferent de prioritate.

Fiecare câmp de trunk ​

CâmpValoriImplicit
directioninbound · outbound · both—
authModecredentials · ip · bothcredentials
registerbooleanfalse
registerExpirySeconds60–864003600
retrySeconds5–360030
transportudp · tcp · tlsudp
srtpPolicydisabled · optional · requireddisabled
verifyTlsPeerboolean
codecslistă de nume["PCMA","PCMU","G722","OPUS"]
dtmfModerfc2833 · inband · inforfc2833
optionsPingEnabled, optionsPingSeconds, keepaliveSeconds5–3600
inboundRequireAcl, inboundSourceAclCIDR-uri
inboundDestinationType / Id / Valuerezerva când niciun DID nu se potrivește
inboundStripDigits, inboundNumberPrefixnormalizarea numerelor
outboundCallerIdNumber / Name / Privacy
leadingPluskeep · strip · add · international_prefix
internationalPrefix, outboundPrefix, outboundStripDigits
allowSipUriDialing, customSipHeaders
maxConcurrentCalls, maxCallsPerSecond

password e criptată de model și nu e întoarsă niciodată de vreun endpoint.

Când authMode e ip, XML-ul Sofia generat omite complet username, password și auth-username, în loc să scrie valori de umplutură, deci un trunk cu autentificare pe IP greșit configurat eșuează vizibil, în loc să se înregistreze ca un trunk cu credențiale stricat.

Codec-uri ​

codecs e emis ca absolute_codec_string — exact lista aia, în ordinea aia, fără negociere de rezervă. G.729 nu e în lista implicită și nu e în setul de module FreeSWITCH livrat. Gateway-ul normalizează tot la PCM mono 16 kHz intern, deci un trunk de 8 kHz și un browser de 48 kHz întâlnesc același pipeline.

DTMF ​

Trei moduri, per trunk pentru că nu sunt carrier-ii de acord între ei:

dtmfModeValoarea SofiaNotă
rfc2833rfc2833Implicit. Evenimente RTP, în afara benzii.
infoinfoMesaje SIP INFO.
inbandnoneSofia nu detectează; detectorul Goertzel propriu al CallAgent citește audio-ul. Fragil la transcodare — așteaptă-te la rateuri pe un leg G.729 sau intens transcodat.

dtmfDurationMs e implicit 2000, iar interDigitDelayMs 120.

O divergență care merită știută

DtmfModeSchema din contractul TypeScript declară cinci valori — rfc2833, rfc4733, sip_info, inband, auto. Enum-ul PHP care generează efectiv configurația Sofia are trei: rfc2833, inband, info. Configurează prin API, cu cele trei.

Rutarea unui apel de intrare ​

Când sosește un apel, resolverul le încearcă în ordinea asta și o ia pe prima care se potrivește:

  1. o rută de intrare care se potrivește pe număr, după prioritate;
  2. hook-ul dinamic al numărului, dacă e configurat;
  3. destinația statică a numărului;
  4. destinația de rezervă a trunk-ului (inboundDestinationType / Id / Value) — doar la intrare pe SIP.

Pentru un trunk anume, pașii 3 și 4 sunt precedați de tabela de DID-uri a trunk-ului: rândurile SipDid se potrivesc primele, apoi un rând din phone_numbers al cărui număr e egal cu DID-ul normalizat, apoi rezerva trunk-ului.

Nicio potrivire înseamnă nimic, iar apelantul e respins. E intenționat: rutarea unui apel pe care nu îl poți identifica spre un asistent implicit e felul în care apelantul unui tenant ajunge la agentul altui tenant.

Tenantul e legat din rândul care s-a potrivit, niciodată din ceva din request.

DID-uri ​

bash
curl -X POST "$CALLAGENT_API_URL/telephony/sip-trunks/$TRUNK_ID/dids" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "+40312345901",
    "destinationType": "assistant",
    "destinationId": "'"$ASSISTANT_ID"'",
    "priority": 100
  }'

Un DID se poate potrivi exact pe number sau pe pattern, cu un matchType. Tipurile de destinație sunt aceleași zece ca peste tot: assistant, squad, queue, ivr, ring_group, extension, voicemail, external, sip_uri, hook.

De unde e citit DID-ul se stabilește per trunk: inboundDidSource e request_uri (implicit), to_header, sau header cu inboundDidHeader care îl numește. Carrier-ii nu sunt de acord între ei pe subiectul ăsta mai mult decât pe oricare altul.

Cele patru endpoint-uri de test ​

Există pentru că „trunk-ul e configurat" și „trunk-ul funcționează" sunt afirmații diferite. Fii precis cu privire la ce afirmație susține fiecare.

EndpointCe face de fapt
POST .../test/optionsTrimite un SIP OPTIONS către fiecare gateway și înregistrează răspunsul. Un pachet real pe fir.
POST .../test/registerÎncearcă un REGISTER și înregistrează rezultatul, inclusiv motivul dat de carrier la eșec. Un pachet real pe fir.
POST .../test/inboundO verificare de pregătire a configurației, nu un apel. Verifică dacă trunk-ul permite intrarea, dacă ACL-ul nu e gol-și-obligatoriu, și dacă există o mapare de DID sau o destinație de rezervă. Raportează last_inbound_at dacă a sosit vreodată un apel, iar altfel o spune în atâtea cuvinte: „No inbound call has arrived on this trunk yet — place one to confirm end to end."
POST .../test/outboundPrimește o destination și încearcă un apel real prin trunk.

GET .../diagnostics întoarce rezultatele înregistrate, cele mai noi primele. GET .../config întoarce XML-ul Sofia generat, per gateway, plus fragmentul de ACL, ca să poți face diff înainte să aplici.

Cum aplici configurația generată

Scrierea XML-ului și reîncărcarea lui în FreeSWITCH e php artisan callagent:sip:apply. php artisan callagent:sip:options-probe trimite OPTIONS către fiecare gateway activ, pe schedule, și înregistrează rezultatul.

Request-uri SIP în dialog ​

Tools-urile sipRequest trimit un request în dialog în timpul apelului — supervizare specifică unui carrier, sau un transfer cu headere custom. Vezi Tools.

Cum îl dovedești fără un carrier ​

Repository-ul livrează un peer de test. telephony/sip-peer/ e un container Kamailio care se comportă ca un carrier pe două socketuri — autentificare digest pe 5060, autentificare pe IP pe 5062 — în spatele profilului de compose sip-lab.

bash
./telephony/sip-peer/prove.sh

Dovedește cinci lucruri pe socketuri reale: un REGISTER provocat care reușește; o parolă greșită care eșuează cu 401 și scoate motivul pe rândul trunk-ului; un INVITE admis de la o adresă permisă; un INVITE refuzat cu 403 de la una care nu e; și reînregistrarea înainte de expirare.

Nu poate dovedi ce poate dovedi doar un carrier: digest SHA-256, TLS și SRTP pe un SBC real, ciudățenii de NAT și de keepalive, DID-uri reale, prezentarea caller ID-ului, facturarea sau tratarea 302/Diversion. Și nu poate dovedi audio-ul, din motivul de la începutul paginii.

API ​

GET/POST         /api/v1/telephony/sip-trunks
GET/PATCH/DELETE /api/v1/telephony/sip-trunks/{id}
POST             /api/v1/telephony/sip-trunks/{id}/test/options
POST             /api/v1/telephony/sip-trunks/{id}/test/register
POST             /api/v1/telephony/sip-trunks/{id}/test/inbound
POST             /api/v1/telephony/sip-trunks/{id}/test/outbound
GET              /api/v1/telephony/sip-trunks/{id}/diagnostics
GET              /api/v1/telephony/sip-trunks/{id}/config

GET/POST         /api/v1/telephony/sip-trunks/{trunk}/gateways
PATCH/DELETE     /api/v1/telephony/sip-trunks/{trunk}/gateways/{id}
POST             /api/v1/telephony/sip-trunks/{trunk}/gateways/{id}/test

GET/POST         /api/v1/telephony/sip-trunks/{trunk}/dids
PATCH/DELETE     /api/v1/telephony/sip-trunks/{trunk}/dids/{id}

Parametri și răspunsuri complete: trunk-uri SIP, gateways, DID-uri.

Scrisă după cod. Ce nu este încă implementat este marcat ca atare, niciodată descris ca funcțional.