📜 ⬆️ ⬇️

Implementing a VoIP card platform on FreeSWITCH using RADIUS

There was a task to get rid of old rubbish in the rack and implement a software version, a slightly forgotten, but still existing technology to provide, as a rule, long-distance / international communication for subscribers of other operators by calling to a special access number and entering a PIN code. Subscriber authorization goes through billing via RADIUS, call records are added to the same place.

The platform itself is of no interest to anyone, but when I wrote configs, I really didn’t have enough usage examples, I hope this example will be useful to someone.

My work is only superficially connected with telephony, so I am not very familiar with Asterisk, so when choosing a base platform I did not have any restrictions or prejudices. It would be quite logical to realize everything on Asterisk, especially considering its active use in the company, but according to bitter experience, its drops occur at the most inappropriate moment and simply restarting the service does not help. Therefore, after reading positive reviews and reviews, FreeSWITCH was chosen as the platform. Documentation for it is certainly much less and even less in Russian, but it didn’t scare, because I remember very well how the Asterisk at dusk h323 was assembled from several packages, in strict correspondence between the versions and examples of installations were units. Before and during the setup process, the Wiki was carefully studied.

Formulation of the problem


There are a number of people who want to make long-distance calls more profitable than tariffs, especially of a mobile operator, without any applications on the phone or simply from the work number, where there is no access to the 8-ku. To do this, an access number (or several) is organized, where the client calls (hereinafter 555555), passes authorization (by caller ID or PIN code), hears his current balance and dials the number he wanted to call, the call data should be included in the billing for billing. Actually all this has already worked since long years (and it was a mega popular service) on the huge and scary Cisco AS5300. Anticipating criticism of caller ID authorization: the prepaid system doesn’t have large balances - the risks are minimal, there are few customers - it’s difficult to guess which callers can call for free, make calls via VoIP and replace the number is useless - the profit from such a call is minimal, local operators are easy to track.
')
Something like a service scheme, the numbers on the right are expression for extensions:



Nothing complicated, but I had to tinker.
  1. Absolutely not familiar to the admin format of configs in xml, besides the logic of their inclusions at first seems very confusing.
  2. The logic of the conditions condition is not very familiar, in particular, I was confused all the time by break = “on-true” (how to interrupt the program execution in truth), I advise you to carefully study this issue and everything becomes very logical. In short, break only affects the hunting process (about hunting a little lower) and interrupts the processing of conditions in the current extension if the condition matches (true), does not match (false by default) or does not interrupt at all and handles the following condition in any case (never ).
  3. All calculations, except those primitive for which you can set inline = "true", are performed only after transfer or execute_extension. The bottom line is that FS handles XML_Dialplan in two stages - hunting and executing (hunting and executing). During a hunt, conditions, actions and anti-actions are executed and applications are selected that must be executed. Therefore, when you need to get the results of a complex application, you need to switch to another extension.
  4. You can’t just take and substitute something into a string without regular expressions, and the absence of simple elementary algebraic functions is very confusing.
  5. Documentation, especially on modules, is very scanty (it was an epic description of the function that was corrected in April: If you don’t have it! :)).


Decision


I will not describe the FS installation, everything is simple there, besides it’s standard, in addition it is only necessary to compile the missing modules. All paths will be relative to the folder where FS is installed.

A separate number was allocated for setting up and sent to FS (there is a feature of using a non-standard port when calling from external servers - 5080). Create a profile for outgoing calls (for receiving a fairly correct extension in public context) via a server called sipgate (IP address 10.10.10.10) in conf / sip_profiles / external / sipgate.xml, in my case, it’s enough without authorization:

<include> <gateway name="sipgate"> <param name="username" value="<  >"/> <param name="proxy" value="10.10.10.10"/> <param name="register" value="false"/> <param name="caller-id-in-from" value="true"/> </gateway> </include> 


For further work, you need to Russify FS and the sounds / ru / RU / elena folder has been filled with sound files of the required bitrate, in my case 8000 ( archive with files ). In the freeswitch.xml file, we change en to ru:

 <section name="languages" description="Language Management"> <!-- <X-PRE-PROCESS cmd="include" data="lang/en/*.xml"/>--> <X-PRE-PROCESS cmd="include" data="lang/ru/*.xml"/> </section> 


