Building scalable IVRs for businesses with Routr and Asterisk
Tags: routr, webrtc, asterisk, voip
Published: 2024-02-25 20:12:37

In this tutorial, I will share how to create a VoIP network combining Routr, RTPEngine, and Asterisk for highly available voicemail, conference, IVR, or any other application in Asterisk.

The added advantage of this setup is that you can use Asterisk as a feature server and have dedicated instances for each feature you would like to provide.

{% github fonoster/routr no-readme %}

## Requirements

Before you start this tutorial, you will need the following:

- Docker Engine installed on your computer or the cloud
- NodeJS 18+ (Use nvm if possible)
- Routr command-line tool (Install it with `npm install -g @routr/ctl`)

## Deploying Routr and Asterisk using Docker Compose

You will use Docker Compose for this setup; however, I recommend using Docker Swarm mode or Kubernetes for a production-ready setup.

> Please see Routr's Helm chart for details on how to run Routr in K8s

To get started, first create a folder named voipnet. In the new folder, create a file name compose.yaml with the following content:

Filename: _voipnet/compose.yaml_

```yaml
version: "3"

services:
  routr:
    image: fonoster/routr-one:latest
    environment:
      EXTERNAL_ADDRS: ${DOCKER_HOST_ADDRESS}
      RTPENGINE_HOST: rtpengine
    ports:
      - 51908:51908
      - 5060:5060
      - 5062:5062
    volumes:
      - shared:/var/lib/postgresql/data

  rtpengine:
    image: fonoster/rtpengine:latest
    ports:
      - 22222:22222/udp
      - 10000-10100:10000-10100/udp
    environment:
      PUBLIC_IP: ${DOCKER_HOST_ADDRESS} 
      PORT_MIN: 10000
      PORT_MAX: 10100

  asterisk-01:
    image: fonoster/mediaserver:latest
    expose:
      - 6060
    environment:
      EXTERN_ADDR: ${DOCKER_HOST_ADDRESS}
      SIPPROXY_HOST: routr
      SIPPROXY_USERNAME: asterisk
      SIPPROXY_SECRET: asterisk
    volumes:
      - ./extensions.conf:/etc/asterisk/extensions.conf
      - ./sounds/count_1.sln16:/var/lib/asterisk/sounds/count.sln16

  asterisk-02:
    image: fonoster/mediaserver:latest
    expose:
      - 6060
    environment:
      EXTERN_ADDR: ${DOCKER_HOST_ADDRESS}
      SIPPROXY_HOST: routr
      SIPPROXY_USERNAME: asterisk
      SIPPROXY_SECRET: asterisk
    volumes:
      - ./extensions.conf:/etc/asterisk/extensions.conf
      - ./sounds/count_2.sln16:/var/lib/asterisk/sounds/count.sln16

  simplephone:
    image: psanders/simplephone:latest
    ports:
      - 8080:8080

volumes:
  shared:
```

A few things to note:

- We added the shared volume to make sure the data survives container restarts
- The simplephone and rtpengine services are optional, and you may remove them if you don't need support for WebRTC
- Notice how we mapped the sounds count_1.sln16 to asterisk-1 and count_2.sln16 to asterisk-2 respectively.

Next, in the same folder, create the file extensions.conf

Filename: _voipnet/extensions.conf_

```
[local-ctx]
exten => asterisk,1,NoOp()
 same => n,Playback(count)
 same => n,Hangup()
```

The previous configuration features the equivalent of a "Hello world" for Asterisk. However, you could use the same principle and instead connect it to a voice application or conference.

