Browse Source

Revert "* Moved lcr module from modules_k to modules and removed it from modules_s."

This reverts commit a3f16850cd2a897710cb0a56174e9f64dcc77653.
Juha Heinanen 16 years ago
parent
commit
32076aefe5

+ 0 - 0
modules/lcr/Makefile → modules_k/lcr/Makefile


+ 0 - 0
modules/lcr/README → modules_k/lcr/README


+ 0 - 0
modules/lcr/doc/Makefile → modules_k/lcr/doc/Makefile


+ 0 - 0
modules/lcr/doc/lcr.xml → modules_k/lcr/doc/lcr.xml


+ 0 - 0
modules/lcr/doc/lcr_admin.xml → modules_k/lcr/doc/lcr_admin.xml


+ 0 - 0
modules/lcr/hash.c → modules_k/lcr/hash.c


+ 0 - 0
modules/lcr/hash.h → modules_k/lcr/hash.h


+ 0 - 0
modules/lcr/lcr_mod.c → modules_k/lcr/lcr_mod.c


+ 0 - 0
modules/lcr/lcr_mod.h → modules_k/lcr/lcr_mod.h


+ 0 - 0
modules/lcr/mi.c → modules_k/lcr/mi.c


+ 0 - 0
modules/lcr/mi.h → modules_k/lcr/mi.h


+ 17 - 0
modules_s/lcr/Makefile

@@ -0,0 +1,17 @@
+#
+# Least Cost Routing Module
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=lcr.so
+LIBS=
+
+DEFS+=-DSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2
+include ../../Makefile.modules
+

+ 259 - 0
modules_s/lcr/README

@@ -0,0 +1,259 @@
+
+Least Cost Routing Module
+
+Juha Heinanen
+
+Edited by
+
+Juha Heinanen
+
+   Copyright © 2005 Juha Heinanen
+     _________________________________________________________
+
+   Table of Contents
+
+   1. User's Guide
+
+        1.1. Overview
+        1.2. Dependencies
+        1.3. Exported Parameters
+        1.4. Exported Functions
+        1.5. FIFO Commands
+        1.6. Known Limitations
+
+   2. TODO
+     _________________________________________________________
+
+Chapter 1. User's Guide
+
+1.1. Overview
+
+   Least cost routing (LCR) module implements two related capabilities:
+
+   (1) sequential forwarding of a request to one or more gateways
+       (functions load_gws and next_gw),
+
+   (2) sequential forwarding to contacts if they don't share the
+       the same qvalue (functions load_contacts and next_contacts).
+
+   Gateway selection is based on caller's RPID URI (if available in
+   caller's RPID AVP after authentication) or From URI and user part of
+   Request-URI (telephone number).  Gateway patterns matching RPID or
+   From URI and telephone number are ordered for forwarding purpose (1)
+   according to longest user part match, (2) according to priority, and
+   (3) randomly.
+
+   Each gateway belongs to a gateway group either alone or among other
+   gateways.  All gateways in a group share the same priority.
+
+   Gateway and routing information is kept in two tables:  gw and lcr.
+
+   When a gateway is selected, Request-URI is rewritten with information
+   from gw table: URI scheme, prefix, IP address, port, and transport
+   protocol.  Valid URI scheme values are NULL = sip, 1 = sip and 2 =
+   sips.  Prefix is appended in front of Request-URI user part.
+   Currently valid transport protocol values are NULL = none, 1 = udp, 2
+   = tcp, and 3 = tls.
+
+   Table lcr contains prefix of user part of Request-URI, From URI,
+   gateway group id, and priority.  From URI can contain special
+   characters % and _ matching any number of any characters and any one
+   character, respectively.  
+
+   In addition to gw and lcr tables there is third table gw_grp that is
+   used for administrative purposes only to associate names with gateway
+   group ids.
+   _________________________________________________________
+
+1.2. Dependencies
+
+   The module depends on the following modules (in the other
+   words the listed modules must be loaded before this module):
+
+     * tm module
+     * mysql module
+     _________________________________________________________
+
+1.3. Exported Parameters
+
+   LCR module exports the following database related parameters that
+   have usual purpose:
+
+     * db_url (default system default)
+     * gw_table (default "gw")
+     * gw_name_column (default "gw_name")
+     * ip_addr_column (default "ip_addr")
+     * port_column (default "port")
+     * uri_scheme_column (default "uri_scheme")
+     * transport_column (default "transport")
+     * grp_id_column (default "grp_id")
+     * lcr_table (default "lcr")
+     * prefix_column (default "prefix")
+     * from_uri_column (default "from_uri")
+     * priority_column (default "priority")
+
+   In addition there are parameters that can be used to override names
+   of the AVPs used by LCR module:
+
+     * gw_uri_avp	  (default "1400")
+     * contact_avp	  (default "1401")
+     * fr_inv_timer_avp	  (default "fr_inv_timer_avp")
+     * rpid_avp		  (default "rpid")
+
+   If string value of an AVP parameter contains only digits, the name of
+   the AVP is int value of the string.
+
+   Finally, the parameters used by sequential forwarding:
+
+     * fr_inv_timer		(default 90)
+     * fr_inv_timer_next	(default 30)
+     * fr_inv_timer_param	(default "")
+     * fr_inv_timer_next_param	(default "")
+
+   Function next_contacts() sets tm fr_inv_timer to fr_inv_timer_next
+   value if, after next contacts, there are still lower qvalue
+   contacts available, and to fr_inv_timer value if next contacts are
+   the last ones left. fr_inv_timer_param can define an AVP
+   overwriting fr_inv_timer value, and similarly, AVP defined by
+   fr_inv_timer_next_param can overwrite fr_inv_timer_next value.
+    _________________________________________________________
+
+1.4. Exported Functions
+
+1.4.1. load_gws()
+
+   Loads URIs of gateways matching RPID AVP (if available) or From URI
+   and user part of Request-URI to gw_uri_avp AVPs.  Returns 1 or -1
+   depending on success.
+
+   Example:
+
+   if (!load_gws()) {
+      sl_send_reply("500", "Server Internal Error - Cannot load gateways");
+      break;
+   };
+
+1.4.2. next_gw()
+
+   If called from a route block, replaces Request-URI by the first
+   gw_uri_avp AVP value and destroys that AVP.
+
+   If called from a failure route block, appends a new branch to request,
+   whose Request-URI is the first gw_uri_avp AVP value, and destroys that
+   AVP.
+
+   Returns 1 on success and -1 if there were no gateways left or if an
+   error occurred (see syslog).
+
+   Must be preceded by successful load_gws() call.
+
+   Example from route block:
+   
+   if (!next_gw()) {
+      sl_send_reply("503", "Service not available - No gateways");
+      break;
+    };
+
+   Example from failure route block:
+
+   if (!next_gw()) {
+      t_reply("503", "Service not available - No more gateways");
+      break;	     
+    };
+
+1.4.3. from_gw()
+
+   Checks if request came from IP address of a gateway.
+
+   Example:
+   
+   if (from_gw()) {
+      ...
+      break;
+   };
+
+1.4.4. to_gw()
+
+   Checks if in-dialog request goes to a gateway.
+
+   Example:
+   
+   if (to_gw()) {
+      ...
+      break;
+   };
+
+1.4.5. load_contacts()
+
+   Loads contacts in destination set in increasing qvalue order as
+   values of lcr_contact AVP.  If all contacts in the destination set
+   have the same qvalue, load_contacts() does not do anything thus
+   minimizing performance impact of sequential forking capability when
+   it is not needed.  Returns 1 if loading of contacts succeeded or
+   there was nothing to do.  Returns -1 on error (see syslog).
+
+   Example:
+
+   if (!load_contacts()) {
+      sl_send_reply("500", "Server Internal Error - Cannot load contacts");
+      break;
+   };
+
+1.4.6. next_contacts()
+
+   If called from a route block, replaces Request-URI with the first
+   lcr_contact AVP value, adds the remaining lcr_contact AVP values with
+   the same qvalue as branches, and destroys those AVPs.  It does
+   nothing if there are no lcr_contact AVPs.  Returns 1 if there were no
+   errors and -1 if an error occurred (see syslog).
+
+   If called from a failure route block, adds the first lcr_contact AVP
+   value and all following lcr_contact AVP values with the same qvalue
+   as new branches to request and destroys those AVPs.  Returns 1 if new
+   branches were successfully added and -1 on error (see syslog) or if
+   there were no more lcr_contact AVPs.
+
+   Must be preceded by successful load_contacts() call.
+
+   Example from route block:
+   
+   if (!next_contacts()) {
+       sl_send_reply("500", "Server Internal Error");
+        break;
+   } else {
+      t_relay();
+   };
+
+   Example from failure route block:
+   
+   if (next_contacts()) {
+       t_relay();
+   };
+
+
+1.5. FIFO Commands
+
+1.5.1. lcr_reload
+
+   Causes lcr module to re-read the contents of gateway table
+   into memory.
+
+1.5.2. lcr_dump
+
+   Causes lcr module to dump the contents of its in-memory gateway
+   table. 
+
+1.6. Known Limitations
+
+   There is an unlikely race condition on lcr reload. If a process uses
+   in memory gw table, which is reloaded at the same time twice through
+   FIFO, the second reload will delete the original table still in use
+   by the process.
+   _________________________________________________________
+
+2.0. TODO
+
+   Function load_gws() currently makes an SQL query for the matching
+   gateways.  In order to avoid the query, also lcr table should be
+   read into memory and the corresponding query should be rewritten in
+   C.