Voice files were used standard and they are not enough, but there are problems with recording.
The dialplan / public.xml config has been truncated to the maximum:

 <include> <context name="public"> <extension name="unloop"> <condition field="${unroll_loops}" expression="^true$"/> <condition field="${sip_looped_call}" expression="^true$"> <action application="deflect" data="${destination_number}"/> </condition> </extension> <X-PRE-PROCESS cmd="include" data="public/*.xml"/> </context> </include> 


And in the file conf / dialplan / public / voip_public.xml, we write an extension for incoming calls, where we immediately try to authorize it by the number:

 <include> <extension name="voip_platform_pub_step1"> <condition field="destination_number" expression="^(555555)$"> <!--          --> <action application="log" data="INFO pub/1 RAD_AUTH STEP1"/> <action application="set" data="process_cdr=b_only"/><!--   Stop accounting    , ..      ,    Start  -    ... --> <action inline="true" application="set" data="pin_auth_count=0"/><!--     ,        -   --> <!--     radius  --> <action inline="true" application="set" data="CALLID=${uuid}"/> <action inline="true" application="set" data="CALLINGNUMBER=${caller_id_number}"/> <action inline="true" application="set" data="USERNAME=${caller_id_number}"/> <action inline="true" application="set" data="STEP=fs1"/><!--      (     ) --> <action application="auth_function" data="in ${CALLEDNUMBER}, in ${USERNAME}, in ${PASSWD}, out AUTH_RESULT"/><!--    radius,    ../../autoload_configs/rad_auth.conf.xml --> <action application="log" data="INFO pub/1 AUTH_RESULT=${AUTH_RESULT}: credit_amount=${credit_amount}; return_code=${return_code}"/> <action application="set" data="domain_name=$${domain}"/> <action application="transfer" data="10 XML voip"/><!--         --> </condition> </extension> </include> 


Separate attention “STEP = fs1” - in my case it was more convenient to tell the billing that the caller ID authorization with the fs1 flag, and the PIN authorization fs1pin.

The comments mention conf / autoload_configs / rad_auth.conf.xml (the IP address of the RADIUS server is 10.20.20.20):

 <configuration name="rad_auth.conf" description="radius authentification module"> <settings> </settings> <client> <param name="authserver" value="10.20.20.20:1812:radiussecret"/> <param name="dictionary" value="/usr/local/etc/radiusclient/dictionary.all"/> <param name="seqfile" value="/var/run/radius.seq"/> <param name="mapfile" value="/usr/local/etc/radiusclient/port-id-map"/> <param name="default_realm" value=""/> <param name="radius_timeout" value="3"/> <param name="radius_retries" value="2"/> <param name="radius_deadtime" value="0"/> <param name="bindaddr" value="*"/> </client> <vsas> <!--name= , id=   dictionary, value=    , pec=  dictionary, expr=       , direction=  --> <param name="Acct-Session-Id" id="44" value="CALLID" pec="0" expr="1" direction="in"/> <param name="Freeswitch-Ani" id="8" value="CALLINGNUMBER" pec="27880" expr="1" direction="in"/> <param name="Freeswitch-Dst" id="5" value="CALLEDNUMBER" pec="27880" expr="1" direction="in"/> <param name="NAS-Port-Type" id="61" value="0" pec="0" expr="0" direction="in"/> <param name="Connect-Info" id="77" value="STEP" pec="0" expr="1" direction="in"/> <param name="CREDIT_AMOUNT" id="101" value="credit_amount" pec="9" expr="0" direction="out"/> <param name="CREDIT_TIME" id="102" value="credit_time" pec="9" expr="0" direction="out"/> <param name="RADIUS_RETURN_CODE" id="103" value="return_code" pec="9" expr="0" direction="out"/> </vsas> </configuration> 


