Debian 13: Split-horizon DNS пішов не так — виправте внутрішні імена, не зламавши інтернет (випадок №33)

Було корисно?

Ви на Debian 13. Половина ваших пристроїв більше не дістає до git.internal, але інша половина — може. Хтось «виправив DNS» вчора, і тепер ваш ноутбук резолює внутрішні імена навіть у кав’ярні, що мило — аж поки запити не витікають і не ламають припущення zero-trust.

Це класична помилка split-horizon DNS: внутрішні імена повинні резолюватися лише в межах внутрішньої мережі (або через VPN), тоді як публічний DNS має залишатися недоторканим. Цікаве те, що split-horizon може ламатися принаймні на чотирьох різних рівнях у Debian 13: авторитарний сервер, рекурсивний резолвер, stub-резолвер та маршрутизація DNS по окремих інтерфейсах. Якщо вгадуєте неправильно, ви «виправляєте» проблему, погіршуючи інтернет.

Що фактично ламається в split-horizon на Debian 13

Split-horizon DNS означає, що той самий запит повертає різні відповіді залежно від того, звідки ви запитуєте. Зазвичай це означає:

  • В середині мережі: git.internal резолюється в адресу RFC1918, можливо за внутрішнім балансувальником навантаження.
  • Зовні мережі: git.internal або не існує (NXDOMAIN), або резолюється в публічну адресу (рідко й ризиковано), або не дає корисної відповіді.

В реальному продакшені «split-horizon» — не одна функція. Це угода між:

  • Авторитарним DNS для ваших внутрішніх зон (зазвичай BIND, Knot, PowerDNS, Windows DNS або керований сервіс).
  • Рекурсивними резолверами, до яких звертаються клієнти (Unbound, BIND recursive, корпоративні резолвери, резолвери від VPN).
  • Клієнтським stub-резолвером і політичною маршрутизацією: на Debian 13 це часто systemd-resolved + рішення resolvectl по кожному інтерфейсу.
  • Пошуковими доменами та доменами маршрутизації, що вирішують, чи має запит піти на «внутрішній DNS», чи на «інтернет DNS».
  • Кешами скрізь, включно з негативним кешуванням (NXDOMAIN), яке може тримати вас у невірному стані хвилинами або годинами навіть після виправлення.

Debian 13 додає одну гостру межу: люди припускають, що ОС усе ще поводиться як «змінити /etc/resolv.conf — і все готово». На сучасній системі з systemd-resolved цей файл може бути stub-версією, симлінком або самою собою обманом, яким ви заспокоюєте себе перед сном.

Коли split-horizon «йде не так», відмови зазвичай потрапляють в одну з цих категорій:

  1. Використовується неправильний резолвер (клієнти питають публічний DNS про внутрішні імена або навпаки).
  2. Брак домену маршрутизації (внутрішні суфікси не прив’язані до VPN-інтерфейсу, тож запити йдуть за замовчуванням в інший напрям).
  3. Розбіжність авторитарних даних (внутрішні та зовнішні зони розійшлися або щось видалили).
  4. Взаємодія з DNSSEC (валідуючі резолвери вважають вашу внутрішню відповідь помилковою через зламані довірчі припущення).
  5. Негативне кешування (клієнти зберігають NXDOMAIN навіть після виправлення зони).
  6. «Оптимізації» як EDNS Client Subnet, DNS64 або агресивне кешування поводяться по-різному в різних резолверах і ваші припущення розлітаються.

Одна операційна заповідь: split-horizon має бути явним і тестованим. Якщо воно імпліцитне («зазвичай працює, коли ви в VPN»), воно рано чи пізно перестане працювати перед важливою зустріччю. DNS любить вибирати невдалий момент.

Цитата, що досі діє в опсах: «Надія — не стратегія» — перефразована ідея, часто приписується кільком операційним лідерам.

Жарт №1: DNS — єдина система, де «воно кешовано» одночасно є поясненням і загрозою.

Цікаві факти та історичний контекст (щоб пояснити сьогоднішній біль)

  • Split-horizon виник задовго до хмари. Це з’явилося з необхідності підприємств публікувати внутрішні імена, одночасно показуючи зовнішнім партнерам відфільтрований вигляд.
  • «Views» в BIND стали канонічним механізмом для подачі різних відповідей клієнтам з різних підмереж; ця концепція досі формує підхід багатьох команд до split DNS.
  • Негативне кешування стандартизоване. Згідно з RFC 2308, резолвери можуть кешувати NXDOMAIN залежно від SOA minimum/negative TTL; «виправлений» запис може виглядати зламаним довше, ніж ви очікуєте.
  • Раніше головним трюком були search-домени. До маршрутизації DNS по інтерфейсу адміністратори покладалися на search в resolv.conf. Це працювало… доки ноутбуки не почали мати кілька мереж і VPN.
  • Пастка з .local — історична, не теоретична. mDNS використовує .local (RFC 6762). Якщо ви використовували його для унікастного внутрішнього DNS, у вас, можливо, блукають привиди в резолвері.
  • Робота ICANN 2013 про «name collision» показала, як часто підприємства використовували «приватні TLD», що потім конфліктували з публічним DNS або новими TLD.
  • systemd-resolved ввів маршрутизацію DNS по інтерфейсу, тож DNS від VPN може використовуватися лише для певних доменів. Це вирішує реальну проблему, але також означає, що старі уявлення про поведінку системи застаріли.
  • EDNS0 змінив поведінку розміру пакетів. Сучасні резолвери часто надсилають більші UDP-пакети; якщо PMTU або правила фаєрволу неправильні, ви отримуєте дивні таймаути, які виглядають як «DNS нестабільний».
  • Деякі резолвери роблять «NXDOMAIN cut» та агресивне кешування NSEC. З DNSSEC резолвер може робити висновки про неіснування для суміжних імен. Якщо ваша внутрішня DNSSEC-історія заплутана, відлагодження перетворюється на інтерпретативний танець.

Швидкий план діагностики (перевірити спочатку/далі/потім)

Спочатку: підтвердіть, яким шляхом насправді йде запит клієнта

  • Чи запущений systemd-resolved і чи /etc/resolv.conf є stub-версією?
  • Які DNS-сервери налаштовані для кожного інтерфейсу (VPN проти LAN проти Wi‑Fi)?
  • Чи бачите ви домени маршрутизації (~corp.example), застосовані до VPN-лінку?

По-друге: відтворіть проблему одним інструментом, одним іменем, одним сервером

  • Виберіть одне внутрішнє ім’я (наприклад, git.internal).
  • Опитайте сконфігурований резолвер, потім опитайте авторитарний сервер напряму.
  • Порівняйте відповіді, TTLи та чи ви отримуєте NXDOMAIN vs SERVFAIL vs timeout.

По-третє: шукайте кеші та політичні перешкоди

  • Очистіть кеш клієнта і спробуйте ще раз.
  • Перевірте negative TTL в SOA.
  • Перевірте стан валідації DNSSEC, якщо ви валідуєте.
  • Проінспектуйте фаєрвол/MTU, якщо бачите випадкові таймаути.

По-четверте: тільки після цього чіпайте конфігурацію

  • Якщо клієнт питає неправильний сервер: виправте маршрутизацію DNS по інтерфейсу або конфігурацію NetworkManager/VPN.
  • Якщо резолвер форвардить неправильно: виправте умовне пересилання / stub-зони.
  • Якщо авторитарні дані неправильні: виправте зону і повідомте про очікувані затримки через кешування.

Практичні завдання (команди, виводи, що це означає, яке рішення)

Це завдання, які я реально виконую, коли приходить інцидент split-horizon. Кожне включає реалістичний приклад виводу і рішення, яке з нього випливає. Замініть імена/IP на свої реалії.

Завдання 1 — Перевірити, чи systemd-resolved у ланцюгу

cr0x@server:~$ systemctl status systemd-resolved --no-pager
● systemd-resolved.service - Network Name Resolution
     Loaded: loaded (/lib/systemd/system/systemd-resolved.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-30 09:12:18 UTC; 2h 4min ago
       Docs: man:systemd-resolved.service(8)
   Main PID: 612 (systemd-resolve)
     Status: "Processing requests..."

Значення: Ви маєте справу не з «простим resolv.conf». Є stub-резолвер і кеш.

Рішення: Використовуйте resolvectl для інспекції per-link DNS та доменів маршрутизації; не редагуйте /etc/resolv.conf вслепу.

Завдання 2 — Перевірити реальність /etc/resolv.conf

cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Dec 30 09:12 /etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf

Значення: Програми, що читають /etc/resolv.conf, вказані на локальний stub (зазвичай 127.0.0.53).

Рішення: Налаштовуйте DNS через systemd/network manager, а не переписуючи /etc/resolv.conf (якщо тільки ви свідомо не відключаєте resolved).

Завдання 3 — Подивитися, які DNS-сервери і домени застосовано по лінку

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
 resolv.conf mode: stub
Current DNS Server: 1.1.1.1
       DNS Servers: 1.1.1.1 8.8.8.8

Link 2 (enp1s0)
    Current Scopes: DNS
         Protocols: +DefaultRoute
       DNS Servers: 192.168.10.53
        DNS Domain: office.example

Link 5 (tun0)
    Current Scopes: DNS
         Protocols: -DefaultRoute
       DNS Servers: 10.60.0.53
        DNS Domain: ~internal.example ~svc.internal.example

Значення: VPN-лінк має домени маршрутизації (~internal.example) і не має DefaultRoute для DNS. Це добре: лише ці домени мають йти до VPN-DNS.

Рішення: Якщо внутрішні імена відпадають, перевірте, чи запитуване ім’я відповідає домену маршрутизації. Якщо це git.internal, а ваш домен маршрутизації — ~internal.example, ви знайшли невідповідність.

Завдання 4 — Протестувати внутрішнє ім’я через stub (що бачать застосунки)

cr0x@server:~$ resolvectl query git.internal.example
git.internal.example: 10.60.12.44                      -- link: tun0

-- Information acquired via protocol DNS in 21.4ms.
-- Data is authenticated: no

Значення: Клієнт маршрутизує цей запит через VPN-інтерфейс. Добре. Відповідь існує.

Рішення: Якщо застосунки все ще падають, ймовірно проблема на рівні кешування застосунку, неправильне ім’я або невідповідність TLS/SNI — не split-horizon.

Завдання 5 — Коли це не працює: відрізнити NXDOMAIN від SERVFAIL чи timeout

cr0x@server:~$ resolvectl query git.internal
git.internal: resolve call failed: 'git.internal' not found

Значення: Не знайдено в DNS-шляху, яким скористалися. Зазвичай це NXDOMAIN, але resolved абстрагує це.

Рішення: Запустіть dig проти конкретних серверів, щоб побачити, чи це справжній NXDOMAIN, чи політичний/маршрутаційний промах.

Завдання 6 — Опитати резолвер VPN напряму

cr0x@server:~$ dig +noall +answer @10.60.0.53 git.internal A
git.internal.            300     IN      A       10.60.12.44

Значення: Внутрішній резолвер знає це ім’я (і апекс зони, ймовірно, internal. або у вас делегований приватний TLD).

Рішення: Переконайтеся, що клієнт фактично маршрутує запити git.internal до VPN-резолвера. Якщо домени маршрутизації лише ~internal.example, це ім’я витече до публічного DNS і впаде.

Завдання 7 — Опитати публічний резолвер напряму (щоб побачити витік)

cr0x@server:~$ dig +noall +comments +answer @1.1.1.1 git.internal A
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 29144

Значення: Публічний DNS повертає NXDOMAIN. Якщо клієнти питають публічні резолвери про git.internal, вони впадуть і можуть кешувати невдачу.

Рішення: Виправте маршрутизацію split DNS (домени маршрутизації / умовне пересилання). Не створюйте публічні записи для внутрішніх імен як «швидке рішення». Це перетворює вашу внутрішню архітектуру на інтернет-архітектуру.

Завдання 8 — Перевірити search-домени і чому короткі імена — пастка

cr0x@server:~$ resolvectl domain
Link 2 (enp1s0): office.example
Link 5 (tun0): ~internal.example ~svc.internal.example

Значення: Лише office.example є search-доменом; VPN-домени — лише для маршрутизації. Запит на git може стати git.office.example, а не git.internal.example.

Рішення: Використовуйте повні доменні імена (FQDN) для critical-інструментів. Якщо люди повинні вводити короткі імена, визначте послідовні search-домени і прийміть операційну ціну цього рішення.

Завдання 9 — Очистити кеші (на клієнті) щоб прибрати застаріле NXDOMAIN

cr0x@server:~$ resolvectl flush-caches
cr0x@server:~$ resolvectl statistics
DNSSEC supported by current servers: no
Transactions: 1283
Cache size: 0
Cache hits: 0
Cache misses: 43

Значення: Кеш очищено. Якщо проблема «вирішилася» після цього, ви боролися з кешованою невдачею.

Рішення: Перевірте negative TTL у вашому SOA і знизьте його, якщо бачите довготривалі NXDOMAIN після змін.

Завдання 10 — Перевірити SOA авторитарної зони на negative TTL (тихий саботажник)

cr0x@server:~$ dig +noall +answer @10.60.0.53 internal.example SOA
internal.example.  3600 IN SOA ns1.internal.example. hostmaster.internal.example. 2025123001 3600 600 1209600 3600

Значення: Останнє поле в SOA (3600) — negative cache TTL у сучасній практиці. NXDOMAIN може триматися годину.

Рішення: Для зон з частими змінами встановіть нижчий negative TTL (наприклад, 60–300 секунд). Не ставте 0, якщо вам подобається підвищене навантаження запитів і нові режими відмов.

Завдання 11 — Підтвердити, що авторитарний сервер дає різні відповіді залежно від підмережі клієнта (BIND views)

cr0x@server:~$ dig +noall +answer @192.168.10.53 git.internal.example A
git.internal.example. 300 IN A 10.60.12.44
cr0x@server:~$ dig +noall +answer @192.168.10.53 git.internal.example A +subnet=203.0.113.10/32
git.internal.example. 300 IN A 198.51.100.44

Значення: Сервер виконує split-horizon поведінку (можливо через views або логіку, що реагує на ECS). Друга відповідь — тривожний знак, якщо вона не є навмисною.

Рішення: Якщо ви явно не використовуєте EDNS Client Subnet, вимкніть його на рекурсорах або переконайтеся, що views базуються на IP-адресі джерела, а не на ECS. ECS може витікати топологію і ускладнювати кешування.

Завдання 12 — Переконатися, що правильний DNS-сервер використовується з точки зору застосунку

cr0x@server:~$ getent ahosts git.internal.example
10.60.12.44     STREAM git.internal.example
10.60.12.44     DGRAM
10.60.12.44     RAW

Значення: NSS-резолюція (glibc) повертає ім’я. Якщо dig працює, а getent — ні, швидше за все у вас NSS-проблема, DNS в контейнері або інша бібліотека резолюції.

Рішення: Розглядайте getent як «те, що бачать більшість застосунків». Дебажте DNS на рівні NSS, а не лише з dig.

Завдання 13 — Підтвердити, що NetworkManager/VPN передали DNS правильно

cr0x@server:~$ nmcli dev show tun0 | sed -n '1,80p'
GENERAL.DEVICE:                         tun0
GENERAL.TYPE:                           tun
GENERAL.STATE:                          100 (connected)
IP4.DNS[1]:                             10.60.0.53
IP4.DOMAIN[1]:                          ~internal.example
IP4.DOMAIN[2]:                          ~svc.internal.example

Значення: Профіль VPN штовхає DNS-сервер і домени маршрутизації. Добре. Якщо цього немає, resolved не зможе робити розумні вибори.

Рішення: Виправте VPN-профіль (або його плагін), щоб штовхати правильні DNS і домени. Не «виправляйте» це хардкодом глобальних резолверів.

Завдання 14 — Інспектувати Unbound forwarding, якщо у вас локальний рекурсор

cr0x@server:~$ sudo unbound-control status
version: 1.19.0
verbosity: 1
threads: 2
modules: 2 [ subnetcache validator ]
uptime: 7261 seconds
options: control(ssl)

Значення: Unbound запущений і може бути компонентом, що виконує умовне пересилання.

Рішення: Переконайтеся, що Unbound має явні forward-zone або stub-zone для внутрішніх доменів; інакше він питатиме публічний інтернет і провалюватиметься (або витікатиме).

Завдання 15 — Побачити, який процес слухає порт 53 локально

cr0x@server:~$ sudo ss -lntup | grep ':53 '
udp   UNCONN 0      0         127.0.0.53%lo:53       0.0.0.0:*    users:(("systemd-resolve",pid=612,fd=15))

Значення: Лише stub-слухач на 127.0.0.53. Якщо ви очікували dnsmasq/unbound, їх немає.

Рішення: Вирішіть, чи хочете ви використовувати лише systemd-resolved, чи локальний рекурсор. Якщо додаєте останній, явно сплануйте володіння портом і інтеграцію.

Випадок №33: split-horizon DNS пішов не так

Цей випадок виникає в трьох варіаціях. Та сама картина, різні актори.

Варіант A: Внутрішня зона «працювала», поки не з’явилися ноутбуки з Debian 13

Протягом років клієнти користувалися простим правилом: внутрішній Wi‑Fi роздає внутрішні DNS-сервери через DHCP. Зовнішні мережі роздавали публічні резолвери. Люди користувалися VPN, але це мало значення, бо внутрішні імена були потрібні переважно на місці.

Потім віддалена робота стала нормою. VPN став стандартом. Хтось увімкнув split DNS «правильно», щоб лише внутрішні домени йшли до внутрішнього DNS, а публічний залишався публічним. Чудова ціль.

Але внутрішня схема імен була купою коротких імен і приватним TLD (напр., .internal), тоді як VPN штовхав домени маршрутизації для ~internal.example. Debian 13 не вгадав, що ви мали на увазі. Він маршрутизував git.internal до публічних резолверів. NXDOMAIN. Кешовано. Аутейдж.

Варіант B: Авторитарний сервер був гаразд; політика клієнта була неправильною

Адміни довели, що запис існує, опитавши внутрішній DNS-сервер напряму. Усі кивають головами. Потім вони «виправляють» клієнта, додавши внутрішні DNS-сервери як глобальні резолвери.

Тепер публічний DNS став повільним. Гірше — внутрішні резолвери почали отримувати запити для публічних доменів, які вони не можуть ефективно резолювати (або не мають права). Хтось додає форвард для «.» до публічного резолвера. Вітаємо, ви побудували ланцюжок рекурсії, що витікає внутрішні запити і додає латентність.

Варіант C: Шлях резолвера був правильний; кешування зробило його схожим на помилку

Днем раніше зміна зони тимчасово видалила запис. Резолвери закешували NXDOMAIN з годинним negative TTL. Запис повернувся через 5 хвилин. Люди ще годину бачили відмови.

Тоді хтось запускає fleet-wide скрипт «очистити DNS кеши» з sudo. Це допомогло. Також це стало новим ритуалом. Саме так формуються племінні DNS-релігії.

Три робочі архітектури (і яку обрати)

Архітектура 1: Авторитарний split-horizon через BIND views (класична)

Як працює: Той самий авторитарний сервер відповідає внутрішнім клієнтам з internal view, а зовнішнім — з external view. Клієнти просто питають «DNS-сервер», а сервер обирає відповідь за адресою джерела.

Плюси: Централізований контроль; клієнти лишаються «дурними»; старі пристрої працюють.

Мінуси: Політика, що базується на IP-адресі джерела, ламається, коли клієнти приходять через NAT або спільні резолвери; складніше в мультихмарі; помилки катастрофічні, бо авторитет — джерело істини.

Коли обирати: Коли мережеві межі чіткі, ви контролюєте рекурсію і можете надійно ідентифікувати клієнтські мережі.

Архітектура 2: Один авторитарний вигляд, split робиться в рекурсії через conditional forwarding (сучасне підприємство)

Як працює: Авторитарні зони чисті й консистентні. Split реалізується в рекурсивних резолверах: внутрішні резолвери знають, як зв’язатися з внутрішніми авторитарними серверами; публічні — ні. Клієнти використовують внутрішні рекурсори лише коли потрібно (VPN/на майданчику).

Плюси: Чистіша архітектура DNS; простіша історія з DNSSEC; уникає «двох джерел істини».

Мінуси: Потребує правильної маршрутизації клієнта (домени маршрутизації). Якщо ця частина неправильна, внутрішні запити витікають до публічних резолверів і падають.

Коли обирати: Коли маєте змішані мережі (офіс, дім, VPN) і хочете детерміновану поведінку без покладання на IP-джерело авторитарного сервера.

Архітектура 3: Клієнтська split DNS через routing domains systemd-resolved (найкраще для Debian 13 кінцевих точок)

Як працює: Клієнт має кілька DNS-серверів. Для конкретних доменів запити йдуть до DNS від VPN. Все інше — до публічного DNS. Саме в цьому systemd-resolved добре працює, якщо його правильно налаштувати.

Плюси: Запобігає витіканню; підтримує множинні мережі; явне і відладжуване через resolvectl.

Мінуси: Потрібна дисципліна щодо найменувань доменів. Приватні TLD та короткі імена помножують біль. Деякі застосунки оминають stub.

Коли обирати: Коли у вас ноутбуки, VPN і ви дбаєте, щоб не ламати публічний інтернет на кінцевих машинах.

Якщо у вас Debian 13 на кінцевих точках, Архітектура 3 є прагматичним вибором у більшості випадків. Якщо ви керуєте серверами в стабільній мережі, Архітектура 2 зазвичай чистіша. Архітектура 1 все ще дієва, але легко помилитися у великому масштабі.

Конкретні конфігурації, що вирішують проблему (без «просто захардкодити 8.8.8.8»)

Опція A: Виправити per-link routing domains на Debian 13 (systemd-resolved)

Ключова ідея: зробіть так, щоб внутрішні суфікси маршрутизувалися до DNS VPN, а глобальний DNS залишався для решти.

VPN, керований NetworkManager (рекомендовано на кінцевих точках)

Переконайтеся, що ваш VPN-зв’язок встановлює DNS і домени маршрутизації. Можна зробити це в профілі з’єднання:

cr0x@server:~$ nmcli connection modify corp-vpn ipv4.dns "10.60.0.53 10.60.0.54"
cr0x@server:~$ nmcli connection modify corp-vpn ipv4.dns-search "~internal.example ~svc.internal.example"
cr0x@server:~$ nmcli connection up corp-vpn
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/12)

Значення: Тильда (~) позначає домени лише для маршрутизації, а не для пошукових доменів. Саме цього ви хочете: лише запити з цими суфіксами йдуть до VPN-DNS.

Рішення: Якщо ви покладаєтесь на git.internal (без крапки в кінці), зупиніться і перейменуйте або забезпечте FQDN. Домени маршрутизації співпадають за суфіксами, а не за інтуїцією.

systemd-networkd (сервери або мінімальні інсталяції)

Для VPN-інтерфейсу ви можете встановити DNS та домени в .network-юнитах. Приклад:

cr0x@server:~$ sudo sed -n '1,120p' /etc/systemd/network/50-tun0.network
[Match]
Name=tun0

[Network]
DNS=10.60.0.53
DNS=10.60.0.54
Domains=~internal.example ~svc.internal.example

Значення: Це прив’язує домени маршрутизації до VPN-лінку. Публічний DNS залишається глобальним.

Рішення: Перезапускайте мережеві сервіси обережно. На віддалених системах робіть це в maintenance-вікні або з консольною доступністю.

Опція B: Conditional forwarding в Unbound (добре для локального «розумного» резолвера)

Якщо ви хочете локальний рекурсивний резолвер, що форвардить внутрішні зони до внутрішніх DNS, а решту резолює нормально, Unbound — простий і відмінний вибір.

Приклад файлу /etc/unbound/unbound.conf.d/forward-internal.conf:

cr0x@server:~$ sudo sed -n '1,120p' /etc/unbound/unbound.conf.d/forward-internal.conf
forward-zone:
  name: "internal.example."
  forward-addr: 10.60.0.53
  forward-addr: 10.60.0.54

forward-zone:
  name: "svc.internal.example."
  forward-addr: 10.60.0.53
  forward-addr: 10.60.0.54

Значення: Запити для цих зон йдуть до внутрішніх резолверів; усе інше резолюється через root hints або ваші upstream-и.

Рішення: Якщо ви робите це на кінцевих точках, все одно віддавайте перевагу per-link routing domains, щоб уникнути витікань внутрішніх імен поза VPN.

Перезапустіть і перевірте:

cr0x@server:~$ sudo systemctl restart unbound
cr0x@server:~$ sudo unbound-control reload
ok

Опція C: BIND views на авторитарному (корисно, але обережно)

Views дають змогу: подавати різні файли зон різним клієнтам. Вони потужні, але також чудово приховують баги до третьої ранку.

Спрощена структура для named.conf:

cr0x@server:~$ sudo sed -n '1,200p' /etc/bind/named.conf.local
acl "internal-nets" { 10.0.0.0/8; 192.168.0.0/16; 172.16.0.0/12; };

view "internal" {
  match-clients { internal-nets; };
  recursion no;

  zone "internal.example" {
    type master;
    file "/etc/bind/zones/db.internal.example.internal";
  };
};

view "external" {
  match-clients { any; };
  recursion no;

  zone "internal.example" {
    type master;
    file "/etc/bind/zones/db.internal.example.external";
  };
};

Значення: Клієнти з internal-nets бачать внутрішні відповіді; інші — зовнішню зону (яка може бути порожньою або NXDOMAIN-подібною).

Рішення: Переконайтеся, що моніторинг опитує обидва view-и. Більшість організацій моніторять лише зсередини, а потім дивуються, чому партнери не можуть нікого резолювати.

Опція D: dnsmasq для невеликого сайту або лабораторії

dnsmasq може робити split forwarding і кешування в одному легкому демоні. Це класно, поки ви випадково не зробите свій ноутбук DNS-сервером для всього кафе.

Приклад:

cr0x@server:~$ sudo sed -n '1,120p' /etc/dnsmasq.d/split-dns.conf
server=/internal.example/10.60.0.53
server=/svc.internal.example/10.60.0.53
no-resolv
server=1.1.1.1
server=8.8.8.8
cache-size=10000

Значення: Внутрішні зони форвардяться до внутрішнього DNS, усе інше — до публічних резолверів.

Рішення: Якщо використовуєте це, обмежте binding по інтерфейсу і налаштуйте фаєрвол. «Випадковий open resolver» — кар’єрообмежувальний хід.

Жарт №2: Запуск open resolver — це як залишити машину відчиненою з табличкою «безкоштовні поїздки» — хтось прийме пропозицію.

Типові помилки (симптом → корінь проблеми → виправлення)

1) Внутрішні імена падають лише при підключенні до VPN

Симптом: Підключено VPN; git.internal.example не резолюється, але публічні сайти працюють.

Корінь проблеми: DNS-сервер VPN налаштований, але домени маршрутизації відсутні. Запити для внутрішніх доменів все ще йдуть до публічних резолверів.

Виправлення: Штовхайте домени маршрутизації через профіль VPN (~internal.example) і перевіряйте resolvectl status. Не ставте VPN-DNS як глобальний за замовчуванням, якщо ви цього не маєте на увазі.

2) Публічний DNS стає повільним або нестабільним після «виправлення» внутрішнього DNS

Симптом: Після додавання внутрішніх DNS-серверів глобально, серфінг сповільнився; періодичні відмови резолюції публічних доменів.

Корінь проблеми: Внутрішні резолвери не призначені/не мають права рекурсувати в інтернет, або вони форвардять до іншого повільного резолвера. Ви вставили непотрібний проміжний хоп.

Виправлення: Використовуйте split маршрутизацію: внутрішні зони — до внутрішнього DNS, усе інше — до публічного DNS або до коректного корпоративного рекурора.

3) Працює з dig, падає в застосунках

Симптом: dig повертає правильний A-запис, але curl/git/apt не можуть резолювати.

Корінь проблеми: Застосунок використовує інший шлях резолюції (DNS в контейнері, статичний resolv.conf в chroot, NSS misconfig), або він закешував помилку.

Виправлення: Використовуйте getent ahosts, перевірте /etc/resolv.conf в контейнері, подивіться resolvectl і очистіть кеші. Переконайтеся, що застосунок не прив’язаний до кастомного DNS.

4) Працює для A, але падає для AAAA (або навпаки)

Симптом: IPv4 працює; IPv6-резолюція веде до таймаутів або неправильних відповідей.

Корінь проблеми: Відсутні AAAA записи всередині, зламані припущення DNS64/NAT64 або фаєрвол блокує більші DNS-відповіді, коли AAAA створює більший набір даних.

Виправлення: Визначте, чи підтримуєте IPv6 внутрішньо. Якщо так — публікуйте AAAA і переконайтеся, що MTU/фаєрвол обробляє EDNS0. Якщо ні — розгляньте фільтрацію AAAA на рекурсорі (останній засіб) і будьте явними.

5) Періодичні SERVFAIL від валідуючих резолверів

Симптом: Деякі клієнти отримують SERVFAIL; інші в порядку; авторитарний сервер здається відповідає.

Корінь проблеми: DNSSEC валідація падає через невідповідне підписування, зламану ланку або split-horizon, що повертає різні DNSSEC-матеріали.

Виправлення: Або правильно підписуйте внутрішні зони й керуйте довірчими анкерами, або вимкніть валідацію для тих внутрішніх зон на валідуючому резолвері. Напів-DNSSEC гірший, ніж його відсутність.

6) Короткі внутрішні імена перестали працювати після переходу на routing domains

Симптом: Люди вводили git і це працювало; тепер — ні.

Корінь проблеми: Search-домени змінилися або зникли; домени маршрутизації не розширюють короткі імена.

Виправлення: Переведіть людей на FQDN для критичних сервісів. Якщо короткі імена необхідні, додайте search-домени усвідомлено і протестуйте колізії (наприклад, git.office.example vs git.internal.example).

7) «Виправлення» призводить до витоку внутрішніх імен у публічні логи DNS

Симптом: Звіти безпеки показують внутрішні імена, які бачать публічні резолвери; підняли питання приватності.

Корінь проблеми: Клієнти питають публічні резолвери для внутрішніх суфіксів, бо split DNS маршрутизація відсутня або неправильна.

Виправлення: Нав’язуйте домени маршрутизації і використовуйте внутрішні рекурсори, які відмовляються форвардити внутрішні зони в публічний інтернет. Моніторте витоки, опитуючи публічні резолвери з контрольованих клієнтів і перевіряючи NXDOMAIN-патерни.

Три міні-історії з корпоративного світу (анонімізовано, правдоподібно, технічно точно)

Історія 1: Аутейдж через неправильне припущення

Вони були середньою компанією з чистою внутрішньою DNS-структурою: corp.example для користувачів, svc.corp.example для сервісів. Команда VPN додала split DNS і штовхала ~svc.corp.example на кінцеві пристрої. У тикеті все виглядало правильно.

Неправильне припущення було тонким: вважали, що всі внутрішні сервіси живуть під svc.corp.example. Насправді один спадковий сервіс був під jira.corp.example, а не jira.svc.corp.example. Користувачі не знали; у них були закладки.

Коли люди підключились з дому, jira.corp.example пішов у публічний DNS, повернув NXDOMAIN і це закешувалося. Декілька користувачів спробували пізніше з офісу і все ще бачили NXDOMAIN, бо локальні кеші були отруєні «не існує».

Інженери витратили ранок на перевірку сервера Jira і його балансувальника. Обидва були в порядку. Командир інциденту нарешті вимагав єдине відтворення з явним вказанням резолверів. Тоді невідповідність доменів маршрутизації і реальної схеми імен стала очевидною.

Виправлення було нудним: додали ~corp.example до доменів маршрутизації VPN і опублікували невеликий внутрішній «DNS-контракт»: які суфікси внутрішні, які публічні і хто відповідає за зміни. Велика зміна була культурною: більше ніяких «внутрішні імена очевидні», бо вони не очевидні.

Історія 2: Оптимізація, що відбилася боком

Інша компанія мала два внутрішні рекурсори і публічний upstream. Хтось помітив сплески латентності DNS і вирішив «оптимізувати», встановивши внутрішні рекурсори як глобальні резолвери скрізь, включно з ноутбуками поза VPN. Ідея була така: внутрішні рекурсори мають кращі кеші і можуть форвардити в інтернет.

Працювало, доки не перестало. Ноутбуки вдома почали питати внутрішні резолвери через VPN-канал, який ще не був підключений, або через шлях, що фільтрував UDP-фрагменти. DNS-запити таймаутили, потім перепадали на TCP, потім викликали повільні fallback-и в застосунках. Сторінки завантажувалися, ніби 2003 рік.

Операційна команда додала другу ланку: локальний dnsmasq на ноутбуках для агресивнішого кешування. Це додало ще один кеш, ще один набір таймаутів і новий режим відмов: коли VPN піднімався, dnsmasq інколи не підхоплював оновлені внутрішні сервери швидко. Користувачі бачили «після перезавантаження працює», що є IT-еквівалентом «вимкни і ввімкни знову», тільки тепер це політика.

Вони відкотили зміни і використали per-domain routing: внутрішні суфікси — до внутрішнього DNS лише коли в VPN. Латентність зменшилась, бо прибрали неправильний маршрут. Урок неприємний: оптимізація продуктивності DNS часто просто переміщує час очікування в менше видиме місце.

Історія 3: Нудна, але правильна практика, що врятувала

У регульованій фірмі зміни DNS вимагали запиту на зміну з pre-flight і post-flight чеклістом. Усі скаржилися, що це повільно. Потім під час злиття сталася split-horizon проблема: дві мережі, два набори внутрішніх зон і VPN-міст.

Що врятувало — не технічна фіча. Це те, що команда DNS мала стандартний «сина» хост поза кожною мережею, який безперервно опитував невеликий список внутрішніх і зовнішніх імен проти призначених резолверів. Він також опитував зсередини, щоб переконатися, що внутрішні view-и лишаються внутрішніми. Кожна перевірка записувала NXDOMAIN/SERVFAIL і TTL.

Коли інцидент почався, не сперечалися, чи DNS зламаний. Сина показав: внутрішні імена питалися на публічних резолверах з VPN-підключених кінцевих точок. Це звело до вузького кола — домени маршрутизації або конфігурацію VPN, а не авторитарні сервери.

Команда VPN натиснула новий профіль, що видалив домени маршрутизації і замінив їх на пошукові домени. Сина спіймав це за хвилини. Відкат був швидким, бо попередній профіль був зафіксований і протестований. Всі повернулися до скарг на бюрократію, але продакшн залишився живим.

Чеклісти / поетапний план (робіть це в такому порядку)

Крок 0 — Визначте, що означає «внутрішній»

  • Перерахуйте внутрішні DNS-суфікси (наприклад: internal.example, svc.internal.example).
  • Забороніть або поступово виведіть приватні TLD, які ви не контролюєте (наприклад: .internal, .corp), якщо у вас немає чіткої документованої політики.
  • Визначте, чи підтримуватимете короткі імена. Якщо так — задайте поведінку search-доменів і прийміть колізії.

Крок 1 — Перевірте авторитарні дані

  • Опитайте SOA і NS для кожної внутрішньої зони зсередини мережі.
  • Підтвердіть TTL і negative TTL значення.
  • Якщо split реалізовано через views на авторитарному, протестуйте обидва view-и з відомих підмереж.

Крок 2 — Перевірте поведінку рекурсії

  • Переконайтеся, що внутрішні резолвери можуть надійно дістатися авторитарних серверів.
  • Переконайтеся, що внутрішні резолвери не стали випадковими open resolvers.
  • Налаштуйте conditional forwarding/stub-зони явно, а не через undocumented magic.

Крок 3 — Виправте клієнти по-справжньому, як у Debian 13

  • Використовуйте resolvectl status, щоб підтвердити per-link DNS-сервери.
  • Переконайтеся, що VPN-лінки несуть домени маршрутизації (~suffix), а не лише search-домени.
  • Залишайте глобальний DNS для публічної резолюції; не направляйте все через VPN, якщо політика цього не вимагає.

Крок 4 — Поставте запобіжники

  • Моніторинг з кількох точок спостереження (всередині, зовні, VPN) з явними резолверами.
  • Алерти на раптові сплески NXDOMAIN для внутрішніх суфіксів.
  • Runbooks, що починаються з «який шлях резолюції використовується», а не з «перезапустіть bind».

Крок 5 — Поясніть реальність кешування

  • Скажіть зацікавленим, що зміни DNS можуть зайняти до TTL + negative TTL, щоб розійтися по кешах.
  • Майте контрольовану процедуру очищення кешів для кінцевих точок (не «всі перезавантажте»).
  • Тримайте TTL-і помірними: низькі для змін, високі — щоб уникнути штормів запитів.

FAQ

1) Чому це почало відбуватися після переходу на Debian 13?

Тому що клієнтський стек резолюції став більш політично-залежним. systemd-resolved підтримує per-link DNS і домени маршрутизації, і він не «вгадає», що git.internal належить вашому VPN, якщо ви йому цього явно не скажете.

2) Чи варто відключити systemd-resolved і повернутися до простого /etc/resolv.conf?

Тільки якщо ви готові замінити його можливості маршрутизації по лінку чимось еквівалентним. Для ноутбуків і split DNS через VPN відключення зазвичай робить речі гіршими, а не кращими.

3) У чому різниця між search-доменом і routing domain (~domain)?

Search-домен розширює короткі імена (запит git стає git.office.example). Routing domain вказує resolved, який DNS-сервер використовувати для імен в межах цього суфікса. Routing domains не розширюють короткі імена.

4) Чому NXDOMAIN «липкий» навіть після додавання запису?

Негативне кешування. Резолвери кешують «не існує» на підставі negative TTL в SOA зони. Очистіть кеші, щоб підтвердити, потім відрегулюйте negative TTL, якщо у вас висока частота змін.

5) Чи добре використовувати .internal або .corp як приватний TLD?

Працює, поки не перестане. Безпечніша практика — використовувати субдомени домену, яким ви володієте (наприклад, internal.example). Приватні TLD ускладнюють маршрутизацію split DNS, виявлення витоків і сумісність.

6) Чому dig працює, а мій браузер — ні?

dig питає саме те, що ви йому сказали. Ваш браузер використовує системний резолвер, може використовувати DoH або бути в межах контейнера. Перевірте getent, налаштування DoH у браузері і DNS-конфігурацію контейнера.

7) Чи можна вирішити це, розмістивши внутрішні записи в публічному DNS?

Можете, але зазвичай це неправильна торгівля. Ви витікаєте внутрішню топологію, ускладнюєте контроль доступу і ризикуєте випадково виставити сервіси. Виправляйте маршрутизацію і умовне пересилання замість цього.

8) Чи потрібен DNSSEC для внутрішніх зон?

Не обов’язково, але потрібен згуртований план. Валідуючі резолвери плюс непослідовний внутрішній DNSSEC — рецепт SERVFAIL. Або правильно підписуйте і керуйте довірчими анкерами, або вимкніть валідацію для внутрішніх зон на резолвері.

9) Який чистіший спосіб підтримувати і локальних, і VPN-користувачів?

Використовуйте FQDN під контролем вами доменом (наприклад, svc.internal.example), штовхайте домени маршрутизації через VPN і тримайте публічний DNS відокремленим. Тестуйте з трьох точок: на місці, у VPN і зовні.

Висновок: наступні кроки, які не підведуть

Split-horizon DNS ламається, коли ви покладаєтесь на припущену поведінку. Стек резолюції в Debian 13 є явним: потрібні явні домени маршрутизації, явні правила пересилання і явні конвенції найменувань. Це хороша новина — означає, що ви можете зробити поведінку детермінованою.

Зробіть наступне:

  1. Виберіть внутрішні суфікси і стандартизуйтесь під доменом, яким ви володієте.
  2. На Debian 13 кінцевих точках перевірте per-link DNS і домени маршрутизації за допомогою resolvectl status.
  3. Виправте VPN-профілі, щоб штовхати домени маршрутизації у форматі ~internal.example, а не лише DNS-сервери.
  4. Переконайтеся, що авторитарні SOA negative TTL не саботують ваш час відновлення.
  5. Впровадьте моніторинг як мінімум з двох вантач-пойнтів і алерти на витік внутрішніх імен у публічні резолвери.

Потім припиніть «виправляти DNS» шляхом хардкодингу глобальних резолверів. Це не виправлення. Це новий аутейдж з кращим маркетингом.

← Попередня
Proxmox: «Permission Denied» у Datacenter — ролі й ACL налаштовано правильно
Наступна →
Docker мережі: bridge vs host vs macvlan — оберіть те, що не підведе пізніше

Залишити коментар