Astuce : un petit script pour alterner entre vos deux stacks DNS-DoT et DNS-DoH

l’Almanet doLys Gnu/Linux – Open Source – Entreprises Forums L’almanet doLys Open Source Astuce : un petit script pour alterner entre vos deux stacks DNS-DoT et DNS-DoH

  • This topic is empty.
Affichage de 1 message (sur 1 au total)
  • Auteur
    Articles
  • #12841
    nam1962nam1962
    Keymaster

      Basculer facilement entre DoT, DoH, fallback neutre et fallback filtrant avec un script Linux

      Si, comme moi, vous souhaitez disposer de plusieurs configurations DNS sécurisées tout en limitant la censure DNS, voici un outil pratique pour basculer facilement entre plusieurs modes :

      • une stack DoT avec Pi-hole et Unbound ;
      • une stack DoH / DNSCrypt avec Pi-hole et dnscrypt-proxy ;
      • un fallback neutre pour retrouver une résolution DNS hors FAI ;
      • un fallback filtrant externe pour conserver un minimum de blocage quand Pi-hole est indisponible.

      L’objectif n’est pas de faire joli. L’objectif est de ne pas rester bloqué quand une stack DNS tombe, quand un résolveur devient inaccessible, ou quand un fournisseur d’accès commence à mentir sur certaines réponses DNS.

      Rappel : DoT, DoH et modes fallback

      • DNS over TLS (DoT) : utilise TLS sur le port 853. C’est lisible comme du trafic DNS chiffré, mais simple, robuste et bien adapté à une stack Pi-hole + Unbound.
      • DNS over HTTPS (DoH) : transporte les requêtes DNS dans HTTPS. Il contourne mieux les blocages DNS classiques et évite de passer par le résolveur du fournisseur d’accès.
      • DNSCrypt : autre protocole de DNS chiffré, géré par dnscrypt-proxy. Il est utile en complément de DoH pour augmenter la résilience.
      • Fallback neutre : utilise des DNS publics non filtrants pour retrouver une résolution hors FAI si les stacks locales sont arrêtées ou cassées.
      • Fallback filtrant externe : utilise des DNS publics filtrants pour conserver un minimum de blocage publicitaire quand Pi-hole est indisponible.

      Modes fallback utilisés

      Fallback neutre

      Ce mode sert à retrouver une résolution DNS hors FAI quand les stacks locales sont arrêtées ou cassées. Il ne remplace pas Pi-hole et ne fournit pas de filtrage publicitaire.

      185.222.222.222  # DNS.SB public-dns-a
      45.11.45.11      # DNS.SB public-dns-b

      UncensoredDNS reste un très bon choix en DoT et DoH, mais son DNS clair sur le port 53 n’est plus disponible. Il n’est donc pas utilisé ici comme fallback DNS classique dans /etc/resolv.conf.

      Fallback filtrant externe

      Ce mode conserve un filtrage DNS minimal quand Pi-hole est indisponible, en déléguant temporairement le filtrage à des résolveurs externes.

      94.140.14.14  # AdGuard DNS filtrant
      45.90.28.0    # NextDNS filtrant configurable

      Ce mode est un choix explicite. Il n’est pas le chemin normal de résolution.

      Prérequis

      • Docker et Docker Compose installés pour les stacks DoT et DoH.
      • Deux dossiers Docker déjà configurés :
        • DoT : ~/docker/dns-dot, avec Unbound et Pi-hole ;
        • DoH : ~/docker/dns-doh, avec dnscrypt-proxy et Pi-hole.
      • dig, fourni par le paquet dnsutils sur Debian ou bind sur Arch.
      • yad si vous voulez l’interface graphique.

      Installer yad

      Sur Debian et dérivés :

      sudo apt update
      sudo apt install -y yad dnsutils

      Sur Arch Linux et dérivés :

      sudo pacman -S yad bind

      1. Script en ligne de commande

      Créez le script dans votre répertoire bin :

      mkdir -p ~/bin
      nano ~/bin/switch-dns

      Contenu du script :

      #!/bin/bash
      set -euo pipefail
      
      REAL_USER="${SUDO_USER:-${USER:-$(whoami)}}"
      USER_HOME="$(getent passwd "$REAL_USER" | cut -d: -f6)"
      
      DNS_PIHOLE_IP="127.0.0.1"
      
      DOT_DIR="$USER_HOME/docker/dns-dot"
      DOH_DIR="$USER_HOME/docker/dns-doh"
      
      LOGFILE="$USER_HOME/dns-check.log"
      
      NEUTRAL_DNS1="185.222.222.222"  # DNS.SB public-dns-a
      NEUTRAL_DNS2="45.11.45.11"      # DNS.SB public-dns-b
      
      FILTERED_DNS1="94.140.14.14"   # AdGuard DNS filtrant
      FILTERED_DNS2="45.90.28.0"     # NextDNS filtrant configurable
      
      log() {
          echo "[switch-dns] $(date): $*" >> "$LOGFILE" 2>/dev/null || true
      }
      
      cap_log() {
          [ -f "$LOGFILE" ] || return 0
          tail -n 200 "$LOGFILE" > "$LOGFILE.tmp" 2>/dev/null && mv "$LOGFILE.tmp" "$LOGFILE" || true
          chown "$REAL_USER:" "$LOGFILE" 2>/dev/null || true
      }
      
      run_as_real_user() {
          if [ "$(id -u)" -eq 0 ] && [ "$REAL_USER" != "root" ]; then
              sudo -u "$REAL_USER" "$@"
          else
              "$@"
          fi
      }
      
      compose() {
          local dir="$1"
          shift
      
          [ -d "$dir" ] || return 0
          cd "$dir"
      
          if run_as_real_user docker compose version >/dev/null 2>&1; then
              run_as_real_user docker compose "$@"
          else
              run_as_real_user docker-compose "$@"
          fi
      }
      
      check_dns_at() {
          local server="$1"
          dig +time=3 +tries=1 whoami.akamai.net "@$server" >/dev/null 2>&1
      }
      
      docker_names() {
          run_as_real_user docker ps --format '{{.Names}}' 2>/dev/null || true
      }
      
      resolv_has() {
          local dns1="$1"
          local dns2="${2:-}"
      
          if [ -n "$dns2" ]; then
              grep -qE "^nameserver[[:space:]]+($dns1|$dns2)$" /etc/resolv.conf 2>/dev/null
          else
              grep -qE "^nameserver[[:space:]]+$dns1$" /etc/resolv.conf 2>/dev/null
          fi
      }
      
      resolv_managed_by_external_tool() {
          grep -qiE 'generated by|systemd-resolved|NetworkManager|resolvconf|DO NOT EDIT' /etc/resolv.conf 2>/dev/null
      }
      
      write_resolvconf_one() {
          local dns="$1"
          printf 'nameserver %s\n' "$dns" | sudo tee /etc/resolv.conf >/dev/null
          sleep 1
      }
      
      write_resolvconf_two() {
          local dns1="$1"
          local dns2="$2"
          printf 'nameserver %s\nnameserver %s\n' "$dns1" "$dns2" | sudo tee /etc/resolv.conf >/dev/null
          sleep 1
      }
      
      stop_stacks() {
          [ -d "$DOT_DIR" ] && compose "$DOT_DIR" down || true
          [ -d "$DOH_DIR" ] && compose "$DOH_DIR" down || true
      }
      
      show_state() {
          local state
          state="$(docker_names)"
      
          log "DOCKER_STATE='$state'"
      
          if echo "$state" | grep -q '^pihole$' && echo "$state" | grep -q '^unbound$' && check_dns_at "$DNS_PIHOLE_IP"; then
              if resolv_has "$DNS_PIHOLE_IP"; then
                  echo "Stack active : Unbound (DoT), DNS système local"
              else
                  echo "Stack active : Unbound (DoT), mais DNS système non local"
              fi
          elif echo "$state" | grep -q '^pihole$' && echo "$state" | grep -q '^dnscrypt-proxy$' && check_dns_at "$DNS_PIHOLE_IP"; then
              if resolv_has "$DNS_PIHOLE_IP"; then
                  echo "Stack active : DNSCrypt-Proxy (DoH), DNS système local"
              else
                  echo "Stack active : DNSCrypt-Proxy (DoH), mais DNS système non local"
              fi
          elif resolv_has "$NEUTRAL_DNS1" "$NEUTRAL_DNS2"; then
              echo "Fallback neutre actif : DNS.SB"
          elif resolv_has "$FILTERED_DNS1" "$FILTERED_DNS2"; then
              echo "Fallback filtrant actif : AdGuard / NextDNS"
          else
              echo "Aucune stack DNS actuellement active"
          fi
      
          cap_log
      }
      
      enable_fallback_neutral() {
          sudo -v
          stop_stacks
          write_resolvconf_two "$NEUTRAL_DNS1" "$NEUTRAL_DNS2"
      
          if resolv_has "$NEUTRAL_DNS1" "$NEUTRAL_DNS2" && { check_dns_at "$NEUTRAL_DNS1" || check_dns_at "$NEUTRAL_DNS2"; }; then
              log "fallback neutre activé"
              echo "Fallback neutre activé : DNS.SB."
              cap_log
              exit 0
          fi
      
          echo "Fallback neutre demandé, mais /etc/resolv.conf n'utilise pas ces DNS."
          if resolv_managed_by_external_tool; then
              echo "Un gestionnaire DNS externe semble garder la main sur /etc/resolv.conf."
          fi
          cap_log
          exit 1
      }
      
      enable_fallback_filtered() {
          sudo -v
          stop_stacks
          write_resolvconf_two "$FILTERED_DNS1" "$FILTERED_DNS2"
      
          if resolv_has "$FILTERED_DNS1" "$FILTERED_DNS2" && { check_dns_at "$FILTERED_DNS1" || check_dns_at "$FILTERED_DNS2"; }; then
              log "fallback filtrant activé"
              echo "Fallback filtrant activé : AdGuard / NextDNS."
              cap_log
              exit 0
          fi
      
          echo "Fallback filtrant demandé, mais /etc/resolv.conf n'utilise pas ces DNS."
          if resolv_managed_by_external_tool; then
              echo "Un gestionnaire DNS externe semble garder la main sur /etc/resolv.conf."
          fi
          cap_log
          exit 1
      }
      
      enable_stack() {
          local target="$1"
      
          sudo -v
      
          if [[ "$target" == "dns-doh" ]]; then
              echo "Arrêt de dns-dot..."
              [ -d "$DOT_DIR" ] && compose "$DOT_DIR" down || true
      
              echo "Démarrage de dns-doh..."
              [ -d "$DOH_DIR" ] || { echo "Dossier introuvable : $DOH_DIR"; exit 1; }
              compose "$DOH_DIR" up -d
          else
              echo "Arrêt de dns-doh..."
              [ -d "$DOH_DIR" ] && compose "$DOH_DIR" down || true
      
              echo "Démarrage de dns-dot..."
              [ -d "$DOT_DIR" ] || { echo "Dossier introuvable : $DOT_DIR"; exit 1; }
              compose "$DOT_DIR" up -d
          fi
      
          echo "Vérification de la nouvelle stack..."
          for _ in {1..15}; do
              if check_dns_at "$DNS_PIHOLE_IP"; then
                  write_resolvconf_one "$DNS_PIHOLE_IP"
      
                  if resolv_has "$DNS_PIHOLE_IP"; then
                      log "stack '$target' activée avec DNS système local"
                      echo "Stack '$target' activée. DNS système : 127.0.0.1."
                      cap_log
                      exit 0
                  fi
      
                  log "stack '$target' active, mais DNS système non local"
                  echo "Stack '$target' active, mais /etc/resolv.conf n'utilise pas 127.0.0.1."
                  if resolv_managed_by_external_tool; then
                      echo "Un gestionnaire DNS externe semble garder la main sur /etc/resolv.conf."
                  fi
                  cap_log
                  exit 1
              fi
              sleep 1
          done
      
          echo "Échec du démarrage DNS local. Activation du fallback neutre."
          write_resolvconf_two "$NEUTRAL_DNS1" "$NEUTRAL_DNS2"
      
          if resolv_has "$NEUTRAL_DNS1" "$NEUTRAL_DNS2"; then
              log "échec stack '$target', fallback neutre activé"
              echo "Fallback neutre activé."
          else
              log "échec stack '$target', fallback neutre demandé mais non effectif"
              echo "Fallback neutre demandé, mais /etc/resolv.conf n'utilise pas les DNS de secours."
          fi
      
          cap_log
          exit 1
      }
      
      if [[ $# -eq 0 ]]; then
          show_state
          exit 0
      fi
      
      case "$1" in
          dns-dot|dns-doh)
              enable_stack "$1"
              ;;
          fallback|fallback-neutral|fallback-neutre)
              enable_fallback_neutral
              ;;
          fallback-filtered|fallback-filtrant)
              enable_fallback_filtered
              ;;
          *)
              echo "Usage: switch-dns [dns-dot|dns-doh|fallback-neutral|fallback-filtered]"
              exit 1
              ;;
      esac

      Rendez le script exécutable :

      chmod +x ~/bin/switch-dns

      2. Interface graphique optionnelle

      Pour plus de confort, on peut créer une interface graphique avec yad.

      Étape 1 : script pour la demande de mot de passe

      nano ~/bin/yad-askpass.sh

      Contenu :

      #!/bin/bash
      yad --entry --title="Authentification requise" \
      --text="Veuillez entrer votre mot de passe pour continuer :" \
      --hide-text --center

      Rendez le script exécutable :

      chmod +x ~/bin/yad-askpass.sh

      Étape 2 : script pour l’interface graphique

      nano ~/bin/dns-switcher-gui.sh

      Contenu :

      #!/bin/bash
      
      export SUDO_ASKPASS="$HOME/bin/yad-askpass.sh"
      
      SWITCH_SCRIPT="$HOME/bin/switch-dns"
      LOCAL_DNS="127.0.0.1"
      
      NEUTRAL_DNS1="185.222.222.222"
      NEUTRAL_DNS2="45.11.45.11"
      
      FILTERED_DNS1="94.140.14.14"
      FILTERED_DNS2="45.90.28.0"
      
      LOGFILE="$HOME/dns-switcher-gui.log"
      
      log() {
          echo "[dns-switcher-gui] $(date): $*" >> "$LOGFILE" 2>/dev/null || true
      }
      
      cap_log() {
          [ -f "$LOGFILE" ] || return 0
          tail -n 200 "$LOGFILE" > "$LOGFILE.tmp" 2>/dev/null && mv "$LOGFILE.tmp" "$LOGFILE" || true
      }
      
      check_local_dns() {
          dig +time=3 +tries=1 whoami.akamai.net "@$LOCAL_DNS" >/dev/null 2>&1
      }
      
      resolv_has() {
          local dns1="$1"
          local dns2="${2:-}"
      
          if [ -n "$dns2" ]; then
              grep -qE "^nameserver[[:space:]]+($dns1|$dns2)$" /etc/resolv.conf 2>/dev/null
          else
              grep -qE "^nameserver[[:space:]]+$dns1$" /etc/resolv.conf 2>/dev/null
          fi
      }
      
      if [ ! -x "$SWITCH_SCRIPT" ]; then
          yad --error --title="DNS Switcher" --text="Script de basculement introuvable : $SWITCH_SCRIPT"
          exit 1
      fi
      
      DOCKER_STATE=$(docker ps --format '{{.Names}}' 2>/dev/null || true)
      
      DOT_CHECKED="FALSE"
      DOH_CHECKED="FALSE"
      FALLBACK_NEUTRAL_CHECKED="FALSE"
      FALLBACK_FILTERED_CHECKED="FALSE"
      
      if echo "$DOCKER_STATE" | grep -q '^pihole$' && echo "$DOCKER_STATE" | grep -q '^unbound$' && check_local_dns; then
          if resolv_has "$LOCAL_DNS"; then
              CURRENT_MODE="dns-dot"
              DISPLAY_STATE="Stack active : Unbound (DoT), DNS système local"
          else
              CURRENT_MODE="dns-dot"
              DISPLAY_STATE="Stack active : Unbound (DoT), mais DNS système non local"
          fi
          DOT_CHECKED="TRUE"
      elif echo "$DOCKER_STATE" | grep -q '^pihole$' && echo "$DOCKER_STATE" | grep -q '^dnscrypt-proxy$' && check_local_dns; then
          if resolv_has "$LOCAL_DNS"; then
              CURRENT_MODE="dns-doh"
              DISPLAY_STATE="Stack active : DNSCrypt-Proxy (DoH), DNS système local"
          else
              CURRENT_MODE="dns-doh"
              DISPLAY_STATE="Stack active : DNSCrypt-Proxy (DoH), mais DNS système non local"
          fi
          DOH_CHECKED="TRUE"
      elif resolv_has "$NEUTRAL_DNS1" "$NEUTRAL_DNS2"; then
          CURRENT_MODE="fallback-neutral"
          DISPLAY_STATE="Fallback neutre actif : DNS.SB"
          FALLBACK_NEUTRAL_CHECKED="TRUE"
      elif resolv_has "$FILTERED_DNS1" "$FILTERED_DNS2"; then
          CURRENT_MODE="fallback-filtered"
          DISPLAY_STATE="Fallback filtrant actif : AdGuard / NextDNS"
          FALLBACK_FILTERED_CHECKED="TRUE"
      else
          CURRENT_MODE="aucune"
          DISPLAY_STATE="Aucune stack DNS active"
          DOH_CHECKED="TRUE"
      fi
      
      log "CURRENT_MODE='$CURRENT_MODE', DISPLAY_STATE='$DISPLAY_STATE'"
      cap_log
      
      CHOICE=$(yad --width=720 --height=380 --center --title="DNS Switcher" \
          --text="Configuration DNS\n\nÉtat actuel : $DISPLAY_STATE\n\nSélectionnez une stack ou un mode de secours :" \
          --list --radiolist --no-headers \
          --column="Sélection" --column="Type" --column="Description" \
          "$DOT_CHECKED" "dns-dot" "Pi-hole + Unbound DoT" \
          "$DOH_CHECKED" "dns-doh" "Pi-hole + dnscrypt-proxy DoH/DNSCrypt" \
          "$FALLBACK_NEUTRAL_CHECKED" "fallback-neutral" "Secours non filtrant : DNS.SB" \
          "$FALLBACK_FILTERED_CHECKED" "fallback-filtered" "Secours filtrant externe : AdGuard / NextDNS")
      
      [ $? -ne 0 ] && exit 0
      
      CHOICE_VALUE=$(echo "$CHOICE" | cut -d'|' -f2)
      
      RESULT=$(sudo -A "$SWITCH_SCRIPT" "$CHOICE_VALUE" 2>&1)
      EXIT_CODE=$?
      
      if [ $EXIT_CODE -eq 0 ]; then
          yad --info --title="DNS Switcher" --text="DNS configuré avec succès : $CHOICE_VALUE"
      else
          yad --error --title="DNS Switcher" --text="Erreur lors du basculement vers $CHOICE_VALUE :\n$RESULT"
      fi

      Rendez le script exécutable :

      chmod +x ~/bin/dns-switcher-gui.sh

      Étape 3 : raccourci dans le menu d’applications

      mkdir -p ~/.local/share/applications
      nano ~/.local/share/applications/dns-switcher.desktop

      Contenu :

      [Desktop Entry]
      Version=1.0
      Type=Application
      Name=DNS Switcher
      Comment=Basculer entre configurations DNS DoT, DoH et fallback
      Exec=/home/VOTRE_UTILISATEUR/bin/dns-switcher-gui.sh
      Icon=network-server
      Terminal=false
      Categories=Network;System;
      Keywords=DNS;network;privacy;security;

      Remplacez VOTRE_UTILISATEUR par votre nom d’utilisateur.

      3. Utilisation

      En ligne de commande

      # Afficher l'état actuel
      ~/bin/switch-dns
      
      # Activer DNS over TLS avec Unbound
      ~/bin/switch-dns dns-dot
      
      # Activer DNS-over-HTTPS / DNSCrypt avec dnscrypt-proxy
      ~/bin/switch-dns dns-doh
      
      # Activer le fallback neutre non filtrant
      ~/bin/switch-dns fallback-neutral
      
      # Activer le fallback filtrant externe
      ~/bin/switch-dns fallback-filtered

      Avec l’interface graphique

      Lancez DNS Switcher depuis votre menu d’applications, sélectionnez la configuration souhaitée, puis entrez votre mot de passe lorsque demandé.

      4. Vérification manuelle

      Pour vérifier l’état actuel :

      ~/bin/switch-dns

      Pour vérifier quel DNS est réellement utilisé par le système :

      dig kernel.org A | grep SERVER

      Pour tester directement la stack locale sans dépendre du DNS système :

      dig @127.0.0.1 kernel.org A +short

      Pour vérifier les conteneurs actifs :

      docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -E 'NAMES|pihole|unbound|dnscrypt'

      5. Notes importantes sur /etc/resolv.conf

      Le script écrit dans /etc/resolv.conf pour faire pointer le système vers 127.0.0.1 ou vers un fallback.

      Si votre distribution utilise un gestionnaire réseau qui réécrit automatiquement /etc/resolv.conf, le script peut lancer correctement la stack tout en indiquant que le DNS système n’est pas local.

      Dans ce cas, la stack peut être fonctionnelle en test direct :

      dig @127.0.0.1 kernel.org A +short

      mais le système peut continuer à utiliser un autre résolveur, visible avec :

      dig kernel.org A | grep SERVER

      Le script signale ce cas au lieu d’annoncer un faux succès.

      6. Pourquoi pas de cron automatique ?

      Une ancienne version de ce type de script utilisait un cron qui réécrivait régulièrement /etc/resolv.conf en cas de panne DNS locale.

      Je ne recommande pas cette méthode ici. Un cron DNS peut se battre avec NetworkManager, systemd-resolved, resolvconf ou une configuration manuelle. Il peut aussi masquer la vraie cause d’une panne.

      En cas de problème, utilisez plutôt une bascule explicite :

      ~/bin/switch-dns fallback-neutral

      ou :

      ~/bin/switch-dns fallback-filtered

      7. Dépannage rapide

      La stack locale ne répond pas

      docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -E 'NAMES|pihole|unbound|dnscrypt'
      docker logs --tail=80 pihole
      docker logs --tail=80 unbound
      docker logs --tail=80 dnscrypt-proxy

      Le switcher indique “DNS système non local”

      La stack locale répond, mais /etc/resolv.conf n’utilise pas 127.0.0.1. Vérifiez :

      cat /etc/resolv.conf
      dig kernel.org A | grep SERVER
      dig @127.0.0.1 kernel.org A +short

      Si le test direct vers 127.0.0.1 fonctionne, la stack est bonne. C’est la configuration DNS système qui est pilotée ailleurs.

      Le fallback neutre ne s’active pas

      Testez directement les deux DNS de secours :

      dig @185.222.222.222 kernel.org A +short
      dig @45.11.45.11 kernel.org A +short

      Si ces commandes répondent mais que /etc/resolv.conf ne contient pas ces IP, un gestionnaire réseau externe reprend probablement la main sur la configuration DNS.

      Conclusion

      Ce switcher permet de basculer proprement entre une stack DoT, une stack DoH/DNSCrypt et deux modes de secours.

      Le mode normal reste une stack locale avec Pi-hole :

      applications -> 127.0.0.1 -> Pi-hole -> Unbound DoT

      ou :

      applications -> 127.0.0.1 -> Pi-hole -> dnscrypt-proxy DoH/DNSCrypt

      Le fallback neutre sert à retrouver une résolution DNS hors FAI. Le fallback filtrant externe sert à conserver un filtrage minimal quand Pi-hole est indisponible.

      L’intérêt principal du switcher est la résilience : si un mode tombe, vous pouvez passer à un autre sans réinstaller toute votre configuration DNS.

      Un jeune site que j'aime bien, la ferrari du T-shirt ...bio en plus : GoudronBlanc

    Affichage de 1 message (sur 1 au total)
    • Vous devez être connecté pour répondre à ce sujet.