PhreakNet Community Docs

← Go Back  ||  PhreakNet Portal

Version 2.0.69 — (last revised 2022/12/21 00:26:20 UTC)

Asterisk/VoIP documentation for hobbyists, switchers, collectors, and phreaks

© 2023 PhreakNet


  1. Change Log
  2. Introduction
    1. Contributions
    2. Dedication
  3. Requirements
    1. Hardware
    2. Software
  4. Getting Started
    1. Mastery
    2. SSH
    3. Pre-Requisites
    4. Installing DAHDI
    5. Installing Asterisk
      1. Automated Installation
      2. Manual Installation
        1. Running as Non-root
    6. Reserving an Office Code
    7. Existing Dialplans
    8. Boilerplate Code
    9. Audio Files
    10. Encryption
      1. RSA Encryption
    11. Initial Configuration
      1. asterisk.conf
      2. modules.conf
      3. sip.conf
      4. pjsip.conf
      5. chan_dahdi.conf
      6. Music on Hold
      7. iax.conf
      8. Verification Subroutines
      9. extensions.conf
    12. ATA Digit Map
    13. Mail
    14. Troubleshooting
    15. DAHDI Troubleshooting
  5. Operator Routings
  6. Billing
    1. ZEnith Numbers
  7. Standards
    1. APIs
      1. Routing & Verification
      2. RSA
      3. Directory Add
      4. Directory Availability
      5. Blacklist
      6. Billing
      7. CNAM
      8. Time Zone
      9. ZIP Code
      10. NPA
      11. Distance
      12. Local Exchanges
      13. Is Local
      14. PSTN local call
      15. Exists
      16. Rates
      17. Telegram
      18. Cisco Phone Directory
    2. Standard Variables
    3. Answer Supervision
    4. Intercept Codes
    5. ANI II Digits
    6. Numbering Plan
      1. #5XB Automatic Call Distributor Hack
      2. Standard Numbers
  8. Dialplan Optimizations
  9. Vintage Add-Ons
    1. Pat Fleet Asterisk Sounds
    2. Automatic Intercept System
    3. ANAC
    4. Speaking Clock
    5. Airport Weather
    6. Millwatt Test Tone
    7. Ringback
    8. Revertive Pulsing Script
    9. MFer
    10. SFer
    11. Dial Pulser
    12. Conference Bridges
    13. Non-Supervising Conference Bridges
    14. Answering Machines
    15. Hook Flashing
    16. Announcement Drums
    17. Crosstalk
    18. Echo Test
    19. Silent Termination
    20. Live Feeds
    21. Loop Arounds
    22. ChanSpy Verification
    23. T1 Trunks
    24. Modems
    25. Fax
    26. MF/ACTS Detectors
    27. Cadence Plan
    28. Multiple Level Precedence and Preemption
    29. Manual Service
    30. NetCID
  10. Payphone Trunks
  11. StepNet
    1. Sounds
  12. Operator Number Identification (ANI Fails)
  13. Automated Operators
  14. Services
    1. Paging
    2. Telegrams
    3. Wake Up Calls
  15. Video
    1. Video SIP Clients
  16. PhreakNet System Practices
  17. C*NET Connectivity
  18. PSTN Connectivity
    1. Incoming Calls
      1. IPComms
        1. Registration
        2. Required Contexts
      2. CallCentric
        1. Registration
        2. Required Contexts
      3. Toll Free
    2. Outgoing Calls
      1. Skyetel
        1. NerdVittles Promotion
        2. Skyetel Configuration
        3. Required Contexts
      2. Google Voice
        1. PJSIP Dependencies
        2. Google Voice Installation
        3. Required Contexts
  19. Further Add-Ons
    1. [public] context
    2. features.conf
    3. Extending Your System: Shell Scripts
      1. Windows & Unix Encoding
    4. Speech Recognition: IBM Watson
    5. Evan Doorbell
  20. Appendix A: Helpful Supplemental Tools
    1. Using SoX to convert audio files
      1. Linux
      2. Windows
    2. Using iptables manually
  21. Appendix B: Provisioning Server
    1. Grandstream
    2. Cisco/Linksys/Sipura
    3. Cisco 79xx

Change Log

PhreakNet Docs Change Log RSS Feed


Early on, it was determined that there existed a great need to document all of its common dialplan code, dependencies, and instructions in order to make it easier for both its operators and any newbies joining the network to find their way. This reference is a continually-growing repository of common code and resources shared across nodes that experienced node operators and newcomers alike will find helpful. This documentation consists of "sub-docs" which individually provide detailed and useful information about different components of the network and the nodes that comprise it.

This documentation is largely generic Asterisk documentation. While some aspects may be specific to specific networks, much — if not most — of the concepts are agnostic and applicable to any Asterisk system. This documentation is designed and written to be accessible to newcomers, to help newbies get up and running and familiar with their Asterisk systems. However, it is also a helpful resource for more seasoned veterans as well.


This is the most comprehensive documentation about using Asterisk for typical and atypical purposes, and this is an actively maintained and living resource. This documentation wasn't written overnight — quite the contrary, it's grown piece by piece since 2018. Community contributions are always appreciated, and if there's something you think should be documented here that isn't, let us know and we can add it.

This documentation is written by hobbyists, for hobbyists. If you notice a typo or something that's not quite right here, we'd love to fix it! If you find something missing here that you think should be present, we'd love to add it! Contributions are always welcome and are greatly appreciated. To make a contribution, please drop the editor a line at docs @ (trim the spaces out please, before copying into your mail program). Alternately, post to the mailing list if you think the change warrants community discussion.

In general, you should create a new ticket on InterLinked Issues so we can keep track of any pending issues. Below are some steps for common contributions.

Contributing to the Docs

Please open a ticket on InterLinked Issues, the in-house issue tracker. The category should be "PhreakNet Docs".

You can either describe the update that is needed and include changes in the issue or provide a diff. In the latter case, you must create the diff on InterLinked Paste and link the paste to the issue. Make sure to set the expiration to at least a couple weeks or longer (the default is 24 hours).

Contributing to PhreakNet boilerplate config

  1. Please open a ticket on InterLinked Issues, the in-house issue tracker. The category should be "PhreakNet" (not "Docs").
  2. Describe what's wrong (e.g. syntax error) or what could be improved with the relevant boilerplate config file(s). If you are contributing a patch, continue, otherwise, stop here.
  3. Updates to boilerplate config are twofold: the boilerplate config itself is updated, but patches for boilerplate config are also generated so that, as much as possible, people do not need to download the file again and start over. User-contributed patches will be run against the boilerplate config to generate the newer version.
  4. Use PhreakScript to generate a PhreakPatch for the files that need to be changes, e.g. phreaknet genpatch. You will need to run this for each file that needs to be updated. This is an interactive utility, so follow the instructions provided for the process. Make sure you update the version number and date in the relevant file. DO NOT include a verbose description of the fix anywhere in the config file (e.g. 2021-07-15 JS: fixed blah-blah-blah). Instead, include this in the issue tracker and it will be included in the change log. This avoids cluttering the boilerplate configs themselves with unnecessary comments. Updating an individual context or subroutine with a last updated date is okay.
  5. Save the patch file in a directory on your server, then upload these to a web directory (link to InterLinked Paste if necessary)
  6. Link the patches to the issue
  7. Once the patch is merged, a description of the bug fix will be included in the PhreakNet Docs change log.
  8. Please submit a pull request to the appropriate GitHub repository.

Contributing to Asterisk or DAHDI

First, you should test your code (a lot) and make sure no regressions or unexpected behavior are encountered. Consider adding test cases using either the test suite or unit tests. Test suite cases are more flexible but unit tests are easier and more self-contained for single modules, especially functions that are expected to produce a certain output. Make sure to build Asterisk with development mode if using PhreakScript. You must compile with developer mode since the compilation requirements for development mode are stricter and will catch errors and warnings that a normal install will not.

Second, you should try to submit your changes upstream to Asterisk or DAHDI. No, seriously, we're not joking. If we think your change is likely to be accepted by Sangoma, we will reject it, unless you have tried and Sangoma has rejected it. As many changes should be submitted upstream to Sangoma as feasibly possible.

For the time being, DAHDI contributions may be done directly since the DAHDI project is not actively being maintained.

  1. Please open a ticket on InterLinked Issues, the in-house issue tracker. The category should be "Asterisk" or "DAHDI" (for both DAHDI Linux and DAHDI Tools as appropriate). Describe the change and why it is relevant. Changes do not necessarily need to relate to PhreakNet or the needs of telephone collectors, provided it is likely to be useful to at least a few people.
  2. You must provide a diff of changes from the current master branch of Asterisk or DAHDI Tools or the current next branch of DAHDI Linux (not master, since next is newer than master).

Contributing to PhreakScript

  1. Please open a ticket on InterLinked Issues, the in-house issue tracker. The category should be "PhreakScript".
  2. Describe the change that needs to be made with sufficient detail.
  3. If the change is significant, you must post to the PhreakNet mailing list and describe the change, so people are given an opportunity to comment and provide feedback. Major changes will not be accepted without a) posting to the mailing list and b) providing at least 24-48 hours for comments. For bug fixes that do not introduce regressions or any minor changes, it is not necessary to post to the mailing list.
  4. If you are contributing a patch, you should either provide a diff (link to InterLinked Paste if necessary) or submit a pull request on GitHub. Pull requests on GitHub will not be accepted if they do not refer to an open issue. The title of the issue should be the name and title of the issue (e.g. [PHREAKSCRIPT-555] Add a thingy here). IF YOU ARE CONTRIBUTING A PATCH, you must follow the steps below BEFORE submitting a pull request:
    1. Fork your own copy of PhreakScript to add your changes. PhreakScript is updated frequently, so you should perform these steps all at the same time.
    2. Make sure you increment the minor version number and add a comment underneath describing the change.
    3. If the behavior of any commands are changed (new options, new commands, etc.), these must be documented in both help and examples.
    4. Upload the final version, with your changes, to some web location. You can upload to InterLinked Paste if you need a temporary host for your file.
    5. Update PhreakScript using the --upstream argument (e.g. phreaknet update --upstream= Make sure that the upstream source specified is a plain text file. If you used InterLinked Paste, make sure you have the .txt at the end so it pulls the text source, not the webpage.
    6. If PhreakScript successfully updates (and you have tested the behavior works correctly), you are good to submit the pull request. If not, you will need to check your code and verify the syntax is correct. Note that you do not need to create an issue on GitHub, but if you do so, it should have the same name as the InterLinked issue.
    7. Make sure to submit a pull request to GitHub.


This documentation is dedicated to the late Brian Clancy, who sadly passed away of terminal illnesses on 2020/02/13. Without Brian, not only would this documentation not exist, this network would not exist. Brian was a good friend to all and a kind and patient mentor from the beginning. Brian lives on as the voice of the network, reachable on network operator numbers, Coin Zone calls, TIME (231-2301), and as a conversation partner (549-3344). But his most important impact has been his influence on the countless individuals whom he has left behind. He will be forever remembered for his persistence, work ethic, and strong-spirited determination. Thank you, Brian, for all you have done!



To operate your own node, you will need a server running Asterisk, a powerful but free VoIP/TDM/analog PBX/CO switch software solution. You don't need a powerful system to run Asterisk — many node operators use servers with 1GB of RAM or less — even 512MB will be sufficient. uLaw (pronounced mu-law or, frequently but erroneously, u-law), is the preferred codec in the United States for VoIP, as it is 64kbps, or PSTN quality. Consequently, you will also need an Internet connection (but you knew that already, didn't you?). Although you can operate a node even with a very low-speed Internet connection, it is undesirable to have a connection speed of under 512kbps. A T1 (1.544Mbps) will be sufficient, and any package of broadband Internet will be more than enough bandwidth for several VoIP calls.

Some Asterisk servers are hosted servers, meaning they are not physically located at the premises of their owners. Hosted servers can be a great option for those just getting started or those without decent Internet connections or the space and resources to operate an on-premises Asterisk server. If you go this route, do understand its limitations. First, we can't stress enough how important it is to choose a reputable host. We once used a HiFormance host on a plan that was (wait for it!) $10/year. Of course, you know what they say: if it sounds too good to be true, it probably is. In mid-December 2018, without warning, HiFormance servers stopped working without warning — owners were unable to SSH into the servers and the services on them went down. With some effort, it was discovered that HiFormance was closing its doors. Ironically, a post on the website informed its customers that HiFormance would be closing its doors and that data should be backed up, yet, HiFormance never emailed its customers and most people discovered this only after their servers stopped working. Two years later, in November 2020, our VPSCheap server stopped working for a few days due to "complications" that arose during a cluster migration. A week later, the server came back up, but it was a brand new server — all the data was gone. Bad VPS hosts are unfortunately quite common, and it may be worth paying a couple bucks more for a more stable and reliable solution.

From our experiences with multiple VPS providers, we've seen some of the things that work and don't work well. We recommend using Digital Ocean's $4 per month Droplet, which is very affordable and reliable. You can sign up using our referral link if you aren't already a member, and we'll both get some free credit (after you verify your account, you'll get $100 to spend on "Droplets", which are VPSes). Thanks for helping to keep our costs low, so we can work on more cool projects for you!

Operating your Asterisk server locally is also an option. Almost any old computer from this millennium will suffice, but your options are really quite flexible. Asterisk can run seamlessly on a Raspberry Pi, which is a compact, low-energy solution that works well for many node operators. While you may run into roadblocks with connecting channel banks and other equipment to a Pi, for those with just ATAs (analog telephone adapters), a Pi should be more than sufficient. Many Asterisk nodes are operated using Raspberry Pis.


Asterisk is a free open-source VoIP telephony toolkit that has "taken the telecom industry by storm". It is widely used in the telephony community, by professionals and hobbyists alike, and is a requirement for operating a node, no matter what hardware is being used.

To start with, you'll need a flavor of Linux (also free and open-source) in order to run Asterisk. We recommend Debian, which works well on a variety of platforms. Any Linux commands featured in this documentation will assume an instance of Debian, but if you use a different flavor of Linux, you can consult your operating system's documentation for the proper commands and syntax. In many cases, minor variations in syntax and keywords will be the key distinguishment.

As for Asterisk itself, we recommend using the latest LTS release, Asterisk 18. Unlike other software packages, Asterisk has a large number of versions circulating, a great number of which are currently in use. Systems running Asterisk 1.0 and 1.2, for instance, are plentiful on C*NET. For our purposes, we've not needed any legacy functionality found in these versions that the newer versions lack; however, if you choose to install the Project MF patches on your system, an older version may be required. However, due to the latest PhreakNet patches, most of this functionality is now available in modern Asterisk.

Getting Started


A quick note before we start here. This documentation goes into a fair amount of detail on some things, but it's simply impossible to cover everything comprehensively. There are two good ways to explore further and learn about the various things you can do with Asterisk and how to use them:

A frequently asked question is how one builds Asterisk expertise. It really is a skill that takes experience to come — that is a lot of doing it. One can spend years trying to master Asterisk and not come close to doing or even learning everything possible, so don't be overwhelmed by not understanding how everything fits together. You will always be finding better ways to do certain things, and often there is more than one way to skin a cat. So, instead, choose to focus on specific objectives and outcomes and then look at the documentation for the relevant resources (e.g. application, function, module, config file, etc.). This kind of curious and progressive learning will go a long way.

If you're trying to do something, chances are somebody else has or might want to as well. Asking questions on the list facilitates the proliferation of knowledge and sharing of techniques, examples, etc. and is encouraged. Don't be shy!


Most of this documentation assumes you are using Debian 10 (if you're using a different Linux distro, you might need to use yum instead of apt for instance, and some of the package names may also be different). It also assumes you're using a headless/non-GUI installation. If you have a GUI, you'll probably want to get rid of it. You'll also want to make sure SSH is enabled so you can remotely manage your system. If you're using a VPS, this part is kind of done for you, but here is the general process, starting with using passwd root to set a strong password for the 'root' account. Some VPS hosts allow you to reset this if needed, but you shouldn't forget this. 'root' is equivalent to the built-in 'Administrator' account on Windows.

You may also wish to use SSH keys instead of a password login. Most SFTP clients, like FileZilla, also support this. If you're using Windows, you should use KiTTY instead of PuTTY. KiTTY is basically "PuTTY on steroids", and allows you to, among things that PuTTY cannot do, store passwords for saved sessions so that you don't need to enter them manually each time. You don't have to use KiTTY, but you should use an SSH client that has this feature. This gives you no excuse not to use a long and secure password (and length is paramount, not complexity!).

Now, run nano /etc/ssh/sshd_config.

Hit CTRL+W to search for PermitRootLogin. It probably won't be found, but we just want to make sure. If it is found, change no to yes. If it isn't found, go to the end of the file and add PermitRootLogin yes on a new line. Then use CTRL+X, y, ENTER to save changes and exit.

Finally, many commands that need to be run throughout the Docs need to be performed as root. If something isn't working, make sure that you either a) logged in as root, b) su to root or c) use sudo to run with root privileges. For general system monitoring and accessing the Asterisk CLI, the root account is not needed, depending on how you configure Asterisk.

If you are using FreeBSD, you will need to run the following in the root account to allow su'ing into the root account from a non-root account: pw usermod USERNAME -G wheel.


This section should be completed using the root user, even if you do not plan to run Asterisk as root. If you are not running as root, use sudo.

First, before doing anything, make sure the timezone on your server is set properly. By default, it will probably be UTC (Universal Coordinated Time, a.k.a. Greenwich Mean Time). If you live in North America, this may be of little use to you. You will want the time to be set to your local time so that in the console and in the logs, the times accurately reflect your experience. Even if you don't care, you should still change the time to your local timezone (and if you have a hosted server, you should change it to your timezone, not that of the locality in which the server is hosted). Your server's time is used for various things, including the "mock billing system", which depends on accurate timekeeping by each node (more on this in the "Billing" section).

For those on Debian 10/11, our OS of choice for running Asterisk, here is how to change the time.

At the shell prompt, type timedatectl list-timezones

Make note of the name that corresponds to your region. Now, change the timezone by typing a command like this one:

timedatectl set-timezone US/Central

The above would set your timezone to Central Time. Similarly, America/Chicago would also work.

Now, verify the time is correct by executing timedatectl alone. Asterisk, MySQL, and any other services that use the system time will need to be restarted to pick it up. You may wish to simply restart the server.

Make sure your system is up to date (apt-get update and then apt-get upgrade).

After this, there are a few packages you will want or need that are easy to install!

The following packages are not Asterisk-related but are a valuable asset to anyone operating a Linux-based server. Generally, you can install a package from the default Debian repository by typing sudo apt-get install name where name is what you are trying to install. Here are a few must-haves for any server exposed to the Internet:

  • NIST — time synchronization
    apt-get install ntp -y
  • iptables — a versatile firewall, can block IP addresses, ports, etc.
    apt-get install iptables -y
  • tcpdump — can help identify spammer IP addresses
    apt-get install tcpdump -y (to use just run tcpdump port 5060, or whatever your SIP port is)
  • sudo
    apt-get install -y sudo
  • fail2ban — a valuable supplement to iptables, can dynamically block IP addresses after repeated authentication failures. Read more about how to install and configure fail2ban.

fail2ban can be used to block SIP spam in Asterisk, which, combined with changing the SIP bindport away from 5060, can very effectively eliminate spam.

The process essentially comes down to:

  • Installing fail2ban
  • Run touch /var/log/asterisk/security
  • In /etc/asterisk/logger.conf, enable the security log by uncommenting it
  • Add the following to /etc/asterisk/fail2ban/jail.local, creating the file if needed:
    bantime = 1h
    ignoreip = 
    enabled = true
    # if more than 4 attempts are made within 6 hours, ban for 24 hours
    enabled  = true
    filter   = asterisk
    action   = iptables-allports[name=ASTERISK, protocol=all]
    logpath  = /var/log/asterisk/security
    maxretry = 4
    findtime = 21600
    bantime = 86400
  • Run service fail2ban restart. Note that if the security file does not exist yet, fail2ban will crash (hence the touch command)
  • To see blocked IPs that were blocked specifically due to Asterisk-related attacks, run fail2ban-client status asterisk-iptables

Once you have iptables installed, run the following command from the shell command-line:

iptables -A INPUT -p udp -m udp --dport 5060 -m string --string "User-Agent: friendly-scanner" --algo bm --icase --to 65535 -j REJECT

It has been reported that 79% of honeypot traffic gets blocked by this one rule!

Here are some other Linux tools you'll want specifically for Asterisk. They are not required for basic Asterisk functionality but you will want to be sure they are all installed. The rest of this documentation will assume that the following components are available on your system. Here's how you would install them from the Debian command line:

  • wget — a basic tool for downloading files non-interactively (may already be installed)
    apt-get install -y wget
  • curl — a basic tool for extracting the content of online documents (may already be installed)
    apt-get install -y curl
  • sox — for converting audio files to the proper formats for Asterisk
    apt-get install -y sox
  • OpenSSL — for crypto and encrypted calls
    sudo apt-get install -y libcurl4-openssl-dev
  • mpg123 — for playing mp3 streams
    apt-get install -y mpg123
  • dig — for querying DNS servers
    apt-get install -y dnsutils
  • PHP — required for the pulsar (revertive-pulsing) add-on as well as speech-to-text
    apt-get install -y php
  • Festival — required for text-to-speech
    apt-get install -y festival
  • Basic Calculator — required for more complex calculations
    apt-get install -y bc
  • Apache — required for web server
    apt-get install -y apache2 a2enmod ssl a2enmod rewrite a2enmod proxy_http a2enmod proxy_connect systemctl restart apache2

If you need a free SSL certificate, you can use Let's Encrypt to get one:

apt install snapd
snap install core
snap install hello-world
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot
certbot certonly --apache
certbot renew --dry-run

Newer versions of Asterisk may have some issues, but here is one list of pre-reqs for Asterisk 18, courtesy S. Y.:

# Install dependencies
apt -y install linux-headers-`uname -r` \
build-essential \
binutils-dev \
git \
bison \
flex \
default-libmysqlclient-dev \
freetds-dev \
libbluetooth-dev \
libcodec2-dev \
libcurl4-openssl-dev \
libedit-dev \
libfftw3-dev \
libghc-postgresql-simple-dev \
libgmime-3.0-dev \
libical-dev \
libjansson-dev \
libneon27-dev \
libosptk-dev \
libnewt-dev \
libradcli-dev \
librust-backtrace-dev \
libsndfile1-dev \
libspandsp-dev \
libspeexdsp-dev \
libsqlite0-dev \
libsqlite3-dev \
libssl-dev \
libunbound-dev \
libvorbis-dev \
libxml2-dev \
unixodbc-dev \
uuid-dev \
xmlstarlet \
libiksemel-dev \
libtolua-dev \
libsrtp2-dev \
libldap2-dev \
libsnmp-dev \

Installing DAHDI

DAHDI is maintained by the same company that maintains Asterisk and is what bridges Asterisk to analog telephony (e.g. channel banks, etc.). Unlike Asterisk, which runs at the user level, DAHDI runs at the kernel level. DAHDI was once called Zaptel, and though there have been some changes, it's basically the same thing, just evolved a bit. Asterisk relies on DAHDI for the lower-level functions needed to work with analog equipment.

If possible, you should probably install DAHDI before Asterisk.

You will need to install DAHDI if you:

  • have channel banks
  • want to use MeetMe
  • want to use TDM equipment

You do not need to install DAHDI if:

  • Your endpoints are only IP-based (e.g. SIP ATAs)

It probably doesn't hurt to install DAHDI even if you don't need it, but if you're sure you won't need it, you can safely skip this step. Asterisk itself doesn't depend on DAHDI, except for DAHDI-specific channel technologies and the MeetMe application.

Generally, in order to be useful, you need a real server physically located near you to be able to take advantage of DAHDI for its TDM features (besides MeetMe, which is deprecated as of 19 anyways and will be removed come 21). However, you can still compile DAHDI on any server, which can be useful for development and basic testing.

dahdi-linux is what contains the actual drivers needed.

apt-get dist-upgrade
apt-get install linux-headers-`uname -r`
cd /usr/src
tar -zxvf dahdi-linux-current.tar.gz
tar -zxvf dahdi-tools-current.tar.gz
cd /usr/src/dahdi-linux-current

# if running make results in an error about pci-aspm.h not existing:
nano include/dahdi/kernel.h


Installing Asterisk

There are several different ways to use Asterisk. Packages like FreePBX exist that offer a GUI (graphical user interface) for configuration, which runs on Asterisk behind the scenes. You can, of course, use "just Asterisk", which is commonly referred to as "vanilla Asterisk", and this is by far the best option and is highly recommended. While GUI-based options like FreePBX may make it easier to get started initially, they are not worth it in the long-run, as you will find yourself restricted in terms of what you can configure, as much of the customizability and flexibility of Asterisk disappears when you use a GUI like FreePBX. The rest of this documentation will assume you are using vanilla Asterisk as it is the only way to truly realize the full power of Asterisk. However, other systems are used not infrequently in the greater hobbyist community so there may be others who have done something you are trying to do.

Method A: Automated Install (Recommended)

The automated install uses the PhreakScript utility to automatically install Asterisk on your server, completely automated. It is recommended for new installations. Method B (below) also explains step by step each part of the install process. The PhreakScript utility simply does all this for you, if you want to get up and running quickly.

Please report feedback, bugs, and comments on the PhreakNet mailing list!

PhreakScript must be run as root. However, Asterisk can be installed as a different user using the -u or --user flag.

Here's how to get and use PhreakScript, assuming you have cd'd into the directory where you want to install it (e.g. /etc/asterisk/scripts or /usr/local/bin. Simply run the following command sequences:

cd /usr/local/src; wget; chmod +x; ./ make
phreaknet update; phreaknet install; phreaknet pulsar; phreaknet sounds --boilerplate
phreaknet config --api-key=YOURAPIKEY --clli=YOURCLLI --disa=YOUR7DIGITDISANUMBER

You should run the command below after you've added a switch in the user portal. phreaknet keygen --rotate

PhreakScript will automatically install all dependencies and install Asterisk. To install boilerplate code, run phreaknet config. For full usage, run phreaknet help.

Method B: Manual Install

While there are many ways to install Asterisk on your system, the best and most reliable way to install Asterisk is by compiling from source. On one occasion, we installed Asterisk from its binaries in the default Debian repository with the result that all of our existing dialplan code didn't work, and other strange issues were encountered as well: differing directory structures, improper reloading of modules, etc. Never, NEVER, ever install Asterisk from a package. "Always from source" is really the only way to go, and will save you a lot of hassle and frustration down the line. Installing from source is easy, and we lay out the process step by step in this section.

WARNING: DO NOT INSTALL ASTERISK WITHOUT INSTALLING ALL THE PRE-REQS! Otherwise, you will probably find later that you are missing something that requires you to recompile and reinstall Asterisk after installing that pre-req. Do it right the first time! If you are not comfortable or sure of how to do this, you should use PhreakScript instead as it automatically handles all dependencies.

We now recommend the latest LTS version of vanilla Asterisk, which is currently Asterisk 18.

Here are instructions for Debian 10 and the latest version of Asterisk 18 (18.5.0 as of this writing), boiled down to just the commands:

rm -rf /usr/lib/asterisk/modules # optional - if upgrading Asterisk, wipe out all the modules in advance of the upgrade, to remove old modules that aren't in the new versions
cd /usr/src
tar -zxvf asterisk-18-current.tar.gz
cd asterisk-18.5.0
./contrib/scripts/install_prereq install
Change country code from 61 to 1 (or your country code, if you're not in the NANPA) if/when prompted
./configure --with-jansson-bundled
cp contrib/scripts/ /usr/local/bin
chmod +x /usr/local/bin/

If you need to support older ATAs, run the following two lines — otherwise, TLS will not work with older ATAs (such as the Grandstream HT7xx series), and encryption won't be possible. TLS 1.0 isn't great, but it's better than nothing. If you only have recent/modern ATAs, or you're not using ATAs/SIP clients, you can probably skip this step:

sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf
sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf

If you do make the changes above, it would not be a bad idea to reboot at this point.

Now, to begin the actual Asterisk compilation process:

make menuselect
Check the following unchecked boxes by using the arrow keys to navigate and pressing ENTER:
  • Add-ons: format_mp3 (optional)
  • Core Sound Packages - Change from GSM to ULAW (required because, unfortunately, the Pat Fleet repository does not replace every single stock recording)
  • Music On Hold: Check ULAW
  • Extras Sound Package: Check ULAW (optional)

Save & Exit.

make # compile Asterisk. This is the longest step, if you are installing for the first time.
make samples # do not run this on upgrades or it will wipe out your config!
make install # actually install modules
make config # install init script
make install-logrotate # auto compress and rotate log files

For a fresh install, make will take a while. It could take anywhere from 30 minutes, if you are using a decent VPS, to 3 hours, if you are using a Raspberry Pi.

If you freshly installed Asterisk, open modules.conf for editing (e.g. nano /etc/asterisk/modules.conf) and look for this:

noload =>

If this line is present and you want to continue using the older SIP channel driver as opposed to the newer PJSIP channel driver, replace with However, you should use PJSIP instead of SIP if you are starting out, and eventually SIP will be removed for those currently using it should make plans to migrate. But, if you're not ready to make the switch yet, you'll need to do this so SIP will continue working for you.

Running as Non-root

Although Asterisk runs as root by default, that isn't generally recommended or necessary. Granular use of the sudoers.d directory will allow you to do operations that require root privileges from within the dialplan.

apt-get install sudo
adduser -c "Asterisk" asterisk
passwd asterisk

# Asterisk needs read access to certs for TLS:
# Allow group to open relevant folders
chmod -R 740 /etc/letsencrypt/live/
chmod -R 740 /etc/letsencrypt/archive/
# Make the relevant letsencrypt folders owned by said group.
chgrp -R asterisk /etc/letsencrypt/live
chgrp -R asterisk /etc/letsencrypt/archive

chown -R asterisk /etc/asterisk/ /usr/lib/asterisk /var/spool/asterisk/ /var/lib/asterisk/ /var/run/asterisk/ /var/log/asterisk /usr/sbin/asterisk

Edit /usr/sbin/safe_asterisk to specify the user as which to run around line 78, like this:

# Don't fork when running "safely"
ASTARGS="-U asterisk"

Now, edit /etc/default/asterisk and uncomment these:


Now, if you do need to perform root operations with Asterisk (such as manipulating iptables), create /etc/sudoers.d/iptables and add this to it:

asterisk ALL=(ALL:ALL) NOPASSWD:/usr/sbin/iptables

Now, you could, say, block an IP address from within the dialplan, without needing to provide a password. Simply prefix sudo to the relevant command. At the same time, you can sleep a little surer at night, knowing that Asterisk has limited access to your system.

If you are trying to install an older (obsolete) version of Asterisk (particular older than 13), you probably won't be able to on recent versions of Debian. Here is an example of an install script for Asterisk 12 and Debian 8.11 that installs a minimal running Asterisk system (not really useful for much):

# WARNING: Do not use this to install modern versions of Asterisk. Use PhreakScript instead.
# This is provided only for reference only for installing obsolete versions of Asterisk on obsolete versions of Debian.
# Super Simple Asterisk Installer
# (C) PhreakNet 2022
# Install Asterisk 12 on Debian 8.11


# nano /etc/apt/sources.list
# deb jessie contrib main non-free
apt-get update

tar -zxvf $RELEASE.tar.gz
apt-get install aptitude -y --force-yes
apt-get install build-essentials -y --force-yes
apt-get install libncurses-dev -y --force-yes
apt-get install uuid-dev -y --force-yes
./contrib/scripts/install_prereq install
make menuselect.makeopts

# Disable everything
menuselect/menuselect --disable-category MENUSELECT_ADDONS --disable-category MENUSELECT_APPS --disable-category MENUSELECT_CDR --disable-category MENUSELECT_CEL --disable-category MENUSELECT_CHANNELS --disable-category MENUSELECT_CODECS --disable-category MENUSELECT_FORMATS --disable-category MENUSELECT_FUNCS --disable-category MENUSELECT_PBX --disable-category MENUSELECT_RES --disable-category MENUSELECT_TESTS --disable-category MENUSELECT_CFLAGS --disable-category MENUSELECT_OPTS_app_voicemail --disable-category MENUSELECT_UTILS --disable-category MENUSELECT_AGIS --disable-category MENUSELECT_EMBED --disable-category MENUSELECT_CORE_SOUNDS --disable-category MENUSELECT_MOH --disable-category MENUSELECT_EXTRA_SOUNDS menuselect.makeopts

# Enable the bare essentials
menuselect/menuselect --enable LOADABLE_MODULES menuselect.makeopts
menuselect/menuselect --enable app_chanspy --enable app_confbridge --enable app_dial --enable app_dumpchan --enable app_originate --enable app_mixmonitor --enable app_playback --enable app_playtones --enable app_read --enable app_senddtmf --enable app_stack --enable app_verbose --enable app_adsiprog --enable app_getcpeid menuselect.makeopts
menuselect/menuselect --enable chan_bridge_media --enable chan_iax2 --enable chan_sip menuselect.makeopts
menuselect/menuselect --enable chan_a_mu --enable codec_ulaw menuselect.makeopts
menuselect/menuselect --enable format_wav --enable format_pcm menuselect.makeopts
menuselect/menuselect --enable func_callerid --enable func_curl --enable func_global --enable func_groupcount --enable func_module --enable func_volume menuselect.makeopts
menuselect/menuselect --enable pbx_config --enable pbx_spool menuselect.makeopts
menuselect/menuselect --enable res_adsi --enable res_convert --enable res_crypto --enable res_curl menuselect.makeopts
menuselect/menuselect --enable EXTRA-SOUNDS-EN-ULAW menuselect.makeopts

make -j2
make install
if [ ! -d /etc/asterisk ]; then
	make samples

Reserving an Office Code

If you're not already a PhreakNet community member, you'll need to follow the Getting Started guide to get set up.

Self-service reservation of office codes can be done through the PhreakNet portal. If you need assistance or have questions, you can post to the list or call the Business Office at 811.

Below, we'll break down the components of the registration process:

  • Thousand Block(s) — The NNX-X blocks you wish to reserve for trunking to and from the network.
  • Exchange Name — To get with the spirit of 2L+5N dialing (as opposed to 7N dialing), we encourage you to pick out an exchange name for your assigned numbers. Unless you have a good reason not to, it should be a Bell System-approved exchange name. You may wish to consult a list of telephone exchange names.
  • Hostname — The public FQDN (fully qualifiede domain name) or IP address of your Asterisk server. The default IAX2 port is 4569. If yours is not, specify it afterwards with a colon.
  • Username — The username of the IAX2 user. If no username is explicitly specified, it defaults to the name of the section.
  • Password — The IAX2 secret. This should be a long alphanumeric string. This is required for IAX2 encryption to function.

If you don't have a static IP address, you can use a DDNS (dynamic DNS) service to automatically have an A record updated whenever your IP address changes. PhreakNet doesn't offer any DDNS services, but our friends over at C*NET do. You'll get your own .ckts subdomain that you can then use for your C*NET, PhreakNet, and other routings.

Other important attributes that are defined for an IAX2 user:

  • Authentication Mode Either none (default), plaintext (deprecated), md5, or rsa. MD5 and RSA should be used. For compatability reasons, RSA cannot be the primary authentication mode — MD5 should be used as the primary authentication mode.
  • Inkeys These are the RSA public keys permitted for RSA authentication on an inbound call. For RSA authentication on PhreakNet, the dialplan manages these automatically.
  • Context The dialplan context(s) to use for the IAX2 user. The last one is the default one if none is specified for an inbound call. Specifying multiple contexts allows a single IAX2 user to be used to access multiple dialplan contexts — very handy for users that use RSA authentication, since these have more moving parts and overhead.

In most cases, calls are peer to peer with all routing info contained in the dial string. A username, host, and password may be specified here.

Existing Dialplans

If you have an existing dialplan, it should be fairly easy to get set up, though some caveats should be noted.

If you are getting started with PhreakNet and are already an NPSTN member, the following should be noted:

  • The verification.conf provided below is the most up-to-date and comprehensive copy, and can replace any existing suite of verification subroutines. It is not PhreakNet specific
  • Any eXtensible dialplan code can be safely removed. SIPxNumToPeer is provided for backwards compatability in the provided extensions.conf
  • The clli variable is not PhreakNet specific. If you are using it currently (e.g. for NPSTN), you should rename clli to npstnclli (NPSTN-specific clli) and use clli for your general clli.
  • phreaknet.conf contains a duplicate [from-internal]. You'll want to set your [from-internal], if you have one, to the desired behavior.
  • Most contexts have been named with a phreaknet- prefix, when PhreakNet specific, to add specifity and clarity and reduce potential naming conflicts.
  • phreaknet-aux.conf contains mostly "library code" as the eXtensible dialplan did. Most things you'll need to edit in the boilerplate code are in phreaknet.conf for easy access.

Boilerplate Code

The sections "Initial Configuration" and "Common Contexts" contain individual contexts in a well-documented manner for those thirsty for the details. However, for your convenience, if you'd like to get up and running with little attention to the details, you can simply use our boilerplate code. Below, you'll find an essential copy of each of the vital Asterisk configuration files you'll need to get started. You will need to adjust some things, such as properly setting certain variables and defining numbers, and so forth, but the code is otherwise as plug and play as possible. Helpful comments are included to help beginners get started, learning, and up and running quickly — no need to fear Asterisk!

If you are getting started for the first time, backup the files below on your system. Then, replace them with the ones below and go through each file, customizing to the extent required as directed.

If you already have an Asterisk system, you'll want to review the contents of these files and merge their contents into your system, since everything you need to get started is included there.

  • asterisk.conf — general Asterisk config file — the two important settings are verbose and timestamps. verbose should be at least 3, and timestamps should be yes.
  • iax.conf — for IAX2 trunking, used for calls to and from the network
  • sip.conf — note that SIP is deprecated and will eventually be removed from Asterisk. Migration to PJSIP is recommended.
  • pjsip.conf — you should use PJSIP instead of SIP if possible (if you are using SIP endpoints). You only need SIP *OR* PJSIP.
  • musiconhold.conf
  • extensions.conf
  • verification.conf — a separate dialplan file containing just the code for the verification contexts, to minimize the clutter in extensions.conf — put this in /etc/asterisk/dialplan
  • verify.conf — configuration file for the app_verify module. See verification for more details.
  • phreaknet.conf — a separate dialplan containing PhreakNet main code, to minimize clutter in extensions.conf — put this in /etc/asterisk/dialplan
  • phreaknet-aux.conf — a separate dialplan containing PhreakNet helper code, to minimize clutter in extensions.conf — put this in /etc/asterisk/dialplan
  • If you have a channel bank, you'll need to set up DAHDI as well (not to mention compile Asterisk with DAHDI). Unfortunately, that is currently beyond the scope of this documentation.

A command like this can be used to download a file and replace an existing one:

wget -O verification.conf -O /etc/asterisk/dialplan/verification.conf --no-cache

Audio Files

First, be sure to grab the Pat Fleet sound library for Asterisk, to replace the default Allison Smith prompts to the extent possible. Trust us — this is a major upgrade.

You will probably want at least a few audio files to get started — the basic call progress tones and intercepts. We encourage all node owners to add variety to the network by giving their nodes their own unique soundscapes. However, if you want to get up and running immediately, we've assembled a set of the basic audio files you will need. Download these files and place them in /var/lib/asterisk/sounds/en/custom/signal/, unless otherwise stated:

These files are provided for convenience, but if everyone uses them, the network's going to sound pretty stagnant, so please consider finding your own unique audio sources. There are a few available at Telephone World, This Is A Recording (for intercepts). You can also scour Evan Doorbell recordings for something more your style if you're looking for something particular. For example, if you decide your switch is going to be a simulated "Number 5 Crossbar" switch, then you should look for #5XB sounds you can use (you can use Audacity to prepare the audio and export it as a WAV file to then convert with SoX).

Note that the above is not your one-stop shop for getting started! Be sure you have completed everything outlined in the "Pre-Requisites" section above.

The boilerplate code above is only a starting point. Many of the contexts individually laid out in this documentation, like the simulated signaling subroutines (e.g. MFer, SFer, dial pulser) are not included in the boilerplate configuration files above. Only the basics that every node really needs to fully participate in the network are included in the boilerplate code. Beyond the contents of the files above, you may wish to further add to your system using other contexts outlined in this documentation.


A word about authentication and encryption:

By default, VoIP, even using IAX2, is not encrypted. Encryption is easy, and it's now included in the boilerplate iax.conf. No other changes are necessary and there are not compatability issues. Some will argue that MD5 is not very robust, but it's better than nothing.

A few notes about IAX2 encryption that are not obvious from looking at the official Asterisk IAX2 or encryption documentation:

  • Although the Asterisk documentation claims it does, plain text authentication does not support encryption (note the difference!). Hence, authentication is a necessary but insufficient condition alone for IAX2 encryption.
  • Encryption requires either MD5 or RSA authentication
  • Plain Text and MD5 authentication use identical configurations and dial syntax. The difference is that MD5 uses a challenge/response rather than sending the password in plain text across the wire
  • A password may be provided even if the dialed destination does not require one. That is perfectly OK. Obviously, if that password is not defined by the peer (and no password is), the call will proceed as usual and it will not be encrypted or authenticated. If the dialed endpoint happens to require authentication and that is, in fact, the correct password, the call will be encrypted.
  • You can tell if IAX2 encryption is working by doing a packet capture in Wireshark. Do a packet capture of an unauthenticated/unencrypted call and then compare with a packet capture of an encrypted call. Import it into Wireshark: the unencrypted pcap will have lots of 7Es, 7Fs, and FFs (if the call was mostly silence); the encrypted call should appear to be mostly random.
  • If you analyze a packet capture of an encrypted call in Wireshark, you may see G.723.1 in the first couple hundred packets. This is nonsense. Wireshark doesn't know the protocol of the packets, since the stream is encrypted.
  • In the Asterisk console, a call is not encrypted if you see Accepting UNAUTHENTICATED call... when a call comes in. It must say Accepting AUTHENTICATED call...

RSA Encryption

RSA is more secure than MD5. However, due to technical limitations and backward-compatability attempts, RSA is only supplementary to non-encrypted or MD5-encrypted "primary" IAX2 users. When supported by both nodes involved in a call, a standardized RSA process will allow for a more securely encrypted call.

RSA encryption has been a stagnant addition to Asterisk for a long time. The key thing to note is that RSA encryption never existed originally in IAX2. RSA authentication predates encryption in IAX2. Encryption was later added, but for plain text and MD5 only (our testing never got to the plain text encryption to work, but that's deprecated now and there's no good reason to ever use plain text). But yes, you read that right. The weak authentication methods got encryption, and the strong one didn't. How much sense does that make? Not?

As early as 2012, people have complained about RSA encryption not working. Digium actually fixed the issue, more or less, in 2014, but the patch was never incorporated into Asterisk. Finally in 2021, after we decided to revisit RSA encryption in Asterisk, we decided to add it to Asterisk ourselves.

There are a few things to know about RSA encryption that you won't find in the IAX2 documentation, so we're providing them here, straight from the horse's mouth.

First off, res_crypto, the cryptography module Asterisk uses, only supports 1024-bit RSA keys. 2048-bit and 4096-bit keys are not supported. The reason for this is that the only code using res_crypto is the IAX2 channel driver and DUNDI, in Asterisk. Though IAX2 remains popular in some communities today, it never really took in the mainstream VoIP world and is basically dead to its developers, and DUNDI never really took off at all, so it's deader than dead. As you can imagine, there's no incentive for Digium/Sangoma to improve res_crypto by adding support for 2048-bit or 4096-bit keys. If it does get added, it will be by the community (read: this is an open invitation to roll up your sleeves if you'd like to make that happen!)

Secondly, Asterisk needs to know the passphrase to your private key, but it requires this be typed in interactively. Fortunately, 1024-bit keys don't require a passphrase, so it's probably best to not have one at all. The instructions below will show you how to generate a private key that doesn't have a passphrase, which eliminates the need for administrator intervention in loading the keys.

Next, some basic syntax. The Dial() application lets you specify a secret, like this:


What you may not have known is that Dial() also lets you specify a private key (or outfile), to use, for the purpose of RSA-authenticated calls:


Here, [privatekeyname] is the name of a private key in /var/lib/asterisk/keys/, without the folder path and without the .key file extension. Just the filename.

Also, notice we said this is how you can make an RSA-authenticated call. Not an RSA-encrypted call. Since the beginning, RSA authentication has not allowed encryption, so this was perfectly sufficient. However, RSA patched to support encryption requires a secret. Well, here is the problem with Dial(). You can't pass both a secret (which was intended for plain text and MD5, only, initially) and a key file name (which was and still is for RSA authentication only). So, you can use RSA authentication without encryption by specifying all the gory details in the Dial() command, but not if you want to use RSA authentication with encryption. Instead, you will need a separate iax.conf peer endpoint for your destination, add all the details there, and then reference that peer in the Dial command, e.g.:



(The above is an example — DO NOT put that in your config).

A quick clarification here. A peer is for an outgoing call. A user is for an incoming call. A friend is for both. (This is for IAX2 only.)

RSA encryption is supported for any nodes that use MD5 encryption for their primary network IAX2 user.

If you are not running Asterisk as root, it goes without saying that keys will need to be readable to the user as which it is running.

Finally, do not use openssl to generate the keys. That is, do not do this:

cd /var/lib/asterisk/keys/
openssl genrsa -des3 -out phreaknetrsa.key -passout pass:SOMESECRETPASSWORDHERE 2048
openssl rsa -in phreaknetrsa.key -pubout -passin pass:SOMESECRETPASSWORDHERE -out

For one, that's a 2048-bit key so it won't work anyways. And mercy help you if you add a secret. Instead, use the astgenkey tool provided with Asterisk to generate your keys. It's super easy to run:

cd /var/lib/asterisk/keys/
astgenkey -q -n phreaknetrsa
cat # upload your public key at
touch /etc/asterisk/iax-phreaknet-rsa-in.conf
touch /etc/asterisk/iax-phreaknet-rsa-out.conf

If you are running Asterisk as not root, make the user as which Asterisk runs own the private key and the new files:

chown asterisk phreaknetrsa.key
chown asterisk /etc/asterisk/iax.conf
chown asterisk /etc/asterisk/iax-phreaknet*

Now, open the Asterisk CLI and run the following commands:

module reload res_crypto
keys init
keys show

You should see your public and private RSA keys loaded. This is a crucial step.

Initialize iax-phreaknet-rsa-in.conf with the following contents:

inkeys=phreaknetrsa ; colon-separated list of public keys to accept

Outgoing Calls

As you can see, iax-phreaknet-rsa-out.conf is an automatically managed file containing users for outgoing calls, which will be updated automatically as needed. This subroutine does make any API requests at all, since it already has all the information needed.

You need to create iax-phreaknet-rsa-out.conf, but no need to add anything to it to initialize it. This means you can occasionally clear it out from time to time by running echo "" > /etc/asterisk/iax-phreaknet-rsa-out.conf from time to time, if you want to get rid of old peers that have piled up every now and then. The dialplan will create new peers as necessary when needed.


If at first you don't succeed, enable IAX2 debugging with iax2 set debug on.

Most failures occur before the call can get to the dialplan, so this is essential for narrowing down the cause of the issue.

The Business Office is happy to assist with Asterisk issues if needed. However, a detailed trace is needed in order to assist with dialplan issues. For dialplan troubleshooting, please use PhreakScript to perform a CLI trace as follows:

phreaknet update
phreaknet trace

If you use PhreakScript's trace feature, you do not need to open the CLI yourself and copy and paste the CLI output. This is handled automatically.

Follow the instructions displayed by the wizard. At the end, a URL to a paste of the CLI trace will be displayed. Then, submit a ticket to the Business Office (choose PhreakNet for Asterisk dialplan related issues, NOT Asterisk!). By default, pastes are only live for 24 hours before they are deleted. You can extend the duration online at You can also configure redactions here.

Initial Configuration

At this point, we will assume you have a working Asterisk server and have reached out to get a thousand block allocated to you. In the meantime, you can work on getting your exchange setup so that everything will be working by the time your exchange appears in the route table (which usually occurs in less than 1 business day).

A note about Asterisk directories is necessary at this point. For those of you using the regular (compiled from source) version of Asterisk on Debian Linux, directories of particular importance are noted below:

/etc/asterisk/ — where the Asterisk configuration files reside
/var/lib/asterisk/agi-bin/ — where Asterisk AGI programs go
/var/lib/asterisk/moh/ — where music-on-hold audio files reside
/var/lib/asterisk/sounds/en/custom/ — where your custom audio files go (most any audio besides MOH)
/var/log/asterisk/ — where the Asterisk logs are located
/var/spool/asterisk/ — where recordings and voicemails are created

These paths are the default ones, assuming you haven't changed them in the Asterisk configuration.

Additionally, the AstDB database (e.g. the underlying database used by DB, DBDelTree, DB_KEYS, DB_DELETE, etc.) is stored in /var/lib/asterisk/astdb.sqlite3. Be sure to back this up periodically if you are using it!

As you spend more time working Asterisk, you'll become more and more familiar with these directories.

To get started, we need to adjust some settings and configure some settings. The following configuration (.conf) files are all located in /etc/asterisk/. You will need to modify them. There are a few ways you can do this. You can opt for the "local modification" approach or the "direct modification" approach.

  • Local Modification — this entails keeping a local copy of all your configuration files, essentially mirroring what is on the server. The principle is simple: perform all your edits locally, and do a one-way write to the server to copy your changes over. If you opt for this approach, you can use a text-editor of your choice, like Notepad++ on Windows, to make changes. If you go this route, you don't need to worry separately about backups, either, since what is on the server in production is just a copy of your local files. If you haven't modified a particular file before, you'll need to copy it over from the server, first.
    If you are using Notepad++, you can add a custom language package to Notepad++ to add dialplan color syntax highlighting.
  • Direct Modification — this entails directly modifying the files on the server. Since there is no GUI, this involves using command-line based text editors, like nano or vim. If you are comfortable with doing complex text manipulation and editing in the terminal, you may find this to be an easier approach. If you go this route, you won't need to worry about version conflicts, but you will need to ensure your configuration files are being backed up (along with your other files, like audio files, etc.!)

Regardless of which method you are using, but particularly if you opt for the local modification approach, you will need an SFTP client, like FileZilla. If you use FileZilla, you will need to change the "Default Transfer Type" to binary. You can do this by going to Edit → Settings → Transfers → FTP: File Types → Default transfer type. Select "Binary" and uncheck the two "Treat files… as ASCII file(s)" at the bottom as well.

You will need to use an FTP client like FileZilla (but using the SFTP protocol) to transfer over your audio files as well as your .conf files each time you make a revision if you opt for the local modification editing approach. You can use your client's site manager to save your connection settings so you don't need to re-enter the connection information each time.

The section below provides detailed commentary and explanation on setting up your Asterisk switch. If you don't care about the details and want to get up and running as soon as possible, see the "Boilerplate Code" section of this documentation. It provides files you can simply copy and paste, rather than providing you with the configuration piecemeal as below.


There are a couple settings you will want to tweak in this file that will impact your debugging. Make sure you have the following options set and uncommented:

verbose = 3
timestamp = yes

By default, the Asterisk console does not give you terribly detailed information as to what's going on when your system is in use. You can manually set the verbosity to, say, 3, for instance, by typing core set verbose 3 at the Asterisk command line interface (or CLI). However, rather than have to manually type this every time you want to debug, it's easier to automatically set the console's verbosity. Verbosities range from 1 through 10 — 1 and 2 are pretty bare in detail, and 10 is not terribly more informative than 3 is (at least that's been our experience), so we'd say setting the default verbosity at 3 is a good starting place. You can tweak this later as you adjust to how much detail each verbosity level provides.

Setting timestamp to yes will provide timestamps in the console, also helpful for debugging. All calls are automatically logged with timestamps, but enabling this will allow you to see your dialplan's execution line by line with important contextual information regarding timing.


Unless you know what you're doing, make sure that you have autoload enabled:


There are a number of VoIP protocols in use today, SIP/PJSIP and IAX2 being two of the more popular ones. There are many others, as well, that should be disabled if you are not using them. Otherwise, they increase the attack surface of your Asterisk system. You can disable other infrequently used or unused modules by adding the following to your [modules] context:

noload => ; Don't load skinny (tcp port 2000)  
noload => ; Don't load MGCP (udp port 2727)
noload => ; Don't load unistim (udp port 5000)
noload => ; Don't load ooh323 (tcp port 1720)
noload => chan_323
noload => ; Don't load dundi (udp port 4520)

The last statement disables DUNDI. If you don't know what that is, you're probably not using it and it's safe to do a "noload" on it. If you are using it by chance, then obviously, don't add that line!

If you so wish, you can look through your modules.conf and/or do some research and disable other modules you are not using. Be absolutely certain not to disable anything you might possibly need! It may help to consult a list of Asterisk modules. At the Asterisk CLI, you can also type module show to see all currently loaded modules, allowing you to see if there's anything loaidng you don't need.


Consult the boilerplate sip.conf for starter code.

Note that SIP is deprecated and will eventually be removed from Asterisk. PJSIP is the replacement for SIP.

A thing or two about bindport. Changing this to a value that is not 5060 will change the port that SIP is running on on your server. Changing the SIP port will make it more difficult for attackers and spammers to probe your Asterisk switch unbeknownst to you. Of course, tools like iptables and fail2ban should be relied on to prevent repeated brute authentication attempts (as well as changing the SSH port away from port 22), but changing the SIP port to a random number can further discourage spammers. The idea is to not use port 5060 for external SIP connections by changing your port to something non-standard. For example, you could set bindport=39145. Pick a port between 16383 and 65535 and never tell anyone what port you are using! Do not forward that UDP port in your router and ensure that UDP port 5600 is not forwarded in your router either. Indeed you should not need RTP UDP ports (usually 10000-20000) forwarded either.

The vast majority of VoIP providers use SIP (as opposed to IAX2, though does support IAX2), so you'll likely need to use SIP or PJSIP for these.

DTMF can be finicky. Some have the best luck with SIP Info and specifying it explictly in sip.conf. Others have the best luck with RFC2833 and not explicitly specifying it in sip.conf (and only specifying it in the ATA's configuration options. You'll need to choose an option and try dialing through a DISA or using an echo test to make sure DTMF digits are being received at the distant end.

If you'd like to know more about what these do, consult the Asterisk documentation.


Consult the boilerplate pjsip.conf for starter code.

PJSIP is the new SIP channel driver in Asterisk. It was released as part of Asterisk 12. SIP was deprecated in 16 and will be removed by 21 (as of 18, or possibly even 16, it is already no longer built by default). PJSIP is the future of SIP in Asterisk, and migrating to PJSIP now before your Asterisk falls off the SIP cliff is highly recommended!

Architecturally, PJSIP is very different from SIP, and Sangoma is very proud of that. chan_sip is fairly monolithic, whereas PJSIP is more modular, standards based, and utilizes the open-source pjproject under the hood. Sangoma has described SIP as "a channel driver that happens to implement the SIP stack" and PJSIP as "a SIP stack that just happens to have a channel driver".

Another difference is that chan_sip will always just "kind of work", even if it's not in the way you intended. PJSIP, on the other hand, is fairly strict with regards to configuration, so if you make a typo or syntax error, the relevant component simply will not reload, alerting you that there is a fatal error you must resolve. Joshua Colp, from Sangoma, sums this up as "we believe that if you told PJSIP to do something, and it can't understand what you meant, then it should fail, because you told it to do something for a reason" (paraphrasing).

chan_sip is no longer receiving any attention from Sangoma as it is, as all of Sangoma's efforts, SIP-wise, are going into PJSIP. This means if you are encountering bugs or issues in SIP, they may very well be resolved by moving to PJSIP. As an example, some ATAs using TLS were having delayed re-registration with Asterisk if Asterisk restarted with SIP. This proved to be very annoying during testing. With PJSIP, this issue disappeared entirely.

Those are the advantages of moving to PJSIP — what about the downsides?

The disadvantage is that PJSIP has a higher learning curve than SIP. There are more moving parts, and for a newbie getting started, chan_sip is arguably much simpler and more straightforward than PJSIP. Below, we'll break down some of the major components involved. There is a lot, so the official Asterisk wiki will be a more in-depth resource.

Below are some code snippets from the boilerplate pjsip.conf. Let's go through each section in detail.

Similar to chan_sip, chan_pjsip supports UDP, TCP, and TLS. UDP and TCP may be bound on the same port, whereas each TLS port is bound on separate ports. We say "each", because nothing stops you from running multiple TLS ports, each running a different version of TLS. This allows you to support TLS 1.0, if needed, for older ATAs, while running your more modern ATAs on TLS 1.2, giving you the best of both worlds — compatability and security.

type = transport
protocol = udp
bind =
tos = cs3

type = transport
protocol = tcp
bind =
tos = cs3

; If you don't have TLS certificates and aren't using TLS, don't uncomment these. PJSIP will error out and not load.
;type = transport
;protocol = tls
;bind =
;tos = cs3
;cert_file = /etc/letsencrypt/live/
;priv_key_file = /etc/letsencrypt/live/
;verify_server = no
;method = tlsv1

Above are the transport sections for UDP, TCP, and TLS 1.0. The actual names of these sections is not set, but something like transport-udp is probably fairly standard.

Similar to with SIP, TLS transports will need to include your certificate information. The method is tlsv1 for TLS 1.0 and tlsv1_2 for TLS 1.2.

In the transports above, we bind UDP, TCP, and TLS to ports 16555 and 16556. Running SIP on non-standard ports (that is, not 5060 or 5061) is a general best practice. It's security by obscurity, but the fact is that a lot of spam traffic will leave you alone if you aren't running on these ports, reducing the load on your system. Running iptables and fail2ban to deal with the rest is still an obvious must (see the relevant sections in the Docs for setup instructions).

Remember, if you run on non-standard ports, you'll need to explicitly include the port on any clients wherever the server might be mentioned, e.g.

So, the big question — which protocol should I use?

The answer is TLS, if you can swing it. Not all SIP devices or softphones will support TLS, might support it only as a "premium" feature (e.g. Zoiper), or might only support up to TLS 1.0. The general rule of thumb is use the best protocol that you can. For some devices, that might be TLS 1.2. For others, it might be TLS 1.0. For some, it might mean just plain old UDP or TCP (UDP is more common than TCP, of the two).

Note that transports are not explicitly associated with specific users. Your SIP users will implicitly choose which transport to use by explicitly providing the port and protocol.

type = endpoint
disallow = all
allow = g722
allow = ulaw
rtp_symmetric = yes
force_rport = yes
rewrite_contact = yes
direct_media = no
inband_progress = yes
tos_audio = ef
device_state_busy_at = 1
trust_id_outbound = no
trust_id_inbound = no
notify_early_inuse_ringing = yes
context = from-internal ; context in the dialplan in which this user originates a call
allow_subscribe = yes
subscribe_context = phreaknet-hints

Similar to SIP, PJSIP supports templates, which allow you to greatly simplify configuration by putting common configuration in a separate section, and then specifying that template section in your actual sections. This reduces clutter and increases manageability.

The template above is designed for "line endpoints", that is ATAs, softphones, and other "clients" (as opposed to for trunks, registrations, etc. where Asterisk is either an equal or a client).

We permit the ulaw and g722 codecs in this template to allow for high-quality audio. If you're not in the US, you should allow alaw as well.

We use device_state_busy_at to ensure that an endpoint is considered "busy" if it has any calls currently in progress, since it's a line, and that's how lines work.

allow_subscribe and subscribe-context are used for presence (e.g. busy lamp fields).

type = aor
max_contacts = 1
qualify_frequency = 30

Here's where PJSIP can start to get a bit funky, if you're used to SIP. An AOR stands for "address of record". The important thing really is that max_contacts should be 1. In theory, it doesn't need to be, and PJSIP actually allows a specific endpoint to have multiple contacts, which means you could register two softphones to the same SIP user. In practice, that's kinds of pointless, because Asterisk has no way of differentiating these, and all your SIP endpoints should have a separate user, as a best practice. Increasing max_contacts is not the right way to make your calls follow you everywhere — there are much better ways to do that, in dialplan.

type = auth

This doesn't really need to be a template, but since you can specify the template on the same line as the auth section name for your individual users, you'll save one line per user.

; every user needs to have an AOR (address of record) section, auth section, and endpoint section. To minimize clutter, we use templates for each.

username = DeskPhone1
password = samplepasswordhere

callerid = "John Smith" <5552368> ; change the CNAM and caller ID of your line here
;media_encryption = sdes ; for SRTP (voice path), if your endpoint supports it. Make sure to connect to the TLS port so the signaling is encrypted, too.
auth = DeskPhone1
;outbound_auth = DeskPhone1
aors = DeskPhone1
;mailboxes = 2368@vmcontext ; for voicemail MWI

Here's where we finally define an actual "user". Notice that each user is comprised of three things we've already discussed — AOR sections, auth sections, and endpoint sections. This can be a big change if you're used to SIP or IAX2, where each user is generally comprised of just a single section. You can't do that in PJSIP, because it's a much more modular channel driver.

The auth section is where you define the credentials for the user. How does this user authenticate to you?

The endpoint is where the "meat" of the user is. The endpoint references the corresponding auth and aor contexts. We've included outbound_auth here but commented it out. outbound_auth is used to specify the auth section containing credentials for peers where Asterisk is the user/client, rather than the server. For an ATA or subscriber line, Asterisk is not the client, so no outbound_auth is needed. Likewise, if Asterisk is only the user, then you would not have an auth section specified, because the remote end doesn't need to authenticate against you — you need to authenticate against it.

media_encryption = sdes is the equivalent of encryption = yes in SIP. PJSIP just supports multiple different kinds — sdes refers to standard SRTP. TLS + SRTP = a secure line (TLS encrypts the signalling, SRTP encrypts the voice path — but SRTP is somewhat pointless without TLS, since the keys are sent in the clear).

If you're using voicemail, you can specify mailboxes here, which will be used for MWI. You can also reference them from the dialplan to avoid duplicating code, using one of the PJSIP functions. Note that it's mailboxes, not mailbox as it was in sip.conf. A lot of the keywords are slightly different.

Another thing to keep in mind is that PJSIP uses RFC4833 for DTMF, not RFC2833 as SIP does. One issue with migrating SIP to PJSIP is that DTMF on some lines may stop working suddenly (e.g. you "can't break dialtone"). This will happen if some ATAs are configured to use SIP INFO and Asterisk is trying to use RFC2833 instead. You should

Unlike SIP, PJSIP does not allow anonymous access by default, which is really a good thing, since there's generally no reason to. You don't need to set allowguest = no as in sip.conf because that is the default behavior.

Finally, making outbound calls to SIP URIs with PJSIP is more involved than with SIP. Dialing a SIP endpoint took care of some things for you which allowed you specify a fairly normal looking dial string, e.g. Dial(SIP/

In PJSIP, there is no such thing as a "naked" dial like that. Every call must be associated explicitly with an endpoint. What this means is you'll actually need to create an endpoint (or multiple) for outgoing calls, even when you're explicitly providing all routing info in the dial string. Here's what that might look like: Dial(PJSIP/outgoing/

Here, outgoing refers to an endpoint section in pjsip.conf. Notice that it looks similar to what we've defined for users, but it's a little bit different.

type = aor
maximum_expiration = 60
minimum_expiration = 60
default_expiration = 180

type = identify
endpoint = outgoing
match =

type = endpoint
rtp_symmetric = yes
force_rport = yes
rewrite_contact = yes
direct_media = no
tos_audio = ef
disallow = all
allow = g722
allow = ulaw
language = en
aors = outgoing

transport = transport-udp

The "default" outgoing endpoint we've set up here uses UDP. If you wanted to make an encrypted TLS call, you might define another outgoing endpoint, e.g. outgoing-tls12 for outgoing TLS 1.2 calls. Note that these are only used for calls where you explicitly say to use these. If you're making a call to something to which you're registered, you're not going to be using this. This is more for "anonymous outbound calls", essentially.

PJSIP has a lot of stuff you can configure and set up, with the caveat that there is a lot you need to configure and set up, as compared to chan_sip, which you can get going fairly easily and lazily, and will do some things behind the scenes for you to infer what it should do. It's a double-edged sword.

If you're already using SIP and you want to migrate to PJSIP, there is a conversion script on the Asterisk wiki that you can run on your sip.conf that will spit out a pjsip.conf. The good news is that this easily converts your config without you needing to worry about what matches up with what, between the two configs. The problem is that it's not 100% accurate, and if your sip.conf is non-trivial, it will likely spit out an incorrect config that has a ton of issues that you'll need to manually rectify (this is what happened to us). Just keep this in mind and be prepared to spend a few hours debugging why your registrations are not working, etc. We've contributed a few bug fixes and additions to this script, to improve it for those using it, but one major issue of which we're aware is the scenario where you have multiple registrations to a single provider. This script will butcher that up majorly, in a seriously epic fail, so you should plan to manually visit those. Once your migration is complete, make sure all your registrations come online and that your users and peers can reach you.


chan_dahdi.conf is the Asterisk config file for the DAHDI channel driver. However, the kernel-level /etc/dahdi/system.conf also needs to be configured.

DAHDI allows you to use real (e.g. TDM) telephone equipment with Asterisk. Most commonly, instead of an ATA, you can use a channel bank, for instance. There is much more overhead to getting set up, but line densities tend to be higher and you'll get more capabilities. Consider this the "premium" telephony experience. It's akin to what POTS TDM switches at the real phone companies are using.

DAHDI is one of the least intuitive and most confusing aspects of Asterisk to work with. A few tips for those getting started:

  • You'll need to connect your channel bank to your Asterisk server (on-premises) using a T1 cable, not a regular Ethernet cable.
  • In chan_dahdi.conf FXS lines use fxo signalling and FXO lines use fxs signalling. It seems backwards, but that's how it is. In chan_dahdi.conf, there are two ways to configure channels. The older and more popular method is the "fallthrough" method, which can be very confusing to those used to working with other Asterisk config files. In the [channels] config section, any channel settings configured persist until they are overridden, and you simply define your channels there, like this:
    signalling = fxo_ks ; yeah, this makes no sense, but it's right...
    callwaiting = no
    callwaitingcallerid = no
    threewaycalling = no
    transfer = no
    canpark = no
    cancallforward = no
    callreturn = no
    mailbox = 123@context
    immediate = yes
    transfertobusy = no ; whether can transfer to a busy station.
    context = from-internal
    adsi = yes 
    useincomingcalleridondahditransfer = no 
    ; Line 1
    callerid = John Smith <211>
    threewaycalling = no
    channel => 1
    ; Line 2
    callerid = Jane Smith <212>
    channel => 2
    ; Line 3
    callerid = Paul Smith <213>
    threewaycalling = yes
    channel => 3
    The problem with this is that when you set threewaycalling to no for line 1, it will apply to any channels you configure until it is explicitly overridden again. This means that you can change a setting in one place, thinking you are changing it for one line, and end up screwing up the configuration of many other lines at the same time in unintended ways.

    Consequently, an alternate way of configuring channels that is similar to how other channel driver config files work is by defining each channel in its own section, and using templates to set "inheritable" settings, like so:

    signalling = fxo_ks ; yeah, this makes no sense, but it's right...
    callwaiting = no
    callwaitingcallerid = no
    threewaycalling = no
    transfer = no
    canpark = no
    cancallforward = no
    callreturn = no
    mailbox = 123@context
    immediate = yes
    transfertobusy = no ; whether can transfer to a busy station.
    context = from-internal
    adsi = yes 
    useincomingcalleridondahditransfer = no 
    dahdichan = 1
    callerid = John Smith <211>
    dahdichan = 2
    callerid = Jane Smith <212>
    description = Line 2

    This is generally considered to be the better way of configuring chan_dahdi, especially if you are starting from scratch with a new system, and if you are configuring lines (as opposed to something like a PRI).

Getting started out with DAHDI can be tricky. Here, we'll assume that you are configuring FXS lines (thus, using FXO signalling). The example above uses kewlstart.

At this point, we'll assume that DAHDI Linux and DAHDI Tools have already been installed (such as by using PhreakScript).

dahdi_genconf will generate a default config file, which is intended for PRI and not for FXS ports, e.g. with the following:

# termtype: te

Never run dahdi_genconf more than once. If you run it again, you'll wipe out your config.

You'll need to manually change /etc/dahdi/system.conf to look something like this:

# Span 1: WCT13x/0 "Wildcard TE131/TE133 Card 0" (MASTER) ESF/B8ZS RED ClockSource

# Global data

loadzone        = us
defaultzone     = us

The example above uses loop start, but you could also use the cooler (or "kewler") kewlstart, which is similar but has more capabilities (simply use fxoks for that). In fact, in most cases, you'll want to use ks, not ls. KS provides loop disconnect on hangup, and this is typical of most "real" POTS switches.

Note that for disconnect supervision to work properly on FXO ports, you also need to set this in the channel bank. For example, using an Adit 600, you might need to do something likeset 2:1-8 signal lscpd (here, 2 refers to card #2) — lscpd will make loop disconnect supervision work properly so that if an incoming call to an FXO port hangs up, it actually clears towards DAHDI. With simply ls, this will not work correctly for FXO ports (but should suffice for FXS ports).

But that's rather repetitive, isn't it? So you could also do:

# Span 1: WCT13x/0 "Wildcard TE131/TE133 Card 0" (MASTER) ESF/B8ZS RED ClockSource
fxoks = 1-24

# Global data

loadzone        = us
defaultzone     = us

(Here, we do use kewlstart).

Take a look at /etc/dahdi/system.conf.sample for more details on usage.

Then, run dahdi_cfg -vvvvv. Each v indicates more verbosity (similar to as with the asterisk or rasterisk arguments.

You may then see something like this (the below is for a single span, with fxols for all):

DAHDI Tools Version - 3.1.0

DAHDI Version: 3.1.0
Echo Canceller(s): HWEC

SPAN 1: ESF/B8ZS Build-out: 0 db (CSU)/0-133 feet (DSX-1)

Channel map:

Channel 01: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 01)
Channel 02: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 02)
Channel 03: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 03)
Channel 04: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 04)
Channel 05: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 05)
Channel 06: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 06)
Channel 07: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 07)
Channel 08: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 08)
Channel 09: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 09)
Channel 10: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 10)
Channel 11: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 11)
Channel 12: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 12)
Channel 13: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 13)
Channel 14: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 14)
Channel 15: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 15)
Channel 16: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 16)
Channel 17: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 17)
Channel 18: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 18)
Channel 19: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 19)
Channel 20: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 20)
Channel 21: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 21)
Channel 22: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 22)
Channel 23: FXO Loopstart (Default) (Echo Canceler: mg2) (Slaves: 23)
Channel 24: FXO Loopstart (Default) (Echo Canceler: none) (Slaves: 24)

24 channels to configure.

Setting echocan for channel 1 to mg2
Setting echocan for channel 2 to mg2
Setting echocan for channel 3 to mg2
Setting echocan for channel 4 to mg2
Setting echocan for channel 5 to mg2
Setting echocan for channel 6 to mg2
Setting echocan for channel 7 to mg2
Setting echocan for channel 8 to mg2
Setting echocan for channel 9 to mg2
Setting echocan for channel 10 to mg2
Setting echocan for channel 11 to mg2
Setting echocan for channel 12 to mg2
Setting echocan for channel 13 to mg2
Setting echocan for channel 14 to mg2
Setting echocan for channel 15 to mg2
Setting echocan for channel 16 to mg2
Setting echocan for channel 17 to mg2
Setting echocan for channel 18 to mg2
Setting echocan for channel 19 to mg2
Setting echocan for channel 20 to mg2
Setting echocan for channel 21 to mg2
Setting echocan for channel 22 to mg2
Setting echocan for channel 23 to mg2
Setting echocan for channel 24 to none

DAHDI needs to be restarted whenever its config is changed, such as if you've changed the type of signalling used by a span, so run service dahdi restart. (Some guides may also tell you to simply reboot).

Then, finally, comes configuring things in "user land" at the Asterisk level, namely chan_dahdi.conf. Once that's been done, you'll want to issue dahdi restart at the Asterisk CLI.

Pay careful attention to any warnings that may arise when you do this, e.g. don't ignore this:

[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 1: Red Alarm
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 2: Red Alarm
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 3: Red Alarm
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 4: Red Alarm
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 5: Red Alarm
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 6: Red Alarm
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 7: Red Alarm
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:12535 mkintf: Attempt to configure channel 7 with signaling FXO Loopstart ignored because it is already configured to be FXO Loopstart.
[Mar 25 20:17:06] WARNING[585]: chan_dahdi.c:7376 handle_alarms: Detected alarm on channel 7: Red Alarm

You can use dahdi show status in the Asterisk CLI to get the status of your telephony cards.

In the case above, red alarm means a T1 signaling issue, likely caused by a loose connection or no connection at all (in the proper place).

Dial Syntax

The DAHDI dial syntax is a bit peculiar. See this old mailing list post for some gory details.

The most important modifier to the regular dial syntax is specifying the ring cadence. This is done together with specifying the channel number, e.g. Dial(DAHDI/4r2) would dial line 4 (in your first span) and use ring cadence 2. Here are some comments from the code that describe all the options:

* Dial(DAHDI/pseudo[/extension])
* Dial(DAHDI/[c|r|d][/extension])
* Dial(DAHDI/(g|G|r|R)[c|r|d][/extension])
* g - channel group allocation search forward
* G - channel group allocation search backward
* r - channel group allocation round robin search forward
* R - channel group allocation round robin search backward
* c - Wait for DTMF digit to confirm answer
* r - Set distintive ring cadance number
* d - Force bearer capability for ISDN/SS7 call to digital.

To troubleshoot audio on DAHDI lines, you can use dahdi_monitor.

DAHDI debug messages are logged to /var/log/messages, but they are not particularly verbose by default, so if you are developing in the DAHDI kernel, you may want to add some module_printk statements of your own.

Music on Hold

Consult the boilerplate musiconhold.conf for starter code.

Music on hold is how custom ringback tones are used in Asterisk.


The [ringback] class is referenced in the Dial Statement in [phreaknet-ring]. If you change the name, make sure to do so in both places.

As written, this context expects your ringback audio file to be located in /var/lib/asterisk/moh/ringback/ and be named rbs1.ulaw. Adjust the paths if needed, but you should keep music on hold files in the moh directory. We recommend creating a separate directory inside of moh for your ringback audio and then placing it inside of that.

If you need a ringback audio file, there are a few available at Telephone World. You can also scour Evan Doorbell recordings for something more your style if you're looking for something particular. For example, if you decide your switch is going to be a simulated "Number 5 Crossbar" switch, then you should look for a #5XB ringback you can use (you can use Audacity to prepare the audio and export it as a WAV file to then convert with SoX). Likewise, you should find #5XB busy and reorder signals you can use.

Now, delete the [default] context and add the following:

	random = no


Create the folder silence inside the moh folder and then copy /var/lib/asterisk/sounds/en/silence/10.ulaw into it.

What did this accomplish? In keeping with the realism factor, by default, Asterisk's default music on hold will play if you flash during a call. In most cases, this is undesirable. Not only is it generally frowned-upon in production systems, but music-on-hold on an otherwise simulated electromechanical switch will stand out like a sore thumb. Now, when you flash, the party you just put on hold will not hear anything.

Remember, to reload music on hold you will need to enter moh reload from the Asterisk CLI.


Consult the boilerplate iax.conf for starter code.

If you're not sure whether you have IAX trunking enabled, you can do the following:

  1. First, SSH into your Asterisk server and open your Asterisk console, like so:
    asterisk -r
  2. Run the following command:
    module show like iax
  3. Make sure the IAX2 module is loaded. If it's not, make sure you don't have a noload statement in modules.conf (see the modules.conf section if you're unsure about this).

Once you've determined that the IAX2 module is working, you'll need to modify iax.conf, which is located in /etc/asterisk/.

Verification Subroutines

The verification subroutines is part of the boilerplate code (verification.conf).

NEW: Verification Module

The verification subroutines are still supported and are being maintained. They are not deprecated. An alternative, however, is to use the Verify dialplan application. This is available if you installed Asterisk using PhreakScript (PhreakNet Asterisk).

app_verify uses the verify.conf config file. The boilerplate config file covers usage with PhreakNet, C*NET, and NPSTN, as well as incoming PSTN calls. Functionality should be identical, more or less, to using the verification subroutines. The major difference from a UX point of view is the module provides some additional statistics and probably performs better, since it's written in C instead of in dialplan. Since it's just a single dialplan call, the CLI doesn't get cluttered up with verification dialplan execution, if that annoys you.

In other words, use either app_verify (the Verify() and OutVerify() applications) or verification.conf, but don't use both. Ultimately, pick whichever method is easier or better for you. They are both fully supported.

PRE-REQUISITE: You MUST have DIG installed on your server (part of the dnsutils). Please see the Pre-Requisites section for more details.

The verification subroutines rely on the following global variables being properly defined: maindisa, allowdisathru, and allowpstnthru.

Here is a list of the 2-digit "caller verification status codes". The network name on the left refers to the original source of the caller. The first upstream node (the node into which an external caller originally dials) determines the code below to use and passes it along to downstream nodes.

  • 00 — Reserved, for local temporary use (e.g. if sending a call over a trunk that doesn't pass channel variables, temporarily turn null CVS into 00 and add that to the extension, then convert back on the other end)
  • 10 — NPSTN, valid and legitimate
  • 11 — NPSTN, valid but illegitimate
  • 19 — NPSTN, untrusted/illegitimate (validity spoofed by upstream node)
  • 20 — C*NET, valid and legitimate
  • 21 — C*NET, valid but illegitimate
  • 30 — US PSTN, valid
  • 31 — US PSTN, invalid
  • 32 — US PSTN, anonymous
  • 40 — UK PSTN, valid
  • 41 — UK PSTN, invalid
  • 42 — UK PSTN, anonymous
  • 50 — AU PSTN, valid
  • 51 — AU PSTN, invalid
  • 52 — AU PSTN, anonymous
  • 60 — Other PSTN, valid
  • 61 — Other PSTN, invalid
  • 62 — Other PSTN, anonymous
  • 70 — PhreakNet, valid and legitimate
  • 71 — PhreakNet, valid but illegitimate
  • 72 — PhreakNet, invalid and illegitimate
  • 75 — PhreakNet, ANI fail, Operator Number Identification
  • 77 — PhreakNet, fatal verification failure
  • 78 — Blacklisted number (drop call)
  • 79 — PhreakNet, untrusted/illegitimate (validity spoofed by upstream node)
  • 90 — Other, valid
  • 91 — Other, invalid
  • 92 — Other, anonymous

Generally, X0 indicates the call is valid. Note that we can only confirm the caller if the CVS (caller verification status) code is 10 or 20. 30 or 40 indicate PSTN calls that, while they appear to be valid, cannot be 100% confirmed to be so. 9X codes are reserved for other use, such as assigning CVS codes to other private networks. This way, such calls do not have an empty CVS code and do not appear to be originating locally when performing CVS branch checks.

The verification subroutines also process STIR/SHAKEN headers as part of the verification process for incoming PSTN calls from VoIP providers. This information is available in adjunct to the CVS code, and may be used by the direct or downstream node operators for further call filtering as desired.

If the last digit the CVS code is not 0, the caller ID is almost definitely spoofed, unless the last digit is 2, which indicates an anonymous PSTN caller whose identity, while not necessarily spoofed, cannot possibly be verified.

Note that validity and legitimacy here are two different concepts. Validity refers to a number being of proper format. PSTN calls can be determined to have a valid number, based on the rules specified for PSTN numbering, but verifying that number is a legitimate is different altogether. Legitimacy can only be easily determined for calls for private peer-to-peer VoIP networks (NOT the PSTN).

CVS codes are frequently used to provide different classes of service. For instance, if you have sensitive contexts to which you wish to allow only trusted callers, you can branch based on the CVS code. You can also use CVS codes to act accordingly in your dial plan based on the origin of the caller. All you would need to do in this case is check the first digit of the CVS code to determine from where the caller originally dialed in (or the second to check the call's legitimacy). The second digit says more about the caller's verification status. Since the first digit is network status and the second is verification status, you could, for example, use GotoIf($["${clidverif:-2:1}"="7"]?phreaknet) and GotoIf($["${clidverif:-2:1}"="2"]?cnet) within your dialplan.

WARNING: If you use CVS codes to configure "forks" in your dialplan, you must always define an exception for calls where the clidverif variable is not defined, which will be the case for any local calls. Since all incoming calls to your node will be assigned a CVS code, if a call does not have a CVS code, it is a local call and you should handle it accordingly, like so: GotoIf($[${ISNULL(${clidverif})}]?local). If you don't add this "null variable exception", any calls originating from your switch will not route properly at any such forks in your dialplan.

At this point, you may wonder why using IAX variables to pass around these 2-digit codes is necessary. If you care to examine the code carefully, the reason becomes obvious, but the quick answer is that it is only possible to accurately verify calls from the last upstream node. If a caller dials through a DISA and then dials to another node, this third node cannot possibly verify the original caller. This is because while its Caller ID is the original Caller ID, the IP address from which the caller will appear to be coming belongs to the second node. Hence, if a simple IP verification technique is used, the caller will be flagged as fradulent. Hence, it is necessary for each upstream node to "pass along" the results of its verification tests to subsequent downstream nodes. In this way, downstream nodes can be as sure of the validity of the caller as if he had dialed that node directly. Furthermore, the validity of the upstream node itself is also verified to be sure we can trust its judgment (i.e. to make sure a rogue impostor server pretending to be an in-network node is not fradulently attempting to pass along inaccurate verification information). Hence, the ${maindisa} variable is used to allow a downstream node to do a reverse lookup and confirm the node is who it says it is. With these subroutines, in conjunction with the CNAM subroutines below, it is impossible for a non-network node to fradulently present itself to any node, no matter how far up or downstream in a call a node may happen to be.


At last, extensions.conf, the heart of the Asterisk dialplan! Here is where all (or at least most) of the action happens.

Consult the boilerplate dialplan files for starter code (extensions.conf, verification.conf, phreaknet.conf, and phreaknet-aux.conf).

While it is highly recommended to grab the latest boilerplate code and adapt them to your dialplan, they are not strictly required. You will, however, need the following, at minimum, for a "minimally viable dialplan":

  • verification.conf - all the verification subroutines
  • The incoming contexts (e.g. from-phreaknet) and the outgoing contexts (e.g. to-phreaknet and dialphreaknet), from the top and bottom of phreaknet.conf, respectively. You can fill in the gaps in the middle as desired.
  • The contexts at the top of phreaknet-aux.conf, e.g. dialphreaknet-helper and dialphreaknet-rsa

A word about variables: don't make them all uppercase. All-uppercase variable names are reserved for use by Asterisk. Use lowercase, camelCase, or CapitalizeEachWord, but don't make them ALLUPPERCASE.

Before continuing, you may wish to reload or restart Asterisk. To restart Asterisk entirely, type core restart now at the Asterisk CLI. To just reload a particular module, you can generally type the module name followed by the reload, such as sip reload. To reload music-on-hold, for example, you would type moh reload. To reload voicemail, you would type voicemail reload. To reload the dialplan (which is something you'll end up doing quite frequently, you'll type dialplan reload. To reload all modules without restarting Asterisk, you can simply type reload.

Occasionally, you may need to reboot your server as well. To do this, from the Asterisk CLI, type exit to quit the Asterisk CLI and exit to the Linux command line. Then type reboot. That's it!

Playback() has a root of /var/lib/asterisk/sounds/en/ so you only need to specify directories downstream from that point. Of course, adjust the Playback() path to that of your audio files. You will need to use SoX to convert WAV recordings of your switch tones to μLaw files; refer to the SoX section in Appendix A for more information.

Notice that the file extension (.ulaw) is not specified. Asterisk will search for the best audio file for the call and automatically select that one. If there is only one file with that name (excluding the file extension), then you can be assured it will be the ulaw file.

Hopefully all the audio files you need are on your Asterisk switch by now. Once you've modified your files (and copied them over to the server if you modified them locally), be sure to enter dialplan reload at the Asterisk CLI prompt. Take care that you don't see a WARNING message when you hit Enter; if you do, debug the error before continuing.

If you end up modifying a lot of different config files for different modules, reload will reload everything in Asterisk. Generally, it's better to just reload the specific thing you need (like dialplan) since reloading everything will take a little while (by this, we mean up to 10 seconds, rather than instantly).

Finally, some (few) things may require a restart: core restart now. Once Asterisk exits, enter asterisk -r to enter back into the Asterisk CLI.

ATA Digit Map

The ATA's default digit map (also frequently, but erroneously, called a dial plan) will not be sufficient. You will need to tweak the digit map/dial plan to the specifications of your system, but for a basic configuration, the following should be sufficient:




{ 0|[2-9]11|11[2-9]|660|95[89]|10[2-9]xx|100xx|101xxxx|[2-9][2-9]xxxxx|1[2-9][2-9]xxxxx}

The above dial plan can be explained as follows:

  • 0, for the operator
  • 11N and N11 codes
  • 660, 958, and 959
  • 10xxx dial-around codes
  • 101xxxx dial-around codes
  • NNX-XXXX dialing for regular 7-digit numbers
  • 1-NNX-XXXX dialing for 1+ dialing (via 2600 Hz trunks)

You may also wish to get dial tone from Asterisk (recommended). That can be accomplished through the use of hotline dialing. For instance, you may wish to automatically connect to extension 20, which will go to a dial tone in Asterisk:


If you want to a lock a particular phone into hotline dialing a certain extension, the first example is one way to go. For regular usage, the second method might be more suitable. In fact, using the second method, you can control the hotline behavior within Asterisk, which is more robust and manageable.

The above example is for Linksys-style digit maps. Grandstream uses can configure the off-hook auto dial number (with a timeout of 0) on the appropriate page. If you'd rather hear city dial tone when you pick up your phone as opposed to the precise dial tone generated by your ATA, you can have your ATA auto-dial your city dial tone DISA. Most DISAs are NNX-1111, meaning the station number 1111 in each active exchange is usually a DISA. In this documentation, we will pretend we are setting up the 555 (KLondike5) exchange. It would follow that your DISA number would be 555-1111 (or KL5-1111). You could, instead of mirroring your Asterisk dial plan on your ATA and having to coordinate the two, have your ATA auto-dial your DISA. The bonus of this is you don't need to worry about updating your ATA dial plan if you make a change to your Asterisk routing. You also get to hear city dial tone (woo hoo!).

If you do this, you should configure a short code on your system corresponding to a non-billing number, like 20, for instance, go to the dial tone in Asterisk. This way, billing is not invoked for the call. Now, whenever you lift your handset, your ATA will connect to your Asterisk system in a split second and you'll be hit with city dial tone immediately!


You may find it useful to automatically have voicemail notifications or voicemail messages themselves emailed to you. Asterisk integrates with the system mailer, so if you do not have one configured, you will need to install and configure one. This guide will cover the installation and configuration of Postfix on Debian Linux.

  1. apt-get update
    apt-get upgrade
    apt-get install mailutils
    apt-get install postfix
  2. Choose the default "Internet Site" when prompted.
  3. Enter your domain name for the system mail name.
  4. sed -i 's/inet_interfaces = all/inet_interfaces = loopback-only/g' /etc/postfix/
  5. sed -i 's/mydestination = $myhostname, /mydestination = $myhostname./g' /etc/postfix/
  6. sed -i 's/myhostname = YOURSERVERNAME/myhostname =' /etc/postfix/ ← you may want to manually make this edit using the nano text editor.
  7. systemctl restart postfix

To enable TLS, something like this should be in your /etc/postfix/

# TLS parameters
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

At this point, there are two main options from which to choose. You could use SMTP to send emails using another account (i.e. a Gmail, Hotmail, Yahoo, etc. account), which may be ideal if you already have an account hosted elsewhere you want to use to send messages. Or, you could have messages be sent directly, in which case you will want to ensure that there is an SPF record for the domain in question pointing to your server. Otherwise, sent emails will likely go straight to spam. We recommend using a subdomain specifically for your Asterisk server if you go this route. Each domain or subdomain may have one SPF record. Set up Let's Encrypt on the server if you haven't already done this for hosting a web server, so that email transmission is encrypted.

Trying to work with mail and sendmail in Linux is usually harder than performing brain surgery, so if you get stressed out easily, Linux email configuration and testing is NOT for you. This is because Linux mail programs are not designed for sending mail to the Internet, but sending mail between users on the same system. You will need to do a fair bit of work to fix this or all of your mail will get classified as spam.

To send a test email, run something like this: echo "Test" | mail -s "Test Email" -a "From:" Or, you can use this method.

At this point, your messages may be going to spam. Wouldn't it be nice if you knew why? Fortunately, such services exist. Try a few and see what you can improve. Another good one is

One improvement beyond TLS and SPF will be to set up DKIM with Postfix.

Check the error log if things aren't working right: tail -50 /var/log/mail.log.

Finally, in Asterisk's voicemail.conf, make sure that it is configured to send voicemail notifications (and attachments as well, if you prefer) by email and that you have the send from address set properly.


If you need assistance, there are several ways to get support.

The first (and recommended) way is to post to the PhreakNet Mailing List. Somebody else may have experienced your issue and may be able to provide tips or a solution.

A second option, more suited for specific issues, is to contact the Business Office for assistance. A representative will ask you for a CLI trace. If you do this, make sure you have timestamps = yes and verbose = 3 set in Asterisk.

The Business Office is happy to assist with Asterisk issues if needed. However, a detailed trace is needed in order to assist with dialplan issues. For dialplan troubleshooting, please use PhreakScript to perform a CLI trace as follows:

phreaknet update
phreaknet trace

If you use PhreakScript's trace feature, you do not need to open the CLI yourself and copy and paste the CLI output. This is handled automatically.

Follow the instructions displayed by the wizard. At the end, a URL to a paste of the CLI trace will be displayed. Then, submit a ticket to the Business Office (choose PhreakNet for Asterisk dialplan related issues, NOT Asterisk!). By default, pastes are only live for 24 hours before they are deleted. You can extend the duration online at You can also configure redactions here.

If your issue is TLS related in any way (and even if it's not), packet captures are likely useful. You'll want to do these from the server, e.g.:

tshark -f "not port 22 and host" -i any -w test1.pcap

Assuming you have tshark installed (apt-get install tshark), this will take a packet capture of anything not on port 22 from or to (change this to your home IP address, or wherever you're trying to troubleshoot). This will narrow down the pcap hopefully to only the minimum needed to troubleshoot, as otherwise you will be logging thousands of packets per second. Once done, hit CTRL+C and then copy the pcap file over to your PC where you can use Wireshark to open it up graphically and inspect it.

If Asterisk is crashing for whatever reason, you need to get a backtrace (use phreaknet backtrace to help with this).

It's also possible that Asterisk isn't crashing but the system is running out of memory and simply killing the process. If Asterisk is terminated mysteriously, run dmesg and see if that's the case. If it is, you will likely want to add some swap to your system (which PhreakScript can also help with).

Note: New to Asterisk? No problem — we're here to help! Simply call the business office at 811 and we'll help you get set up.

DAHDI Troubleshooting

DAHDI can be more difficult to debug issues with compared to Asterisk issues, since there is a complex interaction of hardware, drivers, the kernel, and Asterisk.

Sometimes you'll encounter an issue where the system just doesn't seem to "see" your DAHDI card.

For instance, dahdi_cfg -vvvvvvvv will return an error about a missing span, and your card won't show up if you try using dahdi_tool.

Usually doing modprobe on the kernel module for the card will bring it up, e.g. modprobe wcte13xp.

You may need to do this each reboot (or automate it properly).

IRQ Issues

Make sure your DAHDI cards are on their own, dedicated IRQ.

I once had a system where if you made a three-way call, and God forbid over an IAX2 trunk and out of the system, call quality would go straight to hell. It literally sounded like there was 80-90% packet loss. It was the complete opposite of what DAHDI should be: pristine, perfect audio — on the contrary, lots of this:

[Oct 23 20:08:52] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 3 instead
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:52] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:52] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 2
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead
[Oct 23 20:08:53] DEBUG[24501][C-00000028]: chan_dahdi.c:9047 my_dahdi_write: Write returned -1 (Unknown error 500) on channel 1
[Oct 23 20:08:53] DEBUG[24499][C-00000028]: res_timing_timerfd.c:167 timerfd_timer_ack: Expected to acknowledge 1 ticks but got 2 instead

The usual tips and tricks for DAHDI troubleshooting didn't bring anything up: no IRQ misses, dahdi_test passed with flying colors, and so forth.

Here's what the IRQs looked like on that machine (lspci -b -vv for even more info):

# lspci -vb | grep -B 3 -A 5 "IRQ 11"

00:1a.0 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #4 (rev 02) (prog-if 00 [UHCI])
        Subsystem: Super Micro Computer Inc 82801I (ICH9 Family) USB UHCI Controller
        Flags: bus master, medium devsel, latency 0, IRQ 11
        I/O ports at cc00
        Capabilities: [50] PCI Advanced Features
        Kernel driver in use: uhci_hcd
        Kernel modules: uhci_hcd

        Kernel driver in use: pcieport

00:1c.5 PCI bridge: Intel Corporation 82801I (ICH9 Family) PCI Express Port 6 (rev 02) (prog-if 00 [Normal decode])
        Flags: bus master, fast devsel, latency 0, IRQ 11
        Bus: primary=00, secondary=03, subordinate=03, sec-latency=0
        I/O behind bridge: 0000e000-0000efff
        Memory behind bridge: fea00000-feafffff
        Prefetchable memory behind bridge: 0000000400400000-00000004005fffff
        Capabilities: [40] Express Root Port (Slot+), MSI 00

01:00.0 Network controller: Digium, Inc. Wildcard TE133 single-span T1/E1/J1 card (PCI Express) (rev 02)
        Physical Slot: 0
        Flags: bus master, fast devsel, latency 0, IRQ 11
        Memory at fe8e0000 (32-bit, non-prefetchable)
        Capabilities: [40] Power Management version 3
        Capabilities: [48] MSI: Enable+ Count=1/1 Maskable- 64bit+
        Capabilities: [58] Express Endpoint, MSI 00
        Kernel driver in use: wcte13xp
02:00.0 Ethernet controller: Intel Corporation 82574L Gigabit Network Connection
        Subsystem: Super Micro Computer Inc 82574L Gigabit Network Connection
        Physical Slot: 0-1
        Flags: bus master, fast devsel, latency 0, IRQ 11
        Memory at fe9e0000 (32-bit, non-prefetchable)
        I/O ports at dc00
        Memory at fe9dc000 (32-bit, non-prefetchable)
        Capabilities: [c8] Power Management version 2
        Capabilities: [d0] MSI: Enable- Count=1/1 Maskable- 64bit+

Yeah, you definitely don't want your T1 card sharing an IRQ with your Ethernet card.

Operator Routings

Inward operators function much as they do/did on the PSTN. While the network operator will be able to help with most dialing difficulties, a network operator may occasionally have need to request the assistance of a particular exchange's inward operator, in order to diagnose line difficulties or complete calls.

All node owners may appoint and allocate any destination as the inward operator for some particular numberspace that they own.

The exchange owner with the largest ownership share of an office code (NNX code) will be granted rights to the incumbent/primary routing of that code. Additional inward operators will be reachable by also including a routing code to bypass the main NNX and connect to a different exchange's inward operator.

Inward operators are not intended to be reachable by ordinary dialing procedures / regular subscribers. This means that local inwards on each exchange will be able to reach other inwards, through non-public dialing mechanisms, but hosted users, for example, would not be able to reach inwards.

There are semi-public and non-public routings used on the network. Semi-public routings are those that, in theory, anyone on the network with an Asterisk tandem could reach. Non-public routings are those restricted to access only by the local inward on that switch and a network operator. Other inwards would not be able to access these.

The routings are set up in a way to mirror how routing used to work.

Below are some examples of each:


**In most cases, the "+" above means that the sequence could be sent all at once or broken into multiple stages of keying (e.g. KP + NXX + ST. KP + 121 + ST *OR* simply KP + NXX 121 + ST).

As the examples above illustrate, the mechanism by which routing works is essentially quite recursive.

The "primary" NNX belongs to the first NNX-X office code is assigned, regardless of which thousand block it is. These NNX routings will work automatically — there is no need to set anything additional up. NXX-0XX routings (routing codes / terminating toll center codes) do require additional manual setup:

***Technically it does, but not in a way that is used for routing NNX-XXXX. The immediate route used to get there (NNX or NXX0XX) is present in the dialed IAX variable (on the PSTN, 0XX would go to a different switch, but if you had another inward hosted, you'd get both kinds and might want to differentiate here). This variable is not made use of by default, but if, say, you wanted to put up a different "Milliwatt tone" on one "switch" versus another (on the same server), or know which NXX was called when an inward station is rung, this would allow you to do that. Anyways, think of ${IAXVAR(dialed)} not as digits being sent to you but which "trunk group" a call came in on, allowing you to tell what exchange this is for, if that matters to you.
Also note that due to the way things are set up, supposing NNX is Tandem A, there is a difference between NNX-0XX routing to Tandem B as opposed to a hosted operator on Tandem A. Say NNX-0XX is supposed to go to inward "C". If NXX-0XX goes to Tandem B, things work as one would expect. NNX-0XX+121 will reach Tandem B's inward operator, as will NNX-0XX-121. If NNX-0XX goes to Tandem A, it will not be initially discernible from a routing directly to NNX on Tandem A (or any of its other NNX codes, for the matter, if any). Thus, NNX-0XX+121 will actually ring Tandem A's main inward operator, since NNX-0XX and NNX actually route to the same server. (Technically, the node operator could make some changes to get around this). However, NXX+0XX-121 would likely work as intended (as would NXX-0XX+0XX-121 for that matter, and possibly NXX+0XX+121, depending on how trunks are configured). A note is made about this in phreaknet-inward-semipublic in the boilerplate code. The point is, there is a small technical difference in how routings work when "two-stage" keying is used as opposed to sending them all at once, and if weird things happen with the latter, two-stage keying will probably give the intended route.

For all practical purposes on a network of this size, 121, 131, 141, 151, and 161 at most exchanges will likely all go to the same thing. However, some exchange operators may choose to break things out further, and this enables them to do so.

The basic idea is that, starting from the beginning, NNXs and 0XXs will route around the network. A 1XX code is required to do anything "useful", such as ringing an inward station, for example.


The examples above assume that NNX-XXXX is actually a subscriber line on that switch. If it isn't, the operation will fail. Thus, 555 + 127 + 231-2368 would succeed only if the routing codes 555 and 231 went to the same exact place, in which case it doesn't matter which one is used for the first three digits.

***As you can see, there is a bit of an art here to routing and using these codes, as it truly is dialing "instructions" that may differ depending on what point A is, what point B is, and how you want to get from point A to point B. There are many ways to do things and slight nuances to routing around. Complex routings are probably not very useful, but can be helpful if needed for testing. You could try to tandem stack through operator trunks like in the 60s, but it would probably be more exciting to key NXX + 121 + ST, have an inward connect you to another inward, and then keep repeating them, building a stack through real operator equipment (e.g. cordboards, etc.). Simply keying a long route is unlikely to get you much besides tons of packet loss.

Routing around the network to a non-public destination is not possible, because "routing around" arrives in incoming trunk groups with semipublic permissions only. For instance, 555 + 231 + 121 would reach the 231 inward, but 555 + 231 + 127 + 231-1234 would *not* successfully busy line verify 231-1234 (directly doing 231 + 127 + 231-1234 would, but only if done by a network operator). The 231 local inward would just access an inward trunk on his/her switch and do 127 + 231-1234 directly, since s/he wouldn't need to route to his own switch first.

The sample extension *12 is used in the boilerplate code as a potential code for accessing inward trunks (a Touch-Tone phone is required). This is freely changeable per each exchange operator's discretion and has no impact on anything else, since it is only used for his local access to his own inward trunk context.


The "billing" functionality is purely for fun. The idea is to show you all the calls you make and what they would have cost back in the 1970s, purely for fun and educational purposes. The bill is for fun, and you are not actually charged anything. Nodes participate in this system voluntarily and with little overhead on their part. The code necessary to generate the necessary "toll tickets" is part of the basic boilerplate code. Once a user has made a call from his or her telephone, he or she can login to the member portal to view a mock bill containing calls made within the past 30 days.

If you have made any calls from any of the numbers listed here via a node participating in the billing system, your calls are automatically ticketed. A charge is issued for all completed calls based on the time and day when the call was made, the caller and the callee and the distance between them, and how the call was completed. This means calls during the day will cost more than calls in the evening or on weekends, calls completed to nodes further away will cost more than those to nearby nodes, and calls completed with the assistance of an operator will cost more than those completed without.

All of the rates on the network are based on those issued with the Bell System's 1977 issue of its "Phone Thing", which was distributed to many customers in the late 1970s. If you have one of the originals, it will accurately tell you the cost of all of your calls! Haven't one? Fear not! Print out the document linked above and assemble your own helpful dialing aid. You should be able to accurately predict the rate you will be charged based on the information in the "Phone Thing".

For the most part, the "Phone Thing" will accurately provide rate information. Designed for interstate phone calls on the PSTN, it applies whenever "long distance" calls occur — that is, calls between a node and any other. A few exceptions exist to this otherwise completely accurately simulated billing model exist: local calls are all charged a $0.05 fee and use 1 message unit in the process (to not charge message units, simply do not ticket the call for such calls). In some locales, such as New York City, local calls were not free and used message units; therefore, this is not explicitly unrealistic. Furthermore, all "international calls" to nodes outside of the U.S., as well as calls that involve any international call, are billed at a rate higher than that assessed when placing a continental call of the maximum distance possible. Specifically, international calls are $1 for the first minute and $0.50 for each subsequent minute, if dialed directly during "peak" (full rate) hours. As the "Phone Thing" dictates, a 35% of 60% discount applies during select hours. Calls to special numbers that contain fewer than 7 digits (e.g. 0, 11N, N11, and 958/959, as well as 660+) are not ticketed and are thus completely free. Such calls will not appear on your bill.

The time used when issuing charges is determined by the time zone of the caller. Make sure it's correct in the user portal when you log in. Additionally, make sure that all numbers from which you originate calls are in the White Pages. If you don't want to make listings public, you don't have to, but adding them here is important for many different components of network functionality.

If you have multiple numbers used to make calls, a bill is generated for each number.

As stated before, this entire operation is purely for fun. The rates reflect the value of the dollar during that era; therefore, accounting for inflation, their actual impact would be much greater. However, consider the charges on your billing statement to be an accurate reflection of the cost of your telephone calls — in the late 1970s!

ZEnith Numbers

ZEnith numbers are the predecessor to toll-free numbers. Calls to ZEnith numbers are basically "automatic collect" calls. These calls must be placed through the operator. You cannot dial them yourself. If you'd like a ZEnith number, simply fill out the ZEnith translation reservation form in the user portal.


PhreakNet has a set of standards to which all members are highly encouraged to adhere. Doing so ensures the network operates reliably and smoothly for everyone with an enjoyable experience for all.

New functionality and standards are driven by new and changing network goals and requirements. Such standards are ultimately determined by administration, with input solicited from the community with Requests For Comments during planning periods. Community input is always welcome and encouraged. Administration will direct standards per network requirements on behalf of and in response to the community.

Although this list is inevitably incomplete, here are a few important recommended standards:


PhreakNet provides a series of APIs for member usage. All APIs use the GET method. Overall usage is counted but PhreakNet API calls are not metered.

Routing & Verification

The routing and verification APIs are two sides of the same API. On an inbound call, it functions as a verification API. On an outbound call, it functions as a routing lookup API (with some additional out-verification options). Blacklist integration is also built into the API

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • number — Required. Number to call (outbound) or a 7D number on the upstream node (inbound verification) or empty
  • clid — Required. Caller ID number
  • cvs — Required. CVS (Caller Verification Status) code. 2-digit code or empty for first-leg calls.
  • flags — Optional. Trunking flags, e.g. MF, SF, operator trunk, StepNet, etc. Outbound only. No flags are used by default. Present flags include m (MF), s (SF), t for StepNet routing and o for (~inward) operator routing:
    • a — ACTS trunk (coin phones only)
    • c — Coin Zone trunk (coin phones only)
    • m — MF signaling
    • tStepNet routing. Route the call using StepNet instead of directly peer-to-peer, if possible. (Side note: The boilerplate code is configured to fallback to StepNet if a regular subscriber-dialed call fails the first time, for any reason. The reason for this is that sometimes, for strange bizarre reasons we haven't figured out, Node A cannot call Node C directly but most other nodes can reach Node C and Node A can reach most other nodes. Thus, Node A → Node B → Node C works even though Node A → Node C directly fails due to "congestion". Thus, tandeming through another node (e.g. a la StepNet) usually fixes this issue and can increase call completion rates.)
    • o — Operator routing. Changes the mode by enabling operator routing. This flag should never be used on anything public accessible by "subscribers"/users, local or from the outside. It is only intended to allow each exchange's local inward operator to access operator routings, in a separate isolated context.
    • s — SF signaling
  • nodevia — Required for Outbound. 7D number on the node doing the lookup, similar to number for inbound verification.
  • ani2 — Optional. Used for PhreakNet ANI2/OLI integration. Important for multi-party lines, Operator Number Identification, coin lines, etc.
  • cnam — Optional. Caller Name. Used for blacklisting checks.
  • threshold — Optional. Outbound: The blacklist score above which calls should be diverted to a tandem call restriction recording, to quarantine the caller. Inbound verification: The blacklist score above which you wish to reject inbound calls entirely. Recommended value is 2.4 (between 2 and 3). Default is 2.4. Inbound/outbound thresholds may be the same but need not be — if different, a higher outbound threshold is logical.
  • requestkey — Optional. Outbound only. If provided, requests a verification token and prepends it to the returned dial string. This value is prefixed with a ~ and should be stripped out and set into an IAX variable. This is integrated into the routing API so that nodes on slow Internet connections need only make one API request for any inbound or outbound request. This argument is assumed if the clid argument is empty.

The following arguments are applicable only to usage for verification purposes:

  • verify — If provided, changes the behavior of the API to use it for inbound verification rather than outbound routing.
  • ip — The IP address of the upstream node. Should never be empty.
  • token — The 32-character verification key provided by an upstream node. Provided generally only if inbound IP does not match outbound IP.

For outbound routings, the API returns the dial string routing for the call, optionally prefixed by a verification token if instructed, in which case ~ is used as a delimeter. The API is used even for configuring RSA calls, in order to obtain the IAX2 secret for the call.

For inbound verification, the API returns a 2-digit CVS (Caller Verification Status) code based on the arguments provided. If the CVS code 78 is returned, the call is blacklist per the desired threshold and the call should be immediately dropped. Otherwise, normal call progress may continue.

This API is not designed to be used manually within the dialplan. The provided wrappers part of the verification subroutines should be used to interface with this API (that is, used as a public API to this semipublic API). Otherwise, unexpected behavior may occur.


The RSA API allows you to download the RSA public key of another PhreakNet node.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • fqdn — Required. FQDN (fully qualified domain name) of node of which to retrieve public key.
  • nodevia — Required. 7-digit number on node of which to retrieve public key.
  • fetchall — Optional. Fetch all public keys (instead of by fqdn/nodevia).

The API returns the public key if one is available or a 404 response if not.

Directory Add

The Directory Availability API allows you to programatically add directory listings to the General Directory (not available for White Pages).

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • number — Required. 7-digit number.
  • visibility — Required. 0 for public, 1 for logged in only, 2 for unlisted, 3 for private.
  • category — Optional. Category, see Entries for details.
  • description — Required. Description of directory entry.

The API returns 1 if your rule is successfully updated and 0 if not.

Directory Availability

The Directory Availability API allows you to programatically update your directory availability rule.

The HTTP API endpoint is


The API returns 1 if your rule is successfully updated and 0 if not.


The blacklist API is a standalone API that can be used for screening calls. Blacklist functionality is part of the main routing API, so generally there isn't a need to use this API on its own, but if you wish to, you may (e.g. this would allow you to use it for other calls not arriving through PhreakNet).

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • number — Required. Caller ID number
  • cvs — Required. CVS code (Caller Verification Status). Provide the CVS code if possible, otherwise leave empty.
  • max — Optional. Use the max score instead of the average (default is use average).

The API returns a float score (e.g. 1.5) between 0 and 3, allowing you to make a determination based on the score and your needs. Scores correspond to:

  • 0 - Okay
  • 1 - Suspicious
  • 2 - Quarantine
  • 3 - Blacklist

Billing (Ticketing)

The billing API is used to insert billing tickets into the toll ticketing system. This is used to generate mock bills that you can view anytime in the member portal. There are no actual charges that are due — this is purely a fun and educational way to see what your calls would have cost in 1977. The charges are accurate based on the distance between members in the U.S. International calls are billed at a higher rate than the most expensive domestic call, but at an arbitrary value.

This API is called programatically in the dialplan automatically, never manually.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • ani — Required. Billing (Charge) number (usually the calling number, but in the case of a forwarded call, would be the RDNIS)
  • caller — Required. Caller Number
  • callee — Required. Called Number
  • duration — Required. The supervised (answered) duration of the call
  • start — A numerically filter YYYYmmDDHHMMSS string of when the call was answered (local server time acceptable)
  • type — The call type. Default is direct, for direct-dialed station to station calls. No other public values exist.

Other non-public parameters exist to handle coin calls. Third number billing uses the public parameters.

The API returns 0 if the ticket was processed and non-0 otherwise. Note that this does not necessarily constitute user error — free calls (like Operator) will not be ticketed. Likewise, if the answered duration is 0, the call will not be ticketed. This closely mirrors the way that electromechanical switches handled billing of toll calls. Calls that never supervised were never ticketed.


The CNAM API allows for free CNAM dips for PhreakNet numbers. It can be used in lieu of the ${CALLERID(name)} that might be set on an incoming call, particularly if you want to see who is actually calling you (as opposed to, say, "Living Room Phone"). This is only for PhreakNet CNAM dips (not PSTN CNAM, etc.).

The HTTP API endpoint is


The API returns an uppercase maximum 15-character CNAM string, to be universally compatible with CPE. The CNAM API uses the White Pages, Directory, and route table in that order of preference to generate CNAM data.

Time Zone

The time zone API allows retrieving the time zone associated with a PhreakNet number. The algorithm used is similar to that used for the CNAM API. This allows you to set up your own time-based service number on the network and provide callers with the correct information based on their individual time zones. This is the same API used by network time services.

The HTTP API endpoint is


The API returns a tz-format time zone or an empty result if no time zone could be determined, allowing you to handle the default case.


The ZIP code API allows you to retrieve information about any ZIP code.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • zipcode — Required. 5-digit U.S. ZIP code.
  • property — Required. One of city, state, county, areacode or timezone.

The API returns the requested property for the provided ZIP code.


The NPA API allows you to retrieve the geographic NPA of U.S. PhreakNet numbers.

The HTTP API endpoint is


The API returns the NPA for U.S. numbers.

Distance API

The Distance code API allows you to retrieve the distance, in miles, between two U.S. PhreakNet numbers.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • num1 — Required. 7-digit PhreakNet number.
  • num2 — Required. 7-digit PhreakNet number.

The API returns a float containing the precise distance between the two ZIP codes associated with the provided numbers. An empty response is returned for calls involving unallocated or international numbers.

Local Exchanges API

The Local API returns information about exchanges that are local to a PhreakNet caller.

The HTTP API endpoint is


The API returns a JSON response containing local NNX-X exchanges and their city, state, and country.

Is Local API

The Local API returns whether a call between two PhreakNet numbers is considered "local" or not for the purposes of billing.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • num1 — Required. 7-digit PhreakNet number.
  • num2 — Required. 7-digit PhreakNet number.

The API returns 1 if the call is local or free (no-charge) and 0 if it would be considered a toll call.

PSTN local call API

The PSTN local call tells you if a call between two NPA-NXXs is local or not.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • npanxx1 — Required. An NPA-NXX
  • npanxx2 — Required. Another NPA-NXX
  • property — Required. "islocal" is the only valid value.

The API returns N if a call is not local and Y if it is or otherwise.

Exists API

The existence API tells you if a subscriber-dialed number exists or not.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • number — Required. A PhreakNet number of which to check existence.

The API returns 1 if a number exists and 0 otherwise.

Rates API

The Rates API provides detailed rate information for a call between points A and B on the network and may be used to obtain current rate information for any type of call.

The HTTP API endpoint is


  • key — Required. InterLinked API key.
  • num1 — Required. 7-digit PhreakNet number.
  • num2 — Required. 7-digit PhreakNet number.

The API returns a JSON response with the following data:

  • distance — distance in miles from A to B if both A and B are in the U.S., null otherwise
  • city — city B
  • state — state B, if in the U.S., null o/w
  • zip — ZIP code A, if in the U.S., null o/w
  • zip2 — ZIP code B, if in the U.S., null o/w
  • time — Local time at A
  • ratestep — Rate Step, used by the Rate Quote System (ref. PhreakNet System Practices)
  • addmin — per-minute rate, in cents, after initial (base) period
  • Breakdown of rate information by call type: direct, station, person, collect
    • ratecode — current rate code corresponding to this call type
    • base — base rate for this call type
    • basemin — base minutes for this call type


The Telegram API allows you to retrieve a ticker stream of a telegram you have sent or received.

The HTTP API endpoint is


The API returns an ASCII ticker string for the telegram, suitable for feeding into a telegraph for transmission.

Cisco Phone Directory

The Cisco Phone Directory API allows you to import PhreakNet White Pages entries into a Cisco IP phone or compatible device's contacts.

The HTTP API endpoint is


The API returns a XML file containing the directory.

If there's an API that you'd like to see that doesn't exist yet, post to the list to discuss. Maybe others were looking for the same thing.

Standard Variables

PhreakNet uses a number of standardized dialplan and IAX variables. Their names and purposes are described below:

ScopeDialplan variableIAX variableNamePurposeSet ConditionsValid Values
GlobalinterlinkedkeyInterLinked API KeyContains tandem owner's InterLinked API keyN/A32-char API key
GlobalclliUnique switch CLLI for identification
GlobalmainphreaknetdisanodeviaMain DISAContain's a 7-digit # routing to that nodeN/A7-digit PhreakNet number on node
ChannelclidverifclidverifCaller Verification Status codeContains caller's originating network and verification statusVerification Subroutines2-digit CVS code (see Verification).
ChannelmlppdigitmlppCall PriorityContains call precedenceMLPP/AUTOVON routinesA, B, C, D, 0
ChannelssverstatssverstatSTIR/SHAKENContains validation/attestation dispositionA, B, C, D, E, F

By their very nature, global variables are static.

Variables will be added as they are needed for network features. All channel variables listed above should be rewritten if calls are regenerated in order to maintain proper channel state (if you are not doing complicated things in your dialplan using ConfBridge(), you probably don't need to worry about this).

Answer Supervision

All nodes should aim for 100% correct supervisory status at all times for network compliance purposes. This includes supervising on billable/chargeable calls and not supervising when it is not appropriate (e.g. test numbers, intercept recordings, operator services, etc.).

The most common way to avoid providing false supervision when it is not desired is by using the noanswer flag for Playback() when needed, as follows: same => n,Playback(myaudio,noanswer) — however, note that, to avoid audio passthrough issues, you should use a Progress() statement on calls where there is not immediate answer supervision to allow audio to pass through, e.g:

	exten => s,1,Progress() ; allow audio pass through before supervision
		same => n,Playback(myaudio,noanswer) ; play without supervising
		same => n,Answer() ; NOW answer the call
		same => n,Playback(myaudio2)
		same => n,Hangup()

In other words, you should always explicitly begin with a Progress() or an Answer(). We do not recommend relying on implicit supervisory functions of Asterisk applications like Playback().

If your dialplan is already littered with simple Playback commands, you can use the following RegEx in Notepad++'s find and replace feature to locate Playback() statements that don't have the noanswer switch: ^(?!.*noanswer).*Playback.*$

Our friend Read() also provides answer supervision by default. The n option can be used to disable this, e.g. Read(number,custom/file,7,n).

Also note that the revertive pulsing script provides false supervision (which is NEVER desired). You must manually patch this script as described in the Revertive Pulsing section. If you do not patch this script, you should not use it. Inpulsing and outpulsing should never supervise.

Finally, advanced dialplan code may involve the ConfBridge() application where it is desired not to supervise. You should use the answer_channel=no user option to prevent ConfBridge() from automatically answering.

Intercept Codes

PhreakNet has a number of "NP Intercept" codes. These codes are integrated into the route table and are not provided by end offices or Class 4 tandems. (Thus, these codes should not be used by member nodes, but members may be interested in what these codes mean and what codes exist.)

These codes are manually dialable in the 231-90XX range.

41Nonworking Operator RouteInvalid operator routing
53Authentication FailureCRTC/Route table authentication failed, call redirected to intercept
54Unsupported ProtocolNon-IAX2 protocol in use.
55Feature Group D Code ChangeYou know, "If you dialed a 5 digit code, it has changed…" - that one.
57Centralized InterceptNon-assigned number is dialed (number not in service)
58Permanent SignalNo number is dialed
59Invalid NumberInvalid lookup request
66Call QuarantinedMalicious call quarantine
77Inclement WeatherDestination tandem is unavailable due to weather-related conditions
88Facility TroubleDestination tandem is unavailable due to non-local facility trouble
89Non-Provisioned NumberAssigned but unprovisioned number dialed
90Invalid CodeInvalid NP Code

Member nodes may take advantage of network centralized intercept services if they do not wish to provide their own. To do so, perform a lookup for a non-assigned network number and note the format (57 + the dialed #). In the dialplan, adjust your pattern matching such that the dialed number is suffixed to the 57 extension prefix. If no number can be provided, 57 alone is sufficient.

ANI II Digits

ANI II (pronounced "A-N-I two or Annie two") digits provide information about the type of originating line, call routing, and class of service. They are used on the PSTN for a variety of purposes. They provide additional information to downstream nodes about the nature of the call being received.

For the sake of consistency, the ANI II codes used on the network mirror their PSTN counterparts where relevant or possible. However, the exact usage of many codes is slightly different.

00Normal subscriber line (implied)
01Multiparty line
02ANI failure
27Network controlled payphone
60TRS call (unrestricted)
61IMTS (Improved Mobile Telephone Service)
67TRS call (restricted)
70Non-network controlled payphone (e.g. COCOT, Charge-A-Call)

ANI2 if conveyed using ${CALLERID(ani2)}, which transmits natively over IAX2 trunks. No special variables are required.

Numbering Plan

A few words about the PhreakNet numbering plan:

For the most part, PhreakNet adheres to the traditional NANPA guidelines as they were originally set. This means that although NXX office codes exist today in the PSTN in the U.S., only NNX office codes are allowed on PhreakNet.

PhreakNet does not use country codes. Any node anywhere in the world on the network can be dialed with just 7 digits. This was an intentional decision made in order to make all nodes seem "local" and not "far away". Dialing more than 7 digits can discourage others from dialing a destination. This was a decision reached to better the network's experience for everyone.

Another dialing option is to dial 1+ for long-distance call. Dialing 1 + 7 digits will route your call over 2600 Hz-controlled trunks to the destination. The call goes to the same destination as if you had dialed it using 7 digits, but using a 2600-Hz controlled trunk, so now you can use your blue box on the call if you like.

In addition to that, PhreakNet has Feature Group B (950-WXXX) and Feature Group D dial-around codes (101XXXX). At this time, a public listing of these carrier access codes is not available.

Note that 10[02-9]XX represents former Feature Group D codes. These codes are part of the PhreakNet dial plan and will be routed to the appropriate intercept.

Any regular (NNX) calls dialable as 7 digits may also be dialed as 0+7D or 1+7D. 0+7D will complete the call through TSPS. 1+7D will complete the call over a 2600 Hz trunk which can be blue boxed.

976 exchange numbers are premium-rate numbers charged at a premium rate.

Furthermore, there are special numbers on PhreakNet, such as the following operator and directory services:

  • 0 — Operator — rings human operator, goes to automatic operator if unavailable
  • 411 — Directory Assistance — rings human operator, goes to automatic operator if unavailable
  • KL5-1212 — same as 411, KLondike5-1212 (555-1212) will ring Directory Assistance
  • 611 — Repair Service — rings human operator, goes to automatic operator if unavailable
  • 711 — TTY Relay
  • 811 — Business Office — rings the business office
  • 112 — "Long Distance Operator" — rings automatic operator directly
  • 113 — Information — rings automatic information operator directly
  • 114 — Repair Service — rings automatic repair agent directly
  • 117 — Telegrams
  • 118 — StepNet Trunk
  • 119 — Long Distance Trunk (Stacking)

Other directory services include:

  • POPCORN — POPCORN, a.k.a. 767-2676 will provide your local time
  • 211 — "Long Distance Operator" — rings automatic operator directly
  • 511 — Temperature & Location
  • 811 — Business Office
  • 911 — Volunteer Emergency Dispatch

Finally, network-wide test numbers include:

  • 311 — "Party Line"
  • 660 — Ring & Dial Test
  • 958 — ANAC
  • 959 — Ringback

To avoid confusion, we will use "Directory Assistance" to refer to 411 and 555-1212, which go first to a human operator and then to an automatic one if none is available and "Information" to refer exclusively to 113, which is our automated directory service.

Other special NXX/NXX codes include 101 (Feature Group D), 950 (Feature Group B), 976 (premium rate), 973 (mobile telephone service), and 555 (fictitious numbers).

Automatic Call Distributor Hack

Calls to 555-1212 route over a specially emulated "#5XB automatic call distributor" trunk. It works the way that Bill Acker described in 2014. You'll need a silver box, such as that found at PhreakNet in order to drop into maintenance mode on this trunk.

Standard Numbers

The following is a list of standardized station numbers (the last 4 digits of NNX-XXXX). If you use any of the following, you are highly encouraged to adhere to the following numbering conventions, assuming the "1" and "9" thousand blocks are allocated to you (if not, you may wish to consider allocating X9xx extensions as such instead). Being in compliance ensures that all users can expect a consistent experience across most exchanges. If your node is not in compliance, it is especially important any numbers similar to the following are published in the directory. If your node is in compliance, it is up to you whether you feel it necessary to publish these extensions in the network directory.

  • "0041" → Loop Around Side A
  • "0042" → Loop Around Side B
  • "1111" → Switch DISA
  • "9900" → Milliwatt
  • "9901" → Switch Verification
  • "9906" → Milliwatt Synchronization
  • "9922" → DTMF Test
  • "9931" → Echo Test
  • "9932" → Silent Termination
  • "9941" → Switch Telephone
  • "9945" → Milliwatt
  • "9950" → Business Office
  • "9960" → Milliwatt
  • "9970" → Busy
  • "9971" → Reorder
  • "9972" → Supervision Test
  • "9979" → Tone Sweep (Loop Checker/Loop Check Generator)
  • "9990" → Ringout
  • "9996" → Loop Around Side A
  • "9997" → Loop Around Side B

The 99XX numbers here are called "Official" numbers. For example, 9901 is "Official 01". The "Official" numbers you see above are factually accurate and reflect Evan Doorbell's experiences in the greater New York area. The only non-"Official" number above is 1111, for ease of access. NNX-9941 is also a modern, rather than historical, convention. While NNX-9990 was used historically as the number to reach the switch telephone, since 9941 is already reserved for that purpose, 9990 is generally reserved for ringout tests (i.e. ring, no answer). While NNX-9901 used to commonly go to an actual verification operator, today the number usually provides basic switch information; nevertheless, the name "switch verification" has stuck.

Since echo tests and DTMF tests were not common in the old network, we have settled on sensible numbers here which have no historical basis.

Dialplan Optimizations

In Asterisk, there are a lot of ways to do the same thing. Some ways are simpler than others, and newer functions allow for much simplification of dialplan code. These optimizations will result in some ever-slight performance increases, and most importantly will make your dialplan easier to read.
Here are some basic rules that can get you started. These rules can be plugged right into Notepad++ "Find in All Files" with "Regular expression" as the mode. Line 1 is the Find, Line 2 is the Replace. Or, to simply find matches using grep, you can use grep -P 'expression' *.conf (make sure to use single quotes, not double quotes, and either grep -P or grep -E, but not grep -e, should work.

The easiest to run all of these tests is to use PhreakScript. Simply type phreaknet validate and PhreakScript will test your dialplan for common problems. We recommend that you manually fix any problems that the tool may find, but the below regular expression would allow you to automate this step.

The following rule will identify invalid branching syntax caused by not surrounding functions in an expression (if this matches anything, you probably have syntax errors):


Here is another common syntax error, caused by forgetting the priority 1 before the application:

exten => ([*#\[\]a-zA-z0-9]*?),[^1]

This will find applications that are not closed, if they are followed by a comment:

=> (.*?),([1n]),(.*?)\(([^\)]*) ;
=> ($1),($2),($3)\(($4)\) ;

If you are still using the (now deprecated) pre-release LOOKUP function, you can replace it with EVAL_EXTEN:

The first find/replace will replace the special lookup priorities with regular extensions that EVAL_EXTEN uses. The second pair will replace the LOOKUP function calls with EVAL_EXTEN.

exten => (.*?),lookup,(.*)
exten => ($1),1,Return\(($2)\)


SUBROUTINES ONLY: Replace global channel variables with local channel variables, where appropriate!


NOTE: The first regex does not work with comments. The second regex above does not work for nested LOOKUPs. You should use this to help in migration, but do not blindly run a Find+Replace All Files without knowing what your codebase is like. These are more intended to allow you to quickly manually replace these.

Additionally, the regex above does not find LOOKUPs that are used with functions such as CUT.

Other structural optimizations, many made possible by (merged) PhreakNet patches to Asterisk:

If you have the full suite of unofficial PhreakNet patches, not yet merged into Asterisk, below are further optimizations/simplifications:

One general rule of thumb is if you have lots of System() calls in your dialplan, and in particular, lots of System(asterisk -rx 'command') calls, you are likely doing something wrong or at least inefficiently when there is a better way to do whatever you are trying to do.

Familiarize yourself with the list of applications and functions in Asterisk, especially as new stuff is added. The documentation will explain usage, but being aware of what functions exist to do what will generally help lead you down the right path.

Sometimes, a custom module/application/function in Asterisk makes sense. You can write one yourself, or contact us and see if we can write it for you.

Vintage Add-Ons

The following are useful supplements to your Asterisk system:

Pat Fleet Asterisk Sounds

To replace Asterisk's default Allison Smith voice prompts with "vintage" Pat Fleet prompts, run the following:

cd /var/lib/asterisk/sounds/en
rm -f *
rm -rf dictate
rm -rf digits
rm -rf followme
rm -rf letters
rm -rf phonetic
rm -rf silence
cd /
unzip -d /var/lib/asterisk/sounds/en
rm -f

Note that not all Allison Smith prompts are currently replicated as Pat Fleet prompts. To only overwrite the ones that weren't redone by Pat, don't run the delete commands.

If the code above doesn't work for any reason, just know that the files and folders in /var/lib/asterisk/sounds/en should be replaced with those in the ZIP file, with the exception of the custom subdirectory which contains your custom recordings!

Automatic Intercept System

  1. If you don't already have the Jane Barbe spliced AIS recordings, download them all from the C*NET Sounds Download page. You only need the files in the sounds folder itself, not any of its subfolders. Furthermore, you need only download the .wav files (not the .tgz files, for instance).
  2. Use SoX to convert all of the wav files to ulaw files (see the SoX section in Appendix A for more on this).
  3. Move all of the audio files beginning with a numeral (a number followed by either an n or a d to /var/lib/asterisk/sounds/en/custom/digits/. You may need to create this directory.
  4. Move the remaining files to /var/lib/asterisk/sounds/en/custom/ais/. You may need to create this directory.
  5. Add the following to your extensions.conf:
    [ais7] ; PhreakNet 20190212 NA
    exten => start,1,Set(LOCAL(num)=${ARG1})
    	same => n,Playback(custom/digits/${num:-7:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-6:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-5:1}d,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Playback(custom/digits/${num:-4:1}n,noanswer)
    	same => n,GotoIf($[${num:-3:3}=000]?thousand)
    	same => n,Playback(custom/digits/${num:-3:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-2:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-1:1}d,noanswer)
    	same => n,Return()
    	same => n(thousand),Playback(custom/ais/thousand,noanswer)
    	same => n,Return()
    [aisnis] ; PhreakNet 20190212 NA
    exten => start,1,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,Gosub(ais7,start,1(${ARG1}))
    	same => n,Playback(custom/ais/notinservice,noanswer)
    	same => n,Playback(custom/ais/pleasecheck,noanswer)
    	same => n,Playback(custom/ais/dialagain,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
    [aischanged] ; PhreakNet 20190212 NA ; This requires an argument with the new number
    exten => start,1,Set(LOCAL(num)=${ARG1})
    	same => n,Set(LOCAL(newnum)=${ARG2})
    	same => n,Verbose(AIS Number Changed -- "${num}")
    	same => n,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,Gosub(ais7,start,1(${num}))
    	same => n,Playback(custom/ais/hasbeenchanged,noanswer)
    	same => n,Playback(custom/ais/newnumberis,noanswer)
    	same => n,Gosub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
    [aisnpachanged] ; PhreakNet 20190212 NA ; This requires an argument with the new number
    exten => start,1,Set(LOCAL(oldnum)=${ARG1})
    	same => n,Set(LOCAL(newnum)=${ARG2})
    	same => n,Verbose(AIS Number Changed To Different NPA -- "${oldnum}")
    	same => n,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,GoToIf($[${oldnum}=nonum]?changed)
    	same => n,Gosub(ais7,start,1(${oldnum}))
    	same => n(changed),Playback(custom/ais/hasbeenchanged,noanswer)
    	same => n,Playback(custom/ais/newnumberis,noanswer)
    	same => n,GoToIf($[${LEN(${newnum})}=7]?seven)
    	same => n,Playback(custom/ais/area,noanswer)
    	same => n,Playback(custom/digits/${newnum:-10:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-9:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-8:1}d,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n(seven),Gosub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,Playback(custom/ais/hasbeenchanged,noanswer)
    	same => n,Playback(custom/ais/newnumberis,noanswer)
    	same => n,GoToIf($[${LEN(${newnum})}=7]?seven2)
    	same => n,Playback(custom/ais/area,noanswer)
    	same => n,Playback(custom/digits/${newnum:-10:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-9:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-8:1}d,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n(seven2),Gosub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote,noanswer)
    	same => n,Playback(custom/ais/dialagain,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
    [aisworking] ; PhreakNet 20190212 NA
    exten => start,1,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,Gosub(ais7,start,1(${ARG1}))
    	same => n,Playback(custom/ais/isworkingnum,noanswer)
    	same => n,Playback(custom/ais/willdialagainplease,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
  6. Now, going back to our starter dialplan code, let's modify it so that AIS is used on all calls to nonexistent extensions. Here is what we had initially:
    … all your 555 extensions here …
    include => invalidincoming
    Now, tweak your code so it is as follows:
    … all your 555 extensions here …
    include => KLondike5-unmatched
    exten => _XXXX,1,Gosub(aisnis,start,1(${OC1}${EXTEN:-4}))
    	same => n,Hangup()

Now, any time anyone calls a 555-XXXX number that doesn't exist, he will hear AIS instead of CBCAD!

An important technical footnote is necessary here: if you have multiple office codes, you will need a separate AIS "catch" context for each of them. The reason for this is that by the time a call hits [KLondike5], only the last 4 digits have been sent to that context, meaning the [KLondike5] context has no idea what office code was dialed. However, AIS expects the proper office code! Notice that ${OC1} is specified to add the office code back in before sending the call to AIS.

Consequently, if you had both the KLondike5 and KLondike6 exchanges, your code might look like this:

… all your 555 extensions here …
include => KLondike5-unmatched

exten => _XXXX,1,Gosub(aisnis,start,1(${OC1}${EXTEN:-4}))
	same => n,Hangup()

… all your 556 extensions here …
include => KLondike6-unmatched

exten => _XXXX,1,Gosub(aisnis,start,1(${OC2}${EXTEN:-4}))
	same => n,Hangup()


A prerequisite for creating an ANAC (automatic number announcement circuit) is having the Jane Barbe AIS audio files downloaded. If you already have AIS on your system, you're all set. If you don't, you don't need AIS to have an ANAC. Simply navigate to the C*NET Sounds Download page and download all the files in that directory beginning with a numeral (you will see two for each number, one ending with an "n", and the other ending with a "d"). You don't need the other audio files in this directory unless you'd also like to set AIS up on your node.

Download all these files and use SoX to convert them to ulaw files (see Appendix A for more on this). Then, place the ulaw files inside /var/lib/asterisk/sounds/en/custom/digits/ on your system. You may need to create the digits directory if you don't have AIS on your node (and hence never created that directory).

Then, copy and paste the following code into your dialplan (i.e. extensions.conf):

[anac] ; PhreakNet 20190212 NA
exten => start,1,Set(LOCAL(num)=${FILTER(0-9,${CALLERID(num)})})
	same => n,GotoIf($[${LEN(${num})}=7]?d7)
	same => n,GotoIf($[${LEN(${num})}=6]?d6)
	same => n,GotoIf($[${LEN(${num})}=5]?d5)
	same => n,GotoIf($[${LEN(${num})}=4]?d4)
	same => n,GotoIf($[${LEN(${num})}=3]?d3)
	same => n,GotoIf($[${LEN(${num})}=2]?d2)
	same => n,GotoIf($[${LEN(${num})}=1]?d1)
	same => n,GotoIf($[${LEN(${num})}=0]?d0)
	same => n(d7),Playback(custom/digits/${num:-7:1}n,noanswer)
	same => n(d6),Playback(custom/digits/${num:-6:1}n,noanswer)
	same => n(d5),Playback(custom/digits/${num:-5:1}d,noanswer)
	same => n,Playback(custom/digits/blank,noanswer)
	same => n(d4),Playback(custom/digits/${num:-4:1}n,noanswer)
	same => n(d3),Playback(custom/digits/${num:-3:1}n,noanswer)
	same => n(d2),Playback(custom/digits/${num:-2:1}n,noanswer)
	same => n(d1),Playback(custom/digits/${num:-1:1}d,noanswer)
	same => n(d0),Playback(beep,noanswer)
	same => n(done),Return

Now, to make your ANAC work, you'll need to put it on an extension. "958" is a network-wide ANAC, so you may want to put your ANAC on an extension ending in 958, like so:

exten => 9958,1,Gosub(anac,start,1)
	same => n,Hangup()

The reason we haven't included an Answer() statement here is because this is a test line, and test lines in the old network hardly ever supervised. Your ANAC should not supervise.

Speaking Clock

Doing time announcements is something relatively complex that Asterisk makes relatively simple. The code featured here is the same code that runs POPCORN network-wide. You can set-up a speaking clock or "time number" on your node that is hardcoded to use your timezone, if you wish. The following code makes this relatively easy to do:

[time] ; PhreakNet 20190212 NA ; ARG1 is number of times to announce the time, ARG2 is the timezone to use
exten => s,1,Set(NumLoops=0)
	same => n(start),Set(FutureTime=$[${EPOCH} + 8])
	same => n,Set(FutureTimeMod=$[${FutureTime} % 10])
	same => n,Set(FutureTime=$[${FutureTime} - ${FutureTimeMod} + 10])
	same => n,Gosub(sub-hr12format,s,1(${ARG2}))
	same => n,WaitUntil(${FutureTime})
	same => n(playbeep),Playback(beep)
	same => n,GotoIf($[${LEN(${ARG4})}=6]?skipwait)
	same => n,Wait(5)
	same => n(skipwait),Set(NumLoops=$[${NumLoops} + 1])
	same => n,GotoIf($[${NumLoops} < ${ARG1}]?start)
	same => n,GotoIf($[${LEN(${ARG3})}=4]?skipgoodbye)
	same => n,Playback(goodbye)
	same => n(skipgoodbye),Return()

exten => s,1,Answer
exten => s,n,Playback(at-tone-time-exactly)
exten => s,n,SayUnixTime(${FutureTime},${ARG1},IM 'vm-and' S 'seconds' p)
exten => s,n,Return()
exten => s,n,Playback(at-tone-time-exactly)
exten => s,n,SayUnixTime(${FutureTime},${ARG1},IM 'vm-and' S 'seconds' p)
exten => s,n,Return()

ARG1 is the number of times to announce the time. POPCORN is set to announce the time 100 times, so it goes on for quite a while. You'll probably want to set yours to a number between 5 and 10.

ARG2 is the timezone to use. The timezones are the "tz" format used in Unix. You can use this list of timezones and corresponding TZ database names to help you.

ARG3 and ARG4 are optional, and you will probably not need to use them. If ARG3 is set to skip, or any 4 characters for that matter, the speaking clock will not say "goodbye" before exiting. You may need to use ARG3 if you use this speaking clock as part of a broader context, rather than just a single, dedicated extension.

If ARG4 is set to nowait, or any 6 characters for that matter, then the speaking clock will not wait 5 seconds after the beep before entering another cycle. This is usually useful when you are calling [time] with ARG1 set to 1 (i.e. you only want the time announced once), and you wish to immediately exit the subroutine after the beep and continue with the dialplan code in the extension that called it. Note that if ARG1 is NOT 1 and you use ARG4 to disable the wait, it will not only disable the wait the last cycle, but every cycle, which is potentially undesirable. ARG4 is only meant to be set if ARG1 is equal to 1, but if you want to make the subroutine a bit more foolproof, you might modify it slightly so that it only calls GotoIf($[${LEN(${ARG4})}=6]?skipwait) if it is in its last cycle (in which case NumLoops will be 1 less than ARG1).

Now, to create a speaking clock for say, Chicago, or any city located in the Central Time Zone, that announces the time 7 times, all you need to do is add the following to your dialplan:

exten => 2006,1,Answer() same => n,Gosub(time,s,1(7,America/Chicago)) same => n,Hangup()

The speaking clock above is the traditional "at the tone, the time will be exactly X:YY and ZZ seconds, A.M. … beep.

If you'd like something a bit more customizable, check out the Asterisk TIM project on GitHub. At this time, the voice provided is Pat Simmons, but you can obtain and use your own custom audio prompts.

Airport Weather

For instructions on how to get airport-code-based weather reports in Asterisk, see this NerdVittles tutorial.

Currently, we don't have any of these types of services. The temperature readings provided at BE1-1200 and related numbers are powered by paid APIs.

Milliwatt Test Tone

In Asterisk, there are actually several ways you could implement a milliwatt (or 1004 Hz test) tone. Asterisk itself has a Milliwatt function:

exten => start,1,Wait(0.5)
	same => n,Milliwatt(m)
	same => n,Return

We wait 0.5 seconds before starting as milliwatt numbers usually offer a split second of silence before blasting your eardrums. The m option was added to Asterisk by PhreakNet to add the correct standard timing (1 second pause every 10 seconds).

By now, you probably know that to call this, you would need to call Gosub(mw,start,1(600)) from an extension in your dialplan. (You can tell this is a subroutine because it has the Return statement in it.)

However, a great many people choose to create their milliwatt test numbers themselves. Since a milliwatt test tone consists of pure frequencies, this is relatively easy. To make things interesting, we have here chosen to combine 1004 Hz and 1000 Hz for our milliwatt (the latter was the original frequency, according to Steph Kerman "A mW is inherently a pure tone, originally nominally 1000Hz but eventually these tones were offset by 4Hz to 404, 1004 and 2804 for compatibility with digital transmission"):

exten => start,1,Wait(0.5)
	same => n,PlayTones(1004/1000)
	same => n,Wait(${ARG1})
	same => n,Return

In this case, calling Gosub(mwtone,start,1(600)) would play a 1004 Hz tone for 600 seconds (or 10 minutes). Note that isn't quite the same as a Milliwatt tone test. You can tweak the timings to make it work like one, though.

Now, if you want to really kick it up a notch, you can increase the volume of the channel so the milliwatt tone comes out more loudly than it would otherwise:

exten => start,1,SET(VOLUME(TX)=8)
	same => n,Wait(0.5)
	same => n,PlayTones(1004/1000)
	same => n,Wait(${ARG1})
	same => n,SET(VOLUME(TX)=1)
	same => n,Return

A volume specification of about 8 matches PSTN milliwatt test numbers fairly closely. Of course, you can play around with this until you find a volume setting that works for you. Simply replace 8 with ${ARG2}, and then pass in a second argument containing the new volume. This way, you can easily create multiple extensions with different volume milliwatt tones.


Ringback is a fairly simple task that is quite involved in terms of dialplan code. Here is a simple way to do it:

exten => s,1,Progress
	same => n,PlayTones(600*120)
	same => n,Wait(60)
exten => h,1,Originate(Local/${CALLERID(num)}@to-phreaknet,app,Wait,1209600,,,ac(5559959)n(Ringback))

The PlayTones(600*120) is what will happen when this number is called. In this case, you will here 600 Hz modulated with 120 Hz, or old city dial tone (though it sounds a bit modern, given that it's generated by a computer rather than an electromechanical tone plant). This line is optional, but does provide an indication to the caller that something has happened; if you don't want the caller to hear anything, you could replace this with Wait(60).

The ringback itself doesn't actually happen until the caller hangs up (which is why the h extension is used here as well). When the caller hangs up, Asterisk creates a call file, dumps it in the spool directory, and it gets processed.

You will need to replace 5559959 here with the extension at which you create your ringback number. If you choose to have it be extension 9959 on your exchange, you don't need to change anything; otherwise, adjust the last 4 digits to the proper extension number. You should make sure that the number you use here matches the number used to call your ringback line. That way, if somebody calls your ringback line, the incoming Caller ID on the automatically generated call matches the number he dialed in the first place.

Note that [ringback] is not a subroutine. You will need to call it as exten => 9959,1,Goto(ringback,s,1).

Revertive Pulsing Script

First, as outlined in the "Pre-Requisites" section, you will need to have PHP installed in order to use the revertive pulsing script. Once PHP is installed, exit the Asterisk CLI to the main command line and run the following commands:

mv pulsar-agi.tar.gz /var/lib/asterisk
cd /var/lib/asterisk
tar xvfz pulsar-agi.tar.gz
chmod 777 /var/lib/asterisk/agi-bin/pulsar.agi

A file named pulsar.agi should now be located in /var/lib/asterisk/agi-bin/.

Audio files go in /var/lib/asterisk/sounds/pulsar/.

It's worth mentioning that by default this script DOES supervise, which is incorrect! If you modify the AGI file, you can add the noanswer flag to the Playback command so that revertive pulsing does NOT return premature answer supervision (e.g. turn Playback(file) into Playback(file,noanswer).

Do not use revertive pulsing if your revertive pulsing supervises (i.e. don't use revertive pulsing if you are not going to modify the AGI). Otherwise, it will supervise, which is against the supervision standards.

Now, add the following subroutine to your dialplan:

[revertive] ; PhreakNet 20190212 NA ; Revertive pulsing (requires PHP installation)
exten => start,1,Verbose(Revertive Pulse Generator: ${ARG1})
	same => n,AGI(pulsar.agi,${ARG1},${ARG2},${ARG3}) ; ARG2 can be set to 1 to indicate a "B-side" that adds 5 pulses to the second RP digit.
	same => n,Return() ; ARG1 should be a 4-digit extension and ARG3 must be one of the following: panel,1xb,5xb

ARG1 is the 4-digit extension to revertive pulse.

ARG2 can be set to 1 to indicate a "B-side" that adds 5 pulses to the second RP digit. Otherwise, set it to 0.

ARG3 should be the type of revertive pulsing to simulate. Your options are panel, 1xb, or 5xb. If you are simulating one of these switches by chance, then use that one. Each type of revertive pulsing sounds very distinctive, so make sure to get this one right!

As you can see, you might call the revertive pulser like this: Gosub(revertive,start,1(${EXTEN:-4},0,5xb)). This will simulate #5XB revertive pulsing.

Unlike MF digits, which you could use for both inpulsing and outpulsing, revertive pulsing is a signaling method you should only use for incoming calls. Let's revisit our incoming contexts:

include => local
include => long-distance
include => invalidincoming

include => local
include => invalidincoming

exten => _${OC1}XXXX,1,Goto(KLondike5,${EXTEN:-4:4},1)

You might modify [local] so it is as follows:

exten => _${OC1}XXXX,1,Gosub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,Goto(KLondike5,${EXTEN:-4:4},1)

Now, all callers dialing into whichever office code ${OC1} happens to be will hear Number 5 crossbar revertive inpulsing before the call connects!

The only catch here is, if you look at the [internal-users] context, you will see that you will also hear revertive pulsing on a call that is "local" to you, i.e. within the same exchange. At worst, this is undesired, at best, it is not really technically correct.

The solution is having a different context for incoming "long-distance" calls, like so:

include => local-intraoffice
include => long-distance
include => invalidincoming

include => local-interoffice
include => invalidincoming

exten => _${OC1}XXXX,1,Goto(KLondike5,${EXTEN:-4:4},1)

exten => _${OC1}XXXX,1,Gosub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,Goto(KLondike5,${EXTEN:-4:4},1)

Now, callers from other exchanges will hear revertive pulsing on all calls to KLondike5, but you will not. It is recommended that you adopt this approach.

If you'd like, in order to economize on code, you might adjust the last two contexts to the following:

exten => _${OC1}XXXX,1,Goto(KLondike5,${EXTEN:-4:4},1)

exten => _${OC1}XXXX,1,Gosub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,Goto(local-intraoffice,${EXTEN:-4:4},1)

What's the point of this, you might ask? We just made external callers hop through another step!

The advantage of structuring your code this way is you can simply define the inpulsing for each exchange in [local-interoffice], then send it to the other context. If you wanted a sound played on all incoming calls, you could include it in the first context only; in other words, you can cut down on future duplicated code.

The downside of using this approach is, as before, all internal and external callers are now sent to the same destination context. If you had a [KLondike5-internal] context, you might want to keep them separate, in order to allow different classes of callers access to different classes of extensions:

include => local-intraoffice
include => long-distance
include => invalidincoming

include => local-interoffice
include => invalidincoming

exten => _${OC1}XXXX,1,Goto(KLondike5-internal,${EXTEN:-4:4},1)

exten => _${OC1}XXXX,1,Gosub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,Goto(KLondike5,${EXTEN:-4:4},1)

exten => 6666,1,Goto(verysecretcontext,s,1)
include => KLondike5

; all public extensions here
include => invalidincoming ; if you have AIS, you should include KLondike5-unmatched instead

Now, if you call KL5-6666, you will end up going to [verysecretcontext], but callers from other exchanges will end up hearing an intercept message (or AIS) instead. Technically, this might not make sense, as the extension exists; it's just not accessible to external callers, and from Asterisk's perspective when routing an incoming call from another exchanges, the extension does not exist since it's in a different context to which it does not have access.

Part of Asterisk is structuring your dialplan code properly, and this is a skill that takes time to develop. Fortunately, you have an almost infinite amount of flexibility in terms of how you route your calls, so pick the approach that works best for you, knowing that you have the flexibility to tweak it should you ever need to.


Perhaps one of the most common and popular additions to any node is the MFer. Creating an MFer that would properly work with any number of digits proved to be a challenge, but in the end, it was surmounted. First, you may want to consider adding the MF tone specifications to the [us] context (if you are in the U.S.) in indications.conf, even though we will not be using them expressly:

mf1 = !700+900/55,!0/50
mf2 = !700+1100/55,!0/50
mf3 = !900+1100/55,!0/50
mf4 = !700+1300/55,!0/50
mf5 = !900+1300/55,!0/50
mf6 = !1100+1300/55,!0/50
mf7 = !700+1500/55,!0/50
mf8 = !900+1500/55,!0/50
mf9 = !1100+1500/55,!0/50
mf0 = !1300+1500/55,!0/50
kp = !1100+1700/100,!0/50
kp2 = !1300+1700/100,!0/50
st = !1500+1700/35,!0/50
st2 = !900+1700/35,!0/50

Depending on the specifications you use, the duration of each MF should be 50ms or 55ms, as it is in the case above. KP is 100ms and ST is 35ms, per Bell System specifications.

Here, we have used 50ms for each number. So it is! If you'd prefer to use 55ms tones, change the first 50 you see for MF1 through M0 to 55 (but not the second one!)

Now, add the following subroutines to your dialplan:

[signallookup] ; PhreakNet 20210208 NA 
exten => MF,1,Return("|","!0/${ARG3}","!700+900/${ARG1}|!0/${ARG2}","!700+1100/${ARG1}|!0/${ARG2}","!900+1100/${ARG1}|!0/${ARG2}","!700+1300/${ARG1}|!0/${ARG2}","!900+1300/${ARG1}|!0/${ARG2}","!1100+1300/${ARG1}|!0/${ARG2}","!700+1500/${ARG1}|!0/${ARG2}","!900+1500/${ARG1}|!0/${ARG2}","!1100+1500/${ARG1}|!0/${ARG2}","!1300+1500/${ARG1}|!0/${ARG2}","!1100+1700/${ARG5}|!0/${ARG2}","!1500+1700/${ARG4}|!0/${ARG2}","!900+1700/${ARG4}|!0/${ARG2}","!1300+1700/${ARG4}|!0/${ARG2}","!700+1700/${ARG4}|!0/${ARG2}")
exten => SF,1,Return("|",200,100,"!0/200","!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}") ; ARG1 = frequency (2600), ARG2 = on (60ms), ARG3 = off (40ms) / SFX = interdigit silence

[mfer] ; PhreakNet 20181212 NA/BJC, revised 20210208
exten => start,1,Set(number=${FILTER(0-9,${ARG1})}) ; ARG1 = digits to MF
	same => n,Gosub(signallookup,MF,1(50,50,500,35,100)) ; ARG1 = on, ARG2=off, ARG3=silence, ARG4=ST duration, ARG5=KP duration
	same => n,Set(ARRAY(digitsep,mfx,mf1,mf2,mf3,mf4,mf5,mf6,mf7,mf8,mf9,mf0,mfkp,mfst,mfstp,mfst2p,mfst3p)=${GOSUB_RETVAL}) ; R1, CCIT5: STP ~ code 12, ST2P ~ KP2, ST3P ~ code 11
	same => n,Set(c=0)
	same => n,Set(numdigits=${LEN(${ARG1})})
	same => n,Set(tonestring=${mfx}${digitsep}${mfkp}${digitsep})
	same => n,While($[${c}<${numdigits}])
	same => n,Set(mfnext=${mf${number:${c}:1}})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(c=${INC(c)})
	same => n,EndWhile
	same => n,Set(mfnext=${mfst})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(tonestring=${tonestring}${mfx})
	same => n,Set(toneduration=$[100*${LEN(${ARG1})}+1785])
	same => n,PlayTones(${FILTER(0-9\x21/|+,${tonestring})})
	same => n,Wait($[${toneduration}/1000])
	same => n,StopPlayTones()
	same => n,Return()

Below is an expanded MFer than do sequencing MF using different ST tone variations. It is not currently production ready, as longer sequences suffer from more and more of the end being cut off, so an additional timing buffer would need to be refined:

[mfer2] ; PhreakNet 20181212 NA/BJC, revised 20210208
exten => start,1,NoOp()
	same => n,Set(lon=50) ; 50
	same => n,Set(loff=50) ; 50
	same => n,Set(lsil=500) ; 500
	same => n,Set(lst=50) ; 35/50
	same => n,Set(lkp=100) ; 100
	same => n,Gosub(signallookup,MF,1(${lon},${loff},${lsil},${lst},${lkp})) ; ARG1 = on, ARG2=off, ARG3=silence, ARG4=ST duration, ARG5=KP duration
	same => n,Set(ARRAY(digitsep,mfx,mf1,mf2,mf3,mf4,mf5,mf6,mf7,mf8,mf9,mf0,mfkp,mfst,mfstp,mfst2p,mfst3p)=${GOSUB_RETVAL}) ; R1, CCIT5: STP ~ code 12, ST2P ~ KP2, ST3P ~ code 11
	same => n,Set(number=${FILTER(0-9,${ARG1})}) ; ARG1 = digits to MF
	same => n,Set(mfstart=${FILTER(0-9,${ARG2})}) ; ARG2 = optional ST delimiter
	same => n,Set(number2=${FILTER(0-9,${ARG3})}) ; ARG3 = optional 2nd group of digits to MF
	same => n,Set(cv=0)
	same => n,Set(numdigits=${LEN(${number})})
	same => n,Set(tonestring=${mfx}${digitsep}${mfkp}${digitsep})
	same => n,While($[${cv}<${numdigits}])
	same => n,Set(mfnext=${mf${number:${cv}:1}})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(cv=${INC(cv)})
	same => n,EndWhile()
	same => n,Set(mfnext=${mfst})
	same => n,ExecIf($["${mfstart}"="mfstp"]?Set(mfnext=${mfstp})) ; R1 STP, CCIT5: Code 12
	same => n,ExecIf($["${mfstart}"="mfst2p"]?Set(mfnext=${mfst2p})) ; R1 ST2P, CCIT5: KP2
	same => n,ExecIf($["${mfstart}"="mfst3p"]?Set(mfnext=${mfst3p})) ; R1 ST3P, CCIT5: Code 11
	same => n,Set(tonestring=${tonestring}${mfnext})
	;same => n,Set(tonestring=${digitsep}${tonestring}${mfx})
	same => n,Set(toneduration=$[$[${lon}+${loff}]*$[${LEN(${number})}+${LEN(${number2})}]+${lsil}+${lkp}+${loff}+${lst}+${loff}])
	same => n,GotoIf($[${LEN(${number2})}=0]?play)
	same => n,Set(cv=0)
	same => n,Set(numdigits=${LEN(${number2})})
	same => n,Set(tonestring=${tonestring}${digitsep}${mfkp}${digitsep})
	same => n,While($[${cv}<${numdigits}])
	same => n,Set(mfnext=${mf${number2:${cv}:1}})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(cv=${INC(cv)})
	same => n,EndWhile()
	same => n,Set(tonestring=${tonestring}${mfst})
	same => n,Set(toneduration=$[${toneduration}+${lkp}+${loff}+${lst}+${loff}]) ; add on aditional KP/ST
	same => n(play),Set(td=0)
	same => n,Set(UNSHIFT(tones,|)=${tonestring})
	same => n,While($["${SET(t=${SHIFT(tones,|)})}" != ""])
	same => n,Set(t=${FILTER(0-9,${CUT(t,/,2)})})
	same => n,Set(td=$[${td}+${t}])
	same => n,EndWhile()
	same => n,Set(toneduration=${td}) ; increased buffer needed for longer strings
	same => n,PlayTones(${FILTER(0-9\x21/|+,${tonestring})})
	same => n,Wait($[${toneduration}/1000])
	same => n,StopPlayTones()
	same => n,Return()

A note about the [mfer] subroutine is perhaps warranted here. The original approach to a dynamic MFer was using a subroutine to loop through each digit and generate an MF tone using the tone definitions in indications.conf. However, this approach, originally tested by Don Froula, does not work for one reason or another. Hence, the idea came about of not using indications.conf but instead dynamically building up a tone string and passing that into PlayTones() all at once. Don Froula, intrigued by the idea, admitted this could work, and Naveen and Brian immediately set upon implementing it. With Brian's help, the complex subroutine you see above now allows a number of any length to be MFed. The KP and ST tones are automatically prepended and appended to the tone string automatically; there is no need to specify this.

To call the MFer, all you need to do is pass in the digits to MF (not including KP and ST) as ARG1, like so: Gosub(mfer,start,1(5551212)).

The above will result in KP + 5 + 5 + 5 + 1 + 2 + 1 + 2 + ST being MFed.

You can now use this MFer on incoming and/or outgoing calls from your node. Perhaps you want to have MF inpulsing on calls interoffice calls to your second exchange:

exten => _${OC1}XXXX,1,Gosub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,Goto(KLondike5,${EXTEN:-4:4},1)
exten => _${OC2}XXXX,1,Gosub(mfer,start,1(${EXTEN}))
	same => n,Goto(KLondike6,${EXTEN:-4:4},1)

Or perhaps, you only have one exchange code but be really ambitious:

exten => _${OC1}XXXX,1,Gosub(mfer,start,1(${EXTEN}))
	same => n,Gosub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,Goto(KLondike5,${EXTEN:-4:4},1)

NOTE: All the above is obsolete and has been superseded by the native SendMF() application in Asterisk.

Now, external callers would hear MFing, followed by #5XB revertive pulsing.

You have the flexibility to choose from various inpulsing and outpulsing options for your node, but remember to stay "period-correct". You might hear MFing followed by revertive pulsing, for instance, but you would not be likely to hear revertive pulsing followed by MFing! Pay attention to the order of your inpulsing and outpulsing and make sure it stays realistic.

Note: Do not add any outpulsing directly in the dialphreaknet subroutine! Instead, create a separate outpulsing context that you call from within phreaknet-route.


If you do not already have the [signallookup] subroutine (the same one used for the MFer), you will need that:

[signallookup] ; PhreakNet 20210208 NA ; Bell System specs say 1st 50 should be 55? / MFX - silence / ST - Don has 35 here set to 50
exten => MF,1,Return("|","!0/${ARG3}","!700+900/${ARG1}|!0/${ARG2}","!700+1100/${ARG1}|!0/${ARG2}","!900+1100/${ARG1}|!0/${ARG2}","!700+1300/${ARG1}|!0/${ARG2}","!900+1300/${ARG1}|!0/${ARG2}","!1100+1300/${ARG1}|!0/${ARG2}","!700+1500/${ARG1}|!0/${ARG2}","!900+1500/${ARG1}|!0/${ARG2}","!1100+1500/${ARG1}|!0/${ARG2}","!1300+1500/${ARG1}|!0/${ARG2}","!1100+1700/100|!0/${ARG2}","!1500+1700/${ARG4}|!0/${ARG2}","!1300+1700/100|!0/${ARG2}","!900+1700/${ARG4}|!0/${ARG2}","!700+1700/${ARG4}|!0/${ARG2}")
exten => SF,1,Return("|",200,100,"!0/200","!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}") ; ARG1 = frequency (2600), ARG2 = on (60ms), ARG3 = off (40ms) / SFX = interdigit silence

Regardless, you will need the [sfer] subroutine:

[sfer] ; PhreakNet 20190218 NA, revised 20210208 ; ARG1 = digits to SF
exten => start,1,Set(number=${FILTER(0-9,${ARG1})}) ; ARG1 = digits to do something with
	same => n,Gosub(signallookup,SF,1(2600,60,40))
	same => n,Set(ARRAY(digitsep,sfinter,sfpulse,sfx,sf1,sf2,sf3,sf4,sf5,sf6,sf7,sf8,sf9,sf0)=${GOSUB_RETVAL})
	same => n,Set(c=0)
	same => n,Set(p=0)
	same => n,Set(numdigits=${LEN(${ARG1})})
	same => n,Set(tonestring=)
	same => n,While($[${c}<${numdigits}])
	same => n,Set(nextp=${number:${c}:1})
	same => n,ExecIf($["${nextp}"="0"]?Set(nextp=10))
	same => n,Set(p=$[${p}+${nextp}])
	same => n,Set(sfnext=${sf${number:${c}:1}})
	same => n,Set(tonestring=${tonestring}${sfnext}${digitsep})
	same => n,Set(tonestring=${tonestring}${sfx}${digitsep})
	same => n,Set(c=${INC(c)})
	same => n,EndWhile
	same => n,Set(toneduration=$[${sfpulse}*${p}+${sfinter}*${numdigits}])
	same => n,PlayTones(${FILTER(0-9\x21/|+,${tonestring})})
	same => n,Wait($[${toneduration}/1000])
	same => n,StopPlayTones()
	same => n,Return()

Dial Pulser

Below is the dialplan coded needed for the dial pulser:

[dialpulser] ; PhreakNet 20190212 NA
exten => start,1,Set(number=${ARG1}) ; ARG1 = digits to dial pulse
	same => n,Set(c=0)
	same => n,Set(numdigits=${LEN(${ARG1})})
	same => n,While($[${c}<${numdigits}])
	same => n,Playback(custom/dialpulses/dp${number:${c}:1},noanswer)
	same => n,Set(c=${INC(c)})
	same => n,EndWhile
	same => n,Return

ARG1 is simply what number to dial pulse. The number can be of any length (beside 0, obviously).

You will need audio recordings of each number being dial pulsed. You can easily extract dial pulses from various Evan Doorbell recordings. Prefix each digit with dp when you save the audio file, convert to ulaw, and then move to /var/lib/asterisk/sounds/en/custom/dialpulses/.

If you'd prefer to use our dial pulsing audio files, you may download them here.

Conference Bridges

Creating a conference bridge in Asterisk is relatively easy. Here, we will provide a template for creating a kind of so-called "party line" conference bridge, much like those on which Evan Doorbell spent hours talking and listening in the 1970s.

You will need to add the following to confbridge.conf:

sound_join=/var/lib/asterisk/sounds/en/custom/conf/confjoin  ; The sound played to everyone when someone enters the conference.
sound_leave=/var/lib/asterisk/sounds/en/custom/conf/confdis ; The sound played to everyone when someone leaves the conference.




You will need the "confjoin" and "confdis" audio files. You can use whatever audio files you like here; we recommend extracting actual "party line" conference joining and disconnect noises from an Evan Doorbell recording.

To have an extension go to a conference bridge, specify the following in your dialplan: ConfBridge(1,bridge,caller).

In this case, we have given the bridge an ID of 1. This number should be unique for each conference. You may choose to set this equal to the extension on which this conference is located.

You can have multiple extensions go to the same bridge but with different "experiences". For example, you could create a separate extension that joins the same bridge but also features a menu: ConfBridge(1,bridge,caller,volmenu). As you've probably figured out by now, ARG1 for ConfBridge is which bridge to use, ARG2 is which bridge profile to user, ARG3 is which user profile to use, and ARG4, which is optional, is which menu to use, if any. If you're trying to recreate a 1970s "party line", though, omit the menu.

It is important to note that conferences are not created or defined in confbridge.conf! Creating a second conference is as simple as making another extension with a ConfBridge call that uses something different for ARG1. For instance, ConfBridge(2111,bridge,caller) and ConfBridge(052,bridge,caller) would be two different conferences entirely, though they both use the same bridge and user profiles, so the experiences (i.e. sounds and functionality) will be the same.

Non-Supervising Conference Bridges

By default, the ConfBridge() application will supervise with no alternative, unlike the Playback() application which accepts a noanswer argument. Because ConfBridge is often the only way to get much functionality in Asterisk to work as desired, this is a significant limitation; however, modifying the source code can remove this behavior for better operation.

Patch Instructions:

  1. Navigate to the location where Asterisk was compiled, e.g. /usr/src/asterisk-18.whatever — go to the apps directory.
  2. Open app_confbridge.c for editing
  3. Perform a find and replace operation, replacing ast_answer(chan); with //ast_answer(chan);.
    Alternately, from the main Asterisk source directory (e.g. /usr/src/asterisk-18.whatever), run the following sed one-liner: sed -i 's/ast_answer(chan)/\/\/ast_answer(chan)/g' apps/app_confbridge.c
  4. Navigate to the root source folder, e.g. /usr/src/asterisk-18.whatever
  5. Type make and then make install to recompile Asterisk.
  6. Type service asterisk stop and then service asterisk start to restart Asterisk.

ConfBridge() will no longer automatically supervise a call. Once you've patched your Asterisk system, you must manually perform supervisory functions, as follows:

  • This will replicate the previous behavior, by manually answering and supervising the call.
    exten => s,1,Answer()
    	same => n,ConfBridge(mybridge)
    	same => n,Hangup()
  • This will allow two-way audio without answering. Progress allows passing of audio without supervising. The main use case here is for backend mixing of audio channels, such as for intercept, signaling, or operator services.
    exten => s,1,Progress()
    	same => n,ConfBridge(mybridge)
    	same => n,Hangup()
  • This will not perform any supervisory functions at all! The caller may/will not hear anything! For most cases, you should not use this at all:
    exten => s,1,ConfBridge(mybridge)
    	same => n,Hangup()

As of June 2021, Asterisk's app_confbridge now has the answer_channel user option. Set answer_channel = no, with Asterisk 18.5+/Asterisk 16.19+. No patching is required.

You must either use Progress() or Answer(). Failing to do so may result in no audio being passed at all.

Take care to revisit all uses of ConfBridge in your dialplan code to ensure you're using Progress or Answer before any calls to ConfBridge.

Answering Machines

Answering machines and voicemail are, in many ways, the antithesis of each other. Functionally, an answering machine has no place in Asterisk. However, it may be neat to simulate a physical telephone answering device (or answering machine) in Asterisk, to make it sound like somebody has reached an answering machine. This can be done with fairly trivial dialplan code.

Behind the scenes, the VoiceMail application can still be used. For the most part, the caller would never notice the difference. However, by default, Asterisk plays its own beep tone to the caller. Simply add the t option, specifying a custom beep tone to use, or leaving this argument empty to suppress the beep entirely.

This allows you to use your own outgoing answering machines without a modern Asterisk tone coming on at the end, and you can even use beep tones that are noticably analog and old sounding.

Note that if a user presses the # key while recording, Asterisk will say "thank you" and exit the VoiceMail application (playing auth-thankyou). This patch does not address that behavior. If users are allowed to review messages in voicemail.conf, an additional menu plays afterwards anyways, so it's not merely a question of suppressing an auth-thankyou somewhere, and it's probably best to just leave this behavior be.

Hook Flashing

As of April 2021, Asterisk itself has very minimal support for hook flashing. All Asterisk has historically done is propogate these events across channels from one to the other, if they are compatible. It has not had any ability to do anything with these events or expose them in any way (i.e. AMI).

There are two primary reasons for this:

  • In Asterisk, the "modern" and "hip" way to do certain tasks usually accessed through a hook flash is through special feature codes like *2 that are defined in features.conf
  • ATAs can handle hook flashes locally and perform a small but generally sufficient set of actions, such as Call Waiting, Three Way Calling, etc.

For building advanced functionality, it is essential to be able to handle hook flashes in Asterisk rather than let them be handled locally. In fact, ATAs have better support for this than they do handling these events locally. Grandstream ATAs, for example, will not connect to the switch at all if you flash to spawn a three-way call, even if you have off-hook auto-dial configured. Generally speaking, you are limited to the idiosyncrasies of different ATAs and many events are simply impossible to handle in the desired way. Sending flash events to Asterisk and performing bridge operations switch-side is vastly superior, though it requires a significant investment in configuring such a system (not for the light-hearted!) You will need to handle flash events as desired and implement all relevant functionality yourself, including writing your own Call Waiting from scratch. However, if you are designing an actual Class 5 switch, this is probably what you are trying to do anyways.

Regardless of the purpose, being able to handle flash events increases flexibility. The good news is that as of April 2021, patches to make hook flash events a first class citizen in Asterisk are now available! To add hook flash events to Asterisk, it's as easy as running the following:

cd /tmp
cd /usr/src/asterisk-18.3.0/
patch -u -b channels/chan_sip.c -i /tmp/sip.patch # add application/hook-flash support for SIP INFO flash events
patch -u -b main/file.c -i /tmp/file.patch # ignore flash events and don't throw an error
patch -u -b res/res_rtp_asterisk.c -i /tmp/ASTERISK-29373.diff # don't create duplicate flash events for RTP flashes
patch -u -b configs/samples/stasis.conf.sample /tmp/stasis_sample.patch # AMI flash event
patch -u -b include/asterisk/stasis_channels.h /tmp/stasis_channels.h.patch # AMI flash event
patch -u -b main/stasis_channels.c /tmp/stasis_channels.c.patch # AMI flash event
patch -u -b main/manager_channels.c /tmp/manager_channels.patch # AMI flash event
patch -u -b main/stasis.c /tmp/stasis.patch # AMI flash event
patch -u -b main/channel.c /tmp/channel.patch # stop throwing warning for flash event and handle flash events by triggering AMI event
make install
asterisk -rx "core restart now"

As of Asterisk 18.4 and 18.5, all of these patches are part of "official" Asterisk from Sangoma.

This will expose hook flashes as AMI events. You will need to handle them using an out of band AMI process, such as PAMI for PHP. You can then spawn dialplan execution to perform flash logic and do other operations as desired based on the state of the call(s).

As of April 2021, the patches to add AMI support have been submitted to Digium but are not yet part of the master Asterisk source code. Until the feature is available natively, you simply need to patch your copy and recompile. It should not take more than a minute or two to do.

Credits & Contributions: J. Colp, Digium for correcting improper RTP stream handling of flash events; N.A. for two issues which add application/hook-flash support to SIP, suppress errors in file.c, and adding full AMI (Asterisk Manager Interface) support for flash events.

Announcement Drums

It is relatively simple to set up shared (and even talkable) busy signals or simulated drum announcement machines with Asterisk. The below will cover a simple example of a drum announcement. There are multiple ways to do this and this is just an example.

You will likely want the non-supervising ConfBridge patch for this kind of stuff.

Typically, the idea with these is:

  • If the announcement is idle and it gets a call, "start" the drum up
  • All callers who reach the announcement before it gets to the beginning are all simultaneously cut into it at the same time
  • When no callers are left, the "machine" turns itself off. In software, this is even more important, because there's no legitimate reason to waste resources when nothing needs them

The example below cuts callers into an announcement for one announcement cycle and then sends them to another destination.

The example below supervises when callers are cut into the bridge. For intercepts, this may not be desirable. Instead of the initial Dial(), you could use MusicOnHold() directly and then StopMusicOnHold() instead of Answer() in the [drum]. With the patch to ConfBridge, callers will then be cut into a bridge, and the call still will not have supervised. All depends on how you want to set things up.

The example below is similar to the code that powers Centralized Intercept on PhreakNet. POPCORN (767-2676) on PhreakNet also uses similar "drum technology". Callers are cut in at the same time and stay on for 4 full cycles of the time from when they entered. Then, they're dropped to a "post-time conference" for about 10 seconds, then disconnected. This is an example of a more elaborate system.

exten => _X!,1,Dial(Local/${EXTEN}@drum,,m(ringback)g) ; drum announcement
	same => n,Goto(somewhere-else,s,1)

exten => _X!,1,Set(GROUP(drumintercept)=1)
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Set(CONFBRIDGE(user,wait_marked)=yes)
	same => n,Set(CONFBRIDGE(user,end_marked)=yes)
	same => n,Set(CONFBRIDGE(user,dtmf_passthrough)=no)
	same => n,Set(CONFBRIDGE(user,startmuted)=yes)
	same => n,Set(CONFBRIDGE(user,dsp_drop_silence)=yes)
	same => n,GotoIf($[${GROUP_COUNT(1@drumintercept)}=1]?startannouncement:waitloop)
	same => n(startannouncement),Originate(Local/1@drum-announcement,exten,announcement-access,1,,a)
	same => n,Wait(0.1)
	same => n(waitloop),GotoIf($["${CONFBRIDGE_INFO(parties,drumintercept1)}"="0"]?startannouncement) ; if nobody is in the conference, start it up
	same => n,WaitForCondition(#,#[#{GROUP_COUNT(1@interceptcutin)}>0])
	same => n(bridge),Answer()
	same => n,ConfBridge(drumintercept1,silentbridge,incogtalkeroptimized)
	same => n,Set(GROUP(drumintercept)=)
	same => n,Hangup()

exten => 1,1,Set(CONFBRIDGE(user,marked)=yes)
	same => n,Set(CDR_PROP(disable)=1)
	same => n,ConfBridge(drumintercept1,silentbridge,incogtalkeroptimized)
	same => n,Hangup()

exten => 1,1,Answer()
	same => n,Set(CDR_PROP(disable)=1)
	same => n,ConfBridge(drumintercept1,silentbridge,incoglistener)
	same => n,Hangup()

exten => 1,1,Answer()
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Wait(${RAND(2,3)})
	same => n,Set(GROUP(interceptcutin)=1)
	same => n,Wait(0.85)
	same => n,Set(GROUP(interceptcutin)=)
	same => n,Set(VOLUME(TX)=2)
	same => n,Playback(custom/intercept-announcement,noanswer)
	same => n,Wait(3)
	same => n,System(asterisk -rx "confbridge kick drumintercept1 all") ; you could kicked all the unmarked users here, instead of all the users
	same => n,Hangup() ; hang up, and kick out everyone presently in the bridge

Here is an example of a more featured intercept with configurable options, which does not supervise (provided that the answer_channel = no user option is present, either added at runtime or in the user template used):

	same => n,Gosub(intercept-drum,s,1(sample,ringback,1,0,0)) ; announcement
	same => n,Hangup()

exten => sample,lookup,custom/myintercept

; a dial is not used, which means this works without supervising! Actually ringing the special op. will mean that we need FRAME_DROP.
[intercept-drum] ; ARG1 = intercept, ARG2 = ringback MOH class, ARG3 = # of iterations (0 = forever)
				; ARG4 = 0 = wait for next iteration to cut in, 1 = cut in immediately, ARG5 = talkable intercept: 0 = no, 1 = yes
exten => s,1,Progress()
	same => n,StartMusicOnHold(${ARG2})
	same => n,Set(CONFBRIDGE(user,template)=incogtalkeroptimized)
	same => n,Set(CONFBRIDGE(user,wait_marked)=yes)
	same => n,Set(CONFBRIDGE(user,end_marked)=yes)
	same => n,Set(CONFBRIDGE(user,dtmf_passthrough)=no)
	same => n,ExecIf($["${ARG5}"!="1"]?Set(CONFBRIDGE(user,startmuted)=yes))
	same => n,Set(CONFBRIDGE(user,dsp_drop_silence)=yes)
	; if this seems backwards, it is. But we want variables set on the control channel, not the bridge channel.
	same => n,ExecIf($[${GROUP_COUNT(${ARG1}@interceptdrumon)}=0]?Originate(Local/${ARG1}@intercept-drum-announce,exten,intercept-drum-announcement,${ARG1},1,,av(anycut=${ARG4})))
	same => n,Set(GROUP(interceptdrumlisten)=${ARG1})
	same => n,Wait(0.05) ; wait for the machine to "start up"...
	same => n,Set(c=0)
	same => n,WaitForCondition(#,#[#{GROUP_COUNT(${ARG1}@interceptcutin)}>0],,0.2)
	same => n,StopMusicOnHold() ; ConfBridge marked user wait is useless. It still adds you to the bridge in a janky way.
	same => n,While($["${ARG3}"="0"|${INC(c)}<=${ARG3}])
	same => n,ConfBridge(interceptdrum${ARG1},silentbridge)
	same => n,EndWhile()
	same => n,Set(GROUP(interceptdrumlisten)=)
	same => n,Return()

exten => _[0-9A-Za-z].,1,Set(CDR_PROP(disable)=1)
	same => n,ExecIf($[${GROUP_COUNT(${EXTEN}@interceptdrumon)}>0]?Hangup)
	same => n,Set(GROUP(interceptdrumon)=${EXTEN})
	same => n,Answer()
	same => n,Wait(${RAND(1,3)}) ; takes a moment to "start up"
	same => n(cutin),Set(GROUP(interceptcutin)=${EXTEN})
	same => n,Wait(0.5) ; cut in waiting callers. In theory, we could put everyone in a "waiting" ConfBridge, but that's probably even more CPU.
	same => n,ExecIf($["${anycut}"!="1"]?Set(GROUP(interceptcutin)=)) ; now, nobody else can join until the next loop, unless that's permitted.
	same => n,Playback(${LOOKUP(${EXTEN}@intercept-drum-announcements)})
	same => n,Wait(0.4)
	same => n,ConfKick(interceptdrum${EXTEN},participants) ; kick all current listeners to the intercept if it's 1x only.
	same => n,Wait(0.4)
	same => n,GotoIf($[${GROUP_COUNT(${EXTEN}@interceptdrumlisten)}>0]?cutin) ; callers are waiting for this intercept, cut them in
	same => n,Hangup()

exten => _[0-9A-Za-z].,1,Set(CDR_PROP(disable)=1)
	same => n,Set(CONFBRIDGE(user,template)=incogtalkeroptimized)
	same => n,Set(CONFBRIDGE(user,marked)=yes)
	same => n,Answer()
	same => n,ConfBridge(interceptdrum${EXTEN},silentbridge)
	same => n,Hangup()

exten => _[0-9A-Za-z].,1,Set(CDR_PROP(disable)=1)
	same => n,Answer()
	same => n,ConfBridge(interceptdrum${EXTEN},silentbridge,incoglistener)
	same => n,Hangup()


One would think that ChanSpy() could be used for this kind of thing, but ChanSpy is, for these purposes, almost completely useless, due to a long persistent limitation with audiohooks in Asterisk.

You will need to use ConfBridge. The non-supervising ConfBridge patches are a must-have pre-req. Once you have that, the mechanism is as follows:

  • "Regenerate" the call. We use "regenerate" as pseudo-jargon to precisely mean: generating, via a call file (not via Originate(), due to the significant limitations of this application), a near-identical copy of the current call. This means all channel variables, and of course, the Caller ID, name, and presentation, as well as anything else you want to carry forward henceforth.
  • Drop the original call into a ConfBridge(). Not supervised, so there won't be any answer supervision sent to the caller. The regenerated call is what's actually going to go to the destination, and this will enter the bridge as well.
  • Use the G option for Dial() to fork the call on answer supervision. Once the called party answers, split the caller and callee paths. We don't need the caller path anymore, so get rid of it (Hangup), though be careful with the h extension! Next, set a flag to indicate the call has been answered (e.g. by using a global variable or a DB entry), then drop the original call from the ConfBridge. In the dialplan, it will check whether the call has been answered using that aforeset flag, and if it has, it will Answer() the call and then re-enter the bridge. (Basically, we kick the caller out of the bridge to provide him with answer supervision, then drop him back in.)
  • All in the meanwhile, since we now have a ConfBridge, you can mix whatever audio you want into the bridge. For crosstalk, there's a fairly logical way to do this. You could have unique crosstalk per call or shared crosstalk across all calls, and source this crosstalk from audio files, other conference bridges, etc.

There is more involved, of course. Each call effectively has a unique conference bridge. Hangup handling using the h extension is an art that must be carefully mastered. Because the calling party is no longer directly connected to the called party, you will need to manually check for different cases and kick the other party if one has left. And if you forget to do this in all the required places, then you may find that you have "ghost" bridges with nobody in them piling up as calls take place. When a call is completed, the conference bridge should get destroyed.

Because there are some people interested in this concept, a subset of the crosstalk/carrier code used on the main tandem is provided below for reference.

;;; Carrier Code:

exten => in,1,Set(CDR_PROP(disable)=1)
	same => n,Gosub(carrierlookup,${ARG1},1)
	same => n,ExecIf($[${GOSUB_RETVAL}=0]?Return)
	same => n,Set(LOCAL(channel)=${FILTER(0-9,${UNIQUEID})})
	same => n,Set(__callednumber=${ARG1}) ; do we really need this?
	same => n,ExecIf($[${GROUP_COUNT(1@carriercalls)}=0]?DBdeltree(carrierchannels))
	same => n,Set(GROUP(carriercalls)=1)
	same => n,Set(GROUP(carriercrosstalk)=1)
	same => n,Set(DB(carrierchannels/${channel})=${CHANNEL})
	same => n,ExecIf($[${GROUP_COUNT(0@carriercrosstalk)}=0]?Originate(Local/s@carriercrosstalk,app,Wait,9999999,,,av(CDR_PROP(disable)=1^CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})))
	same => n,Set(randl=BCDE) ; Subtle L-Carrier track options (we'll randomly pick one)
	same => n,Set(LOCAL(carriergroup)=${randl:$[${RAND(1,${LEN(${randl})})}-1]:1})
	same => n,Set(SHARED(carriergroup)=${carriergroup})
	same => n,Originate(Local/${carriergroup}@carriernoise,exten,carrierinject,${channel},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Main L-Carrier
	same => n,Originate(Local/monitor@carriercrosstalk,exten,carrierinject,${channel},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Crosstalk
	same => n,Return(${channel})
exten => out,1,Set(CDR_PROP(disable)=1)
	same => n,Gosub(carrierlookup,${ARG1},1)
	same => n,ExecIf($[${GOSUB_RETVAL}=0]?Return)
	same => n,Set(LOCAL(channel)=${FILTER(0-9,${UNIQUEID})})
	same => n,Set(__callednumber=${ARG1}) ; do we really need this?
	same => n,ExecIf($[${GROUP_COUNT(1@carriercalls)}=0]?DBdeltree(carrierchannels))
	same => n,Set(GROUP(carriercalls)=1)
	same => n,Set(GROUP(carriercrosstalk)=1)
	same => n,Set(DB(carrierchannels/${channel})=${CHANNEL})
	same => n,ExecIf($[${GROUP_COUNT(0@carriercrosstalk)}=0]?Originate(Local/s@carriercrosstalk,app,Wait,9999999,,,av(CDR_PROP(disable)=1^CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})))
	same => n,Set(randl=BCDE) ; Subtle L-Carrier track options (we'll randomly pick one)
	same => n,Set(LOCAL(carriergroup)=${randl:$[${RAND(1,${LEN(${randl})})}-1]:1})
	same => n,Set(SHARED(carriergroup)=${carriergroup})
	same => n,Originate(Local/${channel}@carrierinject,exten,carriernoise,${carriergroup},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Main L-Carrier
	same => n,Originate(Local/monitor@carriercrosstalk,exten,carrierinject,${channel},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Crosstalk
	same => n,Return(${channel})

[regeneratecall] ; ARG1 = local channel ID, ARG2 = local channel context, ARG3 = context, ARG4 = in/out, ARG5 = called #
exten => s,1,Originate(Local/${ARG1}@${ARG2},exten,${ARG3},${ARG5},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)}^CALLERID(pres)=${CALLERID(pres)}^calldirection=${ARG4}^__channel=${ARG1}^__autovonchan=${autovonchan}^__clidverif=${clidverif}^__mlppdigit=${mlppdigit}^__forcesecurechannel=${forcesecurechannel}^__dtlocation=${dtlocation}))
	same => n,Return(${ARG1})

exten => _X!,1,Set(channel=${EXTEN}) ; if caller hangs up, end call
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Progress()
	same => n,Set(carriergroup=${SHARED(carriergroup,${DB(carrierchannels/${channel})})})
	same => n,ExecIf($["${carriergroup}"!="B"]?Set(VOLUME(TX)=-${RAND(7,9)}))
	same => n,ExecIf($["${carriergroup}"!="B"]?Playback(custom/switch/connect/carrierconnect1,noanswer))
	same => n,ExecIf($["${carriergroup}"!="B"]?Set(VOLUME(TX)=1))
	same => n,ConfBridge(channel${channel},silentbridge,incogtalker) ; this MUST NOT ANSWER the call
	same => n,ExecIf($["${SHARED(carrieranswered,${DB(carrierchannels/${channel})})}"="1"]?Answer():Hangup) ; prevent answering the call if it wasn't... actually answered!
	same => n,Set(SHARED(carrieranswered,${DB(carrierchannels/${channel})})=2) ; this is the ${channel} channel so we don't need to explicitly specify it with SHARED
	same => n,ConfBridge(channel${channel},silentbridge,incogtalker)
	same => n,Hangup() ; used was kicked (other party hung up)
exten => h,1,ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfKick(channel${channel},all))

exten => _X!,1,Set(CDR_PROP(disable)=1)
	same => n,Answer() ; bridge audio to caller immediately
	same => n,Wait(${RAND(1,2)})
	same => n,Set(carriergroup=${SHARED(carriergroup,${DB(carrierchannels/${channel})})})
	same => n,Wait(0.5)
	same => n,ExecIf($["${calldirection}"="out"]?Wait(${RAND(1,${RAND(2,5)})})) ; give time for the L-carrier "B-loop" sound to play
	same => n,ExecIf($["${calldirection}"="in"]?Dial(Local/${EXTEN}@intraoffice-connect,,G(split))) ; channel variables will carry
	same => n,ExecIf($["${calldirection}"="out"]?Dial(Local/${EXTEN}@to-phreaknet,,G(split))) ; channel variables will carry
	same => n,ConfKick(channel${channel},all) ; if called party hangs up without answering, end call
	same => n(caller),Goto(carrieronward-hangup,s,1) ; so h extension doesn't execute
	same => n(split),Goto(caller) ; disconnect caller to get rid of an unnecessary channel
	same => n(callee),Set(SHARED(carrieranswered,${DB(carrierchannels/${channel})})=1)
	same => n,ConfKick(channel${channel},${DB(carrierchannels/${channel})}) ; kick the caller from the bridge, which will Answer() the call and then drop him back in
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Goto(carrierbridgecallee,${channel},1)
exten => h,1,ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfKick(channel${channel},all)) ; if calling party hangs up, end call

exten => s,1,Hangup()

exten => _X!,1,Set(channel=${EXTEN})
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Originate(Local/answer@carriernoise,exten,carrierinject,${channel},1,,a)
	same => n(wait),WaitForCondition(#,#["#{SHARED(carrieranswered,#{DB(carrierchannels/#{channel})})}"="2"],1) ; prevents race conditions, where if the ConfKick before carrieronward,caller has NOT YET taken effect, we don't kill the call (that call must finish before this executes)
	same => n(bridge),ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfBridge(channel${channel},silentbridge,incogtalker))
	same => n,Hangup()
exten => h,1,ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfKick(channel${channel},all)) ; if called party hangs up, end call

exten => _X!,1,Set(CDR_PROP(disable)=1)
	same => n,Answer()
	same => n,ConfBridge(channel${EXTEN},silentbridge,incogtalker)
	same => n,Hangup()

Echo Test

The standard way to create an echo test in Asterisk would be as follows:

exten => 9931,1,Goto(echo,s,1)

exten => s,1,Answer() ; Echo test with instructions
	same => n,Playback(demo-echotest)
	same => n,Echo()
	same => n,Playback(demo-echodone)
	same => n,Playback(vm-goodbye)
	same => n,Hangup()

We have created a separate context just for the echo test here, as this is generally good practice. Instead of duplicating code should you wish to add another echo test on a different extension, you can simply copy the GoTo statement and send it to the same context.

Note that the use of the s extension is completely arbitrary and could be anything. In macros, you are required to use the s extension, but subroutines often use the start extension instead (but could use anything, including s, as you may see elsewhere in this documentation).

However, this may not be what you want on your node. If you call extension 9931, by dialing, say, 555-9931, you won't immediately be dumped into the echo test. Most Asterisk systems with echo tests use the code above, which plays an introductory message about the echo test first and a concluding message afterward (if you press # as opposed to simply hanging up). If you simply want your extension to go to an echo test (perhaps more realistic if you are trying to stick with the "vintage" theme, you can replace the above [echo] context with the following:

exten => s,1,Progress() ; Echo test with no instructions
	same => n,Echo()
	same => n,Hangup()

Now, you'll immediately enter the echo test when you call extension 9931, and you won't hear anything if you press "#" before it terminates the call. Here, we use Progress instead of Answer, since an echo test is a test number, so it should not provide answer supervision.

Silent Termination

Creating a silent termination line is incredibly simple:

exten => start,1,Wait(86400)
	same => n,Return

What this subroutine actually does, when called, is wait for 86,400 seconds (or 24 hours), before returning. This way, a caller can't call your silent termination line and stay there forever.

To create a silent termination extension, simply add the following subroutine call to an extension:

exten => 9932,1,Gosub(silentterm,start,1)
	same => n,Hangup

Note that this silent termination line will not "supe" (it won't provide answer supervision). You can add an Answer statement first to change that:

exten => 9932,1,Answer
	same => n,Gosub(silentterm,start,1)
	same => n,Hangup

Live Feeds

Asterisk allows you to use the mpg123 utility to play MP3 streams from the Internet. Although it is not so popular anymore, in the mid to late-20th century, it was quite common to be able to listen to live music feeds and radio stations using certain access numbers, allowing people to listen to their favorite stations and news from wherever they were (provided they were willing to foot the long-distance charges). In Asterisk, there are two approaches you can take to play an MP3 stream.

The first approach is to define the stream in musiconhold.conf. For our example here, we will create a stream for the KZSU radio station which, as of this writing, features, among other things, a 1950s 701B SxS electromechanical switch in its office. We will also create a stream for KPFA, the flagship station of the Pacifica Radio Network:

application = /usr/bin/mpg123 -q -s --mono -r 8000 -f 8192 -b 0

application = /usr/bin/mpg123 -q -s --mono -r 8000 -f 8192 -b 0

Now, you will need to add the appropriate dialplan code to access the Music on Hold streams you defined:

exten => 5078,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MusicOnHold(KZSU)
	same => n,Hangup()
exten => 5732,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MusicOnHold(KPFA)
	same => n,Hangup()

The timeouts you see simply prevent the extension from being tied up forever (even though multiple people can access the stream). After 3600 seconds, or 1 hour, the call will automatically be disconnected. This can help ensure callers don't dial into a stream and let it run for perpetuity.

Now by dialing extension 5078 (or KZSU if dialing by letters) or dialing extension 5732 (or KPFA), you can listen to those respective radio stations.

*Technically, Z is not on the telephone dial, so we have substituted 0 for Z, since the two were equated for ZEnith dialing in many areas.

The downside of this approach is that it doesn't scale well. Every MP3 stream you define in musiconhold.conf is running all the time, 24/7/365. While we don't think this makes much sense (and perhaps constitutes a bug, unintentional or otherwise) in Asterisk, that's how it is. We learned this the hard way when we defined around 100 MP3 streams in musiconhold.conf. As soon we entered moh reload at the Asterisk CLI, the server immediately ground almost to a halt. Needless to say, things were not pleasant. We recommend using this approach only if you have a few streams; if you are looking to have more than 5 or 10 streams, a better approach is to not use musiconhold.conf at all but instead to create the stream on demand directly in the dialplan, like so:

exten => 5078,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MP3Player(
	same => n,Hangup()
exten => 5732,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MP3Player(
	same => n,Hangup()

As you can see, musiconhold.conf is not used at all. The advantage of this is that streams are not running when they are not in use. Resources will only be used to play the stream when there is an active call connected to these extensions.

So, which approach should you use? The downside of the latter approach is that a separate stream instance is created for each caller, even if they are all accessing the same stream. Thus, the approach you implement will depend largely on how and by whom your streams will be used. If you expect a lot of callers will be calling a small number of streams, go with the first method. If you want to create a large number of streams and don't expect much traffic to any single stream in particular, you should consider the second method, since you will not dedicating unnecessary resources to playing the streams when they are not in use.

Finally, worth mentioning is that since the latter approach creates the stream on-demand, rather than accessing an "already-on" stream, there is an additional delay when the stream is called of about 1 second. The delay is not really noticable unless you are listening for it, but you may want to keep this in mind if you need a stream to available the split instant you call it. If this is the case for you, then you will want to define the stream in musiconhold.conf so that it is always playing and immediately accessible. That being said, the discrepancy is only about 1 second, so it is not really worth doing this unless you have a good reason. The former approach is recommended if you only have a few streams and expect high traffic to them, but we personally prefer the latter approach as it allows you to have an unlimited number of streams that, provided they are generally low-traffic, will keep resource utilization to a minimum (i.e. none) when idle.

In other words, if streaming is a big part of what you do, but you don't have too many, define your streams in musiconhold.conf. Otherwise, don't; simply create the streams on demand.

Loop Arounds

Loop arounds are test lines that, while not as common in the PSTN today as they used to be, are still around if you know where to look (or rather, what to call). Of course, you can create your own loop arounds as well!

For those unfamiliar, a loop around is a pair of numbers, generally two consecutive numbers, which could be anything, used by telephone technicians. In the heyday of phreaking, they were prone to abuse by phreaks who would use them to anonymously talk to other people without having to give out their real phone number. If one person called one number and another called the corresponding number of the pair, they would be bridged together — free of charge.

In a loop around pair, while waiting for somebody to call the other line, one of the numbers functions as a milliwatt test while the other functions as a silent termination test. As soon as the other number is called, however, the two lines are immediately bridged. Either the low or high number, in theory, can be the tone or silent side, and it doesn't matter which line is called first or by whom.

Thanks to John Covert for the following code to create loop arounds or "loops" in Asterisk.

[looptest] ; ARG1 is the unique loopid; ARG2 is "tone" for the tone side and nothing for the silent side
exten => s,1,Wait(1)
	same => n,Answer()
	same => n,Set(loopid=${ARG1})
	same => n,Set(looptone=${ARG2})
	same => n,Set(GROUP(side)=looparound_${loopid}${ARG2}) ; only one caller per side of the loop
	same => n,NoOp(${SET(gc=${GROUP_COUNT(looparound_${loopid}${ARG2}@side)})})
	same => n,GotoIf($[${gc}>1]?busy)
	same => n,Set(GROUP(looparound)=looparound_${loopid}) ; This group is for who's first.  Both sides are in it.
	same => n,Goto(loop,s,1)
	same => n(busy),Set(GROUP()=)
	same => n,Goto(stepbusy,s,1) ; only one user on any side at a time

exten => s,1,NoOp(${SET(gc=${GROUP_COUNT(looparound_${loopid}@looparound)})})
	same => n,GotoIf($[${gc}=2]?bridge) ; First Caller waits, second caller bridges
	same => n,Set(loopdbdel=1) ; only this side deals with creating and deleting the database
	same => n,Set(DB(loop/${loopid})=${CHANNEL})
	same => n,ExecIf($["${looptone}" = "tone"]?Playtones(1004/10000,0/2000))
	same => n,Wait(360000) ; 100 hours max wait time for the other guy to show up.
	same => n,Goto(1)
	same => n(bridge),Set(loopchan=${DB(loop/${loopid})})
	same => n,GotoIf($["${loopchan}" = ""]?1) ; Other side hung up just as we arrived, so we're now the first side
	same => n,Bridge(${loopchan})
	same => n,Goto(1)
exten => h,1,ExecIf($["${loopdbdel}" != ""]?DBdel(loop/${loopid}))

A loop pair then might be created as follows:

exten => 0041,1,Gosub(looptest,s,1(${EXTEN:-4:3},tone))
	same => n,Hangup()
exten => 0042,1,Gosub(looptest,s,1(${EXTEN:-4:3}))
	same => n,Hangup()
exten => 9996,1,Gosub(looptest,s,1(${EXTEN:-4:3},tone))
	same => n,Hangup()
exten => 9997,1,Gosub(looptest,s,1(${EXTEN:-4:3}))
	same => n,Hangup()

If you have multiple exchanges, you might opt to adopt the following approach instead:

exten => _004[1-2],1,Goto(looparound,${OC1}${EXTEN},1)
exten => _999[6-7],1,Goto(looparound,${OC1}${EXTEN},1)

exten => _004[1-2],1,Goto(looparound,${OC2}${EXTEN},1)
exten => _999[6-7],1,Goto(looparound,${OC2}${EXTEN},1)

exten => _NNX0041,1,Gosub(looptest,s,1(${EXTEN:-7:6},tone))
	same => n,Hangup()
exten => _NNX0042,1,Gosub(looptest,s,1(${EXTEN:-7:6}))
	same => n,Hangup()
exten => _NNX9996,1,Gosub(looptest,s,1(${EXTEN:-7:6},tone))
	same => n,Hangup()
exten => _NNX9997,1,Gosub(looptest,s,1(${EXTEN:-7:6}))
	same => n,Hangup()

Adopt whichever approach makes sense for you. The important thing to note is that if you strip the very last digit of all of your loop numbers, none of them should be identical unless they are part of the same loop pair. What this means is that if you have 0041 and 0042 setup as a loop pair, you can't have 0048 and 0049 also setup as a loop pair, because "004" is used to identify the loop pair and you've now created two supposedly different "pairs" that are, not, in fact, going to work properly. As long as you don't create more than one loop around pair in the same 10s group, you'll be fine.

ChanSpy Verification

The following would allow you to "tap" any of your lines. Optionally, if you would also like to allow the network operator access to a node's local verification trunks for troubleshooting and test purposes (which is recommended in keeping with the theme of the network)..

There are several levels of "channel spying" in Asterisk. You can simple listen to the channel, you can whisper to one of the parties (the calling party/owner of the channel), and you can barge into the conversation and be heard by both parties (interrupt). You should use the "listen" option for busy-line verification; the "barge" option should be used for busy-line interrupt. To allow the PhreakNet operator to do busy-line verification and interrupt on lines on your node, please leave a message at the number above.

Here is the dialplan code needed to use ChanSpy, assuming the variable chan is at this point the name of the SIP device or channel on which to spy.

Listen only (Verification):

	same => n,ChanSpy(SIP/${chan},q)


	same => n,ChanSpy(SIP/${chan},qw)

Barge (Interrupt):

	same => n,ChanSpy(SIP/${chan},qB)

The 'q' option will not play a beep/announce the channel's name before tapping into the line (in all cases, this is audible by you only, not the spied-on channel). In conjunction with q and other options, you can use v(N) to change the volume where N is between -4 and 4. You can read further about ChanSpy on the VoIP Info site.

T1 Trunks

You can easily setup virtual T1 trunks in Asterisk. Here's how to set up the virtual NICs in Debian 9:

  1. Run the following commands from your terminal:
    sudo apt-get update
    sudo apt-get install build-essential
  2. Enable TUN/TAP on your machine. You may need to modprobe tun to load the tun Linux kernel module. If you are using a hosted VM, you may need to enable "tun" with the virtual machine tun enable switch (it may say "TUN/TAP ON").
  3. Download these 3 attached source code files to your Asterisk server: addr1.c | addr2.c | taptap-modified.c — you can also use wget like so:
  4. Navigate to the directory in which the above 3 files are located. Compile them with the following commands:
    gcc taptap-modified.c -o taptap
    gcc addr1.c -o addr1
    gcc addr2.c -o addr2

    You now have 3 executable binaries: taptap, addr1, and addr2:

    taptap creates two virtual NICs named "tun0" and tun1" with random MAC addresses, and sets up the virtual crossover cable buffers. You can see these along with the hardware NICs with ifconfig -a

    addr1 sets the tun0 random MAC address to 11:11:11:11:11:11

    addr2 sets the tun1 random MAC address to 22:22:22:22:22:22

  5. Now, move the binaries to sbin so they are in an executable path and change the permissions:
    mv /root/addr1 /sbin/
    mv /root/addr2 /sbin/
    mv /root/taptap /sbin/
    sudo chmod 755 /sbin/addr1
    sudo chmod 755 /sbin/addr2
    sudo chmod 755 /sbin/taptap
  6. taptap should be run at an elevated user priority of "-20" for best performance. Use the "nice" command to invoke in the background:
    nice -n -20 taptap&
  7. Then run addr1 and addr2 normally to change the MAC addresses of each interface. You can see the effects of addr1 and addr2 with ifconfig -a
  8. Then, the interfaces must be brought "up" with ifconfig tun0 up and ifconfig tun1 up. Then they are ready for use by ProjectMF. Invoking these needs to be done at VM startup in a script, before Asterisk and Dahdi are started. Of course, all this can be done in a startup script before Zaptel and Asterisk are started.

  9. Now, we need to get Asterisk to use the created interface:

  10. Next step


Contrary to what you may have read elsewhere on the web, you can setup virtual modems in Asterisk! Here, we will set up a virtual modem and use that to create a BBS.

  1. Enter the following in the terminal to install Telnet client and server, as well as other utilities you may need:
    sudo apt-get install telnetd -y
    sudo apt-get install telnet -y
    sudo apt-get install xinetd -y
    sudo apt-get install tcpd -y
    service xinetd restart
    sudo apt-get install lynx -y — text/line mode text browser
    sudo apt-get install less -y — navigation of large outputs
    sudo apt-get install libxml2-utils -y — HTML parser
    sudo apt-get install bc -y — Basic Calculator
    sudo apt-get install sshpass -y — SSH Auto-Password Passer
    sudo apt-get install lynx -y — SSH Auto-Password Passer
  2. If telnet does not work, or later stops working, follow the steps in these two StackOverflow answers:


    Here are the critical steps from the two answers linked above:

    1. Add telnet stream tcp nowait telnetd /usr/sbin/tcpd /usr/sbin/in.telnetd to /etc/inetd.conf.
    2. Make this your default in /etc/xinetd.conf:
      Simple configuration file for xinetd
      # Some defaults, and include /etc/xinetd.d/
      # Please note that you need a log_type line to be able to use log_on_success
      # and log_on_failure. The default is the following :
      # log_type = SYSLOG daemon info
      instances = 60
      log_type = SYSLOG authpriv
      log_on_success = HOST PID
      log_on_failure = HOST
      cps = 25 30
    3. sudo /etc/init.d/xinetd restart
    4. sudo apt-get install tcpd
  3. For this sample, we will be using username "com" and password "com". Wherever you see "com", replace with the info for your system. Enter the following in your terminal:
    nano /etc/
    Delete the text Debian GNU/Linux 9 and replace it with text like:
    Welcome to the BBS! Login with username "com" and password "com"
    Press CTRL+X to save, then Y to confirm, then press ENTER.
  4. Now, create the account "com" with password "com":
    sudo adduser -p $(openssl passwd -l com) com (the first "com" is the password, the second is the password)
  5. To change the password last type sudo passwd com and it will ask for the new password.

    To get the user ID of the user, type id -u com.

    To make this a passwordless account, first modify /etc/ssh/sshd_config and change #PermitEmptyPasswords no to PermitEmptyPasswords yes
    Then, pw usermod com -w none

  6. Now, create a blank shell script that will be used as the entry point for loopback Telnet sessions initiated from Asterisk:
    touch /home/com/
    usermod -s /home/com/ com
    chmod a+x /home/com/
    The second line changes the home directory of "com" to the shell script we created, locking the user's connection into that script. The last line sets the proper permissions. At this point, this shell script doesn't do anything; we'll take care of that once the other mechanics are set up properly.
  7. Alternate Instructions: adduser com; sudo chsh -s /home/com/ com

  8. The following lines remove the normal login information spew provided to users upon initial connection to the server. This does affect all users, but this provides a necessary improvement in realism:
    touch /home/com/.hushlogin
    rm -rf /etc/motd
    touch /etc/motd
  9. Allow certain system access to the controlled shell environment:

    chmod 777 -R /var/spool/asterisk chmod 777 -R /home/com/log
  10. Now the mechanism to initiate a Telnet session from Asterisk has been created. Now, we will install a softmodem in Asterisk that can connect to a shell script via the Telnet mechanisms we just set up:

    mv app_softmodem.c /usr/src/asterisk-13.24.0/apps/
    cd /user/src/asterisk-13.24.0/apps/
    make apps
    make install
    service asterisk restart
  11. Now, modify your dialplan to offer callers a way to connect to your softmodem. Here is the dialplan syntax:
    exten => btx,1,Answer()
    	same => n,Softmodem(host, port, options)
    	same => n,Hangup()
    Without any arguments the application acts as a V.23 modem and connects to a Telnet server (port 23) on localhost.
    Options are:
    	r(...): rx cutoff (dBi, float, default: -35)
    	t(...): tx power (dBi, float, default: -28)
    	v(...): modem version (default: V23):
    			V21        - 300/300 baud
    			V23        - 1200/75 baud
    			Bell103    - 300/300 baud
    			V22        - 1200/1200 baud
    			V22bis     - 2400/2400 baud
    	l or m: least or most significant bit first (default: m)
    	d(...): amount of data bits (5-8, default: 8)
    	s(...): amount of stop bits (1-2, default: 1)
    	u:      Send Ulm Relay Protocol header to Telnet server
    	n:      Send NULL-Byte to modem after carrier detection (Btx specific)
    The modem seems to work fine with VOIP as long as you use a codec like G.711 (alaw/ulaw). Please deactivate any echo cancellation you might use.
    In your dialplan, to use a "Bell 103" 300 baud modem, you might use the following:
    exten => 4221,1,Answer()
    	same => n,Softmodem(,23,v(Bell103)ld(8)s(1)un)
    	same => n,Hangup()
  12. Reload your dialplan by typing asterisk -r and then dialplan reload
  13. At this point, you will be able to make a call to your Asterisk softmodem, which will establish a loopback Telnet connection that will launch the shell script we created earlier. However, at this point it is still blank, so nothing will happen. From here on, you will need to setup everything else you need and launch it from this main shell script. You can use other kinds of scripts, like PHP scripts to read values from databases and perform external authentication using databases, as well. You could, for example, store usernames and passwords to access your system in a database to which you connect using PHP as opposed to keeping track of authentication information locally. Or, the database could be on the same server. Or, you could not use databases at all. From here, everything is called using shell scripts, so you have the freedom to set things up how you like.
    The tutorial for creating a softmodem ends here. You will need shell scripting experience to code up a BBS. You can call the one created in this example at 564-4221 on PhreakNet.


Asterisk actually has very good fax support built in (not including the fax stuff that Digium sells), so if you aren't using it, you're missing out.

Receiving faxes in Asterisk is easier than sending them, purely for logistical reasons. It's easy to have an incoming fax emailed to you. It's a bit more involved to receive documents in a compatible format and get them onto the system in a way that they can be sent.

Here are good starting resources for receiving and sending faxes:

To make incoming faxes behave like voicemails, you can call a System command in the hangup handler to have incoming faxes emailed to you as TIF files.

The link above discussing outgoing faxes discusses an approach for doing this. However, it may be more feasible and conveient to design a secure file upload page than to process incoming emails for fax delivery.

MF/ACTS Detectors

ACTS when used here is an abuse of notation. More properly, they are known as single-slot coin denomination tones and were used in ACTS, among other things.

In December 2019, Asterisk source files, centered around dsp.c, were successfully forked to add MF and ACTS detection support to Asterisk.

Credits: Dylan Cruz, Howard Harte, Joshua Stein, Naveen Albert

Detection support is not integrated, but rather a separate program that can be called at will when a detector is needed. In other words, if you need an ACTS detector, you can bring one into the call and then get rid of it, much like how ACTS detectors are used for PSTN (non-COCOT) payphones.

Thanks to MF and ACTS support, it is now possible to have Project MF in modern Asterisk!

ZIP Download:

The ZIP folder contains the following directories:

  • arm
  • x86

The _arm folder contains code for the ARM architecture (e.g. Raspberry Pi). The x86 folders are for x86/i386.

The contents of the appropriate subfolder of the detect folder should be extracted to /etc/asterisk/scripts/detect. You will need to choose the subfolders specific to your architecture. So, if you are running x86, you would extract detect/x86/mf to /etc/asterisk/scripts/detect/mf — don't preserve the x86 or arm in your file hierarchy — choose the architecture you need and place its subfolders right into /etc/asterisk/scripts/detect/

Note that the detector application is named mf for both the MF and ACTS detectors. Only the folder name distinguishes the two; the executable name should not be changed, and there is no extension.


ACTS tones are used for payphone coin signalling on modern non-COCOTs, succeeding the gong-style signalling used in 3-slot payphones. MF and 2600 are part of the multifrequency and singlefrequency signaling systems. DP is audible dial pulsing for real-time dial pulsing, which is generally not possible with VoIP connections.

There are three actual detectors that work separately:

  • /etc/asterisk/scripts/detect/mf/mf — MF and 2600 Hz detector
  • /etc/asterisk/scripts/detect/acts/acts — ACTS detector
  • /etc/asterisk/scripts/detect/dp/dp — Dial Pulse detector

The ACTS detector is the simplest to use. For each detected ACTS beep (each of which is worth 5 cents), it returns one $ (dollar-sign) symbol.

Likewise, the dial pulse detector will return one P for each dial pulse.

The MF/2600 detector is one detector that supports multifrequency, single frequency, and 2600 Hz (general) detection. For multifrequency 0 through 9, it returns 0 through 9. For KP, it returns * and for ST, it returns #. For every single instance of 2600, it returns one "S". Thus, one loud constant 2600 burst (such as a trunk reset) returns a single "S"; dial-pulsing 2600 Hz (i.e. single-frequency signaling) at 10pps or 20pps will produce one S per pulse, making it possible to detect single-frequency dialing in addition to single 2600 Hz tones.

Here is a simple subroutine that uses the ACTS detector (on x86/i386 architecture, but that can be changed), to listen for ACTS tones. Notice that it automatically calculates how many "$"s appeared (\x24 is the hex escape code for $).

[acts-read] ; NA 20200104 ; ARG1 = max silence, ARG2 = max duration, RETURNs # of ACTS beeps
exten => s,1,Set(ss=${STRFTIME(${EPOCH},,%s)})
	same => n,Set(file=/tmp/${UNIQUEID}-${ss}.wav)
	same => n,Record(${file},${ARG1},${ARG2},qx) ; q = quiet, x = ignore terminator keys
	same => n,Set(beeps=${SHELL(/etc/asterisk/scripts/detect/acts/acts ${file})})
	same => n,Set(beeps=${FILTER(\x24,${beeps})}) ; \x24 = hex for "$"
	same => n,Set(num=${LEN(${beeps})})
	same => n,NoOp(ACTS: ${beeps} -> ${num})
	same => n,Return(${num})

This detector is used on the payphone trunks. So if you're thinking of using this detector to program a payphone controller… it's already been done! Applications of the detectors have already been fairly rigorously exhausted for everyone's benefit, and we've made the applications that make use of these detectors available as well. If you would like to help improve these end-applications or have ideas, please contact the Business Office.

Cadence Plan

The Cadence Plan allows for the use of standardized custom ring cadences across the network. These are used most commonly for party ling (coded) ringing, busy line callback, and priority ring on calls with greater than routine precedence. The cadences and their numbering were carefully chosen to minimize conflicts with the Bellcore specifications while allowing for maximal functionality and accessibility on the network.

ATAs provisioned by the PhreakNet provisioning server are automatically programmed with the cadences below.

Grandstream ATAs typically allow for 10 cadences, while Linksys ATAs typically allow for 8 (sometimes 9) cadences.

Cadence #Cadence LengthCadence DescriptionGrandstreamLinksys
16.0Standard (US)c=2000/4000;300(2/4)
103.0S-S (Standard UK)c=400/200-400/2000;300(.4/.2,.4/2)

If you are using DAHDI, here are the cadence specifications for the [channels] section in chan_dahdi.conf:


If your ATA uses different formats for specifying cadences, please contact the Business Office and we can work with you to get your ATA set up correctly.

Subroutines like the following may be used to send the right cadence to your ATA:

[SIP-RingHeader] ; ARG1 = cadence / ARG2 = peers
exten => s,1,Set(peerstocheck=${ARG2})
	same => n,Set(i=-1)
	same => n(checkfree),Set(i=${LEN(${CUT(peerstocheck,&,1)})})
	same => n,Set(nextpeer=${peerstocheck:0:${i}})
	;same => n,Set(nextpeer=${nextpeer:4})
	same => n,Set(peerstocheck=${peerstocheck:$[${i}+1]})
	same => n,GotoIf($["${nextpeer:0:4}"="SIP/"]?sip)
	same => n,Goto(next)
	same => n(sip),Set(nextpeer=${CUT(nextpeer,/,2)})
	same => n,Set(agent=${FILTER(A-Za-z0-9\x20\x2E\x2F\x2D,${SHELL(asterisk -rx 'sip show peer ${nextpeer}' | grep "Useragent" | grep -o ':.*')})})
	same => n,Gosub(SIP-RingHeader-indiv,s,1(${ARG1}, ${agent:1})) ; Trim space that was after the : from agent
	same => n(next),GotoIf($[${LEN(${peerstocheck})}=0]?allchecked:checkfree)
	same => n(allchecked),Return()

[SIP-RingHeader-indiv] ; ARG1 = cadence / ARG2 = SIP ATA user agent
exten => s,1,NoOp(${ARG2}) ; if we know what user agent this is, we'll only send that header
	same => n,GotoIf($[${REGEX("Grandstream" ${ARG2})}=1]?grandstream,1)
	same => n,GotoIf($[${REGEX("Linksys" ${ARG2})}=1]?linksys,1)
	same => n,GotoIf($[${REGEX("OBIHAI" ${ARG2})}=1]?obi,1)
	;same => n,SIPAddHeader(Alert-Info: info=) ; this is a bad fallback
	same => n,Goto(grandstream,1) ; fallback to Grandstream
exten => grandstream,1,SIPAddHeader("Alert-Info:\;info=ring${ARG1}") ; Supports 1-10
	same => n,Return()
exten => linksys,1,SIPAddHeader("Alert-Info:\;info=Bellcore-r${ARG1}") ; Linksys (e.g. PAP2T), etc.
	same => n,Return()
exten => obi,1,SIPAddHeader(Alert-Info:${ARG1:-1}) ; send only a single digit
	same => n,Return()

You could then set the cadence by calling it like: same => n,Gosub(SIP-RingHeader,s,1(${EXTEN},${ARG3})) (assuming EXTEN is the ring cadence and ARG3 is the SIP peer).

The SIP header should only get sent once, so if the cadence could be changed before ringing the phone, keep track of that in a separate variable. When determining the cadence to use, consider the priority of the call (MLPP) as well.

Multiple Level Precedence and Preemption

MLPP is most commonly associated with AUTOVON, the old automatic voice network, which was used by the military for much of the Cold War. AUTOVON implemented MLPP. MLPP uses the 4th-column DTMF keys (A, B, C, and D), used as FO (flash override), F (flash), I (immediate), and P (priority). If you don't have 4th-column DTMF, you can use a silver box, such as this one.

Priority Audible Ring replaced normal ring for calls within AUTOVON. It consists of 440 Hz + 480 Hz at -16 dBm0/frequency on for 1.65 seconds and off for .35 seconds.

Preemption Tone is provided to both parties of a connection that a priority call from the AUTOVON network preempts. Preemption Tone is 440 Hz and 620 Hz at -18 dBm0/frequency steady for anywhere from three to fifteen seconds.

MLPP is fairly straightforward to use in Asterisk, and is currently used on the main tandem, which is a multi-function switch (e.g. dial 231-1111 if you are not served by this switch and don't have a foreign exchange line). If you encounter an "all circuits busy" condition, you may be able to complete your call by preempting a lower-priority call.

The following priority levels are usable by the following populations:

  • Flash Override - Switch Owners, only
  • Flash - Local switch users, also
  • Immediate & Priority - Any legitimate and verified caller

Note that this means PSTN callers, etc. cannot request priority of any level for any call.

If all circuits remain busy and your call is urgent, dial 0. Operator-assisted calls are completed using a different trunk group.

Further Resources:

The MLPP/AUTOVON library is relatively small:

[autovoninit] ; Returns number of current trunk calls
exten => _[A-D0],1,NoOp(AUTOVON Precedence: ${EXTEN}) ; AUTOVON checks
	same => n,Gosub(currentldcalls,s,1)
	same => n,GotoIf($[${GOSUB_RETVAL}=0]?:database)
	same => n,DBdeltree(autovonA) ; Reset the key families, to wipe out old, unneeded data (none of which is currently needed)
	same => n,DBdeltree(autovonB)
	same => n,DBdeltree(autovonC)
	same => n,DBdeltree(autovonD)
	same => n,DBdeltree(autovon0)
	same => n(database),Set(GROUP(autovon)=${EXTEN})
	same => n,Set(DB(autovon${EXTEN}/${FILTER(0-9,${UNIQUEID})})=${CHANNEL})
	same => n,Return()

exten => s,1,GotoIf($["${autovonprioritydigit}"="0"]?done) ; can't preempt any calls
	same => n,Set(callids=${DB_KEYS(autovon0)})
	same => n,GotoIf($[${LEN(${callids})}>0]?0,1)
	same => n(ad),GotoIf($["${autovonprioritydigit}"="D"]?done) ; can't preempt other priority calls
	same => n,Set(callids=${DB_KEYS(autovonD)})
	same => n,GotoIf($[${LEN(${callids})}>0]?D,1)
	same => n(ac),GotoIf($["${autovonprioritydigit}"="C"]?done) ; can't preempt other immediate calls
	same => n,Set(callids=${DB_KEYS(autovonC)})
	same => n,GotoIf($[${LEN(${callids})}>0]?C,1)
	same => n(ab),GotoIf($["${autovonprioritydigit}"="B"]?done) ; can't preempt other flash calls
	same => n,Set(callids=${DB_KEYS(autovonB)}) ; so, yes, only a flash override can get to this point
	same => n,GotoIf($[${LEN(${callids})}>0]?B,1)
	same => n(done),Return() ; Sorry, can't preempt anything
exten => _[A-D0],1,Set(callid=${CUT(callids,\,,1)})
	same => n,Set(preemptchan=${DB(autovon${EXTEN}/${callid})})
	same => n(preemptcall),ChannelRedirect(${preemptchan},autovonpreempted,s,1)
	same => n,Set(deleted=${DB_DELETE(autovon${EXTEN}/${callid})}) ; call isn't up anymore, so delete from DB
	same => n,Goto(a0) ; try again - hunt for another call to preempt
	same => n(success),Return()

exten => _[ABCD].,1,Progress()
	same => n,Playback(custom/switch/autovon/autovon603&custom/switch/autovon/autovon603,noanswer) ; audio file with 603 intercept
	same => n,Hangup()

exten => s,1,Set(GLOBAL(autovonpreempted${FILTER(0-9,${UNIQUEID})})=1)
	same => n,Set(GROUP(autovon)=)
	same => n(preempt),PlayTones(440+620) ; can also be defined as preemption in indications.conf
	same => n,Wait(15) ; Play preemption tone to caller for 15s
	same => n,StopPlayTones()
	same => n,Hangup(8) ; AST_CAUSE_PRE_EMPTED

exten => s,1,Set(CDR_PROP(disable)=1)
	same => n,Hangup() ; this extension is used if none is specified b/c there was no channel (so, no chance of the call being pre-empted, so don't provide the 8 cause code)
exten => _X!,1,Wait(0.3) ; wait ever so slightly, to give the variable time to get set
	same => n,ExecIf($["${$[autovonpreempted${EXTEN}]}"="1"]?PlayTones(440+620):Hangup()) ; can also be defined as preemption in indications.conf
	same => n,Wait(3) ; Play preemption tone to caller for 3s
	same => n,Hangup(8) ; AST_CAUSE_PRE_EMPTED

exten => s,1,Return($[${GROUP_COUNT(A@autovon)}+${GROUP_COUNT(B@autovon)}+${GROUP_COUNT(C@autovon)}+${GROUP_COUNT(D@autovon)}+${GROUP_COUNT(0@autovon)}])

Some assumptions and simplifications are necessary for using MLPP. Since trunking is peer to peer using IAX2, scarcity is not really a thing. Thus, artificial limitations must be introduced for preemption to ever occur. However, priority ringing is useful and possible without placing artificial limitations on trunking.

To use the library, do the following:

  • Allow dialing of digits A through D at dial tone. Routing must account for this (e.g. exten => _[A-D].,1,Goto(something,${EXTEN},1))
  • If an A through D was dialed as the first digit, set the channel variable __autovonprioritydigit to the letter. Otherwise, set it to 0.
  • Set a global variable called autovonmaxcalls, equal the maximum number of trunk calls that may be up at any time (not the same as the number of calls shown in the Asterisk console, but rather 1:1 trunk calls)
  • Calls may fail for one of two reasons:
    • The caller does not have sufficient privileges to use a desired priority
    • All circuits are busy

    Both should be properly handled, in a manner such as the following

    same => n,Gosub(currentldcalls,s,1)
    same => n,GotoIf($[${GOSUB_RETVAL}<${autovonmaxcalls}]?proceed)
    same => n,Gosub(autovonpreempt,s,1) ; Attempt to preempt a call
    same => n,Wait(3.2) ; may need to adjust to all group count to fall when preempted call clears
    same => n,Gosub(currentldcalls,s,1)
    same => n,GotoIf($[${GOSUB_RETVAL}<${autovonmaxcalls}]?proceed)
    same => n,GotoIf($["${autovonprioritydigit}"=""]?acb)
    same => n,GotoIf($["${autovonprioritydigit}"="0"]?acb)
    same => n,Progress() ; all circuits busy and no calls preemptable
    same => n,Playback(custom/switch/autovon/autovon601,noanswer) ; for priority callers with insufficient precedence to preempt
    same => n,PlayTones(congestion)
    same => n,Wait(30)
    same => n,StopPlayTones()
    same => n,Hangup()
    same => n(acb),Playback(custom/allcktsbusy,noanswer)
    same => n,Hangup()
    same => n(proceed),Gosub(autovoninit,${autovonprioritydigit},1) ; we can proceed with the call...
  • On a successful call that leaves your switch, call Gosub(autovoninit,${autovonprioritydigit},1) as shown above. This stores information about the call in the database so that we can preempt this call later if needed.

    Preemption handling is already built into the boilerplate code, using the F dial option.

It may be desirable to prevent certain callers from using certain priorities. As an example, on the main tandem, non-network callers may not use any priorities. Any network caller may use the D and C (Priority and Immediate) priorities. Only hosted lines on the main tandem may use the B (Flash) priority. Only the switch owner may use the A (Flash Override) priority. This arrangement is not included above, and would be done when we receive the digits _[A-D] and set the autovonprioritydigit variable. If a caller does not have the necessary privileges, he would be routed as follows: Goto(autovonpreemptfail,${EXTEN},1). The exact classes of service each node desires to use may vary and are not prescribed. It is merely a suggestion that this behavior should be restricted by some class of service accordingly.

Priority Audible Ringing and Priority Ring are enabled by sending a custom ring cadence header to the FXS port before ringing the subscriber line. This can be done by checking the value of ${autovonprioritydigit} in the dialplan. If this value is strictly equal to A, B, C, or D (but not 0 or empty), then priority ring should be used. This corresponds to cadence 8 in the Cadence Plan.

Priority Audible Ring is easy: Set(dialoptions=r(priorityring)).

To send the right ring cadence to your ATA, use same => n,Gosub(SIP-RingHeader,s,1(${EXTEN},${ARG3})). See Cadences for this subroutine.

The audio files referenced above are not included and may be recorded locally using the appropriate verbiage.

The latest copy of the verification subroutines ensure MLPP information is transmitted between nodes and will ensure both priority ringing on the called telephone (assuming correct cadence setup).

If the preemption aspect above seems a bit silly, well, it is. In a full MLPP environment, MLPP status would be kept track of per-trunk. The MLPP code is a simplified and dumbed down version of full MLPP routines that keep track of priorities for each trunk in each trunk group. Of course, in the VoIP world with IP trunking, there is only one "trunk group" really.

Manual Service

If you would like to be served by manual service instead of dial service, your ATA should off-hook auto-dial 950-0100, which is a free call. This is a manual operator service trunk. All your calls will go through the operator.

If you want it to go directly to Brian specifically, use 950-0101.


The following information has been compiled courtesy of Don Froula:

For those using Windows machines on their LANs, you may wish to explore a very useful program that I have been using for many years. The Windows "NetCID" program receives an IP broadcast on your LAN from an Asterisk PERL AGI script that goes out to ALL local IP addresses on your LAN on a special port, where a popup and optional sound alerts to the incoming call. The contents of the popup may be customized in the AGI script. I have five different scripts to indicate if the call is coming from the PSTN, CNET, NPSTN or other sources.

You must have PERL installed on your Asterisk machine.

The program is quite old, and a bit hard to find. It does work fine on all versions of Windows, including Windows 10.

You can download the program here. Instructions for setting up the AGI and how to call it in your dialplan are available at VoIP-Info.

In the AGI script, set the first three octets of the IP address to match those of your LAN. Leave the last octet at 255, which signifies the data is to be broadcast to all devices on the LAN. You may need to allow IP broadcasts on your router. Do NOT change the port number.

You may modify the following to customize the popup display with hard-coded text that appears at the bottom of the screen. Change nothing else. Example:

my $MSG1 = “STAT Phone Call to $extension”;

You can call the AGI script from the dialplan like this:

exten => _X.,n,AGI(ncid4.agi,${CALLERID(num)},${CALLERID(name)},${EXTEN})

Note I have multiple versions of the script to display different text at the bottom of the large-sized popup. This is ncid4.agi (for CNET) to adjust as needed.

My old router, since replaced, did have a setting that blocked IP broadcasts on the LAN, although it seems that the broadcasts should not be controllable through the router as they are peer<>peer. When I install NetCID on Windows 10, Windows defender pops up and asks if I want to allow NetCID traffic on my LAN. I don't think this is default behavior in Windows 7. This action openrd all TCP and UDP ports for the NetCID application, but in reality it only uses UDP port 42685, so opening that up should be sufficient.

I installed Asterisk as root. My /var/lib/asterisk/agi-bin/ directory is owned by root with directory permissions of 750. The ncid.agi scripts themselves are 777.

You can change the IP address to target one machine on the LAN to see if broadcasts are being disallowed on the Asterisk machine or the target PC.

The "sleep 5" commands send periodic RING indications to the NetCID app to keep the popup on the screen if the "display until ringing stops" option is set on the NetCID app.

I think there is a typo on the first line of the AGI script on the web page. Note the first line must begin with "#!/usr/bin/perl", making sure the path to the PERL executable is correct.

Here is my working CNET script:

use Socket;

open STDOUT, '>/dev/null';
fork and exit;

my $timedata = localtime(time);
my $cidnum = $ARGV[0];
my $cidname = $ARGV[1];
my $extension = $ARGV[2];

my $MSG1 = "STAT CNET Phone Call to $extension";
my $MSG2 = "RING";
my $MSG3 = "NAME $cidname";
my $MSG4 = "TTSN Call from $cidname";
my $MSG5 = "NMBR $cidnum to $extension";
my $MSG6 = "TYPE U";
my $MSG7 = "IDLE $timedata";

my $ipaddr=;
my $portnum=42685;

socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or die "socket: $!";
setsockopt(SOCKET, SOL_SOCKET, SO_BROADCAST, 1) or die "setsockopt: $!\n";
send(SOCKET, $MSG1, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG3, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG4, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG5, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG6, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG7, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";

There are quite a few options that can be set in the NetCID application, but you need to open them when from the tiny icon in the system tray in the lower right corner of the Windows desktop. Right click on the little TV-looking icon and some options that can be checked or unchecked will pop up. Choose "Configure" for many more. You may need to click the "^" to see the full list in the system tray.

You might try running the script from a Linux terminal window as a PERL script directly, rather than as an SGI script through Asterisk for debugging. That would eliminate a few variables.

perl /var/lib/asterisk/agi-bin/ncid.agi 5555555 "John Smith" 5554444

That invocation pops a window up on all my PCs in the house.

Potential Problems:

The script copied from the web site has open and close double quotes which are apparently not recognized by perl on my system. I found it when I tried to invoke the script from the command line and got error messages about an unrecognized character on a certain line. I changed all the open close double quotes to regular double quotes and all was well. It works from the command line and more importantly, from Asterisk as intended.

Payphone Trunks

Note: This section is currently outdated. See Coin Trunks on the PhreakNet Portal for the most up to date info.

PhreakNet has several coin and coinless payphone trunks for both single-slot and 3-slot payphones. These trunks are designed for use with payphones on regular loop-start ATA lines with no additional hardware configurations. In other words, they are designed for payphones that have no smarts in them and won't act like payphones on their own. For those with a non-COCOT payphone and just an ATA and few other resources, these trunks can be used to bring your payphone to life!

To use the trunk, have the your payphone immediately connect to one of the following numbers below, depending on which kind of trunk you want for your payphone. If you have an ATA hosted by somebody else, you can have the ATA off-hook hotdial the number (e.g. (<:9500903>S0) — Grandstream users would specify { 9500903 } for the digit map and need to configure hotline dialing on the "Lines" tab). If you have an Asterisk switch, you should have a separate incoming context in extensions.conf for each kind of payphone trunk you want to use, which only allows calls to the the number below corresponding with that trunk (thus, the payphone trunk would be specified by the dialplan context specified in sip.conf). This is more secure, because a payphone caller will not be able to flash at payphone trunk dialtone and simply dial calls directly another way (or use a different kind of coin trunk, if all the coin trunks were dialable from that context). Linksys ATAs configured for hotline dialing may also prevent callers from flashing out of the payphone trunk, but Grandstream ATAs will not.

Have your ATA off-hook auto dial to Asterisk, and have Asterisk immediately complete a call if appropriate.

The payphone trunks are fully integrated with the toll ticketing (billing) system. The charges for payphone calls will always equal or exceed the charges that appear on your bill, so the coin revenue from a payphone on these payphone trunks will more than pay for the charges that appear on the bill for the line a payphone is on. Thus, you don't need to worry about charges. All you need to do is make sure your payphone sends proper 7-digit network caller ID (as it should anyways). Based on that, the payphone trunk will determine what rates are appropriate for calls made through the trunk. The charges will be very similar to calls made directly (i.e. not using a payphone trunk) from that number.

The local rate (initial deposit) on these payphone trunks is 10 cents. While information/directory assistance is free normally on PhreakNet, it costs 25 cents from payphones.

The way payphone rates are determined is either a flat rate greater than any possible charge for a call of that type OR a specific rate rounded up to the nearest $0.05 based on the station rate charges to a number are assessed.

Payphone operators (automated) handle select calls (e.g. long-distance). Long-distance can generally be dialed direct at 112 (or dial 0 for local calls or ask for long-distance).

These trunks are designed to be 99% plug and play with ~ZERO configuration on the part of the payphone owner (a small adjunct circuit in the physical vicinity of the phone is needed to monitor the line for MF tones and apply +/- 130V to do collect and return; however, the trunks themselves work fine without such a circuit, you'll just have to make your phone operate "piggy bank style" and not be able to get coins returned). These trunks are the equivalent of the CO-type trunks used for payphones.

Trunk Types

See Coin Trunks on the PhreakNet Portal for a detailed listing.

The Charge-A-Call trunk is designed specifically for Charge-A-Call and other coinless payphones. It is integrated with PhreakNet TSPS and allows for seamless third-number billing. To use Charge-A-Call, you will need an InterLinked PIN, which can be set or reset in your InterLinked account settings. Calling card numbers are 14 digits, in the format 1NNXXXXXPPPPPP, corresponding to 1 + the 7-digit phone number + 6-digit InterLinked PIN. The only difference is that instead of using # to dial sequence calls, dial ** instead.

The reason for the leading 1 is two-fold: a) TSPS calling card numbers were 14 digits, and 13 is kind of an odd number. 6 digits is a standard amount for a PIN, and since numbers are 7 digits, that adds up to 13. So, an additional "1" is inserted first for now. In the future, there may be other uses for this "information digit", but for now, the only valid value is 1.

WECo style plays a second dial tone upon callee answer, indicating coins should be deposited, whereas Automatic Electric muted the mouthpiece and DTMF pad until coins were deposited. The Automatic Electric post-pay trunk relies on the caller to determine whether or not he wishes to talk, or use the keypad, and thus pay for the call. If he doesn't, there is no charge for the call, but it is limited in duration to a reasonable ringback (between 1 and 3 minutes). If/when a coin is deposited, the caller will be able to speak and dial/use the keypad again. We recognize this will allow free calls to listen-only numbers (e.g. time and temperature numbers).

We recognize that it is possible to "phreak" all of the trunks above by using a redbox or similar device capable of reproducing ACTS tones and 3-slot bell gongs. Consequently, payphone trunk usage is limited to verified, legitimate network callers (e.g. CVS code 70). This is necessary because of the real possibility of toll fraud, and it is a choice deliberately made to minimize our losses. All coins deposited into payphones using the payphone trunks are kept by the payphone owner (like with a COCOT; we don't get any share in the profits, even though we're the "telephone company" — at the same time, we still foot the bill as the payphone provider. If you would like to donate to this project to offset our expenses, please contact us!

Payphone Advertisements & Instruction Cards

The following publications have been created for your convenience. They are actual size and ready to print. These are the recommended accessories for your PSTN payphone:


Single Slot Lower Instruction Card:

Single Slot Upper Instruction Card:

Old Instruction Cards:

The following instruction cards are designed for use with semi post-pay coin phones. However, the PSTN trunks use ACTS, which is dial-tone first, not post-pay.

Single Slot Lower Instruction Card:

Single Slot Upper Instruction Card:


StepNet is an additional layer on top of PhreakNet, not a separate network.

The idea is that, instead of the call completing directly peer-to-peer, the call "steps" through the network, adding an interesting soundscape to the call and tandeming in a hierarchical manner similar to the way calls complete on the PSTN, where trunking is not peer to peer and calls must traverse multiple nodes for long distance calls.

A dialplan code repository of approved, standard, and exceptionally fully documented StepNet code for members would also be a good idea. — Brian Clancy

StepNet functionality is built into the boilerplate code. You will need to uncomment the relevant lines in iax.conf and then call the Business Office to enabling tandeming through your node.


In order to make StepNet routing more interesting, some kind of vintage signaling audio should be audible when a StepNet call is being tandemed.

Here are a few extensions that would do the trick:

  • MFer
  • SFer
  • Dial Pulser
  • Revertive Pulser

These can all be found in the "Vintage Add-Ons" section of this documentation.

Other audio files needed for StepNet tandems are available as a standardized ZIP file downloadable below. The ZIP file contains all the ULAW files you need; no need for each node owner to convert them using SoX when we can do it once and also reduce the download size!

ZIP file containing standardized StepNet tandem audio files: click here to download.

Extract the audio files inside to the following location: /var/lib/asterisk/sounds/en/custom/stepnet/ (you will need to create the stepnet folder). The files are named sn1, sn2, sn3.

The ${RAND(min,max)} function in Asterisk will allow you to randomly play some of these audio files in the dialplan.

Operator Number Identification (ANI Fails)

If you have physical ANI equipment that might fail, you may wish to take advantage of Operator Number Identification.

The routing lookup will automatically send ANI fails to a "TSP operator" for Operator Number Identification (ONI). The operator will key the number into the trunk and release the call. If a human operator is not available, an automated operator will handle the call, so the service is available 24/7.

Calls that require ONI should have the ANI II digits set to 1 if the line is an 8-party line or 2 if ANI was attempted but an ANI fail occured. The dialplan and central lookup will automatically route the call for ONI. There is no special handling required.

Automated Operators

We do have human operators who are frequently on-duty. When the operator lines are staffed, calls to operator services (i.e. 0, 411, 611, 555-1212) will be answered by an operator who can help you with long-distance calling, information/directory assistance, or troubleshooting your phone line. The proper numbers to call are documented in the "Numbering Plan" section of this documentation.

With the exception of the Business Office line, if the on-duty operator is, well, off-duty, or otherwise unavailable, calls will automatically be forwarded to an automatic operator who will help you. The voice of these operators is the late Brian Clancy. If you'd prefer, you can also dial an automatic operator directly, bypassing the regular network operator. Again, the proper numbers are documented in the "Numbering Plan" section.

The long-distance and information operators use speech recognition technology coupled with speech-to-text processing to power your request. Though not known for working well with a wide variety of voices, we're quite pleased with how well they work (it is perhaps a bit ironic that Brian Clancy, who has a British accent, sometimes has trouble getting "himself" to recognize himself when calling an automatic operator). Our speech-to-text processing is powered by IBM Watson (see the "Further Add-Ons" section for more on this).

The automated operators work as follows: certain phrases are translated to numbers, and the rest of the translation is discarded. This results in extremely accurate operator assistance. As long as you enunciate your number clearly, operator Brian will be able to place your call for you. In addition to using numerals (such as 0, 1, 2, etc.), you can also use exchange names. If all goes well, it will be interpreted as part of the number.

Our long-distance operators also support service inquiries. If you ask for time and temperature, you will be connected with POPCORN. If you ask for the temperature, you will be connected with 511. If you ask for information, you'll be connected with Information. The automatic operator's high reliability is due, in part, to listening for and expecting only certain key phrases, and discarding the rest. Thus, we can expect a standardized response every time and can parse the request very accurately. Thus, if you say "Get me the police" or "Operator, could you get me the time and temperature, please?", the automatic operator will successfully be able to place your call since everything actionable is extracted and any extraneous text is discarded.

The Information operator, on the other hand, takes the reverse approach. Here, a caller could ask for any number of things that are in the Directory. It is therefore not possible to listen for certain phrases and ignore the rest. Rather, we must do the opposite: detect certain likely phrases (such as "hello", "operator", "information", "please", etc.), discard them from the translation, and then do a lookup in the directory using a separate API. Hence, if the caller is extraneous with his words, unlike the regular long-distance operator, who will not mind at all, the Information agent may not be able to find what you're looking for, as extra words could be counted as part of the lookup. That being said, we have anticipated many of the most common phrases and words. For best results, however, we recommend being succint. "Hello, information? Could you get me the number for the muzak listen line, please?" is perfectly acceptable. "Hello, ouch, I just stubbed my toe, boy that hurt! Oh, hello? This is information, ain't it? Well what do you know! Might you by chance happen to have the number for the, uh, muzak listen line, perhaps?" is not. A bit far-fetched, perhaps, but hopefully you see our point.

Speech-to-text processing is not done for the automatic repair agent. Instead, the repairman will collect your information and, at a later point in time, your inquiry will be reviewed by a service technician. To expedite a ticket, you can try calling our business office during regular hours.

Again, all the numbers described in this section are available in the "Numbering Plan" section of this documentation.

Some general notes that apply to all operator calls:



Paging numbers are available in the 549 exchange. Simply list your pager gateway email address in your InterLinked Account Settings; then call the Business Office for a pager number, no charge. Pager numbers can be either the original type (page only), or the later type (numeric message).


Dial 117 for telegrams, or log in to the user portal.

If you receive a telegram, you will be phoned at the number listed for delivery. You can confirm delivery of the telegram by viewing it online or answering the telegram call. Delivery will be attempted periodically until successful.

Wake Up Calls

You can schedule one-time wake-up calls or recurring, snoozable wakeup calls. One-time wakeup calls can be scheduled at LIncoln9-1414.


Asterisk natively supports video, so time to end that Zoom call! However, there are some limitations and caveats to keep in mind. Unlike as with audio, Asterisk does not do any transcoding whatsoever of video. This means that you are essentially restricted to using a single video codec, as never the twain shall meet. We recommend H.264, since this is most robustly supported by Asterisk; for instance, only H.264 can be recorded; VP8 cannot be. Additionally, most SIP clients with video support typically support H.264.

The quality of live videos can be quite good, but the quality of recordings may leave something to be desired.

The following dialplan applications support video:

Video SIP Clients

Not all SIP clients support video, but some do. The one that we recommend is MicroSIP. It's one of the most popular softphone applications out there, and it runs on any version of Windows NT.

However, using video with MicroSIP isn't entirely intuitive. The key thing to note is that you need to disable single call mode. Otherwise, there won't be any video support, and there's no hint as to why that is.

If you are using single-call mode, there isn't a button labeled "Video Call" on the pop-out panel, but you can click the video button to the left of the "Call" button after keying in a number. For shortcut buttons, you can change the action from "Call" to "Video Call" to automatically make the INVITE with video support every time (easiest option).

Note that upon doing this, it seems that video support needs to be indicated at the time the SIP INVITE is made to Asterisk. If you're off-hook auto-dialing with MicroSIP, that means as soon as you get dial tone from Asterisk, you need to indicate that you have video support. The remote end doesn't need to send video immediately, but if you don't tell Asterisk you support video from the get go, video support can't be enabled later.

Currently, you need to click "Video Call" in the pop-out panel that seems to only appear after you've made one call when using the application. So make any call using MicroSIP, and then the ancillary window should pop up and you'll see a "Video Call" option. That's what you'll need to allow video.

Finally, you also need to enable video support in the channel driver you are using - whether SIP or PJSIP.

For SIP, add:


For PJSIP, add simply:


This needs to be done for any and all peers for which video support is desired.

This allows the H.264 video codec to be used for calls.

Now that you have a SIP client that supports H.264, and Asterisk is configured to allow H.264 for calls, you can take advantage of video in the dialplan. For video to work on a call between two parties, the caller needs to make the call with video support, and the caller needs to receive the SIP INVITE with video support available.

Note that if you use Originate, this means you must explicitly add the h264 codec to the allowed list of codecs (default is simply slin).

If using ConfBridge, the video_mode bridge setting may be useful for controlling video behavior in the bridge.

If video is not working, the first thing to do is get a SIP debug of a call to or from an endpoint. Incoming INVITEs for call originate must have H264 (or the codec being used) in the INVITE. Otherwise, there is no video support. Likewise, calls to endpoints must offer H264. Otherwise, no video support. MicroSIP will have an "Answer with Video" option available if video support is contained in the INVITE on an incoming call.

PhreakNet System Practices

The PhreakNet System Practices are a collection of documents modeled after the Bell System Practices. Each practice or specification contains information on a specific aspect of the network, beyond the level of detail provided in this documentation. Some documents are classified or intended for internal usage only. These documents are the property of PhreakNet and are not for use or circulation outside of PhreakNet.

PSPs are circulated for internal use only. The latest copies are available through the PhreakNet user portal, to authorized users.

C*NET Connectivity

Receiving Inbound Calls

First, at a minimum, you will need to add the following to your iax.conf:


This is the most basic section possible that defines an IAX2 user (i.e. to receive incoming calls — a peer is for outgoing calls, and we won't need to define one since outgoing calls just use the full, direct IAX2 URI. A friend is both, but we don't need a peer, so we can just use a user). You could add username=cnet, but this is implicit due to the section name being cnet. Authentication and encryption options are omitted here.

This assumes that cnet is your C*NET IAX username; adjust this accordingly if you already a C*NET IAX username that is different.

The above also assumes you already have your [general] section configured. If you don't, this extract from the boilerplate iax.conf is a good place to start:

relaxdtmf=yes ; If your server has issues with DTMF this option may help
bandwidth=high ; allows for high-quality codecs like ulaw/g722 to be used. This does NOT mean you have an excellent Internet connection!
disallow=all ; allow only G711 or better codecs
allow=g722 ; HD voice (G.722)
allow=ulaw ; PCM G.711 uLaw (North America)
allow=alaw ; PCM G.711 aLaw (Europe, non-NANPA)
delayreject=yes ; for increased security against brute force attacks
autokill=yes ; prevent stalling on call setup for unavailable hosts
encryption=yes ; Encryption is disabled by default. This *allows* it to be used (it does not force it)
trunk=yes ; More efficient when multiple IAX2 calls are up between two nodes.
authdebug = yes ; Make troubleshooting easier
maxcallnumbers_nonvalidated = 64 ; If you need to support nodes that don't do call tokens, set a limit or your server can be attacked.

A quick word about the last three settings: a lot of C*NET nodes are fairly old, by Asterisk standards (e.g. 15+ years old), and some do not support call tokens, authentication, encryption, and other "modern" aspects of the IAX2 protocol. You may elect to require calltokens for all users, including your C*NET user, but this will prevent some older systems from being able to call you. The approach used above doesn't globally require call tokens, but does add the restriction that only 64 "non validated" calls can be up at any given time, significantly reducing the security risk and attack surface by not requiring call tokens.

Ultimately, there is no single standard here, and if you know that it's unlikely that 15+ year old obsolete Asterisk systems will not be calling your node, you can probably remove the maxcallnumbers_nonvalidated and calltokenoptional and just set requirecalltoken=yes. If you elect to allow calls without requiring call tokens, protect yourself and set a conservative maxcallnumbers_nonvalidated limit. 64 should be quite generous. This is compatible with older nodes but presents a smaller security risk, thus giving you the best of both worlds as much as possible. Anybody who tells you to use requirecalltoken=no without caveats is giving you bad advice which could endanger your system.

There is such a configuration as requirecalltoken = auto. This applies per user, so it might not do what you would think it might do. What this means is that as soon as call is made to a given user and that incoming call is from an Asterisk system that supports call tokens, call tokens will permanently be required for that user. Since all calls from all nodes on C*NET will come into this single user, and since most C*NET nodes do support call tokens, this is effectively the same as requirecalltoken = yes. In practice, there should never be a reason to use requirecalltoken = auto.

Similar caution should be used with other settings as well. If you elect to use authentication and (but not or) encryption, this will apply to all incoming calls, and some older C*NET nodes may be unable to place calls to your node. In particular, forceencryption=yes is not likely to be widely compatible. For a highly compatabible C*NET node, the configuration provided above will suffice.

Routing Inbound Calls

The approach you will adopt to handle C*NET calls will differ based on whether you want to provide access to the same extensions on both networks or not. In the easiest case, if your US C*NET office code matches your PhreakNet office code, so that the only difference between the two numbers on both networks is that your C*NET numbers have a 1 before them, the following will be sufficient:

exten => _X!,1,Gosub(cnet-verify,${EXTEN},1)
	same => n,Goto(external-users,${EXTEN:-7},1) ; strip leading 1 from received number

cnet-verify is part of the verification subroutines. It will screen all incoming calls and assign a 2-digit code to the call. Legitimate C*NET calls will have the channel variable clidverif equal to 20. Anything else is a spoofed, spam, or illegitimate call and could be easily rejected at the owner's discretion. See Verification for more details. (You can also use the app_verify module if you wish.)

As you can see, the last line assumes you are using the same numbers on both networks. Change the destination context if you want to have different thousand block allocations. While using the same numbers may be easier, differentiating between the two networks makes your node more unique. Make changes as needed, e.g. ${EXTEN:-4} if you want to use only the last 4 digits for routing purposes in your dialplan.

You will need to register for a C*NET office code if you don't already have one. All the instructions you need to get setup are on the C*NET website.

Routing Outbound Calls

To dial out, you can use this sample dialcnet subroutine:

[dialcnet] ; CNET NA 20190219, adapted from BJC v1.01 - no caching, subroutine ; ARG1 = full internationalized C*NET number, numerals only
exten => s,1,Set(LOCAL(enum)=${ENUMLOOKUP(+${ARG1},ALL,,1,}) ; Asterisk 1.4+
	same => n,GotoIf(${ISNULL(${enum})}?no_uri)
	same => n,GotoIf($["${enum:0:3}"="iax"]?iax)
	same => n,GotoIf($["${enum:0:3}"="sip"]?sip)
	same => n,GotoIf($["${enum:0:3}"="h32"]?h323)
	same => n(no_uri),PlayTones(congestion) ; CCBCAD
	same => n,Wait(10)
	same => n,Return()
	same => n(iax),Set(LOCAL(dialstr)=IAX2/${enum:5})
	same => n,Goto(dialstr)
	same => n(sip),Set(LOCAL(dialstr)=SIP/${enum:4})
	same => n,Goto(dialstr)
	same => n(h323),Set(LOCAL(dialstr)=H323/${enum:5})
	same => n(dialstr),Dial(${dialstr},,g)
	same => n,Return()

In theory, the above subroutine is written to look for IAX2, SIP, and H323 URIs. In practice, C*NET, like most private hobbyist networks, uses IAX2 almost exclusively. (The original IAX protocol is also long obsolete.)

WARNING: You should not use the default BJC macro provided on the C*NET website. Macros are obsolete in Asterisk and should be avoided in general, and this particular macro is known to contain an infinite loop (around lines 53-55). The caching mechanism it uses was helpful back when the C*NET ENUM server was a lot less reliable, but isn't really necessary nowadays anyways. You'll be much better off with a) a simpler dialplan context and b) a subroutine.

You should ensure your outgoing Caller ID is a valid international C*NET caller ID (e.g. 15551212). A 7-digit caller ID for country code 1 is not valid — it should be 8-digits. There are two approaches you can use:

You can call the above subroutine with the following syntax:

exten => _1NXXXXXX,1,Gosub(dialcnet,s,1(${EXTEN}))
	same => n,Hangup()
exten => _011X.,1,Gosub(dialcnet,s,1(${EXTEN:3}))
	same => n,Hangup()

This allows 1+ dialing for country code and 011 + full international C*NET number for all other numbers.

If you want to dial using just 7 digits to numbers in C*NET country code 1, you can add this:

exten => _NXXXXXX,1,Gosub(dialcnet,s,1(1${EXTEN}))
	same => n,Hangup()

If you don't want to dial 011 before international numbers, you could use a catch-all rule, like this:

exten => _X!,1,Gosub(dialcnet,s,1(${EXTEN}))
	same => n,Hangup()

You can create a dialplan context (e.g. [to-cnet]) with these pattern-matching extensions in them and use something like Goto(to-cnet,${EXTEN},1) to make outbound C*NET calls.

PSTN Connectivity

Incoming Calls



Interested in a free DID? IPComms will give you one! It will even come with two incoming channels that allow unlimited incoming minutes (with the restriction that you can only have 2 incoming calls at a time). The only catch is the number is randomly assigned to you; the area code in which your DID is located is completely random as well. You can sign up with IPComms on its website. The signup link takes you to a social media page, but you don't need to be a registered member there; that's merely where the form is located.

Unfortunately, IPComms has indefinitely suspended their Free DID program. Existing DIDs will continue to work, but newcomers will be unable to acquire one.

Note that to get your free DID setup, you will receive a call from IPComms during business hours. This goes without saying, but make sure your number is accurate!

They will then ask you for some basic information: full name and email address.

Once they hang up, you will receive an email later with your login information — you will need this!

Required Contexts

Unlike hobbyist telephone networks, the vast majority of commercial PSTN offerings use SIP trunking as opposed to IAX trunking. Open up sip.conf and add the following statement in your [general] context:

register =>

The phone number goes first, followed by a colon and then your password. The rest is pretty self-explanatory. The very last "ipcomms" at the end after the slash is the destination SIP context, which you will add below like so:


That's all the SIP configuration needed! At the Asterisk CLI, type sip reload to reload SIP and register with IPComms.

Now, calls from IPComms will come into your Asterisk switch, but they still need to be routed. Add the following to extensions.conf:

[from-ipcomms] ; This is the incoming context for IPComms SIP calls
exten => 2125551212,1,Progress()
	same => n,Set(Var_TO=${CUT(CUT(SIP_HEADER(To),@,1),:,2)}) ; this works with SIP but not PJSIP. PJSIP should go to extension of DNIS.
	same => n,Gosub(pstn-us-verify,s,1)
	;same => n,Answer()
	;same => n,SendDTMF(1) ; for Google Voice
	same => n,Gosub(mfer,start,1(2125551212))
	same => n,Goto(from-pstn,s,1)

exten => s,1,Goto(phreaknet-dialtone,${mainphreaknetdisa},1)

First, change the number of the extension to your 10-digit DID.

Since we're seeing 2125551212 in a couple places, you may want to define a global variable called IPCOMMSDID1 in your [globals] context and use that in lieu of the number itself throughout your dialplan. However, you won't be using the DID itself very much. Unlike network calls, calls to your DID are all coming to the "same extension". Your internal extensions can't be dialed directly. Thus, you'll need to have this go to your node's DISA if you want external callers to be able to reach a specific destination of their choosing; your extensions can't be directly dialed from the PSTN. (This is because you only have 1 PSTN DID; if you have an NNX-X on PhreakNet, you have 1,000 DIDs!)

The second and third-to-last lines here are optional and can be omitted. The SendDTMF is necessary if you have a Google Voice number forwarding to one of your DIDs. This is not an IPComms thing at all; due to the way answer supervision works with Google, you will usually need to provide progress, wait a few seconds (about 4), then Answer it, then send DTMF 1. This context omits the Wait statement, but you may need to add that if Google Voice calls forwarded to your IPComms DID aren't getting answered properly by Asterisk.

Finally, the mfer subroutine MFs your IPComms DID. This is a slight nostalgic touch; while we think it's a nice addition for PSTN callers to hear on their way in, you can omit this (and certainly should if you don't have the required subroutines, which are available elsewhere in this documentation).

Finally, we have chosen here to send the caller to the [from-pstn] context, which in turn directs the caller to the node's DISA (you may need to adjust the extension from DTXB to the one your DISA uses). We have a separate [from-pstn] context for the simple reason that you can have multiple DIDs, each of which should have its own incoming context as the processing needed initially is slightly different. However, you can then send callers to a common PSTN context and from there onward to wherever you'd like them to go. This way, if you decide you'd like to have PSTN callers enter, say, an IVR instead of a DISA, you only need to change the reference in [from-pstn], as opposed to within each of your DID contexts.



CallCentric used to have a free DID plan that offered 2 numbers and 3 incoming channels each with unlimited usage. Unfortunately, they discontinued it in December 2018 for new users and for existing ones in February 2019. However, they still do have a (quite reasonable) $1 DID plan that gives you 1 DID with 2 incoming channels. While that means you only get 2 incoming channels now, instead of 6, and you have to pay for it, it's not by any means a bad deal!

If interested, register with CallCentric. If you opt for the $1 per month DID, select their "Dollar Unlimited" plan. Just like with the old "Free DID" plan, numbers are from New York state area codes 631, 845, and 914. Unlike IPComms, you do get to choose the area code you want; however, the actual number itself is still randomly assigned to you.

Note that CallCentric has a 911 requirement for users located in the US. If you don't need 911 on your switch, you will need to indicate when registering, that you are not located in the United States. Otherwise, you will be forced to pay an additional free for 911 service. If you do need 911 service, make sure to provide accurate location information.

Required Contexts

You will need to add the following line to your [general] context in sip.conf:

register =>

First is your 11-digit internal CallCentric number. This is not the number of your DID, if you have one! Each account is assigned an internal CallCentric extension in the pseudo-area code 777. This allows you to dial other CallCentric users for free (should you want to).

Now, beneath all that, add the following to sip.conf:

























Yes, unfortunately, you really do need all of that! You can see more about this on CallCentric's Asterisk configuration page. If you use PJSIP instead of SIP, due to its better architecture, the configuration is actually less verbose in this case.

Now, in extensions.conf, add the following:

[from-callcentric] ; This is the incoming context for CallCentric SIP calls
exten => callcentric,1,Progress()
	same => n,Set(Var_TO=${CUT(CUT(SIP_HEADER(To),@,1),:,2)}) ; this works with SIP but not PJSIP. PJSIP should go to extension of DNIS.
	same => n,Gosub(pstn-us-verify,s,1)
	;same => n,Wait(4) ; uncomment these if you need support for Google Voice
	;same => n,Answer()
	;same => n,SendDTMF(1)
	same => n,Gosub(mfer,start,1(2125551212))
	same => n,Goto(from-pstn,s,1)

Again, if you want to use the MFer to MF your DID's number on incoming PSTN calls (i.e. inpulsing), you will need to change the argument to reflect your CallCentric DID's number. Otherwise, the same comments about SendDTMF, etc. as with the IPComms configuration apply here also.

For those curious, Var_TO here contains your DID's number, which allows you to send different DIDs to different contexts if you have multiple.


Toll free numbers can be quite useful, even for personal usage. For one, collect calls from payphones these days are a joke. It's much cheaper to have your own toll-free number you can call for use when you don't have easy free access to long distance, such as from airport courtesy phones, payphones, or residential lines without long distance.

(By the way, all PhreakNet members have toll-free access to the network. Just make sure your PIN is set up in your account so you can access TSPS from anywhere, anytime, at no charge to you.)

Many providers offer toll-free inbound services. However, as phone people, there are two important criteria you may want to use when selecting a provider. One is whether payphone calls are accepted (calls with ANI II of 27 or 70, among other sometimes restricted ANI-IIs). If this is a prime reason for getting a toll-free number, then it's no good to get a number that doesn't support payphone calls. Second, most providers don't pass along ANI II/OLI, but some do, which can be a nice perk to have. If you are setting up an ANAC, for instance, you will definitely want this — otherwise, it may not be as useful to most people.

Below, we compare many of the most popular providers and break it down. If you have further information to contribute to this table, please let us know! (You can drop a note to the mailing list or file a Docs ticket here.)

Finally, a word of warning: most payphones are NOT programmed to consider 833 as a toll free area code. Therefore, even though 833 often has some of the cooler numbers, you are encouraged to avoid purchasing any 833 numbers because in practice, 833 is not toll free. Stick to 800, 888, 877, and 866 if possible, as these are universally recognized to be toll-free area codes. Note that some providers may charge more for 800 (if available) or vanity numbers.

Provider NRC MRC Per Call Per Min Payphone Calls OLI Notes
Flowroute $1.00 $1.00 $0.00530 $0.00975 Yes, possible surcharge Yes Require your driver's license and bank statement to sign up. $40 min. deposit.
Twilio ? $2.00 $0.00 $0.0130 Yes! Yes
Callcentric $3.95 $3.95 $0.00 $0.0198 Yes! No Max 3 included channels $0.00 $0.99 $0.00 $0.0190 Yes! No
Vitelity ? ? ? ? Yes No
Skyetel $1.00 $1.20 $0.00 $0.0175 ? ?
Plivo $0.00 $1.00 $0.00 $0.0135 Yes ?
AWS Chime $0.00 $1.00 $0.00 $0.011910 Yes ?
Telnyx $1.00 $1.00 $0.00 $0.0150 No! Yes Make sure to enable 183 Ringback
Anveo Direct $0.00 ? $0.00 $0.0160 $0.864/call ?
DIDForSale $1.00 $1.00 $0.00 $0.0150 No ?
DIDLogic $0.00 $0.99 $0.00 $0.0180 ? ?
SignalWire $0.00 $0.75 $0.00 $0.0145 ? ?
BulkVS $1.00 $0.14 $0.0020 $0.0055 No! No Low cost option but also least versatile/useful. CNAM is not optional. $25 min. deposit.
Global Call Forwarding $0.00 $0.83 $0.00 $0.04 ? ? Minimum $12.95 per month ($12.95 with 303 min.)

Outgoing Calls


NerdVittles is currently sponsoring a Skyetel promotion. They are offering $50 of credit for anyone interested in trying the service. Here's how to take advantage of it on your server!

Why are we recommending Skyetel? Well, apart from the fact that they're offering $50 free credit to us NerdVittles geeks, the service is extremely reliable and we think you'll really like it! It's fast and easy to use. Their customer service is really the best in the industry (we're serious, instant chats are really instant and realtime, and contacting support has about a 5 minute turnaround or less, on average!).

A few tips before getting started:

  1. You will want to use your Skyetel trunk only for outgoing PSTN calls! There are companies that offer DIDs that are free or very cheap, which will provide you with unlimited free incoming PSTN calls. All Skyetel calls (incoming and outgoing) are charged and will use credit! Don't waste your minutes on incoming calls. We will help you set up a message that instructs callers to hang up and dial one of your Free DIDs. Skyetel allows you to set outgoing caller ID, so you may choose to set it to that of your free incoming DIDs as well.
  2. Toll-free calls are free (this isn't listed in the rates), but don't make test calls using this service and don't use it to war-dial! You will get charged extra for calls that are extremely short!
  3. Ensure you having meaningful caller ID sent out. By default, it is whatever it is when you reach the Dial statement. No verification is done and your call will go through, but you will be billed extra if your caller ID is blatantly meaningless (i.e. 0 or 1212 as opposed to an 11 digit US PSTN number).
  4. Don't get more than one phone number. You only get $50 of credit, and each number is a $1 one-time purchase, plus $1 per month. You won't be giving your Skyetel number out to anybody anyways. No point in wasting your credit in getting unnecessary numbers. You can use Skyetel to place an UNLIMITED amount of calls (there is no channel limit) using your number; only minute-by-minute usage is counted and billed.

So, what's the catch? There must be one, right?

Of course there are! Two, really!

  1. You need a valid US mobile phone number to verify your account. Google Voice numbers don't work. Not everyone has or wants a mobile, so this could be a problem for some. You don't need it afterward, though, so you can always ask a friend, as it's a one-time need.
  2. You need a debit or credit card to verify as well. Nothing is billed, but you do need a valid card. If you don't have one, you can also use a bank savings card or temporary card — i.e. use cash to buy a $5 VISA gift card and use that. Don't worry; auto-billing is disabled by default and you can remove your card immediately afterward.
NerdVittles Promotion
  1. Since only NerdVittles readers get this offer, not just anyone signing up for Skyetel, you need to fill out this prequalification form. When you sign up for Skyetel in a second and ask for your account to be credited, they'll make sure your information is entered in here. You must accurately enter all info. It must match the Skyetel signup form, which verifies everything.
  2. Sign up for Skyetel. You'll see a special link to do this once you complete step #1. Enter in all the same info as in Step #1. If the email address doesn't work, try a different one. Again, mobile numbers here will not work for verification; keep that in mind. You'll also need to enter your debit/credit card # at this point.
  3. Contact support. Once you're logged into your account, in the vertical menu, you'll see "Get Support" at the very bottom. Explain you are using the NerdVittles promotion so they know to credit your account with $50.
Skyetel Configuration
  1. In the vertical menu, go to "Phone Numbers" and find a number. You can search by NPA-NXX. It can be in your NPA or not, your choice. Search around for one you like, as this is what your outgoing caller ID should be.
  2. Once you've bought a phone number, go to the number's Settings, then go to Features. Disable "Caller ID" and "Block Spam Calls". Why? These features cost extra, so turn them off so you can save as much as possible for your outgoing minutes. None of the features should be active, like so:
  3. In the same dialog, go to "General" and change SIP Format to the second option, 1NPANXXXXXX.
  4. Now, you'll need to set the Endpoint Group. In the vertical menu, choose "Endpoints", which is right above "Phone Numbers".
  5. When the submenu for Endpoints pops up, choose "Endpoint Groups".
  6. Click "Add Endpoint Group".
  7. Fill in the text fields with the right information, using "skyetel1" as the Name and Description, as shown:
  8. For IP address, enter your Asterisk server's public IP address. For port, enter your SIP port. If you have changed your bindport in sip.conf from the default, you need to use that port.
  9. Click "Add".
  10. Now, go back to "Phone Numbers" → "Local Numbers" and edit the settings of the number you bought. Set the endpoint group to "skyetel1" like so:
Required Contexts

Add the following to sip.conf:


Now, on to extensions.conf!

Now, in order to dial out using Skyetel, add the following to your [internal-users] context:

exten => _1NXXXXXXXXX,1,Dial(SIP/SkyetelTERM/${EXTEN},45)
	same => n,Dial(SIP/SkyetelOR/${EXTEN},45)
	same => n,Dial(SIP/SkyetelCA/${EXTEN},45)
	same => n,Dial(SIP/SkyetelVA/${EXTEN},45)
	same => n,Hangup()

That's it! Now, you have over 50 hours of free long-distance at your disposal!

Google Voice

Instructions exist for implementing Google Voice on IncrediblePBX; however, this guide will target vanilla Asterisk users since instructions targeting native Asterisk are virtually nonexistent. Implementing Google Voice on vanilla Asterisk can be done, but we ran into many roadblocks along the way. The process is rather complicated, so buckle up!

PJSIP Dependencies

Google Voice requires the use of PJSIP, rather than SIP. You will need to install or reinstall Asterisk with the proper PJSIP modules already on your system. Fortunately, this is not as complicated as it sounds.

The first step is making sure PJSIP is installed as per the first part of these instructions. This assumes you have Asterisk 13.8 or later on your system.

  1. Navigate to your Asterisk source directory. If you don't know what it is verbatim, you can find it by typing the following:
    cd /usr/src
  2. Now, enter the Asterisk source directory, e.g.
    cd asterisk-13.24.0
  3. Now, enter the following (you may need to add " install" without quotes after if it prompts you to):
  4. Next, enter the following commands:
    ./configure --with-pjproject-bundled
    make && make install

Okay, now the tricky stuff is done! (Well, some of it anyways.) You don't need to copy any files over to your switch again. None of the configuration files or audio files were modified. However, Asterisk now has the PJSIP dependencies it needs loaded.

Google Voice Installation
Part 1

You will need to configure tokens for use with Asterisk. Navigate to this page; start at "Configuring GV Trunk with Motif in the GUI" to generate the oauth refresh token only. The script is preset to use the guide's Client ID; if you want to use your own then you will need to edit the script.

Part 2

Run the following from the shell:

cd /root
tar xvf gvsip-naf.tar
rm -f gvsip-naf.tar
cd gvsip-naf

This does take a while to install; don't worry!

You should at some point be prompted now to enter your Google Voice information; do so as instructed.

Part 3


sed -i 's|||' /etc/asterisk/pjsip_custom.conf
sed -i 's|||' /root/gvsip-naf/install-gvsip
asterisk -rx "core restart when convenient"

Helpful Resources:

Part 4

Up until now, these instructions have all been IncrediblePBX instructions that work without any hitches on vanilla Asterisk. The rest of this tutorial is more specific to vanilla Asterisk.

Required Contexts

Further Add-Ons

[public] context

Most spam calls to Asterisk systems are directed at the [public] context. Consequently, here is a relatively novel (but incredibly simple) addition to your dialplan that can significantly cut back on spam calls to your switch:

exten => _X!,1,Log(NOTICE, Unauthorized call: ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,System(iptables -A INPUT -s ${CHANNEL(peerip)} -j DROP)
	same => n,Hangup()

You shouldn't intentionally use the [public] context for anything, so it's a pretty safe bet to assume that any call arriving at that context is malicious. If you add the above to your dialplan, once a spam caller makes a call to any extension in your [public] context, he will be banned from your server forever — well, at least until the next time it reboots (see the iptables section in Appendix A for more on this).

Alternately, just set allowguest=no in sip.conf. If you are using PJSIP instead of SIP, anonymous calls are not allowed at all by default.

Of course, this context in extensions.conf alone is not enough. You will need to create a couple iax contexts in iax.conffor public calls as well, like so:


callerid="Guest IAX User"

Many spam calls arrive at the [guest] iax context, so make sure you include both of these. While IAX spam (or SPIT, spam over Internet telephony) is nowhere near as common as SIP spam, if you do get any spam calls, these contexts should take care of them. If you used our starter code for sip.conf in the "Getting Started" section, your server is automatically setup to reject unauthenticated SIP calls.

Previously, calls to your [public] or [guest] iax context would have been rejected by your server as being nonexistent. Now, the calls will be accepted, and the IP address of the server originating the call will be blocked with iptables. Neat! Of course, although most node owners will want to block calls such as these, you have no obligation to do so. One novel use for spam calls is using them for crosstalk. Rather than banning IP addresses who make spam calls, you could add incoming spam channels to a connection that allows live, unique crosstalk to be generated. Talk about novel! (If you're interested in this concept, see the "Crosstalk" section in Vintage Add-Ons.)


In addition to using the switchhook to flash to initiate a 3-way call, you can use Asterisk's 3-way calling capabilities (as opposed to your telephone's) to solve this:

Set the following in features.conf:

atxfernoanswertimeout = 60     ; Timeout for answer on attended transfer default is 15 seconds.
atxferabort = *1               ; cancel the attended transfer
atxfercomplete = *2            ; complete the attended transfer, dropping out of the call
atxferthreeway = *3            ; complete the attended transfer, but stay in the call. This will turn the call into a multi-party bridge
atxferswap = *4                ; swap to the other party. Once an attended transfer has begun, this option may be used multiple times

blindxfer => #1                ; Blind transfer  (default is #) -- Make sure to set the T and/or t option in the Dial() or Queue() app call!
disconnect => *0               ; Disconnect  (default is *) -- Make sure to set the H and/or h option in the Dial() or Queue() app call!
atxfer => *2                   ; Attended transfer  -- Make sure to set the T and/or t option in the Dial() or Queue()  app call!

Now, while in a call, you can press *2 to initiate an attended transfer. If you three-way a call in, you can use *0 to kick it out, provided you add the appropriate Dial() options.

Extending Your System: Shell Scripts

Custom shell scripts can greatly extend the power of Asterisk. For example, time and temperature scripts allow us to run the network time and temperature services. You can do an infinite number of things with shell scripts that interface with Asterisk.

Windows & Unix Encoding

When modifying shell scripts, encoding issues can cause problems.

If you are using Notepad++ on Windows, in the lower-right hand corner, you will see a display that says "Windows (CR LF)". This is perfectly fine for most files. However, if you create and/or modify a shell script in Notepad++, right-click this and change it to "Unix (LF)" before saving the file. Otherwise, the shell interpreter will be unable to parse your script.

In addition, make sure FileZilla is set to binary transfer mode, not ASCII transfer mode. We cover how to do this in more detail in the "Initial Configuration" section.

Speech Recognition: IBM Watson

The automated operators are powered by IBM Watson, which allows 100 free minutes of speech recognition per month. If you have a need for speech-to-text in your dialplan, IBM Watson is a highly reliable service that works quite seamlessly with Asterisk.

To get started with IBM Watson Speech to Text, visit its website.

The following shell script can be used for speech-to-text:


curl -X POST -u "apikey:APIKEY" --header "Content-Type: audio/wav" --data-binary @/var/lib/asterisk/sounds/stt/$1.wav "" >/var/lib/asterisk/sounds/stt/$1.txt
#Extract transcript results from JSON response
TRANSCRIPT=`cat /var/lib/asterisk/sounds/stt/$1.txt | grep transcript | sed 's#^.*"transcript": "##g' | sed 's# "$##g' > /var/lib/asterisk/sounds/stt/stt-$1.txt`
sed -e :a -e N -e `s/\n/ /` ta /var/lib/asterisk/sounds/stt/stt-$1.txt >/var/lib/asterisk/sounds/stt/transcripted-stt-$1.txt

rawtext=`cat /var/lib/asterisk/sounds/stt/transcripted-stt-$1.txt`
echo $rawtext

Save this script as /etc/asterisk/scripts/ Now, run the following from the command line:

chmod 777 /etc/asterisk/scripts/
mkdir /var/lib/asterisk/sounds/stt/

To use the shell script, add the following context to your dialplan:

exten => s,1,Set(stt=${UNIQUEID}.${ARG1})
	same => n,Record(/var/lib/asterisk/sounds/stt/${stt}.wav,3,30,qx)
	same => n,Set(text=${SHELL(sh /etc/asterisk/scripts/ ${stt})})
	same => n,Return(${text})

Now, simply call Gosub(stt,s,1(1)) whenever you need speech-to-text. The speech-to-text result will automatically be returned from the subroutine; you will need to use GOSUB_RETVAL to catch it. If you need speech-to-text more than once per call, your next subroutine call should be Gosub(stt,s,1(2)), and so forth. Otherwise, the otherwise-saved transcript results for speech-to-text conversions will be overwritten.

Evan Doorbell

You can load Evan Doorbell recordings onto your Asterisk system for private or public listening pleasure. If you want to do more than a few, however, it becomes quicker to perform batch operations when working with Evan Doorbell's large collection of recordings. Here is one method you can use to make your life easier.

Special thanks to Anthony Hoppe for the Linux scripts and many of the suggestions used here. The Excel instructions and VBA module were added by Naveen Albert.

    Part A: Downloading Files

  1. First, navigate to the page on Evan Doorbell's site containing the audio files you wish to download.
  2. Copy the entire table; the FLAC column is most important, but be sure to include all rows and all columns. If there are multiple tables, don't worry about omitting the text inbetween each table.
  3. Paste into Excel. If there are multiple URLs in a single cell, don't worry; Excel should automatically put each into its own cell.
  4. In Excel, press ALT+F11
  5. Microsoft Visual Basic for Applications should open. Click "Insert", then click "Module".
  6. Paste the following into the empty text window that appears:
    Public Function GetURL(c As Range) As String
        On Error Resume Next
        GetURL = c.Hyperlinks(1).Address
    End Function
  7. Close the VBA window and return to your table in Excel. Save the file with the .xlsm file extension (since the spreadsheet now contains a Macro). Delete the column containing the MP3 URLs. In the leftmost available column to the right of your table, click in the formula bar for the cell whose row contains the first FLAC URL. Paste the following into it: =GetURL($A1) — make sure you change A1 to the cell address containing the FLAC URL. The $ before the A should be kept, as it prevents Excel from automatically changing the column as you drag the formula down.
  8. Now, drag the formula to the bottom of the table so that all rows in that column are populated with the plain text full URL of the hyperlink in the column containing the FLAC URLs copied and pasted from the Evan Doorbell site.
  9. Now, select the entire column containing the plain text URLs (a down arrow should appear when you do this).
  10. Paste this into a blank text file using Notepad. Save as ed.txt
  11. You now have a text file containing the full hyperlinks for all the Evan Doorbell FLAC (high-quality) recordings on a particular page! All that's left now is to actually download them. Here, we will move from Windows to Linux:

  12. Create a new folder on your Asterisk machine by typing mkdir ed
  13. Transfer the text file you just saved in Windows to the ed directory on your Asterisk machine. You can use an SFTP client, like FileZilla, to do this. Or, if it's easier, transfer the file to your web server and download the file directly to the ed directory on your Asterisk server.
  14. Once the text file is saved on your Asterisk machine in the new folder, run the following commands:

  15. cd /root/ed/
    wget -i ed.txt
    Linux is now going to download all the files whose URLs were in that text file. Depending on how many there were, this could take a very long time. Now might be a good time for a coffee break (or a soda break, if you don't drink coffee).

    Part B: Converting Files

  16. Once the files have finished downloading, delete /root/ed/ed.txt by running rm /root/ed/ed.txt
  17. Create a new shell script on your Asterisk machine in the /root/ directory:
    files=($(ls -1 -p $1 | grep -v /))
    mkdir $path/originals
    for f in "${files[@]}"
            echo "Working on file $f"
            length=$(soxi -d $path/"$f")
            fn=$(basename "$f")
            sox -v 0.68 $path/$f -r 8000 -c 1 --type ul "$path"/"$fn".ulaw
            mv $path/$f $path/originals
            echo "$fn" $length >> $path/durations.txt
    If you do this in Windows using Notepad++, be sure to change the encoding in the lower right to Unix (LF). Change the file permissions in Unix to 777.
  18. Save this as (full path is /root/
  19. If you have HD VoIP sets that support G.722, you may want to use the following script instead:

    mkdir originals
    for f in *.flac
            mv -v "$f" "${f// /_}"
    for f in *.flac
            echo "Working on file $f"
            ffmpeg -i "$f" -filter:a volumedetect -f null /dev/null > output.txt 2>&1
            maxVol=$(grep max_volume output.txt)
            diffVol=$(echo "-14 - ${maxVol:53:4}" | bc)
            fn=$(basename "$f")
            ffmpeg -i "$f" -ac 1 -ar 16000 -acodec g722 -filter:a "volume="$diffVol"dB" "$fn".g722
            mv "$f" originals
    		length=$(soxi -d $path/"$fn".g722)
            echo "$fn" $length >> $path/durations.txt
    Save this as — note that you only need to use this script or the other script. Pick one!
  20. Now, run the following commands, waiting for each script or command to finish before proceeding:
    rm /root/ed/ed.txt
    chmod 777 /root/ (replace _ulaw with _g722 if you're using the second script)
    /root/ /root/ed/ (replace _ulaw with _g722 if you're using the second script)
    mv /root/ed/durations.txt /root/
    rm -rf /root/ed/originals/
    mv /root/ed/ /var/lib/asterisk/sounds/en/custom/playback/ed/production/ — adjust path as needed, but moves these recordings into an Asterisk-ready playback location
    tar -czvf production.tar.gz /var/lib/asterisk/sounds/en/custom/playback/ed/production/ — compresses recordings into an archive for backup purposes (SFTP this to your local PC when done) — adjust path as needed
  21. Note: If you get errors and durations are not sent to the text file, run first to get the durations, then use to cut the size of those .wav files in half by turning them into .ulaw files.

    Part C: Metadata

  22. If you're copying and pasting raw recordings, with multiple parts per segment, it gets messy. You'll need to clean up the metadata, which can get involved. We recommend concatenating the tape number followed by the year in parentheses, and then the description using new line breaks where appropriate, all in one cell. You can use the format CONCATENATE(CELL1,CHAR(10),CELL2,CELL3,CHAR(10),CELL4) to do this. CHAR(10) adds a line break to the concatenation. There's no one size fits all formula for this. You'll need to create a separate column and combine several columns, like this: =CONCATENATE("Tape ",$D2," (",$G2,")",CHAR(10),$H2). Make sure that WRAP TEXT is on for cells where you combine data that includes line breaks.
  23. Add a new column to the right of the column containing the raw URL, titled File Name, and fill in the first cell with =LEFT(TRIM(RIGHT(SUBSTITUTE($D2,"/",REPT(" ",LEN($D2))),LEN($D2))),FIND(".",TRIM(RIGHT(SUBSTITUTE($D2,"/",REPT(" ",LEN($D2))),LEN($D2)))&".")-1) — this column should now be populated with the raw names of the files, excluding the directory path.
  24. Now, leaving at least one blank column to the right of your table, open durations.txt and copy and paste it into your Excel sheet.
  25. Select what you copied and pasted into the sheet and go to Insert → Table. Make sure "This table has headers" is unchecked.
  26. Excel will add a header. Rename the column Name/Length. If you didn't start in Row 1 and the table shifts down one cell, select the entire table, hit CTRL+X to move the table and move the entire table one row up. This table should be aligned exactly with the table to the left.
  27. Now, add a new column to the right of your second table titled Length, and fill in the first cell with =RIGHT($K2,LEN($K2)-FIND(" ",$K2)) — this column should now be populated with just the lengths of each recording.
  28. Now comes the key part. Sort both tables — sort the table on the left by File Name and sort the table on the right by Name/Length.
  29. Both tables should have metadata that is now aligned with each other. Go down the table and make sure the contents of File Name match the beginning of the contents of Name/Length on an exact row by row basis (obviously the latter column contains the durations in the same column as well, just ignore this). If all rows in both tables match, you're good to go. If not, you have duplicate entries somewhere. This could be because some Evan Doorbell pages may have duplicate recordings listed. Delete any duplicates and resort and scan the table again until both tables match.
  30. Now, add a new column in the first table titled Duration. Copy the contents of the Length column in the second table. Right-click in the first row under the Duration heading and choose the second option, which should have a "123" icon — this is paste by Values. This copies the durations of each file as text, rather than formulae.
  31. That's it! You now have all the metadata, including the pure filename and length of each recording, in one Excel table! At this point, you can discard the table on the right (the second, smaller table), though it really doesn't hurt to keep it around.

    Part D: Dialplan

  32. Somewhere in your dialplan, perhaps in a separate included file, add the following:
    exten => _[0-9A-Za-z].,1,ControlPlayback(custom/playback/ed/${EXTEN},60000,9,7,#,8,0)
    	same => n,Hangup()
  33. The above dialplan syntax assumes the last 4 digits are sent to an exchange context. If so, in that context, simply include => evandoorbell. If not, you can always add the following in that exchange instead: exten => _X!,1,Goto(evandoorbell,${EXTEN:-4},1)
  34. Insert a column as the leftmost column in your leftmost Excel table titled #. This will contain the dialable extension number of your recordings. Go through all the recordings you downloaded and assign logical extension numbers. This should be done in chunks, i.e. recordings belong to a series should have consecutive ascending extensions. This will require some thoughtful planning and time.
  35. Add a new column in your leftmost Excel table, titled Dialplan. In this column, we will dynamically generate the dialplan code needed for all these recordings. You do have the filenames in a separate column now, so it would be far easier now than otherwise to manually copy and paste these into new extensions, but that's still a lot of work, don't you think?
  36. In the first table cell in the Dialplan column, populate the cell with =CONCATENATE("exten => ",$A2,",1,Goto(edrecording,production/",$E2,",1)"). A2 references the column containing your extension numbers and E2 references the column containing your raw file name. Change these if that is not the case. Otherwise, the entire column should be populated with dialplan code!
  37. Now, sort the table by the # column so your extensions are in ascending order.
  38. Copy and paste the cells in the Dialplan column into your Asterisk dialplan file.
  39. Save your dialplan file, and SFTP it over to your Asterisk server if necessary. Reload your dialplan and you are all set!

Fortunately, getting this all into the Directory is the easy part — just use the mass upload feature!

Appendix A: Helpful Supplemental Tools

Using SoX to convert audio files

Although Asterisk can natively play WAV files and MP3 files, these tax the system more heavily than do files designed specifically for use with Asterisk. μLaw (pronounced mu-law, or, frequently but erroneously, u-law), is the codec used in the PSTN (high-quality) and it is what we recommend using for audio files as well. The codec used for mobiles, gsm, is more compressed but poorer quality. Space will not be an issue: WAV files that are 50MB+ can be compressed to just a few MB so we recommend using μLaw files unless you have a good reason to use a different audio codec.

You will need to use the sox utility to convert audio from WAV files to μLaw files. The utility is available for both Linux and Windows so you can either convert the files directly on the server or do them on your local PC and then upload them to the server. If you are working with particularly large WAV files, it is recommended you use the latter approach, as otherwise you will have to transfer extremely large WAV files to the server, only to highly compress them and discard the large WAV files.


The general format for converting WAV files is as follows (from the regular shell, not the Asterisk CLI):

sox input.wav --rate 8000 --channels 1 --type ul output.ulaw lowpass 3400 highpass 300

This optimizes your audio specifically for use with telephone systems. Although much information is lost, the end-result generally still sounds quite good.

You will need to adjust input and output respectively to the full path to your audio files. If you use an FTP client like FileZilla to transfer the audio file cbcad.wav to /var/lib/asterisk/sounds/en/custom/, you will need to type the following instead:

sox /var/lib/asterisk/sounds/en/custom/cbcad.wav --rate 8000 --channels 1 --type ul /var/lib/asterisk/sounds/en/custom/cbcad.ulaw lowpass 3400 highpass 300

As you might imagine, this can become quite tedious if you are using this for a large number of audio files. You can run the following script from the command-line to convert all WAV files in a folder to μLaw files:

for f in *.wav; do
sox $f --rate 8000 --channels 1 --type ul $f.ulaw lowpass 3400 highpass 300

The script above must be run in the same directory as the WAV files in question. Navigate to the directory first, e.g. cd /var/lib/asterisk/sounds/en/custom/wav/

The only problem now is all files will end up with the file extension .wav.ulaw, which is not what you want. You can rename the output file using the MV command:

mv cbcad.wav.ulaw cbcad.ulaw

You can integrate this into your script in such a way that the file extensions are truncated first before the sox command is run, so that way files do not manually need to be renamed afterward, but that is beyond the scope of this documentation.


  1. To use SoX on Windows, download the latest release of SoX for Windows. In this tutorial, we will go with the ZIP file option as opposed to the EXE download.
  2. Next, extract the folder to a directory of your choosing, say, the Documents folder at C:\Users\%username%\Documents\.
  3. Now, rename the folder in which the sox files are contained (probably sox-14.4.2-win32 to, simply, sox.

That's it! Now, as on Linux, you can use sox from the command line by using the command line. To do this, open Command Prompt. Then type cd Documents\sox\ (assuming by default you were in your user profile directory). Now type sox /?, and you should see a list of options. You can try using the following command to manually convert files:

C:\Users\%username%\Documents\sox\sox input.wav --rate 8000 --channels 1 --type ul output.ulaw lowpass 3400 highpass 300

Of course, it is much easier to do a batch convert of files. By creating a simple batch script, you can avoid having to use the command line at all to use SoX!

First, created a folder called bin in your sox directory. Its full path should be C:\Users\%username%\Documents\sox\bin\.

Now, open a text editor and copy and paste the following into it:

@echo off
cd C:\Users\%username%\Documents\sox\bin\
forfiles /S /M *.wav /C "cmd /c rename @file @fname"
for /f "delims=|" %%f in ('dir /b "C:\Users\%username%\Documents\sox\bin\"') do "C:\Users\%username%\Documents\sox\sox.exe" "C:\Users\%username%\Documents\sox\bin\%%f" --rate 8000 --channels 1 --type ul "C:\Users\%username%\Documents\sox\bin\%%f.ulaw" lowpass 3400 highpass 300

You will need to adjust the paths accordingly for your system.

Save the file with a .bat extension, e.g. sox.bat in a convenient location (the sox folder would make sense).

Now, to use your script, simply copy the WAV files you need converted to C:\Users\%username%\Documents\sox\bin\. Then, double-click sox.bat to run the script. The script will automatically truncate the file extensions of all the wav files and then convert all of them to μLaw files. Unlike with other cases in which the original WAV file was left intact, your WAV files will still be left intact but the file extensions will be missing. If you wish to keep the WAV files, you should copy, rather than move the WAV files into the bin directory before running your script. Then, you can simply delete the extension-less files afterward and copy the μLaw files over to your Asterisk server.

Using iptables manually

You can manually create iptables rules with or without fail2ban for granular control over your firewall. We recommend configuring and using fail2ban if possible, but you may wish to add a few manual rules from time to time.

To view your iptables rules at any time, enter the following:

iptables -L -n -v

To view how many times each rule has been used (to get an idea of which IP addresses are spamming you the most), enter:

iptables -nvL --line-numbers

of course, the above will only work if you already have a rule blocking an IP address.

There are a few different types of rules you can use. This one blocks a particular port:

iptables -A INPUT -p udp --destination-port PORT -j DROP

For example, to block port 5060 (if you changed your SIP bindport), replace PORT with 5060.

To block a specific IP address, use the following:

iptables -A INPUT -s IP -j DROP

if you change your mind, you can easily delete the rule:

iptables -D INPUT -s IP -j DROP

You can also block entire ranges of IP addresses by specifying the subnet. By default, iptables -A INPUT -s -j DROP is the same as iptables -A INPUT -s -j DROP.

Hence, to block all of 192.168.1.*, you would enter:

iptables -A INPUT -s -j DROP

To block all of 192.168.*.*, you would enter:

iptables -A INPUT -s -j DROP

To block all of 192.*.*.* (often used for blocking entire countries), use

iptables -A INPUT -s -j DROP

If you notice spammers using multiple similar but different IP addresses, you can use this method to effectively and quickly block entire ranges of IP addresses.

Note that iptables rules are cleared when/if your server is rebooted. To save your iptables rules, you'll need to do the following:

cd /etc/
mkdir iptables

You only need to do the above once. Here's how to save your rules:

sudo iptables-save > /etc/iptables/rules.v4

To restore your rules after a reboot, do the following:

sudo iptables-restore < /etc/iptables/rules.v4

You'll want to run iptables-save after you make any changes to your iptables rules so that they're backed up. You can write a script to automatically restore your iptables rules upon startup if you wish.

If you changed your SIP bindport but are still noticing spammer activity in the Asterisk CLI, you can block their IP addresses as dicated above. Authentication attempts to your server itself (e.g. SSH connections) are logged in /var/log/auth.log. However, don't try to block every malicious IP address you see here — you'll only wear yourself out. Tools like fail2ban are designed to automatically keep on top of spammers and attackers. For further resources on defending your Asterisk system, see some of the VoIP resources on the PhreakNet Resources page.

Appendix B: Provisioning Server

Operating a provisioning server allows you to remotely provision and manage ATAs. These can be your ATAs or those of your users. A provisioning server can be involved and frustrating to set up initially, but is a good investment if you are supporting dozens of ATAs "out in the field", in which case it may be expected or desirable that you can remotely manage them.

At a high level, here's how the process works:

Provisioning can be used to deploy generic templates with a few settings or to control every aspect of ATA configuration, including SIP credentials. This is a secure and easy way to set ATAs up, since all a user needs to do is enter the provisioning endpoint.

Here are some general things / lessons learned to keep in mind:

In this guide, we'll be covering how to accomplish this using the LAMP stack: (Debian) Linux + Apache web server + MySQL/MariaDB + PHP. In particular, we'll be going through Apache-specific configuration using a free Let's Encrypt certificate. You could use a different application language or DBMS (database management system) fairly easily.

A good practice is to "bootstrap" ATAs on a config request via http, on regular port 80. This won't hand out any sensitive information, but good things to do here are tell the ATA how to request the https config, how to get the latest firmware, and configure a syslog server for debugging. Setting the resync/upgrade period to 1 minute or less will ensure the ATA immediately bootstraps and then moves on to stage 2, the https portion. Common to any https config is adding, inside a Directory block, the SSLRequireSSL directive, to require SSL.

This process differs by vendor, so we'll break it down in that manner.


SSLEngine on
SSLCertificateFile	/etc/letsencrypt/live/
SSLCertificateKeyFile /etc/letsencrypt/live/
SSLProtocol all
SSLCipherSuite ALL:@SECLEVEL=1 # TLSv1 or better
SSLInsecureRenegotiation on # required for some older ATAs
SSLOptions -StrictRequire +StdEnvVars
SSLVerifyClient require # you must use this in production
#SSLVerifyClient optional_no_ca # if you are just testing, use this
SSLVerifyDepth 1
SSLCACertificateFile /etc/ssl/private/grandstream.crt

The SSLCipherSuite directive restricts ciphers to those use in TLS 1.0 or better (e.g. SSLv2/3 are not permitteed).

+StdEnvVars will provide your application (e.g. PHP) with all the relevant SSL server variables. By default, these aren't available, since there are a lot. If you're using PHP, run something like print_r($_SERVER); to get an idea of the kinds of information available.

To do MTLS (that is, validate client certificates against the Grandstream CA), you'll need the Grandstream root CA cert, which you can get by contacting Grandstream support via a new ticket. is the endpoint of GAPS (Grandstream Automated Provisioning Server). The way it works is companies can provide Grandstream with a list of MACs and their provisioning endpoint, and Grandstream will redirect provisioning requests to thither, for those MAC addresses. This allows for truly "zero-touch" provisioning, since the ATA will automatically try to request a config from GAPS in a factory default state.

However, we won't be using GAPS in this case. (If you're using GAPS, I suspect you know what you're doing, since you're supporting thousands of ATAs, so what are you doing here?) is the actual endpoint for firmware upgrades, and should support HTTPS as well as HTTP.

Assuming you've provided a hostname for your provisioning server, Grandstream ATAs will make GET requests to the following endpoints on your server:

  1. /cfg000a000a00aa
  2. /cfg000a000a00aa.xml
  3. /cfg.xml
  4. /cfght802.xml - newer HTs only

We've used dummy MAC addresses here — what you'll see is the 12-digit alphanumeric MAC address, all lower case, as shown above.

The first request is for a the proprietary binary format.

The requests afterwards are for standard XML configs. The idea is that it'll try to get a MAC-specific config first, intended only for it, and fall back to a generic config template otherwise.

Newer Grandstream ATAs will also request something like /cfght802.xml, with the product number. The strange thing is that this config is only requested after cfg.xml, so the order isn't strictly most specific to most generic, because it flips around at the end. Thus, relying on this config may not be feasible.

Grandstream ATAs will request these configs (assuming no pre/post-fixes are defined) in this order, until they successfully receive a valid config they can use.

It's up to Apache and your application layer to route these and statically provide or dynamically generate and provide (e.g. using your database) the necessary config. There's no formulaic or "default" way to do this.

For MTLS, SSL_CLIENT_S_DN_CN will contain the MAC address, all uppercase, for newer Grandstreams. For older ones, a common name (e.g. VPN) is used instead. You should perform the stringest verification possible, e.g. make sure the DN_CN matches the request URI and the user agent, if possible. For older ATAs, you may only be able to ensure the user agent matches the request URI and ensure the client certificate presented validates against the Grandstream CA cert.

During testing, you'll likely need to factory reset your ATA — refer to the product manual for reset instructions.


SSLEngine on
SSLCertificateFile	/etc/ssl/private/cisco.crt
SSLCertificateKeyFile /etc/ssl/private/cisco.key
SSLProtocol all
SSLCipherSuite ALL:@SECLEVEL=1 # TLSv1 or better
SSLInsecureRenegotiation on # required for some older ATAs
SSLOptions -StrictRequire +StdEnvVars
SSLVerifyClient require # production
# SSLVerifyClient optional_no_ca # testing
SSLVerifyDepth 2  # no more than 2
SSLCACertificatePath /etc/ssl/private/phone_root_certificates
#SSLCACertificateFile /etc/ssl/private/phone_root_certificates/Sipura_Technology_Client_Root_Authority_1.crt

If you are using SSLCACertificatePath, you may need to run chown -R www-data /etc/ssl/private/phone_root_certificates/ to make the Apache user "own" the files, since those are loaded dynamically. Also, ensure you have the right files that this directive expects.

Cisco provisioning has the added caveat that Cisco ATAs will only handshake with Cisco certs. Thus, notice we're not using our regular Let's Encrypt certificate, but a Cisco-signed certificate.

Some good background about the process is available on the Cisco community forums. Instructions on generating your csr file are also available. Basically, you'll need to run the following:

openssl genrsa -out  2048
openssl req -new -key  -out 

The certificate signing request process nowadays is automated. Once you've run those commands, navigate to the Cisco portal and upload your CSR. You'll probably want a SHA1 cert, which they still offer as of now, but SHA256 is also available and better for newer ATAs (you could just grab both). The certificate will be valid for 1 year maximum.

Finally, you'll also need the Cisco root certificates to do MTLS and verify clients. You'll need to contact Cisco to get these.

There aren't really any defaults you should rely on for Cisco rules. Instead, you should explicitly provide full URLs, including the protocol and hostname and full request path. There are variables you can use in your rules that will aide in routing, such as $MA for the device's MAC address. See pg. 74 of this provisioning guide for a complete list of macro expansion variables.

That said, newer ATAs such as the SPA-112 may go in this order (depending on Profile Rules):

  1. /spa.cfg
  2. /spa000a000a00aa.cfg

Cisco provisioning rules work the opposite of Grandstream rules. Grandstream rules will start and go down the list until it receives a valid config, then stop. Cisco will start and go down the list, stopping only when it fails to get a valid config. Thus, rules should go from least-specific to most-specific, which is the opposite order of Grandstream rules. In your bootstrap config, keep this in mind. For permanent, MAC-specific, configs, it really only makes sense to provide one profile rule, since Cisco ATAs will attempt to request all of them, assuming they are all valid.

For MTLS, SSL_CLIENT_S_DN_CN will contain, among other things, the MAC address. SSL_CLIENT_S_DN_ST will contain only the MAC address, all lowercase. This is the case for all Cisco ATAs — they all uniquely identify themselves in their client certificates.

Cisco 79xx phones

The 79xx Cisco series is an older line from Cisco. In comparison to newer SIP hardphones, 79xx phones only supported (unencrypted) TFTP for provisioning, and they can't be provisioned or configured really any other way.

These phones support 3 protocols: SCCP (Skinny), SIP, and MGCP. So there are a few ways you could use them, in increasing order of functionality:

  • Use the deprecated, vanilla chan_sip
  • Use the core supported, vanilla chan_pjsip
  • Use the deprecated, vanilla chan_mgcp
  • Use the deprecated, vanilla chan_skinny
  • Use the deprecated, vanilla chan_sip with the usecallmanager patches (--cisco option to PhreakScript)
  • Use the community chan_sccp (--sccp option to PhreakScript)

Further Resources

Polycom SoundPoint 331

The Polycom SoundPoint 331 is a SIP hardphone. This guide focuses specifically on this model, but it generalizes to a large number of Polycom phones from this era.

This guide will allow you to bootstrap the phone, e.g. use the phone to allow configuration via the web interface, and then use the web interface to allow configuration using an automated dynamic provisioning method.

  1. When the phone is booting up (you cannot do when the phone has booted up), press Setup when it allows you to choose from Start, Setup, etc. Password is 456.
  2. Go to DHCP options and use the right-arrow to set from "Option 66 + Static" to "Static".
  3. Enter your provisioning server URL under Server Menu.

A Polycom phone will make requests for the following files:

  • GET /0004f2ffffff.cfg (if found) OR GET /000000000000.cfg
  • GET /000000000000-license.cfg (if found) OR GET /0004f2ffffff-license.cfg
  • GET /0004f2ffffff-directory.xml
  • PUT /0004f2ffffff-app.log
  • GET /SoundPointIPLocalization/English_United_States/SoundPointIP-dictionary.xml

Here, 0004f2ffffff represents the MAC address of the phone, which is all lowercase.

More details here:

The Polycom 331 only supports SHA1 certs, not SHA256 - otherwise, you may see something like: ParamParser|Parameter identifier is out of range: "14791"(14791).

4.0.15 is the latest UC firmware supported by the 331. Do NOT use the 4.1.x firmware as this only supports Lync, not SIP! (e.g. the firmware in the GitHub repo below is wrong.)

Helpful Resources