🛡️ WireGuard Guardian – MikroTik RouterOS 7.20.x LT
🔧 DynDNS + Anti-Zombie + Conntrack Cleanup + Ping-Gate
—
🎯 OBIETTIVO
Questa configurazione rende WireGuard stabile e autonomo su MikroTik:
🟩 Aggiorna DynDNS automaticamente
🟩 Rileva tunnel “UP ma morto”
🟩 Pulisce solo le connessioni WireGuard (flush chirurgico)
🟩 Evita reset inutili con Ping-Gate
🟩 Riduce interventi manuali e ticket dei clienti
Risultato: VPN resiliente.
—
📌 PARAMETRI STANDARD (nel tuo caso)
| Voce | Valore |
|---|---|
| Interfaccia WG | wireguard2 |
| Porta WG | 51822 |
| Ping Target | 192.168.1.100 |
| Ping Source (IP su WG) | 192.168.253.2 |
| Address List endpoint | WG_ENDPOINTS |
| Connection Mark | WG_OUTER |
| Script | wg-guardian |
| Scheduler | wg-guardian |
🟨 Se su altri router cambiano nomi/porte/IP, modifica questi valori nei blocchi di configurazione.
—
1️⃣ Convenzione commenti peer (DynDNS)
Per i peer con endpoint DynDNS, aggiungi nel commento del peer:
WG_DNS=tuodominio.dyndns.org
✅ Esempio:
SedeRoma WG_DNS=roma.miodominio.it
🔎 Lo script leggerà questo tag e aggiornerà endpoint-address quando cambia l’IP.
—
2️⃣ Address-List endpoint WG_ENDPOINTS
La lista WG_ENDPOINTS serve per:
- far sì che il mangle marchi solo WireGuard
- permettere un flush conntrack “chirurgico”
- centralizzare FQDN/IP (DynDNS e statici)
2.1 Aggiungi FQDN DynDNS
/ip firewall address-list
add list=WG_ENDPOINTS address=roma.miodominio.it comment="SedeRoma"
add list=WG_ENDPOINTS address=casa.miodominio.it comment="Casa"
2.2 Aggiungi endpoint con IP statico (opzionale ma consigliato)
/ip firewall address-list
add list=WG_ENDPOINTS address=2.33.207.149 comment="PeerStatico"
2.3 Verifica lista
/ip firewall address-list print where list="WG_ENDPOINTS"
🟨 Nota: se usi FQDN, RouterOS li risolve internamente. È normale vedere aggiornamenti nel tempo.
—
3️⃣ Mangle: tagging conntrack WireGuard (porta 51822)
🎯 Obiettivo: applicare connection-mark=WG_OUTER al traffico UDP WireGuard outer (WAN), così il flush rimuove solo quello.
3.1 Regola IN (prerouting) — consigliata
/ip firewall mangle
add chain=prerouting action=mark-connection new-connection-mark=WG_OUTER passthrough=yes
protocol=udp dst-port=51822 src-address-list=WG_ENDPOINTS comment="Mark WG outer IN dst-port 51822"
3.2 Regola OUT (output) — consigliata
/ip firewall mangle
add chain=output action=mark-connection new-connection-mark=WG_OUTER passthrough=yes
protocol=udp dst-port=51822 dst-address-list=WG_ENDPOINTS comment="Mark WG outer OUT dst-port 51822"
3.3 Metti le regole in cima (best practice)
/ip firewall mangle move [find comment="Mark WG outer IN dst-port 51822"] destination=0
/ip firewall mangle move [find comment="Mark WG outer OUT dst-port 51822"] destination=0
3.4 Verifica contatori
/ip firewall mangle print stats where comment~"Mark WG outer"
✅ Devi vedere i contatori crescere su entrambe (IN/OUT).
3.5 Verifica conntrack marcato
/ip firewall connection print where connection-mark="WG_OUTER"
✅ Dovresti vedere entry UDP legate alla porta 51822 verso gli endpoint.
—
4️⃣ Script wg-guardian con Ping-Gate (versione “robusta”)
Lo script fa:
1) setta UDP timeouts del conntrack (idempotente)
2) aggiorna endpoint DynDNS (WG_DNS= nel commento)
3) rileva sospetto zombie (last-handshake vecchio + WireGuard “in uso” = conntrack marcato presente)
4) fa ping di verifica (Ping-Gate)
5) se ping FAIL → flush conntrack marcato + bounce peer
4.1 Rimuovi script precedente (se esiste)
/system script remove [find name="wg-guardian"]
4.2 Crea lo script (⚠️ usa source={ ... })
/system script add name=wg-guardian policy=read,write,test source={
:local wgIf "wireguard3"
:local maxHandshakeAge 180
:local udpTimeout "2m"
:local udpStreamTimeout "10m"
:local logPrefix "WG-GUARD: "
# Ping-gate
:local pingTarget "192.168.57.1"
:local pingSrc "192.223.223.3"
# Conntrack tuning (idempotente)
:do {
/ip firewall connection tracking set udp-timeout=$udpTimeout udp-stream-timeout=$udpStreamTimeout
} on-error={
:log warning ($logPrefix . "Conntrack UDP timeout non impostati (CT disabilitato?)")
}
# helper: estrai WG_DNS=... dal commento
:local getDnsFromComment do={
:local c $1
:local p [:find $c "WG_DNS="]
:if ($p = -1) do={ :return "" }
:local s ($p + 7)
:local rest [:pick $c $s [:len $c]]
:local sp [:find $rest " "]
:if ($sp = -1) do={ :return $rest }
:return [:pick $rest 0 $sp]
}
# helper: ping robusto -> ritorna numero risposte (0..count)
:local safePing do={
:local target $1
:local src $2
:local cnt $3
:local replies 0
:do {
:set replies [:ping $target count=$cnt src-address=$src interval=500ms]
} on-error={
:set replies 0
}
# se per qualche motivo non è numerico, azzera
:if ([:typeof $replies] != "num") do={ :set replies 0 }
:return $replies
}
# helper: last-handshake robusto -> ritorna secondi, o 999999 se "never"/non numerico
:local hsAgeSeconds do={
:local v $1
:if ([:typeof $v] = "num") do={ :return $v }
:if ([:typeof $v] = "time") do={ :return $v }
:return 999999
}
# WG "in uso" se esistono connessioni marcate
:local wgInUse false
:if ([:len [/ip firewall connection find where protocol=udp and connection-mark="WG_OUTER"]] > 0) do={
:set wgInUse true
}
:foreach pid in=[/interface wireguard peers find where interface=$wgIf] do={
:local comment [/interface wireguard peers get $pid comment]
:local endpointOld [/interface wireguard peers get $pid endpoint-address]
# 1) DynDNS refresh (solo se comment contiene WG_DNS=host)
:local dnsHost [$getDnsFromComment $comment]
:if ([:len $dnsHost] > 0) do={
:local newIp ""
:do { :set newIp [:resolve $dnsHost] } on-error={ :set newIp "" }
:if ([:len $newIp] > 0 && $newIp != $endpointOld) do={
/interface wireguard peers set $pid endpoint-address=$newIp
:log info ($logPrefix . "DynDNS update: " . $comment . " -> " . $newIp . " (prima: " . $endpointOld . ")")
}
}
# 2) Zombie detect: handshake vecchio + wgInUse
:local hsRaw [/interface wireguard peers get $pid last-handshake]
:local hsAge [$hsAgeSeconds $hsRaw]
:local rxNow [/interface wireguard peers get $pid rx]
:local txNow [/interface wireguard peers get $pid tx]
:if (($hsAge > $maxHandshakeAge) && ($wgInUse = true)) do={
:log warning ($logPrefix . "SUSPECT: " . $comment . " hsAge=" . $hsAge . "s rx=" . $rxNow . " tx=" . $txNow)
# Ping-gate: se ping OK, non fare recovery
:local replies [$safePing $pingTarget $pingSrc 3]
:if ($replies > 0) do={
:log info ($logPrefix . "Ping OK (" . $replies . "/3) " . $pingTarget . " src " . $pingSrc . " -> skip recovery per " . $comment)
} else={
:log warning ($logPrefix . "Ping FAIL (0/3) " . $pingTarget . " src " . $pingSrc . " -> recovery per " . $comment)
# 3) Flush conntrack SOLO marcate WG_OUTER
:local removed 0
:foreach cid in=[/ip firewall connection find where protocol=udp and connection-mark="WG_OUTER"] do={
/ip firewall connection remove $cid
:set removed ($removed + 1)
}
:log warning ($logPrefix . "Conntrack flush WG_OUTER: rimossi " . $removed)
# 4) Bounce peer
/interface wireguard peers disable $pid
:delay 2
/interface wireguard peers enable $pid
:log warning ($logPrefix . "Peer bounce: " . $comment)
}
}
}
}
—
5️⃣ Test manuale
5.1 Esegui lo script
/system script run wg-guardian
5.2 Controlla i log
/log print where message~"WG-GUARD"
🟩 Se tutto è sano potresti non vedere log (a parte eventuali DynDNS update).
🟥 Se c’è un problema vedrai SUSPECT, poi Ping FAIL e recovery.
—
6️⃣ Scheduler (automazione)
Esegui lo script ogni 1 minuto:
/system scheduler add name=wg-guardian interval=1m on-event=wg-guardian
Verifica:
/system scheduler print where name="wg-guardian"
—
7️⃣ Verifiche finali (checklist)
7.1 Mangle IN/OUT attivi e contatori in aumento
/ip firewall mangle print stats where comment~"Mark WG outer"
7.2 Conntrack marcato
/ip firewall connection print where connection-mark="WG_OUTER"
7.3 Ping manuale (deve rispondere quando la VPN è ok)
/ping address=192.168.1.100 src-address=192.168.253.2 count=3
—
8️⃣ Troubleshooting rapido
🟨 Non vedi conntrack marcato (WG_OUTER):
- controlla che gli endpoint siano in
WG_ENDPOINTS - controlla i contatori delle regole mangle
🟨 Ping sempre FAIL anche quando la VPN “sembra ok”:
- verifica che
192.168.1.100risponda davvero - verifica che
192.168.253.2esista sul MikroTik ed è l’IP corretto sulla WG
🟨 Recovery troppo frequenti:
- alza
maxHandshakeAge(es. 300 o 600) - aumenta
countping ointerval - usa un host target ping “sempre acceso” (gateway, server, NAS)
—
9️⃣ Replicazione su altre RouterBoard
🟩 Cosa devi modificare per ogni router:
wgIf(nome interfaccia WG)- porta 51822 nelle regole mangle (se diversa)
- address-list
WG_ENDPOINTS(FQDN/IP endpoint) pingTargetepingSrc
🟩 Best practice: salva questa guida in repo (Git) e tieni una versione per “template cliente”.
—
✅ Risultato
Con questa configurazione:
✅ DynDNS sempre aggiornato
✅ Recovery automatico solo quando serve (Ping-Gate)
✅ Flush conntrack “chirurgico” grazie a WG_OUTER
✅ WireGuard stabile anche in scenari “maledetti” di NAT/ISP
Autore: Frank – WireGuard Guardian System

Scrivi un commento