diff --git a/acSipBridge/astrocom.md b/acSipBridge/astrocom.md new file mode 100644 index 0000000..e1cef4c --- /dev/null +++ b/acSipBridge/astrocom.md @@ -0,0 +1,52 @@ +AstroCom integration for freePBX (Originally made for TandmX by @lachesis_._ @lachesis_._) +1. (caveats: assumes full exchange, assumes 4 digit extensions, assumes you're okay with AstroCom users dialing anything 4-digit on your switch) +2. Put the following in your `globals_custom.conf`: +``` +astrocomkey=(long-key-you-got-from-admins) +``` +3. Put the following in your `iax_custom.conf`: +``` +[from-astrocom] +type=user +username=from-astrocom +secret=(short-secret-you-sent-to-astrocom) +auth=md5 +encryption=yes +context=from-astrocom +``` +4. Put the following in `extensions_custom.conf`, *change 777 to your AstroCom exchange prefix*, and uncomment one or both blocks directly below +``` +[from-internal-additional-custom] +; Change 777 to your exchange prefix +; Uncomment for 7 digit dialing to AstroCom +;exten => _XXXXXXX,1,NoOp +; same => n,Set(CALLERID(num)=777${CALLERID(num)}) +; same => n,Goto(astrocom-dial,${EXTEN},1) +; same => n,Hangup + +; Uncomment for 1-300-XXX-XXXX to AstroCom +;exten => _1300XXXXXXX,1,NoOp +; same => n,Set(CALLERID(num)=777${CALLERID(num)}) +; same => n,Goto(astrocom-dial,${EXTEN:4},1) +; same => n,Hangup + +[astrocom-dial] +exten => _X!,1,Set(number=${EXTEN}) + same => n,Set(lookup=${CURL(https://api.astrocom.tel/api/v1/route/${astrocomkey}/${FILTER(0-9,${CALLERID(num)})}/${FILTER(0-9,${number})})}) + same => n,GotoIf($["${lookup}"=="local"]?local:long) + same => n(local),Goto(astrocom-exchange,${number},1) + same => n(long),Dial(${lookup}) + same => n,Hangup + +[from-astrocom] +exten => _X!,1,NoOp() + same => n,Goto(astrocom-exchange,${EXTEN},1) + same => n,Hangup + +; Change 777 to your exchange prefix +[astrocom-exchange] +exten => _777XXXX,1,Goto(from-internal,${EXTEN:3},1) + same => n,Hangup +``` + +Literally picked straight from tandmx (will make our own docs eventually, but this'll get us going) \ No newline at end of file diff --git a/acSipBridge/extensions.conf b/acSipBridge/extensions.conf new file mode 100644 index 0000000..84d9ce7 --- /dev/null +++ b/acSipBridge/extensions.conf @@ -0,0 +1,161 @@ +; =====================================================; GLOBALS REQUIRED (globals.conf or globals_custom.conf) +; ------------------------------------------------------------- +; [globals] +; BRIDGE1_APIKEY= +; BRIDGE1_EXCHANGE=777 ; NXX prefix — used to build the outbound ANI only, +; ; NOT applied to inbound DIDs (full number passes through) +; BRIDGE2_APIKEY= +; BRIDGE2_EXCHANGE=888 +; ; ...add one pair per bridge; AstroCom SIP Bridge -- Dialplan (extensions.conf) +; /etc/asterisk/extensions.conf +; +; CALL FLOWS +; ------------------------------------------------------------- +; +; INBOUND (AstroCom -> downstream PBX via PJSIP) +; ----------------------------------------------- +; AstroCom delivers the full 7-digit number, e.g. 7771234. +; The full number is forwarded as-is to the downstream PBX as +; the SIP Request-URI / To: user (DID). The PBX is responsible +; for routing it internally however it sees fit. +; +; AstroCom --IAX2--> [bridgeN-from-astrocom] +; | +; Pass full DID (e.g. 7771234) to PBX +; | +; Dial PJSIP/7771234@bridgeN-pbx +; | +; v +; Downstream PBX +; +; OUTBOUND (downstream PBX -> AstroCom via IAX2) +; ------------------------------------------------ +; The PBX dials the full 7-digit AstroCom number. +; The bridge prepends the local exchange so the ANI is also +; 7 digits, then hits the AstroCom routing API: +; +; GET https://api.astrocom.tel/api/v1/route/// +; +; The API returns one of: +; "local" +; --> same block; route to this bridge's own SIP PBX +; "IAX2/:@:/" +; --> dial that URI verbatim +; "/4xx" +; --> rejected; play busy/congestion toward PBX +; +; Downstream PBX --SIP--> [bridgeN-from-pbx] +; | +; CURL api.astrocom.tel/api/v1/route/... +; | +; +----------+----------+ +; local IAX2 URI +; | | +; Dial PJSIP/XXXX@bridgeN-pbx Dial ${lookup} +; +; ISOLATION +; ------------------------------------------------------------- +; * Every bridge has exactly two contexts; they share nothing. +; * [default] silently drops anything with no context match. +; * Inter-bridge calls route through AstroCom automatically -- +; no extra config needed here. +; +; GLOBALS REQUIRED (globals.conf or globals_custom.conf) +; ------------------------------------------------------------- +; [globals] +; BRIDGE1_APIKEY= +; BRIDGE1_EXCHANGE=777 ; NXX prefix (3 digits) for bridge 1 +; BRIDGE2_APIKEY= +; BRIDGE2_EXCHANGE=888 +; ; ...add one pair per bridge +; ============================================================ + +[default] +exten => _X!,1,Verbose(2,DROP: ${EXTEN} landed in [default] -- no matching context) + same => n,Hangup(21) + +; ============================================================ +; --- BRIDGE 1 ----------------------------------------------- +; ============================================================ + +; Inbound from AstroCom -> forward full DID to PBX +; Context name == routes.auth == IAX2 section name in iax.conf +[bridge1-from-astrocom] +exten => _XXXXXXX,1,Verbose(2,BRIDGE1 inbound from AstroCom: ${CALLERID(all)} -> ${EXTEN}) + same => n,Dial(PJSIP/${EXTEN}@bridge1-pbx,30,t) + same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) + same => n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?congestion) + same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer) + same => n,Hangup() + same => n(busy),Busy(5) + same => n(congestion),Congestion(5) + same => n(noanswer),Hangup(19) + +; Outbound from Bridge-1 PBX -> CURL AstroCom API -> IAX2 +[bridge1-from-pbx] +exten => _XXXXXXX,1,Verbose(2,BRIDGE1 outbound from PBX: ${CALLERID(all)} -> ${EXTEN}) + ; Build the 7-digit ANI: exchange prefix + bare extension from CallerID + same => n,Set(ANI=${BRIDGE1_EXCHANGE}${FILTER(0-9,${CALLERID(num)})}) + ; Ask AstroCom where to send this call + same => n,Set(lookup=${CURL(https://api.astrocom.tel/api/v1/route/${BRIDGE1_APIKEY}/${ANI}/${EXTEN})}) + same => n,Verbose(2,BRIDGE1 AstroCom lookup: ${lookup}) + ; "local" -- callee shares our block, route to our own SIP PBX + same => n,GotoIf($["${lookup}" = "local"]?local) + ; Empty or error URL -- reject + same => n,GotoIf($["${lookup}" = ""]?reject) + ; Valid IAX2 URI -- dial it verbatim + same => n,Dial(${lookup},30,t) + same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) + same => n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?congestion) + same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer) + same => n,Hangup() + ; Local path -- strip exchange prefix, dial extension on own PBX + same => n(local),Verbose(2,BRIDGE1 local call: routing ${EXTEN} to own PBX) + same => n,Dial(PJSIP/${EXTEN}@bridge1-pbx,30,t) + same => n,Hangup() + ; Error paths + same => n(reject),Verbose(2,BRIDGE1 AstroCom rejected call: ${lookup}) + same => n,Congestion(5) + same => n(busy),Busy(5) + same => n(congestion),Congestion(5) + same => n(noanswer),Hangup(19) + +; ============================================================ +; --- BRIDGE 3 (copy & uncomment for each additional bridge) - +; ============================================================ +; Also add BRIDGE3_APIKEY and BRIDGE3_EXCHANGE to globals.conf. +; +;[bridge3-from-astrocom] +;exten => _X!,1,Verbose(2,BRIDGE3 inbound: ${CALLERID(all)} -> ${EXTEN}) +; same => n,Dial(PJSIP/${EXTEN}@bridge3-pbx,30,t) +; same => n,Hangup() +; +;[bridge3-from-pbx] +;exten => _X!,1,Verbose(2,BRIDGE3 outbound: ${CALLERID(all)} -> ${EXTEN}) +; same => n,Set(ANI=${BRIDGE3_EXCHANGE}${FILTER(0-9,${CALLERID(num)})}) +; same => n,Set(lookup=${CURL(https://api.astrocom.tel/api/v1/route/${BRIDGE3_APIKEY}/${ANI}/${EXTEN})}) +; same => n,GotoIf($["${lookup}" = "local"]?local) +; same => n,GotoIf($["${lookup}" = ""]?reject) +; same => n,Dial(${lookup},30,t) +; same => n,Hangup() +; same => n(local),Dial(PJSIP/${EXTEN}@bridge3-pbx,30,t) +; same => n,Hangup() +; same => n(reject),Congestion(5) + +; ============================================================ +; --- OPTIONAL: Direct SIP inter-bridge shortcut ------------- +; +; Bridges can already call each other through AstroCom with +; zero extra config. If you want direct SIP routing between +; bridges (no AstroCom hop), add a higher-priority pattern for +; the other bridge's block BEFORE the _X! catch-all. +; +; Example -- Bridge-1 calling Bridge-2's 888XXXX block directly: +; +;[bridge1-from-pbx] +;exten => _888XXXX,1,Verbose(2,BRIDGE1->BRIDGE2 direct: ${EXTEN}) +; same => n,Dial(PJSIP/${EXTEN}@bridge2-pbx,30,t) +; same => n,Hangup() +; +; (The _X! rule below then handles all other numbers normally.) +; ============================================================ diff --git a/acSipBridge/extensions.conf.bak b/acSipBridge/extensions.conf.bak new file mode 100644 index 0000000..d7d7aea --- /dev/null +++ b/acSipBridge/extensions.conf.bak @@ -0,0 +1,148 @@ +; ============================================================ +; AstroCom SIP Bridge — Dialplan (extensions.conf) +; /etc/asterisk/extensions.conf +; +; CALL FLOW +; ───────────────────────────────────────────────────────────── +; +; INBOUND from AstroCom (IAX2 → SIP toward downstream PBX): +; +; AstroCom ──IAX2──▶ [bridgeN-from-astrocom] +; │ +; Bridge to SIP peer +; │ +; SIP/bridgeN-pbx/ +; │ +; ▼ +; Downstream PBX / phone +; +; OUTBOUND from downstream PBX (SIP → IAX2 toward AstroCom): +; +; Downstream PBX ──SIP──▶ [bridgeN-from-pbx] +; │ +; Bridge to IAX2 peer +; │ +; IAX2/bridge1-astrocom/ +; │ +; ▼ +; AstroCom +; +; ISOLATION +; ───────────────────────────────────────────────────────────── +; • Each bridge uses its own pair of contexts +; (bridgeN-from-astrocom / bridgeN-from-pbx). +; • No cross-bridge dialplan references exist UNLESS you +; explicitly uncomment the inter-bridge section at the +; bottom. Until then each bridge is a closed island. +; • The [default] context is intentionally empty / drops +; calls to prevent anything landing there from escaping. +; ============================================================ + +; ============================================================ +; Safety net — calls that arrive in no recognised context are +; silently dropped. +; ============================================================ +[default] +exten => _X.,1,Verbose(2,DROP: call to ${EXTEN} landed in [default] — no context matched) + same => n,Hangup(21) + +; ============================================================ +; ─── BRIDGE 1 ─────────────────────────────────────────────── +; ============================================================ + +; ------ Inbound from AstroCom → forward to Bridge-1 PBX ---- +; This context name MUST equal routes.auth for this bridge. +[bridge1-from-astrocom] +; Accept any extension that AstroCom sends for this block. +exten => _X.,1,Verbose(2,BRIDGE1 inbound from AstroCom: ${CALLERID(all)} → ${EXTEN}) + same => n,Set(CALLERID(name)=${CALLERID(name)}) + same => n,Dial(SIP/bridge1-pbx/${EXTEN},30,t) + same => n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?congestion) + same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) + same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer) + same => n,Hangup() + + same => n(congestion),Congestion(5) + same => n(busy),Busy(5) + same => n(noanswer),Hangup(19) + +; ------ Inbound from Bridge-1 PBX → forward to AstroCom ---- +[bridge1-from-pbx] +exten => _X.,1,Verbose(2,BRIDGE1 outbound from PBX: ${CALLERID(all)} → ${EXTEN}) + ; Strip any leading '9' access code the PBX may prepend, e.g. "9XXXXXXX" + ; Uncomment/adapt if needed: + ;same => n,Set(EXTEN=${EXTEN:1}) + same => n,Set(CALLERID(name)=${CALLERID(name)}) + same => n,Dial(IAX2/bridge1-astrocom/${EXTEN},30,t) + same => n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?congestion) + same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) + same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer) + same => n,Hangup() + + same => n(congestion),Congestion(5) + same => n(busy),Busy(5) + same => n(noanswer),Hangup(19) + +; ============================================================ +; ─── BRIDGE 2 ─────────────────────────────────────────────── +; ============================================================ + +[bridge2-from-astrocom] +exten => _X.,1,Verbose(2,BRIDGE2 inbound from AstroCom: ${CALLERID(all)} → ${EXTEN}) + same => n,Set(CALLERID(name)=${CALLERID(name)}) + same => n,Dial(SIP/bridge2-pbx/${EXTEN},30,t) + same => n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?congestion) + same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) + same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer) + same => n,Hangup() + + same => n(congestion),Congestion(5) + same => n(busy),Busy(5) + same => n(noanswer),Hangup(19) + +[bridge2-from-pbx] +exten => _X.,1,Verbose(2,BRIDGE2 outbound from PBX: ${CALLERID(all)} → ${EXTEN}) + same => n,Set(CALLERID(name)=${CALLERID(name)}) + same => n,Dial(IAX2/bridge2-astrocom/${EXTEN},30,t) + same => n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?congestion) + same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy) + same => n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?noanswer) + same => n,Hangup() + + same => n(congestion),Congestion(5) + same => n(busy),Busy(5) + same => n(noanswer),Hangup(19) + +; ============================================================ +; ─── BRIDGE 3 (template — duplicate & uncomment) ──────────── +; ============================================================ +;[bridge3-from-astrocom] +;exten => _X.,1,Verbose(2,BRIDGE3 inbound from AstroCom: ${CALLERID(all)} → ${EXTEN}) +; same => n,Dial(SIP/bridge3-pbx/${EXTEN},30,t) +; same => n,Hangup() +; +;[bridge3-from-pbx] +;exten => _X.,1,Verbose(2,BRIDGE3 outbound from PBX: ${CALLERID(all)} → ${EXTEN}) +; same => n,Dial(IAX2/bridge3-astrocom/${EXTEN},30,t) +; same => n,Hangup() + +; ============================================================ +; ─── OPTIONAL: Inter-bridge dialling ──────────────────────── +; +; If you want Bridge-1's PBX to be able to call Bridge-2's +; numbers *directly* (without round-tripping through AstroCom) +; you can include the other bridge's inbound SIP context here. +; +; Example: in [bridge1-from-pbx], add a pattern for Bridge-2's +; number block *before* the catch-all _X. rule so it is +; preferred. Bridge-2's block in AstroCom is consulted to +; determine the number range. +; +;[bridge1-from-pbx] +;exten => _7XXXX,1,Verbose(2,BRIDGE1→BRIDGE2 direct: ${EXTEN}) +; same => n,Dial(SIP/bridge2-pbx/${EXTEN},30,t) +; same => n,Hangup() +; +; Without uncommenting the above, bridge1 and bridge2 can still +; reach each other — calls just travel via AstroCom as normal. +; ============================================================ diff --git a/acSipBridge/globals.conf b/acSipBridge/globals.conf new file mode 100644 index 0000000..e69de29 diff --git a/acSipBridge/iax.conf b/acSipBridge/iax.conf new file mode 100644 index 0000000..0791761 --- /dev/null +++ b/acSipBridge/iax.conf @@ -0,0 +1,71 @@ +; ============================================================ +; AstroCom SIP Bridge — IAX2 Configuration (chan_iax2) +; /etc/asterisk/iax.conf +; +; HOW ASTROCOM CALLS THIS BRIDGE +; ────────────────────────────── +; When AstroCom routes a call here it dials: +; IAX2/:@:/ +; +; That means: +; • The SECTION NAME (in brackets) IS the IAX2 username AstroCom +; authenticates with. It must equal routes.auth exactly. +; • 'secret' must equal routes.secret exactly. +; • 'context' is where the call lands in extensions.conf. +; It must also equal routes.auth (the dialplan context is +; named identically for clarity). +; • type=user — AstroCom calls us; we never initiate an IAX2 +; connection back to AstroCom. Outbound calls use a +; dynamically-generated IAX2 URI from the routing API +; (handled in extensions.conf) and need no peer config here. +; +; NAT NOTE +; ──────── +; Do NOT use 'register =>'. If this server has a dynamic IP, +; use tools/astrocom_dynamic_ip.sh (or the /api/v1/user/update +; endpoint) to keep routes.server current in AstroCom. +; ============================================================ + +[general] +bindport=4569 +bindaddr=0.0.0.0 +iaxcompat=yes +authdebug=no +bandwidth=high +jitterbuffer=yes +forcejitterbuffer=yes +dropcount=2 +maxjitterbuffer=1000 +jittertargetextra=40 + +; ============================================================ +; Shared user template +; ============================================================ +[bridge-iax-tmpl](!) +type=user +auth=md5 +encryption=yes +disallow=all +allow=ulaw +allow=alaw +allow=g729 +qualify=yes + +; ============================================================ +; BRIDGE 1 +; routes.auth = bridge1-from-astrocom ← section name +; routes.secret = BRIDGE1_IAX_SECRET +; routes.server = +; routes.port = 4569 +; ============================================================ +[bridge1-from-astrocom](bridge-iax-tmpl) +secret=20fbb81ba96091af1fe45858a070c7 ; Must equal routes.secret in AstroCom +context=bridge1-from-astrocom ; Must equal routes.auth in AstroCom + + +; ============================================================ +; BRIDGE 3 (copy this block for every additional bridge) +; ============================================================ +;[bridge3-from-astrocom](bridge-iax-tmpl) +;secret=BRIDGE3_IAX_SECRET +;context=bridge3-from-astrocom diff --git a/acSipBridge/pjsip.conf b/acSipBridge/pjsip.conf new file mode 100644 index 0000000..6c99f9b --- /dev/null +++ b/acSipBridge/pjsip.conf @@ -0,0 +1,112 @@ +; ============================================================ +; AstroCom SIP Bridge — PJSIP Configuration (chan_pjsip) +; /etc/asterisk/pjsip.conf +; +; Downstream PBXes REGISTER to this server. +; Each bridge requires three stanzas sharing the same base name: +; +; [bridgeN-pbx] type=endpoint — codecs, context, links to auth/aor +; [bridgeN-pbx] type=aor — stores the registered contact URI +; [bridgeN-pbx-auth] type=auth — username / password the PBX uses +; +; The PBX must be configured to register with: +; Registrar : sip::5060 +; Username : bridgeN-pbx (must match auth.username below) +; Password : the 'password' value in [bridgeN-pbx-auth] +; +; Outbound calls to this PBX from the dialplan: +; Dial(PJSIP/${EXTEN}@bridgeN-pbx) +; Asterisk substitutes the registered contact URI automatically. +; +; Disable/remove sip.conf (or set chan_sip bindport=0) so both +; modules do not compete for port 5060. +; ============================================================ + +; ============================================================ +; Transport — one UDP listener for all bridges +; ============================================================ +[bridge-transport] +type=transport +protocol=udp +bind=0.0.0.0:5060 +; For NAT — uncomment and fill in: +;local_net=192.168.0.0/16 +;external_signaling_address=PUBLIC_IP +;external_media_address=PUBLIC_IP + +; ============================================================ +; Shared endpoint template +; ============================================================ +[bridge-endpoint-tmpl](!) +type=endpoint +transport=bridge-transport +disallow=all +allow=ulaw +allow=alaw +allow=g729 +dtmf_mode=rfc4733 +; Force media through this server — required if either side is behind NAT +direct_media=no + +; ============================================================ +; BRIDGE 1 +; Auth username : bridge1-pbx +; Auth password : (set below — must match PBX registration password) +; Inbound calls from this PBX land in [bridge1-from-pbx] (extensions.conf) +; ============================================================ +[bridge1-pbx](bridge-endpoint-tmpl) +type=endpoint +auth=bridge1-pbx-auth +aors=bridge1-pbx +context=bridge1-from-pbx + +[bridge1-pbx-auth] +type=auth +auth_type=userpass +username=bridge1-pbx +password=1242ebce8b420ee4723887ffe0a01211330851776862e6021f680f1406c94eff + +[bridge1-pbx] +type=aor +max_contacts=1 +remove_existing=yes + +; ============================================================ +; BRIDGE 2 +; ============================================================ +[bridge2-pbx](bridge-endpoint-tmpl) +type=endpoint +auth=bridge2-pbx-auth +aors=bridge2-pbx +context=bridge2-from-pbx + +[bridge2-pbx-auth] +type=auth +auth_type=userpass +username=bridge2-pbx +password=BRIDGE2_SIP_SECRET + +[bridge2-pbx] +type=aor +max_contacts=1 +remove_existing=yes + +; ============================================================ +; Additional bridges — copy this triple and increment N +; ============================================================ +;[bridge3-pbx](bridge-endpoint-tmpl) +;type=endpoint +;auth=bridge3-pbx-auth +;aors=bridge3-pbx +;context=bridge3-from-pbx +; +;[bridge3-pbx-auth] +;type=auth +;auth_type=userpass +;username=bridge3-pbx +;password=BRIDGE3_SIP_SECRET +; +;[bridge3-pbx] +;type=aor +;max_contacts=1 +;remove_existing=yes diff --git a/acSipBridge/sip.conf b/acSipBridge/sip.conf new file mode 100644 index 0000000..1401c84 --- /dev/null +++ b/acSipBridge/sip.conf @@ -0,0 +1,74 @@ +; ============================================================ +; AstroCom SIP Bridge — SIP Configuration (chan_sip) +; /etc/asterisk/sip.conf +; +; Downstream PBXes REGISTER to this server. +; Each bridge has one [bridgeN-pbx] peer with host=dynamic. +; +; The PBX must be configured to register with: +; Registrar : :5060 +; Username : bridgeN-pbx (the section name below) +; Password : the 'secret' value below +; +; 'context' is where calls FROM the PBX land (outbound to AstroCom). +; It must match [bridgeN-from-pbx] in extensions.conf. +; ============================================================ + +[general] +bindport=5060 +bindaddr=0.0.0.0 +realm=ac-sip-bridge +srvlookup=no +; NAT handling — set to 'force_rport,comedia' if PBX is behind NAT +nat=no +qualify=no +disallow=all +allow=ulaw +allow=alaw +allow=g729 + +; ============================================================ +; Shared peer template +; ============================================================ +[bridge-sip-tmpl](!) +type=friend +host=dynamic ; IP is learned from the REGISTER request +dtmfmode=rfc2833 +canreinvite=no +disallow=all +allow=ulaw +allow=alaw +allow=g729 +qualify=yes +; No 'insecure=' — registration provides proper authentication. +; Removing it means unauthenticated INVITEs from unknown IPs are rejected. + +; ============================================================ +; BRIDGE 1 +; PBX registers as username 'bridge1-pbx' with the secret below. +; Outbound to this PBX: Dial(SIP/bridge1-pbx/) +; → delivered to the registered contact URI. +; ============================================================ +[bridge1-pbx](bridge-sip-tmpl) +secret=1242ebce8b420ee4723887ffe0a01211330851776862e6021f680f1406c94eff +; If the PBX must register with a different username, set it here: +;username=bridge1 +fromuser=bridge1-pbx ; identity used in outbound SIP From: headers +context=bridge1-from-pbx + +; ============================================================ +; BRIDGE 2 +; PBX registers as username 'bridge2-pbx' with the secret below. +; ============================================================ +[bridge2-pbx](bridge-sip-tmpl) +secret=BRIDGE2_SIP_SECRET +fromuser=bridge2-pbx +context=bridge2-from-pbx + +; ============================================================ +; Additional bridges — copy this block and increment N +; ============================================================ +;[bridge3-pbx](bridge-sip-tmpl) +;secret=BRIDGE3_SIP_SECRET +;fromuser=bridge3-pbx +;context=bridge3-from-pbx diff --git a/acSipBridge/theory.md b/acSipBridge/theory.md new file mode 100644 index 0000000..2f1aaf1 --- /dev/null +++ b/acSipBridge/theory.md @@ -0,0 +1,157 @@ +# AstroCom SIP Bridge — Design Notes + +This directory contains a set of Asterisk configuration files that act as a +**protocol bridge** between IAX2 (used by AstroCom) and SIP (used by +downstream, non-Asterisk phone systems such as FreeSWITCH, 3CX, Cisco +UCM, or any generic SIP PBX). + +--- + +## Files + +| File | Purpose | +|------|---------| +| `iax.conf` | IAX2 peers — one per bridge, pointing at AstroCom | +| `pjsip.conf` | PJSIP endpoints — one triple (endpoint/auth/aor) per bridge | +| `extensions.conf` | Dialplan — contexts that wire IAX↔PJSIP for each bridge | + +--- + +## Architecture + +``` + ┌─────────────────────────────────┐ + │ AstroCom Platform │ + │ (IAX2 server, routes DB) │ + └──────────┬──────────────────────-┘ + │ IAX2 (port 4569) + ┌───────────────┼────────────────┐ + │ │ │ + IAX2 peer IAX2 peer IAX2 peer + bridge1-astrocom bridge2-astrocom bridgeN-astrocom + │ │ │ + ┌───────┴───────────────┴────────────────┴──────┐ + │ Bridge Server │ + │ (this Asterisk instance) │ + │ │ + │ [bridge1-from-astrocom] [bridge1-from-pbx] │ + │ [bridge2-from-astrocom] [bridge2-from-pbx] │ + │ [bridgeN-from-astrocom] [bridgeN-from-pbx] │ + └───────┬───────────────┬────────────────┬──────┘ + │ │ │ + SIP peer SIP peer SIP peer + bridge1-pbx bridge2-pbx bridgeN-pbx + │ │ │ + ▼ ▼ ▼ + PBX / System 1 PBX / System 2 PBX / System N +``` + +### Inbound call (AstroCom → PBX) + +1. AstroCom looks up the dialled number in its `routes` table and finds + the bridge server's IP (`routes.server`) and port (`routes.port`). +2. AstroCom dials over IAX2 using the shared secret (`routes.secret`) + and delivers the call to the context named in `routes.auth` + (e.g. `bridge1-from-astrocom`). +3. The bridge's dialplan receives the call in `[bridge1-from-astrocom]` + and dials `SIP/bridge1-pbx/`. Because the PBX has already + registered (`host=dynamic`), Asterisk knows the current contact URI + and sends the INVITE directly to it. + +### Outbound call (PBX → AstroCom) + +1. The downstream PBX has registered to this server as `bridge1-pbx`. + It sends a SIP INVITE for the target number. +2. `sip.conf` authenticates the REGISTER/INVITE against `secret` and + lands the call in context `bridge1-from-pbx`. +3. The dialplan CURLs the AstroCom routing API: + `GET /api/v1/route///` +4. The API returns either `"local"` (callee is on the same PBX — route + back via `SIP/bridge1-pbx/`) or a full + `IAX2/:@:/` URI which is dialled + verbatim. + +--- + +## AstroCom Database Requirements + +For each bridge you must create one row in the `routes` table: + +| Column | Value | +|--------|-------| +| `server` | Bridge server's public IP | +| `port` | `4569` (IAX2) | +| `auth` | `bridgeN-from-astrocom` (must match context name) | +| `secret` | The shared IAX2 secret (`BRIDGEN_IAX_SECRET`) | +| `block_start` | First number in this bridge's number block | +| `block_length` | Size of the block (default 9999) | +| `apiKey` | Auto-generated by AstroCom on creation | + +--- + +## Isolation Model + +Each bridge is **completely isolated** by default: + +- IAX2 peers use separate contexts (`bridge1-from-astrocom`, etc.) +- PJSIP endpoints use separate contexts (`bridge1-from-pbx`, etc.) +- No context includes or jumps to another bridge's context +- The `[default]` context drops all unmatched calls + +### Calling between bridges + +Calls between Bridge-1 and Bridge-2 work through AstroCom's normal +routing — Bridge-1's PBX dials a number, it travels to AstroCom via +IAX2, AstroCom routes it to Bridge-2's context, and it arrives at +Bridge-2's PBX via PJSIP. **No special config is needed for this.** + +If you want **direct PJSIP** bridging (bypassing AstroCom for inter-bridge +calls), see the commented-out section at the bottom of `extensions.conf`. + +--- + +## Adding a New Bridge + +1. **`iax.conf`** — Copy the `[bridge1-from-astrocom]` block, rename it to + `[bridgeN-from-astrocom]`, and set a new `secret` and `context`. + +2. **`pjsip.conf`** — Copy the three stanzas for `bridge1-pbx`, rename them + to `bridgeN-pbx` / `bridgeN-pbx-auth`, set a new `password`, and update + `context`. The downstream PBX must be configured to register with: + - **Registrar**: `sip::5060` + - **Username**: `bridgeN-pbx` + - **Password**: the `password` value in `[bridgeN-pbx-auth]` + +3. **`extensions.conf`** — Copy both the `[bridge1-from-astrocom]` and + `[bridge1-from-pbx]` context blocks, rename them to + `[bridgeN-from-astrocom]` / `[bridgeN-from-pbx]`, and update the + `Dial()` targets and global variable names. + Add `BRIDGEN_APIKEY` and `BRIDGEN_EXCHANGE` to `globals.conf`. + +4. **AstroCom** — Create the route via the admin panel or API with + `auth = bridgeN-from-astrocom` and the matching `secret`. + +5. Reload Asterisk: + ``` + asterisk -rx "pjsip reload" + asterisk -rx "iax2 reload" + asterisk -rx "dialplan reload" + ``` + +--- + +## Notes + +- **Codecs**: `ulaw`, `alaw`, and `g729` are negotiated; adjust per your + network and licensing constraints. +- **NAT**: If a downstream PBX is behind NAT, set `direct_media=no` (already + in the template) and add `local_net` / `external_signaling_address` / + `external_media_address` to `[bridge-transport]` in `pjsip.conf`. +- **No `register =>`** for IAX2: IP updates use `tools/astrocom_dynamic_ip.sh` + or the `/api/v1/user/update` API endpoint — not IAX2 registration. +- **Security**: Each bridge's IAX2 secret should be a long random string + (AstroCom generates one via `crypto.randomBytes(15).toString('hex')` + if none is specified during route creation). +- **`sip.conf`**: The old `chan_sip` config is superseded by `pjsip.conf`. + Ensure `chan_sip` is not loaded (`noload => chan_sip.so` in `modules.conf`) + to avoid both modules fighting over port 5060.