IPFW порядок проходження пакетів, складні випадки
UPDATE: На численні прохання трудящих опишу деякі туманні питання, не порушені в пості.
1. Відмінності предикатів via/xmit/recv.
Ще раз, іншими словами: метаінформація про інтерфейс входу в машину записується драйвером по отриманню і доступна для перевірки recv весь час життя пакета в ядрі, а інформація про інтерфейс xmit стає доступна тільки на виході. Тому out recv int0 — не безглуздий вираз, на відміну безглуздого in xmit ext0.
Навіщо це може бути корисно? Наприклад, щоб заборонити нашій машині бути «одноруким роутером», коли отриманий на інтерфейсі пакет повинен по таблиці маршрутизації піти в ту ж мережу (трапляється зазвичай через помилку в налаштуваннях того, хто надіслав нам цей пакет, і за умовчанням йому разом з таким пакетом буде надіслано і icmp-редирект, що повідомляє про це):
ipfw add deny ip from any to any out recv fxp0 xmit fxp0
Ну а via просто вибирає найбільш підходящу зараз перевірку, якщо вхідний прохід спрацює як recv, якщо вихідний — то як xmit. Іншими словами, якщо звернутися до першої схеми вгорі посту: там 4 можливі комбінації in/out і recv/xmit, на кожній з двох мережевих. Якщо вам потрібно точно вказати, скажімо, нижній лівий на схемі прохід, ви напишете in recv int0. А якщо потрібно, щоб правило спрацьовувало відразу на обох проходах праворуч і тільки на них (або на обох ліворуч), тобто на одній мережевій незалежно від напрямку, ви можете написати два однакові правила, одне з in recv ext0, інше з out xmit ext0. А можете одне – з via. Тобто можна написати:
ipfw add 2 divert 8668 all from any to any in recv ext0 ipfw add 2 divert 8668 all from any to any out xmitext0
ipfw add 2 divert 8668 all from any to any via ext0
і ці конструкції будуть абсолютно еквівалентні ефекту. Просто в ситуації, коли у вас простий набір правил, і вони йдуть ось так ось поспіль, другий варіант коротше і наскільки-частково-процента ефективніше в обробці пакетів.
ipfw add 2 pipe 3 all from any to any in recv ng[23]?*
і це буде ловити інтерфейси з іменами з ng200 по ng299 і c ng300 по 399, а також з ng20 по ng29 і c ng30 по 39 (насправді з будь-якими символами в назві після, але ми ж знаємо, що mpd зробить назви тільки з циферками).
2. Динамічні правила та ipfw fwd; теги.
Наведений у пості приклад того, як на динамічних правилах зробити аналог reply-to у pf, насправді не працює. У вихідниках було виявлено заборону спеціально саме цього випадку у волохатому 2000 році через якусь паніку ядра на тих версіях. Що давно вже не актуально, але це, звичайно ж, виправити забули. Патчиться одним рядком - відкриваєте /sys/netinet/ip_fw2.c, знаходите слова case O_FORWARD_IP: (конкретний патч не наведу, залежить від версії системи). І ось там трохи нижче в рядку шматок
if (!q dyn_dir == MATCH_FORWARD)
треба замінити на
if (sa->sin_port && (!q dyn_dir == MATCH_FORWARD)) [UPD: 06.07.11 патч закоммічений в 7.x і 8.x, патч більш неактуальний, тепер все працює так, як і описано у пості]
Ще, до речі, не варто забувати, що всі теги, що є на пакетах, чи то метаінформація самої системи (від того ж IPSEC і багато чого ще) або явно навішені теги ipfw/pf — існують тільки всередині ядра. Тобто якщо вивести пакет з ядра через divert, вони загубляться.
3. Порядок виклику файрволів та IPSEC.
in: залізо -> bpf(4)/tcpdump-> ng_ether -> ipfw layer2 -> pfil(9) out: pfil(9) -> ng_ether -> ipfw layer2 -> bpf(4)/tcpdump -> залізо
Все, однак, стає дещо геморойнішим при вкомпілюваному в ядрі IPSEC. Вона взагалі завдає адмінам клопоту багато в чому і на різних платформах, не обійшлося без цього і в ядрі FreeBSD — IPSEC нормально файрволи не враховує. За вихідними даними 6.2, картинка виглядає так:
Тут слід звернути увагу на перевірку історії перед викликом файрволів, це розраховано на тунельні інтерфейси. Тобто оскільки ip_input() викликається драйвером інтерфейсу, для тунельного режиму IPSEC реальна послідовність виглядатиме так: реальний мережевий інтерфейс -> ip_input() -> дешифрування IPSEC, пакет переданий на тунельний інтерфейс, у метаінформації пакета зазначено, що він пройшов через IPSEC -> ip_input() розшифрованого пакету на тунельному інтерфейсі.
Що з того видно? А те, що IPSEC не враховує схему взаємодії ipfw із зовнішніми сутностями. Наприклад, ipfw divert виведе пакет з ip_output(), після обробки пакет буде запущений в ip_output() знову, і потрапить до IPSECдо ipfw. У деяких конфігураціях це людям справді заважає.
У ip_input() також можна побачити перевірку історії IPSEC. Рівно ця сама дія доступна пізніше і в ipfw, за допомогою однойменного предикату - ipsec.
UPD3 : Станом на17.09.10 вище лише опис порядку IPSEC для 6.x (в інших версіях вже інше), та й те, як з'ясувалося, неточний. Крім того:
2. Порядок обробки всередині одного правила торкнувся лише побіжно, де йшлося про оптимізацію OR-блоків. Загалом, такий самий і в усьому правилі, але є нюанси. Кожне правило ipfw представляє аналог BPF-програми, декожна опція один-на-один транслюється в опкод ipfw2 (докладніше див. пост про BPF). При цьому спочатку по порядку ліворуч записуються опкоди опцій з тіла правила, потім йдуть опкоди модифікаторів дій, нарешті, останнім — сам action, і ця програма просто виконується (на відміну від BPF, в ній немає умовних переходів, крім OR-блоків). Що в цьому знанні корисного для практики, окрім вилизування під час оптимізації? Припустимо, є правило:
add pipe tablearg log all from table(1) to table(2)
Як бачимо, тут tablearg отримає значення з тієї таблиці, що була пізніше (правіше у правилі), тобто. table(2). Але що, якщо потрібне значення з table(1)? Тоді доведеться переписати правило так, щоб table(1) була останньою:
add pipe tablearg log dst-ip table(2) src-ip table(1)
Хоч синтаксис без from. to багатьом і здасться незвичним, інакше в такій ситуації ніяк.
UPD4 : Станом на07.07.11, ситуація з апдейту №3 вище тепер така:
1. Ситуація з динамічними правилами та fwd, для роботи яких необхідний патч, виправлена, PR kern/147720 тепер закритий. Тепер все працює так, як описано в пості, виправлення закоммічено в 8-STABLE (r223819) і 7-STABLE (r223820). Заодно закрили кілька інших PR із тією ж причиною (комусь це прозорий сквід ламало). Виправили без будь-якого світлого майбутнього саме в цьому місці.
2. Для встановлення tablearg з потрібної таблиці починаючи з 8.1R можна застосовувати нову опцію ipfw:
3. У реліз, що готується 9.0 (статус MFC поки неясен) додані нові дії для правил ipfw: call і return. Відповідно до духу ipfw як своєрідного асемблера, роблять вони саме це - викликають процедуру і повертаються з неї (причому, як і в асемблері, межі умовні, можна водному місці стрибати на початок процедури, в іншому - у середину). На відміну від skipto, можна викликати правило з будь-яким номером, тобто. робити і стрибки назад - skipto дозволено тільки вперед, щоб уникнути зациклювання пакета при помилці користувача. Використововано це може бути для спрощення організації складних рулесетів, оскільки «процедури» є практично тим самим, що й ланцюжки в iptables просто не виділені в окремий об'єкт. Слід мати на увазі підводні камені використання цієї справи в складних випадках, особливо при помилках користувача (спільність стеку для in і out-проходів, виведення пакету з ядра, помилки виділення пам'яті і т.д.), все це описано в мані.