Что: 793966ed64c5a6884e7f1a3d5491a7be4b3eea5f
Когда: 2021-03-15 22:31:02+03:00
Темы: bsd c
Познакомился с 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 там нет. Буду готовиться к страданиям.
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.
From: Sergey Matveev Date: 2021-03-19 07:24:53Z
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.
From: Sergey Matveev Date: 2021-03-19 18:16:27Z
Сгенерирован: SGBlog 0.34.0