FreeSWITCH configuration for STIR/SHAKEN with ClearIP

This documentation provides instructions on how to configure FreeSWITCH to interoperate with the TransNexus ClearIP software platform.

We’ve tested this configuration in the development and testing of STIR/SHAKEN. We’re sharing this information with the community of telecommunications network engineers who are preparing their company’s FreeSWITCH for testing and deployment of STIR/SHAKEN functionality.

ClearIP is a SIP redirect software platform that provides advanced Least Cost Routing (LCR), fraud control and STIR/SHAKEN features.

Contents

  1. Network diagram and call scenarios
  2. FreeSWITCH configuration
    1. SIP profile configuration
    2. Wrapper dial plan
    3. Main dial plan
    4. Lua script
  3. Full sample configurations
    1. SIP profile
    2. Wrapper dial plan
  4. Lua script options

1. Network diagram and call scenarios

This section provides the simplified network diagram containing two telephone service providers and the call scenarios.

Freeswitch network diagram

Figure 1. FreeSWITCH Network Diagram

In Figure 1, both service providers use FreeSWITCH and ClearIP solution. While this is not necessary to perform the illustrative services, this example configuration enables us to describe the appropriate configuration at both ends of the call.

  1. Origin, a subscriber with Service Provider A, sends a call to their FreeSWITCH-A.
  2. FreeSWITCH-A forwards the call to ClearIP-A, which is a SIP redirect server providing LCR, fraud control, SHAKEN Authentication Service (AS) and other services.
  3. ClearIP-A performs LCR, fraud control and SHAKEN AS services, then sends one of the following responses back to FreeSWITCH-A:
    • SIP 404 Not Found: No fraud or SHAKEN AS errors are detected and routing information is unavailable.
    • SIP 603 Decline: Fraud is detected or SHAKEN AS fails.
    • SIP 3xx Redirect: Routing information (FreeSWITCH-B of Service Provider B) and a SIP X-Identity header including a digitally signed token that includes the calling number (secure caller ID).
  4. FreeSWITCH-A processes the response:
    • If SIP 404 Not Found, then FreeSWITCH-A tries the next destination configured in its dial plan.
    • If SIP 603 Decline, then FreeSWITCH-A proxies the response back to Origin to block the call.
    • If SIP 3xx Redirect, then FreeSWITCH-A redirects the call to FreeSWITCH-B of Service Provider B with the Identity header.
  5. FreeSWITCH-B forwards the call to their ClearIP-B, which is a SIP redirect server providing fraud control, SHAKEN Verification Service (VS) and other services.
  6. ClearIP-B performs fraud control and SHAKEN VS logic, then sends one of the following SIP responses back to FreeSWITCH-B:
    • SIP 404 Not Found: No fraud or SHAKEN VS errors are detected and routing information is unavailable.
    • SIP 603 Decline: Fraud is detected or SHAKEN VS request fails.
    • SIP 3xx Redirect: Routing information (Destination of Service Provider B) is attached.
  7. FreeSWITCH-B processes the response:
    • If SIP 404 Not Found, then FreeSWITCH-B tries the next destination configured in its dial plan.
    • If SIP 603 Decline, then FreeSWITCH-B proxies the response back to FreeSWITCH-A to block the call.
    • If SIP 3xx Redirect, then FreeSWITCH-B redirects the call to Destination.

Note: A variant scenario is that Destination of ServiceProvider-B is configured as the next destination in the dial pan of Service Provider B, ClearIP-B returns SIP 404 Not Found, then FreeSWITCH-B does failover to Destination.

2. FreeSWITCH configuration

This section provides FreeSWITCH configuration for the solution. The SIP Profile and Wrapper Dial Plan configuration code blocks show only the lines that should be added if they do not yet exist or changed if they do exist. Complete examples of these configuration files are shown in section 3. The Main Dial Plan and Lua configuration code blocks are listed blow in its entirety.

These configurations work for both FreeSWITCH-A and FreeSWITCH-B in Figure 1 above.

2.1. SIP profile configuration

$FreeSWITCH/etc/freeswitch/sip_profiles/external.xml

  • Dial plan for test is defined in txnx context
  • Parse all headers (including Identity header) in SIP INVITE
  • Handle SIP 3xx redirect response in dial plan explicitly
<!--<param name="context" value="public"/>-->
<param name="context" value="txnx"/>

<!-- Parse all headers in INVITE message -->
<param name="parse-all-invite-headers" value="true"/>
<!-- Handle SIP 3xx redirect response in dial plan explicitly -->
<param name="manual-redirect" value="true"/>

