DNS 響應報文詳解

上一篇我已經解釋了DNS請求報文怎麼解析,不會的本身坐飛機(飛機入口)。這一篇主要從DNS服務器的角度來解釋,如何本身建立響應報文返回給客戶端。git

就這個命題,能夠羅列出DNS服務器在建立response響應報文時須要解決的問題。github

  • dns數據報類型Buffer?
  • Node.js中Buffer如何建立?
  • 正常狀況咱們操做的字符串和數字等是否能夠轉換爲Buffer?
  • Buffer是否能夠建立response響應報文指定類型的參數值?
  • response響應報文與request請求報文的異同?

說到這,你是否是已經察覺到。既然dns請求和dns響應都作了,那是否是本身動手寫一個dns代理服務器也能夠信手拈來呢。api

答案是: Yesbash

那然咱們繼續完成這最後一步,response響應報文的建立。服務器

DNS響應報文格式

response響應報文和request請求報文格式相同。不一樣的地方是參數的值不一樣。函數

response參數詳解

  • Header 報文頭
  • Question 查詢的問題
  • Answer 應答
  • Authority 受權應答
  • Additional 附加信息
DNS format

  +--+--+--+--+--+--+--+
  |        Header      |
  +--+--+--+--+--+--+--+
  |      Question      |
  +--+--+--+--+--+--+--+
  |      Answer        |
  +--+--+--+--+--+--+--+
  |      Authority     |
  +--+--+--+--+--+--+--+
  |      Additional    |
  +--+--+--+--+--+--+--+
複製代碼

Header報文頭

屬性說明:post

  • 客戶端請求ID是爲了保證收到DNS服務器返回報文時能正確知道是哪個請求的響應報文。因此一個完整的DNS請求和響應,裏面requestresponseID 必須保持一致。
  • header.qr = 1,表示響應報文
  • header.ancount,這個牽涉到應答記錄條目,因此要根據應答字段Answer計算。
var response = {};
  var header = response.header = {};

  header.id = request.header.id;//id相同,視爲一個dns請求
  
  header.qr = 1;    //響應報文
  header.opcode = 0;//標準查詢
  header.rd = 1;
  header.ra = 0;
  
  header.z = 0;
  header.rcode = 0;//沒有錯誤

  header.qdcount = 1;
  header.nscount = 0;
  header.arcount = 0;
  header.ancount = 1;//這裏answer爲一個,因此設置爲1.若是有多個answer那麼就要考慮多個answer
複製代碼

Question 請求數據

將請求數據原樣返回。ui

var question = response.question = {};
  question.qname = request.question.qname;
  question.qtype = request.question.qtype;
  question.qclass = request.question.qclass;
複製代碼

Answer應答報文數據

這個部分的內容就是dns服務器要返回的數據報。spa

RDDATA爲數據字段。代理

name爲域名,長度不固定。

格式:

Answer format

    0  1  2  3  4  5  6  7  0  1  2  3  4  5  6  7
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    NAME                       |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    TYPE                       |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    CLASS                      |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    TTL                        |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    RDLENGTH                   |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    RDATA                      |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

複製代碼
var answer = {};

  answer.name = request.question.qname;
  answer.type = 1;
  answer.class = 1;
  answer.ttl = ttl || 1;//報文有效跳數
  answer.rdlength = 4;
  answer.rdata = rdata;//數據記錄
複製代碼

rdata存放的是ip地址,ip必須通過轉換客戶端才能識別:

var numify = function(ip) {
      ip = ip.split('.').map(function(n) {
          return parseInt(n, 10);
      });

      var result = 0;
      var base = 1;

      for (var i = ip.length-1; i >= 0; i--) {
          result += ip[i]*base;
          base *= 256;
      }
      return result;
  };
複製代碼

rdata4字節,ip地址從.處切開後是由4段數字組成,每段數據不會超過2^8 === 256---一個字節(8bit),那rdata的4個字節恰好能夠存放下一個ip地址。 那如今的問題是怎麼把ip地址數據存進4個字節裏面,而又要保證客戶端可以識別。很簡單按字節存,按字節取就好了。4字節恰好是一個32bit整數的長度。

因此上面計算resultfor(...)循環就是把ip存進rdata的一種方式。

其實你也可使用如下方式計算result:

result = ip[0]*(1<<24) + ip[1]*(1<<16) + ip[2]*(1<<8) + ip[3];
複製代碼

Authority/Additional 數據

本身處理的請求沒有受權應答和附加數據。

Buffer類型響應報文

獲得了想要的一切響應數據以後,下一步就是將這些數據轉換爲客戶端能夠解析的Buffer類型。

那這一步的工做正好與request請求報文解析的工做剛好相反。報上面的數據一一拼湊爲response響應報文格式數據。

Buffer長度肯定

返回一段Buffer報文,總得先建立必定長度的Buffer

