Ключевые слова:iptables, netfilter, firewall, module, gcc, filter, linux, kernel, (найти похожие документы)
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);
}
static void __exit iptest_unload(void)
{
ipt_unregister_match(&test_match);
}
module_init(iptest_init);
module_exit(iptest_unload);
Макросы 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;
Заполним эту структуру:
static struct ipt_match test_match = {
.name = "test",
.match = match,
.matchsize = sizeof(struct ipt_test_info),
.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 наш модуль получит дополнительную информацию,
переданную через параметры командной строки в наш модуль. Определяем
эту структуру мы сами. Для нашего случая будет достаточно хранения
одного десятичного значения и признака инвертирования критерия:
struct ipt_test_info {
u_int8_t test;
u_int8_t invert;
};
Результатом выполнения функции 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;
return (skb->nh.iph->id % 10 == info->test) ^ info->invert;
}
Мы полчаем через matchinfo доступ к данным, используемым нашим модулем
- поле test сравниваем с остатком от деления id проверяемого пакета на
10 и если остаток равен заданному значению, мы возвращаем true. Также
учитывается возможность инвертирования правила (через указание '!' при
добавлении правила) - в этом случае будет возвращено обратное
значение. Доступ к полю id пакета мы получаем через буфер сокета -
структуру sk_buff, поле nh.iph. Структура поля iph описана в файле
linux/ip.h, в своём модуле мы использовали лишь поле id этой
структуры.
Вот полный текст полученного модуля ядра с указанием всех заголовочных
файлов и нужных макросов:
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv4/ip_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Artem Korneev <tema[Shift+2]smartru.com>");
MODULE_DESCRIPTION("test iptables match module");
struct ipt_test_info {
u_int8_t test;
u_int8_t invert;
};
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;
return (skb->nh.iph->id % 10 == info->test) ^ info->invert;
}
static struct ipt_match test_match = {
.name = "test",
.match = match,
.matchsize = sizeof(struct ipt_test_info),
.me = THIS_MODULE,
};
static int __init iptest_init(void)
{
return ipt_register_match(&test_match);
}
static void __exit iptest_unload(void)
{
ipt_unregister_match(&test_match);
}
module_init(iptest_init);
module_exit(iptest_unload);
Напишем Makefile для нашего модуля:
ifneq ($(KERNELRELEASE),)
obj-m := ipt_test.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
rm -f *.o *~ core
endif
Скомпилируем его командой 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 - дополительные опции командной строки;
Заполним эту структуру:
static struct iptables_match test = {
.next = NULL,
.name = "test",
.version = IPTABLES_VERSION,
.size = IPT_ALIGN(sizeof(struct ipt_test_info)),
.userspacesize = IPT_ALIGN(sizeof(struct ipt_test_info)),
.help = &help,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
Напишем реализацию функции help:
static void
help(void) {
printf( "test match v%s options:\n"
"[!] --test value\n",
IPTABLES_VERSION);
fputc('\n', stdout);
}
struct ipt_test_info {
u_int8_t test;
u_int8_t invert;
};
Функция 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(optarg, &invert, &optind, 0);
parse_test(argv[optind-1], testinfo);
if (invert)
testinfo->invert = 1;
*flags = 1;
break;
default:
return 0;
}
return 1;
}
Проверяем корректность параметров, проверяем аргументы на инверсию с
помощью процедуры check_inverse и вызываем собственную функцию
заполнения передаваемой в ядро структуры, описанную ниже:
static void
parse_test(const char *s, struct ipt_test_info *info)
{
unsigned int test;
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'");
}
Функция print также довольно проста:
static void
print(const struct ipt_ip *ip,
const struct ipt_entry_match *match,
int numeric)
{
const struct ipt_test_info *info = (const struct ipt_test_info *)match->data;
printf("test match ");
if (info->invert)
printf("!");
printf("%i", info->test);
}
Мы получаем доступ к параметрам правила через передаваемый указатель
match->data, а далее просто выводим информацию на экран.
Функция save почти аналогична:
static void
save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
{
const struct ipt_test_info *info = (const struct ipt_test_info *)match->data;
if (info->invert)
printf("! ");
printf("--test %i", info->test);
}
Стуктура opts в нашем случае будет иметь вид:
static struct option opts[] = {
{ "test", 1, 0, '1' },
{0}
};
Последним должен быть 0 как признак конца списка. Если Ваш модуль не
имеет параметров - передайте список из одного элемента {0}.
Вот полный текст полученного модуля расширения iptables с указанием
всех заголовочных файлов и нужных макросов:
#include <stdio.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#if defined(__GLIBC__) && __GLIBC__ == 2
#include <net/ethernet.h>
#else
#include <linux/if_ether.h>
#endif
#include <iptables.h>
static void
help(void) {
printf( "test match v%s options:\n"
"[!] --test value\n",
IPTABLES_VERSION);
fputc('\n', stdout);
}
struct ipt_test_info {
u_int8_t test;
u_int8_t invert;
};
static struct option opts[] = {
{ "test", 1, 0, '1' },
{0}
};
static void
parse_test(const char *s, struct ipt_test_info *info)
{
unsigned int test;
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);
}
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(optarg, &invert, &optind, 0);
parse_test(argv[optind-1], testinfo);
if (invert)
testinfo->invert = 1;
*flags = 1;
break;
default:
return 0;
}
return 1;
}
static void
final_check(unsigned int flags)
{
if (!flags)
exit_error(PARAMETER_PROBLEM, "test match: You must specify `--test'");
}
static void
print(const struct ipt_ip *ip,
const struct ipt_entry_match *match,
int numeric)
{
const struct ipt_test_info *info = (const struct ipt_test_info *)match->data;
printf("test match ");
if (info->invert)
printf("!");
printf("%i", info->test);
}
static void
save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
{
const struct ipt_test_info *info = (const struct ipt_test_info *)match->data;
if (info->invert)
printf("! ");
printf("--test %i", info->test);
}
static struct iptables_match test = {
.next = NULL,
.name = "test",
.version = IPTABLES_VERSION,
.size = IPT_ALIGN(sizeof(struct ipt_test_info)),
.userspacesize = IPT_ALIGN(sizeof(struct ipt_test_info)),
.help = &help,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
void _init(void)
{
register_match(&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.
Спасибо! мне интересно почитать статью.. я вижу что модульная архитектура iptables позволяет например вынести какой-нить свой модуль вообще куда угодно, хоть на другое устройство, подключенное к ПК.