Un serveur DNS dynamique avec Bind9


Où l’on va voir comment se passer (sous certaines conditions) des fournisseurs de DNS dynamique.

  Merci aux copains qui m’ont aidé dans la compréhension et de déverminage de cette fonctionnalité.

Je suis possédé par une maison de campagne. Celle-ci est mal reliée aux Internets par une connexion ADSL fournie par Orange. À chaque reboot de la « Livebox », celle-ci change d’adresse IP et le peu d’équipement que j’ai derrière devient inaccessible. J’ai essayé les services de type DynDNS pour mettre à jour un enregistrement DNS. Malheureusement, celui-ci ne correspond en rien avec mon domaine à moi que j’ai. Mais je loue une machine reliée aux Internets et équipée d’une adresse IP fixe. Celle-ci héberge un serveur DNS animé par Bind9. C’est donc lui qui va gérer la zone dynamique de mon domaine avec l’adresse IP de ma « livebox » campagnarde.

Prérequis

Vous aurez besoin de quelques prérequis pour mettre en place cette solution :

  • un serveur DNS en état de marche ;
  • savoir vous en servir (éditer une zone) ;
  • savoir vous débrouiller avec le shell.

Sur le serveur

Nous devons créer une nouvelle zone (en fait un sous-domaine de celui qui existe déjà). La mienne sera externe.example.com.

Ajout de la gestion de la nouvelle zone

Nous allons ajouter la gestion de cette zone dans la configuration de Bind9. Cela se fait à la suite des zones existantes, dans le fichier /usr/local/etc/namedb/named.conf.local1.

1
2
3
4
5
zone "externe.example.com" {
    type master;
    file "/usr/local/etc/namedb/working/db.externe.example.com";
    allow-update { key externe.example.com.key ;};
};

Ce qui signifie :

  • définition de la zone “externe.example.com" ;
  • de type master ;
  • le fichier de définition est celui-ci ;
  • la mise à jour est autorisée avec cette clef.

Génération de la clef

Cette clef, qui sera présente sur le serveur et le client, devra demeurer secrète, est utilisée pour certifier les échanges. Seules les machines disposant cette clef pourront mettre à jour la zone.

1
2
3
4
5
$ sudo  ddns-confgen -r /dev/urandom -q -a hmac-md5 -k externe.example.com -s externe.example.com. | tee /usr/local/etc/namedb/externe.example.com.key
key "externe.example.com" {
    algorithm hmac-md5;
    secret "RXGQ0vVzbKJVxrXneLLf/w==";
};

Création du fichier de la zone externe.example.com

Ce fichier de zone ne comporte que le SOA et un enregistrement de type NS. Il va permettre à Bind9 de connaître la zone. Vous devriez être capable de lire et comprendre ce fichier. Ce fichier se nomme db.externe.example.com

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ORIGIN .
$TTL 28800      ; 8 hours
externe.example.com      IN SOA  ns.example.com. root.example.com. (
                                1          ; serial
                                3600       ; refresh (1 hour)
                                600        ; retry (10 minutes)
                                604800     ; expire (1 week)
                                300        ; minimum (5 minutes)
                                )
                        NS      ns.example.com.
$ORIGIN externe.example.com.

Vérification de la prise en compte de nos modifications

Nous allons vérifier que notre fichier de zone est correct, que la configuration l’est aussi et nous allons redémarrer Bind pour que nos modifications soient prises en compte.

Vérification de la configuration

Pour vérifier la configuration, nous allons utiliser named-checkconf :

1
2
3
4
$ sudo named-checkconf -z named.conf
...
zone externe.example.com/IN: loaded serial 1
...

Vérification de la zone

Nous avons, pour vérifier la zone, à notre disposition l’utilitaire named-checkzone :

1
2
3
$ sudo named-checkzone externem.example.com db.externem.example.com
zone externem.example.com/IN: loaded serial 1
OK

Redémarrage de Bind et test de la résolution de la zone

