Motivations

Actuellement, dans GLPI, plusieurs ressources liées à IP sont manquantes. À titre d'exemple, les adresses IP sont des adresses au format IPv4. Aucune notion de réseau n'éxiste autrement que par la définition, dans les NetworkPort, d'une adresse de réseau et d'un masque de sous-réseau.
Pour une gestion plus fine des ressources d'un parc informatique dans le cadre d'internet, il faut tenir compte de ce qui unit les ordinateurs entre eux. Ainsi, il y a des relations importantes entre les réseaux (ie : découpage en sous-réseaux logiques et réel). De la même manière, il y a une relation entre des domaines et leurs sous-domaines (ie : indepnet.net par rapport .net et par rapport à forge.indepnet.net). Nous pourrions grandement faciliter la gestion d'un parc informatique en proposant de telles ressources au sein de GLPI.

Je proposes, donc, l'ajout des structures ci-dessous.

Adresse IP

A la base des réseaux internet se trouve le protocol IP et son système d'adressage. Depuis plusieurs années, le protocole IPv4 vieillissant laisse la place au protocole IPv6 avec un nouveau format d'adresse.
Ces adresses sont codées sur 128 bits (4 * 4 octets). Elles ont une représentation lisible par les êtres humains (hexadecimal par entier sur 2 octets avec omission des 0). Mais les calculs sont beaucoup plus simples en binaires (de simples "&" et "|"). Lorsqu'il s'agit de faire des requêtes SQL, une représentation binaire est fondamentale, notamment pour modéliser les imbrications de réseaux. Cette structure de 128 bits peut être stockée, au format binaire sous la forme de 4 entiers sur 32 bits. Cette représentation permet, en outre, de définir une adresse IPv4 avec l'un de ces 4 entiers sur 32 bits (une adresse IPv4 contient, justement 32 bits). Ainsi, une adresse IPv4 mappée en adresse IPv6 a la forme : ::ffff:a:b (avec a et b représentant l'adresse IPv4 coupée en 2 entier 16 bits). Cette subtilité permet faire les calculs sur IPv4 et sur IPv6 de la même manière, si ce n'est que les calculs en IPv4 sont limités au dernier entier 32 bits au lieu des 4 pour les adresses IPv6.
Je suggère une double représentation : binaire et textuelle. La première est utilisée, en interne, pour effectuer les calculs et les requêtes SQL alors que la seconde, utilisée pour l'affichage, est compréhensible par l'utilisateur.

MoYo : je ne suis pas du tout pour la double représentation. Avec un stockage binaire complet, des fonctions doivent permettre les recherches (INET_ATON en MySQL) et l'affichage (long2ip en php). Double représentation = nécessité de la maintenir à jour -> potentiel problèmes de desynchro de datas.

Damien : la méthode INET_ATON fonctionne très bien pour IPv4. Mais je n'ai trouvé aucun support natif des adresses IPv6 pour Mysql. Il existe bien des "plugin" mysql pour prendre en charge ce type d'adresse. Mais il ne me semble pas souhaitable d'imposer l'utilisation de tels plugins pour les utilisateurs de GLPI. Par ailleurs, long2ip ne fonctionne pas non plus avec IPv6. Cette double notation ne m'enchante guère non plus, mais j'ai peur que nous ne puissions faire autrement.

MoYo : et tout stocker en littéral ?

