Mai 31 2009

Accounting journalier avec Freeradius et Mysql

Publié par à 10:00 sous Cisco,Freeradius




J’ai déjà vu plusieurs posts sur la liste d’utilisateurs Freeradius et autres forums demandant comment récupérer l’accounting de chaque utilisateur à intervalles réguliers. Le protocole Radius fournit l’accounting mais pas exactement de la façon désirée. Voici une petite liste de choses que j’aimerais modifier ou améliorer:

  • Le traffic n’est pas collecté à intervalles réguliers
    Il en résulte qu’il est difficile de générer des graphiques journaliers des downloads/uploads. Le trafic sera corrompu parce au’une fois de plus, il est impossible de calculer le trafic sur une période donnée avec exactitude

  • La session active n’apparaît pas parce que la base de données ne contient que des valeurs nulles, après qu’un enregistrement de type "start" n’ait été inséré

  • Des enregistrements peuvent être perdus en route (entre le routeur et le serveur Radius). Le protocole Radius utilise UDP, qui ne supporte pas les acknowledgements.

  • La valeur du trafic est réinitialisée à 0 si elle dépasse les 4GB et que la session n’est pas interrompue avant. Le format du champ est une valeur 32 bits non signée comme définie dans la RFC.

Un certain nombre de problèmes se masnifestera pour ceux qui veulent extraire ces données et les traiter. Aucune restriction ne peut être appliquée par exemple. Les graphiques montrent des valeurs nulles si l’utilisateur ne s’est pas déconnecté pendant une longue période. Même s’il existe des moyens d’envoyer des updates depuis le Network Access Server, cette solution ne me satisfait pas puisque la date "stop" reste 00-00-00 et que la requête devient compliquée et prend un certain temps notamment si de précédants stop-records ont été perdus en chemin. Dans le meilleur scenario, le trafic peut être collecté (en incluant la session active), mais il reste impossible de connaître le trafic journalier avec précision par exemple.

 

Méthode

Certains pourront suggérer des réinitialiser toutes les connections à intervalles réguliers mais pourquoi les clients devraient être déconnectés juste pour calculer la valeur exacte de trafic consommé? Comment pourrait-on le justifier pour des connexions 24/24? De simples modifications peuvent palier à ces petits problèmes. Vérifions brièvement ce qui peut être fait:
Le trafic n’est pas collecté à intervalles réguliers. Nous pourrions envoyer des updates depuis le routeur, ce qui est un bon pas. Mais ce n’est pas suffisant puisque les nouvelles valeurs écrasent les anciennes. Une autre manière de procéder est de créer de nouveaux enregistrements stop et start au lieu d’envoyer un update, ou simuler une nouvelle session pour être plus clair. Ceci minimiserait l’impact du dernier stop manquant, si la période de temps est assez petite. Les données perdues ne seraient plus un problème non plus comme l’on compare la nouvelle valeur à la dernière reçue.
Finallement, nous pouvons contourner la limite des 4GB avec les attributs Radius Acct-Input-Gigawords et Acct-Input-Gigawords si votre routeur le supporte (Méthode que nous allons utiliser ici). Si votre hardware ne le supporte pas, quelques modifications au code SQL ci-dessous sont nécessaires. Ceci a été testé sur des plate-formes en production avec plus de 5000 clients.

 

Avant de commencer

J’ai appliqué ces changements sur Fedora Core et Solaris avec Freeradius 1.1.3. Le système d’exploitation n’a pas vraiment d’importance puisque la plupart des changements sont réalisés sur des requêtes SQL. Nous partons d’une installation Freeradius avec Mysql. Scott Barlett a écrit de précieuses notes à ce sujet, disponible à http://www.frontios.com/freeradius.html.
Mysql 5.0 ou plus est absolument nécessaire dans cette implementation. Les versions précédentes ne supportent pas les procédures stockées. Vérifiez également que votre routeur (NAS) supporte les updates pour l’accounting et les attributs Acct-Input-Gigawords (souvent le cas pour Cisco).

 

Ajouter le support pour les extensions Radius

Comme mentionné dans la RFC2869, les attributs additionnels ont été créés pour répondre à certains besoins à travers des fonctions très utiles. Les attributs Acct-Input-Gigawords et Acct-Output-Gigawords indiquent combien de fois les compteurs Acct-Input-Octets et Acct-Output-Octets ont été réinitialisés après avoir atteint 2^32. Ils sont présents dans les envois Stop et Interim-Update, juste ce que nous voulons.
Vous pouvez les activater sur un routeur Cisco en tapant

aaa accounting gigawords

Nous n’avons pas besoin d’ajouter des champs à la base de données Radius; Nous allons calculater le nouveau trafic à la volée. Les bénéfices sont de conserver la structure originelle de la base de données et les scripts accounting que vous auriez pu avoir écrits. D’un autre côté, cela sauve beaucoup d’espace.
La seconde modification est faite dans la requête SQL.

Note Vous devrez redémarrer le routeur pour appliquer les nouveaux paramètres. Faîtes cela au moment approprié.

 

Configuration du NAS pour envoyer des updates accounting

