Overview

In our Flowroute FreeSWITCH series so far, we have 1) configured FreeSWITCH on AWS to make outbound calls from your Flowroute phone number, and 2) enabled inbound and outbound calling on your Flowroute number via a SIP client registering to your FreeSWITCH server. In this demo, we will show you how to access call details real-time and retrieve that information from AWS via the API Gateway and a Lambda function handler.

Requirements


Create your API in AWS

1. Build an API Gateway API as an HTTP or Lambda Proxy

For this tutorial's sake, we will create an API with three resource paths — Call, Call-List, and CDRs. Each of our resource path will be invoking a Lambda function that will altogether demonstrate the capability of the Flowroute HyperNetwork™ to provide adaptive call routing, and with our FreeSWITCH configuration, the ability to simultaneously store call details before a phone call is even answered. To get started on the Amazon API Gateway, please refer to Build an API Gateway API as an HTTP or Lambda Proxy.



2. Write Lambda Functions

a. Originating Number Lookup

First, we will write the Lambda function in Python that will record call information, perform a lookup of the originating number (calling party) based on passed call parameters to determine which HyperNetwork peer to route the call to, and then insert the phone number details into a Postgres database table. This Lambda Proxy is integrated with our Call resource. For this tutorial, we will only be toggling between two peers.

call-lambda-handler.py
import json
import requests
import psycopg2
from netaddr import IPNetwork, IPAddress

conn=psycopg2.connect(dbname= <db_name>, host='ccwdb.<db_host>.us-west-2.rds.amazonaws.com', port= '<port_number>', user= '<db_user>', password= '<db_user_password>')

def lambda_handler(event, context):
    data = {}
    
    data['dest'] = event['pathParameters']['dest']
    data['ip']   = event['pathParameters']['ip']
    data['number'] = event['pathParameters']['number']
    data['uuid'] = event['pathParameters']['uuid']
    data['peer'] = "unknown"
    
    if IPAddress(data['ip']) in IPNetwork("<carrier_a_IP>"):
        data['peer'] = "<carrier_a>"
    if IPAddress(data['ip']) in IPNetwork("<carrier_b_IP>"):
        data['peer'] = "<carrier_b>"        

    print json.dumps(data)

    # curl -v -X POST -d '{"numbers": ["<number>"]}' -H 'Content-Type: application/json' 'http://<aws-account-info>.us-west-2.elb.amazonaws.com/2017-01-12/coverage/portability'

    r = requests.get('https://<aws-account-info>.execute-api.us-west-2.amazonaws.com/dev?number=' + data['number'][1:])
    resp = r.json()
    print json.dumps(resp)
    attr = resp['resp']['data']['attributes']['portable_numbers'][0]
    
    data['orig_sp_name'] = attr['current_service_provider_name']
    data['orig_sp_id'] = attr['current_service_provider_id']
    data['orig_lrn'] = attr['lrn'][:-4] + 'XXXX'
    data['orig_rc'] = attr['rate_center']
    data['orig_state'] = attr['state']
    data['portable'] = len(attr['gaining_service_providers'])
    data['number_obs'] = data['number'][:-4] + 'XXXX'
    data['lata'] = attr['lata']

    print json.dumps(data)

    cur = conn.cursor()
    cur.execute('INSERT INTO call_records ("uuid", "origination", "destination", "hypernetwork_peer", "orig_sp_name", "orig_lrn", "orig_ratecenter", "orig_state", "orig_spid", "portable", "call_start", "lata") VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);',
                (data['uuid'], data['number'], data['dest'], data['peer'], data['orig_sp_name'], data['orig_lrn'], data['orig_rc'], data['orig_state'], data['orig_sp_id'], data['portable'], 'now()', data['lata']))
    cur.close()
    conn.commit()

    return {
        "statusCode": 200,
        "headers": { 'Content-Type': 'application/json' },
        "body": json.dumps({"Success": "True", "data": data})
    }

            

b. Call List

Next, we will write the Lambda function in Python that retrieves the records from our database table above, call_records, and displays them in descending order based on their call start time.

call-list-lambda-handler.py
import json
import psycopg2
import psycopg2.extras
import sys

try:
    conn=psycopg2.connect(dbname= 'ccwdemo', host='ccwdb.<db_host>.us-west-2.rds.amazonaws.com', port= '<port_number>', user= '<db_user>', password= '<db_user_password>')
    print conn
