RAW в RouterOS: notrack не спира веригата, а accept изключва anti-spoof-а
На граничен рутер с няколко хиляди клиента connection tracking е най-скъпото нещо, което върви на всеки пакет. Затова на такива машини се ползва /ip firewall raw — веригата, която се изпълнява преди conntrack и може да каже „този пакет изобщо да не влиза в таблицата“.
Схемата работи. Но при преглед на една такава верига излязоха две неща, които не са описани ясно никъде: едно действие не спира обхождането, макар че изглежда като терминиращо, а друго спира — и с това тихо изключва защитата под него. Комбинацията прави anti-spoof правило, което броячите показват като работещо, а половината трафик изобщо не го докосва.
Трите действия в RAW и какво точно правят
| Действие | Какво прави | Спира ли веригата |
|---|---|---|
accept |
спира обхождането на raw и праща пакета в conntrack | ДА |
notrack |
маркира пакета да не влиза в conntrack | НЕ |
drop |
изхвърля пакета | ДА |
Първият ред е най-често подценяваният. accept в raw не значи „пусни го“ — значи „спри да четеш raw и track-вай този пакет“. Това е съвсем различно от accept във filter.
notrack не спира обхождането — и това се доказва с броячите
Типичната верига изглежда така: няколко изключения най-горе, после catch-all notrack, а под него — anti-spoof правилата.
0 accept prerouting src-address-list=nat-range 1 accept prerouting dst-address-list=nat-range 2 notrack prerouting <-- catch-all 3 notrack output 4 accept prerouting in-interface=ether2 src-address-list=allowed 5 accept prerouting in-interface=ether2 udp 68->67 src=0.0.0.0 6 drop prerouting in-interface=ether2
Ако notrack беше терминиращо, правила 4–6 щяха да са мъртъв код. Броячите казват друго:
| Правило | Пакети |
|---|---|
2 — notrack (catch-all) |
36 796 313 104 |
4 — accept (под него) |
14 535 497 341 |
6 — drop (под него) |
20 572 653 |
Правилата под catch-all-а броят. Значи notrack просто слага маркер и пуска пакета нататък по веригата.
Веригата работи. Но работи, защото
notrackсе държи по начин, на който никой не би трябвало да разчита. Ако някой размени реда или поведението се промени в бъдеща версия, anti-spoof-ът става мъртъв код без грешка и без ред в лога.
Истинският проблем: един accept отгоре изключва всичко отдолу
Ето защо правила 0 и 1 съществуват. Част от клиентите са зад NAT — да речем диапазонът 100.64.0.0/24, който излиза през един публичен адрес. NAT-ът изисква conntrack, затова този диапазон трябва да се изключи от catch-all notrack-а. Изключва се със списък nat-range и accept най-отгоре.
И точно тук се получава дупката. Списъкът nat-range съдържа цялата мрежа, а accept спира веригата. Значи:
пакет от 100.64.0.77
|
правило 0: accept src-address-list=nat-range
100.64.0.77 е в диапазона -> ACCEPT, СТОП
|
правило 6 (drop) никога не се стига
Anti-spoof правилото на ред 6 пази публичните адреси на клиентите — те не са в nat-range, падат надолу и се проверяват коректно. Но целият NAT-нат диапазон го прескача. Хост от него може да е какъвто си иска — веригата не го пита нищо.
Въпросът се задава в грешен ред: „има ли нужда от conntrack?“ се пита преди „разрешен ли е изобщо този източник?“.
Как се вижда, че наистина се случва
Не по броячите — те показват само че правилата се изпълняват. Сравнете живите хостове на клиентския интерфейс със списъка с разрешени адреси, и после вижте дали „неразрешените“ имат живи сесии в conntrack:
# живите хостове /ip arp print terse where interface=ether2 # разрешените /ip firewall address-list print terse where list=allowed # сесиите на един конкретен адрес /ip firewall connection print count-only where src-address~"^100\.64\.0\.77:"
Капан при сравняването. Ако изнесете двата списъка и ги сравнявате с
grep -Ff, ще получите надути числа —grepмачва като подниз, тъй че образецът100.64.0.5хваща и100.64.0.50,.51и така нататък. Ползвайте точно съвпадение:awk 'NR==FNR{s[$0]=1;next} ($0 in s)'.
В разгледания случай почти една четвърт от сесиите в conntrack бяха на хостове, които anti-spoof правилото би трябвало да е спряло.
Поправката: две неща наведнъж
1. Слейте whitelist-а и drop-а в едно правило
Класическата двойка „пусни разрешените, хвърли останалите“ са две правила:
accept in-interface=ether2 src-address-list=allowed drop in-interface=ether2
RouterOS приема отрицание на address-list, така че това е едно правило:
/ip firewall raw add chain=prerouting action=drop \
in-interface=ether2 src-address-list=!allowed
Това не е само козметика. Вариантът с accept има странични ефекти — при него разрешените пакети влизат в conntrack. Слятото правило е чист drop: разрешеният трафик просто не съвпада с него и продължава надолу до notrack-а, както трябва.
2. Вдигнете drop-а над изключенията за conntrack
0 accept prerouting in-interface=ether2 udp 68->67 src=0.0.0.0 dst=255.255.255.255 1 drop prerouting in-interface=ether2 src-address-list=!allowed 2 accept prerouting src-address-list=nat-range 3 accept prerouting dst-address-list=nat-range 4 notrack prerouting 5 notrack output
Сега редът е верен: първо „разрешен ли е“, после „нужен ли е conntrack“. И catch-all notrack-ът е последен — където му е мястото — тъй че веригата вече не зависи от това дали notrack спира обхождането.
DHCP капанът — заради него редът на нулевото правило не е по избор. DHCP Discover тръгва със
src=0.0.0.0, а нулевият адрес не е в никакъв whitelist. Ако drop-ът иде най-отгоре, клиентите спират да си вземат адреси. И понежеlease-timeобикновено е ден, няма да го забележите веднага — ще започне да капе чак на следващия ден, по един клиент.
Как се сменя работеща верига на жив рутер
Пренареждането на firewall верига в продукция е от нещата, при които между две команди мрежата може да остане без защита или клиентите без интернет. Последователност, при която веригата е валидна във всеки един момент:
- Експорт на веригата във файл на самия рутер —
/ip firewall raw export file=raw-before. - Новото правило влиза
disabled=yesи веднага се проверява сprintкак точно го е записал рутерът. Така се хваща и дали синтаксисът с отрицание е приет. - Пренареждане, докато правилото още е изключено. Старите правила продължават да пазят.
- Предпазна мрежа — scheduler, който след 3 минути изключва новото правило сам:
/system scheduler add name=autorevert interval=3m \ on-event="/ip firewall raw disable [find comment=\"new-rule\"]"
- Включване и мерене за 10–15 секунди. Доказателството не е че новият брояч расте, а че старият е замръзнал — значи новото правило хваща точно неговия трафик.
- Чак тогава се махат старите правила и се сваля предпазната мрежа.
Обратното — да изтриете веригата и да я напишете наново „за да е чисто“ — е лоша идея. Има реални случаи, в които за момент правилата изчезват съвсем.
Два капана на инструментите
move ляга с един слот встрани. /ip firewall raw move 7 destination=3 може да остави правилото на позиция 4. При три местения това се случи два пъти. Винаги print след всяко местене и корекция с ново изрично move.
Нов ред в on-event на scheduler изчезва мълчаливо. Ако подавате командата през SSH, \r\n може да се запише буквално като rn и скриптът става счупен — без грешка при добавянето. Пишете on-event с една-единствена команда или проверявайте какво е записано:
:put [/system scheduler get [find name=autorevert] on-event]
Колко CPU спестява всичко това
Нищо. И това си заслужава да се каже направо.
Прогнозата преди промяната беше 1–1,5 процентни пункта. Замерът след нея: категорията firewall в /tool profile беше 3,8–5,6% преди и 3,6–6,5% след — тоест изцяло в шума на профайлъра. Цялата raw верига струва 4–5% от процесора; дори да я изтриете напълно, толкова ще спечелите.
Ако търсите производителност на такава машина, гледайте другаде — при разгледания случай опашките се оказаха 11 пункта, измерени с контролен тест при непроменен трафик. RAW веригата не е там, където са парите.
Печалбата от тази промяна е друга и е по-важна: хостове, които мислите за спрени, наистина са спрени; conntrack таблицата олеква с изчистените паразитни сесии; и подредбата вече не виси на недокументирано поведение.
Накратко
acceptв RAW е терминиращо и праща в conntrack.notrackне е терминиращо.- Всеки
acceptнад drop правилото е дупка в anti-spoof-а с размера на своя address-list. - Питайте „разрешен ли е?“ преди „нужен ли е conntrack?“.
- Whitelist + drop се сливат в едно правило с
src-address-list=!списък— и така разрешеният трафик не влиза в conntrack без нужда. - DHCP-то (
src=0.0.0.0) се изключва над drop-а, иначе чупите lease-ите бавно и незабележимо. - Catch-all
notrackстои последен.