Kill-switch na een overstap van wifi naar mobiel: wat er breekt
Een kill-switch wordt geschreven tegen interfacenamen die een netwerkverandering vervangt. Controleer het buitenste pad, de adresfamilies en de resolver voordat je erop vertrouwt.
Het ene moment staat de laptop op de wifi van kantoor, het volgende hangt hij aan een telefoon, en de tunnelindicator is alweer groen voordat je klaar bent met het wisselen van vensters. Een groen lampje meldt alleen dat de tunnel opnieuw verbonden is; wat het routerings- en blokkeringsbeleid tijdens de onderbreking deed, is een andere vraag. Dat beleid is een verzameling matchvoorwaarden die bij de laatste installatie zijn gecompileerd, en het noemt een interface, een adres en een adresfamilie die de overgang zojuist heeft verwijderd. Dezelfde overstap levert daarom drie uitkomsten op die er identiek uitzien: de blokkering loopt door, het beleid blokkeert zijn eigen herstel, of er opent een pad dat niets dekt.
Niets op het scherm onderscheidt die drie toestanden, dus vraag op welke identificatoren de regels waren vastgezet in plaats van welke kleur de indicator toont. Op Linux kan een regel een interface noemen of een ipset dat daaruit gevuld is; op Android kan een VpnService via setUnderlyingNetworks worden verteld welke onderliggende netwerken te volgen. Elke binding wijst naar een verbinding die op het punt staat te worden vervangen, en elk moet daarna opnieuw worden beoordeeld.
Wat de overgang werkelijk herschrijft
Wanneer de oude verbinding verdwijnt, verwijdert de kernel de adressen, trekt de routes terug die erdoorheen liepen en bouwt de bronadreskeuze opnieuw op uit wat er overblijft. De standaardroute verhuist naar de nieuwe gateway, en de interface-index waar de oude regels naar verwezen is weg of hergebruikt voor een ander apparaat. Resolvers per verbinding worden ook vernieuwd, zij het niet op hetzelfde moment als de routes. Dit alles gebeurt onder de VPN-client, dus een client die alleen naar de toestand van zijn eigen tunnel kijkt, ziet er niets van.
Het probleem is dat een regel een specifieke identificator codeert in plaats van de bedoeling erachter. Een regel die alles laat vallen wat niet via de tunnelinterface naar buiten gaat, codeert een interfacenaam; een regel die het eindpunt toestaat, codeert een adres dat het nieuwe netwerk elders kan routeren. Als de identificator verdwijnt, wordt de regel niet zichtbaar fout: hij stopt met ergens op te matchen of begint op het verkeerde te matchen.
De buitenste tunnel herbouwen zonder een gat te slaan
Het buitenste transport was gebonden aan het oude bronadres, dus het moet een nieuwe socket vanaf het nieuwe openen. Wanneer het eindpunt een hostnaam is en geen letterlijk adres, moet een opzoeking slagen terwijl de blokkering nog van kracht is. Dat is de valkuil van de volgorde: een beleid dat streng genoeg is om alles behalve het eindpunt te laten vallen, laat ook de opzoeking vallen die nodig is om het te vinden, tenzij de resolutie plaatsvindt voordat de blokkering wordt opgeheven of door een nog levende tunnel wordt gedragen. Beide volgordes kunnen werken, maar alleen als de client weet in welke hij zit.
Clients verschillen hier op een manier die bij het diagnosticeren telt. Sommige lossen eerst op en installeren daarna de regels, zodat een overstap een zichtbare storing kost en daarna vanzelf herstelt; andere houden de blokkering in stand en vertrouwen op de overlevende tunnel om de opzoeking te dragen, wat volledig faalt wanneer de tunnel met de verbinding stierf. Een brede directe standaardroute toevoegen om de verbinding te herstellen laat de test altijd slagen en laat altijd het lek achter dat je voorkwam.
Een route die nog naar de oude gateway wijst, geeft hetzelfde vastgelopen herstel door een andere oorzaak, dus lees de tabel voordat je de client de schuld geeft. De opdrachten hieronder noemen de interface en het bronadres dat de kernel echt zou gebruiken, en dat beslist in één regel of het oude pad nog is geïnstalleerd.
ip route get 1.1.1.1
ip -6 route get 2606:4700:4700::1111Wanneer de nieuwe verbinding IPv6 brengt en de regels niet
Mobiele en gastnetwerken delen vaak een globale IPv6-prefix uit, zelfs wanneer het bekabelde LAN van kantoor dat nooit deed. Een beleid dat alleen als IPv4-regels is geschreven, de standaardvorm van de meeste handgemaakte iptables- en nftables-recepten, dekt daar niets van. De tunnel blijft verbonden, applicaties met IPv6-bereikbaarheid gaan erbuiten, en de indicator kan het verschil niet tonen omdat de tunnel echt actief is. Het spiegelgeval is even gewoon: een regelset alleen voor IPv6 doet helemaal niets op een netwerk dat geen v6 biedt.
Vergelijk wat de verbinding biedt met wat het beleid vermeldt, aan beide zijden van de overgang. Deze opdrachten drukken dezelfde feiten af als waarmee de client werkt:
ip -4 route show default
ip -6 route show default
sudo nft list ruleset | grep -c 'ip6 'Als de tweede opdracht een standaardroute afdrukt terwijl de derde nul teruggeeft, kan de nieuwe verbinding de machine via IPv6 naar buiten brengen zonder dat een regel erop let. Voer alle drie opnieuw uit na de wissel, want het interessante getal is het verschil tussen de twee runs. Als de tweede niets afdrukt, geldt het omgekeerde geval en is elk v6-zwaar beleid daar louter versiering.
De resolver wisselt midden in de sessie van eigenaar
Op moderne systemen zijn resolvers aan verbindingen gekoppeld, dus een nieuwe verbinding komt met een nieuwe set resolvers en mogelijk een andere split-DNS-weergave. systemd-resolved houdt één scope per verbinding en kiest daartussen op basis van de route, terwijl macOS scoped resolvers uit het dynamische archief leest en de interfacevolgorde respecteert. Een client die zijn eigen resolver installeert, concurreert met die van de verbinding, en prioriteitsregels waar niemand naar kijkt beslissen de winnaar. Dan kan naamresolutie breken terwijl pakketfiltering precies doet waarvoor het is geschreven.
Lees de resolver die actief is in plaats van de geconfigureerde. Op een systemd-host toont de subopdracht status van welke verbinding elke scope kwam en of een private-DNS-naam van kracht is:
resolvectl status --no-pager
resolvectl query intranet.example.comAls de query via de fysieke verbinding naar buiten gaat in plaats van via de tunnel, heeft de client de prioriteitsstrijd verloren en geen enkele firewall-afstemming zal de naamgeving repareren. Vergelijk het pad van de query aan beide zijden van de overgang om te zien welke verschoof. Een naam die naar een privéadres resolveert dat het nieuwe netwerk niet kan routeren, is een derde geval, en het presenteert zich als een time-out en niet als een fout.
Oefen de overgang en houd het verslag bij
Reproduceer de verandering met opzet in plaats van erop te wachten. Breng de tunnel op de eerste verbinding omhoog, start een langdurige overdracht en een herhaalde opzoeking, wissel dan van verbinding en kijk welke overleeft. Noteer het interval tussen het verdwijnen van de oude verbinding en de melding van de tunnel dat hij verbonden is, want in dat venster hield het beleid stand of niet. Voer de reeks ook in de andere richting uit, zodat het resultaat niet eenzijdig is.
Uitzonderingen verdienen hun eigen ronde, want een omweg die op het oude netwerk gerechtvaardigd was, is dat op het nieuwe niet automatisch. Een printer, een NAS of een captive portal op het thuis-LAN heeft geen tegenhanger op mobiel, en de uitzondering die er kwam, wordt een regel zonder tegenhanger.
Schrijf de interfacenamen, de geboden families, de geldende resolver en de duur van de storing per richting op. Een herverbinding bewijst dat het transport zich herstelde, niet dat de blokkering tijdens de onderbreking standhield, en dat zijn aparte beweringen voor een ticket. Met dat verslag wordt de volgende overgang met een bekende toestand vergeleken in plaats van met het geheugen.