SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor

Hoe werkt WireGuard cryptokey routing?

Begrijp hoe WireGuard werkt via cryptokey routing. Ontdek waarom AllowedIPs tegelijkertijd als routeringstabel en toegangslijst fungeert voor een veilige VPN configuratie.

Hoe WireGuard werkt, in één idee

WireGuard werkt door elk pakket te koppelen aan een publieke sleutel. Dit mechanisme heet cryptokey routing en vormt het volledige ontwerp: de AllowedIPs regel naast een peer fungeert als de routeringstabel voor pakketten die uw machine verlaten en als de toegangscontrolelijst voor pakketten die van die peer binnenkomen. Eén instelling, twee functies. Lees AllowedIPs op die manier en elk WireGuard-configuratiebestand wordt begrijpelijk.

Er is geen sessietabel op basis van IP-adressen en geen gebruikersdatabase. Een peer is een publieke sleutel plus de set adressen die die sleutel mag gebruiken. De handshake en de timers bestaan om die koppeling geldig te houden terwijl het onderliggende netwerk verandert. Wilt u eerst een werkende tunnel voordat u de theorie induikt, bouw er dan een met een zelfgehoste WireGuard VPN op uw eigen VPS, en keer hier terug wanneer een configuratieregel u verrast.

AllowedIPs is een routeringstabel en een toegangslijst

Kijk eerst naar de uitgaande richting. Uw kernel routeert een pakket op de gebruikelijke wijze naar het wg0-apparaat via de hoofdrouteringstabel. WireGuard vergelijkt vervolgens het bestemmingsadres van dat pakket met een tabel die alle toegestane prefixen van elke peer bevat, waarbij de langste prefix eerst wordt gecontroleerd. Een match wijst een peer aan, die een publieke sleutel aanwijst, die weer een sessiesleutel en een UDP-eindpunt aanwijst. Het pakket wordt voor die peer versleuteld en daarheen verzonden.

Als geen enkele AllowedIPs van een peer de bestemming dekt, wordt er niets verzonden, omdat er geen sleutel is om het mee te versturen.

ping: sendmsg: Required key not available

Die fout betekent één ding: het adres dat u probeerde te bereiken, staat niet vermeld onder een peer. Een andere fout, ping: sendmsg: Destination address required, betekent dat er wel een peer overeenkwam, maar dat WireGuard geen eindpunt voor die peer heeft, omdat er geen was geconfigureerd en er nog geen was geleerd.

Kijk nu naar de inkomende richting. Een UDP-pakket komt aan op de luisterpoort. WireGuard vindt de sessie op basis van de ontvangerindex in de header, controleert de teller tegen een verschuivend replay-venster en ontsleutelt en authenticeert vervolgens de payload. Pas daarna leest het het innerlijke pakket, en het bronadres van dat innerlijke pakket moet binnen de AllowedIPs van de verzendende peer vallen. Als dat niet zo is, wordt het pakket genegeerd. Met dynamische debug ingeschakeld, print de kernel de reden in een regel zoals deze:

wg0: Packet has unallowed src IP (10.8.0.9) from peer 2 (203.0.113.10:51820)

Dit is de reden waarom een peer aan de serverzijde een /32 krijgt. Een peer die is geconfigureerd met AllowedIPs = 10.8.0.2/32 mag pakketten verzenden vanaf 10.8.0.2 en vanaf geen enkel ander adres. Schrijf daar in plaats daarvan 0.0.0.0/0 en die specifieke client krijgt toestemming om pakketten te injecteren die elk bronadres binnen uw tunnel claimen, inclusief dat van een andere client.

Overlappende prefixen worden opgelost op basis van specificiteit, aangezien de opzoekmethode de langste prefix-match is. Identieke prefixen bij twee peers gedragen zich anders: het item verplaatst zich naar de peer die als laatste is geconfigureerd, en de eerste peer stopt met het ontvangen van dat verkeer zonder dat er ergens een foutmelding wordt geprint. wg show wg0 allowed-ips print de tabel die daadwerkelijk in de kernel staat, wat doorslaggevend is wanneer het bestand op schijf en de actieve status van elkaar zijn gaan afwijken.

Een configuratiebestand lezen met cryptokey-routing in gedachten

De serverzijde:

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server private key>

[Peer]
PublicKey = <laptop public key>
AllowedIPs = 10.8.0.2/32

De clientzijde:

[Interface]
Address = 10.8.0.2/32
PrivateKey = <laptop private key>

