- Автор темы
- Администратор
- Модер.
- #1
FreeBSD 14.5 + pf: часть 4. Шейпинг трафика — dummynet и ALTQ
В предыдущих частях мы защитили сервер от атак и разнесли веб-стек по jail-ам. Осталась ещё одна проблема, о которой вспоминают, только когда она случается: канал не резиновый.
Типичные ситуации:
- пользователь качает с форума большой архив, и у всех остальных страницы открываются по 5 секунд;
- ночной бэкап на удалённый сервер забивает канал, и SSH превращается в слайд-шоу;
- во время флуда невозможно зайти на сервер, чтобы что-то исправить.
Решение — шейпинг: мы сами распределяем полосу между типами трафика и гарантируем, что SSH и веб получат свою долю всегда.
ALTQ или dummynet?
| dummynet | ALTQ | |
|---|---|---|
| Пересборка ядра | Не нужна, это модуль | Нужна (GENERIC без ALTQ) |
| Обновление через freebsd-update | Работает как обычно | Ядро придётся пересобирать руками |
| Поддержка драйверами | Любые сетевые карты | Только драйверы из man altq |
| Шейпинг входящего трафика | Есть | Только исходящий |
| Ограничение «на каждый IP» | Есть (маски) | Нет |
| Современные алгоритмы (FQ-CoDel) | Есть | Частично |
Начиная с FreeBSD 14, pf умеет отправлять пакеты в dummynet напрямую (ключевые слова
dnpipe и dnqueue), а для управления каналами есть утилита dnctl. Ставить ipfw не нужно. Для нового сервера выбирайте dummynet.1. Измеряем реальный канал
Шейпер работает, только если очередь образуется у нас, а не у провайдера. Поэтому лимит ставится на 90–95% от реальной скорости.
Тариф «до 100 Мбит» на практике может давать 80 Мбит. Измерьте:
Код:
pkg install -y iperf3
# на сервере
iperf3 -s
# на другой машине с хорошим каналом
iperf3 -c IP_СЕРВЕРА # загрузка на сервер (входящий для сервера)
iperf3 -c IP_СЕРВЕРА -R # скачивание с сервера (исходящий)
Дальше в примерах канал 100 Мбит/с, лимит 95 Мбит/с. Подставьте свои цифры.
2. Схема распределения полосы
Используем планировщик WF2Q+ с весами. Вес — это гарантированная доля при перегрузке. Если канал свободен, любой класс может занять его целиком.
| Очередь | Трафик | Вес | Гарантия при забитом канале |
|---|---|---|---|
| 11 / 21 | SSH, ICMP | 50 | ~50%, но реально берёт крохи — SSH почти ничего не потребляет |
| 12 / 22 | Веб (80/443) | 40 | ~40% |
| 13 / 23 | Всё исходящее от сервера: бэкапы, обновления, почта | 10 | ~10% |
Очереди 1x идут в исходящий канал (pipe 1), 2x — во входящий (pipe 2).
Главный приём против «тяжёлых» загрузок — маска по IP клиента в веб-очереди. dummynet создаёт отдельную подочередь для каждого посетителя и делит полосу между ними поровну. Один человек, качающий архив на 2 ГБ, получает свою долю, а остальные открывают форум без задержек.
3. Скрипт настройки каналов
Создайте
/usr/local/etc/dnshaper.sh:
Код:
#!/bin/sh
# Шейпер dummynet для pf
UP="95Mbit/s" # исходящий (сервер -> клиенты)
DOWN="95Mbit/s" # входящий (клиенты -> сервер)
dnctl -q queue flush
dnctl -q sched flush
dnctl -q pipe flush
### Исходящий канал
dnctl pipe 1 config bw $UP
dnctl sched 1 config pipe 1 type wf2q+
# 11: интерактив (SSH, ICMP)
dnctl queue 11 config pipe 1 weight 50
# 12: веб, с честным делением между клиентами
dnctl queue 12 config pipe 1 weight 40 mask dst-ip 0xffffffff dst-ip6 /64
# 13: фоновый трафик сервера
dnctl queue 13 config pipe 1 weight 10
### Входящий канал
dnctl pipe 2 config bw $DOWN
dnctl sched 2 config pipe 2 type wf2q+
dnctl queue 21 config pipe 2 weight 50
dnctl queue 22 config pipe 2 weight 40 mask src-ip 0xffffffff src-ip6 /64
dnctl queue 23 config pipe 2 weight 10
Код:
chmod 700 /usr/local/etc/dnshaper.sh
4. Автозапуск до pf
Каналы должны существовать до загрузки правил pf. Для этого делаем маленький rc-скрипт
/usr/local/etc/rc.d/dnshaper:
Код:
#!/bin/sh
#
# PROVIDE: dnshaper
# REQUIRE: FILESYSTEMS kld
# BEFORE: pf
# KEYWORD: nojail
. /etc/rc.subr
name="dnshaper"
rcvar="dnshaper_enable"
start_cmd="dnshaper_start"
stop_cmd="dnshaper_stop"
dnshaper_start()
{
kldload -n dummynet
/bin/sh /usr/local/etc/dnshaper.sh
echo "dummynet pipes configured."
}
dnshaper_stop()
{
dnctl -q queue flush
dnctl -q sched flush
dnctl -q pipe flush
}
load_rc_config $name
: ${dnshaper_enable:="NO"}
run_rc_command "$1"
Код:
chmod 755 /usr/local/etc/rc.d/dnshaper
sysrc dnshaper_enable="YES"
service dnshaper start
dnctl pipe show
5. Правила pf
Синтаксис очень простой:
Код:
dnqueue (очередь_по_направлению_правила, очередь_для_ответов)
Для правила
pass in первое число относится к входящим пакетам, второе — к ответам сервера, то есть к исходящим. Благодаря keep state все пакеты соединения автоматически попадают в нужные очереди.Добавьте макросы в начало
/etc/pf.conf:
Код:
### Очереди dummynet (in, out)
q_inter_in = "21"
q_inter_out = "11"
q_web_in = "22"
q_web_out = "12"
q_bulk_in = "23"
q_bulk_out = "13"
И допишите
dnqueue к существующим правилам из прошлых частей:
Код:
### Исходящий трафик сервера и jail-ов — фоновый класс
pass out quick on $ext_if keep state \
dnqueue ($q_bulk_out, $q_bulk_in)
### SSH — наивысший приоритет
pass in on $ext_if inet proto tcp from any to ($ext_if) port $ssh_port \
flags S/SA keep state \
(max-src-conn 10, max-src-conn-rate 5/30, \
overload <bruteforce> flush global) \
dnqueue ($q_inter_in, $q_inter_out)
### Веб → jail nginx (из части 3)
pass in on $ext_if inet proto tcp from any to $nginx_ip port $web_ports \
flags S/SA keep state \
(max-src-conn 200, max-src-conn-rate 100/5, \
overload <abusers> flush global) \
dnqueue ($q_web_in, $q_web_out)
pass in on $ext_if inet proto udp from any to $nginx_ip port 443 keep state \
dnqueue ($q_web_in, $q_web_out)
### ICMP — в интерактивный класс, чтобы ping показывал реальную картину
pass in on $ext_if inet proto icmp all icmp-type $icmp_types keep state \
dnqueue ($q_inter_in, $q_inter_out)
pass in on $ext_if inet6 proto ipv6-icmp all keep state \
dnqueue ($q_inter_in, $q_inter_out)
Если веб у вас без jail-ов (конфиг из первой части), просто замените$nginx_ipна($ext_if).
Межджейловый трафик на lo0 (nginx → php → db) не шейпим: он не выходит в физический канал.
Применяем:
Код:
pfctl -nf /etc/pf.conf
nohup sh -c 'sleep 300 && pfctl -d' >/dev/null 2>&1 &
pfctl -f /etc/pf.conf
pkill -f "sleep 300"
6. Тюнинг dummynet
В
/etc/sysctl.conf:
Код:
# Пропускать пакеты без задержки, если канал не загружен
net.inet.ip.dummynet.io_fast=1
# Больше динамических подочередей (по одной на каждого клиента)
net.inet.ip.dummynet.hash_size=1024
net.inet.ip.dummynet.expire=1
Код:
service sysctl restart
7. Вариант Б: FQ-CoDel — «включил и забыл»
Если не хочется возиться с весами, есть современный алгоритм FQ-CoDel. Он сам делит канал поровну между всеми соединениями и активно борется с задержками (bufferbloat). SSH и веб-страницы автоматически остаются отзывчивыми даже при тяжёлой загрузке.
Замените содержимое
dnshaper.sh на:
Код:
#!/bin/sh
UP="95Mbit/s"
DOWN="95Mbit/s"
dnctl -q queue flush
dnctl -q sched flush
dnctl -q pipe flush
dnctl pipe 1 config bw $UP
dnctl sched 1 config pipe 1 type fq_codel
dnctl queue 1 config sched 1
dnctl pipe 2 config bw $DOWN
dnctl sched 2 config pipe 2 type fq_codel
dnctl queue 2 config sched 2
А во всех правилах pf используйте одну пару очередей:
Код:
dnqueue (2, 1) # для правил pass in
dnqueue (1, 2) # для правила pass out
| Когда что выбирать | |
|---|---|
| WF2Q+ с весами | Нужно жёстко гарантировать долю конкретным сервисам (например, бэкап никогда не больше 10%) |
| FQ-CoDel | Нужно, чтобы «просто всё было отзывчивым» без ручной настройки |
8. Жёсткий лимит на скачивание вложений в nginx
dummynet делит канал честно, но не знает, что такое «вложение форума». Если нужно жёстко ограничить скорость скачивания файлов, добавьте лимит в nginx. Вложения XenForo отдаются через PHP, поэтому используем переменную.
В блок
http { }:
Код:
map $request_uri $att_rate {
~^/attachments/ 2m; # 2 МБ/с на одно скачивание
default 0; # 0 = без ограничения
}
В
location ~ \.php$ { }:
Код:
limit_rate_after 5m; # первые 5 МБ на полной скорости
limit_rate $att_rate;
Картинки в постах (до 5 МБ) открываются мгновенно, а большие архивы качаются не быстрее 2 МБ/с на поток.
9. Мониторинг и проверка
Код:
dnctl pipe show # каналы и их загрузка
dnctl sched show # планировщики
dnctl queue show # очереди: пакеты, байты, потери (Drp)
pfctl -vvs rules | grep -A2 dnqueue # какие правила срабатывают
Как проверить, что работает:
- С внешней машины запустите
iperf3 -c IP_СЕРВЕРА -R -t 60 -P 8, чтобы забить исходящий канал. - Параллельно подключитесь по SSH и попечатайте. Без шейпера будут заметные задержки, с ним — нет.
- Параллельно сделайте
pingна сервер. Время ответа не должно вырасти в разы. - В
dnctl queue showпотери (Drp) должны идти в основном в очереди 13, а не в 11.
Важно: iperf3 из примера попадёт в фоновый класс, если у вас нет отдельного pass-правила для его порта 5201. Для честного теста можно временно разрешить его правилом сdnqueue ($q_web_in, $q_web_out).
10. ALTQ — для тех, у кого уже своё ядро
Если вы всё же выбрали ALTQ, в конфиг ядра нужно добавить:
Код:
options ALTQ
options ALTQ_CBQ
options ALTQ_RED
options ALTQ_RIO
options ALTQ_HFSC
options ALTQ_PRIQ
options ALTQ_FAIRQ
options ALTQ_CODEL
options ALTQ_NOPCC # обязательно для SMP-систем
Затем пересобрать ядро (
make buildkernel installkernel KERNCONF=MYKERNEL в /usr/src). Перед этим проверьте в man altq, поддерживает ли ваш сетевой драйвер ALTQ.Пример с простыми приоритетами (PRIQ):
Код:
altq on $ext_if priq bandwidth 95Mb queue { q_ssh, q_web, q_ack, q_def }
queue q_ssh priority 7
queue q_ack priority 6
queue q_web priority 4
queue q_def priority 1 priq(default)
pass in on $ext_if proto tcp to ($ext_if) port $ssh_port keep state \
queue (q_ssh, q_ack)
pass in on $ext_if proto tcp to ($ext_if) port $web_ports keep state \
queue (q_web, q_ack)
Обратите внимание: у ALTQ второй параметр в
queue (a, b) означает очередь для пустых ACK и пакетов с low-delay, а не для обратного направления, как у dnqueue. Кроме того, ALTQ шейпит только исходящий трафик интерфейса.Частые ошибки
- Лимит равен скорости тарифа или выше. Очередь образуется у провайдера, и шейпер ничего не делает. Ставьте 90–95% от измеренного.
- Каналы dnctl не созданы до загрузки pf. Пакеты, отправленные в несуществующую очередь, теряются. rc-скрипт с
BEFORE: pfрешает эту проблему. - dnqueue добавили только в одно правило. Остальной трафик идёт мимо шейпера и съедает канал. Классифицируйте всё, особенно
pass out. - Перепутали направления в dnqueue. Для
pass in—(входящая, исходящая), дляpass out— наоборот. - Ожидают чуда от шейпинга входящего трафика. Входящий трафик уже прошёл через канал, когда добрался до сервера. Шейпер лишь замедляет отправителя через TCP. От объёмного DDoS это не спасёт, тут нужен провайдер или CDN.
Итог
- SSH и ping отзывчивы при любой загрузке канала.
- Посетители форума делят полосу честно, один «качальщик» не тормозит всех.
- Бэкапы и обновления не забивают канал в рабочее время.
- Скорость скачивания больших вложений ограничена в nginx.
- Ядро стандартное, freebsd-update работает как прежде.
В следующей части можно разобрать WireGuard + pf: полностью закрываем SSH снаружи, и админ-доступ остаётся только через VPN. Пишите в теме, если интересно!
