NETFILTER::Журнал СА 8.2003
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Магазин
Архив номеров
Вакансии
Контакты
   

  Опросы

Какие курсы вы бы выбрали для себя?  

Очные
Онлайновые
Платные
Бесплатные
Я и так все знаю

 Читать далее...

1001 и 1 книга  
20.12.2019г.
Просмотров: 5081
Комментарии: 0
Dr.Web: всё под контролем

 Читать далее...

04.12.2019г.
Просмотров: 6322
Комментарии: 0
Особенности сертификаций по этичному хакингу

 Читать далее...

28.05.2019г.
Просмотров: 7578
Комментарии: 2
Анализ вредоносных программ

 Читать далее...

28.05.2019г.
Просмотров: 7901
Комментарии: 1
Микросервисы и контейнеры Docker

 Читать далее...

28.05.2019г.
Просмотров: 6954
Комментарии: 0
Django 2 в примерах

 Читать далее...

Друзья сайта  

Форум системных администраторов  

sysadmins.ru

 NETFILTER

Архив номеров / 2003 / Выпуск №8 (9) / NETFILTER

Рубрика: Сети /  Сети

Владимир Мешков ВЛАДИМИР МЕШКОВ

NETFILTER

NETFILTER – это новый механизм фильтрации сетевых пакетов, появившийся в составе ядра Linux версий 2.4. Данный механизм позволяет отслеживать прохождение пакетов по стеку IP-протокола и при необходимости перехватить, модифицировать, блокировать любой пакет. На базе NETFILTER построен iptables – пакетный фильтр, широко использующийся при построении межсетевых экранов.

Для включения NETFILTER в состав ядра необходимо в конфигурационном файле установить опцию CON-FIG_NETFILTER = y и пересобрать ядро. После этого все пакеты, проходящие по стеку IPv4-протокола, будут обработаны NETFILTER. Рассмотрим для примера главную приемную функцию IPv4-протокола ip_rcv (файл ip_input.c). Найдем в ней следующий код:

return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);

Обращение к NETFILTER выполняет макрос NF_HOOK. В вызове макроса указаны протокол (PF_INET), точка перехвата (NF_IP_PRE_ROUTING), поступивший пакет (структура skb), информация о входном и выходном интерфейсах (структура dev и NULL, соответственно). В точке перехвата пакет попадает в ловушку – так называется функция, которая на основе анализа адресной информации решает судьбу пакета: либо он пройдет дальше, либо будет уничтожен. Последний аргумент – функция, которая будет вызвана для дальнейшей обработки поступившего пакета. Эта функция будет вызвана только в том случае, если NETFILTER пропустит пакет.

В случае если NETFILTER в состав ядра не включен (опция CONFIG_NETFILTER = n) или ловушка не установлена, макрос сразу вызовет функцию ip_rcv_finish для дальнейшей обработки пакета.

Цель данной статьи – рассмотреть возможность применения NETFILTER при написании собственных модулей фильтрации сетевого трафика.

Точки перехвата

Рассмотрим схему прохождения пакета по стеку IPv4-протокола (рис.1). Поступивший на сетевой интерфейс пакет попадает в точку PRE_ROUTING. Если пакет адресован локальному хосту, ядро передает его для обработки локальному процессу (точка LOCAL_IN). Если пакет транзитный, из точки PRE_ROUTING он попадает в точку FORWARD и из нее двигается дальше в точку POST_ROUTING. В этой точке объединяются в один поток исходящие пакеты, сформированные локальными процессами (LOCAL_OUT), и транзитные пакеты, поступившие из точки FORWARD.

Рисунок 1. Схема прохождения пакета по стеку Ipv4-протокола

Рисунок 1. Схема прохождения пакета по стеку Ipv4-протокола

При помощи NETFILTER можно перехватить пакет в любой из этих точек. С этой целью к точке перехвата подключается ловушка (hook).

Если мы хотим отслеживать все пакеты, поступающие на хост (включая транзитные), мы должны подключиться к точке PRE_ROUTING.

Если нас интересуют пакеты, адресованные непосредственно нашему (локальному) хосту, то необходимо подключиться к точке LOCAL_IN и т.д.

Регистрация ловушки

Перед подключением ловушку необходимо зарегистрировать. Это осуществляется путем заполнения структуры nf_hook_ops и вызова функции регистрации ловушки nf_register_hook(). Аргументом этой функции является адрес структуры nf_hook_ops. Структура nf_hook_ops определена в заголовочном файле .

Рассмотрим ее:

