Dans les infrastructures d’entreprise classique, le système DNS est souvent composé de deux serveurs (un primaire et un secondaire).
Bien que cette configuration fonctionne parfaitement pour des infrastructures de taille moyenne, elle peut présenter des limites lorsque le nombre de clients augmente.
Il faudrait trouver une solution qui nous permette d’augmenter le nombre de serveurs DNS tout en gardant une ou deux addresses IP à configurer sur les clients.
C’est la que le Anycast entre en jeu. Il permet à plusieurs appareils de partager une addresse IP et de load-balancer les requettes.
Si nous nous contentons d’attribuer la même addresse IP à plusieurs machines sur le même réseau L2, nous aurons un conflit d’addresses IP.
Il faudra donc passer par du L3 en faisant du routage.

L’ECMP (Equal Cost Multipath)

En BGP, c’est la route la plus courte qui est sélectionnée.
Dans le cas d’un déploiement de plusieurs serveurs en parallèle, les routes aurons une métrique égale.
C’est là que l’ECMP entre en jeu, il permet aux routeurs de répartir le traffic entre plusieures routes de même métrique.
Dans le cas de requêtes unitaires comme le DNS, cela est très simple à mettre en place.
Cependant, dans le cas de sessions TCP, nous devrons garder chaque client sur le même serveur.
Pour cela, il est possible de faire du loadbalancing basé sur le tuple IP Source, Port Source, IP Destination, Port Destination pour décider vers quel serveur router le paquet.
Comme le Port et l’IP source de changent pas dans une session TCP, les paquets seront toujours routés vers le même serveur.

FRR et BGP

Pour ne pas avoir à gérer des routes à la main, chaque serveur établira une session BGP avec le routeur et annoncera sa VIP partagée.
Pour faire du BGP sur Linux, nous utiliserons FRR (Free Range Routing) qui est un ensemble de daemons de routage sous Linux.
Il est aussi possible de faire du BGP sour Linux avec Bird, cependant j’ai moins d’expérience dessus.

L’infrastructure DNS

Schema
Pour notre exemple, nous mettrons en place une petite infrastructure avec une passerelle VyOS connectée à deux machines Debian et l’IP Anycast 10.1.1.1.
Chaque machine Debian fera tourner une instance de FRR et CoreDNS et aura une interface Dummy sur lequel CoreDNS écoutera et qui portera l’IP 10.1.1.1.
La session BGP entre la passerelle et les serveurs sera en eBGP, c’est pour cela que la passerelle aura un numéro d’AS différent des serveurs.
J’utilise eBGP, comme ca, si notre passerelle etablit une session BGP avec d’autres routeurs, elle pourra facilement réanoncer les routes vers 10.1.1.1 sans besoin de route-reflector.
La configuration entre les serveurs DNS étant identique à 99%, nous pourrons facilement en ajouter pour répondre aux besoins de l’infrastructure.

Configuration de deb-dns-1 et deb-dns-2

Configuration du réseau

Pour que nos serveurs puissent avoir l’addresse 10.1.1.1, nous créerons une interface vip0 de type dummy.

modprobe dummy
ip link add vip0 type dummy
ip addr add 10.1.1.1/32 dev vip0

Nous pouvons rendre la création de l’interface persistente au redémarrage en ajoutant ces lignes à /etc/network/interfaces

auto vip0
iface vip0 inet static
        address 10.1.1.1/32
        pre-up ip link add vip0 type dummy
        post-down ip link del vip0

Dans le cas ou le gestionaire de réseau serait systemd-networkd, la configuration se fera dans /etc/netplan/50-cloud-init.yaml

network:
  version: 2
  ethernets:
    eth0:
      ...
    vip0:
      addresses: ["10.1.1.1/32"]

Pour que les deux serveurs puissent router les requettes vers l’interface vip0, nous devrons activer le forwarding ipv4.

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p

Configuration de FRR

Le paquet FRR est présent dans les repository APT de Debian et Ubuntu, l’installation est donc assez simple.

apt update && apt install frr

Avant de commencer à configurer FRR, nous devrons activer le daemon BGP dans /etc/frr/daemons.

# /etc/frr/daemons
bgpd=no -> yes

Pour appliquer les changements, nous pouvons redémarrer FRR.

systemctl restart frr

Maintenant, nous pouvons entrer dans la console de configuration de FRR avec cette commande.

vtysh

Une fois dans la console, nous pouvons entrer en mode configuration.

configure

Nous allons d’abord définir les paramètres par défaut de FRR en mode datacenter (plus adapté pour notre cas de figure).

frr defaults datacenter

Nous allons ensuite créer deux prefix-list, une qui refusera toutes les routes annoncées par la passerelle et l’autre qui n’annoncera que 10.1.1.1.

ip prefix-list PERMIT-IN deny any
ip prefix-list PERMIT-OUT permit 10.1.1.1/32

Finalement, nous pouvons configurer le bgp

router bgp 65421
  bgp router-id 192.0.2.10  # 192.0.2.11 pour deb-dns-2
  neighbor 192.0.2.1 remote-as 64520

  address-family ipv4 unicast
    network 10.1.1.1/32
    neighbor 192.0.2.1 prefix-list PERMIT-IN in
    neighbor 192.0.2.1 prefix-list PERMIT-OUT out
    exit
  exit
exit

Nous pouvons maintenant sauvegarder la configuration

copy running-config startup-config

Configuration de la passerelle VyOS

Dans notre exemple, nous utiliserons une passerelle VyOS.
La syntaxe peut différer entre les différents types de routeurs existants à un autre mais le concept reste le même.
Pour commencer, nous allons activer l’ECMP et définir le router-id de la passerelle.

set protocols bgp parameters bestpath as-path multipath-relax
set protocols bgp parameters router-id 192.0.2.1

Nous pouvons ensuite configurer no deux voisins deb-dns-1 et deb-dns-2.
La ligne set protocols neighbor <IP> bfd sert à activer le BFD, qui nous permet de détecter rapidement quand le lien avec le voisin est down. Cela nous permettra de minimiser le downtime en cas de perte d’un lien.

set prototols bgp neighbor 192.0.2.10 bfd
set protocols bgp neighbor 192.0.2.11 bfd
set protocols bgp neighbor 192.0.2.10 peer-group 'dns-anycast'
set protocols bgp neighbor 192.0.2.11 peer-group 'dns-anycast'
set protocols bgp neighbor 192.0.2.10 remote-as '64521'
set protocols bgp neighbor 192.0.2.11 remote-as '64521'

Pour permettre la création des règles de filtrage des routes annoncées et recues, nous allons créer une prefix-list contenant notre addresse ip anycast.

set policy prefix-list dns-anycast-v4-addr rule 10 action permit
set policy prefix-list dns-anycast-v4-addr rule 10 prefix 10.1.1.1/32

Ensuite, nous pourrons créer des routes-maps, qui, comme pour les filtres FRR, autoriserons la route 10.1.1.1 en entrée et n’en annoncerons aucune.

set policy route-map dns-anycast-v4-in rule 10 action permit
set policy route-map dns-anycast-v4-in rule 10 match ip address prefix-list dns-anycast-v4-addr
set policy route-map dns-anycast-v4-out rule 10 action deny

Nous allons maintenant créer le peer-group dns-anycast qui englobera nos neighbors deb-dns-1 et 2.

set protocols bgp peer-group dns-anycast address-family ipv4-unicast maximim-prefix '1'
set protocols bgp peer-group dns-anycast address-family ipv4-unicast route-map import 'dns-anycast-v4-in'
set protocols bgp peer-group dns-anycast address-family ipv4-unicast route-map export 'dns-anycast-v4-out'

Pour finir, nous appliquerons nos règles de filtrage au peer-group

set proto bgp peer-group pub-localbm address-family ipv4-unicast maximum-prefix 1
set proto bgp peer-group pub-localbm address-family ipv4-unicast route-map export dns-anycast-out-v4
set proto bgp peer-group pub-localbm address-family ipv4-unicast route-map import dns-anycast-in-v4

Une fois la configuration terminée, il ne nous reste plus qu’à l’appliquer et la sauvegarder.

commit
save

Vérifications

Pour vérifier que la session est correctement montée, nous pouvons utiliser cette commande sur VyOS et FRR

show bgp summary

Elle devrait afficher un output de ce type avec un PfxSnt sur les serveurs dns et un PfxRcd sur VyOS

IPv4 Unicast Summary (VRF default):
BGP router identifier 192.0.2.10, local AS number 64521 vrf-id 0
BGP table version 13
RIB entries 1, using 192 bytes of memory
Peers 1, using 724 KiB of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
192.0.2.1       4      64520        57        64        0    0    0       1m            0        1 N/A

Total number of neighbors 1

Sur VyOS

IPv4 Unicast Summary:
BGP router identifier 192.0.2.1, local AS number 64520 VRF default vrf-id 0
BGP table version 1358
RIB entries 2, using 124 bytes of memory
Peers 2, using 47 KiB of memory
Peer groups 1, using 64 bytes of memory

Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
192.0.2.10      4      64521        89        74        1    0    0       2m            1        0 N/A
192.0.2.11      4      64521        92        82        1    0    0       2m            1        0 N/A

Total number of neighbors 2

La commande show ip route bgp sur VyOS nous permet de voir les routes annoncées par nos voisins.
Nous pouvons constater que pour la route menant à 10.1.1.1, nous avons deux nexthops avec la même métrique.
Le routeur va load-balancer le traffic entre les deux serveurs DNS.
Dans le cas ou un des serveurs arreterait d’annoncer sa VIP ou que le lien tomberait, il sera simplement retiré de la liste des next-hops.

Codes: K - kernel route, C - connected, L - local, S - static,
       R - RIP, O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, F - PBR,
       f - OpenFabric, t - Table-Direct,
       > - selected route, * - FIB route, q - queued, r - rejected, b - backup
       t - trapped, o - offload failure

B>* 10.1.1.1/32 [20/0] via 192.0.2.10, eth1, weight 1, 3m
  *                    via 192.0.2.11, eth1, weight 1, 3m

Installation de CoreDNS

Pour finir, nous allons faire une installation simple d’un serveur CoreDNS.
Nous créerons une zone example.local avec quelques enregistrements au format Bind.
Les requettes vers les zones inconnues seront transmises à 1.1.1.1.
Il n’existe pas de paquet coredns dans les repository apt de debian.
Nous allons donc télécharger le binaire coredns depuis leur repo Github puis créer la configuration et un service systemd. Pour télécharger le binaire, il suffit de se rendre sur https://github.com/coredns/coredns/releases et de télécharger l’archive coredns_<version>_linux_<archi>.tgz.\

https://github.com/coredns/coredns/releases/download/v1.14.2/coredns_1.14.2_linux_amd64.tgz

Ensuite, nous pouvons extraire l’archive avec la commande tar et placer le binaire dans /usr/bin

tar xvf coredns_*.tgz
mv coredns /usr/bin/

La configuration de coredns est définie dans /etc/coredns/Corefile, pour cela nous devrons créer le dossier /etc/coredns puis, la configuration dans Corefile.\

mkdir /etc/coredns
vim /etc/coredns/Corefile
example.local.:53 {
    bind 0.0.0.0
    file /etc/coredns/db.example.local
    log
}
.:53 {
    bind 0.0.0.0
    forward . 1.1.1.1
    log
}

Nous pouvons ensuite créer le fichier /etc/coredns/db.example.local qui contiendra les enregistrements dns de la zone example.local

$TTL 120
@   IN  SOA ns.example.local. root.example.local. (
        2026020601 ; Serial
        120       ; Refresh
        1800       ; Retry
        1209600    ; Expire
        120  )    ; Minimum

@       IN      NS      ns.example.local
ns      IN      A       10.1.1.1   
@       IN      A       198.51.100.10
www     IN      A       192.51.100.11

Notre service coredns tournera avec le user coredns, nous devrons le créer.

useradd -M -s /bin/false coredns
chown -R coredns:coredns /etc/coredns

Pour finir, nous pouvons créer le service systemd qui fera tourner coredns dans /etc/systemd/system/coredns.service

[Unit]
Description=CoreDNS Server
After=network.target

[Service]
Type=simple
User=coredns
Group=coredns
ExecStart=/usr/bin/coredns -p 53  -conf /etc/coredns/Corefile
WorkingDirectory=/etc/coredns
Restart=always
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

[Install]
WantedBy=default.target
RequiredBy=network.target

Une fois le service créé, nous pouvons lancer le service

systemctl daemon-reload
systemctl enable coredns
systemctl start coredns

Nous pouvons vérifier l’état du service

systemctl status coredns

Si le service se plante au lancement, vérifiez que systemd-resolved n’est pas actif.
Lorsqu’il est actif, il ecoute sur le port 53 et coredns ne peut donc pas l’utiliser.

systemctl disable systemd-resolved
systemctl stop systemd-resolved

Une fois le service fonctionnel, nous pouvons tenter un nslookup vers le serveur dns depuis une autre machine du réseau pour vérifier qu’il fonctionne.

nslookup www.example.local 10.1.1.1
Server:         10.1.1.1
Address:        10.1.1.1#53

Name:   www.example.local
Address: 193.51.100.11