2.2 Wrapper dial plan

$FreeSWITCH/etc/freeswitch/dialplan/txnx.xml
  • For all test calls, use clearip context
  • Load all txnx dial plan files
<extension name="txnx">
  <!-- For any called number -->
  <condition>
    <!-- Jump to clearip logic with destination_number as called number -->
    <action application="transfer" data="${destination_number} XML clearip"/>
  </condition>
</extension>

<X-PRE-PROCESS cmd="include" data="txnx/*.xml"/>

2.3 Main dial plan

$FreeSWITCH/etc/freeswitch/dialplan/txnx/clearip.xml

  • Pass the Identity header in SIP INVITE to ClearIP server.
  • Export the Identity header in SIP INVITE to SIP 3xx handling dial plan.
  • Forward the call to ClearIP.
    • ClearIP should be accessed by FQDN.
    • ClearIP only supports TCP and TLS. For TCP, “;transport=tcp” must be added into the dial string.
  • Do failover if ClearIP replies SIP 404 Not Found response.
  • Dial plan for SIP 3xx redirect is defined in redirected context.
  • Call Lua script to handle SIP 3xx Redirect.
  • Try the destinations returned in SIP 3xx Redirect.
<include>
<!-- ClearIP logic -->
<context name="clearip">
  <extension>
    <condition>
      <action application="set" data="hangup_after_bridge=true"/>
      <!-- Failover -->
      <action application="set" data="continue_on_fail=true"/>
      <!-- Pass Identity header in SIP INVITE to ClearIP -->
      <action application="set" data="sip_h_Identity=${sip_i_identity}"/>
      <!-- Export Identity header in SIP INVITE to SIP 3xx dial plan -->
      <action application="export" data="identity=${sip_i_identity}"/>
      <!-- Forward call to ClearIP -->
      <action application="bridge" data="sofia/external/${destination_number}@clearip_fqdn;transport=tcp"/>
      <!-- Failover -->
      <action application="transfer" data="${destination_number} XML failover"/>
    </condition>
  </extension>
</context>

<context name="redirected">
  <extension>
    <condition>
      <!-- Call Lua script to handle SIP 3xx -->
      <action application="lua" data="redirect.lua"/>
      <!-- Try destinations in SIP 3xx redirect -->
      <action application="bridge" data="${redirect_dialstring}"/>
    </condition>
  </extension>
</context>

<context name="failover">
  <extension>
    <condition field="${sip_invite_failure_status}" expression="^404$">
      <!-- Failover -->
      <action application="bridge" data="sofia/external/${destination_number}@provider_fqdn_ip"/>
    </condition>
  </extension>
</context>
</include>

Note: It is possible to configure FreeSWITCH to do failover for other SIP negative response codes, such as SIP 403, SIP 503, etc., but FreeSWITCH should not be configured to do failover for SIP 603.

2.4 Lua script

$FreeSWITCH/share/freeswitch/scripts/redirect.lua
  • If there is an Identity header in the SIP INVITE, use it. Otherwise, use the X-Identity header in SIP 3xx.
  • Get the dial string provided by FreeSWITCH based on SIP 3xx.
  • Modify the dial string to insert the Identity header for the destinations supporting STIR/SHAKEN.
-- The flag parameter identifying destination support STIR/SHAKEN
local shakenString = ";shaken"
-- The global option to prevent duplicate Identity header
local globalString = "{exclude_outgoing_extra_header=sip_h_Identity}"
-- The option to insert Identity header per destination
local branchString = "[sip_h_Identity=${identity}]"

-- If no Identity header in INVITE, use SIP 3xx X-Identity header
if not session:getVariable("identity") then
  session:setVariable("identity", session:getVariable("sip_rh_X-Identity"))
end

-- Extract destination dial string from SIP 3xx
local inDialString = session:getVariable("sip_redirect_dialstring")

-- Insert Identity header into SIP INVITE to destinations supporting SHAKEN
local outDialString = globalString
-- For each destination
for part in string.gmatch(inDialString , "([^|]+)") do
if string.match(part, shakenString) then
  -- If destination supports SHAKEN, insert Identity header
  outDialString = outDialString .. branchString .. string.gsub(part, shakenString, "") .. "|"
else
  -- If destination does not support SHAKEN, do nothing
  outDialString = outDialString .. part .. "|"
  end
end

-- Erase trailing pipe char
session:setVariable("redirect_dialstring", string.sub(outDialString, 1, -2))

Note: For certain FreeSWITCH versions, they may insert 2 identical SIP Identity headers into SIP INVITE based on the sip_h_Identity variable. Then exclude_outgoing_extra_header=sip_h_Identity must be set to prevent this behavior.

