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

  Опросы
  Статьи

ИТ-инфраструктура  

Системы мониторинга ИТ-инфраструктуры-2025

Без мониторинга ИТ-инфраструктуры не обходится ни одна компания, хотя бы потому, что

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

Открытое ПО  

Безопасность Open Source: рискуем или контролируем?

Компания «Кросс технолоджис» изучила, как используется ПО с открытым кодом в компаниях

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

Работа с нейросетью  

Скажи, есть ли у тебя AI, и я скажу, кто ты

Недавно сервис по поиску работы SuperJob выяснил, что каждый второй россиянин уже

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

Работа с Debian  

О Linux с любовью или Debian: через знание к любви

Конечно, одним лишь перечислением замечательных качеств любовь к Linux не возникнет. Для

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

Опрос  

Защита личных и клиентских данных: как мошенники используют ИИ и как защититься?

По данным RED Security, общее число кибератак на российские компании в 2024

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

Опрос  

Облачные инструменты для разработчиков

Эксперты ИТ-отрасли отвечают на вопросы «Системного администратора» > Как с помощью облака сделать

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

Опрос  

Рынок мобильных приложений: что будет актуальным в 2025 году?

Эксперты ИТ-отрасли отвечают на вопросы «Системного администратора» > Ваши прогнозы: чего ожидать от

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

Рынок труда  

Как успешно пройти все этапы собеседования на ИТ-должность?

По оценкам государства, дефицит ИТ-специалистов составляет от 740 тысяч до 1 миллиона

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

Спецпроект «Базальт СПО». Развитие Open Source в России  

Алексей Смирнов: «Сейчас трудно найти программный продукт, в котором нет свободного кода»

Какое будущее ждет свободное ПО? Влияет ли свободная или несвободная разработка на

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

Спецпроект «Базальт СПО». Развитие Open Source в России  

Николай Костригин: «Мы создали Hantis, конвейер автоматизации. Проекты, исследуемые разными инструментами, переходят от одного исполнителя к другому, развиваются, возвращаются к автору, и так по кругу»

О том, как идет работа по повышению безопасности отечественного программного обеспечения, рассказывает

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

1001 и 1 книга  
19.03.2018г.
Просмотров: 7705
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

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

12.03.2018г.
Просмотров: 7963
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

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

12.03.2018г.
Просмотров: 5315
Комментарии: 0
Глубокое обучение с точки зрения практика

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

12.03.2018г.
Просмотров: 3394
Комментарии: 0
Изучаем pandas

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

12.03.2018г.
Просмотров: 4184
Комментарии: 0
Программирование на языке Rust (Цветное издание)

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

19.12.2017г.
Просмотров: 4195
Комментарии: 0
Глубокое обучение

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

19.12.2017г.
Просмотров: 6710
Комментарии: 0
Анализ социальных медиа на Python

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

19.12.2017г.
Просмотров: 3540
Комментарии: 0
Основы блокчейна

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

19.12.2017г.
Просмотров: 3814
Комментарии: 0
Java 9. Полный обзор нововведений

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

16.02.2017г.
Просмотров: 7701
Комментарии: 0
Опоздавших не бывает, или книга о стеке

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

17.05.2016г.
Просмотров: 11059
Комментарии: 0
Теория вычислений для программистов

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

30.03.2015г.
Просмотров: 12784
Комментарии: 0
От математики к обобщенному программированию

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

18.02.2014г.
Просмотров: 14559
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

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

13.02.2014г.
Просмотров: 9495
Комментарии: 0
Читайте, размышляйте, действуйте

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

12.02.2014г.
Просмотров: 7465
Комментарии: 0
Рисуем наши мысли

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

10.02.2014г.
Просмотров: 5739
Комментарии: 4
Страна в цифрах

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

18.12.2013г.
Просмотров: 4945
Комментарии: 0
Большие данные меняют нашу жизнь

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

18.12.2013г.
Просмотров: 3804
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

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

04.12.2013г.
Просмотров: 3481
Комментарии: 0
Паутина в облаках

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

03.12.2013г.
Просмотров: 3707
Комментарии: 1
Рецензия на книгу «MongoDB в действии»

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

Друзья сайта  

 Пакетный фильтр

Архив номеров / 2003 / Выпуск №2 (3) / Пакетный фильтр

Рубрика: Программирование /  Анализ данных

ВЛАДИМИР МЕШКОВ

Пакетный фильтр

Если вы используете сетевой анализатор с выводом информации на консоль, то одна из проблем, с которой вы столкнетесь, заключается в том, что анализатор не будет успевать отображать все данные, поступившие из сети, и часть пакетов будет для вас потеряна.

Это особенно актуально, если трафик очень плотный. Вторая проблема – вас могут интересовать только пакеты, адресованные выделенным хостам, а не все подряд. Решение заключается в фильтрации входящих сетевых пакетов по какому-либо определенному признаку, например по адресной части. Одним из вариантов решения является использование оператора условия if, однако данное решение неэффективно. В этом случае ядру приходится вытаскивать полный пакет из сети, на что отнимается процессорное время, затем анализатор вынужден «экзаменовать» заголовок каждого пакета перед принятием решения – отображать данные или нет. Оптимальным является решение отсеять лишние пакеты как можно раньше, на уровне драйвера сетевой карты. Ядро Linux позволяет сделать это при помощи пакетного фильтра.

Описание языка BPF

Пакетный фильтр представляет собой последовательность инструкций, составленных в кодах псевдо-машинного языка, который называется BPF – Berkeley Packet Filter. Этот язык был разработан Стивом Маккеном (Steve McCanne) и Ван Якобсоном (Van Jacobson). BPF похож на язык ассемблер. В нем, как и в ассемблере, есть регистры, инструкции для загрузки и хранения операндов, выполнения арифметико-логических операций, условных и безусловных переходов. Для работы с операндами в BPF используются регистр-аккумулятор (или просто аккумулятор), индексный регистр, ячейка памяти и внутренний программный счетчик.

Формат инструкции языка BPF определяет следующая структура:

struct sock_filter {

    __u16  code;

    __u8   jt;

    __u8   jf;

    __u32  k;

};

которая определена в файле .

Назначение полей структуры следующее:

  • поле k – числовое значение операнда, с которым работает инструкция;
  • поля jt (jump true) и jf (jump false) – меняют порядок выполнения инструкций;
  • поле code – код выполняемой инструкции.

Существует 8 типов инструкций. Вначале их перечислим, а потом остановимся отдельно на каждом. Итак, вот эти 8 типов: BPF_LD, BPF_LDX, BPF_ST, BPF_STX, BPF_ALU, BPF_JMP, BPF_RET, BPF_MISC.

BPF_LD

Инструкция BPF_LD служит для загрузки в аккумулятор следующих величин:

  • константы (BPF_IMM);
  • блока данных, расположенных по фиксированному смещению (BPF_ABS);
  • блока данных, расположенных по смещению, которое является переменной величиной (BPF_IND);
  • длины блока данных (BPF_LEN);
  • значения, находящегося в ячейке памяти (BPF_MEM).

Для значений BPF_IND и BPF_ABS размер загружаемых данных должен быть задан как слово (BPF_W), полуслово (BPF_H), байт (BPF_B). Здесь имеется в виду машинное слово, которое равно числу разрядов в регистрах общего назначения. Для 32-х разрядных процессоров это значение равно 4 байта.

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

BPF_LD+BPF_W+BPF_ABS           A <- P [ k : 4 ]

В аккумулятор загружается 4 байта из блока данных. Смещение в блоке данных задается константой k.

BPF_LD+BPF_H+BPF_ABS           A <- P [ k : 2 ]

В аккумулятор загружается 2 байта из блока данных. Смещение в блоке данных задается константой k.

BPF_LD+BPF_B+BPF_ABS           A <- P [ k : 1 ]

В аккумулятор загружается 1 байт из блока данных. Смещение в блоке данных задается константой k.

BPF_LD+BPF_W+BPF_IND           A <- P [ X + k : 4 -]

В аккумулятор загружается 4 байта из блока данных. Смещение в блоке данных задается суммой переменных X и константы k. Переменная X является значением, находящимся в индексном регистре.

BPF_LD+BPF_H+BPF_IND           A <- P [ X + k : 2 ]

В аккумулятор загружается 2 байта из блока данных. Смещение в блоке данных задается суммой переменных X и константы k. Переменная X является значением, находящимся в индексном регистре.

BPF_LD+BPF_B+BPF_IND           A <- P [ X + k : 1 ]

