Nuclight Ipfw Rootconf2009
-
Upload
highload-2009 -
Category
Technology
-
view
1.523 -
download
0
Transcript of Nuclight Ipfw Rootconf2009
Вадим ГончаровТомский политехнический университет
Реорганизация IPFW и NETGRAPH: новый подход к контролю сетевого стека
Человеко-машинные системы• ПроцедурныеНабор функций (процедур)
внутри прикладной области, описываемых в терминах прикладной области. Набор решений ограничен.
• ПроективныеНа языке инструментальной
области составляется проект, описывающий ее предполагаемое поведение, затем машина его выполняет.
Процедурные системы• Набор слабосвязанных предписаний• Ограниченная осведомленность• Гарантированные (простые) навыки• Быстрота решения предусмотренной ситуации
Примеры:– Пульт управления телевизором– Мастер настройки Internet в Windows
Проективные системы• Сложность решения задачи• Информационная открытость: документация• Ответственность
» Unix way:ls -dt1 `grep -il отчет *` | head -1
История файрволов• Сначала требовалась лишь статическая фильтрация• Затем потребовалась динамика: отслеживание
соединений и поддержка NAT• Затем потребовалась возможность легкого
расширения функционала• Наконец, теперь требуется управляемость всем
этим разросшимся хозяйством
Статическая фильтрацияОснова любого нынешнего файрвола. Наиболее популярный
подход:• набор правил (ruleset)• каждое правило — отдельная логическая единица,
состоящая из одного или нескольких условий и одного (или нескольких вспомогательных) действий, выполняемых при совпадении (match) условий
• правила имеют разный приоритет
Статические правила: реализацияКлассический подход (ipfilter, ipfw1, pf) — фиксированная
структура, описывающая все возможные варианты проверок, поле флагов описывает, какие именно проверки применены в данном конкретном правиле.
Недостатки:• сложность расширения• большой размер одного правила в байтах в ядре: у pf это
более 600 байт (см. pfrulepl в vmstat -z), тогда как типичный размер cache-line процессора — 64 байта.
А в это время...80-е — Berkley Packet Filter (BPF), виртуальная машина в ядре ОС# tcpdump -d -s 123 src host 1.2.3.4(000) ldh [12](001) jeq #0x800 jt 2 jf 4(002) ld [26](003) jeq #0x1020304 jt 8 jf 9(004) jeq #0x806 jt 6 jf 5(005) jeq #0x8035 jt 6 jf 9(006) ld [28](007) jeq #0x1020304 jt 8 jf 9(008) ret #123(009) ret #0
Ассемблер BPF• Аккумулятор A• Индексный регистр X• 16 слов памяти M[]• Опкоды (opcode, код операции, т. е. инструкция)
одинаковой длины (8 байт)• DLT: Data Link Type
– #define DLT_EN10MB 1 /* Ethernet 10Mb */– #define DLT_RAW 12 /* raw IP */
BSD/OS ipfw, 90-еБазируется на расширенном BPF, в который
компилируется из своего языка:implicit permit;output interface(pif0) {srcaddr(212.22.64.128/26) { established { permit; } switch ipprotocol { case tcp: switch dstport { case pop3/tcp: permit;
Точки входа в сетевом стекеBSD/OS ipfw FreeBSD ipfw/pfil Linux iptables
pre-input PFIL_IN PREROUTING
input INPUT
forward FORWARD
pre-output* OUTPUT
output* PFIL_OUT POSTROUTING
output иpre-output в
BSD/OS ipfw:• Всё наоборот с
локальными пакетами
• В других системах forward → output
Linux iptables (netfilter, 2001)• struct ipt_* (entry, match, ip, target, …)• Переменная длина структур, в постоянных полях
ссылки на функции конкретного модуля• Введены средства облегчения управляемости
(цепочки) и отслеживания соединений (conntrack) как неотъемлемая часть самого файрвола
FreeBSD/NetBSD: pfil(9)
ether_outputether_output()()
ip_input()ip_input() ip_output()ip_output()
К локальным сокетам этой машиныК локальным сокетам этой машины
Драйвера сетевых интерфейсовДрайвера сетевых интерфейсов
ether_demuxether_demux()()
FreeBSD: pfil(9)• ipfw:
pfil_add_hook(ipfw_check_in, NULL, PFIL_IN, pfh_inet);pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT, pfh_inet);
• pf:pfil_add_hook(pf_check_in, NULL, PFIL_IN, pfh_inet);pfil_add_hook(pf_check_out, NULL, PFIL_OUT, pfh_inet);
• ip_input():pfil_run_hooks(&inet_pfil_hook, &m, m->m_pkthdr.rcvif,
PFIL_IN, NULL);• pfil_list_add():
if (flags & PFIL_IN) TAILQ_INSERT_HEAD(list, pfh1, pfil_link);else TAILQ_INSERT_TAIL(list, pfh1, pfil_link);
NetGraph• Коммуникационный объектно—ориентированный
фреймворк внутри ядра• Граф в математике: узлы (nodes) и рёбра (edges)• Граф в ядре: каждый узел имеет крючки (hooks),
соединение хуков двух узлов образует ребро, вдоль которого передаются пакеты
• ОО-стиль: узлы управляются сообщениями (msg)
ngctl9010ngctl9010[ng_socket][ng_socket] [ng_echo][ng_echo]
Команды в ngctl+ mkpeer echo localhook hook1+ name .:localhook myecho
localhook hook1myecho
+ show myecho: Name: myecho Type: echo ID: 00001645 Num hooks: 1 Local hook Peer name Peer type Peer ID Peer hook ---------- --------- --------- ------- --------- hook1 ngctl9010 socket 00001644 localhook
Типы узлов• Граничные (edge nodes) — взаимодействие с
остальной частью ядра– ng_ether, ng_socket, ng_ipfw, ng_iface, ...
• Внутренние — взаимодействие только с соседними узлами– ng_echo, ng_tee, ng_bpf, ng_tag, ng_pppoe, ...
ng_ether(4)
Драйвер интерфейсаДрайвер интерфейса
ether_demuxether_demux()()
upper
lower
В норме хуки ноды как бызамкнуты между собой
Обычный сокетint fd = socket(AF_INET);int fd = socket(AF_INET);
Машинерия Машинерия TCP/IPTCP/IP
Generic socket Generic socket layerlayer
Ядро
Userland
int fd = socket(AF_NETGRAPH);int fd = socket(AF_NETGRAPH);
Generic socket Generic socket layerlayer
Узел типа Узел типа ng_socketng_socket
Ядро
Userland
ng_socket
Машинерия Машинерия TCP/IPTCP/IP
Generic socket Generic socket layerlayer Узел типа ng_ksocketУзел типа ng_ksocket
Ядро
Userland ng_ksocket
Пример графаГраф из ядра легко
можно визуализировать в пакете graphviz, скормив ему текстовый вывод команды ngctl dot
А вот такое автоматически создает mpd
(фрагмент для одного интерфейса ng3 в соединенном состоянии)
Правила ipfw2• Расширяемость была достигнута введением
опкодов, смоделированных наподобие BPF.• FreeBSD следует POLA (принцип наименьшего
удивления пользователя), поэтому интерфейс, основанный на правилах, требовалось сохранить
Гибридная схема: связный список правил, каждое есть набор инструкций-опкодов
Опкоды• Опкод начинается с номера, длины в 4-байтных словах
(возможные аргументы) и флагов• На каждое элементарное действие: O_IP_SRC,
O_IP_SRC_MASK, O_IP_SRC_ME, O_IP_DSTPORT, O_RECV, O_VERREVPATH, O_LOG, O_ACCEPT, O_DIVERT и др.
• Парсер располагает сначала опкоды проверки условий, затем в конце — опкоды действия (action) правила; каждый опкод выставляет match в 0 или 1, при 0 исполнение естественным образом прекращается, не доходя до действия
Конъюнктивная нормальная форма (КНФ)• Каждый опкод имеет флаги F_OR и F_OR• По умолчанию правило исполняется как
конъюнкция (AND) опкодов — первый же false прекращает работу данного правила
• В случае выставленного F_OR действует OR-блок — false переходит к следующему опкоду, true же пропускает опкоды до конца OR-блока (оптимизация выполнения)
Пример правила в КНФipfw add deny { src-ip 1.2.3.4 or src-ip 1.2.3.5 or dst-ip 5.6.7.8} { not dst-port 80 or not dst-port 443 }
• O_IP_SRC, F_OR, 1.2.3.4• O_IP_SRC, F_OR, 1.2.3.5• O_IP_DST, 5.6.7.8• O_IP_DSTPORT, F_OR|F_NOT, 80• O_IP_DSTPORT, F_NOT, 443• O_DENY
Динамические правила• Коммутативная относительно
источника/назначения хэш-функция• O_PROBE_STATE, O_CHECK_STATE, O_LIMIT,
O_KEEP_STATE• Указатель на родительское правило: исполнение
начинается с action offset и продолжается дальше как и в статическом варианте
Трюки делать можно, но...• pf: одно правило с кейвордом reply-to• ipfw:
add 100 skipto 300 tag 1 in recv $ext_if1 keep-stateadd 200 skipto 300 tag 2 in recv $ext_if2 keep-stateadd 300 allow { recv $ext_if1 or recv $ext_if2 }add 400 allow in recv $int_ifadd 500 fwd $gw1 tagged 1add 600 fwd $gw2 tagged 2
Но лучше, чем когда нельзяЗадача: отфильтровать внутри транзитного PPTP
GRE src-адреса нашей клиентской подсети• pf: невозможно в принципе• ipfw + netgraph позволяют это сделать, хотя и
дорогой ценой — сложные скрипты и выражения для tcpdump
Выкурим кучу мануалов и начнем...#define GRESTART IPHDRLEN(0)/* Check that is GREv1 with seq num and proto set per RFC 2637 */#define VALID_PPTP_GRE ((ip[GRESTART:4] & 0xff7fffff) = 0x3001880b)/* ACK is optional 4 bytes to previous 12 */#define GRE_DATA_START (GRESTART + ((ip[GRESTART+1] & 0x80) >> 5) + 12)/* Actual IP subnet/Mask to find in the src IP of inner IP datagram */#define SUBNET 0x52754000 /* 82.117.64.0 */#define MASK 0xffffff00 /* 255.255.255.0 */#define INNER_SRC_EQ_SUBNET(ppp_hdr_len) (ip[(GRE_DATA_START+ppp_hdr_len+12):4] & MASK =
SUBNET)/* Finally, expression: sort by most frequent pattern first */proto gre and VALID_PPTP_GRE and ( ( (ip[GRE_DATA_START]=0x21) and INNER_SRC_EQ_SUBNET(1) ) or ( (ip[GRE_DATA_START:2]=0xff03) and (ip[GRE_DATA_START+2]=0x21) and
INNER_SRC_EQ_SUBNET(3) ) or ( (ip[GRE_DATA_START:4]=0xff030021) and INNER_SRC_EQ_SUBNET(4) ) or ( (ip[GRE_DATA_START:2]=0x0021) and INNER_SRC_EQ_SUBNET(2) ))
Совершенно нечитаемо, правда?$ cpp -P tcpdump-gre-addr-cppproto gre and ((ip[((ip[0]&0xf)<<2):4] & 0xff7fffff) = 0x3001880b) and ( ( (ip[(((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12)]=0x21) and
(ip[((((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12)+1 +12):4] & 0xffffff00 = 0x52754000)
) or ( (ip[(((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12):2]=0xff03) and
(ip[(((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12)+2]=0x21) and (ip[((((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12)+3 +12):4] & 0xffffff00 = 0x52754000)
) or ( (ip[(((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12):4]=0xff030021) and
(ip[((((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12)+4 +12):4] & 0xffffff00 = 0x52754000)
) or ( (ip[(((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12):2]=0x0021) and
(ip[((((ip[0]&0xf)<<2) + ((ip[((ip[0]&0xf)<<2)+1] & 0x80) >> 5) + 12)+2 +12):4] & 0xffffff00 = 0x52754000)
))
А теперь — в netgraph!#!/bin/shPATTERN=`cpp -P tcpdump-gre-addr-cpp`BPFPROG=`tcpdump -s 8192 -r dltraw.pcap -ddd
$PATTERN | awk -f /tmp/bpf.awk`...ngctl mkpeer ipfw: bpf 190 $INHOOKngctl name ipfw:190 $NODENAMEngctl msg $NODEPATH setprogram
{ thisHook=\"$INHOOK\" ifMatch=\"$MATCHHOOK\" ifNotMatch=\"$NOTMATCHHOOK\" $BPFPROG }
ipfw add 4492 netgraph 190 gre from 82.117.64.0/24 to any iplen 60-1500
Что будем улучшать?• Единый набор правил для всего — неудобен при большом
количестве правил (хотя можно сгенерировать правила машиной)
• Надо сохранить управляемость машиной, привычный интерфейс (кто хочет учить netgraph?) и возможность знающему сотворить что-то, не укладывающееся в шаблоны
Делаем несколько наборов правил, каждый — как узел netgraph, и человеческий интерфейс (кто хочет — прицепит узлы нестандартным образом сам)
Интерфейс?ipfw chain mychain create [default-policy {allow | deny}]
ipfw chain mychain add 200 ...ipfw chain mychain delete 300ipfw chain mychain destroyipfw bind mychain to interface em0 direction out
ipfw pfil-order move ipfilter last
Влечет за собой другие изменения• Меняем API/ABI — setsockopt() требует правки других
файлов ядра при значительных изменениях типа таблиц; сообщения netgraph же легко расширяемы и естественным образом адресуются конкретному набору правил (узлу)
• Переведем на это же dummynet и введем в юзерлэнде модульную структуру парсера
• Динамические правила: серьезная переработка (единственный номер родительского правила недостаточен), нет интерфейса управлением соединением
Пока мы здесь, надо это перетрясти хорошенько и в других местах
Модули: более удобный к ним и netgraph интерфейс, кейворды ngcall/ngmatch, вызов наборов правил из других наборов правил
• ipfw ng_bpf mygreaddr create• ipfw ng_bpf mygreaddr config `cpp -P tcpdump-gre-addr-cpp`
• ipfw add deny ngmatch mygreaddr
Похоже на BPF?Возьмем еще больше от BPF!• Введем регистры как аналог M[]: хранение
информации о пакете, о динамическом соединении, tablearg будет аналогом аккумулятора A
• Вычисления над регистрами и данными пакета
И пристыкуем• Динамические правила — собственный небольшой
массив опкодов, использование регистров, вычислений– Хотите каждые 10 секунд медленно уменьшать
пакетам соединения TTL, а потом обратно? Без проблем!
• Дополнительные обработчики соединений• Операции над таблицами
Что еще?• Наборы портов: add deny src-ip table(1) src-
port portset(1) dst-port portset(tablearg) …• API/ABI для изменения состояния
динамических правил из netgraph и не только– отслеживание соединений– возможность failover в carp(4)
Более мелкие изменения• Расширим tablearg до uint64_t, таблицы для хранения
MAC-адресов, Ipv6• Действия call/return внутри одного набора правил• Операции над счетчиками и их проверка• Быстрое сохранение/загрузка правил в бинарном виде• Разворачивание и сворачивание типичных конструкций
алиасами в парсере (reply-to?)• Ваши предложения? :-)
Планы на будущее• Можно уйти от ориентации на правила и
сделать плоскую структуру опкодов, с оптимизатором в парсере– Позволит сделать описание конфигов на другом
языке, по типу BSD/OS или Juniper– Проблемы со счетчиками, логами– Позволит сделать компиляцию в машинный код
аналогично BPF JIT в 7.x
Если пофантазировать...• Сделать ipfw узлом netgraph когда-то было
фантастической идеей, из перечня его авторов• Можно пойти дальше, перевести на netgraph и
другие части сетевого стека, создать расширяемый визуальный конфигуратор сетевой системы в едином стиле
Идеи носятся в воздухеСовсем недавно команда разработчиков Netfilter обнародовала
сообщение о выпуске первой альфы нового файрвола для Linux, грядущего на замену iptables.
include "ipv4-filter"chain filter output { ct state established,related accept tcp dport 22 accept counter drop}
Чем оно отличается?• Таблицы реализованы как хэши и RB-деревья прямо
в наборе правил– нет возможности адресовать таблицу как
отдельную сущность• Требование декомпиляции ограничивает
возможности по оптимизации• Отсутствие обратной совместимости• Фиксированные точки входа — не netgraph
Спасибо за внимание!