上一篇我已經解釋了DNS請求報文怎麼解析,不會的本身坐飛機(飛機入口)。這一篇主要從DNS服務器的角度來解釋,如何本身建立響應報文返回給客戶端。git
就這個命題,能夠羅列出DNS服務器在建立response
響應報文時須要解決的問題。github
Buffer
?Buffer
?Buffer
是否能夠建立response
響應報文指定類型的參數值?response
響應報文與request
請求報文的異同?說到這,你是否是已經察覺到。既然dns
請求和dns
響應都作了,那是否是本身動手寫一個dns代理服務器也能夠信手拈來呢。api
答案是: Yes
。bash
那然咱們繼續完成這最後一步,response
響應報文的建立。服務器
response
響應報文和request
請求報文格式相同。不一樣的地方是參數的值不一樣。函數
Header
報文頭Question
查詢的問題Answer
應答Authority
受權應答Additional
附加信息DNS format
+--+--+--+--+--+--+--+
| Header |
+--+--+--+--+--+--+--+
| Question |
+--+--+--+--+--+--+--+
| Answer |
+--+--+--+--+--+--+--+
| Authority |
+--+--+--+--+--+--+--+
| Additional |
+--+--+--+--+--+--+--+
複製代碼
屬性說明:post
request
和response
的ID
必須保持一致。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
複製代碼
將請求數據原樣返回。ui
var question = response.question = {};
question.qname = request.question.qname;
question.qtype = request.question.qtype;
question.qclass = request.question.qclass;
複製代碼
這個部分的內容就是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;
};
複製代碼
rdata
是4
字節,ip
地址從.
處切開後是由4段數字組成,每段數據不會超過2^8 === 256
---一個字節(8bit
),那rdata
的4個字節恰好能夠存放下一個ip
地址。 那如今的問題是怎麼把ip地址數據存進4個字節裏面,而又要保證客戶端可以識別。很簡單按字節存,按字節取就好了。4
字節恰好是一個32bit
整數的長度。
因此上面計算result
的for(...)
循環就是把ip存進rdata
的一種方式。
其實你也可使用如下方式計算result
:
result = ip[0]*(1<<24) + ip[1]*(1<<16) + ip[2]*(1<<8) + ip[3];
複製代碼
本身處理的請求沒有受權應答和附加數據。
獲得了想要的一切響應數據以後,下一步就是將這些數據轉換爲客戶端能夠解析的Buffer
類型。
那這一步的工做正好與request
請求報文解析的工做剛好相反。報上面的數據一一拼湊爲response
響應報文格式數據。
返回一段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
後,就能夠進行參數轉換了。
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;
};
複製代碼