+ 4 - 0
modules_s/lcr/doc/Makefile

@@ -0,0 +1,4 @@
+docs = lcr.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module

+ 17 - 0
modules_s/lcr/doc/functions.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="flatstore.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Functions</title>
+
+</section>

+ 121 - 0
modules_s/lcr/doc/lcr.xml

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="lcr" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<firstname>Juha</firstname>
+		<surname>Heinanen</surname>
+		<affiliation><orgname>FhG FOKUS</orgname></affiliation>
+		<address>
+		    <email></email>
+		</address>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2003</year>
+	    <holder>FhG FOKUS</holder>
+	</copyright>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>LCR Module</title>
+
+    <section id="lcr.overview">
+	<title>Overview</title>
+	<para>
+         Least cost routing (LCR) module implements two related capabilities:
+       <itemizedlist>
+            <listitem> LCR may sequentially forward requests to one or more gateways using the load_gws 
+            and next_gw functions.
+            </listitem>
+
+            <listitem> LCR may sequentially forward contacts if they don't share the same qvalues.
+            Use the functions load_contacts and next_contacts to access and select a contact.
+            </listitem>
+        </itemizedlist>
+
+       <para>Gateway selection is based on caller's RPID URI (if available in caller's RPID AVP after 
+        authentication) or From URI and user part of Request-URI (telephone number).  Gateway patterns
+        matching RPID or From URI and telephone number are ordered for forwarding purposes as follows:
+        <itemizedlist>
+            <listitem>According to longest user part match
+            </listitem>
+
+            <listitem>According to priority
+            </listitem>
+
+            <listitem>Randomly
+            </listitem>
+        </itemizedlist>
+
+        Each gateway belongs to a gateway group either alone or among other gateways.  All gateways in a 
+        group share the same priority.  Gateway and routing information is kept in two tables:  
+        gw and lcr.
+        </para>
+
+        <para> When a gateway is selected, Request-URI is rewritten with information from gw table: 
+         URI scheme, prefix, IP address, port, and transport protocol.  Valid URI scheme values are:
+         <itemizedlist>
+            <listitem>NULL = sip
+            </listitem>
+            <listitem>1 = sip 
+            </listitem>
+            <listitem>2 = sips.  
+            </listitem>
+        </itemizedlist>
+        </para>
+
+        <para> If a prefix is specified it will be prepended to the Request-URI user part.
+        </para>
+
+        <para>Currently valid transport protocol values are: 
+         <itemizedlist>
+            <listitem>NULL = none
+            </listitem>
+            <listitem>1 = udp 
+            </listitem>
+            <listitem>2 = tcp  
+            </listitem>
+            <listitem>3 = tls  
+            </listitem>
+        </itemizedlist>
+        </para>
+
+        <para> Table lcr contains any prefix to be applied to the user part of Request-URI, the From URI,
+         the gateway group id, and the priority.  The From URI can contain special characters % and _ 
+         matching any number of any characters and any one character, respectively. 
+        </para>
+
+        <para> In addition to gw and lcr tables there is third table gw_grp that is used for 
+         administrative purposes only. In this table you may map names to the gateway group ids.
+        </para>
+
+	</para>
+    </section>
+
+    <section id="lcr.dependencies">
+	<title>Dependencies</title>
+	<para>The module depends on the following modules. These modules must be loaded before the LCR module
+         <itemizedlist>
+            <listitem>tm module
+            </listitem>
+            <listitem>mysql module
+            </listitem>
+        </itemizedlist>
+	</para>
+    </section>
+
+    
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+
+</section>
+

+ 235 - 0
modules_s/lcr/doc/params.xml

@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="lcr.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="db_url">
+	<title><varname>db_url</varname> (string)</title>
+	<para>
+	    The URL for accessing the database where the LCR tables reside
+	</para>
+	<para>
+	    Default value is NULL.
+	</para>
+	<example>
+	    <title>Set <varname>db_url</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "db_url", "mysql://ser:pwd@localhost/ser") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="gw_table">
+	<title><varname>gw_table</varname> (string)</title>
+	<para>
+	    The name of the table containing the list of gateways
+	</para>
+	<para>
+	    Default value is gw.
+	</para>
+	<example>
+	    <title>Set <varname>gw_table</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "gw_table", "mygateways") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="gw_name_column">
+	<title><varname>gw_name_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains the actual name of the gateway
+	</para>
+	<para>
+	    Default value is gw_name.
+	</para>
+	<example>
+	    <title>Set <varname>gw_name_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "gw_name_column", "Agateway") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="ip_addr_column">
+	<title><varname>ip_addr_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains the IP address for a specific gateway
+	</para>
+	<para>
+	    Default value is ip_addr.
+	</para>
+	<example>
+	    <title>Set <varname>ip_addr_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "ip_addr_column", "gatewayIPs") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="port_column">
+	<title><varname>port_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains the port number through which this gateway communicates
+	</para>
+	<para>
+	    Default value is port.
+	</para>
+	<example>
+	    <title>Set <varname>port_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "port_column", "gatewayPort") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="uri_scheme_column ">
+	<title><varname>uri_scheme_column </varname> (string)</title>
+	<para>
+	    The name of the column that contains the scheme to be used when rewriting the R-URI
+	</para>
+	<para>
+	    Default value is uri_scheme.
+	</para>
+	<example>
+	    <title>Set <varname>uri_scheme_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "uri_scheme", "myURIScheme") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="transport_column ">
+	<title><varname>transport_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains the transport to be used when contacting a gateway
+	</para>
+	<para>
+	    Default value is transport.
+	</para>
+	<example>
+	    <title>Set <varname>transport_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "transport", "mySIPXport") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="grp_id_column ">
+	<title><varname>grp_id_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains the gateway group ID number
+	</para>
+	<para>
+	    Default value is grp_id.
+	</para>
+	<example>
+	    <title>Set <varname>grp_id_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "grp_id_column", "GwyGroups") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="lcr_table ">
+	<title><varname>lcr_table</varname> (string)</title>
+	<para>
+	    The name of the column that contains the LCR table
+	</para>
+	<para>
+	    Default value is lcr.
+	</para>
+	<example>
+	    <title>Set <varname>lcr_table</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "lcr_table", "lcr") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="prefix_column ">
+	<title><varname>prefix_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains the prefix to be prepended to the R-URI user part.
+	</para>
+	<para>
+	    Default value is prefix.
+	</para>
+	<example>
+	    <title>Set <varname>prefix_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "prefix_column", "prefix") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="from_uri_column ">
+	<title><varname>from_uri_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains the From URI which must match in order to select this gateway.
+            Note that the values in this column may contain wildcard characters as mentioned earlier in this 
+            document.
+	</para>
+	<para>
+	    Default value is from_uri.
+	</para>
+	<example>
+	    <title>Set <varname>from_uri_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "from_uri_column", "FromURI") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="priority_column ">
+	<title><varname>priority_column</varname> (string)</title>
+	<para>
+	    The name of the column that contains a number indicating the priority of this gateway
+	</para>
+	<para>
+	    Default value is priority.
+	</para>
+	<example>
+	    <title>Set <varname>priority_column</varname> parameter</title>
+	    <programlisting>
+		...
+		modparam("lcr", "priority_column", "priority") 
+		...
+	    </programlisting>
+	</example>
+    </section>
+
+</section>

