اگر دامنهای مثل مثال.ir را در مرورگر تایپ کردهاید و بهجای باز شدن سایت، خطای ERR_NAME_NOT_RESOLVED گرفتهاید، یا در پنل DNS دامنهتان بهجای حروف فارسی یک رشتهٔ عجیب مثل xn--mgbh0fb.ir دیدهاید، مشکل از تنظیمات شما نیست. شما با لایهٔ ذخیرهسازی دامنهٔ فارسی روبهرو شدهاید که از سال ۲۰۰۳ در RFC 3492 استاندارد شده و هنوز هم بیشتر تیمهای فنی آن را اشتباه به کار میبرند.
دامنهٔ فارسی در DNS وجود ندارد. آنچه وجود دارد یک برچسب ASCII است که با الگوریتم Punycode از رشتهٔ یونیکد ساخته میشود. هر جایی که با «دامنهٔ فارسی» طرف باشید، در واقع با دو نمایش از یک چیز طرفید: نمایش قابلخواندن برای انسان و نمایش قابلحل برای رزولور.
Punycode دقیقاً چه چیزی را ذخیره میکند
برچسبهای دامنه در DNS محدود به ۶۳ بایت و کل نام محدود به ۲۵۳ بایت است. Punycode رشتهٔ یونیکد را به یک دنبالهٔ ASCII تبدیل میکند که با پیشوند xn-- شروع میشود. برای مثال:
مثال.ir → xn--mgbh0fb.ir
ایران.ir → xn--mgba3a4f16a.ir
تست.ir → xn--hgbk6aj7f.ir
این تبدیل قطعی است. یک ورودی همیشه یک خروجی میدهد و هیچ رزولوری نمیتواند آن را جور دیگری تفسیر کند. اما نکتهٔ مهم این است که این تبدیل در لایهٔ اپلیکیشن انجام میشود، نه در DNS. یعنی وقتی در مرورگر مثال.ir میزنید، مرورگر اول با کتابخانهٔ IDNA آن را به xn--mgbh0fb.ir تبدیل میکند و بعد کوئری DNS میفرستد.
در ترمینال این تبدیل خودکار نیست. اگر با dig مستقیم دامنهٔ فارسی بزنید، بسته به نسخهٔ ابزار، ممکن است پاسخ بگیرید یا خطا. برای اطمینان همیشه از فرم ASCII استفاده کنید:
dig +short xn--mgbh0fb.ir A
dig +short xn--mgbh0fb.ir MX
dig +short xn--mgbh0fb.ir TXT
اگر میخواهید ببینید یک دامنهٔ فارسی به چه رشتهای تبدیل میشود، ابزار idn روی لینوکس یا idn2 روی نسخههای جدیدتر گنو کار را راه میاندازد:
echo "مثال.ir" | idn2
# xn--mgbh0fb.ir
چرا بعضی مرورگرها دامنهٔ فارسی را خام نشان میدهند
مرورگرها از سالها پیش IDN را پشتیبانی میکنند، اما همه یکسان رفتار نمیکنند. کروم و فایرفاکس دامنه را به شکل یونیکد نمایش میدهند، ولی فقط اگر همهٔ برچسبها از یک خط و یک زبان باشند. اگر یک برچسب فارسی و یک برچسب لاتین قاطی شود، مرورگر برای جلوگیری از حملهٔ جعل، نام را به فرم xn-- برمیگرداند.
این رفتار عمدی است. حملهای به نام IDN homograph وجود دارد که در آن مهاجم حرفی از یک الفبای دیگر را جایگزین میکند که در فونت شبیه حرف اصلی دیده میشود. مثلاً حرف «ی» فارسی و حرف «ي» عربی در بسیاری از فونتها یکسان رندر میشوند، ولی کد یونیکد متفاوتی دارند و در نتیجه Punycode متفاوتی میسازند. یک دامنهٔ فیشینگ میتواند دقیقاً شبیه دامنهٔ بانک شما دیده شود و در DNS کاملاً جای دیگری باشد.
مرورگرها با سیاست «همه یا هیچ» با این مسئله برخورد میکنند: یا کل نام یکدست است و نمایش یونیکد میدهد، یا مشکوک است و فرم ASCII را نشان میدهد. اینجا اشتباه میکنند: بسیاری از تیمهای فنی فکر میکنند چون مرورگر دامنه را فارسی نشان میدهد، پس همهجا همینطور کار میکند. بعد در لاگ وبسرور میبینند که Host header به شکل xn--mgbh0fb.ir آمده و vhost آنها مطابقت نمیکند و سایت پیشفرض برمیگردد. نشانهاش این است که سایت با IP مستقیم باز میشود ولی با دامنه نه، و در access.log نام دامنه به فرم ASCII ثبت شده.
رکورد DNS را با کدام فرم بسازیم
پاسخ کوتاه: همیشه با فرم Punycode. پنلهای DNS ایرانی معمولاً هر دو را قبول میکنند و خودشان تبدیل میکنند، ولی پنلهای خارجی و APIها اغلب فقط ASCII را میپذیرند. اگر رکورد را با حروف فارسی ثبت کنید و پنل تبدیل را اشتباه انجام دهد، رکورد در zone file با نامی ذخیره میشود که رزولور هرگز کوئری نمیکند.
یک zone file سالم برای دامنهٔ مثال.ir اینطور است:
$ORIGIN xn--mgbh0fb.ir.
@ IN A 185.xx.xx.xx
www IN CNAME xn--mgbh0fb.ir.
@ IN MX 10 mail.xn--mgbh0fb.ir.
@ IN TXT "v=spf1 include:_spf.example.com ~all"
دقت کنید که در سمت راست رکورد MX هم باید فرم Punycode بنویسید، وگرنه رزولور نمیتواند mail server را پیدا کند. همین قاعده برای رکوردهای NS و CNAME هم برقرار است. اگر دامنه را به یک سرور مجازی وصل میکنید، اتصال دامنه به سرور مجازی؛ راهنمای کامل رکورد A تا SSL ترتیب دقیق رکوردها و زمان صدور گواهی را توضیح میدهد.
SSL و دامنهٔ فارسی: کجا گیر میکنید
گواهیهای SSL از سال ۲۰۰۸ دامنهٔ IDN را پشتیبانی میکنند، ولی فقط در فرم Punycode. اگر در CSR نام را فارسی وارد کنید، Let's Encrypt با خطای Error creating new order :: Cannot issue for "مثال.ir": Domain name contains an invalid character رد میکند. راه درست این است که در certbot همیشه فرم ASCII را بدهید:
certbot certonly --webroot -w /var/www/html \
-d xn--mgbh0fb.ir -d www.xn--mgbh0fb.ir
بعد از صدور، در nginx هم باید server_name را با فرم ASCII بنویسید. اگر فارسی بنویسید، nginx آن را بهعنوان یک رشتهٔ بایتی متفاوت میبیند و درخواست به vhost پیشفرض میرود. این یکی از رایجترین دلایل «SSL نصب شده ولی سایت باز نمیشود» در دامنههای فارسی است.
مقایسهٔ دو رویکرد در ثبت و مدیریت
| موضوع | فرم یونیکد (فارسی) | فرم Punycode (ASCII) |
|---|---|---|
| نمایش به کاربر | خوانا و قابلتبلیغ | غیرقابلخواندن |
| پشتیبانی پنل DNS | متغیر، بعضی پنلها تبدیل میکنند | همهجا کار میکند |
| پشتیبانی SSL | رد میشود | استاندارد |
| ریسک homograph | بالا، چون کاربر تفاوت را نمیبیند | صفر، چون کد یونیکد صریح است |
| لاگ و مانیتورینگ | نیاز به نرمالسازی | مستقیم قابلاستفاده |
اگر مجبورم بین این دو یکی را انتخاب کنم، برای هر چیزی که به زیرساخت مربوط است، Punycode را انتخاب میکنم. فرم یونیکد را فقط در محتوای سایت و تبلیغات به کار میبرم. دلیلش ساده است: هر ابزاری که با دامنه کار میکند، از curl تا openssl s_client تا فایلهای zone، در نهایت با ASCII حرف میزند. اگر از ابتدا با ASCII کار کنید، هیچوقت غافلگیر نمیشوید.
تنها موردی که فرم یونیکد را ترجیح میدهم، دامنهای است که کاملاً برای برندینگ و ترافیک ارگانیک فارسی طراحی شده و تیم فنیاش میداند که باید همهجا نرمالسازی کند. در آن حالت، یک تابع نرمالسازی در کد اپلیکیشن که هر ورودی را قبل از استفاده به Punycode تبدیل کند، اجباری است.
نکتهای که در عمل زیاد میبینم
در لاگهای DNS و وبسرور، دامنههای فارسی همیشه به فرم ASCII ظاهر میشوند. اگر در حال عیبیابی هستید و نمیدانید چرا یک زیردامنه کار نمیکند، اول ببینید در zone file با کدام فرم ثبت شده. ابزار dig را با فرم ASCII بزنید و اگر پاسخ آمد، مشکل از سمت اپلیکیشن است نه DNS. برای بررسی رکوردها و مقایسهٔ پاسخ رزولورهای مختلف، بررسی رکوردهای DNS سریعترین راه است. اگر هم مطمئن نیستید رکورد درست منتشر شده یا کش قدیمی جواب میدهد، انتشار DNS چیست و چرا زمان میبرد؟ راهنمای کامل TTL و کش ترتیب بررسی را با عدد TTL واقعی توضیح میدهد.
برای دامنههای .ir یک لایهٔ اضافه هم وجود دارد: ثبت و مدیریت آنها از طریق ایرنیک انجام میشود و شناسهٔ کاربری لازم است. اگر در مرحلهٔ ثبت گیر کردهاید، راهنمای کامل ثبت و مدیریت دامنه ir با شناسه ایرنیک مراحل را قدمبهقدم پوشش میدهد. و اگر دامنهتان نزدیک انقضاست، تمدید دامنه؛ راهنمای کامل مراحل انقضا و نجات دامنه پنجرهٔ زمانی نجات را دقیق مشخص میکند.
یک نکتهٔ عملی دیگر: اگر روی سروری میزبانی میکنید که چند دامنهٔ فارسی دارد، در nginx از server_name با فرم ASCII استفاده کنید و برای هر دامنه یک بلاک جدا بسازید. ترکیب چند دامنهٔ فارسی در یک بلاک با wildcard کار میکند، ولی اگر زیردامنههای پویا دارید، رکورد wildcard و مدیریت زیردامنههای پویا در DNS تفاوت رفتار catch-all و wildcard را روشن میکند. برای میزبانی خود سایت هم اگر به یک محیط لینوکسی پایدار نیاز دارید، هاست لینوکس گزینهای است که پیکربندی nginx و certbot روی آن بدون دردسر اضافه انجام میشود.
پرسشهای پرتکرار
دامنه فارسی در DNS چطور ذخیره میشود؟
دامنهٔ فارسی در DNS به شکل یک برچسب ASCII با پیشوند xn-- ذخیره میشود که با الگوریتم Punycode از رشتهٔ یونیکد ساخته شده است. مثلاً مثال.ir به xn--mgbh0fb.ir تبدیل میشود. رزولورها هیچوقت فرم یونیکد را نمیبینند؛ تبدیل در مرورگر یا اپلیکیشن انجام میشود.
چرا مرورگر دامنه فارسی من را به شکل xn-- نشان میدهد؟
مرورگر وقتی برچسبهای دامنه از چند خط یا چند زبان مختلف باشند، برای جلوگیری از حملهٔ شباهت بصری، نام را به فرم ASCII برمیگرداند. این رفتار عمدی است و نشانهٔ خطای تنظیمات شما نیست. اگر همهٔ برچسبها فارسی و یکدست باشند، مرورگر نام را به شکل خوانا نشان میدهد.
آیا میتوانم برای دامنه فارسی گواهی SSL بگیرم؟
بله، ولی فقط با فرم Punycode. اگر در درخواست گواهی نام فارسی وارد کنید، مرجع صدور با خطای کاراکتر نامعتبر رد میکند. در certbot و در server_name نگینکس همیشه فرم xn-- را بنویسید.
خطر اصلی دامنه فارسی برای امنیت چیست؟
حملهٔ IDN homograph؛ یعنی جایگزینی یک حرف با حرفی از الفبای دیگر که در فونت شبیه آن دیده میشود ولی کد یونیکد و در نتیجه Punycode متفاوتی دارد. کاربر در نوار آدرس تفاوتی نمیبیند و به دامنهٔ مهاجم میرود. برای همین است که در لاگها و پیکربندیها کار با فرم ASCII امنتر است.