Configure A PBX (Private Branch Exchange) On Linux

Configure Asterisk 18 with Telnyx on Debian 11

December 6, 2021 · 2036 words · 10 minute read
#tutorial #linux #asterisk #telnyx #sip

Recently I was asked to setup a phone line for a small business. I accomplished this with Asterisk 18 on a tiny (1 vCPU, 1GB RAM) Debian 11 virtual server and a SIP Trunk + DID (Direct Inward Dialing) number from Telnyx, the total cost for everything in the end is ~$2.00/month.


This article assumes you understand the basics of GNU/Linux and have a Debian 11 server with a static public IP address. Note that the process will be very similar for any other major distriubtion.

1. Install Asterisk

Running Asterisk requires you to compile it from source, in order to do this you’ll need a few packages.

apt install gcc g++ make patch libedit-dev uuid-dev libxml2-dev libsqlite3-dev libssl-dev

Grab the link to the latest version of Asterisk from https://www.asterisk.org/downloads/ and use wget or curl to download it to your server. You can download it to any directory, but we’ll use /usr/src.

cd /usr/src
wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-18-current.tar.gz

Now you should have asterisk-18-current.tar.gz in /usr/src. To extract the contents use the tar command; specify z for gzip (.gz), x for extract, v for verbose, and f since we’re specifying the file. You can leave out the verbose (v) flag if you don’t care about seeing each file that’s being extracted.

tar zxvf asterisk-18-current.tar.gz

cd into the newly created directory and let’s start compiling.

cd asterisk-18.*.0

Run the provided configure script with the --with-jansson-bundled flag. Jansson is used by Asterisk for media encoding/decoding and we’ll need it. There’s heaps of other flags that can be used here, more information on them should be available on the Asterisk Wiki.

./configure --with-jansson-bundled

Asterisk provides an interactive menu for selecting modules and setting build options called menuselect, let’s make that.

make menuselect

Once in the menu; you can use the arrow keys to navigate, [ENTER] to go into a section, [ESC] to go back and [SPACE] to select/unselect an item. The options I recommend starting with (assuming you’re in the US) are:

Core Sound Packages

[*] CORE-SOUNDS-EN-WAV
[*] CORE-SOUNDS-EN-ULAW
[*] CORE-SOUNDS-EN-ALAW
[*] CORE-SOUNDS-EN-GSM
[*] CORE-SOUNDS-EN-G729
[*] CORE-SOUNDS-EN-G722

Extras Sound Packages

[*] EXTRA-SOUNDS-EN-WAV
[*] EXTRA-SOUNDS-EN-ULAW
[*] EXTRA-SOUNDS-EN-ALAW
[*] EXTRA-SOUNDS-EN-GSM
[*] EXTRA-SOUNDS-EN-G729
[*] EXTRA-SOUNDS-EN-G722

Other than this, the default options should be fine. If you’d like to know more about the individual modules and build options, check the Asterisk Wiki. Once you’re done you can press S to save and exit.

We’re now ready to compile Asterisk, you can do so with the following command. This step may take a while depending on the hardware you’re compiling this on.

make && make all && make install

Assuming you didn’t run into any problems during compile and you see the Asterisk logo in ASCII, you can now finalize the installation. If you did run into problems compiling, it’s time to start pasting errors into your search engine of choice.

If you want Asterisk to populate its directory (/etc/asterisk) with example configs (recommended), run the following command:

make samples

To create the systemd service file / init script, run the following:

make config

You can now start Asterisk using systemctl (systemd) or service (systemv) and set the service to start automatically on system startup.

systemctl start asterisk
systemctl enable asterisk

2. Configure Firewall (nftables)

If you’re using Debian 11, you should have nftables installed and I’ve included an example in this step for it. If you’re using something else, you’ll need to add the necessary rules for Asterisk to work properly.

I recommend reading through the nftables documentation yourself and creating appropriate rules for your environment. I’ve provided an example based on the sample config and modified it slightly to allow 22/tcp from anywhere (yuck) and all traffic (yuck) from Telnyx’s US SIP and media servers. You can find which IPs Telnyx uses at https://sip.telnyx.com.

You’ll also want to add inbound traffic from your home public IP so you can test calls using a SIP client on your computer.