Nous redémarrons Bind le plus simplement du monde :

1
2
3
4
5
$ sudo service named restart
Password:
Stopping named.
Waiting for PIDS: 60500.
Starting named.

Et nous testons la résolution de la zone. Cela se fait de préférence depuis une autre machine que le serveur.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
dig @ns.example.com externe.example.com

; <<>> DiG 9.8.3-P1 <<>> @ns.example.com externe.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6102
;; flags: qr aa rd; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;externe.example.com.        IN  A

;; AUTHORITY SECTION:
externe.example.com. 300 IN  SOA ns.example.com. root.example.com. 3 3600 600 604800 300

;; Query time: 41 msec
;; SERVER: 198.51.100.78#53(198.51.100.78)
;; WHEN: Fri Dec 25 21:48:27 2015
;; MSG SIZE  rcvd: 86

On dirait que la zone est connue.

Copie de la clef sur la machine cliente

Il nous reste à copier la clef sur la machine cliente.

1
$ scp externe.example.com.key 172.16.234.98

Sur la machine cliente

Ma machine cliente est une RaspBerryPi2. Doivent être installé les utilitaires DNS afin de disposer de la commande nsupdate.

1
$ sudo apt-get install dnsutils

Première mise à jour avec nsupdate

Nous allons commencer par utiliser le mode interactif de nsupdate pour comprendre comment fonctionne l’outil.

1
2
3
4
5
6
$ nsupdate -k externe.example.com.key
> server ns.example.com
> zone externe.example.com
> update add raspi.externe.example.com 60 A 172.16.234.98
> send
> quit

En interrogeant le DNS nous devrions voir apparaître notre machine dans la zone :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ dig @ns.example.com raspi.externe.example.com

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> @ns.example.com raspi.externe.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65285
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;raspi.externe.example.com.     IN  A

;; ANSWER SECTION:
raspi.externe.example.com.  60  IN  A  172.16.234.98

;; AUTHORITY SECTION:
externe.example.com. 28800   IN  NS  ns.example.com.

;; ADDITIONAL SECTION:
ns.example.com.    300 IN  A   198.51.100.78

;; Query time: 36 msec
;; SERVER: 198.51.100.78#53(198.51.100.78)
;; WHEN: Sat Dec 26 11:07:06 2015
;; MSG SIZE  rcvd: 94

Houra, ça marche. Toutes vos machines qui utilisent ns.example.com comme resolveur peuvent connaître la RaspberryPI.

Automatisation

Il faut maintenant automatiser cette mise à jour. Il va nous falloir déterminer la fréquence de mise à jour. Est-ce qu’une semaine, une jour ou une heure sans pouvoir joindre la machine est handicapant ? J’ai choisi une journée, mais libre à vous de changer ce réglage. Nous allons faire un script shell qui va devoir vérifier quelques petites choses avant de faire la mise à jour. En effet, celle-ci est inutile si l’adresse IP n’a pas changé. Inutile aussi si le serveur DNS est injoignable. Nous allons également devoir supprimer l’enregistrement avant de l’ajouter à nouveau. Voici mon script :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/bin/sh

# --- Script de mise à jour de l’enregistrement DNS de ma Raspi
# --- Ce script ne fonctionne qu’avec des IPv4

# -- Définiton de fonctions

valid_ip ()
{
    local ip=$1
    dummy=$(echo $ip | grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}")
    if [ $dummy ] ;
    then
        echo $ip | awk -F. '{
            if ( (($1>=0) && ($1<=255)) &&
                 (($2>=0) && ($2<=255)) &&
                 (($3>=0) && ($3<=255)) &&
                 (($4>=0) && ($4<=255)) ) {
                exit 0;
            } else {
                exit 255;
            }
        }'
    else
        return 255
    fi

}


# -- Définition de variables

# Ajout d'un point (.) à la fin de la zone.
# Erreur classique dans la gestion du DNS