except:
    sys.exit()

def lambda_handler(event, context):
    data = {}
    print(event)
    print(context)
    cur = conn.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor)
    cur.execute("SELECT * FROM call_records order by call_start desc;")
    result = cur.fetchall()
    for call in result:
        data[call.uuid] = {"orig_number": call.origination,
                           "dest_number": call.destination,
                           "network_peer": call.hypernetwork_peer,
                           "orig_service_provider": call.orig_sp_name,
                           "orig_lrn": call.orig_lrn,
                           "orig_ratecenter": call.orig_ratecenter,
                           "orig_state": call.orig_state,
                           "orig_spid": call.orig_spid,
                           "portable": call.portable,
                           "call_start": call.call_start,
                           "call_end": call.call_end
        }
    cur.close()
    return {
        "statusCode": 200,
        "headers": { },
        "body": json.dumps(data),
    }

            

c. CDR

Lastly, we will write the Lambda function in Python that will demonstrate Flowroute's ability to move call paths to entirely different physical networks in the event of quality degradation or network congestion. This article does not cover the LRN (Local Routing Network) flip that is performed in the background and is detected by the function below.

import json
import sys
import logging
from netaddr import IPNetwork, IPAddress

#TODO connect to RDS instance

def lambda_handler(event, context):
    print "event:", event
    print json.dumps(event)
    print "Remote media IP: ", event['variables']['remote_media_ip']
    media_ip = event['variables']['remote_media_ip']

    peer = "unknown"
    if IPAddress(data['ip']) in IPNetwork("<carrier_a_IP>"):
        data['peer'] = "<carrier_a>"
    if IPAddress(data['ip']) in IPNetwork("<carrier_b_IP>"):
        data['peer'] = "<carrier_b>"        

    try:
        body = event
    except ValueError, e:
        print "Invalid JSON post", e
        return {
            "statusCode": 400,
            "headers": { 'Content-Type': 'application/json' },
            "body": json.dumps({"error": "Invalid json post body"})
        }
    return {
        "statusCode": 200,
        "headers": { 'Content-Type': 'application/json' },
        "body": json.dumps({"Success": "True"})
    }

            

In the following demo, our very own CTO, William King, demonstrates the HyperNetwork capabilities that updates the call path and reflects the change in the HyperNetwork Peer information within 20 seconds or so.

Configure FreeSWITCH

1. Install necessary FreeSWITCH packages

Since we will be retrieving our Call Detail Records via our API Gateway endpoint, we will have to enable a few FreeSWITCH modules. We will be using a json object for our output. To install the necessary packages, start a command shell session and SSH into your EC2 server as root. Once you're logged in, update then install your preferred text editor for configuration changes. First, update and install the FreeSWITCHbase with a debug package.

#Update and install the base FS package with debug
apt-get update && apt-get install -y freeswitch-meta-all-dbg
            

Next, run the following command:

apt-get install freeswitch-mod-curl-dbg freeswitch-mod-xml-cdr freeswitch-mod-json-cdr


Debian Tips

To search for additional FreeSWITCH modules, run the following:

apt-cache search freeswitch | grep <search_key>

                

To check for existence of packages in your server, run the following:

apt-cache show <package_name> 
 
                

To install FreeSWITCH sound packages, run the following:

apt-get install freeswitch-music-default freeswitch-sounds-en-us-callie

                

2. Update FreeSWITCH default password

As advised by FreeSWITCH, update your password value to avoid being subjected to toll fraud. To do this, open /etc/freeswitch/vars.xml with your preferred text editor.

/etc/freeswitch/vars.xml
<X-PRE-PROCESS cmd="set" data="default_password=<your_updated_password>"/>

3. Remove VP8 from the List of Codecs

Since we have no need for video in this tutorial, let’s remove VP8 (a video compression format) from our list of codec preferences.

<X-PRE-PROCESS cmd="set" data="global_codec_prefs=OPUS,G722,PCMU,PCMA"/>
<X-PRE-PROCESS cmd="set" data="outbound_codec_prefs=OPUS,G722,PCMU,PCMA"/>

			

4. Load necessary FreeSWITCH modules

Uncomment mod_xml_cdr and mod_curl then add mod_json_cdr in /etc/freeswitch/conf/autoload_configs/modules.conf.xml to load them on startup:


<load module="mod_xml_cdr"/>
<load module="mod_json_cdr"/>
<load module="mod_curl"/>

            

mod_json_cdr is a logging module that will send over the CDR (Call Detail Record) to API Gateway in JSON format.

5. Add JSON Configuration

Next, let's prepare our JSON configuration file and specify our URL for HTTPS logging.

/etc/freeswitch/autoload_configs/json.conf.xml
<configuration name="json_cdr.conf" description="JSON CDR">
  <settings>

    <!-- Global parameters -->
    <param name="log-b-leg" value="true"/>
    <param name="prefix-a-leg" value="false"/>

    <!-- Whether to URL encode the individual JSON values. Defaults to true, set to false for standard JSON. -->
    <param name="encode-values" value="true"/>

    <!-- Normally if url and log-dir are present, url is attempted first and log-dir second. This options allows to do both systematically. -->
    <param name="log-http-and-disk" value="false"/>

    <!-- File logging -->
    <!-- Directory where to create the "json_cdr" directory used to store JSON CDRs. Leave empty for no file logging. -->
    <!-- Might be overriden by a channel variable "json_cdr_base". -->
    <param name="log-dir" value=""/>
    <!-- Whether to rotate file CDRs. -->
    <param name="rotate" value="false"/>

    <!-- HTTP(S) logging -->
    <!-- URL where to POST JSON CDRs. Leave empty for no URL logging. Up to 20 URLs may be specified. -->
    <param name="url" value="https://t<aws-account-info>.execute-api.us-west-2.amazonaws.com/dev/cdrs/"/>

    <!-- Authentication scheme for the above URL. May be one of basic|digest|NTLM|GSS-NEGOTIATE|any-->
    <param name="auth-scheme" value="basic"/>
    <!-- Credentials in the form  username:password  if auth-scheme is used. Leave empty for no authentication. -->
    <param name="cred" value="string"/>
    <!-- Whether to base64 encode the entire JSON document before POSTing it. -->
    <param name="encode" value="base64|true|false"/>
    <!-- Number of retries in case of failure. Each specified URL is tried in turn. -->
    <param name="retries" value="0"/>
    <!-- Delay between retries (ms). -->
    <param name="delay" value="5000"/>
    <!-- Disable streaming if the server doesn't support it. -->
    <param name="disable-100-continue" value="false"/>
    <!-- If web posting failed, the CDR is written to a file. -->
    <!-- Error log dir ("json_cdr" is appended). Up to 20 may be specified. Default to log-dir if none is specified. -->
    <param name="err-log-dir" value=""/>


    <!-- SSL options -->
    <param name="ssl-key-path" value=""/>
    <param name="ssl-key-password" value=""/>
    <!-- SSL version. If specified, must be either "SSLv3" or "TLSv1". -->
    <param name="ssl-version" value=""/>
    <param name="enable-ssl-verifyhost" value="false"/>
    <param name="ssl-cert-path" value=""/>
    <param name="enable-cacert-check" value="false"/>
    <param name="ssl-cacert-file" value=""/>
  </settings>
</configuration>

            

6. Update External and Internal Profiles

Update etc/freeswitch/sip_profiles/external.xml and /etc/freeswitch/sip_profiles/internal.xml directly to reflect the public IP address of your Debian Jessie server just like in the Make Outbound Calls via FreeSWITCH on AWS article.

7. Build Your Extension in the Public Dialplan

Let's add our extension and call it public_conference.

<extension name="public_conference">
      <condition field="destination_number" expression="^(1<your_Flowroute_number>|<your_alternate_number>)$">
        <action application="curl" data="https://<aws-account-info>.execute-api.us-west-2.amazonaws.com/dev/call/${sip_contact_host}/${caller_id_number}/${uuid}/+$1"/>
        <action application="transfer" data="3033 XML default"/>
      </condition>
    </extension>

            

Based on the above, if you place a phone call to either of the above destination numbers, FreeSWITCH will perform a cURL GET request to the Amazon API Gateway endpoint with some passed parameters from the phone call which will then trigger the CNSLookup-CCW Lambda function that retrieves the call details that will be inserted into the call_records database table.

8. Configure the Flowroute Gateway

To access the Flowroute network and have it allow phone calls from the FreeSWITCH User Agent, create a gateway as shown in our first article in the series.

9. Review the CDRs of your Test Phone Calls

You should be able to retrieve the CDRs (Call Detail Records) of your test phone calls to our specified destination numbers in the following places:

Related Articles