feat: test de handshake réel sans monter le tunnel
Le diagnostic s'arrêtait sur « le port UDP ne rejette rien ». C'est le maximum qu'une sonde aveugle puisse dire : un serveur WireGuard ignore silencieusement tout paquet non authentifié, si bien qu'un port non redirigé, une clé inconnue du serveur et un serveur en parfaite santé produisent exactement la même absence de réponse. Le tunnel n'étant pas monté, les étapes suivantes étaient sautées et le rapport concluait « chaîne validée » sur une configuration qui n'avait aucune chance de négocier. WGSecure construit désormais un vrai message d'initiation (Noise_IKpsk2_25519_ ChaChaPoly_BLAKE2s) et interprète la réponse. Le serveur ne répond que si notre clé publique statique lui est connue et si sa propre clé est celle que nous croyons : une réponse prouve la configuration, et son déchiffrement dit quelle clé pré-partagée est en jeu. Aucun tunnel n'est monté, aucun privilège demandé, aucun état conservé. Quand le serveur reste muet, une seconde tentative part depuis le port du serveur. S'il répond alors, c'est qu'un équipement en chemin n'accepte le trafic que si le port source égale le port de destination — une règle courante sur FortiGate, dont l'objet Service porte un udp-portrange 51820:51820. Un client WireGuard émettant depuis un port aléatoire ne peut jamais la satisfaire, et rien nulle part ne le disait. Corrige aussi le verdict rendu quand le tunnel est inactif : « chaîne validée » laissait croire que tout avait été vérifié, alors que les étapes sautées sont précisément celles qui échouent. Le rapport les nomme désormais. La fenêtre d'administration revient à 720x600 et les encarts posés au-dessus des sous-onglets disparaissent, où ils répétaient ce que les sous-onglets annoncent déjà.
This commit is contained in:
@@ -5,6 +5,7 @@ import socket
|
||||
import time
|
||||
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
|
||||
from app.core import dns as dns_util
|
||||
from app.core import wg_handshake
|
||||
from app.core.config import Config
|
||||
from app.utils.platform_utils import (
|
||||
get_config_dir,
|
||||
@@ -697,6 +698,39 @@ def _diag_reachability(cfg: Config, timeout: int) -> tuple[list[dict], str]:
|
||||
return steps, server_ip
|
||||
|
||||
|
||||
def _diag_handshake(cfg: Config, timeout: float = 5.0) -> list[dict]:
|
||||
"""Négocie un vrai handshake, sans monter le tunnel.
|
||||
|
||||
Sans cette étape, le diagnostic s'arrêtait sur « le port ne rejette rien »
|
||||
puis, tunnel non monté, concluait « chaîne validée — reste à le monter ».
|
||||
Un serveur WireGuard étant silencieux avec les paquets non authentifiés,
|
||||
ce verdict tombait aussi bien sur une configuration saine que sur un port
|
||||
non redirigé ou une clé inconnue du serveur. C'est le seul test qui
|
||||
distingue les deux.
|
||||
"""
|
||||
wg = cfg.wg
|
||||
issue, detail = wg_handshake.probe_handshake(
|
||||
server_endpoint=(wg.get("server_endpoint") or "").strip(),
|
||||
server_port=int(wg.get("server_port", 51820)),
|
||||
server_public_key=(wg.get("server_public_key") or "").strip(),
|
||||
client_private_key=(wg.get("client_private_key") or "").strip(),
|
||||
preshared_key=((wg.get("preshared_key") or "").strip()
|
||||
if uses_preshared_key(wg) else ""),
|
||||
timeout=timeout,
|
||||
)
|
||||
state = {
|
||||
wg_handshake.OK_NO_PSK: _OK,
|
||||
wg_handshake.OK_PSK: _OK,
|
||||
wg_handshake.COOKIE: _WARN,
|
||||
wg_handshake.SRC_PORT_FILTERED: _FAIL,
|
||||
wg_handshake.PSK_REQUIRED: _FAIL,
|
||||
wg_handshake.NO_REPLY: _FAIL,
|
||||
wg_handshake.BAD_CONFIG: _FAIL,
|
||||
wg_handshake.NET_ERROR: _FAIL,
|
||||
}.get(issue, _WARN)
|
||||
return [_step(state, "Négociation du handshake", detail)]
|
||||
|
||||
|
||||
def _diag_tunnel(cfg: Config) -> list[dict]:
|
||||
"""Étapes portant sur le tunnel lui-même : montage, handshake, trafic."""
|
||||
wg = cfg.wg
|
||||
@@ -815,6 +849,11 @@ def diagnose_connection(cfg: Config, timeout: int = 5) -> list[dict]:
|
||||
if any(s["state"] == _FAIL for s in net_steps):
|
||||
return steps
|
||||
|
||||
hs_steps = _diag_handshake(cfg, timeout)
|
||||
steps += hs_steps
|
||||
if any(s["state"] == _FAIL for s in hs_steps):
|
||||
return steps
|
||||
|
||||
steps += _diag_tunnel(cfg)
|
||||
return steps
|
||||
|
||||
|
||||
Reference in New Issue
Block a user