Verkeer van één cgroup markeren is het makkelijke deel. Drie afzonderlijke beslissingen moeten kloppen voordat één applicatie via de fysieke interface naar buiten gaat terwijl al het andere in de tunnel blijft: welke socket bij de uitzondering hoort, welke routeopzoeking de markering uitlokt, en welk bronadres het pakket draagt wanneer het vertrekt. De meeste mislukte pogingen doen de eerste goed, de tweede half, en kijken nooit naar de derde.

Drie beslissingen, niet één

Classificatie beantwoordt wie vrijgesteld is. Routering beantwoordt waar het gemarkeerde pakket in plaats daarvan heen gaat. Bronselectie beantwoordt wat de verre kant zal zien en waar het antwoord moet terugkeren.

Deze falen onafhankelijk. Een pakket kan worden geclassificeerd, naar de fysieke interface worden omgeleid en toch een privéadres dragen dat geen enkele upstream-router kan terugsturen, wat een time-out oplevert die er precies als een firewallblokkade uitziet. Niets in de classificatiestap waarschuwt je daarvoor. Niets in de routeringsstap ook.

De volgorde is ook belangrijk. Classificatie gebeurt wanneer het pakket wordt gegenereerd, routering daarna, en vertaling op het punt van uitgang.

Als je niet kunt benoemen welke van de drie fasen kapot is, dan debug je niet, dan herschik je regels en hoop je.

Match een cgroup die er morgen nog is

nftables kan het cgroup v2-lidmaatschap van een socket testen, wat echt nuttig is omdat het de proces volgt en niet de bestemming. De valkuil is de identificatie. Een desktopapplicatie die vanuit een menu wordt gestart, leeft vaak in een tijdelijke scope waarvan de naam bij de volgende start verandert.

nft list ruleset
systemctl status session.slice 2>/dev/null
cat /proc/self/cgroup

Een scopennaam uit één sessie kopiëren en hardcoderen levert een regel op die vandaag werkt en morgen stilzwijgend niet meer matcht. Zet de applicatie in plaats daarvan in een bewuste, stabiele uitvoeringscontext, zoals een eigen scope of unit die je beheert, en match die. De regel overleeft dan herstarts en pakketupdates. Het wordt ook iets waar een collega over kan nadenken zonder jouw sessiegeschiedenis.

Wees ook precies over welk mechanisme je gebruikt. net_cls-classificatie van cgroup v1 en socketmatching van cgroup v2 zijn verschillende dingen, en een gelijknamig veld in de documentatie maakt ze niet uitwisselbaar.

Een route-chain is wat de tweede opzoeking uitlokt

Voor lokaal gegenereerd verkeer bepaalt het ketentype of er iets gebeurt nadat de markering is gezet. Een output-chain van het type filter kan een pakket markeren en de kernel blijft de route gebruiken die hij al had gekozen. Een chain van het type route is het mechanisme dat om een nieuwe opzoeking vraagt.

table inet bypass {
  chain mark {
    type route hook output priority mangle; policy accept;
    socket cgroupv2 level 2 "app.slice" meta mark set 0x1 comment "app bypass"
  }
}
ip rule add fwmark 0x1 lookup 200
ip route add default via 203.0.113.1 dev wlan0 table 200

De markering heeft dan een beleidsregel nodig die hem naar een tabel stuurt, en die tabel moet compleet zijn: een standaardroute, lokale bestemmingen die de applicatie nog nodig heeft, en een upstream-gateway die daadwerkelijk antwoordt.

Kies de markeringswaarde bewust. WireGuard gebruikt zijn eigen fwmark voor extern transportverkeer, en per ongeluk op hetzelfde nummer uitkomen kan de route-uitzondering van de tunnel zelf verstoren op manieren die pijnlijk zijn om te diagnosticeren.

Het bronadres volgt de route niet

Een socket die al een bronadres voor het tunnelpad heeft gekozen, houdt dat adres na de omleiding. Niets in de routewijziging herschrijft het, en daarom verlaat het pakket de fysieke interface met een privébron die het upstream-netwerk niet kan terugsturen.

Het zichtbare symptoom is een applicatie die data lijkt te versturen en nooit een antwoord ontvangt, en een firewall-log die niets toont omdat het pakket nooit terugkwam.

De smalle oplossing is een brontranslatieregel die beperkt is tot de markering van die applicatie en zijn echte uitgangsinterface, geïntegreerd met de NAT die al bestaat in plaats van die te vervangen:

table inet bypass {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    meta mark 0x1 oifname "wlan0" masquerade
  }
}

Vertaal de uitzondering en niets anders. Een algemene masquerade-regel verhelpt het symptoom en vernietigt je vermogen om te zeggen welk verkeer welke weg nam. Match op de markering, de echte uitgangsinterface en, als het kan, het adres van de applicatie zelf. Brede regels zijn makkelijk te schrijven en bijna onmogelijk later te auditen.

Wat nog steeds aan de uitzondering ontsnapt

Naamresolutie kan van een apart resolverproces in een andere cgroup komen, dus het classificeren van de socket van de browser classificeert zijn opzoekingen niet. Als de query's van de resolver in de tunnel blijven terwijl de verbinding erbuiten gaat, kunnen split-horizon-antwoorden niet bij het pad passen. Die mismatch levert storingen op die op DNS-bugs lijken en het niet zijn.

Childprocessen erven de cgroup in de meeste opstellingen, maar een helper die zichzelf naar zijn eigen scope verplaatst niet. Controleer in plaats van aan te nemen, vooral voor alles wat zichzelf opnieuw uitvoert.

Beslis ten slotte wat er moet gebeuren als de tunnel plat ligt. Een uitzondering die stilzwijgend een volledige bypass wordt, is een andere beveiligingspositie dan een die fail-closed is, en slechts één daarvan is wat je bedoelde.

Gerelateerde artikelen