An Asterisk instance that answers calls, plays an IVR, holds with music and connects a "human", so the gateway has something real to dial that is not the PSTN: no charges, no strangers, no E911 exposure. Asterisk rather than Kamailio because the unproven risks are media risks. Kamailio is a proxy — it routes signalling and answers nothing, so it would forward the INVITE and find nobody home. Asterisk is a B2BUA: it answers, plays prompts and collects DTMF, which is the hold-slayer scenario itself. Kamailio remains the better model for trunk registration/digest auth later. No application changes are needed to use it. SIP_TRUNK_HOST is just an address, so the production code path runs unmodified — there is no test-only branch anywhere in the gateway. It also means safety is structural: while the trunk points at the lab there is no route to the PSTN at all, an absence of route rather than a policy that could be misconfigured. Nine scenarios (1001-1008 plus an echo test) cover the baseline call, the IVR/DTMF path, hold-then-human, long hold, busy, no-answer, remote hangup and silence. The image ships no sound files, so sounds/generate.py synthesises three fixtures from fixed seeds — byte-identical on every run, which is what makes a classifier regression distinguishable from noise. Verified against AudioClassifier: music→MUSIC 0.85, speech→LIVE_HUMAN 0.75, silence→SILENCE 1.00. The speech formants deliberately avoid the DTMF bands; the first version landed on a valid pair and classified as a keypress. Anonymous inbound calls are refused, and endpoint matching is by source address — Asterisk's default matches the From-header domain, which Hold Slayer populates from its SIP bind address (0.0.0.0 on a wildcard bind). Generated audio and the rendered per-host configs are gitignored: the former is reproducible from a fixed seed, the latter carry a host-specific IP and the lab password. Known limit, documented in the README: MediaPipeline.create_tap is a stub, so the classifier receives no audio on a live call. RTP flows and Asterisk plays audio, but the tap is never fed — the fixture results above were measured by feeding the classifier directly. This blocks scenarios 1002/1003/1004. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
76 lines
2.8 KiB
Plaintext
76 lines
2.8 KiB
Plaintext
; ---------------------------------------------------------------------------
|
|
; Hold Slayer lab — PJSIP configuration
|
|
; ---------------------------------------------------------------------------
|
|
; SECURITY: this endpoint answers calls. Asterisk's stock examples allow
|
|
; anonymous inbound, which is a well-known toll-fraud target. This config
|
|
; refuses it: every call must authenticate as the `hold-slayer` endpoint.
|
|
;
|
|
; There is no PSTN behind this Asterisk — an unauthorised call reaches only
|
|
; the lab dialplan and costs nothing. The lock-down is defence in depth and
|
|
; so this config is never copied somewhere it would matter.
|
|
; ---------------------------------------------------------------------------
|
|
|
|
[global]
|
|
type = global
|
|
; Do not fall through to an `anonymous` endpoint for unmatched calls.
|
|
; This is the single most important line in the file.
|
|
unidentified_request_count = 5
|
|
unidentified_request_period = 5
|
|
unidentified_request_prune_interval = 30
|
|
|
|
[transport-udp]
|
|
type = transport
|
|
protocol = udp
|
|
bind = 0.0.0.0:{{ asterisk_sip_port }}
|
|
; The address Asterisk advertises in SDP. Without this, containers advertise
|
|
; their internal bridge IP and RTP arrives at an unroutable address — the
|
|
; classic "call connects but there is no audio" failure.
|
|
external_media_address = {{ asterisk_external_ip }}
|
|
external_signaling_address = {{ asterisk_external_ip }}
|
|
local_net = {{ asterisk_local_net }}
|
|
|
|
; ---------------------------------------------------------------------------
|
|
; Hold Slayer endpoint
|
|
; ---------------------------------------------------------------------------
|
|
; Hold Slayer authenticates as this endpoint to place calls into the lab.
|
|
|
|
; Identify the endpoint by source address. Asterisk's default matching uses
|
|
; the From-header domain, which Hold Slayer populates from its SIP bind
|
|
; address (0.0.0.0 on a wildcard bind) — never a value Asterisk can match.
|
|
; Matching on where the packet actually came from sidesteps that.
|
|
[hold-slayer]
|
|
type = identify
|
|
endpoint = hold-slayer
|
|
match = {{ asterisk_match_host }}
|
|
|
|
[hold-slayer]
|
|
type = endpoint
|
|
context = hold-slayer-lab
|
|
disallow = all
|
|
; ulaw first: it is what the PSTN uses, so the lab exercises the same codec
|
|
; path a real trunk would. alaw as fallback.
|
|
allow = ulaw
|
|
allow = alaw
|
|
auth = hold-slayer-auth
|
|
aors = hold-slayer
|
|
; RFC 2833 out-of-band DTMF — what send_dtmf must produce. Setting this
|
|
; explicitly (rather than `auto`) means a DTMF failure is a real failure and
|
|
; not a negotiation fallback quietly rescuing it.
|
|
dtmf_mode = rfc4733
|
|
direct_media = no
|
|
force_rport = yes
|
|
rewrite_contact = yes
|
|
rtp_symmetric = yes
|
|
|
|
[hold-slayer-auth]
|
|
type = auth
|
|
auth_type = userpass
|
|
username = {{ asterisk_sip_username }}
|
|
password = {{ asterisk_sip_password }}
|
|
|
|
[hold-slayer]
|
|
type = aor
|
|
max_contacts = 2
|
|
remove_existing = yes
|
|
qualify_frequency = 60
|