Netfilter - подсистема ядра linux 2.4(экспериментальная поддержка была включена
начиная с версий 2.3). Netfilter позволяет осуществлять пакетную фильтрацию, NAT
и существенно расширить возможности работы с сетью за счёт установки специальных
"hook'ов" в часть ядра ОС, ответственной за network. Их можно устанавливать в
ядро одним из двух способов: статически(требуется перекомпиляция ядра) или в виде
LKM, регистрируя тем самым разнообразные функции, которые будут вызываться при
определенных условиях. Например, при получении определенного сетевого пакета.
[2. ][О чем статья]
Статья о работе с Netfilter, подсистемой ядра, расширяющей возможности ОС в ра-
боте с сетевыми фреймами. Язык программирования - ассемблер. Синтаксис GAS.
В статье описано создание простейших модулей(LKM) для демонстрации возможностей
данной подсистемы. Желательно иметь под рукой заголовочные файлы с рассматрива-
емыми в статье структурами. Ну, и разумеется для загрузки(тестирования) модулей
вам необходимы привилегии root ;).
[3. ][Теория]
Ядро подсистемы Netfilter состоит из 5-ти hook-функций, обьявленных в
linux/netfilter_ipv4.h. Видно, что эти функциии для IPv4, хотя больших отличий
от их аналогов для IPv6 нет. С помощью них можно контроллировать пакеты на раз-
личных уровнях сетевого стека.
Ниже приведена схема анализа сетевого пакета системой Netfilter:
[INPUT]--->[1]--->[ROUTE]--->[3]--->[4]--->[OUTPUT]
| ^
| |
| [ROUTE]
v |
[2] [5]
| ^
| |
v |
[INPUT*] [OUTPUT*]
Таблица функций Netfilter с их кодами в правой части
(используются в struct nf_hook_ops, см. ниже)
NF_IP_PRE_ROUTING - первый хук, используемый ядром при получении пакета.
NF_IP_LOCAL_IN - используется в тех случаях, когда поступивший пакет предназна-
чен нашей машине и далее такой пакет "форвардится" не будет.
NF_IP_FORWARD - для пакетов, предназначенных для другого интерфейса.
NF_IP_POST_ROUTING - для пакетов, которые уже настроены для дальнейшего "путе-
шествия" по сети к своему адресату и готовы покинуть наш сетевой стек.
NF_IP_LOCAL_OUT - этой функцией обрабатываются пакеты, исходяшие непосредствен-
но от нас(из нашего собственного сетевого стека).
Несомненно все хук-функции важны, но мы пока сосредоточимся только на первой из
них. После обработки(проверки) пакета функция NF_IP_PRE_ROUTING должна возвра-
тить одно из предопределенных значений(код), чтобы решить дальнейший "маршрут"
пакета:
0 = "NF_DROP" Отбросить пакет.
1 = "NF_ACCEPT" Сохранить пакет.(Отправить пакет след. хук-ф-ии.)
2 = "NF_STOLEN" "Забыть" о пакете.
3 = "NF_QUEUE" Поставить пакет в очередь.
4 = "NF_REPEAT" Вызвать этот хук еще раз.
Установить новый хук в системе очень просто. Во-первых, нужно заполнить струк-
туру nf_hook_ops, определенную в linux/netfilter.h.
На Си структура nf_hook_ops выглядит так:
struct nf_hook_ops {
struct list_head list; //Заполнение данной структуры не является
//обязательным для регистрации хука в системе.
/* Поля которые идут далее заполнять обязательно */
nf_hookfn *hook; //Указатель на нашу главную подпрограмму, которая
//будет вызываться всякий раз при срабатывании нашего
//хука.
int pf; //Семейство протоколов с которым будет работать наш хук,
//обычно PF_INET(см. таблицу ниже и bits/socket.h).
int hooknum; //Одна из 5-ти функций, на которой будет висеть наш хук.
//У нас - NF_IP_PRE_ROUTING(код из таблицы).
int priority; //Приоритет. Подробнее см. linux/netfilter_ipv4.h
// У нас - NF_IP_PRI_FIRST = INT_MIN
//INT_MIN - константа, равная (-(2^32)/2)
};
Второе, что нужно сделать - необходимо зарегистрировать свой hook в сис-
теме. Это делается с помощью системной функции nf_register_hook(). Данная
функция при успешном завершении возвращает 0. В качестве параметра ей пе-
редается - указатель на struct nf_hook_ops(см. net/core/netfilter.c):
Теперь, разберем подробнее прототип функции nf_hookfn, указатель на кото-
рую передаем в struct nf_hook_ops через nf_hookfn *hook.
На Си он выглядит так:
typedef unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *));
Первый аргумент - название(код) одной из пяти типов хук-функций Netfilter.
Например, NF_IP_PRE_ROUTING = 0.
Второй - указатель на указатель на структуру sk_buff(управляющая структура
для описания сетевого пакета, см. linux/skbuff.h).
net_device *in и net_device *out - указатели на структуру net_device(каж-
дое сетевое устройство в ОС Linux описывается этой структурой, например,
используется для описания интерфейсов lo, eth0). Соответственно, in - для
входящего траффика(пакетов), out - для исходящего.
Последний аргумент - указатель функции, также принимающей в качестве аргу-
мента sk_buff struct и возвращающей целое число(см. net/core/netfilter.c).
Работа со структурами, передаваемыми этими аргументами, из нашей функции
our_hook на ассемблере происходит следующим образом:
Пример 1. Проверка имени интерфейса net_device *in:
---------
<...>
interface: .string "lo"
<...>
our_hook:
pushl %ebp
movl %esp, %ebp <---*
subl $16, %esp <---резервируем место в стеке под локальные
переменные
pushl interface
pushl 16(%ebp) <---указатель на struct net_device *in,
в частности на поле char name[IFNAMSIZ]
call strcmp
<...>
На момент исполнения инструкции по адресу (*) программный стек будет выгля-
деть таким образом:
/*
* This is the first field of the "visible" part of this structure
* (i.e. as seen by users in the "Space.c" file). It is the name
* the interface.
*/
char name[IFNAMSIZ]; - offset 0
/*
* I/O specific fields
* FIXME: Merge these and struct ifmap into one
*/
unsigned long rmem_end; /* shmem "recv" end */ - offset 16
unsigned long rmem_start; /* shmem "recv" start */ - offset 20
unsigned long mem_end; /* shared mem end */ - offset 24
unsigned long mem_start; /* shared mem start */ - offset 28
unsigned long base_addr; /* device I/O address */ - offset 32
unsigned int irq; /* device IRQ number */ - offset 36
...
}
Как видно начало структуры, как раз содержит имя интерфейса..
Пример 2. Проверка IP адреса отправителя:
---------
Для начала приведу кусок struct sk_buff:
struct sk_buff {
/* These two members must be first. */
/* Next buffer in list */
struct sk_buff * next; - offset 0
/* Previous buffer in list */
struct sk_buff * prev; - offset 4
/* List we are on */
struct sk_buff_head * list; - offset 8
/* Socket we are owned by */
struct sock *sk; - offset 12
/* Time we arrived */
struct timeval stamp; - offset 16
/* Device we arrived on/are leaving by */
struct net_device *dev; - offset 24
Видно, что для работы с заголовком IP-пакета(IPv4) используется поле по
смещению 32 bytes от начала структуры sk_buff, содержащее *iph(указатель
на заголовок IP-пакета). Сл-но, для определения IP-адреса отправителя мы
должны работать со структурой на которую указывает данное поле..
Теперь посмотрим на саму структуру базового IP-пакета:
|<-------- 8 бит -------->|<-------- 8 бит -------->|
|-------------------------------------------------------------|
0| Версия | Длина | Тип обслуживания |
|-------------------------------------------------------------|
2| Длина пакета |
|-------------------------------------------------------------|
4| Идентификатор |
|-------------------------------------------------------------|
6| 0 | DF | MF | Смещение фрагмента |
|-------------------------------------------------------------|
8| Число переходов | Протокол |
|-------------------------------------------------------------|
10| Контрольная сумма заголовка |
|-------------------------------------------------------------|
12| IP-адрес отправителя |
|-------------------------------------------------------------|
16| IP-адрес получателя |
|-------------------------------------------------------------|
20~ Параметры (до 40 байт) ~
|-------------------------------------------------------------|
20-60~ Данные (до 65535 байт минус заголовок) ~
|-------------------------------------------------------------|
IP адрес отправителя находится по смещению 12 bytes, от начала IP-пакета.
Непосредственное определение структуры IP-пакета на Си:
#typedef unsigned int uint;
#typedef unsigned char uchar;
struct ip_packet {
uint version:4; /* 4-bit version */
uint header_len:4; /* header length in words in 32bit words */
uint serve_type:8; /* how to service packet */
uint packet_len:16; /* total size of packet in bytes */
uint ID:16; /* fragment ID */
uint __reserved:1; /* always zero */
uint dont_frag:1; /* flag to permit fragmentation */
uint more_frags:1; /* flag for "more frags to follow" */
uint frag_offset:13; /* to help reassembly */
uint time_to_live:8; /* maximum router hop count */
uint protocol:8; /* ICMP, UDP, TCP */
uint hdr_chksum:16; /* ones-comp. checksum of header */
uchar IPv4_source; /* IP address of originator */
uchar IPv4_dest; /* IP address of destination */
uchar options[]; /* up to 40 bytes */
uchar data[]; /* message data up to 64KB */
};
На ассемблере определение IP-адреса отправителя будет выглядеть так:
.comm sock_buff,4
/* blocked ip = 127.0.0.1 */
ip_address: .string "x7fx00x00x01"
<...>
/* Проверка структуры sk_buff */
movl 12(%ebp), %eax <--- вытащим адрес
movl (%eax), %eax <--- структуры sk_buff..
movl %eax, sock_buff <--- в переменную sock_buff
cmpl $0, sock_buff
jne .ip_head
movl $1, %eax
jmp .quit
/* Проверка валидности IP пакета */
.ip_head:
movl sock_buff, %eax
cmpl $0, 32(%eax)
jne .check_saddr
movl $1, %eax
jmp .quit
/* Проверка IP-адреса отправителя */
.check_saddr:
movl sock_buff, %eax
movl 32(%eax), %eax <--- вытащим указатель на IP-header из sk_buff..
movl 12(%eax), %eax <--- и source IP из IP-header
movl ip_address, %edx
cmpl (%edx), %eax
jne .accept_packet
movl $0, %eax <--- drop packet, if source == blocked ip
jmp .quit
<...>
Также можно фильтровать пакеты по порту получателя.. здесь я этого не рассма-
триваю, пусть это будет вашим д/з :)
Надеюсь, что работу со струтурами на ассемблере обьяснил более-менее понятно,
если будут вопросы стучите в асю or use mail, а лучше смотрите нужные заголо-
вочные файлы и ковыряйте сами ;)
[4. ][Примеры LKM]
Модуль, блокирующий все входящие пакеты:
/* ---drop_all_incoming_packets.s--- */
/* резервируем поименованную область nf_my_ops в секции bss */
.comm nf_my_ops,24
.globl init_module
.globl cleanup_module
/* Заполняем структуру nf_my_ops = nf_hook_ops
* nf_my_ops+0: struct list_head list - не заполняем
* Заносим адрес нашей главной процедуры our_hook в поле nf_hookfn *hook
*/
movl $our_hook, nf_my_ops+8
С помощью широких возможностей предоставляемых подсистемой Netfilter можно без
особых усилий написать свой собственный фаерволл под требуемые задачи.
to be continued...