#34 — DNS Sinkhole
PLANIFIÉ
Priorité: 🔴 CRITIQUE · Type: TYPE E · Conteneur: rgz-dns · Code: config/unbound/unbound.conf.d/sinkhole.confDépendances: #9 rgz-dns, #3 rgz-portal
Description
Système de DNS sinkhole pour rediriger tout le trafic DNS vers le portail captif avant authentification. Le revendeur se connecte au SSID ACCESS, son téléphone reçoit une adresse IP dans le subnet VLAN alloué (ex: 10.150.0.100) via #30 Kea DHCP avec DNS 10.0.0.9 (Unbound). Toute requête DNS en dehors de la whitelist est interceptée et redirigée vers l'adresse IP du portail captif via HTTP 302 ou HTTPS.
La configuration Unbound utilise local-zone "." redirect pour capturer *.domain.tld en wildcard, puis retourne l'IP du portail. Une whitelist explicite permet à KKiaPay, Letexto, API et other services critiques de fonctionner avant auth. Chaque VLAN peut avoir sa propre configuration (portail custom par revendeur via #18 branding).
Le sinkhole est activé uniquement pour les VLAN non-authentifiés (0-99), désactivé pour les VLAN internes (100-499). Une entrée de log est générée à chaque redirection pour analyser les tentatives de contournement.
Architecture Interne
1. Requête DNS abonné non-auth:
└─> Abonné: 10.150.0.100 (dans VLAN 150, revendeur "Tech Connect")
├─> Résout: www.google.com
├─> Requête → Unbound 10.0.0.9:53/udp
└─> Unbound applique config sinkhole.conf
2. Matching sinkhole:
├─> WHITELIST (nodefault):
│ ├─ api-rgz.duckdns.org → résolution normale
│ ├─ access-rgz.duckdns.org → résolution normale
│ ├─ kkiapay.com → résolution KKiaPay API
│ ├─ api.letexto.com → résolution Letexto SMS
│ └─ ntp.ubuntu.com → résolution NTP
│
├─> REDIRECT ZONE (.*):
│ ├─ google.com → A 10.150.0.1 (IP portail VLAN 150)
│ ├─ facebook.com → A 10.150.0.1
│ ├─ youtube.com → A 10.150.0.1
│ └─ * → A 10.150.0.1 (catchall)
│
└─> Client reçoit: A 10.150.0.1 (IP portail)
3. Browser de l'abonné:
├─> Requête HTTP www.google.com → GET / HTTP/1.1
├─> TCP connexion vers 10.150.0.1:80 (portail)
├─> Portail répond: HTTP 302 / → https://access-rgz.duckdns.org/portal?redirect=www.google.com
└─> Abonné atterrit sur formulaire MSISDN + OTP (#12)
4. Après authentification:
├─> Subscriber créé dans DB, session RADIUS créée
├─> Client reçoit Cookie Session (HTTP-only, Secure)
├─> Portail redirige vers URL d'origine: https://www.google.com
├─> Firewall nftables (#32) ACCEPT trafic pour ce subscriber
└─> Google répond normalement
5. Configuration multi-VLAN:
└─> Chaque revendeur peut avoir portail custom (via #18 branding):
├─ VLAN 150 → local-data "." A 10.150.0.1 (portail Tech Connect)
├─ VLAN 160 → local-data "." A 10.160.0.1 (portail autre revendeur)
└─> Implémenter via Unbound response-ip ou lua plugin (future)
6. Logging:
└─> Chaque redirection → syslog:
├─ "DNS_SINKHOLE: domain=google.com vlan=150 client=10.150.0.100"
└─> Agrégé ELK (#40) pour monitoring contournementConfiguration
Variables d'environnement
PORTAIL_IP=10.0.0.1 # IP portail captif (par défaut)
PORTAIL_IP_VLAN_150=10.150.0.1 # Portail custom par VLAN (optional)
PORTAIL_IP_VLAN_160=10.160.0.1
DNS_WHITELIST=api-rgz.duckdns.org,kkiapay.com,api.letexto.com # CSV
SINKHOLE_ENABLED=true # Activé/désactivé
SINKHOLE_LOG_LEVEL=notice # Log detail (critical, error, warning, notice, info, debug)Unbound Config (sinkhole.conf)
# /config/unbound/unbound.conf.d/sinkhole.conf
# DNS Sinkhole - Redirect all non-whitelisted to captive portal
# Logging
server:
log-time: yes
log-queries: yes
log-replies: yes
verbosity: 1
# Local zones: whitelist (nodefault = ne pas rediriger)
local-zone: "api-rgz.duckdns.org" nodefault
local-zone: "access-rgz.duckdns.org" nodefault
local-zone: "kkiapay.com" nodefault
local-zone: "api.letexto.com" nodefault
local-zone: "ntp.ubuntu.com" nodefault
local-zone: "google-dns-a.google.com" nodefault
local-zone: "8.8.8.8.in-addr.arpa" nodefault
# Sinkhole: wildcard redirect to portal
local-zone: "." redirect
local-data: ". A 10.0.0.1" # Default portail IP
# Multi-VLAN (future implementation via response-ip plugin)
# local-zone: "." redirect with response based on VRF/client subnet
# Access control: seul VLAN 0-99 non-auth (100-499 libre)
access-control: 10.0.0.0/8 allow # Internal networks
access-control: 127.0.0.0/8 allow # Localhost
access-control: ::1 allow # IPv6 loopback
access-control: 0.0.0.0/0 deny # Default deny
# Rate limiting (anti-spam DNS)
ratelimit: 100 # requêtes/secondes
ratelimit-slabs: 4
ratelimit-size: 4m
ratelimit-factor: 10
# Cache
cache-max-ttl: 3600
cache-min-ttl: 60Unbound Config Avancée (future multi-VLAN via Lua)
-- /config/unbound/unbound.conf.d/sinkhole_lua.conf
-- Redirection personnalisée par VLAN du client
server:
module-config: "dns64 validator iterator"
script: "script_sinkhole.lua"
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-port: 8953
function log_sinkhole(vlan, client, domain)
logger.info("DNS_SINKHOLE: vlan=" .. vlan .. " client=" .. client .. " domain=" .. domain)
endEndpoints API (indirects)
| Méthode | Route | Réponse | Auth |
|---|---|---|---|
| GET | /api/v1/dns/sinkhole/stats | 200 {redirects_24h, unique_domains, top_10} | Admin |
| GET | /api/v1/dns/sinkhole/logs?vlan=150&from=... | 200 {items: [...], total} | Admin |
| POST | /api/v1/dns/whitelist/add | 201 | Admin |
| DELETE | /api/v1/dns/whitelist/{domain} | 204 | Admin |
Commandes Utiles
# Vérifier config Unbound
unbound-checkconf -c /etc/unbound/unbound.conf
# Voir logs sinkhole (dans conteneur)
journalctl -u unbound -f
# Tester résolution depuis CLI (non-sinkhole)
nslookup google.com 10.0.0.9 # Devrait retourner 10.0.0.1
# Tester whitelist
nslookup api-rgz.duckdns.org 10.0.0.9 # Devrait résoudre vrai IP
# Recharger config sans restart
unbound-control reload
# Voir stat DNS queries
unbound-control stats
# Tester depuis CPE (simule requête VLAN)
docker exec rgz-dns nslookup youtube.com
# Doit retourner: A 10.0.0.1
# Voir détail redirection
dig @10.0.0.9 facebook.com +short
# Doit retourner: 10.0.0.1
# Logs Unbound (verbose)
tail -f /var/log/unbound/unbound.log
# Voir si sinkhole appliqué
curl -I http://10.0.0.1:80 # Tester HTTP portail
# Doit retourner: HTTP/1.1 302 Found
# Location: https://access-rgz.duckdns.org/portal?redirect=...Implémentation TODO
- [ ] Configuration Unbound de base (local-zone redirect, whitelist nodefault)
- [ ] Whitelist KKiaPay, Letexto, NTP, API RGZ
- [ ] Logging DNS queries → syslog
- [ ] Intégration avec #3 portail captif pour redirection 302
- [ ] Tests: DNS whitelist, DNS sinkhole, redirect HTTP → portail
- [ ] Monitoring: stats DNS redirections (ELK #40)
- [ ] Future: Lua script pour redirection multi-VLAN personnalisée
- [ ] Rate limiting DNS pour éviter abus
- [ ] Gestion cache TTL (min 60s, max 3600s)
- [ ] Paramètres ENV pour configurer portail IP par VLAN
- [ ] Documentation client : "Pourquoi je vois une page de login"
Dernière mise à jour: 2026-02-21