[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Hetzelfde trefwoord heeft een tegengestelde betekenis aan beide zijden. Op de client betekent het "stuur elke bestemming naar deze peer". Op de server betekent het "accepteer alleen dit ene adres van deze peer". De asymmetrie zit in de waarden, niet in een specifieke rol.

De helft van deze sleutels maakt geen deel uit van het protocol. Address, DNS, MTU, PostUp en SaveConfig behoren tot wg-quick, het shell-script dat de interface activeert. De kernel ziet deze nooit. wg-quick strip wg0 toont de gereduceerde configuratie die de wg-tool daadwerkelijk inlaadt; dit is de snelste manier om dat onderscheid te zien.

Wat de handshake daadwerkelijk doet

De handshake van WireGuard is Noise_IKpsk2, afkomstig uit het Noise Protocol Framework. Het IK-gedeelte is het relevante deel voor een systeembeheerder: de statische publieke sleutel van de responder is al bekend bij de initiator, aangezien dit de PublicKey in uw [Peer]-blok is. De initiator verstuurt zijn eigen statische publieke sleutel versleuteld in het eerste bericht. Er vindt dus geen certificaatuitwisseling of identiteits-round-trip plaats. Een passieve waarnemer kan niet zien welke sleutel de verbinding initieert, tenzij deze in het bezit is van de privésleutel van de responder.

De kosten hiervan bedragen één round-trip. Het initiatiebericht is 148 bytes, het antwoord is 92 bytes en daarna vloeit het dataverkeer direct. Elke zijde genereert per handshake een vers efemeer Curve25519-sleutelpaar. De sessiesleutels worden afgeleid uit een keten van Diffie-Hellman-resultaten die de statische en efemere sleutels combineert. De efemere privésleutels worden daarna vernietigd, wat zorgt voor forward secrecy: iemand die vandaag uw verkeer opneemt en volgend jaar de privésleutel van de server steelt, kan de opgenomen data nog steeds niet ontsleutelen.

Een handshake-initiatie bevat een TAI64N-tijdstempel. Elke peer onthoudt de hoogste tijdstempel die hij van de ander heeft ontvangen, waardoor een herhaalde (replayed) initiatie wordt geweigerd. Datapakketten bevatten een 64-bit teller die als nonce dient. De ontvanger houdt een schuivend venster bij van recent geziene tellers, zodat replays en sterke pakket-herschikking worden afgehandeld zonder TCP-achtige verbindingsstatus.

Sessiesleutels hebben een korte levensduur en de timers zijn in de code vastgelegd in plaats van configureerbaar.

ChartWireGuard protocol timers, in seconds
The data behind this chart
[
  {
    "label": "REKEY_TIMEOUT",
    "seconds": 5,
    "notes": "resend a handshake initiation that got no answer"
  },
  {
    "label": "KEEPALIVE_TIMEOUT",
    "seconds": 10,
    "notes": "send a keepalive after receiving data and sending none back"
  },
  {
    "label": "REKEY_ATTEMPT_TIME",
    "seconds": 90,
    "notes": "give up on the handshake and report the peer as down"
  },
  {
    "label": "REKEY_AFTER_TIME",
    "seconds": 120,
    "notes": "sender begins a fresh handshake for a new session key"
  },
  {
    "label": "REJECT_AFTER_TIME",
    "seconds": 180,
    "notes": "the old session key is refused and traffic stops"
  }
]

Dit zijn constanten uit de protocolspecificatie, geen metingen. De 5 ervan sturen de volledige levenscyclus van de sessie aan. Na 120 seconden gebruik start de zender een nieuwe handshake. Na 180 seconden wordt de oude sleutel direct geweigerd, waardoor het verkeer stopt totdat een nieuwe handshake is voltooid. Een initiatie die geen antwoord krijgt, wordt elke 5 seconden opnieuw verzonden en na 90 seconden afgebroken. Dit is de reden waarom wg show de waarde latest handshake als relatieve leeftijd weergeeft, en waarom een actieve, gezonde tunnel die leeftijd laag houdt. Een leeftijd die oploopt terwijl u actief verkeer verstuurt, betekent dat handshakes mislukken en niet dat de tunnel inactief is.

Waarom een peer geen client- of serverrol heeft

Beide zijden draaien identieke code en gebruiken hetzelfde configuratieformaat. Er is geen servermodus. De asymmetrie die u ervaart komt voort uit Endpoint, en Endpoint is optioneel.

Een peer met een geconfigureerd endpoint kan een handshake starten. Een peer zonder endpoint wacht af en leert het adres en de poort van de tegenpartij kennen zodra het eerste pakket correct is geverifieerd. Dit geleerde endpoint wordt opgeslagen en bijgewerkt telkens wanneer een geldig pakket vanaf een nieuw adres binnenkomt. Zo werkt roaming: een laptop die overschakelt van wifi naar een mobiel netwerk behoudt dezelfde tunnel, omdat een sessie wordt geïdentificeerd op basis van sleutel en index in plaats van op IP-adres. Er vindt geen herverbinding plaats, omdat er in de TCP-zin nooit een verbinding was.

Hetzelfde mechanisme zorgt voor een feit dat nuttig is om te weten: de peer met het publieke adres beschikt altijd over het laatst bekende publieke IP-adres van de tegenpartij, en wg show toont dit.

Vaste primitieven, geen onderhandeling

WireGuard bevat geen lijst met ciphersuites. ChaCha20-Poly1305 wordt gebruikt voor geauthenticeerde encryptie, Curve25519 voor sleuteluitwisseling, BLAKE2s voor hashing en HKDF voor sleutelafleiding. Elke implementatie gebruikt deze, waardoor er geen onderhandelingsfase is om te verwerken en geen mogelijkheid bestaat om terug te vallen op een zwakkere optie. De afweging is duidelijk: als een van deze primitieven wordt gekraakt, is de oplossing een nieuwe versie van het volledige protocol en een update aan beide zijden, in plaats van een configuratiewijziging. Deze enkele beslissing verwijdert het merendeel van de code en de meeste faalmodi die een TLS-gebaseerde tunnel met zich meebrengt; dit is waar de vergelijking in WireGuard versus OpenVPN grotendeels op neerkomt.

Waarom de poort niet reageert op een scanner

Elk handshake-bericht bevat een veld genaamd mac1. Dit is een MAC (message authentication code) die over het bericht wordt berekend met een sleutel die is afgeleid van de statische publieke sleutel van de responder. Een afzender die die publieke sleutel niet kent, kan geen geldige mac1 genereren, en de ontvanger negeert een dergelijk pakket zonder enig antwoord. Geen foutmelding, geen reset, geen ICMP-bericht.

Het zichtbare resultaat is een UDP-scan die niets terugkrijgt.

sudo nmap -sU -p 51820 vpn.example.com

nmap rapporteert open|filtered, wat hetzelfde antwoord is als voor een poort die door een firewall stilletjes wordt gedropt. De poort gedraagt zich hetzelfde, ongeacht of WireGuard luistert of niet, althans voor iedereen die niet reeds over uw publieke sleutel beschikt.

Een tweede veld, mac2, vangt druk door denial of service-aanvallen op. Wanneer de ontvanger onder belasting staat, beantwoordt deze een geldige initiatie met een cookie-antwoord van 64 bytes dat gekoppeld is aan het bronadres van de afzender. De ontvanger weigert kostbare berekeningen met publieke sleutels uit te voeren totdat de afzender die cookie terugstuurt. Dit bewijst dat het bronadres echt is voordat er CPU-capaciteit aan wordt besteed, en dit mechanisme wordt alleen geactiveerd onder belasting.

Waarom 0.0.0.0/0 van een peer uw standaardroute maakt

Omdat AllowedIPs de routeringstabel is, claimt AllowedIPs = 0.0.0.0/0, ::/0 elke bestemming voor die peer. Dat is de volledige instelling voor een full tunnel.

De routering die dit mogelijk maakt, is interessanter dan de regel zelf. Een standaardroute via wg0 zou een loop veroorzaken, omdat het versleutelde UDP-pakket dat uw verkeer vervoert ook de machine moet verlaten en zou matchen met zijn eigen standaardroute. wg-quick voorkomt dit met policy routing. Het markeert de uitgaande pakketten van WireGuard met een fwmark, plaatst de standaardroute van de tunnel in een aparte routeringstabel en voegt regels toe zodat alleen ongemarkeerd verkeer deze bereikt. Voer ip rule show uit om het resultaat te zien:

32764:	from all lookup main suppress_prefixlength 0
32765:	not from all fwmark 0xca6c lookup 51820
32766:	from all lookup main

0xca6c is 51820 in hexadecimale notatie, en 51820 is tevens het tabelnummer. De suppress_prefixlength 0-regel zorgt ervoor dat de main-tabel zijn eigen standaardroute overslaat, waardoor specifieke routes zoals uw lokale subnet voorrang behouden, terwijl al het overige verkeer naar de tunneltabel wordt verwezen. Een split tunnel heeft dit niet nodig: een beperktere lijst zoals AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 wordt simpelweg opgenomen als gewone routes in de main-tabel.

Eén aspect dat een full tunnel niet uit zichzelf oplost, is naamresolutie. De resolver die uw client via het lokale netwerk heeft verkregen, blijft meestal actief en de route daarheen is specifieker. Dat is een afzonderlijke taak, die wordt behandeld in DNS die buiten een WireGuard-tunnel lekt.

Waar PersistentKeepalive werkelijk voor dient

WireGuard verstuurt geen gegevens wanneer er geen verkeer is. Er is geen heartbeat, geen sessievernieuwing en niets op de lijn. Deze stilte is gunstig voor de accuduur en helpt in het eerder genoemde scenario met scanners, maar het verstoort één specifieke configuratie.

Een peer achter NAT (network address translation) of achter een stateful firewall is alleen van buitenaf bereikbaar zolang er een mapping in dat apparaat bestaat, en die mapping wordt aangemaakt door een uitgaand pakket. De levensduur van gangbare UDP-mappings begint bij ongeveer 30 seconden. Zodra de mapping verloopt, worden pakketten vanaf de publieke zijde door de middlebox geweigerd en lijkt de tunnel dood totdat de peer achter de NAT zelf iets verstuurt. PersistentKeepalive = 25 verstuurt elke 25 seconden een leeg, geauthenticeerd pakket. Dit valt binnen de kortste gangbare levensduur, waardoor de mapping open blijft.

Stel dit in op de peer die zich achter de NAT bevindt. Een server met een publiek adres en een open UDP-poort heeft dit niet nodig; het instellen ervan op een dergelijke server zorgt alleen voor extra verkeer. Verwar dit niet met de automatische keepalive, die wordt geactiveerd 10 seconden nadat een peer gegevens heeft ontvangen en zelf niets terug te sturen heeft. Die functie staat altijd aan en kan niet worden geconfigureerd.

Het routeren van uw LAN via de tunnel is geen functionaliteit van WireGuard

Stel dat peer B zich op een thuisnetwerk 192.168.50.0/24 bevindt en peer A moet dit kunnen bereiken. Twee afzonderlijke systemen moeten op elkaar worden afgestemd, waarvan er slechts één WireGuard is.

Het deel van WireGuard: voeg 192.168.50.0/24 toe aan de AllowedIPs van B op A. Hierdoor routeert A het prefix naar B en accepteert A pakketten van B die deze bronadressen bevatten. Zonder deze stap heeft cryptokey-routing geen sleutel voor de bestemming en geen toestemming voor de bron.

Het deel van de kernel: op B moet net.ipv4.ip_forward gelijk zijn aan 1, anders verwijdert de kernel elk ontsleuteld pakket dat niet aan B zelf is geadresseerd. De forward-chain van de firewall op B moet het verkeer toestaan. De hosts op het LAN hebben een route terug naar 10.8.0.0/24 nodig, of B moet bron-NAT toepassen zodat antwoorden via B terugkeren.

De taak van WireGuard eindigt wanneer het het ontsleutelde pakket overdraagt aan de kernel. Alles daarna is standaard Linux-routing en -filtering; daarom verschijnt deze fout in nft list ruleset-tellers of in ip -s link show wg0, en niet in wg show. Als u peers liever via een webinterface beheert, genereert het draaien van wg-easy in Docker de peer-vermeldingen voor u, hoewel de forwarding-regels nog steeds bij de host horen.

Waarom WireGuard in de kernel draait

wg0 is een netwerk-device driver. Pakketten bereiken deze via de normale routing-stack, worden versleuteld in de softirq-context en verlaten het systeem via een UDP-socket zonder ooit naar userspace te gaan. Dat is waar de doorvoersnelheid vandaan komt, en het is ook de reden waarom de module rond de vierduizend regels code blijft; klein genoeg om te controleren en in maart 2020 in de mainline Linux 5.6 te mergen. Ubuntu 24.04 en Debian 13 leveren het standaard mee, dus alleen het wireguard-tools-pakket ontbreekt nog.

Het zijn van een normale interface heeft praktische gevolgen. tcpdump -ni wg0 toont de onversleutelde innerlijke pakketten, terwijl tcpdump -ni eth0 udp port 51820 de versleutelde buitenste pakketten laat zien; door beide te vergelijken ziet u direct in welke richting de verbinding verbroken is. netfilter en traffic shaping behandelen wg0 als elke andere link. Waar de kernelmodule niet beschikbaar is, zoals bij container-virtualisatie die de host-kernel deelt, implementeert wireguard-go hetzelfde protocol in userspace via een TUN-device. Dit gaat ten koste van de doorvoersnelheid, omdat elk pakket tweemaal de grens van de kernel moet overschrijden.

Waar WireGuard u niet tegen beschermt

Het dreigingsmodel is bewust beperkt gehouden en een protocol dat zo stil is, nodigt uit tot wensdenken. Hieronder volgen de feiten.

  • Het verbergt niet dat u WireGuard gebruikt. Handshake-berichten hebben vaste groottes, de eerste byte geeft het berichttype aan en het transport verloopt via UDP. Deep packet inspection herkent dit eenvoudig en een netwerk dat VPN's niet toestaat, kan dit blokkeren. Obfuscatie is bewust weggelaten.
  • Het verbergt geen volume of timing. Payloads worden slechts opgevuld tot een grens van 16 bytes, waardoor een waarnemer nog steeds ziet wanneer u gegevens verstuurt en ongeveer hoeveel.
  • Het bewaart het laatst bekende eindpunt. De peer met het publieke adres slaat het huidige publieke IP-adres van de andere kant op en wg show toont dit. Samen met een tunneladres dat vaststaat in de configuratie, vormt dit een stabiele identificatie die een gebruiker volgt tussen netwerken. Op uw eigen VPS is dit geen probleem. Dit is ook de reden waarom commerciële diensten een extra laag boven op het protocol toevoegen.
  • Het authenticeert een sleutel, geen persoon. Wie het private key-bestand bezit, is de peer. Houd /etc/wireguard op modus 700 en de sleutelbestanden op 600.
  • Er is geen intrekkingslijst en geen verloopdatum. De toegang eindigt wanneer u de peer-vermelding verwijdert van elke server die deze bevat, en statische sleutels blijven geldig totdat u ze verwijdert.

Niets hiervan maakt WireGuard zwak. Het maakt het klein, en klein is het doel: het authenticeert en versleutelt, en het laat identiteitsbeheer en adrestoewijzing over aan wat u er bovenop bouwt. Een coördinatielaag zoals beschreven in WireGuard vergeleken met Tailscale bestaat om precies dat gat te dichten, gebruikmakend van hetzelfde datavlak waarover u zojuist heeft gelezen.

FAQ

Wat is cryptokey routing in WireGuard?

Cryptokey routing is de regel die elk pakket koppelt aan een publieke sleutel. Elke peer-entry bevat een lijst met prefixen in AllowedIPs. Bij uitgaand verkeer kiest WireGuard de peer door het bestemmingsadres van het pakket te matchen met de lijst van elke peer, waarbij de langste prefix voorrang krijgt; de lijst fungeert dus als een routeringstabel. Bij inkomend verkeer, zodra een pakket is ontsleuteld en geverifieerd, moet het interne bronadres binnen diezelfde lijst van de peer vallen, anders wordt het pakket genegeerd. De lijst fungeert dus ook als een toegangscontrolelijst. WireGuard heeft geen afzonderlijke routeringsconfiguratie of interne firewall, omdat deze ene lijst beide taken uitvoert.

Heb ik PersistentKeepalive op beide peers nodig?

Nee. Configureer dit aan de kant die zich achter NAT (network address translation) of achter een stateful firewall bevindt, wat meestal de client is. WireGuard verstuurt niets wanneer het inactief is, waardoor de mapping die de andere kant in staat stelt de peer te bereiken verloopt, vaak binnen een minuut, en de tunnel in één richting dood lijkt. PersistentKeepalive = 25 verstuurt elke 25 seconden een leeg, geverifieerd pakket om de mapping open te houden. Een peer met een publiek adres en een open UDP-poort heeft dit niet nodig.

Waarom geeft ping over de tunnel de melding "Required key not available"?

Omdat het bestemmingsadres niet voorkomt in de AllowedIPs van een peer. De cryptokey routing vond dus geen sleutel om het pakket mee te versleutelen en de kernel weigerde het te verzenden. Voer wg show wg0 allowed-ips uit en vergelijk de uitvoer met het adres dat u probeert te pingen. De vergelijkbare foutmelding Destination address required duidt op een ander probleem: er is wel een peer gevonden, maar WireGuard heeft geen endpoint voor deze peer, omdat er geen was geconfigureerd en er nog geen geverifieerd pakket van die peer is ontvangen.

Kan een firewall WireGuard detecteren en blokkeren?

Ja. WireGuard authenticeert en versleutelt uw verkeer, maar doet geen poging om zichzelf te maskeren. Handshake-berichten hebben een vaste grootte van 148 en 92 bytes, de eerste byte van elk bericht identificeert het type, en het transportprotocol is UDP. Deep packet inspection herkent het protocol daarom zonder problemen. Netwerken die UDP blokkeren of protocollen via fingerprinting identificeren, zullen het verkeer stoppen. Het verbergen van de tunnel vereist het inpakken van het verkeer in een ander protocol, wat een taak is voor externe tools en geen instelling binnen WireGuard zelf.