Do-it-खुद DNS नोड.जेएस पर प्रॉक्सी करता है

DNS के लिए दूर के जंगल में धक्कों का एक पैकेट ...
एल। कागानोव "सबसे नीचे हैमलेट"

नेटवर्क एप्लिकेशन विकसित करते समय, कभी-कभी इसे स्थानीय रूप से चलाने के लिए आवश्यक हो जाता है, लेकिन वास्तविक डोमेन नाम का उपयोग करके इसे एक्सेस करें। होस्ट फ़ाइल में डोमेन को पंजीकृत करने के लिए मानक सिद्ध समाधान है। दृष्टिकोण का माइनस यह है कि मेजबान को डोमेन नामों के स्पष्ट पत्राचार की आवश्यकता होती है, अर्थात्। सितारों का समर्थन नहीं करता है। यानी यदि फॉर्म के डोमेन हैं:


dom1.example.com, dom2.example.com, dom3.example.com, ................ domN.example.com, 

तब मेजबान में आपको उन सभी को पंजीकृत करने की आवश्यकता होती है। कुछ मामलों में, तीसरे स्तर के डोमेन को पहले से नहीं जाना जाता है। एक इच्छा है (मैं अपने लिए लिखता हूं, कोई कह सकता है कि यह सामान्य है) इस तरह से एक लाइन के साथ प्राप्त करने के लिए:


 *.example.com 

समस्या का समाधान आपके स्वयं के DNS सर्वर का उपयोग करना हो सकता है, जो निर्दिष्ट तर्क के अनुसार अनुरोधों को संसाधित करेगा। ऐसे सर्वर हैं, जो पूरी तरह से स्वतंत्र हैं और एक सुविधाजनक ग्राफिकल इंटरफ़ेस के साथ हैं, जैसे कि कोरडएनएस । आप राउटर पर DNS रिकॉर्ड भी बदल सकते हैं। अंत में, xip.io जैसी सेवा का उपयोग करें, यह काफी पूर्ण DNS सर्वर नहीं है, लेकिन यह कुछ कार्यों के लिए एकदम सही है। संक्षेप में, तैयार किए गए समाधान मौजूद हैं, आप उपयोग कर सकते हैं और परेशान नहीं कर सकते हैं।


लेकिन यह लेख एक और तरीका बताता है - अपनी खुद की बाइक लिखना, ऊपर सूचीबद्ध लोगों की तरह एक उपकरण बनाने के लिए शुरुआती बिंदु। हम अपने DNS प्रॉक्सी को लिखेंगे, जो आने वाले DNS प्रश्नों को सुनेंगे, और यदि अनुरोधित डोमेन नाम सूची में है, तो यह निर्दिष्ट आईपी वापस कर देगा, और यदि नहीं, तो यह एक उच्च DNS सर्वर का अनुरोध करेगा और अनुरोध किए गए कार्यक्रम में परिवर्तन के बिना प्राप्त प्रतिक्रिया को अग्रेषित करेगा।


उसी समय, आप अनुरोधों और उनसे प्राप्त प्रतिक्रियाओं को लॉग इन कर सकते हैं। चूंकि DNS की आवश्यकता सभी को है - ब्राउज़र, संदेशवाहक और एंटीवायरस, और ऑपरेटिंग सिस्टम सेवाएं, आदि, यह बहुत जानकारीपूर्ण हो सकता है।


सिद्धांत सरल है। IPv4 के लिए नेटवर्क कनेक्शन सेटिंग्स में, हम अपने चल रहे स्व-लिखित DNS प्रॉक्सी (127.0.0.1, यदि हम नेटवर्क पर काम नहीं कर रहे हैं) के साथ DNS सर्वर पते को मशीन के पते पर बदलते हैं, और इसकी सेटिंग्स में हम उच्च DNS सर्वर का पता निर्दिष्ट करते हैं। और, ऐसा लगता है, बस!


