TL;DR: После миграции отказоустойчивого DHCP-сервера из «брони» QinQ-туннеля в «плоский» VLAN ответы от бэкап-ноды начали бесследно исчезать. Пакет физически покидал хост, но до адресата не доходил, при этом коммутаторы клялись, что их счетчики дропов равны нулю.
Вооружившись tcpdump, тотальным port mirroring и дзен-терпением, мы раскрыли заговор: dhcp snooping молча уничтожал пакеты на уровне аппаратного ASIC. Самое пикантное: стек M-LAG с идентичной прошивкой вел себя радикально иначе, чем одиночный коммутатор в той же сети.
Добро пожаловать в мир сетевой магии, где порт 67 (six-seven) — это не просто абсурдный вирусный мем, а реальная причина, по которой инфраструктура вдруг решила сыграть в прятки.
Привет! Меня зовут Иванов Владимир, я системный администратор отдела выделенных серверов в Selectel. Статья будет полезна тем, кто сталкивался с ситуацией, когда пакет «в провод уходит, но не приходит», а сетевое оборудование клянется, что ничего не дропает.
Инфраструктура и архитектура решения
Представим, что в регионах Санкт-Петербурга и Москвы сервис автоматической установки ОС использует виртуальные машины, развернутые в режиме Multi-AZ (в разных зонах доступности) в рамках внутреннего служебного облака. Также работает высокодоступный DHCP-сервис в нескольких экземплярах и с общей БД. Развернут он в контейнерах через Nomad. Архитектура классическая для HA:
- мастер-нода (192.168.0.2) — DHCP-сервер в зоне А;
- две бэкап‑ноды (192.168.0.3 и 192.168.0.4) — резерные DHCP‑серверы в зонах B и C.
Под капотом все устроено для максимальной отказоустойчивости. Настроена связка из трех хостов (192.168.0.[2-4]) с Keepalived. Общий VIP 192.168.0.1 висит на активном мастере и автоматически переносится на другой хост, если тот падает или по иной причине не может обрабатывать запросы.
Одновременно с VIP‑адресом переезжает и контейнер с DHCP-балансировщиком, который слушает VIP-адрес на порту 67. На него также указывает опция boot-helper (dhcp-relay) на клиентских коммутаторах доступа.
Балансировщик умеет работать в нескольких режимах.
- Round Robin (RR) — входящий Discover от клиента отправляется во все доступные DHCP‑серверы по очереди, а остальная цепочка DORA (Offer, Request, Ack) работает с тем из них, который ответил быстрее.
- XID — все цепочка DORA (Discover, Offer, Reques, Ack) обрабатывается одним конкретным DHCP‑сервером. Его выбор псевдослучайный. В работе мы используем именно этот режим.
Трафик между виртуальными машинами внутри Multi-AZ завернут в QinQ. Это важный нюанс, но всему свое время. Такая схема работает уже больше года, мы с ней жили и бед не знали.
Удаленные регионы, «железная» сеть
Была поставлена задача, реализовать подобную отказоустойчивую схему на удаленной площадке — в Новосибирске. Архитектурно решение, описанное выше, показалось нам универсальным, а поддержка legacy потребует дополнительных ресурсов.
Собственно, мы приступили к реализации, за одним исключением — вместо Multi-AZ служебного облака в регионе будут использоваться собственные выделенные серверы, так как сами управляющие службы в клиентском облаке не размещаются.
С точки зрения архитектуры все остается по‑старому, за исключением того, что вместо трех нод получаются две — естественно, в разных стойках, подключенные к отдельным коммутаторам, с двумя подведенными лучами электропитания.
Физическая сеть построена на коммутаторах определенного производителя, с одинаковыми версиями прошивки. Хосты подключены в режиме агрегации (BAGG), аплинки идут в ядро. Для отказоустойчивости используется M-LAG: пара коммутаторов в стеке (sw01-01 и sw01-02) работает как единое логическое устройство для первого хоста. Второй включен в одиночный коммутатор (sw03), соединенный с ними через агрегирующий роутер. Весь DHCP-трафик живет во VLAN 67. (Да, схема подключения немного различается, но «так исторически сложилось» из‑за особенностей развития региона).
Спойлер: как отмечалось выше, сервис стабильно работал в виртуальных сетях на базе Open vSwitch с инкапсуляцией в QinQ (802.1ad).
Почему это важно? Потому что в QinQ трафик «запечатан» во внешний тег. Коммутаторы в таком случае просто не заглядывают внутрь, не видят DHCP-заголовков и передаваемых опций, а просто крутят L2-кадры.
Загадочные потери пакетов начались ровно в тот момент, когда мы перенесли сервис в «плоский» VLAN 67, дав коммутаторам возможность инспектировать трафик. Но про эти особенности мы вспомнили не сразу, а только спустя где-то пару дней отладки, задав себе вопрос, почему раньше все работало.
Инфраструктура в регионе подготовлена: пакет есть, ответа нет
Миграцию legacy‑сервера в первом регионе проводили «на живую», без перерыва обслуживания и ожидаемого даунтайма:
- собрали первую мастер ноду;
- разместили на нем балансировщик и единственный DHCP‑сервер;
- провели первый тест, получили положительный результат;
- пересобрали legacy‑ноду в бэкап‑ноду;
- подняли второй DHCP‑сервер;
- хотели закрыть задачу и передать сервис в эксплуатацию, как вдруг…
Эксплуатация заметила, что некоторая часть клиентов перестали получать IP-адреса. Проблема оказалась плавающая и видимой закономерности не наблюдалось.
Спойлер: причина — псевдослучайное распределение XID у балансировщика.