В аккумулятор загружается 1 байт из блока данных. Смещение в блоке данных задается суммой переменных X и константы k. Переменная X является значением, находящимся в индексном регистре.

BPF_LD+BPF_W+BPF_LEN           A <- len

В аккумулятор загружается длина блока данных.

BPF_LD+BPF_IMM                 A <- k

В аккумулятор загружается константа k.

BPF_LD+BPF_MEM                 A <- M [ k ]

В аккумулятор загружается значение, находящееся в ячейке памяти с адресом k.

BPF_ LDX

Инструкция BPF_LDX служит для загрузки в индексный регистр следующих величин:

  • константы (BPF_IMM);
  • значения, находящегося в ячейке памяти (BPF_MEM);
  • длины блока данных (BPF_LEN).

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

BPF_LDX+BPF_W+BPF_IMM          X <- k

В индексный регистр загружается константа k, размер которой составляет 4 байта.

BPF_LDX+BPF_W+BPF_MEM          X <- M [ k ]

В индексный регистр загружается значение, находящееся в ячейке памяти с адресом k.

BPF_LDX+BPF_W+BPF_LEN          X <- len

В индексный регистр загружается длина блока данных. Следующая инструкция позволяет быстро определить размер заголовка IP-пакета:

BPF_LDX+BPF_B+BPF_MSH          X <- 4 * ( P [ k : 1 ] & 0xF )

Длина заголовка находится в младших 4 битах нулевого байта IP-пакета и содержит количество 32-битных слов в заголовке. Поскольку минимальный размер заголовка 20 байт (т.е. пять 32-битных слов), то минимальное значение этого поля равно 5. Старшие 4 бита содержат версию протокола и для IPv4 это значение равно 4. Итак, предположим, что в нулевом байте находится значение 0x45. Эту величину мы загружаем в индексный регистр и осуществляем операцию логического И: 0x45 & 0xF = 0x5. Умножение на 4 эквивалентно логическому сдвигу на 2 бита в сторону старших разрядов, при этом значения старших разрядов теряются. В итоге в индексном регистре будет находиться значение 0x5 * 4 = 0x14, в десятичном представлении – 20. Это и есть длина заголовка IP-пакета в байтах.

BPF_ST

Инструкция BPF_ST служит для загрузки аккумулятора в ячейку памяти:

BPF_ST           M [ k ] <- A

Значение k определяет адрес ячейки памяти.

BPF_STX

Инструкция BPF_STX служит для загрузки индексного регистра в ячейку памяти:

BPF_STX          M [ k ] <- X

Значение k определяет адрес ячейки памяти.

BPF_ALU

Инструкция BPF_ALU выполняет арифметико-логические между аккумулятором и индексным регистром или константой. Результат сохраняется в аккумуляторе.

BPF_ALU+BPF_ADD+BPF_K          A <- A + k

BPF_ALU+BPF_SUB+BPF_K          A <- A — k

BPF_ALU+BPF_MUL+BPF_K          A <- A * k

BPF_ALU+BPF_DIV+BPF_K          A <- A / k

BPF_ALU+BPF_AND+BPF_K          A <- A & k

BPF_ALU+BPF_OR+BPF_K           A <- A | k

BPF_ALU+BPF_LSH+BPF_K          A <- A << k

BPF_ALU+BPF_RSH+BPF_K          A <- A >> k

BPF_ALU+BPF_ADD+BPF_X          A <- A + X

Здесь я не вижу необходимости в комментариях, все прозрачно.

BPF_JMP

Инструкция BPF_JMP изменяет порядок выполнения программы фильтрации. Данная инструкция может осуществлять как условный, так и безусловный переход между инструкциями. При безусловном переходе (BPF_JA, jump always) смещение задается 32-битным значением, при условном – 8-битным.

BPF_JMP+BPF_JA          pc += k

Безусловный переход по смещению, заданному 32-разрядным значением k.

BPF_JMP+BPF_JGT+BPF_K          pc += ( A > k ) ? jt : jf

Сравнение значений аккумулятора и константы k. Условный переход по смещению, заданному в поле jt при выполнении условия A > k.

BPF_JMP+BPF_JGE+BPF_K          pc += ( A >= k ) ? jt : jf

BPF_JMP+BPF_JEQ+BPF_K          pc += ( A == k ) ? jt : jf