Then, create a sounds folder and download count_2.sln16 and count_2.sln16 from [here.](https://github.com/psanders/rtc-labs/tree/main/ha_ivr_with_routr_and_asterisk/sounds)

Your folder structure will end up like this:

```
.
├── compose.yaml
├── extensions.conf
└── sounds
    ├── count_1.sln16
    └── count_2.sln16
```

Finally, run the following command to start all the services:

```bash
# NOTE: Be sure to update the IP address
DOCKER_HOST_ADDRESS=192.168.1.7 docker compose up
```

The previous command will pull all the services from Docker Hub and run the containers. You could also add the `-d` option to run the service in the background.

## Configuring the network with Routr Command-Line Tool

You will use Routr's command-line tool to issue commands to the server and build the VoIP network.

For the setup, we will create a Domain with the `sip.local` URI, an Agent to test the calls and a Peer to represent Asterisk.

Remember that while we are using Agents for testing, in production, you would instead use Trunks to accept PSTN traffic, which is not covered in this tutorial.

We will start by creating a Domain with:

```bash
rctl domains create --insecure
```

Notice the `--insecure` flag, which is required since we don't have a TLS certificate.

The output of your command will look similar to the output below:

```bash
Press ^C at any time to quit.
 ›   Warning: Egress rules unavailable due to 0 configured numbers.
? Friendly Name Local Domain
? SIP URI sip.local
? IP Access Control List None
? Ready? Yes
Creating Domain Local Domain... 3b20410a-3c80-4f66-b7b3-58f65ff65352
```

Next, create two sets of credentials, one for Asterisk and one for John Doe, by issuing the following command:

```bash
rctl credentials create --insecure
```

Your output will be similar to this:

```bash
This utility will help you create a new set of Credentials.
Press ^C at any time to quit.
? Friendly Name John Doe - Credentials
? Username 1001
? Password [hidden]
? Ready? Yes
Creating Credentials John Doe - Credentials... 5fbc7367-a59d-4555-9fc4-a15ff29c24c8
```

Repeat the process for Asterisk, and set the password to `asterisk`.

Next, create an Agent to represent the test Agent, John Doe, with the following command:

```bash
rctl agents create --insecure
```

Your output will be like the one below:

```bash
This utility will help you create a new Agent.
Press ^C at any time to quit.
? Friendly Name John Doe
? Select a Domain sip.local
? Username 1001
? Credentials Name John Doe - Credentials
? Max Contacts
? Privacy None
? Enabled? Yes
? Ready? Yes
Creating Agent John Doe... 662a379d-66f1-4e6e-9df5-5126f1dcb930
```

Finally, create a Peer to represent Asterisk with:

```bash
rctl peer create --insecure
```

Follow the prompt, select Round Robin as the balancing algorithm, and `sip:asterisk` as the Address of Record (AOR). 

The output should look like this:

```bash
This utility will help you create a new Peer.
Press ^C at any time to quit.
? Friendly Name Asterisk PBX
? Username asterisk
? Address of Record sip:asterisk
? Contact Address 
? Max Contacts 
? IP Access Control List None
? Credentials Name Asterisk - Credentials
? Enable Session Affinity? No
? Balancing Algorithm Round Robin
? Enabled? Yes
? Ready? Yes
Creating Peer Asterisk PBX... 1f859fee-5c4c-4771-b8fd-a6c469ffe9bf
```

You might use the `get` subcommand to verify your settings. 

For example:

```bash
rctl agents get --insecure --extended
```

## Calling Asterisk from the Browser using SIP.js

The compose file we created at the beginning of this tutorial contains the simplephone container, a softphone implemented with SIP.js, which is available at [http://localhost:8080](http://localhost:8080).

Here is what the SimplePhone will look like:

![Example using the SimplePhone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dm4cno1b0oa585bnpwj.png)


Set your instance to look like the one below, then click "Save and connect," followed by "Call."

You should hear the numbers alternate every time you press "Call," showing that the calls are correctly load-balanced. 

## What’s next?

Please comment if you find this tutorial helpful and check out the following relevant tutorials:

- [Calling a door phone from a web browser](https://dev.to/fonoster/calling-a-door-phone-from-a-web-browser-1bko)
- [Browser-to-Browser calling with SIP.js and Routr](https://dev.to/fonoster/browser-to-browser-calling-with-sipjs-and-routr-3l07)
- [Simplify SIP.js security with short-lived tokens](https://dev.to/psanders/simplify-sipjs-security-with-short-lived-tokens-mb8)