Je stelt je voor dat de VPN-server het adres van je laptop uit het pakket leest en in een log schrijft. Wat hij leest, is wat de laatste NAT-hop in de buitenste header heeft gezet, en op een telefoon die als hotspot dient is dat vaak een adres dat met honderden andere abonnees wordt gedeeld. Het eigen adres van je machine 192.168.x.x steekt in die header nooit het internet over, en het tunneladres wordt door de server uitgegeven nadat de verbinding al tot stand is gebracht. Dus wanneer een serverlog en ip addr op de laptop niet overeenkomen, is geen van beide fout; ze beschrijven verschillende lagen van één sessie.

Drie adressen, en slechts één staat in het log

De verwarring ontstaat doordat drie adressen tot één worden samengevat. Elk heeft een andere eigenaar, leeft in een andere header en bereikt de server via een andere route.

AddressWho assigns itWhere it sitsVisible to the VPN server
Device address, e.g. 192.168.1.24Your router via DHCPOuter header, before translationNo, rewritten in place
Translated address and portRouter, ISP CGNAT, or bothOuter header, after translationYes, this is the tuple it logs
Tunnel address, e.g. 10.8.0.6The VPN server itselfInner header, after decapsulationYes, but it chose it

Alleen de middelste rij is onbekend op het moment van verbinden. De server schrijft die afhankelijk van de daemon in zijn peertabel, statusbestand of flowlog: WireGuard noemt het het eindpunt, een OpenVPN-statusbestand noemt het Real Address, en strongSwan meldt het als de externe host van de IKE_SA. Die waarde wordt vernieuwd telkens wanneer een pakket van een andere bron aankomt, dus verandert hij zonder dat iemand iets herstart.

De onderste rij is wat mensen ten onrechte als een identiteitsclaim lezen. Hij markeert een plek in de serverconfiguratie, niet een persoon, omdat de server hem uit zijn eigen pool koos. Twee gebruikers kunnen op verschillende dagen hetzelfde tunneladres krijgen, en één kan er twee van twee apparaten hebben.

Waarom de gelogde tuple verandert terwijl de tunnel blijft staan

Adresvertaling is een tabel met een timer, geen permanente binding. Een thuisrouter houdt zijn UDP-mapping nog even na het laatste pakket, een carriergateway hergebruikt poorten volgens zijn eigen schema, en een telefoon die van Wi-Fi naar LTE gaat komt terug met een heel andere bron. De standaardwaarden van conntrack in Linux zijn hier een nuttige maatstaf: nf_conntrack_udp_timeout is 30 seconden en de streamingvariant is 120, dus een stille tunnel kan zijn mapping verliezen lang voordat de gebruiker het merkt.

De tunnel overleeft omdat het protocol de peer authenticeert, niet het adres. WireGuard is na de handshake staatloos, dus elk geauthenticeerd pakket werkt het opgeslagen eindpunt bij, en een herverbinding herstelt bij het volgende pakket; daarom bestaat persistent-keepalive = 25, om de mapping warm te houden tijdens inactiviteit. De --float van OpenVPN doet hetzelfde en laat een geauthenticeerde client doorgaan vanaf een nieuw adres en een nieuwe poort. Op 2.4- en 2.5-builds waar float standaard uit staat, reset een herverbinding de sessie en verbindt de client opnieuw.

IPsec bereikt dat met meer bewegende delen. IKEv2 merkt de vertaling op door de bron- en bestemmingshashes in de NAT-detectiepayloads te vergelijken, schakelt over op UDP 4500-encapsulatie en volgt keepalives naar de nieuwe tuple. Wanneer keepalives worden geblokkeerd, blijft de SA op papier staan terwijl verkeer in één richting stilvalt.

Wat de server kan toeschrijven, en wat niet

De mythe die het verdient te worden gedood, is dat vertaling je voor de operator verbergt. Het verandert het label, niet de waarneming. Een gateway achter carrier-NAT ziet 100.64.0.0/10-ruimte of één openbaar adres dat veel abonnees delen, en kan de stroom nog steeds koppelen aan een certificaatonderwerp, een openbare WireGuard-sleutel, een toegewezen tunneladres, bytetellers en een tijdvenster. Niets daarvan heeft je LAN-adres nodig.

De grenzen werken ook de andere kant op. Een gedeeld adres in een log wijst op een gateway, niet op een huishouden; twee sessies vanaf hetzelfde vertaalde adres op verschillende poorten zijn verschillende apparaten; hetzelfde adres op twee verschillende dagen kunnen twee ongerelateerde abonnees zijn als de poort is hergebruikt. Een server die alleen peersleutels vastlegt, kan er zonder de accountdatabase geen aan een klant koppelen, en een server die alleen adressen vastlegt, kan een laptop niet van een telefoon achter dezelfde router onderscheiden.

Vergelijk beide uiteinden in plaats van te gissen

Om vast te stellen wat er is gebeurd, leg je dezelfde verbinding aan beide uiteinden op hetzelfde moment vast en vergelijk je de tuple, niet het adres. Op de client, vóór enige vertaling:

ss -uapn 'dport = :51820'
ip -4 route get 203.0.113.10
wg show wg0 endpoints

Lees daarna de vertaalregel op de router of de CGNAT-gateway, en wat uiteindelijk op de server aankomt:

conntrack -L -p udp --dport 51820
tcpdump -ni eth0 udp port 51820

Drie patronen verklaren de meeste afwijkingen. De bronpoort op de server verschilt van de lokale poort terwijl de bestemming overeenkomt: gewone poortvertaling. Het bronadres op de server hoort bij een ISP-blok dat niet het jouwe is: minstens één extra NAT-laag. Pakketten stoppen na een inactieve periode en hervatten met een nieuwe poort: de mapping is verlopen, geen routeringsfout. Als de tijdstempels meer dan een seconde of twee uit elkaar liggen, vergelijk je twee verschillende pogingen.

Waar het verschil dingen stukmaakt

Storingen die op serverfouten lijken, beginnen meestal hier. Een toegangsregel die aan je thuisadres vastzit faalt nadat de ISP-lease verandert. Een fail2ban-regel die op een vertaald adres past, blokkeert alle abonnees achter die gateway tegelijk. Een site-naar-site-peer met een vast extern adres stopt met het vernieuwen van sleutels nadat een mobiele verbinding verandert. Het tunnelprotocol was in orde; de aanname over de buitenste tuple niet.

Dezelfde verwarring verschijnt aan de serverkant. Als de VPN-doos zelf achter NAT staat, heb je portforwarding en een werkend doorstuurpad nodig; de doorstuurhandleiding van strongSwan dekt dat geval voor IPsec. Een server die alleen op een privéadres bereikbaar is, ziet de vertaalde tuple die zijn clients aanbieden nooit, en dat is een ander probleem dan dat clients worden vertaald.

Doorloop deze vier controles in volgorde en stop bij de eerste die faalt:

  • Leg aan beide uiteinden tegelijk vast; bevestig dat bronpoort, bestemmingspoort en tijdstempels overeenkomen.
  • Lees in de peerlijst van de server het adres dat hij nu heeft, niet het adres dat je verwachtte.
  • Controleer de conntrack- of NAT-tabel op de vermelding en de resterende levensduur.
  • Bevestig dat het tunnelinterface-adres op de client overeenkomt met wat de server toewees, niet met het LAN-adres.

Als beide opnamen het eens zijn over de tuple en de tunnel toch faalt, is vertaling niet je probleem; ga verder naar de handshake en naar de routeringstabel binnen de tunnel.

Gerelateerde artikelen