Unbound с RPZ: DNS защита от фишинг и малуер — инсталация, конфигурация и тест
Обновено на 30.10.2026: статията е пренаписана като технически наръчник.
RPZ (Response Policy Zone) е стандартен начин кеширащият DNS сървър да отговаря различно за определени имена. Най-честата употреба е да не резолва известни фишинг, малуер и измамни домейни. Устройството получава отговор „няма такъв домейн“ (NXDOMAIN) и връзката изобщо не започва. Така работят и двата кеширащи DNS сървъра на АЙТИСЪРВИС.
По-долу е целият път на Ubuntu 24.04 с Unbound 1.19: инсталация на рекурсивен резолвер, включване на RPZ първо в режим „само логва“, проверка, преминаване към блокиране, изключения и статистика. Адресите в примерите са документационни: резолверът е 192.0.2.53, клиентите са в 198.51.100.0/24.
1. Инсталация
apt update apt install unbound dnsutils
Пакетът на Ubuntu идва с два файла в /etc/unbound/unbound.conf.d/: root-auto-trust-anchor-file.conf (ключът за DNSSEC) и remote-control.conf (сокетът за unbound-control). Оставяме ги както са.
На Ubuntu
systemd-resolvedслуша на127.0.0.53:53. Ако Unbound слуша на конкретни адреси (както по-долу), двата не си пречат. Ако искатеinterface: 0.0.0.0, първо сложетеDNSStubListener=noв/etc/systemd/resolved.conf.
2. Рекурсивен резолвер
Без forward-zone: сървърът сам пита root, TLD и авторитетните сървъри. Така не зависите от Google или Cloudflare и DNSSEC се проверява при вас. Файл /etc/unbound/unbound.conf.d/server.conf:
server:
verbosity: 1
extended-statistics: yes
interface: 127.0.0.1
interface: 192.0.2.53
interface: 2001:db8::53
do-ip4: yes
do-ip6: yes
# respip е задължителен за RPZ (виж по-долу)
module-config: "respip validator iterator"
# 4 ядра: 4 нишки и 4 slabs за всеки кеш
num-threads: 4
msg-cache-slabs: 4
rrset-cache-slabs: 4
infra-cache-slabs: 4
key-cache-slabs: 4
msg-cache-size: 1024m
rrset-cache-size: 2048m
so-rcvbuf: 8m
so-sndbuf: 8m
outgoing-range: 8192
num-queries-per-thread: 4096
# бързина: обновявай популярното преди да изтече,
# при проблем навън отговаряй от стария кеш
prefetch: yes
prefetch-key: yes
serve-expired: yes
serve-expired-ttl: 3600
serve-expired-client-timeout: 1800
aggressive-nsec: yes
hide-identity: yes
hide-version: yes
harden-dnssec-stripped: yes
# само за вашите мрежи, иначе е отворен резолвер
access-control: 127.0.0.0/8 allow
access-control: ::1 allow
access-control: 198.51.100.0/24 allow
access-control: 2001:db8::/32 allow
За so-rcvbuf: 8m ядрото трябва да позволява толкова голям буфер:
cat > /etc/sysctl.d/90-unbound.conf <<'EOF' net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 EOF sysctl --system
3. Списъкът и изключенията
Ползваме HaGeZi Threat Intelligence Feeds във вариант mini: около 214 000 домейна (фишинг, малуер, командни сървъри на ботнети, измами), готов в RPZ формат и обновяван няколко пъти на ден. Unbound го сваля сам по HTTPS и го пази локално.
Изключенията са отделна малка RPZ зона, която се проверява преди списъка. Файл /etc/unbound/unbound.conf.d/rpz.conf:
rpz:
name: "whitelist.rpz"
zonefile: "/etc/unbound/whitelist.rpz"
rpz-log: yes
rpz-log-name: "whitelist"
rpz:
name: "hagezi-tif.rpz"
url: "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/tif.mini.txt"
zonefile: "/var/lib/unbound/hagezi-tif.rpz"
# първата седмица: само логва, не блокира
rpz-action-override: passthru
rpz-log: yes
rpz-log-name: "hagezi-tif"
Зоните се проверяват по реда в конфигурацията и печели първото съвпадение. Затова whitelist.rpz е първа.
Празната зона с изключения, /etc/unbound/whitelist.rpz:
$TTL 300 @ SOA localhost. root.localhost. 2026102901 3600 600 86400 300 NS localhost. ; изключение: домейн CNAME rpz-passthru. ; дата | причина ; example.com CNAME rpz-passthru. ; *.example.com CNAME rpz-passthru.
Всеки домейн в списъка е записан два пъти: домейн CNAME . и *.домейн CNAME .. CNAME . в RPZ значи NXDOMAIN. Блокира се домейнът и всичките му поддомейни, но не и другите домейни, чиито DNS сървъри са при него. Затова и изключението се пише в два реда.
Капанът с respip. С
rpz:блок и старотоmodule-config: "validator iterator"Unbound не стартира, аunbound-checkconfне го хваща. На продукционен резолвер това е спрял DNS за всички клиенти. Модулътrespipтрябва да е първи.
4. Сух старт на друг порт
Преди да рестартирате работещия резолвер, пуснете втори Unbound със същата конфигурация на порт 5353, с малък кеш и в преден план. Ако не тръгне или не блокира, клиентите не са засегнати.
unbound-checkconf
# тестовият файл ТРЯБВА да е в /etc/unbound/: AppArmor не пуска Unbound да чете /tmp
T=/etc/unbound/test-5353.conf
C=/etc/unbound/unbound.conf.d
sed -e 's/^\(\s*interface:\).*/\1 127.0.0.1/' -e 's/^\(\s*msg-cache-size:\).*/\1 4m/' \
-e 's/^\(\s*rrset-cache-size:\).*/\1 8m/' -e 's/^\(\s*num-threads:\).*/\1 1/' \
$C/server.conf > $T
echo ' port: 5353' >> $T
cat $C/root-auto-trust-anchor-file.conf >> $T
# отделно копие на списъка; тестът блокира наистина (nxdomain), за да се види, че работи
cp /var/lib/unbound/hagezi-tif.rpz /var/lib/unbound/hagezi-test.rpz 2>/dev/null
chown unbound: /var/lib/unbound/hagezi-test.rpz 2>/dev/null
sed -e 's#/var/lib/unbound/hagezi-tif.rpz#/var/lib/unbound/hagezi-test.rpz#' -e 's/passthru/nxdomain/' $C/rpz.conf >> $T
(timeout 120 unbound -d -c $T &)
sleep 30 # при първо пускане списъкът се сваля
dig -p 5353 @127.0.0.1 casinoonline.ac | grep status # очаквано: NXDOMAIN
dig -p 5353 @127.0.0.1 isc.org | grep status # очаквано: NOERROR
pkill -f "unbound -d -c $T"; rm -f $T /var/lib/unbound/hagezi-test.rpz
casinoonline.ac е просто първият домейн в текущия списък. Вземете който и да е:
grep -m1 'CNAME \.$' /var/lib/unbound/hagezi-tif.rpz
5. Включване и проверка
systemctl restart unbound systemctl is-active unbound # двете зони са заредени unbound-control list_auth_zones # DNS работи, DNSSEC се проверява (флаг ad), счупеният подпис дава SERVFAIL dig @192.0.2.53 isc.org +dnssec | grep -E 'status|flags' dig @192.0.2.53 dnssec-failed.org | grep status
В режим passthru всичко се резолва нормално, но всяко съвпадение се записва в журнала:
journalctl -u unbound -g 'rpz: applied' --since -1h | tail ... info: rpz: applied [hagezi-tif] ads.example-bad.com. rpz-passthru 198.51.100.23@41522 ads.example-bad.com. A IN
Кои домейни и от колко устройства:
journalctl -u unbound -g 'rpz: applied \[hagezi' --since -24h -o cat \
| awk '{split($8,a,"@"); print $6, a[1]}' | sort | uniq -c | sort -rn | head -20
Седмица в този режим показва какво би се блокирало и кои легитимни домейни трябва да влязат в изключенията, преди някой клиент да се обади.
6. Блокиране
Сменете passthru на nxdomain, пуснете отново сухия старт и рестартирайте:
cp -a /etc/unbound/unbound.conf.d/rpz.conf /root/rpz.conf.bak-passthru
sed -i 's/rpz-action-override: passthru/rpz-action-override: nxdomain/' \
/etc/unbound/unbound.conf.d/rpz.conf
unbound-checkconf && systemctl restart unbound
dig @192.0.2.53 casinoonline.ac | grep status # NXDOMAIN
dig @192.0.2.53 isc.org | grep status # NOERROR
Връщане: копирайте бекъпа обратно и рестартирайте.
Защо NXDOMAIN, а не страница „Блокирано“: почти всичко е по HTTPS и страница от ваш сървър на мястото на чужд домейн браузърът показва като грешка в сертификата. Потребителят свиква да натиска „Продължи въпреки това“, а точно това не искаме.
7. Изключение за домейн
Добавя се без рестарт и важи веднага:
F=/etc/unbound/whitelist.rpz # 1. сериалът в SOA трябва да се вдигне sed -i 's/ 2026102901 / 2026102902 /' $F # 2. домейнът и поддомейните му cat >> $F <<'EOF' example.com CNAME rpz-passthru. ; 2026-10-30 | новинарски сайт *.example.com CNAME rpz-passthru. EOF # 3. презареждане само на тази зона unbound-control auth_zone_reload whitelist.rpz dig @192.0.2.53 example.com | grep status # NOERROR
При два резолвера (основен и резервен) изключението се прави и на двата.
8. Ако източникът на списъка не е достъпен
Unbound обновява зоната по стойностите в нейния SOA: refresh 8 часа, retry 1 час, expire 3 дни. Ако GitHub не е достъпен, той продължава с последното локално копие. Ако копието изтече или липсва, Unbound пак стартира и DNS работи, само без филтриране (fail-open). Проверено е със спрян достъп до URL-а и изтрит zonefile.
Затова наблюдаваме възрастта на локалния файл:
stat -c '%y' /var/lib/unbound/hagezi-tif.rpz # последно сваляне
awk '$2=="SOA"{print $5; exit}' /var/lib/unbound/hagezi-tif.rpz # сериал
grep -c '^[^*;$@ ].* CNAME \.$' /var/lib/unbound/hagezi-tif.rpz # брой домейни
# ръчно обновяване
unbound-control auth_zone_transfer hagezi-tif.rpz
При нас дневният отчет предупреждава, ако списъкът е на повече от 24 часа, и вдига тревога над 72 часа.
9. Статистика
С extended-statistics: yes Unbound брои отговорите по вид, типовете заявки и действията на RPZ:
unbound-control stats_noreset | grep -E \ '^total\.num\.(queries|cachehits)=|^total\.recursion\.time\.(avg|median)=|^num\.answer\.rcode\.(NOERROR|NXDOMAIN|SERVFAIL)=|^num\.rpz\.action'
- cachehits / queries: колко отговора идват от кеша. При рекурсивен резолвер обикновено е над 80%.
- recursion.time: колко време отнема заявка, която не е в кеша. Десетки ms е нормално.
- SERVFAIL: заявки, за които сървърът не е намерил отговор (счупен DNSSEC, недостъпни авторитетни сървъри). Внезапен скок обикновено значи проблем с връзката навън.
- num.rpz.action.rpz-nxdomain: блокирани заявки. rpz-passthru: заявки, пуснати заради изключение.
unbound-control set_option extended-statistics: yesго включва без рестарт, но тогава се броят само част от отговорите, а типовете заявки изобщо липсват. За верни числа е нужен рестарт.
Как изглежда при нас
Двата кеширащи DNS сървъра на АЙТИСЪРВИС работят точно с тази конфигурация. През първите 24 часа спряха над 13 000 опита за достъп от 125 устройства към 60 зловредни домейна. Статистиката е публична на страницата Локална инфраструктура: блокирани заявки по часове, най-търсените блокирани домейни, изключенията и търсачка в целия списък. Показва се само броят устройства, без IP адреси на клиентите.
Ако ваш домейн е блокиран погрешно от нашите сървъри, пишете на support@itservice-bg.net.