NPSTN Docs

npstn.us/docs

← Go Back  ||  NPSTN Home

Version 1.4.6 — (last revised 2019/09/27)


Contents

  1. Introduction
    1. History
  2. Requirements
    1. Hardware
    2. Software
  3. Getting Started
    1. Pre-Requisites
    2. Installing Asterisk
    3. Reserving an Office Code
    4. User Control Panel
    5. Boilerplate Code
    6. Initial Configuration
      1. asterisk.conf
      2. modules.conf
      3. sip.conf
      4. ATA Digit Map
      5. iax.conf
      6. extensions.conf
  4. Common Contexts
    1. Incoming Route
    2. Basic Contexts
    3. Global Variables
    4. Billing Subroutine
    5. Music on Hold
    6. City Dialtone DISA
      1. Macro
      2. GoTo Contexts
    7. Dial NPSTN
      1. Original Macro
      2. Subroutine
      3. Macro for Older Asterisk Systems
    8. Verification Subroutines
      1. [dtnpstn] patch
      2. Incoming Verification Contexts
      3. Outgoing Verification Contexts
    9. CNAM Subroutines
    10. NetCID
  5. Billing
  6. Standards
    1. Numbering Plan
      1. Case Study: NPSTNNA01
      2. Standard Numbers
  7. Exchanges
    1. Nodes
    2. Exchange Names
    3. PSTN DID Exchanges
      1. Allocation
  8. 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. Crosstalk
    14. Echo Test
    15. Silent Termination
    16. Live Feeds
    17. Loop Arounds
    18. ChanSpy Verification
    19. T1 Trunks
    20. Modems
  9. StepNet
    1. Background
    2. Discussion
      1. Discussion #1
      2. Discussion #2
      3. Discussion #3
    3. Proposal
      1. Draft 1: Retired Conception
      2. Draft 2: Current Conception
    4. Sounds
    5. Dialplan Code
  10. Automatic Operators
  11. Blue Box
  12. Operator Busy Line Verification
  13. C*NET Connectivity
  14. PSTN Connectivity
    1. Incoming Calls
      1. IPComms
        1. Registration
        2. Required Contexts
      2. CallCentric
        1. Registration
        2. Required Contexts
    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
  15. 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
  16. Appendix A: Helpful Supplemental Tools
    1. Using SoX to convert audio files
      1. Linux
      2. Windows
    2. Using iptables manually

Introduction

Given the rapidity with which NPSTN (NoveltyPSTN or NostalgicPSTN) grew in its infancy, 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 NPSTN nodes that experienced node operators and newcomers alike will find helpful.

This reference also provides a set of standards to which node operators are encouraged to adhere. Doing so will result in the best possible experience across the network for everyone.

The basis for this documentation was created by Dylan Cruz. This webpage — and the documentation itself — were largely assembled by Naveen Albert. The inspiration for this documentation stems from Brian Clancy.

History

Created and founded by Dylan Cruz, NPSTN rolled into its beta phase in August 2018 but quickly matured after that. By the beginning of 2019, NPSTN lay claim to approximately a dozen different nodes in several different countries (currently the US, the UK, Canada, and Norway). Conceived as an alternative and supplement to C*NET, NPSTN today is a fully-fledged VoIP telephone network for phone phreaks and phone collectors.

To understand in a nutshell what makes NPSTN unique, here are a few major differences between NPSTN and C*NET:

  • C*NET uses ENUM lookups whereas NPSTN uses custom APIs
  • NPSTN has a more "phreak-friendly" culture that is more tolerant of phreaking activities, i.e. tandem stacking
  • Far more DISAs exist on NPSTN and nearly all use city dialtones
  • Nearly 100% of the network sounds on NPSTN are pre-Precise Tone Plan sounds
  • While C*NET uses country codes, NPSTN uses only conventional 7-digit numbers
  • NPSTN features special numbers like 0, N11, POPCORN, etc.

Otherwise, the two networks are both very similar. Both are home to phreaks and collectors, Asterisk boxes, and electromechanical switches. Both use IAX trunking and both have redundant lookup systems. Many node operators are on both networks, and gateways exist between the two networks, as well as from the PSTN.

Though Dylan Cruz remains at the head of development on NPSTN, particularly the backend, there are other large contributors to the network as well. Naveen Albert has spearheaded improvements to webpages and documentation and has been responsible for the creation of a large amount of Asterisk dialplan code part of the network today, including centralized and automatic operator services. He is also the main developer on the BBS and backend terminal systems, including those used by NPSTN operators. Much thanks to Brian Clancy, a longtime C*NET node owner, who taught Dylan Cruz and Naveen Albert much of what he knew about Asterisk, encouraging the two to write clean, proper, well-documented Asterisk code. You could say he serves as a sort of experiential consultant. Brian has been a major help and resource to both throughout the entire process and NPSTN as it is would not exist without his help, advice, and Asterisk wisdom and expertise. All three have been a part of NPSTN almost since the beginning and constitute its "core development team", bouncing ideas off of each other (although this also takes place on the public NPSTN listserv), discussing network standards, and testing dialplan code.

In its current phase, the next major addition to NPSTN is StepNet, which will (to a limited extent) simulate PSTN tandem switching back in the day. Calls will be routed through NPSTN nodes in a geographically realistic way until they reach their destination. StepNet is a layer on top of NPSTN, and is an experience that can be toggled on or off. StepNet is currently in development, but you can peruse its related documentation here and keep up with the project as progress is made.

Update: StepNet in its first phase is already reality! You can make StepNet calls over NPSTN. The next phase in the project is completing StepNet calls through electromechanical switches, which will require more complicated routing.

Requirements

Hardware

To operate your own NPSTN 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 on C*NET and NPSTN 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 of the core servers on NPSTN 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. Initially, the main nodes on NPSTN were hosted by HiFormance 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. Fortunately, all of the NPSTN servers had been fully backed up and were soon operational with a new host.

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 NPSTN nodes are operated using Raspberry Pis.

Software

Asterisk PBX is a free open-source VoIP PBX solution that has "taken the telecom industry by storm". It is used by both C*NET and NPSTN 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 Asterisk 13. 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.

On the flipside, we don't recommend going much newer than Asterisk 13 either. Some operators are using versions as new as 15 or 16, though many problems have been reported with version 16 that forced some to roll back to 15; for instance, version 16 drops support for macros completely. While macros are considered deprecated and we are in the process of transitioning existing macros to subroutines and regular contexts, we feel it would be ill-advised to jump to such a new version when not much is offered in the way of newer or better functionality. Asterisk 13 is, as of this writing, a fully-supported release of Asterisk, so we recommend you stick as close to version 13 as possible. Asterisk 13.24, in particular, might be a good place to start, as earlier 13.x versions lack PJSIP support which may result in difficulties if you find yourself persuing additional add-ons to your system.

Getting Started

Pre-Requisites

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 is 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 NPSTN "mock billing system", which depends on accurate timekeeping by each node (more on this in the "Billing" section).

For those on Debian 9, our OS of choice for running Asterisk, here are some short and sweet instructions on how to change the time.

Essentially, it comes down to the following:

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:

sudo timedatectl set-timezone America/Chicago

The above would set your timezone to Central Time.

Now, verify the time is correct by executing timedatectl alone.


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

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)
    sudo apt-get install -y wget
  • curl — a basic tool for extracting the content of online documents (may already be installed)
    sudo apt-get install -y curl
  • sox — for converting audio files to the proper formats for Asterisk
    sudo apt-get install -y sox
  • mpg123 — for playing mp3 streams
    sudo apt-get install -y mpg123
  • dig — for querying DNS servers
    sudo apt-get install -y dnsutils
  • PHP — required for the pulsar (revertive-pulsing) add-on as well as speech-to-text
    sudo apt-get install -y php

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. We learned this the hard way, as the main NPSTN tandem was running on FreePBX, and Dylan had more than a few problems with it. When the core servers were migrated from HiFormance to VPSCheap, Dylan installed vanilla Asterisk as opposed to FreePBX - and for good reason. 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, much of which is tapped into on NPSTN.

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.

While providing a tutorial on how to install Asterisk from source is beyond the scope of this documentation, we can point you to some Asterisk documentation as to how to do it.

If you already have an Asterisk server for, say, C*NET, or your own personal VoIP network at home, you can use the same server and the same instance of Asterisk for NPSTN. You have total flexibility with regards to how your Asterisk system is set up. You can choose to reserve the same NPSTN office code as you have on C*NET or you may opt for an entirely different one. You can create different extensions for NPSTN or provide access to the same extensions that your C*NET numbers map to, it's up to you.

Reserving an Office Code

Although you can dial into NPSTN from C*NET or the PSTN using inbounds without configuring your server at all, at some point, you'll probably want to become a full member of NPSTN with your own NPSTN node so you can dial other NPSTN numbers directly. Doing so is incredibly simple!

First, go to the NPSTN Contact Form or call the NPSTN operator and ask to join the network; simply call one of our external access numbers (dial any number that is listed as "terminating to" DISA, from either C*NET or the PSTN, then dial 0). We ask that you be considerate and only claim as many numbers as you actually need and will use. A thousands-block (NNX-X, or 1,000 numbers) should be enough for most people just getting started.

No matter how many numbers you'd like to stake a claim on, check the NPSTN Directory first and make sure someone hasn't already reserved the number blocks you want. For an easier (but more technical) view, you can also check the NPSTN route table which lists all assigned thousand blocks. Note that individual number assignments are not listed in the route table. For example, the number POPCORN (767-2676) is in use and not available on NPSTN.

If you opt to fill out the form linked above as opposed to calling the NPSTN operator personally, please be sure to provide the following information:

  • Thousand blocks you wish to acquire (NNX-X not NXX-X)
  • Full Name
  • Email Address
  • Telephone Number (either C*NET or PSTN)
  • Server IP Address or FQDN/DDNS
  • IAX Username
  • What you will use your office code for
  • Exchange Name
  • Physical Location, including ZIP code if you are in the U.S.
  • CLLI

If you don't know what IAX usernames are, don't worry - they'll be covered in "Initial Configuration". Simply opt for the de facto "npstn" IAX username.

While IAX is the most common protocol used on NPSTN, we officially support IAX2, SIP, H323, and MGCP.

A note about CLLIs is warranted here: you may be familiar with how CLLIs are used on the PSTN — each central office telephone exchange has a unique CLLI. Multiple exchanges (NXXs) may be served off of one physical switch, and consequently, multiple exchanges may share a CLLI. A CLLI is a unique identifier for each switch. You have one CLLI per physical switch, not one per NNX-X or NNX. For our purposes, you can just make up a CLLI for use on NPSTN. Don't be hasty, though! You will need this later and you won't easily be able to change this later on. The CLLI, to be standards-compliant, should be NPSTN followed by 4 to 8 more additional characters briefly characterizing your switch's location and/or function. If you're drawing a blank here or haven't any ideas, NPSTNMS0 (master switch), NPSTNNA01, and NPSTNCFL01 (Central Florida 1) and NPSTNCFL02 are some examples of existing CLLIs on NPSTN. Create a CLLI that makes sense for your switch that matches the format above.

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. Note that the exchange name you pick does not have to coordinate with adjacent exchanges. As an example, 288 on NPSTN is BUtterfield8 while 285 is, not BUtterfield5, as some might think, but ATwater5.

Note:If you only need 10 numbers or fewer, we can host numbers for you. Contact us with the above information using this form and we can host you on the main tandem (FAirfax7 exchange) or another tandem.

User Control Panel

Regardless of whether you operate your own node or are hosted on another, you will need an account to access to the NPSTN User Control Panel. This is used to add directory entries and access any mock billing statements issued to you (more on this in the "Billing" section). Contact us with your name, ZIP code, city, and state to get this setup. We will email you your auth key (you cannot create an account yourself). Node owners should not add directory entries for others using their UCP key. All members should have their own auth key to access the UCP which they should use to add directory entries for themselves — and nobody else.

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 plug and play.

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.

You will need 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 NPSTN 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:

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, including changing your server's timezone to your local timezone!

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


Note: New to Asterisk? No problem — we're here to help! We'll set your NPSTN node up for you! Simply call the business office at 116 on NPSTN or (407) 564-4141 and we'll help get you set up.

Initial Configuration

At this point, we will assume you have a working Asterisk server and have reached out to get some numbers on NPSTN 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

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 (which Naveen prefers) or the "direct modification" approach (which Dylan prefers).

  • 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.
  • 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.

asterisk.conf

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:

[options]
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.

modules.conf

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

[modules]
autoload=yes
				

There are a number of VoIP protocols in use today, SIP 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 => chan_skinny.so ; Don't load skinny (tcp port 2000)  
noload => chan_mgcp.so ; Don't load MGCP (udp port 2727)
noload => chan_unistim.so ; Don't load unistim (udp port 5000)
noload => chan_ooh323.so ; Don't load ooh323 (tcp port 1720)
noload => chan_323
noload => pbx_dundi.so ; 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.

sip.conf

Here, you'll configure the login for your ATA (analog telephone adapter), if you have one. The SIP protocol, due to its popularity, is a common target for abuse, so it is important to have your options set to maximize security. Here is a good starting place:

[general]
bindport=5060
allowguest=no ;keep intruders out
alwaysauthreject=yes    ;make life difficult for scanners trying to find a way into your dialplan
nat=force_rport,comedia ;should make nat more secure
registertimeout=30
registerattempts=2
tos_sip=cs3      ; Sets TOS for SIP packets.
tos_audio=ef     ; Sets TOS for RTP audio packets.
threewaycalling = yes
transfer = yes
disallowed_methods=UPDATE
srvlookup=yes
				

While we won't explain all the options here, we will say 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 IAX), so this is where you'll register with them if required to do so. At this point, you just need the basic configuration template to get your ATA connected. For example:

[ATAs](!) ; template for all personal ATAs
context = from-internal
type = peer
host = dynamic
disallow=all
allow=g722
allow=ulaw
allow=alaw
allow=g729
allow=gsm
allow=ilbc
allow=g726
qualify = yes
insecure = port,invite
canreinvite = no ; don't allow RTP voice traffic to bypass Asterisk
relaxdtmf = yes
progressinband = yes

[ATAx1](ATAs)
defaultuser = ATAx1
secret = verysecretpassword
authid = ATAx1
callerid = "John Smith" <5550101>
				

The template above can avoid duplicated code if you have lots of ATA ports you want to connect to Asterisk. Obviously, modify the template for your own purposes. If you are hosting other users on your system, you'll probably want to specify the context on a per-ATA basis rather than generally for all ATAs.

SIP can be finicky about getting inband DTMF signals to transmit properly. The best options are SIP Info and RFC2833. This step can require some playing around. To manually set the DTMF mode, add the following option:

dtmf=rfc2833
				

This would set the DTMF mode to RFC2833. To change it to SIP Info, use the following instead:

dtmfmode=info
				

As said before, this can be finicky. Dylan has had the best luck with SIP Info and specifying it explictly in sip.conf. Naveen has had 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.

ATA Digit Map

For NPSTN, 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]|660S0|95[89]|10[2-9]xx|100xx|101xxxx|[2-9][2-9]xxxxx|9[2-9]xxxxx|1[2-9][2-9]xxxxx|1709xxxx|91[2-9]xxxxxxxxx|*[12]xxxxxxxxx)
				

The above dial plan can be explained as follows:

  • 0, for the NPSTN operator
  • N11 codes
  • 11N codes
  • 958 and 959
  • 10xxx dial-around codes
  • 101xxxx dial-around codes
  • NNX-XXXX dialing for NPSTN numbers
  • 1-NNX-XXXX dialing for US C*NET, plus 1-709-XXXX
  • 9-1-NPA-NXX-XXXX dialing for PSTN calls
  • *xx+NNX-XXXX for operator feature-codes

With the exception of 1-NNX-XXXX, 1-709-XXXX, and 9-1-NPA-NXX-XXXX in the plan above, all of the other rules are specifically for NPSTN.

Many aspects of this dial plan will not be used on a basic system, but it doesn't hurt to have them in your dial plan for future expansion so updating it later is not necessary. You can adjust this dial plan to your liking. For example, if you are not on C*NET and don't make any C*NET calls, you could eliminate that part and eliminate the need for a 9 to be dialed before PSTN calls. Of course, you will not be able to do either out of the box; see the C*NET and PSTN sections of this documentation for more information about C*NET and PSTN connectivity.

It is worth pointing out at this time that the digit map above does not allow dialing of international C*NET calls, which is typically done on a C*NET-only system by dialing the country code + the number or 011 + the country code + the number. You may need to be creative about structuring your digit map if international C*NET calling is a requirement. On Naveen's system, international C*NET calls are dialed as 110+. This would be entered as a rule by adding 110xxx., followed by another pipe symbol to separate it from the rule after.


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 an NPSTN DISA (logically, one on your system, but you could have it dial any DISA on NPSTN). Most DISAs on NPSTN 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'd like to have your ATA auto-dial a DISA, you can enter the following in the Dial Plan field:

(S0<:5551111>)
				

Obviously, change 555 to your office code.

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!

iax.conf

First, you'll need to configure your incoming IAX trunks so that NPSTN calls can successfully come into your Asterisk system. 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/.

To start with, make sure that you have some general code in iax.conf that applies to all of your IAX2 trunks. Here's a good starting point:

[general]
relaxdtmf=yes ; If your server has issues with DTMF this option may help
bandwidth=high
disallow=all
allow=ulaw
allow=alaw
allow=g722
jitterbuffer=no
tos=0x12
calltokenoptional=0.0.0.0/0.0.0.0
requirecalltoken=no
delayreject=yes ; for increased security against brute force attacks
autokill=yes
encryption=yes
				

Now, in order to give incoming NPSTN calls a route to your dialplan, add the following beneath that:

[npstn]
type=user
context=from-npstn
				

Here, [npstn] is the IAX username. If you chose a different IAX username that was not simply npstn, you will need to change the text in the [brackets] to the username you chose.

extensions.conf

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

Because you have more flexibility here and there are several options from which to choose here, we've made this a separate section but wanted to mention it here. See "Common Contexts" for some starter dialplan code as well as other contexts, subroutines, and global variables you'll want on your system.


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!

Common Contexts

Here is where you'll find all of the dialplan code you need to get started. Unless specified, it will go in extensions.conf.

Incoming Route

The following will allow NPSTN calls to enter your dialplan:

