FreeSWITCH Configuration for SHAKEN-STIR

This blog post provides instructions on how to configure FreeSWITCH to interoperate with the TransNexus OSPrey server. We’ve tested this configuration in the development and testing of SHAKEN/STIR. We’re sharing this information with the community of telecommunications network engineers who are preparing their company’s FreeSWITCH for testing and deployment of SHAKEN/STIR functionality.

OSPrey is a SIP redirect server that provides advanced Least Cost Routing (LCR), fraud control and STIR (Secure Telephony Identity Revisited) / SHAKEN (Secure Handling of Asserted information using toKENs) 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. Network diagram and call scenarios

This section provides the simplified network diagram containing two telephone service providers and the call scenarios. In this example, both service providers use FreeSWITCH and OSPrey redirect servers. 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.

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

Note: A variant scenario is that Destination of ServiceProvider-B is configured as the next destination in the dial pan of ServiceProvider-B, OSPrey-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 4, below. The Main Dial Plan and Lua configuration code blocks below are listed in its entirety.

These configurations work for both FreeSWITCH-A and FreeSWITCH-B in the example above.

2A. SIP Profile Configuration

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

  • Dial plan 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"/>

2B. Wrapper dial plan

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

  • For all calls, use osprey context
  • Load all txnx dial plan files

<extension name="txnx">
<!-- For any called number -->
<condition>
<!-- Jump to osprey logic with destination_number as called number -->
<action application="transfer" data="${destination_number} XML osprey"/>
</condition>
</extension>
...
<X-PRE-PROCESS cmd="include" data="txnx/*.xml"/>

2C. Main dial plan

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

  • Do failover for SIP 404 Not Found response (UNALLOCATED_NUMBER).
  • Pass the Identity header in SIP INVITE to OSPrey server
  • Export the Identity header in SIP INVITE to SIP 3xx handling dial plan
  • Forward the call to OSPrey server

Note: If TCP is requested, add “transport=tcp” in dial string.

  • Do failover if OSPrey replies SIP 404 Not Found response
  • Dial plan for SIP 3xx redirect is defined in redirect context
  • Call Lua script to handle SIP 3xx redirect
  • Try the destinations returned in SIP 3xx redirect
<include>
<!-- OSPrey logic -->
<context name="osprey">
 <extension name="all">
  <condition>
   <action application="set" data="hangup_after_bridge=true"/>
   <!-- Failover on SIP 404 -->
   <action application="set" data="continue_on_fail=UNALLOCATED_NUMBER"/>
   <!-- Pass Identity header in SIP INVITE to OSPrey server -->
   <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 OSPrey -->
   <action application="bridge" data="sofia/external/${destination_number}@osprey_ip"/>
   <!-- Failover -->
   <action application="bridge" data="sofia/external/${destination_number}@destination_ip "/>
  </condition>
 </extension>
</context>
<context name="redirected">
 <extension name="attempt">
  <condition>
   <!-- Call Lua script to handle SIP 3xx redirect >
   <action application="lua" data="redirect.lua"/>
   <!-- Try destinations in SIP 3xx redirect >
  p; <action application="bridge" data="${redirect_dialstring}"/>
  </condition>
 </extension>
</context>
</include>

2D. 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 destination supporting STIR/SHAKEN
-- The flag parameter identifying destination support STIR/SHAKEN
local shakenString = ";shaken"
-- 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 = ""
-- 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))

3. Full sample configurations

SIP Profile configuration - full example

<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="public"/> -->
<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="auto-nat"/>
<param name="ext-sip-ip" value="auto-nat"/>
<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}"/>
<!-- 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>

Wrapper dial plan - full example

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

<extension name="txnx">
<!-- For any called number -->
<condition>
<!-- Jump to osprey logic with destination_number as called number -->
<action application="transfer" data="${destination_number} XML osprey"/>
</condition>
</extension>
</context>

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

 

FreeSWITCH Configuration for SHAKEN/STIR