And now all the main logic in the conf / dialplan / voip.xml file, according to the scheme at the beginning of the article:

 <?xml version="1.0" encoding="utf-8"?> <include> <context name="voip"> <extension name="unloop"> <condition field="${unroll_loops}" expression="^true$"/> <condition field="${sip_looped_call}" expression="^true$"> <action application="deflect" data="${destination_number}"/> </condition> </extension> <extension name="voip_10"> <condition field="destination_number" expression="^10$" break="on-false"/> <condition field="${AUTH_RESULT}" expression="^OK$" break="on-true"> <!--   IVR   dtmf  --> <action application="log" data="INFO voip_10 AUTH_RESULT=${AUTH_RESULT} => Read DTMF"/> <action application="answer"/> <action application="sleep" data="1000"/> <action application="play_and_get_digits" data="6 20 5 30000 # phrase:voip_get_digits voicemail/vm-fail_auth.wav digits ^\**(\d{6}|\d{10,20})\**$ 5000"/><!-- <min> <max> <tries> <timeout> <terminators> <file> <invalid_file> <var_name> <regexp> <digit_timeout> --> <action application="transfer" data="20 XML voip"/> </condition> <condition field="${return_code}" expression="^h323-return-code=6$" break="on-true"> <!--      =>      --> <action application="log" data="INFO voip_10 RETURN_CODE = 6 => Closed account"/> <action application="answer"/> <action application="sleep" data="1000"/> <!--TODO!!!      --> <action application="playback" data="voicemail/vm-not_available.wav"/> <action application="hangup" data="NORMAL_CLEARING"/> </condition> <!--TODO!!!     --> <condition field="${pin_auth_count}" expression="^0$" break="on-true"><!--        --> <!--         ,  PIN --> <action inline="true" application="set" data="pin_auth_count=1"/> <action application="log" data="INFO voip_10 RETURN_CODE = OTHER"/> <action application="answer"/> <action application="sleep" data="1000"/> <action application="play_and_get_digits" data="10 10 5 30000 # phrase:voip_get_pin conference/conf-bad-pin.wav pin ^(\d{10})$ 5000"/><!-- <min> <max> <tries> <timeout> <terminators> <file> <invalid_file> <var_name> <regexp> <digit_timeout> --> <action application="transfer" data="15 XML voip"/> </condition> <condition> <!--      ,        ,       PIN-,     PIN-     --> <action application="log" data="INFO voip_10 Prevent second PIN authentification"/> <action application="answer"/> <action application="sleep" data="1000"/> <!--TODO!!!         --> <action application="playback" data="voicemail/vm-not_available.wav"/> <action application="hangup" data="NORMAL_CLEARING"/> </condition> </extension> <extension name="voip_15"> <condition field="destination_number" expression="^15$"/> <condition field="${pin}" expression="^(\d{6})(\d{4})$"> <!--      PIN- --> <action application="log" data="INFO voip_15 pin=($1+$2) => RAD_AUTH STEP1/PIN"/> <action inline="true" application="set" data="CALLINGNUMBER=${caller_id_number}"/> <action inline="true" application="set" data="USERNAME=$1"/> <action inline="true" application="set" data="PASSWD=$2"/> <action inline="true" application="set" data="STEP=fs1pin"/> <action application="log" data="INFO voip_15 CALLID=${CALLID}; CALLINGNUMBER=${CALLINGNUMBER}; USERNAME=${USERNAME}"/> <action application="auth_function" data="in ${CALLEDNUMBER}, in ${USERNAME}, in ${PASSWD}, out AUTH_RESULT"/> <action application="log" data="INFO voip_15 AUTH_RESULT=${AUTH_RESULT}: credit_amount=${credit_amount}; return_code=${return_code}"/> <action application="transfer" data="10 XML voip"/><!--               --> </condition> </extension> <extension name="voip_20"> <condition field="destination_number" expression="^20$"/> <condition field="${digits}" expression="^\**(\d+)\**$"> <!--         --> <action inline="true" application="set" data="digits=$1"/> <action inline="true" application="set" data="digits=${regex(${digits}|^(\d{6})$|83532%1)}"/><!--  85555 –          ^(\d{6})$  ,      --> <action application="log" data="INFO voip_20 DTMF digits=${digits} => RAD_AUTH STEP2"/> <action application="log" data="INFO voip_20 CALLID=${CALLID}; CALLINGNUMBER=${CALLINGNUMBER}; USERNAME=${USERNAME}"/> <action inline="true" application="set" data="CALLEDNUMBER=${digits}"/> <!--       fs2pin  fs2 --> <action inline="true" application="set" data="STEP=${regex(${STEP}|^fs\d(.*)$|fs2%1)}"/> <action application="auth_function" data="in ${CALLEDNUMBER}, in ${USERNAME}, in ${PASSWD}, out AUTH_RESULT"/> <action application="log" data="INFO voip_20 AUTH_RESULT=${AUTH_RESULT}: credit_amount=${credit_amount}; credit_time=${credit_time}; return_code=${return_code}"/> <!--            --> <action application="export" data="nolocal:api_on_answer=sched_hangup +${credit_time} ${uuid} alloted_timeout" /> <action application="transfer" data="30 XML voip"/> </condition> </extension> <extension name="voip_30"> <condition field="destination_number" expression="^30$" break="on-false"/> <condition field="${AUTH_RESULT}" expression="^OK$" break="on-true"> <action application="log" data="INFO voip_30 AUTH_RESULT=${AUTH_RESULT} => Call number"/> <action inline="true" application="set" data="effective_caller_id_number=35555555555"/> <!--      (|)    (fs2|fs2pin),     Freeswitch-CLID   --> <action inline="true" application="set" data="effective_caller_id_name=${STEP}"/> <!--     USERNAME    ,   mod_radius_cdr        User-Name   --> <action application="export" data="nolocal:acc_username=${USERNAME}"/> <!-- <action application="set_profile_var" data="Caller-Username=${USERNAME}"/>         mod_radius_cdr,         --> <action application="set" data="hangup_after_bridge=true"/><!--  !     --> <action application="bridge" data="sofia/gateway/sipgate/${digits}"/> <!--TODO!!!    --> <action application="log" data="INFO voip_30 AFTER BRIDGE"/> </condition> <!--TODO!!!        ,    .. --> <!--TODO!!! h323-return-code=9 Access denied -     ,        --> <condition> <action application="log" data="INFO voip_30 RETURN_CODE = OTHER"/> <action application="answer"/> <action application="sleep" data="1000"/> <action application="playback" data="zrtp/zrtp-status_error.wav"/> <action application="hangup" data="NORMAL_CLEARING"/> </condition> </extension> </context> </include> 