Please don’t use this for production, this is only meant to be an example. Leaving 22/tcp open to the world and allowing all traffic on any port from Telnyx is a bad idea.

/etc/nftables.conf

#!/usr/sbin/nft -f

flush ruleset

table inet filter {

    chain inbound_ipv4 {
        # accepting ping (icmp-echo-request) for diagnostic purposes.
        # However, it also lets probes discover this host is alive.
        # This sample accepts them within a certain rate limit:
        #
        # icmp type echo-request limit rate 5/second accept
    }

    chain inbound_ipv6 {
        # accept neighbour discovery otherwise connectivity breaks
        #
        icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept

        # accepting ping (icmpv6-echo-request) for diagnostic purposes.
        # However, it also lets probes discover this host is alive.
        # This sample accepts them within a certain rate limit:
        #
        # icmpv6 type echo-request limit rate 5/second accept
    }

    chain inbound {
        # By default, drop all traffic unless it meets a filter
        # criteria specified by the rules that follow below.
        type filter hook input priority 0; policy drop;

        # Allow traffic from established and related packets, drop invalid
        ct state vmap { established : accept, related : accept, invalid : drop }

        # Allow loopback traffic.
        iifname lo accept

        # Jump to chain according to layer 3 protocol using a verdict map
        meta protocol vmap { ip : jump inbound_ipv4, ip6 : jump inbound_ipv6 }

        # Allow 22/TCP from anywhere
        tcp dport 22 accept

        # Telnyx US
      	ip saddr 192.76.120.10/32 accept
      	ip saddr 64.16.250.10/32 accept

      	# Telnyx Media
      	ip saddr 64.16.224.0/19 accept
      	ip saddr 185.246.41.128/25 accept
      	ip saddr 103.115.244.128/25 accept

        # Uncomment to enable logging of denied inbound traffic
        # log prefix "[nftables] Inbound Denied: " counter drop
    }

    chain forward {
        # Drop everything (assumes this device is not a router)
        type filter hook forward priority 0; policy drop;
    }
}

Once you’ve created your nftables config you can start and enable the service. If you’re concerned about blocking yourself out of your server you can create a crontab entry to stop the firewall service every few minutes (just make sure to disable the cron when you’re satisified with the configuration).

crontab -e

This cron entry will stop the nftables service every second minute:

*/2 * * * * systemctl stop nftables

Restart nftables to grab the new config and enable the service to start when the system starts up.

systemctl restart nftables
systemctl enable nftables

3. Get DID Number & SIP Trunk

In order to receive and make phone calls on the PTSN (Public Switched Telephone Network), you’ll need a DID (Direct Inward Dialing) number and a SIP trunk (connection to the PTSN). You don’t have to use Telnyx, in fact there’s a plethora of options for you to pick from. Some alternatives I looked at were; Bandwidth, sip.us, and Flowroute.

Ultimately I decided to go with Telnyx because their sign up process was simple, their pricing seemed fair and they offer some cool API features I may want to utilize down the road. This article isn’t sponsored by them and isn’t meant to be a sales pitch for them, it just so happens they’re the provider I’m using.

The rest of this step is specific to Telnyx, but the process should be somewhat similar for whatever provider you choose.

Purchase A DID Number

You’ll need to add funds to your account, $5.00 should be fine. After your account has funds you can go to Numbers then Search & Buy Numbers and find an available number that you like.

Configure DID Number

Once purcahsed, go to Numbers then My Numbers and create a new SIP Connection. Name the connection whatever you’d like (example: pbx.example.com) and select IP Address under SIP Connection Type. Specifiy the public IP of your server (you can get this by running curl ifconfig.co from your server).

If your server doesn’t have a static IP, you’ll need to use Credentials under SIP Connection Type and you can follow the Telnyx developer docs, support article or adapt my guide as necessary.

Create Outbound Voice Profile

Go to Outbound Voice Profiles then Add New Profile. Name it whatever you like and make sure to associate the SIP Connection you created above with it. Adjust the options as needed for your setup. If you’re not sure, leave the defaults but under Advanced Settings set the Channel Limit to 1 and Max Destination Rate to 0.007.

That should be all you need, if you have any issues with this portion, I recommend reaching out to Telnyx.

4. Configure Asterisk

For this article, we’ll configure Asterisk to talk with Telnyx and setup a basic extension you can test with using a software phone.

You can configure Asterisk to use a database to dynamically load endpoint configurations, but to keep this article simple, we’ll just use the standard configuration files. Let’s start by configuring your PJSIP endpoints.

Open pjsip.conf in the editor of your choice (e.g. vim /etc/asterisk/pjsip.conf and take a look at the comments. Once done, go to the bottom of the file (in vim G) and add the following.

/etc/asterisk/pjsip.conf

; Configuration

[global]
type=global

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

; Telnyx

[telnyx]
type=endpoint
aors=telnyx-aor
transport=transport-udp
allow=!all,ulaw,alaw,G722,G729
from_domain=sip.telnyx.com
dtmf_mode=rfc4733
context=from-telnyx
direct_media=no

[telnyx-aor]
type=aor
contact=sip:sip.telnyx.com
max_contacts=1
remove_existing=yes
qualify_frequency=60

[telnyx-identify]
type=identify
endpoint=telnyx
match=sip.telnyx.com

; 100

[100]
type=endpoint
transport=transport-udp
context=to-telnyx
allow=!all,ulaw,alaw,G722,G729
direct_media=no
rewrite_contact=yes
rtp_symmetric=yes
auth=100-auth
aors=100-aor

[100-auth]
type=auth
auth_type=userpass
password=1Hunnit!
username=100

[100-aor]
type=aor
max_contacts=10
remove_existing=yes

Explaining every option here is beyond the scope of this article, however, if you’re curious you can learn more about these options in the Asterisk Wiki.

This configuration creates an endpoint for Telnyx, associates connections to and from it with the context [to|from]-telnyx, creates a SIP extension 100 with the password 1Hunnit! (make sure to change this), and sets up a basic UDP transport for them to use.

Now we need to tell Asterisk what to do when it has calls that match these contexts, this is done in /etc/asterisk/extensions.conf.

Just like with the pjsip.conf file, look through the comments in the file and add the following to the bottom when you’re ready to move on.

/etc/asterisk/extensions.conf

[from-telnyx]
exten => _X.,1, Dial(PJSIP/100)

[to-telnyx]
exten => _X.,1,Set(CALLERID(num)=15555555555)
same => n,Dial(PJSIP/${EXTEN}@telnyx)

Again without turning this article into a comprehensive overview of the Asterisk dial plan, this tells Asterisk to send calls matching any number (_X.) that match the from-telnyx context to PJSIP extension 100. It also tells Asterisk that calls to any number that match the to-telnyx context to set the Caller ID number to 15555555555 (replace this with your DID number) and send the call to the telnyx endpoint.

Hop into the Asterisk remote console (r) with verbosity level 3 (vvv).

asterisk -rvvv

To reload the changes you made to pjsip.conf you can run the following from inside the Asterisk console:

pjsip reload

Reload changes in extensions.conf by running:

dialplan reload

If you don’t see any errors you should be ready to test. If you do see errors, either I made a mistake or you did. Try pasting the error into your favorite search engine.

5. Testing & Conclusion

To test a call you’ll need a software phone. Linphone is an open source software phone available on all major platforms. However, you should be able to use just about any software phone.

Your client may vary, but generally it will need to know your extension (100), password (whatever you set it to in pjsip.conf), the server address (pbx.example.com or whatever your Asterisk server’s FQDN or IP address is), to use UDP (or port 5060) and which codecs to use (see below).

Here’s what you should need for configuring a SIP account in Linphone:

Replace pbx.example.com with your Asterisk servers FQDN or IP.

Option Value
SIP Address: sip:100@pbx.example.com
SIP Server Address: <sip:pbx.example.com;transport=udp>
Transport: UDP

Under Audio make sure only the following codecs are enabled, in this order:

  • G.722
  • PCMU (G.711 ULAW)
  • PCMA (G.711 ALAW)

If your client shows connected or available and you see Endpoint 100 is now Reachable in the Asterisk console, you should be able to test calling.

At this point I’d recommend looking into setting up dynamic endpoints using a relational database such as PostgreSQL or MariaDB or start diving into learning the Asterisk Dial Plan so you can have some fun with phones.

If you have any questions or comments, you can connect with me on matrix at @bryan:matrix.twosuns.net.

Happy Dialing!