Почему dig находит хост в зоне .local, а Linux-приложения его не открывают
С этим ко мне приходят раз в пару месяцев. Внутренний портал живёт по адресу app01.corp.local. С Windows-станций открывается. С Linux-станции `dig app01.corp.local @10.20.0.10` отдаёт правильный A-адрес за пару миллисекунд, а `ping` того же имени говорит «Name or service not known», браузер — «сервер не найден». Админ идёт править зону на контроллере домена, перезапускает службу DNS и злится. Зона не при чём. Ниже разбираю, почему dig и обычное приложение идут к имени разными дорогами, что systemd-resolved и Avahi делают с суффиксом .local, как это диагностировать за пять минут и как чинить — от разового костыля до стратегического ухода с .local.
dig ничего не доказывает: он вообще не тот путь проверяет
Первое, что нужно принять: dig, nslookup и host — это самостоятельные DNS-клиенты. Они формируют DNS-пакет сами и отправляют его на UDP/53 напрямую. Файл /etc/resolv.conf они читают только чтобы узнать адрес сервера, а если вы написали @10.20.0.10, то не читают и его. Ни одна из этих утилит не обращается к системному механизму разрешения имён. Поэтому успешный ответ dig доказывает ровно одно: на DNS-сервере запись есть и сервер по сети доступен. Про то, как имя будет разрешать браузер, ничего не доказывает.
Обычное приложение — Chrome, curl, psql, java, ваш самописный сервис — зовёт getaddrinfo(3) из glibc. А тот идёт по цепочке модулей, описанной в /etc/nsswitch.conf, строка hosts. Это и есть тот путь, который надо проверять. Правильный «тест приложения» — не dig, а getent:
grep '^hosts' /etc/nsswitch.conf
getent hosts app01.corp.local # так видит имя приложение
dig +short app01.corp.local @10.20.0.10 # так видит имя админЕсли dig отвечает, а getent молчит — вопрос закрыт: DNS-сервер и зона исправны, ломается разрешение имени на клиенте. Дальше остаётся выяснить, какой механизм перехватил запрос. Здесь дистрибутивы расходятся. Мануал nss-resolve(8) рекомендует строку hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns, и Fedora ставит модуль resolve в hosts-строку штатно: запрос уходит в systemd-resolved, а приписка [!UNAVAIL=return] означает, что при работающей службе её отрицательный ответ окончательный и до dns дело не доходит. Ubuntu на десктопной установке идёт другим путём: в hosts-строке обычно files mdns4_minimal [NOTFOUND=return] dns, а /etc/resolv.conf — симлинк на stub-resolv.conf с единственным сервером 127.0.0.53. То есть классический nss-dns отправляет пакет в тот же systemd-resolved, только через stub-listener. Правила маршрутизации к таким запросам применяются те же самые, поэтому итог для .local одинаковый. Точную строку на своей машине всегда смотрите сами — после обновлений и установки пакетов она меняется.
- dig/nslookup/host — собственные DNS-клиенты, NSS не используют;
- getaddrinfo(3) → /etc/nsswitch.conf → модули NSS — это путь приложений;
- getent hosts <имя> и getent ahosts <имя> воспроизводят путь приложения;
- resolve в hosts-строке или resolv.conf со 127.0.0.53 = запрос обрабатывает systemd-resolved, и решает он, куда имя отправить.
Почему .local — заминированный суффикс
Суффикс .local зарезервирован RFC 6762 за Multicast DNS. Это не рекомендация «по этикету», а нормативное резервирование, и systemd-resolved соблюдает его буквально. Мануал systemd-resolved.service(8) формулирует прямо: многокомпонентные имена с суффиксом .local разрешаются через MulticastDNS на всех локальных интерфейсах, где MulticastDNS включён. И тут же вторая половина, ради которой всё и написано: по умолчанию запросы к доменам с суффиксом .local не направляются на DNS-серверы, если домен не задан явно как routing- или search-домен.
Прочитайте это ещё раз. Не «сначала попробуем DNS, потом mDNS». Не «попробуем оба». А «на обычный DNS вообще не пойдём». Ваш контроллер домена с идеальной зоной corp.local просто не спрашивают. Более того, мануал отдельно замечает, что сегодня заводить .local на DNS-сервере в целом не рекомендуется, потому что RFC 6762 резервирует этот домен исключительно под Multicast DNS. Microsoft говорит то же самое со своей стороны: в руководстве по выбору корневого домена леса на learn.microsoft.com прямо написано, что использовать незарегистрированные суффиксы вроде .local не рекомендуется, а в статье об именовании доменов (бывшая KB 909264) — что стоит избегать имён, занятых интернет-стандартными механизмами, и .local приведён как пример.
Откуда тогда взялись тысячи доменов corp.local и office.local? Из мастеров установки Small Business Server и Active Directory середины двухтысячных, где .local предлагался как безопасный вариант «не публичного» имени. Пока парк чисто виндовый, всё тихо: DNS-клиент Windows про RFC 6762 в этой части не переживает и спокойно спрашивает корпоративный DNS. Проблема вылезает ровно в тот день, когда в сети появляется первая Linux-станция, Mac или контейнер.
И это не единственная беда .local. mDNS работает через мультикаст на 224.0.0.251:5353 (и ff02::fb для IPv6) и через маршрутизатор не проходит. Значит, даже если mDNS «поймает» имя, поймает он его только в своём широковещательном домене. Хост в соседнем VLAN не найдётся никогда — а именно так обычно и живут серверы: в отдельном сегменте. Плюс к тому публичный сертификат на имя в .local не выпустит ни один удостоверяющий центр, цепочку доверия DNSSEC до такой зоны не построить, а SRV-записи вроде _ldap._tcp корпоративная инфраструктура через mDNS не публикует.
- mDNS не маршрутизируется между подсетями — сервер в другом VLAN недосягаем;
- публичный TLS-сертификат и цепочку DNSSEC для имени в .local не получить;
- Avahi и systemd-resolved претендуют на один и тот же суффикс и мешают друг другу;
- в контейнерах mDNS обычно недоступен вовсе — там свой /etc/resolv.conf и свой резолвер;
- делегирование поддоменов и SRV-записи AD через mDNS не работают.
Разбор из практики: инкубатор «Бизнес-трамплин», 21 рабочее место
Инкубатор стартапов: общий офис с коворкингом, 21 рабочее место — администрация, менеджеры программ и столы резидентов. Домен AD corp.local подняли много лет назад «по мастеру», сейчас два контроллера на Windows Server 2019 (10.20.0.10 и 10.20.0.12). На внутреннем сервере 10.20.0.11 крутится портал бронирования переговорок и учёта резидентов по адресу app01.corp.local, рядом веб-интерфейс мониторинга на mon.corp.local. Сотрудники администрации работают на Windows, а у резидентов-разработчиков стоят четыре станции Ubuntu 24.04 и две Fedora. Серверный сегмент 10.20.0.0/24, пользовательский 10.20.10.0/24, между ними маршрутизатор.
Заявка пришла обычная: «после переустановки системы не открывается портал бронирования, а у соседа на Windows открывается». Подключились удалённо, посмотрели. С Ubuntu 24.04: dig +short app01.corp.local @10.20.0.10 → 10.20.0.11, время ответа 1 мс. ping app01.corp.local → Name or service not known. getent hosts app01.corp.local → пусто. То есть классика из первого раздела, и падало это почти мгновенно, из-за чего все и решили, что «записи нет».
Смотрим resolvectl status. На линке enp1s0 стоят DNS-серверы 10.20.0.10 и 10.20.0.12 (пришли по DHCP), строки DNS Domain: нет вовсе — DHCP-сервер суффикс не раздавал. В строке Protocols: на Ubuntu-станциях было -mDNS, на одной из Fedora — +mDNS, но разницы в симптоме это не давало. Дальше resolvectl query app01.corp.local: на Ubuntu сразу «No appropriate name servers or networks for name found», на Fedora — то же отрицательное решение после короткой паузы на мультикаст-опрос. Всё сходится: имя многокомпонентное, суффикс .local, routing- или search-домена для corp.local нет — на 10.20.0.10 запрос просто не пошёл, а в эфире такого имени никто не объявляет.
Второй слой сюрприза нашёлся на Ubuntu-станциях. Там стоял libnss-mdns — приехал зависимостью вместе с avahi-daemon и подсистемой печати, в /etc/nsswitch.conf строка hosts: files mdns4_minimal [NOTFOUND=return] dns. На app01.corp.local он не влиял: имя из трёх меток, а nss-mdns по своей эвристике отказывается обслуживать .local-имена длиннее двух меток и пропускает их дальше по цепочке. Зато ломал обращения к самому corp.local — к DFS-пространству \\corp.local\files и при проверке домена через getent hosts corp.local. Это имя из двух меток, модуль его берёт, SOA для верхнеуровневого local через DNS не получает (resolved такой запрос тоже не маршрутизирует), уходит в мультикаст, ничего не находит и отдаёт NOTFOUND, а [NOTFOUND=return] останавливает цепочку. Два независимых механизма, и починка одного второй не лечит.
Чинили так: на всех шести Linux-станциях объявили corp.local search-доменом в профиле NetworkManager и выключили mDNS на уровне соединения, а на Ubuntu убрали mdns4_minimal из hosts-строки (avahi-daemon оставили — через него находится принтер в коворкинге, но NSS-модуль для этого не нужен). Проверка getent hosts app01.corp.local, getent hosts corp.local и resolvectl query mon.corp.local — все отдают адрес, в хвосте вывода query появилась строка о том, что информация получена по протоколу DNS. Работы на весь парк — около 25 минут вместе с проверками. Отдельной задачей в план ушёл вопрос ухода с .local для новых сервисов — про это в последнем разделе.
- Симптом: dig — 1 мс и правильный адрес, getent hosts — пусто, браузер — «сервер не найден».
- Причина №1: systemd-resolved без routing- или search-домена для corp.local — .local в unicast DNS не отправляется, независимо от того, включён ли mDNS.
- Причина №2: libnss-mdns с [NOTFOUND=return] на Ubuntu — ломает двухметочное имя corp.local (DFS, проверки домена).
- Итог: 6 станций, ~25 минут, без единой правки на контроллерах домена.
Диагностика за пять минут: шесть команд
Порядок, которым я пользуюсь на выезде. Он же годится, чтобы отдать его младшему инженеру и не объяснять теорию.
# 1. Что реально в цепочке NSS и куда смотрит resolv.conf
grep '^hosts' /etc/nsswitch.conf
ls -l /etc/resolv.conf
# 2. Путь приложения против пути админа
getent hosts app01.corp.local
dig +short app01.corp.local @10.20.0.10
# 3. Что думает systemd-resolved: DNS на линке, домены, строка Protocols
resolvectl status
# 4. Запрос глазами resolved; легенда (по умолчанию включена) покажет протокол
resolvectl query --legend=yes app01.corp.local
# 5. Стоит ли на машине NSS-модуль Avahi
dpkg -S libnss_mdns4_minimal.so.2 2>/dev/null || rpm -qf /usr/lib64/libnss_mdns4_minimal.so.2
# 6. Что творится в самой службе
sudo resolvectl flush-caches
journalctl -u systemd-resolved -n 50 --no-pagerСамая ценная команда здесь — resolvectl query. В отличие от dig она показывает решение самой службы: в хвосте успешного ответа есть строка вида «Information acquired via protocol DNS», а при отсутствии маршрута вы сразу получаете «No appropriate name servers or networks for name found». Ключ --legend= управляет этими служебными строками: по умолчанию они выводятся, а --legend=no пригодится в скриптах, когда нужен только адрес. Не рассчитывайте обойти запрет ключом -p dns: он ограничивает набор протоколов, но правило «.local не отправлять в unicast DNS без явного домена» от этого не отменяется. Лучшее доказательство диагноза другое — временно объявить домен через resolvectl domain (следующий раздел) и повторить query: если имя тут же находится по протоколу DNS, дело было в маршрутизации, а не в записи.
Отдельно проверьте, а systemd-resolved вообще в игре. Если /etc/resolv.conf — симлинк на /run/systemd/resolve/stub-resolv.conf (сервер 127.0.0.53) или в hosts-строке есть resolve, то да, и правила маршрутизации .local действуют. Если это обычный файл с адресами корпоративных серверов, а в hosts-строке нет resolve — вы имеете дело с классическим nss-dns, и там .local ведёт себя как любая другая зона. В этом случае ищите Avahi и nss-mdns, третьего почти не бывает.
- resolvectl query показывает протокол ответа и честно пишет, когда маршрута нет — dig этого не умеет;
- `--legend=yes` (по умолчанию) выводит служебные строки ответа, `--legend=no` — только данные;
- `-p dns` не заставит resolved отправить .local в unicast DNS — нужен явный домен;
- /etc/resolv.conf → stub-resolv.conf (127.0.0.53) означает, что даже nss-dns ходит через systemd-resolved;
- если systemd-resolved не используется — виновник почти всегда libnss-mdns.
Как чинить: три уровня, от проверки гипотезы до постоянного конфига
Уровень первый — разово, чтобы подтвердить диагноз. Все изменения живут в оперативной памяти службы и слетают при переподключении интерфейса, зато делаются мгновенно и ничего не ломают.
# выключить mDNS на конкретном линке
sudo resolvectl mdns enp1s0 no
# объявить корпоративную зону search-доменом этого линка
sudo resolvectl domain enp1s0 corp.local
# если .local-зон несколько — routing-домен на весь суффикс
# sudo resolvectl domain enp1s0 '~local'
sudo resolvectl flush-caches
resolvectl query app01.corp.localРазница между search- и routing-доменом важна и её постоянно путают. Search-домен (без тильды) делает две вещи: маршрутизирует запросы этого домена на DNS-серверы данного линка и дополнительно подставляет суффикс к однокомпонентным именам, то есть app01 превратится в app01.corp.local. Routing-домен (с тильдой, ~corp.local) только маршрутизирует, суффикс не подставляет. Для корпоративной зоны обычно нужен именно search-домен. А ~. — это «всё остальное сюда», типовой приём для основного линка при живом VPN.
Уровень второй — постоянный конфиг через NetworkManager, это то, что я оставляю у клиента. Свойство connection.mdns принимает значения 0 (no), 1 (resolve), 2 (yes) и -1 (default), а ipv4.dns-search поддерживает тильду для routing-доменов ровно с той же семантикой, что и resolvectl:
nmcli con show # узнать имя профиля
nmcli con mod "Wired connection 1" ipv4.dns-search "corp.local"
nmcli con mod "Wired connection 1" connection.mdns 0
nmcli con mod "Wired connection 1" connection.llmnr 0
nmcli con up "Wired connection 1"Уровень третий — глобально, drop-in для systemd-resolved. Удобен там, где интерфейс один и NetworkManager не используется (серверы, systemd-networkd). Кладём файл /etc/systemd/resolved.conf.d/50-corp.conf:
[Resolve]
DNS=10.20.0.10 10.20.0.12
Domains=~corp.local
MulticastDNS=no
LLMNR=noИ перезапускаем: sudo systemctl restart systemd-resolved. Держите в голове, что глобальные настройки из resolved.conf действуют вместе с per-link, а не вместо них. Для маршрутизации resolved выбирает «лучше всего совпадающий» routing-домен — тот, у которого больше меток, — и шлёт запрос серверам, к которым этот домен привязан, будь то линк или глобальная секция. Поэтому Domains=~corp.local в глобальном конфиге отправит corp.local на глобальные DNS= даже при серверах, пришедших по DHCP. А MulticastDNS=no глобально выключает mDNS везде: по resolved.conf(5) он работает на линке, только если включён и глобально, и на линке. На станциях с DHCP и NetworkManager я всё же предпочитаю профиль соединения — там настройки живут рядом с остальной сетью, а drop-in оставляю для серверов и статики.
Есть и четвёртый, аварийный вариант — вынести systemd-resolved из цепочки NSS: убрать resolve [!UNAVAIL=return] из hosts-строки, оставить files dns и положить статический /etc/resolv.conf. Работает, .local начинает резолвиться как обычная зона. Но вы теряете split-DNS: на машине с корпоративным VPN или несколькими туннелями это выйдет боком очень быстро, потому что резолвиться всё начнёт через один набор серверов. Я так делаю только на изолированных стендах и в качестве временной меры, когда нужно поднять человека здесь и сейчас.
- resolvectl — проверить гипотезу, изменения не переживут reconnect;
- nmcli (ipv4.dns-search + connection.mdns 0) — постоянно, на рабочих станциях с DHCP;
- /etc/systemd/resolved.conf.d/*.conf — глобально, на серверах и статике;
- правка nsswitch + статический resolv.conf — только как аварийная мера, ценой split-DNS.
Второй фронт: Avahi, nss-mdns и приложения со своим резолвером
На Debian и Ubuntu в цепочку часто вклинивается libnss-mdns — NSS-модуль Avahi. Minimal-вариант обслуживает исключительно имена в .local (и обратные запросы для 169.254.x.x) и в типовой конфигурации стоит в hosts-строке до dns: hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4. Логика такая: на имя, которое модуль брать не хочет, он возвращает UNAVAIL и пропускает запрос дальше по цепочке, а вот на .local-имя, которое он взял и не нашёл в мультикаст-эфире, возвращает NOTFOUND — и [NOTFOUND=return] обрывает цепочку. До dns дело не доходит.
Какие имена модуль берёт — вопрос, в котором путаются чаще всего. По документации nss-mdns работают две эвристики. Первая: запросы к .local-именам длиннее двух меток отклоняются — foo.bar.local модуль пропускает дальше. Значит, app01.corp.local он не трогает, а вот сам corp.local, короткие имена вида printer.local и имена DFS-пространства в корне домена — перехватывает. Вторая: перед обслуживанием .local модуль проверяет SOA, и если unicast DNS отдаёт SOA для верхнеуровневого имени local, запрос отклоняется. Ваша зона называется corp.local, SOA на голом local сервер не отдаст — защита не срабатывает. Настроить поведение можно файлом /etc/mdns.allow, но только для полного варианта mdns4: minimal-вариант, по документации, этот файл не читает никогда. Поэтому на машинах, где Avahi нужен только для печати, я просто вычищаю mdns из hosts-строки:
# было: hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4
# стало: hosts: files dns
sudo sed -i 's/ mdns4_minimal \[NOTFOUND=return\]//; s/ mdns4$//' /etc/nsswitch.conf
getent hosts app01.corp.localТретья категория сюрпризов — программы, которые идут к имени не через glibc. Go по документации пакета net предпочитает собственный резолвер, который сам читает /etc/resolv.conf и /etc/hosts, но переключается на cgo и getaddrinfo, если в nsswitch.conf указаны источники, которых он не умеет, — а resolve и mdns4_minimal как раз такие; статически собранный бинарник без cgo этого сделать не сможет. Образы на musl/Alpine NSS-модулей не поддерживают вообще и читают resolv.conf напрямую. SSSD для поиска контроллеров использует собственный асинхронный резолвер поверх resolv.conf. Отсюда вечное «а у нас curl работает, а наш сервис нет» — и наоборот. Важная деталь: если resolv.conf указывает на 127.0.0.53, такие программы всё равно попадают в systemd-resolved, и запрет на .local их тоже касается; обходят его только те, у кого в resolv.conf адреса корпоративных серверов.
И отдельно про контейнеры. Docker при stub-резолвере на хосте не копирует 127.0.0.53 в контейнер: в сетях по умолчанию он подставляет реальные upstream-серверы, а в пользовательских сетях контейнер ходит во встроенный DNS 127.0.0.11, который пересылает запросы наружу. mDNS внутри контейнера, как правило, нет. Получается обратная картина: контейнер спрашивает контроллер домена напрямую и corp.local видит, а хост — нет. Это сбивает с толку при диагностике («в контейнере же работает»), поэтому проверяйте хост и контейнер раздельно и не делайте выводов об одном по другому.
- libnss-mdns с [NOTFOUND=return] перехватывает .local раньше dns;
- эвристика nss-mdns пропускает имена длиннее двух меток — страдают corp.local, printer.local и корень DFS;
- SOA-проверка смотрит на TLD `local`, а не на зону corp.local — и потому не спасает;
- /etc/mdns.allow читает только mdns4, minimal-вариант его игнорирует;
- Go (с cgo), musl/Alpine и SSSD идут к имени разными путями; при resolv.conf → 127.0.0.53 все они всё равно упираются в resolved;
- контейнеры Docker обычно спрашивают upstream напрямую — там .local может работать, а на хосте нет.
Что делать в первую очередь, а на что забить
Приоритет номер один — зафиксировать конфиг на станциях через NetworkManager или drop-in resolved.conf.d и убрать mdns из nsswitch там, где он приехал зависимостью. Это 3-5 минут на машину, делается скриптом, живёт годами и решает 100 % симптома. Никаких правок на контроллерах домена для этого не требуется, и это важный аргумент, когда AD трогать страшно или некому.
Приоритет номер два — решить, живёте ли вы дальше на .local. Штатное переименование домена AD (rendom/gpfixup) технически существует, но это операция на выходные с реальными рисками, а при установленном Exchange за неё браться я не советую вовсе. Практичный путь, который я применяю у клиентов: не переименовывать, а завести внутреннюю зону на реальном домене — corp.example.com, делегированную и видимую только внутри. Новые сервисы публикуются там, старые имена остаются алиасами, и через год-полтора .local встречается только в SPN и Kerberos, куда пользовательские приложения не лезут. Заодно вы получаете возможность выпустить нормальные сертификаты Let's Encrypt на внутренние имена, чего с .local не сделать в принципе.
На что можно спокойно забить. На переименование NetBIOS-имени домена — оно к нашей проблеме отношения не имеет. На попытки «починить DNS-сервер» или «пересоздать зону» — зона исправна, вы это уже доказали командой dig. На идею отключить systemd-resolved целиком: соблазнительно, но вы теряете split-DNS, кеш и per-link конфигурацию, а на машинах с VPN и туннелями это создаст новых проблем больше, чем решит. И на мифы про «mDNS надо просто выключить в фаерволе» — блокировка 5353 не заставит systemd-resolved пойти на unicast DNS, он всё равно не пойдёт, потому что routing-домена нет; вы получите тот же отказ, только медленнее.
Честно про спорное. Единого мнения, как правильно жить с корпоративной зоной .local, в сообществе нет и не будет: мануал systemd-resolved и документация Microsoft прямым текстом не рекомендуют такую зону заводить, а у половины российского малого и среднего бизнеса она заведена лет пятнадцать назад и работает. Обе позиции обоснованы. Моя практическая позиция такая: пока зона .local существует, обслуживайте её явно — routing/search-домен плюс выключенный mDNS на всех Linux-машинах, и никаких «оно само разберётся». А стратегически двигайтесь к нормальному имени, но без героизма и не в ночь с пятницы на субботу.
- Сейчас: NetworkManager/resolved.conf.d + чистка nsswitch — 3-5 минут на станцию.
- В квартал: инвентаризация — где ещё .local зашит в конфигах, ярлыках, кронах и мониторинге.
- В год: новые сервисы на внутренней зоне реального домена, .local — только легаси-алиасы.
- Не делать: переименование AD ради красоты, отключение systemd-resolved, блокировку 5353 «на всякий случай».
Частые вопросы
Почему dig отвечает, а ping и браузер — нет?
Потому что это два разных пути. dig — самостоятельный DNS-клиент, он шлёт пакет на UDP/53 напрямую и системный механизм разрешения имён не использует. ping и браузер зовут getaddrinfo(3), а тот идёт по цепочке модулей из /etc/nsswitch.conf. Для имён с суффиксом .local запрос перехватывает systemd-resolved (уводит в mDNS) или nss-mdns и на обычный DNS не отдаёт. Проверяйте не dig, а `getent hosts <имя>`.
Достаточно ли просто выключить mDNS, чтобы .local заработал?
Нет, этого мало. Мануал systemd-resolved говорит, что запросы .local не направляются на DNS-серверы, пока домен не объявлен явно routing- или search-доменом. Выключив mDNS, вы уберёте перехват, но маршрута на корпоративный DNS всё равно не появится. Нужны обе половины: `resolvectl mdns <link> no` и `resolvectl domain <link> corp.local` (либо `~local`), а лучше — то же самое постоянно через NetworkManager.
Чем routing-домен отличается от search-домена?
Search-домен (без тильды) маршрутизирует запросы этого домена на DNS-серверы линка и дополнительно подставляет суффикс к однокомпонентным именам: `app01` станет `app01.corp.local`. Routing-домен (`~corp.local`) только маршрутизирует, суффикс не подставляет. Для корпоративной зоны обычно нужен search-домен. Особый случай — `~.`: он означает «все остальные запросы сюда» и используется как основной линк при активном VPN.
Мои правки resolvectl слетают после перезагрузки. Как сделать их постоянными?
resolvectl меняет состояние службы в памяти, переподключение интерфейса это откатывает. Постоянно — либо через профиль NetworkManager (`nmcli con mod <профиль> ipv4.dns-search "corp.local"` и `connection.mdns 0`), либо drop-in файлом /etc/systemd/resolved.conf.d/50-corp.conf с секцией [Resolve] и строкой `Domains=~corp.local`. Глобальный routing-домен работает и при DNS-серверах из DHCP: resolved выбирает самый длинный совпадающий домен. Для рабочих станций я всё же предпочитаю профиль NetworkManager.
У нас Ubuntu, systemd-resolved настроил, хосты резолвятся, а \\corp.local и само имя домена — нет. Почему?
Скорее всего, в /etc/nsswitch.conf стоит libnss-mdns: `hosts: files mdns4_minimal [NOTFOUND=return] dns`. Модуль отклоняет .local-имена длиннее двух меток, поэтому app01.corp.local проходит до DNS, а двухметочное corp.local он берёт сам, не находит в мультикаст-эфире, отдаёт NOTFOUND — и `[NOTFOUND=return]` обрывает цепочку. Уберите mdns4_minimal и mdns4 из hosts-строки; avahi-daemon для печати можно оставить.
Надо ли переименовывать домен AD, чтобы уйти с .local?
Не обязательно и обычно не стоит. Переименование (rendom/gpfixup) возможно, но это рискованная операция, а с Exchange в лесу за неё лучше не браться. Практичнее завести внутреннюю зону на реальном домене — например corp.example.com, видимую только внутри, — и публиковать там новые сервисы, оставив .local легаси-алиасами. Побочный плюс: на нормальном имени можно выпускать публичные сертификаты, чего с .local не сделать.
Что официально говорят Microsoft и systemd про домен AD в .local?
Обе стороны против. В руководстве Microsoft по выбору корневого домена леса сказано, что незарегистрированные суффиксы вроде .local использовать не рекомендуется, а в статье об именовании доменов — что стоит избегать имён, занятых интернет-стандартными механизмами, с .local в качестве примера. Мануал systemd-resolved напоминает, что RFC 6762 резервирует .local исключительно под Multicast DNS. Работающий домен из-за этого ломать не нужно, но новые зоны заводите на зарегистрированном имени, например corp.example.com.
Источники
- systemd-resolved.service(8) — Раздел «Protocols and Routing»: многокомпонентные имена с суффиксом .local разрешаются через MulticastDNS на интерфейсах, где он включён; запросы к доменам .local по умолчанию не направляются на DNS-серверы, если домен не задан явно как routing- или search-домен; однокомпонентные имена по умолчанию не уходят на удалённые DNS-серверы без search-доменов; выбор «лучше всего совпадающего» routing-домена; рекомендация не определять .local на DNS-сервере со ссылкой на RFC 6762. https://man7.org/linux/man-pages/man8/systemd-resolved.service.8.html
- resolved.conf(5) — Директивы MulticastDNS= (boolean или resolve; mDNS работает на линке, только если включён и глобально, и на линке), LLMNR=, Domains= с префиксом «~» для routing-доменов, DNS=, DNSStubListener=, ResolveUnicastSingleLabel=, Cache=. https://man7.org/linux/man-pages/man5/resolved.conf.5.html
- resolvectl(1) — Синтаксис status, query, dns LINK SERVER..., domain LINK DOMAIN..., mdns LINK MODE, llmnr, flush-caches; опции -i/--interface, -p/--protocol и --legend=BOOL (по умолчанию true — выводит заголовки и мета-информацию об ответе). https://man7.org/linux/man-pages/man1/resolvectl.1.html
- nss-resolve(8) — Рекомендуемая строка hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns; nss-resolve передаёт запросы службе systemd-resolved, dns ставится после resolve как запасной вариант на случай, когда служба недоступна. https://man7.org/linux/man-pages/man8/nss-resolve.8.html
- Fedora Magazine — systemd-resolved: introduction to split DNS — Разбор routing- и search-доменов, правило «выигрывает самое длинное совпадение», особый домен ~., примеры resolvectl domain/dns/status и оговорка про .local как зарезервированный RFC 6762 суффикс. https://fedoramagazine.org/systemd-resolved-introduction-to-split-dns/
- avahi/nss-mdns — документация проекта — README проекта: minimal-варианты обслуживают только имена в .local и адреса 169.254.x.x; типовая строка hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4; запросы с числом меток больше двух отклоняются (foo.bar.local); проверка SOA для верхнеуровневого имени local; /etc/mdns.allow minimal-вариант не читает. https://github.com/avahi/nss-mdns
- NetworkManager — nm-settings — Свойства connection.mdns и connection.llmnr: yes (2), no (0), resolve (1), default (-1) — поведение по умолчанию определяется DNS-плагином; в ipv4.dns-search домены с префиксом «~» считаются routing-доменами и не используются для дополнения неполных имён. https://networkmanager.dev/docs/api/latest/nm-settings-nmcli.html
- RFC 6762 — Multicast DNS — IETF, февраль 2013: домен local. зарезервирован для link-local имён, разрешаемых через Multicast DNS; адреса 224.0.0.251 и FF02::FB, порт 5353. https://www.rfc-editor.org/rfc/rfc6762
- Microsoft Learn — Selecting the Forest Root Domain — Раздел «Selecting a suffix»: рекомендация использовать зарегистрированные DNS-имена; «we do not recommend using unregistered suffixes, such as .local». https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/selecting-the-forest-root-domain
- Microsoft Learn — Name computers, domains, sites, and OUs (KB 909264) — Раздел «DNS domain names → Reserved names»: избегать имён, используемых интернет-стандартными механизмами, например .local; пример внутреннего домена corp.contoso.com. https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/naming-conventions-for-computer-domain-site-ou
- Go — package net, Name Resolution — Go-резолвер переключается на cgo (getaddrinfo), если /etc/resolv.conf или /etc/nsswitch.conf задают возможности, которые он не реализует. https://pkg.go.dev/net