根據字段分析,除了Question.qname字段和Answer.name字段是長度不固定的,其它的字段都是能夠計算出來。

經過帶入數據能夠獲得須要建立的Buffer的大小。

len = Header + Question + Answer
      = 12 + (Question.qname.length+4) + (Answer.name.length + 14)
      = 30 + Question.qname.length + Answer.name.length
複製代碼

肯定須要建立的Buffer實例的長度爲30 + Question.qname.length + Answer.name.length後,就能夠進行參數轉換了。

Buffer實例參數轉換

response數據大概分爲了3中類別:

  • 普通完整字節類別
  • 須要按位拼接成一個字節的類別
  • 無符號整數類別

普通完整字節類別

這種每每是最好處理的了,直接copy過來就能夠了。

使用buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])函數進行拷貝.

例如拷貝header.id:

header.id.copy(buf,0,0,2);
複製代碼

經過這種方式便可將其它參數進行一一轉換。

須要按位拼接成一個字節的類別

這種主要數針對Header的第[3,4]個字節。應爲這2個字節的數據是按位的長度區分,如今須要拼湊成完整字節。

首先須要肯定的是字節長度,以及默認值,而後肯定位操做符。

1byte = 8bit

默認值爲:0 = 0x00

操做符:

&: 不行,由於任何數&0 == 0
  |: ok ,任何數 | 0 都等於這個數
複製代碼

經過|能夠獲得想要的結果:

buf[2] = 0x00 | header.qr << 7 | header.opcode << 3 | header.aa << 2 | header.tc << 1 | header.rd;
  buf[3] = 0x00 | header.ra << 7 | header.z << 4 | header.rcode;
複製代碼

無符號整數類別

假如你看過Buffer的api或使用Buffer建立過buf無符號整數,那麼這個問題就能夠很容易解決了。

buf.writeUInt16BE(value, offset[, noAssert])buf.writeUInt32BE(value, offset[, noAssert]),一看就知道一個是建立16位,一個是32位。

buf.writeUInt16BE(header.ancount, 6);
 buf.writeUInt32BE(answer.rdata, len-4);
複製代碼

應用場景

除了Answer數據的ttl報文有效跳數和rdata,須要真的從其它地方獲取過來。其它數據基本能夠經過計算或從request中獲得。

封裝成函數的話,只須要傳入(request,ttl,rdata)就能夠了。

如下代碼僅供參考:

var responseBuffer = function(response){
      var buf = Buffer.alloc(30+response.question.qname.length +response.answer.name.length) ,
          offset = response.question.qname.length;

      response.header.id.copy(buf,0,0,2);

      buf[2] = 0x00 | response.header.qr << 7 | response.header.opcode << 3 | response.header.aa << 2 | response.header.tc << 1 | response.header.rd;
      buf[3] = 0x00 | response.header.ra << 7 | response.header.z << 4 | response.header.rcode;

      buf.writeUInt16BE(response.header.qdcount, 4);
      buf.writeUInt16BE(response.header.ancount, 6);
      buf.writeUInt16BE(response.header.nscount, 8);
      buf.writeUInt16BE(response.header.arcount, 10);

      response.question.qname.copy(buf,12);
      response.question.qtype.copy(buf,12+offset,0,2);
      response.question.qclass.copy(buf,14+offset,0,2);

      offset += 16;
      response.answer.name.copy(buf,offset);

      offset += response.answer.name.length;
      buf.writeUInt16BE(response.answer.type , offset);
      buf.writeUInt16BE(response.answer.class , offset+2);
      buf.writeUInt32BE(response.answer.ttl , offset+4);
      buf.writeUInt16BE(response.answer.rdlength , offset+8);
      buf.writeUInt32BE(response.answer.rdata , offset+10);

      return buf;
  };

  var response = function(request , ttl , rdata){
      var response = {};
      response.header = {};
      response.question = {};
      response.answer = resolve(request.question.qname , ttl , rdata);

      response.header.id = request.header.id;

      response.header.qr = 1;
      response.header.opcode = 0;
      response.header.aa = 0;
      response.header.tc = 0;
      response.header.rd = 1;
      response.header.ra = 0;
      response.header.z = 0;
      response.header.rcode = 0;
      response.header.qdcount = 1;
      response.header.ancount = 1;
      response.header.nscount = 0;
      response.header.arcount = 0;

      response.question.qname = request.question.qname;
      response.question.qtype = request.question.qtype;
      response.question.qclass = request.question.qclass;

      return responseBuffer(response);

  };
  var resolve = function(qname , ttl , rdata){
      var answer = {};

      answer.name = qname;
      answer.type = 1;
      answer.class = 1;
      answer.ttl = ttl;
      answer.rdlength = 4;
      answer.rdata = rdata;

      return answer;
  };
複製代碼

參考資料

github.com/mafintosh/d…

相關文章
相關標籤/搜索