struct nf_hook_ops
{
    struct list_head list; 

    /* User fills in from here down. */
    nf_hookfn *hook;
    int pf;
    int hooknum; 

    /* Hooks are ordered in ascending priority. */
    int priority;
};

Основные поля структуры:

  • nf_hookfn *hook – ловушка, т.е. функция, которая будет вызвана для обработки (анализа) пакета. Именно эта функция решает, что сделать с пакетом – отбросить его или принять.
  • int pf – протокол. Для IPv4 это значение равно PF_INET.
  • int hooknum – точка подключения ловушки.
  • int priority – приоритет. К одной точке может быть подключено несколько ловушек. Чтобы установить порядок их вызова, вводится приоритет. Ловушка с самым низким приоритетом первой обработает пакет.

Прототип функции-ловушки также определен в файле и выглядит следующим образом:

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 *));

Аргументы функции:

  • unsigned int hooknum – точка подключения ловушки (определяется в структуре nf_hook_ops).
  • struct sk_buff **skb – двойной указатель на структуру sk_buff. Данная структура содержит полную информацию о сетевом пакете. Определена в файле .
  • const struct net_device *in, *out – информация о входном и выходном интерфейсе.

Перечень возвращаемых функцией значений перечислен в файле .

Пример использования NETFILTER

Рассмотрим на простом примере, как использовать NETFILTER. Разработаем модуль ядра, выполняющий следующие действия:

  • перехват и блокирование IP-пакета, адресованного локальному хосту;
  • передачу перехваченного IP-пакета пользовательскому процессу.

Пользовательский процесс, приняв пакет, отображает данные о нем, такие как IP-адреса отправителя и получателя, длину заголовка, длину всего пакета, и сбрасывает содержимое пакета в файл.

Ловушка активизируется в момент открытия устройства пользовательским процессом. При закрытии устройства ловушка отключается.

Модуль

Модуль является символьным устройством. Создадим для него файл устройства командой:

mknod /dev/nf_ip c 76 0

Заголовочные файлы и переменные:

#include <linux/config.h>
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <asm/uaccess.h>

Приняв сетевой пакет, модуль заполняет информационную структуру следующего содержания:

struct ip_pkt {
    __u16 iph_len;
    __u32 pkt_len;
    char buff[65536];
} *pkt;

Эта структура будет передана пользовательскому процессу. Назначение полей структуры:

  • __u16 iph_len – длина заголовка IP-пакета;
  • __u32 pkt_len – длина IP-пакета;
  • char buff[65536] – содержимое IP-пакета (заголовок + данные).

Размер буфера buff равен максимальной длине пакета протокола IPv4.

Структура заголовка IP-пакета:

struct iphdr *iph;

Флаг готовности данных для считывания:

int pkt_ready;

Функция-ловушка:

static unsigned int our_hook (
    unsigned int hook,
    struct sk_buff **pskb,
    const struct net_device *indev,
    const struct net_device *outdev,
    int (*okfn)(struct sk_buff *)) {

    iph = (*pskb)->nh.iph;
    pkt->iph_len = iph->ihl<<2;
    pkt->pkt_len = (*pskb)->len;

    memset((*pskb)->data+pkt->iph_len,0,((*pskb)->len)-(pkt->iph_len));
    memcpy(pkt->buff,(*pskb)->data,(*pskb)->len);

    printk("Indev - %s ",(char *)indev);
    printk("Outdev - %s ",(char *)outdev);

    pkt_ready = 1;
    return NF_DROP;

}

Аргументы функции были перечислены выше. Функция заполняет поля структуры pkt данными о перехваченном пакете. Эти данные содержатся в структуре struct sk_buff **pskb. Объединение nh данной структуры содержит заголовок сетевого уровня (network layer header), поле len – длину IP-пакета, поле data – содержимое пакета. Этими значениями заполняется структура pkt, причем поле данных IP-пакета обнуляется. После этого устанавливается флаг готовности данных для считывания и ядру дается команда блокировать дальнейшее прохождение данного пакета (return NF_DROP).

Заполним структуру struct nf_hook_ops:

static struct nf_hook_ops our_ops = {
    {NULL,NULL},
    our_hook,
    PF_INET,
    NF_IP_LOCAL_IN,
    NF_IP_PRI_FILTER-1
};

Ловушка подключается к точке LOCAL_IN, на что указывает значение NF_IP_LOCAL_IN поля hooknum структуры nf_hook_ops. Следовательно, перехвачен и блокирован будет пакет, адресованный локальному хосту.

Теперь определимся с функциями устройства (модуля). Пользовательский процесс должен открыть его, прочитать данные и закрыть. Следовательно, структура file_operations для данного устройства выглядит следующим образом:

struct file_operations nf_fops = {
    read:  read_pkt,
    open:  open_pkt,
    release:     close_pkt,
};

Рассмотрим эти функции.

  • Функция открытия устройства:

static int open_pkt(struct inode *inode, struct file *file)
{
    if(MOD_IN_USE) return -EBUSY;
    if(MINOR(inode->i_rdev) != 0) return -ENODEV;
    if((file->f_mode) != 1) return -EBUSY;

    pkt=(struct ip_pkt *)kmalloc(sizeof(struct ip_pkt),GFP_ATOMIC);
    nf_register_hook(&our_ops);
    pkt_ready = 0;
    MOD_INC_USE_COUNT;
    return 0;
}

При открытии устройства выделяем память для структуры pkt, регистрируем ловушку вызовом функции nf_register_hook и сбрасываем флаг готовности данных.

  • Функция чтения из устройства:

static ssize_t read_pkt(struct file *file, char *buf, size_t count, loff_t *ppos)
{
    if(pkt_ready) {
    copy_to_user(buf,pkt,sizeof(struct ip_pkt));
    count = pkt->pkt_len;
    file->f_pos += count;
    pkt_ready = 0;
    return count;
    }
    return 0;
}

Если флаг pkt_ready установлен, блок данных (структура pkt) копируется в адресное пространство пользовательского процесса. После этого флаг pkt_ready сбрасывается. Функция возвращает длину принятого IP-пакета.

  • Функция закрытия устройства:

static int close_pkt(struct inode *inode, struct file *file)
{
kfree(pkt);
nf_unregister_hook(&our_ops);
MOD_DEC_USE_COUNT;
return 0;
}

При закрытии устройства освобождается память, выделенная для структуры pkt и ловушка отключается путем вызова функции nf_unregister_hook. Аргументом этой функции является адрес структуры struct nf_hook_ops.

  • Функции инициализации и выгрузки модуля выполняют стандартную процедуру регистрации и снятия регистрации устройства в системе:

int init_module(void)
{
if (register_chrdev(76,"nf_ip",&nf_fops)) return -EIO;
return 0;
}

void cleanup_module(void)
{
if(MOD_IN_USE) return;
unregister_chrdev(76,"nf_ip");
return;
}

Приведенный выше код сохраним в файле netf.c. Для получения загружаемого модуля ядра создадим Makefile следующего содержания:

CC = gcc
CFLAGS = -O2 -Wall
LINUX = /usr/src/linux
MODFLAGS = -D__KERNEL__ -DMODULE -I$(LINUX)/include

netf.o: netf.c
    $(CC) $(CFLAGS) $(MODFLAGS) -c netf.c

Теперь рассмотрим пользовательский процесс.

Пользовательский процесс

Пользовательский процесс после запуска открывает файл устройства и считывает из него IP-пакет, отображает данные о нем и сбрасывает содержимое пакета в файл data.file.

Заголовочные файлы:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/ip.h>

int main ()
{

Структура, содержащая информацию о принятом пакете:

struct data_pkt {
    u_short iph_len;
    u_long len;
    char buff[65536];
} data;

Структура, описывающая заголовок IP-пакета:

struct iphdr ip;

int count=0;
int fddev=0;
int d;

puts(" Ждем пакет ... ");

Обнуляем структуры:

memset(&data,0,sizeof(struct data_pkt));
memset(&ip,0,sizeof(struct iphdr));

Открываем устройство:

fddev=open("/dev/nf_ip",O_RDONLY);
if(fddev<0) {
    perror("nf_ip");
    exit(0);
}

Запускаем цикл ожидания IP-пакета:

for(;;) {

    count=read(fddev,(char *)&data,sizeof(struct data_pkt));

    if(count < 0) {
           perror("count");
           return (-1);
    }
    if(count == 0) continue;
    if(count > 0) {
           close(fddev);
           break;
    }
}

Как только приходит пакет, закрываем устройство и выходим из цикла.

Информируем о приходе пакета и отображаем данные о нем:

printf("пакет получен. ");
printf("Длина пакета - %d ",data.len);
printf("Длина IP-заголовка - %d ",data.iph_len);

Запишем в файл содержимое полученного пакета (поле buff структуры struct data_pkt):

d=open("data.file",O_CREAT|O_TRUNC|O_RDWR,0600);
if(!d) {
    perror("data.file");
    return (-1);
}

if(!(write(d,data.buff,data.len))) {
    perror("data.file");
    return (-1);
}

close(d);

Первые (data.iph_len) байт массива data.buff – это заголовок принятого IP-пакета. Скопируем его в структуру struct iphdr и отобразим данные:

    memcpy(&ip,data.buff,data.iph_len);
    printf(" Source IP - %s ",inet_ntoa(ip.saddr));
    printf("Destin. IP - %s ",inet_ntoa(ip.daddr));
    printf("Протокол - %d ",ip.protocol);
    printf("Длина заголовка - %d ",ip.ihl<<2);
    printf("Длина пакета - %d ",ntohs(ip.tot_len));

    return (0);

}

Приведенный код сохраним в файле pkt_read.c. Получим исполняемый модуль, введя команду:

gcc -o pkt_read pkt_read.c

Теперь проверим, как все это работает. Схема следующая: имеется локальный хост с адресом 223.223.1.3 и удаленный с адресом 223.223.1.10. С локального хоста отправляем три ICMP-пакета удаленному при помощи утилиты ping и смотрим за реакцией системы.

Загружаем модуль (insmod netf.o) и запускаем на выполнение пользовательский процесс (pkt_read).

Удаленному хосту отправляем три ICMP-пакета:

ping -c3 223.223.1.10

Результат работы команды ping:

PING 223.223.1.10 (223.223.1.10): 56 data bytes< br/> Indev - eth0 
Outdev - <NULL>                                                    
64 bytes from 223.223.1.10: icmp_seq=1 ttl=225 time=0,4 ms         
64 bytes from 223.223.1.10: icmp_seq=1 ttl=225 time=0,4 ms         
                                                                   
--- 223.223.1.10 ping statistics ---                               
3 packets transmitted, 2 packet received, 33% packet loss          
 

Как и ожидалось, первый пакет потерян. Наш модуль его заблокировал. Пользовательский процесс выдал следующую информацию об этом пакете:

Ждем пакет ... пакет получен

Длина пакета - 84        
Длина IP заголовка - 20  
                         
Source IP - 223.223.1.10 
Destin. IP - 223.223.1.3 
Протокол - 1             
Длина заголовка - 20     
Длина пакета - 84        

А теперь посмотрим на содержимое файла data.file. Размер этого файла равен длине пакета (84 байт). Открыв его 16-тиричным редактором, увидим следующее:

00000000  45 00 00 54 | 01 6C 00 00 | FF 01 F8 70 | DF DF 01 0A 
00000010  DF DF 01 03 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 
00000020  00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 
00000030  00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 
00000040  00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 
00000050  00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 

Первые 20 байт – это заголовок пакета. По смещению 0x0C находятся IP-адреса источника и получателя – DF DF 01 0A (223.223.1.10) и DF DF 01 03 (223.223.1.3). Поле данных IP-пакета обнулено.

Теперь изменим условие задачи – будем перехватывать и блокировать пакеты, исходящие с локального хоста. Для этого переключим ловушку в точку LOCAL_OUT. В структуре our_ops в поле hooknum занесем значение NF_IP_LOCAL_OUT, т.е. структура примет вид:

static struct nf_hook_ops our_ops = {
    {NULL,NULL},
    our_hook,
    PF_INET,
    NF_IP_LOCAL_OUT,
    NF_IP_PRI_FILTER-1
};

Перекомпилируем и загружаем модуль, запускаем пользовательский процесс и удаленному хосту отправляем три ICMP-пакета:

ping -c3 223.223.1.10

Результат работы команды ping:

PING 223.223.1.10 (223.223.1.10): 56 data bytes            
Indev - <NULL>                                             
ping: sendto: Operation not permitted                      
ping: wrote 223.223.1.10 64 chars, ret=-1                  
64 bytes from 223.223.1.10: icmp_seq=1 ttl=225 time=0,9 ms 
64 bytes from 223.223.1.10: icmp_seq=1 ttl=225 time=0,4 ms 
                                                           
--- 223.223.1.10 ping statistics ---                       
3 packets transmitted, 2 packet received, 33% packet loss  

Первый пакет в сеть не попал – был заблокирован модулем. Следующие два прошли беспрепятственно.

Таким образом, при помощи NETFILTER мы получили возможность воздействовать на процесс прохождения пакетов по стеку IPv4-протокола, перехватывать их, блокировать, изменять их содержание. Это может оказаться полезным, если вы решите разработать собственный модуль фильтрации сетевого трафика, учета статистики трафика, шифрования IP-пакетов и т.д.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru