From: Artem Korneev <tema[Shift+2]smartru.com>
Date: Sun, 10 Mar 2008 17:02:14 +0000 (UTC)
Subject: Разработка Match-модуля для iptables своими руками.
Оригинал: http://linuxkernel.ru/?q=node/215
Как-то LinuxJournal опубликовал небольшую статью Victor Castro по
написанию собственного фаервола на основе Netfilter -
http://www.linuxjournal.com/article/7184 Это, безусловно, ценная и
полезная статья, но что можно сделать с приведённым примером? Вы
решили писать собственный фаервол? Прекрасно, значит моя статья не для
вас. А я хотел бы помочь тем, кто желает лишь дополнить
функциональность имеющегося в Linux фаервола iptables. Тем, кто хочет
написать собственные дополнения для него, если уже имеющаяся
функциональность модулей iptables по какой-либо причине не устраивает.
Для примера я подробно рассмотрю процесс написания модуля для ipv4,
который будет реагировать на поле ID в заголовке IP-пакета. Модуль
будет носить исключительно демонстрационный характер и будет
реагировать тогда, когда последняя цифра в ID пакета будет
соответствовать заданному параметру. Попросту говоря, мы возьмём
остаток от деления ID нацело на 10 и если он будет равен параметру,
модуль посчитает, что пакет соответствует критерию.
Для реализации задуманного нам понадобится написать модуль ядра,
который будет выполнять проверку и модуль расширения для iptables,
который будет работать с модулем ядра - создавать новые цепочки,
использующие наш модуль, выводить информацию о критерии при выводе
списка правил на экран, а также проверять корректность передаваемых
модулю параметров.
Начнём с модуля для ядра.
Инициализация модуля. Зарегистрируем процедуры нашего модуля:
static int __init iptest_init(void)
{
return ipt_register_match(&test_match);
}
Макросы module_init и module_exit, указывают компиллятору, какая
функция будет вызвана ядром при загрузке нашего модуля, а какая - при
и его удалении. Для регистрации в подсистемах пакетного фильтра, мы
будем использовать процедуру ipt_register_match, для удаления -
процедуру ipt_unregister match.
В качестве параметра функциям ipt_register_match и
ipt_unregister_match передаётся ссылка на экземпляр структуры
ipt_match. Эта структура определена так:
#define ipt_match xt_match
а структура xt_match описана в файле linux/netfilter/x_tables.h. Нас
интересуют следующие поля:
name - имя нашего модуля для netfilter;
match - функция, которая будет выполнять проверку, соответствует ли пакет
критериям нашего модуля;
matchsize - размер структуры, передаваемой в нашу функцию match в поле matchinfo;
me - модуль ядра, обычно указывается THIS_MODULE;
Осталось написать функцию match, которая будет проверять,
соответствует ли данный пакет нашей цепочке. Прототип функции match
приведён в файле linux/netfilter/x_tables.h:
int (*match)(
const struct sk_buff *skb, // буффер сокета, структура подробно описанав
// файле linux/skbuff.h;
const struct net_device *in, // ссылка на устройство, на которое пакет пришёл
const struct net_device *out, // ссылка на устройство, с которого пакет будет
// выпущен после обработки фильтром
const struct xt_match *match, // указатель на структуру xt_match, которой
// принадлежит функция
const void *matchinfo, // указатель на void, по этому указателю функция
// может получить доступ к переданным через cli
// из iptables параметрам
int offset, // честно говоря, не в курсе, что это за
// смещение; если знаете - напишите мне
unsigned int protoff, // аналогично предыдущему
int *hotdrop); // Если установить *hotdrop = 1 и вернуть 0,то
// пакет будет удалён (действие DROP)
По ссылке matchinfo наш модуль получит дополнительную информацию,
переданную через параметры командной строки в наш модуль. Определяем
эту структуру мы сами. Для нашего случая будет достаточно хранения
одного десятичного значения и признака инвертирования критерия:
Результатом выполнения функции match должно стать true для случая,
когда пакет соответствует критериям правила и false, если не
соответствует. Вот как будет выглядеть эта функция для нашего модуля:
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const struct xt_match *match,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
const struct ipt_test_info *info = matchinfo;
Мы полчаем через matchinfo доступ к данным, используемым нашим модулем
- поле test сравниваем с остатком от деления id проверяемого пакета на
10 и если остаток равен заданному значению, мы возвращаем true. Также
учитывается возможность инвертирования правила (через указание '!' при
добавлении правила) - в этом случае будет возвращено обратное
значение. Доступ к полю id пакета мы получаем через буфер сокета -
структуру sk_buff, поле nh.iph. Структура поля iph описана в файле
linux/ip.h, в своём модуле мы использовали лишь поле id этой
структуры.
Вот полный текст полученного модуля ядра с указанием всех заголовочных
файлов и нужных макросов:
Скомпилируем его командой make и загрузим через insmod:
insmod ipt_test.ko
После загрузки Вы можете видеть наш модуль в списке загруженных
модулей ядра:
lsmod | grep ipt_test
ipt_test 2432 0
Отлично. С модулем ядра мы закончили. Теперь нам нужно написать
расширение для iptables (userspace-части), которое позволит нам
использовать наш модуль в качестве критерия выбора пакетов.
Используем функцию register_match для регистрации своего расширения в
iptables:
register_match(&test);
В качестве параметра функция register_match принимает ссылку на
структуру типа iptables_match, описанние которой Вы можете найти в
файле iptables.h. Нас интересуют следующие поля этой структуры:
next - здесь нужно передать NULL, ядро само заполнит это поле;
name - имя, по этому имени позднее мы будем обращаться к нашему критерию
через параметр iptable -m;
version - версия iptables, здесь допустим макрос IPTABLES_VERSION;
size - размер структуры, которую мы передаём в модуль ядра;
userspacesize - размер структуры, видимый в пространстве пользователя;
help - ссылка на функцию, которая будет вызвана для вывода краткой
справки пользователю при запросе iptables -m test -h;
parse - эта функция заполняет структуру, передаваемую модулю ядра на
основе данных, переданных через аргумерты командной строки;
final_check - ссылка на функцию, производящую заключительную проверку
правильности заполнения передаваемой в ядро структуры;
print - ссылка на функцию, которая выводит на экран информацию о нашем
критерии при вызове iptables -L;
save - ссылка на функцию, которая выводин на экран информацию о нашем
критерии при вызове iptables-save;
extra_opts - дополительные опции командной строки;
Функция parse проверяет введённый параметр, заполняет передаваемую в
ядро структуру и возвращает true, если параметр корректен. Прототип
функции описан в объявлении структуры iptables_match.
static int
parse(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry,
unsigned int *nfcache,
struct ipt_entry_match **match)
{
struct ipt_test_info *testinfo = (struct ipt_test_info *)(*match)->data;
switch (c) {
case '1':
if (*flags == 1)
exit_error(PARAMETER_PROBLEM,
"test match: only use --test once!");
Проверяем корректность параметров, проверяем аргументы на инверсию с
помощью процедуры check_inverse и вызываем собственную функцию
заполнения передаваемой в ядро структуры, описанную ниже:
if (string_to_number(s, 0, 9, &test) != -1) {
info->test = ( u_int8_t )test;
return;
}
exit_error(PARAMETER_PROBLEM, "Bad test value `%s'", s);
}
Здесь всё должно быть понятно.
В функции final_check нам нужно лишь убедиться, что все параметры
прочитаны верно. В нашем случае достаточно просто проверить, что flags
имеет ненулевое значение:
static void
final_check(unsigned int flags)
{
if (!flags)
exit_error(PARAMETER_PROBLEM, "test match: You must specify `--test'");
}
Теперь скопируем наш исходник в каталог iptables/extensions исходных
кодов iptables, добавим в Makefile в этом же каталоге упоминание о
нашем модуле в список модулей пакетного фильтра. У меня получилась
такая строчка:
PF_EXT_SLIB:=ah addrtype comment connlimit connmark conntrack dscp ecn
esp hashlimit helper icmp iprange length limit mac mark multiport
owner physdev pkttype policy realm rpc sctp standard state tcp tcpmss
test tos ttl udp unclean CLASSIFY CONNMARK DNAT DSCP ECN LOG MARK
MASQUERADE MIRROR NETMAP NFQUEUE NOTRACK REDIRECT REJECT SAME SNAT
TARPIT TCPMSS TOS TRACE TTL ULOG
Соберём iptables командой make. Где-то в длинном списке сообщений
можно увидеть сообщение об удачной компилляции нашего модуля. У меня
получилось следующее:
cc -shared -o extensions/libipt_tcpmss.so
extensions/libipt_tcpmss_sh.o
cc -O2 -Wall -Wunused -I/usr/src/linux/include -Iinclude/
-DIPTABLES_VERSION="1.3.6" -fPIC -o extensions/libipt_test_sh.o -c
extensions/libipt_test.c
После этого необходимо установить iptables командой make install.
Отлично. Теперь создадим правило, использующее наш модуль:
# iptables -A INPUT -m test --test 1
После выполнения команды мы можем увидеть правило в выводе iptables:
# iptables -L -n --line-numbers -v
Chain INPUT (policy ACCEPT 11451 packets, 967K bytes)
num pkts bytes target prot opt in out source destination
1 1150 95767 0 -- * * 0.0.0.0/0 0.0.0.0/0 test match 1
Теперь, дабы убедиться, что модуль наш работает, добавим ещё одно
правило, регистрирующее все пакеты, попадающие в цепочку INPUT и
обнулим все счётчики в этой цепочке:
#iptables -A INPUT
#iptables -Z INPUT
Если Вы используете сеть и через какое-то время проверите состояние
счётчиков в цепочке INPUT, Вы сможете убедиться, что значения
различаются примерно в 10 раз:
Chain INPUT (policy ACCEPT 25658 packets, 1857K bytes)
num pkts bytes target prot opt in out source destination
1 2501 179K 0 -- * * 0.0.0.0/0 0.0.0.0/0 test match 1
2 25658 1857K 0 -- * * 0.0.0.0/0 0.0.0.0/0
Из чего можно предположить, что наш модуль работает правильно.
Выводимая нашим модулем информация, как было сказано ранее, задаётся в
функции print.
Также мы можем видеть правила, использующие наш модуль, в выводе
iptables-save:
...
-A INPUT -m test --test 1
-A INPUT
...
Как мы можем видеть, правило выводится именно в том формате, который
мы определили в функции save.
Вывод iptables -m test -h также вполне предсказуем:
test match v1.3.6 options:
[!] --test value
Вот и всё.
Все ссылки на заголовочные файлы, относящиеся к модулю ядра, приведены
относительно каталога, содержащего заголовочные файлы ядра. Все ссылки
на заголовочные файлы модуля расширения iptables указаны относительно
каталога, содержащего заголовочные файлы iptables.
Все приведённые примеры были проверены на ядре Linux 2.6.18 SMP для
архитектуры x86_64, версия iptables - 1.3.6, но я предполагаю, что всё
описанное должно работать на любых свежих версиях ядра 2.6, в т.ч. и
для архитектуры i386, а также свежих версиях iptables. Цветовая схема
подсветки синтаксиса в коде примеров соответствует схеме "elflord"
редактора vim.
При составлении статьи не использовались никакие литературные
источники. Вся информация была почерпнута исключительно из
собственного опыта, исходных кодов ядра Linux и исходных кодов
iptables. Допускается свободное копирование текста статьи при условии
указания авторства.
Автор был бы очень признателен, если бы Вы высказали своё мнение о
прочитанной статье. Все замечания, предложения и отзывы о статье можно
присылать на адрес tema[Shift+2]smartru.com
С уважением, Artem Korneev. April 14, 2007.
1307 Прочтений • [Разработка Match-модуля для iptables своими руками. (iptables netfilter firewall module gcc filter linux kernel)] [08.05.2012] [Комментариев: 0]