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…

相关文章
相关标签/搜索