v0.7.1 : diagnostics non-bloquants, DNS hors tunnel détecté

- Nouveau diagnostic : signale un DNS non couvert par les IPs autorisées
  sur un tunnel scindé (résolution forcée via une interface qui n'a pas
  de route vers ce serveur).
- Corrige le test de connexion, l'analyse DNS et la réparation DNS qui
  gelaient l'application (sondes lancées sur le thread graphique).
- Corrige le CHANGELOG absent de l'exécutable compilé (page « À propos »
  vide) et le badge de latence bloqué sur « hors ligne » quand l'ICMP est
  filtré alors que le tunnel fonctionne.
- wgsecure.iss installe désormais WireGuard for Windows automatiquement.
This commit is contained in:
2026-09-01 22:05:20 +02:00
parent ce4df4edb4
commit 8e40812f76
8 changed files with 119 additions and 26 deletions
+65 -20
View File
@@ -14,12 +14,20 @@ from app.core import wireguard as wg_core
from app.core import dns as dns_util
from app.core import mfa as mfa_core
from app.ui import theme
from app.ui.worker import ValueWorker
from app.utils.platform_utils import is_windows
class AdminWindow(QDialog):
def __init__(self, config: Config, parent=None):
super().__init__(parent)
self._cfg = config
# Sondes réseau (ping, `dig`, handshake, sudo…) : jusqu'à plusieurs
# secondes chacune. Lancées telles quelles depuis un clic, elles
# gelaient toute la fenêtre (« Ne répond plus ») le temps de leur
# exécution — ces workers les déportent hors du thread Qt.
self._diag_worker: ValueWorker | None = None
self._dns_worker: ValueWorker | None = None
self._dns_fix_worker: ValueWorker | None = None
self.setWindowTitle("WGSecure — Panneau Administrateur")
self.setWindowFlags(
Qt.WindowType.Dialog
@@ -570,10 +578,10 @@ class AdminWindow(QDialog):
"montage du tunnel, handshake, trafic et DNS — et désigne l'étape\n"
"qui bloque."
))
btn = QPushButton("🚀 Lancer le test de connexion")
btn.setStyleSheet(theme.primary_button_style())
btn.clicked.connect(self._run_test)
g.addWidget(btn)
self._btn_diag_test = QPushButton("🚀 Lancer le test de connexion")
self._btn_diag_test.setStyleSheet(theme.primary_button_style())
self._btn_diag_test.clicked.connect(self._run_test)
g.addWidget(self._btn_diag_test)
# QLabel auparavant : le rapport fait une dizaine d'étapes avec un
# détail par étape, et un QLabel se laisse écraser par le layout —
@@ -604,17 +612,17 @@ class AdminWindow(QDialog):
grp = QGroupBox("🔍 Test de fuite DNS")
g = QVBoxLayout(grp)
g.addWidget(QLabel("Vérifie que le DNS passe bien par le tunnel VPN."))
btn = QPushButton("🔍 Analyser le DNS")
btn.setStyleSheet(theme.primary_button_style())
btn.clicked.connect(self._run_dns_test)
g.addWidget(btn)
btn_fix = QPushButton("🛠️ Réparer le DNS (tunnel arrêté)")
btn_fix.setToolTip(
self._btn_dns_test = QPushButton("🔍 Analyser le DNS")
self._btn_dns_test.setStyleSheet(theme.primary_button_style())
self._btn_dns_test.clicked.connect(self._run_dns_test)
g.addWidget(self._btn_dns_test)
self._btn_dns_fix = QPushButton("🛠️ Réparer le DNS (tunnel arrêté)")
self._btn_dns_fix.setToolTip(
"Retire la configuration DNS laissée par un tunnel arrêté "
"brutalement, qui empêche toute résolution de noms."
)
btn_fix.clicked.connect(self._repair_dns)
g.addWidget(btn_fix)
self._btn_dns_fix.clicked.connect(self._repair_dns)
g.addWidget(self._btn_dns_fix)
self._dns_result = QTextEdit()
self._dns_result.setReadOnly(True)
@@ -1271,15 +1279,24 @@ class AdminWindow(QDialog):
}
def _run_test(self):
from PyQt6.QtWidgets import QApplication
if self._diag_worker is not None:
return
self._save_values()
self._test_result.setHtml(
'<span style="color:rgba(255,255,255,0.6);">Diagnostic en cours…</span>'
)
self._test_result.setStyleSheet(theme.result_view_style("idle"))
QApplication.processEvents()
self._btn_diag_test.setEnabled(False)
steps = wg_core.diagnose_connection(self._cfg)
self._diag_worker = ValueWorker(wg_core.diagnose_connection, self._cfg, parent=self)
self._diag_worker.done.connect(self._on_diag_done)
self._diag_worker.finished.connect(self._diag_worker.deleteLater)
self._diag_worker.start()
def _on_diag_done(self, steps: list[dict] | None):
self._diag_worker = None
self._btn_diag_test.setEnabled(True)
steps = steps or []
self._test_result.setHtml(self._format_diagnostic(steps))
states = [s["state"] for s in steps]
@@ -1441,10 +1458,20 @@ class AdminWindow(QDialog):
dlg.exec()
def _run_dns_test(self):
from PyQt6.QtWidgets import QApplication
if self._dns_worker is not None:
return
self._dns_result.setPlainText("Analyse en cours…")
QApplication.processEvents()
r = wg_core.dns_leak_test(self._cfg)
self._btn_dns_test.setEnabled(False)
self._dns_worker = ValueWorker(wg_core.dns_leak_test, self._cfg, parent=self)
self._dns_worker.done.connect(self._on_dns_test_done)
self._dns_worker.finished.connect(self._dns_worker.deleteLater)
self._dns_worker.start()
def _on_dns_test_done(self, r: dict | None):
self._dns_worker = None
self._btn_dns_test.setEnabled(True)
r = r or {"status": "unknown", "resolvers": [], "expected": ""}
lines = []
status = r["status"]
if status == "ok":
@@ -1551,12 +1578,30 @@ class AdminWindow(QDialog):
ne retire donc jamais l'entrée DNS qu'il avait posée : la résolution
reste dirigée vers un serveur injoignable.
"""
from PyQt6.QtWidgets import QApplication
if self._dns_fix_worker is not None:
return
self._dns_result.setPlainText("Réparation en cours…")
QApplication.processEvents()
self._btn_dns_fix.setEnabled(False)
self._dns_fix_worker = ValueWorker(self._do_repair_dns, parent=self)
self._dns_fix_worker.done.connect(self._on_repair_dns_done)
self._dns_fix_worker.finished.connect(self._dns_fix_worker.deleteLater)
self._dns_fix_worker.start()
def _do_repair_dns(self) -> tuple[list[str], bool]:
"""Corps bloquant de `_repair_dns` — exécuté hors du thread Qt.
`force_cleanup` élève ses privilèges (sudo/askpass/pkexec) et peut
donc rester en attente d'une saisie utilisateur.
"""
actions = wg_core.force_cleanup(self._cfg)
works = dns_util.resolution_works()
return actions, works
def _on_repair_dns_done(self, result: tuple[list[str], bool] | None):
self._dns_fix_worker = None
self._btn_dns_fix.setEnabled(True)
actions, works = result if result else ([], False)
if works:
msg = "✅ Résolution DNS fonctionnelle"