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 un moyen d’augmenter le nombre de serveurs DNS et les placer derrière une seule IP avec un méchanisme de load-balancing.
C’est la que le Anycast entre en jeu. Il permet à plusieurs appareils de partager une addresse IP mais que, contrairement au multicast, chaque requête soit traitée par un seul appareil.
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 utilisant les méchanismes de routage.

L’ECMP (Equal Cost Multipath)

En BGP, c’est la route la plus courte qui est sélectionnée, cependant, dans le cas d’un déploiement de plusieurs serveurs en parallèle, les routes aurons toutes la même métrique.
C’est là que l’ECMP entre en jeu, il permet au routeur 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.
En revanche, 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 son IP partagée.
Pour faire du BGP sur Linux, nous utiliserons FRR (Free Range Routing) qui est un ensemble de daemons de routage sous Linux. nous pourrions aussi utiliser 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 partageant l’addresse 10.1.1.1.
Chaque machine Debian fera tourner CoreDNS et FRR. L’addresse 10.1.1.1 sera attribuée à une interface dummy et le traffic à destination de celle-ci sera routé par la machine Debian.
La session BGP entre la passerelle et les serveurs se fera en eBGP, ainsi, la passerelle pourra réanoncer l’addresse 10.1.1.1 à d’autres routeurs BGP potentiels.
La configuration entre les serveurs DNS étant identique à 99%, nous pourrons facilement en ajouter d’autres pour suivre la croissance de notre infrastructure.

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

Configuration du réseau

Sur chacune des machines Debian, nous allons commencer par créer l’interface dummy, nommée vip0 avec l’addresse 10.1.1.1/32

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

Pour rendre la création de l’interface persistente au démarrage, nous pouvons ajouter 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 d’un serveur Ubuntu, le gestionnaire de réseau est systemd-networkd, la configuration sera donc ajoutée à /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 peut se faire avec une simple commande APT.

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

nous allons appliquer les changements en redémarrant FRR.

systemctl restart frr

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

vtysh

… et entrer dans le contexte de 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