First steps for SIP Bridge

This commit is contained in:
Christopher Cookman 2026-06-29 08:15:12 -06:00
parent 409a12ea89
commit 824e235191
8 changed files with 775 additions and 0 deletions

52
acSipBridge/astrocom.md Normal file
View file

@ -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)

161
acSipBridge/extensions.conf Normal file
View file

@ -0,0 +1,161 @@
; =====================================================; GLOBALS REQUIRED (globals.conf or globals_custom.conf)
; -------------------------------------------------------------
; [globals]
; BRIDGE1_APIKEY=<routes.apiKey for bridge 1>
; BRIDGE1_EXCHANGE=777 ; NXX prefix — used to build the outbound ANI only,
; ; NOT applied to inbound DIDs (full number passes through)
; BRIDGE2_APIKEY=<routes.apiKey for bridge 2>
; 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/<apiKey>/<ANI>/<number>
;
; The API returns one of:
; "local"
; --> same block; route to this bridge's own SIP PBX
; "IAX2/<auth>:<secret>@<host>:<port>/<number>"
; --> dial that URI verbatim
; "<MSG_ROUTE_ADDRESS>/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=<routes.apiKey for bridge 1>
; BRIDGE1_EXCHANGE=777 ; NXX prefix (3 digits) for bridge 1
; BRIDGE2_APIKEY=<routes.apiKey for bridge 2>
; 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.)
; ============================================================

View file

@ -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/<EXTEN>
; │
; ▼
; Downstream PBX / phone
;
; OUTBOUND from downstream PBX (SIP → IAX2 toward AstroCom):
;
; Downstream PBX ──SIP──▶ [bridgeN-from-pbx]
; │
; Bridge to IAX2 peer
; │
; IAX2/bridge1-astrocom/<EXTEN>
; │
; ▼
; 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.
; ============================================================

0
acSipBridge/globals.conf Normal file
View file

71
acSipBridge/iax.conf Normal file
View file

@ -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/<routes.auth>:<routes.secret>@<routes.server>:<routes.port>/<number>
;
; 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 = <this server's public IP>
; 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

112
acSipBridge/pjsip.conf Normal file
View file

@ -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:<this server's IP>: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

74
acSipBridge/sip.conf Normal file
View file

@ -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 : <this server's IP>: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/<exten>)
; → 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

157
acSipBridge/theory.md Normal file
View file

@ -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/<EXTEN>`. 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/<apiKey>/<ANI>/<number>`
4. The API returns either `"local"` (callee is on the same PBX — route
back via `SIP/bridge1-pbx/<exten>`) or a full
`IAX2/<auth>:<secret>@<host>:<port>/<number>` 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:<bridge server IP>: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.