Иногда провайдер/DPI распознаёт WireGuard по первому handshake и начинает “душить” соединение.
Обход: перед включением туннеля отправить любой UDP-пакет на Endpoint с того же локального порта, который использует WireGuard (ListenPort).
Этот репозиторий/гист содержит только helper для pre-UDP.
Он НЕ останавливает/устанавливает/запускает туннель — вы включаете туннель в WireGuard GUI как обычно.
wireguard-client.conf— пример клиентского конфига (важно: фиксированныйListenPort)wg-preudp.ps1— PowerShell helper: читаетListenPortиEndpointиз.confи отправляет pre-UDPwg-preudp-click.vbs— запуск helper по клику (без окна консоли), ищетwg-preudp.ps1в Documents
Пример wireguard-client.conf:
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.8.0.3/32
# IMPORTANT: fixed local UDP port (needed for DPI bypass)
ListenPort = 57932
DNS = 1.1.1.1, 1.0.0.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
PresharedKey = <CLIENT_PSK>
Endpoint = <SERVER_PUBLIC_IP_OR_DOMAIN>:51820
AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 25Важно:
ListenPortдолжен быть постоянным.Endpointдолжен быть в видеhost:port(или[ipv6]:port).
Рекомендуемое размещение:
wg-preudp.ps1→C:\Users\<USER>\Documents\wg-preudp.ps1- ваш WireGuard
.conf(туннель) → на Desktop или Documents (helper сам найдёт) wg-preudp-click.vbs→ на Desktop (чтобы запускать кликом)
Helper выберет самый свежий
.confна Desktop, если там нет — в Documents.
- В WireGuard GUI выключите (Deactivate) туннель
(важно:
ListenPortдолжен быть свободен) - Двойной клик по
wg-preudp-click.vbs(отправится pre-UDP) - Сразу включите (Activate) туннель в WireGuard GUI
param(
[string]$ConfigPath = "" # optional: if not set -> uses newest *.conf from Desktop, else Documents
)
function Find-ConfigFallback {
$desktop = [Environment]::GetFolderPath("Desktop")
$docs = [Environment]::GetFolderPath("MyDocuments")
foreach ($dir in @($desktop, $docs)) {
if (Test-Path $dir) {
$f = Get-ChildItem -Path $dir -Filter "*.conf" -File -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($f) { return $f.FullName }
}
}
return $null
}
function Get-ConfigValue([string]$path, [string]$key) {
$regex = "^\s*$([Regex]::Escape($key))\s*=\s*([^#]+?)\s*(?:#.*)?$"
$m = Select-String -Path $path -Pattern $regex -ErrorAction SilentlyContinue | Select-Object -First 1
if ($m -and $m.Matches.Count -gt 0) { return $m.Matches[0].Groups[1].Value.Trim() }
return $null
}
function Parse-Endpoint([string]$endpointLine) {
if ($endpointLine -match '^\[(.+)\]:(\d+)$') {
return @{ Host = $Matches[1]; Port = [int]$Matches[2] }
}
if ($endpointLine -match '^(.+):(\d+)$') {
return @{ Host = $Matches[1]; Port = [int]$Matches[2] }
}
throw "Cannot parse Endpoint: $endpointLine"
}
try {
# Resolve config path
if ($ConfigPath -and (Test-Path $ConfigPath)) {
# ok
} else {
$fallback = Find-ConfigFallback
if (-not $fallback) { throw "No .conf found on Desktop or Documents. Provide -ConfigPath." }
$ConfigPath = $fallback
}
$tunnelName = [IO.Path]::GetFileNameWithoutExtension($ConfigPath)
# Read ListenPort + Endpoint
$listenPortStr = Get-ConfigValue $ConfigPath "ListenPort"
if (-not $listenPortStr) { throw "ListenPort missing in config" }
$listenPort = [int]$listenPortStr
$endpointStr = Get-ConfigValue $ConfigPath "Endpoint"
if (-not $endpointStr) { throw "Endpoint missing in config" }
$ep = Parse-Endpoint $endpointStr
$endpointHost = $ep.Host
$endpointPort = $ep.Port
# Resolve IPv4
$ip = ([System.Net.Dns]::GetHostAddresses($endpointHost) |
Where-Object { $_.AddressFamily -eq "InterNetwork" } |
Select-Object -First 1)
if (-not $ip) { throw "IPv4 resolve failed for $endpointHost" }
# pre-UDP: bind to ListenPort and send 2 bytes
$endp = New-Object System.Net.IPEndPoint($ip, $endpointPort)
$sock = New-Object System.Net.Sockets.UdpClient($listenPort)
[void]$sock.Send([Text.Encoding]::ASCII.GetBytes(":)"), 2, $endp)
$sock.Close()
Write-Host "OK: pre-UDP sent"
Write-Host "Tunnel=$tunnelName"
Write-Host "Config=$ConfigPath"
Write-Host "ListenPort=$listenPort Endpoint=$endpointHost`:$endpointPort"
} catch {
Write-Host "ERROR: $($_.Exception.Message)"
Write-Host "Hint: Tunnel must be OFF (ListenPort must be free) before running pre-UDP."
}Option Explicit
Dim fso, shell, docsDir, ps1Path, cmd
Set fso = CreateObject("Scripting.FileSystemObject")
Set shell = CreateObject("WScript.Shell")
docsDir = shell.SpecialFolders("MyDocuments")
ps1Path = docsDir & "\wg-preudp.ps1"
If Not fso.FileExists(ps1Path) Then
shell.Popup "Not found: " & ps1Path, 5, "WG pre-UDP", 48
WScript.Quit 1
End If
cmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & ps1Path & """"
shell.Run cmd, 0, False- Если после запуска helper вы слишком долго не включили туннель — просто повторите:
Deactivate → helper → Activate. - Если у вас несколько
.conf— helper выберет самый свежий файл. При необходимости можно жёстко указать-ConfigPath. - Не публикуйте
PrivateKey/PresharedKeyпублично.