हम nslookup और nsresolve डोमेन नाम को हल करने के लिए मानक कार्यों का उपयोग नहीं करेंगे, इसलिए DNS सिस्टम सेटिंग्स और होस्ट्स फ़ाइल की सामग्री प्रोग्राम के संचालन को प्रभावित नहीं करेगी। स्थिति के आधार पर, यह उपयोगी हो सकता है या नहीं, आपको इसे याद रखने की आवश्यकता है। सादगी के लिए, हम खुद को मूल कार्यक्षमता के कार्यान्वयन के लिए प्रतिबंधित करते हैं:


  • IP केवल प्रकार A (होस्ट पता) और वर्ग IN (इंटरनेट) के रिकॉर्ड के लिए स्पूफिंग
  • स्पूफ़ किए गए IP पते केवल संस्करण 4 हैं
  • केवल UDP पर स्थानीय आवक अनुरोधों के लिए कनेक्शन
  • UDP या TLS के माध्यम से अपस्ट्रीम DNS सर्वर से कनेक्शन
  • यदि कई नेटवर्क इंटरफेस हैं, तो आने वाले स्थानीय अनुरोध उनमें से किसी पर भी स्वीकार किए जाएंगे
  • कोई ईडीएनएस समर्थन नहीं

परीक्षणों की बात कर रहे हैं

परियोजना में कुछ यूनिट परीक्षण हैं। सच है, वे सिद्धांत के अनुसार काम करते हैं: मैंने इसे लॉन्च किया, और अगर कंसोल में कुछ फलक प्रदर्शित होता है, तो सब कुछ ठीक है, लेकिन अगर कोई अपवाद उड़ता है, तो एक समस्या है। लेकिन यहां तक ​​कि इस तरह का एक अनाड़ी दृष्टिकोण आपको समस्या को सफलतापूर्वक स्थानीय बनाने की अनुमति देता है, इसलिए यूनिट।


स्टार्ट - सर्वर पोर्ट 53 पर


चलिए शुरू करते हैं। सबसे पहले, आपको आने वाले DNS प्रश्नों को स्वीकार करने के लिए एप्लिकेशन को सिखाना होगा। हम एक साधारण टीसीपी सर्वर लिख रहे हैं जो सिर्फ 53 पोर्ट को सुनता है और आने वाले कनेक्शन को लॉग करता है। नेटवर्क कनेक्शन के गुणों में, हम DNS सर्वर 127.0.0.1 का पता लिखते हैं, एप्लिकेशन लॉन्च करते हैं, कई पन्नों के लिए ब्राउज़र पर जाते हैं - और ... कंसोल में मौन, ब्राउज़र सामान्य रूप से पृष्ठ प्रदर्शित करता है। ठीक है, हम टीसीपी को यूडीपी में बदलते हैं, हम शुरू करते हैं, हम ब्राउज़र से जाते हैं - ब्राउज़र में एक कनेक्शन त्रुटि है, कुछ बाइनरी डेटा कंसोल में डाला जाता है। इसलिए, सिस्टम UDP के माध्यम से अनुरोध भेजता है, और हम आने वाले कनेक्शनों को UDP पोर्ट 53 पर सुनेंगे। आधे घंटे का काम, जिसमें से 15 मिनट Google को 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); //            }); server.on('listening', () => { const address = server.address(); console.log(`server listening ${address.address}:${address.port}`); }); const localListenPort = 53; const localListenAddress = 'localhost'; server.bind(localListenPort, localListenAddress); // server listening 0.0.0.0:53 }()); 

लिस्टिंग 1. स्थानीय DNS प्रश्न प्राप्त करने के लिए आवश्यक न्यूनतम कोड


अगला बिंदु यह समझने के लिए संदेश पढ़ना है कि क्या इसके जवाब में हमारे आईपी को वापस करना आवश्यक है, या बस इसे पास करें।


डीएनएस संदेश


DNS संदेश की संरचना RFC-1035 में वर्णित है। अनुरोध और प्रतिक्रिया दोनों इस संरचना का पालन करते हैं और, सिद्धांत रूप में, संदेश हेडर में एक बिट फ्लैग (क्यूआर फ़ील्ड) में भिन्न होता है। संदेश में पाँच खंड शामिल हैं:


 +---------------------+ | 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 संदेश शीर्ष लेख संरचना (s) https://tools.ietf.org/html/rfc1035#section-4.1.1


प्रश्न अनुभाग


प्रश्न अनुभाग में एक प्रविष्टि होती है जो सर्वर को यह बताती है कि उससे क्या जानकारी चाहिए। सैद्धांतिक रूप से, ऐसे रिकॉर्ड के अनुभाग में एक या कई हो सकते हैं, उनकी संख्या संदेश शीर्षलेख में QDCOUNT फ़ील्ड में इंगित की गई है, और 0, 1 या अधिक हो सकती है। लेकिन व्यवहार में, प्रश्न अनुभाग में केवल एक प्रविष्टि हो सकती है। यदि प्रश्न अनुभाग में कई रिकॉर्ड होते हैं, और उनमें से एक सर्वर पर अनुरोध को संसाधित करते समय एक त्रुटि का कारण होगा, तो एक अपरिभाषित स्थिति उत्पन्न होगी। हालांकि सर्वर प्रतिक्रिया संदेश में RCODE फ़ील्ड में एक त्रुटि कोड लौटाएगा, लेकिन यह इंगित नहीं कर पाएगा कि प्रसंस्करण कब समस्या को रिकॉर्ड करता है, विनिर्देश इसका वर्णन नहीं करता है। रिकॉर्ड में भी कोई फ़ील्ड नहीं है जिसमें त्रुटि और इसके प्रकार का संकेत है। इसलिए, एक समझौता (अविभाजित) है, जिसके अनुसार प्रश्न अनुभाग में केवल एक रिकॉर्ड हो सकता है, और QDCOUNT फ़ील्ड का मान 1 है। यह पूरी तरह से स्पष्ट नहीं है कि सर्वर साइड पर अनुरोध को कैसे संसाधित किया जाए, अगर इसमें अभी भी कई रिकॉर्ड शामिल हैं। किसी ने अनुरोध त्रुटि के साथ एक संदेश वापस करने की सलाह दी। और, उदाहरण के लिए, Google DNS प्रश्न अनुभाग में केवल पहले रिकॉर्ड की प्रक्रिया करता है, यह केवल बाकी की उपेक्षा करता है। जाहिर है, यह DNS सेवाओं के डेवलपर्स के विवेक पर बना हुआ है।


सर्वर से प्रतिक्रिया डीएनएस-संदेश में, प्रश्न अनुभाग भी मौजूद है और अनुरोध के प्रश्न को पूरी तरह से कॉपी करना चाहिए (टकराव से बचने के लिए, यदि एक आईडी फ़ील्ड पर्याप्त नहीं है)।


प्रश्न अनुभाग में केवल प्रविष्टि में फ़ील्ड शामिल हैं: QNAME (डोमेन नाम), QTYPE (प्रकार), QCLASS (वर्ग)। क्यूवाईपीईई और क्यूसीएलएस डबल-बाइट संख्याएं हैं जो अनुरोध के प्रकार और वर्ग को दर्शाती हैं। संभावित प्रकार और कक्षाएं RFC-1035 https://tools.ietf.org/html/rfc1035#section-3.2 में वर्णित हैं, वहां सब कुछ स्पष्ट है। लेकिन एक डोमेन नाम रिकॉर्ड करने की विधि पर हम "डोमेन नामों की रिकॉर्डिंग के लिए प्रारूप" अनुभाग में अधिक विस्तार से बताएंगे।


क्वेरी के मामले में, DNS संदेश सबसे अधिक बार प्रश्न अनुभाग के साथ समाप्त होता है, कभी-कभी अतिरिक्त अनुभाग इसका अनुसरण कर सकता है।


यदि सर्वर पर अनुरोध संसाधित करते समय कोई त्रुटि हुई (उदाहरण के लिए, एक आवक अनुरोध गलत तरीके से बनाया गया था), प्रतिक्रिया संदेश भी प्रश्न या अतिरिक्त अनुभाग के साथ समाप्त हो जाएगा, और प्रतिक्रिया संदेश शीर्ष लेख के RCODE फ़ील्ड में त्रुटि कोड होगा।


उत्तर , प्राधिकरण और अतिरिक्त अनुभाग