Для контекста:
- мастер — 192.168.0.2;
- бэкап — 192.168.0.3;
- VIP — 192.168.0.1;
- dhcp-master (контейнер с DHCP, который всегда запускается на мастер-ноде рядом с балансировщиком и слушает порт 6768) — Docker-CNI 172.16.0.2:6768;
- dhcp-slave (контейнер с DHCP, который крутится на бэкап‑ноде) — Docker-CNI 172.16.0.3:6768.
Мы посмотрели на обмен между мастером и бекапом и увидели странную картину.
- Если поменять политику балансировки с XID на RR, и запросы отправляются всем узлам по очереди, то проблема исчезает.
- Балансировщик от клиента пересылает
DISCOVERна dhcp-slave на порт 6768. - dhcp-slave формирует
DHCP OFFERи отправляет его обратно мастеру на порт 67. - Мастер этот
OFFERникогда не получает! - Если запрос приходит в dhcp-master, то вся цепочка DORA проскакивает на ура.
Первые мысли:
- Firewall — но в логах пусто…
iptables— чисты (кроме стандартных правил CNI);- тест через
socat: UDP на порт 5000 между хостами летает идеально, на 67‑ом — тишина.
# Проходит:
echo "hello" | socat - UDP4-DATAGRAM:192.168.0.1:5000
# Не проходит:
echo "hello" | socat - UDP4-DATAGRAM:192.168.0.1:67
А главное: tcpdump на физическом интерфейсе бэкап-ноды четко показывает, что пакет 192.168.0.3.6768 > 192.168.0.1.67 физически покидает сетевую карту.
И тут мы вышли на сетевых инженеров. Мол, где-то слышали про DHCP Snooping и про то, что его иногда нужно настраивать…
В общем, хосты и контейнеры работают идеально. Пакет в кабель уходит. Проблема — в сети.
Первичная сетевая диагностика
Сетевые инженеры подтвердили: обмен идет чисто на L2 внутри VLAN 67, роутер тут ни при чем. Возможные блокировки uRPF и L3 ACL исключаем, так как OFFER возвращается юникастом, именно поэтому собрать пакеты фильтрами на роутере не получалось. На самом же коммутаторе, снять дамп возможности нет, он в такое не умеет…
Оставался L2. Но где именно теряется пакет? Смотрим счетчики dhcp-drop и настройку DHCP Snooping на всех BAGG-портах и аплинках:
dhcp snooping trust
Счетчик ошибок везде нулевой:
<sw01-01>display dhcp snooping packet statistics
DHCP packets received : 63109034
DHCP packets sent : 77229890
Invalid DHCP packets dropped : 0
<sw01-02>display dhcp snooping packet statistics
DHCP packets received : 63102686
DHCP packets sent : 42113082
Invalid DHCP packets dropped : 0
<sw03>display dhcp snooping packet statistics
DHCP packets received : 8525204
DHCP packets sent : 1963123
Invalid DHCP packets dropped : 0
При этом на всех коммутаторах:
dhcp enable
dhcp relay mac-forward enable
dhcp snooping enable
Также для всех активных VLAN, кроме нашего:
dhcp snooping enable vlan XXX YYY
Счетчики молчат, а пакеты OFFER от dhcp-slave продолжают теряться…
Тут любой, кто разбирается в сетях, начнет сомневаться: точно ли пакет уходит в провод, действительно ли DHCP его отправляет в корректном виде? Ну, и сам‑то я не ламер?
Где-то в этот момент, я рассказывал жене, что делал на работе. Дочь невольно подслушала и выхватив «67» в контексте UDP, крикнула что-то про six-seven, после чего отыскала вот эту картинку. Я себя, примерно, так и чувствовал.

Давайте обманем коммутатор
Наш DHCP‑сервер умный, и мы можем управлять его поведением: а именно изменить хост и (или) порт, которому он будет отправлять OFFER на прилетевший Discover.
В этом месте мы передаем привет коммутаторам, в чью логику заложено правило: отбрасывать DHCP-ответы, если те отправлены:
- не с того адреса, который указан как DHCP-relay;
- не с уважаемого DHCP‑порта (нужен 67, а DHCP-сервер отвечает с 6768).
Ранее мы просто с отправляли OFFER с DHCP-сервиса на DHCP-relay, игнорируя балансировщик. Оказалось, что мы отправляем OFFER без уважения к коммутатору…
Нами была собрана версия DHCP-сервиса, которая всегда отправляла OFFER обратно к балансировщику на порт 6767:
tcpdump 172.16.0.3.6768 > 192.168.0.1.6767
На ноде с балансировщиком применили правило в таблице PREROUTING:
iptables -t nat -A PREROUTING -s 172.16.0.3 -d 192.168.0.1 -p udp --dport 6767 -j DNAT --to-destination 192.168.0.1:67
И наш DHCP заработал! Да так, как мы ожидали с самого начала, точно как и задумывали и хотели — правда, с костылем из неудобных правил. И одновременно — железобетонное доказательство, что проблема где-то в сети. Ведь эксперимент с socat можно подвергать сомнению — в нем нетполноценной DORA, а только сам факт того что соединение устанавливается.
Подведем промежуточный итог. Если DHCP‑сервер находится на бэкап‑ноде:
- DORA не проходит на стандартном порту 67;
- DORA проходит на нестандартном порту.
При смене местами мастера и бекапа ситуация в обоих сценария повторяется. Железо хоста, докер, фаервол, настройка сети на хостах — не причем.
Обходное решение:
- применять политику балансировки RR;
- оставить «костыль» с
iptables PREROUTINGи форком DHCP‑сервера для регионов.
В общем, оба варианта неидеальны, хотелось разобраться в причинах и прийти к целевой схеме, чтобы не увеличивать энтропию…
Тяжелая артиллерия: Port Mirroring (SPAN)
Я писал ранее, что с самих коммутаторов мы не можем снимать дамп из-за ограниченности их операционной системы. Работа с удаленным регионом несет в себе риски «долгой дороги» — в нашем случае дорогостоящая помощь местных инженеров. Учитывая эти ограничения, мы предельно аккуратно настроили зеркалирование портов на всех узлах прохождения трафика. Порты для зеркал настраивали на готовых к аренде клиентских серверах.
Зеркалирование трафика мы настроили на следующих интерфейсах:
- на коммутаторе sw03 — для BAGG-порта бэкап-хоста и аплинков, смотрящих в сторону ядра;
- на стеке sw01-01/02 — для аплинков со стороны ядра и BAGG-интерфейса, к которому подключен мастер-хост.
Запускаем dhclient и смотрим tcpdump. Картина кристально ясная, пакет делает следующее:
- выходит из BAGG на sw03,
- успешно проходит через аплинки,
- заходит на стек sw01-01/02…
- …и исчезает!
Мы видим приходящий OFFER на агрегации аплинков sw01-01/02 но уже теряем его на BAGG‑интерфейсе, к которому подключены балансировщик и dhcp-master То есть пакет бесследно пропадает внутри VLAN 67 на стеке M‑LAG свитча. Коммутатор молча уничтожает его на последнем метре!
Тихий убийца и парадокс прошивки
Почему коммутатор отбрасывает пакет внутри VLAN и на порту, который мы явно пометили как доверенный (dhcp snooping trust)?
Виновник нашелся, но не сразу — DHCP Snooping. На коммутаторах глобально работала команда dhcp snooping enable. Нюанс был в том, что ни настройка доверенных интерфейсов на физических аплинках и портах к хосту, ни даже явное указание их непосредственно внутри самого 67-го VLAN не решало проблему потери DHCP‑ответов.
Когда коммутатор видит в пакете нестандартный порт источника 6768 и наличие Option 82 (Relay Agent Information), его внутренние эвристики помечают трафик как подозрительный (вероятно срабатывает защита от спуфинга) и уничтожают его, полностью игнорируя флаг trust.
Загадка 1. У нас фантомные дропы…
Удивительно, но счетчики показывают:
Invalid DHCP packets dropped : 0 <-- НОЛЬ!
Коммутатор клянется, что не отбрасывает ни одного невалидного пакета. Но трафик все равно не проходил! Это означало, что блокировка происходит где-то внутри логики устройства (возможно, на уровне аппаратного ASIC или скрытых политик безопасности), никак не увеличивая стандартные счетчики DHCP Snooping.
Загадка 2. Магия одинаковой прошивки
В нашей схеме участвовали три узла: одиночный sw03 и стек M-LAG sw01-01/02:
- на sw03, куда подключен бэкап, трафик проходил — коммутатор корректно отрабатывал
dhcp snooping trust; - на стеке sw01, где находитися мастер — пакеты бесследно исчезали.
Все три коммутатора работают на абсолютно одинаковой версии прошивки и имеют идентичную конфигурацию DHCP Snooping. Однако поведение стека M-LAG радикально отличалось от одиночного коммутатора. Возможно, логика M-LAG и синхронизация состояний между членами кластера вносят скрытые артефакты в обработку L2-трафика.
Попытки точечно отключить инспекцию для сети (undo dhcp snooping enable vlan 67) не помогли. Получается, что эта команда работает не как исключение из глобального правила, а ломает логику применения политики.
Элегантный (и вынужденный) обходной маневр
Иметь в сети три коммутатора с одинаковой прошивкой, которые по-разному обрабатывают один и тот же трафик с одинаковыми настройками — это прямой путь к пожарам. Чтобы исключить любые «плавающие» баги M-LAG и привести поведение всей сети к единому знаменателю, мы решили изменить подход к DHCP Snooping на всех коммутаторах — и на sw03, и на стеке sw01.
Вместо того чтобы пытаться заставить коммутатор «доверять» кастомному DHCP-трафику, мы применили жесткий «белый список» для всей сети, кроме нашей.
Конфигурация на обоих коммутаторах была изменена:
# Полностью отключаем глобальный snooping
undo dhcp snooping enable
# Включаем его точечно для всех VLAN, КРОМЕ нашего 67-го
dhcp snooping enable vlan 2 to 66 68 to 4094
Почему это безопасно?
Такая конфигурация полностью исключает VLAN 67 из глубокой инспекции DHCP-пакетов. Наш легитимный трафик с уважаемым портом 67 теперь проходит беспрепятственно, так как коммутатор просто не применяет к нему механизмы DHCP Snooping. При этом защита от rogue DHCP-серверов (если кто-то из клиентов в других VLAN решит поднять свой DHCP) остается полностью активной во всех остальных пользовательских сетях.
После применения конфигурации DHCP OFFER мгновенно долетел до мастера. Сервис вновь заработал так, как мы задумывали.
Выводы и мораль
QinQ — это не только инкапсуляция, но и «защита» от умных коммутаторов. Пока трафик был в QinQ, коммутаторы его не видели и не трогали. Использование простого 802.1q, а не 802.1ad в этой схеме сделало возможным для свитча применять встроенную защиту — фильтрацию от нелегитимных DHCP‑серверов в локальных сетях.
Не верьте tcpdump на отправителе и счетчикам на коммутаторе! То, что пакет ушел с хоста, не значит, что он дошел. А то, что счетчик Invalid DHCP packets dropped равен нулю, не значит, что коммутатор ничего не отбрасывает. Скрытые логики ASIC существуют.
Одинаковая прошивка — не то же самое, что одинаковое поведение. Стек M-LAG — это сложная распределенная система. Можно ожидать, что в пограничных случаях ее поведение может отличаться от одиночного коммутатора, даже если show run и show version идентичны.
Port Mirroring: в L2-детективах только SPAN на всех хопах может показать правду. Без зеркалирования на порту мастера мы бы уже сидели в мягкой комнате в белой рубахе.
Нельзя недооценивать силу коллаборации. Задача была решена только благодаря плотной работе эксплуатации, разработки, отдела выделенных серверов и сетевых инженеров, которые помогли с настройкой SPAN, копали недра коммутаторов и меняли глобальные политики. Огромной спасибо коллегам, за помощь в диагностике. Максим и Володя, привет!