Marcarea traficului dintr-un cgroup este partea ușoară. Trei decizii separate trebuie să se alinieze înainte ca o singură aplicație să iasă prin interfața fizică în timp ce tot restul rămâne în tunel: care socket aparține excepției, ce căutare de rută declanșează marcajul și ce adresă sursă poartă pachetul când iese. Majoritatea încercărilor eșuate o fac corect pe prima, pe jumătate pe a doua și nu se uită niciodată la a treia.

Trei decizii, nu una

Clasificarea răspunde cine este scutit. Rutarea răspunde unde merge în schimb pachetul marcat. Selecția sursei răspunde ce va vedea capătul îndepărtat și unde trebuie să se întoarcă răspunsul.

Acestea eșuează independent. Un pachet poate fi clasificat, rerutat pe interfața fizică și totuși purta o adresă privată pe care niciun router din amonte nu o poate returna, ceea ce produce un timeout care arată exact ca un blocaj de firewall. Nimic din pasul de clasificare nu te avertizează despre asta. Nimic din pasul de rutare nici.

Și ordinea contează. Clasificarea are loc când pachetul este generat, rutarea după aceea, iar traducerea în punctul de ieșire.

Dacă nu poți numi care dintre cele trei etape este stricată, nu depanezi, ci rearanjezi reguli și speri.

Potrivește un cgroup care va exista și mâine

nftables poate testa apartenența unui socket la cgroup v2, ceea ce este cu adevărat util pentru că urmărește procesul și nu destinația. Capcana este identificatorul. O aplicație desktop lansată din meniu trăiește adesea într-un scope temporar al cărui nume se schimbă la următoarea lansare.

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

Copierea unui nume de scope dintr-o sesiune și codificarea lui rigidă produce o regulă care funcționează azi și încetează silențios să se potrivească mâine. Pune în schimb aplicația într-un context de execuție stabil și deliberat, cum ar fi un scope sau o unitate dedicată pe care o controlezi, și potrivește-te cu acela. Regula supraviețuiește astfel repornirilor și actualizărilor de pachete. Devine și ceva la care un coleg poate reflecta fără istoricul tău de sesiune.

Fii de asemenea precis cu privire la mecanismul pe care îl folosești. Clasificarea net_cls din cgroup v1 și potrivirea socketurilor din cgroup v2 sunt lucruri diferite, iar un câmp cu nume similar din documentație nu le face interschimbabile.

Lanțul route este cel care declanșează a doua căutare

Pentru traficul generat local, tipul de lanț decide dacă se întâmplă ceva după ce marcajul este setat. Un lanț output de tip filter poate marca un pachet, iar kernelul va continua să folosească ruta pe care a ales-o deja. Un lanț de tip route este mecanismul care cere o căutare nouă.

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

Marcajul are apoi nevoie de o regulă de politică ce îl trimite într-un tabel, iar acel tabel trebuie să fie complet: o rută implicită, destinațiile locale de care aplicația mai are nevoie și un gateway din amonte care răspunde cu adevărat.

Alege valoarea marcajului în mod deliberat. WireGuard folosește propriul fwmark pentru traficul de transport extern, iar aterizarea din greșeală pe același număr poate perturba excepția de rutare a tunelului însuși în moduri dureroase de diagnosticat.

Adresa sursă nu urmează ruta

Un socket care a ales deja o adresă sursă pentru calea tunelului păstrează acea adresă după rerutare. Nimic din schimbarea rutei nu o rescrie, motiv pentru care pachetul iese prin interfața fizică cu o sursă privată pe care rețeaua din amonte nu o poate returna.

Simptomul vizibil este o aplicație care pare să trimită date și nu primește niciodată răspuns, plus un jurnal de firewall care nu arată nimic pentru că pachetul nu s-a întors niciodată.

Corecția îngustă este o regulă de traducere a sursei limitată la marcajul acelei aplicații și la interfața ei reală de ieșire, integrată cu NAT-ul care există deja în loc să îl înlocuiască:

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

Tradu doar excepția și nimic altceva. O regulă masquerade generală repară simptomul și distruge capacitatea de a spune ce trafic a luat ce cale. Potrivește marcajul, interfața reală de ieșire și, dacă poți, adresa aplicației înseși. Regulile largi sunt ușor de scris și aproape imposibil de auditat mai târziu.

Ce încă scapă de excepție

Rezolvarea numelor poate veni de la un proces resolver separat într-un alt cgroup, așa că clasificarea socketului browserului nu clasifică și căutările lui. Dacă interogările resolverului rămân în tunel în timp ce conexiunea iese în afara lui, răspunsurile split-horizon pot să nu se potrivească cu calea. Acea nepotrivire produce defecțiuni care arată ca buguri DNS și nu sunt.

Procesele copil moștenesc cgroupul în majoritatea configurațiilor, dar un helper care se mută în propriul scope nu. Verifică în loc să presupui, mai ales pentru orice se reexecută.

În final, decide ce trebuie să se întâmple când tunelul este căzut. O excepție care devine silențios un bypass complet este o postură de securitate diferită de una care eșuează închis, și doar una dintre ele este ce ai intenționat.

Articole similare