Il existe 2 moyens d’y parvenir. Soit vous reconfigurer votre routeur (NAS) pour envoyer des updates accounting au serveur Radius, soit vous ajouter un paramètre supplémentaire dans les détails du client. Je préfère le faire sur le routeur comme il a la priorité sur les paramètres Radius. J’utilise un routeur Cisco pour lequel la commande est:

aaa accounting update periodic 180

Ceci envoie une update toutes les 3 heures. Je devrais mentionner que ceci s’applique quand la connexion du client est établie, ce qui signifie que vous devez redémarrer les interfaces appropriées.

Attention Utiliser la commande aaa accounting update periodic peut causer une congestion lorsque beaucoup d’utilisateurs sont connectés au réseau.

 

Definir les nouvelles requêtes Update et Stop

Le code SQL pour les requêtes update et stop doivent être remplacées. J’appelle des stored procÉdures parce qu’il y a un peu de calcul à effectuer et le client Mysql n’accepte pas 2 commandes dans une seule requête. Il existe peut-être une méthode mais je pense que c’est plus flexible de cette manière. Aucun changement n’est nécessaire sur les serveurs Radius. Insérez dans la base Mysql les 2 procédures suivantes

DROP PROCEDURE IF EXISTS radius.acct_update;
delimiter //
CREATE PROCEDURE radius.acct_update(
  IN S DATETIME,
  IN Acct_Session_Time INT(12),
  IN Acct_Input_Octets BIGINT(20),
  IN Acct_Output_Octets BIGINT(20),
  IN Acct_Terminate_Cause VARCHAR(32),
  IN Acct_Session_Id varchar(64),
  IN SQL_User_Name VARCHAR(64),
  IN NAS_IP_Address VARCHAR(15),
  IN Acct_Unique_Session_Id VARCHAR(32),
  IN Realm VARCHAR(64),
  IN NAS_Port VARCHAR(15),
  IN NAS_Port_Type VARCHAR(32),
  IN Acct_Authentic VARCHAR(32),
  IN Called_Station_Id VARCHAR(50),
  IN Calling_Station_Id VARCHAR(50),
  IN Service_Type VARCHAR(32),
  IN Framed_Protocol VARCHAR(32),
  IN Framed_IP_Address VARCHAR(15)
)
BEGIN
  DECLARE Prev_Acct_Input_Octets BIGINT(20);
  DECLARE Prev_Acct_Output_Octets BIGINT(20);
  DECLARE Prev_Acct_Session_Time INT(12);

  # Collect traffic previous values
  SELECT SUM(AcctInputOctets), SUM(AcctOutputOctets), SUM(AcctSessionTime)
    INTO Prev_Acct_Input_Octets, Prev_Acct_Output_Octets, Prev_Acct_Session_Time
    FROM radacct
    WHERE AcctSessionId = Acct_Session_Id
    AND UserName = SQL_User_Name
    AND NASIPAddress = NAS_IP_Address
    AND ( AcctStopTime > 0);

  # Set values to 0 when no previous records
  IF (Prev_Acct_Session_Time IS NULL) THEN
    SET Prev_Acct_Session_Time = 0;
    SET Prev_Acct_Input_Octets = 0;
    SET Prev_Acct_Output_Octets = 0;
  END IF;

  # Update record with new traffic
  UPDATE radacct SET AcctStopTime = S,
    AcctSessionTime = (Acct_Session_Time - Prev_Acct_Session_Time),
    AcctInputOctets = (Acct_Input_Octets - Prev_Acct_Input_Octets),
    AcctOutputOctets = (Acct_Output_Octets - Prev_Acct_Output_Octets),
    AcctTerminateCause = Acct_Terminate_Cause
    WHERE AcctSessionId = Acct_Session_Id
    AND UserName = SQL_User_Name
    AND NASIPAddress = NAS_IP_Address
    AND (AcctStopTime IS NULL OR AcctStopTime = 0);

  # Create new record
  INSERT INTO radacct
   (AcctSessionId, AcctUniqueId, UserName,
    Realm, NASIPAddress, NASPortId, NASPortType,
    AcctStartTime, AcctStopTime, AcctSessionTime,
    AcctAuthentic, AcctInputOctets, AcctOutputOctets,
    CalledStationId, CallingStationId, AcctTerminateCause,
    ServiceType, FramedProtocol, FramedIPAddress,
    AcctStartDelay, AcctStopDelay)
  VALUES
   (Acct_Session_Id, Acct_Unique_Session_Id, SQL_User_Name,
    Realm, NAS_IP_Address, NAS_Port, NAS_Port_Type,
    S, '0', '0',
    Acct_Authentic, '0', '0',
    Called_Station_Id, Calling_Station_Id, '',
    Service_Type, Framed_Protocol, Framed_IP_Address,
    '0', '0');
END;
//
delimiter ;

 

Note Vous devez mettre à jour les noms de tables s’ils ont été changés dans sql.conf. J’utilise les valeurs par défaut présent dans le fichier et la structure de la base: « Radacct ».
Note N’oubliez pas de changer le délimitateur avant et après la procédure ou vous obtiendrez une erreur!
Nous devons récupérer le trafic des updates précédentes parce que le routeur envoie un compteur et pas le trafic généré sur la dernière période de temps. Un enregistrement stop est ajouté avec la différence de trafic. La requête stop doit aussi être modifiée:

