NodeJS编写简单的DNS服务器 - 简书


本站和网页 https://www.jianshu.com/p/8cdcbae986a8 的作者无关,不对其内容负责。快照谨为网络故障时之索引,不代表被搜索网站的即时页面。

NodeJS编写简单的DNS服务器 - 简书登录注册写文章首页下载APP会员IT技术NodeJS编写简单的DNS服务器关爱单身狗成长协会关注赞赏支持NodeJS编写简单的DNS服务器
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的时候,即反向查询)
示例:
119
119
119
98
97
105
100
117
99
111
109
(长度)
(长度)
(长度)
(结束)
3.2.2.查询类型
查询名之后的[0,1]
类型
助记符
说明
由域名获得IPv4地址,一般是这个
NS
查询域名服务器
CNAME
查询规范名称
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);
完成
推荐阅读更多精彩内容一个网站访问从输入URL到页面加载的过程丨WEB前端开发 前端开发者前端开发者丨http请求 https:www.rokub.com 前言见解有限, 如有描述不当之处, 请帮忙指出,...麋鹿_720a阅读 10,471评论 11赞 31面向对象的用电信息数据交换协议国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...庭说阅读 9,101评论 6赞 13第十四章 :DNS:域名系统14.1 引言 域名系统(DNS)是一种用于TCP/IP应用程序的分布式数据库,它提供主机名字和IP地址之间的转换...张芳涛阅读 1,601评论 0赞 8Spring CloudSpring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...卡卡罗2017阅读 131,393评论 18赞 138自由职业者真的“自由”吗?自由职业者真的“自由”吗? 2015年发布的“公众眼中最具幸福感的职业”中,“自由职业者”第一次登上榜首,取代了“...奇兵崔阅读 296评论 0赞 1第一次家长会总结与反思第一次家长会是跟所有家长的第一次见面,第一次会谈,这一次的家长会,我的重点放在了阅读,告诉家长,我们是从萌芽状态的...麦豆铭玉霞阅读 869评论 0赞 154.今晚流氓兔推荐的歌曲是《We Are Young》,Fun./Janelle Monae.这首歌的旋律响起的时候我...佐恬阅读 139评论 0赞 0那年夏天随风飘走的人!车子在一处平台停下,我从车子上下来,抬眼环视两边的大青山。这里是父亲的老家,每年的农历29号,我们都会来,因为父亲...花间醉_ba4b阅读 142评论 0赞 0评论1赞66赞7赞赞赏更多好文