Damien : Si la version littérale est celle que je nommes textuelle, alors le problème de la version littétale réside dans ses différentes notations : les adresses fe80::53b:f6e8:da5c:9639 et fe80:0::053b:f6e8:da5c:9639 sont les mêmes. Alors que leur représentation littérale sont différentes. De plus, il devient extrèmement coder une requête SQL qui confirmera que cette même adresse fait partie du sous-réseau fe80::53b:f6e8:da58:0/109 (si ! si ! je confirmes qu'elle en fait partie ...).
La seule solution que je vois pour supprimer ce doublon est de la stocker sous une forme binaire. Mais cela signifie qu'il faut refactoriser l'adresse à la volée avant chaque affichage. Cela risque d'être lourd sur une page de type "search".

Pour matérialiser cela, je proposes d'introduire la classe "IPAddress" qui contient cette double représentation. Elle contient, en plus des attributs classiques hérités de CommonDBTM, trois attributs :
  • version : version d'IP de l'adresse en cours (4 ou 6) ;
    • MoYo : vu qu'on a 2 choix : is_ipv6 ou is_ipv4 sera plus compréhensible pour le moment.
    • Damien : Effectivement, c'est plus simple. Mais avoir une méthode getVersion permet de s'assurer de la validité de l'adresse (getVersion renvoie false en cas d'adresse invalide).
    • MoYo : je ne comprend pas ta réponse. la fonction getVersion pourra quand même renvoyer false si l'adresse n'est pas valide. D'ailleurs je ne trouve pas ca très logique comme comportement pour cette fonction... Vu son nom ce n'est pas à elle de faire ce contrôle.
    • Damien : La méthode getVersion était pensée d'une manière très générique, au cas où dans quelques années, une version 7 ou 8 d'IP apparaisse ... Quoiqu'il en soit, il est vrai que ce n'est pas son rôle de dire si l'adresse est valide. Elle pourrait être supprimée au profit de is_ipv6 et is_ipv4.
  • textual : adresse au format textuel (ie : lisible par l'utilisateur) ;
  • binary : tableau de 4 "entiers" représentant l'adresse au format binaire ;
    Trois méthodes permettent de récupérer ces attributs. Elles renvoient "false" si les attributs ne sont pas définis (ie : l'adresse n'est pas résolue).

Associée à cette classe, une table (glpi_ipaddresses) permet de stocker cette double représentation ainsi que l'objet auquel se rattache cette adresse. Voici la structure :

id int(11) Identifiant de l'adresse
node_type varchar(11) élément auquel se rattache l'adresse
node_id int(11)
version tinyint unsigned version du protocol IP de l'adresse (doit être 4 ou 6)
name varchar(255) format textuelle de l'adresse (ie. : lisible par l'utilisateur) de l'adresse IP
binary_0 int unsigned int 4 entiers représentant l'adresse au format binaire (pour les calculs)
binary_1 int unsigned int
binary_2 int unsigned int
binary_3 int unsigned int

MoYo : Quel intérêt des 4 champs ? on ne pourra pas simplement utiliser les fonction mysql et PHP de passage IP / entier ?
Damien : comme je l'ai indiqué plus haut, la conversion IPv6 pose quelques problèmes. Mais je me rends compte qu'il existe les types binary et varbinary. Mais je me demande si les recherches sont aussi performantes qu'avec 4 entier non signés. Quelqu'un aurait-il une idée ?
MoYo : OK. Ou est-ce que tu met une adresse IPV4 ? dans la structure IPV6 ? ou juste dans binary_0 ? Ca va vraiment être très très tordu à gérer...
Damien : l'adresse IPv4 est, en fait, stockée dans binary_3. En effet, par convention, une adresse IPv4 est stockée dans une adresse IPv6 en mettant le préfix de réseau ::ffff:xxxx:xxxx. Donc, ce n'est pas si tordu que cela : toute adresse IPv6 appartenant au réseau ::ffff:0:0/112 est une adresse IPv4. En d'autres termes, si une adresse IPv6 est telle que binary_0 = 0, binary_1 = 0 et binary_2 = 0xffff(65535), alors, il s'agit d'une adresse IPv4. C'est extrèmement pratique, car les calculs sont exactement les mêmes entre IPv4 et IPv6. Donc, les requètes SQL sont les mêmes. Si l'on souhaite optimiser, on peut se limiter à une requête uniquement sur binary_3.
En attaché, j'ai mis le patch de mes développements d'évaluation de faisabilité en attaché. Cela donnera une idée plus claire de ce que je proposes.

Il n'y a pas d'entitée car l'entitée d'une adresse est celle de l'objet auquel elle se rattache.

