Merge pull request #215 from outis1one/claude/sip-voip-integration-atsins

Fix off-by-one in the NANP outbound dial patterns
This commit is contained in:
Outis
2026-07-23 16:01:33 -04:00
committed by GitHub
2 changed files with 16 additions and 6 deletions
+12 -2
View File
@@ -174,7 +174,7 @@ separately from that hourly check.
server hostname, and DID are all prompted at install time (VoIP.ms is only server hostname, and DID are all prompted at install time (VoIP.ms is only
the suggested default), so any provider supporting IP auth works. the suggested default), so any provider supporting IP auth works.
- An outbound dialplan route matching US numbers only — **implemented**: - An outbound dialplan route matching US numbers only — **implemented**:
`_1NXXNXXXXX` (11-digit NANP with leading 1) and `_NXXNXXXXX` (10-digit, `_1NXXNXXXXXX` (11-digit NANP with leading 1) and `_NXXNXXXXXX` (10-digit,
auto-prefixed with 1), both routed to the trunk. No catch-all `_X.` auto-prefixed with 1), both routed to the trunk. No catch-all `_X.`
pattern. **Also implemented**: an explicit block on the 27 NANP area pattern. **Also implemented**: an explicit block on the 27 NANP area
codes that aren't actually US (Caribbean/Atlantic nations + US codes that aren't actually US (Caribbean/Atlantic nations + US
@@ -331,10 +331,20 @@ generator output. Fixed by quoting every value in that heredoc.
1. ~~Decide: new `services/pstn-trunk.sh`...~~ Done — separate service file, 1. ~~Decide: new `services/pstn-trunk.sh`...~~ Done — separate service file,
generalized to any IP-auth SIP provider (VoIP.ms is just the default). generalized to any IP-auth SIP provider (VoIP.ms is just the default).
2. ~~IP auth vs. registration~~ Done — IP authentication, no password stored. 2. ~~IP auth vs. registration~~ Done — IP authentication, no password stored.
3. ~~Exact NANP dial pattern(s)~~ Done — `_1NXXNXXXXX` / `_NXXNXXXXX`. ~~NANP 3. ~~Exact NANP dial pattern(s)~~ Done — `_1NXXNXXXXXX` / `_NXXNXXXXXX`. ~~NANP
≠ US gap~~ Done — 27 Caribbean/territory area codes explicitly blocked ≠ US gap~~ Done — 27 Caribbean/territory area codes explicitly blocked
(see toll-fraud nuance above); this was a real, live gap in the design (see toll-fraud nuance above); this was a real, live gap in the design
for a while, not a hypothetical. for a while, not a hypothetical.
**Real bug, confirmed live (2026-07-23):** both patterns shipped one
digit short (`_1NXXNXXXXX` / `_NXXNXXXXX` — 10/9 characters instead of
the correct 11/10), so Asterisk's exact-length pattern matching never
matched a real NANP number at all. Every outbound test call failed with
"extension not found in context 'intercom'" — looked exactly like a
config-loading problem (and several genuinely were, along the way: a
stale settings file, the #include never reaching the live config) but
the actual root cause underneath all of that was this one missing `X`.
Found by dumping `dialplan show intercom` and counting characters by
hand against a real dialed number, not by inspection alone.
4. ~~Inbound~~ Done — rings a configurable list of extensions (ring-group 4. ~~Inbound~~ Done — rings a configurable list of extensions (ring-group
supported), each checked live per-call against its own tier. ~~Permission supported), each checked live per-call against its own tier. ~~Permission
model~~ Done — superseded the original flat allow-list with a 3-tier model~~ Done — superseded the original flat allow-list with a 3-tier
+4 -4
View File
@@ -276,7 +276,7 @@ _pstn_write_dialplan_include() {
; reachable" rule, not something even a "full" tier extension can override, ; reachable" rule, not something even a "full" tier extension can override,
; since "full" means "any US number," not "any NANP-shaped number." ; since "full" means "any US number," not "any NANP-shaped number."
exten => _1NXXNXXXXX,1,NoOp(PSTN outbound call attempt from ${CHANNEL(peername)} to ${EXTEN}) exten => _1NXXNXXXXXX,1,NoOp(PSTN outbound call attempt from ${CHANNEL(peername)} to ${EXTEN})
same => n,Set(PSTN_KILLED=${AST_CONFIG(pstn-trunk-killswitch.conf,state,tripped)}) same => n,Set(PSTN_KILLED=${AST_CONFIG(pstn-trunk-killswitch.conf,state,tripped)})
same => n,GotoIf($["${PSTN_KILLED}" = "1"]?pstn_killed,1) same => n,GotoIf($["${PSTN_KILLED}" = "1"]?pstn_killed,1)
same => n,Set(PSTN_AREA_CODE=${EXTEN:1:3}) same => n,Set(PSTN_AREA_CODE=${EXTEN:1:3})
@@ -290,7 +290,7 @@ __ALERT_DENY_TIER_LINE__
same => n,Busy(15) same => n,Busy(15)
same => n,Hangup() same => n,Hangup()
exten => _NXXNXXXXX,1,NoOp(Assuming NANP - adding leading 1) exten => _NXXNXXXXXX,1,NoOp(Assuming NANP - adding leading 1)
same => n,Goto(1${EXTEN},1) same => n,Goto(1${EXTEN},1)
exten => pstn_intl_blocked,1,NoOp(PSTN outbound call to ${EXTEN} blocked - non-US/premium NANP area code ${PSTN_AREA_CODE}) exten => pstn_intl_blocked,1,NoOp(PSTN outbound call to ${EXTEN} blocked - non-US/premium NANP area code ${PSTN_AREA_CODE})
@@ -1651,7 +1651,7 @@ background, cost estimate, and toll-fraud reasoning.
| Provider | ${PROVIDER_NAME} | | Provider | ${PROVIDER_NAME} |
| Server/POP | ${TRUNK_SERVER} (inbound match IPs: ${TRUNK_SERVER_IPS}) | | Server/POP | ${TRUNK_SERVER} (inbound match IPs: ${TRUNK_SERVER_IPS}) |
| DID | ${TRUNK_DID} | | DID | ${TRUNK_DID} |
| Outbound scope | US/NANP only — \`_1NXXNXXXXX\` / \`_NXXNXXXXX\` patterns, no catch-all, minus 27 non-US/premium NANP area codes (see below) | | Outbound scope | US/NANP only — \`_1NXXNXXXXXX\` / \`_NXXNXXXXXX\` patterns, no catch-all, minus 27 non-US/premium NANP area codes (see below) |
| Permission tiers, messaging, personal numbers | Managed live via the Security Dashboard's PSTN Trunk tab — not set at install, so not shown here (this file isn't regenerated when you change them there). Everyone starts at \`internal\` (no PSTN, no messaging) until granted. | | Permission tiers, messaging, personal numbers | Managed live via the Security Dashboard's PSTN Trunk tab — not set at install, so not shown here (this file isn't regenerated when you change them there). Everyone starts at \`internal\` (no PSTN, no messaging) until granted. |
| Concurrency caps | ${MAX_OUTBOUND} outbound / ${MAX_INBOUND} inbound simultaneous calls (live — see \`pstn-limits.conf\` below) | | Concurrency caps | ${MAX_OUTBOUND} outbound / ${MAX_INBOUND} inbound simultaneous calls (live — see \`pstn-limits.conf\` below) |
| Inbound ring-group | ${RING_EXTS} | | Inbound ring-group | ${RING_EXTS} |
@@ -1682,7 +1682,7 @@ American Samoa (684), Sint Maarten (721), Saint Lucia (758), Dominica
If you have a legitimate reason to call one of these (e.g. family in Puerto If you have a legitimate reason to call one of these (e.g. family in Puerto
Rico), remove that entry from the `REGEX()` pattern in Rico), remove that entry from the `REGEX()` pattern in
`pstn-trunk-dialplan.conf`'s `_1NXXNXXXXX` extension — it'll be `pstn-trunk-dialplan.conf`'s `_1NXXNXXXXXX` extension — it'll be
regenerated exactly the same way on the next reinstall/update, so note the regenerated exactly the same way on the next reinstall/update, so note the
change somewhere you'll remember it, or keep a local diff. change somewhere you'll remember it, or keep a local diff.