Explication de Script de configuration de Kamailio 1.5.x

 

 

 

Introduction

Le fichier de configuration Kamailio (kamailio.cfg) est plus qu’un fichier de configuration typique. Il combine à la fois les paramètres statiques et un environnement de programmation dynamique.

 

En effet, « kamailio.cfg » est un programme qui est exécuté pour chaque message reçu par le routeur Open SIP Express (OpenSER nommé Kamailio à partir de la version 1.4.0).

Pseudo-variables

Le terme "pseudo-variable" est utilisé pour des variables spéciaux qui peuvent être données en tant que paramètres aux fonctions de script, ils seront remplacés par une valeur avant l'exécution de la fonction.

Le tableau ci-dessous présente les pseudo-variables utilisées dans notre configuration :

Pseudo-variables

Description

$si

IP source address: référence à l'adresse IP source du message.

$Ts

Unix time stamp - En cache: référence à la commande unix time stamp

-NB : l’heure du système est récupéré qu'une seule fois pour chaque message SIP traité. Les appels ultérieurs de $Ts pour le même message SIP retournera la même valeur.

$oU

Username in SIP Request's original URI: référence au nom d'utilisateur dans l'original URI (Identificateur de Ressource Uniforme).

$tU

To URI Username: référence au nom d'utilisateur dans URI de 'To' header.

$Ri

Received IP address: référence à l'adresse IP de l'interface dans laquelle la demande a été reçue.

$rm

method

SIP request's method: référence à la méthode de la requête SIP (INITE, CANCEL…) ainsi que ‘method’.

$fu

From URI: référence à l'URI de 'From' header.

$tu

To URI Username: nom d'utilisateur dans URI de 'To' header.

 

Fonctions 

 

Le tableau ci-dessous présente les fonctions utilisées dans notre configuration :

 

Fonction

Description

Module

Xlog(format)

Ecrit un message formaté dans le fichier log du serveur.

xlog

mf_process_maxfwd_header(max_value)

Si aucune Max-Forward-Header est présent dans le message reçu, un en-tête sera ajouté avec la valeur ‘max_value’. Si un en-tête Max-Forward est déjà présente, sa valeur sera décrémenté (sinon 0).

 

Maxfwd

 

subst_uri('/re/repl/flags')

