FusionPBX, or cool again, FreeSWITCH

  • Tutorial

To the same river

More recently, I wrote myself a cheat sheet for setting up FreeSWITCH. The setup process described there led to a working configuration under test conditions. The test was necessary to draw up a preliminary idea of ​​what to deal with after moving the organization and launching telephony in production. However, when the move took place and the connection started in the operating mode, the very first switching on showed the configuration was inoperative: internal calls stopped going.

This was a complete surprise for me, since from the moment of the final setup and health check, based on which the cheat sheet was written, at the time of switching on in the operating mode, no changes were made to the config. Only internal numbers and routes were added in bulk for incoming and outgoing calls for those employees who were assigned direct city numbers (about 60 with a tail of numbers).

There was a debug, a jamb was revealed, and everything worked. However, there was a crutch feeling. I will not describe it, because I am confident that the applied solution is not correct, although it led to the desired result. In addition, the nuances were clarified: for outgoing calls from inside to outside, only the number that was specified in the SIP trunk setting in the default_provider_username field was determined:

and not the one indicated in the configuration of the subscriber number:

The technical support of the provider reported that all the calls arriving to them from us in the From field are exactly the number 3435555555 , that is, the joint is on my side. Plus, I suddenly completely hung up with the task of forwarding calls. And the cherry on the cake was the removal of the brain by Ericsson Dialog 4422 devices, which refused to transfer the call, and Cisco 7945g devices, which decided that their connection duration limit was 90-100 seconds in the absence of the slightest hint of such a setting in the config. At the same time, Yealink T21 E2 devices worked completely flawlessly.

At this stage, I realized that I had reached the limit of my competence in the field of telephony and took a time out so that everything would settle down and fit in my head. This decision was also very powerful due to general fatigue after a completely wild two working weeks without days off and with an irregular work schedule that followed immediately after arrival at the new location of the organization.


Despite my lack of sympathy for graphical interfaces where the console rules and text configs, I nevertheless began to look towards a solution with a web face called FusionPBX. The first reason for such a betrayal of our own principles was the desire to see the entire volume of settings for each functional element, assembled in one place in the form of a workable “out of the box” configuration. This is exactly what the graphical interface provides. An additional bonus of a well-thought-out graphical interface is a visual representation of the relationships between modules and functions. For a beginner (for me personally), a lower level of abstraction with a specific implementation method contributes to faster learning and understanding how this thing works. The second reason was www.pbxforums.com, to which I followed a link through one when searching for information on FreeSWITCH, and, ironically, it was precisely on screenshots of the FusionPBX settings pages.

FusionPBX is a FreeSWITCH with a web muzzle and settings stored in a database. The unattended installation script installs both FreeSWITCH, Nginx, PostgreSQL, and, in fact, the web interface of FusionPBX itself. I will not dwell on this moment, everything is set without hesitation according to the instructions from the documentation. I put everything on the 64-bit Debian 8 recommended by the developers.

Import of subscriber numbers

The process of setting up subscriber numbers and incoming routes will not be considered here. This process is described in official documentation.

Instead, the procedure for importing everything in bulk will be described. I have not found any descriptions, manuals or tips for performing this procedure.

At the end of the installation, enable automatic login to Adminer (analogous to phpMyAdmin):
Advanced → Default settings :
Value: true
Enabled: true
After changing the values ​​on the current page, click Save , on the default settings page Reload .

Go to Adminer: Advanced → Adminer .

The following tables are of interest to us:

v_extensions - subscriber numbers.
v_destinations - routes for incoming calls to city numbers assigned to internal subscriber numbers.
v_dialplans - dialplans directory.
v_dialplan_details - dialplan settings for incoming calls.
v_voicemails - voicemail settings.

The statement of the task was as follows: unload employees and their internal phone numbers from the AD name, save the upload to a CSV file, and import it into the database in the table of subscriber numbers and voice mail settings (voice mail should be disabled).
Using the directory of correspondence between city numbers and internal numbers, create CSV files for import into tables with routes and dialplans of incoming calls.