Cette classe doit contenir un certain nombre de méthodes facilitant le passage entre les deux formats. Les deux plus importantes :
  • function setAddressFromString($address, $item_type = "", $item_id = -1) : définit complètement l'objet courant en convertissant une adresse saisie par l'utilisateur (donc au format lisible) en une adresse au format binaire. Si les "item_type" et "item_id" sont définis, alors, on fait une recherche dans la base de donnée afin de récupérer la bonne adresse.
  • function setAddressFromBinary($address, $item_type = "", $item_id = -1) : définit complètement l'objet courant en convertissant une adresse définit par un tableau de 4 entiers 32 bits en une adresse au format lisible. De la même manière que setAddressFromString, elle permet de récupérer l'entrée de la base de données lorsque item_type et item_id sont définis.

En plus de ces deux méthodes, la classe surcharge les méthodes post_getFromDB et prepareInputForAdd afin d'effectuer le transfert d'information adequat entre les champs utils à la base de donnée et les attributs propres de la classe.

MoYo : ces modificateurs peuvent être intéressants mais je pense que la majorité des modifications se feront directement via les méthodes standards : update / add / delete.
Damien : la surcharge de ces méthodes permettent, justement, de garantir qu'il n'y a pas de désynchronisation entre la version binaire et la version textuelle de l'adresse.
MoYo : les traitements adéquats dans les fonction prepare doivent résoudre ce problème aussi. Mais les 2 ne sont pas incompatibles.

Masque de réseau

Un masque de réseau est une adresse IP au format un petit peu spécifique. En effet, pour la saisie d'un masque, il est courant de donner le nombre de bits à 1. Mais l'ancienne notation (par exemple : 255.255.255.0) est encore très utilisée en IPv4. Ainsi, Je propose la création d'une classe IPNetmask directement héritée de IPAdress, mais sans table (ie : notable=true). Cette dernière n'introduit qu'une unique méthode :
  • function setNetmaskFromString($netmask, $version) elle a le même rôle que IPAddress::setAddressFromString, mais, en plus, elle test si le netmask est un entier. Dans ce cas, elle initialise l'adresse correctement (ie : met le nombre de bits à 1 dans le masque résultant). Si le netmask n'est pas un entier, elle appelle IPAddress::setAddressFromString pour le cas où l'adresse serait définit dans le "vieux" format IPv4.

sous-réseau d'Internet Protocol

Un sous réseau est constitué de trois adresses IP : l'adresse du réseau, son masque et la passerelle. Si les deux premières sont obligatoires, la troisième est facultative (cas du découpage en sous réseaux administratifs).
Grâce à l'adresse IP définie ci-dessus, un réseau possède un découpage très simple. Les champs de la table sont les suivants :

id Identifiant du réseau
entities_id, is_recursive entitée d'appartenance du réseau et aspect recursif
ipnetworks_id, completename, level, ancestors_cache, sons_cache éléments de gestion de l'arbre des réseaux
version version du protocol IP du réseau (doit être 4 ou 6)
address, address_0, address_1, address_2, address_3 version textuelle et champs du format binaire de l'adresse du réseau
netmask, netmask_0, netmask_1, netmask_2, netmask_3 version textuelle et champs du format binaire du masque de sous-réseau
gateway, gateway_0, gateway_1, gateway_2, gateway_3 version textuelle et champs du format binaire de la passerelle du réseau

Il n'y a qu'un seul champs version, car toutes les adresses doivent être définies dans le même format.
En plus des méthodes classiques pour passer des attributs interne aux attributs de CommonDBTM et inversement, l'une des méthodes les plus importante est la méthode static function searchNetworks($relation, $condition, $entityID = -1, $version = 0)
Cette dernière est la clef de voute de toute recherche sur les réseaux. Elle permet de rechercher n'importe quel type de réseau en relation avec un réseau fournit en paramètre (ie : réseaux contenants, contenus, ou égaux à un réseau donné). A noter que pour chercher les réseaux contenant une adresse IP, il suffit de rechercher les réseaux contenant un réseau dont l'adresse est l'adresse IP recherchée et le masque est le masque où tous les bits sont à 1 (ie : sous réseau de dimension 1).

patch Magnifier (182 KB) webmyster, 08/22/2011 06:24 PM

diff.patch Magnifier - Correction de petites bizarreries et ajout de la modification de la base de donnée par rapport à la version précédente du patch (197 KB) webmyster, 08/23/2011 10:17 AM