feat: support split-DNS, infobulles UI et corrections Windows
- Ajout du support complet du split-DNS pour Linux (systemd-resolved) et Windows (NRPT) - Bouton de configuration des permissions ACL pour WireGuard sous Windows - Suppression de tous les appels à sc.exe (remplacés par PowerShell + wireguard.exe) - UAC gérée par wireguard.exe lui-même (pas par WGSecure) - Infobulles ajoutées sur tous les champs de configuration principaux - Correction du blocage du handshake lié au redémarrage de systemd-resolved - Incrémentation de la version à 0.7.3 Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
+229
@@ -169,6 +169,235 @@ _NRPT_CLEAN_PS = (
|
||||
"-ErrorAction SilentlyContinue }"
|
||||
)
|
||||
|
||||
# Split-DNS : rule name template for per-tunnel DNS rules
|
||||
_SPLIT_DNS_RULE_PREFIX = "WGSecure-SplitDNS-"
|
||||
|
||||
|
||||
def _nrpt_add_split_dns_ps(server: str, domains: list[str], rule_name: str) -> str:
|
||||
"""PowerShell script to add a NRPT rule for split-DNS.
|
||||
|
||||
Args:
|
||||
server: DNS server IP address
|
||||
domains: List of domain suffixes (e.g., ["local", "internal"])
|
||||
rule_name: Name for the NRPT rule
|
||||
"""
|
||||
if not domains:
|
||||
return ""
|
||||
# Build namespace list for NRPT rule
|
||||
namespace_list = ", ".join(f"'{d.strip().strip('.')}'" for d in domains if d.strip())
|
||||
return (
|
||||
f"$rule = Get-DnsClientNrptRule -Name '{rule_name}' -ErrorAction SilentlyContinue; "
|
||||
f"if ($rule) {{ Remove-DnsClientNrptRule -Name '{rule_name}' -Force -ErrorAction SilentlyContinue }}; "
|
||||
f"Add-DnsClientNrptRule -Name '{rule_name}' -Namespace @({namespace_list}) "
|
||||
f"-NameServer '{server}' -Comment 'WGSecure Split-DNS' -ErrorAction SilentlyContinue"
|
||||
)
|
||||
|
||||
|
||||
def _nrpt_remove_split_dns_ps(rule_name: str) -> str:
|
||||
"""PowerShell script to remove a NRPT rule for split-DNS."""
|
||||
return (
|
||||
f"Remove-DnsClientNrptRule -Name '{rule_name}' -Force -ErrorAction SilentlyContinue"
|
||||
)
|
||||
|
||||
|
||||
def _systemd_resolved_available() -> bool:
|
||||
"""Vérifie si systemd-resolved est actif (Linux)."""
|
||||
if is_windows():
|
||||
return False
|
||||
import subprocess
|
||||
try:
|
||||
# Vérifier que systemd-resolved est actif
|
||||
code = subprocess.run(
|
||||
["systemctl", "is-active", "systemd-resolved"],
|
||||
capture_output=True, text=True, timeout=5
|
||||
).returncode
|
||||
return code == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _setup_split_dns_linux(server: str, domains: list[str], iface: str) -> tuple[bool, str]:
|
||||
"""Configure le split-DNS sous Linux via systemd-resolved.
|
||||
|
||||
Crée un fichier dans /etc/systemd/resolved.conf.d/ pour router
|
||||
les domaines vers le serveur DNS spécifié.
|
||||
|
||||
Note: Le redémarrage de systemd-resolved est nécessaire pour appliquer
|
||||
la configuration, mais il est effectué de manière asynchrone pour
|
||||
éviter de bloquer le handshake WireGuard.
|
||||
|
||||
Args:
|
||||
server: Adresse IP du serveur DNS (ex: 192.168.1.210)
|
||||
domains: Liste de domaines (ex: ["local", "internal"])
|
||||
iface: Nom de l'interface WireGuard
|
||||
|
||||
Returns:
|
||||
(success, message)
|
||||
"""
|
||||
if not _systemd_resolved_available():
|
||||
return False, "systemd-resolved non disponible. Utilisez resolvconf ou configurez manuellement."
|
||||
|
||||
# Créer le nom du fichier de config
|
||||
config_file = f"/etc/systemd/resolved.conf.d/wgsecure-{iface}.conf"
|
||||
|
||||
# Contenu du fichier
|
||||
domain_str = ", ".join(f"~{d.strip()}" for d in domains if d.strip())
|
||||
content = f"[Resolve]\nDNS={server}\nDomains={domain_str}\n"
|
||||
|
||||
# Écrire le fichier (nécessite sudo)
|
||||
import tempfile
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Créer un fichier temporaire
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.conf', delete=False) as f:
|
||||
f.write(content)
|
||||
temp_path = f.name
|
||||
|
||||
try:
|
||||
# Copier le fichier temporaire vers /etc/systemd/resolved.conf.d/
|
||||
code, out, err = run_privileged(
|
||||
["sudo", "cp", temp_path, config_file],
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if code != 0:
|
||||
return False, f"Échec de la copie du fichier: {err or out}"
|
||||
|
||||
# Redémarrer systemd-resolved en arrière-plan pour ne pas bloquer
|
||||
# Utiliser systemd-run pour un redémarrage asynchrone
|
||||
restart_cmd = ["sudo", "systemctl", "restart", "systemd-resolved"]
|
||||
try:
|
||||
# Lancer en arrière-plan avec nohup pour éviter de bloquer
|
||||
subprocess.Popen(
|
||||
restart_cmd,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
except Exception:
|
||||
pass # Ignorer l'erreur, le fichier est déjà en place
|
||||
|
||||
return True, (
|
||||
f"Split-DNS configuré pour {server} (domaines: {', '.join(domains)}). "
|
||||
f"Redémarrez systemd-resolved manuellement si nécessaire: "
|
||||
f"`sudo systemctl restart systemd-resolved`"
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
os.unlink(temp_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def setup_split_dns(cfg: Config) -> tuple[bool, str]:
|
||||
"""Configure le split-DNS pour Windows (NRPT) ou Linux (systemd-resolved).
|
||||
|
||||
Args:
|
||||
cfg: Configuration contenant split_dns_server et split_dns_domains
|
||||
|
||||
Returns:
|
||||
(success, message)
|
||||
"""
|
||||
server = (cfg.wg.get("split_dns_server") or "").strip()
|
||||
domains = cfg.wg.get("split_dns_domains") or []
|
||||
name = cfg.wg.get("interface_name", "wgs0")
|
||||
|
||||
if not server or not domains:
|
||||
return True, "Aucune configuration split-DNS"
|
||||
|
||||
# Nettoyer les domaines
|
||||
domains = [d.strip() for d in domains if d.strip()]
|
||||
if not domains:
|
||||
return True, "Aucun domaine valide à configurer"
|
||||
|
||||
if is_windows():
|
||||
rule_name = f"{_SPLIT_DNS_RULE_PREFIX}{name}"
|
||||
script = _nrpt_add_split_dns_ps(server, domains, rule_name)
|
||||
|
||||
if not script:
|
||||
return True, "Aucun domaine à configurer"
|
||||
|
||||
# Exécuter avec élévation (nécessite admin pour modifier les règles NRPT)
|
||||
code, out, err = run_privileged(
|
||||
["powershell", "-NoProfile", "-NonInteractive", "-Command", script],
|
||||
timeout=15
|
||||
)
|
||||
|
||||
if code == 0:
|
||||
return True, f"Règle split-DNS (NRPT) configurée pour {server} (domaines: {', '.join(domains)})"
|
||||
else:
|
||||
return False, f"Échec de la configuration split-DNS (NRPT): {err or out or 'Erreur inconnue'}"
|
||||
|
||||
else:
|
||||
# Linux : utiliser systemd-resolved
|
||||
return _setup_split_dns_linux(server, domains, name)
|
||||
|
||||
|
||||
def _cleanup_split_dns_linux(iface: str) -> tuple[bool, str]:
|
||||
"""Supprime la configuration split-DNS sous Linux.
|
||||
|
||||
Note: Ne redémarre PAS systemd-resolved pour éviter de casser
|
||||
la résolution DNS pendant une reconnexion. Le service sera
|
||||
redémarré manuellement par l'utilisateur si nécessaire.
|
||||
|
||||
Args:
|
||||
iface: Nom de l'interface WireGuard
|
||||
|
||||
Returns:
|
||||
(success, message)
|
||||
"""
|
||||
config_file = f"/etc/systemd/resolved.conf.d/wgsecure-{iface}.conf"
|
||||
|
||||
# Supprimer le fichier de config (nécessite sudo)
|
||||
import os
|
||||
if not os.path.exists(config_file):
|
||||
return True, ""
|
||||
|
||||
code, out, err = run_privileged(
|
||||
["sudo", "rm", "-f", config_file],
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if code != 0:
|
||||
return False, f"Échec de la suppression du fichier split-DNS: {err or out}"
|
||||
|
||||
return True, "Configuration split-DNS supprimée (redémarrez systemd-resolved si nécessaire)"
|
||||
|
||||
|
||||
def cleanup_split_dns(cfg: Config) -> tuple[bool, str]:
|
||||
"""Supprime la configuration split-DNS pour Windows (NRPT) ou Linux.
|
||||
|
||||
Args:
|
||||
cfg: Configuration contenant l'interface name
|
||||
|
||||
Returns:
|
||||
(success, message)
|
||||
"""
|
||||
if is_windows():
|
||||
name = cfg.wg.get("interface_name", "wgs0")
|
||||
rule_name = f"{_SPLIT_DNS_RULE_PREFIX}{name}"
|
||||
|
||||
script = _nrpt_remove_split_dns_ps(rule_name)
|
||||
|
||||
# Exécuter avec élévation
|
||||
code, out, err = run_privileged(
|
||||
["powershell", "-NoProfile", "-NonInteractive", "-Command", script],
|
||||
timeout=15
|
||||
)
|
||||
|
||||
if code == 0:
|
||||
return True, "Règle split-DNS (NRPT) supprimée"
|
||||
else:
|
||||
# Ignorer l'erreur si la règle n'existait pas
|
||||
if "cannot find" in (err or out or "").lower() or "not found" in (err or out or "").lower():
|
||||
return True, ""
|
||||
return False, f"Échec de la suppression split-DNS (NRPT): {err or out or 'Erreur inconnue'}"
|
||||
|
||||
else:
|
||||
# Linux : supprimer le fichier de config systemd-resolved
|
||||
name = cfg.wg.get("interface_name", "wgs0")
|
||||
return _cleanup_split_dns_linux(name)
|
||||
|
||||
|
||||
def _powershell(script: str, timeout: int = 20) -> tuple[int, str, str]:
|
||||
return run_command(
|
||||
|
||||
Reference in New Issue
Block a user