निम्नलिखित अनुभाग उत्तर , प्राधिकरण और अतिरिक्त हैं ( उत्तर और प्राधिकरण केवल प्रतिक्रिया DNS संदेश में निहित हैं, अतिरिक्त अनुरोध में और प्रतिक्रिया में दिखाई दे सकता है)। वे वैकल्पिक हैं, अर्थात् अनुरोध के आधार पर उनमें से कोई भी मौजूद हो सकता है या नहीं। इन वर्गों में एक ही संरचना होती है और तथाकथित "संसाधन रिकॉर्ड" ( रिज़ॉर्ट रिकॉर्ड , या आरआर) के प्रारूप में जानकारी होती है। व्यावहारिक रूप से बोलना, इनमें से प्रत्येक खंड संसाधन रिकॉर्ड की एक सरणी है, और एक रिकॉर्ड खेतों के साथ एक वस्तु है। प्रत्येक अनुभाग में एक या अधिक रिकॉर्ड हो सकते हैं, उनकी संख्या संदेश हेडर (ANCOUNT, NSCOUNT, ARCOUNT, क्रमशः) में संबंधित फ़ील्ड में इंगित की गई है। उदाहरण के लिए, डोमेन "google.com" के लिए एक आईपी अनुरोध कई आईपी पते लौटाएगा, इसलिए उत्तर अनुभाग में भी कई प्रविष्टियां होंगी, प्रत्येक पते के लिए एक। यदि अनुभाग अनुपस्थित है, तो संबंधित हेडर फ़ील्ड में 0 होता है।


प्रत्येक संसाधन रिकॉर्ड (RR) एक NAME फ़ील्ड से शुरू होता है जिसमें एक डोमेन नाम होता है। इस फ़ील्ड का प्रारूप प्रश्न अनुभाग के QNAME फ़ील्ड के समान है।
NAME के ​​आगे फ़ील्ड TYPE (रिकॉर्ड प्रकार), और CLASS (इसकी कक्षा) हैं, दोनों फ़ील्ड 16-बिट न्यूमेरिक हैं, रिकॉर्ड के प्रकार और वर्ग को इंगित करते हैं। यह प्रश्न अनुभाग से भी मिलता-जुलता है, इस अंतर के साथ कि इसके क्यूटीपीई और क्यूसीएलएएस में सभी प्रकार के मान हो सकते हैं जैसे कि टाइप और क्लास, और उनके कुछ और भी जो उनके लिए अद्वितीय हैं। अर्थात्, एक सूखी वैज्ञानिक भाषा में, क्यूटीपीईई और क्यूसीएलएएलएस मानों का सेट TYPE और CLES मानों का एक सुपरसेट है। Https://tools.ietf.org/html/rfc1035#section-3.2.2 पर अंतर के बारे में और पढ़ें।
शेष क्षेत्र हैं:


  • टीटीएल एक 32-बिट संख्या है जो यह दर्शाता है कि रिकॉर्ड पिछले (सेकंड में) था।
  • RDLENGTH एक 16-बिट संख्या है जो बाइट्स में अगले RDATA फ़ील्ड की लंबाई को इंगित करता है।
  • RDATA वास्तव में एक पेलोड है, प्रारूप रिकॉर्ड के प्रकार पर निर्भर करता है। उदाहरण के लिए, प्रकार A (होस्ट पता) और वर्ग IN (इंटरनेट) के रिकॉर्ड के लिए, ये 4 बाइट्स हैं जो IPv4 पते का प्रतिनिधित्व करते हैं।

डोमेन नाम रिकॉर्ड प्रारूप


डोमेन नाम रिकॉर्ड करने के लिए प्रारूप QNAME और NAME फ़ील्ड के साथ-साथ RDATA फ़ील्ड के लिए समान है, यदि यह CNAME, MX, NS या अन्य वर्ग रिकॉर्ड है जो परिणाम के रूप में डोमेन नाम मानता है।


एक डोमेन नाम लेबल का एक अनुक्रम है (एक नाम के अनुभाग, उप-डोमेन - यह मूल में एक लेबल है, मुझे बेहतर अनुवाद नहीं मिला)। एक लेबल लंबाई की एक एकल बाइट होती है जिसमें एक संख्या होती है - बाइट्स में लेबल की सामग्री की लंबाई, इसके बाद निर्दिष्ट लंबाई के बाइट्स का क्रम होता है। 0 की लंबाई वाली बाइट का सामना करने तक लेबल एक के बाद एक आते हैं। पहला लेबल तुरंत शून्य लंबाई का हो सकता है, यह रूट डोमेन (रूट डोमेन) को खाली डोमेन नाम (कभी-कभी "") के रूप में लिखा जाता है।


DNS के पुराने संस्करणों में, लेबल में बाइट्स का मान (0 से 255) हो सकता है। ऐसे नियम थे जो एक मजबूत सिफारिश की प्रकृति में थे: कि लेबल एक पत्र से शुरू होता है, एक पत्र या संख्या के साथ समाप्त होता है, और इसमें 7-बिट ASCII एन्कोडिंग में केवल अक्षर, संख्याएं या हाइफ़न होते हैं, जिसमें एक शून्य उच्च बिट होता है। वर्तमान ईडीएनएस विनिर्देश में विचलन के बिना, स्पष्ट रूप से इन नियमों के अनुपालन की आवश्यकता है।


लंबाई बाइट के दो सबसे महत्वपूर्ण बिट्स का उपयोग टैग प्रकार विशेषता के रूप में किया जाता है। यदि वे शून्य ( 0b00xxxxxx ) हैं, तो यह एक सामान्य लेबल है, और लंबाई के बाइट के शेष बिट्स इसकी संरचना में शामिल डेटा के बाइट्स की संख्या को इंगित करते हैं। अधिकतम लेबल की लंबाई 63 वर्ण है। 63 बाइनरी कोडिंग में सिर्फ 0b00111111 है


यदि दो सबसे महत्वपूर्ण बिट क्रमशः 0 और 1 ( 0b01xxxxxx ) हैं , तो यह EDNS मानक ( https://tools.ietf.org/html/rfc2671#section-3.1 ) का एक विस्तारित प्रकार लेबल है, जो 1 फरवरी, 2019 से हमारे पास आया था। निचले छह बिट में लेबल मान होगा। हम इस लेख में ईडीएनएस पर चर्चा नहीं कर रहे हैं, लेकिन यह जानना उपयोगी है कि यह भी होता है।


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 भी विफल हो जाएगा।


एक डोमेन नाम हो सकता है:


  • पूरी तरह से संपीड़न के बिना दर्ज किया जा सकता है,
  • एक जगह से शुरू करें जो संपीड़न का उपयोग करती है
  • संपीड़न के बिना एक या अधिक लेबल के साथ शुरू करें, और फिर संपीड़न पर स्विच करें,
  • खाली हो (रूट डोमेन के लिए)।

उदाहरण के लिए, हम एक डीएनएस संदेश संकलित कर रहे हैं, और हम पहले से ही इसमें "dom3.example.com" नाम का सामना कर चुके हैं, अब हमें "dom4.dom3.example.com" निर्दिष्ट करने की आवश्यकता है। इस स्थिति में, आप संपीड़न के बिना "डोम 4" अनुभाग रिकॉर्ड कर सकते हैं, और फिर संपीड़न पर स्विच कर सकते हैं, अर्थात, "dom3.example.com" के लिए एक लिंक जोड़ें। या इसके विपरीत, यदि नाम "dom4.dom3.example.com" पहले से था, तो "dom3.example.com" को इंगित करने के लिए, आप तुरंत उसमें "dom3" लेबल का उल्लेख करके संपीड़न का उपयोग कर सकते हैं। हम ऐसा नहीं कर सकते, जैसा कि पहले ही कहा जा चुका है, संपीड़न के माध्यम से 'dom4.dom3' के भाग को इंगित करने के लिए, क्योंकि नाम को एक शीर्ष-स्तरीय अनुभाग के साथ समाप्त होना चाहिए। यदि आपको अचानक बीच से सेगमेंट निर्दिष्ट करने की आवश्यकता होती है, तो उन्हें बस संपीड़न के बिना संकेत दिया जाता है।


सरलता के लिए, हमारे कार्यक्रम को पता नहीं है कि संपीड़न के साथ डोमेन नाम कैसे लिखना है, यह केवल पढ़ सकता है। मानक इसे अनुमति देता है, पढ़ना आवश्यक रूप से लागू किया जाना चाहिए, लेखन वैकल्पिक है। तकनीकी रूप से, रीडिंग इस तरह से लागू की जाती है: यदि लंबाई के बाइट के दो सबसे महत्वपूर्ण बिट्स में 1 होता है, तो हम इसके बाद की बाइट को पढ़ते हैं, और बिग एंडेड बिट्स के आदेश के साथ इन दोनों बाइट्स को 16-बिट अहस्ताक्षरित पूर्णांक के रूप में मानते हैं। हम दो सबसे महत्वपूर्ण बिट्स को छोड़ देते हैं (1 युक्त), परिणामी 14-बिट संख्या पढ़ें, और इस नंबर के अनुरूप संख्या के साथ DNS संदेश में बाइट से डोमेन नाम पढ़ना जारी रखें।


डोमेन नाम पढ़ने के समारोह के लिए कोड निम्नानुसार है:


 function readDomainName (buf, startOffset, objReturnValue = {}) { let currentByteIndex = startOffset; //    ,  DNS- ,      let initOctet = buf.readUInt8(currentByteIndex); let domain = ''; //      , ..       0, //  ,      // "the root domain name has no labels." (c) RFC-1035, p. 4.1.4. Message compression objReturnValue['endOffset'] = currentByteIndex; let lengthOctet = initOctet; while (lengthOctet > 0) { //     var label; if (lengthOctet >= 192) { //   :  0b1100 0000   const pointer = buf.readUInt16BE(currentByteIndex) - 49152; // 49152 === 0b1100 0000 0000 0000 === 192 * 256 const returnValue = {} label = readDomainName(buf, pointer, returnValue); domain += ('.' + label); objReturnValue['endOffset'] = currentByteIndex + 1; //      ,      break; } else { currentByteIndex++; label = buf.toString('ascii', currentByteIndex, currentByteIndex + lengthOctet); domain += ('.' + label); currentByteIndex += lengthOctet; lengthOctet = buf.readUInt8(currentByteIndex); objReturnValue['endOffset'] = currentByteIndex; } } return domain.substring(1); //    —  "." } 

लिस्टिंग 2. DNS क्वेरी से डोमेन नाम पढ़ना


बाइनरी बफर से डीएनएस रिकॉर्ड पढ़ने के लिए फ़ंक्शन के लिए पूर्ण कोड:


लिस्टिंग 3. एक बाइनरी बफर से एक DNS रिकॉर्ड पढ़ना
 function parseDnsMessageBytes (buf) { const msgFields = {}; // (c) RFC 1035 p. 4.1.1. Header section format msgFields['ID'] = buf.readUInt16BE(0); const byte_2 = buf.readUInt8(2); //  #2 (starting from 0) const mask_QR = 0b10000000; msgFields['QR'] = !!(byte_2 & mask_QR); //  : 0 "false" => , 1 "true" =>  const mask_Opcode = 0b01111000; const opcode = (byte_2 & mask_Opcode) >>> 3; //   (): 0, 1, 2,   msgFields['Opcode'] = opcode; const mask_AA = 0b00000100; msgFields['AA'] = !!(byte_2 & mask_AA); const mask_TC = 0b00000010; msgFields['TC'] = !!(byte_2 & mask_TC); const mask_RD = 0b00000001; msgFields['RD'] = !!(byte_2 & mask_RD); const byte_3 = buf.readUInt8(3); //  #3 const mask_RA = 0b10000000; msgFields['RA'] = !!(byte_3 & mask_RA); const mask_Z = 0b01110000; msgFields['Z'] = (byte_3 & mask_Z) >>> 4; //  0,  const mask_RCODE = 0b00001111; msgFields['RCODE'] = (byte_3 & mask_RCODE); // 0 => no error; (dec) 1, 2, 3, 4, 5 - errors, see RFC msgFields['QDCOUNT'] = buf.readUInt16BE(4); //     Question,   0  1 msgFields['ANCOUNT'] = buf.readUInt16BE(6); //     Answer msgFields['NSCOUNT'] = buf.readUInt16BE(8); //     Authority msgFields['ARCOUNT'] = buf.readUInt16BE(10); //     Additional //    Question let currentByteIndex = 12; //  Question   12-  DNS- (c) RFC 1035 p. 4.1.2. Question section format msgFields['questions'] = []; for (let qdcount = 0; qdcount < msgFields['QDCOUNT']; qdcount++) { const question = {}; const resultByteIndexObj = { endOffset: undefined }; const domain = readDomainName(buf, currentByteIndex, resultByteIndexObj); currentByteIndex = resultByteIndexObj.endOffset + 1; question['domainName'] = domain; question['qtype'] = buf.readUInt16BE(currentByteIndex); // 1 => "A" record currentByteIndex += 2; question['qclass'] = buf.readUInt16BE(currentByteIndex); // 1 => "IN" Internet currentByteIndex += 2; msgFields['questions'].push(question); } // (c) RFC 1035 p. 4.1.3. Resource record format //    (Resourse Records, RR)  Answer, Authority, Additional ['answer', 'authority', 'additional'].forEach(function(section, i, arr) { let msgFieldsName, countFieldName; switch(section) { case 'answer': msgFieldsName = 'answers'; countFieldName = 'ANCOUNT'; break; case 'authority': msgFieldsName = 'authorities'; countFieldName = 'NSCOUNT'; break; case 'additional': msgFieldsName = 'additionals'; countFieldName = 'ARCOUNT'; break; } msgFields[msgFieldsName] = []; for (let recordsCount = 0; recordsCount < msgFields[countFieldName]; recordsCount++) { let record = {}; const objReturnValue = {}; const domain = readDomainName(buf, currentByteIndex, objReturnValue); currentByteIndex = objReturnValue['endOffset'] + 1; record['domainName'] = domain; record['type'] = buf.readUInt16BE(currentByteIndex); // 1 => "A" record currentByteIndex += 2; record['class'] = buf.readUInt16BE(currentByteIndex); // 1 => "IN" Internet currentByteIndex += 2; // TTL  4  record['ttl'] = buf.readUIntBE(currentByteIndex, 4); currentByteIndex += 4; record['rdlength'] = buf.readUInt16BE(currentByteIndex); currentByteIndex += 2; const rdataBinTempBuf = buf.slice(currentByteIndex, currentByteIndex + record['rdlength']); record['rdata_bin'] = Buffer.alloc(record['rdlength'], rdataBinTempBuf); if (record['type'] === 1 && record['class'] === 1) { //      IPv4,      let ipStr = ''; for (ipv4ByteIndex = 0; ipv4ByteIndex < 4; ipv4ByteIndex++) { ipStr += '.' + buf.readUInt8(currentByteIndex).toString(); currentByteIndex++; } record['IPv4'] = ipStr.substring(1); //    '.' } else { //    ,   currentByteIndex += record['rdlength']; } msgFields[msgFieldsName].push(record); } }); return 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]; // currently, only one question per query is supported by DNS implementations let forgingHostParams = undefined; // ,         IP for (let i = 0; i < config.requestsToForge.length; i++) { const requestToForge = config.requestsToForge[i]; const targetDomainName = requestToForge.hostName; if (functions.domainNameMatchesTemplate(question.domainName, targetDomainName) && question.qclass === 1 && question.qtype === 1) { forgingHostParams = requestToForge; break; } } //  ,    DNS-      if (!!forgingHostParams) { const forgeIp = forgingHostParams.ip; const answers = []; answers.push({ domainName: question.domainName, type: question.qtype, class: question.qclass, ttl: forgedRequestsTTL, rdlength: 4, rdata_bin: functions.ip4StringToBuffer(forgeIp), IPv4: forgeIp }); const localDnsResponse = { ID: dnsRequest.ID, QR: dnsRequest.QR, Opcode: dnsRequest.Opcode, AA: dnsRequest.AA, TC: false, // dnsRequest.TC, RD: dnsRequest.RD, RA: true, Z: dnsRequest.Z, RCODE: 0, // dnsRequest.RCODE, 0 - no errors, look in RFC-1035 for other error conditions QDCOUNT: dnsRequest.QDCOUNT, ANCOUNT: answers.length, NSCOUNT: dnsRequest.NSCOUNT, ARCOUNT: dnsRequest.ARCOUNT, questions: dnsRequest.questions, answers: answers } //     DNS-    const responseBuf = functions.composeDnsMessageBin(localDnsResponse); console.log('response composed for: ', localDnsResponse.questions[0]); server.send(responseBuf, linfo.port, linfo.address, (err, bytes) => {}); } // ,     DNS-,         else { //     DNS-  UDP,     const responseBuf = await functions.getRemoteDnsResponseBin(localReq, upstreamDnsIP, upstreamDnsPort); //        server.send(responseBuf, linfo.port, linfo.address, (err, bytes) => {}); //     DNS-  TLS,   , .  9 } }); 

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; //    ,     TLS- function Module(connectionOptions, funcOnData, funcOnError, funcOnClose, funcOnEnd) { let socket; function connect() { socket = tls.connect(connectionOptions, () => { console.log('client connection established:', socket.authorized ? 'authorized' : 'unauthorized'); }); socket.on('data', funcOnData); // connection.on('end', () => {}); socket.on('close', (hasTransmissionError) => { //   ,     . //   ,     console.log('connection closed; transmission error:', hasTransmissionError); }); socket.on('end', () => { console.log('remote TLS server connection closed.') }); socket.on('error', (err) => { console.log('connection error:', err); console.log('\tmessage:', err.message); console.log('\tstack:', err.stack); }) socket.setTimeout(TLS_SOCKET_IDLE_TIMEOUT); socket.on('timeout', () => { console.log('socket idle timeout, disconnected.'); socket.end(); }); } this.write = function (dataBuf) { if (socket && socket.writable) { //  ,     } else { connect(); } socket.write(dataBuf); } return this; } module.exports = Module; 

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, //        host: config.upstreamDnsTlsHost } const onData = (data) => { //     , .       7 }; remoteTlsClient = new TlsClient(options, onData); 

6. TLS-


, TCP-. TCP/TLS- DNS-, , , , . TCP ( TLS), DNS- 512 , UDP (, EDNS UDP ). , DNS- UDP, . onData() 6.


 const onData = (data) => { //    DNS-,       TLS-   //    ,     2 ,       let dataCurrentPos = 0; try { while (dataCurrentPos < data.length) { const respLen = data.readUInt16BE(dataCurrentPos); respBuf = data.slice(dataCurrentPos + 2, dataCurrentPos + 2 + respLen); const respData = functions.parseDnsMessageBytes(respBuf); const requestKey = functions.getRequestIdentifier(respData); const localResponseParams = localRequestsAwaiting.get(requestKey); localRequestsAwaiting.delete(requestKey); server.send(respBuf, localResponseParams.port, localResponseParams.address, (err, bytesNum) => {}); dataCurrentPos += 2 + respLen; } } catch (err) { console.error(err); //   ,     throw err; } }; 

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:


 //        const requestKey = functions.getRequestIdentifier(respData); //    IP    ,   const localResponseParams = localRequestsAwaiting.get(requestKey); localRequestsAwaiting.delete(requestKey); //      IP   server.send(respBuf, localResponseParams.port, localResponseParams.address, (err, bytesNum) => {}); 

8. 7


TLS-:


 //   ,     const localReqParams = { address: linfo.address, port: linfo.port }; //        const requestKey = functions.getRequestIdentifier(dnsRequest); //       localRequestsAwaiting.set(requestKey, localReqParams); //       ,      const lenBuf = Buffer.alloc(2); lenBuf.writeUInt16BE(localReq.length); const prepReqBuf = Buffer.concat([lenBuf, localReq], 2 + localReq.length); remoteTlsClient.write(prepReqBuf); //  RFC-7766 p.8, 2                

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 () { // config  -,       . //    config        , //         . ,    : // const conf = config; //   conf     ,    : // const requestsToForge = config.requestsToForge; //    , requestsToForge   . const config = {}; Object.defineProperty(this, 'config', { get() { return config; }, enumerable: true }) this.initConfig = async function() { const fileContents = await readConfigFile(CONFIG_FILE_PATH); console.log('initConfig:'); console.log(fileContents); console.log('fileContents logged ^^'); const parsedConfigData = parseConfig(fileContents); Object.assign(config, parsedConfigData); }; async function readConfigFile(configPath) { const promise = new Promise((resolve, reject) => { fs.readFile(configPath, { encoding: 'utf8', flag: 'r' }, (err, data) => { if (err) { console.log('readConfigFile err to throw'); throw err; } resolve(data); }); }) .then( fileContents => { return fileContents; } ) .catch(err => { console.log('readConfigFile error: ', err); }); return promise; } function parseConfig(fileContents) { const configData = JSON.parse(fileContents); return configData; } //   ,       . //  Windows,    fs.watch     , //      ,   configReadInProgress let configReadInProgress = false; fs.watch(CONFIG_FILE_PATH, async () => { if(!configReadInProgress) { configReadInProgress = true; console.log('===== config changed, run initConfig() ====='); try { await this.initConfig(); } catch (err) { console.log('===== error initConfig(), skip =====,', err); configReadInProgress = false; } configReadInProgress = false; } else { console.log('===== config changed, initConfig() already running, skip ====='); } }); } let instance; async function getInstance() { if(!instance) { instance = new Module(); await instance.initConfig(); } return instance; } module.exports = getInstance; 

10.


कुल मिलाकर


DNS- NodeJS, npm . , , , , .


GitHub


:


Source: https://habr.com/ru/post/hi440050/


All Articles