GANDI / Utiliser l'API XML-RPC pour faire de la fixation d'adresse IP dynamique

Si vous arrivez à lire ce post, c'est que j'ai réussi finalement à mettre en place tout ce que je voulais mettre en place au niveau de monserveur. En particulier, la mise à jour automatique de l'adrese IP par un script Python, en utilisant l'API XML-RPC de Gandi. Voici un petit compte-rendu de ce que j'ai compris / implémenté.

Voici ma situation : je suis titulaire d'un accès internet standard par fibre optique chez Orange. Je suis donc bénéficiaire d'une livebox pourrie qui passe son temps à se mettre en vrac, et d'une adresse IP dynamique. Je veux mettre du contenu en ligne, pour cela je dispose d'un petit ordinateur sous Debian. Je dispose également d'un nom de domaine chez Gandi. Problématique : comment faire pointer mon nom de domaine vers mon serveur ? Sachant que:

  1. L'adresse IP change tout le temps (enfin, grosso-modo toutes les 48h),
  2. Il est hors de question que la mise à jour soit faite à la main, il faut un outil automatisé, on est plus au XIXe siècle,
  3. Plutôt crever qu'utiliser un gestionnaire d'IP dynamique (pour avoir essayé DynDNS et No-IP il fut un temps, je n'y reviendrait JAMAIS).

Gandi fournit une interface XML-RPC, permettant de faire de l'administration sans passer par leur interface web. On peut donc faire du code qui gère les paramètres du nom de domaine, vu que XML-RPC est normé et qu'il y a plein de packages permettant de l'utiliser en C, Python, PHP, etc. A partir de là tout devient facile : yaka faire un programme qui mette à jour l'adresse IP automatiquement, puis créer une tâche cron sur le serveur qui lance ce programme régulièrement. Bon, il y a quelques subtilités, et j'ai passé du temps à galérer dessus donc je publie ce CR pour ceux qui, comme moi, ont buté sur l'implémentation (très simple au demeurant) de cette fonctionnalité.

Comment gère-t-on un nom de domaine ?

Un registrar (ici Gandi) fournit à un client (vous, moi) un nom de domaine, enregistré auprès des autorités qui vont bien. A ce client, ils fournissent des credentials de connexion à un compte (login / mdp), donnant accès à une interface web, permettant d'administrer le nom de domaine. Pour un nom de domaine, on peut administrer plein de choses, ce qui m'intéresse ici est l'adresse IP vers laquelle le nom de domaine va pointer.

Périodicité ? Gandi limite les accès à son API à 30 par seconde max. Orange met à jour les adresses IP toutes les semaines environ. Il y a de la marge de choix entre les deux. Ce qui va être dimensionnant au final est le délai acceptable de déconnexion du serveur (le temps entre un changement d'adresse IP de al part d'Orange et la mise à jour de la nouvelle adresse par le serveur). Ceux qui utilisent cette API pour cette fonction disent faire une tâche cron à 5 minute. Ce délai me parait raisonnable.

XML-RPC chez Gandi, comment ça marche ?

Je vais décrire le script au fur et à mesure, c'est plus simple. Il faut se connecter à son compte Gandi en premier lieu. Gandi fournit une clé d'accès à l'API. Elle fait 24 caractères et sert de login/mdp.

D'abord, pour se faciliter la vie, on créée un handle vers l'interface Gandi : api = xmlrpclib.ServerProxy('https://rpc.gandi.net/xmlrpc/')

Chaque action sur ce handle nécessitera la clé d'API. On va la mettre dans une variable, plus facile à manipuler : apikey = XXXXXXXXXXXXXXXXXXXXXXXX

On va aussi avoir besoin de notre nom de domaine : mydomain = 'randogadron.eu'

A ce niveau, on peut déjà récupérer des infos pour vérifier que ça marche : api.domain.list(apikey)
Donne la liste des domaines correspondant à la clé. Dans mon cas, il n'y en a donc qu'un seul : randagodron.eu api.domain.info(apikey)
Donne des infos sur ce domaine. Rentrons dans le détail, ce qui nous intéresse ce sont les zones (j'y reviens plus tard) : api.domain.zone.count(apikey)
Donne le nombre de zone que vous avez en stock. api.domain.zone.list(apikey)
Renvoie une liste des zones dont vous disposez.

BIEN. Maintenant, venons-en au fait : pour mettre à jour l'adresse IP, il faut changer l'adresse IP qui est inscrite dans le champ ("record") correspondant dans la zone que l'on a activé pour le nom de domaine en question.

Zone ? Record ?

A un nom de domaine, on associe une zone DNS, qui est en fait une sorte de fichier de configuration qui (entre autres) décrit tous les sous-domaines. Donc, dans cette zone, il y a un champ qui dit : 'www' -> xxx.xxx.xxx.xxx (adresse IP). Ce champ est appelé un "record", il est défini par un nom, un type, un ID et une valeur. Le nom est le nom du sous-domaine. Pour www.randagodron.eu, randagodron.eu est le nom de domaine, www est le sous-domaine, dans la zone il y a donc un record www. Le type c'est soit une adresse IPv4, soit une adresse IPv6, soit un autre nom de domaine, soit d'autres trucs que je serai bien en peine de décrire. Dans notre cas, on aura un champ IPv4, donc 'A'. L'ID est un numéro pour identifier le champ. De façon absolue dans l'ensemble des champs dans l'ensemble des zones, je suppose. La valeur est le contenu du champ. Donc, il y a un record nommé www, avec un ID (OSEF), de type A, dont la valeur est l'adresse IP du serveur. Donc, mettre à jour l'adresse IP du serveur pour mon site, c'est mettre à jour la valeur du record www dans la zone associée à mon nom de domaine. Concernant les zones, si j'ai bien compris on peut en créer autant que l'on veut, grosso-modo il y a un fichier qui contient tous les champs/records pour chaque zone que l'on créée. Pour chaque nom de domaine on doit associer une zone -> on associe un fichier de zone. Gandi fournit un fichier par défaut. Il faut le copier et l'éditer pour le faire évoluer. Donc, au début, il faut en créer un à soi. Par la suite on va mettre à jour ce fichier de zone nouvellement créé. Un fichier de zone actuellement utilisé sur un nom de domaine ne peut pas être édité. C'est pour cela que dans l'interface Gandi on va créer une copie que l'on peut éditer, sachant que l'ancienne reste vide jusqu'à ce que l'on active la nouvelle copie (modifiée ou non).

Chaque zone est identifiée par un numéro à 6 chiffres, c'est le "zone ID". Pour le trouver, il faut ouvrir la zone ou le fichier de zone, le numéro de zone apparait dans l'adresse (https://www.gandi.net/admin/domain/detail/XXXXXX).

On va avoir besoin de cet ID :
zone_id = XXXXXX

Maintenant on peut obtenir des détails sur la zone en question, vérifier qu'on tape bien dans la bonne : api.domain.zone.count(apikey) donne le nombre de zones créées au total.
api.domain.zone.info(apikey, zone_id) donne les infos sur la zone correspondant à l'ID en paramètre.
Vu qu'il y a une gestion de version automatique des fichiers de zone par Gandi, on peut accéder aussi aux différentes versions de fichier de zone. Mettre un numéro de version 0 correspond à la version en cours de fonctionnement. On approche du but. Maintenant on peut regarder les records de la zone:

api.domain.zone.record.list(apikey, zoneid, versionid) renvoie la liste de tous les records de la zone. Pour mettre à jour tout le nom de domaine, on va mettre à jour le champ '@' de type A, et tous les autres pointeront en CNAME vers randagodron.eu. Comme ça j'aurais un seul champ à mettre à jour.
Pour accéder directement à un champ précis, on va utiliser les filtres optionnels. Pour trouver le champ qui nous intéresse, il faut filtrer par nom.

api.domain.zone.record.list(apikey, zone_id, version, myrecord)

version est le numéro de version de la zone que l'on veut lister. Si on met 0 on a la version active. myrecord est le filtre que l'on va appliquer. Le format des filtres est un tableau de la forme {'champ1' : 'nom1', 'champ2' : 'nom2', ...}. Par exemple, pour chercher le record '@' de type 'A', on va créer un filtre {'name : ' @', 'type' : 'A'}. C'est beau comme une expression régulière en PHP ...

myrecord = {'name': '@', 'type': 'A'}
api.domain.zone.record.list(apikey, zone_id, 0, myrecord)

Boum ! J'obtiens juste le record qui m'intéresse. On va en profiter pour garder en mémoire l'adresse IP actuellement écrite dans le fichier de zone. On va utiliser la méthode get pour récupérer le champ qui nous intéresse dans ce record. get ne marche pas avec une liste, il faut donc sélectionner l'un des éléments du retour de fonction précédent. Du fait du filtre ultra-sélectif que l'on a choisi, il n'y a qu'un seul élément dans la liste, qui sera donc l'élément 0.

oldip = api.domain.zone.record.list(apikey, zone_id, 0, myrecord)0.get('value')

Nous avons suffisamment observé, il est temps d'agir. Pour éviter de se retrouver bloqué bêtement en cas d'indispo de Gandi, on fait un try / catch.

try:

   ... Faire le traitement

except urllib2.HTTPError, xmlrpclib.ProtocolError:

   ... Sortir proprement

Dans le script que j'ai récupéré l'erreur est loggée.

Voyons le traitement. On va récupérer l'adresse IP actuelle du serveur, la comparer à celle déjà inscrite dans le fichier de zone (oldip), et si elle est différente mettre à jour le fichier de zone. Pour obtenir l'adresse actuelle du serveur, pas trop le choix, il faut utiliser un service extérieur. Le site ifconfig.me renvoie l'adresse IP de l'ordinateur qui s'y connecte. C'est ce qu'il nous faut. Au début du script on enregistre l'adresse de ce site:

url_page = 'http://ifconfig.me/ip'

Et on l'utilise :

f = urllib2.urlopen(url_page, None, 10) On ouvre l'URL
data = f.read() On récupère ce que le site nous envoie
f.close() On se déconnecte.
@@ pattern = re.compile('\d+\.\d+\.\d+\.\d+')@ On créé un pattern qui décrit ce que l'on cherche, à savoir quatre char séparés par des points, le tout entouré de guillemets.
@@ result = pattern.search(data, 0)@ On cherche ce pattern dans data.
if result == None:
print("Pas d'ip dans cette page.") Erreur : pas d'adresse IP dans data
@@ sys.exit()@ OK THX BYE
else:
currentip = result.group(0) On récupère l'adresse.

Maintenant qu'on a l'adresse actuelle et celle écrite dans le fichier de zone, on les compare. Si ils sont identiques, c'est que le FAI n'a pas encore changé l'adresse, dans ce cas autant ne rien faire. Sinon il faut mettre à jour le fichier de zone. Comme vu plus haut, on ne peut pas modifier un fichier de zone en activité, on est obligés de faire une nouvelle version, de l'éditer, puis de l'activer. ET BIEN SOIT ! FAISONS CA !



if oldip != currentip: version = api.domain.zone.version.new(apikey, zone_id) On créé une nouvelle version de la zone, on choppe au passage le numéro de version de ce nouveau fichier
api.domain.zone.record.delete(apikey, zone_id, version, myrecord) On efface le record
myrecord'value' = currentip On recréée un record avec la nouvelle adresse IP du serveur.
myrecord'ttl' = myttl Une valeur qui doit être précisée. On met 300 par défaut, on peut le définir au début du script, c'est plus propre
api.domain.zone.record.add(apikey, zone_id, version, myrecord) On ajoute un nouveau record contenant ce que l'on veut
api.domain.zone.version.set(apikey, zone_id, version) On active le nouveau fichier de zone
api.domain.zone.set(apikey, mydomain, zone_id) On valide

Et voilà.

Ajouter un rétrolien

URL de rétrolien : http://blog.randagodron.eu/index.php?trackback/10

Haut de page