ریدایرکت تمام درخواست‌های DNS به یک IP با zone سراسری BIND

یک نیم‌سرور پارکینگ راه انداخته‌اید و می‌خواهید هر دامنه‌ای که DNS آن را روی این سرور تنظیم می‌کنند، بدون اینکه لازم باشد برای تک‌تک آن‌ها zone و رکورد جداگانه بسازید، به یک آی‌پی مشخص برسد و صفحه پیش‌فرض یا صفحه پارک وب‌سرور را نشان بدهد. راه‌حل این است که در BIND یک zone سراسری برای ریشه DNS تعریف کنید تا تمام درخواست‌های DNS رسیده به سرور، بدون توجه به نام دامنه، به آی‌پی شما پاسخ داده شود.

با این کار می‌توانید DNS تمام دامنه‌های مورد نظر را روی سرور خود تنظیم کنید و صفحه default وب‌سرور را مشاهده کنید، بدون آنکه آن آدرس هاست یا رکورد DNS مستقلی داشته باشد.

این zone سراسری دقیقا چه می‌کند

در حالت عادی، سرور DNS شما فقط برای دامنه‌هایی که zone آن‌ها را تعریف کرده‌اید authoritative است و برای بقیه نام‌ها یا از ریشه DNS جهانی پرس‌وجو می‌کند یا پاسخ REFUSED می‌دهد. وقتی zone «.» یعنی خودِ ریشه DNS را از نوع master تعریف کنید، سرور تصور می‌کند مرجع کل فضای نام اینترنت است. در آن zone یک رکورد wildcard می‌گذارید و نتیجه این می‌شود که هر پرسش A برای هر نامی — چه example.com، چه www.test.ir، چه یک نام کاملا ساختگی — همان آی‌پی سرور شما را برمی‌گرداند. کاربرد عملی‌اش هم همین است: دامنه‌های تازه ثبت‌شده یا منقضی‌شده مشتریان، تا زمان راه‌اندازی هاست واقعی، یک صفحه پارک ببینند.

هشدار مهم: این سرور دیگر اینترنت را resolve نمی‌کند

⚠ این کار را فقط روی یک نیم‌سرور اختصاصی پارکینگ انجام دهید. با تعریف zone «.» از نوع master، این سرور خودش را برای ریشهٔ DNS معتبر (authoritative) می‌داند و دیگر هیچ نامی را از اینترنت resolve نمی‌کند. یعنی اگر همین سرور، ریزالور شبکه یا وب‌سرور سایت‌های شما هم باشد، اتصال آن به بیرون (آپدیت، ایمیل، API) قطع می‌شود.

روش درست این است که یک سرور یا نیم‌سرور جدا فقط برای همین کار در نظر بگیرید و آن را به‌عنوان ریزالور به هیچ سیستمی معرفی نکنید. حتی روی خود همان سرور هم مطمئن شوید /etc/resolv.conf به 127.0.0.1 اشاره نمی‌کند، وگرنه دستور ساده‌ای مثل dnf update هم روی همان ماشین کار نخواهد کرد.

نکته دوم اینکه این پیکربندی یک catch-all واقعی است و هیچ فیلتری روی نام دامنه ندارد؛ یعنی هر کسی در دنیا که نیم‌سرور دامنه‌اش را روی آی‌پی شما بگذارد، از سرور شما پاسخ می‌گیرد و ترافیک آن دامنه به آی‌پی شما هدایت می‌شود. اگر این موضوع برایتان قابل قبول نیست، به‌جای zone ریشه باید برای هر دامنه یک zone جداگانه تعریف کنید.

پیش‌نیازها

  • دسترسی root به یک سرور اختصاصی یا VPS که فقط نقش نیم‌سرور پارکینگ را دارد.
  • یک آی‌پی عمومی ثابت که وب‌سرور روی آن در حال اجراست.
  • بسته BIND نصب شده باشد و پورت ۵۳ روی UDP و TCP از بیرون در دسترس باشد. بعضی دیتاسنترها پورت ۵۳ ورودی را به‌صورت پیش‌فرض می‌بندند؛ این را قبل از شروع بررسی کنید.
  • نام‌های نیم‌سرور مثل ns1.yourdomain.com و ns2.yourdomain.com در پنل ثبت‌کننده دامنه به‌عنوان host record یا child nameserver با آی‌پی سرور ثبت شده باشند. بدون این مرحله، درخواست‌های DNS اصلا به سرور شما نمی‌رسد و بقیه کارها بی‌فایده است. برای دامنه‌های .ir این کار در پنل ایرنیک انجام می‌شود.
  • توجه کنید بیشتر رجیستری‌ها حداقل دو نیم‌سرور می‌خواهند و بعضی از آن‌ها دو نیم‌سرور با آی‌پی کاملا یکسان را نمی‌پذیرند.

مراحل پیکربندی BIND

۱. نصب و اطمینان از وجود BIND

# خانواده RHEL: AlmaLinux / Rocky / CentOS
dnf install bind bind-utils
# روی CentOS 7 و قدیمی‌تر: yum install bind bind-utils

# خانواده Debian / Ubuntu (دبیان 11 و اوبونتو 22.04 به بعد)
apt install bind9 bind9-utils bind9-dnsutils
# روی نسخه‌های قدیمی‌تر نام بسته‌ها bind9utils و dnsutils است

نسخه BIND را با named -v ببینید؛ در ادامه به آن نیاز دارید.

۲. حذف zone ریشه پیش‌فرض (hint)

این مرحله معمولا در آموزش‌ها جا می‌افتد و یکی از شایع‌ترین دلایل بالا نیامدن سرویس است. فایل named.conf به‌صورت پیش‌فرض یک zone برای ریشه دارد که از نوع hint است و آدرس سرورهای ریشه جهانی را معرفی می‌کند:

zone "." IN {
        type hint;
        file "named.ca";
};

نمی‌توانید همزمان دو zone با نام «.» داشته باشید. این بلاک را کامنت یا حذف کنید، وگرنه BIND با خطایی شبیه /etc/named.conf:68: zone '.': already exists previous definition: /etc/named.conf:54 بالا نمی‌آید.

در دبیان و اوبونتو این تعریف داخل فایل /etc/bind/named.conf.default-zones است. فقط همان بلاک zone "." را داخل آن فایل کامنت کنید و کل خط include را غیرفعال نکنید؛ zoneهای localhost و 127.in-addr.arpa هم در همان فایل تعریف شده‌اند و با حذف include از بین می‌روند.

۳. تعریف zone سراسری

فایل پیکربندی اصلی را باز کنید:

nano /etc/named.conf
# در دبیان و اوبونتو: nano /etc/bind/named.conf.local

و zone ریشه را با فایل zone دلخواه (اینجا با نام db.catchall) اضافه کنید:

zone "." { type master; file "/var/named/db.catchall"; };

دقت کنید نام zone همان «.» است و db.catchall فقط نام فایل روی دیسک است. از BIND نسخه ۹.۱۶ به بعد واژه‌های primary و secondary به‌عنوان جایگزین ترجیحی master و slave اضافه شده‌اند و روی نسخه‌های جدید می‌توانید بنویسید:

zone "." { type primary; file "/var/named/db.catchall"; };

اما اگر BIND شما قدیمی‌تر از ۹.۱۶ است — مثلا CentOS 7 که BIND 9.11 دارد — واژه primary شناخته نمی‌شود و سرویس بالا نمی‌آید؛ روی آن سرورها حتما master بنویسید. واژه master در نسخه‌های جدید هم هنوز پذیرفته می‌شود، پس اگر مطمئن نیستید همان master امن‌ترین انتخاب است.

۴. ساخت فایل zone

nano /var/named/db.catchall
# در دبیان و اوبونتو: nano /etc/bind/db.catchall

محتویات فایل به این شکل خواهد بود:

$TTL    3600
@       IN      SOA     . root.localhost. (
                              1         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                           3600 )       ; Negative Cache TTL

    IN  NS  .
.   IN  A    Your portal IP
*.  IN  A    Your portal IP

به جای Your portal IP باید آی‌پی سرور خود را قرار دهید. سطر *. همان رکورد wildcard است که هر نامی را پاسخ می‌دهد و سطر . پاسخ خود ریشه را می‌دهد.

در نسخه اصلی این آموزش مقدار $TTL و مقدار Negative Cache TTL هر دو 604800 یعنی هفت روز بود. این دو عدد را به 3600 کاهش داده‌ایم، چون با TTL هفت‌روزه اگر روزی آی‌پی صفحه پارک را عوض کنید، تا یک هفته ریزالورهای دنیا آی‌پی قدیمی را کش نگه می‌دارند. همچنین هر بار که فایل zone را ویرایش می‌کنید عدد Serial را افزایش بدهید؛ در غیر این صورت تغییرات شما در برخی سناریوها نادیده گرفته می‌شود.

توجه کنید این zone فقط به پرسش‌های نوع A پاسخ می‌دهد و پرسش AAAA یا MX یا TXT پاسخ خالی (NODATA) می‌گیرد. اگر آی‌پی نسخه ۶ هم دارید، سطر *. IN AAAA <IPv6> را اضافه کنید و در بلاک options هم listen-on-v6 port 53 { any; }; را ست کنید، وگرنه BIND روی IPv6 گوش نمی‌دهد.

۵. مالکیت فایل و SELinux

chown root:named /var/named/db.catchall
chmod 640 /var/named/db.catchall
restorecon -v /var/named/db.catchall

در دبیان و اوبونتو به‌جای SELinux با AppArmor سروکار دارید؛ پروفایل usr.sbin.named مسیر /etc/bind را فقط خواندنی و مسیرهای /var/lib/bind و /var/cache/bind را قابل نوشتن می‌داند. فایل zone ایستای ما خواندنی بودن برایش کافی است، پس آن را در /etc/bind بگذارید و مسیر دلخواه دیگری انتخاب نکنید.

۶. تنظیمات لازم در بلاک options

پیکربندی پیش‌فرض BIND در خانواده RHEL فقط روی 127.0.0.1 گوش می‌دهد و فقط به localhost پاسخ می‌دهد. برای یک نیم‌سرور عمومی باید این‌طور باشد:

options {
        listen-on port 53 { any; };
        directory       "/var/named";
        allow-query     { any; };

        // این سرور فقط authoritative است و نباید ریزالور باشد
        recursion no;
        allow-recursion { none; };

        // چون اصلا resolve نمی‌کند، اعتبارسنجی DNSSEC برایش بی‌معنی است
        dnssec-validation no;
};

گذاشتن recursion no; اختیاری نیست. یک سرور DNS عمومی با recursion باز، هم به open resolver تبدیل می‌شود و هم در حملات تقویتی (DNS amplification) علیه دیگران به کار می‌رود و معمولا خیلی زود از سمت دیتاسنتر اخطار می‌گیرید.

راه‌اندازی و تست پیکربندی

قبل از ریستارت، صحت فایل‌ها را بررسی کنید. اگر این دو دستور خروجی خطا ندهند، مشکل نحوی ندارید (آی‌پی واقعی را قبلا جای Your portal IP گذاشته باشید، وگرنه named-checkzone خطا می‌دهد):

named-checkconf
named-checkzone . /var/named/db.catchall

سپس سرویس را اجرا و فعال کنید:

systemctl restart named
systemctl enable named
systemctl status named
journalctl -u named -n 50 --no-pager

در آموزش‌های قدیمی‌تر (قبل از CentOS 7) دستورهای دیگری می‌بینید. روی RHEL 7 و ۸ این دستورها هنوز از طریق یک لایه سازگاری به systemd منتقل می‌شوند، اما روی RHEL 9 و AlmaLinux/Rocky 9 بسته‌های initscripts و chkconfig به‌صورت پیش‌فرض نصب نیستند و اصلا چنین دستوری وجود ندارد؛ /etc/init.d/named هم روی هیچ‌کدام از نسخه‌های امروزی موجود نیست. معادل امروزی را بنویسید:

service named restart      →  systemctl restart named
/etc/init.d/named restart  →  systemctl restart named
chkconfig named on         →  systemctl enable named

برای اعمال تغییرات بعدی فایل zone نیازی به ریستارت کامل نیست:

rndc reload

باز کردن پورت ۵۳ در فایروال

# firewalld در خانواده RHEL
firewall-cmd --permanent --add-service=dns
firewall-cmd --reload

# ufw در اوبونتو
ufw allow 53/udp
ufw allow 53/tcp

# روش قدیمی iptables در CentOS 6 (منسوخ)
# iptables -I INPUT -p udp --dport 53 -j ACCEPT
# service iptables save

چطور مطمئن شویم کار کرده است

تست را از یک ماشین دیگر انجام دهید، نه از خود سرور. یک نام کاملا تصادفی بپرسید که مطمئن هستید وجود ندارد؛ اگر آی‌پی شما برگشت، wildcard کار می‌کند:

dig @SERVER_IP hich-domaini-nist-1234.com A +short
dig @SERVER_IP www.example-park.ir A +short

# تست روی TCP هم فراموش نشود
dig @SERVER_IP example.com A +tcp +short

خروجی باید در هر سه حالت دقیقا آی‌پی سرور شما باشد. سپس برای یک دامنه واقعی که نیم‌سرورش را تغییر داده‌اید بررسی کنید مسیر کامل درست است:

dig NS yourdomain.ir +short
curl -I http://yourdomain.ir

سمت وب‌سرور: نمایش صفحه پیش‌فرض یا صفحه پارک

DNS فقط بازدیدکننده را به آی‌پی شما می‌رساند؛ اینکه چه صفحه‌ای ببیند به وب‌سرور بستگی دارد. Apache برای Host header ناشناخته، اولین VirtualHost تعریف‌شده روی آن پورت را نمایش می‌دهد:

<VirtualHost *:80>
    ServerName parking.yourdomain.com
    DocumentRoot /var/www/parking
    <Directory /var/www/parking>
        # Apache 2.4 و بالاتر
        Require all granted

        # معادل منسوخ در Apache 2.2
        # Order allow,deny
        # Allow from all
    </Directory>
</VirtualHost>

اگر روی سروری با Apache 2.2 هستید فقط دو خط Order و Allow کار می‌کنند و Require شناخته نمی‌شود؛ از Apache 2.4 به بعد برعکس است و باید ماژول mod_access_compat فعال باشد تا نحو قدیمی هنوز پذیرفته شود. پیکربندی جدید را با Require بنویسید. در Nginx معادل همین کار با default_server انجام می‌شود:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    root /var/www/parking;
    index index.html;
}

قبل از افزودن این بلاک مطمئن شوید روی همان پورت از قبل یک default_server دیگر (معمولا در /etc/nginx/sites-enabled/default) تعریف نشده باشد؛ دو default_server روی یک پورت باعث خطای duplicate default server می‌شود و Nginx اصلا بالا نمی‌آید. همیشه با nginx -t تست کنید و بعد systemctl reload nginx بزنید.

مشکلات رایج

  • سرویس named بالا نمی‌آید — تقریبا همیشه به‌خاطر باقی ماندن zone ریشه از نوع hint است. خروجی named-checkconf و journalctl -u named دقیقا همین را می‌گوید.
  • خطای permission denied هنگام بارگذاری zone — مالکیت یا برچسب SELinux فایل درست نیست. مرحله ۵ را دوباره اجرا کنید.
  • dig از بیرون timeout می‌شود — یا listen-on هنوز روی 127.0.0.1 است، یا فایروال سرور بسته است، یا دیتاسنتر پورت ۵۳ ورودی را فیلتر کرده است.
  • پاسخ REFUSED می‌گیرید — مقدار allow-query هنوز روی localhost مانده است.
  • همه چیز درست است ولی دامنه‌ها به سرور نمی‌رسند — یا نیم‌سرورها در پنل ثبت‌کننده تعریف نشده‌اند، یا host record و glue ندارند، یا TTL رکوردهای NS قبلی هنوز کش است. با dig NS yourdomain.ir از یک ریزالور بیرونی بررسی کنید.
  • مرورگر هنوز صفحه قبلی را نشان می‌دهد — کش سمت کلاینت است. در لینوکس resolvectl flush-caches (دستور قدیمی systemd-resolve --flush-caches منسوخ شده) و در ویندوز ipconfig /flushdns.
  • خود سرور دیگر به اینترنت وصل نمی‌شود — همان هشدار ابتدای مطلب؛ /etc/resolv.conf را روی ریزالور بیرونی بگذارید.

مسیرها در سایر توزیع‌ها

توجه کنید این آموزش برای CentOS نوشته شده و در سایر سیستم‌عامل‌ها ممکن است مسیرها اندکی متفاوت باشد. جمع‌بندی تفاوت‌ها:

  • AlmaLinux و Rocky و RHEL نسخه ۸ و ۹ — دقیقا مثل CentOS: پیکربندی در /etc/named.conf، فایل‌های zone در /var/named، نام سرویس named.
  • CentOS — نسخه ۶ در سال ۲۰۲۰، نسخه ۸ در سال ۲۰۲۱ و نسخه ۷ در سال ۲۰۲۴ به پایان پشتیبانی رسیدند. اگر هنوز روی CentOS هستید، برای یک سرور عمومی که پورت ۵۳ آن باز است، مهاجرت به AlmaLinux یا Rocky را جدی بگیرید.
  • Debian و Ubuntu — بسته bind9، تعریف zone در /etc/bind/named.conf.local، فایل zone در /etc/bind/، zone ریشه پیش‌فرض در /etc/bind/named.conf.default-zones. نام سرویس در نسخه‌های قدیمی‌تر bind9 و در نسخه‌های جدیدتر named است و معمولا هر دو به هم alias شده‌اند.

چه زمانی این کار را نکنید

اگر سرور شما همزمان وب‌سرور سایت‌های واقعی، سرور ایمیل یا ریزالور شبکه است، سراغ zone ریشه نروید. برای پارک کردن چند دامنه مشخص، تعریف یک zone جداگانه برای هر دامنه هم امن‌تر است و هم سرور را از resolve کردن اینترنت محروم نمی‌کند.

روی سرورهایی که پنل مدیریت مثل cPanel یا DirectAdmin یا Plesk دارند این تغییر را دستی اعمال نکنید؛ این پنل‌ها خودشان named.conf و فایل‌های zone را می‌سازند و بازنویسی می‌کنند و تغییر دستی شما یا از بین می‌رود یا کل DNS مشتریان را از کار می‌اندازد.

و اگر هدف شما اصلا اینترنت نیست و می‌خواهید در یک شبکه داخلی یا کپتیو پورتال، تمام درخواست‌های DNS کاربران را به یک صفحه هدایت کنید، ابزار مناسب‌تر dnsmasq است که با یک خط address=/#/192.0.2.10 همین کار را روی ریزالور محلی انجام می‌دهد، بدون آنکه لازم باشد سروری را برای ریشه DNS جهانی authoritative اعلام کنید.