Подробное объяснение сообщения DNS-запроса

Node.js задняя часть сервер Операционная система DNS
Подробное объяснение сообщения DNS-запроса

DNS

DNS [Система доменных имен: (англ. Domain Name System, аббревиатура: DNS)] — служба Интернета. Он действует как распределенная база данных, которая сопоставляет доменные имена и IP-адреса друг с другом, облегчая доступ людей в Интернет. DNS использует TCP и UDP порт 53.

Народная версия

Это доменное имя веб-сайта, переданное клиентом (например, браузер), найдите соответствующий IP-адрес в списке DNS и верните его клиенту, а затем клиент может найти соответствующий сервер в соответствии с IP-адресом и может отправить запрос к серверу.

Проще говоря: цель DNS — предоставить клиенту соответствующий IP-адрес сервера. Наконец, когда клиент взаимодействует с сервером, DNS не имеет ничего общего.

Формат DNS-пакета

Формат пакетов DNS, будь то пакет запроса или пакет ответа, возвращаемый DNS-сервером, использует унифицированный формат.

  • Headerзаголовок
  • Questionзапрос вопрос
  • Answerотвечать
  • AuthorityОтвет об авторизации
  • AdditionalДополнительная информация
  DNS format

  +--+--+--+--+--+--+--+
  |        Header      |
  +--+--+--+--+--+--+--+
  |      Question      |
  +--+--+--+--+--+--+--+
  |      Answer        |
  +--+--+--+--+--+--+--+
  |      Authority     |
  +--+--+--+--+--+--+--+
  |      Additional    |
  +--+--+--+--+--+--+--+

Заголовок заголовка

  • ID: 2байты (16bit), поле идентификации, клиент будет анализировать ответное сообщение DNS, возвращенное сервером, получитьIDЗначение устанавливается с сообщением запросаIDЗначения сравниваются, и если они совпадают, они считаются одной и той же сессией DNS.
  • FLAGS: 2байты (16bit) поля флага. Содержит следующие свойства:
    • QR: 0Указывает сообщение запроса,1Представляет ответное сообщение;
    • opcode: обычно значение равно0(стандартный запрос), остальные значения1(обратный поиск) и2(запрос состояния сервера),[3,15]зарезервированное значение;
    • AA: Указывает авторитетный ответ — этот бит имеет смысл только тогда, когда дан ответ, указывая, что сервер, который дал ответ, является полномочным сервером разрешения для запроса доменного имени;
    • TC: указывает на усечение (truncated) — используется для обозначения того, что сообщение длиннее допустимой длины, в результате чего оно усечено;
    • RD: указывает на необходимость рекурсии — этот бит устанавливается запросом и возвращается с тем же значением, что и в ответе. Если установлен RD, рекомендуется, чтобы сервер доменных имен выполнял рекурсивное разрешение, а поддержка рекурсивного запроса не является обязательной;
    • RA: Указывает, что рекурсия поддерживается (Recursion Available) — этот бит устанавливается или снимается в ответе, чтобы указать, поддерживает ли сервер рекурсивный запрос;
    • Z: зарезервированное значение, еще не использованное;
    • RCODE: Код ответа. Эти 4 бита устанавливаются в ответном сообщении и имеют следующие значения:
      • 0: ошибок нет.
      • 1: ошибка формата сообщения (Format error) - сервер не понимает запрошенное сообщение;
      • 2: Сбой сервера - запрос не может быть обработан из-за сервера;
      • 3: Ошибка имени — имеет значение только для авторизованных серверов разрешения доменных имен, указывая на то, что разрешенное доменное имя не существует;
      • 4: Not Implemented - сервер имен не поддерживает данный тип запроса;
      • 5:Refused - Сервер отказывается давать ответ из-за заданной политики, например, сервер не хочет отвечать на некоторые запросы, или сервер не хочет выполнять определенные операции (например, передачу зоны);
      • [6,15]: Зарезервированное значение, еще не используется.
  • QDCOUNT: без знака16bitЦелое число, указывающее количество проблемных записей в сегменте запроса сообщения.
  • ANCOUNT: без знака16bitЦелое число указывает количество записей ответа в сегменте ответа сообщения.
  • NSCOUNT: без знака16bitЦелое число, указывающее количество записей авторизации в сегменте авторизации сообщения.
  • ARCOUNT: без знака16bitЦелое число, указывающее количество дополнительных записей в дополнительном сегменте сообщения.
  Header format

    0  1  2  3  4  5  6  7  0  1  2  3  4  5  6  7
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                      ID                       |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |QR|  opcode   |AA|TC|RD|RA|   Z    |   RCODE   |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    QDCOUNT                    |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    ANCOUNT                    |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    NSCOUNT                    |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    ARCOUNT                    |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Поле запроса вопроса

  • QNAMEнеподписанный8bitДлина блока не ограничивается указанием имени запроса (в широком смысле: доменного имени).
  • QTYPEнеподписанный16bitЦелое число, представляющее запрошенный тип протокола.
  • QCLASSнеподписанный16bitЦелое число, представляющее класс запроса, например,INобозначает Интернет.
  Question format

    0  1  2  3  4  5  6  7  0  1  2  3  4  5  6  7
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                     ...                       |
  |                    QNAME                      |
  |                     ...                       |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    QTYPE                      |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    QCLASS                     |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

