In the last article I explained how TO parse DNS request messages. You can’t fly by yourself. This chapter explains how to create a response message and send it back to the client from the perspective of the DNS server.

Based on this proposition, you can list the problems that need to be solved when the DNS server creates response packets.

  • DNS datagram typeBuffer?
  • How to create Buffer in Node.js?
  • Normally we operate strings and numbers can be converted toBuffer?
  • BufferCan I createresponseParameter value of the specified type of response message?
  • responseResponse message andrequestWhat are the similarities and differences of request packets?

Speaking of which, if you’ve noticed. Since DNS request and DNS response are done, it is not their own hands to write a DNS proxy server can also be handy.

The answer is: Yes.

So let’s move on to the last step, creating the response message.

Format of DNS response packets

Response The format of a response packet is the same as that of a request packet. The difference is that the parameter values are different.

Description of response parameters

  • HeaderMessage header
  • QuestionQuery questions
  • AnswerThe reply
  • AuthorityAuthorization response
  • AdditionalAdditional information
  DNS format

  +--+--+--+--+--+--+--+
  |        Header      |
  +--+--+--+--+--+--+--+
  |      Question      |
  +--+--+--+--+--+--+--+
  |      Answer        |
  +--+--+--+--+--+--+--+
  |      Authority     |
  +--+--+--+--+--+--+--+
  |      Additional    |
  +--+--+--+--+--+--+--+
Copy the code

The Header packet Header

Attribute Description:

  • The client requests the ID to ensure that it can correctly identify the response packet to the DNS server. So a complete DNS request and response insiderequestandresponsetheIDYou have to be consistent.
  • header.qr = 1Is a response packet
  • header.ancountThis refers to the reply record entry, so according to the reply fieldAnswerCalculation.
  var response = {};
  var header = response.header = {};

  header.id = request.header.id;// Same ID, treated as a DNS request
  
  header.qr = 1;    // Response message
  header.opcode = 0;// standard query
  header.rd = 1;
  header.ra = 0;
  
  header.z = 0;
  header.rcode = 0;// There are no errors

  header.qdcount = 1;
  header.nscount = 0;
  header.arcount = 0;
  header.ancount = 1;1. If there are multiple answers then consider multiple answers
Copy the code

Question request data

Return the request data as is.

  var question = response.question = {};
  question.qname = request.question.qname;
  question.qtype = request.question.qtype;
  question.qclass = request.question.qclass;
Copy the code

Answer Indicates the reply packet data

The content of this section is the datagram returned by the DNS server.

RDDATA is a data field.

Name indicates the domain name, and the length is not fixed.

Format:

Answer format 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NAME | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH |  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDATA | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+Copy the code
  var answer = {};

  answer.name = request.question.qname;
  answer.type = 1;
  answer.class = 1;
  answer.ttl = ttl || 1;// Indicates the number of valid hops
  answer.rdlength = 4;
  answer.rdata = rdata;// Data records
Copy the code

Rdata stores IP addresses that must be recognized by translation clients:

  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;
  };
Copy the code

Rdata is a 4-byte IP address from. Rdata consists of four segments of data, each of which does not exceed 2^8 === 256– a byte (8 bits), so 4 bytes of Rdata is just enough to hold an IP address. The problem now is how to store the IP address data in four bytes, but ensure that the client can recognize it. It’s easy to save by word, by byte. Four bytes is exactly the length of a 32bit integer.

So the above computes the result for(…) A loop is a way of storing IP in Rdata.

You can also calculate result as follows:

  result = ip[0] * (1<<24) + ip[1] * (1<<16) + ip[2] * (1<<8) + ip[3];
Copy the code

The Authority/Additional data

A request processed by itself has no authorized reply and no additional data.

Buffer Indicates the response packet

Once you have all the response data you want, the next step is to convert it to a Buffer type that the client can parse.

This step is the exact opposite of the request parsing. The data is pieced together as response data in response packet format.

Buffer length determination

Returns a Buffer, always creating a Buffer of a certain length first.

According to field analysis, except for field question. qname and field answer. name, whose length is not fixed, other fields can be calculated.

You can get the size of the Buffer you want to create by plugging in the data.

  len = Header + Question + Answer
      = 12 + (Question.qname.length+4) + (Answer.name.length + 14)
      = 30 + Question.qname.length + Answer.name.length
Copy the code

After confirming that the length of the Buffer instance to be created is 30 + question.qname. length + answer.name.

Buffer instance parameter conversion

Response data can be roughly divided into three categories:

  • Normal full byte category
  • A category that needs to be concatenated bitwise into a byte
  • Category of unsigned integers

Normal full byte category

This is often the best treatment, direct copy can come over.

Copy (target[, targetStart[, sourceStart[, sourceEnd]])).

For example, copy header.id:

  header.id.copy(buf,0.0.2);
Copy the code

In this way, the other parameters can be converted one by one.

A category that needs to be concatenated bitwise into a byte

This primary is for the [3,4] byte of the Header. Because these two bytes of data are bitwise separated, they now need to be pieced together into complete bytes.

The first thing you need to determine is the byte length, along with the default value, and then the bitwise operator.

1byte = 8bit

The default value is 0 = 0x00

Operator:

& : no, because any number & 0 = = 0 | : ok, any number | 0 equals the numberCopy the code

Through | can get the desired results:

  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;
Copy the code

Category of unsigned integers

If you have read the Buffer API or created buF unsigned integers using Buffer, this problem can be easily solved.

Buf. writeUInt16BE(value, offset[, noAssert]) and buf.writeUInt32BE(value, offset[, noAssert]).

 buf.writeUInt16BE(header.ancount, 6);
 buf.writeUInt32BE(answer.rdata, len4 -);
Copy the code

Application scenarios

In addition to the TTL packet valid hop count and Rdata of the Answer data, you need to obtain it from other sources. The rest of the data can be calculated or obtained from the request.

To encapsulate a function, you simply pass in (request, TTL,rdata).

The following code is for reference only:

  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;
  };
Copy the code

The resources

Github.com/mafintosh/d…