Zvyk je železná košile a po půldruhém desetiletí s iptables jsem musel s trochou mírné nelibosti přijmout fakt, že můj oblíbený Debian je označil jako zastaralou technologii a začal protěžovat nftables. V Busteru (Debian 10) je to tak, že binárka iptables je defakto překladač zápisu pravidel iptables to nftables. Vsadím se, že v další verzi se budou muset iptables doinstalovávat nějakým šamanským způsobem a v přespříští "sorry jako." A protože Debian provozuji převážně na síťových uzlech, kde firewall hraje hlavní roli, rozhodl jsem se, že je lepší být připraven již nyní a nftables jsem vzal na milost. Zpětně musím uznat, že to bylo dobré rozhodnutí, které mi pomohlo vyřešit jednu zapeklitou situaci naší síti malého lokálního ISP.
Starám se o síť jednoho malého ISP (cca. 800 uživatelů, resp. 900 přípojek). Technicky jde o slepenec lokálních sítí propojených s centrálním uzlem pomocí bezdrátových, metalických a optických linek. Od poskytovatele připojení k Internetu máme k dispozici dostatečný počet veřejných IP adres, které tak můžeme přidělit každému uživateli. Avšak není jich tolik, abychom jimi mohli plýtvat rozsekáváním na subnety a vzhledem k různorodému HW není možné v rámci lokálních sítí navazovat tunely s cílem dostat veřejnou IP až na koncové zařízení klienta - musíme tak veřejné adresy překládat na adresy lokální - tedy SNAT a DNAT, resp. 1:1 NAT.
Aktuálně máme k dispozici linku do Internetu 1.2 Gbit. Ta končí na malém serveru HP ML310e Gen8 s CPU E3-1220v3 3.1GHz, 4 jádra, 4GB RAM. Není to bůhvíjaké dělo, ale slouží absolutně bez jakýchkoliv HW problémů již několik let. V serveru je 5 síťovek, jedna na připojení do Internetu, čtyři na různé linky k uživatelům. Síťovky serverové, tedy žádné levné šunty bez jakýchkoliv HW podpor provozu. Všechno to fungovalo výborně do příchodu koronakrize v březnu 2020. Co se stalo? Lidi zůstali doma, omezené vycházení, venku hnusně a tak si začali předplácet různé video služby (Netflix, HBO GO aj.). Výrazně nám stoupl traffic a to hlavně přenos malých paketů - čili nikoliv rychlost absolutně, ale PPS (packets per seconds). Slušně řečno - server začal padat na držku - resp. CPU rychlost maximální, zatížení téměř naplno, zahozené RX pakety - naštvaní klienti...
Trochu zabrala první pomoc v podobě vyhození a zakázání irqbalance démona a ruční nastavení asociace obsluhy přerušení jednotlivých front síťovek na jednotlivá jádra CPU (smp_affinity) dle jejich počtu. To vyrovnalo zátěž jader na téměř shodnou úroveň. Dále pomohla změna velikosti kruhového rx bufferu sítovky vedoucí do Internetu. Naopak zcela bez výsledku bylo experimentování s interrupt moderation, tedy ladění parametrů, kdy bude vyvoláno přerušení od síťovky. Co se týče nastavení systému, tak se jedná o klasický statefull NAT, pravidla SNAT+DNAT pro všechny klienty a k tomu traffic shaping na fixní rychlosti pro každou přípojku. Celá sranda vypada následovně - 900x toto:
# SNAT iptables -t nat -A POSTROUTING -s $LOKALNI_IP -o $IF_INTERNET -j SNAT --to $VEREJNA_IP # DNAT iptables -t nat -A PREROUTING -d $VEREJNA_IP -j DNAT --to $LOKALNI_IP # Markovani download iptables -w -t mangle -A POSTROUTING -o $IF_LAN -d $LOKALNI_IP -j MARK --set-mark 0x1 # Markovani upload iptables -w -t mangle -A POSTROUTING -o $IF_INTERNET -s $LOKALNI_IP -j MARK --set-mark 0x2 # Omezeni download tc class add dev $IF_LAN parent 1:0 classid 1:0x1 htb rate 50Mbit quantum 59999 tc filter add dev $IF_LAN parent 1:0 protocol ip handle 0x1 fw flowid 1:0x1 # Omezeni upload tc class add dev $IF_INTERNET parent 1:0 classid 1:0x2 htb rate 20Mbit tc filter add dev $IF_INTERNET parent 1:0 protocol ip handle 0x2 fw flowid 1:0x2
Uvedný zápis fungoval 15 let, jen se občas vyměnil server za nový, zvýšila se rychlost Internetu a zvýšil se počet těchto pravidel.
Nftables jako takové mají mj. několik výborných vlastností, které dokáží zrychlit vyhodnocování pravidel (mapy) a zpřehlednit zápis pravidel (sety). Velmi pěkná je také možnost atomické aktualizace pravidel, kdy lze nftables naplnit jedním příkazem nft -f pravidla.nft. Je to opravdu rychlé. Firewall s iptables mi bez tc příkazů startoval 15 sekund, zde je to hned. Na druhou stranu jsem si ještě nezvyknul na příliš volný a někdy zdlouhavý zápis pravidel. Je to podobné jako u utility ip nebo tc - tedy cheat sheet nutností.
Výhodou iptables bylo, že člověk s nimi vstoupil do hotového prostředí a mohl ihned začít tvořit pravidla. To s nftables není možné, neboť na počátku je nutné vytvořit vlastní tabulky, základní chainy a hooky do netfiltru. Narozdíl od ostatních návodů jsem si tabulky a chainy pojmenoval velkými písmeny tak, aby bylo zřejmé, co je klíčové slovo nftables a co je moje pojmenování. Dále jsem zakomentoval ty řádky, které nejsou použity.
# -- zakladni nastaveni obsahu tabulky FILTER a nastaveni POLICY (zde záměrně accept) -- add table ip FILTER add chain ip FILTER INPUT '{ type filter hook input priority 0; policy accept; }' add chain ip FILTER OUTPUT '{ type filter hook output priority 0; policy accept; }' add chain ip FILTER FORWARD '{ type filter hook forward priority 0; policy accept; }' # -- zakladni nastaveni obahu tabulky NAT -- add table ip NAT add chain ip NAT PREROUTING '{ type nat hook prerouting priority -100; policy accept; }' add chain ip NAT POSTROUTING '{ type nat hook postrouting priority 100; policy accept; }' #add chain ip NAT INPUT '{ type nat hook input priority 100; policy accept; }' #add chain ip NAT OUTPUT '{ type nat hook output priority -100; policy accept; }' # -- zakladni nastaveni obahu tabulky MANGLE -- add table ip MANGLE #add chain ip MANGLE PREROUTING '{ type filter hook prerouting priority -150; policy accept; }' add chain ip MANGLE POSTROUTING '{ type filter hook postrouting priority -150; policy accept; }' # add chain ip MANGLE INPUT '{ type filter hook input priority -150; policy accept; }' # add chain ip MANGLE OUTPUT '{ type filter hook output priority -150; policy accept; }' # add chain ip MANGLE FORWARD '{ type filter hook forward priority -150; policy accept; }' # -- zakladni nastaveni obahu tabulky RAW -- #add table ip RAW #add chain ip RAW PREROUTING '{ type filter hook prerouting priority -300; }' #add chain ip RAW OUTPUT '{ type filter hook output priority -300; }'
Celý NAT pro všech 900 přípojek je realizován dvěma pravidly. Jedno pro SNAT, druhé pro DNAT. V pravidlech je odkaz na mapy map_SNAT a map_DNAT. Výhodou nftables je, že s mapami umí pracovat výrazně rychleji než by tomu bylo při vyhodnocování 1800 samostatných pravidel.
# mapa pro SNAT lokalniIP:verejnaIP add map NAT map_SNAT { type ipv4_addr: ipv4_addr\; } # mapa pro DNAT, verejnaIP:lokalniIP add map NAT map_DNAT { type ipv4_addr: ipv4_addr\; } # pravidlo pro SNAT add rule ip NAT POSTROUTING oifname $IF_INTERNET snat ip saddr map @map_SNAT # pravidlo pro DNAT add rule ip NAT PREROUTING dnat ip daddr map @map_DNAT # toto je zapis pro jednoho klienta, zopakovano 900x pro ruzne IP adresy add element NAT map_SNAT { $LOKALNI_IP : $VEREJNA_IP } add element NAT map_DNAT { $VEREJNA_IP : $LOKALNI_IP }
Traffic shaper je založen na tc a pomocí nftables se zařazují jednotlivé toky do odpovídajících tříd. Zařazování všech přípojek je opět pomocí dvou pravidel odkazující na mapy map_DOWNLOAD a map_UPLOAD. Obsahem map je lokální IP adresa a třída - zvlášť pro upload a pro download.
# mapy shaperu pro download lokalniIP:tcclass add map MANGLE map_DOWNLOAD { type ipv4_addr: classid\; } # mapy shaperu pro upload lokalniIP:tcclass add map MANGLE map_UPLOAD { type ipv4_addr: classid\; } # pravidlo pro download, nastavi tridu pro tc add rule ip MANGLE POSTROUTING oifname $IF_LAN meta priority set ip daddr map @map_DOWNLOAD # pravidlo pro upload, nastavi tridu pro tc add rule ip MANGLE POSTROUTING oifname $IF_INTERNET meta priority set ip saddr map @map_UPLOAD # toto je zapis pro jednoho klienta, zopakovano 900x pro ruzne IP a tridy add element MANGLE map_DOWNLOAD { $LOKALNI_IP : "1:0x1" } add element MANGLE map_UPLOAD { $LOKALNI_IP : "1:0x2" }
Samotné omezení rychlosti je pak dáno jednoduchým stromem vytvořeným utilitou tc.
# Vytvoreni stromu pro LAN tc qdisc del dev $IF_LAN root tc qdisc add dev $IF_LAN root handle 1:0 htb default 14 # Vytvoreni stromu pro Internet tc qdisc del dev $IF_INTERNET root tc qdisc add dev $IF_INTERNET root handle 1:0 htb default 14 # zápis pro jednoho klienta, zopakováno 900x pro různé třídy a rychlosti tc class add dev $IF_LAN parent 1:0 classid 1:0x1 htb rate 20Mbit quantum 59999 tc class add dev $IF_INTERNET parent 1:0 classid 1:0x2 htb rate 5Mbit
Původní firewall s iptables měl 3600 pravidel pro DNAT, SNAT a značkování paketů pro traffic shaper. Ten pak obsahoval pravidla tc filter pro zařazování označkovaného paketu do odpovídající třídy. Po přechodu na nftables s mapami a přímým zařazováním paketů do tříd (classify) došlo se snížení zátěže CPU serveru na zhruba 30% původního stavu.
Další možnou optimalizací záteže CPU by mohlo být použití bezstavového 1:1 NATu. To samozřejmě jde zprovoznit i s iptables (nebo i s ip utilitou), ale nftables a mapami by tomu mohlo dát větší štávu :-). Níže jsou uvedena dvě pravidla, která bezstavový NAT umožní:
# DNAT add rule ip RAW PREROUTING iifname $IF_INTERNET ip daddr $VEREJNA_IP ip daddr set $LOKALNI_IP notrack # SNAT add rule ip RAW PREROUTING iifname $IF_LAN ip daddr != { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 } ip saddr $LOKALNI_IP ip saddr set $VEREJNA_IP notrack
Podmínkou je, že $LOKALNI_IP
a $VEREJNA_IP
nesmí být přímo na stroji, co NAT dělá. Pokud tomu tak není a $VEREJNA_IP
je přímo na routeru, pravidlo pro SNAT by se změnilo takto:
# SNAT s verejnou IP na routeru add rule ip RAW PREROUTING iifname $IF_LAN ip daddr != { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 } ip saddr $LOKALNI_IP notrack add rule ip MANGLE POSTROUTING oifname $IF_INTERNET ip saddr $LOKALNI_IP ip saddr set $VEREJNA_IP
Bezstavový NAT prozatím nemám vyzkoušený, neboť po přechodu na nftables je výkonově vše OK a možnost analýzy datových toků z conntrack tabulky se občas hodí.