How the Public Sandbox Works
Download
If you'd like a copy of the custom infrastructure Jay Phillips developed for this, you can download it from Github here.
Problems solved
- Installing Asterisk may be too much work for some people
- We (the Adhearsion team) will run a pre-configured Asterisk server against which to test apps
- When a call comes in, Asterisk must find the user's local Adhearsion app which is probably behind a NAT
- A custom sandbox Adhearsion component enables an Adhearsion app to receive calls even behind a NAT by maintaining a persistent connection to our servers with a custom protocol that's a superset of AGI
Client side
Step 1: Create sandbox-enabled application
When an Adhearison user creates a new application, an initially disabled component named "sandbox" is copied into the app's components/ directory. The user then manually enables the sandbox component.
ahn create foobar cd foobar ahn enable component sandbox
Lastly, the user must edit the sandbox configuration file and enter the login credentials.
Step 2: Acquire Metadata
The next time the application is started it'll ask Adhearsion.com whether the current component and Adhearsion versions are still supported. The protocol may change in the future and this allows legacy applications to fail gracefully in the event of necessary deprecations. The response contains a YAML document instructing where to connect for the AGI tunneling. A sample response looks like this:
connect_to: host: sandbox.adhearsion.com port: 20000
Step 3: Establish tunnels
The sandbox component will also instantiate a new Thread which keeps a socket alive with the hostname and port originally dictated by the YAML response in step "Acquire Metadata". Normally AGI works by establishing a socket out to Adhearsion but, in this case, the Adhearsion user may be behind a firewall and thus unreachable from the outside world without port forwarding. By having Adhearsion keep a socket open to the sandbox server, we can use it as an already-open AGI "call" and, when the user dials in, Asterisk will connect via AGI to the same process that's keeping the Adhearsion socket tunnel alive. This separate process (referred to as the inbound_agi_tunnel process) then bridges the two sockets.
After the socket is established, the sandbox component is expected to authenticate itself by sending an MD5 in the following format:
MD5.md5(COMPONENTS.sandbox["username"] + ":" + COMPONENTS["password"]).to_s + "\n"
Once the server checks the MD5 hash sent, it will respond with a single line response. The possible responses are:
- "wait NN\n" (Wait NN seconds before reconnecting)
- "authentication accepted\n"
- "authentication failed\n"
Server side
Step 1: User signup
The Rails app which controls Adhearsion.com collects new account information on the Getting Started page. The information is stored in a MySQL database on the server. The ActiveRecord models used by this Rails app are also used by the authentication Adhearsion application (mentioned below).
Step 2: Incoming SIP call authentication
Once a user has signed up for an account and started her local Adhearsion application, she'll call a SIP URI assigned to her username in the format username@adhearsion.com. This comes into an Asterisk instance running on the sandbox server. The Asterisk box immediately goes to an Adhearsion application which does the following:
- Check if the username is in the database
- If found, set SANDBOX_USERNAME to the SIP username of the caller
- If not found, play an error message and hangup
- Releases control by ending its dialplan
Step 3: Bridging call with the component's outbound AGI connection
Once the call is authenticated by the small Adhearsion app in Step 2, extensions.conf establishes an AGI request to the Erlang AGI tunnel with the URI agi://127.0.0.1:4574. The Erlang AGI tunnel has a very simple AGI protocol parser. The Erlang AGI tunnel's responsibility is to:
- Read and buffer all AGI headers
- Extract the SANDBOX_USERNAME variable
- See if another PID (thread) is holding onto a socket from the remote Adhearsion app with a matching username
- If so:
- Hand the other PID (thread) the Asterisk AGI socket to bridge together
- Set the BRIDGE_OUTCOME channel variable to "SUCCESS"
- if not:
- Set the BRIDGE_OUTCOME variable to "FAILED"
- Release control
- Allow the authenticator Adhearsion app to play an error and hangup
- If so:
- When either socket closes, immediately close the other.
Edge cases
- Adhearsion sandbox component being configured with an invalid username and/or password
- The Erlang tunnel program sends back "autentication failed" and the Adhearsion app is expected to abort initializing
- Adhearsion is not running when the user calls in via SIP
- Set BRIDGE_OUTCOME to FAILED. See Server side Step 3.
Improvement possibilities
- Send back the "authentication accepted" before an AGI call comes in
- Have a signal which semantically means "You must upgrade Adhearsion and/or the sandbox component to use the sandbox now"