Skip to main content

mod_lcr — Least Cost Routing

mod_lcr is an applications module that selects the cheapest (or otherwise ranked) carrier for an outbound call by querying rate data stored in an ODBC-accessible relational database. On each call, it expands the dialed digits into a set of candidate prefix matches, runs a SQL query, ranks the resulting carrier routes by cost (and optionally quality or reliability), and builds a sequenced dialstring that FreeSWITCH can bridge through.

The module is appropriate for carriers or resellers who maintain a rate deck in MySQL or PostgreSQL and want FreeSWITCH to automatically select among multiple upstream providers. It also registers a custom dialplan so that the context name in a SIP profile can be set to an LCR profile name, directing all calls through the LCR engine without explicit dialplan entries. In addition, it registers a lcr:// endpoint that can be dialed directly from a bridge application.

Loading the Module

The module name for load and modules.conf.xml is mod_lcr. In the vanilla default configuration it is not loaded; the entry is commented out:

<!--<load module="mod_lcr"/>-->

To activate it, uncomment that line in conf/vanilla/autoload_configs/modules.conf.xml (or add it to your deployment's equivalent). No special compile flags are required beyond having ODBC support enabled in the FreeSWITCH build (--enable-core-odbc-support).

Configuration: lcr.conf.xml

The configuration file lives at conf/vanilla/autoload_configs/lcr.conf.xml. It has two sections: <settings> for global parameters, and <profiles> for one or more named routing profiles.

Global settings

ParameterPurposeAccepted ValuesDefault
odbc-dsnODBC data source name (DSN), username, and password separated by colonsdsn:user:pass string(none — required)

Profile parameters

Each <profile> element accepts the following <param> children.

ParameterPurposeAccepted ValuesDefault
idNumeric profile ID used to filter rows in the lcr table (lcr_profile column). 0 matches all rows.Integer0
order_bySort order applied to query results. Keywords rate, quality, and reliability map to DB columns; any other value is passed verbatim to ORDER BY.Comma-separated list of rate, quality, reliability, or a column namerate
custom_sqlReplaces the built-in SELECT entirely. Use %q as a placeholder for the expanded digit list, or channel-variable syntax (${var}) for session variables.SQL string(built-in query)
reorder_by_rateAfter the database returns results, re-sort the route list in memory by ascending rate.true / falsefalse
info_in_headersAdd X-LCR-INFO SIP header with carrier name and rate to outbound legs.true / falsefalse
quote_in_listWrap each digit prefix in single quotes inside the SQL IN() list (required for PostgreSQL text columns).true / falsefalse
single_bridgeAllow at most one gateway per carrier in the route list (deduplicate by carrier name as well as by gateway prefix/suffix).true / falsefalse
export_fieldsComma-separated list of extra SQL column names to export as channel variables on the outbound leg.Comma-separated column names(none)
limit_typeBackend used by switch_limit when enforcing per-carrier concurrent-call caps from the lcr table.db / hashdb
enable_sip_redirEmit a SIP 302 redirect with all LCR routes instead of bridging directly.true / falsefalse

Example excerpt

<configuration name="lcr.conf" description="LCR Configuration">
<settings>
<param name="odbc-dsn" value="freeswitch-mysql:freeswitch:Fr33Sw1tch"/>
</settings>
<profiles>
<profile name="default">
<param name="id" value="0"/>
<param name="order_by" value="rate,quality,reliability"/>
</profile>
<profile name="qual_rel">
<param name="id" value="1"/>
<param name="order_by" value="quality,reliability"/>
</profile>
</profiles>
</configuration>

Dialplan Applications

ApplicationPurposeArguments
lcrPerform an LCR lookup and store the ordered routes as channel variables<number> [<profile>]

lcr

Looks up routes for <number> using the named profile (or the default profile when omitted). On success it sets a series of numbered channel variables and a combined dialstring. It does not bridge the call; the dialplan must call bridge separately using ${lcr_auto_route} or the individual lcr_route_N variables.

<action application="lcr" data="15555551212"/>
<action application="bridge" data="${lcr_auto_route}"/>

With an explicit profile:

<action application="lcr" data="15555551212 qual_rel"/>
<action application="bridge" data="${lcr_auto_route}"/>

The intrastate channel variable (set to true) causes the lookup to use the intrastate_rate column instead of rate. The lrn variable, if set, supplies a ported (LRN) number for the digit-prefix expansion.

Endpoint and Custom Dialplan

In addition to the lcr application, the module registers an lcr endpoint and an lcr dialplan parser. Both run the LCR lookup and dial the resulting routes directly, without an explicit bridge action.

lcr:// endpoint

The endpoint dial-string takes the form lcr/<profile>/<destination>, where <profile> is the LCR profile name and <destination> is the number to route. It can be used anywhere a dial-string is accepted, such as bridge or the originate API. If only a single token is given (no /), it is treated as the destination and the default profile is used.

<action application="bridge" data="lcr/default/15555551212"/>
freeswitch@> originate lcr/default/15555551212 &echo

lcr dialplan

The module registers a custom dialplan named lcr. When a SIP profile (or other channel) is configured to use the lcr dialplan, the channel's context is used as the LCR profile name and the dialed destination number is looked up against that profile. Matching routes are added to the call's extension and dialed automatically, so no explicit extensions are needed in XML.

For example, setting context="default" on a profile that uses the lcr dialplan routes all calls through the LCR profile named default.

API Commands

CommandPurposeSyntax
lcrLook up and display LCR routes for a numberlcr <digits> [<lcr profile>] [caller_id] [intrastate] [lrn <number>] [as xml]
lcr_adminDisplay loaded LCR profileslcr_admin show profiles

lcr

Queries the database and prints a formatted table (or XML) of matching routes to the fs_cli console. Useful for debugging rate tables interactively.

freeswitch@> lcr 15555551212
freeswitch@> lcr 15555551212 default 18005551212 intrastate
freeswitch@> lcr 15555551212 default lrn 16175551212
freeswitch@> lcr 15555551212 default as xml

Arguments after the profile may appear in any order:

  • caller_id — a bare numeric token overrides the caller-ID number used for CID-manipulation lookups (defaults to 18005551212).
  • intrastate — switches the rate column to intrastate_rate.
  • lrn <number> — supplies a ported (LRN) number that is used alongside the dialed number in the digit-prefix expansion. The keyword lrn must be followed by the number as a separate token.
  • as xml — formats the route list as XML instead of the default console table.

lcr_admin

Lists every loaded profile and its configuration, including whether intrastate, intralata, or NPA-NXX data is available.

freeswitch@> lcr_admin show profiles

Channel Variables

VariableSet / ReadPurpose
intrastateReadWhen true, the lookup uses intrastate_rate instead of rate. Read by the app, the dialplan engine, and the endpoint.
intralataReadWhen true, uses intralata_rate. Read by the dialplan engine and the endpoint (not the lcr dialplan application).
lrnReadIf set, provides the LRN (ported) number used alongside the dialed number in digit-prefix expansion.
max_rateReadUpper bound on per-minute rate; routes with a higher rate are skipped.
lcr_auto_routeSetPipe-separated dialstring of all matching routes (comma-separated when enable_sip_redir is true), ready to pass directly to bridge.
lcr_route_countSetTotal number of routes found.
lcr_route_NSetDialstring for the Nth route (1-based).
lcr_rate_NSetRate string for the Nth route.
lcr_carrier_NSetCarrier name for the Nth route.
lcr_codec_NSetCodec string for the Nth route (empty if not set in the DB).
lcr_carrierSetCarrier name for the connected (winning) route, exported to the B-leg via import.
lcr_rateSetRate for the connected route, exported to the B-leg.
lcr_user_rateSetUser-facing rate for the connected route, exported to the B-leg.
lcr_rate_fieldSetName of the SQL rate column chosen for this call (rate, intrastate_rate, or intralata_rate).
lcr_user_rate_fieldSetName of the SQL user-rate column chosen for this call.
lcr_query_digitsSetDigit-only copy of the dialed number used in the SQL query.
lcr_query_profileSetNumeric ID of the profile used for the lookup.
lcr_query_expanded_digitsSetComma-expanded prefix list passed to the SQL IN() clause.
lcr_query_expanded_lrn_digitsSetComma-expanded LRN prefix list passed to the SQL IN() clause.
lcr_lrnSetLRN number used in the query (set only when an LRN number is supplied).

Dependencies

mod_lcr requires an ODBC-connected relational database (MySQL or PostgreSQL). The FreeSWITCH core must be built with --enable-core-odbc-support and the appropriate ODBC driver (e.g. unixODBC with the MySQL Connector/ODBC or psqlODBC) must be installed and configured in odbc.ini. The module does not create the database schema itself; SQL scripts for MySQL and PostgreSQL are provided in src/mod/applications/mod_lcr/sql/.

Source: src/mod/applications/mod_lcr, conf/vanilla/autoload_configs/lcr.conf.xml