NodeJS编写简单的DNS服务器

简介: 1.获取响应报文首先我们要在网络配置设置DNS服务搭建简单的UPD服务const dgram = require('dgram');const server = dgram.
img_6d43c0892a48d362325b10970dc70b68.png

1.获取响应报文

首先我们要在网络配置设置DNS服务

img_969a1477815c5e7c3ae9146f1c87f06b.png

搭建简单的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);
img_6e7fbae8eb377a0ecea79d27e7245a16.png

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);
img_c1d71daf41a25ac591b19ab474665d1c.gif

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,也就说明
QRRA标志都是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);

完成

img_9fd213b57296e938d69242a614cc92eb.gif
相关文章
|
存储 缓存 网络协议
阿里云特惠云服务器99元与199元配置与性能和适用场景解析:高性价比之选
2025年,阿里云长效特惠活动继续推出两款极具吸引力的特惠云服务器套餐:99元1年的经济型e实例2核2G云服务器和199元1年的通用算力型u1实例2核4G云服务器。这两款云服务器不仅价格亲民,而且性能稳定可靠,为入门级用户和普通企业级用户提供了理想的选择。本文将对这两款云服务器进行深度剖析,包括配置介绍、实例规格、使用场景、性能表现以及购买策略等方面,帮助用户更好地了解这两款云服务器,以供参考和选择。
|
存储 缓存 负载均衡
阿里云服务器实例选择指南:热门实例性能、适用场景解析对比参考
2025年,在阿里云的活动中,主售的云服务器实例规格除了轻量应用服务器之外,还有经济型e、通用算力型u1、计算型c8i、通用型g8i、计算型c7、计算型c8y、通用型g7、通用型g8y、内存型r7、内存型r8y等,以满足不同用户的需求。然而,面对众多实例规格,用户往往感到困惑,不知道如何选择。本文旨在全面解析阿里云服务器实例的各种类型,包括经济型、通用算力型、计算型、通用型和内存型等,以供参考和选择。
|
7月前
|
存储 域名解析 弹性计算
阿里云上云流程参考:云服务器+域名+备案+域名解析绑定,全流程图文详解
对于初次通过阿里云完成上云的企业和个人用户来说,很多用户不仅是需要选购云服务器,同时还需要注册域名以及完成备案和域名的解析相关流程,从而实现网站的上线。本文将以上云操作流程为核心,结合阿里云的活动政策与用户系统梳理云服务器选购、域名注册、备案申请及域名绑定四大关键环节,以供用户完成线上业务部署做出参考。
|
9月前
|
网络协议
利用Private Zone DNS - 搭建AD但不搭建DNS服务器如何加域
利用Private Zone DNS - 搭建AD但不搭建DNS服务器如何加域
利用Private Zone DNS - 搭建AD但不搭建DNS服务器如何加域
|
弹性计算 JavaScript 前端开发
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
Node.js 是一种高效的 JavaScript 运行环境,基于 Chrome V8 引擎,支持在服务器端运行 JavaScript 代码。本文介绍如何在阿里云上一键部署 Node.js 环境,无需繁琐配置,轻松上手。前提条件包括 ECS 实例运行中且操作系统为 CentOS、Ubuntu 等。功能特点为一键安装和稳定性好,支持常用 LTS 版本。安装步骤简单:登录阿里云控制台,选择扩展程序管理页面,安装 Node.js 扩展,选择实例和版本,等待创建完成并验证安装成功。通过阿里云的公共扩展,初学者和经验丰富的开发者都能快速进入开发状态,开启高效开发之旅。
|
Kubernetes API 网络安全
当node节点kubectl 命令无法连接到 Kubernetes API 服务器
当Node节点上的 `kubectl`无法连接到Kubernetes API服务器时,可以通过以上步骤逐步排查和解决问题。首先确保网络连接正常,验证 `kubeconfig`文件配置正确,检查API服务器和Node节点的状态,最后排除防火墙或网络策略的干扰,并通过重启服务恢复正常连接。通过这些措施,可以有效解决与Kubernetes API服务器通信的常见问题,从而保障集群的正常运行。
1000 17
|
存储 机器学习/深度学习 应用服务中间件
阿里云服务器架构解析:从X86到高性能计算、异构计算等不同架构性能、适用场景及选择参考
当我们准备选购阿里云服务器时,阿里云提供了X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器以及高性能计算等多种架构,每种架构都有其独特的特点和适用场景。本文将详细解析这些架构的区别,探讨它们的主要特点和适用场景,并为用户提供选择云服务器架构的全面指南。
1191 18
|
弹性计算 JavaScript 前端开发
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
|
存储 弹性计算 安全
阿里云服务器ECS通用型规格族解析:实例规格、性能基准与场景化应用指南
作为ECS产品矩阵中的核心序列,通用型规格族以均衡的计算、内存、网络和存储性能著称,覆盖从基础应用到高性能计算的广泛场景。通用型规格族属于独享型云服务器,实例采用固定CPU调度模式,实例的每个CPU绑定到一个物理CPU超线程,实例间无CPU资源争抢,实例计算性能稳定且有严格的SLA保证,在性能上会更加稳定,高负载情况下也不会出现资源争夺现象。本文将深度解析阿里云ECS通用型规格族的技术架构、实例规格特性、最新价格政策及典型应用场景,为云计算选型提供参考。