Unbound ile Split DNS Yapılandırması
İç ağda bir DNS sunucusu kurup çalıştırmak bir şeydir, ama aynı alan adının içeriden ve dışarıdan farklı IP adresleri döndürmesini sağlamak bambaşka bir iştir. Yıllarca bunu NAT hairpin (ya da NAT loopback) ile çözdük, firewall kuralları yazdık, hosts dosyalarını düzenledik. Ama bunların hepsi geçici çözümdü. Unbound ile split DNS yapılandırması, bu problemi temiz ve sürdürülebilir bir şekilde çözer.
Bu yazıda gerçek bir kurumsal senaryoyu ele alacağız: sirket.com.tr alan adının dışarıdan erişildiğinde 203.0.113.10 (gerçek public IP) döndürmesi, iç ağdan erişildiğinde ise 192.168.10.50 (internal sunucu IP’si) döndürmesi gerekiyor. Bunu Unbound ile nasıl kurarız, nasıl yönetiriz, nelere dikkat etmemiz gerekir, hepsini adım adım geçeceğiz.
Split DNS Nedir ve Neden Gereklidir
Önce kavramı netleştirelim. Split DNS (bölünmüş DNS ya da split-horizon DNS de denir), aynı alan adı sorgusu için farklı ağ konumlarından gelen istemcilere farklı yanıtlar vermek demektir. İki temel kullanım senaryosu vardır:
- İç/dış ayrımı:
mail.sirket.com.triç ağdan sorgulandığında 192.168.10.25 dönsün, internetten sorgulandığında 203.0.113.25 dönsün. - VPN kullanıcıları için özel çözümleme: VPN’e bağlı kullanıcılar iç kaynaklara doğrudan erişebilsin, dışarıdakiler göremeysin.
- Ortam ayrımı:
api.sirket.com.trstaging ortamında farklı, production ortamında farklı bir sunucuya işaret etsin.
NAT loopback ile bu işi yapmak başta kolay görünür ama ölçeklendiğinde kabus olur. Her yeni sunucu için firewall kuralı eklemek, stateful connection tracking’i gereksiz yere zorlamak, performans kaybı… Unbound ile bunların hiçbirini yaşamazsınız.
Unbound Kurulumu
Debian/Ubuntu tabanlı sistemlerde:
apt update && apt install -y unbound
systemctl enable unbound
systemctl start unbound
RHEL/CentOS/Rocky Linux tabanlı sistemlerde:
dnf install -y unbound
systemctl enable unbound
systemctl start unbound
Kurulum sonrası temel kontrol:
unbound-control status
# veya
systemctl status unbound
Unbound’un varsayılan konfigürasyon dosyası /etc/unbound/unbound.conf altındadır. Ama ben genellikle bu dosyaya dokunmam, onun yerine /etc/unbound/unbound.conf.d/ dizini altında ayrı dosyalar oluştururum. Hem daha düzenli olur hem de bir şeyleri bozduğunuzda neyi bozduğunuzu hemen anlarsınız.
Temel Unbound Yapılandırması
Split DNS’e geçmeden önce sağlam bir temel yapılandırma şart. /etc/unbound/unbound.conf.d/base.conf dosyasını oluşturalım:
cat > /etc/unbound/unbound.conf.d/base.conf << 'EOF'
server:
# Hangi arayüzlerde dinleyeceğimiz
interface: 0.0.0.0
interface: ::0
# Sadece iç ağdan gelen sorguları kabul et
access-control: 127.0.0.0/8 allow
access-control: 192.168.0.0/16 allow
access-control: 10.0.0.0/8 allow
access-control: 172.16.0.0/12 allow
access-control: 0.0.0.0/0 refuse
# Temel güvenlik ayarları
hide-identity: yes
hide-version: yes
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: yes
# Önbellek ayarları
cache-min-ttl: 300
cache-max-ttl: 86400
neg-cache-size: 4m
# Log ayarları
verbosity: 1
log-queries: no
# EDNS buffer size
edns-buffer-size: 1472
# Performans
num-threads: 2
so-rcvbuf: 1m
EOF
Yapılandırmayı test etmek için:
unbound-checkconf /etc/unbound/unbound.conf
Bu komut herhangi bir syntax hatası varsa size söyler. Üretime almadan önce mutlaka çalıştırın.
Split DNS Yapılandırması: Local Zone Yaklaşımı
Şimdi işin özüne geliyoruz. Unbound’da split DNS’in en temiz yolu local-zone ve local-data direktiflerini kullanmaktır. /etc/unbound/unbound.conf.d/split-dns.conf dosyasını oluşturalım:
cat > /etc/unbound/unbound.conf.d/split-dns.conf << 'EOF'
server:
# sirket.com.tr için iç zone tanımı
# "static" tipi: sadece bizim tanımladığımız kayıtları döndür,
# upstream'e sorma
local-zone: "sirket.com.tr." static
# A kayıtları
local-data: "sirket.com.tr. 3600 IN A 192.168.10.50"
local-data: "www.sirket.com.tr. 3600 IN A 192.168.10.50"
local-data: "mail.sirket.com.tr. 3600 IN A 192.168.10.25"
local-data: "vpn.sirket.com.tr. 3600 IN A 192.168.10.100"
local-data: "gitlab.sirket.com.tr. 3600 IN A 192.168.10.75"
# MX kaydı
local-data: "sirket.com.tr. 3600 IN MX 10 mail.sirket.com.tr."
# PTR kayıtları (reverse DNS)
local-data-ptr: "192.168.10.50 sirket.com.tr."
local-data-ptr: "192.168.10.25 mail.sirket.com.tr."
# Sadece iç ağda olan servisler
local-zone: "internal.sirket.com.tr." static
local-data: "jira.internal.sirket.com.tr. 3600 IN A 192.168.20.10"
local-data: "confluence.internal.sirket.com.tr. 3600 IN A 192.168.20.11"
local-data: "monitoring.internal.sirket.com.tr. 3600 IN A 192.168.20.20"
EOF
local-zone tiplerini anlamak önemli:
- static: Sadece tanımladığınız kayıtlar döner, başka hiçbir şey. Tanımlamadığınız alt alan adları NXDOMAIN döner.
- transparent: Tanımladığınız kayıtlar döner, tanımlamadıklarınız upstream’e sorulur. Kısmi override için kullanışlı.
- redirect: Zone altındaki tüm sorgular belirttiğiniz hedefe yönlendirilir.
- refuse: Zone için gelen sorguları reddeder.
- deny: Zone için gelen sorgulara yanıt vermez (drop).
Stub Zone ile Yetkilendirme
Bazen iç DNS sunucunuzun bir zone için yetkili olmayan bir DNS sunucusuna (mesela Windows Active Directory DNS’i) yönlendirmesi gerekir. Bunun için stub-zone kullanırız:
cat > /etc/unbound/unbound.conf.d/stub-zones.conf << 'EOF'
# Active Directory domain'i için iç DNS'e yönlendir
stub-zone:
name: "ad.sirket.com.tr."
stub-addr: 192.168.1.10
stub-addr: 192.168.1.11
stub-first: no
# Ayrı bir datacenter'daki DNS'e yönlendir
stub-zone:
name: "dc2.sirket.com.tr."
stub-addr: 10.10.1.53
stub-first: no
EOF
stub-first: no diyerek Unbound’un önce upstream’e sormadan direkt stub adresine gitmesini sağlıyoruz. Bu önemli, aksi takdirde beklenmedik davranışlar görebilirsiniz.
Forward Zone ile Seçici Yönlendirme
Belirli domain’ler için farklı upstream DNS kullanmak isteyebilirsiniz. Örneğin .local domain’leri için iç DNS’iniz varsa ya da belirli cloud provider domain’leri için özel DNS kullanmanız gerekiyorsa:
cat > /etc/unbound/unbound.conf.d/forward-zones.conf << 'EOF'
# Genel sorgular için upstream DNS (DoT ile)
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net
# Şirket VPN'inin DNS'i için özel yönlendirme
forward-zone:
name: "vpn-partner.com."
forward-addr: 10.200.1.53
forward-first: no
EOF
Burada dikkat edin: sirket.com.tr için hem local-zone tanımı hem de forward-zone tanımı yaparsanız, local-zone öncelikli olur. Unbound önce local tanımlamalara bakar.
Ters Arama (Reverse DNS) Split Konfigürasyonu
İç ağ için PTR kayıtları da önemlidir. Özellikle monitoring araçları, log analizleri için. /etc/unbound/unbound.conf.d/reverse-zones.conf:
cat > /etc/unbound/unbound.conf.d/reverse-zones.conf << 'EOF'
server:
# 192.168.10.0/24 bloğu için PTR kayıtları
local-zone: "10.168.192.in-addr.arpa." static
local-data: "50.10.168.192.in-addr.arpa. 3600 IN PTR sirket.com.tr."
local-data: "25.10.168.192.in-addr.arpa. 3600 IN PTR mail.sirket.com.tr."
local-data: "100.10.168.192.in-addr.arpa. 3600 IN PTR vpn.sirket.com.tr."
local-data: "75.10.168.192.in-addr.arpa. 3600 IN PTR gitlab.sirket.com.tr."
# 192.168.20.0/24 bloğu
local-zone: "20.168.192.in-addr.arpa." static
local-data: "10.20.168.192.in-addr.arpa. 3600 IN PTR jira.internal.sirket.com.tr."
local-data: "11.20.168.192.in-addr.arpa. 3600 IN PTR confluence.internal.sirket.com.tr."
EOF
Yapılandırmayı Test Etme
Yapılandırmayı reload etmeden önce mutlaka test edin:
# Syntax kontrolü
unbound-checkconf /etc/unbound/unbound.conf
# Eğer her şey yolundaysa reload et
systemctl reload unbound
# veya
unbound-control reload
Test sorguları atmak için dig kullanın:
# İç IP'yi döndürüyor mu?
dig @127.0.0.1 sirket.com.tr A
# Yok dediğimiz kayıt gerçekten yok mu?
dig @127.0.0.1 yokboylesibir.sirket.com.tr A
# PTR sorgusu
dig @127.0.0.1 -x 192.168.10.50
# Mail sunucusu
dig @127.0.0.1 sirket.com.tr MX
Beklenen çıktı kontrolü için basit bir test scripti yazabilirsiniz:
#!/bin/bash
# /usr/local/bin/test-split-dns.sh
DNS_SERVER="127.0.0.1"
PASS=0
FAIL=0
check_dns() {
local host=$1
local expected=$2
local result
result=$(dig @${DNS_SERVER} +short "${host}" A 2>/dev/null | head -1)
if [ "${result}" = "${expected}" ]; then
echo "[PASS] ${host} -> ${result}"
((PASS++))
else
echo "[FAIL] ${host} -> beklenen: ${expected}, gelen: ${result}"
((FAIL++))
fi
}
check_dns "sirket.com.tr" "192.168.10.50"
check_dns "www.sirket.com.tr" "192.168.10.50"
check_dns "mail.sirket.com.tr" "192.168.10.25"
check_dns "jira.internal.sirket.com.tr" "192.168.20.10"
echo ""
echo "Sonuc: ${PASS} basarili, ${FAIL} basarisiz"
chmod +x /usr/local/bin/test-split-dns.sh
/usr/local/bin/test-split-dns.sh
DNSSEC Dikkat Noktası
Local zone’larınız için DNSSEC imzalaması yapmıyorsanız (ki çoğu iç ağ kurulumunda yapılmaz), bunu Unbound’a söylemeniz gerekir. Aksi takdirde DNSSEC-aware istemciler sorun yaşayabilir:
cat >> /etc/unbound/unbound.conf.d/split-dns.conf << 'EOF'
# DNSSEC validasyonu iç zone'larda hata vermesin
domain-insecure: "sirket.com.tr."
domain-insecure: "internal.sirket.com.tr."
domain-insecure: "168.192.in-addr.arpa."
EOF
Unbound-control ile Canlı Yönetim
Unbound çalışırken unbound-control aracıyla çok şey yapabilirsiniz. Önce kontrol arayüzünü aktif edin:
# unbound-control için anahtar ve sertifika üret
unbound-control-setup
# base.conf'a ekleyin
cat >> /etc/unbound/unbound.conf.d/base.conf << 'EOF'
remote-control:
control-enable: yes
control-interface: 127.0.0.1
control-port: 8953
server-key-file: "/etc/unbound/unbound_server.key"
server-cert-file: "/etc/unbound/unbound_server.pem"
control-key-file: "/etc/unbound/unbound_control.key"
control-cert-file: "/etc/unbound/unbound_control.pem"
EOF
systemctl restart unbound
Kullanışlı unbound-control komutları:
# Önbelleği temizle (bir domain'i test ederken çok işe yarar)
unbound-control flush sirket.com.tr
unbound-control flush_zone sirket.com.tr
# İstatistikleri gör
unbound-control stats_noreset
# Çalışan local zone'ları listele (debug için)
unbound-control list_local_zones
# Canlı olarak local data ekle (kalıcı değil, restart'ta gider)
unbound-control local_data "test.sirket.com.tr. 60 IN A 192.168.99.99"
# Eklediğini test et
dig @127.0.0.1 test.sirket.com.tr
# Sonra kaldır
unbound-control local_data_remove "test.sirket.com.tr."
Yaygın Sorunlar ve Çözümleri
Deneyimlerimden derlediğim, sık karşılaşılan sorunlar:
Sorun: Local zone tanımlıyken bazı kayıtlar çözümlenemiyor
static tip yerine transparent kullandığınızda tanımlamadığınız kayıtlar upstream’e sorulur. Ama static ile tüm kayıtları kendiniz tanımlamanız gerekir. Hangi tipte ne beklediğinizi net bilin.
Sorun: DNSSEC validation hatası
domain-insecure direktifini eklediğinizden emin olun. Özellikle harden-dnssec-stripped: yes açıkken iç zone’lar sorun çıkarabilir.
Sorun: Reload sonrası eski kayıtlar hala geliyor
İstemci tarafında önbellekleme olabilir. unbound-control flush_zone sirket.com.tr ile Unbound önbelleğini temizleyin, istemcide de ipconfig /flushdns (Windows) veya resolvectl flush-caches (Linux systemd-resolved) çalıştırın.
Sorun: Stub zone çalışmıyor, hala public DNS’e soruyor
stub-first: no ayarını kontrol edin. Ayrıca stub adresinin Unbound’dan erişilebilir olduğunu nc -u 192.168.1.10 53 ile doğrulayın.
Sonuç
Unbound ile split DNS, şirket içi DNS altyapısının olmazsa olmazlarından biri. Burada anlattıklarımı özetleyeyim:
local-zonevelocal-dataile temel split DNS kurulumu düşündüğünüzden çok daha basit.- Konfigürasyonları ayrı dosyalara bölün, hem okunabilirlik hem de hata ayıklama açısından işinizi kolaylaştırır.
unbound-checkconfalışkanlığı edinin, production’a syntax hatasıyla gitmeyin.unbound-control flushvestats_noresetgünlük operasyonların vazgeçilmezi.- DNSSEC ve
domain-insecureayarını gözden kaçırmayın. - Test scriptini CI/CD pipeline’ına ya da monitoring sisteminize entegre edin, yapılandırma değişikliklerinin regresyon testini otomatikleştirin.
Split DNS bir kez düzgün kurulduğunda artık “içeriden neden açılmıyor?” sorusunu duymayı bırakırsınız. Ve NAT loopback konfigürasyonlarına bir daha dönüp bakmak zorunda kalmazsınız.
