Захват и изменение пакетов, а также создание собственного брандмауэра
В этой главе:
В фильтрации пакетов участвуют следующие псевдоустройства:
Псевдоустройство pf реализовано с использованием pfil хуков. bpf представляет собой средство прослушивания всех сетевых драйверов. Мы кратко рассмотрим их подключение к остальной части стека.
Интерфейс pfil полностью интегрирован в стек и поддерживает хуки для фильтрации пакетов. Фильтры пакетов могут регистрировать хуки, которые вызываются в процессе обработки пакетов; pfil фактически представляет собой список функций, вызываемых при наступлении определенных событий. Интерфейс pfil не только позволяет регистрировать фильтр входящих и исходящих пакетов, но и поддерживает интерфейс уведомлений об операциях присоединения/отсоединения и смены адреса.
Интерфейс pfil является одним из множества уровней, в которых пользовательское приложение может регистрироваться для выполнения в контексте стека. В результате компиляции этих уровней формируются загружаемые совместно используемые модули (Loadable Shared Modules, lsm) или загружаемые модули ядра (Loadable Kernel Modules, lkm) в терминологии BSD.
Существуют два обязательных этапа регистрации в стеке io-pkt-*:
Совместно используемые модули динамически загружаются в стек. Их можно указывать в командной строке с помощью ключа -p при запуске менеджера io-pkt-*, либо подключать к существующему процессу io-pkt-* командой mount.
Модуль приложения должен включать в себя точку входа, которая определяется следующим образом:
#include "sys/io-pkt.h"#include "nw_datastruct.h"int mod_entry( void *dll_hdl, struct _iopkt_self *iopkt, char *options ){...}
Входная функция имеет следующие параметры:
struct _iopkt_self
*iopkt Далее следует структура регистрации, которую ищет стек для получения точки входа с помощью функции dlopen() после загрузки модуля:
struct _iopkt_lsm_entry IOPKT_LSM_ENTRY_SYM( mod ) = IOPKT_LSM_ENTRY_SYM_INIT( mod_entry );
Регистрация точки входа осуществляется всеми совместно используемыми модулями независимо от уровня, к которому присоединяется остальная часть кода. Для регистрации на уровне pfil используются следующие функции:
#include <sys/param.h>#include <sys/mbuf.h>#include <net/if.h>#include <net/pfil.h>struct pfil_head * pfil_head_get( int af, u_long dlt );struct packet_filter_hook * pfil_hook_get( int dir, struct pfil_head *ph );int pfil_add_hook( int (*func)(), void *arg, int flags, struct pfil_head *ph );int pfil_remove_hook( int (*func)(), void *arg, int flags, struct pfil_head *ph );int (*func)( void *arg, struct mbuf **mp, struct ifnet *, int dir );
Функция head_get() возвращает заголовок соответствующего списка функций-хуков pfil. Аргумент af может иметь значение PFIL_TYPE_AF
(хук семейства адресов) или PFIL_TYPE_IFNET
(хук интерфейса) для «стандартных» интерфейсов.
Если указано значение PFIL_TYPE_AF
, аргумент dlt (Data Link Type, тип канала передачи), содержит семейство протоколов. В настоящее время поддерживаются точки фильтрации только для AF_INET
(IPv4) или AF_INET6
(IPv6).
При использовании хука интерфейса (PFIL_TYPE_IFNET
) аргумент dlt содержит указатель на структуру сетевого интерфейса. В этом случае все присоединяемые хуки относятся к указанному сетевому интерфейсу.
После получения заголовка соответствующего списка можно добавить хук в список фильтра с помощью функции pfil_add_hook(). Эта функция принимает в качестве аргументов функцию-хук фильтра, непрозрачный указатель, который передается в пользовательскую функцию фильтра как arg, описанные ниже флаги (параметр flags) и заголовок соответствующего списка, возвращаемый функцией pfil_head_get().
Параметр flags определяет, для каких пакетов вызывается функция-хук:
При вызове фильтра пакет представлен в виде, в котором он передается по физическому каналу связи, т.е. во всех полях протокола используется сетевой порядок следования байтов. Фильтр возвращает ненулевое значение, если пакет отбрасывается, и нуль, если пакет пропускается.
В хуках интерфейсов аргумент flags может иметь следующие значения:
mbuf **
содержит номер ioctl()). mbuf **
содержит значение PFIL_IFNET_ATTACH
или PFIL_IFNET_DETACH
). Ниже приведен пример простого хука pfil, который отображает информацию о присоединении и отсоединении интерфейса. При отсоединении интерфейса с помощью ifconfig iface destroy, фильтр выгружается.
#include <sys/types.h>#include <errno.h>#include <sys/param.h>#include <sys/conf.h>#include <sys/socket.h>#include <sys/mbuf.h>#include <net/if.h>#include <net/pfil.h>#include <netinet/in.h>#include <netinet/ip.h>#include "sys/io-pkt.h"#include "nw_datastruct.h"static int in_bytes = 0;static int out_bytes = 0;static int input_hook( void *arg, struct mbuf **m, struct ifnet *ifp, int dir ){in_bytes += (*m)->m_len;return (0);}static int output_hook( void *arg, struct mbuf **m, struct ifnet *ifp, int dir ){out_bytes += (*m)->m_len;return (0);}static int deinit_module( void );static int iface_hook( void *arg, struct mbuf **m, struct ifnet *ifp, int dir ){printf( "Iface hook called ... " );if ( (int)m == PFIL_IFNET_ATTACH ){printf( "Interface attached\n" );printf( "%d bytes in, %d bytes out\n", in_bytes, out_bytes );} elseif ( (int)m == PFIL_IFNET_DETACH ){printf( "Interface detached\n" );printf( "%d bytes in, %d bytes out\n", in_bytes, out_bytes );deinit_module();}return (0);}static int ifacecfg_hook( void *arg, struct mbuf **m, struct ifnet *ifp, int dir ){printf( "Iface cfg hook called with 0x%08X\n", (int)(m) );return (0);}static int deinit_module( void ){struct pfil_head *pfh_inet;pfh_inet = pfil_head_get( PFIL_TYPE_AF, AF_INET );if ( pfh_inet == NULL )return (ESRCH);pfil_remove_hook( input_hook, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet );pfil_remove_hook( output_hook, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet );pfh_inet = pfil_head_get( PFIL_TYPE_IFNET, 0 );if ( pfh_inet == NULL )return (ESRCH);pfil_remove_hook( ifacecfg_hook, NULL, PFIL_IFNET, pfh_inet );pfil_remove_hook( iface_hook, NULL, PFIL_IFNET | PFIL_WAITOK, pfh_inet );printf( "Unloaded pfil hook\n" );return (0);}int pfil_entry( void *dll_hdl, struct _iopkt_self *iopkt, char *options ){struct pfil_head *pfh_inet;pfh_inet = pfil_head_get( PFIL_TYPE_AF, AF_INET );if ( pfh_inet == NULL )return (ESRCH);pfil_add_hook( input_hook, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet );pfil_add_hook( output_hook, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet );pfh_inet = pfil_head_get( PFIL_TYPE_IFNET, 0 );if ( pfh_inet == NULL )return (ESRCH);pfil_add_hook( iface_hook, NULL, PFIL_IFNET, pfh_inet );pfil_add_hook( ifacecfg_hook, NULL, PFIL_IFADDR, pfh_inet );printf( "Loaded pfil hook\n" );return (0);}struct _iopkt_lsm_entry IOPKT_LSM_ENTRY_SYM( pfil ) = IOPKT_LSM_ENTRY_SYM_INIT( pfil_entry );
С помощью интерфейса pfil фильтр пакетов (Packet Filter, pf) подключается к потоку пакетов для реализации брандмауэров и трансляции сетевых адресов (NAT). Фильтр пакетов представляет собой загружаемый модуль для стеков v4 и v6 ( lsm-pf-v4.so или lsm-pf-v6.so соответственно). После загрузки (например, командой mount -T io-pkt /lib/dll/lsm-pf-v4.so) этот модуль создает псевдоустройство pf.
Псевдоустройство pf обеспечивает приблизительно те же функции, что и комплект ipfilter для фильтрации и трансляции сетевых адресов (NAT), в котором также используются хуки pfil.
Более подробная информация указана на страницах:
Для запуска устройства pf используется утилита pfctl, которая выполняет команду DIOCSTART
функции ioctl(). В результате pf вызывает функцию pf_pfil_attach(), которая выполняет необходимые процедуры присоединения pfil. Затем выполняются две важные функции – pf_test() и pf_test6(), которые определяют, какие пакеты IPv4 и IPv6 следует отправлять, принимать и отбрасывать. Хуки фильтра пакетов, а вместе с ними и все устройство pf, отключаются с помощью команды DIOCSTOP
функции ioctl(), которая обычно выполняется в виде pfctl -d.
Дополнительную информацию об использовании фильтров пакетов PF см. в документации OpenBSD по адресу http://www.obsd.si/pub/OpenBSD/doc/pf-faq.pdf. Некоторые фрагменты этого документа (касающиеся очередей пакетов, CARP и др.) неприменимы к стеку io-pkt-*, однако общая информация о конфигурировании является актуальной. В указанном документе рассматриваются конфигурации брандмауэров и NAT, которые можно применять с помощью фильтра пакетов PF.
Фильтр пакетов Berkeley (Berkeley Packet Filter, BPF) обеспечивает доступ к сетевым данным канального уровня с помощью интерфейсов, присоединенных к системе. Для использования BPF следует сначал открыть узел устройства /dev/bpf
, а затем передавать устройству команды с помощью функции ioctl(). Популярным инструментом, который использует BPF, является утилита tcpdump.
Поскольку устройство /dev/bpf
поддерживает клонирование, его можно открывать несколько раз. В целом BPF похож на интерфейс клонирования, но предоставляет не сетевой интерфейс, а лишь метод для многократного открытия одного и того же устройства.
Для захвата сетевого трафика необходимо присоединить устройство BPF к интерфейсу. После этого трафик интерфейса передается BPF для анализа. Присоединение интерфейса к открытому устройству BPF осуществляется командой BIOCSETIF
функции ioctl(). Для идентификации интерфейса используется параметр struct ifreq
, в котором указано имя интерфейса в кодировке ASCII. Это имя используется для поиска интерфейса в таблицах ядра. BPF регистрируется в поле if_bpf (в struct ifnet
), чтобы уведомить систему о намерении пользоваться доступом к трафику этого интерфейса. Слушатель также может указывать набор правил фильтрации, чтобы захватывать только определенные пакеты (например, с заданным сочетанием узла и порта).
Для захвата пакетов BPF предоставляет интерфейс прослушивания bpf_tap() драйверам канального уровня с расчетом, что драйверы будут передавать ему все пакеты. Как правило, драйверы поступают именно так и включают в себя код, который выполняет следующие действия в процессе движения пакетов по входному и выходному трактам:
#if NBPFILTER > 0if ( ifp->if_bpf )bpf_mtap( ifp->if_bpf, m0 );#endif
Этот код передает пакет mbuf
фильтру BPF для проверки. BPF анализирует данные и определяет, какие слушатели этого интерфейса заинтересованы в них. Фильтр, который проверяет данные, тщательно оптимизирован и максимально быстро проверяет каждый пакет. Если пакет удовлетворяет условиям фильтра, он копируется и ожидает считывания устройством.
Функция прослушивания BPF и интерфейсы pfil имеют схожее назначение, однако работают по-разному. BPF mtap пользуется непосредственным доступом к пакетам на физическом уровне и может копировать их для дальнейшего использования. Клиенты pfil могут изменять и отбрасывать пакеты.
BPF имеет богатый многофункциональный синтаксис и является стандартным интерфейсом, который используется многими сетевыми программами. При необходимости перехвата/передачи пакетов применение BPF следует рассматривать в первую очередь. Интерфейс BPF не обладает максимальной производительностью, поскольку копирует отфильтрованные пакеты перед тем, как передать их приложению, находящемуся вне стека. Утилита tcpdump и библиотека libpcap используют интерфейс BPF для перехвата пакетов и отображения статистики по ним.
Предыдущий раздел: перейти