feat: 优化DNS上游查询
This commit is contained in:
parent
6a467ca16c
commit
3bd0e42de3
99
index.js
99
index.js
@ -1,20 +1,25 @@
|
|||||||
|
// 引入UDP模块和dns-packet模块用于解析和构建DNS数据包
|
||||||
import dgram from 'dgram';
|
import dgram from 'dgram';
|
||||||
import dnsPacket from 'dns-packet';
|
import dnsPacket from 'dns-packet';
|
||||||
import { records } from './dns-records.js';
|
import { records } from './dns-records.js';
|
||||||
|
|
||||||
|
// 创建UDP服务器,监听53端口(DNS默认端口)
|
||||||
const server = dgram.createSocket('udp4');
|
const server = dgram.createSocket('udp4');
|
||||||
const PORT = 53;
|
const PORT = 53;
|
||||||
|
|
||||||
|
// 处理服务器异常
|
||||||
server.on('error', (err) => {
|
server.on('error', (err) => {
|
||||||
console.error(`服务器异常:\n${err.stack}`);
|
console.error(`服务器异常:\n${err.stack}`);
|
||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 处理收到的DNS查询消息
|
||||||
server.on('message', (msg, rinfo) => {
|
server.on('message', (msg, rinfo) => {
|
||||||
console.log(`收到来自 ${rinfo.address}:${rinfo.port} 的DNS查询`);
|
console.log(`收到来自 ${rinfo.address}:${rinfo.port} 的DNS查询`);
|
||||||
|
|
||||||
|
// 解析DNS查询包
|
||||||
const request = dnsPacket.decode(msg);
|
const request = dnsPacket.decode(msg);
|
||||||
// 确保至少有一个问题
|
// 确保至少有一个问题(question)
|
||||||
if (!request.questions || request.questions.length === 0) {
|
if (!request.questions || request.questions.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -22,18 +27,19 @@ server.on('message', (msg, rinfo) => {
|
|||||||
|
|
||||||
console.log(`查询的域名: ${question.name}, 类型: ${question.type}`);
|
console.log(`查询的域名: ${question.name}, 类型: ${question.type}`);
|
||||||
|
|
||||||
// 只处理 A 记录查询 (IPv4)
|
// 只处理A记录(IPv4地址)查询,其他类型直接忽略
|
||||||
if (question.type !== 'A') {
|
if (question.type !== 'A') {
|
||||||
// 对于非A记录查询,可以不响应或发送适当的错误
|
// 对于非A记录查询,可以不响应或发送适当的错误
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = question.name;
|
const domain = question.name;
|
||||||
const ip = records[domain];
|
const ip = records[domain]; // 从本地记录查找域名对应的IP
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
if (ip) {
|
if (ip) {
|
||||||
|
// 本地有记录,直接构造响应包返回
|
||||||
console.log(`为 ${domain} 找到IP: ${ip}`);
|
console.log(`为 ${domain} 找到IP: ${ip}`);
|
||||||
response = dnsPacket.encode({
|
response = dnsPacket.encode({
|
||||||
type: 'response',
|
type: 'response',
|
||||||
@ -48,17 +54,7 @@ server.on('message', (msg, rinfo) => {
|
|||||||
data: ip,
|
data: ip,
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
} else {
|
// 发送响应给客户端
|
||||||
console.log(`未找到域名 ${domain} 的记录`);
|
|
||||||
response = dnsPacket.encode({
|
|
||||||
type: 'response',
|
|
||||||
id: request.id,
|
|
||||||
flags: dnsPacket.AUTHORITATIVE_ANSWER | dnsPacket.RECURSION_AVAILABLE,
|
|
||||||
questions: request.questions,
|
|
||||||
rcode: 'NXDOMAIN' // Non-Existent Domain
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
server.send(response, rinfo.port, rinfo.address, (err) => {
|
server.send(response, rinfo.port, rinfo.address, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('发送响应失败:', err);
|
console.error('发送响应失败:', err);
|
||||||
@ -66,15 +62,88 @@ server.on('message', (msg, rinfo) => {
|
|||||||
console.log(`已向 ${rinfo.address}:${rinfo.port} 发送响应`);
|
console.log(`已向 ${rinfo.address}:${rinfo.port} 发送响应`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// 本地无记录,尝试多个上游DNS服务器
|
||||||
|
queryUpstreams(msg, rinfo, request, request.questions);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 上游DNS服务器列表,可根据需要添加或调整
|
||||||
|
const upstreamServers = [
|
||||||
|
{ address: '114.114.114.114', port: 53 },// 114DNS
|
||||||
|
{ address: '223.5.5.5', port: 53 }, // 阿里DNS
|
||||||
|
{ address: '119.29.29.29', port: 53 }, // 腾讯DNSPod
|
||||||
|
{ address: '180.76.76.76', port: 53 }, // 百度DNS
|
||||||
|
{ address: '1.2.4.8', port: 53 }, // CNNIC SDNS
|
||||||
|
{ address: '8.8.8.8', port: 53 },
|
||||||
|
{ address: '1.1.1.1', port: 53 }
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 依次尝试多个上游DNS服务器,直到有一个返回响应或全部失败
|
||||||
|
* @param {*} msg 原始DNS查询包
|
||||||
|
* @param {*} rinfo 客户端信息
|
||||||
|
* @param {*} request 解析后的DNS请求对象
|
||||||
|
* @param {*} questions DNS查询问题列表
|
||||||
|
* @param {*} tryIndex 当前尝试的上游DNS索引
|
||||||
|
*/
|
||||||
|
function queryUpstreams(msg, rinfo, request, questions, tryIndex = 0) {
|
||||||
|
if (tryIndex >= upstreamServers.length) {
|
||||||
|
// 所有上游DNS都失败,返回NXDOMAIN(域名不存在)
|
||||||
|
const response = dnsPacket.encode({
|
||||||
|
type: 'response',
|
||||||
|
id: request.id,
|
||||||
|
flags: dnsPacket.AUTHORITATIVE_ANSWER | dnsPacket.RECURSION_AVAILABLE,
|
||||||
|
questions: questions,
|
||||||
|
rcode: 'NXDOMAIN'
|
||||||
|
});
|
||||||
|
server.send(response, rinfo.port, rinfo.address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { address, port } = upstreamServers[tryIndex];
|
||||||
|
const upstreamSocket = dgram.createSocket('udp4'); // 创建临时UDP socket用于与上游DNS通信
|
||||||
|
let responded = false; // 标记是否已收到上游响应
|
||||||
|
console.log(`No Local DNS Record, Try Upstream DNS: ${address}`);
|
||||||
|
// 向当前上游DNS发送查询包
|
||||||
|
upstreamSocket.send(msg, port, address, (err) => {
|
||||||
|
if (err) {
|
||||||
|
// 发送失败,尝试下一个上游DNS
|
||||||
|
upstreamSocket.close();
|
||||||
|
queryUpstreams(msg, rinfo, request, questions, tryIndex + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听上游DNS的响应
|
||||||
|
upstreamSocket.on('message', (upstreamMsg) => {
|
||||||
|
if (!responded) {
|
||||||
|
responded = true;
|
||||||
|
// 收到响应后,直接转发给客户端
|
||||||
|
server.send(upstreamMsg, rinfo.port, rinfo.address, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('转发上游DNS响应失败:', err);
|
||||||
|
} else {
|
||||||
|
console.log(`已将上游DNS(${address})响应转发给 ${rinfo.address}:${rinfo.port}`);
|
||||||
|
}
|
||||||
|
upstreamSocket.close(); // 关闭临时socket
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 超时处理,若2秒内未收到响应则尝试下一个上游DNS
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!responded) {
|
||||||
|
upstreamSocket.close();
|
||||||
|
queryUpstreams(msg, rinfo, request, questions, tryIndex + 1);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 服务器启动监听
|
||||||
server.on('listening', () => {
|
server.on('listening', () => {
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
console.log(`DNS 服务器正在监听 ${address.address}:${address.port}`);
|
console.log(`DNS 服务器正在监听 ${address.address}:${address.port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 绑定53端口,Windows下需管理员权限
|
||||||
server.bind(PORT, () => {
|
server.bind(PORT, () => {
|
||||||
// 在 Windows 上,可能需要管理员权限才能绑定到 53 端口
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
console.log('在 Windows 上运行,请确保您有管理员权限来绑定到53端口。');
|
console.log('在 Windows 上运行,请确保您有管理员权限来绑定到53端口。');
|
||||||
}
|
}
|
||||||
|
19
node-dns.service
Normal file
19
node-dns.service
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Node.js DNS Application
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/root/node-dns
|
||||||
|
ExecStart=/opt/node/bin/node /root/node-dns/index.js
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
Environment=NODE_ENV=production
|
||||||
|
LimitNOFILE=65536
|
||||||
|
|
||||||
|
# 日志配置(systemd 236+ 支持 file:,否则建议用 journal)
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
Loading…
Reference in New Issue
Block a user