FreeSWITCH-driven routing in OpenSIPS 2.3

SIP setups where OpenSIPS acts as a front-end for a farm of class 5 servers are becoming increasingly popular. Such architectures combine the best of both worlds: robust and optimized handling of SIP signaling with feature-rich class 5 softswitches.

This way of integrating open-source projects has lead to a unique opportunity for our team, along with the FreeSWITCH team, to collaboratively work together for the benefit of both communities. Throughout the past year, the brainstorming sessions have lead to a series of additions that bring added value to the above-mentioned platforms, including the mid-registrar concept, which was implemented a few months ago.

Considering an OpenSIPS-FreeSWITCH setup, the improvements discussed in this article would benefit both layers, as the former (the front-end) will benefit from a more accurate balancing – hence less call re-tries, while the latter (the class 5 layer) will enjoy a better load distribution, thus potentially making full use of the available resources.

The limitation

The fact that the dispatcher and load_balancer OpenSIPS modules have had positive reviews over the years does not mean they perfectly solve the underlying problem. In fact, just like the majority of balancing software that’s out there, they incorporate several assumptions:

  • accurately provisioned destination weights
  • no other VoIP component is also using the destinations (hidden extra used channels)
  • no other service is putting load on the destinations (extra CPU load)

To summarize, both modules are agnostic to any external factors that may influence the routing, and make routing decisions strictly based on internal, OpenSIPS-level logic.

The solution

We can begin addressing the above issues by making OpenSIPS aware, in real-time, of the current load level of each destination. Having this information not only eliminates all OpenSIPS-side provisioning for the destinations (which is already nice!), but it also improves the quality of the balancing, which now takes into account the external factors mentioned above (SIP traffic from other components and OS services).

For FreeSWITCH-type destinations, an implementation of all the above is already available on the OpenSIPS development branch (master), which will be officially available starting with the upcoming OpenSIPS 2.3 beta release, which is due mid-March.

How does the FreeSWITCH integration work?

OpenSIPS will make use of the FreeSWITCH Event Socket Layer, an API which can be used to both control a FreeSWITCH server and fetch information from it. In our case, OpenSIPS will subscribe to HEARTBEAT events from FreeSWITCH. A heartbeat message looks like this:

 "Core-UUID": "8e16a74f-194d-4414-8c76-77a208ed8eb0",
 "Event-Calling-File": "switch_core.c",
 "Event-Calling-Function": "send_heartbeat",
 "Event-Calling-Line-Number": "74",
 "Event-Date-GMT": "Mon, 30 Jan 2017 14:44:00 GMT",
 "Event-Date-Local": "2017-01-30 14:44:00",
 "Event-Date-Timestamp": "1485787440021923",
 "Event-Info": "System Ready",
 "Event-Name": "HEARTBEAT",
 "Event-Sequence": "86536",
 "FreeSWITCH-Hostname": "pbx2",
 "FreeSWITCH-IPv4": "",
 "FreeSWITCH-IPv6": "::1",
 "FreeSWITCH-Switchname": "pbx2",
 "FreeSWITCH-Version": "1.6.9+git~20160613T181044Z~d574870720~64bit", 
 "Idle-CPU": "78.400000", 
 "Max-Sessions": "1000", 
 "Session-Count": "0", 
 "Session-Peak-FiveMin": "0", 
 "Session-Peak-Max": "0", 
 "Session-Per-Sec": "30", 
 "Session-Per-Sec-FiveMin": "0", 
 "Session-Per-Sec-Last": "0", 
 "Session-Per-Sec-Max": "0", 
 "Session-Since-Startup": "0", 
 "Up-Time": "0 years, 3 days, 3 hours, 16 minutes, 39 seconds, 516 milliseconds, 854 microseconds", 
 "Uptime-msec": "270999516"

In FreeSWITCH 1.6 and below, ESL heartbeat statistics are sent every 20s. As a result of the joint efforts, this interval is now configurable – a feature which will be officially available starting with FreeSWITCH 1.8.

OpenSIPS 2.3 includes a new freeswitch_esl module, which acts as a FreeSWITCH ESL driver. It takes care of both managing TCP connections to the ESL (including reconnects), as well as subscribing to HEARTBEAT events and subsequently collecting statistics as they start arriving.

Both the dispatcher and load_balancer modules have been extended in order to optionally depend on and make use of freeswitch_esl whenever ESL URLs of the form fs://[username]:password@host[:port] are provisioned for each destination. The following provisioning examples show how fast it is to get everything up and running:

dispatcher module

Current table (2.2 schema, socket, priority, attrs and description columns omitted)

| id | setid | destination         | state | weight |
|  1 |     1 | sip:   |     0 | 10     |
|  2 |     1 | sip:   |     0 | 5      |
|  3 |     2 | sip: |     0 | 50     |
|  4 |     2 | sip: |     0 | 50     |

New table (2.3 schema, socket, priority, attrs and description columns omitted)

| id | setid | destination         | state | weight                    |
|  1 |     1 | sip:   |     0 | 10                        |
|  2 |     1 | sip:   |     0 | 5                         |
|  3 |     2 | sip: |     0 | fs://:pwd@ |
|  4 |     2 | sip: |     0 | fs://:pwd@ |

Note that the weight column’s type has changed from INT to STRING. Each ESL URL will result in a dynamically calculated weight, based on the destinations’ current load (“CPU” and “sessions”).

More detailed explanations about how the FreeSWITCH statistics are used in order to calculate weights can be found in the dispatcher_documentation.

load_balancer module

Current table (2.2 schema, probe_mode and description columns omitted)

| id | group_id | dst_uri             | resources |
|  1 |        1 | sip:   | ch=10     |
|  2 |        1 | sip:   | ch=10     |
|  3 |        2 | sip: | ch=50     |
|  4 |        2 | sip: | ch=50     |

New table (same schema, probe_mode and description columns omitted)

| id | group_id | dst_uri             | resources               |
|  1 |        1 | sip:   | ch=10                   |
|  2 |        1 | sip:   | ch=10                   |
|  3 |        2 | sip: | ch=fs://:pwd@ |
|  4 |        2 | sip: | ch=fs://:pwd@ |

Notice how the max resource value is not provisioned in the OpenSIPS table anymore for group_id=2, but rather on each FreeSWITCH box. At runtime, this max value of each dynamic resource will be periodically determined based on the current “CPU” and “sessions” statistics fetched from the FreeSWITCH box.

More detailed explanations about how the FreeSWITCH statistics are used in order to calculate the max value of a resource can be found in the load_balancer documentation.

How does it improve my platform?

Each time we route a call to a destination which is not capable of handling it, the following ensue on the platform:

  • more SIP traffic (since we perform additional routing attempts)
  • higher PDD (on average, calls take slightly longer to establish)
  • destinations are not evenly utilized (the call could have been routed correctly!)

The FreeSWITCH-driven routing ensures we take the correct routing decision more often, based on real-time statistics, leading to:

  • less SIP traffic handled by the platform
  • lower PDD
  • even load distribution over the FreeSWITCH boxes

For those of you who are interested to learn more about OpenSIPS, as well as to connect with seasoned VoIP professionals, do not miss out on the OpenSIPS Summit in Amsterdam, 2-5 May 2017!

Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s