[from-npstn]
exten => _X!,1,GoSub(npstn-verify,${EXTEN},1)
	same => n,GotoIf($["${clidverif}"="11"]?:allow)
	same => n,GotoIf($[${GROUP_COUNT([email protected])}<5]?:reject)
	same => n(allow),Log(NOTICE, Incoming call from NPSTN: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,GoTo(external-users,${EXTEN},1)
	same => n(reject),Log(NOTICE, Incoming call from NPSTN rejected: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,Hangup()
			

The "5" only allows a maximum of 5 spam NPSTN calls. This way, if someone with malicious intentions tried to spam your node, only 5 calls would get through; all subsequent calls would get rejected.

The following will allow calls from your ATA to enter your dialplan:

[from-internal]
exten => _X!,1,Log(Local call from ATA: ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,GoSub(clunkcomplete,s,1)
	same => n,GoTo(internal-users,${EXTEN},1)
			

Sending external and internal calls to separate contexts will give you more flexibility in terms of call routing and private/public destinations. For now, we'll assume that all internal destinations are available to all internal and external callers. To create an internal-only extension, you could create it in a separate context (e.g. [KLondike5-private] that you then include in internal-users).

[internal-users]
include => local
include => long-distance
include => invalidincoming

[external-users]
include => local
include => invalidincoming

[local]
exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)
			

By creating the [local] context, we can dial locally numbers that have been allocated to us, rather than falling through to [dialnpstn] and doing a lookup and effectively dialing out using an IAX trunk and dialing right back in.

If you happened to own only 555-1XXX, instead of all of 555-XXXX, you would change this line to:

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

Now, if you dial, say, 555-2638, a match won't be found in [local], so the call will go out to NPSTN. Speaking of which, here is the code necessary to do just that:

[long-distance]
exten => 0,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _11N,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _N11,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => 660,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _95[89],1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _101XXXX,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _NNXXXXX,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
			

Basic Contexts

Creating a separate context for your exchange helps compartmentalize your code. For example, if you were the owner of the 555 exchange on NPSTN, you might choose to create a KLondike5 context:

[KLondike5]
exten => 1111,1,GoTo(dt,DTXB,1)
exten => 1212,1,GoTo(ATAx,1,1)
exten => 1213,1,GoTo(ATAx,2,1)
exten => _89XX,1,SayDigits(${EXTEN}) ; your NNX-89XX extensions will read back the last 4 digits of the number called
include => invalidincoming
			

Here we have included a couple extensions for you to get started. You can easily and quickly create a few more "typical" extensions for your node, in particular a milliwatt number and an echo test (instructions for these are available in the "Vintage Add-Ons" section. Inevitably, you'll create a great number of your own as time goes on, once your system is fully set up. Other extensions you created will go in this context. If you have more than one office code, you can create another context (e.g. [KLondike6]) and add the appropriate line to [local] (e.g. exten => _${OC2}XXXX,1,GoTo(KLondike6,${EXTEN:-4:4},1)).

We have included the _89XX example for newbies particularly (you'll probably want to remove this line when copying it to your node), though this may also serve as a friendly reminder to more seasoned Asterisk programmers. When you do pattern matching (i.e. specifying a range of possible extensions, in this case 8900-8999, you must start with an underscore. Otherwise, it will take it to mean the literal extension "89XX", which is probably not what you want! Any time you match on multiple possible numbers, start with the underscore!

You can change the name of that context to one that makes sense for your exchange, or call it something else altogether. Make sure to update all references to that context.

You can create a line of code for each ATA port you have connected to Asterisk. This code assumes you followed the naming conventions outlined in sip.conf earlier.

[ATAx] ; This common context avoids duplicated code by allowing one context to ring the appropriate line
exten => _s,1,Dial(SIP/ATAx${ARG1},${DTL},m(rbs)g)
	same => n,Verbose(ATA Line ${ARG1} Dial Status: ${DIALSTATUS})
	same => n,GoTo(ATA-${DIALSTATUS},1)
exten => ATA-ANSWER,1,Hangup
exten => ATA-BUSY,1,GoSub(linebusy,s,1)
	same => n,Hangup()
exten => ATA-CONGESTION,1,GoSub(allcktsbusy,s,1)
	same => n,Hangup()
exten => ATA-CHANUNAVAIL,1,GoSub(allcktsbusy,s,1)
	same => n,Hangup()
exten => _ATA-.,1,GoSub(linereorder,s,1)
	same => n,Hangup()
			

Putting your busy and reorder signals in a separate subroutine will allow you to easily change the audio files used for busy and reorder in one place:

[linebusy]
exten => s,1,Playback(custom/busy)
	same => n,Return()

[linereorder]
exten => s,1,Playback(custom/reorder)
	same => n,Return()

[allcktsbusy]
exten => s,1,Playback(custom/acbn)
	same => n,Return()

[clunkcomplete]
exten => start,1,Playback(custom/clunk)
	same => n,Return()
			

[clunkcomplete] plays quick "clunk" sound, usually after the caller has finished dialing. You can find an electromechanical clunk sound of your choosing to play here. If you don't have a clunk sound, or would rather not play one, instead of deleting the reference to [clunkcomplete], you can simply modify [clunkcomplete] so it is as follows:

[clunkcomplete]
exten => start,1,Return()
			

If you examine this, you will see that this subroutine does not do anything beside immediately return. However, if you wish to add a clunk when a user finishes dialing a call at some point in the future, you can easily do so by adding a Playback() statement before the Return.

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.

Now, let's create the CBCAD context:

[invalidincoming]
exten => s,1,Playback(custom/ccad)
	same => n,Hangup()
exten => _X!,1,Playback(custom/ccad)
	same => n,Hangup()
			

Obviously, adjust the path specified to that of your CBCAD (cannot be completed as dialed) recording.

Global Variables

At this point, you will want to set some global variables. Add the following to the top of your extensions.conf:

[globals]
clli=NPSTNXY00
zipcode=00000
allowdisathru=YES
allowpstnthru=YES
maindisa=5551111
npstnkey=YOURNPSTNKEY
OC1=555 ; "KLondike5"
DTL=60
			

NOTE: All of these variables will need to be tweaked to match your setup, as follows:

  • clli — this is the CLLI you chose for switch when reserving an office code. For example, if you chose NPSTNSTHTX01 (NPSTN South Texas Switch 1), then change this statement to clli=NPSTNSTHTX01.
  • zipcode — pretty self-explanatory: this is the 5-digit ZIP code in which your switch is physically located. If you have a hosted server, you can choose to set this to either the ZIP in which you personally live or the ZIP in which your server is located (if you happen to know). Note that you should set this to a U.S. ZIP code if you are located in the United States. If you do not live in the U.S., you should set this to 00000, which indicates "international switch" on NPSTN.
  • allowdisathru — this variable should be set to YES by default but can also take on the value NO. When set to YES, incoming calls from another node's DISA will be allowed to make outgoing calls via this node's DISA(s). If set to NO, incoming calls from another node's DISA will not be allowed to make outgoing calls from this node; instead, they will only be allowed to make calls to numbers on this node; other calls will be sent to intercept.
  • allowpstnthru — similar to the allowdisathru global variable; if set to YES, callers from the PSTN that arrived at this node from another node will be allowed to make outgoing calls from this node. If set to NO, callers from the PSTN that arrived at this node from anotehr node will not be allowed to make outgoing calls from this node; instead, they will be "trapped" here — only allowed to make calls to numbers on this node; other calls will be sent to intercept.
  • maindisa — this is the 7-digit NPSTN number of your switch's primary DISA (if you have more than one). Even if you don't have a DISA, set this variable equal to a number that has been assigned to that particular Asterisk switch. For instance, if you owned both 555 and 556, but the 555 exchange went to your home Asterisk server at one IP address and the 556 exchange went to your work Asterisk server at another IP address, if you are configuring this variable on your home server, it must be set equal to some 555 number that has been allocated to you. If you owned just 555-1XXX, it would have to be 5551000 through 5551999. Conversely, if you had both 555 and 556 on the same Asterisk server, and you had DISAs at both 555-1111 and 556-1111, you could set maindisa to either 5551111 or 5561111. But, as the variable's name indicates, it should be the number of one of your NPSTN DISAs on that server, if you have one; otherwise it should be a number on that server (like 5550000), even if it's not necessarily an active extension.
  • npstnkey — this is the 32-character auth id used to log in to the NPSTN User Control Panel. While multiple auth keys may be associated with numbers on one node if that node's owner is hosting others, this variable should be set to the auth key of the node owner. This value is used exclusively for authenticating with the NPSTN database for the mock billing system (more on this in the "Billing" section). The auth key's value is not used in any way; any valid auth key value is merely required in order to prevent spam from entering the billing system. Thus, a node owner using his auth key will not need to worry about it being used in any way in the backend for other users whom he hosts.

  • OC1 — rather than hardcoding the office code 555 throughout your dialplan, it is good practice to use a variable, in this case OC1, in place of the office code. You will need to change 555 to whatever your office code is. If you have more than one office code, you could continue the pattern with OC2, OC3, etc.
  • DTL — this is the length of your ringback audio in seconds. Unless you are using an audio file for your ringback signal that happens to be exactly 60 seconds, you will need to change this. Notice that the DTL variable is used in [ATAx]. If you hardcoded the duration of your ringback audio into the Dial statement and you were to change your ringback audio at some point and it happened to be shorter than your hardcoded ringback duration, the audio file would loop in the middle, which would be problematic. Best programming practices dictate that any repetitive meaningful numbers, like "ringback duration", be assigned a variable that can be changed in one place later if necessary. If you have multiple ringback audios and want to use a different one for different purposes, you could have DTL1, DTL2, etc. and specify the proper duration variable in the Dial statement.

You'll probably find yourself adding more global variables at some point in the future, but this is the bare minimum that you need to get started.

Billing Subroutine

The following subroutine is used as a hangup handler in order to generate a "toll ticket" for each NPSTN call — whether "local" or "long distance".

[npstnbilling] ; NPSTN NA 20190504 NPSTN Toll Ticketing; EXTEN= number dialed, ARG1= start time of call in seconds, ARG2= datetime that the call started
exten => _X!,1,Set(endtime=${STRFTIME(${EPOCH},,%s)})
	same => n,Set(duration=$[${endtime} -${ARG1}])
	same => n,GoToIf($["${ARG3}"=""]?:special)
	same => n,Set(type=direct)
	same => n,GoTo(request)
	same => n(special),Set(type=${ARG3})
	same => n(request),Set(request=${SHELL(curl "https://npstn.us/api/billing/?ani=${FILTER(0-9A-Za-z,${CALLERID(num)})}&callee=${FILTER(0-9,${EXTEN})}&type=${type}&duration=${duration}&key=${npstnkey}&clli=${clli}&year=${ARG2:0:4}&month=${ARG2:5:2}&day=${ARG2:8:2}&hr=${ARG2:11:2}&min=${ARG2:14:2}&sec=${ARG2:17:2}")})
	same => n(done),Return()
			

You must have the npstnkey and clli global variables properly set in order to use this!

Calls to this are done before a call is made. In circumstances where there is no possibility of more than one call being made by a channel at a time, the following before the call is placed will be sufficient:

Set(CHANNEL(hangup_handler_push)=npstnbilling,${EXTEN},1(${STRFTIME(${EPOCH},,%s)},${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)}))
			

The only value above that might depend on your implementation is EXTEN. If the number that was dialed is stored in a variable — rather than being used as the extension of the parent context — this should be adjust appropriately. The subroutine will never need to be modified in any way, nor will any of the arguments in the subroutine call. How you call the subroutine will depend on how call routing flow is defined on your node. When integrating this into a DISA that returns to dial tone upon callee hangup, for example, a hangup handler will not work and more complicated measures must be taken. In our boilerplate template code, we have already integrated this into the dialplan so you don't need to worry about adding this functionality. Integration into DISAs is as complicated as it gets; hangup handlers suffice in very simple DISAs that don't return to dial tone upon callee hangup, or with more basic call flow. However, if you use our template(s), you should not need to worry about this.

If you want to opt out of the mock billing system, simply remove this call. The billing system is only for fun. You will never need to pay the "balance" that appears on your statement. It simply gives users an idea of what they would have paid for their calls in the late 1970s. More information about this is available in the "Billing" section of this documentation.

Music on Hold

At this point, you will want to configure a music-on-hold class for your ringback signal.

Open up musiconhold.conf and add the following:

[rbs]
mode=files
directory=moh/ringback/rbs1
			

The [rbs] class is referenced in the Dial Statement in [ATAx]. 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 the NPSTN Sounds Repository. 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 in your [linebusy] and [linereorder] subroutines.

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

[default]
mode=files
directory=moh/noise
random = no

[asteriskdefault]
mode=files
directory=moh
random=yes
			

Right-click this and click "Save As". Download the file and then transfer it to your Asterisk server. Alternatively, you can use wget on your Asterisk switch to download the file. Either way, you will need to move it to the proper directory. Create the folder noise inside the moh folder and then place the audio file inside of it.

What did this accomplish? In keeping with the realism factor of NPSTN, 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 hear 2600Hz trunk noise!

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

City Dialtone DISA

One realm where NPSTN has been particularly innovative was with its approach to DISAs. Asterisk contains a built in DISA function, but this posed severe limitations for NPSTN. The DISA() function in Asterisk uses the dialtone specifications outlined in indications.conf. This doesn't leave any room for custom audio files to be used as the dialtone; only combined and modulated tones can be used. Though a set of old city tones does exist in indications.conf by default, the results are less than pleasing. A pure 600 Hz modulated by 120 Hz is a far cry from an actual city dial tone which can't be recreated by computer. For maximum realism, a custom audio file must be used for the dial tone.

Hence, most DISAs on NPSTN do not use the DISA() function in Asterisk at all. Instead, custom contexts are used that simulate an old city dialtone very accurately. On some switches, there are unique sounds between each digit as dialing is in progress. By specifying how you want your dialtone to work, line by line, you have full control over its operation. Furthermore, calls complete immediately when the last digit is dialed — users of the DISA() function must generally press # or suffer a timeout, not at all what happened in the old network.

Macro

Originally, all the dialtones on NPSTN were macros, but this is no longer the case. There are several ways to go about implementing a custom dialtone, and a few of them are covered here. You are advised to opt for a regular context rather than a macro, but here is an example of one of the earlier macros, slightly tweaked for context:

[dialtone] ; Example on how to dial onto NPSTN
exten => s,1,Answer
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=1)
exten => s,n,Read(number,custom/dialtone,7)
exten => s,n,Macro(dialnpstn,${number})
exten => i,1,Hangup
exten => h,1,Hangup
exten => bad,1,Hangup
				

The above is an extremely basic dialtone that will play the audio file dialtone (located in the custom directory) and dial all NPSTN numbers by performing a lookup, even those that are local. Below is a more complex macro that, still only is designed for 7-digit dialing, but does handle local calls specially:

[macro-dt]
exten => s,1,Answer
exten => s,n,Read(num,dialtone,7)
exten => s,n,NoOp(Office Code Dialed: ${num:0:1}${num:1:1}${num:2:1})
exten => s,n,GotoIf($["${num:0:1}${num:1:1}${num:2:1}"="${OC1}"]?100:200)
exten => s,100,Answer
exten => s,101,Macro(dl,${num})
exten => s,102,GoTo(1)
exten => s,200,Macro(dialnpstn,${num})
exten => s,201,GoTo(1)
exten => i,1,Hangup
exten => h,1,Hangup
exten => bad,1,Hangup

[macro-dl]
exten => s,1,Answer
exten => s,n,Set(num=${ARG1})
exten => s,n,Dial(Local/${OC1}${num:3:1}${num:4:1}${num:5:1}${num:6:1}@external-users,${DTL},m(rbs))
exten => s,n,GotoIf($["${DIALSTATUS}"="CHANUNAVAIL"]?400)
exten => s,n,GotoIf($["${DIALSTATUS}"="BUSY"]?600)
exten => s,n,GotoIf($["${DIALSTATUS}"="CONGESTION"]?800)
exten => s,n,GotoIf($["${DIALSTATUS}"="NOANSWER"]?454)
exten => s,400,Macro(dt)
exten => s,600,Playback(custom/busy)
exten => s,601,Macro(dt)
exten => s,800,Playback(custom/reorder)
exten => s,801,Macro(dt)
exten => s,454,Playback(custom/busy)
exten => s,455,Macro(dt)
				

It looks intricate, but in effect, this is really quite simple. It uses the Read statement to prompt for 7 digits. But what about special numbers like 0 and 911? In order to complete the call immediately when a special number is dialed, more code is required.

In the macro below, in addition to matching on 0, 411, 611, and 911, as well as any 7 digit number, randomly-chosen crosstalk/line noise files are played between each digit. Furthermore, if the dialtone times out (i.e. the entire audio file plays), the line will go to permanent signal.

In both the macro above and the one below, the caller is dropped back to dialtone after the callee hangs up. If this behavior is undesired, you can change the GoTo(1) statements to Hangup(). Otherwise, unless the caller times out to permanent signal (which can also be acheived by pressing # before dialing has begun), the caller will be dropped back to dialtone after each call he makes forever and ever.

[macro-dt]
exten => s,1,Answer
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=0)
exten => s,n,Set(n1=n${RAND(1,3)})
exten => s,n,Set(n2=n${RAND(1,3)})
exten => s,n,Set(n3=n${RAND(1,3)})
exten => s,n,Set(n4=n${RAND(1,3)})
exten => s,n,Set(n5=n${RAND(1,3)})
exten => s,n,Set(n6=n${RAND(1,3)})
exten => s,n,Read(num1,dialtone,1)
exten => s,n,GotoIf($["${num1}"=""]?ps)
exten => s,n,GotoIf($["${num1}"="0"]?operator)
exten => s,n,Read(num2,${n1},1)
exten => s,n,GotoIf($["${num2}"=""]?dial_complete)
exten => s,n,Read(num3,${n2},1)
exten => s,n,GotoIf($["${num3}"=""]?dial_complete)
exten => s,n,GotoIf($["${num1}${num2}${num3}${num4}${num5}${num6}${num7}"="911"]?dial_complete)
exten => s,n,GotoIf($["${num1}${num2}${num3}${num4}${num5}${num6}${num7}"="411"]?dial_complete)
exten => s,n,GotoIf($["${num1}${num2}${num3}${num4}${num5}${num6}${num7}"="611"]?dial_complete)
exten => s,n,Read(num4,${n3},1)
exten => s,n,GotoIf($["${num4}"=""]?dial_complete)
exten => s,n,Read(num5,${n4},1)
exten => s,n,GotoIf($["${num5}"=""]?dial_complete)
exten => s,n,Read(num6,${n5},1)
exten => s,n,GotoIf($["${num6}"=""]?dial_complete)
exten => s,n,Read(num7,${n6},1)
exten => s,n,GotoIf($["${LEN(${num7})}"="7"]?dial_complete)
exten => s,n(dial_complete),GotoIf($["${num:0:1}${num:1:1}${num:2:1}"="${OC1}"]?dl)
exten => s,n,Macro(dialnpstn,${num1}${num2}${num3}${num4}${num5}${num6}${num7})
exten => s,n,GoTo(1)
exten => s,n(dl),Macro(dl,${num4}${num5}${num6}${num7})
exten => s,n,GoTo(1)
exten => s,n(ps),Playback(custom/permsig)
exten => s,n,Playback(custom/crybaby)
exten => s,n,Hangup
exten => s,n(operator),Macro(dialnpstn,0)
exten => i,1,Hangup
exten => h,1,Hangup
exten => bad,1,Hangup
				

The problem with this approach is it doesn't scale well. The amount of code required to do complex pattern matching grows exponentially, and furthermore, the use of outdated dialplan syntax (exten => s,n, on each new line as opposed to an indent followed by same => n, can make this a headache to read and debug. With the requirement of immediately completing calls for 0, N11, 10XXX, 101XXXX, NNXXXXX, N0, 1N, 11N, 958, and 959, [macro-dialtone] neared 200 lines of code on its own. Problems plagued [macro-dialtone] almost continually; nesting issues with macros required that [macro-dl] be integrated directly into [macro-dialtone] to prevent stack errors. Eventually, it was decided that the dialtone macro should be rewritten as a regular context.

GoTo Contexts

Ultimately, it was not a single context that was the result of this process but a collection of contexts that, taken together, form the dialtone. Nearly 200 lines of code was split into several contexts that, together, were about 120 lines of code, a slight but sizable improvement in efficiency and readability. The new contexts allow for easier editing and management of the dialtone - as well as greater modularization of components. The same context can be used and different audio files can be played in-between each digit. Different dialtones can be specified. The syntax for using the new [dt] context is more particular, however, and warrants careful inspection.

While this is an extremely sophisticated and intricate dial tone, it is incredibly complicated. If you want a simpler, no-frills dial tone, see the "Boilerplate Code" section of this documentation. The [dt] context contained there is a simpler dial tone that will still complete on the proper number of digits but is otherwise quite basic.

Below is the general basic structure necessary for this new and improved dialtone:

[numclear] ; NPSTN 20190212 NA
exten => s,1,Return(,,,,,,,,0,0)
				

In a fairly straightforward fashion, [numclear] resets the variables used to collect the number being dialed (in addition to a counter used to play the correct interdigit clang sound). This is called at the beginning of this context. This is necessary because calls can return to this context if the Dial statement is used (this is configured in the [dtcheck] context), meaning if the variables are not cleared, digits from the previous call could result in a wrong number if a non-7-digit number is dialed or the # key is used during dialing.

[clanglookup] ; NPSTN 20190212 NA
exten => DTXB,1,Return(dialclang1a,dialclang2a,dialclang3a,dialclang4a,dialclang5a,dialclang6a,dialclang7)
exten => _DT.,1,Return(dialclang1a,dialclang2a,dialclang3a,dialclang4a,dialclang5a,dialclang6a,dialclang7)

[dtlooplookup]
exten => _DT.,1,Return(0)

[dt] ; NPSTN 20190212 NA
exten => _DT.,1,Answer
	same => n,GoSub(clanglookup,${EXTEN},1)
	same => n,Set(ARRAY(clang1,clang2,clang3,clang4,clang5,clang6,clang7)=${GOSUB_RETVAL})
	same => n(begin),GoSub(numclear,s,1)
	same => n,Set(ARRAY(num,num1,num2,num3,num4,num5,num6,num7,digit,clangcount)=${GOSUB_RETVAL})
	same => n(digit1),Set(digit=$[${digit}+1])
	same => n(read1),Set(TIMEOUT(response)=0)
	same => n,Read(num${digit},custom/switch/tones/dialtones/${$[${EXTEN}]},1)
	same => n,GoSub(dtlooplookup,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="1"]?loopyes:loopno)
	same => n(loopyes),GotoIf($["${$[num${digit}]}"=""]?read1:postloopcheck)
	same => n(loopno),GotoIf($["${$[num${digit}]}"=""]?dtroute,permsig,1)
	same => n(postloopcheck),GoTo(processdigit)
	same => n(nextdigit),Set(digit=$[${digit}+1])
	same => n,Set(clangcount=$[${clangcount}+1])
	same => n,Read(num${digit},custom/dialsounds/${$[clang${clangcount}]},1)
	same => n,GotoIf($["${$[num${digit}]}"=""]?dtroute,${num},1)
	same => n,GotoIf($["${LEN(${num})}"="6"]?dialclang7)
	same => n,GoTo(processdigit)
	same => n(dialclang7),Playback(custom/dialsounds/${clang7})
	same => n(processdigit),Set(num=${num}${$[num${digit}]})
	same => n,GoSub(dtcheck,${num},1)
	same => n,Set(ARRAY(lineaction,linereturn,lineclunk)=${GOSUB_RETVAL})
	same => n,GotoIf($[${lineaction}=TERMINATE]?terminate)
	same => n,GoTo(nextdigit)
	same => n(terminate),Set(TIMEOUT(response)=15)
	same => n,GotoIf($[${lineclunk}=no]?linereturn)
	same => n,GoSub(clunkcomplete,start,1)
	same => n(linereturn),GotoIf($[${linereturn}=yes]?dialyes)
	same => n(dialno),GoTo(dtroute,${num},1)
	same => n(dialyes),Set(ss=${STRFTIME(${EPOCH},,%s)})
	same => n,Set(tt=${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)})
	same => n,Set(__dtnum=${num})
	same => n,Set(__ticketed=0)
	same => n,Dial(Local/${num}@dtroute,${DTL1XB},m(default)g)
	same => n,GoSub(npstnbilling,${num},1(${ss},${tt}))
	same => n,Set(__ticketed=1)
	same => n,GoTo(begin)