DROP PROCEDURE IF EXISTS radius.acct_stop;
delimiter //
CREATE PROCEDURE radius.acct_stop(
  IN S DATETIME,
  IN Acct_Session_Time INT(12),
  IN Acct_Input_Octets BIGINT(20),
  IN Acct_Output_Octets BIGINT(20),
  IN Acct_Terminate_Cause VARCHAR(32),
  IN Acct_Delay_Time INT(12),
  IN Connect_Info VARCHAR(32),
  IN Acct_Session_Id varchar(64),
  IN SQL_User_Name VARCHAR(64),
  IN NAS_IP_Address VARCHAR(15)
)
BEGIN
  DECLARE Prev_Acct_Input_Octets BIGINT(20);
  DECLARE Prev_Acct_Output_Octets BIGINT(20);
  DECLARE Prev_Acct_Session_Time INT(12);

  # Collect traffic previous values
  SELECT SUM(AcctInputOctets), SUM(AcctOutputOctets), SUM(AcctSessionTime)
    INTO Prev_Acct_Input_Octets, Prev_Acct_Output_Octets, Prev_Acct_Session_Time
    FROM radacct
    WHERE AcctSessionId = Acct_Session_Id
    AND UserName = SQL_User_Name
    AND NASIPAddress = NAS_IP_Address
    AND ( AcctStopTime > 0);

  # Set values to 0 when no previous records
  IF (Prev_Acct_Session_Time IS NULL) THEN
    SET Prev_Acct_Session_Time = 0;
    SET Prev_Acct_Input_Octets = 0;
    SET Prev_Acct_Output_Octets = 0;
  END IF;

  # Update record with new traffic
  UPDATE radacct SET AcctStopTime = S,
    AcctSessionTime = (Acct_Session_Time - Prev_Acct_Session_Time),
    AcctInputOctets = (Acct_Input_Octets - Prev_Acct_Input_Octets),
    AcctOutputOctets = (Acct_Output_Octets - Prev_Acct_Output_Octets),
    AcctTerminateCause = Acct_Terminate_Cause,
    AcctStopDelay = Acct_Delay_Time,
    ConnectInfo_stop = Connect_Info
    WHERE AcctSessionId = Acct_Session_Id
    AND UserName = SQL_User_Name
    AND NASIPAddress = NAS_IP_Address
    AND (AcctStopTime IS NULL OR AcctStopTime=0);
END;
//
delimiter ;

 

C’est la même que la requête originale à part qu’elle récupère le trafic précédent. Si vous n’utilisez pas les updates accounting, le comportement sera le même qu’avant.

 

Mise à jour de sql.conf

La dernière étape est de remplacer le code SQL dans sql.conf, pour appeler les 2 procédures ci-dessus.
Remplacez accounting_update_query avec

accounting_update_query = " \
          CALL acct_update( \
          '%S', \
          '%{Acct-Session-Time}', \
          '%{%{Acct-Input-Gigawords}:-0}'  << 32 | '%{%{Acct-Input-Octets}:-0}', \
          '%{%{Acct-Output-Gigawords}:-0}' << 32 | '%{%{Acct-Output-Octets}:-0}', \
          'Acct-Update', \
          '%{Acct-Session-Id}', \
          '%{SQL-User-Name}', \
          '%{NAS-IP-Address}', \
          '%{Acct-Unique-Session-Id}', \
          '%{Realm}', \
          '%{NAS-Port}', \
          '%{NAS-Port-Type}', \
          '%{Acct-Authentic}', \
          '%{Called-Station-Id}', \
          '%{Calling-Station-Id}', \
          '%{Service-Type}', \
          '%{Framed-Protocol}', \
          '%{Framed-IP-Address}')"

 
et accounting_stop_query avec

accounting_stop_query = " \
          CALL acct_stop( \
          '%S', \
          '%{Acct-Session-Time}', \
          '%{%{Acct-Input-Gigawords}:-0}' << 32 | '%{%{Acct-Input-Octets}:-0}', \
          '%{%{Acct-Output-Gigawords}:-0}' << 32 | '%{%{Acct-Output-Octets}:-0}', \
          '%{Acct-Terminate-Cause}', \
          '%{%{Acct-Delay-Time}:-0}', \
          '%{Connect-Info}', \
          '%{Acct-Session-Id}', \
          '%{SQL-User-Name}', \
          '%{NAS-IP-Address}')"

 

Rebootez le serveur Radius pour appliquer les nouveaux changements, et voilà!


2 responses so far

2 Réponses à “Accounting journalier avec Freeradius et Mysql”

  1. LnddMileson 24 Juil 2009 at 10:02

    The best information i have found exactly here. Keep going Thank you

  2. daveon 24 Juil 2009 at 5:41

    Cheers LnddMiles
    Same information is available here in english: http://www.netexpertise.eu/en/freeradius/daily-accounting.html

Comments RSS

Leave a Reply