ZONE="externe.example.com."
SERVER="ns.example.com"
IP=$(host -4 myip.opendns.com resolver1.opendns.com | tail -1 | awk '{print $NF}')
DATUM=$(date +"%Y%m%d-%T")
LOG="/var/log/update-dns.$(DATUM).log"
# Modification de la variable host en fonction de ce qui est
# retourné par la commande _hostname_

HOST=$(hostname)

KEYFILE=/root/externe.example.com.key


# Le serveur DNS est-il joinable ?
ping -c 2 $SERVER
if [ $? = 255 ] ; then
    echo "Serveur DNS injoinable. Fin" >> $LOG
    exit 255
fi

# Test adresse IP

valid $IP
if [ $? = 255 ] ; then
    echo "$IP n\'est pas une adresse IP valide. Fin" >> $LOG
    exit 255
fi

# L’adresse IP a-t-elle changé ?

oldIP=$(dig @$SERVER $HOST.$ZONE A +short )

if [ $oldIP = $IP] ; then
    echo "Rien à faire. Fin" >> $LOG
fi

# Tout semble OK, faisons la mise à jour

cat <<EOF | nsupdate -k $KEYFILE
server $SERVER
zone $ZONE
update delete $HOST.$ZONE
update add $HOST.$ZONE 60 IN A $IP
send
EOF
>> $LOG 2>&1

exit 0

Et IPv6 ?

Depuis l’écriture de billet, je me suis lancé dans IPv6 pour de vrai. Il est donc logique que j’ai envie de joindre ma raspberry pi via son adresse V6.

J’ai donc ajouté un petit morceau à mon script pour prendre en compte ce changement.

  Je ne cherche pas à valider l’adresse IPv6, celle-ci étant, dans mon script, donnée par ifconfig, il y a des chances qu’elle soit valide.

  Je ne met pas en place tout ce qui peux l’être pour IPv6, seule l’adresse publique m’interesse ici.

1
2
3
4
5
6
7
8
9
# IPv6

oldIPv6=$(dig @$SERVER $HOST.$ZONE AAA +short)
newIPv6=$(ifconfig eth0 | grep inet6 | awk '{print $2}'| grep -v fe80)
if [ $oldIPv6 = $newIPv6 ] ; then
    echo "Rien à faire. Fin" >> $LOG
else
    echo "Changement de ${oldIPv6} → {newIPv6}" >> $LOG
fi

Et dans la partie de mise à jour de la zone, on ajoute l’adress V6 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cat <<EOF | nsupdate -k $KEYFILE
server $SERVER
zone $ZONE
update delete $HOST.$ZONE
update add $HOST.$ZONE 60 IN A $IP
update add $HOST.$ZONE 60 IN AAAA $newIPv6
show
send
quit
EOF
>> $LOG 2>&1

Il reste à tester le script et à le mettre en production avec cron.

Mettre le script dans cron

Nous allons le mettre dans la crontab de l’utilisateur root.

1
2
3
$ sudo crontab -e

10 4 * * * /root/updatedns.sh

Le script est lancé automatiquement tous les jours à 4 heures 10 du matin.

Conclusion

Il n’est pas très difficile de mettre en place une zone dynamique avec Bind9 et nsupdate. Cette technique peut être utilisé en conjonction avec un serveur DHCP pour que toutes les machines qui obtiennent une adresse via ce protocole soit enregistré dans une zone spécifique du DNS.

Mise à jour du 22 décembre 2018

Petite relecture et mise à jour du script par rapport à la réalité.

Mise à jour du 4 novembre 2019

Ajout de la gestion d’IPv6

Mise à jour du 5 octobre 2021

Mise à jour de la commandes d’obtention des adresses IP et des vérifications.


  1. les chemins vers mes fichiers sont ceux d’une installation FreeBSD. Ils différent sur une installation Linux, mais je suis sûr que vous saurez faire la convertion. ↩︎

powered by FreeBSD