+ 1113 - 0
modules_s/lcr/lcr_mod.c

@@ -0,0 +1,1113 @@
+/*
+ * Least Cost Routing module (also implements sequential forking)
+ *
+ * Copyright (C) 2005 Juha Heinanen
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * -------
+ *  2005-02-14: Introduced lcr module (jh)
+ *  2005-02-20: Added sequential forking functions (jh)
+ *  2005-02-25: Added support for int AVP names, combined addr and port
+ *              AVPs (jh)
+ *  2005-07-23: Added support for gw URI scheme and transport (jh)
+ *  2005-08-20: Added support for gw prefixes (jh)
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+#include "../../error.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+#include "../../lib/srdb2/db.h"
+#include "../../usr_avp.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/msg_parser.h"
+#include "../../action.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../qvalue.h"
+#include "../../dset.h"
+#include "../../ip_addr.h"
+#include "../../config.h"
+#include "../../route.h"
+#include "lcr_rpc.h"
+#include "lcr_mod.h"
+
+MODULE_VERSION
+
+/* usr_avp flag for sequential forking */
+#define DEF_Q_FLAG	"q_flag"
+avp_flags_t	Q_FLAG = 0;
+
+static void destroy(void);       /* Module destroy function */
+static int child_init(int rank); /* Per-child initialization function */
+static int mod_init(void);       /* Module initialization function */
+
+int reload_gws ( void );
+
+#define LCR_MAX_QUERY_SIZE 512
+#define MAX_PREFIX_LEN 16
+
+/* Default avp names */
+#define DEF_GW_URI_AVP "1400"
+#define DEF_CONTACT_AVP "1401"
+#define DEF_FR_INV_TIMER_AVP "$t.callee_fr_inv_timer"
+#define DEF_FR_INV_TIMER 90
+#define DEF_FR_INV_TIMER_NEXT 30
+#define DEF_RPID_AVP "rpid"
+
+/*
+ * Database variables
+ */
+db_ctx_t* ctx = NULL;
+db_cmd_t *lcr_load = NULL;
+db_cmd_t *lcr_reload = NULL;
+
+/* This is the stack of all used IP addresses, this stack is
+ * used to make sure that no IP address (gateway) gets the same
+ * request more than once.
+ */
+static unsigned int addrs[MAX_BRANCHES];
+unsigned int addrs_top = 0;
+
+/*
+ * Module parameter variables
+ */
+static str db_url    = STR_STATIC_INIT(DEFAULT_RODB_URL);
+
+char* gw_table         = "gw";
+char* gw_name_col      = "gw_name";
+char* ip_addr_col      = "ip_addr";
+char* port_col         = "port";
+char* uri_scheme_col   = "uri_scheme";
+char* transport_col    = "transport";
+char* grp_id_col       = "grp_id";
+char* lcr_table        = "lcr";
+char* prefix_col       = "prefix";
+char* from_uri_col     = "from_uri";
+char* priority_col     = "priority";
+
+str gw_uri_avp       = STR_STATIC_INIT(DEF_GW_URI_AVP);
+str contact_avp      = STR_STATIC_INIT(DEF_CONTACT_AVP);
+str inv_timer_avp    = STR_STATIC_INIT(DEF_FR_INV_TIMER_AVP);
+int inv_timer        = DEF_FR_INV_TIMER;
+int inv_timer_next   = DEF_FR_INV_TIMER_NEXT;
+str inv_timer_ps     = STR_STATIC_INIT("");
+str inv_timer_next_ps = STR_STATIC_INIT("");
+str rpid_avp         = STR_STATIC_INIT(DEF_RPID_AVP);
+
+/*
+ * Other module types and variables
+ */
+
+struct contact {
+    str uri;
+    qvalue_t q;
+    unsigned short q_flag;
+    struct contact *next;
+};
+
+int_str gw_uri_name, contact_name, rpid_name;
+unsigned short gw_uri_avp_name_str;
+unsigned short contact_avp_name_str;
+unsigned short rpid_avp_name_str;
+
+static avp_ident_t tm_timer_param;	/* TM module's invite timer avp */
+
+struct gw_info **gws;	/* Pointer to current gw table pointer */
+struct gw_info *gws_1;	/* Pointer to gw table 1 */
+struct gw_info *gws_2;	/* Pointer to gw table 2 */
+struct tm_binds tmb;
+
+/* AVPs overwriting the module parameters */
+static avp_ident_t *inv_timer_param = NULL;
+static avp_ident_t *inv_timer_next_param = NULL;
+
+/*
+ * Module functions that are defined later
+ */
+int load_gws(struct sip_msg* _m, char* _s1, char* _s2);
+int next_gw(struct sip_msg* _m, char* _s1, char* _s2);
+int from_gw(struct sip_msg* _m, char* _s1, char* _s2);
+int to_gw(struct sip_msg* _m, char* _s1, char* _s2);
+int load_contacts (struct sip_msg*, char*, char*);
+int next_contacts (struct sip_msg*, char*, char*);
+
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+	{"load_gws",      load_gws,      0, 0, REQUEST_ROUTE},
+	{"next_gw",       next_gw,       0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
+	{"from_gw",       from_gw,       0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
+	{"to_gw",         to_gw,         0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
+	{"load_contacts", load_contacts, 0, 0, REQUEST_ROUTE},
+	{"next_contacts", next_contacts, 0, 0, REQUEST_ROUTE | FAILURE_ROUTE},
+	{0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"db_url",                   PARAM_STR,  &db_url              },
+	{"gw_table",                 PARAM_STRING, &gw_table          },
+	{"gw_name_column",           PARAM_STRING, &gw_name_col       },
+	{"ip_addr_column",           PARAM_STRING, &ip_addr_col       },
+	{"port_column",              PARAM_STRING, &port_col          },
+	{"uri_scheme_column",        PARAM_STRING, &uri_scheme_col    },
+	{"transport_column",         PARAM_STRING, &transport_col     },
+	{"grp_id_column",            PARAM_STRING, &grp_id_col        },
+	{"lcr_table",                PARAM_STRING, &lcr_table         },
+	{"prefix_column",            PARAM_STRING, &prefix_col        },
+	{"from_uri_column",          PARAM_STRING, &from_uri_col      },
+	{"priority_column",          PARAM_STRING, &priority_col      },
+	{"gw_uri_avp",               PARAM_STR,    &gw_uri_avp        },
+	{"contact_avp",              PARAM_STR,    &contact_avp       },
+	{"fr_inv_timer_avp",         PARAM_STR,    &inv_timer_avp     },
+	{"fr_inv_timer",             PARAM_INT,    &inv_timer         },
+	{"fr_inv_timer_next",        PARAM_INT,    &inv_timer_next    },
+	{"fr_inv_timer_param",       PARAM_STR,    &inv_timer_ps      },
+	{"fr_inv_timer_next_param",  PARAM_STR,    &inv_timer_next_ps },
+	{"rpid_avp",                 PARAM_STR,    &rpid_avp          },
+	{0, 0, 0}
+};
+
+
+/*
+ * Module interface
+ */
+struct module_exports exports = {
+	"lcr",
+	cmds,      /* Exported functions */
+	lcr_rpc,   /* RPC methods */
+	params,    /* Exported parameters */
+	mod_init,  /* module initialization function */
+	0,         /* response function */
+	destroy,   /* destroy function */
+	0,         /* oncancel function */
+	child_init /* child initialization function */
+};
+
+
+void lcr_db_close()
+{
+	if (lcr_load) db_cmd_free(lcr_load);
+	lcr_load = NULL;
+
+	if (lcr_reload) db_cmd_free(lcr_reload);
+	lcr_reload = NULL;
+
+	if (ctx) {
+		db_disconnect(ctx);
+		db_ctx_free(ctx);
+		ctx = NULL;
+	}
+}
+
+
+int lcr_db_init(char* db_url)
+{
+	int q_len;
+    static char query[LCR_MAX_QUERY_SIZE];
+	db_fld_t reload_cols[] = {
+		{.name = ip_addr_col,    .type = DB_INT},
+		{.name = port_col,       .type = DB_INT},
+		{.name = uri_scheme_col, .type = DB_INT},
+		{.name = transport_col,  .type = DB_INT},
+		{.name = prefix_col,     .type = DB_STR},
+		{.name = 0}
+	};
+
+	db_fld_t load_cols[] = {
+		{.name = "gw.ip_addr",    .type = DB_INT},
+		{.name = "gw.port",       .type = DB_INT},
+		{.name = "gw.uri_scheme", .type = DB_INT},
+		{.name = "gw.transport",  .type = DB_INT},
+		{.name = "gw.prefix",     .type = DB_STR},
+		{.name = 0}
+	};
+
+	db_fld_t load_match[] = {
+		{.name = "lcr.from_uri",      .type = DB_STR},
+		{.name = "lcr.ruri_username", .type = DB_STR},
+		{.name = 0}
+	};
+	  
+	ctx = db_ctx("lcr");
+	if (!ctx) goto err;
+	if (db_add_db(ctx, db_url) < 0) goto err;
+	if (db_connect(ctx) < 0) goto err;
+
+    q_len = snprintf(query, LCR_MAX_QUERY_SIZE, 
+					 "SELECT %s.%s, %s.%s, %s.%s, %s.%s, %s.%s from %s, %s "
+					 "WHERE ? LIKE %s.%s AND ? LIKE CONCAT(%s.%s, '%%') "
+					 "AND %s.%s = %s.%s ORDER BY CHAR_LENGTH(%s.%s) DESC, "
+					 "%s.%s, RAND()",
+					 gw_table, ip_addr_col, gw_table, port_col, 
+					 gw_table, uri_scheme_col, gw_table, transport_col, 
+					 gw_table, prefix_col, gw_table, lcr_table,
+					 lcr_table, from_uri_col, lcr_table, prefix_col,
+					 lcr_table, grp_id_col, gw_table, grp_id_col,
+					 lcr_table, prefix_col, lcr_table, priority_col);
+    if (q_len < 0 || q_len >= LCR_MAX_QUERY_SIZE) {
+		ERR("lcr: Database query too long\n");
+		return -1;
+    }
+	
+	lcr_load = db_cmd(DB_SQL, ctx, query, load_cols, load_match, NULL);
+	if (!lcr_load) goto err;
+
+	lcr_reload = db_cmd(DB_GET, ctx, gw_table, reload_cols, NULL, NULL);
+	if (!lcr_reload) goto err;
+    return 0;
+
+err:
+	lcr_db_close();
+	ERR("lcr: Error while initializing database layer\n");
+	return -1;
+}
+
+
+/*
+ * Module initialization function callee in each child separately
+ */
+static int child_init(int rank)
+{
+	if (rank==PROC_INIT || rank==PROC_MAIN || rank==PROC_TCP_MAIN)
+		return 0; /* do nothing for the main or tcp_main processes */
+	if (lcr_db_init(db_url.s) < 0) {
+		ERR("lcr: Unable to initialize database layer\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* get AVP module parameter */
+static int get_avp_modparam(str *s, avp_ident_t *avp)
+{
+	if (!s->s || (s->len < 2)) return -1;
+
+	if (s->s[0] != '$') {
+		ERR("lcr: get_avp_modparam(): "
+			"unknown AVP identifier: %.*s\n", s->len, s->s);
+		return -1;
+	}
+	s->s++;
+	s->len--;
+	if (parse_avp_ident(s, avp)) {
+		ERR("lcr: get_avp_modparam(): "
+			"cannot parse AVP identifier: %.*s\n", s->len, s->s);
+		return -1;
+	}
+	return 0;
+}
+
+/*
+ * Module initialization function that is called before the main process forks
+ */
+static int mod_init(void)
+{
+	load_tm_f  load_tm;
+	int i;
+	unsigned int par;
+
+	DBG("lcr - initializing\n");
+
+	Q_FLAG = register_avpflag(DEF_Q_FLAG);
+	if (Q_FLAG == 0) {
+		ERR("lcr: Cannot regirser AVP flag: %s\n", DEF_Q_FLAG);
+		return -1;
+	}
+
+	/* import the TM auto-loading function */
+	if (!(load_tm = (load_tm_f)find_export("load_tm", NO_SCRIPT, 0))) {
+	    ERR("lcr: cannot import load_tm\n");
+		goto err;
+	}
+	/* let the auto-loading function load all TM stuff */
+	if (load_tm(&tmb) == -1) goto err;
+
+	/* Initializing gw tables and gw table pointer variable */
+	gws_1 = (struct gw_info *)shm_malloc(sizeof(struct gw_info) * (MAX_NO_OF_GWS + 1));
+	if (gws_1 == 0) {
+	    ERR("lcr: mod_init(): No memory for gw table\n");
+	    goto err;
+	}
+	gws_2 = (struct gw_info *)shm_malloc(sizeof(struct gw_info) * (MAX_NO_OF_GWS + 1));
+	if (gws_2 == 0) {
+	    ERR("lcr: mod_init(): No memory for gw table\n");
+	    goto err;
+	}
+	for (i = 0; i < MAX_NO_OF_GWS + 1; i++) {
+		gws_1[i].ip_addr = gws_2[i].ip_addr = 0;
+	}
+	gws = (struct gw_info **)shm_malloc(sizeof(struct gw_info *));
+	*gws = gws_1;
+
+	if (lcr_db_init(db_url.s) < 0) {
+		ERR("lcr: Unable to initialize database layer\n");
+		return -1;
+	}
+
+	/* First reload */
+	if (reload_gws() == -1) {
+		LOG(L_CRIT, "lcr: failed to reload gateways\n");
+		goto err;
+	}
+
+	lcr_db_close();
+
+	/* Assign parameter names */
+	if (str2int(&gw_uri_avp, &par) == 0) {
+	    gw_uri_name.n = par;
+	    gw_uri_avp_name_str = 0;
+	} else {
+	    gw_uri_name.s = gw_uri_avp;
+	    gw_uri_avp_name_str = AVP_NAME_STR;
+	}
+	if (str2int(&contact_avp, &par) == 0) {
+	    contact_name.n = par;
+	    contact_avp_name_str = 0;
+	} else {
+	    contact_name.s = contact_avp;
+	    contact_avp_name_str = AVP_NAME_STR;
+	}
+	if (str2int(&rpid_avp, &par) == 0) {
+	    rpid_name.n = par;
+	    rpid_avp_name_str = 0;
+	} else {
+	    rpid_name.s = rpid_avp;
+	    rpid_avp_name_str = AVP_NAME_STR;
+	}
+
+	if (get_avp_modparam(&inv_timer_avp, &tm_timer_param))
+		goto err;
+
+	if (inv_timer_ps.len) {
+		inv_timer_param = (avp_ident_t*)pkg_malloc(sizeof(avp_ident_t));
+		if (!inv_timer_param) {
+			ERR("lcr: Not enough memory\n");
+			return -1;
+		}
+		if (get_avp_modparam(&inv_timer_ps, inv_timer_param)) 
+			goto err;
+	}
+	
+	if (inv_timer_next_ps.len) {
+		inv_timer_next_param = (avp_ident_t*)pkg_malloc(sizeof(avp_ident_t));
+		if (!inv_timer_next_param) {
+			ERR("lcr: Not enough memory\n");
+			return -1;
+		}
+		if (get_avp_modparam(&inv_timer_next_ps, inv_timer_next_param)) 
+			goto err;
+	}
+	
+	return 0;
+	
+ err:
+	return -1;
+}
+
+
+static void destroy(void)
+{
+	lcr_db_close();
+	
+	if (inv_timer_param) pkg_free(inv_timer_param);
+	if (inv_timer_next_param) pkg_free(inv_timer_next_param);
+}
+
+
+/*
+ * Reload gws to unused gw table and when done, make the unused gw table
+ * the one in use.
+ */
+int reload_gws ( void )
+{
+    int i;
+    unsigned int ip_addr, port, prefix_len;
+    uri_type scheme;
+    uri_transport transport;
+    char* prefix;
+    db_res_t* res;
+    db_rec_t* rec;
+	
+	res = NULL;
+	if (db_exec(&res, lcr_reload) < 0) {
+		ERR("lcr: Failed to query gw data\n");
+		goto error;
+	}
+	if (res == NULL) {
+		ERR("lcr: Gw table query returned no data\n");
+		goto error;
+	}
+
+	for(rec = db_first(res), i = 0; rec; rec = db_next(res), i++) {
+		if (i >= MAX_NO_OF_GWS) {
+			ERR("lcr: Too many gw entries\n");
+			goto error;
+		}
+
+		if (rec->fld[0].flags & DB_NULL) {
+			ERR("lcr: IP address of GW is NULL\n");
+			goto error;
+		}
+		ip_addr = (unsigned int)rec->fld[0].v.int4;
+
+		if (rec->fld[1].flags & DB_NULL) port = 0;
+		else port = (unsigned int)rec->fld[1].v.int4;
+
+		if (port > 65535) {
+			ERR("lcr: Port of GW is too large: %u\n", port);
+			goto error;
+		}
+
+		if (rec->fld[2].flags & DB_NULL) scheme = SIP_URI_T;
+		else {
+			scheme = (uri_type)rec->fld[2].v.int4;
+			if ((scheme != SIP_URI_T) && (scheme != SIPS_URI_T)) {
+				ERR("lcr: Unknown or unsupported URI scheme: %u\n", (unsigned int)scheme);
+				goto error;
+			}
+		}
+
+		if (rec->fld[3].flags & DB_NULL) transport = PROTO_NONE;
+		else {
+			transport = (uri_transport)rec->fld[3].v.int4;
+			if ((transport != PROTO_UDP) && (transport != PROTO_TCP) &&
+				(transport != PROTO_TLS) && (transport != PROTO_SCTP)) {
+				ERR("lcr: Unknown or unsupported transport: %u\n", (unsigned int)transport);
+				goto error;
+			}
+		}
+
+		if (rec->fld[4].flags & DB_NULL) {
+			prefix_len = 0;
+			prefix = NULL;
+		} else {
+			prefix = rec->fld[4].v.lstr.s;
+			prefix_len = rec->fld[4].v.lstr.len;
+			if (prefix_len > MAX_PREFIX_LEN) {
+				ERR("lcr: Too long prefix\n");
+				goto error;
+			}
+		}
+
+		if (*gws == gws_1) {
+			gws_2[i].ip_addr = ip_addr;
+			gws_2[i].port = port;
+			gws_2[i].scheme = scheme;
+			gws_2[i].transport = transport;
+			gws_2[i].prefix_len = prefix_len;
+			if (prefix_len)
+				memcpy(&(gws_2[i].prefix[0]), prefix, prefix_len);
+		} else {
+			gws_1[i].ip_addr = ip_addr;
+			gws_1[i].port = port;
+			gws_1[i].scheme = scheme;
+			gws_1[i].transport = transport;
+			gws_1[i].prefix_len = prefix_len;
+			if (prefix_len)
+				memcpy(&(gws_1[i].prefix[0]), prefix, prefix_len);
+		}
+	}
+
+	db_res_free(res);
+
+    if (*gws == gws_1) {
+	    gws_2[i].ip_addr = 0;
+	    *gws = gws_2;
+    } else {
+	    gws_1[i].ip_addr = 0;
+	    *gws = gws_1;
+    }
+    return 1;
+
+	error:
+	if (res) db_res_free(res);
+	return -1;
+}
+
+/*
+ * Load GW info from database to lcr_gw_addr_port AVPs
+ */
+int load_gws(struct sip_msg* _m, char* _s1, char* _s2)
+{
+    db_res_t* res = NULL;
+    db_rec_t *rec;
+    str ruri_user, from_uri, value, addr_str, port_str;
+    static char ruri[MAX_URI_SIZE];
+    unsigned int i, j, prefix_len, addr, port;
+    uri_type scheme;
+    uri_transport transport;
+    struct ip_addr address;
+    char *at, *prefix;
+    int_str val;
+
+    /* Find Request-URI user */
+    if (parse_sip_msg_uri(_m) < 0) {
+	    ERR("lcr: Error while parsing R-URI\n");
+	    return -1;
+    }
+    ruri_user = _m->parsed_uri.user;
+	
+	/* Look for Caller RPID or From URI */
+    if (search_first_avp(rpid_avp_name_str, rpid_name, &val, 0) &&
+		val.s.s && val.s.len) {
+		/* Get URI user from RPID */
+		from_uri.len = val.s.len;
+		from_uri.s = val.s.s;
+    } else {
+		/* Get URI from From URI */
+		if ((!_m->from) && (parse_headers(_m, HDR_FROM_F, 0) == -1)) {
+			ERR("lcr: Error while parsing message\n");
+			return -1;
+		}
+		if (!_m->from) {
+			ERR("lcr: FROM header field not found\n");
+			return -1;
+		}
+		if ((!(_m->from)->parsed) && (parse_from_header(_m) < 0)) {
+			ERR("lcr: Error while parsing From body\n");
+			return -1;
+		}
+		from_uri = get_from(_m)->uri;
+    }
+	
+	lcr_load->match[0].v.lstr = from_uri;
+	lcr_load->match[1].v.lstr = ruri_user;
+
+    if (db_exec(&res, lcr_load) < 0) {
+		ERR("lcr: Failed to query accept data\n");
+		return -1;
+    }
+	if (res == NULL) {
+		ERR("lcr: Database query did not return any result\n");
+		return -1;
+	}
+
+	addrs_top = 0;
+	for(i = 0, rec = db_first(res); rec; rec = db_next(res), i++) {
+		if (rec->fld[0].flags & DB_NULL) {
+			ERR("lcr: Gateway IP address is NULL\n");
+			continue;
+		}
+      	addr = (unsigned int)rec->fld[0].v.int4;
+
+		if (addrs_top >= MAX_BRANCHES) {
+			INFO("lcr: Too many destinations\n");
+			goto end;
+		}
+		for(j = 0; j < addrs_top; j++) {
+			if (addrs[j] == addr) goto skip;
+		}
+		addrs[addrs_top++] = addr;
+
+		if (rec->fld[1].flags & DB_NULL) port = 0;
+		else port = (unsigned int)rec->fld[1].v.int4;
+
+		if (rec->fld[2].flags & DB_NULL) scheme = SIP_URI_T;
+		else scheme = (uri_type)rec->fld[2].v.int4;
+
+		if (rec->fld[3].flags & DB_NULL) transport = PROTO_NONE;
+		else transport = (uri_transport)rec->fld[3].v.int4;
+
+		if (rec->fld[4].flags & DB_NULL) {
+			prefix = NULL;
+			prefix_len = 0;
+		} else {
+			prefix = rec->fld[4].v.lstr.s;
+			prefix_len = rec->fld[4].v.lstr.len;
+		}
+
+		if (5 + prefix_len + ruri_user.len + 1 + 15 + 1 + 5 + 1 + 14 > MAX_URI_SIZE) {
+			ERR("lcr: Request URI would be too long\n");
+			continue;
+		}
+
+		at = (char *)&(ruri[0]);
+		if (scheme == SIP_URI_T) {
+			memcpy(at, "sip:", 4); at = at + 4;
+		} else if (scheme == SIPS_URI_T) {
+			memcpy(at, "sips:", 5); at = at + 5;
+		} else {
+			ERR("lcr: Unknown or unsupported URI scheme: %u\n", (unsigned int)scheme);
+			continue;
+		}
+
+		if (prefix_len) {
+			memcpy(at, prefix, prefix_len); at = at + prefix_len;
+		}
+		memcpy(at, ruri_user.s, ruri_user.len); at = at + ruri_user.len;
+		*at = '@'; at = at + 1;
+		address.af = AF_INET;
+		address.len = 4;
+		address.u.addr32[0] = addr;
+		addr_str.s = ip_addr2a(&address);
+		addr_str.len = strlen(addr_str.s);
+		memcpy(at, addr_str.s, addr_str.len); at = at + addr_str.len;
+		if (port != 0) {
+			if (port > 65535) {
+				ERR("lcr: Port of GW is too large: %u\n", port);
+				continue;
+			}
+			*at = ':'; at = at + 1;
+			port_str.s = int2str(port, &port_str.len);
+			memcpy(at, port_str.s, port_str.len); at = at + port_str.len;
+		}
+		if (transport != PROTO_NONE) {
+			memcpy(at, ";transport=", 11); at = at + 11;
+			if (transport == PROTO_UDP) {
+				memcpy(at, "udp", 3); at = at + 3;
+			} else if (transport == PROTO_TCP) {
+				memcpy(at, "tcp", 3); at = at + 3;
+			} else if (transport == PROTO_TLS) {
+				memcpy(at, "tls", 3); at = at + 3;
+			} else if (transport == PROTO_SCTP) {
+				memcpy(at, "sctp", 4); at = at + 4;
+			} else {
+				ERR("lcr: Unknown or unsupported transport: %u\n", (unsigned int)transport);
+				continue;
+			}
+		}
+		value.s = (char *)&(ruri[0]);
+		value.len = at - value.s;
+		val.s = value;
+		add_avp(gw_uri_avp_name_str | AVP_VAL_STR, gw_uri_name, val);
+		DBG("lcr: Added gw_uri_avp <%.*s>\n", STR_FMT(&value));
+
+	skip:
+		continue;
+    }
+		
+	end:
+	if (res) db_res_free(res);
+    return 1;
+}
+
+
+/*
+ * If called from route block, rewrites host:port part of R-URI with the
+ * first lcr_gw_addr:lcr_gw_port AVP values, which are then destroyed.
+ * If called from failure route block, appends a new branch to request,
+ * where host:port part of its R-URI is replaced by the first
+ * lcr_gw_addr:lcr_gw_port AVP value, which is then destroyed.
+ * Returns 1 upon success and -1 upon failure.
+ */
+int next_gw(struct sip_msg* _m, char* _s1, char* _s2)
+{
+    int_str val;
+    struct action act;
+    int rval;
+    struct usr_avp *avp;
+	struct run_act_ctx ra_ctx;
+	
+    avp = search_first_avp(gw_uri_avp_name_str, gw_uri_name, &val, 0);
+    if (!avp) return -1;
+	
+    memset(&act, 0, sizeof(act));
+	init_run_actions_ctx(&ra_ctx);
+    if (is_route_type(REQUEST_ROUTE)) {
+		
+		act.type = SET_URI_T;
+		act.val[0].type = STRING_ST;
+		act.val[0].u.string = val.s.s;
+		rval = do_action(&ra_ctx, &act, _m);
+		destroy_avp(avp);
+		if (rval != 1) {
+			ERR("lcr: do_action failed with return value <%d>\n", rval);
+			return -1;
+		}
+		
+		return 1;
+		
+    } else { /* MODE_ONFAILURE */
+		
+		act.type = APPEND_BRANCH_T;
+		act.val[0].type = STRING_ST;
+		act.val[0].u.string = val.s.s;
+		act.val[1].type = NUMBER_ST;
+		act.val[1].u.number = 0;
+		rval = do_action(&ra_ctx, &act, _m);
+		destroy_avp(avp);
+		if (rval != 1) {
+			ERR("lcr: ERROR: do_action failed with return value <%d>\n", rval);
+			return -1;
+		}
+		
+		return 1;
+    }
+}
+
+
+/*
+ * Checks if request comes from a gateway
+ */
+int from_gw(struct sip_msg* _m, char* _s1, char* _s2)
+{
+    int i;
+    unsigned int src_addr;
+
+    src_addr = _m->rcv.src_ip.u.addr32[0];
+
+    for (i = 0; i < MAX_NO_OF_GWS; i++) {
+	    if ((*gws)[i].ip_addr == 0) {
+		    return -1;
+	    }
+	    if ((*gws)[i].ip_addr == src_addr) {
+		    return 1;
+	    }
+    }
+	
+    return -1;
+}
+
+
+/*
+ * Checks if in-dialog request goes to gateway
+ */
+int to_gw(struct sip_msg* _m, char* _s1, char* _s2)
+{
+    char host[16];
+    struct in_addr addr;
+    unsigned int i;
+	
+    if((_m->parsed_uri_ok == 0) && (parse_sip_msg_uri(_m) < 0)) {
+		ERR("lcr: Error while parsing the R-URI\n");
+		return -1;
+	}
+	
+    if (_m->parsed_uri.host.len > 15) {
+		return -1;
+    }
+    memcpy(host, _m->parsed_uri.host.s, _m->parsed_uri.host.len);
+    host[_m->parsed_uri.host.len] = 0;
+	
+    if (!inet_aton(host, &addr)) {
+		return -1;
+    }
+	
+    for (i = 0; i < MAX_NO_OF_GWS; i++) {
+		if ((*gws)[i].ip_addr == 0) {
+			return -1;
+		}
+		if ((*gws)[i].ip_addr == addr.s_addr) {
+			return 1;
+		}
+    }
+	
+    return -1;
+}
+
+
+/*
+ * Frees contact list used by load_contacts function
+ */
+static inline void free_contact_list(struct contact *curr) 
+{
+    struct contact *prev;
+    while (curr) {
+		prev = curr;
+		curr = curr->next;
+		pkg_free(prev);
+    }
+}
+
+
+/*
+ * Loads contacts in destination set into "lcr_contact" AVP in reverse
+ * priority order and associated each contact with Q_FLAG telling if
+ * contact is the last one in its priority class.  Finally, removes
+ * all branches from destination set.
+ */
+int load_contacts(struct sip_msg* msg, char* key, char* value)
+{
+	str branch, *ruri;
+	qvalue_t q, ruri_q;
+	struct contact *contacts, *next, *prev, *curr;
+	int_str val;
+
+	/* Check if anything needs to be done */
+	if (nr_branches == 0) {
+	    DBG("lcr: Nothing to do - no branches!\n");
+	    return 1;
+	}
+	
+	ruri = GET_RURI(msg);
+	if (!ruri) {
+	    ERR("lcr: No Request-URI found\n");
+	    return -1;
+	}
+	ruri_q = get_ruri_q();
+	
+	init_branch_iterator();
+	while((branch.s = next_branch(&branch.len, &q, 0, 0, 0))) {
+	    if (q != ruri_q) {
+			goto rest;
+	    }
+	}
+	DBG("lcr: Nothing to do - all same q!\n");
+	return 1;
+	
+ rest:
+	/* Insert Request-URI to contact list */
+	contacts = (struct contact *)pkg_malloc(sizeof(struct contact));
+	if (!contacts) {
+	    ERR("lcr: No memory for Request-URI\n");
+	    return -1;
+	}
+	contacts->uri.s = ruri->s;
+	contacts->uri.len = ruri->len;
+	contacts->q = ruri_q;
+	contacts->next = (struct contact *)0;
+	
+	/* Insert branch URIs to contact list in increasing q order */
+	init_branch_iterator();
+	while((branch.s = next_branch(&branch.len, &q, 0, 0, 0))) {
+	    next = (struct contact *)pkg_malloc(sizeof(struct contact));
+	    if (!next) {
+			ERR("lcr: No memory for branch URI\n");
+			free_contact_list(contacts);
+			return -1;
+	    }
+	    next->uri = branch;
+	    next->q = q;
+	    prev = (struct contact *)0;
+	    curr = contacts;
+	    while (curr && (curr->q < q)) {
+			prev = curr;
+			curr = curr->next;
+	    }
+	    if (!curr) {
+			next->next = (struct contact *)0;
+			prev->next = next;
+	    } else {
+			next->next = curr;
+			if (prev) {
+				prev->next = next;
+			} else {
+				contacts = next;
+			}
+	    }
+	}
+	
+	/* Assign values for q_flags */
+	curr = contacts;
+	curr->q_flag = 0;
+	while (curr->next) {
+	    if (curr->q < curr->next->q) {
+			curr->next->q_flag = Q_FLAG;
+	    } else {
+			curr->next->q_flag = 0;
+	    }
+	    curr = curr->next;
+	}
+	
+	/* Add contacts to "contacts" AVP */
+	curr = contacts;
+	while (curr) {
+	    val.s = curr->uri;
+	    add_avp(contact_avp_name_str|AVP_VAL_STR|(curr->q_flag),
+				contact_name, val);
+	    DBG("lcr: DEBUG: Loaded <%s>, q_flag <%d>\n",
+			val.s.s, curr->q_flag);
+	    curr = curr->next;
+	}
+	
+	/* Clear all branches */
+	clear_branches();
+
+	/* Free contacts list */
+	free_contact_list(contacts);
+	
+	return 1;
+}
+
+/*
+ * Returns the value of the given AVP.
+ * The default value is returned in case of missing AVP
+ */
+static int get_timer_value(avp_ident_t *avp, int def_value)
+{
+	struct usr_avp	*ret_avp;
+	avp_value_t	avp_val;
+	unsigned int	i;
+	
+	/* avp is not defined, use the default value */
+	if (!avp) return def_value;
+	
+	ret_avp = search_avp_by_index(avp->flags,
+								  avp->name,
+								  &avp_val,
+								  avp->index);
+	/* avp is missing, use the default value */
+	if (!ret_avp) return def_value;
+
+	if (ret_avp->flags & AVP_VAL_STR) {
+		if (str2int(&avp_val.s, &i)) {
+			WARN("lcr: cannot convert AVP string value to int: %.*s\n", 
+				 STR_FMT(&avp_val.s));
+			return def_value;
+		}
+		return (int)i;
+	} else {
+		return avp_val.n;
+	}
+}
+
+/*
+ * Adds to request a destination set that includes all highest priority
+ * class contacts in "lcr_contact" AVP.   If called from a route block,
+ * rewrites the request uri with first contact and adds the remaining
+ * contacts as branches.  If called from failure route block, adds all
+ * contacts as brances.  Removes added contacts from "lcr_contact" AVP.
+ */
+int next_contacts(struct sip_msg* msg, char* key, char* value)
+{
+    struct search_state st;
+    struct usr_avp *avp, *prev;
+    int_str val;
+    struct action act;
+    int rval;
+	struct run_act_ctx ra_ctx;
+
+    if (is_route_type(REQUEST_ROUTE)) {
+		
+		/* Find first lcr_contact_avp value */
+		avp = search_first_avp(contact_avp_name_str, contact_name, &val, &st);
+		if (!avp) {
+			DBG("lcr: No AVPs -- we are done!\n");
+			return 1;
+		}
+		
+		/* Set Request-URI */
+		memset(&act, 0, sizeof(act));
+		act.type = SET_URI_T;
+		act.val[0].type = STRING_ST;
+		act.val[0].u.string = val.s.s;
+		init_run_actions_ctx(&ra_ctx);
+		rval = do_action(&ra_ctx, &act, msg);
+		if (rval != 1) {
+			destroy_avp(avp);
+			return rval;
+		}
+		DBG("lcr: R-URI is <%s>\n", val.s.s);
+		if (avp->flags & Q_FLAG) {
+			destroy_avp(avp);
+			/* Set fr_inv_timer */
+			val.n = get_timer_value(inv_timer_next_param, inv_timer_next);
+			if (add_avp(tm_timer_param.flags, tm_timer_param.name, val) != 0) {
+				ERR("lcr: setting of fr_inv_timer_avp failed\n");
+				return -1;
+			}
+			return 1;
+		}
+		
+		/* Append branches until out of branches or Q_FLAG is set */
+		prev = avp;
+		while ((avp = search_next_avp(&st, &val))) {
+			destroy_avp(prev);
+			memset(&act, 0, sizeof(act));
+			act.type = APPEND_BRANCH_T;
+			act.val[0].type = STRING_ST;
+			act.val[0].u.string = val.s.s;
+			act.val[1].type = NUMBER_ST;
+			act.val[1].u.number = 0;
+			init_run_actions_ctx(&ra_ctx);
+			rval = do_action(&ra_ctx, &act, msg);
+			if (rval != 1) {
+				destroy_avp(avp);
+				ERR("lcr: do_action failed with return value <%d>\n", rval);
+				return -1;
+			}
+			DBG("lcr: Branch is <%s>\n", val.s.s);
+			if (avp->flags & Q_FLAG) {
+				destroy_avp(avp);
+				val.n = get_timer_value(inv_timer_next_param, inv_timer_next);
+				if (add_avp(tm_timer_param.flags, tm_timer_param.name, val) != 0) {
+					ERR("lcr: setting of fr_inv_timer_avp failed\n");
+					return -1;
+				}
+				return 1;
+			}
+			prev = avp;
+		}
+		
+    } else { /* MODE_ONFAILURE */
+		
+		avp = search_first_avp(contact_avp_name_str, contact_name, &val, &st);
+		if (!avp) return -1;
+		
+		prev = avp;
+		do {
+			memset(&act, 0, sizeof(act));
+			act.type = APPEND_BRANCH_T;
+			act.val[0].type = STRING_ST;
+			act.val[0].u.string = val.s.s;
+			act.val[1].type = NUMBER_ST;
+			act.val[1].u.number = 0;
+			init_run_actions_ctx(&ra_ctx);
+			rval = do_action(&ra_ctx, &act, msg);
+			if (rval != 1) {
+				destroy_avp(avp);
+				return rval;
+			}
+			DBG("lcr: New branch is <%s>\n", val.s.s);
+			if (avp->flags & Q_FLAG) {
+				destroy_avp(avp);
+				return 1;
+			}
+			prev = avp;
+			avp = search_next_avp(&st, &val);
+			destroy_avp(prev);
+		} while (avp);
+		
+		/* Restore fr_inv_timer */
+		/* delete previous value */
+		if ((avp = search_first_avp(tm_timer_param.flags, tm_timer_param.name, 0, 0))) {
+			destroy_avp(avp);
+		}
+		
+		/* add new value */
+		val.n = get_timer_value(inv_timer_param, inv_timer);
+		DBG("lcr: val.n=%d!\n", val.n);
+		
+		if (add_avp(tm_timer_param.flags, tm_timer_param.name, val) != 0) {
+			ERR("lcr: Setting of fr_inv_timer_avp failed\n");
+			return -1;
+		}
+	}
+	
+	return 1;
+}

+ 61 - 0
modules_s/lcr/lcr_mod.h

@@ -0,0 +1,61 @@
+/*
+ * Various lcr related functions
+ *
+ * Copyright (C) 2005 Juha Heinanen
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * --------
+ * 2005-02-06: created by jh
+ */
+
+
+#ifndef LCR_MOD_H
+#define LCR_MOD_H
+
+#include <stdio.h>
+#include "../../parser/msg_parser.h"
+
+#define MAX_NO_OF_GWS 32
+
+/*
+ * Type definitions
+ */
+
+typedef enum sip_protos uri_transport;
+
+struct gw_info {
+    unsigned int ip_addr;
+    unsigned int port;
+    uri_type scheme;
+    uri_transport transport;
+    unsigned int prefix_len;
+    char prefix[16];
+};
+
+extern struct gw_info **gws;	/* Pointer to current gw table pointer */
+
+void print_gws (FILE *reply_file);
+int reload_gws (void);
+
+#endif /* LCR_MOD_H */

+ 104 - 0
modules_s/lcr/lcr_rpc.c

@@ -0,0 +1,104 @@
+/*
+ * Various lcr related functions
+ *
+ * Copyright (C) 2005 Juha Heinanen
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "lcr_mod.h"
+#include "lcr_rpc.h"
+
+static const char* reload_doc[2] = {
+	"Reload gateway table from database.",
+	0
+};
+
+
+static void reload(rpc_t* rpc, void* c)
+{
+	if (reload_gws () != 1) {
+		rpc->fault(c, 500, "LCR Gateway Reload Failed");
+	}
+}
+
+
+static const char* dump_doc[2] = {
+	"Dump the contents of the gateway table.",
+	0
+};
+
+
+static void dump (rpc_t* rpc, void* c)
+{
+	void* st;
+	unsigned int i;
+	uri_transport transport;
+	
+	for (i = 0; i < MAX_NO_OF_GWS; i++) {
+		if ((*gws)[i].ip_addr == 0) {
+			return;
+		}
+		
+		if (rpc->add(c, "{", &st) < 0) return;
+		
+		if ((*gws)[i].scheme == SIP_URI_T) {
+		    rpc->struct_add(st, "s", "scheme", "sip");
+		} else {
+		    rpc->struct_add(st, "s", "scheme", "sips");
+		}
+		if ((*gws)[i].port == 0) {
+			rpc->struct_printf(st, "host", "%d.%d.%d.%d",
+							   ((*gws)[i].ip_addr << 24) >> 24,
+							   (((*gws)[i].ip_addr >> 8) << 24) >> 24,
+							   (((*gws)[i].ip_addr >> 16) << 24) >> 24,
+							   (*gws)[i].ip_addr >> 24);
+		} else {
+			rpc->struct_printf(st, "host", "%d.%d.%d.%d:%d",
+							   ((*gws)[i].ip_addr << 24) >> 24,
+							   (((*gws)[i].ip_addr >> 8) << 24) >> 24,
+							   (((*gws)[i].ip_addr >> 16) << 24) >> 24,
+							   (*gws)[i].ip_addr >> 24,
+							   (*gws)[i].port);
+		}
+		transport = (*gws)[i].transport;
+		if (transport == PROTO_UDP) {
+			rpc->struct_add(st, "s", "transport", "UDP");
+		} else  if (transport == PROTO_TCP) {
+			rpc->struct_add(st, "s", "transport", "TCP");
+		} else  if (transport == PROTO_TLS) {
+			rpc->struct_add(st, "s", "transport", "TLS");
+		}else if (transport == PROTO_SCTP) {
+			rpc->struct_add(st, "s", "transport", "SCTP");
+		}
+		if ((*gws)[i].prefix_len) {
+			rpc->struct_add(st, "s", "prefix", (*gws)[i].prefix);
+		}
+	}
+}
+
+
+rpc_export_t lcr_rpc[] = {
+	{"lcr.reload", reload, reload_doc, 0},
+	{"lcr.dump",   dump,   dump_doc,   0},
+	{0, 0, 0, 0}
+};

+ 35 - 0
modules_s/lcr/lcr_rpc.h

@@ -0,0 +1,35 @@
+/*
+ * Various lcr related functions
+ *
+ * Copyright (C) 2005 Juha Heinanen
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _LCR_RPC_H
+#define _LCR_RPC_H
+
+#include "../../rpc.h"
+
+extern rpc_export_t lcr_rpc[];
+
+#endif /* _LCR_RPC_H */

+ 24 - 0
modules_s/lcr/ser-lcr.cfg

@@ -0,0 +1,24 @@
+#
+# Minimalistic SER configuration file that can be used to 
+# test the lcr module.
+#
+debug = 4
+fork = no
+children = 1
+log_stderror = yes
+listen=127.0.0.1
+
+loadpath "./modules"
+
+loadmodule "mysql"
+loadmodule "sl"
+loadmodule "tm"
+loadmodule "lcr"
+
+modparam("lcr", "db_url", "mysql://ser:heslo@localhost/ser")
+
+route {
+	load_gws();
+	t_relay();
+	break;
+}