Remplace ‘re’ avec ‘repl’ (comme subst mais ne fonctionne que sur l'URI).

re’- est une expression régulière.

repl’- est la chaîne de remplacement, elle peut contenir des pseudo-variables.

flags’- flags de substitution.

 

 

textops

has_totag()

Vérifiez si l'en-tête URI contient le paramètre tag.

siputils

loose_route()

La fonction effectue le routage des requêtes SIP qui contiennent un itinéraire défini.

rr

record_route()

La fonction ajoute un nouveau record-Route en-tête.

rr

is_method("SIP Method")

Vérifie la méthode avec laquelle la requête SIP est envoyée (INITE, CANCEL…).

 

t_newtran()

Crée une nouvelle transaction, retourne une valeur négative en cas d'erreur.

tm

t_check_trans()

Retourne true si le message SIP en cours est associé à une transaction.

tm

sql_query (connection, query, result)

Exécute une requête SQL à partir de connection et stockent des données dans result.

connection’- le nom de la connexion à utiliser pour la recherche (définies par le paramètre sqlcon).

query’- SQL query string, peuvent contenir des pseudo-variables.

result’- le nom de chaîne pour identifier le résultat. Seront utilisées par DBR $ (...) pseudo-variable pour accéder aux attributs de résultat.

 

 

 

 

sqlops

sql_result_free(result)

libère les données SQL dans result.

sqlops

rewritehost(string)

sethost(string)

Réécriture du domaine de la R-URI avec la valeur du ‘string’. D'autres parties de la R-URI comme identifiant, mot de port et les paramètres URI restent inchangés.

 

-

 

sl_send_reply (code, reason)

Pour la demande en cours, une réponse est renvoyée après avoir donné le code et la raison.

code’ et la ‘raison’ peut contenir des pseudo-variables qui sont remplacées à l'exécution.

 

sl

t_reply(code, reason)

Génère et envoie une réponse pour la SIP transaction entrant.

tm

t_relay()

Relayer un message statefully à destination indiquée dans le courant URI.

tm

t_on_failure(code)

Définit la réponse de routage par catégorie, à laquelle le contrôle est passé (route[code]), mais avant d'envoyer une réponse définitive.

 

tm

tl_reply_error()

Renvoie une réponse d'erreur décrivant la nature de l'erreur interne dernière. Habituellement, cette fonction doit être utilisée après une fonction de script qui a renvoyé un code d'erreur.

 

sl

Script ‘Kamailio.cfg’

Le tableau ci-dessous présente en détails le script de configuration utilisée dans le routage dynamique avec ACD :

 

Script Kamailio

Commentaires

 

debug=3

log_stderror=no

log_facility=LOG_LOCAL0

log_name="ser"

 

fork=yes

children=4

 

check_via=no

dns=no     

rev_dns=no

disable_dns_blacklist=yes

auto_aliases=no

 

disable_tcp=yes

server_signature=yes

sip_warning=1

 

listen=87.106.212.173    # INA (Colt)

listen=212.227.250.146   # Colt

listen=87.106.213.52     # Verizon

 

La première partie de kamailio.cfg concerne la configuration locale du serveur.

mpath="/usr/lib/kamailio/modules/"

 

loadmodule "sl.so"

loadmodule "tm.so"

loadmodule "rr.so"

loadmodule "pv.so"

loadmodule "siputils.so"

loadmodule "maxfwd.so"

loadmodule "textops.so"

loadmodule "mi_fifo.so"

loadmodule "xlog.so"

loadmodule "sqlops.so"

loadmodule "db_mysql.so"

# ACD Routing

loadmodule "cfgutils.so"

 

modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo")

modparam("rr", "enable_full_lr", 1)

modparam("rr", "append_fromtag", 0)

 

modparam("sqlops","sqlcon","routing_ina=>mysql://root:revolution1025@127.0.0.1/ina")

modparam("sqlops","sqlcon","routing_acd=>mysql://root:revolution1025@127.0.0.1/acd_armenia")

 

Ici on charge les modules qui se trouvent dans mpath en fixant leurs paramètres.

 

   Maintenant nous entrons dans le ‘main’ de la configuration.

   Dans la configuration Kamailio tous les messages SIP sont traités par un seul bloc de code nommé route

route {

 

  if ($si!="91.121.66.202" and $si!="91.121.19.149" and $si!="91.121.101.126" and

$si!="91.121.75.124" and $si!="91.121.167.75" and $si!="91.121.70.119" and $si!="66.234.138.73"

and $si!="82.103.128.3" and $si!="217.168.45.3" and $si!="212.249.15.3" and $si!="212.249.15.9")

  {

      xlog("L_INFO","$Ts ==> Unallowed access for $si\n");

      sl_send_reply("403","Forbidden here xx");

      exit;

  }

 

 

  if (!mf_process_maxfwd_header("10"))

 {

    sl_send_reply("483","Too Many Hops");

    exit;

  }

 

  if (has_totag())

  {

    if (loose_route())

    {

      if(method=="INVITE")

      {

        if(t_newtran())

        {

          t_reply("100","Your Re-INVITE is received");

          t_reply("488","Your Re-INVITE is ignored");

          exit;

        }

        else

        {

          xlog("L_INFO","$Ts ==> Newtran error $oU\n");

          sl_send_reply("100","Your Re-INVITE is received (SL)");

          sl_send_reply("488","Your Re-INVITE is ignored (SL)");

          exit;

        }

      }

      t_relay();

      exit;

    }

On ignore tous les paquets de type INVITE qui sont déjà traités.

Pour un seul appel on peut avoir plusieurs paquets INVITE, on s’intéresse qu’au premier INVITE reçu qui doit être routé vers un vendeur pour que la connexion soit établie.  

    else

    {

      if ( is_method("ACK") )

      {

        if ( t_check_trans() )

        {

          # non loose-route, but stateful ACK; must be an ACK after a 487 or e.g. 404 from upstream server

          t_relay();

          exit;

        }

        else

        {

          # ACK without matching transaction ... ignore and discard.\n");

          exit;

        }

      }

    }

    exit;

  }

On renvoi tous les paquets SIP reçus dont le type est ACK et sa transaction est active et on ignore le reste.

   Dans ce stade tous les paquets INVITE correspondent à des nouveaux appels.

   Donc ils ont besoin d’être routés et marqués en ajoutant la signature de notre serveur.

   if(is_method("INVITE"))

  {

    record_route();

 

    #418XX numbers

    if($tU=~"^418(00|40|42|44|48)")

    {

      xlog("INA: processing call to $tU\n");

      sql_query("routing_ina", "select right(l.ina_number,9),l.tsp_id,s.description

from lig l,status s where l.ina_status=s.ina_status and l.ina_number=concat(\"0\",right($tU,9))

and s.description in (\"SRVIN\",\"SRVTO\",\"ALLOC\",\"ALLNO\",\"ALLEX\")

order by concat(l.trans_date,l.trans_time) desc limit 1","result_ina");

 

      if ($dbr(result_ina=>rows)>0)

      {

        xlog("$Ts ==> INA: $dbr(result_ina=>colname[0]) -> $dbr(result_ina=>[0,0])\n");

        xlog("$Ts ==> INA: $dbr(result_ina=>colname[1]) -> $dbr(result_ina=>[0,1])\n");

        xlog("$Ts ==> INA: $dbr(result_ina=>colname[2]) -> $dbr(result_ina=>[0,2])\n");

 

        $var(ina_number)=$dbr(result_ina=>[0,0]);

        $var(tsp_id)=$dbr(result_ina=>[0,1]);

        $var(ina_status)=$dbr(result_ina=>[0,2]);

 

        sql_result_free("result_ina");

 

        xlog("$Ts ==> INA: ina_number: $var(ina_number)\n");

        xlog("$Ts ==> INA: tsp_id: $var(tsp_id)\n");

        xlog("$Ts ==> INA: ina_status: $var(ina_status)\n");

 

        switch($var(ina_status))

        {

          case "SRVIN":

            $var(prefix)=$var(tsp_id)+"10335";

            xlog("$Ts ==> INA: prefix -> $var(prefix)\n");

            subst_uri("/^sip:41/sip:$var(prefix)/i");

            prefix("83");

            rewritehost("217.168.45.3");

            t_relay();

            break;

          case "SRVTO":

            sl_send_reply("480","INA SRVTO");

            break;

          case "ALLOC":

            sl_send_reply("480","INA ALLOC");

            break;

          case "ALLNO":

            sl_send_reply("404","INA ALLNO");

            break;

          case "ALLEX":

            sl_send_reply("404","INA ALLEX");

            break;

        }

        exit;

      }

      else {

        xlog("$Ts ==> INA: $tU not found!\n");

        sl_send_reply("404","INA not found");

        exit;

      }

    }

 

if ($tU=~"^(\+|00)? 37410")

  {

  $var(Ri_V)="87.106.213.52";

  $var(Ri_C)="212.227.250.146";

On vérifie si l’appel possède comme destination « Armenia, Yerevan ».

$var(Ri_V) et $var(Ri_C) présente respectivement l’interface de réception de l’appel pour Colt et Verizon. 

 sql_query("routing_acd", "select ACD, reject, Nb_reject, total_calls from acd_vendors

where i_vendor=55 and prefix=37410 order by date desc limit 1;","result_Verizon");

 

  sql_query("routing_acd", "select ACD, reject, Nb_reject, total_calls from acd_vendors

where i_vendor=62 and prefix=37410 order by date desc limit 1;","result_Colt");

On exécute les deux requêtes SQL qui nous permet de calculer la probabilité de routage et les variables de décision.

  if ($dbr(result_Verizon=>rows) && $dbr(result_Colt=>rows))

    {

      $var(Verizon)="ACD="+$dbr(result_Verizon=>[0,0])+";Rej="+$dbr(result_Verizon=>[0,1])

+";NbRej="+$dbr(result_Verizon=>[0,2])+";TOT_C="+$dbr(result_Verizon=>[0,3]);

 

      $var(Colt)="ACD="+$dbr(result_Colt=>[0,0])+";Rej="+$dbr(result_Colt=>[0,1])+

";NbRej="+$dbr(result_Colt=>[0,2])+";TOT_C="+$dbr(result_Colt=>[0,3]);

Si la requête est exécutée correctement alors on stocke les valeurs de result en utilisant la classe « Parameters (param) ».

    sql_result_free("result_Verizon");

    sql_result_free("result_Colt");   

On libère ici les données SQL dans result_Verizon et result_Colt.

    if ($Ri==$var(Ri_V))

      {

      $var(Target_Load)= 100 - $(var(Verizon){param.value,Rej}{s.int}) ;  

      }

    else

      {

      $var(Target_Load)= 100 - $(var(Colt){param.value,Rej}{s.int}) ;

      }

    }

La probabilité de routage est le complément à 100 du pourcentage de rejet, c’est la probabilité pour que l’appel passe par le vendeur spécifié par « $var(Ri_X)» (l’interface de réception de l’appel).

  else

      {

      xlog("=> ACD $tU:  DB connection failed (Select query) \n");

      }

Dans le cas où la requête SQL n’a pas pu s’exécute correctement alors on log cette erreur mais on n’arrête pas d’exécution.

Dans ce cas le script va router l’appel vers le vendeur qui vient de le recevoir dans son interface car la probabilité de routage est égale à 100.

  $var(TOT_Calls) = $(var(Verizon){param.value,TOT_C}{s.int}) +  $(var(Colt){param.value,TOT_C}{s.int});

  $var(TOT_Reject) = $(var(Verizon){param.value,NbRej}{s.int}) +  $(var(Colt){param.value,NbRej}{s.int}) ;

  $var(C1) = $var(TOT_Calls) - $var(TOT_Reject) ;

  $var(C1) = $var(C1) * 100;

  $var(C2) = $var(Target_Load) * $var(TOT_Calls) ;

On calcule le nombre total des appels reçus dans l’intervalle courant et aussi le nombre total des appels rejetés.

$var(C1) et $var(C2) sont les variables de décision, si  $var(C1) <= $var(C2) alors l’appel est accepté sinon il est rejeté.

  xlog("=> ACD $tU:  $rm IP_Reception = $Ri \n");

  xlog("=> ACD $tU:  ACD_Colt = $(var(Colt){param.value,ACD}) | ACD_Verizon = $(var(Verizon){param.value,ACD}) \n");

  xlog("=> ACD $tU:  Reject_Colt = $(var(Colt){param.value,Rej}) | Reject_Verizon = $(var(Verizon){param.value,Rej})\n");       

  xlog("=> ACD $tU:  Total_Calls_Colt = $(var(Colt){param.value,TOT_C}) | Total_Calls_Verizon = $(var(Verizon){param.value,TOT_C})\n");

  xlog("=> ACD $tU:  Target_Load = $var(Target_Load) \n");

  xlog("=> ACD $tU:  TOT_Calls = $var(TOT_Calls) | TOT_Reject = $var(TOT_Reject) \n");

On écrit dans le fichier log de serveur toutes les valeurs des variables de décision dans le but de suivre l’exécution de ce programme.

if ( $var(C1) <= $var(C2) )

    {

    xlog("=> ACD $tU: Call Accepted   : $var(C1) < $var(C2)\n\n\n\n");

    if ($Ri==$var(Ri_C))

      {

      rewritehost("217.168.45.3");

      sql_query("routing_acd","update acd_vendors set total_calls = total_calls + 1 where i_vendor=62 and prefix =37410 order by date desc limit 1;","result_upd");

      }

    else

      {

      rand_set_prob("50");

      if (rand_event())

        rewritehost("212.249.15.9");

      else

        rewritehost("212.249.15.3");

      sql_query("routing_acd","update acd_vendors set total_calls = total_calls + 1 where i_vendor=55 and prefix =37410 order by date desc limit 1;","result_upd");

      }

    t_relay();

    }

Si l’appel est acceptée, on envoi l’appel vers le vendeur qui correspond à l’interface qui vienne de recevoir l’appel et on incrémente le nombre d’appel routé pour ce dernier.

Le vendeur Verizon possède deux IP, on va alors router 50% pour chacune.

Il y a trois cas possible pour avoir une probabilité de routage égale à 100 :

-Dans le cas où l’appel est rejeté.

-Le premier appel dans l’intervalle courant.

-Dans le cas d’une erreur SQL (faible chance car le serveur SQL est locale et utilisé seulement par cette application).

  else

    {

    xlog("=> ACD $tU: Call Rejected : $var(C1) >= $var(C2)\n\n\n\n");

    if ($Ri==$var(Ri_V))

      {

      sql_query("routing_acd","update acd_vendors set Nb_reject = Nb_reject + 1 where i_vendor=55 and prefix =37410 order by date desc limit 1;","result_upd");  

      }

    else

      {

      sql_query("routing_acd","update acd_vendors set Nb_reject = Nb_reject + 1 where i_vendor=62 and prefix =37410 order by date desc limit 1;","result_upd");

      }

    sl_send_reply("480","Call Rejected");

    }

  sql_result_free("result_upd");

  exit;

  }   

Les appels ici sont rejeté, on incrémente le nombre des appels rejetés pour ce vendeur et on envoi une réponse d’erreur à Portasip.

 

    if($tU=~"^4190")

    {

      xlog("L_INFO","$Ts ==> Forbidden $tU\n");

      sl_send_reply("403","Forbidden");

      exit;

    }

 

    # Unassigned

    else

    {

      xlog("L_INFO","$Ts ==> Unassigned Number $tU\n");

      sl_send_reply("404","Unassigned Number");

      exit;

    }

  xlog("=> ACD $tU: Unmatched Packet $rm IP, Source address $si, From URI $fu, To URI $tu\n");

  }

 

  t_check_trans();

  route(1);

  xlog("=> ACD $tU: Unmatched Packet $rm IP, Source address $si, From URI $fu, To URI $tu\n");

}

 

route[1]

{

  # for INVITEs enable some additional helper routes

  if (is_method("INVITE")) {

    t_on_failure("1");

  }

  if (!t_relay()) {

    sl_reply_error();

  };

  exit;

}

 

route [1] route les paquets qui ne sont pas considérés par le route principale généralement se sont des paquets de type CANCEL.

Les réponses

Ce tableau présent les codes de réponses utilisées par le script ci-dessus.

 

Code

Représentation

100

Your Re-INVITE is received

403

Forbidden here xx

Forbidden

 

404

INA ALLNO INA ALLEX

INA not found

Unassigned Number

480

INA SRVTO INA ALLOC

Call Rejected

483

Too Many Hops

488

Your Re-INVITE is ignored



Référence:

Le Protocole SIP [091023]

La documentation des différents modules [kamailio 1.5.x]

Kamailio (OpenSER) pseudo-variables [kamailio 1.5.x]

Index des fonctions disponible [kamailio 1.5.x]

Configuration de Kamailio (OpenSER) [091023]

ACD Routing for Fighting Wrong Signal Supervision [091020  ii]

Les Testes de ACD-Routage [091023]

Statistique ACD Routing pour Armenia, Yerevan [091029]

 

 

 

* * *

       2009 © swirzernet.com