3. Full sample configurations

3.1 SIP profile

<profile name="external">
 <!-- http://wiki.freeswitch.org/wiki/Sofia_Configuration_Files -->
 <!-- This profile is only for outbound registrations to providers -->
 <gateways>
  <X-PRE-PROCESS cmd="include" data="external/*.xml"/>
 </gateways>

 <aliases>
  <!--
    <alias name="outbound"/>
    <alias name="nat"/>
  -->
 </aliases>

 <domains>
  <domain name="all" alias="false" parse="true"/>
 </domains>

 <settings>
  <param name="debug" value="0"/>
  <!-- If you want FreeSWITCH to shutdown if this profile fails to load, uncomment the next line. -->
  <!-- <param name="shutdown-on-fail" value="true"/> -->
  <param name="sip-trace" value="no"/>
  <param name="sip-capture" value="no"/>
  <param name="rfc2833-pt" value="101"/>
  <!-- RFC 5626 : Send reg-id and sip.instance -->
  <!--<param name="enable-rfc-5626" value="true"/> -->
  <param name="sip-port" value="$${external_sip_port}"/>
  <param name="dialplan" value="XML"/>
  <param name="context" value="txnx"/>
  <param name="dtmf-duration" value="2000"/>
  <param name="inbound-codec-prefs" value="$${global_codec_prefs}"/>
  <param name="outbound-codec-prefs" value="$${outbound_codec_prefs}"/>
  <param name="hold-music" value="$${hold_music}"/>
  <param name="rtp-timer-name" value="soft"/>
  <!--<param name="enable-100rel" value="true"/>-->
  <!--<param name="disable-srv503" value="true"/>-->
  <!-- This could be set to "passive" -->
  <param name="local-network-acl" value="localnet.auto"/>
  <param name="manage-presence" value="false"/>

  <!-- used to share presence info across sofia profiles
     manage-presence needs to be set to passive on this profile
     if you want it to behave as if it were the internal profile
     for presence.
  -->
  <!-- Name of the db to use for this profile -->
  <!--<param name="dbname" value="share_presence"/>-->
  <!--<param name="presence-hosts" value="$${domain}"/>-->
  <!--<param name="force-register-domain" value="$${domain}"/>-->
  <!--all inbound reg will stored in the db using this domain -->
  <!--<param name="force-register-db-domain" value="$${domain}"/>-->
  <!-- ************************************************* -->

  <!--<param name="aggressive-nat-detection" value="true"/>-->
  <param name="inbound-codec-negotiation" value="generous"/>
  <param name="nonce-ttl" value="60"/>
  <param name="auth-calls" value="false"/>
  <param name="inbound-late-negotiation" value="true"/>
  <param name="inbound-zrtp-passthru" value="true"/> <!-- (also enables late negotiation) -->
  <!--
    DO NOT USE HOSTNAMES, ONLY IP ADDRESSES IN THESE SETTINGS!
  -->
  <param name="rtp-ip" value="$${local_ip_v4}"/>
  <param name="sip-ip" value="$${local_ip_v4}"/>
  <param name="ext-rtp-ip" value="$${external_rtp_ip}"/>
  <param name="ext-sip-ip" value="$${external_sip_ip}"/>
  <param name="rtp-timeout-sec" value="300"/>
  <param name="rtp-hold-timeout-sec" value="1800"/>
  <!--<param name="enable-3pcc" value="true"/>-->

  <!-- TLS: disabled by default, set to "true" to enable -->
  <param name="tls" value="$${external_ssl_enable}"/>
  <!-- Set to true to not bind on the normal sip-port but only on the TLS port -->
  <param name="tls-only" value="false"/>
  <!-- additional bind parameters for TLS -->
  <param name="tls-bind-params" value="transport=tls"/>
  <!-- Port to listen on for TLS requests. (5081 will be used if unspecified) -->
  <param name="tls-sip-port" value="$${external_tls_port}"/>
  <!-- Location of the agent.pem and cafile.pem ssl certificates (needed for TLS server) -->
  <!--<param name="tls-cert-dir" value=""/>-->
  <!-- Optionally set the passphrase password used by openSSL to encrypt/decrypt TLS private key files -->
  <param name="tls-passphrase" value=""/>
  <!-- Verify the date on TLS certificates -->
  <param name="tls-verify-date" value="true"/>
  <!-- TLS verify policy, when registering/inviting gateways with other servers (outbound) or handling inbound registration/invite requests how should we verify their certificate -->
  <!-- set to 'in' to only verify incoming connections, 'out' to only verify outgoing connections, 'all' to verify all connections, also 'subjects_in', 'subjects_out' and 'subjects_all' for subject validation. Multiple policies can be split with a '|' pipe -->
  <param name="tls-verify-policy" value="none"/>
  <!-- Certificate max verify depth to use for validating peer TLS certificates when the verify policy is not none -->
  <param name="tls-verify-depth" value="2"/>
  <!-- If the tls-verify-policy is set to subjects_all or subjects_in this sets which subjects are allowed, multiple subjects can be split with a '|' pipe -->
  <param name="tls-verify-in-subjects" value=""/>
  <!-- TLS version ("sslv23" (default), "tlsv1"). NOTE: Phones may not work with TLSv1 -->
  <param name="tls-version" value="$${sip_tls_version}"/>
  <!-- TLS ciphers default: ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH -->
  <param name="tls-ciphers" value="$${sip_tls_ciphers}"/>
  <!-- Parse all headers in INVITE message -->
  <param name="parse-all-invite-headers" value="true"/>
  <!-- Handle SIP 3xx redirect response in dial plan explicitly -->
  <param name="manual-redirect" value="true"/>
 </settings>
