![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
К посту http://nuclight.livejournal.com/124348.html в свое время не всё влезло, из-за ограничений на объем поста в ЖЖ (судя по всему 65535 в байтах, причем UTF-8, так как для разной пропорции русского текста длина переменная и непредсказуемая). Ниже идет недостающий кусок, с того места, где там прервалось. Может быть, со временем, что-нибудь еще добавится :)
Итак, продолжение UPD2 про IPSEC:
...Хотя я не знаю, насколько в нем большой смысл и понятность — в IPSEC я не эксперт совсем, случая хорошо его пощупать мне не представилось, черный ящик такой. Потому и не выкладывал, собственно — это выдержка из письма попросившему человеку... Да и версия системы уже старовата. Но пусть будет. Лучше, чем ничего.
...Всё, однако, становится несколько геморройнее при вкомпилированном в ядре 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 (в других версиях уже другое), да и то, как выяснилось, неточное. Кроме того:
1. В первом апдейте выше упоминались динамические правила и fwd, для работы которых необходим патч. Тов. Dmitriy Demidov сделал баг-репорт kern/147720, в котором приводится этот патч. Делать именно так пока не хотят, мол, когда-нибудь в будущем будет придумано правильное решение на базе setfib, и всё такое. Но официальных комментариев к этому PR на текущий момент нет. Кому нужна эта функциональность — не поленитесь, отметьтесь там, пусть пошевелятся раньше наступления светлого будущего.
2. Порядок обработки внутри одного правила был затронут лишь вскользь, где говорилось про оптимизацию OR-блоков. В общем-то, точно такой же и во всём правиле, но есть нюансы. Каждое правило ipfw представляет аналог BPF-программы, где каждая опция один-в-один транслируется в опкод ipfw2 (подробнее см. пост о BPF). При этом сначала по порядку слева направо записываются опкоды опций из тела правила, затем идут опкоды модификаторов действий, наконец, последним — сам action, и эта программа просто исполняется (в отличие от BPF, в ней нет условных переходов, кроме OR-блоков). Что в этом знании полезного для практики, кроме вылизывания при оптимизации? Предположим, есть правило:
add pipe tablearg log all from table(1) to table(2)
Тогда при проверке сначала будет исполнен опкод O_IP_SRC_LOOKUP для таблицы 1, если адрес подходит, исполнение продолжается и переменная tablearg получается значение из таблицы, затем аналогично выполняется O_IP_DST_LOOKUP для адреса назначения, который ищется в таблице 2, и если нашелся, то переменная tablearg переписывается значением из таблицы 2, после чего O_LOG всегда "совпадает", отправляя данные о пакете в syslog. Каждый опкод возвращает true/false, если false, то считается, что всё правило несовпало, и машина переходит к следующему правилу (см. схемы ранее); только OR-блок внутри себя проверяет опкоды далее при false. Так что до O_PIPE, непосредственно отправляющего пакет в трубу, очередь дойдет в случае совпадения всех перед ним.
Как можно видеть, здесь 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:
lookup {dst-ip | dst-port | src-ip | src-port | uid | jail} N
Она точно так же выполняет просмотр таблицы с номером N и выставляет tablearg, но позволяет делать это не только для IP-адресов, но так же и помещать в таблицу номера портов, uid и jailid, вместо IP-адресов. Однако порядок просмотра правила и выстановки tablearg слева направо, описанный в апдейте №3 выше, сохраняется — что следует учитывать на случай, если таблицы опять-таки используются в правиле несколько раз.
3. В готовящийся релиз 9.0 (статус MFC пока неясен) добавлены новые действия для правил ipfw: call и return. В соответствии с духом ipfw как своеобразного ассемблера, делают они именно это — вызывают процедуру и возвращаются из неё (причем, как и в ассемблере, границы условны, можно в одном месте прыгать в начало процедуры, в другом — в середину). В отличие от skipto, вызывать можно правило с любым номером, т.е. делать и прыжки назад — skipto разрешен только вперед во избежание зацикливания пакета при ошибке пользователя. Использовано это может быть для упрощения организации сложных рулесетов, поскольку "процедуры" являются практически тем же, что и цепочки в iptables, просто не выделены в отдельный объект. Следует иметь в виду подводные камни использования этого дела в сложных случаях, особенно при ошибках пользователя (общность стека для in и out-проходов, вывод пакета из ядра, ошибки выделения памяти и т.д.), подробнее всё это описано в мане.
Итак, продолжение UPD2 про IPSEC:
...Хотя я не знаю, насколько в нем большой смысл и понятность — в IPSEC я не эксперт совсем, случая хорошо его пощупать мне не представилось, черный ящик такой. Потому и не выкладывал, собственно — это выдержка из письма попросившему человеку... Да и версия системы уже старовата. Но пусть будет. Лучше, чем ничего.
...Всё, однако, становится несколько геморройнее при вкомпилированном в ядре IPSEC. Оная вещь вообще доставляет админам хлопоты много в чем и на разных платформах, не обошлось без этого и в ядре FreeBSD — IPSEC нормально файрволы не учитывает. По исходникам 6.2, картинка выглядит так:
| v вход в ip_input() ======|=================== | основные проверки заголовка | #if вкомпилирован IPSEC (или FAST_IPSEC) без IPSEC_FILTERGIF | есть ли на пакете история ipsec? | \ нет да-->----можно доверять,----->-. | пропустим файрвол | #endif | | v v | | прогнать пакет через pfil_run_hooks() | был ли пакет разрешен? | | \ | DENY: уничтожить да нет->--------------------------|----->-- пакет молча (некому | | возвращать ошибку) | ___________________________________| |/ v | проверка адреса назначения пакета — нам или не нам? | \ нам не нам, будем готовить к ip_forward() | | v v | | #if вкомпилирован IPSEC или FAST_IPSEC | | выполнение входной выполнение входной обработи обработки IPSEC IPSEC для форвардинга | | #endif v v | | передача пакета на L4, проверка sysctl net.inet.ip.forwarding локальным приложениям и вызов ip_forward() | | v v =========== ВЫХОД из ip_input() ===============
Тут следует обратить внимание на проверку истории перед вызовом файрволов — это рассчитано на туннельные интерфейсы. То есть, поскольку ip_input() вызывается драйвером интерфейса, для туннельного режима IPSEC реальная последовательность будет выглядеть так: реальный сетевой интерфейс -> ip_input() -> дешифровка IPSEC, пакет передан на туннельный интерфейс, в метаинформации пакета помечено, что он прошел через IPSEC -> ip_input() расшифрованного пакета на туннельном интерфейсе.
| v вход в ip_output() ======|=================== | просмотр таблицы маршрутизации и т.п. подготовка <----. | | #if вкомпилирован IPSEC или FAST_IPSEC | | | применение политик IPSEC | | | #endif | | поставить флаг | пропуска ipfw вызов pfil_run_hooks(), что сказали файрволы? | | | \ ^ всё OK DENY пакета ipfw fwd | | | \________________' v вернуть EPERM | вызвавшему нас верхнему уровню | ВЫХОД из ip_output(): поставить пакет в очередь выходного интерфейса | v
Что из этого видно? А то, что IPSEC не учитывает схему взаимодействия ipfw с внешними сущностями. Например, ipfw divert выведет пакет из ip_output(), после обработки пакет будет запущен в ip_output() вновь, и попадет в IPSEC до ipfw. В некоторых конфигурациях это людям действительно мешает.
В ip_input() также можно заметить проверку истории IPSEC. Ровно это же самое действие доступно позже и в ipfw, с помощью одноименного предиката — ipsec.
UPD3: По состоянию на 17.09.10 выше только описание порядка IPSEC для 6.x (в других версиях уже другое), да и то, как выяснилось, неточное. Кроме того:
1. В первом апдейте выше упоминались динамические правила и fwd, для работы которых необходим патч. Тов. Dmitriy Demidov сделал баг-репорт kern/147720, в котором приводится этот патч. Делать именно так пока не хотят, мол, когда-нибудь в будущем будет придумано правильное решение на базе setfib, и всё такое. Но официальных комментариев к этому PR на текущий момент нет. Кому нужна эта функциональность — не поленитесь, отметьтесь там, пусть пошевелятся раньше наступления светлого будущего.
2. Порядок обработки внутри одного правила был затронут лишь вскользь, где говорилось про оптимизацию OR-блоков. В общем-то, точно такой же и во всём правиле, но есть нюансы. Каждое правило ipfw представляет аналог BPF-программы, где каждая опция один-в-один транслируется в опкод ipfw2 (подробнее см. пост о BPF). При этом сначала по порядку слева направо записываются опкоды опций из тела правила, затем идут опкоды модификаторов действий, наконец, последним — сам action, и эта программа просто исполняется (в отличие от BPF, в ней нет условных переходов, кроме OR-блоков). Что в этом знании полезного для практики, кроме вылизывания при оптимизации? Предположим, есть правило:
add pipe tablearg log all from table(1) to table(2)
Тогда при проверке сначала будет исполнен опкод O_IP_SRC_LOOKUP для таблицы 1, если адрес подходит, исполнение продолжается и переменная tablearg получается значение из таблицы, затем аналогично выполняется O_IP_DST_LOOKUP для адреса назначения, который ищется в таблице 2, и если нашелся, то переменная tablearg переписывается значением из таблицы 2, после чего O_LOG всегда "совпадает", отправляя данные о пакете в syslog. Каждый опкод возвращает true/false, если false, то считается, что всё правило несовпало, и машина переходит к следующему правилу (см. схемы ранее); только OR-блок внутри себя проверяет опкоды далее при false. Так что до O_PIPE, непосредственно отправляющего пакет в трубу, очередь дойдет в случае совпадения всех перед ним.
Как можно видеть, здесь 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:
lookup {dst-ip | dst-port | src-ip | src-port | uid | jail} N
Она точно так же выполняет просмотр таблицы с номером N и выставляет tablearg, но позволяет делать это не только для IP-адресов, но так же и помещать в таблицу номера портов, uid и jailid, вместо IP-адресов. Однако порядок просмотра правила и выстановки tablearg слева направо, описанный в апдейте №3 выше, сохраняется — что следует учитывать на случай, если таблицы опять-таки используются в правиле несколько раз.
3. В готовящийся релиз 9.0 (статус MFC пока неясен) добавлены новые действия для правил ipfw: call и return. В соответствии с духом ipfw как своеобразного ассемблера, делают они именно это — вызывают процедуру и возвращаются из неё (причем, как и в ассемблере, границы условны, можно в одном месте прыгать в начало процедуры, в другом — в середину). В отличие от skipto, вызывать можно правило с любым номером, т.е. делать и прыжки назад — skipto разрешен только вперед во избежание зацикливания пакета при ошибке пользователя. Использовано это может быть для упрощения организации сложных рулесетов, поскольку "процедуры" являются практически тем же, что и цепочки в iptables, просто не выделены в отдельный объект. Следует иметь в виду подводные камни использования этого дела в сложных случаях, особенно при ошибках пользователя (общность стека для in и out-проходов, вывод пакета из ядра, ошибки выделения памяти и т.д.), подробнее всё это описано в мане.
no subject
Date: 2010-03-03 05:43 pm (UTC)Именно так делает FAST_IPSEC в шестерке - не направляет расшифрованный пакет сразу в L4, а снова пропускает по ip_input и pfil, в случае natd срабатывает divert и т.п. Подробное изложение, со схемой сети и с патчем для FreeBSD6, вводящим sysctl для включения такой обработки пакета для KAME IPSEC есть тут:
http://www.freebsd.org/cgi/query-pr.cgi?pr=kern/123793
Патч работает у меня в продакшне скоро два года как, без проблем. bz@ отказался его коммитить в шестерку. В семерке и новее это поведение единственно возможное.
no subject
Date: 2010-09-24 02:56 pm (UTC)и из man ipfw не понятен переход от длинного правила ipfw nat 123 config к пачке строчек ipfw nat 1 config .. ipfw nat 5 config.
там что, по инстансам будет бежать в порядке возрастания номеров? или надо будет кучу add nat добавлять?
no subject
Date: 2010-09-24 03:09 pm (UTC)Вот только обвязка ipfw nat сделана довольно криво - когда у вас много редиректов или инстансов, оно начинает глючить. Причем это проблема именно конфигурялки. Некоторые для этого патчат ядро... ну в общем поищите в базе PR.
no subject
Date: 2010-09-24 03:17 pm (UTC)а непонятно следующее -- если по add nat отправлю трафик, который не попадает под редирект, например, он стухнет или вернется и на следующее правило пойдет? т.е. надо ли заморачиваться на check_state и всем остальным?
и опять же, к ману. вроде как из их примера следует эквивалентность предложенного разбиения на правила. из твоих слов следует противоположное.
no subject
Date: 2010-09-24 03:26 pm (UTC)>вроде как из их примера следует эквивалентность предложенного разбиения на правила. из твоих слов следует противоположное.
О чем конкретно речь? Вопрос мне был задан другой, на него я и ответил.
no subject
Date: 2010-09-24 03:30 pm (UTC)===
Or a redirect rule with mixed modes could looks like:
ipfw nat 123 config redirect_addr 10.0.0.1 10.0.0.66
redirect_port tcp 192.168.0.1:80 500
redirect_proto udp 192.168.1.43 192.168.1.1
redirect_addr 192.168.0.10,192.168.0.11
10.0.0.100 # LSNAT
redirect_port tcp 192.168.0.1:80,192.168.0.10:22
500 # LSNAT
or it could be split in:
ipfw nat 1 config redirect_addr 10.0.0.1 10.0.0.66
ipfw nat 2 config redirect_port tcp 192.168.0.1:80 500
ipfw nat 3 config redirect_proto udp 192.168.1.43 192.168.1.1
ipfw nat 4 config redirect_addr
192.168.0.10,192.168.0.11,192.168.0.12
10.0.0.100
ipfw nat 5 config redirect_port tcp
192.168.0.1:80,192.168.0.10:22,192.168.0.20:25 500
===
всем один номер надо, конечно
Date: 2010-09-24 03:35 pm (UTC)Re: всем один номер надо, конечно
Date: 2010-09-24 03:47 pm (UTC)если всем один номер, то останется только один -- последний.