Mentioned are the phrase: voip_get_digits and the phrase: voip_get_pin - “phrases” that will be told to the client while waiting for input, and the second could be done without it. Stored in conf / lang / ru / viop.xml file:

 <include> <macro name="voip_get_digits" pause="250"> <input pattern="(.*)"> <match> <action function="play-file" data="ivr/ivr-account_balance_is.wav"/> <action function="say" data="${credit_amount}" method="pronounced" type="currency"/> <action function="play-file" data="ivr/ivr-please_enter_the_phone_number.wav"/> </match> </input> </macro> <macro name="voip_get_pin" pause="250"> <input pattern="(.*)"> <match> <action function="play-file" data="ivr/ivr-please_enter_pin_followed_by_pound.wav"/> <action function="execute" data="sleep(1000)"/> </match> </input> </macro> </include> 


And also the conf / autoload_configs / mod_radius_cdr.conf.xml config, where almost nothing is configured and in fact all the logic is rigidly written in the code:

 <configuration name="mod_radius_cdr.conf" description="RADIUS CDR Configuration"> <settings> <param name="dictionary" value="/usr/local/etc/radiusclient/dictionary.all"/> <param name="seqfile" value="/var/run/radius.seq"/> <param name="acctserver" value="10.20.20.20:1813:radiussecret"/> <param name="radius_retries" value="2"/> <param name="radius_timeout" value="3"/> <param name="radius_deadtime" value="0"/> </settings> </configuration> 


I had to slightly modify the code of this module, since the statistics should fall down either by number or by login, and I did not find the way to use my regular means to skip my variable with the flag of the authorization used through this module.

As a result, when everything was already working, the common FS config file was brutally castrated in order to reduce the final log / freeswitch.xml.fsxml config file.
According to the text, there are tudushki, without them everything works, but if they are done it will be more beautiful.

Source: https://habr.com/ru/post/189148/


All Articles