I will not consider this task in detail, I just hide the ready-made scripts under the spoiler.

You use the proposed scripts at your own risk, the author is not responsible for their incorrect use or unexpected side effects of their correct use.

  • Set the $ nums variable to match your numbers.
  • Before using scripts, you must always replace the domain UUID with the value assigned to the domain during installation (field domain_uuid ).
  • It is also necessary to replace the IP address of the domain ( with yours.
  • Do not forget to adjust the value of the -SearchBase switch , indicating your sampling area instead of “OU = Ekaterinburg, DC = dc, DC = domain, DC = local”
  • The UUID of the Voicemail application ( app_uuid field ) is also replaced by the UUID assigned during installation.
  • UUID values ​​can be viewed, for example, in the v_dialplans table .
  • All subscriber numbers will be assigned a password for registration “12345”, the password for voicemail and other services will be the same as the subscriber number.
  • The script appends files line by line! Therefore, do not forget to delete files before each script run or to clear their contents!
Subscriber numbers and voicemail
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::AppendAllText("d:\v_extensions.csv", "extension_uuid;domain_uuid;extension;number_alias;password;accountcode;effective_caller_id_name;effective_caller_id_number;outbound_caller_id_name;outbound_caller_id_number;emergency_caller_id_name;emergency_caller_id_number;directory_full_name;directory_visible;directory_exten_visible;limit_max;limit_destination;missed_call_app;missed_call_data;user_context;toll_allow;call_timeout;call_group;call_screen_enabled;user_record;hold_music;auth_acl;cidr;sip_force_contact;nibble_account;sip_force_expires;mwi_account;sip_bypass_media;unique_id;dial_string;dial_user;dial_domain;do_not_disturb;forward_all_destination;forward_all_enabled;forward_busy_destination;forward_busy_enabled;forward_no_answer_destination;forward_no_answer_enabled;follow_me_uuid;enabled;description;forward_caller_id_uuid;absolute_codec_string;forward_user_not_registered_destination;forward_user_not_registered_enabled;force_ping`r`n", $Utf8NoBomEncoding)
[System.IO.File]::AppendAllText("d:\v_voicemails.csv", "domain_uuid;voicemail_uuid;voicemail_id;voicemail_password;greeting_id;voicemail_alternate_greet_id;voicemail_mail_to;voicemail_sms_to;voicemail_attach_file;voicemail_file;voicemail_local_after_email;voicemail_enabled;voicemail_description;voicemail_name_base64`r`n", $Utf8NoBomEncoding)
Get-ADUser -Filter * -SearchBase "OU=Ekaterinburg,DC=dc,DC=domain,DC=local" -Properties Telephonenumber,sn,initials,cn|%{
if(-not $_.Telephonenumber -eq ""){
    if($nums.Get_Item($_.Telephonenumber) -eq $null)
    {$outn = "5555555"}
    {$outn = $nums.Get_Item($_.Telephonenumber)}
    $extension_uuid = (New-Guid).Tostring()
    $domain_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff" ## Заменить!!!
    $extension = $_.Telephonenumber
    $number_alias = ""
    $password = "12345"
    $accountcode = ""
    $effective_caller_id_name = $_.sn + " " + $_.initials
    $effective_caller_id_number = $extension
    $outbound_caller_id_name = $outn
    $outbound_caller_id_number = $outn
    $emergency_caller_id_name = $effective_caller_id_name
    $emergency_caller_id_number = $extension
    $directory_full_name = $_.cn
    $directory_visible = "true"
    $directory_exten_visible = "true"
    $limit_max = "1"
    $limit_destination = "error/user_busy"
    $missed_call_app = ""
    $missed_call_data = ""
    $user_context = ""
    $toll_allow = "domestic,international,local"
    $call_timeout = "30"
    $call_group = ""
    $call_screen_enabled = "false"
    $user_record = ""
    $hold_music = "local_stream://default"
    $auth_acl = ""
    $cidr = ""
    $sip_force_contact = ""
    $nibble_account = ""
    $sip_force_expires = "3600"
    $mwi_account = ""
    $sip_bypass_media = ""
    $unique_id = ""
    $dial_string = ""
    $dial_user = ""
    $dial_domain = ""
    $do_not_disturb = ""
    $forward_all_destination = ""
    $forward_all_enabled = ""
    $forward_busy_destination = ""
    $forward_busy_enabled = ""
    $forward_no_answer_destination = ""
    $forward_no_answer_enabled = ""
    $follow_me_uuid = ""
    $enabled = "true"
    $description = $_.sn + " " + $_.initials
    $forward_caller_id_uuid = ""
    $absolute_codec_string = ""
    $forward_user_not_registered_destination = ""
    $forward_user_not_registered_enabled = ""
    $force_ping = ""
    [System.IO.File]::AppendAllText("d:\v_extensions.csv", $csv, $Utf8NoBomEncoding)
    $voicemail_uuid = (New-Guid).Tostring()
    $voicemail_id = $extension
    $voicemail_password = $extension
    $voicemail_mail_to = ""
    $voicemail_file = ""
    $voicemail_local_after_email = "true"
    $voicemail_enabled = "false"
    $voicemail_description = $description
    [System.IO.File]::AppendAllText("d:\v_voicemails.csv", "$domain_uuid;$voicemail_uuid;$voicemail_id;$voicemail_password;$greeting_id;$voicemail_alternate_greet_id;$voicemail_mail_to;$voicemail_sms_to;$voicemail_attach_file;$voicemail_file;$voicemail_local_after_email;$voicemail_enabled;$voicemail_description;$voicemail_name_base64`r`n", $Utf8NoBomEncoding)}}

Routes and Dialplans
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[System.IO.File]::AppendAllText("d:\v_destinations.csv", "domain_uuid;destination_uuid;dialplan_uuid;fax_uuid;destination_type;destination_number;destination_number_regex;destination_caller_id_name;destination_caller_id_number;destination_cid_name_prefix;destination_context;destination_app;destination_data;destination_enabled;destination_description;destination_accountcode`r`n", $Utf8NoBomEncoding)
[System.IO.File]::AppendAllText("d:\v_dialplans.csv", "domain_uuid;dialplan_uuid;app_uuid;dialplan_context;dialplan_name;dialplan_number;dialplan_continue;dialplan_order;dialplan_enabled;dialplan_description`r`n", $Utf8NoBomEncoding)
[System.IO.File]::AppendAllText("d:\v_dialplan_details.csv", "domain_uuid;dialplan_uuid;dialplan_detail_uuid;dialplan_detail_tag;dialplan_detail_type;dialplan_detail_data;dialplan_detail_break;dialplan_detail_inline;dialplan_detail_group;dialplan_detail_order`r`n", $Utf8NoBomEncoding)
    $innum = $_.Split("=")[0]
    $outnum = $_.Split("=")[1]
    $domain_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff" ## Заменить!!!
    $destination_uuid = (New-Guid).Tostring()
    $dialplan_uuid = (New-Guid).Tostring()
    $destination_type = "inbound"
    $destination_number = "343$outnum"
    $destination_number_regex = "^(343$outnum)$"
    $destination_context = "public"
    $destination_enabled = "true"
    $destination_description = "$outnum-$innum" 
    [System.IO.File]::AppendAllText("d:\v_destinations.csv", "$domain_uuid;$destination_uuid;$dialplan_uuid;$fax_uuid;$destination_type;$destination_number;$destination_number_regex;$destination_caller_id_name;$destination_caller_id_number;$destination_cid_name_prefix;$destination_context;$destination_app;$destination_data;$destination_enabled;$destination_description;$destination_accountcode`r`n", $Utf8NoBomEncoding)
    $app_uuid = "ffffffff-ffff-ffff-ffff-ffffffffffff" ## Заменить!!!
    $dialplan_context = "public"
    $dialplan_name = $destination_number
    $dialplan_number = $destination_number
    $dialplan_continue = "false"
    $dialplan_order = "100"
    $dialplan_enabled = "true"
    $dialplan_description = $destination_description
    [System.IO.File]::AppendAllText("d:\v_dialplans.csv", "$domain_uuid;$dialplan_uuid;$app_uuid;$dialplan_context;$dialplan_name;$dialplan_number;$dialplan_continue;$dialplan_order;$dialplan_enabled;$dialplan_description`r`n", $Utf8NoBomEncoding)
    $dialplan_detail_uuid = (New-Guid).Tostring()
    $dialplan_detail_tag = "condition"
    $dialplan_detail_type = "destination_number"
    $dialplan_detail_data = "^(343$outnum)$"
    $dialplan_detail_order = 20    
    [System.IO.File]::AppendAllText("d:\v_dialplan_details.csv", "$domain_uuid;$dialplan_uuid;$dialplan_detail_uuid;$dialplan_detail_tag;$dialplan_detail_type;$dialplan_detail_data;$dialplan_detail_break;$dialplan_detail_inline;$dialplan_detail_group;$dialplan_detail_order`r`n", $Utf8NoBomEncoding)
    $dialplan_detail_uuid = (New-Guid).Tostring()
    $dialplan_detail_tag = "action"
    $dialplan_detail_type = "transfer"
    $dialplan_detail_data = "$innum XML"
    $dialplan_detail_order = 30
    [System.IO.File]::AppendAllText("d:\v_dialplan_details.csv", "$domain_uuid;$dialplan_uuid;$dialplan_detail_uuid;$dialplan_detail_tag;$dialplan_detail_type;$dialplan_detail_data;$dialplan_detail_break;$dialplan_detail_inline;$dialplan_detail_group;$dialplan_detail_order`r`n", $Utf8NoBomEncoding)

Checking the connection to randomly selected numbers showed the import to work.

Accounts → Gateways
Username: 3435555555
Password: not-used
From User: 3435555555
From Domain:
Register: False
Caller ID In From: True
The FusionPBX documentation explicitly states that when making settings, the fields in bold are required.
However, for some reason I didn’t understand, the fat content of the Proxy field was not discernible and I did not set the value for it. As a result, I received working incoming external calls, but not working outgoing calls. The command sofia status gateway ffffffff-ffff-ffff-ffff-ffffffffffffdid not show configuration anomalies and even showed the assigned value of the Proxy field corresponding to the Gateway value . Exactly the same output from the team with exactly the same settings was demonstrated by the “bare” FreeSWITCH in the previous installation, and at the same time it allowed to make outgoing calls outward without any problems.
FusionPBX also earned only afterexplicitly specifying the proxy value .
* ffffffff-ffff-ffff-ffff-ffffffffffff- Gateway UUID
Configuring ACL

I made the settings in accordance with the cheat sheet and immediately received broken internal calls. Logs showed that for some reason the devices were in the external context , respectively, they were processed by a "different" dialpan, from which the call ended with the ROUTE_NOT_FOUND error.

Lyrical digression
The humor of the situation also consisted in the fact that this “trouble” happened to me before I discovered the mandatory filling of the Proxy field in the gateway settings. And as soon as I set up the ACL, the calls started to come from outside, but the internal ones broke. And no matter how I played with the use of ACLs and with their meanings, the result was one: either calls from outside to inside, or internal calls without calls from inside to outside and from outside to inside.

As it turned out, the ACL configuration was not performed correctly!

ACLs are only for network and domain providers.
Your own networks and domains should not be in them .
The domains list should be deny by default .
The rules themselves must be permissive and the IP address of the provider's gateway with the mask / 32 must be written in them, the domain field does not need to be filled.

So, we configure the ACL: Advanced → Access Controls → domains . We delete the existing rules, create a new one:
Type: allow
Description: default SIP-trunk

At the end, click Save , then for the new ACLs to take effect: Status → Sip Status and click Reload ACL .

System Variables

Advanced → Default Settings
Here we will indicate the external IP address given to us by the provider, which we used when setting up 1: 1 NAT in the cheat sheet, we will specify the region’s telephone code, language and voice for voice answers, and the type of dial tone. Defaults

section :
default_areacode: 343
default_language: ru
default_dialect: RU
default_voice: elena
ringback: $${ru-ring}
transfer_ringback: $${ru-ring}
IP Address Section
SIP Profile Section : Internal
internal_auth_calls: true
Actually, it is this variable in the true value that is responsible for reading the settings of the subscriber number and transferring the values $ {outbound_caller_id_number} and $ {outbound_caller_id_name} from it . For this variable to be valid, it is necessary that authorization of internal subscriber numbers via ACL be disabled. By default, out of the box, it is done, and so: ACL-authorization is not used instead Digest (for subscriber number and password) internal_auth_calls: true.

In order to correctly determine the direct city numbers assigned to the internal numbers in the settings through the Outbound Caller ID Name and Outbound Caller ID Number fields , three conditions must be met:
  1. Lack of ACL authorization of internal subscribers
  2. Enabled Digest authentication in SIP profile settings:
    internal_auth_calls: true
  3. Availability in the gateway settings:
    Caller ID In From: True
Outgoing Routes

Dialplan → Outbound Routes
Perhaps this is the only settings item that has not been rethought.
I will not disassemble it in detail. I only note that the following regular expressions were used for various directions:

  • Intracity: ^(\d{7})$(dialing a direct city 7-digit number without any prefixes in the form of zeros, nines, etc.).
  • Intracity with a city code: ^(8343\d{7})$(dialing a city 7-digit number with a prefix of 8343).
  • Cellular: ^(89\d{9})$(call to cellular with prefix 8, which is the de facto standard)
  • Intercity: ^(8\d{10})$(intercity call, also familiar: 8, location code, subscriber number)
  • International: ^(810\d+)$(the standard prefix is ​​810, then the country code, area code, subscriber number).

For all routes, two set type action tags were edited : so that the caller’s number transmitted to the operator included the area code. We treat a call reset after 90-100 seconds on Cisco devices. As noted above, a surprise was the disconnection of the established connection after 90-100 seconds on all Cisco 7945g devices. The twisting of all timers with a more or less relevant variable name in the device configuration did not give a result. Smoking logs in the FreeSWITCH console revealed Session Expire. Googling, except for mats towards the unwillingness of Cisco devices to work normally with at least someone other than Call Manager, revealed that such behavior could very well be cured by disabling the variable .effective_caller_id_name=${default_areacode}${outbound_caller_id_name}effective_caller_id_number=${default_areacode}${outbound_caller_id_number}


Advanced → SIP Profile
Value: true
Enabled: False
Russification of the voice response

We will need voice files created by altruistic professionals.


files.freeswitch.org/releases/sounds/freeswitch-sounds-ru-RU-elena -32000-1.0.51.tar.gz
files.freeswitch.org/releases/sounds/freeswitch -sounds-ru-RU-elena-8000-1.0.51.tar.gz

Each of the archives contains a ready-made directory structure. Unpack each of the archives in / usr / share / freeswitch / sound /

Since we had already done the default settings, from now on the Russian voiceover files will be picked up and will start playing without additional movements. The only thing you may have to do (I had to) is in all four folders ru / RU / elena / voicemail / _bitrate_ / rename the file vm-not_available_no_voicemail.wav and give it a new name vm-no_answer_no_vm.wav . Only after this manipulation did I receive a voice response to the inaccessibility event of the called subscriber.

PS: Like the previous part, this text was written solely for the purpose of documenting the difficulties encountered and their solutions. Despite the fact that the text also illuminates the quick start from scratch of the same FreeSWITCH, albeit with a “graphic face”, I believe that the text is self-sufficient and is a kind of fork, and has the right to independent life. The previous part also retains some value due to the described setup of network equipment. Incorrect settings in that text will be corrected and reduced to those used in this article.

Also popular now: