FreeBSD: Home NAS, part 4 – локальний DNS з Unbound

Автор |  21/12/2025
 

В попередньому пості FreeBSD: WireGuard VPN, Linux peer та routing між мережами підняли VPN для поєднання двох мереж – моєї офісної та домашньої, все працює.

Але зараз, аби підключитись до якогось хосту в мережах треба вказувати IP-адресу.

Можна, звісно, прописувати все в файлах /etc/hosts, але це і не дуже зручно, і будуть клієнти типу Android-телефонів, і взагалі – хочеться все красівоє.

Тому зробимо локальний DNS, на якому буде централізований DNS для всієї інфраструктури:

  • власну локальну DNS-зону .setevoy
  • DNS-відповіді для .setevoy, що залежать від мережі, з якої приходить запит (office/home/VPN)
  • резолвінг зовнішніх доменів через forward-DNS (Cloudflare/Google)

Вибір DNS серверу

Справа смаку.

Я колись мав BIND, який обслуговував навіть зону самого rtfm.co.ua – але для маленької домашньої мережі це зовсім overkill.

Мав справу з dnsmasq – простий, швидкий, вміє в DHCP, але трохи обмежений в можливостях.

І Unbound – рідний для FreeBSD, теж простий і швидкий, вміє в DNSSEC validation прям з коробки, вміє в views/access-control – те, що треба.

Routers та DHCP зі Static IP

Взагалі, можна було б заморочитись і автоматизувати навіть видачу IP для хостів в мережах, але, по-перше, ну камон – в мене 3 машинки у двох мережах 🙂

По-друге – робочий ноут і ThinkCenter знаходяться в офісі, а домашній ноутбук – вдома. Тобто, робити DHCP на хості з FreeBSD – така собі затєя, бо домашній ноутбук може бути не підключеним до VPN.

Тому – прибиваємо все статично в налаштуваннях роутера (чекаю, чекаю, коли до мене доїде MikroTik hAP ax3!):

Тепер для робочого ноута, коли він підключений по Ethernet, адреса завжди буде 192.168.0.3.

Базовий конфіг Unbound

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

root@setevoy-nas:/home/setevoy # pkg install -y unbound

Файл налаштувань – /usr/local/etc/unbound/unbound.conf.

Є цікавий приклад конфігу Example of how to configure Unbound as a local forwarder using DNS-over-TLS to forward queries, треба буде глянути.

Документація – unbound.conf і офіційна документація Unbound by NLnet Labs.

Можна забекапити дефолтний конфіг, напишемо свій з нуля:

root@setevoy-nas:/home/setevoy # cp /usr/local/etc/unbound/unbound.conf /usr/local/etc/unbound/unbound.conf-origin

Редагуємо/створюємо /usr/local/etc/unbound/unbound.conf, поки мінімальна конфігурація:

server:
    interface: 0.0.0.0

    access-control: 127.0.0.0/8 allow         # local
    access-control: 192.168.0.0/24 allow      # office LAN
    access-control: 192.168.100.0/24 allow    # home LAN
    access-control: 10.8.0.0/24 allow         # WireGuard VPN

    do-ip6: no
    do-daemonize: yes

    hide-identity: yes
    hide-version: yes

    local-zone: "setevoy." static
    local-data: "nas.setevoy. A 192.168.0.2"
    local-data: "work.setevoy. A 192.168.0.1"
    local-data: "home.setevoy. A 192.168.100.205"

Тут вказуємо:

  • на якій адресі приймати підключення
  • з яких мереж приймати запити
  • відключаємо IPv6 (бо нашо це вдома?)
  • ховаємо ім’я серверу та версію
    • навіть не знав, що так можна – dig CHAOS TXT id.server @192.168.0.2 +short поверне CH TXT "setevoy-nas"
  • і описуємо локальну зону .setevoy з трьома DNS records

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

root@setevoy-nas:/home/setevoy # unbound-checkconf
unbound-checkconf: no errors in /usr/local/etc/unbound/unbound.conf

Додаємо в автостарт:

root@setevoy-nas:/home/setevoy # sysrc unbound_enable=YES
unbound_enable:  -> YES

Запускаємо:

root@setevoy-nas:/home/setevoy # service unbound start

Перевіряємо локально, з хоста з FreeBSD з drill замість dig:

root@setevoy-nas:/home/setevoy # drill nas.setevoy @127.0.0.1
...
;; ANSWER SECTION:
nas.setevoy.    3600    IN      A       192.168.0.2
...

І якийсь зовнішній домен:

root@setevoy-nas:/home/setevoy # drill google.com @127.0.0.1
...
;; ANSWER SECTION:
google.com.     300     IN      A       142.251.98.101
google.com.     300     IN      A       142.251.98.139
...

Додаємо правило pf для доступу до DNS (див. FreeBSD: знайомство з Packet Filter (PF) firewall):

...
# DNS
pass in on em0 proto { udp tcp } from { 192.168.0.0/24, 192.168.100.0/24, 10.8.0.0/24 } to (em0) port 53 keep state
...

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

root@setevoy-nas:/home/setevoy # pfctl -nvf /etc/pf.conf && pfctl -f /etc/pf.conf

І пробуємо вже з робочого ноута:

[setevoy@setevoy-work ~]  $ dig nas.setevoy @192.168.0.2 +short
192.168.0.2

[setevoy@setevoy-work ~]  $ dig google.com @192.168.0.2 +short
142.251.98.138
142.251.98.102
...

Задаємо кастомні DNS сервери на офісному роутері:

WireGuard DNS settings

Домашній ноут підключається з WireGuard, де при підключенні можна задавати наш новий DNS.

З дому при підключеному VPN у нас сервер DNS буде на 10.8.0.1:

[setevoy@setevoy-home ~]$ dig work.setevoy @10.8.0.1 +short
192.168.0.1

Редагуємо /etc/wireguard/wg0.conf, в блок [Interface] додаємо DNS:

[Interface]
PrivateKey = 0Cu***UWU=
Address = 10.8.0.3/24
DNS = 10.8.0.1, 192.168.100.1

[Peer]
PublicKey = xLWA/FgF3LBswHD5Z1uZZMOiCbtSvDaUOOFjH4IF6W8=
Endpoint = setevoy-***.ddns.me:51830

AllowedIPs = 10.8.0.1/32, 192.168.0.0/24
PersistentKeepalive = 25

Тепер при підключенні VPN буде задаватись 10.8.0.1 як основний, і адреса домашнього роутера 192.168.100.1 як fallback опція.

Ще можна буде налаштувати split-DNS, задавши Domains = ~setevoy, аби до 10.8.0.1 робити запити тільки для зони .setevoy, див. systemd-resolved та systemd-networkd.

Налаштування forward-only DNS

З поточним конфігом наш unbound виконує повний рекурсивний пошук.

Тобто, коли ми з клієнта робимо запит google.com – то:

  • unbound звертається до root-серверів DNS
  • там шукає Name Servers для зони .com
  • звертається до цих Name Servers, запитуючи інформацію про Name Servers для google.com
  • і тільки після цього йде на Name Servers google.com, звідки бере інформацію по IP, яку потім повертає клієнту

Натомість можемо налаштувати forward-only, і тоді Unbound одним запитом сходить на 1.1.1.1 або 8.8.8.8, і відразу поверне відповідь клієнту.

Додаємо в кінець конфігу:

...

forward-zone:
    name: "."
    forward-addr: 1.1.1.1
    forward-addr: 8.8.8.8

Перевіряємо, застосовуємо зміни:

root@setevoy-nas:/home/setevoy # unbound-checkconf && service unbound reload

Тепер Unbound в порядку пріорітету спочатку перевірить свою локальну зону, якщо google.com в нього на обслуговуванні нема – то він переадресує запит до Cloudflare або Google.

Налаштування логів

Робив для того, аби подивись як впливає налаштування forward-zone – тому теж нехай тут буде.

В блок server: додаємо:

...
server:
    ...

    # enable logging
    verbosity: 3
    log-queries: yes
    log-replies: yes
    logfile: "/var/log/unbound.log"

    # disable caching during tests
    cache-min-ttl: 0
    cache-max-ttl: 0

...

На час тестів можна відключити локальне кешування – cache-min/max-ttl.

По дефолту Unbound працює в chroot-оточенні з кореневою директорією в /usr/local/etc/unbound – створюємо там файл логу:

root@setevoy-nas:/home/setevoy # mkdir -p /usr/local/etc/unbound/var/log
root@setevoy-nas:/home/setevoy # touch /usr/local/etc/unbound/var/log/unbound.log
root@setevoy-nas:/home/setevoy # chown unbound:unbound /usr/local/etc/unbound/var/log/unbound.log
root@setevoy-nas:/home/setevoy # chmod 640 /usr/local/etc/unbound/var/log/unbound.log
root@setevoy-nas:/home/setevoy # unbound-checkconf
unbound-checkconf: no errors in /usr/local/etc/unbound/unbound.conf
root@setevoy-nas:/home/setevoy # service unbound reload

Перевірка роботи Recursive DNS

Тепер відключаємо forward-zone, з робочого ноутбука робимо запит про google.com до нашого DNS:

[setevoy@setevoy-work ~]  $ time dig +short google.com @192.168.0.2
...

real    0m0.109s

Час виконання – 0.109s.

Дивимось лог Unbound:

...
info: 192.168.0.3 google.com. A IN
...
info: priming . IN NS
info: sending query: . NS IN
info: reply from <.> 199.7.83.42#53
info: priming successful for . NS IN
...
info: sending query: com. A IN
info: reply from <.> 202.12.27.33#53
info: query response was REFERRAL
...
info: sending query: google.com. A IN
info: reply from <google.com.> 216.239.38.10#53
info: query response was ANSWER
...
info: 192.168.0.3 google.com. A IN NOERROR 0.101574
...

Тут добре видно, як Unbound працює як рекурсивний резолвер :

  • спочатку виконує запит до root-зони “.“, де отримує Name Servers для зони .com
  • далі звертається до Name Servers зони .com, де отримує адреси Name Servers зони google.com
  • і лише після цього звертається вже до Name Servers домену google.com, отримуючи фінальну A-відповідь з IP-адресами серверів Google

А тепер повертаємо forward-zone, але залишаємо відключеним кешування (cache-min/max-ttl) і робимо запит з клієнта ще раз:

[setevoy@setevoy-work ~]  $ time dig +short google.com @192.168.0.2
...
real    0m0.016s

Тепер час на отримання відповіді – 0.030s, а без forward-zone він був 0.109s (або з логу – google.com. A IN NOERROR 0.101574). Майже в 3 раз швидше. Іноді буває і 0.01s.

І логи Unbound тепер набагато менші:

...
debug: forwarding request
...
debug:    ip4 1.1.1.1 port 53
debug:    ip4 8.8.8.8 port 53
info: sending query: google.com. A IN
debug: sending to target: <.> 8.8.8.8#53
...
info: 192.168.0.3 google.com. A IN NOERROR 0.039006
...

Все, що він зробив – це передав запит до 8.8.8.8 і повернув результат, отриманий від нього.

Unbound DNS views для різних мереж

Зараз у нас з дому, який за VPN, при запиті nas.setevoy повернеться адреса 192.168.0.1:

...
local-data: "nas.setevoy. A 192.168.0.2"
...

Але якщо хочемо ходити чисто через нетворк VPN – можемо розділити зони з views і для кожної мережі створити власні зони:

server:
    interface: 0.0.0.0
    do-daemonize: yes
    do-ip6: no

    hide-identity: yes
    hide-version: yes

    # map client networks to views
    access-control-view: 127.0.0.0/8 office
    access-control-view: 192.168.0.0/24 office
    access-control-view: 10.8.0.0/24 vpn

#######################
### Hosts list note ###
#######################
# -Office: 
#  - setevoy-work: Arch Linux laptop
#  - setevoy-pc: Arch Linux/Windows gaming PC
#  - setevoy-nas: FreeBSD ThinkCentre
#  - TP-Link router
# - Home:
#  - setevoy-home: Arch Linux laptop
#  - TP-Link router

################
# OFFICE VIEW #
################
view:
    name: "office"
    local-zone: "setevoy." static
    local-data: "nas.setevoy. A 192.168.0.2"
    local-data: "work.setevoy. A 192.168.0.3"
    local-data: "pc.setevoy. A 192.168.0.4"

#############
# VPN VIEW #
#############
view:
    name: "vpn"
    local-zone: "setevoy." static
    local-data: "nas.setevoy. A 10.8.0.1"
    # not VPN-connected, but Home has routing to the 192.168.0.0/24
    local-data: "work.setevoy. A 192.168.0.3"
    local-data: "pc.setevoy. A 192.168.0.4"

#################
# FORWARD ONLY #
#################
forward-zone:
    name: "."
    forward-addr: 1.1.1.1
    forward-addr: 8.8.8.8

Тут в access-control-view: 10.8.0.0/24 vpn прив’язує клієнтів з VPN-мережі 10.8.0.0/24 до view з ім’ям vpn, для якого далі окремо описується DNS-логіка.

Конкретно в моєму сетапі домашній ноутбук все одно має роутинг в мережу 192.168.0.0/24 через 10.8.0.1, і можна повертати адреси з неї – але в цілому штука корисна.

Перевіряємо, застосовуємо:

root@setevoy-nas:/home/setevoy # unbound-checkconf && service unbound reload
unbound-checkconf: no errors in /usr/local/etc/unbound/unbound.conf

Перевіряємо з дому – отримуємо адресу з мережі VPN:

[setevoy@setevoy-home ~] $ dig nas.setevoy @10.8.0.1 +short
10.8.0.1

І з офісу – результат буде з офісної мережі:

[setevoy@setevoy-work ~] $ dig nas.setevoy @192.168.0.2 +short
192.168.0.2

Готово.