[О блоге] [наверх] [пред] [2021-03-15 22:31:02+03:00] [793966ed64c5a6884e7f1a3d5491a7be4b3eea5f]
Темы: [bsd]

Познакомился с Capsicum, kqueue, libnv, privsep и privdrop

https://en.wikipedia.org/wiki/Capsicum_(Unix)
https://en.wikipedia.org/wiki/Kqueue
https://oshogbo.vexillium.org/blog/42/
https://utcc.utoronto.ca/~cks/space/blog/solaris/SolarisNvpairLibrary
Реализовал программу с честным (как мне кажется) полноценным privsep-ом,
когда она fork-ается, разные процессы занимаются разными вещами и имеют
доступ к разным ресурсам. Общаются между собой по Unix-сокету. Умеют
делать chroot, сбрасывать привилегии root-а после этого. Capsicum
включают, в том числе и устанавливая ограничения на каждый файловый
дескриптор выборочно. Закрывают всё лишнее и ненужное (stdin/out/err
всякие).

С Capsicum работать по сути тривиально, но архитектура программы
выстраивается вокруг использования файловых дескрипторов. Благо, в
отличии от уродского Linux, в FreeBSD даже процессы можно представлять в
виде файлов (pdfork()).

Так как системные вызовы типа waitpid() уже нельзя использовать в
Capsicum окружении, то нужно делать pdfork(), чтобы получить файл. В
man-е присутствует pdwait4(), однако его нет в исходном коде, кроме как
с пометками "ещё не реализовано". Подсмотрел как с этим живут
capsicum-изированные программы в самой ОС. Оказалось что просто
используют kqueue. Пришлось впервые и с ним поработать. Думал будет
сложно. Но... под рукой был ровно один только man kqueue и через
считанные минуты я полностью реализовал код и ожидания события когда в
Unix сокете что-то будет для чтения и когда процесс завершит свою
работу, оповещая об этом через process descriptor файл. Я очень очень
удивлён как просто работать с kqueue и как много он умеет. Можно даже
просто таймер поставить -- что я часто делал в Go языке в select-ах.

А по сокету мне нужно гонять разношёрстные данные. Точнее, не то чтобы
нужно, а хотелось бы. Как вариант можно открыть несколько сокетов и
ошибки и события отправлять по одним, а полезную нагрузку по другим (как
в FTP отдельные TCP соединения для данных и команд). Но попробовал libnv
библиотеку. У меня никогда не возникло бы мысли о том, что
разнотипизированные данные, где могут быть и вложенные словари и
массивы, могут хотя бы в теории быть просты в использовании в Си или Go.
libnv библиотека супер проста! Даже ошибку абсолютно штатно можно
проверять только во время сериализации. Даже в цикле накапливать и
доделывать (append) данные спокойно. Читать всё аналогично просто. Сам
код является схемой. Очень эффективна по использованию памяти: можно
даже брать значения из nv-пар и они сразу же будут очищать из
nv-структур, оставляя заботу о free() на самом пользователе. И добавлять
значения в nv-структуры можно тоже сразу же их очищая. Да я на Go такой
простой работы не встречал наверное нигде. Одно удовольствие работать с
данными в таком виде. Причём она и endianness блюдёт и можно спокойно
сериализовывать всё на диск. И куча типов данных: null, bool, number,
строки, списки вложенных nv структур, файловый дескриптор, бинарь,
массивы bool/number/строк/дескрипторов.

Но корни libnv растут ещё из Sun Microsystems и Solaris. В ZFS nvpairs
используются всюду и везде. libnv это функциональный аналог nvpairs. В
Solaris оно и вне ZFS встречается. Причём реализация Sun сериализует в
XDR формате всё. Который мне уже и так давно нравился, а недавно я
прочувствовал его крутость с 32-х битным выравниваем всего и вся -- это
существенно упрощает работу с ним на системах которым не нравится
невыровненный доступ к памяти. Ну и пускай что bool занимает всё равно
32-бита, но зато какой простой код получается для работы со всем этим!
Бегло поглядел на libnv и там уже не XDR, а что-то своё, без этой
alignment красоты. XDR я на практике ведь в NNCP использую. А nvpairs
сериализованные данные хранятся прямо на диске в ZFS структурах и без
всякой схемы можно выводить их содержимое.

Дальше всё аналогичное я хочу проделать в экосистеме GNU/Linux. Мне
кажется это будет настоящий ад. Seccomp вроде бы мне достаточен в его
strict режиме. Например после входа в Capsicum в FreeBSD я дополнительно
ограничиваю read/write/kqueue возможности на оставшиеся сокеты -- а в
seccomp strict режиме они сразу же будут только в read/write. Но вопросы
сериализации остаются и... как мне waitpid то там сделать? С ходу не
знаю, если в strict. Плюс kqueue там нет. Буду готовиться к страданиям.

    [оставить комментарий]
    комментарий 0:
    From: kmeaw
    Date: 2021-03-19 06:46:38Z
    
    > отличии от уродского Linux, в FreeBSD даже процессы можно представлять
    > в виде файлов
    
    Можно воспользоваться pidfd_open(2):
    
    > A PID file descriptor can be monitored using poll(2), select(2), and
    > epoll(7).  When the process that it refers to terminates, these
    > interfaces indicate the file descriptor as readable.
    
    В портабельных программах для ожидания завершения процессов, которые не
    являются прямыми потомками, часто используют pipe, пишущий конец
    которого остаётся за дочерним процессом (и закрывается операционной
    системой в момент его завершения). Недостаток этого подхода в том, что
    дочерний процесс может "симулировать свою смерть", сделав close.
    
    > Дальше всё аналогичное я хочу проделать в экосистеме GNU/Linux.
    
    > я дополнительно
    > ограничиваю read/write/kqueue возможности на оставшиеся сокеты -- а в
    > seccomp strict режиме они сразу же будут только в read/write
    
    А от чего хочется защититься, ограничивая сокет в режимах read-only или
    write-only?
    
    Если действительно необходимо ограничить передачу данных в одном
    направлении и не хочется использовать seccomp bpf, то можно
    воспользоваться memfd_create(2) и file sealing.
    
    > Плюс kqueue там нет.
    
    Есть epoll.
    
    комментарий 1:
    From: Sergey Matveev
    Date: 2021-03-19 07:24:53Z
    
    *** kmeaw [2021-03-19 09:37]:
    >Можно воспользоваться pidfd_open(2):
    >> Плюс kqueue там нет.
    >Есть epoll.
    
    Судя по статьям, который ни в какое сравнение с kqueue не идёт, ибо не
    поддерживает слежение на файлами (вместо этого же, насколько слышал,
    всякие inotify используют? для AIO ещё что-то отдельное). Возможно
    что-то поменялось со временем, но и старые и новые статьи имеют только
    критику непродуманности epoll и его ограниченность. kqueue умеет ещё и
    пачкой сразу получать cобытия.
    https://long-zhou.github.io/2012/12/21/epoll-vs-kqueue.html
    https://www.opennet.ru/base/dev/kqueue_vs_epoll.txt.html (хотя это 2003г)
    ну и автор libev очень нелестного о нём мнения:
    http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#FUNCTIONS_CONTROLLING_EVENT_LOOPS
    Это конечно не отменяет того что с epoll на GNU/Linux придётся работать,
    куда деваться (там где select/poll не подойдут).
    
    >В портабельных программах для ожидания завершения процессов, которые не
    >являются прямыми потомками, часто используют pipe, пишущий конец
    >которого остаётся за дочерним процессом
    
    Да, я это так и применяю. Просто через pipe не отловить точно ли он
    упал, был ли убит или ещё чего. Сейчас я временно делаю ещё и третий
    процесс который только и делает waitpid (всё остальное запрещено, грубо
    говоря) и в pipe пишет статус завершения. Wrapper который waitpid
    результат сериализует в pipe. Но в целом можно жить и с: или получаем
    штатно данные из pipe-а, или отлавливаем close, что говорит о том что
    нештатно процесс завершился.
    
    >А от чего хочется защититься, ограничивая сокет в режимах read-only или
    >write-only?
    
    Просто так :-). Но в seccomp уже увидел и на практике проверил что там
    через BPF режим фильтрации можно довольно легко чётко сказать какие
    вызовы на какой файловый дескриптор разрешены.
    
    >Если действительно необходимо ограничить передачу данных в одном
    >направлении и не хочется использовать seccomp bpf, то можно
    >воспользоваться memfd_create(2) и file sealing.
    
    Спасибо за информацию! Ещё на эту тему нашёл: https://lwn.net/Articles/591108/
    Но пока BPF режима seccomp хватает для этого. Сильно углубляться в
    GNU/Linux-specific вещи не хочется совершенно.
    
    комментарий 2:
    From: kmeaw
    Date: 2021-03-19 17:31:23Z
    
    >>Есть epoll.
    
    > Судя по статьям, который ни в какое сравнение с kqueue не идёт
    
    Статьи посмотрел. kqueue и epoll решают одну и ту же задачу похожим
    способом - создают специальную сущность (обладающую состоянием) для
    слежения за объектами, разделяя interest и ready set.
    
    В статьях жалуются по сути на три проблемы:
    
    1. epoll не умеет следить за чем-то.
    
    Эта проблема решается, новые ядра умеют всё больше объектов представлять
    в виде файловых дескрипторов или данных, которые по ним передаются. В
    Linux даже есть signalfd и timerfd. Семантику семафоров можно
    реализовать через eventfd с флагом EFD_SEMAPHORE.
    
    inotify и pidfd защёлкнуть в epoll можно. Даже epoll можно защёлкнуть в
    epoll. То, что пока нельзя завернуть в epoll непосредственно, можно всё
    равно засунуть в общий event loop с помощью eventfd и второго потока,
    который будет ждать события.
    
    Интересно, а может ли kqueue следить за другим инстансом kqueue?
    
    2. epoll_ctl приходится вызывать несколько раз.
    
    Так можно почти про почти любой системный вызов сказать. Решить эту
    проблему до конца можно только сделав интерфейс, который будет
    передавать в ядро программу на каком-то ограниченном языке (bpf?),
    которая только выполняет системные вызовы и не делает больше ничего.
    Такой подход мог бы экономить переключения контекста.
    
    Для наиболее "тяжёлых" операций есть batch-аналоги: векторный
    ввод-вывод, AIO, чтение директории и так далее.
    
    Необходимость в ожидании сразу большого числа объектов обычно в сетевых
    сервисах в главном цикле обработки событий. Изменения interest set в
    таких случаях малы - например, после accept нужно всего один дескриптор
    добавить.
    
    Для epoll мне сложно придумать сценарий, когда количество операций,
    которые нужно выполнить не является константой. Конечно, всегда лучше,
    когда константа равна единице (а ещё лучше - нулю :) ).
    
    > Это конечно не отменяет того что с epoll на GNU/Linux придётся работать,
    > куда деваться (там где select/poll не подойдут).
    
    Говорят, есть странный способ взаимодействия с сетью через интерфейсы
    AIO, но я так никогда не пробовал делаnь:
    https://blog.cloudflare.com/io_submit-the-epoll-alternative-youve-never-heard-about/
    
    3. epoll не умеет какой-то сценарий нотификаций.
    
    Это уже исправлено. Сейчас есть целая куча флажков на любой вкус -
    EPOLLRDHUP, EPOLLET, EPOLLONESHOT, EPOLLWAKEUP и даже EPOLLEXCLUSIVE.
    
    комментарий 3:
    From: Sergey Matveev
    Date: 2021-03-19 18:16:27Z
    
    *** kmeaw [2021-03-19 20:22]:
    >Интересно, а может ли kqueue следить за другим инстансом kqueue?
    
    Бегло судя по man-у не вижу такого.
    
    >2. epoll_ctl приходится вызывать несколько раз.
    >Так можно почти про почти любой системный вызов сказать.
    
    И возникает вопрос как часто эти вызовы делаются. А ведь epoll/kqueue
    предназначены чтобы рулить большим кол-вом объектов/событий -- поэтому
    сразу можно было бы этим озадачиться. Причём libev говорит о том что при
    малом кол-ве событий -- epoll даже медленне будет чем select.
    
    >https://blog.cloudflare.com/io_submit-the-epoll-alternative-youve-never-heard-about/
    
    Помню тоже видел эту статью. Далёк ещё от всей этой темы, но со стороны
    выглядит как "а давайте AIO, предназначенный для файлов, использовать и
    для сетевых вещей", и параллельно "давайте epoll, предназначенный для
    сетевых вещей, использовать и для файлов" :-). Тогда как kqueue выглядит
    как "давайте сделаем просто систему оповещения о событиях", где ещё и
    payload в событиях можно нести сразу же.
    
    Вот весь Linux мне и представляется как нагромождение одного на другое,
    потому что плохо всё продумано :-(. И ничего под одну гребёнку нет и не
    делается. Capsicum хотели в Linux впилить -- забросили. Seccomp впилили,
    а Торвальдс даже выпилить хотел назад. Это из того что последние дни
    успел потрогать.
    
    Наверное вопросы вкусовщины, но люто мне не близок подходы GNU/Linux
    экосистемы в целом.