diff --git a/index.js b/index.js index d57ccf0..1b1879e 100644 --- a/index.js +++ b/index.js @@ -1,20 +1,25 @@ +// 引入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; } @@ -22,18 +27,19 @@ server.on('message', (msg, rinfo) => { console.log(`查询的域名: ${question.name}, 类型: ${question.type}`); - // 只处理 A 记录查询 (IPv4) + // 只处理A记录(IPv4地址)查询,其他类型直接忽略 if (question.type !== 'A') { // 对于非A记录查询,可以不响应或发送适当的错误 return; } const domain = question.name; - const ip = records[domain]; + const ip = records[domain]; // 从本地记录查找域名对应的IP let response; if (ip) { + // 本地有记录,直接构造响应包返回 console.log(`为 ${domain} 找到IP: ${ip}`); response = dnsPacket.encode({ type: 'response', @@ -48,33 +54,96 @@ server.on('message', (msg, rinfo) => { data: ip, }], }); + // 发送响应给客户端 + server.send(response, rinfo.port, rinfo.address, (err) => { + if (err) { + console.error('发送响应失败:', err); + } else { + console.log(`已向 ${rinfo.address}:${rinfo.port} 发送响应`); + } + }); } else { - console.log(`未找到域名 ${domain} 的记录`); - response = dnsPacket.encode({ + // 本地无记录,尝试多个上游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: request.questions, - rcode: 'NXDOMAIN' // Non-Existent Domain + questions: questions, + rcode: 'NXDOMAIN' }); + server.send(response, rinfo.port, rinfo.address); + return; } - - server.send(response, rinfo.port, rinfo.address, (err) => { + 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) { - console.error('发送响应失败:', err); - } else { - console.log(`已向 ${rinfo.address}:${rinfo.port} 发送响应`); + // 发送失败,尝试下一个上游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, () => { - // 在 Windows 上,可能需要管理员权限才能绑定到 53 端口 if (process.platform === 'win32') { console.log('在 Windows 上运行,请确保您有管理员权限来绑定到53端口。'); } diff --git a/node-dns.service b/node-dns.service new file mode 100644 index 0000000..9376ba9 --- /dev/null +++ b/node-dns.service @@ -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 \ No newline at end of file