1.获取响应报文
首先我们要在网络配置设置DNS服务
搭建简单的UPD服务
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
server.on('message', (msg, rinfo) => {
console.log(JSON.stringify(msg));//接收查询
})
server.on('error', (err) => {
console.log('server error:'+err.stack)
server.close()
})
server.on('listening', () => {
const addr = server.address()
console.log(`run ${addr.address}:${addr.port}`)
})
server.bind(53);
2.添加转发
转发很简单,接收到DNS解析请求后将请求转发到另外一个服务器上
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
const fbSer = '192.168.1.1';//默认DNS服务器
function forward(msg, rinfo) {
const client = dgram.createSocket('udp4');
client.on('error', (err) => {
console.log(`client error:`+err.stack)
client.close();
})
client.on('message', (fMsg, fbRinfo) => {
console.log("result:",JSON.stringify(fMsg));//获取响应报文
server.send(fMsg, rinfo.port, rinfo.address, (err) => {
err && console.log(err);
});
client.close();
})
client.send(msg, 53, fbSer, (err) => {
if (err) {
console.log(err);
client.close();
}
});
}
server.on('message', (msg, rinfo) => {
console.log(JSON.stringify(msg));//获取接收查询
forward(msg, rinfo);//转发
})
server.on('error', (err) => {
console.log('server error:' + err.stack)
server.close()
})
server.on('listening', () => {
const addr = server.address()
console.log(`run ${addr.address}:${addr.port}`)
})
server.bind(53);
3.DNS协议请求的报文格式
从控制台把数组值拷出来
www.baidu.com
//十进制数组
[159, 136, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 119, 119, 119, 5,98, 97, 105, 100, 117, 3, 99, 111, 109, 0, 0, 1, 0, 1]
DNS协议请求的报文简单的可以分为头部和正文
头部 | 159,136,1,0,0,1,0,0,0,0,0,0 |
---|---|
正文 | 3,119,119,119,5,98,97,105,100,117,3,99,111,109,0,0,1,0,1 |
3.1.头部信息
3.1.1.会话标识(2字节):
也就是上面头部中的[159,136]
,顾名思义就是用来标识是哪个请求的
3.1.2.标志(2字节):
也就是上面头部中标识紧接着后面的[1,0]
对应二进制是0000 0001 0000 0000
每个二进制值所对应的说明如下:
标志 | 说明 |
---|---|
QR(1bit) | 查询/响应标志,0为查询,1为响应 |
opcode(4bit) | 0表示标准查询,1表示反向查询,2表示服务器状态请求 |
AA(1bit) | 表示授权回答 |
TC(1bit) | 表示可截断的 |
RD(1bit) | 表示期望递归 |
RA(1bit) | 表示可用递归 |
ZERO(3bit) | 表示保留字段 |
rcode(4bit) | 表示返回码,0表示没有差错,3表示名字差错,2表示服务器错误(Server Failure) |
3.1.3.数量字段(共8字节):
头部最后几个字节是[0,1,0,0,0,0,0,0]
字段 | 说明 |
---|---|
Questions(2bit)(查询问题数) | 表示查询问题区域节的数量,在请求的时候一般为1 |
Answer RRs(2bit)(回答RR数) | 表示回答区域的数量,根据请求一般为1 |
Authority RRs(2bit)(权威RR数) | 表示授权区域的数量,一般为0 |
Additional RRs(2bit) (附加RR数) | 表示附加区域的数量一般为0 |
3.2.正文信息
3.2.1.查询名
包含域名的可变长度字段,每个域以计数开头,最后一个字符为0。(也会有IP的时候,即反向查询)
示例:
3 | 119 | 119 | 119 | 5 | 98 | 97 | 105 | 100 | 117 | 3 | 99 | 111 | 109 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
(长度) | w | w | w | (长度) | b | a | i | d | u | (长度) | c | o | m | (结束) |
3.2.2.查询类型
查询名之后的[0,1]
类型 | 助记符 | 说明 |
---|---|---|
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 | 对所有记录的请求 |
3.2.3.查询类
也就是最后的[0,1]
通常为1,1表示因特网
4.DNS协议响应的报文格式
同样从控制台把数组值拷出来
www.baidu.com
//十进制数组
[159, 136,129,128, 0, 1, 0, 1, 0, 0, 0, 0, 3, 119, 119, 119, 5,98, 97, 105, 100, 117, 3, 99, 111, 109, 0, 0, 1, 0, 1, 192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4, 115, 239, 210, 27]
对比请求响应我们发现,标志部分是[129,128]
转为二进制是1000 0001 1000 0000
,也就说明
QR与RA标志都是1
Answer RRs(2bit)(回答RR数)也是1
当然最明显的是在请求正文最后多了[ 192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4, 115, 239, 210, 27]
我们可以叫他资源记录
4.1.偏移量
其中[192,12]
是一个(2字节)指针,一般响应报文中,资源部分的地址(域名)一般都是指针C00C(1100000000001100),偏移量是12,指向请求部分的地址(域名)。
4.2.资源记录的响应类型
响应类型,也就是后面的[0,1]
,含义与查询问题部分的类型相同
4.3.资源记录的响应类
响应类,也就是后面的[0,1]
,含义与查询问题部分的类相同
4.4.生存时间(4字节)
接下去的是[0, 0, 0, 218]
,以秒为单位,表示的是资源记录的生命周期,可以理解为获取到的资源记录的缓存时间
4.5.资源长度
资源长度是[0, 4]
,ipv4是00 04
4.6.资源数据
资源数据是可变长度的字段,在这里我们拿它来指向IP地址,例如:[115, 239, 210, 27]
5.编写自定义解析服务
这里就简单的声明一个hosts
对象通过匹配key
将匹配到的ip
响应到请求端
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
const hosts = {//声明host
'aaaaaaaa.bbbbbbbbb.com': '127.0.0.1'//自定义
};
const fbSer= '192.168.1.1';//默认DNS服务器
function forward(msg, rinfo) {
const client = dgram.createSocket('udp4');
client.on('error', (err) => {
console.log(`client error:` + err.stack)
client.close();
})
client.on('message', (fMsg, fbRinfo) => {
server.send(fMsg, rinfo.port, rinfo.address, (err) => {
err && console.log(err);
});
client.close();
})
client.send(msg, 53, fbSer, (err) => {
if (err) {
console.log(err);
client.close();
}
});
}
function parseHost(msg) {//转换域名
let num = msg[0];
let offset = 1;
let host = "";
while (num !== 0) {
host += (msg.slice(offset, offset + num).toString());
offset += num;
num = msg[offset];
offset += 1;
if (num !== 0) host += ('.');
}
return host;
}
function resolve(ip, msg, rinfo) {//响应
let len = msg.length;
let templet = [192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4].concat(ip.split(".").map(i=>Number(i)));//<===可以自定义
const response = new ArrayBuffer(len + 16);
var bufView = new Uint8Array(response);
for (let i = 0; i < msg.length; i++)bufView[i] = msg[i];
for (let i = 0; i < templet.length; i++)bufView[msg.length + i] = templet[i];
bufView[2] = 129;
bufView[3] = 128;
bufView[7] = 1;
server.send(bufView, rinfo.port, rinfo.address, (err) => {
if (err) {
console.log(err);
server.close();
}
})
}
server.on('message', (msg, rinfo) => {
let host = parseHost(msg.slice(12));
let ip = hosts[host];
if (ip) {
console.log("resolve:", host, "==>", ip);
resolve(ip, msg, rinfo); //解析与响应
} else {
forward(msg, rinfo);//转发
}
})
server.on('error', (err) => {
console.log('server error:' + err.stack);
server.close();
})
server.on('listening', () => {
const addr = server.address();
console.log(`run ${addr.address}:${addr.port}`);
})
server.bind(53);
6.Python 代码的实现
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
const hosts = {
'aaaaaaaa.bbbbbbbbb.com': '127.0.0.1', #自定义
};
const fallbackServer = '192.168.1.1';//默认DNS服务器
function forward(msg, rinfo) {
const client = dgram.createSocket('udp4');
client.on('error', (err) => {
console.log(`client error:` + err.stack)
client.close();
})
client.on('message', (fMsg, fbRinfo) => {
server.send(fMsg, rinfo.port, rinfo.address, (err) => {
err && console.log(err);
});
client.close();
})
client.send(msg, 53, fallbackServer, (err) => {
if (err) {
console.log(err);
client.close();
}
});
}
function parseHost(msg) {//转换域名
let num = msg[0];
let offset = 1;
let host = "";
while (num !== 0) {
host += (msg.slice(offset, offset + num).toString());
offset += num;
num = msg[offset];
offset += 1;
if (num !== 0) host += ('.');
}
return host;
}
function resolve(ip, msg, rinfo) {//响应
let len = msg.length;
let templet = [192, 12, 0, 1, 0, 1, 0, 0, 0, 218, 0, 4].concat(ip.split(".").map(i=>Number(i)));//<===可以自定义
const response = new ArrayBuffer(len + 16);
var bufView = new Uint8Array(response);
for (let i = 0; i < msg.length; i++)bufView[i] = msg[i];
for (let i = 0; i < templet.length; i++)bufView[msg.length + i] = templet[i];
bufView[2] = 129;
bufView[3] = 128;
bufView[7] = 1;
server.send(bufView, rinfo.port, rinfo.address, (err) => {
if (err) {
console.log(err);
server.close();
}
})
}
server.on('message', (msg, rinfo) => {
let host = parseHost(msg.slice(12));
let ip = hosts[host];
if (ip) {
console.log("resolve:", host, "==>", ip);
resolve(ip, msg, rinfo); //解析与响应
} else {
forward(msg, rinfo);//转发
}
})
server.on('error', (err) => {
console.log('server error:' + err.stack)
server.close()
})
server.on('listening', () => {
const addr = server.address()
console.log(`run ${addr.address}:${addr.port}`)
})
server.bind(53);