</profile>

3.2 Wrapper dial plan

<include>
 <context name="txnx">
  <extension name="unloop">
   <condition field="${unroll_loops}" expression="^true$"/>
   <condition field="${sip_looped_call}" expression="^true$">
    <action application="deflect" data="${destination_number}"/>
   </condition>
  </extension>

  <!--
    Tag anything pass thru here as an outside_call so you can make sure
    not to create any routing loops based on the conditions that it came
    from the outside of the switch.
  -->

  <extension name="outside_call" continue="true">
   <condition>
    <action application="set" data="outside_call=true"/>
   </condition>
  </extension>

  <extension name="call_debug" continue="true">
   <condition field="${call_debug}" expression="^true$" break="never">
    <action application="info"/>
   </condition>
  </extension>

  <!-- XML dialplan for OSP -->
  <extension name="osp">
   <!-- For any called number -->
   <condition>
    <!-- Jump to ClearIP logic with destination_number as called number -->
    <action application="transfer" data="${destination_number} XML clearip"/>
   </condition>
  </extension>
 </context>

 <!--
   You can place files in txnx directory to get included.
 -->
 <X-PRE-PROCESS cmd="include" data="txnx/*.xml"/>
</include>

4. Lua script options

  1. The test configuration uses a Lua script to handle SIP 3xx. It is easier to perform complicated string options by Lua script. It may not be necessary if the string options are simple.
  2. Some local policies can be implemented by the Lua script. For example, in the test, FreeSWITCH redirects the calls with the Identity headers from the inbound SIP INVITE messages. It is possible to implement different policy, for example, appending both Identity headers from the inbound SIP INVITE and from the SIP 3xx, which is for STIR/DIV.
  3. ClearIP is for SIP protocol. The target destinations returned in SIP 3xx from ClearIP are in SIP URI format. By default, FreeSWITCH uses the same Sofia SIP profile that forwards call to ClearIP (sofia/external in the test) to redirect the calls to the target destinations.
    1. If the calls must be sent by another Sofia SIP profile, it is possible by modifying the Lua script. For example, a parameter, say “;internal”, is configured in ClearIP for the target destinations using sofia/internal SIP profile. The Lua script can replace “sofia/external/sip:called_number@destination_address;internal” with “sofia/internal/sip:called_number@destination_address” in the redirect_dialstring variable.
    2. If the calls must be sent to a gateway, it is also possible. Since ClearIP only supports IP address and FQND for target destinations, a dummy IP with parameters can be used to identify the gateway. For example, “127.0.0.1;gateway=gateway_name” is configured in ClearIP for the target gateway. The Lua script can replace “sofia/external/sip:called_number@127.0.0.1;gateway=gateway_name” with “sofia/gateway/gateway_name/called_number” in the redirect_dialstring variable.
    3. If the calls must be sent to a local registered endpoint, it is also possible. Since ClearIP only supports IP address and FQND for target destinations, a dummy IP with parameters can be used to identify the endpoint. For example, “127.0.0.1;endpoint” is configured in ClearIP for the target endpoint. The Lua script can replace “sofia/external/sip:called_number@127.0.0.1;endpoint=endpoint_name” with “sofia/internal/endpoint_name%{sip_profile}” in the redirect_dialstring variable.

Note: b and c are useful for the inbound call scenarios showed in the test for Server Provider B.

gears