Незаетите публични адреси пълнят LAN-а с ARP: как се спира в RAW
Повечето хора мислят, че незает публичен адрес е просто „празно място“ в мрежата. Никой не го ползва, значи и трафик към него няма. Не е така. Ако адресът е от мрежа, която рутерът ти държи директно в LAN-а, всеки пакет от интернет към него влиза навътре. Рутерът няма как да знае, че адресът е свободен, и прави единственото, което може: пита целия LAN кой е собственикът.
Ето как го видяхме на живо, какво струва и с едно RAW правило на мрежа как се спира.
Казусът
Мрежата е доставчик в няколко села. Публичните /24 мрежи са конфигурирани директно на LAN интерфейсите на рутерите. Отдолу е едно L2 ядро с над 2000 устройства: суичове, EPON OLT-та и безжични сектори до клиентите. Поводът за одита беше ICMP флуд между двама абонати, който удари и съседите им. Докато търсехме защо, погледнахме колко broadcast стига до всеки порт на ядрото.
Отговорът беше около 200 пакета в секунда, постоянно, на всеки порт надолу. Денем и нощем, без значение от натоварването. По обем е малко, но е шум, който никой не беше поръчвал.
Механизмът
Да вземем, че на рутера е зададено 203.0.113.1/24 на LAN интерфейса и от 254 адреса са раздадени 200. Някъде в интернет скенер (Shodan, Censys, поредният ботнет) обикаля всеки IPv4 адрес подред. Стига до 203.0.113.57, който не е на никого:
- Пакетът идва по BGP или по default маршрута до твоя рутер. Всичко е нормално, мрежата е твоя и е анонсирана.
- Рутерът вижда, че
203.0.113.0/24е connected. Значи трябва да го достави директно, на L2. - За това му трябва MAC адрес. Няма го в ARP таблицата и затова праща ARP who-has 203.0.113.57 като broadcast.
- Суичът разлива broadcast-а на всеки порт: към всеки суич надолу, всяко OLT, всеки безжичен сектор и всеки клиентски рутер.
- Никой не отговаря. Записът остава
failed, рутерът опитва отново, а скенерът вече праща към .58, .59…
Рутерът е длъжен да пита. Той не знае кой адрес е зает. Знае само, че мрежата е негова. Затова всеки скенер, който мине през твоите префикси, на практика генерира broadcast във всеки ъгъл на мрежата ти.
Blackhole маршрутът за анонса (
/ip route add dst-address=203.0.113.0/24 blackhole) не помага. Connected маршрутът за същия префикс е с distance 0 и печели. Blackhole-ът се ползва само ако интерфейсът падне.
Защо това е проблем
- Ефирът. В безжичните сектори broadcast се праща на най-ниската basic скорост, за да го чуят всички. Сто малки пакета в секунда на 6 Mbps заемат реално ефирно време от всеки сектор, всяка секунда.
- Процесорът на всяко крайно устройство. Всеки клиентски рутер получава ARP заявката, разглежда я и я изхвърля. Камера, принтер или евтин CPE правят това 200 пъти в секунда.
- Шумът крие истинските проблеми. Когато нивото на broadcast е постоянно високо, истинска L2 примка или бурен клиент не се виждат на графиките.
- Неплатилите абонати. Абонат, който е спрян, но адресът му стои в мрежата, също събира трафик от интернет, който никой няма да обработи.
Как се мери
Първо колко адреса рутерът търси и не намира:
/ip arp print count-only where !complete interface=ether2
У нас на трите рутера бяха 50, 280 и 365 записа. Почти всички бяха в нашите публични мрежи и нито един не беше на абонат.
После кой точно пълни LAN-а с ARP:
/tool sniffer quick interface=ether2 mac-protocol=arp duration=10s
Броиш редовете с FF:FF:FF:FF:FF:FF по SRC-MAC. Клиентите бяха с по няколко заявки за 10 секунди. Рутерите бяха с по 100 до 1100.
Трето, ако имаш LibreNMS или друг SNMP мониторинг: броячите ifOutBroadcastPkts на портовете на ядрото показват колко broadcast получава всеки суич надолу. Това е числото, което реално има значение.
Правилото
Идеята е проста: пакет към адрес от моята мрежа, който не е на абонат и не е на самия рутер, се изхвърля, преди да стигне до ARP. В RAW, за да не струва нищо на connection tracking-а:
/ip firewall raw add chain=prerouting \
dst-address=203.0.113.0/24 \
dst-address-list=!customers \
dst-address-type=!local \
action=drop comment=drop-unassigned
| Условие | Защо |
|---|---|
dst-address=203.0.113.0/24 |
Само нашата мрежа. Транзитът към интернет не се пипа. |
dst-address-list=!customers |
Адресът не е на активен абонат. Списъкът е същият, с който режеш неплатилите в посока навън. |
dst-address-type=!local |
Пази адресите на самия рутер: шлюза .1 и адреса за NAT. Без това режеш управлението и NAT-а. |
без in-interface |
Хваща и трафика от LAN към незаетите адреси: абонат, който сканира съседите си, също пуска ARP. |
Къде се слага
Подредбата в raw има значение. При нас prerouting изглежда така:
0 accept DHCP (src 0.0.0.0 → 255.255.255.255) 1 drop in-interface=ether2 src-address-list=!customers ← неплатилите навън 2 drop dst-address=203.0.113.0/24 !customers !local ← drop-unassigned 3 drop dst-address=198.51.100.0/24 !customers !local ← drop-unassigned 4 accept src-address-list=nat-range 5 accept dst-address-list=nat-range 6 notrack
Правилото е след drop-а на неплатилите и преди всеки accept. Accept в raw е терминиращ: каквото мине през него, не стига до следващите правила. Повече за това е в статията RAW в RouterOS: notrack не спира веригата, а accept изключва anti-spoof-а.
Едно правило на мрежа, или диапазон
Изкушението е публичните мрежи да влязат в една address list и да стане едно правило. Не става. Едно правило в RouterOS приема dst-address-list само веднъж, а то вече е заето от !customers. Затова нашите мрежи се описват с dst-address, по една на правило.
Ако мрежите са съседни, например /25 и /24 една след друга, dst-address приема и диапазон:
dst-address=198.51.100.128-198.51.101.255
Преди да го пуснеш: кой ще отрежеш
Правилото реже всичко в мрежата, което не е в customers. Ако в същата /24 има твой сървър, камера или суич с публичен адрес, който не е в списъка, той изчезва от интернет. Затова преди това провери кои адреси с жив или статичен ARP не са в списъка:
:foreach i in=[/ip arp find where complete address~"^203\\.0\\.113\\."] do={
:local a [/ip arp get $i address]
:if ([:len [/ip firewall address-list find list=customers address=$a]] = 0) do={
:put ($a . " " . [/ip arp get $i mac-address] . " " . [/ip arp get $i comment])
}
}
При нас излязоха няколко клиентски рутера на стари абонати. Те и без това не можеха да излизат навън, защото правило 1 ги реже. Наше устройство нямаше. Ако при теб има, добави го в списъка или в отделно accept преди drop-а.
Странични ефекти, които трябва да приемеш съзнателно:
- Трафикът от интернет към спрени абонати също се реже. Според нас това е желано: и без това не могат да отговорят.
- Нов абонат работи едва когато адресът му влезе в
customers. Ако списъкът се поддържа автоматично, това вече е така и в посока навън.
Връщането е един ред:
/ip firewall raw remove [find comment=drop-unassigned]
Резултатът
Мерено от SNMP броячите на портовете на L2 ядрото, средно в пакети в секунда:
| предния ден, вечер | същия ден, преди | след | |
|---|---|---|---|
| broadcast към всеки суич / OLT надолу | 188 | 211 | 6 |
| broadcast от рутерите към ядрото | 185 | 209 | 4 |
| общо broadcast, влизащ в ядрото | 229 | 255 | 50 |
Почти целият broadcast в мрежата се оказа ARP на рутерите за адреси, които не съществуват. Около 97% от шума, стигащ до клиентите, изчезна с няколко правила, без нито една промяна по VLAN-ите или суичовете. Останалите ~50 pps идват от самите абонати. Port isolation на ядрото пуска техния broadcast само към рутерите, не към съседите.
По процесора на рутерите ефект почти няма: правилата са евтини, а отрязаните пакети са десетки в секунда. Печалбата е в мрежата под рутера, не в него.
Какво това правило не решава
Има близък, но различен проблем: трафик към абонат, който е в списъка, но е изключен. Ако рутерът държи статичен ARP за него, ARP заявка няма. Суичът обаче забравя MAC адреса му след ageing-time и започва да разлива всеки пакет към него като unknown unicast, пак на всеки порт. Това се решава на L2 и е тема за отделна статия.
Но ако имаш публични мрежи директно в LAN-а и не си ги филтрирал, започни от /ip arp print count-only where !complete. Ако числото е над няколко десетки, интернет вече ползва твоя LAN.