Answer/Authority/Additional

Все три поля имеют одинаковый формат.

  • NAMEДоменное имя, содержащееся в записи ресурса.
  • TYPEвыражатьDNSТип соглашения.
  • CLASSКласс, представляющий RDATA.
  • TTL4-байтовое целое число без знака, указывающее, как долго запись ресурса может кэшироваться. 0 означает, что его можно только передавать, но не кэшировать.
  • RDLENGTH2 байта целое число без знака, представляющее длину RDATA
  • RDATAСтрока переменной длины для представления записи, корневой формат TYPE связан с CLASS. Например, если TYPE — A, а CLASS — IN, то RDATA — это 4-байтовый сетевой адрес ARPA.
  Answer/Authority/Additional format

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

Разбор сообщения DNS-запроса

Просто говорите и не делайте фейков. Как разобрать сообщение DNS-запроса? Давайте посмотрим на сообщение запроса DNS:

  6dca 0100 0001 0000 0000 0000 0377 7777
  0561 7070 6c65 0363 6f6d 0000 0100 01 

ЭтоBufferПример, если вы запутались после прочтения, не нервничайте, посмотрите сначала анализconsole.logКажется, что мир стал лучше в одно мгновение.

Ниже приведен запрос запросаwww.apple.comПакет DNS-запроса веб-сайта ip.

  //Header
  ID:  <Buffer 6d ca>
  FLAG:  QR:  0 opcode:  0 AA:  0 TC:  0 RD:  1
  RA:  0 zero:  0 recode:  0
  QDCOUNT:  <Buffer 00 01> ANCOUNT:  <Buffer 00 00> NSCOUNT:  <Buffer 00 00> ARCOUNT:  <Buffer 00 00>
  
  //QUESTION
  QNAME:  <Buffer 03 77 77 77 05 61 70 70 6c 65 03 63 6f 6d 00> QTYPE:  <Buffer 00 01> QCLASS:  <Buffer 00 01>
  
  QUESTION STRING:  www.apple.com 

Парсинг сообщения запроса разбит на 2 небольших блока:

  • HeaderРазбор заголовка пакета
  • QUESTIONАнализ проблемы запроса

Разбор заголовка заголовка

Разберите часть заголовка.

Сначала определите размер каждого поля:

  ID: 2 字节
  QR: 1 bit
  opcode: 4bit
  AA: 1bit
  TC: 1bit
  RD: 1bit
  RA: 1bit
  Z : 3bit
  RCODE: 4bit
  QDCOUNT: 2 字节
  ANCOUNT: 2 字节
  NSCOUNT: 2 字节
  ARCOUNT: 2 字节

Всего 12 байт.

Если мы оставим[3,4]На самом деле разобрать заголовок несложно, но блокbitдолжен быть правbufferЗначение экземпляра подвергается побитовым операциям.

Таким образом, значения следующих параметров могут быть напрямую изменены сbufferПолучено в:

  var header = {};

  header.id = buf.slice(0,2);
  header.qdcount = buf.slice(4,6);
  header.ancount = buf.slice(6,8);
  header.nscount = buf.slice(8,10);
  header.arcount = buf.slice(10, 12);

Трудность в том, как получить первый[3,4]значение, сначала нужно поставитьbufferБайты, соответствующие экземпляру, преобразуются в2Затем базовая строка преобразуется в число, а окончательный результат вычисляется по длине аргумента.

Первый шаг, будетbufferПреобразовать в двоичную строку, а затем в числовую (при условии, что сообщение DNSbuf):

  //对第3个字节转成`2`进制字符串然后转换为数值
  var b = buf.slice(2,3).toString('binary', 0, 1).charCodeAt(0);

Шаг 2, выполните резку данных:

Прежде всего, вам нужно понять следующую функцию, которая представляет собой не что иное, как извлечение изoffsetначало, длинаlengthЧисловые биты, преобразованные в побитовые операцииIntegerЗатем возвращается номер типа.

Грубо говоря, это преобразование нужных вам бинарных данных вIntegerвведите и верните.

  var bitSlice = function(b, offset, length) {
      return (b >>> (7-(offset+length-1))) & ~(0xff << length);
  };

Обратите внимание, потому что рассматривается только один байт ===8bit, поэтому его можно записать как(7-(offset+length-1))и0xff << length. Если это не байт, то вам может понадобиться изменить число в нем7и0xffзначение .

Демонстрация начинается:

  'use strict';

  var buf = Buffer.from([0x2d]);
  var b = buf.toString('binary' , 0,1).charCodeAt(0);

  console.log(bitSlice(b , 0, 1));//0
  console.log(bitSlice(b , 1, 1));//0
  console.log(bitSlice(b , 2, 1));//1
  console.log(bitSlice(b , 3, 1));//0
  console.log(bitSlice(b , 4, 1));//1
  console.log(bitSlice(b , 5, 1));//1
  console.log(bitSlice(b , 6, 1));//0
  console.log(bitSlice(b , 7, 1));//1
  console.log(bitSlice(b , 5, 3));//5  === 0000 0101

  /**
   * 16进制:0x2d
   * 10进制:45
   * 2进制: 0010 1101
   *
   * (45,0,1):45>>>7 & ~(0xff<<1) 
   *    45>>>7 = 0000 0000
   *    (0xff<<1)  = 0000 0000 0000 0000 0000 0001 1111 1110   510
   *    ~(0xff<<1) = 1111 1111 1111 1111 1111 1110 0000 0001   -511 = -((0xff<<1)+1)
   *
   *      0000 0000 0000 0000 0000 0000 0000 0000  === 45>>>7
   *    & 1111 1111 1111 1111 1111 1110 0000 0001  === ~(0xff<<1)
   *      ----------------------------------------
   *      0000 0000 0000 0000 0000 0000 0000 0000 = 0
   *
   * (45,2,1):45>>>5 & ~(0xff<<1) 
   *    45>>>5 = 0000 0001
   *    (0xff<<1)  = 0000 0000 0000 0000 0000 0001 1111 1110   510
   *    ~(0xff<<1) = 1111 1111 1111 1111 1111 1110 0000 0001   -511 = -((0xff<<1)+1)
   *
   *      0000 0000 0000 0000 0000 0000 0000 0001  === 45>>>5
   *    & 1111 1111 1111 1111 1111 1110 0000 0001  === ~(0xff<<1)
   *      ----------------------------------------
   *      0000 0000 0000 0000 0000 0000 0000 0001 = 1
   */

После понимания функции вышеуказанной функции вы действительно можете использовать эту функцию для получения первой части заголовка пакета DNS.[3,4]значение в байтах.

На кончиках ваших пальцев:

  //第3个字节
  var b = buf.slice(2,3).toString('binary', 0, 1).charCodeAt(0);
  header.qr = bitSlice(b,0,1);
  header.opcode = bitSlice(b,1,4);
  header.aa = bitSlice(b,5,1);
  header.tc = bitSlice(b,6,1);
  header.rd = bitSlice(b,7,1);
  
  //第4个字节
  b = buf.slice(3,4).toString('binary', 0, 1).charCodeAt(0);
  header.ra = bitSlice(b,0,1);
  header.z = bitSlice(b,1,3);
  header.rcode = bitSlice(b,4,4);

Разрешение поля запроса QUESTION

В основном это имя домена запроса, тип протокола и категория.

эти 3 параметраQTYPEиQCLASSфиксированный2байт,QNAMEне фиксируется.

Поэтому вам нужно обратить внимание на выборку данных, потому чтоQUESTIONинформация следующаяHeaderПосле этого, так из12Вернуть байты обратно:

var question = {};
  question.qname = buf.slice(12, buf.length-4);
  question.qtype = buf.slice(buf.length-4, buf.length-2);
  question.qclass = buf.slice(buf.length-2, buf.length);

qnameиспользуетlen+dataсмешанное кодирование для0x00конец. Каждая строка начинается с длины, за которой следует содержимое.qnameдлина должна начинаться с8Байт - это единица измерения.

Напримерwww.apple.com(Примечание: середина.добавляется при разборе), егоbufferЭкземпляр представлен как:

  03 77 77 77 05 61 70 70 6c 65 03 63 6f 6d 00
  //约等于
  3www5apple3com

То есть первый бит указывает длину, за ним следуют данные той же длины и так далее.

  var domainify = function(qname) {
    var parts = [];

    for (var i = 0; i < qname.length && qname[i];) {
      var len = qname[i] , offset = i+1;//获取每一块域名长度

      parts.push(qname.slice(offset,offset+len).toString());//获取每一块域名

      i = offset+len;
    }

    return parts.join('.');//拼凑成完整域名
  };

qtypeтип соглашения.смотрите подробности

Список, соответствующий типу протокола:

ценность тип соглашения описывать
1 A IPv4-адрес
2 NS сервер имен
5 CNAME Каноническое имя определяет псевдоним для официального имени хоста.
6 SOA Начальная авторизация отмечает начало зоны
11 WKS Знаком с сетевыми службами, предоставляемыми хостом определения службы.
12 PTR Указатель преобразует IP-адрес в доменное имя
13 HINFO Информация о хосте дает представление об оборудовании и операционной системе, используемых хостом.
15 MX Mail Exchange направляет изменения почты на почтовые серверы
28 AAAA IPv6-адрес
252 AXFR Передать запрос на всю зону
255 ANY запрос на все записи

qclassОбычно 1, ссылаясь на интернет-данные.

Сценарий приложения — прокси-сервер запроса DNS

Сохраните следующий код как.jsфайл, затем используйтеNode.jsВыполните, используйте машину в той же локальной сети, чтобы настроить DNS для этой машины.

Следующий код предназначен только для справки:

  'use strict';

  const dgram = require('dgram');
  const dns = require('dns');
  const fs = require('fs');
  const server = dgram.createSocket('udp4');

  var bitSlice = function(b, offset, length) {
      return (b >>> (7-(offset+length-1))) & ~(0xff << length);
  };

  var domainify = function(qname) {
      var parts = [];

      for (var i = 0; i < qname.length && qname[i];) {
          var length = qname[i];
          var offset = i+1;

          parts.push(qname.slice(offset,offset+length).toString());

          i = offset+length;
      }

      return parts.join('.');
  };

  var parse = function(buf) {
      var header = {};
      var question = {};
      var b = buf.slice(2,3).toString('binary', 0, 1).charCodeAt(0);
      console.log('b:',b,buf.slice(2,3));
      header.id = buf.slice(0,2);
      header.qr = bitSlice(b,0,1);
      header.opcode = bitSlice(b,1,4);
      header.aa = bitSlice(b,5,1);
      header.tc = bitSlice(b,6,1);
      header.rd = bitSlice(b,7,1);

      b = buf.slice(3,4).toString('binary', 0, 1).charCodeAt(0);

      header.ra = bitSlice(b,0,1);
      header.z = bitSlice(b,1,3);
      header.rcode = bitSlice(b,4,4);

      header.qdcount = buf.slice(4,6);
      header.ancount = buf.slice(6,8);
      header.nscount = buf.slice(8,10);
      header.arcount = buf.slice(10, 12);

      question.qname = buf.slice(12, buf.length-4);
      question.qtype = buf.slice(buf.length-4, buf.length-2);
      question.qclass = buf.slice(buf.length-2, buf.length);

      return {header:header, question:question};
  };

  server.on('error' , (err)=>{
      console.log(`server error: ${err.stack}`);
  });

  server.on('message' , (msg , rinfo)=>{
      //fs.writeFile('dns.json' ,msg, {flag:'w',endcoding:'utf-8'} ,(err)=>{
      //    console.log(err);
      //});
      var query = parse(msg);
      console.log('标识ID: ' ,query.header.id);
      console.log('标识FLAG: ' , 'QR: ',query.header.qr , 'opcode: ',query.header.opcode , 'AA: ',query.header.aa , 'TC: ',query.header.tc,'RD: ',query.header.rd);
      
      console.log('RA: ',query.header.ra , 'zero: ',query.header.z , 'recode: ',query.header.rcode);

      console.log('QDCOUNT: ',query.header.qdcount , 'ANCOUNT: ' , query.header.ancount, 'NSCOUNT: ' , query.header.nscount,'ARCOUNT: ',query.header.arcount);
          
      console.log('QNAME: ',query.question.qname , 'QTYPE: ', query.question.qtype ,'QCLASS: ' , query.question.qclass);

      console.log('QUESTION STRING: ' ,domainify(query.question.qname));

      server.close();
  });

  server.on('listening' , ()=>{
      var address = server.address();
      console.log(`server listening ${address.address}:${address.port}`);
  });

  server.bind({port:53,address:'8.8.8.8'});//address需要指定到你要用于进行代理的机器ip

использованная литература

doc store.Mick.UA/или Elly/net для…

woohoo.comp tech doc.org/independent…

Протокол Woohoo.I Sec.com/2012/01/13/…