BPF_JMP+BPF_JSET+BPF_K         pc += ( A & k ) ? jt : jf

BPF_JMP+BPF_JGT+BPF_X          pc += ( A > X ) ? jt : jf

Сравнение значений аккумулятора и индексного регистра. Условный переход по смещению, заданному в поле jt при выполнении условия A > X.

BPF_JMP+BPF_JGE+BPF_X          pc += ( A >= X ) ? jt : jf

BPF_JMP+BPF_JEQ+BPF_K          pc += ( A == X ) ? jt : jf

BPF_JMP+BPF_JSET+BPF_K         pc += ( A & X ) ? jt : jf

BPF_RET

Программа фильтрации выполняется для каждого пакета, поступающего на сетевой интерфейс. Результатом работы фильтра является целое положительное число, показывающее, сколько байт в принятом пакете будет доступно для дальнейшей обработки приложению пользователя. Если принятый пакет не удовлетворяет условиям фильтрации, он отбрасывается и программой фильтрации возвращается нулевое значение. Инструкция BPF_RET завершает выполнение программы фильтрации и возвращает число байт в пакете, доступных для дальнейшей обработки.

BPF_RET+BPF_A

Возвращаемый результат находится в аккумуляторе.

BPF_RET+BPF_K

Результат возвращается в виде константы.

BPF_MISC

Инструкция BPF_MISC служит для копирования значения индексного регистра в аккумулятор и наоборот.

BPF_MISC+BPF_TAX        X <- A

BPF_MISC+BPF_TXA        A <- X

Пример реализации пакетного фильтра

Здесь мы с помощью фильтра модифицируем исходный код анализатора сетевого трафика (см. «Анализатор сетевого трафика», «Системный администратор» №1, октябрь 2002 г.). Все изменения необходимо внести только в главную функцию. Перед тем как приступить к реализации пакетного фильтра, необходимо определить условия фильтрации. В нашем примере условия следующие: обрабатываться будут пакеты IP-протокола, адрес отправителя – 192.168.1.2, транспортный протокол – TCP, порт источника – 23.

Алгоритм реализации следующий:

  • определить необходимые переменные и заголовочные файлы;
  • составить программу фильтрации в кодах языка BPF;
  • привязать полученный фильтр к сокету.

Переменные и заголовочные файлы

Нам необходим один заголовочный файл:

# include

и структура:

struct sock_fprog *Filter;

– непосредственно сам подключаемый фильтр.

Программа фильтрации

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

struct sock_filter BPF_code [ ]  = {

BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),

В принятом Ethernet-кадре по смещению, равному 12 байт (6 байт MAC-адреса источника + 6 байт MAC-адреса назначения), находится 2-х байтовый идентификатор протокола сетевого уровня. Эти 2 байта мы загружаем в аккумулятор.

BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETH_P_IP, 0, 8),

Проверяем соответствие значения, загруженного в аккумулятор, идентификатору IP-протокола (ETH_P_IP = 0x800). При выполнении условия переходим к следующей инструкции (jt = 0). В противном случае смещаемся на 8 структур вниз (jf = 8) и выходим из программы фильтрации с возвратом нулевого значения. Это значит, что данный пакет отброшен.

BPF_STMT(BPF_LD+BPF_W+BPF_ABS, 26),

Загружаем в аккумулятор 4-байтовое значение, находящееся по смещению 26 в принятом пакете. Это значение соответствует IP-адресу источника.

BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0xC0A80102, 0, 6),

Проверяем соответствие значения, загруженного в аккумулятор, IP-адресу 192.168.1.2. Значение 0xC0A80102 – это шестнадцатиричное представление данного IP-адреса. Однако в сетевом формате адрес 192.168.1.2 выглядит как 0x201A8C0. Это связано с порядком передачи в сети – передача начинается с бита младшего разряда. Если адрес не совпадает – выходим из программы фильтрации.

BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 23),

Загружаем в аккумулятор 1 байт, находящийся по смещению 23. В этом поле содержится идентификатор протокола транспортного уровня. Для протокола TCP это значение равно 6.

BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_TCP, 0, 4),

Проверяем соответствие транспортного протокола.

Далее нам необходимо проверить поле «Порт источника» на соответствие значению 23. Для этого необходимо сперва установить длину заголовка IP-пакета.

BPF_STMT(BPF_LDX+BPF_B+BPF_MSH, 14),

В индексный регистр будет загружено значение длины заголовка IP-пакета. По смещению, равному сумме длин Ethernet-заголовка и IP-заголовка, будет находиться поле «Порт источника». Загрузим его в аккумулятор:

BPF_STMT(BPF_LD+BPF_H+BPF_IND, 14),

и проверим полученное значение:

BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x17, 0, 1),

Выходим из программы фильтрации.

BPF_STMT(BPF_RET+BPF_K,1500),

BPF_STMT(BPF_RET+BPF_K,0),

};

Заполним поля структуры struct sock_fprog *Filter :

Filter -> len = 11;            - значение поля len равно числу структур в массиве BPF_code [ ]

Filter -> filter = BPF_code;   - указатель на массив структур BPF_code [ ]

Привязка фильтра к сокету выполняется при помощи вызова setsockopt следующим образом:

if ( setsockopt ( e0_r, SOL_SOCKET, SO_ATTACH_FILTER, Filter, sizeof (*Filter) ) < 0 ) {

    perror ( "SO_ATTACH_FILTER" );

    close ( e0_r );

    exit (1);

}

В приведенном примере есть один недостаток: исходные данные, такие как IP-адрес, номер порта, вводятся в текст программы. При необходимости изменения условий фильтрации придется каждый раз вносить изменения в исходный текст и перекомпилировать программу. Модифицируем программу фильтрации для возможности гибкой настройки фильтра на новое условие, например, на новый IP-адрес. IP-адрес будем вводить в командной строке (аргумент argv [1]). В массиве BPF_code третий элемент перепишем в виде:

BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 4),

В этой инструкции мы обнуляем поле, содержащее IP-адрес. Для хранения введенного IP-адреса нам потребуются дополнительные переменные:

union {

    u_long net;

    char point [4];

} addr;

В поле net объединения addr будет храниться введенный IP-адрес в сетевом формате. Заполним поле net:

addr. net = inet_addr (argv [1]);

Теперь необходимо зеркально перевернуть значение IP-адреса в сетевом формате. Сделаем это, поменяв местами 0-й и 3-й байты, 1-й и 2-й:

addr. point [0] ^= addr. point [3];

addr. point [3] ^= addr. point [1];

addr. point [0] ^= addr. point [3];

addr. point [1] ^= addr. point [2];

addr. point [2] ^= addr. point [1];

addr. point [1] ^= addr. point [2];

Заполним необходимое поле в массиве BPF_code:

BPF_code [3]. k = addr. net;

Таким образом, если мы введем адрес 192.168.1.2, поле BPF_code [3]. k будет содержать значение 0xC0A80102. Для контроля можно отобразить это значение:

printf( « %x «, BPF_code[3]. k );

После этого подключаем фильтр к сокету и работаем.

Использование tcpdump

Для упрощения задачи составления программы фильтрации можно воспользоваться утилитой tcpdump. Давайте рассмотрим, как это делается. Предположим, нам необходим пакетный фильтр, принимающий IP-пакеты, адресатом или отправителем которых является хост с IP-адресом 192.168.9.1. Вводим следующую команду:

tcpdump -dd host 192.168.9.1

На консоли будет отображен результат работы программы:

{ 0x28, 0, 0, 0x0000000c },

{ 0x15, 0, 4, 0x00000800 },

{ 0x20, 0, 0, 0x0000001a },

{ 0x15, 8, 0, 0xc0a80901 },

{ 0x20, 0, 0, 0x0000001e },

{ 0x15, 6, 7, 0xc0a80901 },

{ 0x15, 1, 0, 0x00000806 },

{ 0x15, 0, 5, 0x00008035 },

{ 0x20, 0, 0, 0x0000001c },

{ 0x15, 2, 0, 0xc0a80901 },

{ 0x20, 0, 0, 0x00000026 },

{ 0x15, 0, 1, 0xc0a80901 },

{ 0x6, 0, 0, 0x00000044 },

{ 0x6, 0, 0, 0x00000000 },

Это и есть искомая программа фильтрации.

Вот в принципе все, о чем я хотел рассказать. Обо всех замечаниях и предложениях пишите на ubob@mail.ru.


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

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

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

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

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