exten => h,1,GotoIf($["${ticketed}"="0"]?:done)
	same => n,GoSub(npstnbilling,${dtnum},1(${ss},${tt}))
	same => n(done),Hangup()
				

This is the main context, which calls all of the other contexts presented here at some point or another. Although this is much denser code that is much more complex than that used in [macro-dt], and is subsequently much harder to follow, it is far more efficient and far more powerful. Although [dt] will only work properly with a maximum number length of 7 digits, this is not because of [dt] itself but rather because of the specifications in [dtcheck]. If you'd like to modify your dialtone to support dialing numbers longer than 7 digits, however, you will need to tweak these lines:

	same => n,Set(ARRAY(clang1,clang2,clang3,clang4,clang5,clang6,clang7)=${GOSUB_RETVAL})
	same => n,Set(ARRAY(num,num1,num2,num3,num4,num5,num6,num7,digit,clangcount)=${GOSUB_RETVAL})
				

You can probably guess you will have to add clang8, num8, clang9, num9, etc. variables for each additional digit you wish to support. You will also need to add audio file names to [clanglookup] to return more items to [dt] when it is called.

You will also need to add an additional comma inside the Return() statement in [numclear] for each additional digit you wish to support. Finally, ensure you have a rule in [dtcheck] that will terminate all calls of your maximum digit length. Otherwise, the conditions necessary to terminate a call will not be present and one could continue dialing a number forever (actually, the context would run out of the necessary variables and audio files first and would probably crash).

The [dtlooplookup] subroutine is called after the first digit and returns 0 if the dialtone audio file is not meant to be looped (default) and 1 if it is. If you have a dialtone audio file that can loop seamlessly (and you would like it to), define an entry for it in this subroutine (as in [clanglookup] that returns 1 rather than 0. Otherwise, 0 will be return and if nothing is entered, the call will go to permanent signal intercept. If 1 is returned rather than 0, the dialtone file will simply play again and it will loop in this manner forever until at least 1 digit is entered. This only applies to the first digit; subsequent audio files will not loop forever if nothing is entered after the first digit.

Of course, there is no reason to modify [dt] at all for an NPSTN node, so you can safely ignore the above commentary. However, we do want to draw special attention to the following line:

	same => n,Read(num${digit},custom/dialsounds/${$[clang${clangcount}]},1)
				

The above line is designed to play an audio file while the caller is dialing. You can set the audio files that are played (crosstalk, line noise, etc.) but this line expects the audio files returned from [clanglookup] to exist and it will not work without it! If you do not want to play any audio during dialing, replace the above line with the following:

	same => n,Read(num${digit},,1)
				

Now, even if the audio files referenced in [clanglookup] don't exist, it won't cause any errors and will work normally.

Now, here are the other contexts required for [dt] to function:

[dtcheck] ; NPSTN 20190212 NA ; value 2 = whether to return to [dt], value 3 = whether to play the clunk sound
exten => 0,1,Return(TERMINATE,yes,yes)
exten => _11N,1,Return(TERMINATE,yes,yes)
exten => _N11,1,Return(TERMINATE,yes,yes)
exten => 660,1,Return(TERMINATE,no,no)
exten => _95[89],1,Return(TERMINATE,yes,no)
exten => _XXXXXXX,1,Return(TERMINATE,yes,yes)
include => dtcheckdef

[dtcheckdef] ; NPSTN 20190212 NA
exten => _X!,1,Return(n,n,n)
				

The above is equivalent to a digit map (or dial plan) in an ATA. It says nothing about what will happen when a call satisfying those requirements is dialed. It merely reports that the caller has finished dialing or that more digits are still expected. Functionally, you can think of this as the equivalent of an ATA digit map, which only specifies dialable patterns and completes when there is a match. Likewise, [dtcheck] reports when a call can complete.

In addition, when a call is allowed to complete, important contextual information is returned about how to complete the call. As indicated by the comment, the second line (in this case, yes in all three cases), indicates whether to return to [dt] once the call has completed. The actual effect of this is, in [dt], if this option is set to yes, a call is placed using the Dial command (which specifies to return to the beginning once the call is over). Otherwise, GoTo is used instead, which means that the caller will not return to [dt] when the call is over. Any calls completed to regular 7-digit numbers on NPSTN should return to dial tone after the callee has hung up (in other words, yes should be returned for this value).

The third item in the Return statement indicates whether to play the clunk sound specified in the context [clunkcomplete]. If you opted to remove the Playback() statement in your [clunkcomplete] earlier, then it makes no difference whether this is set to yes or no. If you do have a clunk sound, you can specify yes to play it before completing the call or no to skip playing the sound.

In most cases, you will want this option set to yes, meaning a clunk will play whenever a call is placed through the DISA. One scenario in which it might make more sense not to play a "dialing complete" clunk sound is if you are connecting to test equipment located inside the central office, such as a special test circuit a tie-line. Usually, these can be dialed with a 2 or 3-digit code. To preserve realism when dialing a tieline, for instance, it would not make sense to play a clunk before getting another dial tone. If you have a tieline, you can add a separate, more specific rule in addition to the three present that will refrain from playing the clunk. A related case is when a special test number is dialed, such as 660.

[dtroute] ; NPSTN 20190212 NA
exten => _X!,1,Answer()
	same => n,Log(NOTICE, Call from ${CALLERID(all)} to ${EXTEN} via ${clli} DISA)
	same => n,GoTo(dtdest,${EXTEN},1)
exten => permsig,1,Log(NOTICE, Call from ${CALLERID(all)} at "${CHANNEL(peerip)}" timed out to permanent signal)
	same => n,GoTo(dtdest,${EXTEN},1)
				

The only thing [dtroute] does is centrally log all calls dialed through the DISA before sending them on to [dtdest]. This avoids having to duplicate code by including a centralized Log statement; otherwise, a Log statement would be required for each extension pattern in [dtdest].

[dtdest] ; NPSTN 20190212 NA
exten => permsig,1,Playback(custom/permsig)
	same => n,Playback(custom/crybaby)
	same => n,Hangup
exten => _${OC1}XXXX,1,GoTo(dtlocal,${EXTEN},1)
exten => 0,1,GoTo(dtnpstn,${EXTEN},1)
exten => _11N,1,GoTo(dtnpstn,${EXTEN},1)
exten => _N11,1,GoTo(dtnpstn,${EXTEN},1)
exten => 660,1,GoTo(dtnpstn,${EXTEN},1)
exten => _95[89],1,GoTo(dtnpstn,${EXTEN},1)
exten => _101XXXX,1,GoTo(dtnpstn,${EXTEN},1)
exten => _XXXXXXX,1,GoTo(dtnpstn,${EXTEN},1)
include => invalidincoming
				

Remember how we said that [dtcheck] was solely responsible for determining if the caller had dialed enough digits to complete the call? Well, [dtdest] is the context that is actually responsible for routing calls made through your DISA. There will always be at least as many pattern matches here as there were in [dtcheck] — and probably more.

First off, you will need the files permsig (permanent signal intercept recording) and crybaby (crybaby "off-hook" tone) for your system. If you don't have these already, you can find samples of these in Evan Doorbell recordings.

Secondly, you will need a "diverter" statement for each office code that you have. If you only have one, you're all set, provided that the OC1 global variable has already been properly set. If you had a second office code, you would need to add the following:

exten => _${OC2}XXXX,1,GoTo(dtlocal,${EXTEN},1)
				

Next, we'd like to point out it doesn't matter much if the last entry is XXXXXXX or NNXXXXX. The difference is if, say, 2063333 is dialed, with the former, the call will complete to the main NPSTN tandem which will provide an intercept message. With the latter, the call will never make it to [dtnpstn] and will fall through to invalidincoming and you will hear a CBCAD message.

Astute readers, though, may notice that 101XXXX and XXXXXX are separate statements. If you do change the last statement to NNXXXXX, you will need to keep 101XXXX as a separate statement, since any 7-digit call beginning with 101 can't possible meet the criteria for NNXXXXX. However, in this case 101XXXX clearly would fall under XXXXXXX, so isn't the 101XXXX statement redundant, since it does the same thing as the broader statement?

In this case, yes, but that's only because this code has been condensed slightly to create a more basic, less complex dialtone. Here is the original code from the server from which this code was originally taken:

exten => _101XXXX,1,GoTo(dtnpstn,${EXTEN},1)
exten => _XXXXXXX,1,GoSub(registersender,start,1(${EXTEN}))
	same => n,GoSub(sfinteroffice,start,1(${EXTEN}))
	same => n,GoTo(dtnpstn,${EXTEN},1)
				

As you can see, in this case, 101XXXX calls are differentiated in this case. 101XXXX calls complete immediately here, but all other 7-digit calls include outpulsing. If you wish, you can add your own outpulsing as well. Many of these inpulsing/outpulsing subroutines are included in the "Vintage Add-Ons" section of this documentation.

Finally, here are the contexts through which all 7-digit NPSTN calls pass:

[dtlocal] ; NPSTN 20190212 NA
exten => _NXXXXXX,1,GoTo(local,${EXTEN},1)
				

Again, [dtlocal] might seem redundant. Why is it necessary if all it is doing is sending the call to yet another context? Here is the same context in its original form from the aforementioned server:

[dtlocal] ; NPSTN 20190212 NA
exten => _NXXXXXX,1,GoSub(mfinteroffice,start,1(${EXTEN}))
	same => n,GoTo(local,${EXTEN},1)
				

Here, you can see that the number dialed is MFed whenever a local call is dialed through the DISA. You can configure your desired outpulsing and signaling sounds once you add those subroutines.

Also note that, no matter how many office codes you have, the pattern match here should be kept to NXXXXXX (or XXXXXXX or NNXXXXX, it doesn't matter). This is because if a call has landed here, it will have been due to it already being detected to be a local call, so there is no need to further differentiate calls landing in this context. Differences between your office codes should be handled in [local].

Finally, here is the context where all calls that "go out" to NPSTN end up:

[dtnpstn] ; NPSTN 20190212 NA
exten => _X!,1,Set(originalclid=${CALLERID(all)})
	same => n,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode},${maindisa}))
	same => n,Set(CALLERID(all)=${originalclid})
	same => n,Hangup()
				

The reason Caller ID is reset after the call is callers can potentially make multiple calls in a single session. The verification contexts will potentially modify outgoing CNAM, so resetting this afterward ensures that the altered CNAM doesn't carry over between calls.

It is important that you preserve this context as-is. [dialnpstn] is the subroutine you'll need to make calls to other NPSTN nodes and is available in the next section. [disa-rewrite-cnam] is a subroutine that rewrites CNAM properly for calls that leave your switch, in order to comply with NPSTN standards. You'll find this subroutine later on as well.

Finally, in order to use [dt], we will need to add a global variable:

[globals]
DTXB=city/5xb_dialtone
				

You've likely already created the [globals] context, in which case you need only add the line beneath that to your existing [globals] context.

The global variable created here references a relative path to your dialtone audio. You can see this in action in this line from [dt]:

	same => n,Read(num${digit},custom/dialtones/${$[${EXTEN}]},1)
				

In this case, we are expecting that the audio file for the dialtone is called 5xb_dialtone and is located in the directory /var/lib/asterisk/sounds/en/custom/dialtones/city/.

What may not make sense is the ${$[${EXTEN}]} part. Let's review how [dt] is called in the first place:

exten => 1111,1,GoTo(dt,DTXB,1)
				

As you can see, [dt] is called with the DTXB extension. In this case, that is shorthand for "Crossbar dial tone". The XB should be changed to something that more accurately reflects the type of switch you are simulating and, consequently, the type of dialtone you are using.

When you change DTXB to another name of your choosing (the only requirement being that the name start with DT and it consist of at least one more character, meaning you can't just settle for DT by itself), you will also need to update the following reference in [clanglookup]:

exten => DTXB,1,Return(dialclang1a,dialclang2a,dialclang3a,dialclang4a,dialclang5a,dialclang6a,dialclang7)
				

By using the type of dial tone as the extension, you can create multiple dial tones with different dialtones and different clangs simply by calling a different extension. For example, you could have DTXY be a Stromberg Carlson dialtone. By adding an entry in [clanglookup], you can use a separate set of inter-digit audio files. You could, for instance, have two different DISAs using different dialtones and different inter-digit audio files by doing the following:

exten => 1111,1,GoTo(dt,DTXB,1) ; a "crossbar" dial tone
exten => 2222,1,GoTo(dt,DTXY,1) ; an "SC XY" dial tone
				

All you need to do is create a global variable for each DT… extension you create specifying the audio file to use, as well as create an entry for it in [clanglookup] about what interdigit audio files to play (if you chose earlier to disable interdigit audio by modifying that line in [dt] you can ignore this). If you'd like to use the same interdigit clangs for all your DISAs and save yourself some work, you don't need to add an entry; if the DT… extension isn't found in [clanglookup], it'll fall through to [missingclang] and use those audio files as a default.

If you do want to use interdigit audio files, make sure they're all located in the following directory (or modify the code to point it to a directory of your choosing):

/var/lib/asterisk/sounds/en/custom/dialsounds/

Dial NPSTN

The dialnpstn subroutine (or macro) is necessary to make calls to NPSTN.

Original Macro

As stated before, macros are deprecated and don't nest very well, so unless you have a good reason to use macros, you should use the subroutine form. Macros on NPSTN are no longer supported or maintained, but here is the original dialnpstn macro for historical purposes:

[macro-dialnpstn] ; NPSTN DC
exten => s,1,Set(num=${ARG1})
exten => s,n,Set(lookup=${CURL(https://crtc.npstn.us/api/v1/?lookup=${ARG1}&cid=${CALLERID(num)}))}
exten => s,n,NoOp(NPSTN ROUTE TO: ${lookup})
exten => s,n,Dial(${lookup},60,g)
exten => s,n,MacroExit
				

The above has been fully rewritten as a subroutine that you should use instead. There are additional components in the [dialnpstn] subroutine required to be standards-compliant.

Subroutine

Here is the [dialnpstn] subroutine:

[dialnpstn] ; NPSTN 20190215 NA ; Sends a call to NPSTN
exten => start,1,Set(num=${ARG1})
	same => n,GotoIf($[${LEN("${ARG4}")}>2]?clidoverride)
	same => n,Set(lookup=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${ARG1}&cid=${CALLERID(num)}&sntandem=${ARG2}&zipcode=${ARG3}")})
	same => n,GoTo(verify)
	same => n(clidoverride),Set(lookup=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${ARG1}&sntandem=${ARG2}&zipcode=${ARG3}")})
	same => n(verify),GoSub(lookupchan,s,1(${lookup})) ; verifies lookup for extra security
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-BLOCKED,1)
	same => n,NoOp(NPSTN ROUTE TO: ${lookup})
	same => n,Dial(${lookup},,g)
	same => n,GoTo(npstn-${DIALSTATUS},1)
exten => npstn-ANSWER,1,Return()
exten => npstn-BUSY,1,GoSub(linebusy,s,1())
	same => n,Return()
exten => npstn-CONGESTION,1,Playback(custom/all-ckts-busy-now)
	same => n,Return()
exten => npstn-CHANUNAVAIL,1,GoSub(linereorder,s,1())
	same => n,Return()
exten => npstn-BLOCKED,1,Playback(custom/ccad)
	same => n,Return()
exten => _npstn-.,1,Return()
				

Note: To use the code as it is presented above, you will need an "all circuits busy" intercept. If you don't have one, you can change the CONGESTION extension to simply play a reorder signal (like CHANUNAVAIL), but the all circuits busy intercept can be informative in these circumstances if you do have one.

ARG1 is the number on which to do a lookup.

ARG2 (usually disable) is whether or not to activate StepNet (see the StepNet section for more on this). This way, if you do want to use StepNet trunking, all you have to do is instead of calling GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode})), call GoSub(dialnpstn,start,1(${EXTEN},enable,${zipcode})).

ARG3 is your ZIP code (refer to "Reserving an Office Code" for more info on this), but you should use the zipcode global variable rather than specifying it manually. That way, if you move, you only need to change the global variable's value. The advantage of specifying the ZIP code in each call rather than having the zipcode variable used automatically in the dialnpstn subroutine is that if you need to change the ZIP code in one particular circumstance, you can do so easily without modifying the dialnpstn subroutine at all. If are located outside of the US, your zipcode global variable should be set to 00000.

ARG4 is an optional argument and is usually not used. The one case in which it is used is in [dtnpstn], where ${maindisa} is passed in. This will NOT sent the ANI of the caller to the lookup request. This way, if a caller dials into your DISA and dials himself right back, he won't get a NOYOL intercept (Number on Your Own Line). The way the lookup API works is if the Caller ID argument matches the lookup argument, instead of returning that number's actual destination, it plays a NOYOL message and then rings you back. Although we choose to handle all local calls locally, if you sent everything to [dialnpstn], you would get this service whenever you dialed your own number. Since this is not desired behavior with network DISAs, ARG4 prevents ANI from being sent to the lookup (as it is omitted from the lookup), allowing callers to dial themselves normally through DISAs, instead of getting NOYOL intercepts.

In case you're feeling left out if you don't send local calls to [dialnpstn], don't! You can manually use this service from any line by calling BE1-9061. This is the NOYOL ringback. If you just want plain and simple ringback, 959 is the network-wide test number to do that.

The subroutine call to lookupchan is intended as an extra security measure. If the API, hypothetically, were to be compromised, this subroutine call will prevent potentially fradulent lookup returns from allow malicious callers access to sensitive areas of your switch. Note that the lookupchan subroutine is part of the NPSTN verification subroutines and can be found in the "Verification" section of this documentation.

One unrelated difference between the original macro and the subroutine here is the curl statement used by each (ignoring the sntandem and zipcode flags in the subroutine):
Set(lookup=${CURL(https://crtc.npstn.us/api/v1/?lookup=${ARG1}&cid=${CALLERID(num)}))} as opposed to
Set(lookup=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${ARG1}&cid=${CALLERID(num)}")})

This is an unrelated difference; not all Asterisk systems support the former, but the latter should work for everyone. You can try out the former line, but if it doesn't work, try using a SHELL command that invokes curl as prescribed in the latter statement, as opposed to trying to use CURL directly.

Macro for Older Asterisk Systems

Some Asterisk systems are so old that CURL can not be used directly from within Asterisk in any way. Furthermore, not only are subroutines not supported, but even macros don't work in the same way. Here is an implementation to workaround that for older systems, such as Asterisk 1.2. These instructions are provided courtesy of Don Froula, who uses this to access NPSTN from his Asterisk 1.2 Project MF switch:

Curl must be compiled with openssl of at least 1.0.2. I downloaded 1.0.2r and compiled it on my Debian 4 "Etch" installation.

I then uninstalled the default curl package with and compiled and installed curl 7.59.0, which picked up the newer openssl libraries. (Complete Instructions)

I followed the instruction exactly and it worked perfectly. After upgrading, test the NPSTN connection from curl with: curl -v https://crtc.npstn.us/api/v1/?lookup=5551212

The output should spit out a verbose listing of handshaking details, with the resolved dial string on the final line (no CR/LF).

Here is the macro:

[macro-dialnpstn]
exten => s,1,System(/bin/rm -f /var/spool/asterisk/monitor/npstn.txt)
exten => s,n,System(curl -o /var/spool/asterisk/monitor/npstn.txt https://crtc.npstn.us/api/v1/?lookup=${ARG1}&cid=${CALLERID(num)})
exten => s,n,Wait(1)
exten => s,n,ReadFile(lookup=/var/spool/asterisk/monitor/npstn.txt,255)
exten => s,n,NoOp(NPSTN ROUTE TO: ${lookup})
exten => s,n,Dial(${lookup},60,g)
exten => s,n,MacroExit
				

The macro gets around the lack of a "shell" command on earlier versions of Asterisk by directing the curl output to /var/spool/asterisk/monitor/npstn.txt, the reading it back in with ReadFile. The delay after the curl command is needed to give curl time to open, write, and close the file.

Verification Subroutines

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" used on NPSTN. 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.

  • 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

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.

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 NPSTN and C*NET callers.

If you have sensitive contexts to which you wish to allow only trusted callers, you can, in addition to blocking calls with "via NPSTN" in the CNAM (which will block calls made via other nodes' DISAs or a via a node's PSTN gateway), choose to allow only calls with a CVS code of 10 or 20, as you can at least trust these callers are who they say they are.

You can also use CVS codes to act accordingly in your dial plan based on the origin of the caller, i.e. sending NPSTN callers to one context and C*NET callers to another. 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. 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}"="1"]?npstn) 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($["${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 NPSTN 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-NPSTN node to fradulently present itself to any node, no matter how far up or downstream in a call a node may happen to be.

[dtnpstn] patch

First, you'll need to modify your [dtnpstn] context; previously, it was something like this:

[dtnpstn] ; NPSTN 20190212 NA
exten => _X!,1,Set(originalclid=${CALLERID(all)})
	same => n,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode},${maindisa}))
	same => n,Set(CALLERID(all)=${originalclid})
	same => n,Hangup()
				

Now, patch it to the following:

[dtnpstn] ; NPSTN 20190215 NA
exten => _X!,1,Set(originalclid=${CALLERID(all)})
	same => n,GoSub(npstn-out-verify,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-out-blocked,s,1)
	same => n,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode},${maindisa}))
	same => n,Set(CALLERID(all)=${originalclid})
	same => n,Hangup()
				

Note that this assumes that all outbound NPSTN access available to any external callers is through your city dial tone DISA. If you use the DISA() function in Asterisk, you can create a DISA context and send all non-local calls to the [dtnpstn] context as a workaround.

Incoming Verification Contexts

Please be aware DIG (one of the NPSTN pre-requisites) is required for these subroutines to work properly.

[pstn-us-verify] ; NPSTN 20190212 NA ; to be called immediately on any incoming call from a US PSTN DID
exten => s,1,Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})})
	same => n,GoSub(anonymous-check,s,1)
	same => n,Set(anon=${GOSUB_RETVAL})
	same => n,GotoIf($[${anon}=0]?anonymous:checklen)
	same => n(checklen),GotoIf($[${LEN(${CALLERID(num)})}<10]?invalid)
	same => n,Set(cnum=${CALLERID(num):-10:10})
	same => n,GotoIf($[${cnum:0:1}=0]?invalid)
	same => n,GotoIf($[${cnum:0:1}=1]?invalid)
	same => n,GotoIf($[${cnum:3:1}=0]?invalid)
	same => n(valid),Set(__clidverif=30)
	same => n,GoTo(done)
	same => n(invalid),Set(__clidverif=31)
	same => n,GoTo(done)
	same => n(anonymous),Set(__clidverif=32)
	same => n,GoTo(done)
	same => n(done),Set(CALLERID(name)=${CALLERID(name)} via ${clli} US PSTN)
	same => n,Return()

[pstn-uk-verify] ; NPSTN 20190212 NA ; to be called immediately on any incoming call from a UK PSTN DID
exten => s,1,Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})})
	same => n,GoSub(anonymous-check,s,1)
	same => n,Set(anon=${GOSUB_RETVAL})
	same => n,GotoIf($[${anon}=0]?anonymous:checklen)
	same => n(checklen),GotoIf($[${LEN(${CALLERID(num)})}<8]?invalid) ; minimum acceptable UK CLID length assumed to be 8
	same => n(valid),Set(__clidverif=40)
	same => n,GoTo(done)
	same => n(invalid),Set(__clidverif=41)
	same => n,GoTo(done)
	same => n(anonymous),Set(__clidverif=42)
	same => n,GoTo(done)
	same => n(done),Set(CALLERID(name)=${CALLERID(name)} via ${clli} UK PSTN)
	same => n,Return()

[anonymous-check] ; NPSTN 20190212 NA ; returns 0 if the CLID or CNAM is likely private/anonymous/withheld/blocked/restricted/etc. ; otherwise returns 1
exten => s,1,Set(cnam=${CALLERID(name)})
	same => n,Set(cnum=${CALLERID(num)})
	same => n,GotoIf($["${TOLOWER(${cnam}):0:9}"="anonymous"]?anon)
	same => n,GotoIf($["${TOLOWER(${cnum}):0:9}"="anonymous"]?anon)
	same => n,GotoIf($["${TOLOWER(${cnam}):0:7}"="private"]?anon)
	same => n,GotoIf($["${TOLOWER(${cnum}):0:7}"="private"]?anon)
	same => n,GotoIf($["${TOLOWER(${cnam}):0:10}"="restricted"]?anon)
	same => n,GotoIf($["${TOLOWER(${cnum}):0:10}"="restricted"]?anon)
	same => n,GotoIf($["${TOLOWER(${cnam}):0:8}"="withheld"]?anon)
	same => n,GotoIf($["${TOLOWER(${cnum}):0:8}"="withheld"]?anon)
	same => n,Return(1)
	same => n(anon),Return(0)

[cnet-verify] ; NPSTN 20190212 NA ; to be called immediately on all [from-cnet] calls
exten => _X!,1,Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})})
	;same => n,Set(clidverified=${IAXVAR(clidverif)})
	same => n,GoSub(cnet-verifyclid,${EXTEN},1) ; Will flag PSTN callers via another C*NET node's DISA as "21"
	same => n,Set(valid=${GOSUB_RETVAL})
	same => n,GotoIf($[${valid}=1]?valid:invalid)
	same => n(valid),Set(__clidverif=20)
	same => n,Return()
	same => n(invalid),Set(__clidverif=21)
	same => n,Return()

[cnet-verifyclid] ; NPSTN 20190212 NA
exten => _X!,1,Set(cnum=${CALLERID(num)})
	same => n(ewr),GoSub(cnet-reverse-iax,${EXTEN},1(${cnum}))
	same => n,Set(ARRAY(calleriax,cnumvalid)=${GOSUB_RETVAL})
	same => n,GotoIf($[${cnumvalid}=1]?:invalid)
	same => n,GotoIf($["${calleriax}"=""]?invalid)
	same => n,GoSub(isitip,${EXTEN},1(${calleriax}))
	same => n,Set(ARRAY(iaxisip,calleriplookup)=${GOSUB_RETVAL})
	same => n,GotoIf($[${iaxisip}=1]?getpeer)
	same => n,GoSub(fqdn2ip,${EXTEN},1(${calleriax}))
	same => n,Set(ARRAY(calleriaxip,calleriplookup)=${GOSUB_RETVAL})
	same => n(getpeer),GoSub(getpeerip,${EXTEN},1)
	same => n,Set(ipactual=${GOSUB_RETVAL})
	;same => n,GotoIf($[${ipactual}=1]?:invalid)
	same => n,GoSub(ipcomp,${EXTEN},1(${calleriplookup},${ipactual}))
	same => n,Set(match=${GOSUB_RETVAL})
	same => n,GotoIf($[${match}=1]?valid:invalid)
	same => n(invalid),Return(0)
	same => n(valid),Return(1)

[npstn-verify] ; NPSTN 20190212 NA ; to be called immediately on all [from-npstn] calls
exten => _X!,1,Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})})
	same => n,Set(fromnum=${IAXVAR(DISAVIA)})
	same => n,Set(clidverified=${IAXVAR(clidverif)})
	same => n,GotoIf($[${LEN(${clidverified})}=2]?verifynode) ; Should the node reporting the caller's verification status be trusted? (Confirm it's a valid NPSTN node telling us, not a rogue server)
	same => n,GoSub(npstn-verifyclid,${EXTEN},1)	; Allows callers dialing through DISAs to still maintain verification status
	same => n,Set(valid=${GOSUB_RETVAL})			; Now, not just any server can set IAXVAR(clidverif) to spoof verification status (only a valid NPSTN node could do this)
	same => n,GotoIf($[${valid}=1]?valid:invalid)
	same => n(verifynode),GoSub(npstn-verifyclidother,${EXTEN},1(${fromnum})) ; Verify the NPSTN node through which the NPSTN call is passing
	same => n,Set(valid=${GOSUB_RETVAL})
	same => n,GotoIf($[${valid}=1]?judgedvalid:judgedinvalid)
	same => n(valid),Set(__clidverif=10)
	same => n,Return()
	same => n(invalid),Set(__clidverif=11)
	same => n,Set(GROUP(cvs)=npstnspam)
	same => n,Return()
	same => n(judgedvalid),Set(__clidverif=${clidverified})
	same => n,Return()
	same => n(judgedinvalid),Set(__clidverif=19)
	same => n,Return()

[npstn-verifyclid] ; NPSTN 20190212 NA
exten => _X!,1,Set(cnum=${CALLERID(num)})
	same => n,GotoIf($[${cnum}=0]?operator:ewr)
	same => n(operator),Set(cnum=6461217) ; Translates CLID of 0 to an NPSTNCFL01 # so it can still be flagged as verified (0 resolves to NPSTNNA01 but calls with CLID of 0 originate from NPSTNCFL01)
	same => n(ewr),GoSub(npstn-reverse-iax,${EXTEN},1(${cnum}))
	same => n,Set(ARRAY(calleriax,cnumvalid)=${GOSUB_RETVAL})
	same => n,GotoIf($[${cnumvalid}=1]?:invalid)
	same => n,GotoIf($["${calleriax}"=""]?invalid)
	same => n,GoSub(isitip,${EXTEN},1(${calleriax}))
	same => n,Set(ARRAY(iaxisip,calleriplookup)=${GOSUB_RETVAL})
	same => n,GotoIf($[${iaxisip}=1]?getpeer)
	same => n,GoSub(fqdn2ip,${EXTEN},1(${calleriax}))
	same => n,Set(ARRAY(calleriaxip,calleriplookup)=${GOSUB_RETVAL})
	same => n(getpeer),GoSub(getpeerip,${EXTEN},1)
	same => n,Set(ipactual=${GOSUB_RETVAL})
	;same => n,GotoIf($[${ipactual}=1]?:invalid)
	same => n,GoSub(ipcomp,${EXTEN},1(${calleriplookup},${ipactual}))
	same => n,Set(match=${GOSUB_RETVAL})
	same => n,GotoIf($[${match}=1]?valid:invalid)
	same => n(invalid),Return(0)
	same => n(valid),Return(1)

[npstn-verifyclidother] ; NPSTN 20190212 NA
exten => _X!,1,Set(cnum=${ARG1})
	same => n(ewr),GoSub(npstn-reverse-iax,${EXTEN},1(${cnum}))
	same => n,Set(ARRAY(disafromiax,cnumvalid)=${GOSUB_RETVAL})
	same => n,GotoIf($[${cnumvalid}=0]?invalid)
	same => n,GoSub(isitip,${EXTEN},1(${disafromiax}))
	same => n,Set(ARRAY(iaxisip,disafromiplookup)=${GOSUB_RETVAL})
	same => n,GotoIf($[${iaxisip}=1]?getpeer)
	same => n,GoSub(fqdn2ip,${EXTEN},1(${disafromiax}))
	same => n,Set(ARRAY(disafromiaxip,disafromiplookup)=${GOSUB_RETVAL})
	same => n(getpeer),GoSub(getpeerip,${EXTEN},1)
	same => n,Set(ipactual=${GOSUB_RETVAL})
	same => n,GotoIf($[${ipactual}=0]?invalid)
	same => n,GoSub(ipcomp,${EXTEN},1(${disafromiplookup},${ipactual}))
	same => n,Set(match=${GOSUB_RETVAL})
	same => n,GotoIf($[${match}=0]?invalid)
	same => n,Return(1)
	same => n(invalid),Return(0)

[cktslookup] ; NPSTN 20190218 NA ; ARG1 = full international number to lookup on C*NET
exten => s,1,Set(ENUM=${ENUMLOOKUP(+${ARG1},ALL,,1,std.ckts.info)})
	same => n,GotoIf($[${LEN(${ENUM})}=0]?fail)
	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(iax),Set(DIALSTR=IAX2/${ENUM:5})
	same => n,GoTo(done)
	same => n(sip),Set(DIALSTR=SIP/${ENUM:4})
	same => n,GoTo(done)
	same => n(h323),Set(DIALSTR=H323/${ENUM:5})
	same => n(done),Return(${DIALSTR})
	same => n(fail),Return(${ENUM})

[cnet-reverse-iax] ; NPSTN 20190212 NA ; based on npstn-ewr-fetch
exten => _X!,1,Set(nocid=CNET_NUMBER_NOT_VALID)})
	same => n,Set(badcid=)})
	same => n,GoSub(cktslookup,s,1(${ARG1}))
	same => n,Set(rtcr=${GOSUB_RETVAL})
	same => n,Set(cv=${IF($[$["${rtcr}"="${nocid}"]|$["${rtcr}"="${badcid}"]]?0:1)})
	same => n,Return(${rtcr},${cv})

[npstn-reverse-iax] ; NPSTN 20190212 NA
exten => _X!,1,Set(nocid=${SHELL(curl "https://dc4.us/npstn/api/v1/?lookup=")})
	same => n,Set(badcid=${SHELL(curl "https://dc4.us/npstn/api/v1/?lookup=442072221234")})
	same => n,Set(rtcr=${SHELL(curl "https://dc4.us/npstn/api/v1/?lookup=${ARG1}")})
	same => n,Set(cv=${IF($[$["${rtcr}"="${nocid}"]|$["${rtcr}"="${badcid}"]]?0:1)})
	same => n,Return(${rtcr},${cv})

[isitip]    ;20181116 BJC - ARG1 is a iaxuri - if ARG1 contains an IP address, returns dnf=1 otherwise dnf=0  
exten => _X!,1,Set(oc1=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,1)})})})    ;slice iaxuri and evaluate octet1
	same => n,Set(oc2=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,2)})})})            ;slice iaxuri and evaluate octet2
	same => n,Set(oc3=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,3)})})})            ;slice iaxuri and evaluate octet3
	same => n,Set(oc4=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,4)})})})            ;slice iaxuri and evaluate octet4
	same => n,Set(dnf=${IF($[${oc1}|${oc2}|${oc3}|${oc4}]=0?0:1)})                            ;OR sum octets
	same => n,Set(ip=${IF($["${dnf}"="1"]?${CUT(CUT(ARG1,@,2),/,1)}:0)})                    ;IP address
	same => n,Return(${dnf},${ip})                                                            ;return IP flag & IP  

[fqdn2ip]    ;20181114 BJC - ARG1 is iaxuri containing an fqdn, returns iaxuri containing ip address and ip address if host exists else returns Unknown Host and fqdn
exten => _X!,1,Set(ARRAY(ds1,ds2,ds3)=${CUT(ARG1,@,1)},${CUT(CUT(ARG1,@,2),/,1)},${CUT(CUT(ARG1,@,2),/,2)})        ;extract fqdn to {DS2}
	same => n,Set(ds2=${FILTER(0-9.,${SHELL(dig ${ds2} +noall +short | sed -n '$'p)})})                                ;convert fqdn to IP
	same => n,GotoIf($["${ds2}"=""]?nn)                                                                                ;unknown host?
	same => n,Return(${ds1}@${ds2}/${ds3},${ds2})                                                                    ;return converted iaxuri & IP address
	same => n(nn),Return(Unknown Host,${ds2})                                                                        ;Unknown Host - no IP address

[getpeerip]    ;20181114 BJC / 20190223 NA - returns peerip for IAX2/SIP/H323/MGCP channel or 0 for all other channel technologies
exten => _X!,1,Set(ct=${CHANNEL(channeltype)})            ;get technology
	same => n,GotoIf($["${ct}"="IAX2"]?setpip)            ;incoming NPSTN calls are IAX2 or one of the other 3 technologies, any other technology must be local/test channels
	same => n,GotoIf($["${ct}"="SIP"]?setpip)
	same => n,GotoIf($["${ct}"="H323"]?setpip)
	same => n,GotoIf($["${ct}"="MGCP"]?setpip)
	same => n,GoTo(z)
	same => n(setpip),Set(pip=${CHANNEL(peerip)})                    ;get peer IP
	same => n,Return(${pip})                                ;return peer IP
	same => n(z),Return(0)                                    ;return 0 for local channels

[ipcomp]    ;20181114 BJC - ARG1 and ARG2 are IP addresses - returns cpf=1 if ARG1 & ARG2 coincidence otherwise cpf=0
exten => _X!,1,Set(cpf=${IF($[${FILTER(0-9,${ARG1})}=${FILTER(0-9,${ARG2})}]?1:0)})    ;compare ip addresses
	same => n,Return(${cpf})                                                           ;return compare flag
				

Outgoing Verification Contexts

[npstn-out-verify] ; NPSTN 20190212 NA ; set IAX flags and modifies CNAM if necessary
exten => s,1,GoTo(00,1)
exten => _X!,1,GotoIf($["${clidverif}"=""]?done)
	same => n(setflags),GoSub(npstn-set-flags,s,1)
	same => n,GoSub(disa-rewrite-cnam,s,1)
	same => n(done),GoSub(npstn-out-allow,s,1(${GOSUB_RETVAL}))
	same => n,Return(${GOSUB_RETVAL}) ; Return 1 to complete outgoing call, 0 to divert to intercept

[npstn-set-flags]
exten => s,1,Set(IAXVAR(clidverif)=${clidverif})
	same => n,Set(IAXVAR(DISAVIA)=${maindisa}) ; Lets other nodes know where this call came from so they can verify this NPSTN node
	same => n,Return()

[npstn-out-allow] ; NPSTN 20190215 NA ; determine whether outgoing call should be allowed to complete
exten => s,1,Set(cnamcode=${ARG1}) ; 0=CNAM just modified,1=CNAM received tagged as via DISA,2=CNAM received tagged as via PSTN
	same => n,GotoIf($["${cnamcode}"=1]?disa)
	same => n,GotoIf($["${cnamcode}"=2]?disa)
	same => n,Return(1) ; if cnamcode=0 (CNAM just modified), allow outgoing call to complete regardless
	same => n(disa),GotoIf($["${allowdisathru}"="NO"]?term) ; allowdisathru global var. must be set to YES/NO per NPSTN Docs
	same => n,Return(1) ; if cnamcode=1 (CNAM received as via DISA), allow outgoing call if global var. allowdisathru=YES
	same => n(pstn),GotoIf($["${allowpstnthru}"="NO"]?term) ; allowpstnthru global var. must be set to YES/NO per NPSTN Docs
	same => n(term),Return(0) ; call should not be allowed to leave this switch

[npstn-out-blocked] ; NPSTN 20190215 NA ; destination for calls that are not allowed to leave this node
exten => s,1,Answer()
	same => n,Wait(0.7)
	same => n,PlayTones(2600)
	same => n,Wait(0.3)
	same => n,GoTo(invalidincoming,s,1)

[lookupchan] ; NPSTN 20190316 NA ; analyzes lookup request and returns 0 if it is not a supported channel
exten => s,1,Set(lookup=${ARG1}) ; allowed channel technologies are IAX, SIP, MGCP, and H323
	same => n,NoOp(${TOUPPER(${lookup:0:3})})
	same => n,GotoIf($["${TOUPPER(${lookup:0:3})}"="SIP"]?sip) ; don't further check the destination if it's SIP
	same => n,Set(regres=${REGEX("^[iI][aA][xX]2/[a-zA-Z0-9_\-][email protected]([a-zA-Z0-9_\-]+\.)+[a-zA-Z0-9_\-]+/[0-9]+$" ${lookup})})
	same => n,GotoIf($[${regres}=1]?:bad) ; above ensures there is at least 1 "." in the host (that it isn't a reference to a local peer with username/password)
	same => n,GotoIf($["${TOUPPER(${lookup:0:4})}"="IAX2"]?iax)
	same => n,GotoIf($["${TOUPPER(${lookup:0:4})}"="MGCP"]?mgcp)
	same => n,GotoIf($["${TOUPPER(${lookup:0:4})}"="H323"]?h323)
	same => n(bad),Return(0)
	same => n(iax),Return(1)
	same => n(sip),Return(2)
	same => n(mgcp),Return(3)
	same => n(h323),Return(4)
				

CNAM Subroutines

Copy and paste the following exactly into your dialplan:

[disa-rewrite-cnam] ; NPSTN 20190216 NA ; rewrites CNAM if not already tagged
exten => s,1,Set(pstn=" pstn")
	same => n,Set(disa="disa")
	same => n,Set(gw="gateway")
	same => n,Set(via="via npstn")
	same => n,Set(lowered=${TOLOWER("${CALLERID(name)}")})
	same => n,GoSub(cnamsearch,s,1(${lowered},${pstn}))
	same => n,Set(found=${GOSUB_RETVAL})
	same => n,GotoIf($[${found}=1]?norewritepstn)
	same => n,GoSub(cnamsearch,s,1(${lowered},${disa}))
	same => n,Set(found=${GOSUB_RETVAL})
	same => n,GotoIf($[${found}=1]?norewritedisa)
	same => n,GoSub(cnamsearch,s,1(${lowered},${gw}))
	same => n,Set(found=${GOSUB_RETVAL})
	same => n,GotoIf($[${found}=1]?norewritedisa)
	same => n,GoSub(cnamsearch,s,1(${lowered},${via}))
	same => n,Set(found=${GOSUB_RETVAL})
	same => n,GotoIf($[${found}=1]?norewritedisa)
	same => n(rewrite),Set(CALLERID(name)=${CALLERID(name)} via ${clli} DISA)
	same => n,Return(0) ;;;;;;;;;;;;;;;;;;;; Return NONE code to [npstn-out-verify]
	same => n(norewritedisa),Return(1) ;;;;; Return DISA code to [npstn-out-verify]
	same => n(norewritepstn),Return(2) ;;;;; Return PSTN code to [npstn-out-verify]

[cnamsearch] ; NPSTN 20190216 NA ; loops through ARG1 checking for ARG2 to appear in sequence
exten => s,1,Set(base=${ARG1}) ; base = variable being searched
	same => n,Set(str=${ARG2}) ; str = variable for which to search
	same => n,Set(base=${FILTER(A-Za-z\x20,${base})})
	same => n,Set(str=${FILTER(A-Za-z\x20,${str})})
	same => n,NoOp(Comparing word "${base}" with "${str}")
	same => n,Set(chars=${LEN(${base})})
	same => n,Set(reqch=${LEN(${str})})
	same => n,Set(match=0)
	same => n,Set(a=0)
	same => n,While($[${a}<${reqch}]) ; for each character in ARG2, check if a character in ARG1 matches it
	same => n,Set(b=0)
	same => n,While($[${b}<${chars}]) ; for each character 
	same => n,GotoIf($["${str:${a}:1}"="${base:${b}:1}"]?:nextb) ; if no match, don't check for 2-in-a-row match
	same => n,Set(c=0)
	same => n,Set(matchedchars=0)
	same => n,While($[${c}<${reqch}]) ; if match, check for all subsequent LEN(ARG2) chars. to match
	same => n,GotoIf($["${base:$[${c}+${b}]:1}"="${str:${c}:1}"]?:nextc)
	same => n,Set(matchedchars=$[${matchedchars}+1])
	same => n,GotoIf($["${matchedchars}"="${reqch}"]?success:nextc) ; have all LEN(ARG2) chars. been interated through?
	same => n(success),Set(match=1) ; complete match found
	same => n(nextc),Set(c=$[${c}+1])
	same => n,EndWhile()
	same => n(nextb),Set(b=$[${b}+1])
	same => n,EndWhile()
	same => n,Set(a=$[${a}+1])
	same => n,EndWhile()
	same => n,Return(${match})
			

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.

You won't need to do this every time you update your dialplan, but since we modified several files here, it would be a good idea to just reload Asterisk in its entirety and restart it:

reload
core restart now

If you do happen to be using FreePBX, you can reload by typing fwconsole reload. In vanilla Asterisk, you can also try reload asterisk.

Once Asterisk exits, enter asterisk -r to enter back into the Asterisk CLI.


NetCID

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 NPSTN 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:

#!/usr/bin/perl
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=192.168.1.255;
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): $!";
sleep(5);
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
sleep(5);
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
sleep(5);
send(SOCKET, $MSG7, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
close(SOCKET);
exit;
			

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.

Billing

NPSTN is a free unique telephone collectors' network. NPSTN has a "billing" functionality that is purely for fun. The idea is to show you all the calls you make on NPSTN and what they would have cost back in the day. The bill is for fun, and you are not actually charged anything. NPSTN is and always will be 100% free.

One of the most unique things about NPSTN is its state-of-the-art mock "billing" system. 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 NPSTN code. Once a user has made a call from his or her telephone, he or she can login to the NPSTN User Control Panel. Once logged in, in the directory, you will see all numbers you have listed in both the general and residential directories.

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 made. This means calls during the day will cost more than calls in the evening or on weekends, calls completed to NPSTN 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.

Note that all of these charges are just for show. You will never be issued a real bill demanding payment. This billing system exists exclusively for the entertainment and amusement of NPSTN callers, although it also functions as a historical complement. 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 NPSTN 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 to NPSTN whenever "long distance" calls occur — that is, calls between a node and any other. A few exceptions exist to NPSTN's otherwise completely accurately simulated billing model exist: local calls are all charged a $0.05 fee and use 1 message unit in the process. 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 a charge for each call is determined by the local timezone of the switch from which a call originates. Therefore, it is possible members hosted on other members' nodes who are not located in the same time zone may experience discrepancies between their local time and the time used when determining the rate period for that call. Consequently, however, it is imperative that the time on all nodes are correct, and not merely left to the default (most likely UTC, which won't be helpful unless you are, actually, in UTC!).

Additionally, because callers' locations are used when calculating the rates for each call, all NPSTN users should have their own NPSTN UCP (User Control Panel) account, which they will use to add themselves to the directory. Node owners should not add hosted users on their node to any of the directories using their account. All users should have their own UCP account. If you don't have one, contact us and ask for an NPSTN UCP account to be created for you (currently, there is no way to create one yourselves). You will need to provide us with your full name, your ZIP code, your city, and your state. International users don't need to provide a ZIP code, obviously, but should let us know the country in which they are located, in addition to their city. We will email you your auth key, which is used to access the UCP (in lieu of a username and password - it goes without saying, don't share this with anyone else!). In order to view your bill, you should ensure that you've added yourself to the White Pages on NPSTN (residential listings) using your billing number (in other words, the number used for your caller ID on outgoing NPSTN calls). If you are hosted, this is likely set by your host. If not, then you as a node owner are responsible for ensuring the billing system is in place on your node. See the "Common Contexts" section for more on this. If you use our bare bones boilerplate code to get started, you should be all set! You don't need to do anything else to have a bill generated for you other than make some phone calls! Once you have a balance under a number, that number will appear hyperlinked in all of the directories in the UCP in which that number appears. Click on the link to open the bill for that number. Charges reflect all calls made during the last billing cycle, which — on NPSTN — is merely the last 30 days. After 30 days, charges will disappear from your bill and no longer will be reflected in your balance.

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. You will never have to pay the balance that appears on your billing statements. However, the charges you see do accurately reflect the charges that would have been issued on any NPSTN calls you make had you made those same calls to the same people using the PSTN in the late 1970s. 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!

Standards

NPSTN 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.

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

Numbering Plan

A few words about the NPSTN numbering plan:

In order to be a valid NPSTN number, a number must be an odd number of digits. This does not necessarily mean a number is valid if its digit length is odd! All valid numbers on NPSTN are either 1, 3, 5, or 7 digits long.

For the most part, NPSTN 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 NPSTN.

Furthermore, unlike C*NET, NPSTN 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 reached by the NPSTN Board of Directors 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.

In addition to that, NPSTN 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.

Furthermore, there are special numbers on NPSTN, 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
  • 112 — "NPSTN Long Distance Operator" — rings automatic operator directly
  • 113 — Information — rings automatic information operator directly
  • 114 — Repair Service — rings automatic repair agent directly
  • 115 — "C*NET Long Distance Operator" — rings automatic operator directly
  • 116 — Business Office — rings the business office
  • 117 — Telegrams
  • 118 — StepNet Trunk
  • 119 — Long Distance Trunk

Other directory services include:

  • POPCORN — POPCORN, a.k.a. 767-2676 will provide your local time
  • 211 — Time & Temperature
  • 511 — Temperature & Location
  • 911 — Emergency Intercept Recording

Finally, network-wide test numbers include:

  • 311 — "Party Line"
  • 660 — Ring & Tone 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.

Case Study: NPSTNNA01

The 3-digit codes above form the basic exceptions to 7-digit dialing on NPSTN that you will need to handle. Of course, many exchanges have even more complex routing. Below is the dial plan for NPSTNNA01. You can try the following out for yourself by dialing into one of its DISAs (e.g. BE1-1111).

Below, N indicates a digit from 2 through 9, X indicates any digit from 0 to 9, W indicates either a 0 or 1, and Z indicates any digit except for 1. Our use of the W here is based off of its use in Notes on the Network, Signaling → LATA Access. Z was used in same capacity as X in Notes in the same section, but we are choosing to use it as Asterisk uses it (any digit from 1 through 9). We have defined U to arbitrarily mean 0 or 2 through 9.

X = [0-9], N = [2-9], W = [01], Z = [1-9], U = [02-9]

  • "" → permanent signal intercept
  • "*" → Momentary 2600 Hz Supervision
  • "0" → Operator
  • "1N" → 0xx/1xx Intercept
  • "N0" → Tie Lines
  • "11N" → 11N codes
    • "112" → Long Distance Operator
    • "113" → Information
    • "114" → Repair
    • "115" → Long Distance (C*NET)
    • "116" → Business Office
    • "117" → Telegrams
    • "118" → StepNet Trunk
    • "119" → Long Distance Trunk
  • "N1N" → Vacant (Switch Level Busy)
  • "310" → Tie Line to SxS PBX
  • "N11" → N11 codes
    • "211" → Time & Temperature
    • "311" → "Party Line" Conference
    • "411" → Directory Assistance
    • "511" → Temperature & Location
    • "611" → Repair
    • "711" → TTY Relay
    • "911" → Emergency
  • "660" → Ring & Tone Test
  • "958" → ANAC
  • "959" → Ringback
  • "10UXX" → Feature Group D CAC Changed Intercept
  • "5551212" → Directory Assistance
  • "7365000" → PEnnsylvania6-5000
  • "7672676" → POPCORN (Time)
  • "8675309" → Jenny/867-5309
  • "NNXXXXX" → Local (Intra-Office)
    • "231XXXX" → BEechwood1
    • "234XXXX" → BEechwood4
    • "251XXXX" → CLearbrook1
    • "288XXXX" → BUtterfield8
    • "355XXXX" → FLeetwood5
    • "444XXXX" → HIckory4
    • "549XXXX" → LIncoln9
    • "650XXXX" → OLdfield0
    • "747XXXX" → PIoneer7
  • "950WXXX" → Feature Group B Dial-Around
  • "101XXXX" → Feature Group D Dial-Around
  • "NNXXXXX" → Long-Distance (NNX-XXXX)

Standard Numbers

The following is a list of standardized station numbers (the last 4 digits of NNX-XXXX) on NPSTN. 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, which is purely an NPSTN convention. 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.

Exchanges

Nodes

The following is a partial list of NPSTN nodes. Other related information can be found in the NPSTN Route Table. Here, "StepNet?" is short for "StepNet participating tandem?"; nodes are not required to participate as StepNet tandems.

This information is no longer actively updated and is incomplete. However, it provides a partial glimpse as to network structure. Inquiries about current network nodes should be directed to the Network Operators Center; please call the Business Office.

CLLI Owner ZIPCODE/Location Allocated Prefixes StepNet? Notes
NPSTNMS0 TANDEM 01 89107/Las Vegas, NV 229-[0-2], 327-[0-26], 555-1, 564-4, 842-4 Yes MN1
NPSTNCFL01 Dylan Cruz 32708/Winter Springs, FL 221-[0-2], 542-1, 646-[0-2], 695 Yes PS2
NPSTNSCXY01 Dylan Cruz 32708/Winter Springs, FL 655 No PS2
NPSTNUK01 Brian Clancy 00000/Lincoln, UK 543, 544 No
NPSTNNA01 Naveen Albert 53189/Waukesha, WI 231, 234, 251, 288, 355, 444, 549, 650, 747, 950-[01] Yes NA01
STJHNF01DS0 Andrew Green 00000/St. John’s, NL, Canada 222 No
Don Froula 60175/Saint Charles, IL 762-[0-3], 763-[0-2] No
Keith Hlavacs 48746/Millington, MI 823-[239] No
NPSTNMT01 Daniel Lode 59701/Butte, MT 782-6 No
NPSTNCFL02 Jacob Stewart 32708/Winter Springs, FL 365-[0-1] No PS1
NPSTNSFL01 Harry Smith 33071/Coral Springs, FL 325 No
NPSTNNRW01 DSK 00000/Norway 527 No
NPSTNCTX01 Rod Bangert 75019/Coppell, TX 285-[0-2] No
ESCLCAX0DS0 Anthony Hoppe 95320/Escalon, CA 324 No
ACTNMA2FVS0 John Covert 01720/Acton, MA 263 No
ACTNMA4GVLP John Covert 01720/Acton, MA 264 No
Evan Sutherland 24701/Bluefield, WV 873 No
LSVGNVGCDS0 Rudy Valencia 80631/Greeley, CO 677 No
DBLNCA01XBT Howard Harte 94568/Dublin, CA 551-[7-8], 699-[0-3] No
NPSTNNYC01 Chaz Antonelli 10001/New York, NY 266-8, 266-9 No
MTHWVASHMG0 Noah Hendrix 23109/Mathews, VA 725-3, 725-4 No
Ken Spacil 19365/Parkesburg, PA 857-[1-389] No
Ramadya Sasongko 77042/Houston, TX 785-[189] No
Con Blackett 00000/UK 224-[19] No
NPSTNSNV01/NPSTNLVNV01 Dennis Warner 89193/Las Vegas, NV 438-[09] No

Exchange Names

Following is a list of exchanges and associated exchange names. More information about the node on which each exchange is located can be found in the table above. You can use the following table to determine if the prefix you want is taken or not (or check the route table as described in "Registering an Office Code").

The exchanges 660, 950, 958, and 959 are not available, in addition to any exchanges already claimed.

767-2676 (POPCORN), PEnnsylvania6-5000 (736-5000), and 867-5309 are also network-reserved numbers and are not available.

NNX-X Exchange Name
221-[0-3]
222-X
229-[0-2]
231-X BEechwood1
234-X BEechwood4
251-X CLearbrook1
263-X COlonial3
264-X COlonial4
266-[8-9]
285-[0-2] ATwater5
288-X BUtterfield8
324-X
325-X
327-[0-26] FAirfax7
355-X FLeetwood5
365-[0-2]
444-X HIckory4
527-X
542-1
543-X LIberty3
544-X LIberty4
549-X LIncoln9
551-[7-8]
555-1 KLondike5
564-4 LOcust4
646-[0-2] MIlton6
650-X OLdfield0
655-X OLdfield5
677-X ORange7
695-X OXford5
699-[0-3]
725-[34]
747-X PIoneer7
762-[0-3]
763-[0-2]
782-6
785-[189]
823-[239]
842-4 VInewood2
857-[12389] TRinity3
873-X TRinity3
950-[01]

PSTN DID Exchanges

The thousands blocks (407) 564-4XXX and (407) 842-4XXX on the PSTN are direct inward dial** into NPSTN — terminating to our LOcust4-4 and VInewood2-4 exchanges.

PSTN DID numbers route into NPSTN through NPSTNMS0 and then over real 2600 Hz carrier trunks to the terminating node.

Qualified NPSTN and C*NET members may qualify for a DID number. Contact NPSTN for more details. You may dial 116 during business hours to reach the NPSTN business office.

**Some numbers may not be available. DIDs are available in the 842-3XXX range.

Mountain Pacific Telephone Co. — RTC-NPSTN01 and RTC-NPSTN02

LOcust4-4 terminates to NPSTNNA01 while VInewood2-4 terminates to NPSTNCFL01.

PSTN DID Allocation

Please see the NPSTN Directory for 564-4 and 842-4 listings. These exchanges are DID from both NPSTN and the PSTN's 407 NPA.

Some numbers in the LOcust4-4 and VInewood2-4 DID ranges are "missing":

The following station numbers from 564-4 are missing:

  1. 4000
  2. 4100
  3. 4111
  4. 4200
  5. 4222
  6. 4300
  7. 4333
  8. 4400
  9. 4444
  10. 4466
  11. 4487
  12. 4500
  13. 4555
  14. 4600
  15. 4666
  16. 4683
  17. 4700
  18. 4747
  19. 4777
  20. 4800
  21. 4888
  22. 4900
  23. 4999

The following station numbers from 842-4 are missing:

  1. 4000
  2. 4001
  3. 4100
  4. 4111
  5. 4200
  6. 4222
  7. 4242
  8. 4300
  9. 4333
  10. 4400
  11. 4444
  12. 4500
  13. 4555
  14. 4600
  15. 4666
  16. 4700
  17. 4777
  18. 4800
  19. 4888
  20. 4900
  21. 4970
  22. 4999

In addition, (407) 842-8403 is assigned as well (an outlier as it is not in the 842-4 thousand block).

For specific 564-4 and 842-4 listings, please see the Directory. The following is the general structure of the 842-4 exchange:

  • 41xx — Dylan's numbers
  • 42xx — Vintage switches
  • 43xx — NPSTN/DID Subscribers
  • 44xx — NPSTN/DID Subscribers
  • 45xx — Dylan's numbers
  • 46xx — Dylan's numbers
  • 47xx — Reserved
  • 48xx — Reserved
  • 49xx — Reserved
  • 40xx — Reserved

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 /
wget https://github.com/hharte/PatFleet-asterisk/archive/master.zip
unzip PatFleet-asterisk-master.zip -d /var/lib/asterisk/sounds/en
rm -f PatFleet-asterisk-master.zip

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] ; NPSTN 20190212 NA
    exten => start,1,Set(num=${ARG1})
    	same => n,Playback(custom/digits/${num:-7:1}n)
    	same => n,Playback(custom/digits/${num:-6:1}n)
    	same => n,Playback(custom/digits/${num:-5:1}d)
    	same => n,Playback(custom/digits/blank)
    	same => n,Playback(custom/digits/${num:-4:1}n)
    	same => n,GotoIf($[${num:-3:3}=000]?thousand)
    	same => n,Playback(custom/digits/${num:-3:1}n)
    	same => n,Playback(custom/digits/${num:-2:1}n)
    	same => n,Playback(custom/digits/${num:-1:1}d)
    	same => n,Return()
    	same => n(thousand),Playback(custom/ais/thousand)
    	same => n,Return()
    
    [aisnis] ; NPSTN 20190212 NA
    exten => start,1,Set(num=${ARG1})
    	same => n,Verbose(AIS Not In Service -- "${num}")
    	same => n,Playback(custom/ais/clunk)
    	same => n,Playback(custom/ais/numreach)
    	same => n,GoSub(ais7,start,1(${num}))
    	same => n,Playback(custom/ais/notinservice)
    	same => n,Playback(custom/ais/pleasecheck)
    	same => n,Playback(custom/ais/dialagain)
    	same => n,Playback(custom/digits/blank)
    	same => n,Return
    
    [aischanged] ; NPSTN 20190212 NA ; This requires an argument with the new number
    exten => start,1,Set(num=${ARG1})
    	same => n,Set(newnum=${ARG2})
    	same => n,Verbose(AIS Number Changed -- "${num}")
    	same => n,Playback(custom/ais/clunk)
    	same => n,Playback(custom/ais/numreach)
    	same => n,GoSub(ais7,start,1(${num}))
    	same => n,Playback(custom/ais/hasbeenchanged)
    	same => n,Playback(custom/ais/newnumberis)
    	same => n,GoSub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote)
    	same => n,Playback(custom/digits/blank)
    	same => n,Return
    
    [aisnpachanged] ; NPSTN 20190212 NA ; This requires an argument with the new number
    exten => start,1,Set(oldnum=${ARG1})
    	same => n,Set(newnum=${ARG2})
    	same => n,Verbose(AIS Number Changed To Different NPA -- "${oldnum}")
    	same => n,Playback(custom/ais/clunk)
    	same => n,Playback(custom/ais/numreach)
    	same => n,GoToIf($[${oldnum}=nonum]?changed)
    	same => n,GoSub(ais7,start,1(${oldnum}))
    	same => n(changed),Playback(custom/ais/hasbeenchanged)
    	same => n,Playback(custom/ais/newnumberis)
    	same => n,GoToIf($[${LEN(${newnum})}=7]?seven)
    	same => n,Playback(custom/ais/area)
    	same => n,Playback(custom/digits/${newnum:-10:1}n)
    	same => n,Playback(custom/digits/${newnum:-9:1}n)
    	same => n,Playback(custom/digits/${newnum:-8:1}d)
    	same => n,Playback(custom/digits/blank)
    	same => n(seven),GoSub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote)
    	same => n,Playback(custom/digits/blank)
    	same => n,Playback(custom/ais/numreach)
    	same => n,Playback(custom/ais/hasbeenchanged)
    	same => n,Playback(custom/ais/newnumberis)
    	same => n,GoToIf($[${LEN(${newnum})}=7]?seven2)
    	same => n,Playback(custom/ais/area)
    	same => n,Playback(custom/digits/${newnum:-10:1}n)
    	same => n,Playback(custom/digits/${newnum:-9:1}n)
    	same => n,Playback(custom/digits/${newnum:-8:1}d)
    	same => n,Playback(custom/digits/blank)
    	same => n(seven2),GoSub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote)
    	same => n,Playback(custom/ais/dialagain)
    	same => n,Playback(custom/digits/blank)
    	same => n,Return
    
    [aisworking] ; NPSTN 20190212 NA
    exten => start,1,Set(num=${ARG1})
    	same => n,Verbose(AIS Working Number -- "${num}")
    	same => n,Playback(custom/ais/clunk)
    	same => n,Playback(custom/ais/numreach)
    	same => n,GoSub(ais7,start,1(${num}))
    	same => n,Playback(custom/ais/isworkingnum)
    	same => n,Playback(custom/ais/willdialagainplease)
    	same => n,Playback(custom/digits/blank)
    	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:
    [KLondike5]
    … all your 555 extensions here …
    include => invalidincoming
    					
    Now, tweak your code so it is as follows:
    [KLondike5]
    … all your 555 extensions here …
    include => KLondike5-unmatched
    
    [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:

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

[KLondike5-unmatched]
exten => _XXXX,1,GoSub(aisnis,start,1(${OC1}${EXTEN:-4}))
	same => n,Hangup()

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

[KLondike6-unmatched]
exten => _XXXX,1,GoSub(aisnis,start,1(${OC2}${EXTEN:-4}))
	same => n,Hangup()
			

ANAC

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] ; NPSTN 20190212 NA
exten => start,1,Set(num=${CALLERID(num)})
	same => n,Verbose(ANAC -- "${num}")
	same => n,Set(num=${FILTER(0-9,${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)
	same => n(d6),Playback(custom/digits/${num:-6:1}n)
	same => n(d5),Playback(custom/digits/${num:-5:1}d)
	same => n,Playback(custom/digits/blank)
	same => n(d4),Playback(custom/digits/${num:-4:1}n)
	same => n(d3),Playback(custom/digits/${num:-3:1}n)
	same => n(d2),Playback(custom/digits/${num:-2:1}n)
	same => n(d1),Playback(custom/digits/${num:-1:1}d)
	same => n(d0),Playback(beep)
	same => n(done),Return
			

Now, to make your ANAC work, you'll need to put it on an extension. "958" network-wide on NPSTN is an ANAC (the very same one you see here), 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. You can choose to have your ANAC supervise if you wish — NPSTN and C*NET callers are never "charged" for making calls, so unless you call this inbound from the PSTN from a payphone or home phone without flat-rate long-distance, it's really a non-issue — but to be "time-period correct", we've opted here to 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] ; NPSTN 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(waitloop),Set(TimeLeft=$[${FutureTime} - ${EPOCH}])
	same => n,GotoIf($[${TimeLeft} < 1]?playbeep)
	same => n,Wait(1)
	same => n,Goto(waitloop)
	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()

[sub-hr12format]
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()

You can find the timezone name for your location as described above and customize the number of cycles to your liking.

Note: On NPSTN, there are several numbers (like 211) that include a speaking clock as part of a broader time and temperature service that is provided to all callers. Note that both this service and POPCORN rely on your node's location being manually entered into a lookup table on one of the tandems. If you call POPCORN or 211 and hear incorrect information, you will need to leave a voicemail at BEechwood1-2131 with your U.S. ZIP code, city, and the PSTN NPA (area code) in which you are located.


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 on NPSTN. 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:

[mw]
exten => start,1,Wait(0.5)
	same => n,Milliwatt(${ARG1})
	same => n,Return
			

ARG1 is the number of seconds for which to play the test tone. Usually, 10 minutes (or 600 seconds) will suffice. We wait 0.5 seconds before starting as milliwatt numbers usually offer a split second of silence before blasting your eardrums.

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"):

[mwtone] ; NPSTN 2018 DC/NA
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).

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:

[mwtone] ; NPSTN 2018 DC/NA
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

Ringback is a fairly simple task that is quite involved in terms of dialplan code. Here is the code that operates the 959 ringback number on NPSTN (at least, it's some of the code; the 959 code is more involved as it will also ring back C*NET callers in addition to NPSTN callers):

[ringbacknpstn] ; NPSTN 2018 DC/NA
exten => s,1,Answer
	same => n,PlayTones(600*120)
	same => n,Wait(60)
exten => h,1,Verbose(Ringing Back NPSTN Caller ${CALLERID(num)})
	same => n,Set(lookup=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${CALLERID(num)}")})
	same => n,Log(NOTICE, NPSTN Ringback: ${lookup})
	same => n,System(echo Channel: ${lookup} >> /tmp/cf.tmp)
	same => n,System(echo Application: Wait >> /tmp/cf.tmp)
	same => n,System(echo Data: 1209600 >> /tmp/cf.tmp)
	same => n,System(echo CallerID: "Ringback <${OC1}9959>" >> /tmp/cf.tmp)
	same => n,System(mv /tmp/cf.tmp /var/spool/asterisk/outgoing/)
	same => n,Hangup
			

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(600. Another option is choosing to play a NOYOL (number on your own line) announcement, which was used frequently when party lines were more common. You could then add some dialplan code in your exchange context to call [ringbacknpstn] whenever ${CALLERID(num)} is the same as ${OC1}${EXTEN}. This would allow you to form a simple intercom by allowing callers to dial their own number and hang up, which would allow multiple people to pick up and converse, even with just a single line.

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 ${OC1}9959 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 [ringbacknpstn] is not a subroutine. You will need to call it as exten => 9959,1,GoTo(ringbacknpstn,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:

wget https://octothorpe.info/downloads/pulsar-agi.tar.gz
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/.

Now, add the following subroutine to your dialplan:

[revertive] ; NPSTN 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:

[internal-users]
include => local
include => long-distance
include => invalidincoming

[external-users]
include => local
include => invalidincoming

[local]
exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)
			

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

[local]
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:

[internal-users]
include => local-intraoffice
include => long-distance
include => invalidincoming

[external-users]
include => local-interoffice
include => invalidincoming

[local-intraoffice]
exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)

[local-interoffice]
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:

[local-intraoffice]
exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)

[local-interoffice]
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:

[internal-users] include => local-intraoffice include => long-distance include => invalidincoming [external-users] include => local-interoffice include => invalidincoming [local-intraoffice] exten => _${OC1}XXXX,1,GoTo(KLondike5-internal,${EXTEN:-4:4},1) [local-interoffice] exten => _${OC1}XXXX,1,GoSub(revertive,start,1(${EXTEN:-4},0,5xb)) same => n,GoTo(KLondike5,${EXTEN:-4:4},1) [KLondike5-internal] exten => 6666,1,GoTo(verysecretcontext,s,1) include => KLondike5 [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.

MFer

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.

Next, add the following global variables to the [globals] context of your dialplan:

MFX = "!0/500" ; silence
MF1 = "!700+900/50|!0/50"
MF2 = "!700+1100/50|!0/50"
MF3 = "!900+1100/50|!0/50"
MF4 = "!700+1300/50|!0/50"
MF5 = "!900+1300/50|!0/50"
MF6 = "!1100+1300/50|!0/50"
MF7 = "!700+1500/50|!0/50"
MF8 = "!900+1500/50|!0/50"
MF9 = "!1100+1500/50|!0/50"
MF0 = "!1300+1500/50|!0/50"
MFKP = "!1100+1700/100|!0/50"
MFST = "!1500+1700/35|!0/50"
DIGITSEP = "|"
			

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!)

The global variables above are necessary for the MFer subroutine to work properly.

Now, add the following subroutines to your dialplan:

[mfer] ; NPSTN 20181212 NA/BJC
exten => start,1,Set(number=${ARG1}) ; ARG1 = digits to MF
	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
			

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:

[local-interoffice]
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)
			

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 dialnpstn subroutine! Instead, create a separate outpulsing context that you call before dialnpstn (or call the particular outpulsing subroutine). Either way, call any outpulsing separately before calling the dialnpstn subroutine.

SFer

The original approach to the SFer was (so we thought) fairly straightforward: a single SF burst is about 62ms of 1600 Hz, so why not write a subroutine that would take in as ARG1 the number to SF and, for each digit, call another subroutine that simply looped the same number of times as the digit in order to "SF" that digit. Unfortunately, this approach did not work.

As a workaround, an SFer incorporating audio files. It sounds decent, but it's not as good as it could be, since modifying how it sounds would require extensive audio editing, which is somewhat impractical. This SFer has since been rewritten in much the same manner that our MFer was — that is, using only pure tones generated by Asterisk itself and, rather than playing a tone for each digit, calling a subroutine that plays a 2600 Hz tone for a variable-defined length of time (about 60ms) and loops the number of times specified by ARG1, which would SF a single digit.

The new SFer is for most purposes better than the old one, but here is the audio file-based SFer:

[sfer] ; NPSTN 20190212 NA
exten => start,1,Set(number=${ARG1}) ; ARG1 = digits to SF, ARG2 = delay between digits
	same => n,Set(c=0)
	same => n,Set(numdigits=${LEN(${ARG1})})
	same => n,While($[${c}<${numdigits}])
	same => n,Playback(custom/sf/sf${number:${c}:1})
	same => n,Wait(${ARG2}) ; sound files factor in the appropriate pause, ARG2 should be ~0
	same => n,Set(c=${INC(c)})
	same => n,EndWhile
	same => n,Return
			

Click here to download the audio files needed for the SFer at this time. You will need to extract the audio files to /var/lib/asterisk/sounds/en/custom/sf/.

To use the new SFer, you'll need some global variables defined. If you don't have the MFer global variables, you'll have to add this one first:

DIGITSEP = "|"
			

Either way, you will need to add these global variables:

SFINTER=200
SFX = "!0/200" ; interdigit silence
SFPULSE=100
SFHZ=2600
SFP=60
SFB=40
SF1 = "!${SFHZ}/${SFP}|!0/${SFB}"
SF2 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF3 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF4 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF5 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF6 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF7 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF8 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF9 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
SF0 = "!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}|!${SFHZ}/${SFP}|!0/${SFB}"
			

Here is the new and improved [sfer] subroutine:

[sfer] ; NPSTN 20190218 NA ; ARG1 = digits to SF
exten => start,1,Set(number=${ARG1}) ; ARG1 = digits to do something with
	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(p=$[${p}+${number:${c}:1}])
	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] ; NPSTN 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})
	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:

[bridge]
type=bridge
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.

[caller]
type=user
dsp_drop_silence=no
announce_only_user=no

[volmenu]
type=menu
4=decrease_listening_volume
5=reset_listening_volume
6=increase_listening_volume
7=decrease_talking_volume
8=reset_talking_volume
9=increase_talking_volume
0=toggle_mute

[advmenu]
type=menu
1=participant_count
4=decrease_listening_volume
5=reset_listening_volume
6=increase_listening_volume
7=decrease_talking_volume
8=reset_talking_volume
9=increase_talking_volume
0=toggle_mute
			

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.

Crosstalk

To create a crosstalk mechanism for your switch, add the following to your dialplan:

[KLondike5] ; don't create this context again - add the following line to your existing exchange context!
exten => 1057,1,ConfBridge(57)

[crosstalk] ; NPSTN 2019 DC/NA
exten => _X!,1,Answer
	same => n,Set(VOLUME(TX)=1)
	same => n,Set(VOLUME(RX)=1)
	same => n,System(echo Channel: Local/[email protected] >> /tmp/cf.tmp)
	same => n,System(echo Application: Playback >> /tmp/cf.tmp)
	same => n,System(echo Data: custom/crosstalk/l >> /tmp/cf.tmp)
	same => n,System(echo CallerID: "Crosstalk <${OC1}1057>" >> /tmp/cf.tmp)
	same => n,System(mv /tmp/cf.tmp /var/spool/asterisk/outgoing/)
	same => n,BackGround(custom/crosstalk/h)
	same => n,ConfBridge(57)
	same => n,Hangup
			

We have chosen to use the number "57" here, but it can be any unique number. This conference bridge will be used solely for creating crosstalk.

The Background statement plays a file to add to the crosstalk — in this case a CBCAD intercept that is "bleeding over" into other trunks.

Here, there are two files in the ccad folder, one named "h" and one named "l". The "h" recording is slightly longer.

Now, to create crosstalk, call GoTo(crosstalk,${EXTEN},1). This will automatically establish another call playing a similar (but slightly different) audio file in the background on the same conference bridge.

Echo Test

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

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

[echo]
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:

[echo]
exten => s,1,Answer() ; 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.

Silent Termination

Creating a silent termination line is incredibly simple:

[silentterm]
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:

[KZSU]
mode=custom
application = /usr/bin/mpg123 -q -s --mono -r 8000 -f 8192 -b 0 http://171.66.118.110:8080/kzsu-1-128.mp3

[KPFA]
mode=custom
application = /usr/bin/mpg123 -q -s --mono -r 8000 -f 8192 -b 0 http://streams.kpfa.org:8000/kpfa
			

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(http://171.66.118.110:8080/kzsu-1-128.mp3)
	same => n,Hangup()
exten => 5732,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MP3Player(http://streams.kpfa.org:8000/kpfa)
	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

[loop]
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:

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

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

[looparound]
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), please leave us a message at BE1-9942.

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 NPSTN 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)
			

Whisper:

	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:
    wget https://telephony.ml/docs/virtual-nic/addr1.c
    wget https://telephony.ml/docs/virtual-nic/addr2.c
    wget https://telephony.ml/docs/virtual-nic/taptap-modified.c
  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

Modems

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
    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
  2. 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/issue.net
    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.
  3. 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)
  4. 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

  5. Now, create a blank shell script that will be used as the entry point for loopback Telnet sessions initiated from Asterisk:
    touch /home/com/main.sh
    usermod -s /home/com/main.sh com
    chmod a+x /home/com/main.sh
    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.
  6. 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
  7. 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:
    wget https://raw.githubusercontent.com/proquar/asterisk-Softmodem/app_softmodem/app_softmodem.c
    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
    reboot
  8. 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(127.0.0.1,23,v(Bell103)ld(8)s(1)un)
    	same => n,Hangup()
    					
  9. Reload your dialplan by typing asterisk -r and then dialplan reload
  10. 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 NPSTN.

StepNet

StepNet is an additional layer on top of NPSTN, not a separate network. It is what is actively in development right now, in addition to some other related items coming down the pipe.

Using StepNet is very simple. When calling the dialnpstn subroutine, instead of calling it like so:
GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode})), call it like so:
GoSub(dialnpstn,start,1(${EXTEN},enable,${zipcode})).

Now, your call will use StepNet for that call!

At this point, StepNet is still in development, so we recommend you keep all your subroutine calls using disable as ARG2 at this point. When StepNet is in beta or ready for public use, we will post updates here informing users that they can try it out.

Background

The idea is to replicate the call routing of the 1950's into the NPSTN VoIP Network with a flag set in the API Lookup request. This flag can be disabled for normal peer-to-peer calling or enabled for peer-tandems-peer calling. Although more dialplan code will need to be developed for each Asterisk switch to actually implement StepNet trunking, the code needed to make the proper lookup request is already in the dialnpstn subroutine.

StepNet is an adjunct to, rather than a principle part of, each node's dialplan. Members are free to allow StepNet trunking or not at their node. The route table will keep track of what nodes have opted-in to StepNet trunking. You must opt-in to participate (a node is not a participant by default). Note by participant, any node can make a StepNet call without being a registered StepNet tandem, but only those who nodes who have opted-in will process (or trunk) the call along the way.

Once a caller makes a StepNet call by calling dialnpstn with ARG2 set to enable (as opposed to disable), the Central Route Table Controller then creates a physical route between the caller and the called party routing through physical tandems based on geographic areas in between the communications path. These tandems will be community-run, just like the end office switches.

Discussion

Thoughts on StepNet from Brian Clancy, Senior Network Adviser

I see StepNet as an exciting project, fun for those who like to dial around one or more nodes, stack calls, etc. Without really having discussed this project yet I see it as something apart from the the local exchange network that NPSTN is currently. In the real world the trunk network and the local network are different parts of the telephone system albeit closely integrated. I see StepNet and NPSTN in the same way, two parts of the whole.

To my mind StepNet is the trunk network whereas NPSTN as is, is the local network. Thus I believe that StepNet should be a second dialplan at each node handling trunk connections between nodes and interfacing to the local network dialplan at each node. The local network node has junctions to its near neighbours whereas the trunk network node has direct or indirect access to every other node in the network.

Whereas local junction calls route via [email protected] my thinking is that trunk calls should route via [email protected] thus allowing the dialplan at each node to be two separate entities with local links giving access between the two. There would be no 'subscribers' on the trunk network and in the local network there would be a distinction between local calls (home and adjacent nodes) and trunk calls making for more interesting call routing.

StepNet would be optional for those wishing to take advantage of its 'phreaking' possibilities and those less keen on such things would have access to all other nodes as if they were local and with CLID verification.

Words of Wisdom from Brian Clancy, Senior Network Adviser

Before I say more let me roughly explain how 4 wire transit working happened in the UK trunk network because my suspicion is that the US network worked in similar fashion.

Assume national number 052271299 was to be called. This number was actually 0+LC2+71+299 where 0 was the national access code, LC2 was the code for Lincoln GSC, 71 was the local code for Scampton exchange and 299 was the number on Scampton exchange. LC2 or 522 were known as the A,B and C digits and these were used to route the call from originating GSC to destination GSC via a maximum of one intermediate GSC. The ABC digits were used to select a route relay at the originating GSC which gave a route towards the destination GSC by stepping the local 4 wire switches. The 52271299 was then pulsed ahead to the next GSC, if this GSC was not the destination GSC then again the ABC digits would route the call as before to the next GSC which would be the destination GSC. At the destination GSC the C digit (2in this case) was used to determine which 10000 line exchange unit (there could be several such units controlled from/at a GSC where one unit was at the GSC and the others were dependent group switching centres, Lincoln GSC had its own unit and three dependents). At Lincoln GSC C digit selected Lincoln local exchange. Lincoln subscribers were on levels 2XXXX, 3XXXX, 4XXXX at that time while local Lincoln dependent exchanges were on 7X and 8X codes and 9X codes were local routes to adjacent GSCs and their dependent exchanges. So at Lincoln 71 was used to step the local selectors to seize a junction route to Scampton exchange.

Access to the national network from local exchange subscribers was by dialling the national access digit 0. The local exchange first selector recognised 0 and immediately, without stepping, seized a Local Register. Seizure of the local register sent a predetermined routing digit to the first selector (in my local exchange that digit was 9 but it could have been any digit) stepping it vertically to give a route to the group switching centre (GSC)and seize a register translator (R/T). In the meantime the subscriber dialled digits were stored and then pulsed out to the GSC to be stored in the R/T. The route to the next GSC was determined until the destination GSC was reached and the local code 71 was used to route to the local exchange and number 299 used to select the subscriber.

The key part of the above is obviously the trunk switching portion which was done with 4 wire amplified lines, AC9 (2280Hz signalling) Strowger (Motor Uniselector) and Crossbar 4 wire switching. The trunk portion of the call being set up using the same set of digits from originating GSC to destination GSC with or without an intermediate GSC. Modern digital switching uses similar routing principles whereby the same digits are used repeatedly to route a call before they are all spent.

Now in Asterisk it simply is not possible to replicate this type of call forwarding using forward and reverse handshaking so another method needs to be employed to simulate it. A lookup table would seem to be the obvious answer to the problem of forward routing. A node must have access to all adjacent nodes but it must also know which of those nodes is en route to a given destination so life immediately becomes more complicated. Each node must have at least two lookup tables, the first will be a list of all possible destinations which can be accessed from each adjacent node, the second will be the route access data to each adjacent node. Thus multiple tables would seem to be required, i.e. 10 nodes = 20 tables but a better way perhaps is to have a single destination table that lists all nodes with direct and indirect access to each destination and a second nodes table which lists each node and its directly adjacent nodes. That said the destination table could possibly be rationalised into say 5 sub areas which would reduce the amount of data duplication required and thus reduce lookup time.

Thus the calling extension would apparently need to have all the routing data needed to set up an end to end call before that call is dialled. Switching at the intermediate and destination nodes would need to be somewhat automatic at each node. There is a lot of thinking to be done in how to achieve this because you really wouldn't want to send 40 digits to setup a call through 5 nodes. Obviously what needs to be sent forward is enough information for a meaningful lookup of he next route leg. It would seem to be essential to retain the dialled number without 'spending' the digits en route as the dialled number would be the most obvious lookup data at every node of a call. The best way to look into this is perhaps to create a small model of say 5 nodes and 20 destinations, allowing two or three nodes in a route and then creating the example destination and node tables to see just how complex their population would become and what lookup difficulties would ensue.

Before you go any further my best suggestion is to establish the fundamental call setup principles first, prove them with basic lookup tables and a simple set of simulated nodes and routes. Forget all the clunks, clicks, stacking etc until you have a basic working model with an algorithm that is sound as a pound.

Commentary about StepNet from Brian Clancy, Senior Network Adviser

All the real work is in the database and I think I have shown that a 'known route' approach to lookups using one route table and one node table is the most efficient way to execute the task.

Dylan's idea of throwing in extra digits for routing is also a poor one. I have already mentioned that the industry used its routing digits carefully although I do not know the specifics of the NANP trunk network and I cannot be arsed to trawl through my full set of BSTJ documentation as I don't have an index for it. However, in the real world adding digits added complexity and incurred real additional costs for extra ranks of switches throughout the network. Editor's Note: The concept of "throwing in extra digits for routing" has since been discarded.

In the UK routing digits were limited to 6 maximum and clever methods of trunking and grading of electromechanical switching plant were used to get the most from the hardware. This did mean that some routes remained unavailable in the auto network until quite late in the day so trunk mechanisation took around 35 years to complete although the vast majority of the UK network was available in 25 years. I expect that the NANP made similar compromises while building the long distance automatic network in order to make best use of available funds.

Assume you want to place a call from say 2231234 to 9951234 and the route is via four intermediate exchanges, 444, 555, 666 and 777. The outgoing CLID(num) is 2221234.

The caller at 2231234 dials 9951234 and is routed to StepNet trunk network, this does a route table lookup which returns a route to 444 as the adjacent switching centre on the route to 995 and 9951234 is forwarded. At 444 a route table lookup for 995 results in a route to 555 and 9951234 is forwarded, and so on through 666 and 777 routing to destination 995 exchange where 1234 routes to the called party.

As you can see all the 'work' has to be in the route table, the route switching does not spend the dialled digits but simply forwards them to the next switching point in the route and the destination exchange spends the digits to call the local extension.

The routing table has to know how to route the call from origin to destination i.e. it has to know which of the possible switching centres with which it has connections is on the route to 995 from 223, indeed 444, 555, 666 and 777 will also need that information. None of them need to 'know' where the call originated as verification of CLID(num) at 223 can be confirmed by the addition of a suitable CLID(name) suffix at 223. There need be no subsequent CLID modification thereafter as the StepNet trunks will be secure password protected routes.

The concept is simple enough and does not differ from the one that Dylan floated when he first mentioned the StepNet project. The problem is the arrangement of data in the lookup table to allow efficient route lookup.

Assume 223 has access to half a dozen StepNet switching nodes, how will it know which one is on the route to 995? Here's a possible portion of the route table:
Node 223 has access to 227, 331, 444, 475, 626, 681
Node 227 has access to 229, 232, 561
Node 331 has access to 407, 409, 445, 479, 727, 731, 771, 802
Node 444 has access to 445, 477, 525, 555, 630
Node 475 .......etc
Node 555 has access to 525, 532, 666, 681
Node 666 ......................XXX, XXX, 777, XXX
Node 777 ......................XXX, XXX, XXX, XXX, 995
etc.

So 223 looks up it's own entry to find a switching centre to which it has access. It has to look at each of the six entries to see if any of them are on the route to 995 and then return an appropriate iaxuri and password for the route to 444. How can this be achieved? One way is to look at each entry in turn and search its entries in turn until a path is found via 444, 555, 666 and 777 to 995.

A better method is the 'two ends towards the middle' common control approach i.e. look at source and destination nodes searching through entries to reach a common node. Thus simultaneously searching from 223 and 995.

Both these methods are going to be very slow for more than three intermediate points and will involve some complex search algorithm nesting!

Of course the node doing the lookup is not interested in the data for the whole route, it only requires a path to the next node in the route.

There has to be a better way, which I will call the 'known routes approach'. This involves knowing all the routes in advance and having the intermediate points in a route lookup table entry as here:
From 223 To 388 Route 441, 472, 480, 398, 392
From 223 To 657 Route 444, 553, 592
From 223 To 995 Route 444, 555, 666, 777

The lookup node knows the origin node from the CLID(num) and destination from the dialled number so it can then look for the route. A 223 lookup for a route to 995 will find 444 as the first switching point in the list and return its iaxuri and route password from a separate node lookup table.

The lookup from 444 node for the 223 to 995 route will find itself as the first switching point in the list and then return the iaxuri for the next item, 555

This is a far more efficient method of routing on a per node basis.

The node lookup table will contain the iaxuri for the node and the password for the route.

Keeping things simple is always the best approach to my mind.

The lookup tables allow routes to be edited quickly and simply by changing database entries. The routes passwords should be centrally managed at the database, i.e. they will not appear anywhere in the dialplan at each node so the routes will be secure and automatically opened to genuine callers.

Proposal

StepNet will make meaningful use out of each switch's geographic location. The zipcode global variable on each server will play an integral role in StepNet routing. Switches located in the United States should be sure their zipcode global variable is set to their location's ZIP code (you may wish to do this even if you have a hosted server), as per the NPSTN standards. Switches located abroad are considered "international tandems" in the StepNet hierarchy; the zipcode global variable should be set to 00000 as per the standards, which indicates the tandem is an international one. NPSTNMS0, the main NPSTN tandem, serves as the primary international routing tandem, switching calls between international nodes and domestic (U.S.) nodes. (Note, this is just for StepNet, which by its very nature is not peer-to-peer; normally, NPSTN calls are peer-to-peer no matter where the two nodes are located, international or not.)

It is currently undecided whether we will use IAX variables or not. If we do, another API will need to be created to populate the necessary IAX variable(s) initially and/or along the way.

Draft 1: Retired Conception

The following is an old proposal, but it is documented here for the sake of archival purposes:

Drafted by Dylan Cruz

This is an example of the call flow:

User at 330-1212 dials 646-1217:

That switch sends a HTTPS Request to https://crtc.npstn.us/api/v1/?number=6461217&cid=3301212&sntandem=enable&zipcode=32707

API Generates a route from Casselbery, FL (32707) to the end office in Miami, FL (33101)
API Generates a route to destination switch 3301212 (IAX2/[email protected]/3301212)
IAX2/[email protected]/06461217033012120998384001 <---- First Tandem in Winter Springs,FL (32708)
IAX2/[email protected]/06461217033012120998356002 <--- Second Tandem in Winter Park, FL (32922)
IAX2/[email protected]/06461217033012120998545003 <--- Third Tandem in Melbourne, FL (32901)
IAX2/[email protected]/3301212 <--- Final Switch in Miami, FL (33101)

All of the Tandems operated by community members will have a list of standards to follow, and a vintage sounds set to use.

So dialing 330-1212 in zip code 32707 will sound like this:

tick
click
clunk
Ringback
Party Answers

All decked out will line noise and bleeding sounds like 2600 Hz!

With MF tones and dial pulses it will be awesome; dialing a number will sound different to everyone based on their physical area!

Another thing about the Route Table handling StepNet calls: it checks each server before responding to the subscriber to make sure everything is A-OK, with automatic fail over!

Draft 2: Current Conception

Drafted by Naveen Albert

Here is an example of the call flow:

A caller at 330-1212 dials 231-6000. One way or another, ARG2 of the dialnpstn subroutine is set to enable (perhaps a special code is dialed for StepNet calls or perhaps this node has it always enabled, it's immaterial).

The lookup request that gets sent to the lookup API looks like this:
https://crtc.npstn.us/api/v1/?lookup=2316000&cid=3301212&sntandem=enable&zipcode=32707

The API determines the route for the call, but only returns what the next immediate tandem is to the caller. Let's say the API lookup examines available routes (taking into account which nodes have opted into StepNet trunking and which have not) and decides the caller at 330 will be connected through 646, then 327, then finally connected to 231.

Note that it is the ZIP code that determines the routing, not any information about the exchanges themselves (like NNX). Thus, if the ZIP code were spoofed (perhaps for testing), this would result in a different routing that is geographically sensible.

For the sake of simplicity, let's assume the FQDNs and zip codes of each server are as follows:
330 - np330.npstn.us - 32707
646 - dc646.npstn.us - 32901
327 - np327.npstn.us - 33101
231 - na231.npstn.us - 53189

Once 330 sends the lookup request above, the following will be returned:
IAX2/[email protected]/2316000

330 will establish a connection with 646.

646 will detect an incoming NPSTN call. It will verify the call and determine the call's CVS code is 10.

646 will see that the extension is 2316000, which does not exist on that node. It will know it is a StepNet call and process it accordingly by going to the [stepnet] context. Here, after playing an audio file or perhaps playing some in/outpulsing (MFs, etc.), it will ensure the caller is verified. If not, he is sent to a CBCAD intercept. But since he is, it will perform the following lookup:
https://crtc.npstn.us/api/v1/?lookup=2316000&cid=3301212&sntandem=enable&zipcode=32901

The central route table will recognize the call and know to return the following:
IAX2/[email protected]/2316000

646 will establish a connection with 327, passing along the CVS code of 10 (as with any multiple-link call, StepNet or otherwise).

327 will detect an incoming NPSTN call. It will see that it was passed the CVS code of 10. It will verify the node passing it the call and determine the node is trusted. It will perpetuate the CVS code of 10.

327 will see that the extension is 2316000, which does not exist on that node. As before, it will be processed as a StepNet call. More outpulsing, clicks, bangs, etc. The node does not need to know which leg of the call it is since it will randomly choose to either play MFs, SFs, dial pulsing, a simple click, revertive pulsing, etc. (thus the need for an IAXVAR is essentially absolved). For more on the technicalities of this, see the contexts provided below.

Anyways, in due time, 327 will, after confirming the CVS code is 10 (if it were not, it would send the caller to CBCAD), perform the following lookup:
https://crtc.npstn.us/api/v1/?lookup=2316000&cid=3301212&sntandem=enable&zipcode=33101

Consequently, the following will be returned:
IAX2/[email protected]/2316000

327 will establish a connection with 231.

231 will detect an incoming NPSTN call. It will see that it was passed the CVS code of 10 and, as before, determine the last node is trustworthy.

231 will see that the number does exist on that node, and it will terminate the call "as normal". 231 has no idea that this was a "StepNet" call since the number exists on that node; it is blissfully unaware of the routing and terminates the call, using its regular inpulsing subroutines.

And there you have it!


As of yet, the mechanism by which the route table would be able to keep track of each call is unknown. If it can be guaranteed that a particular lookup will return the same information at any particular time (i.e. a particular algorithim is followed as opposed to randomness), the API does not have to keep track. If 330 to 231 will always go through 646 and 327 first, and 646 to 231 will always go through 327 first, at least at the particular instant in time (i.e. the few seconds) that a call is taking place, then the route does not actually need to be centrally planned in advance, nor does it need to be kept track of, since it can be guaranteed that if the lookup request were done again, it would render the same result; wherefore it can be guaranteed that all StepNet calls will reach their intended destination with no "oversight", so to speak. It may also help to keep in the mind that the lookup number (calling party) and the cid number (called party) remain the same for each request; only the ZIP code differs. It will have to be determined how it will be handled if multiple nodes are in the same ZIP code (will it matter or not?)

Historically, the number of links maximum in the old network was 6 in the U.K. and 7 in the U.S. Therefore, we will limit the number of StepNet trunks to 7 maximum. Calls can, of course, complete using fewer than 7 trunks.

Footnote: A slight suggestion offered as an addendum is to have a separate IAX context called [sntandem] on each node to which StepNet calls would automatically be sent. This separate context will make it easier for nodes to accept StepNet calls and process them, and is capable of diverting calls that should terminate on that node to its regular incoming context.

An alternative approach bypassing the API is outlined in Discussion #3.

Sounds

All of the audio required for StepNet trunking is available here. StepNet-participating tandems will be expected to have all the following subroutines (and any prerequisites) on their nodes:

  • MFer
  • SFer
  • Dial Pulser
  • Revertive Pulser

These can all be found in the "Vintage Add-Ons" section of this documentation.

All 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.

Dialplan Code

A dialplan code repository of approved, standard, and exceptionally fully documented StepNet code for members would also be a good idea. — Brian Clancy

Add the following to iax.conf

[sntandem]
type=user
context=sntandem
			

Next, add the following to confbridge.conf and reload Asterisk:

[silentbridge]
type=bridge
sound_join=/var/lib/asterisk/sounds/en/silence/1
sound_leave=/var/lib/asterisk/sounds/en/silence/1

[stepnetcaller]
type=user
dsp_drop_silence=no
dtmf_passthrough=yes
quiet=yes
			

Now, the dialplan code! First, in extensions.conf, add the following at the top:

include => stepnet.conf
include => stepnetsrr.conf
			

Then, add the following global variable to your [globals] section if it doesn't already exist:

[globals]
sndialtone=custom/dialtone
			

The sndialtone variable should contain the path to the audio file containing the dial tone you would like used for your nodes StepNet DISA. The code for a node's StepNet DISA is already present in the below dialplan code, but you do not need to create a StepNet DISA on your node. If you don't plan to use it at all and don't reference, you technically don't need to create the above variable but it's still a good practice to do so. Because each node's dialtone file will not necessarily have the same path, the standardized code on each node simply includes the ${sndialtone} variable, and it's up to each node owner to define that in his or her [globals] context.

In case you might be wondering, our rationale for calling the variable sndialtone as opposed to simply dialtone was that this is such a common name for a variable that it's likely to already be used on some nodes for other purposes, and perhaps that dialtone may not be the one the node owner wishes to use for his StepNet DISA. You may find it redundant to define a second variable in this case, but it is done this way to explicitly avoid any possible conflicting usages.

Create a blank file called stepnetsrr.conf. This file is dynamically and automatically populated by other dialplan code, so don't worry about the contents, which will be overwritten (thus just save a text file with, say, a semicolon ";" in it, as stepnetsrr.conf). This is necessary because although this file is automatically populated, it won't be at present and so reloading the dialplan will otherwise throw an error that the file doesn't exist.

Note: The Linux utility wget is a prerequisite for the dynamically updated dialplan code. See the "Pre-Requisites" section of this documentation for more on how to install this utility if it is not already installed on your system.

Now, create a separate dialplan file called stepnet.conf and populate it with the following code — pay careful attention; you will need to tweak the path for the dial tone audio file if you wish to have a local DISA to StepNet (as opposed to using the network-wide "118" access number). To access this DISA add GoTo(sndisa,0,1) somewhere in your main dialplan, perhaps at x1112. Also be sure to list all your local number assignments in [sntrunk].

It is also required that the [linebusy], [linereorder], and [allcktsbusy] subroutines exist on StepNet tandems. The [clunkcomplete] subroutine (which plays a "done dialing" sound) is also included in the code below but can be removed if this subroutine does not exist on your node. The code necessary to create these is presented in the "Basic Contexts" section of this documentation. These subroutines all use the "s" extension and play the audio file corresponding to a node's busy or reorder signal or "all circuits busy" intercept message and then return. You must use these subroutines with these names and with the "s" extension.

[sntandem] ; Incoming StepNet context referenced in iax.conf
exten => _X!,1,GoSub(npstn-verify,${EXTEN},1)
	same => n,GotoIf($["${clidverif}"="11"]?:accept)
	same => n,GotoIf($[${GROUP_COUNT([email protected])}<5]?:reject)
	same => n(accept),Log(NOTICE, Incoming StepNet call: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,GoTo(sntrunk,${EXTEN},1)
	same => n(reject),Log(NOTICE, Incoming StepNet call rejected: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,Hangup()

[sntrunk] ; Terminates calls homed on this switch and routes all others back out again
exten => _${OC1}XXXX,1,GoTo(external-users,${EXTEN},1) ; terminating StepNet calls should not encounter further inpulsing
;;;;;;;;;;;;;;;;;;;;;;; Add any other local office code "diversions" here
exten => _NNXXXXX,1,GoTo(stepnet,${EXTEN},1)

[sndisa] ; Entry Point to StepNet: EXTEN=0 for dialtone; EXTEN is 7 digits to terminate call directly
exten => _X!,1,GotoIf($["${LEN(${EXTEN})}"="7"]?:dt)
	same => n,Set(num=${EXTEN})
	same => n,GoTo(logic)
	same => n(dt),Read(num,${sndialtone},7)
	same => n(logic),GotoIf($["${LEN(${num})}"="7"]?:1) ; Only allow 7-digit StepNet calls
	same => n,GoSub(clunkcomplete,start,1)
	same => n,Set(CHANNEL(hangup_handler_push)=npstnbilling,${num},1(${STRFTIME(${EPOCH},,%s)},${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)}))
	same => n,GoTo(stepnet,${num},local)
	same => n(reject),Hangup()

[stepnet] ; NPSTN 20190212 NA ; Main StepNet context to use on StepNet-participating tandems
exten => _NNXXXXX,1,Log(NOTICE, Trunking StepNet call: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,Set(__snrouting=${IAXVAR(snrouting)}) ; Used until CRTC supports StepNet (tracks SN routing)
	same => n,Set(__sncount=${IAXVAR(sncount)}) ; Used to count # of nodes through which a SN call passes
	same => n,GoTo(begin)
	same => n(local),Log(NOTICE, Originating StepNet call: "${clidverif}" ${CALLERID(all)} to ${EXTEN})
	same => n(begin),GoToIf($["${clidverif}"=""]?check)
	same => n,GoToIf($["${clidverif}"="10"]?check) ; only NPSTN callers allowed to use StepNet...
	same => n,GoToIf($["${clidverif}"="20"]?check:block) ; ...verified C*NET callers also allowed to use StepNet
	same => n(block),GoTo(invalidincoming,s,1) ; divert non-NPSTN callers to CBCAD intercept
	same => n(sounds),
	same => n(check),GoSub(testcrtc,s,1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="1"]?lookup:srr)
	same => n(lookup),GoSub(fetchssr,s,1) ; required for audio
	same => n,GoSub(stepnetsounds,${EXTEN},1)
	same => n,GoSub(incrementsncount,s,1)
	same => n,GoSub(npstn-set-flags,s,1)
	same => n,GoSub(dialnpstn,start,1(${EXTEN},enable,${zipcode},${maindisa}))
	same => n,Hangup()
	same => n(srr),GoSub(fetchssr,s,1)
	same => n,GoTo(stepnetsrr,${EXTEN},1)

[fetchssr] ; 20190325 NA ; downloads and loads dialplan code for updated routing ; wget is a prerequisite
exten => s,1,Set(confexists=${STAT(e,/etc/asterisk/stepnetsrr.conf)})
	;same => n,GoToIf($["${confexists}"="1"]?done) ; for testing ; DO NOT uncomment
	same => n,SYSTEM(wget http://telephony.ml/code/stepnetsrr.conf -P /etc/asterisk/ --timestamping --no-cache)
	same => n,SYSTEM(asterisk -rx "dialplan reload")
	same => n(done),Return()

[testcrtc] ; 20190325 NA ; Returns 1 if the CRTC supports StepNet; 0 if not (i.e. use SRR)
exten => s,1,Set(num1=2310000)
	same => n,Set(num2=6460000)
	same => n,Set(stepnetno=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${num2}&cid=${num1}&sntandem=no&zipcode=10100")})
	same => n,Set(stepnetyes=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${num2}&cid=${num1}&sntandem=yes&zipcode=10100")})
	same => n,GoToIf($["${stepnetno}"="${stepnetyes}"]?test2:usecrtc)
	same => n(test2),Set(stepnetno=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${num2}&cid=${num1}&sntandem=yes&zipcode=10100")})
	same => n,Set(stepnetyes=${SHELL(curl "https://crtc.npstn.us/api/v1/?lookup=${num2}&cid=${num1}&sntandem=yes&zipcode=90900")})
	same => n,GoToIf($["${stepnetno}"="${stepnetyes}"]?usessr:usecrtc)
	same => n(usessr),Return(0)
	same => n(usecrtc),Return(1)
			

Automatic Operators

NPSTN does a have a human operator who is 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 transferred to an automatic operator who will help you. The voice of these operators is our very own 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 NPSTN and C*NET long-distance 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 the "time", you will be connected with POPCORN. If you say you have an emergency, or that you need the police, you will be connected with the NPSTN emergency intercept recording. If you ask for the temperature, you will be connected with 511. If you ask for time and temperature, you'll be connected with 211. 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 NPSTN 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 an NPSTN 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.

Blue Box

To access the NPSTN "blue box", you must access a DISA tandem trunk on NPSTNNA01 (e.g. BE1-1111).

Press the "*" key to drop the dial tone and drop into a "2600 Hz trunk".

The * key serves as KP; the # serves as ST. You must dial KP + # + ST.

Access to these trunks is highly restricted and controlled. All access is logged. The trunk allows access to special areas of NPSTN that are not dialable directly, including TTY lines, toll-free lines, certain PSTN lines and access trunks, inward operators, special directory services, and verification trunks.

Operator Busy Line Verification

The following code is not currently in use on any node. It is a theoretical implementation that would allow the operator to verify if a line is busy. There are currently no plans for it to be implemented. At present, the network operator rings an exchange's inward operator if he or she needs assistance in determining the status of a line. Please do not put the following code into production! Operators currently use the network pseudo-blue box to access verification trunks.

For a context you could implement for busy-line verification capabilities, please see the "ChanSpy" section of the Docs. The code below is here for archival purposes only:

[operatorverify]
exten => start,1,Playtones(450/340)
	same => n,Wait(3)
	same => n,StopPlaytones
	same => n,Read(pin,,5)
	same => n,Set(cpin=${CURL(http://npstn.us/api/v1/operator.php?pin=${pin}&cid=${CALLERID(num)}&sw=${OC1}${EXTEN})})
	same => n,GotoIf($["${pin}"="${cpin}"]?operator:disconnect)
	same => n(operator),Playtones(450/340)
	same => n,Wait(3)
	same => n,StopPlaytones
	same => n,Read(opt,,1)
	same => n,GotoIf($["${opt}"="0"]?blvi)
	same => n,GotoIf($["${opt}"="1"]?dial)
	same => n(disconnect),Set(VOLUME(TX)=9)
	same => n,Playtones(750/440) ; ALERT INVALID OPTION
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(550/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(750/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(550/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(750/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(550/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(750/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(550/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(750/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(550/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(750/440)
	same => n,Wait(0.4)
	same => n,StopPlaytones
	same => n,Playtones(1004)
	same => n,Wait(1)
	same => n,StopPlaytones
	same => n,Set(VOLUME(TX)=1)
	same => n,Hangup
	same => n(blvi),Playtones(550/440)
	same => n,Wait(2)
	same => n,StopPlaytones
	same => n,Read(blvinun,,7)
	same => n,ChanSpy(SIP/${blvinum},qB)
	same => n(dial),Playtones(550/440)
	same => n,Wait(2)
	same => n.StopPlaytones
	same => n,Read(dialnun,,7)
	same => n,Dial(Local/${dialnum})
	same => n,Hangup
		

C*NET Connectivity

First, you will need to add the following to your iax.conf:

[cnet]
type=user
context=from-cnet
		

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 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 NPSTN 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:

[from-cnet]
exten => _X!,1,GoSub(cnet-verify,${EXTEN},1)
	same => n,Log(NOTICE, Incoming call from C*NET: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,GoTo(external-users,${EXTEN:-7},1) ; strip leading 1 from received number
		

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 numbers for NPSTN and C*NET. While using the same numbers may be easier, differentiating between the two networks makes your node more unique.

You will need to register for a C*NET office code if you don't already have one. You'll need to do this with C*NET, with whom we are not affiliated. All the instructions you need to get setup are on the C*NET website.

To dial out, you will need the official [dialcnet] macro, developed by, once again, Brian Clancy. The code is also included below for your convenience:

[macro-dialcnet]                                        ;version bjc1.01 - asterisk1.4+ and asterisk 1.2
exten => s,1,Set(NUMBER=${ARG1})                       ;Store number to be called
exten => s,2,GotoIf($[ ${ARG1:0:1} = "+"]?search)       ;Is number prefixedwith '+'?
exten => s,3,Set(ARG1=+${ARG1})         ;Prefix number with '+',required toproperly retrieve US ENUM entries!!!
exten => s,4(search),Set(ENUM=${ENUMLOOKUP(${ARG1},ALL,,1,std.ckts.info)}) ;Search ENUM database (Asterisk 1.4+)
exten => s,5,GotoIf($[${LEN(${ENUM})}=0]?no_uri)        ;Is ENUM record found?
exten => s,6,Gotoif(${DB_EXISTS(ELBD/${NUMBER})}?cdata) ;Does backup entry exist?
exten => s,7,Set(DB(ELBD/${NUMBER})=${ENUM})            ;No existing backup so store backup ENUM data
exten => s,8(test),GotoIf($[${ENUM:0:3} = iax ]?iaxuri) ;Yes-IAX2 protocol
exten => s,9,GotoIf($[${ENUM:0:3} = sip ]?sipuri)       ;Yes-SIP protocol
exten => s,10,GotoIf($[${ENUM:0:3} = h32 ]?h323uri)      ;Yes-H323 protocol
exten => s,11(no_uri),Gotoif(${DB_EXISTS(ELBD/${NUMBER})}?seek)  ;ENUM="", is there a backup entry?
exten => s,12,Macro(invalid-office-code,${NUMBER})               ;No valid ENUM and no backup entry
exten => s,13,Wait(5)                                    ;Pause
exten => s,14,Hangup                                     ;Done - failed to make call - Goodbye
exten => s,15(seek),Set(ENUM=${DB_RESULT})               ;Set ENUM to backup base entry
exten => s,16,GotoIf(${REGEX("iax2,sip,h323"${ENUM})}?test)      ;If it's a single backup proceed to dial out
exten => s,17,Set(NE=${CUT(ENUM,":",1)})                 ;Get backup entry field 1
exten => s,18,Set(LU=${CUT(ENUM,":",2)})                 ;Get backup entry field 2
exten => s,19,GotoIf($[${NE}>${LU}]?grec)                ;Was last used the last entry in list?
exten => s,20(frec),Set(LU=0)                            ;Reset entry pointer
exten => s,21(grec),Set(LU=$[${LU}+1])                   ;Increment last used pointer
exten => s,22,Set(ENUM=${DB(ELBD/${NUMBER}/${LU})})      ;Read ENUM backup entry
exten => s,23,Set(DB(ELBD/${NUMBER})=${NE}:${LU})        ;Update last used record
exten => s,24,GoTo(test)                                 ;Proceed to dial out
exten => s,25(iaxuri),Set(DIALSTR=IAX2/${ENUM:5})        ;IAX2
exten => s,26,GoTo(dodial)                               ;Make call
exten => s,27(sipuri),Set(DIALSTR=SIP/${ENUM:4})         ;SIP
exten => s,28,GoTo(dodial)                               ;Make call
exten => s,29(h323uri),Set(DIALSTR=H323/${ENUM:5})       ;H323
exten => s,30,Macro(invalid,${NUMBER})                   ;Make Call
exten => s,31(dodial),NoOp(Outbound Caller ID is ${CALLERID(all)})
;exten => s,31(dodial),Set(CALLERID(all)= Brian Clancy - PBX6 < 4418072345 >)
exten => s,32,Dial(${DIALSTR})                           ;Dial Out
exten => s,33,Hangup                                     ;Done -call attempted - Goodbye
exten => s,34(cdata),Gotoif($["${DB_RESULT}"="${ENUM}"]?test)    ;Does entry have a single backup?
exten => s,35,Set(SR=${DB_RESULT})                               ;Backup does not match current
exten => s,36,GotoIf(${REGEX("iax2,sip,h323"${SR})}?shuffle)     ;Is more than one backup entry stored?
exten => s,37,Set(NE=${CUT(SR,":",1)})                   ;Get backup entry field 1
exten => s,38,Set(LU=${CUT(SR,":",2)})                   ;Get backup entry field 2
exten => s,39,Gosub(rent)                                ;Check multiple backup entries
exten => s,40,Set(DB(ELBD/${NUMBER})=${NE}:${MR})        ;Store no. of entries and match as entry last used
exten => s,41,GotoIf($[${MR}>0]?test)                    ;If backup exists proceed to dial out
exten => s,42,Set(NE=$[${NE}+1])                         ;Increment entries pointer
exten => s,43,Set(DB(ELBD/${NUMBER})=${NE}:${NE})        ;Update entry count and usage
exten => s,44,Set(DB(ELBD/${NUMBER}/${NE})=${ENUM})      ;Store nth backup entry
exten => s,45,GoTo(test)                                 ;Nth entry stored - proceed to dial out
exten => s,46(shuffle),Set(DB(ELBD/${NUMBER}/1)=${SR})   ;Move stored entry to backup entry /1
exten => s,47,Set(DB(ELBD/${NUMBER}/2)=${ENUM})          ;Create second backup entry /2
exten => s,48,Set(DB(ELBD/${NUMBER})=2:2)                ;Store no. of backup entries & last called
exten => s,49,GoTo(test)                                 ;Alternative backup entry stored - proceed to dial out
exten => s,50(rent),Set(i=0)                             ;Load test counter
exten => s,51,Set(MR=0)                                  ;Load match store
exten => s,52,While($[${i}<${NE}])                       ;Check multiple entries for a NUMBER looking for a match
exten => s,53,GotoIf($["${DB(ELBD/${NUMBER}/$[${i}+1])}"="${ENUM}"]?mark)
;Mark entry matching ENUM result
exten => s,54,Set(i=$[${i}+1])                           ;Increment counter
exten => s,55,EndWhile                                   ;Look at next entry if one exists
exten => s,56,Return                                     ;Done searching for ENUM result backup record
exten => s,57(mark),Set(MR=$[${i}+1])                    ;ENUM result already backed up
exten => s,58,Set(i=${NE})                               ;Load counter with no. of last entry
exten => s,59,ContinueWhile                              ;Stop searching

[macro-invalid-office-code]
exten => s,1,Playback(custom/switch/ccad)
		

The version above caches ENUM lookups so that, if the ENUM lookup were ever to become unavailable, you would still be able to reach destinations to which you previously placed calls successfully. Here is an updated adapted and simplified version of the C*NET macro that is, in fact, not a macro but a subroutine, and does not cache ENUM lookups:

[dialcnet] ; CNET NA 20190219, adapted from BJC v1.01
exten => s,1,Set(NUMBER=${ARG1})
	same => n,GotoIf($[ ${NUMBER:0:1} = "+"]?search)
	same => n,Set(NUMBER=+${NUMBER})
	same => n(search),Set(ENUM=${ENUMLOOKUP(${NUMBER},ALL,,1,std.ckts.info)}) ; Asterisk 1.4+
	same => n,GotoIf($[${LEN(${ENUM})}=0]?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),GoSub(invalidcnet,s,1)
	same => n,Return
	same => n(iax),Set(DIALSTR=IAX2/${ENUM:5})
	same => n,GoTo(dialstr)
	same => n(sip),Set(DIALSTR=SIP/${ENUM:4})
	same => n,GoTo(dialstr)
	same => n(h323),Set(DIALSTR=H323/${ENUM:5})
	same => n(dialstr),Dial(${DIALSTR},,g)
	same => n,Return()

[invalidcnet]
exten => s,1,Playback(custom/ccad) ; CCBCAD
	same => n,Return()
		

You will need to adjust invalidcnet to play your local CBCAD intercept.

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()
		

PSTN Connectivity

Incoming Calls

IPComms

Registration

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.

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 NPSTN and C*NET, 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 => 2125551212:[email protected]/ipcomms
					

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:

[ipcomms]
secret=password
defaultuser=2125551212
host=freedid.ipcomms.net
allow=ulaw
context=from-ipcomms
dtmfmode=rfc2833
insecure=port,invite
type=peer
directmedia=no
trustrpid=yes
					

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)})
	same => n,GoSub(pstn-us-verify,s,1)
	same => n,Log(NOTICE, Incoming call from IPComms PSTN ${Var_TO} DID: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" via ${EXTEN})
	same => n,Answer()
	same => n,SendDTMF(1) ; for Google Voice
	same => n,GoSub(mfer,start,1(2125551212))
	same => n,GoTo(from-pstn,s,1)

[from-pstn]
exten => s,1,GoTo(dt,DTXB,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 calls from NPSTN and C*NET, 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; if you have an NNX-X on NPSTN or C*NET, 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

Registration

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 => 17775551212:[email protected]/callcentric
					

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:

[callcentric]
type=peer
context=from-callcentric
host=callcentric.com
fromdomain=callcentric.com
defaultuser=17775551212
fromuser=17775551212
secret=password
insecure=port,invite
disallowed_methods=UPDATE
directmedia=no
videosupport=no
disallow=all
allow=ulaw

[callcentric1](callcentric)
host=alpha1.callcentric.com

[callcentric2](callcentric)
host=alpha2.callcentric.com

[callcentric3](callcentric)
host=alpha3.callcentric.com

[callcentric4](callcentric)
host=alpha4.callcentric.com

[callcentric5](callcentric)
host=alpha5.callcentric.com

[callcentric6](callcentric)
host=alpha6.callcentric.com

[callcentric7](callcentric)
host=alpha7.callcentric.com

[callcentric8](callcentric)
host=alpha8.callcentric.com

[callcentric9](callcentric)
host=alpha9.callcentric.com

[callcentric10](callcentric)
host=alpha10.callcentric.com

[callcentric11](callcentric)
host=alpha11.callcentric.com

[callcentric12](callcentric)
host=alpha12.callcentric.com

[callcentric13](callcentric)
host=alpha13.callcentric.com

[callcentric14](callcentric)
host=alpha14.callcentric.com

[callcentric15](callcentric)
host=alpha15.callcentric.com

[callcentric16](callcentric)
host=alpha16.callcentric.com

[callcentric17](callcentric)
host=alpha17.callcentric.com

[callcentric18](callcentric)
host=alpha18.callcentric.com

[callcentric19](callcentric)
host=alpha19.callcentric.com

[callcentric20](callcentric)
host=alpha20.callcentric.com

[callcentricA](callcentric)
host=doll3.callcentric.com

[callcentricB](callcentric)
host=doll4.callcentric.com

[callcentricC](callcentric)
host=doll5.callcentric.com
					

Yes, unfortunately, you really do need all of that! You can see more about this on CallCentric's Asterisk configuration page.

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)})
	same => n,GoSub(pstn-us-verify,s,1)
	same => n,Log(NOTICE, Incoming call from CallCentric PSTN ${Var_TO} DID: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,Wait(4)
	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.

Outgoing Calls

Skyetel

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 NPSTN 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:

[SkyetelCA]
disallow=all
type=peer
insecure=port,invite
host=52.8.201.128
dtmfmode=rfc2833
context=from-skyetel
allow=ulaw
qualify=yes
directmedia=no
directrtpsetup=no
sendrpid=no
canreinvite=no
 
[SkyetelTERM]
disallow=all
type=peer
qualify=yes
insecure=port,invite
host=term.skyetel.com
dtmfmode=rfc2833
context=from-skyetel
allow=ulaw
directmedia=no
directrtpsetup=no
sendrpid=no
canreinvite=no
 
[SkyetelOR]
disallow=all
type=peer
insecure=port,invite
host=52.41.52.34
dtmfmode=rfc2833
context=from-skyetel
allow=ulaw
qualify=yes
directmedia=no
directrtpsetup=no
sendrpid=no
canreinvite=no
 
[SkyetelVA]
disallow=all
host=50.17.48.216
type=peer
allow=ulaw
dtmfmode=rfc2833
insecure=port,invite
context=from-skyetel
qualify=yes
directmedia=no
directrtpsetup=no
sendrpid=no
canreinvite=no
 
[SkyetelHC]
disallow=all
host=hc.skyetel.com
type=peer
allow=ulaw
dtmfmode=rfc2833
insecure=port,invite
context=from-skyetel
directmedia=no
directrtpsetup=no
sendrpid=no
canreinvite=no
 
[SkyetelHC1]
disallow=all
host=52.32.223.28
type=peer
allow=ulaw
dtmfmode=rfc2833
insecure=port,invite
context=from-skyetel
directmedia=no
directrtpsetup=no
sendrpid=no
canreinvite=no
 
[SkyetelHC2]
disallow=all
host=52.4.178.107
type=peer
allow=ulaw
dtmfmode=rfc2833
insecure=port,invite
context=from-skyetel
directmedia=no
directrtpsetup=no
sendrpid=no
canreinvite=no
					

Now, on to extensions.conf!

If you don't already have one, create a global public variable in extensions.conf containing the PSTN DID that you want people to call, like so:

[globals]
publicdid=3034360093
					

This variable should contain your 10-digit Free DID (the number you'd like people to call, NOT your Skyetel DID). If you don't have a free DID, then what are you waiting for?

What is this variable used for? Since you don't want incoming calls to this number, the context below will automatically inform callers of your (hopefully free, or at least cheaper) DID so that way you can reserve these minutes for outgoing calls only.

[from-skyetel]
exten => _1XXXXXXXXXX,1,NoOp()
	same => n,Set(IAXVAR(publicdid)=${publicdid:-10:10})
	same => n,Dial(IAX2/[email protected]/${EXTEN})
					

This will call Automatic Intercept Service, informing callers that your Skyetel number has been changed to whatever the global variable publicdid is set (which should be to a number you want callers to call instead). The context above assumes you don't have the AIS subroutines. If you do, you can replace it with a call to [aischanged] or [aisnpachanged] if the numbers are in different area codes.

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
    dir
  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):
    ./contrib/scripts/install_prereq
  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
wget http://9142978376.com/newfiles/GV/gvsip-naf.tar
tar xvf gvsip-naf.tar
rm -f gvsip-naf.tar
cd gvsip-naf
./install-gvsip
					

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

Run:

sed -i 's|obihai.telephony.goog|voice.telephony.goog|' /etc/asterisk/pjsip_custom.conf
sed -i 's|obihai.telephony.goog|voice.telephony.goog|' /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:

[public]
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).

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:

[pubic]
type=user
context=public

[guest]
type=user
context=public
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 on NPSTN is using them for crosstalk. The MIlton6 exchange, rather than banning IP addresses who make spam calls, adds 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.)

features.conf

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:

[general]
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

[featuremap]
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 time and temperature services on NPSTN. 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, it can become corrupted when you transfer it over to the server.

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 automatic operators on NPSTN 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:

#!/bin/bash

curl -X POST -u "apikey:APIKEY" --header "Content-Type: audio/wav" --data-binary @/var/lib/asterisk/sounds/stt/$1.wav "https://stream.watsonplatform.net/speech-to-text/api/v1/recognize?model=en-US_NarrowbandModel" >/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/stt.sh. Now, run the following from the command line:

chmod 777 /etc/asterisk/scripts/stt.sh
mkdir /var/lib/asterisk/sounds/stt/

To use the shell script, add the following context to your dialplan:

[stt]
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.sh ${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:
    #!/bin/bash
    
    path=$1
    files=($(ls -1 -p $1 | grep -v /))
    
    mkdir $path/originals
    
    for f in "${files[@]}"
    do
            echo "Working on file $f"
            length=$(soxi -d $path/"$f")
            fn=$(basename "$f")
            fn="${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
    done
    				
    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 ed_ulaw.sh (full path is /root/ed_ulaw.sh)
  19. If you have HD VoIP sets that support G.722, you may want to use the following script instead:

    #!/bin/bash
    
    mkdir originals
    
    for f in *.flac
    do
            mv -v "$f" "${f// /_}"
    done
    
    for f in *.flac
    do
            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")
            fn="${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
    done
    				
    Save this as ed_g722.sh — 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/ed_ulaw.sh (replace _ulaw with _g722 if you're using the second script)
    /root/ed_ulaw.sh /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 ed_wav.sh first to get the durations, then use ed_ulaw.sh 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:
    [edrecording]
    exten => _[0-9A-Za-z].,1,ControlPlayback(custom/playback/ed/${EXTEN},60000,9,7,#,8,0)
    	same => n,Hangup()
    
    [evandoorbell]
    				
  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!

Now comes the hard part: getting these directory entries into a directory. If you have your own personal webpage with directory extensions, or need to put them into an online DB-based directory, you might need to manually do this extension by extension. However, support for CSV-based upload of directory entries is coming soon to NPSTN, so stay tuned for that! With that functionality, all you would have do is use CONCATENATE to concatenate the prefix (NNX) to the extension number in a separate column and then upload that column along with the Name column (not the File Name column, but rather the title of the recording). Follow the CSV mass-upload instructions if necessary. Note that all Evan Doorbell recordings currently are or will be available on NPSTN's PIoneer7 (747) exchange.

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.

Linux

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
done

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.

Windows

  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 192.168.1.1 -j DROP is the same as iptables -A INPUT -s 192.168.1.1/32 -j DROP.

Hence, to block all of 192.168.1.*, you would enter:

iptables -A INPUT -s 192.168.1.0/24 -j DROP

To block all of 192.168.*.*, you would enter:

iptables -A INPUT -s 192.168.0.0/16 -j DROP

To block all of 192.*.*.* (often used for blocking entire countries), use

iptables -A INPUT -s 192.0.0.0/8 -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 Telephony Resources page.