Arch Linux: DNS-детектив – VPN, systemd-resolved та Unbound
0 (0)

Автор |  21/05/2026
Click to rate this post!
[Total: 0 Average: 0]

Давно гемороївся  з проблемою доступу до AWS EKS із офісу – нарешті психанув, і розібрався 🙂

В чому проблема: є AWS EKS кластер з Public і Private endpoint для API.

При роботі з офісного ноутбука іноді запити до нього проходять нормально – а іноді відвалюються з помилкою “i/o timeout“:

$ kk get pod
[...] Get \"https://F07***D78.gr7.us-east-1.eks.amazonaws.com/api?timeout=32s\": dial tcp 10.0.64.9:443: i/o timeout"
...

Поїхали копати – бо тут нюанси і з DNS, і з роутами в мережі.

AWS VPC DNS та мої VPN

У моєму випадку для EKS увімкнено і Public і Private endpoints – тому при DNS resolution працює split-horizon DNS:

  • на запит з “публічного інтернету” AWS VPC DNS поверне Public IP
  • на запит з середини VPC – то буде Private IP

Далі: я маю два активних VPN-підключення + офісний WiFi, а проблема починається, коли я додаю AWS VPC DNS до resolv.conf, бо:

  • є проектний Pritunl/OpenVPN і є домени в AWS, які мають резолвитись через 10.0.0.2 – AWS VPC DNS
  • є мій власний WireGuard і домени які мають резолвитись з мого домашнього MikroTik через 10.100.0.1 (див. MikroTik: налаштування WireGuard та підключення Linux peers)
  • і є просто публічні DNS-зони, які мають резолвитись через 1.1.1.1

В resolv.conf це виглядає так:

nameserver 1.1.1.1      # CloudFlare DNS, returns EKS Endpoint Public IP
nameserver 10.100.0.1   # my MikroTik with WireGuard, returns EKS Endpoint Public IP
nameserver 10.0.0.2     # AWS VPC DNS via OpenVPN, returns EKS Endpoint Private IP EKS 10.0.64.9

Файл менеджиться з openresolv, який запускається WireGuard при старті тунелю – WireGuard прописує свої DNS:

$ sudo cat /etc/wireguard/wg0.conf
...
DNS = 10.100.0.1, 10.0.0.2, 192.168.0.1
...

В помилці з timeout бачимо, що запит до F07***D78.gr7.us-east-1.eks.amazonaws.com йде на IP 10.0.64.9 – тобто DNS resolution був через OpenVPN і AWS VPC DNS 10.0.0.2.

Linux DNS та systemd-resolved

Перевіряємо хто в системі взагалі відповідає за DNS – грепаємо файл /etc/nsswitch.conf:

$ grep hosts /etc/nsswitch.conf
hosts: mymachines resolve [!UNAVAIL=return] files myhostname dns

Тут опція resolve – це використання модулю nss-resolve через D-Bus до systemd-resolved.

І він йде першим, до параметрів files (nss-files та /etc/hosts) та dns (модуль nss-dns і “класичний” glibc DNS resolver) – тому спочатку запити йдуть до systemd-resolved.

Див. Domain name resolution на Arch Wiki.

systemd-resolved, DNS resolution та network interfaces

Тепер цікава частина – як саме systemd-resolved виконує DNS resolution.

systemd-resolved використовує openresolv – дивимось його параметри:

$ resolvectl status
Global
           Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
    resolv.conf mode: foreign
  Current DNS Server: 10.0.0.2
         DNS Servers: 10.0.0.2 10.100.0.1 192.168.0.1
...

Далі перевіримо що відбувається в системі – включаємо debug log для openresolv:

$ sudo resolvectl log-level debug

Потім в одному вікні відкриваємо логи:

$ sudo journalctl -u systemd-resolved -f | grep "F07***D78.gr7.us-east-1.eks.amazonaws.com"

Робимо kubectl get pod – і в логах бачимо:

...
May 20 12:06:39 setevoy-office systemd-resolved[698]: varlink-28-28: Received message: {"method":"io.systemd.Resolve.ResolveHostname","parameters":{"name":"F07***D78.gr7.us-east-1.eks.amazonaws.com","flags":0,"ifindex":0}}
...
May 20 12:06:39 setevoy-office systemd-resolved[698]: varlink-28-28: Sending message: {"parameters":{"addresses":[{"ifindex":6,"family":2,"address":[10,0,64,9]},{"ifindex":6,"family":2,"address":[10,0,65,205]}],"name":"F07***D78.gr7.us-east-1.eks.amazonaws.com","flags":1048577}}

Тут:

  • Received message: запит прийшов з параметром "ifindex":0 – “пофіг, де шукати
  • Sending message: відповідь повернулась через "ifindex":6 – це tun0, openVPN і AWS VPC DNS

Перевіряємо інтерфейси:

$ ip -o link | awk -F': ' '{print $1, $2}'
1 lo
2 enp0s31f6
4 wlan0
5 enp0s13f0u3u4u4
6 tun0
...

"ifindex":6 – це інтерфейс tun0, робочий OpenVPN, і результат, отриманий від AWS VPC DNS – "address":[10,0,64,9], бо AWS  VPC DNS буде повертати приватну адресу.

Повторюємо запит – і тепер результат буде інший:

...
varlink-28-28: Sending message: {"parameters":{"addresses":[{"ifindex":4,"family":2,"address":[44,216,7,46]},{"ifindex":4,"family":2,"address":[3,***,***,161]}],"name":"F07***D78.gr7.us-east-1.eks.amazonaws.com","flags":8388609}}
...

Відповідь вже з ifindex":4wlan0, і маємо публічний IP.

Чому – бо в тому ж логу бачимо:

...
Firing regular transaction 49587 ... IN A> scope dns on */* 
Firing regular transaction 59798 ... IN A> scope dns on wlan0/*
...

Тут перший запис – це запит через global pool, на всі сервери  в ньому:

 $ resolvectl status
Global
           Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
    resolv.conf mode: foreign
  Current DNS Server: 10.0.0.2
         DNS Servers: 10.0.0.2 10.100.0.1 192.168.0.1
...

А результат повернеться від того, хто перший відповідає, див. systemd-resolved.service:

If lookups are routed to multiple interfaces, the first successful response is returned

І в цьому випадку це був wlan0:

...
Added positive ... cache entry ... on wlan0/INET/10.0.0.1
...

Раз запит через wlan0 – то і у відповідь від AWS DNS для EKS endpoint отримали публічний IP.

А при першій спробі це був tun0:

...
Added positive ... cache entry ... on tun0/INET/10.0.0.2
...

І у відповідь отримали приватний IP 10.0.64.9.

Отже:

  • systemd-resolved робить запити до всіх доступних DNS
  • повертає результат від того, хто перший відповів
  • якщо запит від wlan0, офісної мережі – отримуємо публічний IP, і підключення проходить
  • якщо запит від tun0, OpenVPN і AWS VPC DNS – отримуємо приватний IP, і підключення падає з таймаутом

Не забуваємо повернути лог info:

$ sudo resolvectl log-level info

Тепер переходимо до маршрутизації – чому саме підключення падає з timeout error?

VPN та Linux IP routes mess

Глянемо що в маршрутах на робочому ноуті:

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.0.1        0.0.0.0         UG    600    0        0 wlan0
10.0.0.0        0.0.0.0         255.255.255.0   U     600    0        0 wlan0
10.0.0.2        172.16.0.1      255.255.255.255 UGH   0      0        0 tun0
10.0.6.162      172.16.0.1      255.255.255.255 UGH   0      0        0 tun0
10.0.32.0       172.16.0.1      255.255.240.0   UG    0      0        0 tun0
10.0.48.0       172.16.0.1      255.255.240.0   UG    0      0        0 tun0
10.0.66.0       172.16.0.1      255.255.255.0   UG    0      0        0 tun0
10.0.67.0       172.16.0.1      255.255.255.0   UG    0      0        0 tun0
10.100.0.0      0.0.0.0         255.255.255.0   U     0      0        0 wg0
...

Тут:

  • 10.0.0.0 через wlan0: бо це офісна мережа і у нас тут MacMini до яких треба доступ, ну і доступ в інтернет
  • 10.0.0.2, 10.0.32.0, 10.0.48.0 etc – через tun0: це AWS VPC Private Subnets – сюди роутятся запити через робочий OpenVPN для доступу до AWS RDS і інших приватних ресурсів
  • 10.100.0.0 через wg0: це мережа мого WireGuard через MikroTik для доступу в мої домашні мережі

А тепер – де виникає проблема: коли EKS ендпоінт резолвиться через AWS VPC DNS 10.0.0.2 – отримуємо приватну адресу 10.0.64.9.

Але для неї нема окремого роуту через OpenVPN – і вона роутиться через 10.0.0.1, офісний роутер і публічний інтернет:

$ kk get pod
[...] Get \"https://F07***D78.gr7.us-east-1.eks.amazonaws.com/api?timeout=32s\": dial tcp 10.0.64.9:443: i/o timeout"
...

Перевіряємо сам маршрут – бачимо, що піде через via 10.0.0.1, офісний роутер, а не  tun0 і OpenVPN:

$ ip route get 10.0.64.9
10.0.64.9 via 10.0.0.1 dev wlan0 src 10.0.0.133 uid 1000 
    cache 

Ну і traceroute (btw, див. Unix: что такое traceroute – писав ще у 2016,але досі актуально):

$ traceroute 10.0.64.9
traceroute to 10.0.64.9 (10.0.64.9), 30 hops max, 60 byte packets
 1  office.example.dev (10.0.0.1)  14.261 ms  14.614 ms  14.592 ms
 2  * * *
 3  * * *
...

І звісно, що запит на адресу в приватному сабнеті відправлений через публічний інтернет тупо падає.

Варіанти вирішення

Є кілька опцій – або просто додати 10.0.64.9 до OpenVPN, або налаштувати split-DNS – і правильно резолвити домени:

  • просто додати 10.0.64.9 до OpenVPN – тоді він створить роут через tun0 на додачу до вже існуючих
  • можна налаштувати split-DNS через systemd-resolved
  • можна налаштувати split-DNS  на локальному Unbound чи dnsmasq – і переключити всі DNS запити на нього

Варіант з 10.0.64.9 на OpenVPN – костиль.

Note: вже коли дописав весь пост – згадав, що Control Plane EKS живе у власних VPC Subnets, і я міг тупо додати їх до OpenVPN так само, як це роблено для RDS, але вже ок – вийшло все одно цікаво 🙂

Рішення зі split-DNS через systemd-resolved виглядає якось геморно.

А Unbound я вже крутив на FreeBSD для домашнього NAS (див. FreeBSD: Home NAS, part 4 – локальний DNS з Unbound), конфіг простий і зрозумілий, ще і викинути зі схеми systemd-resolved з його складностями – нормальний варіант.

Хоча dnsmasq як рішення для ноутбука може був би кращий – бо ще простіший конфіг, але мені Unbound дуже зайшов – тому зробив з ним.

Arch Linux та Unbound

Встановлюємо сам пакет:

$ sudo pacman -S unbound

Що треба зробити:

  • всі запити до compute.internal (AWS EC2 etc) – роутити через OpenVPN і AWS VPC DNS
  • всі запити до ops.example.com – теж, бо там у нас записи для AWS RDS типу db.prod.ops.example.com
  • всі запити до grafana.net.setevoy – роутити через MikroTik, бо це моя локальна зона для домашніх хостів
  • решту – відправляємо на 1.1.1.1 та 8.8.8.8

Пишемо файл /etc/unbound/unbound.conf, описуємо три forward-zone з власними DNS і одну з публічними DNS:

server:
    interface: 127.0.0.1
    access-control: 127.0.0.0/8 allow
    do-ip6: no
    hide-identity: yes
    hide-version: yes
    prefetch: yes

# local homelab via MikroTik
forward-zone:
    name: "setevoy."
    forward-addr: 10.100.0.1
    forward-addr: 192.168.0.1

forward-zone:
    name: "compute.internal."
    forward-addr: 10.0.0.2

forward-zone:
    name: "ops.example.co."
    forward-addr: 10.0.0.2

# everything else
forward-zone:
    name: "."
    forward-addr: 1.1.1.1
    forward-addr: 8.8.8.8

Перевіряємо синтаксис:

$ sudo unbound-checkconf
unbound-checkconf: no errors in /etc/unbound/unbound.conf

Відключення systemd-resolved

В пості Arch Linux: WireGuard Peer для підключення до MikroTik описував рішення іншої проблеми, і там для NetworkManager додавав dns=systemd-resolved.

Якщо є – прибираємо в /etc/NetworkManager/NetworkManager.conf, задаємо просто dns=none:

...

[main]
dns=none

Відключаємо systemd-resolved (тут інтернет відвалиться – бо нема куди слати DNS):

$ sudo systemctl disable --now systemd-resolved systemd-resolved-monitor.socket systemd-resolved-varlink.socket

Перезапускаємо NetworkManager:

$ sudo systemctl restart NetworkManager

Перевіряємо 53 порт – якщо systemd-resolve ще живий, значить щось тригерить його запуск:

$ sudo ss -tulpn | grep ':53'
...
tcp   LISTEN 0      4096   127.0.0.53%lo:53         0.0.0.0:*    users:(("systemd-resolve",pid=723720,fd=25))
tcp   LISTEN 0      4096      127.0.0.54:53         0.0.0.0:*    users:(("systemd-resolve",pid=723720,fd=27))

Можна жорстко заблокувати запуск через systemctl mask:

$ sudo systemctl mask systemd-resolved
$ sudo systemctl stop systemd-resolved

Ще раз перевіряємо порти, і якщо на порту 53 вже нема нікого – то запускаємо unbound.service:

$ sudo systemctl stop systemd-resolved
$ sudo systemctl enable --now unbound
Created symlink '/etc/systemd/system/multi-user.target.wants/unbound.service' → '/usr/lib/systemd/system/unbound.service'.

Перевіряємо порт:

$ sudo ss -tulpn | grep ':53\b'
udp   UNCONN 0      0          127.0.0.1:53         0.0.0.0:*    users:(("unbound",pid=727532,fd=3))
tcp   LISTEN 0      256        127.0.0.1:53         0.0.0.0:*    users:(("unbound",pid=727532,fd=4))

Редагуємо /etc/resolv.conf – задаємо всі DNS через нього:

nameserver 127.0.0.1

І пробуємо щось публічне:

$ dig google.com +short
216.58.207.14

Потім ендпоінт EKS – має повернути публічні IP:

$ dig F07***D78.gr7.us-east-1.eks.amazonaws.com +short
3.***.***.161
44.***.***.46

Пробуємо RDS – має повернути приватні IP із пулу VPC:

$ dig prod.db.kraken.ops.example.com +short
kraken-ops-rds-prod.***.us-east-1.rds.amazonaws.com.
10.0.66.14

Редагуємо WireGuard /etc/wireguard/wg0.conf – міняємо параметр DNS:

[Interface]
...
DNS = 127.0.0.1

...

Виконуємо sudo resolvconf -u, бо робили зміни в /etc/resolv.conf вручну і WireGuard буде сваритись.

Перезапускаємо WireGuard:

$ sudo wg-quick down wg0 && sudo wg-quick up wg0

Перевіряємо файл:

$ cat /etc/resolv.conf
# Generated by resolvconf
nameserver 127.0.0.1

І тепер все працює, як треба.

Loading