Псевдоустройство фильтрации пакетов
/dev/pf
ЗОСРВ «Нейтрино»
aarch64, arm, armv7, e2k, mips, ppc, x86
Фильтрация пакетов выполняется в io-pkt-*. Псевдоустройство /dev/pf
позволяет управлять поведением фильтра пакетов через интерфейс ioctl(). Существуют команды, с помощью которых можно активировать или деактивировать фильтр, загружать наборы правил, добавлять или удалять отдельные правила или записи таблицы состояний и собирать статистику. Утилита pfctl обеспечивает наиболее часто используемые функции.
Несмотря на то, что в документации по NetBSD описывается ioctl(), в коде фильтрации пакетов следует использовать ioctl_socket(). В архитектуре передачи сообщений микроядра вызовы команды ioctl() с встроенными указателями должны обрабатываться по специальной схеме. В случае если специальная обработка не требуется функции ioctl_socket() по умолчанию представляет собой функцию ioctl().
Для таких операций манипулирования, как загрузка набора правил, предполагающая неоднократный вызов ioctl_socket(), требуется мандат, предотвращающий выполнение нескольких противоречивых операций. |
Поля структур параметра ioctl_socket(), относящиеся к пакетным данным (например, адреса и порты), обычно располагаются в сетевом порядке байтов.
Правила и таблицы адресов содержатся в закладках. Если поле закладки структуры аргумента не заполнено, то при обслуживании запроса ioctl_socket()) администратор io-pkt-* использует для выполнения операций закладку по умолчанию (т.е. основной набор правил). Закладки определяются по имени и могут быть вложенными, при этом компоненты разделяются символами косой черты, аналогично иерархиям файловой системы. Конечный компонент пути к закладке – собственно закладка, в соответствии с которой выполняются операции.
Интерфейс ioctl_socket()
Псевдоустройство /dev/pf
поддерживает следующие команды ioctl_socket(), которые описаны в заголовочном файле <net/pfvar.h>
:
DIOCADDADDR
, DIOCADDRULE
и DIOCCHANGERULE
. Структура pfioc_pooladdr определена следующимобразом: struct pfioc_pooladdr {u_int32_t action;u_int32_t ticket;u_int32_t nr;u_int32_t r_num;u_int8_t r_action;u_int8_t r_last;u_int8_t af;char anchor[MAXPATHLEN];struct pf_pooladdr addr;};
DIOCADDRULE
или DIOCCHANGERULE
. Все остальные элементы структуры игнорируются. struct pfioc_rule {u_int32_t action;u_int32_t ticket;u_int32_t pool_ticket;u_int32_t nr;char anchor[MAXPATHLEN];char anchor_call[MAXPATHLEN];struct pf_rule rule;};
Для выполнения этого вызова необходимо наличие мандата ticket, полученного в результате предварительного вызова команды DIOCXBEGIN
, и мандата пула pool_ticket, полученного в результате вызова DIOCBEGINADDRS
. Кроме того, если требуются адреса пулов, следует вызвать команду DIOCADDADDR
. Дополнительное имя закладки указывает на закладку, к которой требуется добавить правило. Элементы nr и action игнорируются.
struct pfioc_altq {u_int32_t action;u_int32_t ticket;u_int32_t nr;struct pf_altq altq;};
DIOCGETRULE
и число nr правил в активном наборе правил. DIOCGETRULES
. DIOCGETADDR
и число nr адресов пулов в правиле, определенном элементами r_action, r_num и anchor. DIOCGETADDRS
. DIOCGETALTQ
и число nr очередей в активном списке. DIOCGETALTQS
. struct pfioc_qstats {u_int32_t ticket;u_int32_t nr;void *buf;int nbytes;u_int8_t scheduler;};
При вызове этой команды заполняется указатель на буфер статистики buf длиной nbytes для очереди с номером nr.
DIOCGETRULESET
. Структура pfioc_ruleset определена следующим образом: struct pfioc_ruleset {u_int32_t nr;char path[MAXPATHLEN];char name[PF_ANCHOR_NAME_SIZE];};
Вложенные закладки игнорируются, поскольку они напрямую не присоединены к данной закладке. Если эта закладка отсутствует, команда ioctl_socket() возвращает EINVAL
.
DIOCGETRULESETS
. Если эта закладка не существует, команда ioctl_socket() возвращает EINVAL
, а если набор правил одновременно обновляется другим процессом – EBUSY
. struct pfioc_state {u_int32_t nr;struct pf_state state;};
struct pfioc_state_kill {sa_family_t psk_af;int psk_proto;struct pf_rule_addr psk_src;struct pf_rule_addr psk_dst;char psk_ifname[IFNAMSIZ];};
DIOCKILLSTATES
, однако при ее выполнении игнорируются поля psk_af, psk_proto, psk_src и psk_dst структуры pfioc_state_kill. struct pfioc_if {char ifname[IFNAMSIZ];};
struct pf_status {u_int64_t counters[PFRES_MAX];u_int64_t lcounters[LCNT_MAX];u_int64_t fcounters[FCNT_MAX];u_int64_t scounters[SCNT_MAX];u_int64_t pcounters[2][2][3];u_int64_t bcounters[2][2];u_int64_t stateid;u_int32_t running;u_int32_t states;u_int32_t src_nodes;u_int32_t since;u_int32_t debug;u_int32_t hostid;char ifname[IFNAMSIZ];};
struct pfioc_natlook {struct pf_addr saddr;struct pf_addr daddr;struct pf_addr rsaddr;struct pf_addr rdaddr;u_int16_t sport;u_int16_t dport;u_int16_t rsport;u_int16_t rdport;sa_family_t af;u_int8_t proto;u_int8_t direction;};
struct pfioc_states {int ps_len;union {caddr_t psu_buf;struct pf_state *psu_states;} ps_u;#define ps_buf ps_u.psu_buf#define ps_states ps_u.psu_states};
Если значение ps_len равно нулю, то все состояния суммируются в элементе pf_states, а ps_len принимает значение, соответствующее объему памяти, который они занимают (т.е. sizeof( struct pf_state ) * nr). Если значение ps_len отлично от нуля, то максимальное количество состояний суммируется в элементе ps_len, а значение ps_len обновляется в соответствии с объемом памяти, который занимают правила.
PF_CHANGE_GET_TICKET
, для ticket следует установить значение, полученное в результате вызова команды PF_CHANGE_GET_TICKET
. Для всех действий, за исключением PF_CHANGE_REMOVE
и PF_CHANGE_GET_TICKET
, для pool_ticket следует установить значение, полученное в результате вызова команды DIOCBEGINADDRS
. Значение anchor определяет, для какой закладки выполняется операция. Элемент nr определяет номер правила, к которому применяются действия PF_CHANGE_ADD_BEFORE
, PF_CHANGE_ADD_AFTER
или PF_CHANGE_REMOVE
. struct pfioc_tm {int timeout;int seconds;};
Предыдущее значение указывается в поле seconds. Возможные значения элемента timeout приведены в списке значений PFTM_*
в заголовочном файле <net/pfvar.h>
.
struct pfioc_limit {int index;unsigned limit;};enum {PF_LIMIT_STATES,PF_LIMIT_SRC_NODES,PF_LIMIT_FRAGS};
DIOCRCLRTABLES
в поле pfrio_ndel содержится количество удаленных таблиц. Структура pfioc_table определена следующим образом: struct pfioc_table {struct pfr_table pfrio_table;void *pfrio_buffer;int pfrio_esize;int pfrio_size;int pfrio_size2;int pfrio_nadd;int pfrio_ndel;int pfrio_nchange;int pfrio_flags;u_int32_t pfrio_ticket;};#define pfrio_exists pfrio_nadd#define pfrio_nzero pfrio_nadd#define pfrio_nmatch pfrio_nadd#define pfrio_naddr pfrio_size2#define pfrio_setflag pfrio_size2#define pfrio_clrflag pfrio_nadd
struct pfr_table {char pfrt_anchor[MAXPATHLEN];char pfrt_name[PF_TABLE_NAME_SIZE];u_int32_t pfrt_flags;u_int8_t pfrt_fback;};
DIOCRGETTABLES
, но она используется для получения массива структур pfr_tstats. Структура pfr_tstats определена следующим образом: struct pfr_tstats {struct pfr_table pfrts_t;u_int64_t pfrts_packets[PFR_DIR_MAX][PFR_OP_TABLE_MAX];u_int64_t pfrts_bytes[PFR_DIR_MAX][PFR_OP_TABLE_MAX];u_int64_t pfrts_match;u_int64_t pfrts_nomatch;long pfrts_tzero;int pfrts_cnt;int pfrts_refcnt[PFR_REFCNT_MAX];};#define pfrts_name pfrts_t.pfrt_name#define pfrts_flags pfrts_t.pfrt_flags
struct pfr_addr {union {struct in_addr _pfra_ip4addr;struct in6_addr _pfra_ip6addr;} pfra_u;u_int8_t pfra_af;u_int8_t pfra_net;u_int8_t pfra_not;u_int8_t pfra_fback;};#define pfra_ip4addr pfra_u._pfra_ip4addr#define pfra_ip6addr pfra_u._pfra_ip6addr
DIOCRGETADDRS
. DIOCRGETADDRS
, но используется для извлечения массива структур pfr_astats: struct pfr_astats {struct pfr_addr pfras_a;u_int64_t pfras_packets[PFR_DIR_MAX][PFR_OP_ADDR_MAX];u_int64_t pfras_bytes[PFR_DIR_MAX][PFR_OP_ADDR_MAX];long pfras_tzero;};
PFR_TFLAG_CONST
или PFR_TFLAG_PERSIST
. При запуске команды в поле pfrio_buffer[pfrio_size] содержится таблица структур pfr_table, в поле pfrio_setflag – флаги, которые требуется добавить, а в поле pfrio_clrflag – флаги, которые требуется удалить. После завершения выполнения команды в полях pfrio_nchange и pfrio_ndel содержится количество таблиц, измененных или удаленных администратором io-pkt-*. Таблицы можно удалить путем удаления флага PFR_TFLAG_PERSIST таблицы, ссылка на которую отсутствует. |
0
, если таблица уже определена в неактивном списке, или значение 1
, если создана новая таблица. Элемент pfrio_naddr содержит количество адресов, успешно добавленных в таблицу. struct pfioc_trans {int size; /* количество элементов */int esize; /* размер каждого элемента в байтах */struct pfioc_trans_e {int rs_num;char anchor[MAXPATHLEN];u_int32_t ticket;} *array;};
Для каждого набора правил возвращается мандат для последующего выполнения команд "добавления правила" ioctl_socket(), а также для вызова команд DIOCXCOMMIT
и DIOCXROLLBACK
. Имеются следующие типы наборов правил, определяемые элементом rs_num:
EBUSY
. DIOCXBEGIN
. DIOCXROLLBACK
игнорирует наборы правил, для которых мандант недействителен, без оповещения. struct pf_osfp_ioctl {struct pf_osfp_entry {SLIST_ENTRY( pf_osfp_entry ) fp_entry;pf_osfp_t fp_os;char fp_class_nm[PF_OSFP_LEN];char fp_version_nm[PF_OSFP_LEN];char fp_subtype_nm[PF_OSFP_LEN];} fp_os;pf_tcpopts_t fp_tcpopts;u_int16_t fp_wsize;u_int16_t fp_psize;u_int16_t fp_mss;u_int16_t fp_flags;u_int8_t fp_optcnt;u_int8_t fp_wscale;u_int8_t fp_ttl;int fp_getnum;};
В поле fp_os.fp_os указывается упакованный отпечаток, в поле fp_os.fp_class_nm – имя класса (Linux, Windows и т.д.), в поле fp_os.fp_version_nm – имя версии (NT, 95, 98), а в поле fp_os.fp_subtype_nm – имя подтипа или уровня исправлений. Элементы fp_mss, fp_wsize, fp_psize, fp_ttl, fp_optcnt и fp_wscale определяют соответственно максимальный размер сегмента TCP, размер окна TCP, длину IP-пакетов, предельное время существования IP-пакетов, количество опций TCP и константу масштабирования окна TCP для TCP- пакета SYN.
Значения в элемент fp_flags подставляются в соответствии с определением PF_OSFP_*
в заголовочном файле <net/pfvar.h>
. Элемент fp_tcpopts содержит упакованные опции TCP. Каждая опция использует упакованное значение битов PF_OSFP_TCPOPT_BITS
. Варианты опций:
EBUSY
. struct pfioc_src_nodes {int psn_len;union {caddr_t psu_buf;struct pf_src_node *psu_src_nodes;} psn_u;#define psn_buf psn_u.psu_buf#define psn_src_nodes psn_u.psu_src_nodes};
Необходимо один раз вызвать функцию ioctl_socket() с нулевым значением psn_len. Если ioctl_socket() не возвращает ошибки, в качестве значения psn_len устанавливается размер буфера, необходимого для сохранения всех структур из таблицы pf_src_node. Затем требуется выделить буфер этого размера и установить указатель на этот буфер в psn_buf. Затем необходимо повторно вызвать ioctl_socket() для заполнения буфера актуальными данными из узла-источника. После этого вызова в поле psn_len указывается фактически используемый размер буфера.
/dev/pf
. Все команды ioctl_socket() для манипулирования интерфейсами имеют следующую структуру: struct pfioc_iface {char pfiio_name[IFNAMSIZ];void *pfiio_buffer;int pfiio_esize;int pfiio_size;int pfiio_nzero;int pfiio_flags;};#define PFI_FLAG_GROUP 0x0001 /* получение групп интерфейсов */#define PFI_FLAG_INSTANCE 0x0002 /* получение отдельных интерфейсов */#define PFI_FLAG_ALLMASK 0x0003
Для ограничения поиска определенным интерфейсом или драйвером можно использовать аргумент pfiio_name, если его значение определено. Элемент pfiio_buffer[pfiio_size] представляет собой пользовательский буфер для возврата данных. При запуске команды в поле pfiio_size указывается количество записей pfi_if, вмещаемых буфером. Менеджер io-pkt-* заменяет это значение фактическим количеством возвращаемых записей.
В поле pfiio_esize следует установить значение sizeof( struct pfi_if ). Для возврата администратором io-pkt-* группы интерфейсов (драйверов, например fxp), реальных экземпляров интерфейсов (например, fxp1), или группы интерфейсов и экземпляров для элемента pfiio_flags следует установить значение PFI_FLAG_GROUP
, PFI_FLAG_INSTANCE
или оба эти значения. Данные, возвращаемые pfi_if, имеют следующую структуру:
struct pfi_if {char pfif_name[IFNAMSIZ];u_int64_t pfif_packets[2][2][2];u_int64_t pfif_bytes[2][2][2];u_int64_t pfif_addcnt;u_int64_t pfif_delcnt;long pfif_tzero;int pfif_states;int pfif_rules;int pfif_flags;};#define PFI_IFLAG_GROUP 0x0001 /* группа интерфейсов */#define PFI_IFLAG_INSTANCE 0x0002 /* один экземпляр */#define PFI_IFLAG_CLONABLE 0x0010 /* клонируемая группа */#define PFI_IFLAG_DYNAMIC 0x0020 /* динамическая группа */#define PFI_IFLAG_ATTACHED 0x0040 /* связанный интерфейс */
DIOCIGETIFACES
. Для элемента pfiio_nzero администратор io-pkt-* устанавливает значение количества интерфейсов и драйверов, для которых удалены счетчики статистики. /dev/pf
: DIOCIGETIFACES
. DIOCSETIFFLAG
, но удалить флаги. Пример использования команды DIOCNATLOOK
для поиска внутреннего хоста/порта соединения NAT:
#include <sys/types.h>#include <sys/socket.h>#include <sys/ioctl.h>#include <sys/fcntl.h>#include <net/if.h>#include <netinet/in.h>#include <net/pfvar.h>#include <err.h>#include <stdio.h>#include <stdlib.h>u_int32_t read_address( const char *s ){int a, b, c, d;sscanf( s, "%i.%i.%i.%i", &a, &b, &c, &d );return htonl( a << 24 | b << 16 | c << 8 | d );}void print_address( u_int32_t a ){a = ntohl( a );printf( "%d.%d.%d.%d", a >> 24 & 255, a >> 16 & 255, a >> 8 & 255, a & 255 );}int main( int argc, char *argv[] ){struct pfioc_natlook nl;int dev;if ( argc != 5 ){printf( "%s \n", argv[0] );return (1);}dev = open( "/dev/pf", O_RDWR );if ( dev == -1 )err( 1, "open(\"/dev/pf\") failed" );memset( &nl, 0, sizeof( struct pfioc_natlook ) );nl.saddr.v4.s_addr = read_address( argv[1] );nl.sport = htons( atoi( argv[2] ) );nl.daddr.v4.s_addr = read_address( argv[3] );nl.dport = htons( atoi( argv[4] ) );nl.af = AF_INET;nl.proto = IPPROTO_TCP;nl.direction = PF_IN;if ( ioctl_socket( dev, DIOCNATLOOK, &nl ) )err( 1, "DIOCNATLOOK" );printf( "internal host" );print_address( nl.rsaddr.v4.s_addr );printf( ":%u\n", ntohs( nl.rsport ) );return (0);}
В данной версии NetBSD отсутствуют следующие функциональные возможности /dev/pf
:
Базовые подсистемы ЗОСРВ «Нейтрино», NetBSD
Предыдущий раздел: Сервисы