DNS 請求報文詳解

DNS

DNS【域名系統:(英文:Domain Name System,縮寫:DNS)】是互聯網的一項服務。 它做爲將域名和IP地址相互映射的一個分佈式數據庫,可以令人更方便地訪問互聯網。 DNS使用TCP和UDP端口53。html

白話版

就是客戶端(例如:瀏覽器)傳入的網站域名,到DNS列表中找到對應的ip返回給客戶端,而後客戶端根據ip就能夠找到對應的服務器,就能夠向服務器發送請求了。node

說的在直接點:DNS目的就是把對應服務器IP給客戶端。最後客戶端與服務器通訊就沒DNS什麼事了。數據庫

DNS 報文格式

DNS報文格式,不管是請求報文,仍是DNS服務器返回的應答報文,都使用統一的格式。json

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

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

Header 報文頭

  • ID: 2個字節(16bit),標識字段,客戶端會解析服務器返回的DNS應答報文,獲取ID值與請求報文設置的ID值作比較,若是相同,則認爲是同一個DNS會話。
  • FLAGS: 2個字節(16bit)的標誌字段。包含如下屬性:
    • QR: 0表示查詢報文,1表示響應報文;
    • opcode: 一般值爲0(標準查詢),其餘值爲1(反向查詢)和2(服務器狀態請求),[3,15]保留值;
    • AA: 表示受權回答(authoritative answer)-- 這個比特位在應答的時候纔有意義,指出給出應答的服務器是查詢域名的受權解析服務器;
    • TC: 表示可截斷的(truncated)--用來指出報文比容許的長度還要長,致使被截斷;
    • RD: 表示指望遞歸(Recursion Desired) -- 這個比特位被請求設置,應答的時候使用的相同的值返回。若是設置了RD,就建議域名服務器進行遞歸解析,遞歸查詢的支持是可選的;
    • RA: 表示支持遞歸(Recursion Available) -- 這個比特位在應答中設置或取消,用來表明服務器是否支持遞歸查詢;
    • Z : 保留值,暫未使用;
    • RCODE: 應答碼(Response code) - 這4個比特位在應答報文中設置,表明的含義以下:
      • 0 : 沒有錯誤。
      • 1 : 報文格式錯誤(Format error) - 服務器不能理解請求的報文;
      • 2 : 服務器失敗(Server failure) - 由於服務器的緣由致使沒辦法處理這個請求;
      • 3 : 名字錯誤(Name Error) - 只有對受權域名解析服務器有意義,指出解析的域名不存在;
      • 4 : 沒有實現(Not Implemented) - 域名服務器不支持查詢類型;
      • 5 : 拒絕(Refused) - 服務器因爲設置的策略拒絕給出應答.好比,服務器不但願對某些請求者給出應答,或者服務器不但願進行某些操做(好比區域傳送zone transfer);
      • [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                    |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
複製代碼

Question 查詢字段

  • QNAME 無符號8bit爲單位長度不限表示查詢名(普遍的說就是:域名).
  • QTYPE 無符號16bit整數表示查詢的協議類型.
  • QCLASS 無符號16bit整數表示查詢的類,好比,IN表明Internet.
Question format

    0  1  2  3  4  5  6  7  0  1  2  3  4  5  6  7
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                     ...                       |
  |                    QNAME                      |
  |                     ...                       |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    QTYPE                      |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |                    QCLASS                     |
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
複製代碼

Answer/Authority/Additional

這3個字段的格式都是同樣的。api

  • NAME 資源記錄包含的域名.
  • TYPE 表示DNS協議的類型.
  • CLASS 表示RDATA的類.
  • TTL 4字節無符號整數表示資源記錄能夠緩存的時間。0表明只能被傳輸,可是不能被緩存。
  • RDLENGTH 2個字節無符號整數表示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實例,看完後是否是一臉懵B,別緊張,先看解析後console.log大概的樣子,是否是世界瞬間變美好了。緩存

下面是一個請求查詢www.apple.com網站ip的DNS請求報文。bash

//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查詢問題解析

Header 報文頭解析

對Header部分進行解析。網絡

先肯定一下每一個字段的大小:

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]個字節,其實很容易就能夠把header解析,可是單位爲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轉換爲2進制字符串而後轉換爲數值(假設dns報文是buf):

//對第3個字節轉成`2`進制字符串而後轉換爲數值
  var b = buf.slice(2,3).toString('binary', 0, 1).charCodeAt(0);
複製代碼

第2步,進行數據切割:

首先須要理解下面這個函數,功能無非就是提取從offset開始,長度爲length數字位,經過位運算轉換爲Integer類型的數而後返回。

說直白一點,就是把你須要的那一段2進制數據轉換爲Integer類型,並返回。

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

注意這裏由於只考慮一個字節 === 8bit,因此能夠寫成(7-(offset+length-1))0xff << length。假如不是一個字節,那麼可能須要改變一下里面的數字70xff的值。

demo走起:

 '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報文Header的第[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個參數QTYPEQCLASS是固定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 郵件交換把郵件改變路由送到郵件服務器
28 AAAA IPv6地址
252 AXFR 傳送整個區的請求
255 ANY 對全部記錄的請求

qclass一般爲1,指Internet數據.

應用場景--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

複製代碼

參考資料

docstore.mik.ua/orelly/netw…

www.comptechdoc.org/independent…

www.iprotocolsec.com/2012/01/13/…

相關文章
相關標籤/搜索