вторник, 4 апреля 2017 г.

Новые модули ядра ipfw_pmod, ipfw_nptv6 и ipfw_nat64

Вчера я закоммитил новый модуль для ipfw - ipfw_pmod, а так же смержил в stable/11 модули ipfw_nptv6 и ipfw_nat64.
Во FreeBSD 11.0 в ipfw было добавлено много нового, и среди прочего - поддержка "внешних действий", или external action. Она представляет собой интерфейс, который даёт возможность в рантайме добавлять и удалять в ipfw дополнительные действия для правил (actions, такие как allow, deny, count и прочие) . 
Как это планировалось использовать в теории. Предположим, у нас имеется стоковая FreeBSD, и ряд задач для ipfw, которые строго специфичны для нашей организации, и всем другим людям такие функции вряд ли потребуются. Чтобы не добавлять подобные функции в базовую систему, и в то же время, чтобы упростить поддержку внутри организации (адаптацию патчей с каждой новой версией), были добавлены несколько новых опкодов.
Этих опкодов (сначала их было два, теперь стало три) достаточно, чтобы реализовать практически любую новую функцию в ipfw. Имеются в виду опкоды типа action. Достаточно только подгрузить модуль ядра и правила, использующие эти новые действия будут работать. Даже стоковый бинарник ipfw(8) способен с некоторыми ограничениями отображать такие правила. Для добавления и модификации таких правил необходимо стороннее приложение, исходный код которого менять при обновлении нет необходимости.
В случае если подобный модуль достаточно полезен и для всех других людей, то достаточно включить код для добавления и отображения правил в состав ipfw(8). Ядерная же составляющая остаётся без изменений.
Используя этот интерфейс были созданы три, перечисленные в начале, модуля. Модуль ipfw_pmod предназначен для модификации пакетов различных протоколов. На данный момент он содержит в себе только опкод "tcp-setmss", который как нетрудно догадаться, модифицирует опцию TCP пакета MSS. Правило использующее это действие может выглядеть, например, так:
ipfw add tcp-setmss 1400 tcp from any to any
Работает оно примерно так же, как netgraph модуль ng_tcpmss. Но ещё и поддерживает IPv6. После обработки правилом, действие продолжается со следующего правила. Т.е. правило не прерывает поиск.

Модуль ipfw_nptv6 реализует транслятор IPv6 префиксов и работает по алгоритму, описанному в  RFC6296. Сразу хочу заметить, что трансляция IPv6 префиксов происходит несколько иначе, как можно было бы ожидать. Адреса не транслируются 1 в 1, например 2001:1000::1 не будет оттранслирован в 2a02:6b8::1. Согласно RFC адреса транслируются таким образом, что у транслятора нет необходимости делать пересчёт контрольных сумм. Поэтому в адрес добавляется некоторое число, которое делает вид адреса менее читаемым.
Использование этого модуля несколько отличается от ipfw_pmod. Здесь применяется концепция именованных экземпляров (instance) со своими собственными настройками. Прежде чем использовать nptv6 в правилах, необходимо создать именованный экземпляр и настроить его. После этого уже можно добавлять правила в ipfw:
ipfw nptv6 NPT create int_prefix fd00:dead:c0de:: ext_prefix 2001:470:7ad7:: prefixlen 48
ipfw add nptv6 NPT ip6 from any to any

Модуль ipfw_nat64 реализует транслятор IPv6 в IPv4. Он содержит две реализации stateful и stateless, и тоже использует концепцию именованных экземпляров. NAT64 без отслеживания состояний настраивается при помощи ключевого слова nat64stl. Он использует две таблицы, содержащие отображение IPv6->IPv4 и IPv4->IPv6. Согласен, это не очень удобно, но пока так.
NAT64 с отслеживанием состояний использует ключевое слово nat64lsn. Для его настройки нужен только используемый IPv4 префикс, который определяет количество ресурсов необходимых транслятору.
Оба транслятора используют Well-Known IPv6 префикс для NAT64 64:ff9b::/96. Ну и для работы конечно же нужен работающий DNS64.

пятница, 24 марта 2017 г.

Драйвер для принтера Brother DCP7060D

На прошлой неделе обновил свой рабочий ноутбук. Старенький Lenovo T520 ушёл на покой. Теперь свежая FreeBSD 12.0 установлена на Lenovo X1 Carbon 4th Generation. Пока не всё гладко и особо нет времени глубоко ковыряться, работать можно - этого пока достаточно. 
Решил подключить свой старенький принтер Brother DCP7060D. Раньше я уже адаптировал линуксовый драйвер для него, но так как это было давно, в этот раз установка драйвера не прошла гладко и пришлось заново разбираться.
В итоге, я запаковал изменённые файлики драйвера в архив, если кому-то будет нужно, то взять можно здесь. В идеале было бы здорово сделать порт, но это не такая уж и тривиальная задача, поэтому лучше так, чем никак.
Для работы нужно распаковать архив в /usr/local; установить linux_base, cups, cups-filters и psutils. После этого запустить пару комманд из install.txt, и можно добавлять принтер в cups. Со сканером не разбирался, пока нужды не было.

среда, 22 февраля 2017 г.

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

Как многие уже знают, во FreeBSD 12.0-CURRENT был добавлен новый сетевой псевдоинтерфейс if_ipsec(4). На первый взгляд кажется, что он выполняет те же самые задачи что и уже существующий десятилетия if_gif(4). Но это не совсем так.
Оба интерфейса предоставляют виртуальный туннель, в который может маршрутизироваться трафик. Оба интерфейса настраиваются подобным образом: при помощи ifconfig(8) создаётся интерфейс, ему назначаются адреса конечных точек туннеля, настраиваются адреса внутри туннеля, при необходимости добавляются дополнительные маршруты. В случае с if_gif(4) этих действий уже достаточно для работы интерфейса. Он может принимать и передавать инкапсулированные в IPv4 или IPv6 пакеты.
Чтобы зашифровать передаваемый внутри туннеля трафик необходимо настроить политики и ассоциации безопасности, которые будут применять IPsec преобразования к трафику туннеля, т.е. в качестве селектора адресов для политики должны выступать адреса конечных точек туннеля, а селектором протокола - в зависимости от настроек могут быть различные вариации инкапсуляции IP в IP. Можно конечно "матчить" весь трафик между конечными точками туннеля, но это чаще всего неудобно.   Когда таких туннелей нужно создать много, то встаёт задача синхронизации настроек каждого if_gif(4) туннеля и настроек политик безопасности IPsec. К тому же, наличие большого числа политик безопасности не ускоряет работу по поиску подходящей политики для каждого пакета.
В решении части этих проблем и должен помочь if_ipsec(4). Его особенность в том, что он не выполняет инкапсуляцию сам, как это делает if_gif(4), а использует применяемую для туннельного режима "встроенную" в IPsec инкапсуляцию. При настройке туннеля if_ipsec(4) автоматически создаёт нужные политики безопасности, в которых уже указаны адреса конечных точек туннеля. Стоит заметить, что и принцип применения этих политик отличается от используемого для if_gif(4). Создаваемые интерфейсом if_ipsec(4) политики безопасности "матчат" вообще весь IPv4 и IPv6 трафик. Но так как они привязаны к конкретному интерфейсу, то и "матчат" они только трафик, проходящий через интерфейс.
В итоге можно иметь сотни таких политик безопасности в системе, но при прохождении пакетов поиск подходящей политики вообще не выполняется, т.к. каждый if_ipsec(4) интерфейс выбирает свою единственную политику, которой владеет он. 
На маршрутизаторе, где используется только if_ipsec(4), даже fastforwarding будет работать для всего остального трафика, чего нельзя получить с классическим if_gif(4) и глобальными политиками безопасности, т.к. fastforwarding автоматически отключается при наличии таких политик.
Теперь про особенности настройки. Как я уже сказал, с точки зрения конфигурации интерфейса, всё настраивается точно так же как и для if_gif(4). Но есть один дополнительный параметр reqid. Который может назначаться автоматически, но если не предполагается использовать IKE, то лучше назначить его руками. Этот параметр помагает системе различать политики нескольких интерфейсов и выполнять поиск подходящей ассоциации безопасности.
Ещё одна особенность, отличающая if_ipsec(4) от if_gif(4) - при использовании if_ipsec(4) нет возможности "выключить" IPsec и проверить, что всё работает без шифрования.
Ну и конечно же, настройку ассоциаций безопасности никто не отменял. Пароли и ключи шифрования автоматически никто придумывать за вас не будет. Чтобы ассоциация безопасности начала использоваться интерфейсом if_ipsec(4), нужно при её добавлении использовать специальный параметр у утилиты setkey(8) "-u id", где id - это упомянутый ранее reqid интерфейса. 
Пример создания туннеля между двумя тестовыми машинами test15 и test25:
test15# ifconfig ipsec0 create reqid 145
test15# ifconfig ipsec0 inet tunnel 87.250.242.145 87.250.242.144
test15# ifconfig ipsec0 inet 10.0.0.145/24 10.0.0.144
test15# ifconfig ipsec0
ipsec0: flags=8051 metric 0 mtu 1400
 tunnel inet 87.250.242.145 --> 87.250.242.144
 inet 10.0.0.145 --> 10.0.0.144 netmask 0xffffff00 
 inet6 fe80::225:90ff:fef9:3c92%ipsec0 prefixlen 64 scopeid 0x4 
 nd6 options=23
 reqid: 145
 groups: ipsec 
test15# setkey -c
add 87.250.242.145 87.250.242.144 esp 0xbadcafe -u 145 -E rijndael-cbc "1234567890987654" -A hmac-sha2-256 "12345678901234561234567890123456";
add 87.250.242.144 87.250.242.145 esp 0xcafebad -u 145 -E rijndael-cbc "0987654321123456" -A hmac-sha2-256 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa12";
^D

После настройки адресов туннеля на интерфейсе создаются вот такие политики безопасности.
test15# setkey -DP
0.0.0.0/0[any] 0.0.0.0/0[any] any
 in ipsec
 esp/tunnel/87.250.242.144-87.250.242.145/unique:145
 spid=1 seq=3 pid=9427
 refcnt=1
::/0[any] ::/0[any] any
 in ipsec
 esp/tunnel/87.250.242.144-87.250.242.145/unique:145
 spid=3 seq=2 pid=9427
 refcnt=1
0.0.0.0/0[any] 0.0.0.0/0[any] any
 out ipsec
 esp/tunnel/87.250.242.145-87.250.242.144/unique:145
 spid=2 seq=1 pid=9427
 refcnt=1
::/0[any] ::/0[any] any
 out ipsec
 esp/tunnel/87.250.242.145-87.250.242.144/unique:145
 spid=4 seq=0 pid=9427
 refcnt=1
Изменить политики можно только уничтожив интерфейс или изменив его настройки (адреса или reqid). Удалить их при помощи setkey -DPF так же не выйдет.
test25# ifconfig ipsec0 create reqid 144
test25# ifconfig ipsec0 inet tunnel 87.250.242.144 87.250.242.145
test25# ifconfig ipsec0 inet 10.0.0.144/24 10.0.0.145
test15# setkey -c
add 87.250.242.145 87.250.242.144 esp 0xbadcafe -u 144 -E rijndael-cbc "1234567890987654" -A hmac-sha2-256 "12345678901234561234567890123456";
add 87.250.242.144 87.250.242.145 esp 0xcafebad -u 144 -E rijndael-cbc "0987654321123456" -A hmac-sha2-256 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa12";
^D
test15# tcpdump -qnvi ix0 host 87.250.242.144
tcpdump: listening on ix0, link-type EN10MB (Ethernet), capture size 262144 bytes
14:28:25.170705 IP (tos 0x0, ttl 64, id 33289, offset 0, flags [none], proto ESP (50), length 156)
    87.250.242.144 > 87.250.242.145: ESP(spi=0x0cafebad,seq=0x3), length 136
14:28:25.170928 IP (tos 0x0, ttl 64, id 4871, offset 0, flags [none], proto ESP (50), length 156, bad cksum 0 (->d212)!)
    87.250.242.145 > 87.250.242.144: ESP(spi=0x0badcafe,seq=0x1), length 136
14:28:45.288935 IP (tos 0x0, ttl 64, id 21702, offset 0, flags [none], proto ICMP (1), length 84)
    87.250.242.144 > 87.250.242.145: ICMP echo request, id 37388, seq 0, length 64
14:28:45.288955 IP (tos 0x0, ttl 64, id 32712, offset 0, flags [none], proto ICMP (1), length 84, bad cksum 0 (->65ca)!)
    87.250.242.145 > 87.250.242.144: ICMP echo reply, id 37388, seq 0, length 64
test25# ping -c1 10.0.0.145
PING 10.0.0.145 (10.0.0.145): 56 data bytes
64 bytes from 10.0.0.145: icmp_seq=0 ttl=64 time=0.508 ms

--- 10.0.0.145 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.508/0.508/0.508/0.000 ms
test25# ping -c1 87.250.242.145
PING 87.250.242.145 (87.250.242.145): 56 data bytes
64 bytes from 87.250.242.145: icmp_seq=0 ttl=64 time=0.090 ms

--- 87.250.242.145 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.090/0.090/0.090/0.000 ms 

Этот пример показывает как использовать if_ipsec(4) в режиме ручной настройки. Но if_ipsec(4) вполне может использоваться совместно с различными IKE демонами. В этом случае создание политик безопасности от IKE демона не требуется. Ядро запросит у IKEd создание ассоциации безопасности при первой же попытке прохождения пакета через интерфейс, что может инициировать согласование параметров ассоциаций безопасности между IKE.

пятница, 10 февраля 2017 г.

Отключение проверки на "поддерживаемость" модулей для сетевых карт Intel XL710

Новое поколение сетевых карт Intel 710 серии обслуживается во FreeBSD драйвером if_ixl(4). И как это обычно любит делать Intel, если используемый с картой модуль не находится в "белом" списке производителя, то карта отказывается с ним работать. Во FreeBSD это сопровождается такими сообщениями:
ixl0: Link failed because an unqualified module was detected!
ixl1: Link failed because an unqualified module was detected!
ixl2: Link failed because an unqualified module was detected!
ixl3: Link failed because an unqualified module was detected!
В драйвере if_ixgbe(4) для отключения такого поведения была специальная настройка в loader.conf: hw.ix.unsupported_sfp="1".
В этом драйвере такой настройки нет и судя по всему, эта особенность контролируется firmware карты. Т.е. если карта обнаруживает неподдерживаемый модуль, она отказывается с ним работать и никакие проверки в драйвере этому помешать не могут.
Никаких утилит от производителя для настройки этого поведения найти не удалось. В линуксах, судя по архивам рассылок, эта проблема тоже присутствует. И для линукса был найден вот этот репозиторий, в котором содержится две утилиты. Одна для поиска нужной структуры в NVM карты, а вторая для модификации нужных данных. Автору не удалось найти закономерность в расположении данных в NVM, поэтому, видимо, он не оформил утилиты в виде законченного решения.

Проведя несколько часов за чтением спецификации и драйвера, я написал аналог его утилиты поиска нужной структуры для работы во FreeBSD. И разглядывая полученные данные я нашёл закономерность для получения адреса нужной структуры данных. В результате чего появилась на свет  утилита ixl_unlock. Утилита была проверена на работоспособность с версией драйвера ixl 1.6.6 и версией firmware:
dev.ixl.0.fw_version: fw 5.0.40043 api 1.5 nvm 5.05 etid 800028a2 oem 1.262.0
Чтобы проверить, находит ли она нужные данные, надо использовать её с ключиком -g:
# ./ixl_unlock -g ixl0
EMP SR: 0x66ac
PHY CAP DATA OFFSET: 0x67fa
PHY Capability data structure 0:
000067fa  00  0x000b (Section Length) should be 0x000b
000067fa  01  0x0003
000067fa  02  0x0880
000067fa  03  0x00f0
000067fa  04  0x0000
000067fa  05  0x0000
000067fa  06  0x0e0d
000067fa  07  0x0000
000067fa  08  0x0f08 (PHY Capabilities Misc0) <== will be modified
000067fa  09  0x0000
000067fa  0a  0x0a1e (40 LESM Timer Values) should be 0x0a1e
000067fa  0b  0x0001

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

понедельник, 9 января 2017 г.

Новая реализация fastfwd в FreeBSD 11

Не так давно я смержил в stable/11 обновлённую реализацию fastfwd из head/. А сегодня подошло время MFC аналогичной реализации для IPv6. Для тех кто не знает, поясню чем отличается fastfwd от обычной маршрутизации. До выхода 11-ой версии в FreeBSD было две sysctl переменных, управляющих поведением маршрутизатора: net.inet.ip.forwarding и net.inet.ip.fastforwarding. Первая включает собственно обычную маршрутизацию, т.е. пересылку входящих IPv4 пакетов, которые адресованы других хостам. Вторая - включает использование упрощённого варианта, который выполняет меньшее количество действий и не поддерживает IPsec.

Как это реализовано внутри: входящий пакет после обработки канальным уровнем (например, Ethernet) ставится в очередь обработки IP (netisr queue). Далее эта очередь обрабатывается стеком IP - функцией ip_input(). К пакету применяется ряд проверок на корректность, выполняется обработка пакетными фильтрами (на интерфейсе получения), затем определяется необходимость выполнить пересылку (т.к. пакет адресован не нам). После этого пакет передаётся в функцию маршрутизации ip_forward(). Тут у пакета уменьшается TTL, проверяется необходимость применения IPsec преобразований, выполняется поиск маршрута до адреса назначения и пакет передаётся в функцию отправки ip_output(). Здесь опять же выполняется ряд проверок, пакет передаётся на обработку пакетным фильтрам (уже на интерфейсе отправления); выполняется фрагментация, если нужно. И в конце концов пакет отправляется в канальный уровень, где после добавления Ethernet заголовка он передаётся в драйвер сетевой карты.

Вот такая, довольно громоздкая цепочка действий, в случае с включённым режимом fastforwarding, упрощается до следующих действий: входящий пакет сразу из канального уровня передаётся в функцию ip_fastforward(), где выполняются базовые проверки корректности; пакет обрабатывается пакетными фильтрами на интерфейсе получения; находится необходимый маршрут; уменьшается TTL и выполняется обработка пакетными фильтрами на интерфейсе отправления; после чего пакет сразу отправляется в канальный уровень (если нужна фрагментация, она делается тут же).

За счёт уменьшения числа действий и прямого исполнения без использования очередей netisr в итоге этот вариант получает довольно заметный прирост в производительности. Особенно если исключить из этих действий ещё и пакетные фильтры. На многоядерных процессорах и при использовании сетевых карт с несколькими очередями обработки fastfwd получает ещё более заметный прирост.

В FreeBSD 11 режим fastforwarding стал использоваться по-умолчанию. Последовательность обработки немного отличается от того, что описано выше, но принцип получения прироста производительности в общем-то сохранился.

С увеличением числа процессорных ядер и обрабатываемых пакетов в единицу времени на первый план вышла другая проблема. Теперь несколько одновременно выполняемых потоков борются за доступ к общим ресурсам. Например, при маршрутизации в несколько потоков каждый поток хочет получить эксклюзивный доступ к записи в таблице маршрутизации. Что приводит к простою в ожидании доступа. В результате  общая производительность может даже ухудшиться.

Старый API, используемый для поиска маршрута, обладает таким недостатком. Поэтому обновлённая реализация использует API, который основан на наработках проекта projects/routing. Вот несколько графиков, демонстрирующих изменения в производительности от использования нового API на различном железе:

Тесты и графики построены автором проекта BSD Router Project Olivier Cochard-Labbé. Для IPv6 графиков не нашёл, но тоже можно ожидать примерно такого же роста. Ещё интересный график влияния изменений в FreeBSD CURRENT на производительность маршрутизации за 2016 год:


воскресенье, 11 декабря 2016 г.

Тестирование projects/ipsec

Доброго времени суток.
Хочу предложить читателям (если таковые ещё остались) принять участие в тестировании обновлённой реализации IPsec во FreeBSD. Над этим проектом я в той или иной мере работал последние несколько месяцев. До этого я уже делал несколько "подходов" к коду IPsec, но в основном это были мелкие исправления. Затем измнения стали более сложными, что взволновало некоторых товарищей и меня попросили сначала выкладывать патчи на ревью. :-)
Хотя ситуации это не изменило, ревью делать особо никто не хотел, а я на некоторое время оставил эту область. Но вот, на работе сообщили, что с этим нужно что-то делать - производительность никуда не годится! Это и стало началом projects/ipsec. 
Хочу заметить, что мои изменения не связаны с криптографией, только с сетевыми протоколами. В общем, что сейчас мы имеем в итоге:
  • были переработаны структуры данных SPDB и SADB, правила изменения данных в них; эксклюзивные локи заменены на read-mostly.
  • изменения в SPDB/SADB привели к необходимости переработки реализации PF_KEY и методов обработки входящего и исходящего трафика. На этом этапе я подумал о возможности выделения всего кода IPsec в отдельном месте, что в теории может позволить оформить его в виде модуля ядра.
  • консолидация всего  кода в одном месте вынудила меня разобраться и переделать код подписывания TCP сегментов MD5 сигнатурами. За ним последовал код UDP инкапсуляции, используемый в NAT-T.
  • раз уж я весь код перелопатил, то я решил попробовать сделать специальный туннельный интерфейс if_ipsec, идея по реализации которого уже давно ходит в сети. Вот только некому было.
Это если кратко. Теперь о том, как помочь тестированию. Так как изменений много, все варианты конфигураций проверить я не могу. Если вы с настройкой IPsec на "ты", то сложностей вызывать не должно. Нужно просто обновить какую-либо машину до нужной версии и проверить, не сломалось ли что-то :-)
Если серьёзно, то лучше конечно не экспериментировать с "боевыми" серверами, а выполнять тестирование в лабораторных условиях. Либо в домашних.
Исходный код хранится в отдельном бранче, который основан на FreeBSD 12.0-CURRENT. Патчей для более старых версий нет. Поэтому для тестирования нужно будет установить FreeBSD и обновить её из исходников. Получить исходный код можно, например из svn:
# svn co https://svn.freebsd.org/base/projects/ipsec src
Если кто-то хочет проверить работоспособность NAT-T, я написал небольшой патч к racoon'у, когда пытался протестировать. Патченый порт можно взять тут.
Для тестирования if_ipsec нужно просто создать интерфейс, повесить адреса туннеля на него и добавить соответствующие SA, либо настроить IKEd, чтобы он добавлял SA. Тут есть старые примеры (только сейчас он просто в ядре, не оформлен в виде модуля), небольшое описание того, как он работает тут.

понедельник, 11 января 2016 г.

Восстановление GPT - теперь чуть проще.

Вчера смержил в stable/10 патч, который учит ядро и загрузчик FreeBSD обнаруживать GPT при повреждённом PMBR. У этого есть два эффекта. Во-первых, теперь если повредить первые несколько секторов, то GPT всёравно будет определяться. Не нужно выполнять странных манипуляций с записью /boot/pmbr в первый сектор при помощи dd(1). Второй эффект следует из первого - теперь GPT не уничтожить простым обнулением первого сектора, нужно  обнулять и последний тоже. Проще всего это сделать при помощи gpart destroy
Если вдруг, у вас есть диски, где раньше была GPT но потом вы обнулили первый сектор, то GPT может "неожиданно" появиться после обновления. В случае, если в первом секторе находится MBR, то MBR будет иметь более высокий приоритет - это поведение сохранилось. Так что, кажется, положительных моментов в этом изменении больше.
Изменение в загрузчике сделано для случая с несколькими дисками. Если у вас один диск и первые сектора повреждены, то конечно загружаться ничего не будет. Например, можно загрузиться с флешки в загрузчик и выбрать в нём загрузку с диска с повреждённой GPT, т.к. загрузчик тоже будет находить такую GPT.