Иногда проблема в проде выглядит как «плавающие таймауты», «рандомные 502», «странные ретраи», а в лаборатории всё идеально. В таких случаях выручает network emulation: вы искусственно ухудшаете канал и проверяете, как ведёт себя приложение, балансировщик, TCP-стек и логика ретраев клиента.
В Linux для этого используется tc (traffic control) и qdisc netem. Он умеет эмулировать delay, jitter, packet loss, reorder и duplicate. А для контроля очередей и борьбы с bufferbloat полезно комбинировать его с fq_codel (или fq/cake в отдельных сценариях).
Дальше — практичный набор приёмов: как быстро включить tc netem на интерфейсе, как ограничить влияние только нужным портом/хостом, как проверить эффект и как безопасно откатить настройки.
Что такое qdisc и где живёт netem
tc управляет дисциплинами очереди (qdisc) на сетевом интерфейсе. Есть корневая qdisc (root) и дочерние. Фильтры (классификаторы) могут направлять часть трафика в отдельную «ветку» с нужными правилами.
netem — это qdisc, которая искусственно меняет судьбу пакетов: задерживает, теряет, переставляет местами или дублирует. Обычно её ставят либо root-qdisc для всего интерфейса (быстро, но грубо), либо как дочернюю — чтобы «портить» только выбранный трафик.
Важно: применяя
tcк интерфейсу, вы влияете на трафик, который выходит через этот интерфейс. Входящий (ingress) трафик нельзя просто «задержать» без дополнительных приёмов (например, IFB). Проще всего эмулировать условия на стороне клиента или на промежуточном хосте.
Подготовка: что проверить перед стартом
1) Убедитесь, что qdisc уже не используется
Посмотрите текущие дисциплины очередей и список интерфейсов:
tc qdisc show
ip -br link
Часто по умолчанию вы увидите fq_codel или fq (зависит от дистрибутива/ядра). Если вы поставите netem как root, вы замените текущую root-qdisc для интерфейса.
2) Выберите интерфейс
Например, eth0, ens3, enp1s0. В примерах ниже используется eth0 — подставьте свой.
3) План отката (обязательно)
Самый безопасный откат — удалить root-qdisc на интерфейсе (ядро вернёт дефолтную):
tc qdisc del dev eth0 root
Держите эту команду под рукой в отдельной SSH-сессии. Если вы тестируете на удалённом сервере, начинайте с мягких значений (например, 20–50 мс), чтобы не «отрезать» себе доступ.
Если тесты идут на инфраструктуре с публичными сервисами, разумно проводить эксперименты на отдельной машине (или отдельном сетевом namespace) — на VDS это удобно: быстро подняли стенд, испортили сеть, откатили, удалили.

Быстрый старт: задержка и jitter
Добавим фиксированную задержку 100 мс на исходящий трафик интерфейса:
tc qdisc add dev eth0 root netem delay 100ms
Проверим счётчики и RTT:
tc -s qdisc show dev eth0
ping -c 5 1.1.1.1
Теперь добавим jitter: 100 мс базовой задержки и ±20 мс разброса:
tc qdisc change dev eth0 root netem delay 100ms 20ms
Для более реалистичной модели часто используют нормальное распределение:
tc qdisc change dev eth0 root netem delay 100ms 20ms distribution normal
Если нужно воспроизвести «мобильность», одной вариативности задержки может быть мало: добавьте небольшой loss или reorder, иначе TCP может вести себя слишком «идеально».
Эмуляция packet loss: постоянная и коррелированная
Packet loss — основа для тестов ретраев, таймаутов и поведения протоколов поверх TCP/QUIC.
Постоянная потеря
Например, 1% потерь:
tc qdisc change dev eth0 root netem loss 1%
Комбинация delay + loss:
tc qdisc change dev eth0 root netem delay 80ms 15ms distribution normal loss 0.5%
Коррелированная потеря (burst loss)
Реальные каналы часто теряют пакеты пачками. В netem можно задать корреляцию (условно: вероятность того, что следующий пакет тоже потеряется):
tc qdisc change dev eth0 root netem loss 1% 25%
Здесь 1% — базовая вероятность, 25% — корреляция. Чем выше корреляция, тем более «кучными» становятся потери.
Reorder и duplicate: редкие, но показательные тесты
Reorder и duplicate полезны для тестов приложений и библиотек, которые неверно предполагают «идеальный» порядок/доставку на уровне выше TCP. Особенно заметно это в UDP-протоколах, телеметрии, туннелях и при работе через некоторые middlebox.
Reorder (переупорядочивание)
Пример: 0.2% пакетов будут приходить «не на месте». Обычно reorder требует наличия задержки, иначе переставлять нечего:
tc qdisc change dev eth0 root netem delay 50ms 10ms reorder 0.2% 50%
Где 0.2% — доля пакетов, которые будут переупорядочены, 50% — корреляция эффекта.
Duplicate (дублирование пакетов)
Например, 0.1% пакетов будут продублированы:
tc qdisc change dev eth0 root netem duplicate 0.1%
В сочетании с задержкой и небольшим loss можно получить «живую» картину для тестов идемпотентности запросов и дедупликации на уровне приложения (там, где это применимо).
Как комбинировать netem и fq_codel (и зачем)
Частая ошибка — повесить только netem и удивляться странным задержкам под нагрузкой. netem сам по себе не всегда решает проблему очередей; при высокой скорости отправки может появляться bufferbloat, который «смазывает» ваш сценарий теста.
Практичный подход: разделять ответственность. Netem задаёт «качество канала», а fq_codel — «как ведёт себя очередь под нагрузкой». В зависимости от цели теста вы либо ставите netem как root (быстро), либо строите дерево qdisc и направляете в netem только нужный поток.
Если хотите тестировать узкий канал (например, 20–50 Мбит/с), одного netem недостаточно: нужна дисциплина, ограничивающая скорость (например, tbf или классы в htb). Тогда сценарий будет ближе к реальности, особенно для очередей и latency under load.
Точечное ухудшение: только один порт/адрес, а не весь интерфейс
Глобально «портить» интерфейс удобно, но часто опасно: вы заденете SSH, мониторинг, репликации. Надёжнее сделать классификацию: выделить нужный трафик фильтром и направить его в отдельную qdisc с netem.
Пример: эмулируем плохую сеть только для исходящих соединений к IP 203.0.113.10 на TCP порт 443. Для классификации используем prio и u32:
tc qdisc add dev eth0 root handle 1: prio
tc qdisc add dev eth0 parent 1:1 handle 10: fq_codel
tc qdisc add dev eth0 parent 1:2 handle 20: netem delay 120ms 30ms loss 1%
tc filter add dev eth0 protocol ip parent 1: prio 2 u32 match ip dst 203.0.113.10/32 match ip dport 443 0xffff flowid 1:2
Что здесь важно:
prioсоздаёт несколько band. Band 1 (1:1) — «нормальный» сfq_codel, band 2 (1:2) — «плохой» сnetem.- Фильтр отправляет только совпавший трафик в band 2.
- SSH (например, порт 22) не пострадает, если вы не добавите под него отдельный фильтр.
Проверить, что фильтр матчится, можно по счётчикам:
tc -s qdisc show dev eth0
tc -s filter show dev eth0 parent 1:
Если вы делаете такие эксперименты на боевых серверах, сначала приведите в порядок базовую сетевую гигиену (SSH, фаервол, ограничение доступа). Подробный чеклист есть в статье про защиту VDS: SSH и firewall.

Проверка результата: как понять, что эмуляция реально работает
1) ping для RTT и потерь
ping -c 20 203.0.113.10
Даже простой ping даст ощущение изменения RTT и джиттера.
2) Нагрузка (очередь проявляется на трафике)
Если вы тестируете поведение под нагрузкой, одного ping мало: очередь проявляется на потоке. Запустите генератор трафика и параллельно измеряйте задержки. Для диагностики важно, чтобы трафик действительно шёл через тот интерфейс и попадал в нужную qdisc.
3) Счётчики tc
tc -s показывает количество пакетов/байт, дропы и состояние очередей:
tc -s qdisc show dev eth0
Если вы включили loss, но дропов не видно — вероятно, трафик не попадает в нужную qdisc (ошибка в фильтре) или нет нагрузки.
Типовые профили для тестов (готовые рецепты)
Несколько «профилей», которые удобно применять в разработке и при регрессе. Подставляйте интерфейс и аккуратно начинайте с меньших значений.
Профиль 1: умеренная WAN-задержка
tc qdisc replace dev eth0 root netem delay 60ms 10ms distribution normal
Профиль 2: мобильная сеть или нестабильный Wi‑Fi
tc qdisc replace dev eth0 root netem delay 120ms 40ms distribution normal loss 1% 25% duplicate 0.05%
Профиль 3: случайные таймауты из-за потерь
tc qdisc replace dev eth0 root netem loss 3% 40%
Профиль 4: переупорядочивание
tc qdisc replace dev eth0 root netem delay 40ms 10ms reorder 0.5% 50%
Откат и уборка: как вернуть всё как было
Самый простой способ — удалить root-qdisc:
tc qdisc del dev eth0 root
Если вы строили дерево (prio + дочерние qdisc + фильтры), удаление root обычно убирает всё дерево целиком.
Полезная привычка: перед экспериментом сохранить текущее состояние:
tc qdisc show dev eth0
tc filter show dev eth0
А после — снова проверить, что всё чисто.
Частые ошибки и практические советы
- Портите весь интерфейс и теряете SSH. Делайте точечные фильтры или тестируйте на отдельной машине/namespace.
- Пытаетесь эмулировать ingress обычным netem. Для входящего трафика обычно используют IFB и перенаправление ingress в egress IFB.
- Нет нагрузки — нет эффекта. Для проверки очередей нужен поток; измеряйте задержки параллельно.
- Слишком большие значения jitter/loss. Начинайте с малого: 20–50 мс и 0.1–0.5% потерь, затем усиливайте.
- Забыли про очереди. Для реалистичных тестов под нагрузкой комбинируйте
netemсfq_codelи, при необходимости, с шейпингом скорости.
Мини-чеклист перед тем, как использовать tc netem в стенде
- Определите, где эмулировать (клиент, сервер, промежуточный хоп) и какой именно трафик ухудшать.
- Сначала примените настройки на «неопасном» интерфейсе или через фильтры.
- Замерьте базовую линию (RTT, пропускная способность, ретраи приложения).
- Включите профиль (delay/jitter/loss/reorder/duplicate) и зафиксируйте результат.
- Откатите qdisc и убедитесь, что состояние вернулось.
Если вы регулярно гоняете такие тесты, оформляйте команды в понятные профили и применяйте их скриптом с авто-откатом по таймеру — так вы снижаете риск человеческой ошибки. А для сложных сценариев (классы, ingress через IFB, эмуляция на нескольких интерфейсах) удобнее держать конфигурацию как код рядом с тест-планом.


