Персональный
сайт
Игоря
Сысоева


 
english
обо мне
 
sysoev.ru
 
nginx
 
mod_accel
mod_realip
mod_deflate
программирование
всякая всячина
windows
freebsd
apache
pppd
unix
web
 
 

accept-фильтры

 

19.06.2002

Два года назад во FreeBSD появились accept-фильтры. Они позволяют не передавать в accept() пришедшее соединение до тех пор, пока не придёт первый пакет с данными (фильтр dataready) или заголовок HTTP-запроса (фильтр httpready). Использование фильтров в Apache (а в нём они поддерживаются, начиная с версии 1.3.14) позволяет уменьшить число процессов. В серверах, использующих select(), poll() или kqueue(), например, в thttpd-2.22, фильтры уменьшают число открытых файлов. И наконец, в обоих случаях accept-фильтры уменьшают число переключений контекста процесса.

Идея accept-фильтра принадлежит одному из основателей Yahoo! Дэвиду Фило (David Filo). В Yahoo! использовался фильтр для запросов HTTP. 15.06.2000 Alfred Perlstein внёс во FreeBSD 5.0-CURRENT код этого фильтра и добавил фильтр для первого пакета с данными. Для включения фильтра нужно было с помощью setsockopt(2) установить опцию SO_DELAYACCEPT или SO_HTTPACCEPT для сокета, который принимал соединения. Но этот вариант просуществовал не больше трёх суток — уже 18.06.2002 он был удалён и 20.06.2000 во FreeBSD 5.0-CURRENT появилась новая, более общая реализация accept-фильтров. Теперь фильтры включались с помощью опции SO_ACCEPTFILTER, вместе с которой передавался указатель на структуру, описывающую фильтр. Для современных фильтров в этой структуре нужно передать только имя фильтра — dataready или httpready. В отличие от первой реализации, каждый фильтр нужно явно указать в конфигурации ядра:

options ACCEPT_FILTER_DATA
options ACCEPT_FILTER_HTTP
или же загрузить как ядерный модуль. Именно это вариант появился 28.07.2000 во FreeBSD 4.1-STABLE. Подробнее об использовании accept-фильтрах можно прочитать в setsockopt(2), accf_data(9) и accf_http(9).

Надо заметить, что первоначальная реализация фильтра httpready была далека от суровой реальности, так как поддерживала только протоколы HTTP/1.x и не поддерживала протокол HTTP/0.9 — если первый пришедший пакет начинался со строки "GET ", то фильтр не передавал соединение в очередь завершённых соединений до тех пор, пока не находил во входном потоке пустую строку. Однако 6.09.2000 во FreeBSD 5.0-CURRENT и 20.09.2000 во FreeBSD 4.1-STABLE (практически перед самым 4.1.1-RELEASE) фильтр httpready был переписан — при получении запроса, непохожего на "^(GET|HEAD) .+ HTTP/1.x", соединение сразу же помещается в очередь завершённых соединений.

Оба фильтра — dataready и httpready не предполагали никаких таймаутов, но при переполнении очереди входящих соединений вызывалась функция sodropablereq(), которая удаляла старые незавершённые соединения или же соединения, завершённые с точки зрения TCP/IP, но незавершённые с точки зрения фильтра.

После того, как во FreeBSD 5.0-CURRENT и 4.4-STABLE появился syncache, с accept-фильтрами стали возникать некоторые проблемы. Первая из них, по-видимому, наблюдалась только с httpready фильтром — после того, как соединение было передано accept()'у, следующий за ним read() ничего не мог прочитать. Эта ошибка была исправлена 21.12.2001 во FreeBSD 5.0-CURRENT и 27.12.2001 во FreeBSD 4.5-PRERELEASE. Недавно разработчики обнаружили, что сервер, использующий accept-фильтры, может быть легко подвержен DoS-атаке. Как уже говорилось, до syncache избытком незавершённых соединений занималась функция sodropablereq(), но в syncache эта функция не используется и в результате очередь легко переполняется соединениями, незавершёнными с точки зрения фильтра. 26.04.2002 ошибка была исправлена во FreeBSD 5.0-CURRENT, а 1.05.2002 — во FreeBSD 4.5-STABLE.

Была идея на основе accept-фильтров реализовать ядерный http-сервер, который бы возвращал в accept() те запросы, которые не мог обработать сам, но она, по-видимому, так и осталась нереализованной.

Надо заметить, что accept-фильтры — не единственное решение отложить accept() до прихода данных. В Linux 2.4.x можно установить опцию TCP_DEFER_ACCEPT, а в Windows NT 3.51 появилась функция AcceptEx().

(C) Игорь Сысоев
http://sysoev.ru