FreeBSD 14.5 + pf: часть 4. Шейпинг трафика — dummynet и ALTQ

Itnull

Admin
Регистрация
22.05.13
Сообщения
29.228
Реакции
13.526
Веб-сайт
itnull.me

FreeBSD 14.5 + pf: часть 4. Шейпинг трафика — dummynet и ALTQ​


В предыдущих частях мы защитили сервер от атак и разнесли веб-стек по jail-ам. Осталась ещё одна проблема, о которой вспоминают, только когда она случается: канал не резиновый.

Типичные ситуации:
  • пользователь качает с форума большой архив, и у всех остальных страницы открываются по 5 секунд;
  • ночной бэкап на удалённый сервер забивает канал, и SSH превращается в слайд-шоу;
  • во время флуда невозможно зайти на сервер, чтобы что-то исправить.

Решение — шейпинг: мы сами распределяем полосу между типами трафика и гарантируем, что SSH и веб получат свою долю всегда.

ALTQ или dummynet?​


dummynetALTQ
Пересборка ядраНе нужна, это модульНужна (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 / 21SSH, ICMP50~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   # какие правила срабатывают

Как проверить, что работает:

  1. С внешней машины запустите iperf3 -c IP_СЕРВЕРА -R -t 60 -P 8, чтобы забить исходящий канал.
  2. Параллельно подключитесь по SSH и попечатайте. Без шейпера будут заметные задержки, с ним — нет.
  3. Параллельно сделайте ping на сервер. Время ответа не должно вырасти в разы.
  4. В 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 шейпит только исходящий трафик интерфейса.

Частые ошибки​


  1. Лимит равен скорости тарифа или выше. Очередь образуется у провайдера, и шейпер ничего не делает. Ставьте 90–95% от измеренного.
  2. Каналы dnctl не созданы до загрузки pf. Пакеты, отправленные в несуществующую очередь, теряются. rc-скрипт с BEFORE: pf решает эту проблему.
  3. dnqueue добавили только в одно правило. Остальной трафик идёт мимо шейпера и съедает канал. Классифицируйте всё, особенно pass out.
  4. Перепутали направления в dnqueue. Для pass in — (входящая, исходящая), для pass out — наоборот.
  5. Ожидают чуда от шейпинга входящего трафика. Входящий трафик уже прошёл через канал, когда добрался до сервера. Шейпер лишь замедляет отправителя через TCP. От объёмного DDoS это не спасёт, тут нужен провайдер или CDN.

Итог​


  • SSH и ping отзывчивы при любой загрузке канала.
  • Посетители форума делят полосу честно, один «качальщик» не тормозит всех.
  • Бэкапы и обновления не забивают канал в рабочее время.
  • Скорость скачивания больших вложений ограничена в nginx.
  • Ядро стандартное, freebsd-update работает как прежде.

В следующей части можно разобрать WireGuard + pf: полностью закрываем SSH снаружи, и админ-доступ остаётся только через VPN. Пишите в теме, если интересно!
 

Создайте аккаунт или войдите в систему, чтобы комментировать

Вы должны быть зарегистрированным, чтобы оставить комментарий

Создать аккаунт

Создайте аккаунт в нашем сообществе.

Войти

У вас уже есть аккаунт? Войдите здесь.

Назад
Сверху Снизу