custom-dns/index.js
2025-07-22 12:52:42 +08:00

150 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 引入UDP模块和dns-packet模块用于解析和构建DNS数据包
import dgram from 'dgram';
import dnsPacket from 'dns-packet';
import { records } from './dns-records.js';
// 创建UDP服务器监听53端口DNS默认端口
const server = dgram.createSocket('udp4');
const PORT = 53;
// 处理服务器异常
server.on('error', (err) => {
console.error(`服务器异常:\n${err.stack}`);
server.close();
});
// 处理收到的DNS查询消息
server.on('message', (msg, rinfo) => {
console.log(`收到来自 ${rinfo.address}:${rinfo.port} 的DNS查询`);
// 解析DNS查询包
const request = dnsPacket.decode(msg);
// 确保至少有一个问题question
if (!request.questions || request.questions.length === 0) {
return;
}
const question = request.questions[0];
console.log(`查询的域名: ${question.name}, 类型: ${question.type}`);
// 只处理A记录IPv4地址查询其他类型直接忽略
if (question.type !== 'A') {
// 对于非A记录查询可以不响应或发送适当的错误
return;
}
const domain = question.name;
const ip = records[domain]; // 从本地记录查找域名对应的IP
let response;
if (ip) {
// 本地有记录,直接构造响应包返回
console.log(`${domain} 找到IP: ${ip}`);
response = dnsPacket.encode({
type: 'response',
id: request.id,
flags: dnsPacket.AUTHORITATIVE_ANSWER,
questions: request.questions,
answers: [{
type: 'A',
class: 'IN',
name: domain,
ttl: 300, // Time-to-live
data: ip,
}],
});
// 发送响应给客户端
server.send(response, rinfo.port, rinfo.address, (err) => {
if (err) {
console.error('发送响应失败:', err);
} else {
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', () => {
const address = server.address();
console.log(`DNS 服务器正在监听 ${address.address}:${address.port}`);
});
// 绑定53端口Windows下需管理员权限
server.bind(PORT, () => {
if (process.platform === 'win32') {
console.log('在 Windows 上运行请确保您有管理员权限来绑定到53端口。');
}
});