حزمة فوق المطبات في مجموعة تفرعات بعيدة عن DNS ...
ل. كاجانوف "هاملت في القاع"
عند تطوير تطبيق شبكة ، قد يكون من الضروري في بعض الأحيان تشغيله محليًا ، ولكن الوصول إليه باستخدام اسم مجال حقيقي. الحل القياسي المثبت هو تسجيل المجال في ملف المضيفين. ناقص المنهج هو أن المضيفين يحتاجون إلى مراسلات واضحة لأسماء المجال ، أي لا يدعم النجوم. أي إذا كانت هناك مجالات في النموذج:
dom1.example.com, dom2.example.com, dom3.example.com, ................ domN.example.com,
ثم في المضيفين تحتاج إلى تسجيل كل منهم. في بعض الحالات ، لا يعرف نطاق المستوى الثالث مقدمًا. هناك رغبة (أنا أكتب لنفسي ، قد يقول شخص ما أنه من الطبيعي) أن أتقدم بخط مثل هذا:
*.example.com
قد يكون حل المشكلة هو استخدام خادم DNS الخاص بك ، والذي سيعالج الطلبات وفقًا للمنطق المحدد. هناك مثل هذه الخوادم ، مجانية تمامًا مع واجهة رسومية مريحة ، مثل CoreDNS . يمكنك أيضًا تغيير سجلات DNS على جهاز التوجيه. أخيرًا ، استخدم خدمة مثل xip.io ، فهي ليست خادم DNS متكامل تمامًا ، ولكنها مثالية لبعض المهام. باختصار ، توجد حلول جاهزة ، يمكنك استخدامها وليس عناء.
لكن هذه المقالة تصف طريقة أخرى - كتابة الدراجة الخاصة بك ، وهي نقطة الانطلاق لإنشاء أداة مثل تلك المذكورة أعلاه. سنكتب وكيل DNS الخاص بنا ، والذي سيستمع إلى استفسارات DNS الواردة ، وإذا كان اسم المجال المطلوب في القائمة ، فسوف يُرجع عنوان IP المحدد ، وإذا لم يكن كذلك ، فسوف يطلب ملقم DNS أعلى ويعيد توجيه الاستجابة المستلمة دون تغييرات في البرنامج الطالب.
في الوقت نفسه ، يمكنك تسجيل الطلبات والردود الواردة عليها. نظرًا لأن DNS يحتاجه الجميع - المتصفحات والمراسلات ومكافحة الفيروسات وخدمات نظام التشغيل ، وما إلى ذلك ، فقد تكون مفيدة للغاية.
المبدأ بسيط. في إعدادات اتصال الشبكة لـ IPv4 ، نغير عنوان خادم DNS إلى عنوان الجهاز من خلال وكيل DNS المكتوب ذاتي التشغيل (127.0.0.1 ، إذا لم نعمل على الشبكة) ، وفي إعداداته ، نحدد عنوان خادم DNS الأعلى. ويبدو أن هذا كل شيء!
لن نستخدم الوظائف القياسية لحل أسماء النطاقات nslookup و nsresol ، لذلك لن تؤثر إعدادات نظام DNS ومحتويات ملف المضيفين على تشغيل البرنامج. اعتمادًا على الموقف ، قد يكون مفيدًا أو لا ، تحتاج فقط إلى تذكر هذا. للبساطة ، نحن نقتصر على تنفيذ الوظائف الأساسية نفسها:
- خداع IP فقط لسجلات النوع A (عنوان المضيف) والفئة IN (الإنترنت)
- عناوين IP المخادعة فقط الإصدار 4
- اتصال للطلبات الواردة المحلية عبر UDP فقط
- اتصال خادم DNS المنبع عبر UDP أو TLS
- إذا كان هناك العديد من واجهات الشبكة ، سيتم قبول الطلبات المحلية الواردة على أي منها
- لا يوجد دعم EDNS
الحديث عن الاختباراتهناك عدد قليل من اختبارات الوحدة في المشروع. صحيح أنهم يعملون وفقًا للمبدأ: لقد أطلقته ، وإذا تم عرض شيء عاقل في وحدة التحكم ، فكل شيء على ما يرام ، ولكن إذا حدث استثناء ، فهناك مشكلة. ولكن حتى مثل هذا النهج الخرقاء يسمح لك بتحديد مكان المشكلة بنجاح ، لذلك الوحدة.
ابدأ - الخادم على المنفذ 53
لنبدأ. بادئ ذي بدء ، تحتاج إلى تعليم التطبيق لقبول استعلامات DNS الواردة. نكتب خادم TCP بسيط يستمع إلى المنفذ 53 ويسجل الاتصالات الواردة. في خصائص اتصال الشبكة ، نكتب عنوان خادم DNS 127.0.0.1 ، ونطلق التطبيق ، ونذهب إلى المتصفح لعدة صفحات - و ... الصمت في وحدة التحكم ، يعرض المستعرض الصفحة بشكل طبيعي. حسنًا ، نغير TCP إلى UDP ، نبدأ ، نذهب من خلال المتصفح - في المتصفح يوجد خطأ في الاتصال ، سكب بعض البيانات الثنائية في وحدة التحكم. لذلك ، يرسل النظام الطلبات عبر UDP ، وسوف نستمع إلى الاتصالات الواردة عبر UDP على المنفذ 53. نصف ساعة من العمل ، منها 15 دقيقة google كيفية رفع خادم TCP و UDP على NodeJS - وقمنا بحل مهمة حجر الأساس للمشروع ، الذي يحدد هيكل التطبيق في المستقبل. الكود كما يلي:
const dgram = require('dgram'); const server = dgram.createSocket('udp4'); (function() { server.on('error', (err) => { console.log(`server error:\n${err.stack}`); server.close(); }); server.on('message', async (localReq, linfo) => { console.log(localReq);
القائمة 1. الحد الأدنى للرمز المطلوب لتلقي استعلامات DNS المحلية
النقطة التالية هي قراءة الرسالة لفهم ما إذا كان من الضروري إعادة عنوان IP الخاص بنا استجابةً له ، أو ببساطة نقله.
رسالة DNS
تم وصف بنية رسالة DNS في RFC-1035. تتبع كل من الطلبات والاستجابات هذا الهيكل ، ومن حيث المبدأ ، تختلف في علامة واحدة (حقل QR) في رأس الرسالة. تتضمن الرسالة خمسة أقسام:
+---------------------+ | Header | +---------------------+ | Question | the question for the name server +---------------------+ | Answer | RRs answering the question +---------------------+ | Authority | RRs pointing toward an authority +---------------------+ | Additional | RRs holding additional information +---------------------+
بنية (هياكل) رسائل DNS العامة https://tools.ietf.org/html/rfc1035#section-4.1
تبدأ رسالة DNS برأس بطول ثابت (هذا ما يسمى قسم رأس ) ، والذي يحتوي على حقول من 1 بت إلى وحدتي بايت (وبالتالي ، يمكن أن تحتوي بايت واحد في الرأس على عدة حقول). يبدأ العنوان بحقل المعرف - هذا هو معرّف الطلب 16 بت ، ويجب أن يكون للرد نفس المعرف. فيما يلي الحقول التي تصف نوع الطلب ونتائج تنفيذه وعدد السجلات في كل قسم من الأقسام التالية من الرسالة. صفهم جميعًا لفترة طويلة ، لذا من يهتم - جيدًا في RFC: https://tools.ietf.org/html/rfc1035#section-4.1.1 . قسم الرأس موجود دائمًا في رسالة DNS.
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
هيكل (هياكل) رأس رسالة DNS https://tools.ietf.org/html/rfc1035#section-4.1.1
قسم الأسئلة
يحتوي قسم الأسئلة على إدخال يخبر الخادم بالتحديد عن المعلومات المطلوبة منه. من الناحية النظرية ، في قسم هذه السجلات ، يمكن أن يكون هناك سجل واحد أو عدة أرقام ، ويُشار إلى رقمها في حقل QDCOUNT في رأس الرسالة ، ويمكن أن يكون 0 أو 1 أو أكثر. ولكن في الممارسة العملية ، يمكن أن يحتوي قسم الأسئلة على إدخال واحد فقط. إذا احتوى قسم الأسئلة على عدة سجلات ، وقد يؤدي أحدها إلى حدوث خطأ أثناء معالجة الطلب على الخادم ، فسيظهر موقف غير محدد. على الرغم من أن الخادم سيعود برمز خطأ في حقل RCODE في رسالة الاستجابة ، إلا أنه لن يكون قادرًا على الإشارة عند معالجة السجل الذي حدثت فيه المشكلة ، لا توضح المواصفات ذلك. لا تحتوي السجلات أيضًا على حقول تحتوي على إشارة للخطأ ونوعه. لذلك ، هناك اتفاقية (غير موثقة) ، والتي بموجبها يمكن أن يحتوي قسم الأسئلة على سجل واحد فقط ، وحقل QDCOUNT له قيمة 1. كما أنه ليس من الواضح تمامًا كيفية معالجة الطلب على جانب الخادم ، إذا كان لا يزال يحتوي على عدة سجلات في السؤال . ينصح شخص ما بإرجاع رسالة بها خطأ في الطلب. على سبيل المثال ، يعالج Google DNS السجل الأول فقط في قسم الأسئلة ، بل يتجاهل الباقي. على ما يبدو ، يبقى هذا وفقًا لتقدير مطوري خدمات DNS.
في استجابة رسالة DNS من الخادم ، يوجد قسم السؤال أيضًا ويجب نسخ نسخة السؤال بالكامل (لتجنب التعارضات ، في حالة عدم كفاية حقل معرف واحد).
يحتوي الإدخال الوحيد في قسم الأسئلة على الحقول: QNAME (اسم المجال) ، QTYPE (النوع) ، QCLASS (الفئة). QTYPE و QCLASS عبارة عن أرقام مزدوجة البايت تشير إلى نوع الطلب وفئة الطلب. يتم وصف الأنواع والفئات المحتملة في RFC-1035 https://tools.ietf.org/html/rfc1035#section-3.2 ، كل شيء واضح هناك. لكن فيما يتعلق بطريقة تسجيل اسم النطاق ، سنناقش بمزيد من التفصيل في قسم "تنسيق تسجيل أسماء النطاقات".
في حالة وجود استعلام ، تنتهي رسالة DNS غالبًا بقسم السؤال ، وأحيانًا يتبعها القسم الإضافي .
إذا حدث خطأ أثناء معالجة الطلب على الخادم (على سبيل المثال ، تم تشكيل طلب وارد بشكل غير صحيح) ، فإن رسالة الاستجابة ستنتهي أيضًا بقسم السؤال أو القسم الإضافي ، وسيحتوي حقل RCODE لرأس رسالة الاستجابة على رمز خطأ.
الإجابة ، السلطة ، وأقسام إضافية
الأقسام التالية هي " الإجابة" و " الصلاحية" و " إضافية" (يتم تضمين " الإجابة" و " التفويض" فقط في رسالة DNS الخاصة بالرد ، وقد تظهر " إضافية" في الطلب وفي الرد). إنها اختيارية ، أي قد يكون أي منها حاضرًا أم لا ، حسب الطلب. هذه الأقسام لها نفس البنية وتحتوي على معلومات في شكل ما يسمى "سجلات الموارد" ( سجل resourse ، أو RR). بالمعنى المجازي ، كل قسم من هذه الأقسام عبارة عن مجموعة من سجلات الموارد ، والسجل عبارة عن كائن به حقول. يمكن أن يحتوي كل قسم على سجل واحد أو أكثر ، ويشار إلى رقمهم في الحقل المقابل في رأس الرسالة (ANCOUNT ، NSCOUNT ، ARCOUNT ، على التوالي). على سبيل المثال ، يؤدي طلب IP الخاص بالمجال "google.com" إلى إرجاع عدة عناوين IP ، لذلك سيكون هناك أيضًا عدة إدخالات في قسم " الإجابة" ، واحد لكل عنوان. إذا كان القسم غائبًا ، فسيحتوي حقل الرأس المقابل على 0.
يبدأ كل سجل مورد (RR) بحقل NAME يحتوي على اسم مجال. تنسيق هذا الحقل هو نفسه حقل QNAME في قسم الأسئلة .
بجانب NAME توجد الحقول TYPE (نوع السجل) ، و CLASS (فئتها) ، كلا الحقلين رقمي 16 بت ، ويشير إلى نوع السجل وفئته. يشبه هذا أيضًا قسم الأسئلة ، مع اختلاف أن QTYPE و QCLASS يمكن أن يكون لهما نفس القيم مثل TYPE و CLASS ، وبعضهما خاص بهما. هذا هو ، في لغة علمية جافة ، مجموعة من القيم QTYPE و QCLASS هي مجموعة شاملة من القيم TYPE و CLASS. اقرأ المزيد حول الاختلافات على https://tools.ietf.org/html/rfc1035#section-3.2.2 .
الحقول المتبقية هي:
- TTL هو رقم 32 بت يشير إلى الوقت الذي استغرقه السجل (بالثواني).
- RDLENGTH هو رقم 16 بت يشير إلى طول حقل RDATA التالي بالبايت.
- RDATA هو في الواقع حمولة ، ويعتمد التنسيق على نوع السجل. على سبيل المثال ، بالنسبة لسجل النوع A (عنوان المضيف) والفئة IN (الإنترنت) ، فهذه هي 4 بايت تمثل عنوان IPv4.
تنسيق تسجيل أسماء النطاق هو نفسه بالنسبة لحقول QNAME و NAME ، وكذلك بالنسبة لحقل RDATA ، إذا كان CNAME أو MX أو NS أو سجل فئة آخر يفترض أن يكون اسم المجال هو النتيجة.
اسم النطاق هو سلسلة من التسميات (أقسام الاسم ، والنطاقات الفرعية - هذه تسمية باللغة الأصلية ، ولم أجد ترجمة أفضل). التسمية عبارة عن بايت واحد من الطول يحتوي على رقم - طول محتويات التسمية بالبايت ، متبوعًا بتسلسل بايت من الطول المحدد. تتبع الملصقات واحدة تلو الأخرى حتى يتم العثور على بايت طوله يحتوي على 0. يمكن أن يكون التصنيف الأول على الفور بطول الصفر ، وهذا يشير إلى مجال الجذر (مجال الجذر) باسم مجال فارغ (تتم كتابته أحيانًا باسم "").
في الإصدارات السابقة من DNS ، يمكن أن يكون للبايتات في التصنيف أي قيمة من (0 إلى 255). كانت هناك قواعد ذات طبيعة توصية عاجلة: أن تبدأ التسمية بحرف ، وتنتهي بحرف أو رقم ، وتحتوي فقط على أحرف أو أرقام أو واصلات في ترميز ASCII ذي 7 بتات ، مع صفر بت مهم. تتطلب مواصفات EDNS الحالية بالفعل الامتثال لهذه القواعد بوضوح ، دون انحراف.
يتم استخدام البتات الأكثر أهمية في البايت الطول كسمة نوع علامة. إذا كانت صفرية ( 0b00xxxxxx ) ، فهذه تسمية عادية ، وتشير البتات المتبقية من بايت الطول إلى عدد بايتات البيانات المضمنة في تكوينها. الحد الأقصى لطول التسمية هو 63 حرفًا. 63 في الترميز الثنائي هو 0b00111111 فقط.
إذا كان الباقتان الأكثر أهمية هما 0 و 1 ( 0b01xxxxxx ) ، على التوالي ، فهذا يعد تسمية من النوع الممتد لمعيار EDNS ( https://tools.ietf.org/html/rfc2671#section-3.1 ) ، والتي جاءت إلينا من 1 فبراير 2019. ستحتوي البتات الستة الأدنى على قيمة التسمية. نحن لا نناقش EDNS في هذه المقالة ، ولكن من المفيد معرفة أن هذا يحدث أيضًا.
مزيج من اثنين من أهم البتات ، أي ما يعادل 1 و 0 ( 0b10xxxxxx ) ، محفوظة للاستخدام في المستقبل.
إذا كانت كلتا البتات العالية تساوي 1 ( 0b11xxxxxx ) ، فهذا يعني أن أسماء النطاق مضغوطة ( ضغط ) ، وسنتناول هذا بمزيد من التفاصيل.
ضغط اسم المجال
لذلك ، إذا كانت البايتة ذات الطول بها اثنين من البتات العالية تساوي 1 ( 0b11xxxxxx ) ، فهذه علامة على ضغط اسم المجال. يستخدم الضغط لجعل الرسائل أقصر وأكثر إيجازًا. هذا صحيح بشكل خاص عند العمل على UDP ، عندما يقتصر الطول الإجمالي لرسالة DNS على 512 بايت (على الرغم من أن هذا هو المعيار القديم ، راجع https://tools.ietf.org/html/rfc1035#section-2.3.4 حدود الحجم ، EDNS الجديدة يسمح بإرسال رسائل UPD وأطول). يكمن جوهر العملية في أنه إذا كانت رسالة DNS تحتوي على أسماء مجال لها نفس النطاقات الفرعية ذات المستوى الأعلى (على سبيل المثال ، mail.yandex.ru و yandex.ru ) ، فبدلاً من إعادة تحديد اسم المجال بالكامل ، رقم البايت في رسالة DNS التي منها مواصلة القراءة اسم المجال. يمكن أن يكون هذا أي بايت من رسالة DNS ، ليس فقط في السجل أو القسم الحالي ، ولكن بشرط أن يكون بايت لطول تسمية المجال. لا يمكنك الرجوع إلى منتصف العلامة. لنفترض وجود مجال mail.yandex.ru في الرسالة ، ثم بمساعدة الضغط ، يمكنك أيضًا تعيين نطاقات yandex.ru و ru و root (بالطبع ، من السهل الكتابة الجذر دون ضغط ، لكن من الممكن تقنيًا القيام بذلك باستخدام الضغط) ، هنا لجعل ndex.ru لن تعمل. أيضًا ، ستنتهي جميع أسماء النطاقات المستمدة في مجال الجذر ، أي ، الكتابة ، على سبيل المثال ، mail.yandex ستفشل أيضًا.
اسم المجال يمكنه:
- أن تكون مسجلة بالكامل دون ضغط ،
- تبدأ من المكان الذي يستخدم الضغط
- ابدأ بتسمية واحدة أو أكثر بدون ضغط ، ثم انتقل إلى الضغط ،
- تكون فارغة (للمجال الجذر).
على سبيل المثال ، نقوم بتجميع رسالة DNS ، وقد صادفنا بالفعل اسم "dom3.example.com" فيها ، والآن نحتاج إلى تحديد "dom4.dom3.example.com". في هذه الحالة ، يمكنك تسجيل قسم "dom4" بدون ضغط ، ثم التبديل إلى ضغط ، أي إضافة رابط إلى "dom3.example.com". أو بالعكس ، إذا كان الاسم "dom4.dom3.example.com" قد تمت مصادفته مسبقًا ، ثم للإشارة إلى "dom3.example.com" يمكنك استخدام الضغط على الفور بالرجوع إلى التصنيف "dom3" فيه. ما لا يمكننا فعله هو ، كما قيل بالفعل ، الإشارة إلى جزء "dom4.dom3" من خلال الضغط ، لأن الاسم يجب أن ينتهي بقسم المستوى الأعلى. إذا كنت بحاجة فجأة إلى تحديد شرائح من الوسط ، فسيتم الإشارة إليها ببساطة دون ضغط.
للبساطة ، لا يعرف برنامجنا كيفية كتابة أسماء النطاقات بالضغط ، بل يمكنه القراءة فقط. المعيار يسمح بذلك ، يجب تنفيذ القراءة بالضرورة ، والكتابة اختيارية. من الناحية الفنية ، يتم تنفيذ القراءة على هذا النحو: إذا كانت البتات الأكثر دلالة في البايتة الطولية تحتوي على 1 ، فسنقرأ البايت الذي يتبعه ، ونتعامل مع هاتين البايتتين كعدد صحيح غير موقَّع 16 بت ، بترتيب البتات End End الكبيرة. نحن نتجاهل أهم اثنين من البتات (تحتوي على 1) ، وقراءة الرقم 14 بت الناتج ، ومواصلة قراءة اسم المجال من البايت في رسالة DNS تحت الرقم المقابل لهذا الرقم.
رمز وظيفة قراءة اسم المجال هو كما يلي:
function readDomainName (buf, startOffset, objReturnValue = {}) { let currentByteIndex = startOffset;
سرد 2. قراءة أسماء المجال من استعلام DNS
رمز كامل لوظيفة لقراءة سجل DNS من المخزن المؤقت الثنائي:
سرد 3. قراءة سجل DNS من المخزن المؤقت ثنائي function parseDnsMessageBytes (buf) { const msgFields = {};
سرد 3. قراءة سجل DNS من المخزن المؤقت ثنائي
, . , , , . , DNS-, , . , .
, - server.on("message", () => {})
1. :
4. DNS- server.on('message', async (localReq, linfo) => { const dnsRequest = functions.parseDnsMessageBytes(localReq); const question = dnsRequest.questions[0];
سرد 4. معالجة استعلام DNS وارد
TLS
DNS-. , DNS- TLS (HTTPS ). DNS- TLS TCP, , TLS . TCP, RFC-7766 DNS Transport over TCP ( https://tools.ietf.org/html/rfc7766 ). , : TLS, TCP ( , DNS TCP, TLS- TCP-, ).
TLS-
TLS- , , . , TLS-, . RFC-7858 - :
In order to amortize TCP and TLS connection setup costs, clients and servers SHOULD NOT immediately close a connection after each response. Instead, clients and servers SHOULD reuse existing connections for subsequent queries as long as they have sufficient resources. In some cases, this means that clients and servers may need to keep idle connections open for some amount of time. () https://tools.ietf.org/html/rfc7858#section-3.4
, TLS-, , , , , . , 30 , , , DNS-. 30 ~ ~ , 15 60 , . , . - .
TLS- NodeJS. , TLS- :
const tls = require('tls'); const TLS_SOCKET_IDLE_TIMEOUT = 30000;
5. , TLS-
DNS-over-TLS , Google DNS. , socket = tls.connect(connectionOptions, () => {})
. NodeJS: https://nodejs.org/api/tls.html#tls_tls_connect_options_callback , .
TLS- :
const options = { port: config.upstreamDnsTlsPort,
6. TLS-
, TCP-. TCP/TLS- DNS-, , , , . TCP ( TLS), DNS- 512 , UDP (, EDNS UDP ). , DNS- UDP, . onData() 6.
const onData = (data) => {
7. TLS- DNS- 6
DNS-
, , . , ID QNAME, QTYPE QCLASS Question :
Since pipelined responses can arrive out of order, clients MUST match responses to outstanding queries on the same TLS connection using the Message ID. If the response contains a Question Section, the client MUST match the QNAME, QCLASS, and QTYPE fields. () https://tools.ietf.org/html/rfc7858#section-3.3
, , , ID Question ( , ).
UDP (. 4), , -, , UDP- . , DNS-, . , -. , , UDP- -. , , .
TLS, . (IP ), , .
IP "-". , , , DNS-. , , IP , . 7:
8. 7
TLS-:
9. DNS- TLS- ( . 4)
, , . JSON, , NodeJS JSON- . JSON — , . , JSON- "comment" ( ) . , , , , . , , . , - , , NodeJS. , , . , , ; , . , - .
10. const path = require('path'); const fs = require('fs'); const CONFIG_FILE_PATH = path.resolve('./config.json'); function Module () {
قائمة 10. القارئ وحدة التكوين التكوين
المجموع
DNS- NodeJS, npm . , , , , .
GitHub
: